@steerable/agent-shell-web 0.6.53 → 0.6.56
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/README.md +3 -4
- package/package.json +4 -4
- package/src/AppShell.tsx +3 -3
- package/src/brand.ts +1 -1
- package/src/components/AgentSidebar.tsx +64 -56
- package/src/components/ChatHeader.tsx +41 -25
- package/src/components/ConfirmDialog.tsx +4 -3
- package/src/components/CreateProjectModal.tsx +31 -21
- package/src/components/LocalLlmSettingsModal.tsx +4 -3
- package/src/components/SidebarRelease.tsx +6 -5
- package/src/components/TerminalPanel.tsx +6 -5
- package/src/components/TerminalView.tsx +15 -10
- package/src/components/chat/ApprovalModal.tsx +40 -26
- package/src/components/chat/ApprovalPromptProvider.tsx +5 -5
- package/src/components/chat/AskUserPromptProvider.tsx +4 -4
- package/src/components/chat/AskUserQuestionMenu.tsx +19 -14
- package/src/components/chat/AssistantMessage.tsx +18 -13
- package/src/components/chat/ChatInput.tsx +56 -40
- package/src/components/chat/ChatProjectBadge.tsx +27 -22
- 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 +33 -23
- package/src/components/chat/TaskProcessPanel.tsx +16 -15
- 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/chat/useChatTasks.ts +3 -3
- package/src/components/settings/AgentCapabilityFields.tsx +26 -23
- package/src/components/settings/AgentsSettingsPanel.tsx +33 -28
- 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 +58 -43
- package/src/components/settings/LlmSettingsPanel.tsx +107 -86
- package/src/components/settings/McpSettingsPanel.tsx +72 -46
- 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 +26 -23
- package/src/components/settings/SecuritySettingsPanel.tsx +58 -34
- package/src/components/settings/SettingsNavMenu.tsx +3 -2
- package/src/components/settings/SettingsSaveButton.tsx +2 -1
- package/src/components/settings/SkillsSettingsPanel.tsx +43 -38
- package/src/components/settings/TelemetrySettingsPanel.tsx +20 -17
- package/src/components/settings/UsagePanel.tsx +22 -23
- package/src/components/settings/WebSearchSettingsPanel.tsx +28 -19
- 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/useChatsAndAgents.ts +9 -9
- package/src/hooks/useProjects.ts +3 -3
- 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 +13 -12
- package/src/lib/app-release.ts +16 -11
- package/src/lib/attachments.ts +18 -9
- package/src/lib/{browser-dev-electron-mock.ts → browser-dev-host-mock.ts} +42 -42
- package/src/lib/chat-title.ts +3 -2
- package/src/lib/chat-transport.ts +16 -12
- package/src/lib/date-groups.ts +23 -15
- package/src/lib/host-bridge.ts +371 -4
- package/src/lib/host-paste.ts +1 -1
- package/src/lib/http-bridge.ts +12 -9
- package/src/lib/insights.ts +2 -2
- package/src/lib/local-api.ts +5 -5
- package/src/lib/portable.ts +11 -10
- package/src/lib/show-thinking-content.ts +15 -3
- package/src/lib/slash-sources.ts +3 -3
- package/src/lib/tauri-bridge.ts +1 -1
- package/src/main.tsx +27 -4
- package/src/pages/AgentPage.tsx +39 -34
- package/src/pages/ChatPanelPreviewPage.tsx +46 -44
- package/src/pages/SettingsPage.tsx +51 -45
- package/src/vite-env.d.ts +3 -0
- package/vite.base.js +6 -2
- package/src/lib/electron-bridge.ts +0 -390
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
|
|
2
|
+
HostBridge,
|
|
3
3
|
LocalBackendRequestInput,
|
|
4
4
|
LocalBackendStreamEvent,
|
|
5
|
-
} from './
|
|
5
|
+
} from './host-bridge';
|
|
6
6
|
import type {
|
|
7
7
|
ChatAgentWriteInput,
|
|
8
8
|
LocalChat,
|
|
@@ -97,33 +97,33 @@ function createInitialState(): BrowserMockState {
|
|
|
97
97
|
{
|
|
98
98
|
id: 'coach',
|
|
99
99
|
slug: 'coach',
|
|
100
|
-
name: '
|
|
100
|
+
name: 'Action coach',
|
|
101
101
|
icon: null,
|
|
102
102
|
color: '#2563eb',
|
|
103
|
-
description: '
|
|
104
|
-
rolePrompt: '
|
|
103
|
+
description: 'Breaks a goal into an executable plan. Useful for debugging the standard chat path.',
|
|
104
|
+
rolePrompt: 'You are an action coach.',
|
|
105
105
|
isBuiltin: true,
|
|
106
106
|
sortOrder: 1,
|
|
107
107
|
},
|
|
108
108
|
{
|
|
109
109
|
id: 'tool-demo',
|
|
110
110
|
slug: 'tool-demo',
|
|
111
|
-
name: '
|
|
111
|
+
name: 'Tool demo',
|
|
112
112
|
icon: null,
|
|
113
113
|
color: '#7c3aed',
|
|
114
|
-
description: '
|
|
115
|
-
rolePrompt: '
|
|
114
|
+
description: 'Simulates local tool calls and execution summaries. Useful for debugging tool cards.',
|
|
115
|
+
rolePrompt: 'You are the tool-demo agent.',
|
|
116
116
|
isBuiltin: true,
|
|
117
117
|
sortOrder: 2,
|
|
118
118
|
},
|
|
119
119
|
{
|
|
120
120
|
id: 'writer',
|
|
121
121
|
slug: 'writer',
|
|
122
|
-
name: '
|
|
122
|
+
name: 'Writing advisor',
|
|
123
123
|
icon: null,
|
|
124
124
|
color: '#16a34a',
|
|
125
|
-
description: '
|
|
126
|
-
rolePrompt: '
|
|
125
|
+
description: 'Longer text replies, for debugging markdown and scrolling.',
|
|
126
|
+
rolePrompt: 'You are a writing advisor.',
|
|
127
127
|
isBuiltin: true,
|
|
128
128
|
sortOrder: 3,
|
|
129
129
|
},
|
|
@@ -132,14 +132,14 @@ function createInitialState(): BrowserMockState {
|
|
|
132
132
|
const chats = [
|
|
133
133
|
createChatRecord({
|
|
134
134
|
id: 'chat-browser-demo',
|
|
135
|
-
title: '
|
|
135
|
+
title: 'Browser UI debug sample',
|
|
136
136
|
agentId: 'coach',
|
|
137
137
|
minutesAgo: 8,
|
|
138
138
|
isPinned: true,
|
|
139
139
|
}),
|
|
140
140
|
createChatRecord({
|
|
141
141
|
id: 'chat-tool-demo',
|
|
142
|
-
title: '
|
|
142
|
+
title: 'Tool-call card preview',
|
|
143
143
|
agentId: 'tool-demo',
|
|
144
144
|
minutesAgo: 64,
|
|
145
145
|
}),
|
|
@@ -155,14 +155,14 @@ function createInitialState(): BrowserMockState {
|
|
|
155
155
|
chatId: 'chat-browser-demo',
|
|
156
156
|
role: 'assistant',
|
|
157
157
|
content:
|
|
158
|
-
'
|
|
158
|
+
'You can debug the chat UI in a normal browser here. This reply comes from the dev mock bridge and does not call a live model.',
|
|
159
159
|
minutesAgo: 7,
|
|
160
160
|
}),
|
|
161
161
|
createMessage({
|
|
162
162
|
id: 'msg-demo-user-1',
|
|
163
163
|
chatId: 'chat-browser-demo',
|
|
164
164
|
role: 'user',
|
|
165
|
-
content: '
|
|
165
|
+
content: 'I want to debug the chat UI in the browser.',
|
|
166
166
|
minutesAgo: 8,
|
|
167
167
|
}),
|
|
168
168
|
],
|
|
@@ -171,7 +171,7 @@ function createInitialState(): BrowserMockState {
|
|
|
171
171
|
id: 'msg-tool-assistant-1',
|
|
172
172
|
chatId: 'chat-tool-demo',
|
|
173
173
|
role: 'assistant',
|
|
174
|
-
content: '
|
|
174
|
+
content: 'I simulated a local command and put the result in a tool card.',
|
|
175
175
|
minutesAgo: 63,
|
|
176
176
|
messageMetadata: JSON.stringify({
|
|
177
177
|
executedActions: [
|
|
@@ -187,7 +187,7 @@ function createInitialState(): BrowserMockState {
|
|
|
187
187
|
id: 'msg-tool-user-1',
|
|
188
188
|
chatId: 'chat-tool-demo',
|
|
189
189
|
role: 'user',
|
|
190
|
-
content: '
|
|
190
|
+
content: 'Show me the recent tool-call records.',
|
|
191
191
|
minutesAgo: 64,
|
|
192
192
|
}),
|
|
193
193
|
],
|
|
@@ -201,9 +201,9 @@ function createInitialState(): BrowserMockState {
|
|
|
201
201
|
maxTotalTokens: 60_000,
|
|
202
202
|
},
|
|
203
203
|
skills: [
|
|
204
|
-
{ name: 'tool-demo', description: '
|
|
205
|
-
{ name: 'local-exec', description: '
|
|
206
|
-
{ name: 'anti-deferred', description: '
|
|
204
|
+
{ name: 'tool-demo', description: 'Simulates query and replay tools.', builtin: true },
|
|
205
|
+
{ name: 'local-exec', description: 'Simulates local command execution.', builtin: true },
|
|
206
|
+
{ name: 'anti-deferred', description: 'Simulates the anti-deferral execution policy.', builtin: true },
|
|
207
207
|
],
|
|
208
208
|
activeStreams: new Map(),
|
|
209
209
|
source: 'fallback',
|
|
@@ -235,8 +235,8 @@ function createStateFromFixture(fixture: BrowserDevFixture): BrowserMockState |
|
|
|
235
235
|
Array.isArray(fixture.skills) && fixture.skills.length > 0
|
|
236
236
|
? fixture.skills
|
|
237
237
|
: [
|
|
238
|
-
{ name: 'tool-demo', description: '
|
|
239
|
-
{ name: 'local-exec', description: '
|
|
238
|
+
{ name: 'tool-demo', description: 'Query and replay tools.', builtin: true },
|
|
239
|
+
{ name: 'local-exec', description: 'Local command execution.', builtin: true },
|
|
240
240
|
],
|
|
241
241
|
activeStreams: new Map(),
|
|
242
242
|
source: 'fixture',
|
|
@@ -318,7 +318,7 @@ async function request<T>(input: LocalBackendRequestInput): Promise<T> {
|
|
|
318
318
|
const agent: LocalChatAgent = {
|
|
319
319
|
id,
|
|
320
320
|
slug: null,
|
|
321
|
-
name: (body.name || '
|
|
321
|
+
name: (body.name || 'New assistant').trim() || 'New assistant',
|
|
322
322
|
icon: null,
|
|
323
323
|
color: body.color ?? '#4f46e5',
|
|
324
324
|
description: body.description ?? null,
|
|
@@ -392,10 +392,10 @@ async function request<T>(input: LocalBackendRequestInput): Promise<T> {
|
|
|
392
392
|
if (method === 'GET' && cleanPath === '/api/v2/chat-agents/tools') {
|
|
393
393
|
return {
|
|
394
394
|
tools: [
|
|
395
|
-
{ name: 'local_exec_shell', description: '
|
|
396
|
-
{ name: 'local_read_file', description: '
|
|
397
|
-
{ name: 'local_write_file', description: '
|
|
398
|
-
{ name: 'web_search', description: '
|
|
395
|
+
{ name: 'local_exec_shell', description: 'Run a local shell command.', category: 'local' },
|
|
396
|
+
{ name: 'local_read_file', description: 'Read a local text file.', category: 'local' },
|
|
397
|
+
{ name: 'local_write_file', description: 'Write a local file.', category: 'local' },
|
|
398
|
+
{ name: 'web_search', description: 'Search the web.', category: 'local' },
|
|
399
399
|
],
|
|
400
400
|
} as T;
|
|
401
401
|
}
|
|
@@ -404,7 +404,7 @@ async function request<T>(input: LocalBackendRequestInput): Promise<T> {
|
|
|
404
404
|
const body = input.body as { path?: string } | undefined;
|
|
405
405
|
const name = body?.path?.split(/[\\/]/).filter(Boolean).pop() || 'browser-skill';
|
|
406
406
|
if (!state.skills.some((skill) => skill.name === name)) {
|
|
407
|
-
state.skills.push({ name, description: '
|
|
407
|
+
state.skills.push({ name, description: 'Mock skill imported in the browser preview.' });
|
|
408
408
|
}
|
|
409
409
|
return { success: true, name } as T;
|
|
410
410
|
}
|
|
@@ -444,7 +444,7 @@ async function request<T>(input: LocalBackendRequestInput): Promise<T> {
|
|
|
444
444
|
const chatId = `chat-browser-${Date.now()}`;
|
|
445
445
|
const chat = createChatRecord({
|
|
446
446
|
id: chatId,
|
|
447
|
-
title: '
|
|
447
|
+
title: 'New chat',
|
|
448
448
|
agentId,
|
|
449
449
|
minutesAgo: 0,
|
|
450
450
|
});
|
|
@@ -515,7 +515,7 @@ async function startStream(
|
|
|
515
515
|
}
|
|
516
516
|
|
|
517
517
|
const body = input.body as { message?: string } | undefined;
|
|
518
|
-
const message = body?.message?.trim() || '
|
|
518
|
+
const message = body?.message?.trim() || 'Please demo a browser mock reply.';
|
|
519
519
|
const streamId = `browser-stream-${Date.now()}`;
|
|
520
520
|
state.activeStreams.set(streamId, []);
|
|
521
521
|
|
|
@@ -530,10 +530,10 @@ async function startStream(
|
|
|
530
530
|
state.messagesByChatId[chatId] = [...(state.messagesByChatId[chatId] ?? []), userMessage];
|
|
531
531
|
|
|
532
532
|
const reply =
|
|
533
|
-
|
|
534
|
-
'
|
|
533
|
+
`This is a streaming reply from the browser dev mock. You wrote: "${message}".` +
|
|
534
|
+
' Use it to debug message bubbles, scrolling, the stop button, tool cards, and later status.';
|
|
535
535
|
const chunks = reply.match(/.{1,8}/g) ?? [reply];
|
|
536
|
-
const reasoning = '
|
|
536
|
+
const reasoning = 'Call one mock tool, then interleave the reply with that call.';
|
|
537
537
|
const mockAction = {
|
|
538
538
|
id: 'browser-mock-1',
|
|
539
539
|
tool: 'browser_mock',
|
|
@@ -580,7 +580,7 @@ async function startStream(
|
|
|
580
580
|
{ type: 'text', content: reply },
|
|
581
581
|
],
|
|
582
582
|
durationMs: 1_200,
|
|
583
|
-
suggestedReplies: ['
|
|
583
|
+
suggestedReplies: ['Keep improving this result', 'Try another presentation', 'Tell me the next step'],
|
|
584
584
|
}),
|
|
585
585
|
});
|
|
586
586
|
assistantMessage.createdAt = new Date().toISOString();
|
|
@@ -593,7 +593,7 @@ async function startStream(
|
|
|
593
593
|
pushSse(onEvent, '[DONE]');
|
|
594
594
|
onEvent({ type: 'end', status: 200 });
|
|
595
595
|
state.activeStreams.delete(streamId);
|
|
596
|
-
const suggestions = ['
|
|
596
|
+
const suggestions = ['Keep improving this result', 'Try another presentation', 'Tell me the next step'];
|
|
597
597
|
for (const listener of suggestedReplyListeners) {
|
|
598
598
|
listener({ chatId, messageId: assistantMessage.id, suggestions });
|
|
599
599
|
}
|
|
@@ -608,13 +608,13 @@ function cancelStream(streamId: string) {
|
|
|
608
608
|
state.activeStreams.delete(streamId);
|
|
609
609
|
}
|
|
610
610
|
|
|
611
|
-
export function
|
|
612
|
-
if (typeof window === 'undefined' || window.
|
|
611
|
+
export function installBrowserDevHostMock() {
|
|
612
|
+
if (typeof window === 'undefined' || window.steerableHost) return;
|
|
613
613
|
// DEV 浏览器预览自动安装;官网静态 demo 构建(app-demo 入口)经 demo flag
|
|
614
614
|
// 显式安装。普通 prod 构建两条路都不通,本模块根本不会进产物。
|
|
615
615
|
if (!import.meta.env.DEV && !isDemoMode()) return;
|
|
616
616
|
|
|
617
|
-
window.
|
|
617
|
+
window.steerableHost = {
|
|
618
618
|
runtime: 'local',
|
|
619
619
|
platform: 'win32',
|
|
620
620
|
local: {
|
|
@@ -622,10 +622,10 @@ export function installBrowserDevElectronMock() {
|
|
|
622
622
|
canceled: false,
|
|
623
623
|
filePaths: ['C:\\browser-preview\\mock-skill'],
|
|
624
624
|
}),
|
|
625
|
-
// 浏览器 dev
|
|
625
|
+
// 浏览器 dev 模式没有桌面截图和剪贴板,如实返回失败。
|
|
626
626
|
captureScreenshot: async () => ({
|
|
627
627
|
success: false as const,
|
|
628
|
-
error: '
|
|
628
|
+
error: 'Browser dev mode cannot capture a window screenshot.',
|
|
629
629
|
}),
|
|
630
630
|
},
|
|
631
631
|
localBackend: {
|
|
@@ -644,10 +644,10 @@ export function installBrowserDevElectronMock() {
|
|
|
644
644
|
suggestedReplyListeners.delete(callback);
|
|
645
645
|
};
|
|
646
646
|
},
|
|
647
|
-
} satisfies
|
|
647
|
+
} satisfies HostBridge;
|
|
648
648
|
|
|
649
649
|
console.info(
|
|
650
|
-
'[browser-dev-
|
|
650
|
+
'[browser-dev-host-mock] Installed mock window.steerableHost bridge.',
|
|
651
651
|
state.source === 'fixture'
|
|
652
652
|
? { source: state.fixtureMeta?.source, exportedAt: state.fixtureMeta?.exportedAt }
|
|
653
653
|
: { source: 'built-in fallback' },
|
package/src/lib/chat-title.ts
CHANGED
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
* automation.py:_AUTOMATION_TITLE_PREFIX), so the same parser works for both.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
const
|
|
8
|
+
// 与后端 automation.py 的标题前缀是同一条持久化约定,不能按界面语言改写。
|
|
9
|
+
export const AUTOMATION_TITLE_PREFIX = '[自动化] '; // i18n:allow
|
|
10
|
+
const BARE_BRACKET_PREFIX = '[自动化]'; // i18n:allow
|
|
10
11
|
|
|
11
12
|
export interface ParsedChatTitle {
|
|
12
13
|
displayTitle: string;
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* user types in ChatPanel
|
|
8
8
|
* -> useChatStream.sendUserMessage()
|
|
9
9
|
* -> transport.stream(input, onEvent)
|
|
10
|
-
* -> window.
|
|
10
|
+
* -> window.steerableHost.localBackend.startStream({ POST /api/v2/chats/:id/run })
|
|
11
11
|
* -> main process forwards each raw SSE chunk via webContents.send
|
|
12
12
|
* -> preload re-emits as `{ type: 'data'; chunk: string }`
|
|
13
13
|
* -> we parse the SSE frames with the framework's `SSEParser`, normalise
|
|
@@ -52,7 +52,8 @@ import {
|
|
|
52
52
|
parseSSEData,
|
|
53
53
|
} from '@steerable/agent-ui/state';
|
|
54
54
|
import type { SSEEvent } from '@steerable/agent-protocol';
|
|
55
|
-
import {
|
|
55
|
+
import { getHostBridge } from './host-bridge';
|
|
56
|
+
import { t } from '@/i18n';
|
|
56
57
|
import {
|
|
57
58
|
appendDelta,
|
|
58
59
|
freezeReasoningDurations,
|
|
@@ -213,7 +214,7 @@ class LocalBackendSseAdapter {
|
|
|
213
214
|
// starts a fresh SSE pipe against /api/v2/chats/:id/run.
|
|
214
215
|
// ---------------------------------------------------------------------------
|
|
215
216
|
|
|
216
|
-
export interface
|
|
217
|
+
export interface HostChatTransport extends ChatStreamTransport {
|
|
217
218
|
/**
|
|
218
219
|
* 立即取消当前进行中的流(如果有)。
|
|
219
220
|
*
|
|
@@ -225,7 +226,7 @@ export interface ElectronChatTransport extends ChatStreamTransport {
|
|
|
225
226
|
cancelActive: () => void;
|
|
226
227
|
}
|
|
227
228
|
|
|
228
|
-
export function
|
|
229
|
+
export function createHostChatTransport(chatId: string): HostChatTransport {
|
|
229
230
|
// 当前 in-flight 流的 id。startStream resolve 后写入,流结束(end/error)
|
|
230
231
|
// 或被取消后清空。同一 transport 一次只会有一条流(useChatStream 在
|
|
231
232
|
// isStreaming 时拒绝重入)。
|
|
@@ -233,7 +234,7 @@ export function createElectronChatTransport(chatId: string): ElectronChatTranspo
|
|
|
233
234
|
|
|
234
235
|
return {
|
|
235
236
|
cancelActive: () => {
|
|
236
|
-
const bridge =
|
|
237
|
+
const bridge = getHostBridge();
|
|
237
238
|
if (active.streamId) {
|
|
238
239
|
bridge?.localBackend.cancelStream(active.streamId);
|
|
239
240
|
active.streamId = null;
|
|
@@ -254,15 +255,15 @@ export function createElectronChatTransport(chatId: string): ElectronChatTranspo
|
|
|
254
255
|
// 轮中转向:仅在 CoreLoop 路径(STEERABLE_USE_CORELOOP=1)且回合仍
|
|
255
256
|
// 运行时生效;否则主进程软失败,useChatStream 保留草稿。
|
|
256
257
|
steer: async (content: string) => {
|
|
257
|
-
const bridge =
|
|
258
|
+
const bridge = getHostBridge();
|
|
258
259
|
if (!bridge?.localBackend.steerChat) return false;
|
|
259
260
|
return await bridge.localBackend.steerChat(chatId, content);
|
|
260
261
|
},
|
|
261
262
|
stream: async (input: ChatStreamSendInput, onEvent) => {
|
|
262
|
-
const bridge =
|
|
263
|
+
const bridge = getHostBridge();
|
|
263
264
|
if (!bridge) {
|
|
264
265
|
throw new Error(
|
|
265
|
-
'
|
|
266
|
+
'Host bridge unavailable — chat transport requires the desktop shell.',
|
|
266
267
|
);
|
|
267
268
|
}
|
|
268
269
|
|
|
@@ -347,10 +348,10 @@ export async function regenerateChatMessage(
|
|
|
347
348
|
chatId: string,
|
|
348
349
|
messageId: string,
|
|
349
350
|
): Promise<void> {
|
|
350
|
-
const bridge =
|
|
351
|
+
const bridge = getHostBridge();
|
|
351
352
|
if (!bridge) {
|
|
352
353
|
throw new Error(
|
|
353
|
-
'
|
|
354
|
+
'Host bridge unavailable — regenerate requires the desktop shell.',
|
|
354
355
|
);
|
|
355
356
|
}
|
|
356
357
|
await new Promise<void>((resolve, reject) => {
|
|
@@ -372,7 +373,7 @@ export async function regenerateChatMessage(
|
|
|
372
373
|
if (frame.event !== 'error' || !frame.data) return;
|
|
373
374
|
const parsed = parseSSEData(frame.data);
|
|
374
375
|
const message = (parsed as { message?: unknown } | null)?.message;
|
|
375
|
-
refusal = typeof message === 'string' && message ? message : '
|
|
376
|
+
refusal = typeof message === 'string' && message ? message : t('Regeneration failed');
|
|
376
377
|
},
|
|
377
378
|
});
|
|
378
379
|
bridge.localBackend
|
|
@@ -388,7 +389,10 @@ export async function regenerateChatMessage(
|
|
|
388
389
|
parser.end();
|
|
389
390
|
settle(
|
|
390
391
|
payload.status >= 400
|
|
391
|
-
? new Error(
|
|
392
|
+
? new Error(
|
|
393
|
+
refusal ??
|
|
394
|
+
t('Regeneration failed (HTTP {status})', { status: payload.status }),
|
|
395
|
+
)
|
|
392
396
|
: undefined,
|
|
393
397
|
);
|
|
394
398
|
} else if (payload.type === 'error') settle(new Error(payload.error));
|
package/src/lib/date-groups.ts
CHANGED
|
@@ -1,34 +1,42 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Date-group helpers used by AgentSidebar to label chat groups
|
|
3
|
-
* ("
|
|
4
|
-
*
|
|
3
|
+
* ("Today" / "Yesterday" / "N days ago" / "M/D" / "M/D/YYYY"). Lifted from
|
|
4
|
+
* `deeppath/apps/web/src/app/agent/AgentSidebar.tsx` so the visual
|
|
5
5
|
* grouping stays in lockstep.
|
|
6
6
|
*
|
|
7
7
|
* priority is used for ordering groups: smaller = newer = listed first.
|
|
8
|
+
* 标签走 t(),排序只看日期,不解析标签文本。
|
|
8
9
|
*/
|
|
10
|
+
import { t } from '@/i18n';
|
|
9
11
|
|
|
10
|
-
|
|
12
|
+
function dayBuckets(date: Date): { diffDays: number; target: Date; today: Date } {
|
|
11
13
|
const today = new Date();
|
|
12
14
|
today.setHours(0, 0, 0, 0);
|
|
13
15
|
const target = new Date(date);
|
|
14
16
|
target.setHours(0, 0, 0, 0);
|
|
15
17
|
const diffDays =
|
|
16
18
|
(today.getTime() - target.getTime()) / (1000 * 60 * 60 * 24);
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const
|
|
19
|
+
return { diffDays, target, today };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function getDateGroupLabel(date: Date): string {
|
|
23
|
+
const { diffDays, target, today } = dayBuckets(date);
|
|
24
|
+
if (diffDays === 0) return t('Today');
|
|
25
|
+
if (diffDays === 1) return t('Yesterday');
|
|
26
|
+
if (diffDays < 4) return t('{count} days ago', { count: Math.floor(diffDays) });
|
|
27
|
+
const month = target.getMonth() + 1;
|
|
28
|
+
const day = target.getDate();
|
|
22
29
|
if (target.getFullYear() !== today.getFullYear()) {
|
|
23
|
-
return
|
|
30
|
+
return t('{month}/{day}/{year}', { year: target.getFullYear(), month, day });
|
|
24
31
|
}
|
|
25
|
-
return
|
|
32
|
+
return t('{month}/{day}', { month, day });
|
|
26
33
|
}
|
|
27
34
|
|
|
28
|
-
export function getDateGroupPriority(
|
|
29
|
-
|
|
30
|
-
if (
|
|
31
|
-
if (
|
|
32
|
-
if (
|
|
35
|
+
export function getDateGroupPriority(date: Date): number {
|
|
36
|
+
const { diffDays, target, today } = dayBuckets(date);
|
|
37
|
+
if (diffDays === 0) return 1;
|
|
38
|
+
if (diffDays === 1) return 2;
|
|
39
|
+
if (diffDays < 4) return 2 + Math.floor(diffDays);
|
|
40
|
+
if (target.getFullYear() !== today.getFullYear()) return 1000;
|
|
33
41
|
return 100;
|
|
34
42
|
}
|