@jaychang1989/dsh-webchat 0.3.0 → 0.4.0
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/NOTICE +10 -3
- package/README.md +44 -40
- package/README.zh.md +43 -44
- package/cordis.patch.yml +8 -7
- package/lib/client.js +402 -1693
- package/lib/index.js +246 -3884
- package/package.json +3 -35
- package/lib/client.js.map +0 -1
- package/lib/types/client/api.d.ts +0 -74
- package/lib/types/client/controller.d.ts +0 -19
- package/lib/types/client/index.d.ts +0 -30
- package/lib/types/client/locales.d.ts +0 -169
- package/lib/types/client/mount.d.ts +0 -23
- package/lib/types/client/panel/Markdown.d.ts +0 -15
- package/lib/types/client/panel/WebChatPanel.d.ts +0 -28
- package/lib/types/client/sidebar-entry-core.d.ts +0 -60
- package/lib/types/client/sidebar-entry.d.ts +0 -22
- package/lib/types/engine/engine.d.ts +0 -247
- package/lib/types/engine/html-md.d.ts +0 -23
- package/lib/types/index.d.ts +0 -63
- package/lib/types/protocol.d.ts +0 -107
- package/lib/types/routes.d.ts +0 -22
- package/lib/types/store.d.ts +0 -69
- package/lib/types/tools.d.ts +0 -27
- package/lib/types/transfer.d.ts +0 -138
- package/src/client/api.ts +0 -90
- package/src/client/controller.ts +0 -43
- package/src/client/css-modules.d.ts +0 -5
- package/src/client/index.ts +0 -75
- package/src/client/locales.ts +0 -172
- package/src/client/mount.tsx +0 -125
- package/src/client/panel/Markdown.tsx +0 -251
- package/src/client/panel/WebChatPanel.tsx +0 -760
- package/src/client/panel/panel.module.css +0 -936
- package/src/client/sidebar-entry-core.ts +0 -207
- package/src/client/sidebar-entry.ts +0 -49
- package/src/engine/engine.ts +0 -1385
- package/src/engine/html-md.ts +0 -126
- package/src/index.ts +0 -212
- package/src/protocol.ts +0 -116
- package/src/routes.ts +0 -307
- package/src/store.ts +0 -217
- package/src/tools.ts +0 -257
- package/src/transfer.ts +0 -602
package/lib/client.js
CHANGED
|
@@ -1,1725 +1,434 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-webchat — browser half.
|
|
3
|
+
*
|
|
4
|
+
* One sidebar entry and one panel holding one button. The panel does not chat:
|
|
5
|
+
* chat.deepseek.com cannot be framed (`frame-ancestors 'none'`) and this build
|
|
6
|
+
* has `webviewTag: false`, so the official page opens in a window instead (see
|
|
7
|
+
* lib/index.js). Everything here is plain DOM — no React, no build step.
|
|
8
|
+
*
|
|
9
|
+
* The shell exposes no slot an external plugin can register into, so the entry
|
|
10
|
+
* row and the center-column view are injected at the DOM level and self-heal
|
|
11
|
+
* against React re-renders, exactly as the previous release did.
|
|
12
|
+
*/
|
|
1
13
|
window.__ModuleLoader__.load({
|
|
2
14
|
id: "@jaychang1989/dsh-webchat",
|
|
3
|
-
factory: (
|
|
4
|
-
var module = { exports: {} }
|
|
5
|
-
var exports = module.exports
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
headers: body === void 0 ? void 0 : { "content-type": "application/json" },
|
|
39
|
-
body: body === void 0 ? void 0 : JSON.stringify(body)
|
|
40
|
-
});
|
|
41
|
-
const payload = await response.json().catch(() => ({}));
|
|
42
|
-
if (!response.ok && payload.ok !== true) return {
|
|
43
|
-
...payload,
|
|
44
|
-
ok: false,
|
|
45
|
-
error: payload.error ?? `HTTP ${response.status}`
|
|
46
|
-
};
|
|
47
|
-
return payload;
|
|
48
|
-
}
|
|
49
|
-
var WebChatApi = class {
|
|
50
|
-
state() {
|
|
51
|
-
return request(WEBChat_API.state);
|
|
52
|
-
}
|
|
53
|
-
openLogin() {
|
|
54
|
-
return request(WEBChat_API.openLogin);
|
|
55
|
-
}
|
|
56
|
-
closeBrowser() {
|
|
57
|
-
return request(WEBChat_API.closeBrowser);
|
|
58
|
-
}
|
|
59
|
-
newChat() {
|
|
60
|
-
return request(WEBChat_API.newChat);
|
|
61
|
-
}
|
|
62
|
-
send(text, images) {
|
|
63
|
-
return request(WEBChat_API.send, {
|
|
64
|
-
text,
|
|
65
|
-
images
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
stop() {
|
|
69
|
-
return request(WEBChat_API.stop);
|
|
70
|
-
}
|
|
71
|
-
setDeepThink(enabled) {
|
|
72
|
-
return request(WEBChat_API.deepThink, { enabled });
|
|
73
|
-
}
|
|
74
|
-
setSearch(enabled) {
|
|
75
|
-
return request(WEBChat_API.search, { enabled });
|
|
76
|
-
}
|
|
77
|
-
transfer(chatId, cwd, mode, workspaceId, targetSessionId) {
|
|
78
|
-
return request(WEBChat_API.transfer, {
|
|
79
|
-
chatId,
|
|
80
|
-
cwd,
|
|
81
|
-
mode,
|
|
82
|
-
workspaceId,
|
|
83
|
-
targetSessionId
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
exportFile(chatId, cwd) {
|
|
87
|
-
return request(WEBChat_API.exportFile, {
|
|
88
|
-
chatId,
|
|
89
|
-
cwd
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
renameChat(chatId, title) {
|
|
93
|
-
return request(WEBChat_API.renameChat, {
|
|
94
|
-
chatId,
|
|
95
|
-
title
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
deleteChat(chatId) {
|
|
99
|
-
return request(WEBChat_API.deleteChat, { chatId });
|
|
100
|
-
}
|
|
101
|
-
clearChats() {
|
|
102
|
-
return request(WEBChat_API.clearChats);
|
|
103
|
-
}
|
|
104
|
-
webChats() {
|
|
105
|
-
return request(WEBChat_API.webChats);
|
|
106
|
-
}
|
|
107
|
-
recover(title) {
|
|
108
|
-
return request(WEBChat_API.recover, { title });
|
|
109
|
-
}
|
|
110
|
-
};
|
|
111
|
-
//#endregion
|
|
112
|
-
//#region src/client/controller.ts
|
|
113
|
-
const INITIAL = { panelOpen: false };
|
|
114
|
-
/** Plain pub/sub store (no React dependency — used by the DOM sidebar entry). */
|
|
115
|
-
var PanelController = class {
|
|
116
|
-
snapshot = INITIAL;
|
|
117
|
-
listeners = /* @__PURE__ */ new Set();
|
|
118
|
-
getSnapshot() {
|
|
119
|
-
return this.snapshot;
|
|
120
|
-
}
|
|
121
|
-
subscribe(listener) {
|
|
122
|
-
this.listeners.add(listener);
|
|
123
|
-
return () => {
|
|
124
|
-
this.listeners.delete(listener);
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
set(patch) {
|
|
128
|
-
this.snapshot = {
|
|
129
|
-
...this.snapshot,
|
|
130
|
-
...patch
|
|
131
|
-
};
|
|
132
|
-
for (const listener of [...this.listeners]) listener();
|
|
15
|
+
factory: () => {
|
|
16
|
+
var module = { exports: {} }
|
|
17
|
+
var exports = module.exports
|
|
18
|
+
|
|
19
|
+
/** Routes served by lib/index.js. */
|
|
20
|
+
var API = { state: '/api/dsh-webchat/state', open: '/api/dsh-webchat/open' }
|
|
21
|
+
/** The page the launcher opens (mirrors PAGE_URL on the host half). */
|
|
22
|
+
var PAGE_URL = 'https://chat.deepseek.com/'
|
|
23
|
+
var ENTRY_ATTR = 'data-dsh-webchat-entry'
|
|
24
|
+
var VIEW_ATTR = 'data-dsh-webchat-view'
|
|
25
|
+
var ACTIVE_ATTR = 'data-dsh-webchat-active'
|
|
26
|
+
var OTHER_ACTIVE_ATTRS = ['data-dsh-taskboard-active', 'data-dsh-ssh-active']
|
|
27
|
+
var ACTIVATE_EVENT = 'dsh-panel-activate'
|
|
28
|
+
var PANEL_NAME = 'webchat'
|
|
29
|
+
var STYLE_ID = 'dsh-webchat-style'
|
|
30
|
+
|
|
31
|
+
var ZH = typeof navigator === 'object' && typeof navigator.language === 'string'
|
|
32
|
+
&& navigator.language.toLowerCase().indexOf('zh') === 0
|
|
33
|
+
|
|
34
|
+
/** Surface copy; the plugin ships both languages rather than a locale service. */
|
|
35
|
+
var T = ZH
|
|
36
|
+
? {
|
|
37
|
+
label: 'DeepSeek 网页',
|
|
38
|
+
tooltip: '在窗口里打开 chat.deepseek.com',
|
|
39
|
+
title: 'DeepSeek 网页',
|
|
40
|
+
lede: '官方网页版就是完整的客户端:模型选择、深度思考、智能搜索、历史记录、附件上传都在里面。这里只负责把它打开。',
|
|
41
|
+
open: '打开 chat.deepseek.com',
|
|
42
|
+
reopen: '回到已打开的窗口',
|
|
43
|
+
hint: '首次使用请在打开的这个窗口里登录 DeepSeek,之后登录态会保留。',
|
|
44
|
+
checking: '检查中…',
|
|
45
|
+
closed: '窗口未打开',
|
|
46
|
+
opened: '窗口已打开',
|
|
47
|
+
busy: '正在打开…',
|
|
48
|
+
done: '已打开',
|
|
49
|
+
failed: '打开失败',
|
|
133
50
|
}
|
|
134
|
-
|
|
135
|
-
|
|
51
|
+
: {
|
|
52
|
+
label: 'DeepSeek Web',
|
|
53
|
+
tooltip: 'Open chat.deepseek.com in a window',
|
|
54
|
+
title: 'DeepSeek Web',
|
|
55
|
+
lede: 'The official web app is the whole client: model picker, deep think, smart search, history and attachments all live there. This plugin only opens it.',
|
|
56
|
+
open: 'Open chat.deepseek.com',
|
|
57
|
+
reopen: 'Focus the open window',
|
|
58
|
+
hint: 'Sign in once inside the window it opens; the session is kept afterwards.',
|
|
59
|
+
checking: 'Checking…',
|
|
60
|
+
closed: 'Window is closed',
|
|
61
|
+
opened: 'Window is open',
|
|
62
|
+
busy: 'Opening…',
|
|
63
|
+
done: 'Opened',
|
|
64
|
+
failed: 'Could not open',
|
|
136
65
|
}
|
|
137
|
-
|
|
138
|
-
|
|
66
|
+
|
|
67
|
+
/** Panel and entry styling, scoped to this plugin's own attributes. */
|
|
68
|
+
var CSS = [
|
|
69
|
+
"[data-pane='conversation'],[class*='centerCol']{position:relative}",
|
|
70
|
+
'[' + VIEW_ATTR + ']{position:absolute;inset:0;display:none;z-index:60;background:var(--dsw-alias-bg-base)}',
|
|
71
|
+
'html[' + ACTIVE_ATTR + ']:not([data-dsh-taskboard-active]):not([data-dsh-ssh-active]) [' + VIEW_ATTR + ']{display:block}',
|
|
72
|
+
"html[" + ACTIVE_ATTR + "]:not([data-dsh-taskboard-active]):not([data-dsh-ssh-active]) [data-pane='conversation'] > :not([" + VIEW_ATTR + ']),',
|
|
73
|
+
'html[' + ACTIVE_ATTR + "]:not([data-dsh-taskboard-active]):not([data-dsh-ssh-active]) [class*='centerCol'] > :not([" + VIEW_ATTR + ']){display:none !important}',
|
|
74
|
+
'[' + ENTRY_ATTR + ']{display:flex;align-items:center;gap:8px;width:100%;height:32px;padding:0 12px;background:transparent;border:none;border-radius:8px;color:var(--dsw-alias-label-secondary);cursor:pointer;font-size:13px;white-space:nowrap}',
|
|
75
|
+
'[' + ENTRY_ATTR + ']:hover{background:var(--dsw-specific-sidebar-nav-item-hover);color:var(--dsw-alias-label-primary)}',
|
|
76
|
+
'[' + ENTRY_ATTR + '][data-active]{background:var(--dsw-specific-sidebar-nav-item-active);color:var(--dsw-alias-label-primary);font-weight:600}',
|
|
77
|
+
'[' + ENTRY_ATTR + '] [data-part=icon]{display:inline-flex;align-items:center;justify-content:center;flex:none}',
|
|
78
|
+
'[' + ENTRY_ATTR + '] [data-part=label]{overflow:hidden;text-overflow:ellipsis}',
|
|
79
|
+
'[data-dsh-frame][data-sidebar-collapsed] [' + ENTRY_ATTR + ']{justify-content:center;padding:0}',
|
|
80
|
+
'[data-dsh-frame][data-sidebar-collapsed] [' + ENTRY_ATTR + '] [data-part=label]{display:none}',
|
|
81
|
+
'.dsh-wc-panel{box-sizing:border-box;display:flex;flex-direction:column;height:100%;min-height:0;padding:18px 20px;gap:14px;background:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-primary);font-family:var(--dsw-font-family);overflow:auto}',
|
|
82
|
+
'.dsh-wc-head{display:flex;align-items:center;gap:10px;flex:none}',
|
|
83
|
+
'.dsh-wc-title{margin:0;font-size:16px;font-weight:700;letter-spacing:-.01em}',
|
|
84
|
+
'.dsh-wc-chip{margin-left:auto;display:inline-flex;align-items:center;gap:7px;padding:4px 10px;border:1px solid var(--dsw-alias-border-l1);border-radius:999px;font-size:11.5px;color:var(--dsw-alias-label-secondary)}',
|
|
85
|
+
'.dsh-wc-dot{width:7px;height:7px;border-radius:50%;background:var(--dsw-alias-label-tertiary);flex:none}',
|
|
86
|
+
".dsh-wc-dot[data-state=ok]{background:var(--dsw-alias-state-success,#22c55e)}",
|
|
87
|
+
".dsh-wc-dot[data-state=bad]{background:var(--dsw-alias-state-danger,#ef4444)}",
|
|
88
|
+
".dsh-wc-dot[data-state=busy]{background:var(--dsw-alias-state-info,#3b82f6)}",
|
|
89
|
+
'.dsh-wc-card{margin:auto;max-width:520px;display:flex;flex-direction:column;align-items:center;gap:14px;text-align:center;padding:26px 22px;border:1px solid var(--dsw-alias-border-l1);border-radius:14px;background:var(--dsw-alias-bg-layer1,var(--dsw-alias-bg-elevated))}',
|
|
90
|
+
'.dsh-wc-lede{margin:0;font-size:13px;line-height:1.65;color:var(--dsw-alias-label-secondary)}',
|
|
91
|
+
'.dsh-wc-url{font-family:var(--dsw-font-mono,ui-monospace,Consolas,monospace);font-size:12px;color:var(--dsw-alias-label-tertiary)}',
|
|
92
|
+
'.dsh-wc-button{display:inline-flex;align-items:center;gap:6px;padding:9px 18px;font-size:13.5px;font-weight:600;border:1px solid transparent;border-radius:9px;background:var(--dsw-alias-state-business-primary,#4f6bfa);color:#fff;cursor:pointer}',
|
|
93
|
+
'.dsh-wc-button:hover:not(:disabled){filter:brightness(1.08)}',
|
|
94
|
+
'.dsh-wc-button:disabled{opacity:.5;cursor:not-allowed}',
|
|
95
|
+
'.dsh-wc-hint{margin:0;font-size:12px;line-height:1.6;color:var(--dsw-alias-label-tertiary)}',
|
|
96
|
+
'.dsh-wc-result{margin:0;min-height:18px;font-size:12px;color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere}',
|
|
97
|
+
".dsh-wc-result[data-state=bad]{color:var(--dsw-alias-state-danger,#ef4444)}",
|
|
98
|
+
].join('\n')
|
|
99
|
+
|
|
100
|
+
/** Inject the stylesheet once. */
|
|
101
|
+
function ensureStyle() {
|
|
102
|
+
if (document.getElementById(STYLE_ID) !== null) return
|
|
103
|
+
var tag = document.createElement('style')
|
|
104
|
+
tag.id = STYLE_ID
|
|
105
|
+
tag.textContent = CSS
|
|
106
|
+
document.head.appendChild(tag)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Tiny observable for the panel's open state. */
|
|
110
|
+
function createController() {
|
|
111
|
+
var open = false
|
|
112
|
+
var listeners = new Set()
|
|
113
|
+
var notify = function () {
|
|
114
|
+
listeners.forEach(function (listener) {
|
|
115
|
+
try {
|
|
116
|
+
listener()
|
|
117
|
+
} catch (error) {
|
|
118
|
+
console.warn('[dsh-webchat] listener failed:', error)
|
|
119
|
+
}
|
|
120
|
+
})
|
|
139
121
|
}
|
|
140
|
-
|
|
141
|
-
|
|
122
|
+
return {
|
|
123
|
+
isOpen: function () { return open },
|
|
124
|
+
subscribe: function (listener) {
|
|
125
|
+
listeners.add(listener)
|
|
126
|
+
return function () { listeners.delete(listener) }
|
|
127
|
+
},
|
|
128
|
+
toggle: function () { open = !open; notify() },
|
|
129
|
+
close: function () { if (!open) return; open = false; notify() },
|
|
142
130
|
}
|
|
143
|
-
};
|
|
144
|
-
//#endregion
|
|
145
|
-
//#region src/client/locales.ts
|
|
146
|
-
/**
|
|
147
|
-
* dsh-webchat surface copy: zh is the key source, en mirrors every key. The
|
|
148
|
-
* key union (not an object shape) is what the locale machinery wants —
|
|
149
|
-
* LocaleKeysOf<N> intersects the namespace with `string`, so the namespace
|
|
150
|
-
* value must itself be the key union (same pattern as dsh-ssh).
|
|
151
|
-
*/
|
|
152
|
-
const zh = {
|
|
153
|
-
"entry.label": "网页聊天",
|
|
154
|
-
"entry.tooltip": "DeepSeek 网页端聊天(deepseek-chat,Codex ChatGPT 模式)",
|
|
155
|
-
"panel.title": "DeepSeek 网页端聊天",
|
|
156
|
-
"status.engine.stopped": "浏览器未启动",
|
|
157
|
-
"status.engine.launching": "浏览器启动中…",
|
|
158
|
-
"status.engine.ready": "浏览器就绪",
|
|
159
|
-
"status.engine.error": "浏览器错误",
|
|
160
|
-
"status.loggedIn": "已登录",
|
|
161
|
-
"status.notLoggedIn": "未登录",
|
|
162
|
-
"status.unknown": "状态未知",
|
|
163
|
-
"action.openLogin": "打开登录窗口",
|
|
164
|
-
"action.closeBrowser": "关闭浏览器",
|
|
165
|
-
"action.newChat": "新对话",
|
|
166
|
-
"action.recover": "从网页恢复",
|
|
167
|
-
"action.refresh": "刷新",
|
|
168
|
-
"action.send": "发送",
|
|
169
|
-
"action.stop": "停止生成",
|
|
170
|
-
"action.transferToHarness": "转移到 Harness",
|
|
171
|
-
"action.exportFile": "导出为文件",
|
|
172
|
-
"transfer.mode.distill": "蒸馏为任务简报",
|
|
173
|
-
"transfer.mode.raw": "原始记录",
|
|
174
|
-
"transfer.mode.hint": "蒸馏:用 harness LLM 把对话提炼成可执行任务简报;原始记录:原样放入新会话首条消息",
|
|
175
|
-
"transfer.workspace.label": "目标工作区",
|
|
176
|
-
"transfer.workspace.ungrouped": "未分组",
|
|
177
|
-
"transfer.workspace.new": "新建工作区…",
|
|
178
|
-
"transfer.workspace.hint": "转移到所选工作区;未分组则不归入任何工作区",
|
|
179
|
-
"transfer.workspace.created": "已创建并选中工作区 {title}",
|
|
180
|
-
"transfer.workspace.createFailed": "创建工作区失败:{error}",
|
|
181
|
-
"transfer.session.label": "延续到会话",
|
|
182
|
-
"transfer.session.hint": "选择已有 harness 会话以延续(把简报作为新消息追加);默认新建会话",
|
|
183
|
-
"transfer.session.new": "新建会话",
|
|
184
|
-
"transfer.attached.failed": "会话已创建({sessionId}),但归入工作区失败,现为未分组",
|
|
185
|
-
"handoff.eyebrow": "交付到 Harness",
|
|
186
|
-
"toggle.deepThink": "深度思考",
|
|
187
|
-
"toggle.search": "智能搜索",
|
|
188
|
-
"toggle.deepThink.hint": "开启 R1 深度思考",
|
|
189
|
-
"toggle.search.hint": "开启联网检索",
|
|
190
|
-
"sidebar.empty": "暂无对话",
|
|
191
|
-
"chats.count": "{count} 条",
|
|
192
|
-
"role.you": "你",
|
|
193
|
-
"chat.rename": "重命名",
|
|
194
|
-
"chat.delete": "删除",
|
|
195
|
-
"chat.delete.confirm": "确认删除?",
|
|
196
|
-
"chat.clearAll": "清空全部",
|
|
197
|
-
"chat.clearAll.confirm": "确认清空全部?",
|
|
198
|
-
"chat.rename.placeholder": "输入新标题…",
|
|
199
|
-
"chat.rename.ok": "确定",
|
|
200
|
-
"chat.rename.cancel": "取消",
|
|
201
|
-
"chat.manage.hint": "管理本地保存的聊天记录(不影响网页端)",
|
|
202
|
-
"toast.rename.done": "已重命名",
|
|
203
|
-
"toast.rename.failed": "重命名失败:{error}",
|
|
204
|
-
"toast.delete.done": "已删除该对话",
|
|
205
|
-
"toast.delete.failed": "删除失败:{error}",
|
|
206
|
-
"toast.clear.done": "已清空全部对话",
|
|
207
|
-
"toast.clear.failed": "清空失败:{error}",
|
|
208
|
-
"model.deepseek-chat": "DeepSeek",
|
|
209
|
-
"model.deepseek-reasoner": "DeepSeek",
|
|
210
|
-
"composer.placeholder": "给 DeepSeek 发送消息…(将发送到网页端)",
|
|
211
|
-
"composer.hint": "Enter 发送 · Shift+Enter 换行",
|
|
212
|
-
"composer.busy": "正在生成回复…",
|
|
213
|
-
"composer.notLoggedIn": "请先登录 DeepSeek 网页端",
|
|
214
|
-
"composer.attach": "图片",
|
|
215
|
-
"composer.attach.hint": "添加本地图片(绝对路径,逗号/换行分隔)",
|
|
216
|
-
"composer.attach.placeholder": "图片绝对路径,逗号或换行分隔",
|
|
217
|
-
"empty.eyebrow": "DeepSeek 网页端 · 桥接开发",
|
|
218
|
-
"empty.title": "先聊,再交付",
|
|
219
|
-
"empty.body": "登录网页端即可开始对话;任何对话都能蒸馏成任务简报,转入 Harness 会话继续开发。",
|
|
220
|
-
"transfer.done": "已创建 Harness 会话 {sessionId},正在为你打开…",
|
|
221
|
-
"transfer.continued": "已延续 Harness 会话 {sessionId},正在为你打开…",
|
|
222
|
-
"transfer.failed": "转移到 Harness 失败:{error}",
|
|
223
|
-
"export.done": "已导出到工作区文件:{filePath}",
|
|
224
|
-
"export.failed": "导出失败:{error}",
|
|
225
|
-
"recover.hint": "列出网页端尚未导入本地的会话",
|
|
226
|
-
"recover.empty": "网页端没有未导入的会话",
|
|
227
|
-
"recover.done": "已恢复「{title}」",
|
|
228
|
-
"msg.empty": "(空)",
|
|
229
|
-
"msg.error": "(该回复可能不完整:{error})",
|
|
230
|
-
"streaming": "生成中"
|
|
231
|
-
};
|
|
232
|
-
/** Bilingual balance enforced: en must cover every zh key. */
|
|
233
|
-
const en = {
|
|
234
|
-
"entry.label": "Web Chat",
|
|
235
|
-
"entry.tooltip": "DeepSeek web chat (deepseek-chat, Codex ChatGPT mode)",
|
|
236
|
-
"panel.title": "DeepSeek Web Chat",
|
|
237
|
-
"status.engine.stopped": "Browser not started",
|
|
238
|
-
"status.engine.launching": "Starting browser…",
|
|
239
|
-
"status.engine.ready": "Browser ready",
|
|
240
|
-
"status.engine.error": "Browser error",
|
|
241
|
-
"status.loggedIn": "Logged in",
|
|
242
|
-
"status.notLoggedIn": "Not logged in",
|
|
243
|
-
"status.unknown": "Unknown",
|
|
244
|
-
"action.openLogin": "Open login window",
|
|
245
|
-
"action.closeBrowser": "Close browser",
|
|
246
|
-
"action.newChat": "New chat",
|
|
247
|
-
"action.recover": "Recover from web",
|
|
248
|
-
"action.refresh": "Refresh",
|
|
249
|
-
"action.send": "Send",
|
|
250
|
-
"action.stop": "Stop",
|
|
251
|
-
"action.transferToHarness": "Transfer to Harness",
|
|
252
|
-
"action.exportFile": "Export to file",
|
|
253
|
-
"transfer.mode.distill": "Distill to task brief",
|
|
254
|
-
"transfer.mode.raw": "Raw transcript",
|
|
255
|
-
"transfer.mode.hint": "Distill: condense the chat into an executable brief via the harness LLM; Raw: replay the full transcript as the session's first message",
|
|
256
|
-
"transfer.workspace.label": "Target workspace",
|
|
257
|
-
"transfer.workspace.ungrouped": "Ungrouped",
|
|
258
|
-
"transfer.workspace.new": "New workspace…",
|
|
259
|
-
"transfer.workspace.hint": "Transfer into the chosen workspace; ungrouped leaves it out of any workspace",
|
|
260
|
-
"transfer.workspace.created": "Created and selected workspace {title}",
|
|
261
|
-
"transfer.workspace.createFailed": "Create workspace failed: {error}",
|
|
262
|
-
"transfer.session.label": "Continue into session",
|
|
263
|
-
"transfer.session.hint": "Continue into an existing harness session (append the brief as a new message); default creates a new one",
|
|
264
|
-
"transfer.session.new": "New session",
|
|
265
|
-
"transfer.attached.failed": "Session created ({sessionId}) but attaching to workspace failed; it is ungrouped",
|
|
266
|
-
"handoff.eyebrow": "Handoff to Harness",
|
|
267
|
-
"toggle.deepThink": "DeepThink",
|
|
268
|
-
"toggle.search": "Search",
|
|
269
|
-
"toggle.deepThink.hint": "Enable R1 deep think",
|
|
270
|
-
"toggle.search.hint": "Enable web search",
|
|
271
|
-
"sidebar.empty": "No chats",
|
|
272
|
-
"chats.count": "{count} msgs",
|
|
273
|
-
"role.you": "You",
|
|
274
|
-
"chat.rename": "Rename",
|
|
275
|
-
"chat.delete": "Delete",
|
|
276
|
-
"chat.delete.confirm": "Delete?",
|
|
277
|
-
"chat.clearAll": "Clear all",
|
|
278
|
-
"chat.clearAll.confirm": "Clear all?",
|
|
279
|
-
"chat.rename.placeholder": "New title…",
|
|
280
|
-
"chat.rename.ok": "OK",
|
|
281
|
-
"chat.rename.cancel": "Cancel",
|
|
282
|
-
"chat.manage.hint": "Manage locally saved transcripts (does not affect the web)",
|
|
283
|
-
"toast.rename.done": "Renamed",
|
|
284
|
-
"toast.rename.failed": "Rename failed: {error}",
|
|
285
|
-
"toast.delete.done": "Chat deleted",
|
|
286
|
-
"toast.delete.failed": "Delete failed: {error}",
|
|
287
|
-
"toast.clear.done": "All chats cleared",
|
|
288
|
-
"toast.clear.failed": "Clear failed: {error}",
|
|
289
|
-
"model.deepseek-chat": "DeepSeek",
|
|
290
|
-
"model.deepseek-reasoner": "DeepSeek",
|
|
291
|
-
"composer.placeholder": "Message DeepSeek… (sent via the web)",
|
|
292
|
-
"composer.hint": "Enter to send · Shift+Enter for newline",
|
|
293
|
-
"composer.busy": "Generating…",
|
|
294
|
-
"composer.notLoggedIn": "Sign in to DeepSeek web first",
|
|
295
|
-
"composer.attach": "Images",
|
|
296
|
-
"composer.attach.hint": "Attach local images (absolute paths, comma/newline separated)",
|
|
297
|
-
"composer.attach.placeholder": "Image absolute paths (comma or newline separated)",
|
|
298
|
-
"empty.eyebrow": "DeepSeek Web · Bridge to Code",
|
|
299
|
-
"empty.title": "Chat first, hand off later",
|
|
300
|
-
"empty.body": "Sign in to the web and start chatting; any chat can be distilled into a task brief and handed off to Harness.",
|
|
301
|
-
"transfer.done": "Created Harness session {sessionId}; opening it…",
|
|
302
|
-
"transfer.continued": "Continued Harness session {sessionId}; opening it…",
|
|
303
|
-
"transfer.failed": "Transfer failed: {error}",
|
|
304
|
-
"export.done": "Exported to workspace file: {filePath}",
|
|
305
|
-
"export.failed": "Export failed: {error}",
|
|
306
|
-
"recover.hint": "List web conversations not yet imported locally",
|
|
307
|
-
"recover.empty": "No unimported web conversations",
|
|
308
|
-
"recover.done": "Recovered \"{title}\"",
|
|
309
|
-
"msg.empty": "(empty)",
|
|
310
|
-
"msg.error": "(reply may be incomplete: {error})",
|
|
311
|
-
"streaming": "generating"
|
|
312
|
-
};
|
|
313
|
-
//#endregion
|
|
314
|
-
//#region \0dsh-css:src/client/panel/panel.module.css.mjs
|
|
315
|
-
const css = "[data-pane=conversation],[class*=centerCol]{position:relative}[data-dsh-webchat-view]{z-index:60;background:var(--dsw-alias-bg-base);display:none;position:absolute;inset:0}html[data-dsh-webchat-active]:not([data-dsh-taskboard-active]):not([data-dsh-ssh-active]) [data-dsh-webchat-view]{display:block}html[data-dsh-webchat-active]:not([data-dsh-taskboard-active]):not([data-dsh-ssh-active]) [data-pane=conversation]>:not([data-dsh-webchat-view]),html[data-dsh-webchat-active]:not([data-dsh-taskboard-active]):not([data-dsh-ssh-active]) [class*=centerCol]>:not([data-dsh-webchat-view]){display:none!important}.jbF1yq_entry{width:100%;height:32px;color:var(--dsw-alias-label-secondary);cursor:pointer;white-space:nowrap;background:0 0;border:none;border-radius:8px;align-items:center;gap:8px;padding:0 12px;font-size:13px;display:flex}.jbF1yq_entry:hover{background:var(--dsw-specific-sidebar-nav-item-hover);color:var(--dsw-alias-label-primary)}.jbF1yq_entry[data-active]{background:var(--dsw-specific-sidebar-nav-item-active);color:var(--dsw-alias-label-primary);font-weight:600}.jbF1yq_entryIcon{flex:none;justify-content:center;align-items:center;display:inline-flex}.jbF1yq_entryLabel{text-overflow:ellipsis;overflow:hidden}[data-dsh-frame][data-sidebar-collapsed] .jbF1yq_entry{justify-content:center;width:100%;padding:0}[data-dsh-frame][data-sidebar-collapsed] .jbF1yq_entryLabel{display:none}.jbF1yq_view{box-sizing:border-box;overflow:hidden}.jbF1yq_panel,.jbF1yq_panel *{box-sizing:border-box}.jbF1yq_panel{background:var(--dsw-alias-bg-base);min-width:0;height:100%;min-height:0;color:var(--dsw-alias-label-primary);font-family:var(--dsw-font-family);flex-direction:column;gap:8px;padding:12px 14px 14px;display:flex}.jbF1yq_panelHeader{flex-wrap:wrap;flex:none;align-items:center;gap:10px;display:flex}.jbF1yq_panelTitle{letter-spacing:-.01em;color:var(--dsw-alias-label-primary);white-space:nowrap;margin:0;font-size:15px;font-weight:700}.jbF1yq_headerSpacer{flex:1;min-width:8px}.jbF1yq_statusChip{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer1,var(--dsw-alias-bg-elevated));color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;align-items:center;gap:7px;padding:4px 10px;font-size:11.5px;display:inline-flex}.jbF1yq_statusText{text-overflow:ellipsis;white-space:nowrap;min-width:0;font-family:var(--dsw-font-mono,ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);letter-spacing:.01em;overflow:hidden}.jbF1yq_statusDot{border-radius:50%;flex:none;width:7px;height:7px}.jbF1yq_statusDot[data-state=ok]{background:var(--dsw-alias-state-success,#22c55e)}.jbF1yq_statusDot[data-state=warn]{background:var(--dsw-alias-state-warning,#f59e0b)}.jbF1yq_statusDot[data-state=bad]{background:var(--dsw-alias-state-danger,#ef4444)}.jbF1yq_statusDot[data-state=busy]{background:var(--dsw-alias-state-info,#3b82f6);animation:1.2s ease-in-out infinite jbF1yq_dshWcPulse}@keyframes jbF1yq_dshWcPulse{0%,to{opacity:1}50%{opacity:.35}}.jbF1yq_panelBody{flex:1;gap:10px;min-height:0;display:flex}.jbF1yq_sidebar{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer1,var(--dsw-alias-bg-elevated));border-radius:10px;flex-direction:column;flex:0 0 172px;gap:6px;min-width:0;padding:8px;display:flex;overflow:hidden}.jbF1yq_sidebarNew{justify-content:center;width:100%}.jbF1yq_recoverList{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer1,var(--dsw-alias-bg-elevated));border-radius:8px;flex-direction:column;gap:2px;max-height:160px;padding:4px;display:flex;overflow-y:auto}.jbF1yq_recoverItem{width:100%;color:var(--dsw-alias-label-primary);font:inherit;text-align:left;cursor:pointer;background:0 0;border:none;border-radius:6px;padding:6px 8px;font-size:12.5px;display:block}.jbF1yq_recoverItem:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#8080801f)}.jbF1yq_recoverItem:disabled{opacity:.5;cursor:default}.jbF1yq_recoverTitle{text-overflow:ellipsis;white-space:nowrap;max-width:100%;display:inline-block;overflow:hidden}.jbF1yq_recoverEmpty{color:var(--dsw-alias-label-tertiary);padding:6px 8px;font-size:12px}.jbF1yq_sidebarList{flex-direction:column;flex:1;gap:2px;min-height:0;display:flex;overflow-y:auto}.jbF1yq_sidebarItem{border-radius:7px;align-items:center;gap:2px;display:flex}.jbF1yq_sidebarItem:hover{background:var(--dsw-alias-interactive-bg-hover,#8080801f)}.jbF1yq_sidebarItem[data-active]{background:var(--dsw-specific-sidebar-nav-item-active,var(--dsw-alias-interactive-bg-hover))}.jbF1yq_sidebarRow{min-width:0;color:var(--dsw-alias-label-primary);cursor:pointer;text-align:left;background:0 0;border:none;border-radius:7px;flex-direction:column;flex:1;align-items:flex-start;gap:1px;padding:6px 8px;display:flex}.jbF1yq_sidebarTitle{text-overflow:ellipsis;white-space:nowrap;width:100%;font-size:12.5px;line-height:1.3;display:block;overflow:hidden}.jbF1yq_sidebarMeta{font-family:var(--dsw-font-mono,ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);color:var(--dsw-alias-label-tertiary);font-size:10px}.jbF1yq_sidebarActions{opacity:0;flex:none;gap:2px;padding-right:4px;transition:opacity .12s;display:flex}.jbF1yq_sidebarItem:hover .jbF1yq_sidebarActions,.jbF1yq_sidebarItem[data-active] .jbF1yq_sidebarActions{opacity:1}.jbF1yq_sidebarAction{width:22px;height:22px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:6px;justify-content:center;align-items:center;padding:0;display:inline-flex}.jbF1yq_sidebarAction:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.jbF1yq_sidebarActionDanger{background:var(--dsw-alias-state-danger,#ef4444);color:#fff;cursor:pointer;white-space:nowrap;border:none;border-radius:6px;justify-content:center;align-items:center;height:22px;padding:0 6px;font-size:11px;display:inline-flex}.jbF1yq_sidebarRename{flex:1;align-items:center;gap:2px;min-width:0;padding:2px;display:flex}.jbF1yq_sidebarRenameInput{min-width:0;color:var(--dsw-alias-label-primary);background:var(--dsw-specific-input-major,var(--dsw-alias-bg-layer1));border:1px solid var(--dsw-alias-state-business-primary,#4f6bfa);border-radius:6px;outline:none;flex:1;padding:4px 6px;font-size:12px}.jbF1yq_sidebarEmpty{text-align:center;color:var(--dsw-alias-label-tertiary);margin:auto;padding:12px 8px;font-size:12px}.jbF1yq_sidebarClear{justify-content:center;width:100%}.jbF1yq_main{flex-direction:column;flex:1;gap:8px;min-width:0;min-height:0;display:flex;overflow:hidden}.jbF1yq_composerToolbar{flex:none;align-items:center;gap:6px;padding:2px 12px;display:flex}.jbF1yq_toggleButton{appearance:none;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);font-family:var(--dsw-font-mono,ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);cursor:pointer;white-space:nowrap;background:0 0;border-radius:999px;align-items:center;gap:5px;padding:4px 11px;font-size:11.5px;transition:color .12s,border-color .12s,background .12s;display:inline-flex}.jbF1yq_toggleButton:hover:not(:disabled){color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-border-l1)}.jbF1yq_toggleButton:disabled{opacity:.45;cursor:not-allowed}.jbF1yq_toggleButton[data-active]{color:var(--dsw-alias-state-business-primary,#4f6bfa);border-color:var(--dsw-alias-state-business-primary,#4f6bfa);background:var(--dsw-alias-interactive-bg-hover,#8080801a);font-weight:600}.jbF1yq_segmented{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base);border-radius:8px;flex:none;align-items:stretch;gap:2px;padding:2px;display:inline-flex}.jbF1yq_segmentedButton{appearance:none;font-family:var(--dsw-font-mono,ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);color:var(--dsw-alias-label-secondary);cursor:pointer;white-space:nowrap;background:0 0;border:none;border-radius:6px;padding:4px 10px;font-size:11.5px}.jbF1yq_segmentedButton:hover:not([data-active]):not(:disabled){color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}.jbF1yq_segmentedButton[data-active]{background:var(--dsw-alias-bg-elevated,var(--dsw-alias-bg-layer1));color:var(--dsw-alias-state-business-primary,#4f6bfa);font-weight:600;box-shadow:0 1px 2px #0000001f}.jbF1yq_button{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-specific-input-major,var(--dsw-alias-bg-layer1));color:var(--dsw-alias-label-primary);cursor:pointer;white-space:nowrap;border-radius:8px;align-items:center;gap:6px;padding:6px 12px;font-size:12.5px;transition:background .12s,filter .12s;display:inline-flex}.jbF1yq_button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.jbF1yq_button:disabled{opacity:.45;cursor:not-allowed}.jbF1yq_buttonPrimary{background:var(--dsw-alias-state-business-primary,#4f6bfa);color:#fff;border-color:#0000}.jbF1yq_buttonPrimary:hover:not(:disabled){filter:brightness(1.08)}.jbF1yq_buttonDanger{background:var(--dsw-alias-state-danger,#ef4444);color:#fff;border-color:#0000}.jbF1yq_buttonGhost{color:var(--dsw-alias-label-secondary);background:0 0;border-color:#0000;padding:5px 8px;font-size:12px}.jbF1yq_buttonGhost:hover:not(:disabled){color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}.jbF1yq_messageList{flex-direction:column;flex:1;gap:14px;min-height:0;padding:4px 2px;display:flex;overflow-y:auto}.jbF1yq_messageRow{flex-direction:column;gap:5px;max-width:92%;display:flex}.jbF1yq_messageRow[data-role=user]{align-self:flex-end;align-items:flex-end}.jbF1yq_messageRow[data-role=assistant]{align-self:flex-start;align-items:flex-start}.jbF1yq_messageMeta{font-family:var(--dsw-font-mono,ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);align-items:baseline;gap:8px;padding:0 4px;font-size:10.5px;line-height:1;display:flex}.jbF1yq_messageSpeaker{color:var(--dsw-alias-label-secondary);font-weight:600}.jbF1yq_messageTime{color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums}.jbF1yq_streamBadge{color:var(--dsw-alias-state-info,#3b82f6)}.jbF1yq_messageBubble{overflow-wrap:anywhere;border-radius:12px;min-width:0;max-width:100%;padding:10px 14px;font-size:13.5px;line-height:1.6}.jbF1yq_messageRow[data-role=user] .jbF1yq_messageBubble{background:var(--dsw-alias-state-business-primary,#4f6bfa);color:#fff;border-bottom-right-radius:4px}.jbF1yq_messageRow[data-role=assistant] .jbF1yq_messageBubble{background:var(--dsw-alias-bg-layer1,var(--dsw-alias-bg-elevated));border:1px solid var(--dsw-alias-border-l1);border-bottom-left-radius:4px}.jbF1yq_messageError{color:var(--dsw-alias-state-danger,#ef4444);font-size:11px}.jbF1yq_emptyState{text-align:center;flex-direction:column;align-items:center;gap:10px;max-width:420px;margin:auto;padding:24px 20px;display:flex}.jbF1yq_emptyEyebrow{font-family:var(--dsw-font-mono,ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);letter-spacing:.12em;text-transform:uppercase;color:var(--dsw-alias-state-business-primary,#4f6bfa);font-size:10.5px}.jbF1yq_emptyTitle{letter-spacing:-.01em;color:var(--dsw-alias-label-primary);margin:0;font-size:17px;font-weight:700}.jbF1yq_emptyBody{color:var(--dsw-alias-label-secondary);margin:0;font-size:13px;line-height:1.6}.jbF1yq_composer{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-specific-input-major,var(--dsw-alias-bg-layer1));border-radius:12px;flex-direction:column;flex:none;transition:border-color .12s;display:flex}.jbF1yq_composer:focus-within{border-color:var(--dsw-alias-state-business-primary,#4f6bfa)}.jbF1yq_composerInput{resize:none;min-height:44px;max-height:180px;color:var(--dsw-alias-label-primary);font:inherit;background:0 0;border:none;outline:none;flex:1;padding:10px 12px 6px;font-size:13.5px;line-height:1.5}.jbF1yq_composerInput::placeholder{color:var(--dsw-alias-label-tertiary)}.jbF1yq_attachInput{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer1,var(--dsw-alias-bg-elevated));color:var(--dsw-alias-label-primary);font:inherit;border-radius:8px;margin:0 12px 4px;padding:7px 10px;font-size:12.5px}.jbF1yq_attachInput::placeholder{color:var(--dsw-alias-label-tertiary)}.jbF1yq_composerFooter{align-items:center;gap:8px;padding:0 8px 8px 12px;display:flex}.jbF1yq_composerHint{font-family:var(--dsw-font-mono,ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);color:var(--dsw-alias-label-tertiary);letter-spacing:.02em;font-size:10.5px}.jbF1yq_composerSpacer{flex:1}.jbF1yq_handoffDock{border:1px solid var(--dsw-alias-border-l2);border-left:2px solid var(--dsw-alias-state-business-primary,#4f6bfa);background:var(--dsw-alias-bg-layer1,var(--dsw-alias-bg-elevated));border-radius:10px;flex-wrap:wrap;flex:none;align-items:center;gap:10px;padding:9px 12px;display:flex}.jbF1yq_handoffEyebrow{font-family:var(--dsw-font-mono,ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);letter-spacing:.1em;text-transform:uppercase;color:var(--dsw-alias-label-tertiary);white-space:nowrap;flex:none;font-size:10.5px}.jbF1yq_handoffSpacer{flex:1;min-width:8px}.jbF1yq_workspaceSelect{appearance:none;max-width:280px;font-size:12px;font-family:var(--dsw-font-mono,ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);color:var(--dsw-alias-label-primary);background:var(--dsw-specific-input-major,var(--dsw-alias-bg-layer1));border:1px solid var(--dsw-alias-border-l2);cursor:pointer;border-radius:8px;flex:none;padding:6px 10px}.jbF1yq_workspaceSelect:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#4f6bfa);outline-offset:1px}.jbF1yq_markdown{font-size:13.5px;line-height:1.65}.jbF1yq_markdown p{margin:.4em 0}.jbF1yq_markdown pre{background:var(--dsw-alias-bg-deep,#00000073);border-radius:8px;margin:.6em 0;padding:10px 12px;font-size:12.5px;line-height:1.5;position:relative;overflow-x:auto}.jbF1yq_markdown code{font-family:var(--dsw-font-mono,ui-monospace, SFMono-Regular, Menlo, monospace);font-size:.92em}.jbF1yq_markdown :not(pre)>code{background:var(--dsw-alias-interactive-bg-hover,#8080802e);border-radius:5px;padding:1px 5px}.jbF1yq_markdown h1,.jbF1yq_markdown h2,.jbF1yq_markdown h3,.jbF1yq_markdown h4{margin:.8em 0 .4em;line-height:1.3}.jbF1yq_markdown ul,.jbF1yq_markdown ol{margin:.4em 0;padding-left:1.4em}.jbF1yq_markdown blockquote{border-left:3px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);margin:.5em 0;padding:2px 12px}.jbF1yq_tableWrap{overflow-x:auto}.jbF1yq_markdown table{border-collapse:collapse;margin:.6em 0;font-size:12.5px}.jbF1yq_markdown th,.jbF1yq_markdown td{border:1px solid var(--dsw-alias-border-l2);padding:4px 10px}.jbF1yq_markdown sup.jbF1yq_citation{font-family:var(--dsw-font-mono,ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);color:var(--dsw-alias-state-business-primary,#4f6bfa);background:var(--dsw-alias-interactive-bg-hover,#80808029);vertical-align:super;white-space:nowrap;border-radius:999px;margin:0 2px;padding:0 4px;font-size:9.5px;font-weight:600;line-height:1.4}.jbF1yq_markdown a{color:var(--dsw-alias-state-business-primary,#4f6bfa)}.jbF1yq_markdown details{background:var(--dsw-alias-interactive-bg-hover,#8080801f);border-radius:8px;margin:.5em 0;padding:6px 10px;font-size:12.5px}.jbF1yq_markdown summary{cursor:pointer;color:var(--dsw-alias-label-secondary);font-weight:600}.jbF1yq_toast{z-index:200;background:var(--dsw-alias-bg-elevated);border:1px solid var(--dsw-alias-border-l2);max-width:420px;color:var(--dsw-alias-label-primary);border-radius:10px;padding:10px 14px;font-size:13px;animation:.18s ease-out jbF1yq_dshWcToast;position:fixed;bottom:20px;right:20px;box-shadow:0 8px 24px #0000002e}@keyframes jbF1yq_dshWcToast{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}.jbF1yq_button:focus-visible,.jbF1yq_segmentedButton:focus-visible,.jbF1yq_toggleButton:focus-visible,.jbF1yq_sidebarRow:focus-visible,.jbF1yq_sidebarAction:focus-visible,.jbF1yq_sidebarActionDanger:focus-visible,.jbF1yq_sidebarRenameInput:focus-visible,.jbF1yq_composerInput:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#4f6bfa);outline-offset:1px}@media (prefers-reduced-motion:reduce){.jbF1yq_statusDot[data-state=busy],.jbF1yq_toast{animation:none}.jbF1yq_button,.jbF1yq_composer{transition:none}}";
|
|
316
|
-
const tagId = "dsh-webchat/panel.module.css";
|
|
317
|
-
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
318
|
-
const tag = document.createElement("style");
|
|
319
|
-
tag.dataset.plugin = "dsh-webchat";
|
|
320
|
-
tag.dataset.pluginCss = tagId;
|
|
321
|
-
tag.textContent = css;
|
|
322
|
-
document.head.appendChild(tag);
|
|
323
131
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
"
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
"composerFooter": "jbF1yq_composerFooter",
|
|
333
|
-
"composerHint": "jbF1yq_composerHint",
|
|
334
|
-
"composerInput": "jbF1yq_composerInput",
|
|
335
|
-
"composerSpacer": "jbF1yq_composerSpacer",
|
|
336
|
-
"composerToolbar": "jbF1yq_composerToolbar",
|
|
337
|
-
"dshWcPulse": "jbF1yq_dshWcPulse",
|
|
338
|
-
"dshWcToast": "jbF1yq_dshWcToast",
|
|
339
|
-
"emptyBody": "jbF1yq_emptyBody",
|
|
340
|
-
"emptyEyebrow": "jbF1yq_emptyEyebrow",
|
|
341
|
-
"emptyState": "jbF1yq_emptyState",
|
|
342
|
-
"emptyTitle": "jbF1yq_emptyTitle",
|
|
343
|
-
"entry": "jbF1yq_entry",
|
|
344
|
-
"entryIcon": "jbF1yq_entryIcon",
|
|
345
|
-
"entryLabel": "jbF1yq_entryLabel",
|
|
346
|
-
"handoffDock": "jbF1yq_handoffDock",
|
|
347
|
-
"handoffEyebrow": "jbF1yq_handoffEyebrow",
|
|
348
|
-
"handoffSpacer": "jbF1yq_handoffSpacer",
|
|
349
|
-
"headerSpacer": "jbF1yq_headerSpacer",
|
|
350
|
-
"main": "jbF1yq_main",
|
|
351
|
-
"markdown": "jbF1yq_markdown",
|
|
352
|
-
"messageBubble": "jbF1yq_messageBubble",
|
|
353
|
-
"messageError": "jbF1yq_messageError",
|
|
354
|
-
"messageList": "jbF1yq_messageList",
|
|
355
|
-
"messageMeta": "jbF1yq_messageMeta",
|
|
356
|
-
"messageRow": "jbF1yq_messageRow",
|
|
357
|
-
"messageSpeaker": "jbF1yq_messageSpeaker",
|
|
358
|
-
"messageTime": "jbF1yq_messageTime",
|
|
359
|
-
"panel": "jbF1yq_panel",
|
|
360
|
-
"panelBody": "jbF1yq_panelBody",
|
|
361
|
-
"panelHeader": "jbF1yq_panelHeader",
|
|
362
|
-
"panelTitle": "jbF1yq_panelTitle",
|
|
363
|
-
"recoverEmpty": "jbF1yq_recoverEmpty",
|
|
364
|
-
"recoverItem": "jbF1yq_recoverItem",
|
|
365
|
-
"recoverList": "jbF1yq_recoverList",
|
|
366
|
-
"recoverTitle": "jbF1yq_recoverTitle",
|
|
367
|
-
"segmented": "jbF1yq_segmented",
|
|
368
|
-
"segmentedButton": "jbF1yq_segmentedButton",
|
|
369
|
-
"sidebar": "jbF1yq_sidebar",
|
|
370
|
-
"sidebarAction": "jbF1yq_sidebarAction",
|
|
371
|
-
"sidebarActionDanger": "jbF1yq_sidebarActionDanger",
|
|
372
|
-
"sidebarActions": "jbF1yq_sidebarActions",
|
|
373
|
-
"sidebarClear": "jbF1yq_sidebarClear",
|
|
374
|
-
"sidebarEmpty": "jbF1yq_sidebarEmpty",
|
|
375
|
-
"sidebarItem": "jbF1yq_sidebarItem",
|
|
376
|
-
"sidebarList": "jbF1yq_sidebarList",
|
|
377
|
-
"sidebarMeta": "jbF1yq_sidebarMeta",
|
|
378
|
-
"sidebarNew": "jbF1yq_sidebarNew",
|
|
379
|
-
"sidebarRename": "jbF1yq_sidebarRename",
|
|
380
|
-
"sidebarRenameInput": "jbF1yq_sidebarRenameInput",
|
|
381
|
-
"sidebarRow": "jbF1yq_sidebarRow",
|
|
382
|
-
"sidebarTitle": "jbF1yq_sidebarTitle",
|
|
383
|
-
"statusChip": "jbF1yq_statusChip",
|
|
384
|
-
"statusDot": "jbF1yq_statusDot",
|
|
385
|
-
"statusText": "jbF1yq_statusText",
|
|
386
|
-
"streamBadge": "jbF1yq_streamBadge",
|
|
387
|
-
"tableWrap": "jbF1yq_tableWrap",
|
|
388
|
-
"toast": "jbF1yq_toast",
|
|
389
|
-
"toggleButton": "jbF1yq_toggleButton",
|
|
390
|
-
"view": "jbF1yq_view",
|
|
391
|
-
"workspaceSelect": "jbF1yq_workspaceSelect"
|
|
392
|
-
};
|
|
393
|
-
//#endregion
|
|
394
|
-
//#region src/client/panel/Markdown.tsx
|
|
395
|
-
/**
|
|
396
|
-
* Minimal markdown renderer for the web-chat panel. Deliberately small and
|
|
397
|
-
* dependency-free: headings, paragraphs, bold/italic, inline + fenced code,
|
|
398
|
-
* links, DeepSeek `[citation:N]` markers (rendered as superscript badges),
|
|
399
|
-
* unordered/ordered lists, blockquotes, GFM tables, horizontal rules, and
|
|
400
|
-
* <details> passthrough (used for DeepSeek R1 thinking blocks). Anything
|
|
401
|
-
* unrecognized renders as plain text — never raw HTML.
|
|
402
|
-
*/
|
|
403
|
-
/** Escape text for safe rendering (no dangerouslySetInnerHTML anywhere). */
|
|
404
|
-
function esc(text) {
|
|
405
|
-
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
132
|
+
|
|
133
|
+
/** The sidebar shell root, or undefined while it is not mounted. */
|
|
134
|
+
function sidebarRoot() {
|
|
135
|
+
var column = document.querySelector('[data-pane="sidebar"], [class*="sidebarCol"]')
|
|
136
|
+
if (column === null) return undefined
|
|
137
|
+
var logoRow = column.querySelector('[class*="logoRow"]')
|
|
138
|
+
var owner = logoRow === null ? null : logoRow.parentElement
|
|
139
|
+
return owner !== null && owner !== undefined ? owner : (column.firstElementChild || undefined)
|
|
406
140
|
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
const nodes = [];
|
|
415
|
-
const re = /(`[^`]+`)|(\[[^\]]+\]\([^)\s]+\))|(\[citation:[^\]]+\])|(\*\*[^*]+\*\*|__[^_]+__)|(\*[^*\n]+\*|_[^_\n]+_)/g;
|
|
416
|
-
let last = 0;
|
|
417
|
-
let index = 0;
|
|
418
|
-
let match;
|
|
419
|
-
while ((match = re.exec(text)) !== null) {
|
|
420
|
-
if (match.index > last) nodes.push(esc(text.slice(last, match.index)));
|
|
421
|
-
const key = `${keyBase}-${index}`;
|
|
422
|
-
if (match[1] !== void 0) nodes.push((0, react.createElement)("code", { key: `${key}-code` }, match[1].slice(1, -1)));
|
|
423
|
-
else if (match[2] !== void 0) {
|
|
424
|
-
const link = /^\[([^\]]+)\]\(([^)\s]+)\)$/.exec(match[2]);
|
|
425
|
-
nodes.push((0, react.createElement)("a", {
|
|
426
|
-
key: `${key}-a`,
|
|
427
|
-
href: link[2],
|
|
428
|
-
target: "_blank",
|
|
429
|
-
rel: "noreferrer"
|
|
430
|
-
}, link[1]));
|
|
431
|
-
} else if (match[3] !== void 0) {
|
|
432
|
-
const nums = match[3].slice(10, -1).trim();
|
|
433
|
-
nodes.push((0, react.createElement)("sup", {
|
|
434
|
-
key: `${key}-cite`,
|
|
435
|
-
className: panel_module_css_default.citation
|
|
436
|
-
}, nums));
|
|
437
|
-
} else if (match[4] !== void 0) {
|
|
438
|
-
const inner = match[4].slice(2, -2);
|
|
439
|
-
nodes.push((0, react.createElement)("strong", { key: `${key}-b` }, ...inline(inner, `${key}-b`)));
|
|
440
|
-
} else if (match[5] !== void 0) {
|
|
441
|
-
const inner = match[5].slice(1, -1);
|
|
442
|
-
nodes.push((0, react.createElement)("em", { key: `${key}-i` }, ...inline(inner, `${key}-i`)));
|
|
443
|
-
}
|
|
444
|
-
last = re.lastIndex;
|
|
445
|
-
index++;
|
|
141
|
+
|
|
142
|
+
/** The New Session button the entry is ordered against. */
|
|
143
|
+
function newSessionButton(root) {
|
|
144
|
+
var nested = root.querySelector('button[class*="newSession"]')
|
|
145
|
+
if (nested !== null) return nested
|
|
146
|
+
for (var i = 0; i < root.children.length; i++) {
|
|
147
|
+
if (root.children[i].tagName === 'BUTTON') return root.children[i]
|
|
446
148
|
}
|
|
447
|
-
|
|
448
|
-
return nodes;
|
|
149
|
+
return undefined
|
|
449
150
|
}
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
while (start < end && cells[start].trim() === "") start++;
|
|
471
|
-
while (end > start && cells[end - 1].trim() === "") end--;
|
|
472
|
-
return cells.slice(start, end).map((cell) => cell.trim());
|
|
151
|
+
|
|
152
|
+
/** Build the entry row. */
|
|
153
|
+
function createEntry(controller) {
|
|
154
|
+
var entry = document.createElement('button')
|
|
155
|
+
entry.type = 'button'
|
|
156
|
+
entry.setAttribute(ENTRY_ATTR, '')
|
|
157
|
+
entry.setAttribute('data-dsh-plugin', 'webchat')
|
|
158
|
+
entry.setAttribute('data-dsh-part', 'sidebar-entry')
|
|
159
|
+
entry.setAttribute('aria-label', T.label)
|
|
160
|
+
entry.setAttribute('title', T.tooltip)
|
|
161
|
+
var icon = document.createElement('span')
|
|
162
|
+
icon.setAttribute('data-part', 'icon')
|
|
163
|
+
icon.innerHTML = '<svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8 2.5a5.5 5.5 0 0 0-4.7 8.3L2.5 13.5l2.8-.8A5.5 5.5 0 1 0 8 2.5z"/><circle cx="8" cy="8" r="1.1" fill="currentColor" stroke="none"/></svg>'
|
|
164
|
+
var label = document.createElement('span')
|
|
165
|
+
label.setAttribute('data-part', 'label')
|
|
166
|
+
label.textContent = T.label
|
|
167
|
+
entry.appendChild(icon)
|
|
168
|
+
entry.appendChild(label)
|
|
169
|
+
entry.addEventListener('click', function () { controller.toggle() })
|
|
170
|
+
return entry
|
|
473
171
|
}
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
172
|
+
|
|
173
|
+
/** Re-insert the entry after the New Session row when React displaces it. */
|
|
174
|
+
function placeEntry(root, entry) {
|
|
175
|
+
var button = newSessionButton(root)
|
|
176
|
+
if (button === undefined) return false
|
|
177
|
+
if (entry.parentElement === root) return true
|
|
178
|
+
var row = button.closest('[class*="logoRow"]')
|
|
179
|
+
var base = row !== null && row.parentElement === root ? row : button
|
|
180
|
+
var family = Array.prototype.filter.call(root.children, function (el) {
|
|
181
|
+
return el.matches && el.matches('[' + ENTRY_ATTR + '], [data-dsh-taskboard-entry], [data-dsh-ssh-entry]')
|
|
182
|
+
})
|
|
183
|
+
var anchor = family.length > 0 ? family[family.length - 1].nextElementSibling : base.nextElementSibling
|
|
184
|
+
root.insertBefore(entry, anchor)
|
|
185
|
+
return true
|
|
478
186
|
}
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
if (
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
const trimmed = line.trim();
|
|
493
|
-
const fence = /^```([\w+-]*)\s*$/.exec(trimmed);
|
|
494
|
-
if (fence !== null) {
|
|
495
|
-
const language = fence[1] ?? "";
|
|
496
|
-
const body = [];
|
|
497
|
-
index++;
|
|
498
|
-
while (index < lines.length && !/^```\s*$/.test(lines[index].trim())) {
|
|
499
|
-
body.push(lines[index]);
|
|
500
|
-
index++;
|
|
501
|
-
}
|
|
502
|
-
index++;
|
|
503
|
-
push(codeBlock(language, body.join("\n"), `code${blockIndex}`));
|
|
504
|
-
continue;
|
|
505
|
-
}
|
|
506
|
-
const heading = /^(#{1,6})\s+(.*)$/.exec(trimmed);
|
|
507
|
-
if (heading !== null) {
|
|
508
|
-
const level = heading[1].length;
|
|
509
|
-
push((0, react.createElement)(`h${level}`, null, ...inline(heading[2], `h${blockIndex}`)));
|
|
510
|
-
index++;
|
|
511
|
-
continue;
|
|
512
|
-
}
|
|
513
|
-
if (line.includes("|") && lines[index + 1] !== void 0 && isDelimiterRow(lines[index + 1])) {
|
|
514
|
-
const header = splitCells(line);
|
|
515
|
-
index += 2;
|
|
516
|
-
const rows = [];
|
|
517
|
-
while (index < lines.length) {
|
|
518
|
-
const row = lines[index];
|
|
519
|
-
if (row.trim() === "" || !row.includes("|")) break;
|
|
520
|
-
rows.push(splitCells(row));
|
|
521
|
-
index++;
|
|
522
|
-
}
|
|
523
|
-
const width = Math.max(header.length, ...rows.map((row) => row.length));
|
|
524
|
-
const rowNode = (cells, rowIndex) => (0, react.createElement)("tr", { key: rowIndex }, Array.from({ length: width }, (_, col) => (0, react.createElement)("td", { key: col }, ...inline(cells[col] ?? "", `t${blockIndex}-${rowIndex}-${col}`))));
|
|
525
|
-
blocks.push((0, react.createElement)("div", {
|
|
526
|
-
key: `tw${blockIndex}`,
|
|
527
|
-
className: panel_module_css_default.tableWrap
|
|
528
|
-
}, (0, react.createElement)("table", { key: `t${blockIndex}` }, (0, react.createElement)("thead", null, (0, react.createElement)("tr", null, header.map((cell, col) => (0, react.createElement)("th", { key: col }, ...inline(cell, `th${blockIndex}-${col}`))))), (0, react.createElement)("tbody", null, rows.map(rowNode)))));
|
|
529
|
-
blockIndex++;
|
|
530
|
-
continue;
|
|
531
|
-
}
|
|
532
|
-
if (/^(-{3,}|\*{3,}|_{3,})$/.test(trimmed)) {
|
|
533
|
-
push((0, react.createElement)("hr", { key: `hr${blockIndex}` }));
|
|
534
|
-
index++;
|
|
535
|
-
continue;
|
|
536
|
-
}
|
|
537
|
-
if (trimmed.startsWith(">")) {
|
|
538
|
-
const quote = [];
|
|
539
|
-
while (index < lines.length && lines[index].trim().startsWith(">")) {
|
|
540
|
-
quote.push(lines[index].trim().replace(/^>\s?/, ""));
|
|
541
|
-
index++;
|
|
542
|
-
}
|
|
543
|
-
push((0, react.createElement)("blockquote", { key: `q${blockIndex}` }, ...renderMarkdown(quote.join("\n"))));
|
|
544
|
-
continue;
|
|
545
|
-
}
|
|
546
|
-
if (/^<details>/.test(trimmed)) {
|
|
547
|
-
const body = [];
|
|
548
|
-
const summaryMatch = /^<summary>\s*(.*?)\s*<\/summary>/.exec(trimmed);
|
|
549
|
-
index++;
|
|
550
|
-
while (index < lines.length && !/^<\/details>/.test(lines[index].trim())) {
|
|
551
|
-
body.push(lines[index]);
|
|
552
|
-
index++;
|
|
553
|
-
}
|
|
554
|
-
index++;
|
|
555
|
-
push((0, react.createElement)("details", { key: `d${blockIndex}` }, (0, react.createElement)("summary", null, summaryMatch?.[1] ?? "详情"), ...renderMarkdown(body.join("\n"))));
|
|
556
|
-
continue;
|
|
557
|
-
}
|
|
558
|
-
if (/^[-*+]\s+/.test(trimmed)) {
|
|
559
|
-
const items = [];
|
|
560
|
-
while (index < lines.length && /^[-*+]\s+/.test(lines[index].trim())) {
|
|
561
|
-
items.push(lines[index].trim().replace(/^[-*+]\s+/, ""));
|
|
562
|
-
index++;
|
|
563
|
-
}
|
|
564
|
-
push((0, react.createElement)("ul", { key: `ul${blockIndex}` }, items.map((item, itemIndex) => (0, react.createElement)("li", { key: itemIndex }, ...inline(item, `uli${blockIndex}-${itemIndex}`)))));
|
|
565
|
-
continue;
|
|
566
|
-
}
|
|
567
|
-
if (/^\d+[.)]\s+/.test(trimmed)) {
|
|
568
|
-
const items = [];
|
|
569
|
-
while (index < lines.length && /^\d+[.)]\s+/.test(lines[index].trim())) {
|
|
570
|
-
items.push(lines[index].trim().replace(/^\d+[.)]\s+/, ""));
|
|
571
|
-
index++;
|
|
572
|
-
}
|
|
573
|
-
push((0, react.createElement)("ol", { key: `ol${blockIndex}` }, items.map((item, itemIndex) => (0, react.createElement)("li", { key: itemIndex }, ...inline(item, `oli${blockIndex}-${itemIndex}`)))));
|
|
574
|
-
continue;
|
|
575
|
-
}
|
|
576
|
-
const paragraph = [];
|
|
577
|
-
while (index < lines.length) {
|
|
578
|
-
const current = lines[index].trim();
|
|
579
|
-
if (current === "") break;
|
|
580
|
-
if (/^(```|#{1,6}\s|[-*+]\s|\d+[.)]\s|>\s|<\/(details|table)>)/.test(current)) break;
|
|
581
|
-
if (current.includes("|") && lines[index + 1] !== void 0 && isDelimiterRow(lines[index + 1])) break;
|
|
582
|
-
paragraph.push(current);
|
|
583
|
-
index++;
|
|
584
|
-
}
|
|
585
|
-
if (paragraph.length === 0) {
|
|
586
|
-
index++;
|
|
587
|
-
continue;
|
|
187
|
+
|
|
188
|
+
/** Mount the sidebar entry with its self-healing observers. */
|
|
189
|
+
function mountSidebarEntry(controller) {
|
|
190
|
+
if (document.querySelector('[' + ENTRY_ATTR + ']') !== null) return function () {}
|
|
191
|
+
var entry = createEntry(controller)
|
|
192
|
+
var root
|
|
193
|
+
var placed = false
|
|
194
|
+
|
|
195
|
+
var tryPlace = function () {
|
|
196
|
+
if (root !== undefined && !root.isConnected) {
|
|
197
|
+
rootObserver.disconnect()
|
|
198
|
+
root = undefined
|
|
199
|
+
placed = false
|
|
588
200
|
}
|
|
589
|
-
|
|
201
|
+
if (placed) {
|
|
202
|
+
if (document.body.contains(entry)) return
|
|
203
|
+
rootObserver.disconnect()
|
|
204
|
+
root = undefined
|
|
205
|
+
placed = false
|
|
206
|
+
}
|
|
207
|
+
if (root === undefined) root = sidebarRoot()
|
|
208
|
+
if (root === undefined) return
|
|
209
|
+
placed = placeEntry(root, entry)
|
|
210
|
+
if (placed) rootObserver.observe(root, { childList: true, subtree: true })
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
var waitObserver = new MutationObserver(function () { tryPlace() })
|
|
214
|
+
waitObserver.observe(document.body, { childList: true, subtree: true })
|
|
215
|
+
var rootObserver = new MutationObserver(function () {
|
|
216
|
+
if (root === undefined || !root.isConnected) {
|
|
217
|
+
placed = false
|
|
218
|
+
tryPlace()
|
|
219
|
+
return
|
|
220
|
+
}
|
|
221
|
+
if (!root.contains(entry)) placed = placeEntry(root, entry)
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
var syncActive = function () {
|
|
225
|
+
if (controller.isOpen()) entry.dataset.active = 'true'
|
|
226
|
+
else delete entry.dataset.active
|
|
227
|
+
}
|
|
228
|
+
var unsubscribe = controller.subscribe(syncActive)
|
|
229
|
+
syncActive()
|
|
230
|
+
tryPlace()
|
|
231
|
+
|
|
232
|
+
return function () {
|
|
233
|
+
waitObserver.disconnect()
|
|
234
|
+
rootObserver.disconnect()
|
|
235
|
+
unsubscribe()
|
|
236
|
+
entry.remove()
|
|
590
237
|
}
|
|
591
|
-
return blocks;
|
|
592
238
|
}
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
239
|
+
|
|
240
|
+
/** The center column that hosts takeover panels. */
|
|
241
|
+
function conversationColumn() {
|
|
242
|
+
return document.querySelector('[data-pane="conversation"], [class*="centerCol"]') || undefined
|
|
596
243
|
}
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
*
|
|
606
|
-
* Layout mirrors chat.deepseek.com: a left conversation sidebar (new chat +
|
|
607
|
-
* history + per-item rename/delete + clear-all) beside the main chat column
|
|
608
|
-
* (messages → composer with 深度思考 / 智能搜索 toggles → handoff dock). The
|
|
609
|
-
* mono register stays reserved for machine things — timestamps, counts,
|
|
610
|
-
* status, and the handoff eyebrow.
|
|
611
|
-
*/
|
|
612
|
-
/** Interpolate {placeholder}s in a localized template. */
|
|
613
|
-
function fmt(template, values) {
|
|
614
|
-
return template.replace(/\{(\w+)\}/g, (_, key) => values[key] ?? `{${key}}`);
|
|
244
|
+
|
|
245
|
+
/** Call one host route. */
|
|
246
|
+
async function call(path, method) {
|
|
247
|
+
const response = await fetch(path, { method: method || 'GET', headers: { accept: 'application/json' } })
|
|
248
|
+
let payload = {}
|
|
249
|
+
try { payload = await response.json() } catch (error) { payload = {} }
|
|
250
|
+
if (!response.ok && payload.error === undefined) payload.error = 'HTTP ' + response.status
|
|
251
|
+
return payload
|
|
615
252
|
}
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
title: session.displayTitle,
|
|
676
|
-
running: session.running
|
|
677
|
-
}];
|
|
678
|
-
});
|
|
679
|
-
const workspaceOverridden = (0, react.useRef)(false);
|
|
680
|
-
(0, react.useEffect)(() => {
|
|
681
|
-
if (workspaceOverridden.current || !workspaceBaselinesReady) return;
|
|
682
|
-
const currentSessionId = sessions.list.getSnapshot().current;
|
|
683
|
-
const currentWorkspace = currentSessionId === void 0 ? void 0 : workspaceItems.find((ws) => ws.sessionIds.includes(currentSessionId));
|
|
684
|
-
setTargetWorkspaceId(currentWorkspace?.workspaceId ?? workspaceSnapshot.recentWorkspaceId);
|
|
685
|
-
}, [
|
|
686
|
-
workspaceBaselinesReady,
|
|
687
|
-
workspaceItems,
|
|
688
|
-
workspaceSnapshot.recentWorkspaceId,
|
|
689
|
-
sessions
|
|
690
|
-
]);
|
|
691
|
-
const creatingWorkspace = (0, react.useRef)(false);
|
|
692
|
-
const createWorkspace = (0, react.useCallback)(async () => {
|
|
693
|
-
if (creatingWorkspace.current) return;
|
|
694
|
-
creatingWorkspace.current = true;
|
|
695
|
-
try {
|
|
696
|
-
const path = await workspaces.pickDirectory();
|
|
697
|
-
if (path === null || path === "") return;
|
|
698
|
-
const created = await workspaces.create({ path });
|
|
699
|
-
setTargetWorkspaceId(created.workspaceId);
|
|
700
|
-
showToast(fmt(tt("transfer.workspace.created"), { title: created.title }));
|
|
701
|
-
} catch (error) {
|
|
702
|
-
showToast(fmt(tt("transfer.workspace.createFailed"), { error: String(error) }), true);
|
|
703
|
-
} finally {
|
|
704
|
-
creatingWorkspace.current = false;
|
|
705
|
-
}
|
|
706
|
-
}, [
|
|
707
|
-
workspaces,
|
|
708
|
-
showToast,
|
|
709
|
-
tt
|
|
710
|
-
]);
|
|
711
|
-
(0, react.useEffect)(() => {
|
|
712
|
-
let cancelled = false;
|
|
713
|
-
let timer;
|
|
714
|
-
const poll = async () => {
|
|
715
|
-
if (cancelled) return;
|
|
716
|
-
try {
|
|
717
|
-
const snapshot = await api.state();
|
|
718
|
-
if (cancelled) return;
|
|
719
|
-
setState(snapshot);
|
|
720
|
-
setViewChatId((previous) => {
|
|
721
|
-
const active = snapshot.activeChatId;
|
|
722
|
-
if (previous !== void 0 && snapshot.chats.some((chat) => chat.id === previous)) return previous;
|
|
723
|
-
return active;
|
|
724
|
-
});
|
|
725
|
-
} catch {}
|
|
726
|
-
const streaming = stateRef.current?.chats.some((chat) => chat.streaming) ?? false;
|
|
727
|
-
timer = window.setTimeout(poll, streaming ? POLL_STREAM_MS : POLL_IDLE_MS);
|
|
728
|
-
};
|
|
729
|
-
poll();
|
|
730
|
-
return () => {
|
|
731
|
-
cancelled = true;
|
|
732
|
-
window.clearTimeout(timer);
|
|
733
|
-
};
|
|
734
|
-
}, [api]);
|
|
735
|
-
(0, react.useEffect)(() => {
|
|
736
|
-
if (state === null) return;
|
|
737
|
-
setDeepThink(state.deepThink);
|
|
738
|
-
setSearch(state.search);
|
|
739
|
-
}, [state?.deepThink, state?.search]);
|
|
740
|
-
(0, react.useEffect)(() => {
|
|
741
|
-
const list = listRef.current;
|
|
742
|
-
if (list === null) return;
|
|
743
|
-
const switched = prevChatRef.current !== viewChatId;
|
|
744
|
-
prevChatRef.current = viewChatId;
|
|
745
|
-
if (switched || pinnedRef.current) list.scrollTop = list.scrollHeight;
|
|
746
|
-
}, [state, viewChatId]);
|
|
747
|
-
const handleScroll = (0, react.useCallback)(() => {
|
|
748
|
-
const list = listRef.current;
|
|
749
|
-
if (list === null) return;
|
|
750
|
-
const nearBottom = list.scrollHeight - list.scrollTop - list.clientHeight < 96;
|
|
751
|
-
pinnedRef.current = nearBottom;
|
|
752
|
-
}, []);
|
|
753
|
-
const chats = state?.chats ?? [];
|
|
754
|
-
const viewChat = chats.find((chat) => chat.id === viewChatId) ?? chats[0];
|
|
755
|
-
const busy = state?.busy ?? false;
|
|
756
|
-
const loggedIn = state?.loggedIn ?? null;
|
|
757
|
-
const streaming = viewChat?.streaming ?? false;
|
|
758
|
-
const modelLabel = (0, react.useCallback)((model) => {
|
|
759
|
-
return model === "deepseek-reasoner" ? tt("model.deepseek-reasoner") : tt("model.deepseek-chat");
|
|
760
|
-
}, [tt]);
|
|
761
|
-
const send = (0, react.useCallback)(async () => {
|
|
762
|
-
const text = draft.trim();
|
|
763
|
-
const images = imagePaths.split(/[\n,;]+/).map((path) => path.trim()).filter((path) => path !== "");
|
|
764
|
-
if (text === "" && images.length === 0 || busy) return;
|
|
765
|
-
setDraft("");
|
|
766
|
-
setImagePaths("");
|
|
767
|
-
pinnedRef.current = true;
|
|
768
|
-
try {
|
|
769
|
-
const result = await api.send(text, images.length > 0 ? images : void 0);
|
|
770
|
-
if (result.ok !== true) showToast(result.error ?? "send failed", true);
|
|
771
|
-
else setViewChatId(result.chatId);
|
|
772
|
-
} catch (error) {
|
|
773
|
-
showToast(String(error), true);
|
|
774
|
-
}
|
|
775
|
-
}, [
|
|
776
|
-
draft,
|
|
777
|
-
imagePaths,
|
|
778
|
-
busy,
|
|
779
|
-
api,
|
|
780
|
-
showToast
|
|
781
|
-
]);
|
|
782
|
-
const stop = (0, react.useCallback)(async () => {
|
|
783
|
-
await api.stop().catch(() => void 0);
|
|
784
|
-
}, [api]);
|
|
785
|
-
const newChat = (0, react.useCallback)(async () => {
|
|
786
|
-
try {
|
|
787
|
-
const result = await api.newChat();
|
|
788
|
-
if (result.ok === true && result.chatId !== void 0) setViewChatId(result.chatId);
|
|
789
|
-
else showToast(result.error ?? "new chat failed", true);
|
|
790
|
-
} catch (error) {
|
|
791
|
-
showToast(String(error), true);
|
|
792
|
-
}
|
|
793
|
-
}, [api, showToast]);
|
|
794
|
-
const toggleDeepThink = (0, react.useCallback)(async () => {
|
|
795
|
-
const next = !deepThink;
|
|
796
|
-
setDeepThink(next);
|
|
797
|
-
try {
|
|
798
|
-
const result = await api.setDeepThink(next);
|
|
799
|
-
if (result.ok !== true) showToast(result.error ?? "toggle deep think failed", true);
|
|
800
|
-
} catch (error) {
|
|
801
|
-
showToast(String(error), true);
|
|
802
|
-
}
|
|
803
|
-
}, [
|
|
804
|
-
deepThink,
|
|
805
|
-
api,
|
|
806
|
-
showToast
|
|
807
|
-
]);
|
|
808
|
-
const toggleSearch = (0, react.useCallback)(async () => {
|
|
809
|
-
const next = !search;
|
|
810
|
-
setSearch(next);
|
|
811
|
-
try {
|
|
812
|
-
const result = await api.setSearch(next);
|
|
813
|
-
if (result.ok !== true) showToast(result.error ?? "toggle search failed", true);
|
|
814
|
-
} catch (error) {
|
|
815
|
-
showToast(String(error), true);
|
|
816
|
-
}
|
|
817
|
-
}, [
|
|
818
|
-
search,
|
|
819
|
-
api,
|
|
820
|
-
showToast
|
|
821
|
-
]);
|
|
822
|
-
const openLogin = (0, react.useCallback)(async () => {
|
|
823
|
-
try {
|
|
824
|
-
const result = await api.openLogin();
|
|
825
|
-
if (result.ok !== true) showToast(result.error ?? "open login failed", true);
|
|
826
|
-
} catch (error) {
|
|
827
|
-
showToast(String(error), true);
|
|
828
|
-
}
|
|
829
|
-
}, [api, showToast]);
|
|
830
|
-
const closeBrowser = (0, react.useCallback)(async () => {
|
|
831
|
-
await api.closeBrowser().catch(() => void 0);
|
|
832
|
-
}, [api]);
|
|
833
|
-
const toggleRecover = (0, react.useCallback)(async () => {
|
|
834
|
-
if (recoverOpen) {
|
|
835
|
-
setRecoverOpen(false);
|
|
836
|
-
return;
|
|
837
|
-
}
|
|
838
|
-
try {
|
|
839
|
-
const result = await api.webChats();
|
|
840
|
-
setWebMissing(result.ok === true ? result.missing ?? [] : []);
|
|
841
|
-
setRecoverOpen(true);
|
|
842
|
-
} catch (error) {
|
|
843
|
-
showToast(String(error), true);
|
|
844
|
-
}
|
|
845
|
-
}, [
|
|
846
|
-
recoverOpen,
|
|
847
|
-
api,
|
|
848
|
-
showToast
|
|
849
|
-
]);
|
|
850
|
-
const recoverFromWeb = (0, react.useCallback)(async (title) => {
|
|
851
|
-
if (recovering) return;
|
|
852
|
-
setRecovering(true);
|
|
253
|
+
|
|
254
|
+
/** Build the panel body once; returns the parts the caller updates. */
|
|
255
|
+
function buildPanel(container) {
|
|
256
|
+
var panel = document.createElement('div')
|
|
257
|
+
panel.className = 'dsh-wc-panel'
|
|
258
|
+
|
|
259
|
+
var head = document.createElement('div')
|
|
260
|
+
head.className = 'dsh-wc-head'
|
|
261
|
+
var title = document.createElement('h2')
|
|
262
|
+
title.className = 'dsh-wc-title'
|
|
263
|
+
title.textContent = T.title
|
|
264
|
+
var chip = document.createElement('span')
|
|
265
|
+
chip.className = 'dsh-wc-chip'
|
|
266
|
+
var dot = document.createElement('span')
|
|
267
|
+
dot.className = 'dsh-wc-dot'
|
|
268
|
+
var chipText = document.createElement('span')
|
|
269
|
+
chipText.textContent = T.checking
|
|
270
|
+
chip.appendChild(dot)
|
|
271
|
+
chip.appendChild(chipText)
|
|
272
|
+
head.appendChild(title)
|
|
273
|
+
head.appendChild(chip)
|
|
274
|
+
|
|
275
|
+
var card = document.createElement('div')
|
|
276
|
+
card.className = 'dsh-wc-card'
|
|
277
|
+
var lede = document.createElement('p')
|
|
278
|
+
lede.className = 'dsh-wc-lede'
|
|
279
|
+
lede.textContent = T.lede
|
|
280
|
+
var url = document.createElement('p')
|
|
281
|
+
url.className = 'dsh-wc-url'
|
|
282
|
+
url.textContent = PAGE_URL
|
|
283
|
+
var button = document.createElement('button')
|
|
284
|
+
button.type = 'button'
|
|
285
|
+
button.className = 'dsh-wc-button'
|
|
286
|
+
button.textContent = T.open
|
|
287
|
+
var hint = document.createElement('p')
|
|
288
|
+
hint.className = 'dsh-wc-hint'
|
|
289
|
+
hint.textContent = T.hint
|
|
290
|
+
var result = document.createElement('p')
|
|
291
|
+
result.className = 'dsh-wc-result'
|
|
292
|
+
card.appendChild(lede)
|
|
293
|
+
card.appendChild(url)
|
|
294
|
+
card.appendChild(button)
|
|
295
|
+
card.appendChild(hint)
|
|
296
|
+
card.appendChild(result)
|
|
297
|
+
|
|
298
|
+
panel.appendChild(head)
|
|
299
|
+
panel.appendChild(card)
|
|
300
|
+
container.appendChild(panel)
|
|
301
|
+
|
|
302
|
+
var mark = function (state, text) {
|
|
303
|
+
dot.setAttribute('data-state', state)
|
|
304
|
+
chipText.textContent = text
|
|
305
|
+
}
|
|
306
|
+
var idle = function () {
|
|
307
|
+
button.disabled = false
|
|
308
|
+
button.textContent = T.open
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
var refresh = async function () {
|
|
853
312
|
try {
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
return;
|
|
858
|
-
}
|
|
859
|
-
setWebMissing((list) => list.filter((item) => item !== title));
|
|
860
|
-
showToast(fmt(tt("recover.done"), { title: result.title ?? title }));
|
|
313
|
+
var state = await call(API.state)
|
|
314
|
+
mark(state.appWindowOpen ? 'ok' : '', state.appWindowOpen ? T.opened : T.closed)
|
|
315
|
+
button.textContent = state.appWindowOpen ? T.reopen : T.open
|
|
861
316
|
} catch (error) {
|
|
862
|
-
|
|
863
|
-
} finally {
|
|
864
|
-
setRecovering(false);
|
|
317
|
+
mark('bad', T.closed)
|
|
865
318
|
}
|
|
866
|
-
}
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
setTransferring(true);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
button.addEventListener('click', async function () {
|
|
322
|
+
button.disabled = true
|
|
323
|
+
button.textContent = T.busy
|
|
324
|
+
mark('busy', T.busy)
|
|
325
|
+
result.removeAttribute('data-state')
|
|
326
|
+
result.textContent = ''
|
|
875
327
|
try {
|
|
876
|
-
|
|
877
|
-
if (
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
328
|
+
var outcome = await call(API.open, 'POST')
|
|
329
|
+
if (outcome.ok) {
|
|
330
|
+
var via = String(outcome.via || '')
|
|
331
|
+
mark('ok', T.done)
|
|
332
|
+
result.textContent = via === 'system-browser'
|
|
333
|
+
? (ZH ? '已交给系统默认浏览器打开。' : 'Handed to the system default browser.')
|
|
334
|
+
: (ZH ? '已打开应用窗口:' + via : 'Opened an app window: ' + via)
|
|
335
|
+
} else {
|
|
336
|
+
mark('bad', T.failed)
|
|
337
|
+
result.setAttribute('data-state', 'bad')
|
|
338
|
+
result.textContent = String(outcome.error || outcome.via || T.failed)
|
|
886
339
|
}
|
|
887
|
-
if (result.attached === false && targetWorkspaceId !== void 0) showToast(fmt(tt("transfer.attached.failed"), { sessionId: target }), true);
|
|
888
|
-
else showToast(fmt(tt("transfer.done"), { sessionId: target }));
|
|
889
|
-
const refreshable = sessions;
|
|
890
|
-
try {
|
|
891
|
-
await refreshable.refresh?.();
|
|
892
|
-
} catch {}
|
|
893
|
-
const tryOpen = () => {
|
|
894
|
-
if (sessions.list.getSnapshot().byId[target] !== void 0) {
|
|
895
|
-
sessions.open(target);
|
|
896
|
-
return;
|
|
897
|
-
}
|
|
898
|
-
window.setTimeout(tryOpen, 300);
|
|
899
|
-
};
|
|
900
|
-
window.setTimeout(tryOpen, 200);
|
|
901
|
-
} catch (error) {
|
|
902
|
-
showToast(String(error), true);
|
|
903
|
-
} finally {
|
|
904
|
-
setTransferring(false);
|
|
905
|
-
}
|
|
906
|
-
}, [
|
|
907
|
-
viewChat,
|
|
908
|
-
transferring,
|
|
909
|
-
api,
|
|
910
|
-
currentCwd,
|
|
911
|
-
sessions,
|
|
912
|
-
showToast,
|
|
913
|
-
tt,
|
|
914
|
-
transferMode,
|
|
915
|
-
targetWorkspaceId,
|
|
916
|
-
targetSessionId
|
|
917
|
-
]);
|
|
918
|
-
const exportFile = (0, react.useCallback)(async () => {
|
|
919
|
-
if (viewChat === void 0 || exporting) return;
|
|
920
|
-
setExporting(true);
|
|
921
|
-
try {
|
|
922
|
-
const result = await api.exportFile(viewChat.id, currentCwd());
|
|
923
|
-
if (result.ok !== true || result.filePath === void 0) showToast(result.error ?? "export failed", true);
|
|
924
|
-
else showToast(fmt(tt("export.done"), { filePath: result.filePath }));
|
|
925
|
-
} catch (error) {
|
|
926
|
-
showToast(String(error), true);
|
|
927
|
-
} finally {
|
|
928
|
-
setExporting(false);
|
|
929
|
-
}
|
|
930
|
-
}, [
|
|
931
|
-
viewChat,
|
|
932
|
-
exporting,
|
|
933
|
-
api,
|
|
934
|
-
currentCwd,
|
|
935
|
-
showToast,
|
|
936
|
-
tt
|
|
937
|
-
]);
|
|
938
|
-
const resetArmTimer = (0, react.useCallback)((next) => {
|
|
939
|
-
window.clearTimeout(armTimer.current);
|
|
940
|
-
armTimer.current = next === void 0 ? void 0 : window.setTimeout(next, 3e3);
|
|
941
|
-
}, []);
|
|
942
|
-
const startRename = (0, react.useCallback)((chat) => {
|
|
943
|
-
setDeleteArmId(void 0);
|
|
944
|
-
setClearArm(false);
|
|
945
|
-
setRenameDraft(chat.title);
|
|
946
|
-
setRenamingId(chat.id);
|
|
947
|
-
}, []);
|
|
948
|
-
const commitRename = (0, react.useCallback)(async () => {
|
|
949
|
-
const id = renamingId;
|
|
950
|
-
if (id === void 0) return;
|
|
951
|
-
const title = renameDraft.trim().replace(/\s+/g, " ");
|
|
952
|
-
setRenamingId(void 0);
|
|
953
|
-
const original = chats.find((chat) => chat.id === id);
|
|
954
|
-
if (title === "" || original !== void 0 && title === original.title) return;
|
|
955
|
-
try {
|
|
956
|
-
const result = await api.renameChat(id, title);
|
|
957
|
-
if (result.ok !== true) showToast(fmt(tt("toast.rename.failed"), { error: result.error ?? "" }), true);
|
|
958
|
-
else showToast(tt("toast.rename.done"));
|
|
959
|
-
} catch (error) {
|
|
960
|
-
showToast(fmt(tt("toast.rename.failed"), { error: String(error) }), true);
|
|
961
|
-
}
|
|
962
|
-
}, [
|
|
963
|
-
renamingId,
|
|
964
|
-
renameDraft,
|
|
965
|
-
chats,
|
|
966
|
-
api,
|
|
967
|
-
showToast,
|
|
968
|
-
tt
|
|
969
|
-
]);
|
|
970
|
-
const cancelRename = (0, react.useCallback)(() => {
|
|
971
|
-
setRenamingId(void 0);
|
|
972
|
-
setRenameDraft("");
|
|
973
|
-
}, []);
|
|
974
|
-
const requestDelete = (0, react.useCallback)(async (id) => {
|
|
975
|
-
if (deleteArmId !== id) {
|
|
976
|
-
setClearArm(false);
|
|
977
|
-
setRenamingId(void 0);
|
|
978
|
-
setDeleteArmId(id);
|
|
979
|
-
resetArmTimer(() => setDeleteArmId(void 0));
|
|
980
|
-
return;
|
|
981
|
-
}
|
|
982
|
-
resetArmTimer(void 0);
|
|
983
|
-
setDeleteArmId(void 0);
|
|
984
|
-
try {
|
|
985
|
-
const result = await api.deleteChat(id);
|
|
986
|
-
if (result.ok !== true) showToast(fmt(tt("toast.delete.failed"), { error: result.error ?? "" }), true);
|
|
987
|
-
else showToast(tt("toast.delete.done"));
|
|
988
340
|
} catch (error) {
|
|
989
|
-
|
|
341
|
+
mark('bad', T.failed)
|
|
342
|
+
result.setAttribute('data-state', 'bad')
|
|
343
|
+
result.textContent = error instanceof Error ? error.message : String(error)
|
|
990
344
|
}
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
resetArmTimer
|
|
997
|
-
]);
|
|
998
|
-
const requestClear = (0, react.useCallback)(async () => {
|
|
999
|
-
if (chats.length === 0) return;
|
|
1000
|
-
if (!clearArm) {
|
|
1001
|
-
setDeleteArmId(void 0);
|
|
1002
|
-
setRenamingId(void 0);
|
|
1003
|
-
setClearArm(true);
|
|
1004
|
-
resetArmTimer(() => setClearArm(false));
|
|
1005
|
-
return;
|
|
1006
|
-
}
|
|
1007
|
-
resetArmTimer(void 0);
|
|
1008
|
-
setClearArm(false);
|
|
1009
|
-
try {
|
|
1010
|
-
const result = await api.clearChats();
|
|
1011
|
-
if (result.ok !== true) showToast(fmt(tt("toast.clear.failed"), { error: result.error ?? "" }), true);
|
|
1012
|
-
else showToast(tt("toast.clear.done"));
|
|
1013
|
-
} catch (error) {
|
|
1014
|
-
showToast(fmt(tt("toast.clear.failed"), { error: String(error) }), true);
|
|
1015
|
-
}
|
|
1016
|
-
}, [
|
|
1017
|
-
chats.length,
|
|
1018
|
-
clearArm,
|
|
1019
|
-
api,
|
|
1020
|
-
showToast,
|
|
1021
|
-
tt,
|
|
1022
|
-
resetArmTimer
|
|
1023
|
-
]);
|
|
1024
|
-
const engineState = state?.engine ?? "stopped";
|
|
1025
|
-
const engineError = state?.engineError;
|
|
1026
|
-
const statusDot = busy ? "busy" : engineState === "error" ? "bad" : loggedIn === true ? "ok" : "warn";
|
|
1027
|
-
const statusText = busy ? tt("composer.busy") : engineState === "error" ? `${tt("status.engine.error")}${engineError !== void 0 ? `: ${engineError}` : ""}` : engineState === "launching" ? tt("status.engine.launching") : loggedIn === true ? tt("status.loggedIn") : loggedIn === false ? tt("status.notLoggedIn") : tt("status.unknown");
|
|
1028
|
-
const togglesDisabled = busy || loggedIn !== true;
|
|
1029
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1030
|
-
className: panel_module_css_default.panel,
|
|
1031
|
-
children: [
|
|
1032
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
|
|
1033
|
-
className: panel_module_css_default.panelHeader,
|
|
1034
|
-
children: [
|
|
1035
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
1036
|
-
className: panel_module_css_default.panelTitle,
|
|
1037
|
-
children: tt("panel.title")
|
|
1038
|
-
}),
|
|
1039
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: panel_module_css_default.headerSpacer }),
|
|
1040
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1041
|
-
className: panel_module_css_default.statusChip,
|
|
1042
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1043
|
-
className: panel_module_css_default.statusDot,
|
|
1044
|
-
"data-state": statusDot
|
|
1045
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1046
|
-
className: panel_module_css_default.statusText,
|
|
1047
|
-
children: statusText
|
|
1048
|
-
})]
|
|
1049
|
-
}),
|
|
1050
|
-
loggedIn !== true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1051
|
-
className: `${panel_module_css_default.button} ${panel_module_css_default.buttonPrimary}`,
|
|
1052
|
-
onClick: () => {
|
|
1053
|
-
openLogin();
|
|
1054
|
-
},
|
|
1055
|
-
children: tt("action.openLogin")
|
|
1056
|
-
}),
|
|
1057
|
-
engineState !== "stopped" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1058
|
-
className: panel_module_css_default.buttonGhost,
|
|
1059
|
-
onClick: () => {
|
|
1060
|
-
closeBrowser();
|
|
1061
|
-
},
|
|
1062
|
-
children: tt("action.closeBrowser")
|
|
1063
|
-
})
|
|
1064
|
-
]
|
|
1065
|
-
}),
|
|
1066
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1067
|
-
className: panel_module_css_default.panelBody,
|
|
1068
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("aside", {
|
|
1069
|
-
className: panel_module_css_default.sidebar,
|
|
1070
|
-
children: [
|
|
1071
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1072
|
-
className: `${panel_module_css_default.button} ${panel_module_css_default.buttonPrimary} ${panel_module_css_default.sidebarNew}`,
|
|
1073
|
-
onClick: () => {
|
|
1074
|
-
newChat();
|
|
1075
|
-
},
|
|
1076
|
-
children: tt("action.newChat")
|
|
1077
|
-
}),
|
|
1078
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1079
|
-
className: `${panel_module_css_default.buttonGhost} ${panel_module_css_default.sidebarNew}`,
|
|
1080
|
-
disabled: loggedIn !== true,
|
|
1081
|
-
onClick: () => {
|
|
1082
|
-
toggleRecover();
|
|
1083
|
-
},
|
|
1084
|
-
title: tt("recover.hint"),
|
|
1085
|
-
children: tt("action.recover")
|
|
1086
|
-
}),
|
|
1087
|
-
recoverOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1088
|
-
className: panel_module_css_default.recoverList,
|
|
1089
|
-
children: webMissing.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1090
|
-
className: panel_module_css_default.recoverEmpty,
|
|
1091
|
-
children: tt("recover.empty")
|
|
1092
|
-
}) : webMissing.map((title) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1093
|
-
className: panel_module_css_default.recoverItem,
|
|
1094
|
-
disabled: recovering,
|
|
1095
|
-
onClick: () => {
|
|
1096
|
-
recoverFromWeb(title);
|
|
1097
|
-
},
|
|
1098
|
-
title,
|
|
1099
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1100
|
-
className: panel_module_css_default.recoverTitle,
|
|
1101
|
-
children: title
|
|
1102
|
-
})
|
|
1103
|
-
}, title))
|
|
1104
|
-
}),
|
|
1105
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1106
|
-
className: panel_module_css_default.sidebarList,
|
|
1107
|
-
children: chats.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1108
|
-
className: panel_module_css_default.sidebarEmpty,
|
|
1109
|
-
children: tt("sidebar.empty")
|
|
1110
|
-
}) : chats.map((chat) => renamingId === chat.id ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1111
|
-
className: panel_module_css_default.sidebarItem,
|
|
1112
|
-
"data-active": chat.id === viewChatId ? "true" : void 0,
|
|
1113
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1114
|
-
className: panel_module_css_default.sidebarRename,
|
|
1115
|
-
children: [
|
|
1116
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1117
|
-
className: panel_module_css_default.sidebarRenameInput,
|
|
1118
|
-
value: renameDraft,
|
|
1119
|
-
placeholder: tt("chat.rename.placeholder"),
|
|
1120
|
-
"aria-label": "rename",
|
|
1121
|
-
autoFocus: true,
|
|
1122
|
-
onChange: (event) => setRenameDraft(event.target.value),
|
|
1123
|
-
onKeyDown: (event) => {
|
|
1124
|
-
if (event.key === "Enter") {
|
|
1125
|
-
event.preventDefault();
|
|
1126
|
-
commitRename();
|
|
1127
|
-
} else if (event.key === "Escape") cancelRename();
|
|
1128
|
-
}
|
|
1129
|
-
}),
|
|
1130
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1131
|
-
className: panel_module_css_default.sidebarAction,
|
|
1132
|
-
onClick: () => {
|
|
1133
|
-
commitRename();
|
|
1134
|
-
},
|
|
1135
|
-
"aria-label": tt("chat.rename.ok"),
|
|
1136
|
-
dangerouslySetInnerHTML: { __html: CHECK_ICON }
|
|
1137
|
-
}),
|
|
1138
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1139
|
-
className: panel_module_css_default.sidebarAction,
|
|
1140
|
-
onClick: cancelRename,
|
|
1141
|
-
"aria-label": tt("chat.rename.cancel"),
|
|
1142
|
-
dangerouslySetInnerHTML: { __html: CROSS_ICON }
|
|
1143
|
-
})
|
|
1144
|
-
]
|
|
1145
|
-
})
|
|
1146
|
-
}, chat.id) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1147
|
-
className: panel_module_css_default.sidebarItem,
|
|
1148
|
-
"data-active": chat.id === viewChatId ? "true" : void 0,
|
|
1149
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1150
|
-
className: panel_module_css_default.sidebarRow,
|
|
1151
|
-
onClick: () => setViewChatId(chat.id),
|
|
1152
|
-
title: chat.title,
|
|
1153
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1154
|
-
className: panel_module_css_default.sidebarTitle,
|
|
1155
|
-
children: chat.title
|
|
1156
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1157
|
-
className: panel_module_css_default.sidebarMeta,
|
|
1158
|
-
children: fmt(tt("chats.count"), { count: String(chat.messages.length) })
|
|
1159
|
-
})]
|
|
1160
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1161
|
-
className: panel_module_css_default.sidebarActions,
|
|
1162
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1163
|
-
className: panel_module_css_default.sidebarAction,
|
|
1164
|
-
onClick: () => startRename(chat),
|
|
1165
|
-
"aria-label": tt("chat.rename"),
|
|
1166
|
-
title: tt("chat.rename"),
|
|
1167
|
-
dangerouslySetInnerHTML: { __html: PENCIL_ICON }
|
|
1168
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1169
|
-
className: deleteArmId === chat.id ? panel_module_css_default.sidebarActionDanger : panel_module_css_default.sidebarAction,
|
|
1170
|
-
onClick: () => {
|
|
1171
|
-
requestDelete(chat.id);
|
|
1172
|
-
},
|
|
1173
|
-
"aria-label": deleteArmId === chat.id ? tt("chat.delete.confirm") : tt("chat.delete"),
|
|
1174
|
-
title: deleteArmId === chat.id ? tt("chat.delete.confirm") : tt("chat.delete"),
|
|
1175
|
-
children: deleteArmId === chat.id ? tt("chat.delete.confirm") : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { dangerouslySetInnerHTML: { __html: TRASH_ICON } })
|
|
1176
|
-
})]
|
|
1177
|
-
})]
|
|
1178
|
-
}, chat.id))
|
|
1179
|
-
}),
|
|
1180
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1181
|
-
className: clearArm ? `${panel_module_css_default.button} ${panel_module_css_default.buttonDanger} ${panel_module_css_default.sidebarClear}` : `${panel_module_css_default.buttonGhost} ${panel_module_css_default.sidebarClear}`,
|
|
1182
|
-
disabled: chats.length === 0,
|
|
1183
|
-
onClick: () => {
|
|
1184
|
-
requestClear();
|
|
1185
|
-
},
|
|
1186
|
-
children: clearArm ? tt("chat.clearAll.confirm") : tt("chat.clearAll")
|
|
1187
|
-
})
|
|
1188
|
-
]
|
|
1189
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1190
|
-
className: panel_module_css_default.main,
|
|
1191
|
-
children: [
|
|
1192
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1193
|
-
className: panel_module_css_default.messageList,
|
|
1194
|
-
ref: listRef,
|
|
1195
|
-
onScroll: handleScroll,
|
|
1196
|
-
children: viewChat === void 0 || viewChat.messages.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1197
|
-
className: panel_module_css_default.emptyState,
|
|
1198
|
-
children: [
|
|
1199
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1200
|
-
className: panel_module_css_default.emptyEyebrow,
|
|
1201
|
-
children: tt("empty.eyebrow")
|
|
1202
|
-
}),
|
|
1203
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
1204
|
-
className: panel_module_css_default.emptyTitle,
|
|
1205
|
-
children: tt("empty.title")
|
|
1206
|
-
}),
|
|
1207
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1208
|
-
className: panel_module_css_default.emptyBody,
|
|
1209
|
-
children: tt("empty.body")
|
|
1210
|
-
}),
|
|
1211
|
-
loggedIn !== true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1212
|
-
className: `${panel_module_css_default.button} ${panel_module_css_default.buttonPrimary}`,
|
|
1213
|
-
onClick: () => {
|
|
1214
|
-
openLogin();
|
|
1215
|
-
},
|
|
1216
|
-
children: tt("action.openLogin")
|
|
1217
|
-
})
|
|
1218
|
-
]
|
|
1219
|
-
}) : viewChat.messages.map((message) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1220
|
-
className: panel_module_css_default.messageRow,
|
|
1221
|
-
"data-role": message.role,
|
|
1222
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1223
|
-
className: panel_module_css_default.messageMeta,
|
|
1224
|
-
children: [
|
|
1225
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1226
|
-
className: panel_module_css_default.messageSpeaker,
|
|
1227
|
-
children: message.role === "user" ? tt("role.you") : modelLabel(viewChat.model)
|
|
1228
|
-
}),
|
|
1229
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1230
|
-
className: panel_module_css_default.messageTime,
|
|
1231
|
-
children: new Date(message.ts).toLocaleTimeString()
|
|
1232
|
-
}),
|
|
1233
|
-
message.streaming === true && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1234
|
-
className: panel_module_css_default.streamBadge,
|
|
1235
|
-
children: [tt("streaming"), "…"]
|
|
1236
|
-
})
|
|
1237
|
-
]
|
|
1238
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1239
|
-
className: panel_module_css_default.messageBubble,
|
|
1240
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Markdown, { source: message.content }), message.error !== void 0 && message.content !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1241
|
-
className: panel_module_css_default.messageError,
|
|
1242
|
-
children: fmt(tt("msg.error"), { error: message.error })
|
|
1243
|
-
})]
|
|
1244
|
-
})]
|
|
1245
|
-
}, message.id))
|
|
1246
|
-
}),
|
|
1247
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1248
|
-
className: panel_module_css_default.composer,
|
|
1249
|
-
children: [
|
|
1250
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1251
|
-
className: panel_module_css_default.composerToolbar,
|
|
1252
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1253
|
-
className: panel_module_css_default.toggleButton,
|
|
1254
|
-
"data-active": deepThink ? "true" : void 0,
|
|
1255
|
-
disabled: togglesDisabled,
|
|
1256
|
-
title: tt("toggle.deepThink.hint"),
|
|
1257
|
-
"aria-pressed": deepThink,
|
|
1258
|
-
onClick: () => {
|
|
1259
|
-
toggleDeepThink();
|
|
1260
|
-
},
|
|
1261
|
-
children: tt("toggle.deepThink")
|
|
1262
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1263
|
-
className: panel_module_css_default.toggleButton,
|
|
1264
|
-
"data-active": search ? "true" : void 0,
|
|
1265
|
-
disabled: togglesDisabled,
|
|
1266
|
-
title: tt("toggle.search.hint"),
|
|
1267
|
-
"aria-pressed": search,
|
|
1268
|
-
onClick: () => {
|
|
1269
|
-
toggleSearch();
|
|
1270
|
-
},
|
|
1271
|
-
children: tt("toggle.search")
|
|
1272
|
-
})]
|
|
1273
|
-
}),
|
|
1274
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
1275
|
-
className: panel_module_css_default.composerInput,
|
|
1276
|
-
value: draft,
|
|
1277
|
-
placeholder: busy ? tt("composer.busy") : loggedIn !== true ? tt("composer.notLoggedIn") : tt("composer.placeholder"),
|
|
1278
|
-
disabled: busy || loggedIn !== true,
|
|
1279
|
-
onChange: (event) => setDraft(event.target.value),
|
|
1280
|
-
onKeyDown: (event) => {
|
|
1281
|
-
if (event.key === "Enter" && !event.shiftKey) {
|
|
1282
|
-
event.preventDefault();
|
|
1283
|
-
send();
|
|
1284
|
-
}
|
|
1285
|
-
}
|
|
1286
|
-
}),
|
|
1287
|
-
attachOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1288
|
-
className: panel_module_css_default.attachInput,
|
|
1289
|
-
value: imagePaths,
|
|
1290
|
-
placeholder: tt("composer.attach.placeholder"),
|
|
1291
|
-
disabled: busy || loggedIn !== true,
|
|
1292
|
-
onChange: (event) => setImagePaths(event.target.value)
|
|
1293
|
-
}),
|
|
1294
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1295
|
-
className: panel_module_css_default.composerFooter,
|
|
1296
|
-
children: [
|
|
1297
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1298
|
-
className: panel_module_css_default.toggleButton,
|
|
1299
|
-
"data-active": attachOpen ? "true" : void 0,
|
|
1300
|
-
title: tt("composer.attach.hint"),
|
|
1301
|
-
"aria-pressed": attachOpen,
|
|
1302
|
-
disabled: busy || loggedIn !== true,
|
|
1303
|
-
onClick: () => setAttachOpen((open) => !open),
|
|
1304
|
-
children: tt("composer.attach")
|
|
1305
|
-
}),
|
|
1306
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1307
|
-
className: panel_module_css_default.composerHint,
|
|
1308
|
-
children: tt("composer.hint")
|
|
1309
|
-
}),
|
|
1310
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: panel_module_css_default.composerSpacer }),
|
|
1311
|
-
streaming ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1312
|
-
className: `${panel_module_css_default.button} ${panel_module_css_default.buttonDanger}`,
|
|
1313
|
-
onClick: () => {
|
|
1314
|
-
stop();
|
|
1315
|
-
},
|
|
1316
|
-
children: tt("action.stop")
|
|
1317
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1318
|
-
className: `${panel_module_css_default.button} ${panel_module_css_default.buttonPrimary}`,
|
|
1319
|
-
disabled: busy || loggedIn !== true || draft.trim() === "" && imagePaths.trim() === "",
|
|
1320
|
-
onClick: () => {
|
|
1321
|
-
send();
|
|
1322
|
-
},
|
|
1323
|
-
children: tt("action.send")
|
|
1324
|
-
})
|
|
1325
|
-
]
|
|
1326
|
-
})
|
|
1327
|
-
]
|
|
1328
|
-
}),
|
|
1329
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1330
|
-
className: panel_module_css_default.handoffDock,
|
|
1331
|
-
children: [
|
|
1332
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1333
|
-
className: panel_module_css_default.handoffEyebrow,
|
|
1334
|
-
children: tt("handoff.eyebrow")
|
|
1335
|
-
}),
|
|
1336
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: panel_module_css_default.handoffSpacer }),
|
|
1337
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1338
|
-
className: panel_module_css_default.segmented,
|
|
1339
|
-
role: "group",
|
|
1340
|
-
"aria-label": "transfer mode",
|
|
1341
|
-
title: tt("transfer.mode.hint"),
|
|
1342
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1343
|
-
className: panel_module_css_default.segmentedButton,
|
|
1344
|
-
"data-active": transferMode === "distill" ? "true" : void 0,
|
|
1345
|
-
onClick: () => setTransferMode("distill"),
|
|
1346
|
-
children: tt("transfer.mode.distill")
|
|
1347
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1348
|
-
className: panel_module_css_default.segmentedButton,
|
|
1349
|
-
"data-active": transferMode === "raw" ? "true" : void 0,
|
|
1350
|
-
onClick: () => setTransferMode("raw"),
|
|
1351
|
-
children: tt("transfer.mode.raw")
|
|
1352
|
-
})]
|
|
1353
|
-
}),
|
|
1354
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
1355
|
-
className: panel_module_css_default.workspaceSelect,
|
|
1356
|
-
value: targetSessionId ?? "",
|
|
1357
|
-
disabled: continuationTargets.length === 0,
|
|
1358
|
-
title: tt("transfer.session.hint"),
|
|
1359
|
-
"aria-label": tt("transfer.session.label"),
|
|
1360
|
-
onChange: (event) => setTargetSessionId(event.target.value === "" ? void 0 : event.target.value),
|
|
1361
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1362
|
-
value: "",
|
|
1363
|
-
children: tt("transfer.session.new")
|
|
1364
|
-
}), continuationTargets.map((session) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
|
|
1365
|
-
value: session.sessionId,
|
|
1366
|
-
children: [session.title, session.running === true ? " ·●" : ""]
|
|
1367
|
-
}, session.sessionId))]
|
|
1368
|
-
}),
|
|
1369
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
1370
|
-
className: panel_module_css_default.workspaceSelect,
|
|
1371
|
-
value: targetWorkspaceId ?? "",
|
|
1372
|
-
disabled: targetSessionId !== void 0,
|
|
1373
|
-
title: tt("transfer.workspace.hint"),
|
|
1374
|
-
"aria-label": tt("transfer.workspace.label"),
|
|
1375
|
-
onChange: (event) => {
|
|
1376
|
-
workspaceOverridden.current = true;
|
|
1377
|
-
setTargetWorkspaceId(event.target.value === "" ? void 0 : event.target.value);
|
|
1378
|
-
},
|
|
1379
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1380
|
-
value: "",
|
|
1381
|
-
children: tt("transfer.workspace.ungrouped")
|
|
1382
|
-
}), workspaceItems.map((ws) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
|
|
1383
|
-
value: ws.workspaceId,
|
|
1384
|
-
children: [ws.title, ws.path !== ws.title ? ` — ${ws.path}` : ""]
|
|
1385
|
-
}, ws.workspaceId))]
|
|
1386
|
-
}),
|
|
1387
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1388
|
-
className: panel_module_css_default.buttonGhost,
|
|
1389
|
-
onClick: () => {
|
|
1390
|
-
createWorkspace();
|
|
1391
|
-
},
|
|
1392
|
-
title: tt("transfer.workspace.new"),
|
|
1393
|
-
disabled: targetSessionId !== void 0,
|
|
1394
|
-
children: tt("transfer.workspace.new")
|
|
1395
|
-
}),
|
|
1396
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1397
|
-
className: `${panel_module_css_default.button} ${panel_module_css_default.buttonPrimary}`,
|
|
1398
|
-
disabled: viewChat === void 0 || transferring,
|
|
1399
|
-
onClick: () => {
|
|
1400
|
-
transferToHarness();
|
|
1401
|
-
},
|
|
1402
|
-
children: tt("action.transferToHarness")
|
|
1403
|
-
}),
|
|
1404
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1405
|
-
className: panel_module_css_default.button,
|
|
1406
|
-
disabled: viewChat === void 0 || exporting,
|
|
1407
|
-
onClick: () => {
|
|
1408
|
-
exportFile();
|
|
1409
|
-
},
|
|
1410
|
-
children: tt("action.exportFile")
|
|
1411
|
-
})
|
|
1412
|
-
]
|
|
1413
|
-
})
|
|
1414
|
-
]
|
|
1415
|
-
})]
|
|
1416
|
-
}),
|
|
1417
|
-
toast !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1418
|
-
className: panel_module_css_default.toast,
|
|
1419
|
-
"data-error": toast.error === true ? "true" : void 0,
|
|
1420
|
-
children: toast.text
|
|
1421
|
-
})
|
|
1422
|
-
]
|
|
1423
|
-
});
|
|
1424
|
-
}
|
|
1425
|
-
//#endregion
|
|
1426
|
-
//#region src/client/mount.tsx
|
|
1427
|
-
/**
|
|
1428
|
-
* Panel view mounting. The conversation slot is single-occupant and external
|
|
1429
|
-
* plugins cannot declare slots, so the panel takes over the center column at
|
|
1430
|
-
* the DOM level (same strategy as dsh-ssh / dsh-task-board): a container is
|
|
1431
|
-
* appended inside the center column as an extra trailing child React never
|
|
1432
|
-
* manages, and a stylesheet rule hides the conversation content while the
|
|
1433
|
-
* panel is active. Toggling is a data attribute on <html> — no React
|
|
1434
|
-
* involvement, so the conversation subtree underneath stays mounted and
|
|
1435
|
-
* stateful.
|
|
1436
|
-
*/
|
|
1437
|
-
const CONVERSATION_COLUMN_SELECTOR = "[data-pane=\"conversation\"], [class*=\"centerCol\"]";
|
|
1438
|
-
const ACTIVE_ATTR = "data-dsh-webchat-active";
|
|
1439
|
-
/** The sibling panels' activation attributes, removed when this panel opens. */
|
|
1440
|
-
const OTHER_ACTIVE_ATTRS = ["data-dsh-taskboard-active", "data-dsh-ssh-active"];
|
|
1441
|
-
/** Cross-plugin activation event; detail is the activating panel name. */
|
|
1442
|
-
const ACTIVATE_EVENT = "dsh-panel-activate";
|
|
1443
|
-
const PANEL_NAME = "webchat";
|
|
1444
|
-
/** Find the center column, or undefined while the frame is not mounted. */
|
|
1445
|
-
function conversationColumn() {
|
|
1446
|
-
return document.querySelector(CONVERSATION_COLUMN_SELECTOR) ?? void 0;
|
|
345
|
+
idle()
|
|
346
|
+
await refresh()
|
|
347
|
+
})
|
|
348
|
+
|
|
349
|
+
return { refresh: refresh }
|
|
1447
350
|
}
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
351
|
+
|
|
352
|
+
/** Mount the panel view into the center column and bind its visibility. */
|
|
353
|
+
function mountPanel(controller) {
|
|
354
|
+
var container
|
|
355
|
+
var panel
|
|
356
|
+
|
|
357
|
+
var ensure = function () {
|
|
358
|
+
if (container !== undefined) {
|
|
359
|
+
if (container.isConnected) return
|
|
360
|
+
container.remove()
|
|
361
|
+
container = undefined
|
|
362
|
+
panel = undefined
|
|
363
|
+
}
|
|
364
|
+
var column = conversationColumn()
|
|
365
|
+
if (column === undefined) return
|
|
366
|
+
container = document.createElement('div')
|
|
367
|
+
container.setAttribute(VIEW_ATTR, '')
|
|
368
|
+
container.setAttribute('data-dsh-plugin', 'webchat')
|
|
369
|
+
column.appendChild(container)
|
|
370
|
+
panel = buildPanel(container)
|
|
371
|
+
void panel.refresh()
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
var waitObserver = new MutationObserver(function () { ensure() })
|
|
375
|
+
|
|
376
|
+
var applyActive = function () {
|
|
377
|
+
if (controller.isOpen()) {
|
|
378
|
+
OTHER_ACTIVE_ATTRS.forEach(function (attr) { document.documentElement.removeAttribute(attr) })
|
|
379
|
+
document.documentElement.setAttribute(ACTIVE_ATTR, '')
|
|
380
|
+
document.dispatchEvent(new CustomEvent(ACTIVATE_EVENT, { detail: PANEL_NAME }))
|
|
381
|
+
ensure()
|
|
382
|
+
if (panel !== undefined) void panel.refresh()
|
|
383
|
+
} else {
|
|
384
|
+
document.documentElement.removeAttribute(ACTIVE_ATTR)
|
|
1465
385
|
}
|
|
1466
|
-
const column = conversationColumn();
|
|
1467
|
-
if (column === void 0) return;
|
|
1468
|
-
container = document.createElement("div");
|
|
1469
|
-
container.dataset.dshWebchatView = "";
|
|
1470
|
-
container.dataset.dshPlugin = "webchat";
|
|
1471
|
-
container.className = panel_module_css_default.view;
|
|
1472
|
-
column.appendChild(container);
|
|
1473
|
-
root = (0, react_dom_client.createRoot)(container);
|
|
1474
|
-
root.render(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(WebChatPanel, {
|
|
1475
|
-
api,
|
|
1476
|
-
tt,
|
|
1477
|
-
sessions,
|
|
1478
|
-
workspaces,
|
|
1479
|
-
currentCwd
|
|
1480
|
-
}));
|
|
1481
|
-
};
|
|
1482
|
-
const waitObserver = new MutationObserver(() => {
|
|
1483
|
-
ensure();
|
|
1484
|
-
});
|
|
1485
|
-
waitObserver.observe(document.body, {
|
|
1486
|
-
childList: true,
|
|
1487
|
-
subtree: true
|
|
1488
|
-
});
|
|
1489
|
-
const applyActive = () => {
|
|
1490
|
-
if (controller.getSnapshot().panelOpen) {
|
|
1491
|
-
for (const attr of OTHER_ACTIVE_ATTRS) document.documentElement.removeAttribute(attr);
|
|
1492
|
-
document.documentElement.setAttribute(ACTIVE_ATTR, "");
|
|
1493
|
-
document.dispatchEvent(new CustomEvent(ACTIVATE_EVENT, { detail: PANEL_NAME }));
|
|
1494
|
-
} else document.documentElement.removeAttribute(ACTIVE_ATTR);
|
|
1495
|
-
};
|
|
1496
|
-
const onOtherActivate = (event) => {
|
|
1497
|
-
const detail = event.detail;
|
|
1498
|
-
if ((detail === "taskboard" || detail === "ssh") && controller.getSnapshot().panelOpen) controller.close();
|
|
1499
|
-
};
|
|
1500
|
-
const SIDEBAR_ROW_SELECTOR = "[class*=\"sessionRow\"], [class*=\"projectRow\"], [class*=\"searchResultRow\"], [class*=\"searchResultWorkspace\"], [class*=\"newSession\"]";
|
|
1501
|
-
const onClickSidebarRow = (event) => {
|
|
1502
|
-
if (!controller.getSnapshot().panelOpen) return;
|
|
1503
|
-
const target = event.target;
|
|
1504
|
-
if (target === null) return;
|
|
1505
|
-
if (target.closest(SIDEBAR_ROW_SELECTOR) !== null) controller.close();
|
|
1506
|
-
};
|
|
1507
|
-
document.addEventListener("click", onClickSidebarRow, true);
|
|
1508
|
-
document.addEventListener(ACTIVATE_EVENT, onOtherActivate);
|
|
1509
|
-
const unsubscribe = controller.subscribe(applyActive);
|
|
1510
|
-
applyActive();
|
|
1511
|
-
ensure();
|
|
1512
|
-
return () => {
|
|
1513
|
-
document.removeEventListener("click", onClickSidebarRow, true);
|
|
1514
|
-
document.removeEventListener(ACTIVATE_EVENT, onOtherActivate);
|
|
1515
|
-
waitObserver.disconnect();
|
|
1516
|
-
unsubscribe();
|
|
1517
|
-
document.documentElement.removeAttribute(ACTIVE_ATTR);
|
|
1518
|
-
root?.unmount();
|
|
1519
|
-
root = void 0;
|
|
1520
|
-
container?.remove();
|
|
1521
|
-
container = void 0;
|
|
1522
|
-
};
|
|
1523
|
-
}
|
|
1524
|
-
//#endregion
|
|
1525
|
-
//#region src/client/sidebar-entry-core.ts
|
|
1526
|
-
/** Find the sidebar shell root element, or undefined while not yet mounted. */
|
|
1527
|
-
function sidebarRoot() {
|
|
1528
|
-
const column = document.querySelector("[data-pane=\"sidebar\"], [class*=\"sidebarCol\"]");
|
|
1529
|
-
if (column === null) return void 0;
|
|
1530
|
-
return column.querySelector("[class*=\"logoRow\"]")?.parentElement ?? column.firstElementChild;
|
|
1531
|
-
}
|
|
1532
|
-
/** The New Session button: nested in the logo row on current shells, a direct child on legacy shells. */
|
|
1533
|
-
function newSessionButton(root) {
|
|
1534
|
-
const nested = root.querySelector("button[class*=\"newSession\"]");
|
|
1535
|
-
if (nested !== null) return nested;
|
|
1536
|
-
for (const child of root.children) if (child.tagName === "BUTTON") return child;
|
|
1537
|
-
}
|
|
1538
|
-
/** Build the entry row (a detached button; insert once the shell is up). */
|
|
1539
|
-
function createEntry(options) {
|
|
1540
|
-
const entry = document.createElement("button");
|
|
1541
|
-
entry.type = "button";
|
|
1542
|
-
entry.setAttribute(options.rowAttribute, "");
|
|
1543
|
-
if (options.plugin !== void 0) {
|
|
1544
|
-
entry.setAttribute("data-dsh-plugin", options.plugin);
|
|
1545
|
-
entry.setAttribute("data-dsh-part", "sidebar-entry");
|
|
1546
386
|
}
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
387
|
+
var onOtherActivate = function (event) {
|
|
388
|
+
var detail = event.detail
|
|
389
|
+
if ((detail === 'taskboard' || detail === 'ssh') && controller.isOpen()) controller.close()
|
|
390
|
+
}
|
|
391
|
+
var SIDEBAR_ROW_SELECTOR = '[class*="sessionRow"], [class*="projectRow"], [class*="searchResultRow"], [class*="searchResultWorkspace"], [class*="newSession"]'
|
|
392
|
+
var onClickSidebarRow = function (event) {
|
|
393
|
+
if (!controller.isOpen()) return
|
|
394
|
+
var target = event.target
|
|
395
|
+
if (target === null || target.closest === undefined) return
|
|
396
|
+
if (target.closest(SIDEBAR_ROW_SELECTOR) !== null) controller.close()
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
document.addEventListener('click', onClickSidebarRow, true)
|
|
400
|
+
document.addEventListener(ACTIVATE_EVENT, onOtherActivate)
|
|
401
|
+
var unsubscribe = controller.subscribe(applyActive)
|
|
402
|
+
applyActive()
|
|
403
|
+
ensure()
|
|
404
|
+
waitObserver.observe(document.body, { childList: true, subtree: true })
|
|
405
|
+
|
|
406
|
+
return function () {
|
|
407
|
+
document.removeEventListener('click', onClickSidebarRow, true)
|
|
408
|
+
document.removeEventListener(ACTIVATE_EVENT, onOtherActivate)
|
|
409
|
+
waitObserver.disconnect()
|
|
410
|
+
unsubscribe()
|
|
411
|
+
document.documentElement.removeAttribute(ACTIVE_ATTR)
|
|
412
|
+
if (container !== undefined) container.remove()
|
|
413
|
+
container = undefined
|
|
414
|
+
panel = undefined
|
|
1564
415
|
}
|
|
1565
|
-
return true;
|
|
1566
|
-
}
|
|
1567
|
-
/**
|
|
1568
|
-
* Mount the sidebar entry, waiting for the shell to render and self-healing
|
|
1569
|
-
* on later React re-renders.
|
|
1570
|
-
* @param options - the row's attribute/icon/copy/action/ordering configuration.
|
|
1571
|
-
* @returns disposer removing the entry and its observers.
|
|
1572
|
-
*/
|
|
1573
|
-
function mountSidebarEntry$1(options) {
|
|
1574
|
-
if (typeof document !== "undefined" && document.querySelector(options.rowSelector) !== null) return () => {};
|
|
1575
|
-
const entry = createEntry(options);
|
|
1576
|
-
let root;
|
|
1577
|
-
let placed = false;
|
|
1578
|
-
const tryPlace = () => {
|
|
1579
|
-
if (root !== void 0 && !root.isConnected) {
|
|
1580
|
-
rootObserver.disconnect();
|
|
1581
|
-
root = void 0;
|
|
1582
|
-
placed = false;
|
|
1583
|
-
}
|
|
1584
|
-
if (placed) {
|
|
1585
|
-
if (document.body.contains(entry)) return;
|
|
1586
|
-
rootObserver.disconnect();
|
|
1587
|
-
root = void 0;
|
|
1588
|
-
placed = false;
|
|
1589
|
-
}
|
|
1590
|
-
root ??= sidebarRoot();
|
|
1591
|
-
if (root === void 0) return;
|
|
1592
|
-
placed = placeEntry(root, entry, options);
|
|
1593
|
-
if (placed) rootObserver.observe(root, {
|
|
1594
|
-
childList: true,
|
|
1595
|
-
subtree: true
|
|
1596
|
-
});
|
|
1597
|
-
};
|
|
1598
|
-
const waitObserver = new MutationObserver(() => {
|
|
1599
|
-
tryPlace();
|
|
1600
|
-
});
|
|
1601
|
-
waitObserver.observe(document.body, {
|
|
1602
|
-
childList: true,
|
|
1603
|
-
subtree: true
|
|
1604
|
-
});
|
|
1605
|
-
const rootObserver = new MutationObserver(() => {
|
|
1606
|
-
if (root === void 0 || !root.isConnected) {
|
|
1607
|
-
placed = false;
|
|
1608
|
-
tryPlace();
|
|
1609
|
-
return;
|
|
1610
|
-
}
|
|
1611
|
-
if (!root.contains(entry)) placed = placeEntry(root, entry, options);
|
|
1612
|
-
});
|
|
1613
|
-
const unsubscribeActive = options.active === void 0 ? void 0 : (() => {
|
|
1614
|
-
const syncActive = () => {
|
|
1615
|
-
if (options.active.isOpen()) entry.dataset.active = "true";
|
|
1616
|
-
else delete entry.dataset.active;
|
|
1617
|
-
};
|
|
1618
|
-
const unsubscribe = options.active.subscribe(syncActive);
|
|
1619
|
-
syncActive();
|
|
1620
|
-
return unsubscribe;
|
|
1621
|
-
})();
|
|
1622
|
-
tryPlace();
|
|
1623
|
-
return () => {
|
|
1624
|
-
waitObserver.disconnect();
|
|
1625
|
-
rootObserver.disconnect();
|
|
1626
|
-
unsubscribeActive?.();
|
|
1627
|
-
entry.remove();
|
|
1628
|
-
};
|
|
1629
|
-
}
|
|
1630
|
-
//#endregion
|
|
1631
|
-
//#region src/client/sidebar-entry.ts
|
|
1632
|
-
/** Stable data attribute identifying the injected entry row. */
|
|
1633
|
-
const ENTRY_SELECTOR = "[data-dsh-webchat-entry]";
|
|
1634
|
-
/** Inline icon (matches the shell's 16px nav-icon look): a chat bubble with a globe dot. */
|
|
1635
|
-
const ICON = "<svg viewBox=\"0 0 16 16\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M8 2.5a5.5 5.5 0 0 0-4.7 8.3L2.5 13.5l2.8-.8A5.5 5.5 0 1 0 8 2.5z\"/><circle cx=\"8\" cy=\"8\" r=\"1.1\" fill=\"currentColor\" stroke=\"none\"/></svg>";
|
|
1636
|
-
/**
|
|
1637
|
-
* Mount the sidebar entry, waiting for the shell to render and self-healing
|
|
1638
|
-
* on later React re-renders.
|
|
1639
|
-
* @param deps - controller and copy accessor.
|
|
1640
|
-
* @returns disposer removing the entry and its observers.
|
|
1641
|
-
*/
|
|
1642
|
-
function mountSidebarEntry(deps) {
|
|
1643
|
-
return mountSidebarEntry$1({
|
|
1644
|
-
rowAttribute: "data-dsh-webchat-entry",
|
|
1645
|
-
rowSelector: ENTRY_SELECTOR,
|
|
1646
|
-
plugin: "webchat",
|
|
1647
|
-
icon: ICON,
|
|
1648
|
-
css: panel_module_css_default,
|
|
1649
|
-
label: () => deps.tt("entry.label"),
|
|
1650
|
-
tooltip: () => deps.tt("entry.tooltip"),
|
|
1651
|
-
onToggle: () => {
|
|
1652
|
-
deps.controller.toggle();
|
|
1653
|
-
},
|
|
1654
|
-
position: "after",
|
|
1655
|
-
familySelectors: [
|
|
1656
|
-
"[data-dsh-taskboard-entry]",
|
|
1657
|
-
"[data-dsh-ssh-entry]",
|
|
1658
|
-
"[data-dsh-webchat-entry]"
|
|
1659
|
-
],
|
|
1660
|
-
active: {
|
|
1661
|
-
subscribe: (listener) => deps.controller.subscribe(listener),
|
|
1662
|
-
isOpen: () => deps.controller.getSnapshot().panelOpen
|
|
1663
|
-
}
|
|
1664
|
-
});
|
|
1665
416
|
}
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
/** Locale namespace this plugin owns. */
|
|
1669
|
-
const NS = "dsh-webchat";
|
|
1670
|
-
/** Required services (fiber inject waiting — the runtime must be up first). */
|
|
1671
|
-
const inject = [
|
|
1672
|
-
"slots",
|
|
1673
|
-
"locale",
|
|
1674
|
-
"sessions",
|
|
1675
|
-
"workspaces"
|
|
1676
|
-
];
|
|
1677
|
-
/**
|
|
1678
|
-
* Mount the web-chat panel.
|
|
1679
|
-
* @param ctx - client root context (locale + sessions services).
|
|
1680
|
-
*/
|
|
417
|
+
|
|
418
|
+
/** Mount the entry and the panel. */
|
|
1681
419
|
function apply(ctx) {
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
const tt = ctx.locale.bind(NS);
|
|
1691
|
-
const currentCwd = () => {
|
|
1692
|
-
const snapshot = sessions.list.getSnapshot();
|
|
1693
|
-
const current = snapshot.current;
|
|
1694
|
-
if (current === void 0) return void 0;
|
|
1695
|
-
return snapshot.byId[current]?.cwd;
|
|
1696
|
-
};
|
|
1697
|
-
const disposers = [];
|
|
1698
|
-
try {
|
|
1699
|
-
disposers.push(mountSidebarEntry({
|
|
1700
|
-
controller,
|
|
1701
|
-
tt
|
|
1702
|
-
}));
|
|
1703
|
-
disposers.push(mountPanel({
|
|
1704
|
-
sessions,
|
|
1705
|
-
workspaces,
|
|
1706
|
-
api,
|
|
1707
|
-
controller,
|
|
1708
|
-
tt,
|
|
1709
|
-
currentCwd
|
|
1710
|
-
}));
|
|
1711
|
-
} catch (error) {
|
|
1712
|
-
console.warn("[dsh-webchat] mount failed:", error);
|
|
1713
|
-
}
|
|
1714
|
-
ctx.effect(() => () => {
|
|
1715
|
-
for (const dispose of disposers.splice(0)) dispose();
|
|
1716
|
-
}, "dsh-webchat: ui mounts");
|
|
420
|
+
ensureStyle()
|
|
421
|
+
var controller = createController()
|
|
422
|
+
var disposers = [mountSidebarEntry(controller), mountPanel(controller)]
|
|
423
|
+
ctx.effect(function () {
|
|
424
|
+
return function () {
|
|
425
|
+
disposers.splice(0).forEach(function (dispose) { dispose() })
|
|
426
|
+
}
|
|
427
|
+
}, 'dsh-webchat: ui mounts')
|
|
1717
428
|
}
|
|
1718
|
-
//#endregion
|
|
1719
|
-
exports.apply = apply;
|
|
1720
|
-
exports.inject = inject;
|
|
1721
|
-
return module.exports;
|
|
1722
|
-
}
|
|
1723
|
-
});
|
|
1724
429
|
|
|
1725
|
-
|
|
430
|
+
exports.apply = apply
|
|
431
|
+
exports.inject = []
|
|
432
|
+
return module.exports
|
|
433
|
+
},
|
|
434
|
+
})
|