@elizaos/app-core 2.0.0-alpha.13 → 2.0.0-alpha.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +0 -1
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +26 -12
- package/dist/api/client.d.ts +3 -3
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/client.js +24 -13
- package/dist/bridge/plugin-bridge.d.ts.map +1 -1
- package/dist/components/AvatarLoader.d.ts +3 -1
- package/dist/components/AvatarLoader.d.ts.map +1 -1
- package/dist/components/AvatarLoader.js +4 -1
- package/dist/components/BscTradePanel.d.ts +1 -1
- package/dist/components/BscTradePanel.d.ts.map +1 -1
- package/dist/components/CharacterView.d.ts.map +1 -1
- package/dist/components/CharacterView.js +50 -15
- package/dist/components/ChatView.js +1 -1
- package/dist/components/ConfigPageView.d.ts.map +1 -1
- package/dist/components/ConfigPageView.js +9 -8
- package/dist/components/ConversationsSidebar.js +1 -1
- package/dist/components/CustomActionEditor.js +1 -1
- package/dist/components/FineTuningView.d.ts.map +1 -1
- package/dist/components/FineTuningView.js +2 -2
- package/dist/components/GlobalEmoteOverlay.d.ts.map +1 -1
- package/dist/components/GlobalEmoteOverlay.js +1 -1
- package/dist/components/HeartbeatsView.js +1 -1
- package/dist/components/LoadingScreen.d.ts.map +1 -1
- package/dist/components/LoadingScreen.js +38 -7
- package/dist/components/OnboardingWizard.js +1 -1
- package/dist/components/PluginsView.d.ts.map +1 -1
- package/dist/components/PluginsView.js +4 -1
- package/dist/components/ShellOverlays.js +1 -1
- package/dist/components/VoiceConfigView.d.ts.map +1 -1
- package/dist/components/VoiceConfigView.js +4 -2
- package/dist/components/avatar/VrmEngine.d.ts +2 -0
- package/dist/components/avatar/VrmEngine.d.ts.map +1 -1
- package/dist/components/avatar/VrmEngine.js +14 -6
- package/dist/components/companion/CompanionSceneHost.d.ts +1 -1
- package/dist/components/companion/CompanionSceneHost.d.ts.map +1 -1
- package/dist/components/companion/CompanionSceneHost.js +1 -1
- package/dist/components/index.d.ts +10 -10
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +10 -10
- package/dist/components/inventory/TokensTable.js +2 -2
- package/dist/components/onboarding/IdentityStep.d.ts.map +1 -1
- package/dist/components/onboarding/IdentityStep.js +1 -1
- package/dist/components/shared/ShellHeaderControls.js +1 -1
- package/dist/config/config-field.d.ts.map +1 -1
- package/dist/config/config-field.js +7 -8
- package/dist/config/index.d.ts +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +1 -1
- package/dist/hooks/useVoiceChat.d.ts.map +1 -1
- package/dist/hooks/useVoiceChat.js +3 -1
- package/dist/i18n/locales/en.json +1192 -1192
- package/dist/i18n/locales/es.json +1192 -1192
- package/dist/i18n/locales/ko.json +1192 -1192
- package/dist/i18n/locales/pt.json +1192 -1192
- package/dist/i18n/locales/zh-CN.json +1192 -1192
- package/dist/package.json +181 -0
- package/dist/platform/lifo.d.ts.map +1 -1
- package/dist/platform/lifo.js +4 -1
- package/dist/state/AppContext.d.ts.map +1 -1
- package/dist/state/AppContext.js +23 -6
- package/dist/state/internal.d.ts +1 -1
- package/dist/state/internal.d.ts.map +1 -1
- package/dist/state/internal.js +1 -1
- package/dist/state/parsers.d.ts.map +1 -1
- package/dist/state/parsers.js +3 -2
- package/dist/state/persistence.js +1 -1
- package/dist/styles/anime.css +6324 -0
- package/dist/styles/base.css +196 -0
- package/dist/styles/onboarding-game.css +738 -0
- package/dist/styles/styles.css +2087 -0
- package/dist/styles/xterm.css +241 -0
- package/package.json +4 -4
- package/src/App.tsx +35 -14
- package/src/ambient.d.ts +5 -5
- package/src/api/client.ts +36 -23
- package/src/bridge/plugin-bridge.ts +1 -1
- package/src/components/AvatarLoader.tsx +6 -0
- package/src/components/BscTradePanel.tsx +1 -1
- package/src/components/CharacterView.tsx +536 -367
- package/src/components/ChatView.tsx +3 -3
- package/src/components/ConfigPageView.tsx +9 -8
- package/src/components/ConversationsSidebar.tsx +1 -1
- package/src/components/CustomActionEditor.tsx +6 -6
- package/src/components/FineTuningView.tsx +6 -3
- package/src/components/GlobalEmoteOverlay.tsx +1 -4
- package/src/components/HeartbeatsView.tsx +1 -1
- package/src/components/InventoryView.tsx +2 -2
- package/src/components/LoadingScreen.tsx +39 -6
- package/src/components/OnboardingWizard.tsx +1 -1
- package/src/components/PluginsView.tsx +6 -0
- package/src/components/ShellOverlays.tsx +1 -1
- package/src/components/VoiceConfigView.tsx +4 -5
- package/src/components/avatar/VrmEngine.ts +25 -7
- package/src/components/companion/CompanionSceneHost.tsx +5 -1
- package/src/components/index.ts +10 -10
- package/src/components/inventory/TokensTable.tsx +2 -2
- package/src/components/onboarding/IdentityStep.tsx +9 -13
- package/src/components/shared/ShellHeaderControls.tsx +1 -1
- package/src/config/config-field.tsx +7 -8
- package/src/config/index.ts +3 -3
- package/src/hooks/useVoiceChat.ts +5 -3
- package/src/platform/lifo.ts +14 -4
- package/src/state/AppContext.tsx +24 -1
- package/src/state/internal.ts +6 -6
- package/src/state/parsers.ts +4 -3
- package/src/state/persistence.ts +1 -1
- package/test/app/MessageContent.test.tsx +42 -0
- package/test/app/bug-report-modal.test.tsx +3 -3
- package/test/app/chat-view.test.tsx +3 -3
- package/test/app/cloud-login-lock.test.ts +3 -2
- package/test/app/custom-actions-smoke.test.ts +3 -3
- package/test/app/onboarding-language.test.tsx +3 -3
- package/test/app/pages-navigation-smoke.e2e.test.ts +13 -8
- package/test/app/plugin-bridge.test.ts +1 -1
- package/test/app/provider-dropdown-default.test.tsx +2 -4
- package/test/app/restart-banner.test.tsx +3 -3
- package/test/app/shell-mode-switching.e2e.test.ts +6 -6
- package/test/app/shell-mode-tab-memory.test.tsx +1 -1
- package/test/app/startup-chat.e2e.test.ts +3 -3
- package/test/app/triggers-view.e2e.test.ts +3 -2
- package/test/app/wallet-api-save-lock.test.ts +2 -1
- package/test/utils/assistant-text.test.ts +64 -0
- package/test/utils/streaming-text.test.ts +89 -0
|
@@ -8,7 +8,6 @@ import {
|
|
|
8
8
|
type StylePreset,
|
|
9
9
|
type VoiceConfig,
|
|
10
10
|
} from "@elizaos/app-core/api";
|
|
11
|
-
import { AvatarSelector } from "./AvatarSelector";
|
|
12
11
|
import {
|
|
13
12
|
dispatchWindowEvent,
|
|
14
13
|
VOICE_CONFIG_UPDATED_EVENT,
|
|
@@ -20,8 +19,17 @@ import {
|
|
|
20
19
|
type VoicePreset,
|
|
21
20
|
} from "@elizaos/app-core/voice";
|
|
22
21
|
import { Button, Input, Textarea, ThemedSelect } from "@elizaos/ui";
|
|
23
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
BookOpen,
|
|
24
|
+
FileText,
|
|
25
|
+
Lock,
|
|
26
|
+
LockOpen,
|
|
27
|
+
Palette,
|
|
28
|
+
Volume2,
|
|
29
|
+
VolumeX,
|
|
30
|
+
} from "lucide-react";
|
|
24
31
|
import { useCallback, useEffect, useState } from "react";
|
|
32
|
+
import { AvatarSelector } from "./AvatarSelector";
|
|
25
33
|
|
|
26
34
|
const DEFAULT_ELEVEN_FAST_MODEL = "eleven_flash_v2_5";
|
|
27
35
|
|
|
@@ -856,9 +864,17 @@ export function CharacterView({
|
|
|
856
864
|
const scrollPaneCls =
|
|
857
865
|
"min-h-0 overflow-y-auto pr-1 [scrollbar-gutter:stable] custom-scrollbar";
|
|
858
866
|
|
|
859
|
-
const SIDEBAR_TABS: {
|
|
867
|
+
const SIDEBAR_TABS: {
|
|
868
|
+
key: ActiveSection;
|
|
869
|
+
icon: typeof FileText;
|
|
870
|
+
labelKey: string;
|
|
871
|
+
}[] = [
|
|
860
872
|
{ key: "aboutMe", icon: FileText, labelKey: "characterview.aboutMe" },
|
|
861
|
-
{
|
|
873
|
+
{
|
|
874
|
+
key: "directions",
|
|
875
|
+
icon: BookOpen,
|
|
876
|
+
labelKey: "characterview.directionsAndThing",
|
|
877
|
+
},
|
|
862
878
|
{ key: "styleRules", icon: Palette, labelKey: "characterview.StyleRules" },
|
|
863
879
|
];
|
|
864
880
|
|
|
@@ -1101,10 +1117,7 @@ export function CharacterView({
|
|
|
1101
1117
|
{customOverridesEnabled && (
|
|
1102
1118
|
<div className="mt-3 flex justify-end">
|
|
1103
1119
|
{/* ── Book container ── */}
|
|
1104
|
-
<div
|
|
1105
|
-
className={notebookShellCls}
|
|
1106
|
-
data-testid="character-notebook"
|
|
1107
|
-
>
|
|
1120
|
+
<div className={notebookShellCls} data-testid="character-notebook">
|
|
1108
1121
|
<div className="pointer-events-none absolute inset-x-5 bottom-[-1.2rem] top-6 rounded-[1.9rem] bg-[linear-gradient(180deg,rgba(35,27,18,0.12)_0%,rgba(16,12,9,0.32)_100%)] blur-xl" />
|
|
1109
1122
|
<div className="pointer-events-none absolute inset-[0.35rem] translate-x-2 translate-y-2 rounded-[1.9rem] border border-[#d7c5a4]/50 bg-[linear-gradient(180deg,rgba(251,246,235,0.86)_0%,rgba(236,224,198,0.7)_100%)] shadow-[0_12px_26px_rgba(62,44,21,0.12)]" />
|
|
1110
1123
|
<div className="pointer-events-none absolute bottom-[-0.95rem] left-8 h-14 w-4 rounded-b-sm bg-[linear-gradient(180deg,#ddb45e_0%,#be8530_100%)] [clip-path:polygon(0_0,100%_0,100%_78%,50%_100%,0_78%)] shadow-[0_10px_20px_rgba(141,97,34,0.28)]" />
|
|
@@ -1115,410 +1128,564 @@ export function CharacterView({
|
|
|
1115
1128
|
<div className="pointer-events-none absolute bottom-4 right-4 h-2 w-2 rounded-full border border-white/10 bg-white/10 shadow-[0_0_0_2px_rgba(255,255,255,0.03)]" />
|
|
1116
1129
|
<div className="pointer-events-none absolute inset-x-0 bottom-0 h-14 bg-[linear-gradient(180deg,rgba(0,0,0,0)_0%,rgba(104,77,39,0.07)_100%)]" />
|
|
1117
1130
|
<div className="pointer-events-none absolute bottom-3 right-[4.95rem] top-3 w-[0.7rem] rounded-full bg-[linear-gradient(90deg,rgba(116,92,55,0.16)_0%,rgba(255,255,255,0.5)_45%,rgba(112,88,52,0.2)_100%)] shadow-[inset_0_0_7px_rgba(89,67,37,0.16),0_0_18px_rgba(255,246,220,0.16)]" />
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
<button
|
|
1128
|
-
type="button"
|
|
1129
|
-
className={`relative h-9 rounded-b-md rounded-t-[1rem] border px-3.5 text-[10px] font-bold uppercase tracking-[0.18em] transition-all ${
|
|
1130
|
-
customizeStep === "core"
|
|
1131
|
-
? "border-[#c59b47] bg-[linear-gradient(180deg,#f4cf79_0%,#d69f45_100%)] text-[#2d2418] shadow-[0_10px_18px_rgba(137,97,36,0.22)]"
|
|
1132
|
-
: "border-[#d4c6ad]/90 bg-[linear-gradient(180deg,rgba(255,252,245,0.96)_0%,rgba(243,235,222,0.82)_100%)] text-[#7b7365] shadow-[inset_0_1px_0_rgba(255,255,255,0.75)] hover:bg-[linear-gradient(180deg,rgba(255,255,255,0.98)_0%,rgba(248,240,226,0.9)_100%)] dark:border-white/10 dark:bg-[linear-gradient(180deg,rgba(255,255,255,0.1)_0%,rgba(255,255,255,0.04)_100%)] dark:text-muted dark:shadow-none dark:hover:bg-[linear-gradient(180deg,rgba(255,255,255,0.14)_0%,rgba(255,255,255,0.05)_100%)]"
|
|
1133
|
-
}`}
|
|
1134
|
-
onClick={() => setCustomizeStep("core")}
|
|
1135
|
-
>
|
|
1136
|
-
{t("characterview.core")}
|
|
1137
|
-
</button>
|
|
1138
|
-
{activeSection !== "styleRules" && (
|
|
1131
|
+
{/* ── Book page (left) ── */}
|
|
1132
|
+
<div
|
|
1133
|
+
className={`${bookPageCls} flex flex-1 flex-col rounded-l-[1.75rem] border-r border-[#cdbb98]/60 text-[#1e2329] dark:border-white/[0.08] dark:text-[hsl(40,10%,84%)]`}
|
|
1134
|
+
>
|
|
1135
|
+
<div className="pointer-events-none absolute inset-[0.7rem] rounded-[1.25rem] border border-[#efe3cc]/85 shadow-[inset_0_0_0_1px_rgba(207,184,141,0.24)] dark:border-white/5 dark:shadow-[inset_0_0_0_1px_rgba(255,255,255,0.03)]" />
|
|
1136
|
+
<div className="pointer-events-none absolute inset-0 bg-[radial-gradient(circle_at_top_left,rgba(255,255,255,0.72)_0%,rgba(255,255,255,0)_36%),linear-gradient(180deg,rgba(255,255,255,0.14)_0%,rgba(255,255,255,0)_24%),repeating-linear-gradient(0deg,rgba(168,139,87,0.035)_0px,rgba(168,139,87,0.035)_1px,transparent_1px,transparent_11px)] opacity-85 dark:opacity-25" />
|
|
1137
|
+
<div className="pointer-events-none absolute bottom-0 right-0 top-0 w-5 bg-[linear-gradient(90deg,rgba(123,98,61,0)_0%,rgba(123,98,61,0.08)_55%,rgba(255,255,255,0.14)_100%)] dark:bg-[linear-gradient(90deg,rgba(255,255,255,0)_0%,rgba(255,255,255,0.03)_60%,rgba(255,255,255,0.08)_100%)]" />
|
|
1138
|
+
{/* Mode toggle: Core / Examples */}
|
|
1139
|
+
<div className="relative z-10 flex items-end gap-2 bg-[linear-gradient(180deg,rgba(255,255,255,0.26)_0%,rgba(255,255,255,0)_100%)] px-5 pb-3 pt-4 dark:bg-[linear-gradient(180deg,rgba(255,255,255,0.06)_0%,rgba(255,255,255,0)_100%)]">
|
|
1139
1140
|
<button
|
|
1140
1141
|
type="button"
|
|
1141
1142
|
className={`relative h-9 rounded-b-md rounded-t-[1rem] border px-3.5 text-[10px] font-bold uppercase tracking-[0.18em] transition-all ${
|
|
1142
|
-
customizeStep === "
|
|
1143
|
+
customizeStep === "core"
|
|
1143
1144
|
? "border-[#c59b47] bg-[linear-gradient(180deg,#f4cf79_0%,#d69f45_100%)] text-[#2d2418] shadow-[0_10px_18px_rgba(137,97,36,0.22)]"
|
|
1144
1145
|
: "border-[#d4c6ad]/90 bg-[linear-gradient(180deg,rgba(255,252,245,0.96)_0%,rgba(243,235,222,0.82)_100%)] text-[#7b7365] shadow-[inset_0_1px_0_rgba(255,255,255,0.75)] hover:bg-[linear-gradient(180deg,rgba(255,255,255,0.98)_0%,rgba(248,240,226,0.9)_100%)] dark:border-white/10 dark:bg-[linear-gradient(180deg,rgba(255,255,255,0.1)_0%,rgba(255,255,255,0.04)_100%)] dark:text-muted dark:shadow-none dark:hover:bg-[linear-gradient(180deg,rgba(255,255,255,0.14)_0%,rgba(255,255,255,0.05)_100%)]"
|
|
1145
1146
|
}`}
|
|
1146
|
-
onClick={() => setCustomizeStep("
|
|
1147
|
+
onClick={() => setCustomizeStep("core")}
|
|
1147
1148
|
>
|
|
1148
|
-
{t("characterview.
|
|
1149
|
+
{t("characterview.core")}
|
|
1149
1150
|
</button>
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1151
|
+
{activeSection !== "styleRules" && (
|
|
1152
|
+
<button
|
|
1153
|
+
type="button"
|
|
1154
|
+
className={`relative h-9 rounded-b-md rounded-t-[1rem] border px-3.5 text-[10px] font-bold uppercase tracking-[0.18em] transition-all ${
|
|
1155
|
+
customizeStep === "examples"
|
|
1156
|
+
? "border-[#c59b47] bg-[linear-gradient(180deg,#f4cf79_0%,#d69f45_100%)] text-[#2d2418] shadow-[0_10px_18px_rgba(137,97,36,0.22)]"
|
|
1157
|
+
: "border-[#d4c6ad]/90 bg-[linear-gradient(180deg,rgba(255,252,245,0.96)_0%,rgba(243,235,222,0.82)_100%)] text-[#7b7365] shadow-[inset_0_1px_0_rgba(255,255,255,0.75)] hover:bg-[linear-gradient(180deg,rgba(255,255,255,0.98)_0%,rgba(248,240,226,0.9)_100%)] dark:border-white/10 dark:bg-[linear-gradient(180deg,rgba(255,255,255,0.1)_0%,rgba(255,255,255,0.04)_100%)] dark:text-muted dark:shadow-none dark:hover:bg-[linear-gradient(180deg,rgba(255,255,255,0.14)_0%,rgba(255,255,255,0.05)_100%)]"
|
|
1158
|
+
}`}
|
|
1159
|
+
onClick={() => setCustomizeStep("examples")}
|
|
1160
|
+
>
|
|
1161
|
+
{t("characterview.examples")}
|
|
1162
|
+
</button>
|
|
1163
|
+
)}
|
|
1164
|
+
<div className="pointer-events-none absolute inset-x-4 bottom-0 h-px bg-[linear-gradient(90deg,rgba(209,193,165,0)_0%,rgba(209,193,165,0.92)_10%,rgba(209,193,165,0.92)_90%,rgba(209,193,165,0)_100%)] dark:bg-[linear-gradient(90deg,rgba(255,255,255,0)_0%,rgba(255,255,255,0.08)_10%,rgba(255,255,255,0.08)_90%,rgba(255,255,255,0)_100%)]" />
|
|
1165
|
+
</div>
|
|
1153
1166
|
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1167
|
+
{/* Content area */}
|
|
1168
|
+
<div
|
|
1169
|
+
className="relative z-10 flex flex-1 flex-col overflow-y-auto p-5 custom-scrollbar"
|
|
1170
|
+
role="tabpanel"
|
|
1171
|
+
aria-labelledby={`notebook-tab-${activeSection}`}
|
|
1172
|
+
>
|
|
1173
|
+
{/* ── About Me (Core) ── */}
|
|
1174
|
+
{activeSection === "aboutMe" && customizeStep === "core" && (
|
|
1175
|
+
<div
|
|
1176
|
+
className="flex flex-1 flex-col gap-3"
|
|
1177
|
+
data-testid="character-core-editor"
|
|
1178
|
+
>
|
|
1179
|
+
{/* Name */}
|
|
1180
|
+
<div className="flex flex-col gap-1.5">
|
|
1181
|
+
<span className="text-xs font-medium text-[#6d737a] dark:text-muted">
|
|
1182
|
+
Name
|
|
1183
|
+
</span>
|
|
1184
|
+
<Input
|
|
1185
|
+
type="text"
|
|
1186
|
+
value={d.name ?? ""}
|
|
1187
|
+
placeholder="Agent name"
|
|
1188
|
+
onChange={(e) =>
|
|
1189
|
+
handleFieldEdit("name", e.target.value)
|
|
1190
|
+
}
|
|
1191
|
+
className="h-8 rounded-lg border-[#d6d3c6] dark:border-border/40 bg-white/60 dark:bg-white/5 text-sm text-[#1e2329] dark:text-[hsl(40,10%,84%)] focus-visible:border-accent focus-visible:ring-accent/50"
|
|
1192
|
+
/>
|
|
1193
|
+
</div>
|
|
1170
1194
|
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1195
|
+
{/* Avatar */}
|
|
1196
|
+
<div className="flex flex-col gap-1.5">
|
|
1197
|
+
<span className="text-xs font-medium text-[#6d737a] dark:text-muted">
|
|
1198
|
+
Avatar
|
|
1199
|
+
</span>
|
|
1200
|
+
<AvatarSelector
|
|
1201
|
+
selected={selectedVrmIndex}
|
|
1202
|
+
onSelect={(index: number) =>
|
|
1203
|
+
setState("selectedVrmIndex", index)
|
|
1204
|
+
}
|
|
1205
|
+
onUpload={handleCustomVrmUpload}
|
|
1206
|
+
showUpload={true}
|
|
1207
|
+
fullWidth={true}
|
|
1208
|
+
/>
|
|
1209
|
+
</div>
|
|
1182
1210
|
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1211
|
+
<div className="flex items-center justify-between">
|
|
1212
|
+
<span className="text-xs font-medium text-[#6d737a] dark:text-muted">
|
|
1213
|
+
{t("characterview.aboutMe")}
|
|
1214
|
+
</span>
|
|
1215
|
+
<Button
|
|
1216
|
+
variant="ghost"
|
|
1217
|
+
size="sm"
|
|
1218
|
+
className="h-6 rounded-md px-2 text-[10px] font-bold text-accent"
|
|
1219
|
+
onClick={() => void handleGenerate("bio")}
|
|
1220
|
+
disabled={generating === "bio"}
|
|
1221
|
+
>
|
|
1222
|
+
{generating === "bio"
|
|
1223
|
+
? "generating..."
|
|
1224
|
+
: "regenerate"}
|
|
1225
|
+
</Button>
|
|
1226
|
+
</div>
|
|
1227
|
+
<Textarea
|
|
1228
|
+
value={bioText}
|
|
1229
|
+
placeholder={t("characterview.describeWhoYourAg")}
|
|
1230
|
+
onChange={(e) => handleFieldEdit("bio", e.target.value)}
|
|
1231
|
+
className="flex-1 resize-none overflow-y-auto rounded-lg border-[#d6d3c6] dark:border-border/40 bg-white/60 dark:bg-white/5 p-3 font-mono text-xs leading-relaxed text-[#1e2329] dark:text-[hsl(40,10%,84%)] focus-visible:border-accent focus-visible:ring-accent/50"
|
|
1232
|
+
/>
|
|
1194
1233
|
</div>
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1234
|
+
)}
|
|
1235
|
+
|
|
1236
|
+
{/* ── Directions (Core) ── */}
|
|
1237
|
+
{activeSection === "directions" &&
|
|
1238
|
+
customizeStep === "core" && (
|
|
1239
|
+
<div className="flex flex-1 flex-col gap-3">
|
|
1240
|
+
<div className="flex items-center justify-between">
|
|
1241
|
+
<span className="text-xs font-medium text-[#6d737a] dark:text-muted">
|
|
1242
|
+
{t("characterview.directionsAndThing")}
|
|
1243
|
+
</span>
|
|
1244
|
+
<Button
|
|
1245
|
+
variant="ghost"
|
|
1246
|
+
size="sm"
|
|
1247
|
+
className="h-6 rounded-md px-2 text-[10px] font-bold text-accent"
|
|
1248
|
+
onClick={() => void handleGenerate("system")}
|
|
1249
|
+
disabled={generating === "system"}
|
|
1250
|
+
>
|
|
1251
|
+
{generating === "system"
|
|
1252
|
+
? "generating..."
|
|
1253
|
+
: "regenerate"}
|
|
1254
|
+
</Button>
|
|
1255
|
+
</div>
|
|
1256
|
+
<Textarea
|
|
1257
|
+
value={d.system ?? ""}
|
|
1258
|
+
maxLength={10000}
|
|
1259
|
+
placeholder={t("characterview.writeInFirstPerso")}
|
|
1260
|
+
onChange={(e) =>
|
|
1261
|
+
handleFieldEdit("system", e.target.value)
|
|
1262
|
+
}
|
|
1263
|
+
className="flex-1 resize-none overflow-y-auto rounded-lg border-[#d6d3c6] dark:border-border/40 bg-white/60 dark:bg-white/5 p-3 font-mono text-xs leading-relaxed text-[#1e2329] dark:text-[hsl(40,10%,84%)] focus-visible:border-accent focus-visible:ring-accent/50"
|
|
1264
|
+
/>
|
|
1265
|
+
</div>
|
|
1266
|
+
)}
|
|
1203
1267
|
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
variant="ghost"
|
|
1211
|
-
size="sm"
|
|
1212
|
-
className="h-6 rounded-md px-2 text-[10px] font-bold text-accent"
|
|
1213
|
-
onClick={() => void handleGenerate("system")}
|
|
1214
|
-
disabled={generating === "system"}
|
|
1268
|
+
{/* ── Style Rules (Core) ── */}
|
|
1269
|
+
{activeSection === "styleRules" &&
|
|
1270
|
+
customizeStep === "core" && (
|
|
1271
|
+
<div
|
|
1272
|
+
className="flex flex-col gap-3"
|
|
1273
|
+
data-testid="character-style-editor"
|
|
1215
1274
|
>
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1275
|
+
<div className="flex items-center justify-between">
|
|
1276
|
+
<span className="text-xs font-medium text-[#6d737a] dark:text-muted">
|
|
1277
|
+
{t("characterview.StyleRules")}
|
|
1278
|
+
</span>
|
|
1279
|
+
<Button
|
|
1280
|
+
variant="ghost"
|
|
1281
|
+
size="sm"
|
|
1282
|
+
className="h-6 rounded-md px-2 text-[10px] font-bold text-accent"
|
|
1283
|
+
onClick={() =>
|
|
1284
|
+
void handleGenerate("style", "replace")
|
|
1285
|
+
}
|
|
1286
|
+
disabled={generating === "style"}
|
|
1287
|
+
>
|
|
1288
|
+
{generating === "style"
|
|
1289
|
+
? "generating..."
|
|
1290
|
+
: "regenerate"}
|
|
1291
|
+
</Button>
|
|
1292
|
+
</div>
|
|
1293
|
+
{STYLE_SECTION_KEYS.map((key) => {
|
|
1294
|
+
const items = d.style?.[key] ?? [];
|
|
1295
|
+
return (
|
|
1296
|
+
<div
|
|
1297
|
+
key={key}
|
|
1298
|
+
className="flex flex-col gap-1.5"
|
|
1299
|
+
data-testid={`style-section-${key}`}
|
|
1300
|
+
>
|
|
1301
|
+
<div className="flex items-center gap-2 border-b border-[#d6d3c6]/60 dark:border-border/30 pb-1">
|
|
1302
|
+
<span className="text-[10px] font-bold uppercase tracking-widest text-[#6d737a] dark:text-muted">
|
|
1303
|
+
{key}
|
|
1304
|
+
</span>
|
|
1305
|
+
<span className="text-[9px] font-medium uppercase tracking-wider text-[#9ca3af] dark:text-muted/60">
|
|
1306
|
+
{items.length} rule
|
|
1307
|
+
{items.length === 1 ? "" : "s"}
|
|
1308
|
+
</span>
|
|
1309
|
+
</div>
|
|
1310
|
+
<div className={`${scrollPaneCls} max-h-[12rem]`}>
|
|
1311
|
+
<div className="flex flex-col gap-1">
|
|
1312
|
+
{items.length > 0 ? (
|
|
1313
|
+
items.map((item, index) => (
|
|
1314
|
+
<div
|
|
1315
|
+
key={getStyleEntryRenderKey(
|
|
1316
|
+
key,
|
|
1317
|
+
items,
|
|
1318
|
+
item,
|
|
1319
|
+
index,
|
|
1320
|
+
)}
|
|
1321
|
+
className="group flex items-start gap-2 rounded-md border border-[#d6d3c6]/40 dark:border-border/20 bg-white/50 dark:bg-white/3 px-2.5 py-2"
|
|
1322
|
+
data-testid={`style-entry-${key}-${index}`}
|
|
1323
|
+
>
|
|
1324
|
+
<span className="mt-0.5 shrink-0 text-[10px] font-bold text-accent">
|
|
1325
|
+
{index + 1}
|
|
1326
|
+
</span>
|
|
1327
|
+
<Textarea
|
|
1328
|
+
value={
|
|
1329
|
+
styleEntryDrafts[key]?.[index] ??
|
|
1330
|
+
item
|
|
1331
|
+
}
|
|
1332
|
+
rows={1}
|
|
1333
|
+
onChange={(e) =>
|
|
1334
|
+
handleStyleEntryDraftChange(
|
|
1335
|
+
key,
|
|
1336
|
+
index,
|
|
1337
|
+
e.target.value,
|
|
1338
|
+
)
|
|
1339
|
+
}
|
|
1340
|
+
onBlur={() =>
|
|
1341
|
+
handleCommitStyleEntry(key, index)
|
|
1342
|
+
}
|
|
1343
|
+
className="min-h-[2rem] min-w-0 flex-1 resize-none rounded border-0 bg-transparent p-0 font-mono text-xs leading-relaxed text-[#1e2329] dark:text-[hsl(40,10%,84%)] focus-visible:ring-0"
|
|
1344
|
+
data-testid={`style-entry-editor-${key}-${index}`}
|
|
1345
|
+
/>
|
|
1346
|
+
<button
|
|
1347
|
+
type="button"
|
|
1348
|
+
className="mt-0.5 shrink-0 text-[#9ca3af] opacity-0 transition-opacity group-hover:opacity-100 hover:text-red-500"
|
|
1349
|
+
onClick={() =>
|
|
1350
|
+
handleRemoveStyleEntry(key, index)
|
|
1351
|
+
}
|
|
1352
|
+
title={t("characterview.remove")}
|
|
1353
|
+
>
|
|
1354
|
+
<svg
|
|
1355
|
+
width="10"
|
|
1356
|
+
height="10"
|
|
1357
|
+
viewBox="0 0 10 10"
|
|
1358
|
+
fill="none"
|
|
1359
|
+
stroke="currentColor"
|
|
1360
|
+
strokeWidth="1.5"
|
|
1361
|
+
strokeLinecap="round"
|
|
1362
|
+
aria-label={t(
|
|
1363
|
+
"characterview.remove",
|
|
1364
|
+
)}
|
|
1365
|
+
>
|
|
1366
|
+
<path d="M2 2l6 6M8 2l-6 6" />
|
|
1367
|
+
</svg>
|
|
1368
|
+
</button>
|
|
1369
|
+
</div>
|
|
1370
|
+
))
|
|
1371
|
+
) : (
|
|
1372
|
+
<div className="rounded-md border border-dashed border-[#d6d3c6]/60 dark:border-border/30 px-3 py-2 text-[11px] text-[#9ca3af] dark:text-muted">
|
|
1373
|
+
{STYLE_SECTION_EMPTY_STATES[key]}
|
|
1374
|
+
</div>
|
|
1375
|
+
)}
|
|
1376
|
+
</div>
|
|
1377
|
+
</div>
|
|
1378
|
+
<div className="flex items-center gap-2">
|
|
1379
|
+
<Input
|
|
1380
|
+
type="text"
|
|
1381
|
+
value={pendingStyleEntries[key]}
|
|
1382
|
+
placeholder={STYLE_SECTION_PLACEHOLDERS[key]}
|
|
1383
|
+
onChange={(e) =>
|
|
1384
|
+
handlePendingStyleEntryChange(
|
|
1385
|
+
key,
|
|
1386
|
+
e.target.value,
|
|
1387
|
+
)
|
|
1388
|
+
}
|
|
1389
|
+
onKeyDown={(e) => {
|
|
1390
|
+
if (e.key === "Enter") {
|
|
1391
|
+
e.preventDefault();
|
|
1392
|
+
handleAddStyleEntry(key);
|
|
1393
|
+
}
|
|
1394
|
+
}}
|
|
1395
|
+
className="h-7 min-w-0 flex-1 rounded-md border-[#d6d3c6]/60 dark:border-border/30 bg-white/50 dark:bg-white/5 text-xs text-[#1e2329] dark:text-[hsl(40,10%,84%)] focus-visible:border-accent focus-visible:ring-accent/50"
|
|
1396
|
+
data-testid={`style-entry-input-${key}`}
|
|
1397
|
+
/>
|
|
1398
|
+
<Button
|
|
1399
|
+
variant="ghost"
|
|
1400
|
+
size="sm"
|
|
1401
|
+
className="h-7 shrink-0 rounded-md px-2 text-[10px] font-bold text-accent"
|
|
1402
|
+
onClick={() => handleAddStyleEntry(key)}
|
|
1403
|
+
disabled={!pendingStyleEntries[key].trim()}
|
|
1404
|
+
>
|
|
1405
|
+
+ add
|
|
1406
|
+
</Button>
|
|
1407
|
+
</div>
|
|
1408
|
+
</div>
|
|
1409
|
+
);
|
|
1410
|
+
})}
|
|
1411
|
+
</div>
|
|
1412
|
+
)}
|
|
1228
1413
|
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
variant="ghost"
|
|
1236
|
-
size="sm"
|
|
1237
|
-
className="h-6 rounded-md px-2 text-[10px] font-bold text-accent"
|
|
1238
|
-
onClick={() => void handleGenerate("style", "replace")}
|
|
1239
|
-
disabled={generating === "style"}
|
|
1414
|
+
{/* ── Chat Examples (About Me + Examples mode) ── */}
|
|
1415
|
+
{activeSection === "aboutMe" &&
|
|
1416
|
+
customizeStep === "examples" && (
|
|
1417
|
+
<div
|
|
1418
|
+
className="flex flex-col gap-2"
|
|
1419
|
+
data-testid="character-chat-examples-card"
|
|
1240
1420
|
>
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
</
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1421
|
+
<div className="flex items-center justify-between">
|
|
1422
|
+
<span className="text-xs font-medium text-[#6d737a] dark:text-muted">
|
|
1423
|
+
{t("characterview.chatExamples")}
|
|
1424
|
+
</span>
|
|
1425
|
+
<Button
|
|
1426
|
+
variant="ghost"
|
|
1427
|
+
size="sm"
|
|
1428
|
+
className="h-6 rounded-md px-2 text-[10px] font-bold text-accent"
|
|
1429
|
+
onClick={() =>
|
|
1430
|
+
void handleGenerate("chatExamples", "replace")
|
|
1431
|
+
}
|
|
1432
|
+
disabled={generating === "chatExamples"}
|
|
1433
|
+
>
|
|
1434
|
+
{generating === "chatExamples"
|
|
1435
|
+
? "generating..."
|
|
1436
|
+
: "generate"}
|
|
1437
|
+
</Button>
|
|
1438
|
+
</div>
|
|
1439
|
+
<div className={`${scrollPaneCls} flex flex-col gap-2`}>
|
|
1440
|
+
{(d.messageExamples ?? []).map((convo, ci) => (
|
|
1441
|
+
<div
|
|
1442
|
+
key={convo.examples
|
|
1443
|
+
.map(
|
|
1444
|
+
(msg) =>
|
|
1445
|
+
`${msg.name}:${msg.content?.text ?? ""}`,
|
|
1446
|
+
)
|
|
1447
|
+
.join("|")}
|
|
1448
|
+
className="rounded-lg border border-[#d6d3c6]/40 dark:border-border/30 p-2.5"
|
|
1449
|
+
>
|
|
1450
|
+
<div className="mb-2 flex items-center justify-between">
|
|
1451
|
+
<span className="text-[9px] font-bold uppercase tracking-widest text-[#9ca3af] dark:text-muted/60">
|
|
1452
|
+
{t("characterview.conversation")} {ci + 1}
|
|
1453
|
+
</span>
|
|
1454
|
+
<button
|
|
1455
|
+
type="button"
|
|
1456
|
+
className="text-[#9ca3af] hover:text-red-500 transition-colors"
|
|
1457
|
+
onClick={() => {
|
|
1458
|
+
const updated = [
|
|
1459
|
+
...(d.messageExamples ?? []),
|
|
1460
|
+
];
|
|
1461
|
+
updated.splice(ci, 1);
|
|
1462
|
+
handleFieldEdit("messageExamples", updated);
|
|
1463
|
+
}}
|
|
1464
|
+
>
|
|
1465
|
+
<svg
|
|
1466
|
+
width="10"
|
|
1467
|
+
height="10"
|
|
1468
|
+
viewBox="0 0 10 10"
|
|
1469
|
+
fill="none"
|
|
1470
|
+
stroke="currentColor"
|
|
1471
|
+
strokeWidth="1.5"
|
|
1472
|
+
strokeLinecap="round"
|
|
1473
|
+
aria-label={t("characterview.remove")}
|
|
1474
|
+
>
|
|
1475
|
+
<path d="M2 2l6 6M8 2l-6 6" />
|
|
1476
|
+
</svg>
|
|
1477
|
+
</button>
|
|
1478
|
+
</div>
|
|
1479
|
+
<div className="flex flex-col gap-1.5">
|
|
1480
|
+
{convo.examples.map((msg, mi) => (
|
|
1262
1481
|
<div
|
|
1263
|
-
key={
|
|
1264
|
-
className="
|
|
1265
|
-
data-testid={`style-entry-${key}-${index}`}
|
|
1482
|
+
key={`${msg.name}:${msg.content?.text ?? ""}`}
|
|
1483
|
+
className="flex items-center gap-2"
|
|
1266
1484
|
>
|
|
1267
|
-
<span
|
|
1268
|
-
{
|
|
1485
|
+
<span
|
|
1486
|
+
className={`w-10 shrink-0 text-right text-[9px] font-bold uppercase tracking-wider ${msg.name === "{{user1}}" ? "text-[#9ca3af] dark:text-muted" : "text-accent"}`}
|
|
1487
|
+
>
|
|
1488
|
+
{msg.name === "{{user1}}"
|
|
1489
|
+
? "user"
|
|
1490
|
+
: "agent"}
|
|
1269
1491
|
</span>
|
|
1270
|
-
<
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
onChange={(e) =>
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1492
|
+
<input
|
|
1493
|
+
type="text"
|
|
1494
|
+
value={msg.content?.text ?? ""}
|
|
1495
|
+
onChange={(e) => {
|
|
1496
|
+
const updated = [
|
|
1497
|
+
...(d.messageExamples ?? []),
|
|
1498
|
+
];
|
|
1499
|
+
const convoClone = {
|
|
1500
|
+
examples: [...updated[ci].examples],
|
|
1501
|
+
};
|
|
1502
|
+
convoClone.examples[mi] = {
|
|
1503
|
+
...convoClone.examples[mi],
|
|
1504
|
+
content: { text: e.target.value },
|
|
1505
|
+
};
|
|
1506
|
+
updated[ci] = convoClone;
|
|
1507
|
+
handleFieldEdit(
|
|
1508
|
+
"messageExamples",
|
|
1509
|
+
updated,
|
|
1510
|
+
);
|
|
1511
|
+
}}
|
|
1512
|
+
className="h-7 flex-1 rounded-md border border-[#d6d3c6]/40 dark:border-border/30 bg-white/60 dark:bg-white/5 px-2 font-mono text-[11px] text-[#1e2329] dark:text-[hsl(40,10%,84%)] outline-none focus:border-accent"
|
|
1277
1513
|
/>
|
|
1278
|
-
<button
|
|
1279
|
-
type="button"
|
|
1280
|
-
className="mt-0.5 shrink-0 text-[#9ca3af] opacity-0 transition-opacity group-hover:opacity-100 hover:text-red-500"
|
|
1281
|
-
onClick={() => handleRemoveStyleEntry(key, index)}
|
|
1282
|
-
title={t("characterview.remove")}
|
|
1283
|
-
>
|
|
1284
|
-
<svg width="10" height="10" viewBox="0 0 10 10" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round"><path d="M2 2l6 6M8 2l-6 6" /></svg>
|
|
1285
|
-
</button>
|
|
1286
1514
|
</div>
|
|
1287
|
-
))
|
|
1288
|
-
|
|
1289
|
-
<div className="rounded-md border border-dashed border-[#d6d3c6]/60 dark:border-border/30 px-3 py-2 text-[11px] text-[#9ca3af] dark:text-muted">
|
|
1290
|
-
{STYLE_SECTION_EMPTY_STATES[key]}
|
|
1291
|
-
</div>
|
|
1292
|
-
)}
|
|
1515
|
+
))}
|
|
1516
|
+
</div>
|
|
1293
1517
|
</div>
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
<
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
onChange={(e) => handlePendingStyleEntryChange(key, e.target.value)}
|
|
1301
|
-
onKeyDown={(e) => {
|
|
1302
|
-
if (e.key === "Enter") {
|
|
1303
|
-
e.preventDefault();
|
|
1304
|
-
handleAddStyleEntry(key);
|
|
1305
|
-
}
|
|
1306
|
-
}}
|
|
1307
|
-
className="h-7 min-w-0 flex-1 rounded-md border-[#d6d3c6]/60 dark:border-border/30 bg-white/50 dark:bg-white/5 text-xs text-[#1e2329] dark:text-[hsl(40,10%,84%)] focus-visible:border-accent focus-visible:ring-accent/50"
|
|
1308
|
-
data-testid={`style-entry-input-${key}`}
|
|
1309
|
-
/>
|
|
1310
|
-
<Button
|
|
1311
|
-
variant="ghost"
|
|
1312
|
-
size="sm"
|
|
1313
|
-
className="h-7 shrink-0 rounded-md px-2 text-[10px] font-bold text-accent"
|
|
1314
|
-
onClick={() => handleAddStyleEntry(key)}
|
|
1315
|
-
disabled={!pendingStyleEntries[key].trim()}
|
|
1316
|
-
>
|
|
1317
|
-
+ add
|
|
1318
|
-
</Button>
|
|
1319
|
-
</div>
|
|
1518
|
+
))}
|
|
1519
|
+
{(d.messageExamples ?? []).length === 0 && (
|
|
1520
|
+
<div className="rounded-md border border-dashed border-[#d6d3c6]/60 dark:border-border/30 py-2 text-center text-[11px] text-[#9ca3af] dark:text-muted">
|
|
1521
|
+
{t("characterview.noChatExamplesYet")}
|
|
1522
|
+
</div>
|
|
1523
|
+
)}
|
|
1320
1524
|
</div>
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
</div>
|
|
1324
|
-
)}
|
|
1525
|
+
</div>
|
|
1526
|
+
)}
|
|
1325
1527
|
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
variant="ghost"
|
|
1333
|
-
size="sm"
|
|
1334
|
-
className="h-6 rounded-md px-2 text-[10px] font-bold text-accent"
|
|
1335
|
-
onClick={() => void handleGenerate("chatExamples", "replace")}
|
|
1336
|
-
disabled={generating === "chatExamples"}
|
|
1528
|
+
{/* ── Post Examples (Directions + Examples mode) ── */}
|
|
1529
|
+
{activeSection === "directions" &&
|
|
1530
|
+
customizeStep === "examples" && (
|
|
1531
|
+
<div
|
|
1532
|
+
className="flex flex-col gap-2"
|
|
1533
|
+
data-testid="character-post-examples-card"
|
|
1337
1534
|
>
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1535
|
+
<div className="flex items-center justify-between">
|
|
1536
|
+
<span className="text-xs font-medium text-[#6d737a] dark:text-muted">
|
|
1537
|
+
{t("characterview.postExamples")}
|
|
1538
|
+
</span>
|
|
1539
|
+
<Button
|
|
1540
|
+
variant="ghost"
|
|
1541
|
+
size="sm"
|
|
1542
|
+
className="h-6 rounded-md px-2 text-[10px] font-bold text-accent"
|
|
1543
|
+
onClick={() =>
|
|
1544
|
+
void handleGenerate("postExamples", "replace")
|
|
1545
|
+
}
|
|
1546
|
+
disabled={generating === "postExamples"}
|
|
1547
|
+
>
|
|
1548
|
+
{generating === "postExamples"
|
|
1549
|
+
? "generating..."
|
|
1550
|
+
: "generate"}
|
|
1551
|
+
</Button>
|
|
1552
|
+
</div>
|
|
1343
1553
|
<div
|
|
1344
|
-
|
|
1345
|
-
className="rounded-lg border border-[#d6d3c6]/40 dark:border-border/30 p-2.5"
|
|
1554
|
+
className={`${scrollPaneCls} flex flex-col gap-1.5`}
|
|
1346
1555
|
>
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
className="text-[#9ca3af] hover:text-red-500 transition-colors"
|
|
1354
|
-
onClick={() => {
|
|
1355
|
-
const updated = [...(d.messageExamples ?? [])];
|
|
1356
|
-
updated.splice(ci, 1);
|
|
1357
|
-
handleFieldEdit("messageExamples", updated);
|
|
1358
|
-
}}
|
|
1359
|
-
>
|
|
1360
|
-
<svg width="10" height="10" viewBox="0 0 10 10" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round"><path d="M2 2l6 6M8 2l-6 6" /></svg>
|
|
1361
|
-
</button>
|
|
1362
|
-
</div>
|
|
1363
|
-
<div className="flex flex-col gap-1.5">
|
|
1364
|
-
{convo.examples.map((msg, mi) => (
|
|
1365
|
-
<div key={`${msg.name}:${msg.content?.text ?? ""}`} className="flex items-center gap-2">
|
|
1366
|
-
<span className={`w-10 shrink-0 text-right text-[9px] font-bold uppercase tracking-wider ${msg.name === "{{user1}}" ? "text-[#9ca3af] dark:text-muted" : "text-accent"}`}>
|
|
1367
|
-
{msg.name === "{{user1}}" ? "user" : "agent"}
|
|
1368
|
-
</span>
|
|
1556
|
+
{(d.postExamples ?? []).map(
|
|
1557
|
+
(post: string, pi: number) => (
|
|
1558
|
+
<div
|
|
1559
|
+
key={post || `post-${pi}`}
|
|
1560
|
+
className="group flex items-center gap-1.5"
|
|
1561
|
+
>
|
|
1369
1562
|
<input
|
|
1370
1563
|
type="text"
|
|
1371
|
-
value={
|
|
1564
|
+
value={post}
|
|
1372
1565
|
onChange={(e) => {
|
|
1373
|
-
const updated = [...(d.
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
updated[ci] = convoClone;
|
|
1377
|
-
handleFieldEdit("messageExamples", updated);
|
|
1566
|
+
const updated = [...(d.postExamples ?? [])];
|
|
1567
|
+
updated[pi] = e.target.value;
|
|
1568
|
+
handleFieldEdit("postExamples", updated);
|
|
1378
1569
|
}}
|
|
1379
1570
|
className="h-7 flex-1 rounded-md border border-[#d6d3c6]/40 dark:border-border/30 bg-white/60 dark:bg-white/5 px-2 font-mono text-[11px] text-[#1e2329] dark:text-[hsl(40,10%,84%)] outline-none focus:border-accent"
|
|
1380
1571
|
/>
|
|
1572
|
+
<button
|
|
1573
|
+
type="button"
|
|
1574
|
+
className="shrink-0 text-[#9ca3af] opacity-0 transition-opacity group-hover:opacity-100 hover:text-red-500"
|
|
1575
|
+
onClick={() => {
|
|
1576
|
+
const updated = [...(d.postExamples ?? [])];
|
|
1577
|
+
updated.splice(pi, 1);
|
|
1578
|
+
handleFieldEdit("postExamples", updated);
|
|
1579
|
+
}}
|
|
1580
|
+
>
|
|
1581
|
+
<svg
|
|
1582
|
+
width="10"
|
|
1583
|
+
height="10"
|
|
1584
|
+
viewBox="0 0 10 10"
|
|
1585
|
+
fill="none"
|
|
1586
|
+
stroke="currentColor"
|
|
1587
|
+
strokeWidth="1.5"
|
|
1588
|
+
strokeLinecap="round"
|
|
1589
|
+
aria-label={t("characterview.remove")}
|
|
1590
|
+
>
|
|
1591
|
+
<path d="M2 2l6 6M8 2l-6 6" />
|
|
1592
|
+
</svg>
|
|
1593
|
+
</button>
|
|
1381
1594
|
</div>
|
|
1382
|
-
)
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1595
|
+
),
|
|
1596
|
+
)}
|
|
1597
|
+
{(d.postExamples ?? []).length === 0 && (
|
|
1598
|
+
<div className="rounded-md border border-dashed border-[#d6d3c6]/60 dark:border-border/30 py-2 text-center text-[11px] text-[#9ca3af] dark:text-muted">
|
|
1599
|
+
{t("characterview.noPostExamplesYet")}
|
|
1600
|
+
</div>
|
|
1601
|
+
)}
|
|
1389
1602
|
</div>
|
|
1390
|
-
|
|
1391
|
-
</div>
|
|
1392
|
-
</div>
|
|
1393
|
-
)}
|
|
1394
|
-
|
|
1395
|
-
{/* ── Post Examples (Directions + Examples mode) ── */}
|
|
1396
|
-
{activeSection === "directions" && customizeStep === "examples" && (
|
|
1397
|
-
<div className="flex flex-col gap-2" data-testid="character-post-examples-card">
|
|
1398
|
-
<div className="flex items-center justify-between">
|
|
1399
|
-
<span className="text-xs font-medium text-[#6d737a] dark:text-muted">{t("characterview.postExamples")}</span>
|
|
1400
|
-
<Button
|
|
1401
|
-
variant="ghost"
|
|
1402
|
-
size="sm"
|
|
1403
|
-
className="h-6 rounded-md px-2 text-[10px] font-bold text-accent"
|
|
1404
|
-
onClick={() => void handleGenerate("postExamples", "replace")}
|
|
1405
|
-
disabled={generating === "postExamples"}
|
|
1406
|
-
>
|
|
1407
|
-
{generating === "postExamples" ? "generating..." : "generate"}
|
|
1408
|
-
</Button>
|
|
1409
|
-
</div>
|
|
1410
|
-
<div className={`${scrollPaneCls} flex flex-col gap-1.5`}>
|
|
1411
|
-
{(d.postExamples ?? []).map((post: string, pi: number) => (
|
|
1412
|
-
<div key={post || `post-${pi}`} className="group flex items-center gap-1.5">
|
|
1413
|
-
<input
|
|
1414
|
-
type="text"
|
|
1415
|
-
value={post}
|
|
1416
|
-
onChange={(e) => {
|
|
1417
|
-
const updated = [...(d.postExamples ?? [])];
|
|
1418
|
-
updated[pi] = e.target.value;
|
|
1419
|
-
handleFieldEdit("postExamples", updated);
|
|
1420
|
-
}}
|
|
1421
|
-
className="h-7 flex-1 rounded-md border border-[#d6d3c6]/40 dark:border-border/30 bg-white/60 dark:bg-white/5 px-2 font-mono text-[11px] text-[#1e2329] dark:text-[hsl(40,10%,84%)] outline-none focus:border-accent"
|
|
1422
|
-
/>
|
|
1603
|
+
<div className="border-t border-[#d6d3c6]/40 dark:border-border/30 pt-2">
|
|
1423
1604
|
<button
|
|
1424
1605
|
type="button"
|
|
1425
|
-
className="
|
|
1606
|
+
className="text-[10px] font-bold text-accent hover:underline"
|
|
1426
1607
|
onClick={() => {
|
|
1427
|
-
const updated = [...(d.postExamples ?? [])];
|
|
1428
|
-
updated.splice(pi, 1);
|
|
1608
|
+
const updated = [...(d.postExamples ?? []), ""];
|
|
1429
1609
|
handleFieldEdit("postExamples", updated);
|
|
1430
1610
|
}}
|
|
1431
1611
|
>
|
|
1432
|
-
|
|
1612
|
+
{t("characterview.AddPost")}
|
|
1433
1613
|
</button>
|
|
1434
1614
|
</div>
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
{t("characterview.noPostExamplesYet")}
|
|
1439
|
-
</div>
|
|
1440
|
-
)}
|
|
1441
|
-
</div>
|
|
1442
|
-
<div className="border-t border-[#d6d3c6]/40 dark:border-border/30 pt-2">
|
|
1443
|
-
<button
|
|
1444
|
-
type="button"
|
|
1445
|
-
className="text-[10px] font-bold text-accent hover:underline"
|
|
1446
|
-
onClick={() => {
|
|
1447
|
-
const updated = [...(d.postExamples ?? []), ""];
|
|
1448
|
-
handleFieldEdit("postExamples", updated);
|
|
1449
|
-
}}
|
|
1450
|
-
>
|
|
1451
|
-
{t("characterview.AddPost")}
|
|
1452
|
-
</button>
|
|
1453
|
-
</div>
|
|
1454
|
-
</div>
|
|
1455
|
-
)}
|
|
1615
|
+
</div>
|
|
1616
|
+
)}
|
|
1617
|
+
</div>
|
|
1456
1618
|
</div>
|
|
1457
|
-
</div>
|
|
1458
1619
|
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
e.
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1620
|
+
{/* ── Sidebar (right) ── */}
|
|
1621
|
+
<div
|
|
1622
|
+
className={`${bookSidebarCls} flex w-20 flex-col items-center rounded-r-[1.75rem] border-l border-white/[0.08] py-3`}
|
|
1623
|
+
role="tablist"
|
|
1624
|
+
aria-orientation="vertical"
|
|
1625
|
+
>
|
|
1626
|
+
<div className="pointer-events-none absolute inset-x-2 top-2 h-10 rounded-full bg-[linear-gradient(180deg,rgba(255,255,255,0.12)_0%,rgba(255,255,255,0)_100%)] blur-sm" />
|
|
1627
|
+
<div className="pointer-events-none absolute inset-y-0 left-0 w-px bg-white/[0.08]" />
|
|
1628
|
+
<div className="pointer-events-none absolute inset-y-0 right-0 w-px bg-black/35" />
|
|
1629
|
+
{SIDEBAR_TABS.map(({ key, icon: Icon, labelKey }) => {
|
|
1630
|
+
const isActive = activeSection === key;
|
|
1631
|
+
return (
|
|
1632
|
+
<button
|
|
1633
|
+
key={key}
|
|
1634
|
+
type="button"
|
|
1635
|
+
role="tab"
|
|
1636
|
+
aria-selected={isActive}
|
|
1637
|
+
className={`relative flex w-full items-center justify-center px-2 py-3.5 transition-colors border-b border-white/[0.06] last:border-b-0 ${
|
|
1638
|
+
isActive
|
|
1639
|
+
? "text-[#f7e7bf]"
|
|
1640
|
+
: "text-[#999da7] hover:text-white/80"
|
|
1641
|
+
}`}
|
|
1642
|
+
onClick={() => {
|
|
1643
|
+
setActiveSection(key);
|
|
1644
|
+
if (
|
|
1645
|
+
key === "styleRules" &&
|
|
1646
|
+
customizeStep === "examples"
|
|
1647
|
+
) {
|
|
1648
|
+
setCustomizeStep("core");
|
|
1649
|
+
}
|
|
1650
|
+
}}
|
|
1651
|
+
onKeyDown={(e) => {
|
|
1652
|
+
const keys = SIDEBAR_TABS.map((tab) => tab.key);
|
|
1653
|
+
const idx = keys.indexOf(key);
|
|
1654
|
+
if (e.key === "ArrowDown") {
|
|
1655
|
+
e.preventDefault();
|
|
1656
|
+
const next = keys[(idx + 1) % keys.length];
|
|
1657
|
+
setActiveSection(next);
|
|
1658
|
+
} else if (e.key === "ArrowUp") {
|
|
1659
|
+
e.preventDefault();
|
|
1660
|
+
const prev =
|
|
1661
|
+
keys[(idx - 1 + keys.length) % keys.length];
|
|
1662
|
+
setActiveSection(prev);
|
|
1663
|
+
}
|
|
1664
|
+
}}
|
|
1665
|
+
id={`notebook-tab-${key}`}
|
|
1666
|
+
data-testid={`notebook-tab-${key}`}
|
|
1667
|
+
>
|
|
1668
|
+
{isActive && (
|
|
1669
|
+
<div className="absolute inset-y-2 left-0 w-[4px] rounded-r-full bg-[#d4a14c] shadow-[0_0_12px_rgba(212,161,76,0.45)]" />
|
|
1670
|
+
)}
|
|
1671
|
+
<div className="relative z-10 flex flex-col items-center gap-1.5">
|
|
1672
|
+
<div
|
|
1673
|
+
className={`flex h-10 w-10 items-center justify-center rounded-full border transition-all ${
|
|
1674
|
+
isActive
|
|
1675
|
+
? "border-[#f0ce85]/55 bg-[radial-gradient(circle_at_30%_30%,rgba(255,236,195,0.28)_0%,rgba(255,236,195,0.05)_42%,rgba(255,255,255,0)_100%)] shadow-[0_0_18px_rgba(220,176,90,0.22)]"
|
|
1676
|
+
: "border-white/10 bg-white/[0.03]"
|
|
1677
|
+
}`}
|
|
1678
|
+
>
|
|
1679
|
+
<Icon className="h-4.5 w-4.5" />
|
|
1680
|
+
</div>
|
|
1681
|
+
<span className="max-w-[4.1rem] text-center text-[8px] font-semibold uppercase tracking-[0.16em] leading-[1.22]">
|
|
1682
|
+
{t(labelKey)}
|
|
1683
|
+
</span>
|
|
1513
1684
|
</div>
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
</button>
|
|
1519
|
-
);
|
|
1520
|
-
})}
|
|
1521
|
-
</div>
|
|
1685
|
+
</button>
|
|
1686
|
+
);
|
|
1687
|
+
})}
|
|
1688
|
+
</div>
|
|
1522
1689
|
</div>
|
|
1523
1690
|
</div>
|
|
1524
1691
|
</div>
|
|
@@ -1596,7 +1763,9 @@ export function CharacterView({
|
|
|
1596
1763
|
? handleTestVoice(activeVoicePreset.previewUrl)
|
|
1597
1764
|
: undefined
|
|
1598
1765
|
}
|
|
1599
|
-
aria-label={
|
|
1766
|
+
aria-label={
|
|
1767
|
+
voiceTesting ? "Stop voice preview" : "Preview voice"
|
|
1768
|
+
}
|
|
1600
1769
|
title={voiceTesting ? "Stop voice preview" : "Preview voice"}
|
|
1601
1770
|
disabled={!activeVoicePreset || voiceLoading}
|
|
1602
1771
|
>
|