bingocode 1.0.3 → 1.0.4
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 +1 -1
- package/desktop/README.md +0 -30
- package/desktop/bunfig.toml +0 -1
- package/desktop/index.html +0 -17
- package/desktop/package.json +0 -55
- package/desktop/pnpm-lock.yaml +0 -3832
- package/desktop/public/app-icon.jpg +0 -0
- package/desktop/public/fonts/inter-latin-ext.woff2 +0 -0
- package/desktop/public/fonts/inter-latin.woff2 +0 -0
- package/desktop/public/fonts/jetbrains-mono-latin-ext.woff2 +0 -0
- package/desktop/public/fonts/jetbrains-mono-latin.woff2 +0 -0
- package/desktop/public/fonts/manrope-latin-ext.woff2 +0 -0
- package/desktop/public/fonts/manrope-latin.woff2 +0 -0
- package/desktop/public/fonts/material-symbols-outlined.woff2 +0 -0
- package/desktop/public/icons/bilibili.svg +0 -1
- package/desktop/public/icons/douyin.svg +0 -1
- package/desktop/public/icons/github.svg +0 -3
- package/desktop/public/icons/xiaohongshu.svg +0 -1
- package/desktop/scripts/build-macos-arm64.sh +0 -270
- package/desktop/scripts/build-sidecars.ts +0 -183
- package/desktop/scripts/build-windows-x64.ps1 +0 -295
- package/desktop/scripts/scan-missing-imports.ts +0 -235
- package/desktop/sidecars/claude-sidecar.ts +0 -156
- package/desktop/src/App.tsx +0 -5
- package/desktop/src/__tests__/agentsSettings.test.tsx +0 -349
- package/desktop/src/__tests__/pages.test.tsx +0 -290
- package/desktop/src/__tests__/skillsSettings.test.tsx +0 -205
- package/desktop/src/api/adapters.ts +0 -12
- package/desktop/src/api/agents.ts +0 -36
- package/desktop/src/api/cliTasks.ts +0 -28
- package/desktop/src/api/client.ts +0 -63
- package/desktop/src/api/computerUse.ts +0 -76
- package/desktop/src/api/filesystem.ts +0 -30
- package/desktop/src/api/hahaOAuth.ts +0 -38
- package/desktop/src/api/models.ts +0 -28
- package/desktop/src/api/providers.ts +0 -63
- package/desktop/src/api/search.ts +0 -29
- package/desktop/src/api/sessions.ts +0 -56
- package/desktop/src/api/settings.ts +0 -20
- package/desktop/src/api/skills.ts +0 -19
- package/desktop/src/api/tasks.ts +0 -36
- package/desktop/src/api/teams.ts +0 -44
- package/desktop/src/api/websocket.ts +0 -164
- package/desktop/src/components/chat/AskUserQuestion.tsx +0 -268
- package/desktop/src/components/chat/AssistantMessage.tsx +0 -29
- package/desktop/src/components/chat/AttachmentGallery.tsx +0 -113
- package/desktop/src/components/chat/ChatInput.tsx +0 -622
- package/desktop/src/components/chat/CodeViewer.tsx +0 -161
- package/desktop/src/components/chat/ComputerUsePermissionModal.test.tsx +0 -174
- package/desktop/src/components/chat/ComputerUsePermissionModal.tsx +0 -311
- package/desktop/src/components/chat/DiffViewer.tsx +0 -157
- package/desktop/src/components/chat/FileSearchMenu.tsx +0 -198
- package/desktop/src/components/chat/ImageGalleryModal.tsx +0 -91
- package/desktop/src/components/chat/InlineImageGallery.tsx +0 -106
- package/desktop/src/components/chat/InlineTaskSummary.tsx +0 -60
- package/desktop/src/components/chat/MermaidRenderer.test.tsx +0 -98
- package/desktop/src/components/chat/MermaidRenderer.tsx +0 -361
- package/desktop/src/components/chat/MessageActionBar.tsx +0 -27
- package/desktop/src/components/chat/MessageList.test.tsx +0 -313
- package/desktop/src/components/chat/MessageList.tsx +0 -249
- package/desktop/src/components/chat/PermissionDialog.tsx +0 -262
- package/desktop/src/components/chat/SessionTaskBar.test.tsx +0 -99
- package/desktop/src/components/chat/SessionTaskBar.tsx +0 -159
- package/desktop/src/components/chat/StreamingIndicator.tsx +0 -41
- package/desktop/src/components/chat/TerminalChrome.tsx +0 -35
- package/desktop/src/components/chat/ThinkingBlock.tsx +0 -87
- package/desktop/src/components/chat/ToolCallBlock.tsx +0 -247
- package/desktop/src/components/chat/ToolCallGroup.tsx +0 -617
- package/desktop/src/components/chat/ToolResultBlock.tsx +0 -107
- package/desktop/src/components/chat/UserMessage.tsx +0 -38
- package/desktop/src/components/chat/chatBlocks.test.tsx +0 -136
- package/desktop/src/components/chat/clipboard.ts +0 -25
- package/desktop/src/components/chat/composerUtils.test.ts +0 -55
- package/desktop/src/components/chat/composerUtils.ts +0 -149
- package/desktop/src/components/controls/ModelSelector.tsx +0 -156
- package/desktop/src/components/controls/PermissionModeSelector.tsx +0 -229
- package/desktop/src/components/layout/AppShell.tsx +0 -107
- package/desktop/src/components/layout/ContentRouter.tsx +0 -27
- package/desktop/src/components/layout/ProjectFilter.tsx +0 -126
- package/desktop/src/components/layout/Sidebar.test.tsx +0 -158
- package/desktop/src/components/layout/Sidebar.tsx +0 -384
- package/desktop/src/components/layout/StatusBar.tsx +0 -31
- package/desktop/src/components/layout/TabBar.test.tsx +0 -136
- package/desktop/src/components/layout/TabBar.tsx +0 -318
- package/desktop/src/components/layout/TitleBar.tsx +0 -96
- package/desktop/src/components/layout/WindowControls.test.tsx +0 -69
- package/desktop/src/components/layout/WindowControls.tsx +0 -89
- package/desktop/src/components/markdown/MarkdownRenderer.test.tsx +0 -100
- package/desktop/src/components/markdown/MarkdownRenderer.tsx +0 -229
- package/desktop/src/components/settings/ClaudeOfficialLogin.tsx +0 -107
- package/desktop/src/components/shared/Button.tsx +0 -63
- package/desktop/src/components/shared/CopyButton.tsx +0 -58
- package/desktop/src/components/shared/DirectoryPicker.tsx +0 -316
- package/desktop/src/components/shared/Dropdown.tsx +0 -91
- package/desktop/src/components/shared/Input.tsx +0 -38
- package/desktop/src/components/shared/Modal.tsx +0 -65
- package/desktop/src/components/shared/ProjectContextChip.tsx +0 -30
- package/desktop/src/components/shared/Spinner.tsx +0 -30
- package/desktop/src/components/shared/Textarea.tsx +0 -38
- package/desktop/src/components/shared/Toast.tsx +0 -47
- package/desktop/src/components/shared/UpdateChecker.tsx +0 -90
- package/desktop/src/components/skills/SkillDetail.test.tsx +0 -89
- package/desktop/src/components/skills/SkillDetail.tsx +0 -403
- package/desktop/src/components/skills/SkillList.tsx +0 -254
- package/desktop/src/components/tasks/DayOfWeekPicker.tsx +0 -57
- package/desktop/src/components/tasks/NewTaskModal.tsx +0 -407
- package/desktop/src/components/tasks/PromptEditor.tsx +0 -74
- package/desktop/src/components/tasks/TaskEmptyState.tsx +0 -30
- package/desktop/src/components/tasks/TaskList.tsx +0 -46
- package/desktop/src/components/tasks/TaskRow.tsx +0 -253
- package/desktop/src/components/tasks/TaskRunsPanel.tsx +0 -195
- package/desktop/src/components/teams/TeamStatusBar.tsx +0 -147
- package/desktop/src/config/providerPresets.ts +0 -78
- package/desktop/src/config/spinnerVerbs.ts +0 -193
- package/desktop/src/hooks/useKeyboardShortcuts.ts +0 -60
- package/desktop/src/i18n/index.ts +0 -54
- package/desktop/src/i18n/locales/en.ts +0 -670
- package/desktop/src/i18n/locales/zh.ts +0 -670
- package/desktop/src/lib/__tests__/cronDescribe.test.ts +0 -93
- package/desktop/src/lib/cronDescribe.ts +0 -188
- package/desktop/src/lib/desktopRuntime.ts +0 -54
- package/desktop/src/lib/parseRunOutput.ts +0 -79
- package/desktop/src/main.tsx +0 -13
- package/desktop/src/mocks/data.ts +0 -202
- package/desktop/src/pages/ActiveSession.test.tsx +0 -181
- package/desktop/src/pages/ActiveSession.tsx +0 -219
- package/desktop/src/pages/AdapterSettings.tsx +0 -375
- package/desktop/src/pages/AgentTeams.tsx +0 -200
- package/desktop/src/pages/ComputerUseSettings.tsx +0 -420
- package/desktop/src/pages/EmptySession.tsx +0 -518
- package/desktop/src/pages/NewTaskModal.tsx +0 -346
- package/desktop/src/pages/ScheduledTasks.tsx +0 -66
- package/desktop/src/pages/ScheduledTasksEmpty.tsx +0 -152
- package/desktop/src/pages/ScheduledTasksList.tsx +0 -416
- package/desktop/src/pages/SessionControls.tsx +0 -460
- package/desktop/src/pages/Settings.tsx +0 -1448
- package/desktop/src/pages/ToolInspection.tsx +0 -235
- package/desktop/src/stores/adapterStore.ts +0 -106
- package/desktop/src/stores/agentStore.ts +0 -34
- package/desktop/src/stores/chatStore.test.ts +0 -505
- package/desktop/src/stores/chatStore.ts +0 -850
- package/desktop/src/stores/cliTaskStore.ts +0 -152
- package/desktop/src/stores/hahaOAuthStore.test.ts +0 -77
- package/desktop/src/stores/hahaOAuthStore.ts +0 -97
- package/desktop/src/stores/providerStore.ts +0 -101
- package/desktop/src/stores/sessionStore.test.ts +0 -63
- package/desktop/src/stores/sessionStore.ts +0 -102
- package/desktop/src/stores/settingsStore.ts +0 -120
- package/desktop/src/stores/skillStore.ts +0 -51
- package/desktop/src/stores/tabStore.ts +0 -169
- package/desktop/src/stores/taskStore.ts +0 -68
- package/desktop/src/stores/teamStore.ts +0 -344
- package/desktop/src/stores/uiStore.ts +0 -100
- package/desktop/src/stores/updateStore.test.ts +0 -71
- package/desktop/src/stores/updateStore.ts +0 -221
- package/desktop/src/theme/globals.css +0 -465
- package/desktop/src/types/adapter.ts +0 -33
- package/desktop/src/types/chat.ts +0 -152
- package/desktop/src/types/cliTask.ts +0 -24
- package/desktop/src/types/provider.ts +0 -62
- package/desktop/src/types/session.ts +0 -27
- package/desktop/src/types/settings.ts +0 -22
- package/desktop/src/types/skill.ts +0 -38
- package/desktop/src/types/task.ts +0 -56
- package/desktop/src/types/team.ts +0 -38
- package/desktop/src-tauri/Cargo.lock +0 -5549
- package/desktop/src-tauri/Cargo.toml +0 -20
- package/desktop/src-tauri/app-icon.svg +0 -13
- package/desktop/src-tauri/build.rs +0 -3
- package/desktop/src-tauri/capabilities/default.json +0 -106
- package/desktop/src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml +0 -5
- package/desktop/src-tauri/icons/android/values/ic_launcher_background.xml +0 -4
- package/desktop/src-tauri/icons/icon.icns +0 -0
- package/desktop/src-tauri/icons/icon.ico +0 -0
- package/desktop/src-tauri/src/lib.rs +0 -408
- package/desktop/src-tauri/src/main.rs +0 -6
- package/desktop/src-tauri/tauri.conf.json +0 -78
- package/desktop/src-tauri/tauri.macos.conf.json +0 -18
- package/desktop/src-tauri/tauri.release-ci.json +0 -5
- package/desktop/src-tauri/tauri.windows.conf.json +0 -16
- package/desktop/src-tauri/windows-installer-hooks.nsh +0 -17
- package/desktop/tsconfig.json +0 -25
- package/desktop/vite.config.ts +0 -26
- package/desktop/vitest.config.ts +0 -18
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { api } from './client'
|
|
2
|
-
import type { SkillMeta, SkillDetail } from '../types/skill'
|
|
3
|
-
|
|
4
|
-
export const skillsApi = {
|
|
5
|
-
list: (cwd?: string) => {
|
|
6
|
-
const query = cwd ? `?cwd=${encodeURIComponent(cwd)}` : ''
|
|
7
|
-
return api.get<{ skills: SkillMeta[] }>(`/api/skills${query}`)
|
|
8
|
-
},
|
|
9
|
-
|
|
10
|
-
detail: (source: string, name: string, cwd?: string) => {
|
|
11
|
-
const query = new URLSearchParams({
|
|
12
|
-
source,
|
|
13
|
-
name,
|
|
14
|
-
})
|
|
15
|
-
if (cwd) query.set('cwd', cwd)
|
|
16
|
-
|
|
17
|
-
return api.get<{ detail: SkillDetail }>(`/api/skills/detail?${query.toString()}`)
|
|
18
|
-
},
|
|
19
|
-
}
|
package/desktop/src/api/tasks.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { api } from './client'
|
|
2
|
-
import type { CronTask, CreateTaskInput, TaskRun } from '../types/task'
|
|
3
|
-
|
|
4
|
-
type TasksResponse = { tasks: CronTask[] }
|
|
5
|
-
type TaskResponse = { task: CronTask }
|
|
6
|
-
type RunsResponse = { runs: TaskRun[] }
|
|
7
|
-
|
|
8
|
-
export const tasksApi = {
|
|
9
|
-
list() {
|
|
10
|
-
return api.get<TasksResponse>('/api/scheduled-tasks')
|
|
11
|
-
},
|
|
12
|
-
|
|
13
|
-
create(input: CreateTaskInput) {
|
|
14
|
-
return api.post<TaskResponse>('/api/scheduled-tasks', input)
|
|
15
|
-
},
|
|
16
|
-
|
|
17
|
-
update(id: string, updates: Partial<CronTask>) {
|
|
18
|
-
return api.put<TaskResponse>(`/api/scheduled-tasks/${id}`, updates)
|
|
19
|
-
},
|
|
20
|
-
|
|
21
|
-
delete(id: string) {
|
|
22
|
-
return api.delete<{ ok: true }>(`/api/scheduled-tasks/${id}`)
|
|
23
|
-
},
|
|
24
|
-
|
|
25
|
-
runTask(id: string) {
|
|
26
|
-
return api.post<{ ok: true }>(`/api/scheduled-tasks/${id}/run`, {})
|
|
27
|
-
},
|
|
28
|
-
|
|
29
|
-
getRecentRuns(limit = 50) {
|
|
30
|
-
return api.get<RunsResponse>(`/api/scheduled-tasks/runs?limit=${limit}`)
|
|
31
|
-
},
|
|
32
|
-
|
|
33
|
-
getTaskRuns(taskId: string) {
|
|
34
|
-
return api.get<RunsResponse>(`/api/scheduled-tasks/${taskId}/runs`)
|
|
35
|
-
},
|
|
36
|
-
}
|
package/desktop/src/api/teams.ts
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { api } from './client'
|
|
2
|
-
import type { TeamSummary, TeamDetail } from '../types/team'
|
|
3
|
-
|
|
4
|
-
type TeamsResponse = { teams: TeamSummary[] }
|
|
5
|
-
|
|
6
|
-
type TranscriptMessage = {
|
|
7
|
-
id: string
|
|
8
|
-
type: string
|
|
9
|
-
content: unknown
|
|
10
|
-
timestamp: string
|
|
11
|
-
model?: string
|
|
12
|
-
parentToolUseId?: string
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
type TranscriptResponse = { messages: TranscriptMessage[] }
|
|
16
|
-
|
|
17
|
-
export type { TranscriptMessage }
|
|
18
|
-
|
|
19
|
-
export const teamsApi = {
|
|
20
|
-
list() {
|
|
21
|
-
return api.get<TeamsResponse>('/api/teams')
|
|
22
|
-
},
|
|
23
|
-
|
|
24
|
-
get(name: string) {
|
|
25
|
-
return api.get<TeamDetail>(`/api/teams/${encodeURIComponent(name)}`)
|
|
26
|
-
},
|
|
27
|
-
|
|
28
|
-
getMemberTranscript(teamName: string, agentId: string) {
|
|
29
|
-
return api.get<TranscriptResponse>(
|
|
30
|
-
`/api/teams/${encodeURIComponent(teamName)}/members/${encodeURIComponent(agentId)}/transcript`,
|
|
31
|
-
)
|
|
32
|
-
},
|
|
33
|
-
|
|
34
|
-
sendMemberMessage(teamName: string, agentId: string, content: string) {
|
|
35
|
-
return api.post<{ ok: true }>(
|
|
36
|
-
`/api/teams/${encodeURIComponent(teamName)}/members/${encodeURIComponent(agentId)}/messages`,
|
|
37
|
-
{ content },
|
|
38
|
-
)
|
|
39
|
-
},
|
|
40
|
-
|
|
41
|
-
delete(name: string) {
|
|
42
|
-
return api.delete<{ ok: true }>(`/api/teams/${encodeURIComponent(name)}`)
|
|
43
|
-
},
|
|
44
|
-
}
|
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
import type { ClientMessage, ServerMessage } from '../types/chat'
|
|
2
|
-
import { getBaseUrl } from './client'
|
|
3
|
-
|
|
4
|
-
type MessageHandler = (msg: ServerMessage) => void
|
|
5
|
-
|
|
6
|
-
type Connection = {
|
|
7
|
-
ws: WebSocket
|
|
8
|
-
handlers: Set<MessageHandler>
|
|
9
|
-
reconnectTimer: ReturnType<typeof setTimeout> | null
|
|
10
|
-
reconnectAttempt: number
|
|
11
|
-
pingInterval: ReturnType<typeof setInterval> | null
|
|
12
|
-
intentionalClose: boolean
|
|
13
|
-
pendingMessages: ClientMessage[]
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
class WebSocketManager {
|
|
17
|
-
private connections = new Map<string, Connection>()
|
|
18
|
-
|
|
19
|
-
isConnected(sessionId: string): boolean {
|
|
20
|
-
const conn = this.connections.get(sessionId)
|
|
21
|
-
return conn?.ws.readyState === WebSocket.OPEN
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
getConnectedSessionIds(): string[] {
|
|
25
|
-
return [...this.connections.keys()]
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
connect(sessionId: string) {
|
|
29
|
-
const existing = this.connections.get(sessionId)
|
|
30
|
-
if (existing && !existing.intentionalClose) return
|
|
31
|
-
|
|
32
|
-
const wsUrl = getBaseUrl().replace(/^http/, 'ws')
|
|
33
|
-
const ws = new WebSocket(`${wsUrl}/ws/${sessionId}`)
|
|
34
|
-
|
|
35
|
-
const conn: Connection = {
|
|
36
|
-
ws,
|
|
37
|
-
handlers: existing?.handlers ?? new Set(),
|
|
38
|
-
reconnectTimer: null,
|
|
39
|
-
reconnectAttempt: 0,
|
|
40
|
-
pingInterval: null,
|
|
41
|
-
intentionalClose: false,
|
|
42
|
-
pendingMessages: [],
|
|
43
|
-
}
|
|
44
|
-
this.connections.set(sessionId, conn)
|
|
45
|
-
|
|
46
|
-
ws.onopen = () => {
|
|
47
|
-
conn.reconnectAttempt = 0
|
|
48
|
-
this.startPingLoop(sessionId)
|
|
49
|
-
while (conn.pendingMessages.length > 0) {
|
|
50
|
-
const msg = conn.pendingMessages.shift()!
|
|
51
|
-
ws.send(JSON.stringify(msg))
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
ws.onmessage = (event) => {
|
|
56
|
-
try {
|
|
57
|
-
const msg = JSON.parse(event.data as string) as ServerMessage
|
|
58
|
-
for (const handler of conn.handlers) {
|
|
59
|
-
handler(msg)
|
|
60
|
-
}
|
|
61
|
-
} catch {
|
|
62
|
-
// Ignore malformed messages
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
ws.onclose = () => {
|
|
67
|
-
this.stopPingLoop(sessionId)
|
|
68
|
-
if (!conn.intentionalClose && this.connections.get(sessionId) === conn) {
|
|
69
|
-
this.scheduleReconnect(sessionId, conn)
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
ws.onerror = () => {
|
|
74
|
-
// onclose will fire after onerror
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
disconnect(sessionId: string) {
|
|
79
|
-
const conn = this.connections.get(sessionId)
|
|
80
|
-
if (!conn) return
|
|
81
|
-
|
|
82
|
-
conn.intentionalClose = true
|
|
83
|
-
this.stopPingLoop(sessionId)
|
|
84
|
-
if (conn.reconnectTimer) {
|
|
85
|
-
clearTimeout(conn.reconnectTimer)
|
|
86
|
-
conn.reconnectTimer = null
|
|
87
|
-
}
|
|
88
|
-
conn.pendingMessages = []
|
|
89
|
-
|
|
90
|
-
conn.ws.close()
|
|
91
|
-
this.connections.delete(sessionId)
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
disconnectAll() {
|
|
95
|
-
for (const sessionId of [...this.connections.keys()]) {
|
|
96
|
-
this.disconnect(sessionId)
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
send(sessionId: string, message: ClientMessage) {
|
|
101
|
-
const conn = this.connections.get(sessionId)
|
|
102
|
-
if (!conn) return
|
|
103
|
-
|
|
104
|
-
if (conn.ws.readyState === WebSocket.OPEN) {
|
|
105
|
-
conn.ws.send(JSON.stringify(message))
|
|
106
|
-
} else if (conn.ws.readyState === WebSocket.CONNECTING) {
|
|
107
|
-
conn.pendingMessages.push(message)
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
onMessage(sessionId: string, handler: MessageHandler): () => void {
|
|
112
|
-
const conn = this.connections.get(sessionId)
|
|
113
|
-
if (!conn) return () => {}
|
|
114
|
-
conn.handlers.add(handler)
|
|
115
|
-
return () => { conn.handlers.delete(handler) }
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
clearHandlers(sessionId: string) {
|
|
119
|
-
const conn = this.connections.get(sessionId)
|
|
120
|
-
if (conn) conn.handlers.clear()
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
private startPingLoop(sessionId: string) {
|
|
124
|
-
this.stopPingLoop(sessionId)
|
|
125
|
-
const conn = this.connections.get(sessionId)
|
|
126
|
-
if (!conn) return
|
|
127
|
-
conn.pingInterval = setInterval(() => {
|
|
128
|
-
this.send(sessionId, { type: 'ping' })
|
|
129
|
-
}, 30_000)
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
private stopPingLoop(sessionId: string) {
|
|
133
|
-
const conn = this.connections.get(sessionId)
|
|
134
|
-
if (conn?.pingInterval) {
|
|
135
|
-
clearInterval(conn.pingInterval)
|
|
136
|
-
conn.pingInterval = null
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
private scheduleReconnect(sessionId: string, conn: Connection) {
|
|
141
|
-
if (conn.reconnectTimer) {
|
|
142
|
-
clearTimeout(conn.reconnectTimer)
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const delay = Math.min(1000 * 2 ** conn.reconnectAttempt, 30_000)
|
|
146
|
-
conn.reconnectAttempt++
|
|
147
|
-
|
|
148
|
-
conn.reconnectTimer = setTimeout(() => {
|
|
149
|
-
if (this.connections.get(sessionId) === conn && !conn.intentionalClose) {
|
|
150
|
-
this.connections.delete(sessionId)
|
|
151
|
-
this.connect(sessionId)
|
|
152
|
-
// Migrate handlers to new connection
|
|
153
|
-
const newConn = this.connections.get(sessionId)
|
|
154
|
-
if (newConn) {
|
|
155
|
-
for (const handler of conn.handlers) {
|
|
156
|
-
newConn.handlers.add(handler)
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
}, delay)
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
export const wsManager = new WebSocketManager()
|
|
@@ -1,268 +0,0 @@
|
|
|
1
|
-
import { useState, useRef } from 'react'
|
|
2
|
-
import { useChatStore } from '../../stores/chatStore'
|
|
3
|
-
import { useTabStore } from '../../stores/tabStore'
|
|
4
|
-
import { useTranslation } from '../../i18n'
|
|
5
|
-
import { Button } from '../shared/Button'
|
|
6
|
-
|
|
7
|
-
type QuestionOption = {
|
|
8
|
-
label: string
|
|
9
|
-
description?: string
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
type Question = {
|
|
13
|
-
question: string
|
|
14
|
-
header?: string
|
|
15
|
-
options?: QuestionOption[]
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
type AskUserInput = {
|
|
19
|
-
questions?: Question[]
|
|
20
|
-
question?: string
|
|
21
|
-
options?: QuestionOption[]
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
type Props = {
|
|
25
|
-
toolUseId: string
|
|
26
|
-
input: unknown
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Parse the AskUserQuestion input which may come in different shapes.
|
|
31
|
-
*/
|
|
32
|
-
function parseInput(input: unknown): Question[] {
|
|
33
|
-
if (!input || typeof input !== 'object') return []
|
|
34
|
-
const obj = input as AskUserInput
|
|
35
|
-
|
|
36
|
-
// Shape 1: { questions: [...] }
|
|
37
|
-
if (Array.isArray(obj.questions)) {
|
|
38
|
-
return obj.questions
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Shape 2: { question: "...", options: [...] }
|
|
42
|
-
if (typeof obj.question === 'string') {
|
|
43
|
-
return [{ question: obj.question, options: obj.options }]
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return []
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export function AskUserQuestion({ toolUseId: _toolUseId, input }: Props) {
|
|
50
|
-
const { sendMessage } = useChatStore()
|
|
51
|
-
const activeTabId = useTabStore((s) => s.activeTabId)
|
|
52
|
-
const t = useTranslation()
|
|
53
|
-
const questions = parseInput(input)
|
|
54
|
-
const [activeTab, setActiveTab] = useState(0)
|
|
55
|
-
const [selections, setSelections] = useState<Record<number, string>>({})
|
|
56
|
-
const [freeText, setFreeText] = useState('')
|
|
57
|
-
const [submitted, setSubmitted] = useState(false)
|
|
58
|
-
const composingRef = useRef(false)
|
|
59
|
-
|
|
60
|
-
if (questions.length === 0) return null
|
|
61
|
-
|
|
62
|
-
const handleSelect = (qIndex: number, label: string) => {
|
|
63
|
-
if (submitted) return
|
|
64
|
-
setSelections((prev) => {
|
|
65
|
-
// Toggle: deselect if already selected
|
|
66
|
-
if (prev[qIndex] === label) {
|
|
67
|
-
const next = { ...prev }
|
|
68
|
-
delete next[qIndex]
|
|
69
|
-
return next
|
|
70
|
-
}
|
|
71
|
-
return { ...prev, [qIndex]: label }
|
|
72
|
-
})
|
|
73
|
-
setFreeText('')
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const handleSubmit = () => {
|
|
77
|
-
if (submitted) return
|
|
78
|
-
|
|
79
|
-
const parts: string[] = []
|
|
80
|
-
for (let i = 0; i < questions.length; i++) {
|
|
81
|
-
const selected = selections[i]
|
|
82
|
-
if (selected) parts.push(selected)
|
|
83
|
-
}
|
|
84
|
-
const response = freeText.trim() || parts.join('; ') || ''
|
|
85
|
-
if (!response) return
|
|
86
|
-
|
|
87
|
-
setSubmitted(true)
|
|
88
|
-
if (!activeTabId) return
|
|
89
|
-
sendMessage(activeTabId, response)
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// All questions must be answered (via selection or free text) to enable submit
|
|
93
|
-
const allAnswered = freeText.trim().length > 0 || questions.every((_, i) => selections[i] !== undefined)
|
|
94
|
-
const safeActiveTab = Math.min(activeTab, questions.length - 1)
|
|
95
|
-
const activeQuestion = questions[safeActiveTab]
|
|
96
|
-
|
|
97
|
-
if (!activeQuestion) return null
|
|
98
|
-
|
|
99
|
-
return (
|
|
100
|
-
<div className={`mb-4 ml-10 rounded-[var(--radius-lg)] border overflow-hidden ${
|
|
101
|
-
submitted
|
|
102
|
-
? 'border-[var(--color-outline-variant)]/40 bg-[var(--color-surface-container-low)] opacity-70'
|
|
103
|
-
: 'border-[var(--color-secondary)] bg-[var(--color-surface-container-lowest)]'
|
|
104
|
-
}`}>
|
|
105
|
-
{/* Header */}
|
|
106
|
-
<div className={`flex items-center gap-3 px-4 py-3 ${
|
|
107
|
-
submitted
|
|
108
|
-
? 'bg-[var(--color-surface-container-low)]'
|
|
109
|
-
: 'bg-[var(--color-surface-container)]'
|
|
110
|
-
}`}>
|
|
111
|
-
<div className="flex items-center justify-center w-8 h-8 rounded-[var(--radius-md)] bg-[var(--color-secondary)]/10">
|
|
112
|
-
<span className="material-symbols-outlined text-[18px] text-[var(--color-secondary)]">
|
|
113
|
-
help
|
|
114
|
-
</span>
|
|
115
|
-
</div>
|
|
116
|
-
<div className="flex-1 min-w-0">
|
|
117
|
-
<span className="text-sm font-semibold text-[var(--color-text-primary)]">
|
|
118
|
-
{t('question.needsInput')}
|
|
119
|
-
</span>
|
|
120
|
-
{submitted && (
|
|
121
|
-
<span className="ml-2 inline-flex items-center px-2 py-0.5 rounded-full text-[10px] font-bold uppercase tracking-wider bg-[var(--color-surface-container-high)] text-[var(--color-text-tertiary)]">
|
|
122
|
-
{t('question.answered')}
|
|
123
|
-
</span>
|
|
124
|
-
)}
|
|
125
|
-
</div>
|
|
126
|
-
</div>
|
|
127
|
-
|
|
128
|
-
{/* Question tabs — horizontal tab bar (only show when multiple questions) */}
|
|
129
|
-
{questions.length > 1 && (
|
|
130
|
-
<div className="flex px-4 border-b border-[var(--color-outline-variant)]/20 bg-[var(--color-surface-container-low)] overflow-x-auto">
|
|
131
|
-
{questions.map((q, i) => {
|
|
132
|
-
const isActive = safeActiveTab === i
|
|
133
|
-
const isAnswered = selections[i] !== undefined
|
|
134
|
-
const tabLabel = q.header || `Q${i + 1}`
|
|
135
|
-
return (
|
|
136
|
-
<button
|
|
137
|
-
key={i}
|
|
138
|
-
onClick={() => setActiveTab(i)}
|
|
139
|
-
className={`relative flex items-center gap-1.5 px-4 py-2.5 text-xs font-medium whitespace-nowrap transition-colors ${
|
|
140
|
-
isActive
|
|
141
|
-
? 'text-[var(--color-secondary)]'
|
|
142
|
-
: 'text-[var(--color-text-tertiary)] hover:text-[var(--color-text-secondary)]'
|
|
143
|
-
}`}
|
|
144
|
-
>
|
|
145
|
-
{isAnswered && (
|
|
146
|
-
<span className="material-symbols-outlined text-[14px] text-[var(--color-success)]">check_circle</span>
|
|
147
|
-
)}
|
|
148
|
-
{tabLabel}
|
|
149
|
-
{isActive && (
|
|
150
|
-
<div className="absolute bottom-0 left-2 right-2 h-[2px] bg-[var(--color-secondary)] rounded-t" />
|
|
151
|
-
)}
|
|
152
|
-
</button>
|
|
153
|
-
)
|
|
154
|
-
})}
|
|
155
|
-
</div>
|
|
156
|
-
)}
|
|
157
|
-
|
|
158
|
-
{/* Active question content */}
|
|
159
|
-
<div className="px-4 py-3">
|
|
160
|
-
<p className="text-sm font-medium text-[var(--color-text-primary)] mb-3">
|
|
161
|
-
{activeQuestion.question}
|
|
162
|
-
</p>
|
|
163
|
-
|
|
164
|
-
{/* Option cards */}
|
|
165
|
-
{activeQuestion.options && activeQuestion.options.length > 0 && (
|
|
166
|
-
<div className="space-y-2 mb-3">
|
|
167
|
-
{activeQuestion.options.map((opt, optIndex) => {
|
|
168
|
-
const isSelected = selections[activeTab] === opt.label
|
|
169
|
-
return (
|
|
170
|
-
<button
|
|
171
|
-
key={optIndex}
|
|
172
|
-
onClick={() => handleSelect(safeActiveTab, opt.label)}
|
|
173
|
-
disabled={submitted}
|
|
174
|
-
className={`w-full text-left px-4 py-3 rounded-[var(--radius-md)] border transition-all duration-150 cursor-pointer ${
|
|
175
|
-
isSelected
|
|
176
|
-
? 'border-[var(--color-secondary)] bg-[var(--color-secondary)]/8 ring-1 ring-[var(--color-secondary)]/30'
|
|
177
|
-
: 'border-[var(--color-outline-variant)]/40 bg-[var(--color-surface)] hover:border-[var(--color-outline-variant)] hover:bg-[var(--color-surface-container-low)]'
|
|
178
|
-
} ${submitted ? 'cursor-default' : ''}`}
|
|
179
|
-
>
|
|
180
|
-
<div className="flex items-start gap-3">
|
|
181
|
-
{/* Check indicator */}
|
|
182
|
-
<div className={`mt-0.5 flex-shrink-0 w-4 h-4 rounded-full border-2 flex items-center justify-center transition-colors ${
|
|
183
|
-
isSelected
|
|
184
|
-
? 'border-[var(--color-secondary)] bg-[var(--color-secondary)]'
|
|
185
|
-
: 'border-[var(--color-outline)]'
|
|
186
|
-
}`}>
|
|
187
|
-
{isSelected && (
|
|
188
|
-
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round">
|
|
189
|
-
<polyline points="20 6 9 17 4 12" />
|
|
190
|
-
</svg>
|
|
191
|
-
)}
|
|
192
|
-
</div>
|
|
193
|
-
<div className="flex-1 min-w-0">
|
|
194
|
-
<span className={`text-sm font-medium ${
|
|
195
|
-
isSelected
|
|
196
|
-
? 'text-[var(--color-secondary)]'
|
|
197
|
-
: 'text-[var(--color-text-primary)]'
|
|
198
|
-
}`}>
|
|
199
|
-
{opt.label}
|
|
200
|
-
</span>
|
|
201
|
-
{opt.description && (
|
|
202
|
-
<p className="text-xs text-[var(--color-text-secondary)] mt-0.5">
|
|
203
|
-
{opt.description}
|
|
204
|
-
</p>
|
|
205
|
-
)}
|
|
206
|
-
</div>
|
|
207
|
-
</div>
|
|
208
|
-
</button>
|
|
209
|
-
)
|
|
210
|
-
})}
|
|
211
|
-
</div>
|
|
212
|
-
)}
|
|
213
|
-
|
|
214
|
-
{/* Free text input */}
|
|
215
|
-
{!submitted && (
|
|
216
|
-
<div>
|
|
217
|
-
<label className="text-xs text-[var(--color-text-tertiary)] mb-1.5 block">
|
|
218
|
-
{t('question.customResponse')}
|
|
219
|
-
</label>
|
|
220
|
-
<input
|
|
221
|
-
type="text"
|
|
222
|
-
value={freeText}
|
|
223
|
-
onChange={(e) => {
|
|
224
|
-
setFreeText(e.target.value)
|
|
225
|
-
if (e.target.value.trim()) setSelections({})
|
|
226
|
-
}}
|
|
227
|
-
onCompositionStart={() => { composingRef.current = true }}
|
|
228
|
-
onCompositionEnd={() => { composingRef.current = false }}
|
|
229
|
-
onKeyDown={(e) => {
|
|
230
|
-
if (composingRef.current || e.nativeEvent.isComposing || e.keyCode === 229) return
|
|
231
|
-
if (e.key === 'Enter' && allAnswered) handleSubmit()
|
|
232
|
-
}}
|
|
233
|
-
placeholder={t('question.typePlaceholder')}
|
|
234
|
-
className="w-full px-3 py-2 text-sm bg-[var(--color-surface)] border border-[var(--color-outline-variant)]/40 rounded-[var(--radius-md)] text-[var(--color-text-primary)] placeholder:text-[var(--color-text-tertiary)] focus:outline-none focus:border-[var(--color-secondary)] focus:ring-1 focus:ring-[var(--color-secondary)]/30"
|
|
235
|
-
/>
|
|
236
|
-
</div>
|
|
237
|
-
)}
|
|
238
|
-
|
|
239
|
-
{/* Submitted answer display */}
|
|
240
|
-
{submitted && (
|
|
241
|
-
<div className="flex items-center gap-2 text-xs text-[var(--color-text-secondary)]">
|
|
242
|
-
<span className="material-symbols-outlined text-[14px] text-[var(--color-success)]">check_circle</span>
|
|
243
|
-
<span>
|
|
244
|
-
{t('question.answeredPrefix')}<strong>{freeText.trim() || Object.values(selections).join(', ')}</strong>
|
|
245
|
-
</span>
|
|
246
|
-
</div>
|
|
247
|
-
)}
|
|
248
|
-
</div>
|
|
249
|
-
|
|
250
|
-
{/* Submit button */}
|
|
251
|
-
{!submitted && (
|
|
252
|
-
<div className="flex items-center gap-2 px-4 py-3 border-t border-[var(--color-outline-variant)]/20 bg-[var(--color-surface-container-low)]">
|
|
253
|
-
<Button
|
|
254
|
-
variant="primary"
|
|
255
|
-
size="sm"
|
|
256
|
-
disabled={!allAnswered}
|
|
257
|
-
onClick={handleSubmit}
|
|
258
|
-
icon={
|
|
259
|
-
<span className="material-symbols-outlined text-[14px]">send</span>
|
|
260
|
-
}
|
|
261
|
-
>
|
|
262
|
-
{t('question.submit')}
|
|
263
|
-
</Button>
|
|
264
|
-
</div>
|
|
265
|
-
)}
|
|
266
|
-
</div>
|
|
267
|
-
)
|
|
268
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { MarkdownRenderer } from '../markdown/MarkdownRenderer'
|
|
2
|
-
import { MessageActionBar } from './MessageActionBar'
|
|
3
|
-
import { InlineImageGallery } from './InlineImageGallery'
|
|
4
|
-
|
|
5
|
-
type Props = {
|
|
6
|
-
content: string
|
|
7
|
-
isStreaming?: boolean
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function AssistantMessage({ content, isStreaming }: Props) {
|
|
11
|
-
return (
|
|
12
|
-
<div className="group mb-5 ml-10 flex items-end gap-1.5">
|
|
13
|
-
<div className="min-w-0">
|
|
14
|
-
<div className="rounded-[20px] rounded-tl-[8px] border border-[var(--color-border)]/60 bg-[var(--color-surface)] px-4 py-3 text-sm text-[var(--color-text-primary)] shadow-sm">
|
|
15
|
-
<MarkdownRenderer content={content} />
|
|
16
|
-
{!isStreaming && <InlineImageGallery text={content} />}
|
|
17
|
-
{isStreaming && (
|
|
18
|
-
<span className="ml-0.5 inline-block h-4 w-0.5 animate-shimmer bg-[var(--color-brand)] align-text-bottom" />
|
|
19
|
-
)}
|
|
20
|
-
</div>
|
|
21
|
-
</div>
|
|
22
|
-
|
|
23
|
-
<MessageActionBar
|
|
24
|
-
copyText={isStreaming ? undefined : content}
|
|
25
|
-
copyLabel="Copy reply"
|
|
26
|
-
/>
|
|
27
|
-
</div>
|
|
28
|
-
)
|
|
29
|
-
}
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
import { useMemo, useState } from 'react'
|
|
2
|
-
import { ImageGalleryModal } from './ImageGalleryModal'
|
|
3
|
-
|
|
4
|
-
export type AttachmentPreview = {
|
|
5
|
-
id?: string
|
|
6
|
-
type: 'image' | 'file'
|
|
7
|
-
name: string
|
|
8
|
-
data?: string
|
|
9
|
-
previewUrl?: string
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
type Props = {
|
|
13
|
-
attachments: AttachmentPreview[]
|
|
14
|
-
variant?: 'composer' | 'message'
|
|
15
|
-
onRemove?: (id: string) => void
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function AttachmentGallery({ attachments, variant = 'message', onRemove }: Props) {
|
|
19
|
-
const [activeImageIndex, setActiveImageIndex] = useState<number | null>(null)
|
|
20
|
-
|
|
21
|
-
const images = useMemo(
|
|
22
|
-
() =>
|
|
23
|
-
attachments
|
|
24
|
-
.filter((attachment) => attachment.type === 'image' && (attachment.previewUrl || attachment.data))
|
|
25
|
-
.map((attachment) => ({
|
|
26
|
-
src: attachment.previewUrl || attachment.data || '',
|
|
27
|
-
name: attachment.name,
|
|
28
|
-
})),
|
|
29
|
-
[attachments],
|
|
30
|
-
)
|
|
31
|
-
|
|
32
|
-
if (attachments.length === 0) return null
|
|
33
|
-
|
|
34
|
-
const isComposer = variant === 'composer'
|
|
35
|
-
|
|
36
|
-
return (
|
|
37
|
-
<>
|
|
38
|
-
<div className={isComposer ? 'flex flex-wrap items-center gap-2' : 'grid grid-cols-1 gap-2 sm:grid-cols-2'}>
|
|
39
|
-
{attachments.map((attachment, index) => {
|
|
40
|
-
if (attachment.type === 'image' && (attachment.previewUrl || attachment.data)) {
|
|
41
|
-
const src = attachment.previewUrl || attachment.data || ''
|
|
42
|
-
return (
|
|
43
|
-
<div
|
|
44
|
-
key={attachment.id || `${attachment.name}-${index}`}
|
|
45
|
-
className={isComposer ? 'group relative' : ''}
|
|
46
|
-
>
|
|
47
|
-
<button
|
|
48
|
-
type="button"
|
|
49
|
-
onClick={() => setActiveImageIndex(images.findIndex((image) => image.src === src))}
|
|
50
|
-
className={
|
|
51
|
-
isComposer
|
|
52
|
-
? 'overflow-hidden rounded-xl border border-[var(--color-border)] bg-[var(--color-surface-container-low)]'
|
|
53
|
-
: 'overflow-hidden rounded-2xl border border-[var(--color-border)] bg-[var(--color-surface-container-low)] text-left shadow-sm transition-transform hover:scale-[1.01]'
|
|
54
|
-
}
|
|
55
|
-
>
|
|
56
|
-
<img
|
|
57
|
-
src={src}
|
|
58
|
-
alt={attachment.name}
|
|
59
|
-
className={
|
|
60
|
-
isComposer
|
|
61
|
-
? 'h-16 w-16 object-cover'
|
|
62
|
-
: 'max-h-[340px] w-full max-w-[360px] object-cover'
|
|
63
|
-
}
|
|
64
|
-
/>
|
|
65
|
-
</button>
|
|
66
|
-
{onRemove && attachment.id && (
|
|
67
|
-
<button
|
|
68
|
-
type="button"
|
|
69
|
-
onClick={() => onRemove(attachment.id!)}
|
|
70
|
-
className="absolute -right-1 -top-1 flex h-5 w-5 items-center justify-center rounded-full bg-[var(--color-error)] text-[10px] text-white opacity-0 transition-opacity group-hover:opacity-100"
|
|
71
|
-
aria-label={`Remove ${attachment.name}`}
|
|
72
|
-
>
|
|
73
|
-
×
|
|
74
|
-
</button>
|
|
75
|
-
)}
|
|
76
|
-
</div>
|
|
77
|
-
)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
return (
|
|
81
|
-
<div
|
|
82
|
-
key={attachment.id || `${attachment.name}-${index}`}
|
|
83
|
-
className="flex items-center gap-2 rounded-xl border border-[var(--color-border)] bg-[var(--color-surface-container-low)] px-3 py-2 text-xs text-[var(--color-text-secondary)]"
|
|
84
|
-
>
|
|
85
|
-
<span className="material-symbols-outlined text-[14px]">attach_file</span>
|
|
86
|
-
<span className="max-w-[220px] truncate">{attachment.name}</span>
|
|
87
|
-
{onRemove && attachment.id && (
|
|
88
|
-
<button
|
|
89
|
-
type="button"
|
|
90
|
-
onClick={() => onRemove(attachment.id!)}
|
|
91
|
-
className="ml-1 text-[var(--color-text-tertiary)] transition-colors hover:text-[var(--color-error)]"
|
|
92
|
-
aria-label={`Remove ${attachment.name}`}
|
|
93
|
-
>
|
|
94
|
-
<span className="material-symbols-outlined text-[14px]">close</span>
|
|
95
|
-
</button>
|
|
96
|
-
)}
|
|
97
|
-
</div>
|
|
98
|
-
)
|
|
99
|
-
})}
|
|
100
|
-
</div>
|
|
101
|
-
|
|
102
|
-
{activeImageIndex !== null && activeImageIndex >= 0 && (
|
|
103
|
-
<ImageGalleryModal
|
|
104
|
-
open={activeImageIndex !== null}
|
|
105
|
-
images={images}
|
|
106
|
-
activeIndex={activeImageIndex}
|
|
107
|
-
onClose={() => setActiveImageIndex(null)}
|
|
108
|
-
onSelect={setActiveImageIndex}
|
|
109
|
-
/>
|
|
110
|
-
)}
|
|
111
|
-
</>
|
|
112
|
-
)
|
|
113
|
-
}
|