@steerable/agent-shell-web 0.6.52 → 0.6.55
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/package.json +4 -4
- package/src/brand.ts +1 -1
- package/src/components/AgentSidebar.tsx +60 -52
- package/src/components/ChatHeader.tsx +41 -25
- package/src/components/ConfirmDialog.tsx +4 -3
- package/src/components/CreateProjectModal.tsx +29 -19
- package/src/components/LocalLlmSettingsModal.tsx +4 -3
- package/src/components/SidebarRelease.tsx +2 -1
- package/src/components/TerminalPanel.tsx +3 -2
- package/src/components/TerminalView.tsx +12 -7
- package/src/components/chat/ApprovalModal.tsx +39 -25
- package/src/components/chat/ApprovalPromptProvider.tsx +1 -1
- package/src/components/chat/AskUserPromptProvider.tsx +1 -1
- package/src/components/chat/AskUserQuestionMenu.tsx +19 -14
- package/src/components/chat/AssistantMessage.tsx +18 -13
- package/src/components/chat/ChatInput.tsx +138 -42
- package/src/components/chat/ChatProjectBadge.tsx +24 -19
- package/src/components/chat/EmptyChatHero.tsx +2 -1
- package/src/components/chat/ExecPolicyPicker.tsx +12 -11
- package/src/components/chat/ExecutedActionsCard.tsx +45 -33
- package/src/components/chat/FilePathCode.tsx +8 -3
- package/src/components/chat/InterruptedTurnCard.tsx +4 -3
- package/src/components/chat/LocalChatPanel.tsx +2 -1
- package/src/components/chat/Markdown.tsx +2 -1
- package/src/components/chat/MessageList.tsx +3 -2
- package/src/components/chat/ModelPicker.tsx +25 -16
- package/src/components/chat/OrchestrationChildrenCard.tsx +2 -1
- package/src/components/chat/OrchestrationFlowCard.tsx +23 -22
- package/src/components/chat/PlanTodoList.tsx +2 -1
- package/src/components/chat/ProjectTrustBanner.tsx +19 -5
- package/src/components/chat/SessionOrchestrationFlow.tsx +27 -26
- package/src/components/chat/SessionTodoList.tsx +8 -7
- package/src/components/chat/SessionTreeModal.tsx +10 -9
- package/src/components/chat/StreamingStatus.tsx +4 -3
- package/src/components/chat/SuggestedReplies.tsx +3 -2
- package/src/components/chat/TaskOutcomeCards.tsx +8 -5
- package/src/components/chat/TaskPanelModal.tsx +31 -21
- package/src/components/chat/TaskProcessPanel.tsx +14 -13
- package/src/components/chat/TurnFilesCard.tsx +14 -13
- package/src/components/chat/UserMessage.tsx +12 -11
- package/src/components/chat/executed-actions-model.ts +47 -35
- package/src/components/chat/orchestration-flow-model.ts +16 -5
- package/src/components/chat/process-status.ts +8 -7
- package/src/components/chat/todo-list-model.ts +5 -4
- package/src/components/chat/turn-files.ts +8 -8
- package/src/components/settings/AgentCapabilityFields.tsx +24 -21
- package/src/components/settings/AgentsSettingsPanel.tsx +30 -25
- package/src/components/settings/AppUpdateSettingsPanel.tsx +3 -2
- package/src/components/settings/AppearanceSettingsPanel.tsx +81 -37
- package/src/components/settings/DiagnoseSettingsPanel.tsx +11 -8
- package/src/components/settings/InsightsSettingsPanel.tsx +43 -28
- package/src/components/settings/LlmSettingsPanel.tsx +104 -83
- package/src/components/settings/McpSettingsPanel.tsx +193 -71
- package/src/components/settings/ModelIdCombobox.tsx +2 -1
- package/src/components/settings/OrchestrationSettingsPanel.tsx +8 -5
- package/src/components/settings/PortableSettingsPanel.tsx +83 -55
- package/src/components/settings/PythonRunnerSettingsPanel.tsx +22 -19
- package/src/components/settings/SecuritySettingsPanel.tsx +55 -31
- package/src/components/settings/SettingsNavMenu.tsx +3 -2
- package/src/components/settings/SettingsSaveButton.tsx +2 -1
- package/src/components/settings/SkillsSettingsPanel.tsx +36 -31
- package/src/components/settings/TelemetrySettingsPanel.tsx +15 -12
- package/src/components/settings/UsagePanel.tsx +19 -20
- package/src/components/settings/WebSearchSettingsPanel.tsx +23 -14
- package/src/components/settings/llm-vendors.ts +12 -7
- package/src/components/settings/model-capabilities.ts +16 -15
- package/src/components/settings/preset-choice-model.ts +6 -3
- package/src/fixtures/browser-dev-data.json +24 -24
- package/src/hooks/useProjects.ts +1 -1
- package/src/i18n/agent-label.ts +15 -0
- package/src/i18n/index.tsx +68 -0
- package/src/i18n/runtime.ts +117 -0
- package/src/i18n/translate.ts +65 -0
- package/src/layouts/AgentLayout.tsx +7 -6
- package/src/lib/app-release.ts +15 -10
- package/src/lib/attachments.ts +24 -7
- package/src/lib/browser-dev-electron-mock.ts +34 -34
- package/src/lib/chat-title.ts +3 -2
- package/src/lib/chat-transport.ts +6 -2
- package/src/lib/date-groups.ts +23 -15
- package/src/lib/electron-bridge.ts +19 -6
- package/src/lib/host-file-drop.ts +68 -0
- package/src/lib/host-paste.ts +38 -9
- package/src/lib/http-bridge.ts +4 -1
- package/src/lib/portable.ts +8 -7
- package/src/lib/show-thinking-content.ts +15 -3
- package/src/lib/tauri-bridge.ts +5 -0
- package/src/main.tsx +25 -2
- package/src/pages/AgentPage.tsx +17 -12
- package/src/pages/ChatPanelPreviewPage.tsx +46 -44
- package/src/pages/SettingsPage.tsx +48 -42
- package/src/vite-env.d.ts +3 -0
- package/vite.base.js +6 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@steerable/agent-shell-web",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.55",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Steerable framework — product-neutral renderer SPA source for @steerable/agent-shell (React + Vite). Consumed by product web entry points via the `@/` alias (anchored at this package's src/) and the createProductViteConfig factory. Published as a source package (src/), plus the compiled vite.base config factory (vite.base.js + .d.ts) for Node-side config loading.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -45,8 +45,8 @@
|
|
|
45
45
|
"react-router-dom": "^6.30.0",
|
|
46
46
|
"rehype-raw": "^7.0.0",
|
|
47
47
|
"remark-gfm": "^4.0.1",
|
|
48
|
-
"@steerable/agent-protocol": "0.6.
|
|
49
|
-
"@steerable/agent-ui": "0.6.
|
|
48
|
+
"@steerable/agent-protocol": "0.6.55",
|
|
49
|
+
"@steerable/agent-ui": "0.6.55"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@tailwindcss/vite": "^4.3.0",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"typescript": "^5.8.3",
|
|
60
60
|
"vite": "^5.4.21",
|
|
61
61
|
"vitest": "^3.2.4",
|
|
62
|
-
"@steerable/pack-sdk": "0.6.
|
|
62
|
+
"@steerable/pack-sdk": "0.6.55"
|
|
63
63
|
},
|
|
64
64
|
"scripts": {
|
|
65
65
|
"gen": "echo agent-shell-web",
|
package/src/brand.ts
CHANGED
|
@@ -28,7 +28,7 @@ export const BRAND_TITLE: string =
|
|
|
28
28
|
* 空字符串表示不显示这句。未 define 时用这句默认。
|
|
29
29
|
*/
|
|
30
30
|
export const BRAND_HOME_HINT: string =
|
|
31
|
-
import.meta.env.VITE_BRAND_HOME_HINT ?? '
|
|
31
|
+
import.meta.env.VITE_BRAND_HOME_HINT ?? 'Type a message to start a new chat.';
|
|
32
32
|
|
|
33
33
|
/** shell 默认 logo(Steerable 舵轮标);包品牌 logo 由包 web 模块注册覆盖。 */
|
|
34
34
|
let brandLogoUrl: string = shellLogoUrl;
|
|
@@ -117,6 +117,7 @@ import {
|
|
|
117
117
|
useAppRelease,
|
|
118
118
|
} from "@/components/SidebarRelease";
|
|
119
119
|
import { CreateProjectModal } from "@/components/CreateProjectModal";
|
|
120
|
+
import { t } from "@/i18n";
|
|
120
121
|
|
|
121
122
|
function placeProjectMenu(anchor: HTMLElement): { top: number; left: number } {
|
|
122
123
|
const box = anchor.getBoundingClientRect();
|
|
@@ -169,7 +170,7 @@ function ProjectOverflowMenu({
|
|
|
169
170
|
<span className="min-w-0 truncate">{project.name}</span>
|
|
170
171
|
</div>
|
|
171
172
|
<div className="mt-0.5 pl-[22px] text-[10px] text-agent-muted-foreground">
|
|
172
|
-
{chatCount}
|
|
173
|
+
{t("Chats: {count}", { count: chatCount })}
|
|
173
174
|
</div>
|
|
174
175
|
<div
|
|
175
176
|
className="mt-0.5 truncate pl-[22px] font-mono text-[10px] text-agent-muted-foreground/70"
|
|
@@ -182,22 +183,22 @@ function ProjectOverflowMenu({
|
|
|
182
183
|
<button
|
|
183
184
|
type="button"
|
|
184
185
|
role="menuitem"
|
|
185
|
-
title="
|
|
186
|
+
title={t("Rename project")}
|
|
186
187
|
onClick={onRename}
|
|
187
188
|
className="flex w-full items-center gap-2 rounded-lg px-2 py-1.5 text-left text-xs text-agent-foreground hover:bg-agent-foreground/5"
|
|
188
189
|
>
|
|
189
190
|
<LuPencil className="h-3.5 w-3.5 shrink-0 text-agent-muted-foreground" />
|
|
190
|
-
|
|
191
|
+
{t("Rename")}
|
|
191
192
|
</button>
|
|
192
193
|
<button
|
|
193
194
|
type="button"
|
|
194
195
|
role="menuitem"
|
|
195
|
-
title="
|
|
196
|
+
title={t("Edit project")}
|
|
196
197
|
onClick={onChangeFolder}
|
|
197
198
|
className="flex w-full items-center gap-2 rounded-lg px-2 py-1.5 text-left text-xs text-agent-foreground hover:bg-agent-foreground/5"
|
|
198
199
|
>
|
|
199
200
|
<LuFolderOpen className="h-3.5 w-3.5 shrink-0 text-agent-muted-foreground" />
|
|
200
|
-
|
|
201
|
+
{t("Edit project")}
|
|
201
202
|
</button>
|
|
202
203
|
{onReveal && (
|
|
203
204
|
<button
|
|
@@ -215,12 +216,12 @@ function ProjectOverflowMenu({
|
|
|
215
216
|
<button
|
|
216
217
|
type="button"
|
|
217
218
|
role="menuitem"
|
|
218
|
-
title="
|
|
219
|
+
title={t("Delete project (its chats stay as chats without a project)")}
|
|
219
220
|
onClick={onDelete}
|
|
220
221
|
className="flex w-full items-center gap-2 rounded-lg px-2 py-1.5 text-left text-xs text-agent-foreground hover:bg-agent-destructive/10"
|
|
221
222
|
>
|
|
222
223
|
<LuTrash2 className="h-3.5 w-3.5 shrink-0" />
|
|
223
|
-
|
|
224
|
+
{t("Delete project")}
|
|
224
225
|
</button>
|
|
225
226
|
</div>
|
|
226
227
|
</>,
|
|
@@ -644,7 +645,7 @@ export function AgentSidebar({
|
|
|
644
645
|
const { displayTitle, isAutomation } = parseChatTitle(chat.title);
|
|
645
646
|
return {
|
|
646
647
|
id: chat.id,
|
|
647
|
-
title: displayTitle || "
|
|
648
|
+
title: displayTitle || t("New conversation"),
|
|
648
649
|
isAutomation,
|
|
649
650
|
isPinned: chat.isPinned,
|
|
650
651
|
sortDate,
|
|
@@ -710,7 +711,7 @@ export function AgentSidebar({
|
|
|
710
711
|
if (!map.has(label)) {
|
|
711
712
|
map.set(label, {
|
|
712
713
|
label,
|
|
713
|
-
priority: getDateGroupPriority(
|
|
714
|
+
priority: getDateGroupPriority(chat.sortDate),
|
|
714
715
|
items: [],
|
|
715
716
|
});
|
|
716
717
|
}
|
|
@@ -746,7 +747,7 @@ export function AgentSidebar({
|
|
|
746
747
|
await setChatPinned(id, nextPinned);
|
|
747
748
|
await refreshChats();
|
|
748
749
|
} catch (err) {
|
|
749
|
-
console.error("
|
|
750
|
+
console.error("Failed to toggle chat pin:", err);
|
|
750
751
|
} finally {
|
|
751
752
|
setPinningChatId(null);
|
|
752
753
|
}
|
|
@@ -787,22 +788,24 @@ export function AgentSidebar({
|
|
|
787
788
|
: "text-agent-muted-foreground hover:bg-agent-foreground/5 hover:text-agent-foreground",
|
|
788
789
|
].join(" ")}
|
|
789
790
|
title={
|
|
790
|
-
chat.isAutomation
|
|
791
|
+
chat.isAutomation
|
|
792
|
+
? t("[Triggered by automation] {title}", { title: chat.title })
|
|
793
|
+
: chat.title
|
|
791
794
|
}
|
|
792
795
|
>
|
|
793
796
|
{needsUserInput ? (
|
|
794
797
|
<span
|
|
795
798
|
className="flex shrink-0 items-center justify-center text-amber-500 dark:text-amber-400"
|
|
796
|
-
title="
|
|
797
|
-
aria-label="
|
|
799
|
+
title={t("Has a question that needs your input")}
|
|
800
|
+
aria-label={t("Waiting for user input")}
|
|
798
801
|
>
|
|
799
802
|
<LuCircleHelp className="h-3.5 w-3.5 animate-pulse" />
|
|
800
803
|
</span>
|
|
801
804
|
) : isStreaming ? (
|
|
802
805
|
<span
|
|
803
806
|
className="flex shrink-0 items-center justify-center text-agent-foreground/70"
|
|
804
|
-
title="
|
|
805
|
-
aria-label="
|
|
807
|
+
title={t("Conversation in progress")}
|
|
808
|
+
aria-label={t("Generating")}
|
|
806
809
|
>
|
|
807
810
|
<LuLoaderCircle className="h-3.5 w-3.5 animate-spin" />
|
|
808
811
|
</span>
|
|
@@ -810,7 +813,7 @@ export function AgentSidebar({
|
|
|
810
813
|
{chat.isAutomation && (
|
|
811
814
|
<LuCloudCog
|
|
812
815
|
className="h-3 w-3 shrink-0 text-agent-muted-foreground"
|
|
813
|
-
aria-label="
|
|
816
|
+
aria-label={t("Triggered by automation")}
|
|
814
817
|
/>
|
|
815
818
|
)}
|
|
816
819
|
<span className="min-w-0 truncate leading-none">{chat.title}</span>
|
|
@@ -832,8 +835,8 @@ export function AgentSidebar({
|
|
|
832
835
|
}}
|
|
833
836
|
disabled={isPinning}
|
|
834
837
|
className="flex h-6 w-6 items-center justify-center rounded-full text-agent-foreground/70 transition-all duration-200 hover:bg-agent-foreground/10 hover:text-agent-foreground disabled:cursor-not-allowed"
|
|
835
|
-
title={chat.isPinned ? "
|
|
836
|
-
aria-label={chat.isPinned ? "
|
|
838
|
+
title={chat.isPinned ? t("Unpin") : t("Pin chat")}
|
|
839
|
+
aria-label={chat.isPinned ? t("Unpin") : t("Pin chat")}
|
|
837
840
|
data-testid="sidebar-chat-pin"
|
|
838
841
|
>
|
|
839
842
|
{chat.isPinned ? (
|
|
@@ -850,8 +853,8 @@ export function AgentSidebar({
|
|
|
850
853
|
}}
|
|
851
854
|
disabled={isDeleting}
|
|
852
855
|
className="flex h-6 w-6 items-center justify-center rounded-full text-agent-foreground/70 transition-all duration-200 hover:bg-agent-foreground/10 hover:text-agent-destructive disabled:cursor-not-allowed disabled:opacity-100"
|
|
853
|
-
title="
|
|
854
|
-
aria-label="
|
|
856
|
+
title={t("Delete chat")}
|
|
857
|
+
aria-label={t("Delete chat")}
|
|
855
858
|
data-testid="sidebar-chat-delete"
|
|
856
859
|
>
|
|
857
860
|
<LuTrash2
|
|
@@ -917,8 +920,8 @@ export function AgentSidebar({
|
|
|
917
920
|
type="button"
|
|
918
921
|
onClick={onCollapse}
|
|
919
922
|
className="flex h-7 w-7 items-center justify-center rounded-full text-agent-muted-foreground transition-colors duration-200 hover:bg-agent-foreground/5 hover:text-agent-foreground"
|
|
920
|
-
title="
|
|
921
|
-
aria-label="
|
|
923
|
+
title={t("Collapse sidebar")}
|
|
924
|
+
aria-label={t("Collapse sidebar")}
|
|
922
925
|
>
|
|
923
926
|
<LuPanelLeftClose className="h-3.5 w-3.5" />
|
|
924
927
|
</button>
|
|
@@ -938,11 +941,11 @@ export function AgentSidebar({
|
|
|
938
941
|
? "bg-agent-foreground/10 font-medium text-agent-foreground"
|
|
939
942
|
: "text-agent-muted-foreground hover:bg-agent-foreground/5 hover:text-agent-foreground",
|
|
940
943
|
].join(" ")}
|
|
941
|
-
title="
|
|
944
|
+
title={t("Start a new chat")}
|
|
942
945
|
data-testid="sidebar-new-chat"
|
|
943
946
|
>
|
|
944
947
|
<LuSquarePen className="h-3.5 w-3.5" />
|
|
945
|
-
<span
|
|
948
|
+
<span>{t("New chat")}</span>
|
|
946
949
|
</button>
|
|
947
950
|
{pluginsAvailable && (
|
|
948
951
|
<button
|
|
@@ -954,11 +957,11 @@ export function AgentSidebar({
|
|
|
954
957
|
? "bg-agent-foreground/10 font-medium text-agent-foreground"
|
|
955
958
|
: "text-agent-muted-foreground hover:bg-agent-foreground/5 hover:text-agent-foreground",
|
|
956
959
|
].join(" ")}
|
|
957
|
-
title="
|
|
960
|
+
title={t("Plugins")}
|
|
958
961
|
data-testid="sidebar-plugins"
|
|
959
962
|
>
|
|
960
963
|
<LuBlocks className="h-3.5 w-3.5" />
|
|
961
|
-
<span
|
|
964
|
+
<span>{t("Plugins")}</span>
|
|
962
965
|
</button>
|
|
963
966
|
)}
|
|
964
967
|
</div>
|
|
@@ -970,7 +973,7 @@ export function AgentSidebar({
|
|
|
970
973
|
{isChatLoading && chats.length === 0 && projects.length === 0 ? (
|
|
971
974
|
<div className="flex items-center justify-center py-4 text-xs text-agent-muted-foreground">
|
|
972
975
|
<LuLoaderCircle className="mr-1.5 h-3 w-3 animate-spin" />
|
|
973
|
-
|
|
976
|
+
{t("Loading...")}
|
|
974
977
|
</div>
|
|
975
978
|
) : (
|
|
976
979
|
<>
|
|
@@ -988,7 +991,7 @@ export function AgentSidebar({
|
|
|
988
991
|
}}
|
|
989
992
|
className="flex flex-1 min-w-0 items-center gap-1 text-left font-medium text-agent-muted-foreground hover:text-agent-foreground"
|
|
990
993
|
>
|
|
991
|
-
<span
|
|
994
|
+
<span>{t("Pinned")}</span>
|
|
992
995
|
{isPinnedExpanded ? (
|
|
993
996
|
<LuChevronDown className="h-3 w-3 shrink-0 opacity-70" />
|
|
994
997
|
) : (
|
|
@@ -1018,7 +1021,7 @@ export function AgentSidebar({
|
|
|
1018
1021
|
}}
|
|
1019
1022
|
className="flex flex-1 min-w-0 items-center gap-1 text-left font-medium text-agent-muted-foreground hover:text-agent-foreground"
|
|
1020
1023
|
>
|
|
1021
|
-
<span
|
|
1024
|
+
<span>{t("Projects")}</span>
|
|
1022
1025
|
{isProjectsExpanded ? (
|
|
1023
1026
|
<LuChevronDown className="h-3 w-3 shrink-0 opacity-70" />
|
|
1024
1027
|
) : (
|
|
@@ -1033,8 +1036,8 @@ export function AgentSidebar({
|
|
|
1033
1036
|
setCreateProjectOpen(true);
|
|
1034
1037
|
}}
|
|
1035
1038
|
className="flex h-5 w-5 items-center justify-center rounded-full text-agent-muted-foreground opacity-0 transition-opacity duration-200 hover:bg-agent-foreground/10 hover:text-agent-foreground group-hover/section:opacity-100 focus:opacity-100"
|
|
1036
|
-
title="
|
|
1037
|
-
aria-label="
|
|
1039
|
+
title={t("New project")}
|
|
1040
|
+
aria-label={t("New project")}
|
|
1038
1041
|
>
|
|
1039
1042
|
<LuFolderPlus className="h-3.5 w-3.5" />
|
|
1040
1043
|
</button>
|
|
@@ -1044,7 +1047,7 @@ export function AgentSidebar({
|
|
|
1044
1047
|
<div className="space-y-0.5">
|
|
1045
1048
|
{projectGroups.length === 0 ? (
|
|
1046
1049
|
<div className="px-2.5 py-1 text-[11px] text-agent-muted-foreground/60">
|
|
1047
|
-
|
|
1050
|
+
{t("No projects")}
|
|
1048
1051
|
</div>
|
|
1049
1052
|
) : (
|
|
1050
1053
|
projectGroups.map(({ project, items }) => (
|
|
@@ -1103,13 +1106,13 @@ export function AgentSidebar({
|
|
|
1103
1106
|
type="button"
|
|
1104
1107
|
onClick={() => handleOpenNewChat(project.id)}
|
|
1105
1108
|
className="flex h-5 w-5 items-center justify-center rounded-full text-agent-muted-foreground hover:bg-agent-foreground/10 hover:text-agent-foreground"
|
|
1106
|
-
title="
|
|
1109
|
+
title={t("New chat in this project")}
|
|
1107
1110
|
>
|
|
1108
1111
|
<LuSquarePen className="h-3 w-3" />
|
|
1109
1112
|
</button>
|
|
1110
1113
|
<button
|
|
1111
1114
|
type="button"
|
|
1112
|
-
aria-label="
|
|
1115
|
+
aria-label={t("Project menu")}
|
|
1113
1116
|
aria-expanded={projectMenu?.id === project.id}
|
|
1114
1117
|
onClick={(event) => {
|
|
1115
1118
|
const button = event.currentTarget;
|
|
@@ -1120,7 +1123,7 @@ export function AgentSidebar({
|
|
|
1120
1123
|
);
|
|
1121
1124
|
}}
|
|
1122
1125
|
className="flex h-5 w-5 items-center justify-center rounded-full text-agent-muted-foreground hover:bg-agent-foreground/10 hover:text-agent-foreground"
|
|
1123
|
-
title="
|
|
1126
|
+
title={t("Project menu")}
|
|
1124
1127
|
>
|
|
1125
1128
|
<LuEllipsis className="h-3 w-3" />
|
|
1126
1129
|
</button>
|
|
@@ -1132,7 +1135,7 @@ export function AgentSidebar({
|
|
|
1132
1135
|
<div className="space-y-0.5 pl-2">
|
|
1133
1136
|
{items.length === 0 ? (
|
|
1134
1137
|
<div className="px-2.5 py-0.5 text-[11px] text-agent-muted-foreground/60">
|
|
1135
|
-
|
|
1138
|
+
{t("No chats yet. Hover the project name and click + to start one.")}
|
|
1136
1139
|
</div>
|
|
1137
1140
|
) : (
|
|
1138
1141
|
items.map((chat) => renderChatRow(chat))
|
|
@@ -1160,7 +1163,7 @@ export function AgentSidebar({
|
|
|
1160
1163
|
}}
|
|
1161
1164
|
className="flex flex-1 min-w-0 items-center gap-1 text-left font-medium text-agent-muted-foreground hover:text-agent-foreground"
|
|
1162
1165
|
>
|
|
1163
|
-
<span
|
|
1166
|
+
<span>{t("Recent")}</span>
|
|
1164
1167
|
{isRecentsExpanded ? (
|
|
1165
1168
|
<LuChevronDown className="h-3 w-3 shrink-0 opacity-70" />
|
|
1166
1169
|
) : (
|
|
@@ -1175,8 +1178,8 @@ export function AgentSidebar({
|
|
|
1175
1178
|
handleOpenNewChat();
|
|
1176
1179
|
}}
|
|
1177
1180
|
className="flex h-5 w-5 items-center justify-center rounded-full text-agent-muted-foreground hover:bg-agent-foreground/10 hover:text-agent-foreground"
|
|
1178
|
-
title="
|
|
1179
|
-
aria-label="
|
|
1181
|
+
title={t("New chat")}
|
|
1182
|
+
aria-label={t("New chat")}
|
|
1180
1183
|
>
|
|
1181
1184
|
<LuSquarePen className="h-3 w-3" />
|
|
1182
1185
|
</button>
|
|
@@ -1188,11 +1191,11 @@ export function AgentSidebar({
|
|
|
1188
1191
|
chats.length === 0 && projects.length === 0 ? (
|
|
1189
1192
|
<div className="flex flex-col items-center gap-1.5 py-4 text-xs text-agent-muted-foreground">
|
|
1190
1193
|
<LuMessageSquare className="h-4 w-4 text-agent-muted-foreground/60" />
|
|
1191
|
-
|
|
1194
|
+
{t("No chats yet")}
|
|
1192
1195
|
</div>
|
|
1193
1196
|
) : (
|
|
1194
1197
|
<div className="px-2.5 py-1 text-[11px] text-agent-muted-foreground/60">
|
|
1195
|
-
|
|
1198
|
+
{t("No chats yet")}
|
|
1196
1199
|
</div>
|
|
1197
1200
|
)
|
|
1198
1201
|
) : (
|
|
@@ -1217,12 +1220,12 @@ export function AgentSidebar({
|
|
|
1217
1220
|
{isLoadingMoreChats ? (
|
|
1218
1221
|
<span className="inline-flex items-center gap-1">
|
|
1219
1222
|
<LuLoaderCircle className="h-3 w-3 animate-spin" />
|
|
1220
|
-
|
|
1223
|
+
{t("Loading more...")}
|
|
1221
1224
|
</span>
|
|
1222
1225
|
) : hasMoreChats ? (
|
|
1223
|
-
"
|
|
1226
|
+
t("Scroll down to load more")
|
|
1224
1227
|
) : (
|
|
1225
|
-
|
|
1228
|
+
t("Total chats: {count}", { count: normalizedChats.length })
|
|
1226
1229
|
)}
|
|
1227
1230
|
</div>
|
|
1228
1231
|
)}
|
|
@@ -1250,11 +1253,11 @@ export function AgentSidebar({
|
|
|
1250
1253
|
? "bg-agent-foreground/10 font-medium text-agent-foreground"
|
|
1251
1254
|
: "text-agent-muted-foreground hover:bg-agent-foreground/5 hover:text-agent-foreground",
|
|
1252
1255
|
].join(" ")}
|
|
1253
|
-
title="
|
|
1256
|
+
title={t("Settings")}
|
|
1254
1257
|
data-testid="sidebar-llm-settings"
|
|
1255
1258
|
>
|
|
1256
1259
|
<LuSettings className="h-3.5 w-3.5" />
|
|
1257
|
-
<span
|
|
1260
|
+
<span>{t("Settings")}</span>
|
|
1258
1261
|
<SidebarVersionLabel release={release} />
|
|
1259
1262
|
</button>
|
|
1260
1263
|
)}
|
|
@@ -1277,7 +1280,7 @@ export function AgentSidebar({
|
|
|
1277
1280
|
project={project}
|
|
1278
1281
|
chatCount={chatCount}
|
|
1279
1282
|
anchor={projectMenu.anchor}
|
|
1280
|
-
revealLabel={isMac ? "
|
|
1283
|
+
revealLabel={isMac ? t("Show in Finder") : t("Show in file manager")}
|
|
1281
1284
|
onClose={closeProjectMenu}
|
|
1282
1285
|
onRename={() => {
|
|
1283
1286
|
setProjectMenu(null);
|
|
@@ -1306,10 +1309,12 @@ export function AgentSidebar({
|
|
|
1306
1309
|
|
|
1307
1310
|
<ConfirmDialog
|
|
1308
1311
|
open={pendingDeleteChat !== null}
|
|
1309
|
-
title="
|
|
1312
|
+
title={t("Delete chat")}
|
|
1310
1313
|
description={
|
|
1311
1314
|
pendingDeleteChat
|
|
1312
|
-
?
|
|
1315
|
+
? t("Delete \"{title}\"? This cannot be undone.", {
|
|
1316
|
+
title: pendingDeleteChat.title,
|
|
1317
|
+
})
|
|
1313
1318
|
: ""
|
|
1314
1319
|
}
|
|
1315
1320
|
pending={
|
|
@@ -1324,13 +1329,16 @@ export function AgentSidebar({
|
|
|
1324
1329
|
/>
|
|
1325
1330
|
<ConfirmDialog
|
|
1326
1331
|
open={pendingDeleteProject !== null}
|
|
1327
|
-
title="
|
|
1332
|
+
title={t("Delete project")}
|
|
1328
1333
|
description={
|
|
1329
1334
|
pendingDeleteProject
|
|
1330
|
-
?
|
|
1335
|
+
? t(
|
|
1336
|
+
"Delete project \"{name}\"? Its chats will stay as chats without a project.",
|
|
1337
|
+
{ name: pendingDeleteProject.name },
|
|
1338
|
+
)
|
|
1331
1339
|
: ""
|
|
1332
1340
|
}
|
|
1333
|
-
confirmLabel="
|
|
1341
|
+
confirmLabel={t("Delete project")}
|
|
1334
1342
|
pending={
|
|
1335
1343
|
pendingDeleteProject !== null &&
|
|
1336
1344
|
deletingProjectId === pendingDeleteProject.id
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
saveJsonFile,
|
|
20
20
|
} from '@/lib/portable';
|
|
21
21
|
import { hostToolChrome } from '@/lib/host-tools';
|
|
22
|
+
import { t } from '@/i18n';
|
|
22
23
|
|
|
23
24
|
interface ChatHeaderProps {
|
|
24
25
|
chat: LocalChat | null;
|
|
@@ -147,8 +148,8 @@ export function ChatHeader({
|
|
|
147
148
|
useEffect(() => {
|
|
148
149
|
if (!branchMenuOpen) return;
|
|
149
150
|
const onPointerDown = (e: MouseEvent) => {
|
|
150
|
-
const
|
|
151
|
-
if (buttonRef.current?.contains(
|
|
151
|
+
const target = e.target as Node;
|
|
152
|
+
if (buttonRef.current?.contains(target) || menuRef.current?.contains(target)) return;
|
|
152
153
|
setBranchMenuOpen(false);
|
|
153
154
|
};
|
|
154
155
|
document.addEventListener('mousedown', onPointerDown);
|
|
@@ -166,7 +167,7 @@ export function ChatHeader({
|
|
|
166
167
|
>
|
|
167
168
|
{!branches || branchCount === 0 ? (
|
|
168
169
|
<div className="px-2.5 py-1.5 text-xs text-agent-muted-foreground">
|
|
169
|
-
|
|
170
|
+
{t('No branches yet. After you regenerate a reply, the old version stays here.')}
|
|
170
171
|
</div>
|
|
171
172
|
) : (
|
|
172
173
|
<>
|
|
@@ -201,7 +202,7 @@ export function ChatHeader({
|
|
|
201
202
|
data-action="branch-tree"
|
|
202
203
|
>
|
|
203
204
|
<LuListTree className="h-3 w-3 shrink-0" />
|
|
204
|
-
|
|
205
|
+
{t('View full branch tree')}
|
|
205
206
|
</button>
|
|
206
207
|
</div>
|
|
207
208
|
</>
|
|
@@ -218,7 +219,7 @@ export function ChatHeader({
|
|
|
218
219
|
className="break-words text-xs font-medium text-agent-foreground"
|
|
219
220
|
title={chat?.title}
|
|
220
221
|
>
|
|
221
|
-
{chat?.title ?? '
|
|
222
|
+
{chat?.title ?? t('No chat selected')}
|
|
222
223
|
</span>
|
|
223
224
|
</div>
|
|
224
225
|
{chat && isPortableEnabled() && (
|
|
@@ -229,7 +230,7 @@ export function ChatHeader({
|
|
|
229
230
|
setExporting(true);
|
|
230
231
|
setExportError(null);
|
|
231
232
|
void fetchChatDocument(chat.id)
|
|
232
|
-
.then((doc) => saveJsonFile(safeDownloadName(chat.title, '
|
|
233
|
+
.then((doc) => saveJsonFile(safeDownloadName(chat.title, t('Chat')), doc))
|
|
233
234
|
.then((saved) => {
|
|
234
235
|
if (!saved) return;
|
|
235
236
|
})
|
|
@@ -237,12 +238,12 @@ export function ChatHeader({
|
|
|
237
238
|
.finally(() => setExporting(false));
|
|
238
239
|
}}
|
|
239
240
|
className="flex h-7 shrink-0 items-center gap-1 rounded-full px-2 text-xs text-agent-muted-foreground transition-colors hover:bg-agent-foreground/5 hover:text-agent-foreground disabled:opacity-60"
|
|
240
|
-
title={exportError ?? '
|
|
241
|
-
aria-label=
|
|
241
|
+
title={exportError ?? t('Export this chat')}
|
|
242
|
+
aria-label={t('Export this chat')}
|
|
242
243
|
data-testid="header-export-chat"
|
|
243
244
|
>
|
|
244
245
|
<LuDownload className="h-3.5 w-3.5" />
|
|
245
|
-
{exportError ? '
|
|
246
|
+
{exportError ? t('Export failed') : exporting ? t('Exporting') : t('Export')}
|
|
246
247
|
</button>
|
|
247
248
|
)}
|
|
248
249
|
{chat && onBranchSwitched && (
|
|
@@ -252,8 +253,8 @@ export function ChatHeader({
|
|
|
252
253
|
type="button"
|
|
253
254
|
onClick={() => void openBranchMenu()}
|
|
254
255
|
className="flex h-7 items-center gap-1 rounded-full px-2 text-xs text-agent-muted-foreground transition-colors hover:bg-agent-foreground/5 hover:text-agent-foreground"
|
|
255
|
-
title=
|
|
256
|
-
aria-label=
|
|
256
|
+
title={t('Chat branches (forks created by regenerating)')}
|
|
257
|
+
aria-label={t('Chat branches')}
|
|
257
258
|
data-action="branches"
|
|
258
259
|
>
|
|
259
260
|
<LuGitBranch className="h-3.5 w-3.5" />
|
|
@@ -282,7 +283,11 @@ export function ChatHeader({
|
|
|
282
283
|
: 'w-7 justify-center text-agent-muted-foreground hover:bg-agent-foreground/5 hover:text-agent-foreground'
|
|
283
284
|
}`}
|
|
284
285
|
title={taskButtonTitle(taskBadge, taskShortcut)}
|
|
285
|
-
aria-label={
|
|
286
|
+
aria-label={
|
|
287
|
+
taskBadge
|
|
288
|
+
? t('Background tasks: {title}', { title: taskBadge.title })
|
|
289
|
+
: t('Background tasks')
|
|
290
|
+
}
|
|
286
291
|
data-action="tasks"
|
|
287
292
|
data-task-state={taskBadge?.state}
|
|
288
293
|
data-task-shortcut={taskShortcut?.kind}
|
|
@@ -308,7 +313,11 @@ export function ChatHeader({
|
|
|
308
313
|
? 'bg-agent-foreground/10 text-agent-foreground'
|
|
309
314
|
: 'text-agent-muted-foreground hover:bg-agent-foreground/5 hover:text-agent-foreground'
|
|
310
315
|
}`}
|
|
311
|
-
title={
|
|
316
|
+
title={
|
|
317
|
+
open
|
|
318
|
+
? t('Close {name}', { name: slot.title })
|
|
319
|
+
: t('Open {name}', { name: slot.title })
|
|
320
|
+
}
|
|
312
321
|
aria-label={slot.title}
|
|
313
322
|
aria-pressed={open}
|
|
314
323
|
data-action={`chat-slot-${slot.slotId}`}
|
|
@@ -328,8 +337,12 @@ export function ChatHeader({
|
|
|
328
337
|
? 'bg-agent-foreground/10 text-agent-foreground'
|
|
329
338
|
: 'text-agent-muted-foreground hover:bg-agent-foreground/5 hover:text-agent-foreground'
|
|
330
339
|
}`}
|
|
331
|
-
title={
|
|
332
|
-
|
|
340
|
+
title={
|
|
341
|
+
rightPanel === 'terminal'
|
|
342
|
+
? t('Close terminal panel ({shortcut})', { shortcut: isMac ? '⌘T' : 'Ctrl+T' })
|
|
343
|
+
: t('Open terminal panel ({shortcut})', { shortcut: isMac ? '⌘T' : 'Ctrl+T' })
|
|
344
|
+
}
|
|
345
|
+
aria-label={t('Terminal')}
|
|
333
346
|
aria-pressed={rightPanel === 'terminal'}
|
|
334
347
|
data-action="terminal"
|
|
335
348
|
data-testid="header-terminal"
|
|
@@ -386,14 +399,16 @@ function describeTaskBadge(summary: ChatTaskSummary): TaskBadge | null {
|
|
|
386
399
|
if (summary.total === 0) return null;
|
|
387
400
|
|
|
388
401
|
const detail = [
|
|
389
|
-
summary.running > 0 ?
|
|
390
|
-
summary.blocked > 0 ?
|
|
391
|
-
summary.needsReview > 0 ?
|
|
392
|
-
summary.failed > 0 ?
|
|
402
|
+
summary.running > 0 ? t('{count} in progress', { count: summary.running }) : null,
|
|
403
|
+
summary.blocked > 0 ? t('{count} waiting on dependencies', { count: summary.blocked }) : null,
|
|
404
|
+
summary.needsReview > 0 ? t('{count} awaiting merge', { count: summary.needsReview }) : null,
|
|
405
|
+
summary.failed > 0 ? t('{count} failed', { count: summary.failed }) : null,
|
|
393
406
|
]
|
|
394
407
|
.filter((part) => part !== null)
|
|
395
|
-
.join('
|
|
396
|
-
const title =
|
|
408
|
+
.join(t(', '));
|
|
409
|
+
const title = detail
|
|
410
|
+
? t('Background tasks: {total} ({detail})', { total: summary.total, detail })
|
|
411
|
+
: t('Background tasks: {total}', { total: summary.total });
|
|
397
412
|
|
|
398
413
|
// 等依赖的任务也算在推进中:依赖跑完它会自动点火,用户不用做任何事。
|
|
399
414
|
const active = summary.running + summary.blocked;
|
|
@@ -459,11 +474,12 @@ function taskButtonTitle(
|
|
|
459
474
|
badge: TaskBadge | null,
|
|
460
475
|
shortcut: TaskShortcut | null,
|
|
461
476
|
): string {
|
|
462
|
-
const base =
|
|
477
|
+
const base =
|
|
478
|
+
badge?.title ?? t('Background tasks (run across turns; view results and merge worktrees)');
|
|
463
479
|
if (!shortcut) return base;
|
|
464
480
|
return shortcut.kind === 'process'
|
|
465
|
-
?
|
|
466
|
-
:
|
|
481
|
+
? t('{base} · Click to view the reasoning', { base })
|
|
482
|
+
: t('{base} · Click to handle the worktree', { base });
|
|
467
483
|
}
|
|
468
484
|
|
|
469
485
|
/** Pin the branch menu to the trigger, clamped inside the chat panel so
|
|
@@ -505,7 +521,7 @@ function BranchRow({
|
|
|
505
521
|
data-branch-row
|
|
506
522
|
data-active={active || undefined}
|
|
507
523
|
>
|
|
508
|
-
<span className="min-w-0 flex-1 truncate">{label || '(
|
|
524
|
+
<span className="min-w-0 flex-1 truncate">{label || t('(empty branch)')}</span>
|
|
509
525
|
{active && (
|
|
510
526
|
<LuCheck className="h-3 w-3 shrink-0 text-emerald-600 dark:text-emerald-400" />
|
|
511
527
|
)}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { useEffect, useId } from 'react';
|
|
5
5
|
import { createPortal } from 'react-dom';
|
|
6
|
+
import { t } from '@/i18n';
|
|
6
7
|
|
|
7
8
|
export interface ConfirmDialogProps {
|
|
8
9
|
open: boolean;
|
|
@@ -19,7 +20,7 @@ export function ConfirmDialog({
|
|
|
19
20
|
open,
|
|
20
21
|
title,
|
|
21
22
|
description,
|
|
22
|
-
confirmLabel = '
|
|
23
|
+
confirmLabel = t('Delete'),
|
|
23
24
|
pending = false,
|
|
24
25
|
onCancel,
|
|
25
26
|
onConfirm,
|
|
@@ -71,7 +72,7 @@ export function ConfirmDialog({
|
|
|
71
72
|
className="h-9 rounded-full px-4 text-sm text-agent-muted-foreground transition-colors hover:bg-agent-muted hover:text-agent-foreground disabled:opacity-40"
|
|
72
73
|
data-testid={`${testId}-cancel`}
|
|
73
74
|
>
|
|
74
|
-
|
|
75
|
+
{t('Cancel')}
|
|
75
76
|
</button>
|
|
76
77
|
<button
|
|
77
78
|
type="button"
|
|
@@ -80,7 +81,7 @@ export function ConfirmDialog({
|
|
|
80
81
|
className="h-9 rounded-full bg-agent-destructive px-4 text-sm font-medium text-white transition-opacity hover:opacity-90 disabled:opacity-40"
|
|
81
82
|
data-testid={`${testId}-confirm`}
|
|
82
83
|
>
|
|
83
|
-
{pending ? '
|
|
84
|
+
{pending ? t('Deleting…') : confirmLabel}
|
|
84
85
|
</button>
|
|
85
86
|
</div>
|
|
86
87
|
</div>
|