dsh-agy-cli 0.0.0-stage → 0.12.1
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/LICENSE +31 -0
- package/README.en.md +48 -0
- package/README.md +67 -2
- package/agents/dsh-agy-image-view.md +17 -0
- package/agents/dsh-agy-mcp-executor.md +32 -0
- package/agents/dsh-agy-read-only.md +20 -0
- package/agents/dsh-agy-tool-free.md +16 -0
- package/agents/dsh-agy-workspace-write.md +23 -0
- package/bin/dsh-agy-cli.js +9 -0
- package/bin/win32-x64/agy-launcher.exe +0 -0
- package/cordis.patch.yml +16 -0
- package/dist/client.js +4259 -0
- package/lib/agent-installer.d.ts +66 -0
- package/lib/agent-installer.js +147 -0
- package/lib/agent-presets.d.ts +42 -0
- package/lib/agent-presets.js +130 -0
- package/lib/agents-cli.d.ts +3 -0
- package/lib/agents-cli.js +110 -0
- package/lib/agy/diagnostics.d.ts +29 -0
- package/lib/agy/diagnostics.js +109 -0
- package/lib/agy/errors.d.ts +20 -0
- package/lib/agy/errors.js +77 -0
- package/lib/agy/experimental-transport.d.ts +79 -0
- package/lib/agy/experimental-transport.js +612 -0
- package/lib/agy/keepalive.d.ts +140 -0
- package/lib/agy/keepalive.js +445 -0
- package/lib/agy/limiter.d.ts +32 -0
- package/lib/agy/limiter.js +111 -0
- package/lib/agy/log.d.ts +214 -0
- package/lib/agy/log.js +503 -0
- package/lib/agy/models.d.ts +49 -0
- package/lib/agy/models.js +203 -0
- package/lib/agy/overrides.d.ts +17 -0
- package/lib/agy/overrides.js +73 -0
- package/lib/agy/parser.d.ts +102 -0
- package/lib/agy/parser.js +492 -0
- package/lib/agy/persistent-transport.d.ts +101 -0
- package/lib/agy/persistent-transport.js +753 -0
- package/lib/agy/process.d.ts +144 -0
- package/lib/agy/process.js +452 -0
- package/lib/agy/redact.d.ts +3 -0
- package/lib/agy/redact.js +24 -0
- package/lib/agy/stream-protocol.d.ts +50 -0
- package/lib/agy/stream-protocol.js +156 -0
- package/lib/agy/windows-launcher.d.ts +21 -0
- package/lib/agy/windows-launcher.js +76 -0
- package/lib/diagnostics.d.ts +78 -0
- package/lib/diagnostics.js +162 -0
- package/lib/doctor.d.ts +138 -0
- package/lib/doctor.js +940 -0
- package/lib/dsh/context.d.ts +133 -0
- package/lib/dsh/context.js +352 -0
- package/lib/dsh-home.d.ts +6 -0
- package/lib/dsh-home.js +11 -0
- package/lib/index.d.ts +54 -0
- package/lib/index.js +267 -0
- package/lib/mcp/agy-registration.d.ts +70 -0
- package/lib/mcp/agy-registration.js +245 -0
- package/lib/mcp/bridge.d.ts +73 -0
- package/lib/mcp/bridge.js +295 -0
- package/lib/mcp/endpoint.d.ts +41 -0
- package/lib/mcp/endpoint.js +142 -0
- package/lib/mcp/protocol.d.ts +107 -0
- package/lib/mcp/protocol.js +197 -0
- package/lib/mcp/tool-projection.d.ts +64 -0
- package/lib/mcp/tool-projection.js +168 -0
- package/lib/provider/agy.d.ts +153 -0
- package/lib/provider/agy.js +2159 -0
- package/lib/provider/config.d.ts +226 -0
- package/lib/provider/config.js +294 -0
- package/lib/provider/error-codes.d.ts +22 -0
- package/lib/provider/error-codes.js +29 -0
- package/lib/provider/image-bridge.d.ts +60 -0
- package/lib/provider/image-bridge.js +290 -0
- package/lib/provider/mock.d.ts +15 -0
- package/lib/provider/mock.js +78 -0
- package/lib/provider/prompt-budget.d.ts +45 -0
- package/lib/provider/prompt-budget.js +596 -0
- package/lib/provider/serialize.d.ts +15 -0
- package/lib/provider/serialize.js +82 -0
- package/lib/provider/tool-protocol.d.ts +80 -0
- package/lib/provider/tool-protocol.js +848 -0
- package/lib/provider/tool-schema-file.d.ts +11 -0
- package/lib/provider/tool-schema-file.js +27 -0
- package/lib/session/file-store.d.ts +56 -0
- package/lib/session/file-store.js +186 -0
- package/lib/session/store.d.ts +49 -0
- package/lib/session/store.js +62 -0
- package/lib/web/router.d.ts +19 -0
- package/lib/web/router.js +994 -0
- package/package.json +115 -4
package/dist/client.js
ADDED
|
@@ -0,0 +1,4259 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({ id: "dsh-agy-cli", factory: (require) => {
|
|
2
|
+
var __modules = Object.create(null);
|
|
3
|
+
var __require = (specifier) => {
|
|
4
|
+
if (specifier in __modules) {
|
|
5
|
+
const inner = __modules[specifier];
|
|
6
|
+
inner(__require, inner.exports ??= {}, inner);
|
|
7
|
+
return inner.exports;
|
|
8
|
+
}
|
|
9
|
+
return require(specifier);
|
|
10
|
+
};
|
|
11
|
+
__modules["client/brand-icons.js"] = function (require, exports, module) {
|
|
12
|
+
"use strict";
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.UI_PATHS = exports.BRAND_COLORS = exports.BRAND_PATHS = void 0;
|
|
15
|
+
// Brand logos (simple-icons paths, Apache-2.0/CC0 icon data) rendered as
|
|
16
|
+
// inline SVGs — no asset pipeline, no network fetch at runtime.
|
|
17
|
+
exports.BRAND_PATHS = {
|
|
18
|
+
gemini: "M11.04 19.32Q12 21.51 12 24q0-2.49.93-4.68.96-2.19 2.58-3.81t3.81-2.55Q21.51 12 24 12q-2.49 0-4.68-.93a12.3 12.3 0 0 1-3.81-2.58 12.3 12.3 0 0 1-2.58-3.81Q12 2.49 12 0q0 2.49-.96 4.68-.93 2.19-2.55 3.81a12.3 12.3 0 0 1-3.81 2.58Q2.49 12 0 12q2.49 0 4.68.96 2.19.93 3.81 2.55t2.55 3.81",
|
|
19
|
+
claude: "m4.7144 15.9555 4.7174-2.6471.079-.2307-.079-.1275h-.2307l-.7893-.0486-2.6956-.0729-2.3375-.0971-2.2646-.1214-.5707-.1215-.5343-.7042.0546-.3522.4797-.3218.686.0608 1.5179.1032 2.2767.1578 1.6514.0972 2.4468.255h.3886l.0546-.1579-.1336-.0971-.1032-.0972L6.973 9.8356l-2.55-1.6879-1.3356-.9714-.7225-.4918-.3643-.4614-.1578-1.0078.6557-.7225.8803.0607.2246.0607.8925.686 1.9064 1.4754 2.4893 1.8336.3643.3035.1457-.1032.0182-.0728-.164-.2733-1.3539-2.4467-1.445-2.4893-.6435-1.032-.17-.6194c-.0607-.255-.1032-.4674-.1032-.7285L6.287.1335 6.6997 0l.9957.1336.419.3642.6192 1.4147 1.0018 2.2282 1.5543 3.0296.4553.8985.2429.8318.091.255h.1579v-.1457l.1275-1.706.2368-2.0947.2307-2.6957.0789-.7589.3764-.9107.7468-.4918.5828.2793.4797.686-.0668.4433-.2853 1.8517-.5586 2.9021-.3643 1.9429h.2125l.2429-.2429.9835-1.3053 1.6514-2.0643.7286-.8196.85-.9046.5464-.4311h1.0321l.759 1.1293-.34 1.1657-1.0625 1.3478-.8804 1.1414-1.2628 1.7-.7893 1.36.0729.1093.1882-.0183 2.8535-.607 1.5421-.2794 1.8396-.3157.8318.3886.091.3946-.3278.8075-1.967.4857-2.3072.4614-3.4364.8136-.0425.0304.0486.0607 1.5482.1457.6618.0364h1.621l3.0175.2247.7892.522.4736.6376-.079.4857-1.2142.6193-1.6393-.3886-3.825-.9107-1.3113-.3279h-.1822v.1093l1.0929 1.0686 2.0035 1.8092 2.5075 2.3314.1275.5768-.3218.4554-.34-.0486-2.2039-1.6575-.85-.7468-1.9246-1.621h-.1275v.17l.4432.6496 2.3436 3.5214.1214 1.0807-.17.3521-.6071.2125-.6679-.1214-1.3721-1.9246L14.38 17.959l-1.1414-1.9428-.1397.079-.674 7.2552-.3156.3703-.7286.2793-.6071-.4614-.3218-.7468.3218-1.4753.3886-1.9246.3157-1.53.2853-1.9004.17-.6314-.0121-.0425-.1397.0182-1.4328 1.9672-2.1796 2.9446-1.7243 1.8456-.4128.164-.7164-.3704.0667-.6618.4008-.5889 2.386-3.0357 1.4389-1.882.929-1.0868-.0062-.1579h-.0546l-6.3385 4.1164-1.1293.1457-.4857-.4554.0608-.7467.2307-.2429 1.9064-1.3114Z",
|
|
20
|
+
openai: "M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z",
|
|
21
|
+
};
|
|
22
|
+
exports.BRAND_COLORS = {
|
|
23
|
+
gemini: '#8E75B2',
|
|
24
|
+
claude: '#D97757',
|
|
25
|
+
openai: '#10a37f',
|
|
26
|
+
};
|
|
27
|
+
exports.UI_PATHS = {
|
|
28
|
+
trash: 'M3 6h18M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2M10 11v6M14 11v6',
|
|
29
|
+
plus: 'M12 5v14M5 12h14',
|
|
30
|
+
refresh: 'M21.5 2v6h-6M2.5 22v-6h6M2.5 11.5a10 10 0 0 1 17.5-4.5l1.5 2M21.5 12.5a10 10 0 0 1-17.5 4.5l-1.5-2',
|
|
31
|
+
star: 'M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z',
|
|
32
|
+
mail: 'M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2zM22 6l-10 7L2 6',
|
|
33
|
+
globe: 'M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20zM2 12h20M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z',
|
|
34
|
+
zap: 'M13 2L3 14h9l-1 8 10-12h-9l1-8z',
|
|
35
|
+
chevronDown: 'M6 9l6 6 6-6',
|
|
36
|
+
chevronUp: 'M18 15l-6-6-6 6',
|
|
37
|
+
check: 'M20 6L9 17l-5-5',
|
|
38
|
+
x: 'M18 6L6 18M6 6l12 12',
|
|
39
|
+
externalLink: 'M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6M15 3h6v6M10 14L21 3',
|
|
40
|
+
alert: 'M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0zM12 9v4M12 17h.01',
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
__modules["client/locales.js"] = function (require, exports, module) {
|
|
44
|
+
"use strict";
|
|
45
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
|
+
exports.es = exports.ptBR = exports.en = exports.zh = exports.NS = void 0;
|
|
47
|
+
/** Native DSH locale dictionaries for the Antigravity UI. */
|
|
48
|
+
exports.NS = 'agy-cli';
|
|
49
|
+
/** Simplified Chinese is preserved as the original UI language and key source. */
|
|
50
|
+
exports.zh = {
|
|
51
|
+
'code.title': '代码', 'code.inspect': '检查',
|
|
52
|
+
'tv.command': '命令', 'tv.write': '写入', 'tv.edit': '编辑', 'tv.read': '读取', 'tv.search': '搜索', 'tv.webSearch': '网页搜索', 'tv.list': '列表', 'tv.delete': '删除', 'tv.tool': '工具',
|
|
53
|
+
'tv.noOutput': '(无输出)', 'tv.noMatches': '(无匹配)', 'tv.noChanges': '(无文本变更)', 'tv.emptyDir': '(空目录)', 'tv.noDetail': '(无详情)',
|
|
54
|
+
'tv.hint.expandInspect': '点击展开 · 右键检查', 'tv.hint.collapseInspect': '点击收起 · 右键检查', 'tv.hint.expand': '点击展开', 'tv.hint.collapse': '点击收起',
|
|
55
|
+
'tv.diffMore': '… 其余 {count} 行未渲染',
|
|
56
|
+
'code.copy': '复制', 'code.copied': '已复制',
|
|
57
|
+
'auth.tokenLeft': '凭据 {minutes} 分钟后过期', 'auth.tokenRenewing': '凭据已过期,正在自动续期', 'auth.tokenExpired': '凭据已过期', 'auth.tokenRenewFailed': '凭据已过期,自动续期失败', 'auth.tokenHint': 'Keyring 里 gemini:antigravity 的 access_token 有效期;到期前一刻插件会自动启动 agy 续期,消耗零 token',
|
|
58
|
+
'status.ready': '就绪', 'status.needsAuth': '待认证', 'status.loading': '正在加载 Antigravity 状态...', 'status.retry': '重试',
|
|
59
|
+
'status.authRequired': '需重新登录', 'status.primary': '主用', 'status.proxy': '代理', 'status.inUse': '当前使用',
|
|
60
|
+
'account.add': '添加 Google 账号', 'account.aliasDefault': '备用 Google 账号 {number}', 'account.defaultAlias': '主账号(系统登录)',
|
|
61
|
+
'account.aliasPlaceholder': '账号别名(例如:备用账号 2)', 'account.removeTitle': '移除此账号',
|
|
62
|
+
'aria.aliasInput': '账号别名', 'aria.proxyInput': '专属代理 URL', 'aria.authCodeInput': '授权码或回调 URL', 'aria.closeToast': '关闭提示',
|
|
63
|
+
'account.setPrimary': '设为主用', 'account.settingPrimary': '设置中', 'account.details': '明细', 'account.collapse': '收起',
|
|
64
|
+
'account.sync': '同步', 'account.syncing': '同步中', 'account.syncTitle': '刷新此账号(换号后立即同步 email / 额度 / 模型)',
|
|
65
|
+
'account.models': '单模型明细:', 'account.cooldown': '部分模型限流中,已自动切换账号', 'account.clearCooldown': '清除冷却',
|
|
66
|
+
'account.localCooldown': ' · 本地冷却中', 'account.removed': '已移除账号:{alias}',
|
|
67
|
+
'quota.fiveHour': '5 小时额度', 'quota.weekly': '周额度', 'quota.refreshed': '额度已刷新',
|
|
68
|
+
'quota.expiredHint': '登录凭据已过期,额度暂无法读取。发一条 AGY 消息(或运行一次 agy)即可自动刷新。',
|
|
69
|
+
'quota.accountUnknown': '本机账号',
|
|
70
|
+
'proxy.button': '代理', 'proxy.placeholder': '专属代理 URL(如:http://127.0.0.1:7890;留空则使用全局)',
|
|
71
|
+
'proxy.save': '保存', 'proxy.saved': '代理已保存', 'cancel': '取消',
|
|
72
|
+
'auth.openBrowser': '打开浏览器登录', 'auth.openingBrowser': '正在打开浏览器...',
|
|
73
|
+
'auth.browserOpened': '浏览器已打开 Google 授权页,完成授权后将自动同步',
|
|
74
|
+
'auth.browserManual': '无法自动打开浏览器,请点击下方链接手动完成授权',
|
|
75
|
+
'auth.startFailed': '无法启动 Google 授权。请检查网络或代理配置。', 'auth.networkHint': '请检查网络或代理配置',
|
|
76
|
+
'auth.activated': '账号已激活入池', 'auth.failed': '授权失败', 'auth.added': '成功添加并激活 Google 账号',
|
|
77
|
+
'auth.codeFailed': '授权码验证失败', 'auth.exchanging': '正在验证授权并激活账号,请稍候...',
|
|
78
|
+
'auth.waiting': '等待浏览器中完成 Google 授权,成功后将自动激活。',
|
|
79
|
+
'auth.openManual': '若浏览器未打开,请点击此处手动打开 Google 登录页',
|
|
80
|
+
'auth.pasteHint': '未完成自动回调时,可粘贴授权码或回调 URL:',
|
|
81
|
+
'auth.codePlaceholder': '授权码或 http://localhost:51121/oauth-callback?code=... 完整链接',
|
|
82
|
+
'auth.activate': '手动激活', 'auth.activating': '验证激活中...',
|
|
83
|
+
'auth.code.browser_manual': '无法自动打开浏览器,请使用下方链接手动授权。', 'auth.code.callback_failed': '授权回调失败。请重新开始授权。',
|
|
84
|
+
'auth.code.no_active_flow': '当前没有进行中的授权流程。', 'auth.code.invalid_code': '无法识别授权码。请粘贴授权码或完整回调 URL。',
|
|
85
|
+
'auth.code.invalid_state': '授权链接不属于当前流程或已过期。请重新开始授权。', 'auth.code.exchange_failed': '授权验证失败。请重新开始授权。',
|
|
86
|
+
'auth.code.primary_activated': '主账号已登录。', 'auth.code.account_activated': '账号已添加并激活。', 'auth.code.missing_code': '请输入授权码。',
|
|
87
|
+
'auth.code.request_failed': '请求未完成。请重试。', 'api.genericError': '操作未完成。请重试。',
|
|
88
|
+
'duration.day': '天', 'duration.hour': '小时', 'duration.minute': '分钟',
|
|
89
|
+
'permission.label': '权限模式:', 'permission.plan': 'plan(只读)', 'permission.acceptEdits': 'accept-edits(改代码)', 'permission.skip': 'skip(自动批准;AGY 内部保护仍生效)',
|
|
90
|
+
'effort.label': '思考强度:', 'effort.auto': 'auto', 'effort.low': 'low', 'effort.medium': 'medium', 'effort.high': 'high',
|
|
91
|
+
'pool.label': '号池调度:', 'pool.sequential': '顺次耗尽', 'pool.roundRobin': '轮询均衡',
|
|
92
|
+
'models.default': '默认模型:', 'panel.expand': '展开详情', 'panel.collapse': '收起详情',
|
|
93
|
+
'models.label': '模型显示:', 'models.empty': '模型目录还没有可用模型,发一次对话或点“刷新额度”后再来管理',
|
|
94
|
+
'models.hint': '点击切换显示/隐藏;关闭的模型不在模型选择器中列出,进行中的会话不受影响。',
|
|
95
|
+
'header.refreshQuota': '刷新额度', 'header.refreshing': '刷新中',
|
|
96
|
+
};
|
|
97
|
+
exports.en = {
|
|
98
|
+
'code.title': 'Code', 'code.inspect': 'Inspect',
|
|
99
|
+
'tv.command': 'Command', 'tv.write': 'Write', 'tv.edit': 'Edit', 'tv.read': 'Read', 'tv.search': 'Search', 'tv.webSearch': 'Web Search', 'tv.list': 'List', 'tv.delete': 'Delete', 'tv.tool': 'Tool',
|
|
100
|
+
'tv.noOutput': '(no output)', 'tv.noMatches': '(no matches)', 'tv.noChanges': '(no textual changes)', 'tv.emptyDir': '(empty directory)', 'tv.noDetail': '(no detail)',
|
|
101
|
+
'tv.hint.expandInspect': 'Click to expand · right-click to inspect', 'tv.hint.collapseInspect': 'Click to collapse · right-click to inspect', 'tv.hint.expand': 'Click to expand', 'tv.hint.collapse': 'Click to collapse',
|
|
102
|
+
'tv.diffMore': '… {count} more lines not rendered',
|
|
103
|
+
'code.copy': 'Copy', 'code.copied': 'Copied',
|
|
104
|
+
'auth.tokenLeft': 'Credentials expire in {minutes} min', 'auth.tokenRenewing': 'Credentials expired — renewing automatically', 'auth.tokenExpired': 'Credentials expired', 'auth.tokenRenewFailed': 'Credentials expired — automatic renewal failed', 'auth.tokenHint': 'Lifetime of the gemini:antigravity access_token in the Windows keyring. The plugin starts agy just before it lapses to renew it, at zero token cost',
|
|
105
|
+
'status.ready': 'Ready', 'status.needsAuth': 'Authentication required', 'status.loading': 'Loading Antigravity status...', 'status.retry': 'Retry',
|
|
106
|
+
'status.authRequired': 'Sign in again required', 'status.primary': 'Primary', 'status.proxy': 'Proxy', 'status.inUse': 'In use',
|
|
107
|
+
'account.add': 'Add Google account', 'account.aliasDefault': 'Backup Google account {number}', 'account.defaultAlias': 'Primary account (system sign-in)',
|
|
108
|
+
'account.aliasPlaceholder': 'Account alias (for example: Backup account 2)', 'account.removeTitle': 'Remove this account',
|
|
109
|
+
'aria.aliasInput': 'Account alias', 'aria.proxyInput': 'Dedicated proxy URL', 'aria.authCodeInput': 'Authorization code or callback URL', 'aria.closeToast': 'Dismiss notification',
|
|
110
|
+
'account.setPrimary': 'Set as primary', 'account.settingPrimary': 'Setting...', 'account.details': 'Details', 'account.collapse': 'Collapse',
|
|
111
|
+
'account.sync': 'Sync', 'account.syncing': 'Syncing...', 'account.syncTitle': 'Refresh this account (sync email, quota, and models after switching accounts)',
|
|
112
|
+
'account.models': 'Per-model details:', 'account.cooldown': 'Some models are rate-limited; the account was switched automatically', 'account.clearCooldown': 'Clear cooldown',
|
|
113
|
+
'account.localCooldown': ' · local cooldown active', 'account.removed': 'Account removed: {alias}',
|
|
114
|
+
'quota.fiveHour': '5-hour quota', 'quota.weekly': 'Weekly quota', 'quota.refreshed': 'Quota refreshed',
|
|
115
|
+
'quota.expiredHint': 'Sign-in credentials have expired, so the quota cannot be read right now. Send one AGY message (or run agy once) and they refresh automatically.',
|
|
116
|
+
'quota.accountUnknown': 'This machine',
|
|
117
|
+
'proxy.button': 'Proxy', 'proxy.placeholder': 'Dedicated proxy URL (for example: http://127.0.0.1:7890; leave blank to use the global proxy)',
|
|
118
|
+
'proxy.save': 'Save', 'proxy.saved': 'Proxy saved', 'cancel': 'Cancel',
|
|
119
|
+
'auth.openBrowser': 'Open browser to sign in', 'auth.openingBrowser': 'Opening browser...',
|
|
120
|
+
'auth.browserOpened': 'The Google authorization page opened in your browser and will sync automatically after authorization',
|
|
121
|
+
'auth.browserManual': 'The browser could not be opened automatically; use the link below to authorize manually',
|
|
122
|
+
'auth.startFailed': 'Could not start Google authorization. Check your network or proxy configuration.', 'auth.networkHint': 'Check your network or proxy configuration',
|
|
123
|
+
'auth.activated': 'Account activated in the pool', 'auth.failed': 'Authorization failed', 'auth.added': 'Google account added and activated',
|
|
124
|
+
'auth.codeFailed': 'Authorization code validation failed', 'auth.exchanging': 'Verifying authorization and activating the account; please wait...',
|
|
125
|
+
'auth.waiting': 'Complete Google authorization in the browser. The account will activate automatically.',
|
|
126
|
+
'auth.openManual': 'If the browser did not open, click here to open the Google sign-in page manually',
|
|
127
|
+
'auth.pasteHint': 'If the automatic callback did not complete, paste the authorization code or callback URL:',
|
|
128
|
+
'auth.codePlaceholder': 'Authorization code or full http://localhost:51121/oauth-callback?code=... URL',
|
|
129
|
+
'auth.activate': 'Activate manually', 'auth.activating': 'Verifying and activating...',
|
|
130
|
+
'auth.code.browser_manual': 'The browser could not be opened automatically; use the link below to authorize manually.', 'auth.code.callback_failed': 'The authorization callback failed. Start authorization again.',
|
|
131
|
+
'auth.code.no_active_flow': 'There is no active authorization flow.', 'auth.code.invalid_code': 'The authorization code was not recognized. Paste the code or full callback URL.',
|
|
132
|
+
'auth.code.invalid_state': 'The authorization link does not belong to this flow or has expired. Start authorization again.', 'auth.code.exchange_failed': 'Authorization verification failed. Start authorization again.',
|
|
133
|
+
'auth.code.primary_activated': 'Primary account signed in.', 'auth.code.account_activated': 'Account added and activated.', 'auth.code.missing_code': 'Enter an authorization code.',
|
|
134
|
+
'auth.code.request_failed': 'The request did not complete. Try again.', 'api.genericError': 'The operation did not complete. Try again.',
|
|
135
|
+
'duration.day': 'd', 'duration.hour': 'h', 'duration.minute': 'm',
|
|
136
|
+
'permission.label': 'Permission mode:', 'permission.plan': 'plan (read-only)', 'permission.acceptEdits': 'accept-edits (edit code)', 'permission.skip': 'skip (auto-approve; AGY internal protections remain)',
|
|
137
|
+
'effort.label': 'Thinking effort:', 'effort.auto': 'auto', 'effort.low': 'low', 'effort.medium': 'medium', 'effort.high': 'high',
|
|
138
|
+
'pool.label': 'Account-pool scheduling:', 'pool.sequential': 'Use sequentially', 'pool.roundRobin': 'Round-robin',
|
|
139
|
+
'models.default': 'Default model:', 'panel.expand': 'Expand details', 'panel.collapse': 'Collapse details',
|
|
140
|
+
'models.label': 'Model visibility:', 'models.empty': 'No models in the catalog yet — send a message or click "Refresh quota", then come back to manage them',
|
|
141
|
+
'models.hint': 'Click to show/hide. Hidden models are not listed in the model picker; ongoing sessions are unaffected.',
|
|
142
|
+
'header.refreshQuota': 'Refresh quota', 'header.refreshing': 'Refreshing...',
|
|
143
|
+
};
|
|
144
|
+
exports.ptBR = {
|
|
145
|
+
'code.title': 'Código', 'code.inspect': 'Inspecionar',
|
|
146
|
+
'tv.command': 'Comando', 'tv.write': 'Escrever', 'tv.edit': 'Editar', 'tv.read': 'Ler', 'tv.search': 'Buscar', 'tv.webSearch': 'Busca na web', 'tv.list': 'Listar', 'tv.delete': 'Excluir', 'tv.tool': 'Ferramenta',
|
|
147
|
+
'tv.noOutput': '(sem saída)', 'tv.noMatches': '(nenhuma correspondência)', 'tv.noChanges': '(sem alterações de texto)', 'tv.emptyDir': '(diretório vazio)', 'tv.noDetail': '(sem detalhes)',
|
|
148
|
+
'tv.hint.expandInspect': 'Clique para expandir · clique direito para inspecionar', 'tv.hint.collapseInspect': 'Clique para recolher · clique direito para inspecionar', 'tv.hint.expand': 'Clique para expandir', 'tv.hint.collapse': 'Clique para recolher',
|
|
149
|
+
'tv.diffMore': '… {count} linhas restantes não renderizadas',
|
|
150
|
+
'code.copy': 'Copiar', 'code.copied': 'Copiado',
|
|
151
|
+
'auth.tokenLeft': 'As credenciais expiram em {minutes} min', 'auth.tokenRenewing': 'Credenciais expiradas — renovando automaticamente', 'auth.tokenExpired': 'Credenciais expiradas', 'auth.tokenRenewFailed': 'Credenciais expiradas — a renovação automática falhou', 'auth.tokenHint': 'Validade do access_token gemini:antigravity no Windows keyring. O plugin inicia o agy pouco antes de expirar para renová-lo, sem custo de tokens',
|
|
152
|
+
'status.ready': 'Pronto', 'status.needsAuth': 'Autenticação necessária', 'status.loading': 'Carregando o status do Antigravity...', 'status.retry': 'Tentar novamente',
|
|
153
|
+
'status.authRequired': 'É necessário entrar novamente', 'status.primary': 'Principal', 'status.proxy': 'Proxy', 'status.inUse': 'Em uso',
|
|
154
|
+
'account.add': 'Adicionar conta Google', 'account.aliasDefault': 'Conta Google reserva {number}', 'account.defaultAlias': 'Conta principal (login do sistema)',
|
|
155
|
+
'account.aliasPlaceholder': 'Apelido da conta (por exemplo: Conta reserva 2)', 'account.removeTitle': 'Remover esta conta',
|
|
156
|
+
'aria.aliasInput': 'Apelido da conta', 'aria.proxyInput': 'URL do proxy exclusivo', 'aria.authCodeInput': 'Código de autorização ou URL de retorno', 'aria.closeToast': 'Fechar notificação',
|
|
157
|
+
'account.setPrimary': 'Definir como principal', 'account.settingPrimary': 'Definindo...', 'account.details': 'Detalhes', 'account.collapse': 'Recolher',
|
|
158
|
+
'account.sync': 'Sincronizar', 'account.syncing': 'Sincronizando...', 'account.syncTitle': 'Atualizar esta conta (sincroniza e-mail, cota e modelos depois de trocar de conta)',
|
|
159
|
+
'account.models': 'Detalhes por modelo:', 'account.cooldown': 'Alguns modelos estão limitados; a conta foi alternada automaticamente', 'account.clearCooldown': 'Limpar espera',
|
|
160
|
+
'account.localCooldown': ' · espera local ativa', 'account.removed': 'Conta removida: {alias}',
|
|
161
|
+
'quota.fiveHour': 'Cota de 5 horas', 'quota.weekly': 'Cota semanal', 'quota.refreshed': 'Cota atualizada',
|
|
162
|
+
'quota.expiredHint': 'As credenciais de login expiraram, então a cota não pode ser lida agora. Envie uma mensagem AGY (ou execute o agy uma vez) e elas serão atualizadas automaticamente.',
|
|
163
|
+
'quota.accountUnknown': 'Esta máquina',
|
|
164
|
+
'proxy.button': 'Proxy', 'proxy.placeholder': 'URL do proxy exclusivo (por exemplo: http://127.0.0.1:7890; deixe vazio para usar o proxy global)',
|
|
165
|
+
'proxy.save': 'Salvar', 'proxy.saved': 'Proxy salvo', 'cancel': 'Cancelar',
|
|
166
|
+
'auth.openBrowser': 'Abrir navegador para entrar', 'auth.openingBrowser': 'Abrindo navegador...',
|
|
167
|
+
'auth.browserOpened': 'A página de autorização do Google foi aberta no navegador e será sincronizada após a autorização',
|
|
168
|
+
'auth.browserManual': 'Não foi possível abrir o navegador automaticamente; use o link abaixo para autorizar manualmente',
|
|
169
|
+
'auth.startFailed': 'Não foi possível iniciar a autorização do Google. Verifique a configuração de rede ou proxy.', 'auth.networkHint': 'Verifique a configuração de rede ou proxy',
|
|
170
|
+
'auth.activated': 'Conta ativada no pool', 'auth.failed': 'Falha na autorização', 'auth.added': 'Conta Google adicionada e ativada',
|
|
171
|
+
'auth.codeFailed': 'Falha ao validar o código de autorização', 'auth.exchanging': 'Verificando a autorização e ativando a conta; aguarde...',
|
|
172
|
+
'auth.waiting': 'Conclua a autorização do Google no navegador. A conta será ativada automaticamente.',
|
|
173
|
+
'auth.openManual': 'Se o navegador não abriu, clique aqui para abrir manualmente a página de login do Google',
|
|
174
|
+
'auth.pasteHint': 'Se o retorno automático não foi concluído, cole o código de autorização ou a URL de retorno:',
|
|
175
|
+
'auth.codePlaceholder': 'Código de autorização ou URL completa http://localhost:51121/oauth-callback?code=...',
|
|
176
|
+
'auth.activate': 'Ativar manualmente', 'auth.activating': 'Verificando e ativando...',
|
|
177
|
+
'auth.code.browser_manual': 'Não foi possível abrir o navegador automaticamente; use o link abaixo para autorizar manualmente.', 'auth.code.callback_failed': 'O retorno da autorização falhou. Inicie a autorização novamente.',
|
|
178
|
+
'auth.code.no_active_flow': 'Não há um fluxo de autorização em andamento.', 'auth.code.invalid_code': 'O código de autorização não foi reconhecido. Cole o código ou a URL completa de retorno.',
|
|
179
|
+
'auth.code.invalid_state': 'O link de autorização não pertence a este fluxo ou expirou. Inicie a autorização novamente.', 'auth.code.exchange_failed': 'A validação da autorização falhou. Inicie a autorização novamente.',
|
|
180
|
+
'auth.code.primary_activated': 'Conta principal conectada.', 'auth.code.account_activated': 'Conta adicionada e ativada.', 'auth.code.missing_code': 'Informe um código de autorização.',
|
|
181
|
+
'auth.code.request_failed': 'A solicitação não foi concluída. Tente novamente.', 'api.genericError': 'A operação não foi concluída. Tente novamente.',
|
|
182
|
+
'duration.day': 'd', 'duration.hour': 'h', 'duration.minute': 'min',
|
|
183
|
+
'permission.label': 'Modo de permissão:', 'permission.plan': 'plan (somente leitura)', 'permission.acceptEdits': 'accept-edits (editar código)', 'permission.skip': 'skip (aprovação automática; proteções internas do AGY permanecem)',
|
|
184
|
+
'effort.label': 'Intensidade de raciocínio:', 'effort.auto': 'auto', 'effort.low': 'low', 'effort.medium': 'medium', 'effort.high': 'high',
|
|
185
|
+
'pool.label': 'Agendamento do pool de contas:', 'pool.sequential': 'Usar em sequência', 'pool.roundRobin': 'Rodízio',
|
|
186
|
+
'models.default': 'Modelo padrão:', 'panel.expand': 'Expandir detalhes', 'panel.collapse': 'Recolher detalhes',
|
|
187
|
+
'models.label': 'Visibilidade dos modelos:', 'models.empty': 'Ainda não há modelos no catálogo — envie uma mensagem ou clique em "Atualizar cota" e volte para gerenciá-los',
|
|
188
|
+
'models.hint': 'Clique para mostrar/ocultar. Modelos ocultos não aparecem no seletor de modelos; as sessões em andamento não são afetadas.',
|
|
189
|
+
'header.refreshQuota': 'Atualizar cota', 'header.refreshing': 'Atualizando...',
|
|
190
|
+
};
|
|
191
|
+
exports.es = {
|
|
192
|
+
'code.title': 'Código', 'code.inspect': 'Inspeccionar',
|
|
193
|
+
'tv.command': 'Comando', 'tv.write': 'Escribir', 'tv.edit': 'Editar', 'tv.read': 'Leer', 'tv.search': 'Buscar', 'tv.webSearch': 'Búsqueda web', 'tv.list': 'Listar', 'tv.delete': 'Eliminar', 'tv.tool': 'Herramienta',
|
|
194
|
+
'tv.noOutput': '(sin salida)', 'tv.noMatches': '(sin coincidencias)', 'tv.noChanges': '(sin cambios de texto)', 'tv.emptyDir': '(directorio vacío)', 'tv.noDetail': '(sin detalles)',
|
|
195
|
+
'tv.hint.expandInspect': 'Clic para expandir · clic derecho para inspeccionar', 'tv.hint.collapseInspect': 'Clic para contraer · clic derecho para inspeccionar', 'tv.hint.expand': 'Clic para expandir', 'tv.hint.collapse': 'Clic para contraer',
|
|
196
|
+
'tv.diffMore': '… {count} líneas restantes no renderizadas',
|
|
197
|
+
'code.copy': 'Copiar', 'code.copied': 'Copiado',
|
|
198
|
+
'auth.tokenLeft': 'Las credenciales caducan en {minutes} min', 'auth.tokenRenewing': 'Credenciales caducadas — renovando automáticamente', 'auth.tokenExpired': 'Credenciales caducadas', 'auth.tokenRenewFailed': 'Credenciales caducadas — la renovación automática falló', 'auth.tokenHint': 'Vigencia del access_token gemini:antigravity en el Windows keyring. El plugin inicia agy justo antes de que caduque para renovarlo, sin coste de tokens',
|
|
199
|
+
'status.ready': 'Listo', 'status.needsAuth': 'Se requiere autenticación', 'status.loading': 'Cargando el estado de Antigravity...', 'status.retry': 'Reintentar',
|
|
200
|
+
'status.authRequired': 'Debe iniciar sesión de nuevo', 'status.primary': 'Principal', 'status.proxy': 'Proxy', 'status.inUse': 'En uso',
|
|
201
|
+
'account.add': 'Añadir cuenta de Google', 'account.aliasDefault': 'Cuenta de Google de respaldo {number}', 'account.defaultAlias': 'Cuenta principal (inicio de sesión del sistema)',
|
|
202
|
+
'account.aliasPlaceholder': 'Alias de la cuenta (por ejemplo: Cuenta de respaldo 2)', 'account.removeTitle': 'Eliminar esta cuenta',
|
|
203
|
+
'aria.aliasInput': 'Alias de la cuenta', 'aria.proxyInput': 'URL de proxy exclusivo', 'aria.authCodeInput': 'Código de autorización o URL de devolución', 'aria.closeToast': 'Cerrar notificación',
|
|
204
|
+
'account.setPrimary': 'Establecer como principal', 'account.settingPrimary': 'Configurando...', 'account.details': 'Detalles', 'account.collapse': 'Contraer',
|
|
205
|
+
'account.sync': 'Sincronizar', 'account.syncing': 'Sincronizando...', 'account.syncTitle': 'Actualizar esta cuenta (sincroniza correo, cuota y modelos tras cambiar de cuenta)',
|
|
206
|
+
'account.models': 'Detalles por modelo:', 'account.cooldown': 'Algunos modelos tienen límite de uso; la cuenta se cambió automáticamente', 'account.clearCooldown': 'Borrar espera',
|
|
207
|
+
'account.localCooldown': ' · espera local activa', 'account.removed': 'Cuenta eliminada: {alias}',
|
|
208
|
+
'quota.fiveHour': 'Cuota de 5 horas', 'quota.weekly': 'Cuota semanal', 'quota.refreshed': 'Cuota actualizada',
|
|
209
|
+
'quota.expiredHint': 'Las credenciales de acceso han caducado, por lo que ahora no se puede leer la cuota. Envía un mensaje AGY (o ejecuta agy una vez) y se actualizarán automáticamente.',
|
|
210
|
+
'quota.accountUnknown': 'Esta máquina',
|
|
211
|
+
'proxy.button': 'Proxy', 'proxy.placeholder': 'URL de proxy exclusivo (por ejemplo: http://127.0.0.1:7890; déjelo vacío para usar el proxy global)',
|
|
212
|
+
'proxy.save': 'Guardar', 'proxy.saved': 'Proxy guardado', 'cancel': 'Cancelar',
|
|
213
|
+
'auth.openBrowser': 'Abrir navegador para iniciar sesión', 'auth.openingBrowser': 'Abriendo navegador...',
|
|
214
|
+
'auth.browserOpened': 'La página de autorización de Google se abrió en el navegador y se sincronizará después de autorizar',
|
|
215
|
+
'auth.browserManual': 'No se pudo abrir el navegador automáticamente; use el enlace de abajo para autorizar manualmente',
|
|
216
|
+
'auth.startFailed': 'No se pudo iniciar la autorización de Google. Revise la configuración de red o proxy.', 'auth.networkHint': 'Revise la configuración de red o proxy',
|
|
217
|
+
'auth.activated': 'Cuenta activada en el pool', 'auth.failed': 'Falló la autorización', 'auth.added': 'Cuenta de Google añadida y activada',
|
|
218
|
+
'auth.codeFailed': 'Falló la validación del código de autorización', 'auth.exchanging': 'Verificando la autorización y activando la cuenta; espere...',
|
|
219
|
+
'auth.waiting': 'Complete la autorización de Google en el navegador. La cuenta se activará automáticamente.',
|
|
220
|
+
'auth.openManual': 'Si el navegador no se abrió, haga clic aquí para abrir manualmente la página de inicio de sesión de Google',
|
|
221
|
+
'auth.pasteHint': 'Si la devolución automática no se completó, pegue el código de autorización o la URL de devolución:',
|
|
222
|
+
'auth.codePlaceholder': 'Código de autorización o URL completa http://localhost:51121/oauth-callback?code=...',
|
|
223
|
+
'auth.activate': 'Activar manualmente', 'auth.activating': 'Verificando y activando...',
|
|
224
|
+
'auth.code.browser_manual': 'No se pudo abrir el navegador automáticamente; use el enlace de abajo para autorizar manualmente.', 'auth.code.callback_failed': 'La devolución de la autorización falló. Inicie la autorización de nuevo.',
|
|
225
|
+
'auth.code.no_active_flow': 'No hay un flujo de autorización en curso.', 'auth.code.invalid_code': 'No se reconoció el código de autorización. Pegue el código o la URL completa de devolución.',
|
|
226
|
+
'auth.code.invalid_state': 'El enlace de autorización no pertenece a este flujo o caducó. Inicie la autorización de nuevo.', 'auth.code.exchange_failed': 'Falló la validación de la autorización. Inicie la autorización de nuevo.',
|
|
227
|
+
'auth.code.primary_activated': 'Cuenta principal conectada.', 'auth.code.account_activated': 'Cuenta añadida y activada.', 'auth.code.missing_code': 'Introduzca un código de autorización.',
|
|
228
|
+
'auth.code.request_failed': 'La solicitud no se completó. Inténtelo de nuevo.', 'api.genericError': 'La operación no se completó. Inténtelo de nuevo.',
|
|
229
|
+
'duration.day': 'd', 'duration.hour': 'h', 'duration.minute': 'min',
|
|
230
|
+
'permission.label': 'Modo de permisos:', 'permission.plan': 'plan (solo lectura)', 'permission.acceptEdits': 'accept-edits (editar código)', 'permission.skip': 'skip (aprobación automática; se mantienen las protecciones internas de AGY)',
|
|
231
|
+
'effort.label': 'Intensidad de razonamiento:', 'effort.auto': 'auto', 'effort.low': 'low', 'effort.medium': 'medium', 'effort.high': 'high',
|
|
232
|
+
'pool.label': 'Programación del pool de cuentas:', 'pool.sequential': 'Usar en secuencia', 'pool.roundRobin': 'Turno rotativo',
|
|
233
|
+
'models.default': 'Modelo predeterminado:', 'panel.expand': 'Expandir detalles', 'panel.collapse': 'Contraer detalles',
|
|
234
|
+
'models.label': 'Visibilidad de modelos:', 'models.empty': 'Aún no hay modelos en el catálogo: envía un mensaje o haz clic en "Actualizar cuota" y vuelve para gestionarlos',
|
|
235
|
+
'models.hint': 'Haz clic para mostrar/ocultar. Los modelos ocultos no aparecen en el selector de modelos; las sesiones en curso no se ven afectadas.',
|
|
236
|
+
'header.refreshQuota': 'Actualizar cuota', 'header.refreshing': 'Actualizando...',
|
|
237
|
+
};
|
|
238
|
+
};
|
|
239
|
+
__modules["client/decisions.js"] = function (require, exports, module) {
|
|
240
|
+
"use strict";
|
|
241
|
+
// Interactive User Decision & Choice Quick-Actions for dsh-agy-link.
|
|
242
|
+
//
|
|
243
|
+
// Detects decision sections and choice prompts in model responses (e.g.
|
|
244
|
+
// "### 💬 请您拍板:", "### 💬 接下来请您指示:", "### 请选择:")
|
|
245
|
+
// and attaches quick-action chip buttons that insert the user's selected choice
|
|
246
|
+
// straight into DSH's chat composer.
|
|
247
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
248
|
+
exports.insertTextIntoChatComposer = insertTextIntoChatComposer;
|
|
249
|
+
exports.scanAndEnhanceDecisions = scanAndEnhanceDecisions;
|
|
250
|
+
exports.installDecisionChips = installDecisionChips;
|
|
251
|
+
const DECISION_CSS = `
|
|
252
|
+
.agy-decision-container {
|
|
253
|
+
margin: 10px 0 6px 0;
|
|
254
|
+
padding: 10px 12px;
|
|
255
|
+
border-radius: 8px;
|
|
256
|
+
background: var(--dsw-alias-bg-l2, rgba(127, 127, 127, 0.07));
|
|
257
|
+
border: 1px dashed var(--dsw-alias-border-l3, rgba(127, 127, 127, 0.3));
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.agy-decision-header {
|
|
261
|
+
font-size: 12px;
|
|
262
|
+
font-weight: 600;
|
|
263
|
+
color: var(--dsw-alias-label-secondary, #aaa);
|
|
264
|
+
margin-bottom: 8px;
|
|
265
|
+
display: flex;
|
|
266
|
+
align-items: center;
|
|
267
|
+
gap: 6px;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.agy-decision-chips {
|
|
271
|
+
display: flex;
|
|
272
|
+
flex-wrap: wrap;
|
|
273
|
+
gap: 8px;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.agy-decision-chip {
|
|
277
|
+
background: var(--dsw-alias-bg-l1, rgba(255, 255, 255, 0.05));
|
|
278
|
+
border: 1px solid var(--dsw-alias-border-l2, rgba(127, 127, 127, 0.25));
|
|
279
|
+
color: var(--dsw-alias-label-primary, #eee);
|
|
280
|
+
padding: 5px 12px;
|
|
281
|
+
border-radius: 999px;
|
|
282
|
+
cursor: pointer;
|
|
283
|
+
font-size: 12px;
|
|
284
|
+
font-weight: 500;
|
|
285
|
+
display: inline-flex;
|
|
286
|
+
align-items: center;
|
|
287
|
+
gap: 6px;
|
|
288
|
+
transition: all 0.15s ease;
|
|
289
|
+
user-select: none;
|
|
290
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
291
|
+
text-align: left;
|
|
292
|
+
max-width: 100%;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
.agy-decision-chip:hover {
|
|
296
|
+
background: var(--dsw-alias-bg-hover, rgba(127, 127, 127, 0.18));
|
|
297
|
+
border-color: var(--dsw-alias-border-l4, rgba(127, 127, 127, 0.5));
|
|
298
|
+
transform: translateY(-1px);
|
|
299
|
+
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.18);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.agy-decision-chip:active {
|
|
303
|
+
transform: translateY(0);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
.agy-decision-chip.selected {
|
|
307
|
+
background: rgba(16, 185, 129, 0.15);
|
|
308
|
+
border-color: #10b981;
|
|
309
|
+
color: #10b981;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.agy-decision-chip-num {
|
|
313
|
+
font-weight: 700;
|
|
314
|
+
opacity: 0.8;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
.agy-decision-chip-text {
|
|
318
|
+
overflow: hidden;
|
|
319
|
+
text-overflow: ellipsis;
|
|
320
|
+
white-space: nowrap;
|
|
321
|
+
}
|
|
322
|
+
`;
|
|
323
|
+
function injectStyles() {
|
|
324
|
+
if (typeof document === 'undefined')
|
|
325
|
+
return;
|
|
326
|
+
const styleId = 'dsh-agy-link-decisions-css';
|
|
327
|
+
if (document.getElementById(styleId) === null) {
|
|
328
|
+
const st = document.createElement('style');
|
|
329
|
+
st.id = styleId;
|
|
330
|
+
st.textContent = DECISION_CSS;
|
|
331
|
+
const host = document.head ?? document.documentElement;
|
|
332
|
+
if (host)
|
|
333
|
+
host.appendChild(st);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Insert text directly into DSH's chat composer
|
|
338
|
+
*/
|
|
339
|
+
function insertTextIntoChatComposer(text) {
|
|
340
|
+
if (typeof document === 'undefined' || !text)
|
|
341
|
+
return false;
|
|
342
|
+
const inputEl = document.querySelector('[data-composer-input]') ??
|
|
343
|
+
document.querySelector('[role="textbox"][contenteditable="true"]') ??
|
|
344
|
+
document.querySelector('textarea');
|
|
345
|
+
if (!inputEl)
|
|
346
|
+
return false;
|
|
347
|
+
inputEl.focus();
|
|
348
|
+
if (inputEl instanceof HTMLTextAreaElement || inputEl instanceof HTMLInputElement) {
|
|
349
|
+
const start = inputEl.selectionStart ?? inputEl.value.length;
|
|
350
|
+
const end = inputEl.selectionEnd ?? inputEl.value.length;
|
|
351
|
+
inputEl.setRangeText(text, start, end, 'end');
|
|
352
|
+
inputEl.dispatchEvent(new Event('input', { bubbles: true }));
|
|
353
|
+
inputEl.dispatchEvent(new Event('change', { bubbles: true }));
|
|
354
|
+
return true;
|
|
355
|
+
}
|
|
356
|
+
// Lexical / ContentEditable
|
|
357
|
+
try {
|
|
358
|
+
const ok = document.execCommand('insertText', false, text);
|
|
359
|
+
if (ok)
|
|
360
|
+
return true;
|
|
361
|
+
}
|
|
362
|
+
catch {
|
|
363
|
+
// ignore
|
|
364
|
+
}
|
|
365
|
+
// Fallback direct text update
|
|
366
|
+
const existing = inputEl.textContent?.trim() ?? '';
|
|
367
|
+
inputEl.textContent = existing ? `${existing} ${text}` : text;
|
|
368
|
+
inputEl.dispatchEvent(new InputEvent('input', { bubbles: true, inputType: 'insertText', data: text }));
|
|
369
|
+
return true;
|
|
370
|
+
}
|
|
371
|
+
/** Check if text header suggests an actionable decision or choice prompt */
|
|
372
|
+
const DECISION_HEADER_REGEX = /(?:请(?:您)?(?:拍板|指示|决定|选择|决策)|decision|next\s+step|choose|拍板|选择方案)/i;
|
|
373
|
+
/** Extract numbered options or choice items from a decision list */
|
|
374
|
+
function extractOptionsFromList(container) {
|
|
375
|
+
const options = [];
|
|
376
|
+
const listItems = container.querySelectorAll('li');
|
|
377
|
+
if (listItems.length > 0) {
|
|
378
|
+
for (let i = 0; i < listItems.length; i++) {
|
|
379
|
+
const li = listItems[i];
|
|
380
|
+
if (!li)
|
|
381
|
+
continue;
|
|
382
|
+
const text = (li.textContent ?? '').trim();
|
|
383
|
+
if (!text)
|
|
384
|
+
continue;
|
|
385
|
+
// Extract title: up to first colon, dash, or 60 chars
|
|
386
|
+
const m = text.match(/^([0-9]+[、..:]\s*)?(.*?)(?:[::—\-·]|\n|$)/);
|
|
387
|
+
const num = m?.[1]?.trim() ?? `${i + 1}.`;
|
|
388
|
+
let title = m?.[2]?.trim() || text.slice(0, 50);
|
|
389
|
+
if (title.length > 40)
|
|
390
|
+
title = title.slice(0, 40) + '…';
|
|
391
|
+
options.push({
|
|
392
|
+
num,
|
|
393
|
+
title,
|
|
394
|
+
fullText: text,
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
return options;
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Scan DOM for decision prompts and mount interactive choice chips
|
|
402
|
+
*/
|
|
403
|
+
function scanAndEnhanceDecisions() {
|
|
404
|
+
if (typeof document === 'undefined')
|
|
405
|
+
return;
|
|
406
|
+
const markdownContainers = document.querySelectorAll('div[class*="_markdown_"], [data-markdown-variant]');
|
|
407
|
+
for (let i = 0; i < markdownContainers.length; i++) {
|
|
408
|
+
const container = markdownContainers[i];
|
|
409
|
+
if (!container || container.dataset.agyDecisionsScanned === 'true')
|
|
410
|
+
continue;
|
|
411
|
+
// Check headings (h2, h3, h4, or strong paragraphs)
|
|
412
|
+
const headings = container.querySelectorAll('h2, h3, h4, p > strong');
|
|
413
|
+
for (let j = 0; j < headings.length; j++) {
|
|
414
|
+
const heading = headings[j];
|
|
415
|
+
if (!heading || heading.dataset.agyDecisionAttached === 'true')
|
|
416
|
+
continue;
|
|
417
|
+
const headingText = (heading.textContent ?? '').trim();
|
|
418
|
+
if (!DECISION_HEADER_REGEX.test(headingText))
|
|
419
|
+
continue;
|
|
420
|
+
// Find the list immediately following this heading
|
|
421
|
+
let sibling = heading.nextElementSibling;
|
|
422
|
+
let targetList = null;
|
|
423
|
+
while (sibling) {
|
|
424
|
+
if (sibling.tagName === 'OL' || sibling.tagName === 'UL') {
|
|
425
|
+
targetList = sibling;
|
|
426
|
+
break;
|
|
427
|
+
}
|
|
428
|
+
if (/^H[1-4]$/.test(sibling.tagName))
|
|
429
|
+
break; // hit another heading
|
|
430
|
+
sibling = sibling.nextElementSibling;
|
|
431
|
+
}
|
|
432
|
+
if (!targetList)
|
|
433
|
+
continue;
|
|
434
|
+
const options = extractOptionsFromList(targetList);
|
|
435
|
+
if (options.length === 0)
|
|
436
|
+
continue;
|
|
437
|
+
heading.dataset.agyDecisionAttached = 'true';
|
|
438
|
+
// Build decision chip toolbar
|
|
439
|
+
const box = document.createElement('div');
|
|
440
|
+
box.className = 'agy-decision-container';
|
|
441
|
+
const header = document.createElement('div');
|
|
442
|
+
header.className = 'agy-decision-header';
|
|
443
|
+
header.innerHTML = '<span>💬 点击选项快速填入:</span>';
|
|
444
|
+
box.appendChild(header);
|
|
445
|
+
const chips = document.createElement('div');
|
|
446
|
+
chips.className = 'agy-decision-chips';
|
|
447
|
+
for (const opt of options) {
|
|
448
|
+
const chip = document.createElement('button');
|
|
449
|
+
chip.type = 'button';
|
|
450
|
+
chip.className = 'agy-decision-chip';
|
|
451
|
+
chip.title = `点击填入: ${opt.fullText}`;
|
|
452
|
+
const numSpan = document.createElement('span');
|
|
453
|
+
numSpan.className = 'agy-decision-chip-num';
|
|
454
|
+
numSpan.textContent = opt.num ?? '👉';
|
|
455
|
+
const textSpan = document.createElement('span');
|
|
456
|
+
textSpan.className = 'agy-decision-chip-text';
|
|
457
|
+
textSpan.textContent = opt.title;
|
|
458
|
+
chip.appendChild(numSpan);
|
|
459
|
+
chip.appendChild(textSpan);
|
|
460
|
+
chip.onclick = (e) => {
|
|
461
|
+
e.stopPropagation();
|
|
462
|
+
const cleanText = opt.fullText.replace(/^[0-9]+[、..:]\s*/, '').trim();
|
|
463
|
+
const insertPayload = `我选择 ${opt.num ? opt.num + ' ' : ''}${cleanText}`;
|
|
464
|
+
insertTextIntoChatComposer(insertPayload);
|
|
465
|
+
chip.classList.add('selected');
|
|
466
|
+
setTimeout(() => chip.classList.remove('selected'), 1200);
|
|
467
|
+
};
|
|
468
|
+
chips.appendChild(chip);
|
|
469
|
+
}
|
|
470
|
+
box.appendChild(chips);
|
|
471
|
+
targetList.parentNode?.insertBefore(box, targetList.nextSibling);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
let decisionChipsDispose = null;
|
|
476
|
+
function installDecisionChips() {
|
|
477
|
+
if (typeof window === 'undefined' || typeof document === 'undefined')
|
|
478
|
+
return () => { };
|
|
479
|
+
// Same lifecycle contract as installImageEnhancer: a reload replaces the
|
|
480
|
+
// observer instead of accumulating one per plugin load.
|
|
481
|
+
decisionChipsDispose?.();
|
|
482
|
+
injectStyles();
|
|
483
|
+
const observer = new MutationObserver(() => {
|
|
484
|
+
scanAndEnhanceDecisions();
|
|
485
|
+
});
|
|
486
|
+
const setup = () => {
|
|
487
|
+
if (document.body) {
|
|
488
|
+
observer.observe(document.body, { childList: true, subtree: true });
|
|
489
|
+
scanAndEnhanceDecisions();
|
|
490
|
+
}
|
|
491
|
+
};
|
|
492
|
+
if (document.readyState === 'loading') {
|
|
493
|
+
window.addEventListener('DOMContentLoaded', setup, { once: true });
|
|
494
|
+
}
|
|
495
|
+
else {
|
|
496
|
+
setup();
|
|
497
|
+
}
|
|
498
|
+
const dispose = () => {
|
|
499
|
+
observer.disconnect();
|
|
500
|
+
window.removeEventListener('DOMContentLoaded', setup);
|
|
501
|
+
if (decisionChipsDispose === dispose)
|
|
502
|
+
decisionChipsDispose = null;
|
|
503
|
+
};
|
|
504
|
+
decisionChipsDispose = dispose;
|
|
505
|
+
return dispose;
|
|
506
|
+
}
|
|
507
|
+
};
|
|
508
|
+
__modules["client/images.js"] = function (require, exports, module) {
|
|
509
|
+
"use strict";
|
|
510
|
+
// Client-side local image display & lightbox enhancer for dsh-agy-link.
|
|
511
|
+
//
|
|
512
|
+
// Solves local image display issues in DSH Web and Desktop:
|
|
513
|
+
// 1. Normalizes media endpoints across local Desktop and remote Web clients
|
|
514
|
+
// 2. Retroactively enhances historical sessions where file:/// images were rendered as inert alt text
|
|
515
|
+
// 3. Enhances markdown file:// image links into clickable preview thumbnails
|
|
516
|
+
// 4. Provides a full-featured Lightbox modal (zoom, pan, copy path, fullscreen)
|
|
517
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
518
|
+
exports.buildMediaUrl = buildMediaUrl;
|
|
519
|
+
exports.openLightbox = openLightbox;
|
|
520
|
+
exports.isImageFile = isImageFile;
|
|
521
|
+
exports.scanAndEnhanceImages = scanAndEnhanceImages;
|
|
522
|
+
exports.installImageEnhancer = installImageEnhancer;
|
|
523
|
+
let lightboxOverlay = null;
|
|
524
|
+
let currentLightboxImg = null;
|
|
525
|
+
let lightboxScale = 1.0;
|
|
526
|
+
let lightboxTranslateX = 0;
|
|
527
|
+
let lightboxTranslateY = 0;
|
|
528
|
+
let isDraggingLightbox = false;
|
|
529
|
+
let dragStartX = 0;
|
|
530
|
+
let dragStartY = 0;
|
|
531
|
+
const IMAGE_CSS = `
|
|
532
|
+
.agy-image-card {
|
|
533
|
+
display: inline-flex;
|
|
534
|
+
flex-direction: column;
|
|
535
|
+
margin: 6px 0;
|
|
536
|
+
max-width: 100%;
|
|
537
|
+
border-radius: 8px;
|
|
538
|
+
overflow: hidden;
|
|
539
|
+
border: 1px solid var(--dsw-alias-border-l2, rgba(127, 127, 127, 0.25));
|
|
540
|
+
background: var(--dsw-alias-bg-l1, rgba(255, 255, 255, 0.03));
|
|
541
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
542
|
+
transition: transform 0.15s ease, box-shadow 0.15s ease;
|
|
543
|
+
vertical-align: middle;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
.agy-image-card:hover {
|
|
547
|
+
border-color: var(--dsw-alias-border-l3, rgba(127, 127, 127, 0.45));
|
|
548
|
+
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.25);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
.agy-image-thumb-wrap {
|
|
552
|
+
position: relative;
|
|
553
|
+
overflow: hidden;
|
|
554
|
+
max-height: 480px;
|
|
555
|
+
cursor: zoom-in;
|
|
556
|
+
display: flex;
|
|
557
|
+
align-items: center;
|
|
558
|
+
justify-content: center;
|
|
559
|
+
background: repeating-conic-gradient(rgba(127,127,127,0.08) 0% 25%, transparent 0% 50%) 50% / 16px 16px;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
.agy-image-thumb {
|
|
563
|
+
display: block;
|
|
564
|
+
max-width: 100%;
|
|
565
|
+
max-height: 480px;
|
|
566
|
+
height: auto;
|
|
567
|
+
object-fit: contain;
|
|
568
|
+
transition: transform 0.2s ease;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
.agy-image-card:hover .agy-image-thumb {
|
|
572
|
+
transform: scale(1.015);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
.agy-image-caption {
|
|
576
|
+
padding: 6px 10px;
|
|
577
|
+
font-size: 11.5px;
|
|
578
|
+
color: var(--dsw-alias-label-secondary, #aaa);
|
|
579
|
+
background: var(--dsw-alias-bg-l2, rgba(127, 127, 127, 0.08));
|
|
580
|
+
border-top: 1px solid var(--dsw-alias-border-l2, rgba(127, 127, 127, 0.15));
|
|
581
|
+
display: flex;
|
|
582
|
+
align-items: center;
|
|
583
|
+
justify-content: space-between;
|
|
584
|
+
gap: 8px;
|
|
585
|
+
overflow: hidden;
|
|
586
|
+
text-overflow: ellipsis;
|
|
587
|
+
white-space: nowrap;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
.agy-image-link-chip {
|
|
591
|
+
display: inline-flex;
|
|
592
|
+
align-items: center;
|
|
593
|
+
gap: 4px;
|
|
594
|
+
padding: 2px 6px;
|
|
595
|
+
margin: 0 2px;
|
|
596
|
+
border-radius: 4px;
|
|
597
|
+
font-size: 12px;
|
|
598
|
+
background: var(--dsw-alias-bg-l2, rgba(127, 127, 127, 0.1));
|
|
599
|
+
border: 1px solid var(--dsw-alias-border-l2, rgba(127, 127, 127, 0.2));
|
|
600
|
+
color: var(--dsw-alias-label-primary, #ddd);
|
|
601
|
+
cursor: pointer;
|
|
602
|
+
text-decoration: none;
|
|
603
|
+
transition: all 0.15s ease;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
.agy-image-link-chip:hover {
|
|
607
|
+
background: var(--dsw-alias-bg-hover, rgba(127, 127, 127, 0.2));
|
|
608
|
+
border-color: var(--dsw-alias-border-l3, rgba(127, 127, 127, 0.35));
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/* Lightbox Modal */
|
|
612
|
+
.agy-lightbox-backdrop {
|
|
613
|
+
position: fixed;
|
|
614
|
+
inset: 0;
|
|
615
|
+
z-index: 999999;
|
|
616
|
+
background: rgba(0, 0, 0, 0.85);
|
|
617
|
+
backdrop-filter: blur(8px);
|
|
618
|
+
display: flex;
|
|
619
|
+
flex-direction: column;
|
|
620
|
+
user-select: none;
|
|
621
|
+
opacity: 0;
|
|
622
|
+
transition: opacity 0.2s ease;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
.agy-lightbox-backdrop.open {
|
|
626
|
+
opacity: 1;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
.agy-lightbox-header {
|
|
630
|
+
height: 48px;
|
|
631
|
+
display: flex;
|
|
632
|
+
align-items: center;
|
|
633
|
+
justify-content: space-between;
|
|
634
|
+
padding: 0 16px;
|
|
635
|
+
background: rgba(20, 20, 20, 0.65);
|
|
636
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
|
637
|
+
color: #fff;
|
|
638
|
+
font-size: 13px;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
.agy-lightbox-title {
|
|
642
|
+
font-weight: 500;
|
|
643
|
+
overflow: hidden;
|
|
644
|
+
text-overflow: ellipsis;
|
|
645
|
+
white-space: nowrap;
|
|
646
|
+
max-width: 60%;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
.agy-lightbox-actions {
|
|
650
|
+
display: flex;
|
|
651
|
+
align-items: center;
|
|
652
|
+
gap: 8px;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
.agy-lightbox-btn {
|
|
656
|
+
background: rgba(255, 255, 255, 0.1);
|
|
657
|
+
border: 1px solid rgba(255, 255, 255, 0.15);
|
|
658
|
+
color: #fff;
|
|
659
|
+
border-radius: 6px;
|
|
660
|
+
padding: 4px 10px;
|
|
661
|
+
cursor: pointer;
|
|
662
|
+
font-size: 12px;
|
|
663
|
+
display: inline-flex;
|
|
664
|
+
align-items: center;
|
|
665
|
+
gap: 4px;
|
|
666
|
+
transition: all 0.15s ease;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
.agy-lightbox-btn:hover {
|
|
670
|
+
background: rgba(255, 255, 255, 0.2);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
.agy-lightbox-btn-close {
|
|
674
|
+
background: rgba(239, 68, 68, 0.25);
|
|
675
|
+
border-color: rgba(239, 68, 68, 0.4);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
.agy-lightbox-btn-close:hover {
|
|
679
|
+
background: rgba(239, 68, 68, 0.45);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
.agy-lightbox-body {
|
|
683
|
+
flex: 1;
|
|
684
|
+
position: relative;
|
|
685
|
+
overflow: hidden;
|
|
686
|
+
display: flex;
|
|
687
|
+
align-items: center;
|
|
688
|
+
justify-content: center;
|
|
689
|
+
cursor: grab;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
.agy-lightbox-body.grabbing {
|
|
693
|
+
cursor: grabbing;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
.agy-lightbox-img-wrap {
|
|
697
|
+
transform-origin: center center;
|
|
698
|
+
transition: transform 0.05s ease-out;
|
|
699
|
+
display: inline-block;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
.agy-lightbox-img {
|
|
703
|
+
max-width: 90vw;
|
|
704
|
+
max-height: 85vh;
|
|
705
|
+
object-fit: contain;
|
|
706
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
|
|
707
|
+
border-radius: 4px;
|
|
708
|
+
pointer-events: none;
|
|
709
|
+
}
|
|
710
|
+
`;
|
|
711
|
+
function injectStyles() {
|
|
712
|
+
if (typeof document === 'undefined')
|
|
713
|
+
return;
|
|
714
|
+
const styleId = 'dsh-agy-link-images-css';
|
|
715
|
+
if (document.getElementById(styleId) === null) {
|
|
716
|
+
const st = document.createElement('style');
|
|
717
|
+
st.id = styleId;
|
|
718
|
+
st.textContent = IMAGE_CSS;
|
|
719
|
+
const host = document.head ?? document.documentElement;
|
|
720
|
+
if (host)
|
|
721
|
+
host.appendChild(st);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
function getOrigin() {
|
|
725
|
+
if (typeof window !== 'undefined' && window.location?.origin) {
|
|
726
|
+
return window.location.origin;
|
|
727
|
+
}
|
|
728
|
+
return '';
|
|
729
|
+
}
|
|
730
|
+
/** Build media URL from path */
|
|
731
|
+
function buildMediaUrl(rawPath) {
|
|
732
|
+
const origin = getOrigin();
|
|
733
|
+
return `${origin}/plugins/agy-cli/media?path=${encodeURIComponent(rawPath.trim())}`;
|
|
734
|
+
}
|
|
735
|
+
/**
|
|
736
|
+
* Open fullscreen Lightbox modal
|
|
737
|
+
*/
|
|
738
|
+
function openLightbox(src, title = 'Image Preview', rawPath) {
|
|
739
|
+
if (typeof document === 'undefined')
|
|
740
|
+
return;
|
|
741
|
+
injectStyles();
|
|
742
|
+
if (lightboxOverlay) {
|
|
743
|
+
lightboxOverlay.remove();
|
|
744
|
+
lightboxOverlay = null;
|
|
745
|
+
}
|
|
746
|
+
lightboxScale = 1.0;
|
|
747
|
+
lightboxTranslateX = 0;
|
|
748
|
+
lightboxTranslateY = 0;
|
|
749
|
+
const overlay = document.createElement('div');
|
|
750
|
+
overlay.className = 'agy-lightbox-backdrop';
|
|
751
|
+
const header = document.createElement('div');
|
|
752
|
+
header.className = 'agy-lightbox-header';
|
|
753
|
+
const titleEl = document.createElement('div');
|
|
754
|
+
titleEl.className = 'agy-lightbox-title';
|
|
755
|
+
titleEl.textContent = title;
|
|
756
|
+
header.appendChild(titleEl);
|
|
757
|
+
const actions = document.createElement('div');
|
|
758
|
+
actions.className = 'agy-lightbox-actions';
|
|
759
|
+
const btnZoomIn = document.createElement('button');
|
|
760
|
+
btnZoomIn.className = 'agy-lightbox-btn';
|
|
761
|
+
btnZoomIn.textContent = '🔍+';
|
|
762
|
+
btnZoomIn.title = '放大 / Zoom In';
|
|
763
|
+
const btnZoomOut = document.createElement('button');
|
|
764
|
+
btnZoomOut.className = 'agy-lightbox-btn';
|
|
765
|
+
btnZoomOut.textContent = '🔍-';
|
|
766
|
+
btnZoomOut.title = '缩小 / Zoom Out';
|
|
767
|
+
const btnReset = document.createElement('button');
|
|
768
|
+
btnReset.className = 'agy-lightbox-btn';
|
|
769
|
+
btnReset.textContent = '1:1';
|
|
770
|
+
btnReset.title = '实际大小 / Reset';
|
|
771
|
+
actions.appendChild(btnZoomIn);
|
|
772
|
+
actions.appendChild(btnZoomOut);
|
|
773
|
+
actions.appendChild(btnReset);
|
|
774
|
+
if (rawPath) {
|
|
775
|
+
const btnCopyPath = document.createElement('button');
|
|
776
|
+
btnCopyPath.className = 'agy-lightbox-btn';
|
|
777
|
+
btnCopyPath.textContent = '📋 复制路径';
|
|
778
|
+
btnCopyPath.onclick = (e) => {
|
|
779
|
+
e.stopPropagation();
|
|
780
|
+
if (navigator.clipboard) {
|
|
781
|
+
navigator.clipboard.writeText(rawPath).then(() => {
|
|
782
|
+
btnCopyPath.textContent = '✓ 已复制';
|
|
783
|
+
setTimeout(() => { btnCopyPath.textContent = '📋 复制路径'; }, 1500);
|
|
784
|
+
}).catch(() => { });
|
|
785
|
+
}
|
|
786
|
+
};
|
|
787
|
+
actions.appendChild(btnCopyPath);
|
|
788
|
+
}
|
|
789
|
+
const btnClose = document.createElement('button');
|
|
790
|
+
btnClose.className = 'agy-lightbox-btn agy-lightbox-btn-close';
|
|
791
|
+
btnClose.textContent = '✕ 关闭';
|
|
792
|
+
actions.appendChild(btnClose);
|
|
793
|
+
header.appendChild(actions);
|
|
794
|
+
overlay.appendChild(header);
|
|
795
|
+
const body = document.createElement('div');
|
|
796
|
+
body.className = 'agy-lightbox-body';
|
|
797
|
+
const imgWrap = document.createElement('div');
|
|
798
|
+
imgWrap.className = 'agy-lightbox-img-wrap';
|
|
799
|
+
const img = document.createElement('img');
|
|
800
|
+
img.className = 'agy-lightbox-img';
|
|
801
|
+
img.src = src;
|
|
802
|
+
img.alt = title;
|
|
803
|
+
currentLightboxImg = img;
|
|
804
|
+
imgWrap.appendChild(img);
|
|
805
|
+
body.appendChild(imgWrap);
|
|
806
|
+
overlay.appendChild(body);
|
|
807
|
+
const updateTransform = () => {
|
|
808
|
+
imgWrap.style.transform = `translate(${lightboxTranslateX}px, ${lightboxTranslateY}px) scale(${lightboxScale})`;
|
|
809
|
+
};
|
|
810
|
+
btnZoomIn.onclick = (e) => {
|
|
811
|
+
e.stopPropagation();
|
|
812
|
+
lightboxScale = Math.min(lightboxScale * 1.3, 5.0);
|
|
813
|
+
updateTransform();
|
|
814
|
+
};
|
|
815
|
+
btnZoomOut.onclick = (e) => {
|
|
816
|
+
e.stopPropagation();
|
|
817
|
+
lightboxScale = Math.max(lightboxScale / 1.3, 0.2);
|
|
818
|
+
updateTransform();
|
|
819
|
+
};
|
|
820
|
+
btnReset.onclick = (e) => {
|
|
821
|
+
e.stopPropagation();
|
|
822
|
+
lightboxScale = 1.0;
|
|
823
|
+
lightboxTranslateX = 0;
|
|
824
|
+
lightboxTranslateY = 0;
|
|
825
|
+
updateTransform();
|
|
826
|
+
};
|
|
827
|
+
const closeLightbox = () => {
|
|
828
|
+
window.removeEventListener('keydown', onKeyDown);
|
|
829
|
+
overlay.classList.remove('open');
|
|
830
|
+
setTimeout(() => {
|
|
831
|
+
overlay.remove();
|
|
832
|
+
if (lightboxOverlay === overlay)
|
|
833
|
+
lightboxOverlay = null;
|
|
834
|
+
}, 200);
|
|
835
|
+
};
|
|
836
|
+
btnClose.onclick = (e) => {
|
|
837
|
+
e.stopPropagation();
|
|
838
|
+
closeLightbox();
|
|
839
|
+
};
|
|
840
|
+
overlay.onclick = (e) => {
|
|
841
|
+
if (e.target === overlay || e.target === body) {
|
|
842
|
+
closeLightbox();
|
|
843
|
+
}
|
|
844
|
+
};
|
|
845
|
+
const onKeyDown = (e) => {
|
|
846
|
+
if (e.key === 'Escape') {
|
|
847
|
+
closeLightbox();
|
|
848
|
+
}
|
|
849
|
+
};
|
|
850
|
+
window.addEventListener('keydown', onKeyDown);
|
|
851
|
+
// Drag and pan
|
|
852
|
+
body.onmousedown = (e) => {
|
|
853
|
+
isDraggingLightbox = true;
|
|
854
|
+
dragStartX = e.clientX - lightboxTranslateX;
|
|
855
|
+
dragStartY = e.clientY - lightboxTranslateY;
|
|
856
|
+
body.classList.add('grabbing');
|
|
857
|
+
};
|
|
858
|
+
const onMouseMove = (e) => {
|
|
859
|
+
if (!isDraggingLightbox)
|
|
860
|
+
return;
|
|
861
|
+
lightboxTranslateX = e.clientX - dragStartX;
|
|
862
|
+
lightboxTranslateY = e.clientY - dragStartY;
|
|
863
|
+
updateTransform();
|
|
864
|
+
};
|
|
865
|
+
const onMouseUp = () => {
|
|
866
|
+
if (isDraggingLightbox) {
|
|
867
|
+
isDraggingLightbox = false;
|
|
868
|
+
body.classList.remove('grabbing');
|
|
869
|
+
}
|
|
870
|
+
};
|
|
871
|
+
window.addEventListener('mousemove', onMouseMove);
|
|
872
|
+
window.addEventListener('mouseup', onMouseUp);
|
|
873
|
+
// Wheel zoom
|
|
874
|
+
body.onwheel = (e) => {
|
|
875
|
+
e.preventDefault();
|
|
876
|
+
const factor = e.deltaY < 0 ? 1.15 : 0.87;
|
|
877
|
+
lightboxScale = Math.max(0.2, Math.min(lightboxScale * factor, 5.0));
|
|
878
|
+
updateTransform();
|
|
879
|
+
};
|
|
880
|
+
document.body.appendChild(overlay);
|
|
881
|
+
lightboxOverlay = overlay;
|
|
882
|
+
requestAnimationFrame(() => overlay.classList.add('open'));
|
|
883
|
+
}
|
|
884
|
+
/** Check if a URL or path is an image */
|
|
885
|
+
function isImageFile(pathOrUrl) {
|
|
886
|
+
if (!pathOrUrl || typeof pathOrUrl !== 'string')
|
|
887
|
+
return false;
|
|
888
|
+
const clean = pathOrUrl.split(/[?#]/)[0] ?? '';
|
|
889
|
+
return /\.(png|jpe?g|webp|gif|svg|bmp|ico|avif)$/i.test(clean);
|
|
890
|
+
}
|
|
891
|
+
/** Get React props of a DOM element */
|
|
892
|
+
function getReactProps(el) {
|
|
893
|
+
const key = Object.keys(el).find((k) => k.startsWith('__reactProps'));
|
|
894
|
+
if (!key)
|
|
895
|
+
return null;
|
|
896
|
+
return el[key];
|
|
897
|
+
}
|
|
898
|
+
/**
|
|
899
|
+
* Scan DOM for imageAlt spans and enhance them with live rendered images
|
|
900
|
+
*/
|
|
901
|
+
function scanAndEnhanceImages() {
|
|
902
|
+
if (typeof document === 'undefined')
|
|
903
|
+
return;
|
|
904
|
+
const origin = getOrigin();
|
|
905
|
+
// 1. Rewrite any existing <img> src pointing to 127.0.0.1 to current origin if remote
|
|
906
|
+
if (origin && !origin.includes('127.0.0.1') && !origin.includes('localhost')) {
|
|
907
|
+
const images = document.querySelectorAll('img[src*="/plugins/agy-cli/media"]');
|
|
908
|
+
for (let i = 0; i < images.length; i++) {
|
|
909
|
+
const img = images[i];
|
|
910
|
+
if (img && img.src.includes('127.0.0.1')) {
|
|
911
|
+
const u = new URL(img.src);
|
|
912
|
+
img.src = `${origin}${u.pathname}${u.search}`;
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
// 2. Scan markdown containers for imageAlt spans and replace with real images
|
|
917
|
+
const markdownContainers = document.querySelectorAll('div[class*="_markdown_"], [data-markdown-variant]');
|
|
918
|
+
for (let i = 0; i < markdownContainers.length; i++) {
|
|
919
|
+
const container = markdownContainers[i];
|
|
920
|
+
if (!container)
|
|
921
|
+
continue;
|
|
922
|
+
const altSpans = container.querySelectorAll('span[class*="imageAlt"], span[class*="_imageAlt_"]');
|
|
923
|
+
if (altSpans.length === 0)
|
|
924
|
+
continue;
|
|
925
|
+
// Check React props for original markdown text
|
|
926
|
+
const props = getReactProps(container);
|
|
927
|
+
const mdText = typeof props?.text === 'string' ? props.text : '';
|
|
928
|
+
if (!mdText)
|
|
929
|
+
continue;
|
|
930
|
+
// Extract all image markdown definitions: 
|
|
931
|
+
const imgMatches = [];
|
|
932
|
+
const re = /!\[(.*?)\]\((file:\/\/\/[^\s)]+|file:\/\/[^\s)]+|[A-Za-z]:[\\/][^\s)]+|\/home\/[^\s)]+|\/mnt\/[a-zA-Z]\/[^\s)]+|\\\\wsl\.localhost\\[^\s)]+)\)/g;
|
|
933
|
+
let m;
|
|
934
|
+
while ((m = re.exec(mdText)) !== null) {
|
|
935
|
+
imgMatches.push({ alt: m[1] ?? '', target: m[2] ?? '' });
|
|
936
|
+
}
|
|
937
|
+
if (imgMatches.length === 0)
|
|
938
|
+
continue;
|
|
939
|
+
for (let j = 0; j < altSpans.length; j++) {
|
|
940
|
+
const span = altSpans[j];
|
|
941
|
+
if (!span || span.dataset.agyEnhanced === 'true')
|
|
942
|
+
continue;
|
|
943
|
+
const altText = (span.textContent ?? '').trim();
|
|
944
|
+
const match = imgMatches.find((im) => im.alt.trim() === altText) ?? imgMatches[j];
|
|
945
|
+
if (!match)
|
|
946
|
+
continue;
|
|
947
|
+
span.dataset.agyEnhanced = 'true';
|
|
948
|
+
const mediaUrl = buildMediaUrl(match.target);
|
|
949
|
+
// Create card
|
|
950
|
+
const card = document.createElement('div');
|
|
951
|
+
card.className = 'agy-image-card';
|
|
952
|
+
const thumbWrap = document.createElement('div');
|
|
953
|
+
thumbWrap.className = 'agy-image-thumb-wrap';
|
|
954
|
+
thumbWrap.title = `${altText || 'Image'} (点击全屏放大)`;
|
|
955
|
+
const img = document.createElement('img');
|
|
956
|
+
img.className = 'agy-image-thumb';
|
|
957
|
+
img.src = mediaUrl;
|
|
958
|
+
img.alt = altText || 'Preview';
|
|
959
|
+
img.loading = 'lazy';
|
|
960
|
+
img.onerror = () => {
|
|
961
|
+
// Fall back to original alt text if media loading fails
|
|
962
|
+
card.style.display = 'none';
|
|
963
|
+
span.style.display = '';
|
|
964
|
+
};
|
|
965
|
+
thumbWrap.appendChild(img);
|
|
966
|
+
card.appendChild(thumbWrap);
|
|
967
|
+
if (altText) {
|
|
968
|
+
const caption = document.createElement('div');
|
|
969
|
+
caption.className = 'agy-image-caption';
|
|
970
|
+
caption.textContent = altText;
|
|
971
|
+
card.appendChild(caption);
|
|
972
|
+
}
|
|
973
|
+
thumbWrap.onclick = (e) => {
|
|
974
|
+
e.stopPropagation();
|
|
975
|
+
openLightbox(mediaUrl, altText || 'Image Preview', match.target);
|
|
976
|
+
};
|
|
977
|
+
span.style.display = 'none';
|
|
978
|
+
span.parentNode?.insertBefore(card, span);
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
// 3. Scan for <a> links to file:// images
|
|
982
|
+
const links = document.querySelectorAll('a[href^="file://"]');
|
|
983
|
+
for (let i = 0; i < links.length; i++) {
|
|
984
|
+
const link = links[i];
|
|
985
|
+
if (!link || link.dataset.agyLinkEnhanced === 'true')
|
|
986
|
+
continue;
|
|
987
|
+
const href = link.href ?? '';
|
|
988
|
+
if (!isImageFile(href))
|
|
989
|
+
continue;
|
|
990
|
+
link.dataset.agyLinkEnhanced = 'true';
|
|
991
|
+
const mediaUrl = buildMediaUrl(href);
|
|
992
|
+
const linkText = link.textContent ?? 'Image';
|
|
993
|
+
link.onclick = (e) => {
|
|
994
|
+
e.preventDefault();
|
|
995
|
+
e.stopPropagation();
|
|
996
|
+
openLightbox(mediaUrl, linkText, href);
|
|
997
|
+
};
|
|
998
|
+
// Add a visual preview indicator
|
|
999
|
+
if (!link.querySelector('.agy-image-link-icon')) {
|
|
1000
|
+
const badge = document.createElement('span');
|
|
1001
|
+
badge.className = 'agy-image-link-chip agy-image-link-icon';
|
|
1002
|
+
badge.textContent = '🖼️ 预览';
|
|
1003
|
+
link.appendChild(badge);
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
let imageEnhancerDispose = null;
|
|
1008
|
+
function installImageEnhancer() {
|
|
1009
|
+
if (typeof window === 'undefined' || typeof document === 'undefined')
|
|
1010
|
+
return () => { };
|
|
1011
|
+
// A plugin reload must DISCONNECT the previous observer instead of stacking
|
|
1012
|
+
// one more per reload (the ctx.effect contract main's installers follow).
|
|
1013
|
+
imageEnhancerDispose?.();
|
|
1014
|
+
injectStyles();
|
|
1015
|
+
const observer = new MutationObserver(() => {
|
|
1016
|
+
scanAndEnhanceImages();
|
|
1017
|
+
});
|
|
1018
|
+
const setup = () => {
|
|
1019
|
+
if (document.body) {
|
|
1020
|
+
observer.observe(document.body, { childList: true, subtree: true });
|
|
1021
|
+
scanAndEnhanceImages();
|
|
1022
|
+
}
|
|
1023
|
+
};
|
|
1024
|
+
if (document.readyState === 'loading') {
|
|
1025
|
+
window.addEventListener('DOMContentLoaded', setup, { once: true });
|
|
1026
|
+
}
|
|
1027
|
+
else {
|
|
1028
|
+
setup();
|
|
1029
|
+
}
|
|
1030
|
+
const dispose = () => {
|
|
1031
|
+
observer.disconnect();
|
|
1032
|
+
window.removeEventListener('DOMContentLoaded', setup);
|
|
1033
|
+
if (imageEnhancerDispose === dispose)
|
|
1034
|
+
imageEnhancerDispose = null;
|
|
1035
|
+
};
|
|
1036
|
+
imageEnhancerDispose = dispose;
|
|
1037
|
+
return dispose;
|
|
1038
|
+
}
|
|
1039
|
+
};
|
|
1040
|
+
__modules["client/toolview.js"] = function (require, exports, module) {
|
|
1041
|
+
"use strict";
|
|
1042
|
+
// Native tool-card toolview for the agy_tool mirror (DSH >= 0.1.5).
|
|
1043
|
+
//
|
|
1044
|
+
// Matches the official DSH tool row and card presentation:
|
|
1045
|
+
// - Collapsed state: clean, borderless 24px row with official tool SVG icon,
|
|
1046
|
+
// tool title, separator dot, and summary/preview.
|
|
1047
|
+
// - Mouse hover on small icon: smoothly transitions to the chevron down arrow.
|
|
1048
|
+
// - Expanded state: leading icon is the chevron down arrow.
|
|
1049
|
+
// - Expanded card: rounded 12px card with header (prompt/cwd/path + Copy button)
|
|
1050
|
+
// and scrollable content (max-height with custom scrollbars) for long outputs.
|
|
1051
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1052
|
+
exports.makeToggle = makeToggle;
|
|
1053
|
+
exports.useToggle = useToggle;
|
|
1054
|
+
exports.IconChevronDownOutline14 = IconChevronDownOutline14;
|
|
1055
|
+
exports.IconApiOutline14 = IconApiOutline14;
|
|
1056
|
+
exports.IconEditOutline16 = IconEditOutline16;
|
|
1057
|
+
exports.IconBrowseOutline16 = IconBrowseOutline16;
|
|
1058
|
+
exports.IconSearchOutline16 = IconSearchOutline16;
|
|
1059
|
+
exports.IconCodeOutline16 = IconCodeOutline16;
|
|
1060
|
+
exports.IconInspectOutline12 = IconInspectOutline12;
|
|
1061
|
+
exports.IconCopyOutline16 = IconCopyOutline16;
|
|
1062
|
+
exports.IconCheckOutline14 = IconCheckOutline14;
|
|
1063
|
+
exports.IconQuestionOutline16 = IconQuestionOutline16;
|
|
1064
|
+
exports.IconImageOutline16 = IconImageOutline16;
|
|
1065
|
+
exports.mirrorCardModel = mirrorCardModel;
|
|
1066
|
+
exports.trimTrailingBlankLines = trimTrailingBlankLines;
|
|
1067
|
+
exports.previewLine = previewLine;
|
|
1068
|
+
exports.AgyMirrorToolView = AgyMirrorToolView;
|
|
1069
|
+
exports.runCodeModel = runCodeModel;
|
|
1070
|
+
exports.AgyCodeToolView = AgyCodeToolView;
|
|
1071
|
+
exports.installAgyToolView = installAgyToolView;
|
|
1072
|
+
const locales_ts_1 = require("client/locales.js");
|
|
1073
|
+
const decisions_ts_1 = require("client/decisions.js");
|
|
1074
|
+
const images_ts_1 = require("client/images.js");
|
|
1075
|
+
function react() {
|
|
1076
|
+
const mod = require('react');
|
|
1077
|
+
if (mod === undefined || mod === null) {
|
|
1078
|
+
throw new Error('dsh-agy-link toolview requires react at render time (browser host)');
|
|
1079
|
+
}
|
|
1080
|
+
return mod;
|
|
1081
|
+
}
|
|
1082
|
+
function makeToggle(setValue) {
|
|
1083
|
+
return () => setValue((prev) => !prev);
|
|
1084
|
+
}
|
|
1085
|
+
function useToggle(initial = false) {
|
|
1086
|
+
const R2 = react();
|
|
1087
|
+
const [value, setValue] = R2.useState(initial);
|
|
1088
|
+
return [value, makeToggle(setValue)];
|
|
1089
|
+
}
|
|
1090
|
+
function hx(type, props, ...children) {
|
|
1091
|
+
return react().createElement(type, props, ...children);
|
|
1092
|
+
}
|
|
1093
|
+
// ---- Official DSH SVGs (100% faithful to native DSH client-ui) ----------
|
|
1094
|
+
function IconChevronDownOutline14(size = 14, className = '') {
|
|
1095
|
+
return hx('svg', {
|
|
1096
|
+
width: size,
|
|
1097
|
+
height: size,
|
|
1098
|
+
className: className || undefined,
|
|
1099
|
+
viewBox: '0 0 14 14',
|
|
1100
|
+
fill: 'none',
|
|
1101
|
+
style: { display: 'block', flexShrink: 0 },
|
|
1102
|
+
'aria-hidden': true,
|
|
1103
|
+
}, hx('path', {
|
|
1104
|
+
d: 'M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z',
|
|
1105
|
+
fill: 'currentColor',
|
|
1106
|
+
}));
|
|
1107
|
+
}
|
|
1108
|
+
function IconApiOutline14(size = 14, className = '') {
|
|
1109
|
+
return hx('svg', {
|
|
1110
|
+
width: size,
|
|
1111
|
+
height: size,
|
|
1112
|
+
className: className || undefined,
|
|
1113
|
+
viewBox: '0 0 14 14',
|
|
1114
|
+
fill: 'none',
|
|
1115
|
+
style: { display: 'block', flexShrink: 0 },
|
|
1116
|
+
'aria-hidden': true,
|
|
1117
|
+
}, hx('path', {
|
|
1118
|
+
transform: 'translate(0.6689 1.073)',
|
|
1119
|
+
d: 'M11.4818 5.57813C11.4818 4.45301 11.4807 3.66237 11.4075 3.05908C11.3359 2.46953 11.2024 2.13852 10.9939 1.89441C10.9247 1.81341 10.8493 1.73801 10.7683 1.66882C10.5242 1.46033 10.1932 1.32686 9.60364 1.25525C9.00034 1.18198 8.20974 1.18091 7.0846 1.18091L5.57813 1.18091C4.45301 1.18091 3.66238 1.18198 3.05908 1.25525C2.46953 1.32686 2.13852 1.46033 1.89441 1.66882C1.81341 1.73801 1.73801 1.81341 1.66882 1.89441C1.46033 2.13852 1.32686 2.46953 1.25525 3.05908C1.18198 3.66238 1.18091 4.45301 1.18091 5.57813L1.18091 6.2771C1.18091 7.40218 1.18197 8.19288 1.25525 8.79614C1.32687 9.38553 1.46036 9.71674 1.66882 9.96082C1.73797 10.0417 1.81347 10.1173 1.89441 10.1864C2.13851 10.3948 2.46965 10.5275 3.05908 10.5991C3.66238 10.6724 4.45298 10.6735 5.57813 10.6735L7.0846 10.6735C8.20977 10.6735 9.00033 10.6724 9.60364 10.5991C10.1931 10.5275 10.5242 10.3948 10.7683 10.1864C10.8493 10.1173 10.9247 10.0417 10.9939 9.96082C11.2024 9.71674 11.3358 9.38553 11.4075 8.79614C11.4808 8.19288 11.4818 7.40218 11.4818 6.2771L11.4818 5.57813ZM12.6627 6.2771C12.6627 7.37222 12.6637 8.247 12.5798 8.93799C12.4942 9.64284 12.3133 10.2359 11.8928 10.7282C11.7834 10.8562 11.6637 10.9751 11.5356 11.0845C11.0434 11.5049 10.4511 11.6867 9.74634 11.7723C9.05525 11.8563 8.17999 11.8552 7.0846 11.8552L5.57813 11.8552C4.48273 11.8552 3.60747 11.8563 2.91638 11.7723C2.21157 11.6867 1.61933 11.5049 1.12708 11.0845C0.99901 10.9751 0.879281 10.8562 0.769898 10.7282C0.349454 10.2359 0.168506 9.64284 0.0828864 8.93799C-0.00101964 8.247 4.88512e-07 7.37222 6.47206e-07 6.2771L6.47206e-07 5.57813C6.47206e-07 4.48273 -0.00106163 3.60747 0.0828864 2.91638C0.168502 2.21168 0.349594 1.61928 0.769898 1.12708C0.879302 0.998981 0.998981 0.879302 1.12708 0.769898C1.61928 0.349594 2.21168 0.168502 2.91638 0.0828864C3.60747 -0.00106163 4.48273 6.47206e-07 5.57813 6.47206e-07L7.0846 6.47206e-07C8.17999 6.47206e-07 9.05525 -0.00106163 9.74634 0.0828864C10.451 0.168505 11.0434 0.349587 11.5356 0.769898C11.6637 0.879302 11.7834 0.998981 11.8928 1.12708C12.3131 1.61928 12.4942 2.21169 12.5798 2.91638C12.6638 3.60747 12.6627 4.48273 12.6627 5.57813L12.6627 6.2771Z',
|
|
1120
|
+
fill: 'currentColor',
|
|
1121
|
+
}), hx('path', {
|
|
1122
|
+
transform: 'translate(0.6689 1.073)',
|
|
1123
|
+
d: 'M6.02607 5.50955L6.44306 5.9274L3.84284 8.52762L3.425 8.11063L3.00715 7.69278L4.77253 5.9274L3.00715 4.16202L3.84284 3.32633L6.02607 5.50955Z',
|
|
1124
|
+
fill: 'currentColor',
|
|
1125
|
+
}), hx('path', {
|
|
1126
|
+
transform: 'translate(0.6689 1.073)',
|
|
1127
|
+
d: 'M9.23789 7.35397L9.23789 8.53488L6.96238 8.53488L6.96238 7.35397L9.23789 7.35397Z',
|
|
1128
|
+
fill: 'currentColor',
|
|
1129
|
+
}));
|
|
1130
|
+
}
|
|
1131
|
+
function IconEditOutline16(size = 14, className = '') {
|
|
1132
|
+
return hx('svg', {
|
|
1133
|
+
width: size,
|
|
1134
|
+
height: size,
|
|
1135
|
+
className: className || undefined,
|
|
1136
|
+
viewBox: '0 0 16 16',
|
|
1137
|
+
fill: 'none',
|
|
1138
|
+
style: { display: 'block', flexShrink: 0 },
|
|
1139
|
+
'aria-hidden': true,
|
|
1140
|
+
}, hx('path', {
|
|
1141
|
+
d: 'M9.94076 1.34942C10.7047 0.90231 11.6503 0.902415 12.4143 1.34942C12.7061 1.52015 12.9688 1.79118 13.3104 2.13284C13.6521 2.47448 13.9231 2.73721 14.0939 3.02894C14.5408 3.79294 14.5409 4.73856 14.0939 5.50251C13.9231 5.79415 13.652 6.05704 13.3104 6.39861L6.65932 13.0497C6.28068 13.4284 6.00695 13.7108 5.66543 13.9097C5.32391 14.1085 4.94315 14.2074 4.42705 14.3498L3.24394 14.6761C2.77527 14.8054 2.34538 14.9262 2.00131 14.9684C1.65196 15.0112 1.17964 15.0013 0.810764 14.6325C0.441921 14.2637 0.432107 13.7913 0.47486 13.442C0.517035 13.0979 0.6379 12.668 0.767181 12.1993L1.09352 11.0162C1.23588 10.5001 1.33481 10.1193 1.5336 9.77784C1.7325 9.43632 2.0149 9.1626 2.39355 8.78395L9.04466 2.13284C9.38625 1.79126 9.64911 1.52016 9.94076 1.34942ZM15.5427 14.8398H7.55223L8.96707 13.425H15.5427V14.8398ZM3.39382 9.78422C2.965 10.213 2.84244 10.3436 2.75709 10.49C2.67183 10.6366 2.61862 10.8079 2.45733 11.3925L2.13099 12.5756C2.00183 13.0439 1.92194 13.3419 1.88863 13.5536C2.10041 13.5204 2.39872 13.4416 2.86764 13.3123L4.05075 12.9859C4.63544 12.8246 4.80669 12.7715 4.95323 12.6862C5.09968 12.6008 5.23022 12.4783 5.65905 12.0494L10.721 6.98644L8.45577 4.72121L3.39382 9.78422ZM11.7 2.57079C11.3774 2.38198 10.9777 2.38198 10.6551 2.57079C10.5602 2.62647 10.4487 2.72931 10.0449 3.13311L9.45604 3.72094L11.7213 5.98617L12.3102 5.39833C12.7139 4.99457 12.8168 4.88307 12.8725 4.78818C13.0613 4.46561 13.0612 4.06585 12.8725 3.74326C12.8169 3.64827 12.7146 3.53752 12.3102 3.13311C11.9057 2.72863 11.795 2.6264 11.7 2.57079Z',
|
|
1142
|
+
fill: 'currentColor',
|
|
1143
|
+
}));
|
|
1144
|
+
}
|
|
1145
|
+
function IconBrowseOutline16(size = 14, className = '') {
|
|
1146
|
+
return hx('svg', {
|
|
1147
|
+
width: size,
|
|
1148
|
+
height: size,
|
|
1149
|
+
className: className || undefined,
|
|
1150
|
+
viewBox: '0 0 16 16',
|
|
1151
|
+
fill: 'none',
|
|
1152
|
+
style: { display: 'block', flexShrink: 0 },
|
|
1153
|
+
'aria-hidden': true,
|
|
1154
|
+
}, hx('path', { d: 'M11.2426 4.80473V6.10551H4.75819V4.80473H11.2426Z', fill: 'currentColor' }), hx('path', { d: 'M9.40858 7.84478V9.14557H4.75819V7.84478H9.40858Z', fill: 'currentColor' }), hx('path', {
|
|
1155
|
+
d: 'M9.23438 0.546389C10.1941 0.546389 10.9683 0.544914 11.5859 0.611819C12.2161 0.680096 12.7634 0.825745 13.2393 1.17139C13.5172 1.3733 13.7619 1.61812 13.9639 1.896C14.3096 2.37183 14.4551 2.91922 14.5234 3.54932C14.5903 4.16686 14.5889 4.94133 14.5889 5.90088V10.0981C14.5889 11.0576 14.5903 11.8321 14.5234 12.4497C14.4552 13.0798 14.3094 13.6272 13.9639 14.103C13.7619 14.381 13.5172 14.6257 13.2393 14.8276C12.7633 15.1734 12.2163 15.3189 11.5859 15.3872C10.9683 15.4541 10.1942 15.4536 9.23438 15.4536H6.76563C5.80591 15.4536 5.03168 15.4541 4.41407 15.3872C3.78385 15.3189 3.23665 15.1734 2.76074 14.8276C2.48291 14.6257 2.23802 14.3809 2.03614 14.103C1.69066 13.6272 1.54483 13.0798 1.47657 12.4497C1.40973 11.8321 1.41114 11.0576 1.41114 10.0981V5.90088C1.41113 4.94132 1.40966 4.16686 1.47657 3.54932C1.54488 2.91921 1.69042 2.37184 2.03614 1.896C2.2381 1.61807 2.4828 1.37333 2.76074 1.17139C3.23665 0.825682 3.78386 0.680109 4.41407 0.611819C5.03168 0.544905 5.80591 0.546389 6.76563 0.546389H9.23438ZM6.76563 1.896C5.77586 1.896 5.0876 1.89738 4.55957 1.95459C4.0443 2.01043 3.76214 2.11349 3.55469 2.26416C3.39135 2.38284 3.24761 2.52662 3.12891 2.68994C2.97821 2.89736 2.8752 3.17967 2.81934 3.69483C2.76214 4.22279 2.76075 4.91131 2.76074 5.90088V10.0981C2.76074 11.0876 2.76221 11.7762 2.81934 12.3042C2.87516 12.8194 2.97829 13.1026 3.12891 13.3101C3.24754 13.4733 3.39147 13.6172 3.55469 13.7358C3.76213 13.8865 4.04438 13.9896 4.55957 14.0454C5.0876 14.1026 5.77586 14.103 6.76563 14.103H9.23438C10.2242 14.103 10.9124 14.1026 11.4404 14.0454C11.9556 13.9896 12.2379 13.8865 12.4453 13.7358C12.6086 13.6172 12.7525 13.4733 12.8711 13.3101C13.0217 13.1026 13.1248 12.8195 13.1807 12.3042C13.2378 11.7762 13.2393 11.0876 13.2393 10.0981V5.90088C13.2393 4.91131 13.2379 4.22279 13.1807 3.69483C13.1248 3.17969 13.0218 2.89736 12.8711 2.68994C12.7524 2.52667 12.6086 2.38281 12.4453 2.26416C12.2379 2.11355 11.9556 2.01041 11.4404 1.95459C10.9124 1.8974 10.2241 1.896 9.23438 1.896H6.76563Z',
|
|
1156
|
+
fill: 'currentColor',
|
|
1157
|
+
}));
|
|
1158
|
+
}
|
|
1159
|
+
function IconSearchOutline16(size = 14, className = '') {
|
|
1160
|
+
return hx('svg', {
|
|
1161
|
+
width: size,
|
|
1162
|
+
height: size,
|
|
1163
|
+
className: className || undefined,
|
|
1164
|
+
viewBox: '0 0 16 16',
|
|
1165
|
+
fill: 'none',
|
|
1166
|
+
style: { display: 'block', flexShrink: 0 },
|
|
1167
|
+
'aria-hidden': true,
|
|
1168
|
+
}, hx('path', {
|
|
1169
|
+
d: 'M11.894845 6.647401C11.894845 3.725463 9.534486 1.356779 6.623219 1.35657C3.711786 1.35657 1.351635 3.725338 1.351635 6.647401C1.351843 9.569296 3.711911 11.938273 6.623219 11.938273C9.534361 11.938064 11.894637 9.569171 11.894845 6.647401ZM13.245462 6.647401C13.245254 10.317935 10.280401 13.293613 6.623219 13.293821C2.965871 13.293821 0.000204 10.31806 0 6.647401C0 2.976574 2.965746 0 6.623219 0C10.280526 0.000205 13.245462 2.9767 13.245462 6.647401Z',
|
|
1170
|
+
fill: 'currentColor',
|
|
1171
|
+
}), hx('path', {
|
|
1172
|
+
d: 'M16.000417 15.041079L15.044449 16.000433L11.530434 12.473588L12.486298 11.514234L16.000417 15.041079Z',
|
|
1173
|
+
fill: 'currentColor',
|
|
1174
|
+
}));
|
|
1175
|
+
}
|
|
1176
|
+
function IconCodeOutline16(size = 14, className = '') {
|
|
1177
|
+
return hx('svg', {
|
|
1178
|
+
width: size,
|
|
1179
|
+
height: size,
|
|
1180
|
+
className: className || undefined,
|
|
1181
|
+
viewBox: '0 0 16 16',
|
|
1182
|
+
fill: 'none',
|
|
1183
|
+
style: { display: 'block', flexShrink: 0 },
|
|
1184
|
+
'aria-hidden': true,
|
|
1185
|
+
}, hx('path', {
|
|
1186
|
+
fillRule: 'evenodd',
|
|
1187
|
+
clipRule: 'evenodd',
|
|
1188
|
+
d: 'M12.3368 1.53569L11.931 4.43172H14.8086V5.79673H11.7404L11.1962 9.67859H14.2839V11.0436H11.0056L10.4994 14.6529L9.14873 14.4643L9.62731 11.0436H5.75876L5.25252 14.6529L3.90186 14.4643L4.38043 11.0436H1.69141V9.67859H4.57104L5.11417 5.79673H2.21609V4.43172H5.30581L5.73724 1.34713L7.08995 1.53569L6.68414 4.43172H10.5527L10.9841 1.34713L12.3368 1.53569ZM5.94937 9.67859H9.81791L10.361 5.79673H6.49353L5.94937 9.67859Z',
|
|
1189
|
+
fill: 'currentColor',
|
|
1190
|
+
}));
|
|
1191
|
+
}
|
|
1192
|
+
function IconInspectOutline12(size = 12, className = '') {
|
|
1193
|
+
return hx('svg', {
|
|
1194
|
+
width: size,
|
|
1195
|
+
height: size,
|
|
1196
|
+
className: className || undefined,
|
|
1197
|
+
viewBox: '0 0 16 16',
|
|
1198
|
+
fill: 'none',
|
|
1199
|
+
style: { display: 'inline-block', verticalAlign: '-1.5px', flexShrink: 0 },
|
|
1200
|
+
'aria-hidden': true,
|
|
1201
|
+
}, hx('path', {
|
|
1202
|
+
d: 'M16 8L10.8571 12V10.552L14.1383 8L10.8571 5.448V4L16 8ZM5.14286 10.552L1.86171 8L5.14286 5.448V4L0 8L5.14286 12V10.552ZM9.02514 4L5.59657 12H6.84057L10.2691 4H9.02514Z',
|
|
1203
|
+
fill: 'currentColor',
|
|
1204
|
+
}));
|
|
1205
|
+
}
|
|
1206
|
+
function IconCopyOutline16(size = 12, className = '') {
|
|
1207
|
+
return hx('svg', {
|
|
1208
|
+
width: size,
|
|
1209
|
+
height: size,
|
|
1210
|
+
className: className || undefined,
|
|
1211
|
+
viewBox: '0 0 16 16',
|
|
1212
|
+
fill: 'none',
|
|
1213
|
+
style: { display: 'inline-block', verticalAlign: '-1.5px', flexShrink: 0 },
|
|
1214
|
+
'aria-hidden': true,
|
|
1215
|
+
}, hx('path', {
|
|
1216
|
+
d: 'M6.14929 4.02032C7.11197 4.02032 7.87983 4.02016 8.49597 4.07598C9.12128 4.13269 9.65792 4.25188 10.1415 4.53106C10.7202 4.8653 11.2008 5.3459 11.535 5.92462C11.8142 6.40818 11.9334 6.94481 11.9901 7.57012C12.0459 8.18625 12.0458 8.95419 12.0458 9.9168C12.0458 10.8795 12.0459 11.6473 11.9901 12.2635C11.9334 12.8888 11.8142 13.4254 11.535 13.909C11.2008 14.4877 10.7202 14.9683 10.1415 15.3025C9.65792 15.5817 9.12128 15.7009 8.49597 15.7576C7.87984 15.8134 7.11196 15.8133 6.14929 15.8133C5.18667 15.8133 4.41874 15.8134 3.80261 15.7576C3.1773 15.7009 2.64067 15.5817 2.1571 15.3025C1.5784 14.9683 1.09778 14.4877 0.76355 13.909',
|
|
1217
|
+
fill: 'currentColor',
|
|
1218
|
+
}));
|
|
1219
|
+
}
|
|
1220
|
+
function IconCheckOutline14(size = 12, className = '') {
|
|
1221
|
+
return hx('svg', {
|
|
1222
|
+
width: size,
|
|
1223
|
+
height: size,
|
|
1224
|
+
className: className || undefined,
|
|
1225
|
+
viewBox: '0 0 14 14',
|
|
1226
|
+
fill: 'none',
|
|
1227
|
+
style: { display: 'inline-block', verticalAlign: '-1.5px', flexShrink: 0 },
|
|
1228
|
+
'aria-hidden': true,
|
|
1229
|
+
}, hx('path', {
|
|
1230
|
+
d: 'M11.5635 4.58984L7.61426 9.07715C7.35154 9.37561 7.11346 9.64812 6.89453 9.84668C6.66593 10.054 6.38519 10.2506 6.01465 10.3164C5.82079 10.3508 5.62207 10.3529 5.42773 10.3213C5.0561 10.2609 4.77266 10.0674 4.54102 9.86328C4.31926 9.66791 4.07752 9.39911 3.81055 9.10449L2.44531 7.59863L3.55664 6.59082L4.92188 8.09766C5.21256 8.41844 5.38878 8.61191 5.53223 8.73828C5.61022 8.80699 5.65253 8.83192 5.66895 8.83984C5.69648 8.84429 5.72449 8.84467 5.75195 8.83984C5.72657 8.84451 5.75564 8.85422 5.88672 8.73535C6.02833 8.60692 6.20225 8.41088 6.48828 8.08594L10.4385 3.59961L11.5635 4.58984Z',
|
|
1231
|
+
fill: 'currentColor',
|
|
1232
|
+
}));
|
|
1233
|
+
}
|
|
1234
|
+
function IconQuestionOutline16(size = 14, className = '') {
|
|
1235
|
+
return hx('svg', {
|
|
1236
|
+
width: size,
|
|
1237
|
+
height: size,
|
|
1238
|
+
className: className || undefined,
|
|
1239
|
+
viewBox: '0 0 16 16',
|
|
1240
|
+
fill: 'none',
|
|
1241
|
+
style: { display: 'block', flexShrink: 0 },
|
|
1242
|
+
'aria-hidden': true,
|
|
1243
|
+
}, hx('path', {
|
|
1244
|
+
d: 'M8 1C4.134 1 1 3.91 1 7.5c0 1.547.575 2.973 1.559 4.093L1.13 14.54a.5.5 0 00.627.63l3.238-1.079C5.875 14.316 6.91 14.5 8 14.5c3.866 0 7-2.91 7-6.5S11.866 1 8 1zm0 10.5a.75.75 0 110-1.5.75.75 0 010 1.5zm1.09-3.41c-.42.34-.59.54-.59.91H7.5v-.5c0-.66.36-1.12.82-1.48.44-.35.68-.61.68-1.02 0-.55-.45-1-1-1s-1 .45-1 1H6c0-1.1.9-2 2-2s2 .9 2 2c0 .73-.39 1.25-.91 1.67z',
|
|
1245
|
+
fill: 'currentColor',
|
|
1246
|
+
}));
|
|
1247
|
+
}
|
|
1248
|
+
function IconImageOutline16(size = 14, className = '') {
|
|
1249
|
+
return hx('svg', {
|
|
1250
|
+
width: size,
|
|
1251
|
+
height: size,
|
|
1252
|
+
className: className || undefined,
|
|
1253
|
+
viewBox: '0 0 16 16',
|
|
1254
|
+
fill: 'none',
|
|
1255
|
+
style: { display: 'block', flexShrink: 0 },
|
|
1256
|
+
'aria-hidden': true,
|
|
1257
|
+
}, hx('path', {
|
|
1258
|
+
d: 'M14 3H2a1 1 0 00-1 1v8a1 1 0 001 1h12a1 1 0 001-1V4a1 1 0 00-1-1zm-1 8H3l2.5-3.2 1.8 2.3 2.7-3.5L13 11zM5.5 6a1.5 1.5 0 110-3 1.5 1.5 0 010 3z',
|
|
1259
|
+
fill: 'currentColor',
|
|
1260
|
+
}));
|
|
1261
|
+
}
|
|
1262
|
+
function toolIconFor(kind, size = 14) {
|
|
1263
|
+
switch (kind) {
|
|
1264
|
+
case 'terminal':
|
|
1265
|
+
return IconApiOutline14(size);
|
|
1266
|
+
case 'diff':
|
|
1267
|
+
case 'delete':
|
|
1268
|
+
return IconEditOutline16(size);
|
|
1269
|
+
case 'read':
|
|
1270
|
+
return IconBrowseOutline16(size);
|
|
1271
|
+
case 'search':
|
|
1272
|
+
case 'list':
|
|
1273
|
+
return IconSearchOutline16(size);
|
|
1274
|
+
case 'question':
|
|
1275
|
+
return IconQuestionOutline16(size);
|
|
1276
|
+
case 'image':
|
|
1277
|
+
return IconImageOutline16(size);
|
|
1278
|
+
default:
|
|
1279
|
+
return IconCodeOutline16(size);
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
// ---- argument parsing -----------------------------------------------------
|
|
1283
|
+
function parsedArgsRaw(block) {
|
|
1284
|
+
const settled = block;
|
|
1285
|
+
if ('call' in settled && settled.call !== null && typeof settled.call.argsRaw === 'string') {
|
|
1286
|
+
return settled.call.argsRaw;
|
|
1287
|
+
}
|
|
1288
|
+
const running = block;
|
|
1289
|
+
if (typeof running.argsRaw === 'string')
|
|
1290
|
+
return running.argsRaw;
|
|
1291
|
+
return '';
|
|
1292
|
+
}
|
|
1293
|
+
function parseArgs(block) {
|
|
1294
|
+
const raw = parsedArgsRaw(block);
|
|
1295
|
+
if (raw === '')
|
|
1296
|
+
return null;
|
|
1297
|
+
try {
|
|
1298
|
+
const v = JSON.parse(raw);
|
|
1299
|
+
return typeof v === 'object' && v !== null && !Array.isArray(v) ? v : null;
|
|
1300
|
+
}
|
|
1301
|
+
catch {
|
|
1302
|
+
return null;
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
function mirrorInfo(block) {
|
|
1306
|
+
const a = parseArgs(block);
|
|
1307
|
+
if (a === null)
|
|
1308
|
+
return null;
|
|
1309
|
+
const tool = typeof a.tool === 'string' ? a.tool : '';
|
|
1310
|
+
if (tool === '')
|
|
1311
|
+
return null;
|
|
1312
|
+
let input = {};
|
|
1313
|
+
const raw = a.input;
|
|
1314
|
+
if (typeof raw === 'object' && raw !== null && !Array.isArray(raw))
|
|
1315
|
+
input = raw;
|
|
1316
|
+
else if (typeof raw === 'string') {
|
|
1317
|
+
try {
|
|
1318
|
+
const p = JSON.parse(raw);
|
|
1319
|
+
if (typeof p === 'object' && p !== null && !Array.isArray(p))
|
|
1320
|
+
input = p;
|
|
1321
|
+
else
|
|
1322
|
+
input = { value: raw };
|
|
1323
|
+
}
|
|
1324
|
+
catch {
|
|
1325
|
+
input = { value: raw };
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
return { tool, input };
|
|
1329
|
+
}
|
|
1330
|
+
function pick(input, ...keys) {
|
|
1331
|
+
for (const k of keys) {
|
|
1332
|
+
const v = input[k];
|
|
1333
|
+
if (typeof v === 'string' && v !== '')
|
|
1334
|
+
return v;
|
|
1335
|
+
}
|
|
1336
|
+
return undefined;
|
|
1337
|
+
}
|
|
1338
|
+
function num(input, ...keys) {
|
|
1339
|
+
for (const k of keys) {
|
|
1340
|
+
const v = input[k];
|
|
1341
|
+
if (typeof v === 'number' && Number.isFinite(v))
|
|
1342
|
+
return v;
|
|
1343
|
+
}
|
|
1344
|
+
return undefined;
|
|
1345
|
+
}
|
|
1346
|
+
function resultText(block) {
|
|
1347
|
+
const settled = block;
|
|
1348
|
+
if (!Array.isArray(settled.content))
|
|
1349
|
+
return '';
|
|
1350
|
+
const parts = [];
|
|
1351
|
+
for (const b of settled.content) {
|
|
1352
|
+
if (b && typeof b === 'object' && b.type === 'text' && typeof b.text === 'string') {
|
|
1353
|
+
parts.push(b.text);
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
return parts.join('\n');
|
|
1357
|
+
}
|
|
1358
|
+
function isSettled(block) {
|
|
1359
|
+
return block.kind === 'tool-result' || 'content' in block;
|
|
1360
|
+
}
|
|
1361
|
+
function isError(block) {
|
|
1362
|
+
const settled = block;
|
|
1363
|
+
return settled.isError === true || (settled.error !== undefined && settled.error !== null);
|
|
1364
|
+
}
|
|
1365
|
+
// ---- Card classification --------------------------------------------------
|
|
1366
|
+
const TERMINAL_TOOLS = new Set(['run_command', 'bash', 'execute_command']);
|
|
1367
|
+
const DIFF_TOOLS = new Set(['replace_file_content', 'edit_file', 'replace_in_file', 'edit', 'write_to_file', 'write_file', 'create_file']);
|
|
1368
|
+
const READ_TOOLS = new Set(['read_file', 'view_file', 'read', 'open_file']);
|
|
1369
|
+
const SEARCH_TOOLS = new Set(['find_by_name', 'glob', 'search_files', 'grep_search', 'search', 'search_file_content', 'grep', 'search_web', 'web_search']);
|
|
1370
|
+
const LIST_TOOLS = new Set(['list_dir', 'ls']);
|
|
1371
|
+
const DELETE_TOOLS = new Set(['delete_file', 'remove_file', 'rm']);
|
|
1372
|
+
const QUESTION_TOOLS = new Set(['ask_question', 'ask', 'question']);
|
|
1373
|
+
const IMAGE_TOOLS = new Set(['generate_image', 'create_image', 'render_image']);
|
|
1374
|
+
function mirrorCardModel(block, cwd, home) {
|
|
1375
|
+
const info = mirrorInfo(block);
|
|
1376
|
+
const out = resultText(block);
|
|
1377
|
+
if (info === null) {
|
|
1378
|
+
return { kind: 'generic', title: 'agy_tool', output: out, raw: parsedArgsRaw(block) };
|
|
1379
|
+
}
|
|
1380
|
+
const { tool, input } = info;
|
|
1381
|
+
const desc = pick(input, 'Description', 'description', 'toolAction', 'toolSummary', 'Instruction', 'instruction');
|
|
1382
|
+
if (QUESTION_TOOLS.has(tool)) {
|
|
1383
|
+
const questionsRaw = input.questions ?? input.Questions;
|
|
1384
|
+
const questions = Array.isArray(questionsRaw) ? questionsRaw : [];
|
|
1385
|
+
const firstQ = questions[0];
|
|
1386
|
+
const qTitle = firstQ?.question ?? desc ?? 'Ask Question';
|
|
1387
|
+
return {
|
|
1388
|
+
kind: 'question',
|
|
1389
|
+
tool,
|
|
1390
|
+
title: qTitle,
|
|
1391
|
+
output: out,
|
|
1392
|
+
questions,
|
|
1393
|
+
raw: parsedArgsRaw(block),
|
|
1394
|
+
};
|
|
1395
|
+
}
|
|
1396
|
+
if (IMAGE_TOOLS.has(tool)) {
|
|
1397
|
+
const prompt = pick(input, 'Prompt', 'prompt') ?? '';
|
|
1398
|
+
const name = pick(input, 'ImageName', 'image_name', 'name') ?? 'image';
|
|
1399
|
+
const ratio = pick(input, 'AspectRatio', 'aspect_ratio') ?? '1:1';
|
|
1400
|
+
return {
|
|
1401
|
+
kind: 'image',
|
|
1402
|
+
tool,
|
|
1403
|
+
title: desc ?? `Generate Image · ${name}`,
|
|
1404
|
+
prompt,
|
|
1405
|
+
imageName: name,
|
|
1406
|
+
aspectRatio: ratio,
|
|
1407
|
+
output: out,
|
|
1408
|
+
raw: parsedArgsRaw(block),
|
|
1409
|
+
};
|
|
1410
|
+
}
|
|
1411
|
+
if (TERMINAL_TOOLS.has(tool)) {
|
|
1412
|
+
const command = pick(input, 'CommandLine', 'command_line', 'command', 'cmd', 'Cmd') ?? JSON.stringify(input);
|
|
1413
|
+
const cwdVal = pick(input, 'Cwd', 'cwd', 'WorkingDirectory', 'working_directory') ?? cwd;
|
|
1414
|
+
return {
|
|
1415
|
+
kind: 'terminal',
|
|
1416
|
+
tool,
|
|
1417
|
+
title: desc !== undefined ? desc + ' · ' + command : command,
|
|
1418
|
+
command,
|
|
1419
|
+
...(cwdVal !== undefined ? { cwd: cwdVal } : {}),
|
|
1420
|
+
output: out,
|
|
1421
|
+
};
|
|
1422
|
+
}
|
|
1423
|
+
if (DIFF_TOOLS.has(tool)) {
|
|
1424
|
+
const rawPath = pick(input, 'TargetFile', 'target_file', 'path', 'file_path', 'Path', 'FilePath', 'AbsolutePath', 'targetFile', 'filename', 'FileName') ?? 'file';
|
|
1425
|
+
const path = relativize(rawPath, cwd, home);
|
|
1426
|
+
const oldText = pick(input, 'TargetContent', 'target_content', 'old_string', 'oldText', 'OldString', 'OldText', 'targetContent') ?? null;
|
|
1427
|
+
const newText = pick(input, 'ReplacementContent', 'replacement_content', 'new_string', 'newText', 'content', 'NewString', 'NewText', 'Content', 'replacementContent', 'CodeContent', 'code_content', 'contents', 'FileContents', 'codeContent') ?? '';
|
|
1428
|
+
const action = (tool === 'write_to_file' || tool === 'write_file' || tool === 'create_file') ? 'Write' : 'Edit';
|
|
1429
|
+
const isGenericDesc = desc !== undefined && /^(file edit|editing file|write file|writing file|file write|edit file|editing|writing|file create|creating file)$/i.test(desc.trim());
|
|
1430
|
+
const title = desc !== undefined && !isGenericDesc && !desc.includes(path)
|
|
1431
|
+
? `${action} ${path} · ${desc}`
|
|
1432
|
+
: `${action} ${path}`;
|
|
1433
|
+
return { kind: 'diff', tool, title, path: rawPath, oldText, newText, output: out };
|
|
1434
|
+
}
|
|
1435
|
+
if (READ_TOOLS.has(tool)) {
|
|
1436
|
+
const path = pick(input, 'AbsolutePath', 'absolute_path', 'TargetFile', 'target_file', 'path', 'file_path', 'filename', 'Path', 'FilePath', 'FileName', 'targetFile') ?? '';
|
|
1437
|
+
const offset = num(input, 'offset', 'Offset') ?? num(input, 'StartLine', 'start_line');
|
|
1438
|
+
const line = offset !== undefined ? (offset > 0 && (tool === 'view_file' || tool === 'read_file') ? offset : offset + 1) : undefined;
|
|
1439
|
+
const isImg = (0, images_ts_1.isImageFile)(path);
|
|
1440
|
+
return {
|
|
1441
|
+
kind: isImg ? 'image' : 'read',
|
|
1442
|
+
tool,
|
|
1443
|
+
title: desc ? `${desc} · ${path}` : path !== '' ? 'Read ' + path : 'Read',
|
|
1444
|
+
path,
|
|
1445
|
+
output: out,
|
|
1446
|
+
isImage: isImg,
|
|
1447
|
+
imagePath: isImg ? path : undefined,
|
|
1448
|
+
prompt: desc,
|
|
1449
|
+
imageName: path.split(/[/\\]/).pop() || 'image',
|
|
1450
|
+
...(path !== '' ? { location: { path, ...(line !== undefined ? { line } : {}) } } : {}),
|
|
1451
|
+
};
|
|
1452
|
+
}
|
|
1453
|
+
if (SEARCH_TOOLS.has(tool)) {
|
|
1454
|
+
const q = pick(input, 'query', 'Query', 'pattern', 'Pattern', 'regex', 'Regex', 'QueryString') ?? '';
|
|
1455
|
+
return { kind: 'search', tool, title: desc ?? (q !== '' ? 'Search ' + q : 'Search'), output: out };
|
|
1456
|
+
}
|
|
1457
|
+
if (LIST_TOOLS.has(tool)) {
|
|
1458
|
+
const path = pick(input, 'DirectoryPath', 'directory_path', 'path', 'directory', 'Path', 'Directory', 'SearchDirectory', 'search_directory', 'AbsolutePath') ?? '';
|
|
1459
|
+
return { kind: 'list', tool, title: desc ?? (path !== '' ? 'List ' + path : 'List directory'), output: out };
|
|
1460
|
+
}
|
|
1461
|
+
if (DELETE_TOOLS.has(tool)) {
|
|
1462
|
+
const path = pick(input, 'TargetFile', 'target_file', 'path', 'file_path', 'Path', 'FilePath', 'AbsolutePath') ?? '';
|
|
1463
|
+
return { kind: 'delete', tool, title: desc ?? 'Delete ' + path, output: out };
|
|
1464
|
+
}
|
|
1465
|
+
const title = desc ?? (tool !== '' ? tool : 'agy_tool');
|
|
1466
|
+
return { kind: 'generic', tool, title, output: out, raw: parsedArgsRaw(block) };
|
|
1467
|
+
}
|
|
1468
|
+
// ---- Helpers --------------------------------------------------------------
|
|
1469
|
+
function relativize(p, cwd, home) {
|
|
1470
|
+
if (cwd && p.startsWith(cwd)) {
|
|
1471
|
+
const rel = p.slice(cwd.length).replace(/^[/\\]+/, '');
|
|
1472
|
+
return rel !== '' ? rel : p;
|
|
1473
|
+
}
|
|
1474
|
+
if (home && p.startsWith(home))
|
|
1475
|
+
return '~' + p.slice(home.length);
|
|
1476
|
+
return p;
|
|
1477
|
+
}
|
|
1478
|
+
function clip(s, max) {
|
|
1479
|
+
return s.length > max ? s.slice(0, max) + '\n… (+' + (s.length - max) + ' chars)' : s;
|
|
1480
|
+
}
|
|
1481
|
+
function trimTrailingBlankLines(s) {
|
|
1482
|
+
return s.replace(/[\t ]*\r?\n[\t ]*(\r?\n[\t ]*)*$/, '\n');
|
|
1483
|
+
}
|
|
1484
|
+
function previewLine(model, cwd, home) {
|
|
1485
|
+
if (model.kind === 'diff') {
|
|
1486
|
+
const raw = model.path ?? 'file';
|
|
1487
|
+
const rel = relativize(raw, cwd, home);
|
|
1488
|
+
const display = rel.length > 40 ? '…' + rel.slice(-40) : rel;
|
|
1489
|
+
return model.oldText != null ? `± ${display}` : `+ ${display}`;
|
|
1490
|
+
}
|
|
1491
|
+
const out = model.output;
|
|
1492
|
+
if (out !== undefined && out !== '') {
|
|
1493
|
+
const first = out.split('\n').find((l) => l.trim() !== '');
|
|
1494
|
+
if (first !== undefined && first.trim() !== '')
|
|
1495
|
+
return first.trim().slice(0, 80);
|
|
1496
|
+
}
|
|
1497
|
+
return undefined;
|
|
1498
|
+
}
|
|
1499
|
+
function cls(...names) {
|
|
1500
|
+
return names.filter((n) => typeof n === 'string' && n !== '').join(' ');
|
|
1501
|
+
}
|
|
1502
|
+
/**
|
|
1503
|
+
* Locale lookup with an English fallback: the mirror card renders through
|
|
1504
|
+
* host-provided `t` when the registration declares the namespace, but tests
|
|
1505
|
+
* and hosts without the seat call it bare. The old literals were mixed
|
|
1506
|
+
* 中文 and English as BOTH fallback and primary, so non-Chinese users saw
|
|
1507
|
+
* Chinese buttons on mirror cards.
|
|
1508
|
+
*/
|
|
1509
|
+
function tr(t, key, fallback, params) {
|
|
1510
|
+
if (typeof t === 'function') {
|
|
1511
|
+
try {
|
|
1512
|
+
const v = t(key, params);
|
|
1513
|
+
if (typeof v === 'string' && v !== '' && v !== key)
|
|
1514
|
+
return v;
|
|
1515
|
+
}
|
|
1516
|
+
catch {
|
|
1517
|
+
// unbound namespace — fall through
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
if (params !== undefined && params.count !== undefined && fallback.includes('{count}')) {
|
|
1521
|
+
return fallback.replace('{count}', String(params.count));
|
|
1522
|
+
}
|
|
1523
|
+
return fallback;
|
|
1524
|
+
}
|
|
1525
|
+
function displayTitle(model, t) {
|
|
1526
|
+
switch (model.kind) {
|
|
1527
|
+
case 'terminal':
|
|
1528
|
+
return tr(t, 'tv.command', 'Command');
|
|
1529
|
+
case 'diff':
|
|
1530
|
+
return (model.tool === 'write_to_file' || model.tool === 'write_file' || model.tool === 'create_file') ? tr(t, 'tv.write', 'Write') : tr(t, 'tv.edit', 'Edit');
|
|
1531
|
+
case 'read':
|
|
1532
|
+
return tr(t, 'tv.read', 'Read');
|
|
1533
|
+
case 'search':
|
|
1534
|
+
return (model.tool === 'search_web' || model.tool === 'web_search') ? tr(t, 'tv.webSearch', 'Web Search') : tr(t, 'tv.search', 'Search');
|
|
1535
|
+
case 'list':
|
|
1536
|
+
return tr(t, 'tv.list', 'List');
|
|
1537
|
+
case 'delete':
|
|
1538
|
+
return tr(t, 'tv.delete', 'Delete');
|
|
1539
|
+
case 'question':
|
|
1540
|
+
return tr(t, 'tv.question', 'Question');
|
|
1541
|
+
case 'image':
|
|
1542
|
+
return tr(t, 'tv.image', 'Image');
|
|
1543
|
+
default:
|
|
1544
|
+
return model.tool ? model.tool.replace(/_/g, ' ') : tr(t, 'tv.tool', 'Tool');
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
function displaySummary(model, cwd, home) {
|
|
1548
|
+
if (model.kind === 'terminal') {
|
|
1549
|
+
return model.command ?? model.title;
|
|
1550
|
+
}
|
|
1551
|
+
if (model.kind === 'diff') {
|
|
1552
|
+
const rawPath = model.path ?? 'file';
|
|
1553
|
+
return relativize(rawPath, cwd, home);
|
|
1554
|
+
}
|
|
1555
|
+
if (model.kind === 'read') {
|
|
1556
|
+
const rawPath = model.path ?? '';
|
|
1557
|
+
return rawPath !== '' ? relativize(rawPath, cwd, home) : model.title;
|
|
1558
|
+
}
|
|
1559
|
+
if (model.kind === 'search') {
|
|
1560
|
+
return model.title.replace(/^(Web search|Search)\s*/i, '') || model.title;
|
|
1561
|
+
}
|
|
1562
|
+
if (model.kind === 'list') {
|
|
1563
|
+
return model.title.replace(/^List\s*(directory\s*)?/i, '') || model.title;
|
|
1564
|
+
}
|
|
1565
|
+
if (model.kind === 'question') {
|
|
1566
|
+
return model.title;
|
|
1567
|
+
}
|
|
1568
|
+
if (model.kind === 'image') {
|
|
1569
|
+
return model.prompt || model.title;
|
|
1570
|
+
}
|
|
1571
|
+
return model.title;
|
|
1572
|
+
}
|
|
1573
|
+
function diffStat(model) {
|
|
1574
|
+
if (model.kind !== 'diff')
|
|
1575
|
+
return null;
|
|
1576
|
+
const oldText = model.oldText ?? '';
|
|
1577
|
+
const newText = model.newText ?? '';
|
|
1578
|
+
const oldLines = oldText === '' ? 0 : oldText.split('\n').length;
|
|
1579
|
+
const newLines = newText === '' ? 0 : newText.split('\n').length;
|
|
1580
|
+
if (oldLines === 0 && newLines === 0)
|
|
1581
|
+
return null;
|
|
1582
|
+
if (oldLines === 0)
|
|
1583
|
+
return `+${newLines}`;
|
|
1584
|
+
return `+${newLines} -${oldLines}`;
|
|
1585
|
+
}
|
|
1586
|
+
// ---- Copy button component ------------------------------------------------
|
|
1587
|
+
function CopyButton({ text, labels, t }) {
|
|
1588
|
+
const [copied, setCopied] = react().useState(false);
|
|
1589
|
+
const copyLabel = labels?.[0] ?? tr(t, 'code.copy', 'Copy');
|
|
1590
|
+
const copiedLabel = labels?.[1] ?? tr(t, 'code.copied', 'Copied');
|
|
1591
|
+
const onCopy = (e) => {
|
|
1592
|
+
e.stopPropagation();
|
|
1593
|
+
if (typeof navigator !== 'undefined' && navigator.clipboard) {
|
|
1594
|
+
navigator.clipboard.writeText(text).then(() => {
|
|
1595
|
+
setCopied(true);
|
|
1596
|
+
setTimeout(() => setCopied(false), 1500);
|
|
1597
|
+
}).catch(() => {
|
|
1598
|
+
// Clipboard unavailable (insecure context): show the failure on
|
|
1599
|
+
// the button instead of silently doing nothing.
|
|
1600
|
+
setCopied(false);
|
|
1601
|
+
});
|
|
1602
|
+
}
|
|
1603
|
+
};
|
|
1604
|
+
return hx('button', {
|
|
1605
|
+
type: 'button',
|
|
1606
|
+
className: 'agy-tv-copy-btn',
|
|
1607
|
+
onClick: onCopy,
|
|
1608
|
+
title: copied ? copiedLabel : copyLabel,
|
|
1609
|
+
}, copied ? IconCheckOutline14(11) : IconCopyOutline16(11), hx('span', null, copied ? copiedLabel : copyLabel));
|
|
1610
|
+
}
|
|
1611
|
+
// ---- Diff body renderer ---------------------------------------------------
|
|
1612
|
+
/** Per-side render cap: a 5000-line write_to_file used to build 10k DOM rows
|
|
1613
|
+
* inside a max-height:280px pane (99% of them invisible). */
|
|
1614
|
+
const DIFF_MAX_LINES = 400;
|
|
1615
|
+
function DiffBody({ model, t }) {
|
|
1616
|
+
const h = hx;
|
|
1617
|
+
const oldText = model.oldText ?? '';
|
|
1618
|
+
const newText = model.newText ?? '';
|
|
1619
|
+
const rows = [];
|
|
1620
|
+
const moreRow = (count, key) => h('div', { key, className: 'agy-tv-diff-line', style: { color: 'var(--dsw-alias-label-tertiary)' } }, tr(t, 'tv.diffMore', '… {count} more lines not rendered', { count }));
|
|
1621
|
+
if (oldText !== '') {
|
|
1622
|
+
const lines = oldText.split('\n');
|
|
1623
|
+
const shown = Math.min(lines.length, DIFF_MAX_LINES);
|
|
1624
|
+
for (let i = 0; i < shown; i++) {
|
|
1625
|
+
const line = lines[i];
|
|
1626
|
+
rows.push(h('div', { key: 'del-' + i, className: 'agy-tv-diff-line agy-tv-diff-del' }, h('span', { className: 'agy-tv-diff-marker' }, '-'), h('span', { style: { flex: 1 } }, line === '' ? ' ' : line)));
|
|
1627
|
+
}
|
|
1628
|
+
if (lines.length > shown)
|
|
1629
|
+
rows.push(moreRow(lines.length - shown, 'del-more'));
|
|
1630
|
+
}
|
|
1631
|
+
if (newText !== '') {
|
|
1632
|
+
const lines = newText.split('\n');
|
|
1633
|
+
const shown = Math.min(lines.length, DIFF_MAX_LINES);
|
|
1634
|
+
for (let i = 0; i < shown; i++) {
|
|
1635
|
+
const line = lines[i];
|
|
1636
|
+
rows.push(h('div', { key: 'add-' + i, className: 'agy-tv-diff-line agy-tv-diff-add' }, h('span', { className: 'agy-tv-diff-marker' }, '+'), h('span', { style: { flex: 1 } }, line === '' ? ' ' : line)));
|
|
1637
|
+
}
|
|
1638
|
+
if (lines.length > shown)
|
|
1639
|
+
rows.push(moreRow(lines.length - shown, 'add-more'));
|
|
1640
|
+
}
|
|
1641
|
+
if (rows.length === 0) {
|
|
1642
|
+
rows.push(h('div', { key: 'empty', className: 'agy-tv-diff-line', style: { color: 'var(--dsw-alias-label-tertiary)' } }, tr(t, 'tv.noChanges', '(no textual changes)')));
|
|
1643
|
+
}
|
|
1644
|
+
return h('div', { className: 'agy-tv-diff-list' }, ...rows);
|
|
1645
|
+
}
|
|
1646
|
+
// ---- Question card renderer -----------------------------------------------
|
|
1647
|
+
function QuestionCardBody({ model }) {
|
|
1648
|
+
const h = hx;
|
|
1649
|
+
const questions = model.questions ?? [];
|
|
1650
|
+
const [selectedMap, setSelectedMap] = react().useState({});
|
|
1651
|
+
const [inserted, setInserted] = react().useState(false);
|
|
1652
|
+
const toggleOption = (qIdx, opt, isMulti) => {
|
|
1653
|
+
setSelectedMap((prev) => {
|
|
1654
|
+
const next = { ...prev };
|
|
1655
|
+
const current = new Set(next[qIdx] ?? []);
|
|
1656
|
+
if (isMulti) {
|
|
1657
|
+
if (current.has(opt))
|
|
1658
|
+
current.delete(opt);
|
|
1659
|
+
else
|
|
1660
|
+
current.add(opt);
|
|
1661
|
+
}
|
|
1662
|
+
else {
|
|
1663
|
+
current.clear();
|
|
1664
|
+
current.add(opt);
|
|
1665
|
+
}
|
|
1666
|
+
next[qIdx] = current;
|
|
1667
|
+
return next;
|
|
1668
|
+
});
|
|
1669
|
+
};
|
|
1670
|
+
const onInsert = () => {
|
|
1671
|
+
const parts = [];
|
|
1672
|
+
for (let i = 0; i < questions.length; i++) {
|
|
1673
|
+
const sel = Array.from(selectedMap[i] ?? []);
|
|
1674
|
+
if (sel.length > 0) {
|
|
1675
|
+
parts.push(sel.join('; '));
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
if (parts.length > 0) {
|
|
1679
|
+
const text = parts.join('\n');
|
|
1680
|
+
(0, decisions_ts_1.insertTextIntoChatComposer)(text);
|
|
1681
|
+
setInserted(true);
|
|
1682
|
+
setTimeout(() => setInserted(false), 1500);
|
|
1683
|
+
}
|
|
1684
|
+
};
|
|
1685
|
+
const qNodes = [];
|
|
1686
|
+
for (let i = 0; i < questions.length; i++) {
|
|
1687
|
+
const q = questions[i];
|
|
1688
|
+
if (!q)
|
|
1689
|
+
continue;
|
|
1690
|
+
const options = q.options ?? [];
|
|
1691
|
+
const isMulti = q.is_multi_select === true;
|
|
1692
|
+
const sel = selectedMap[i] ?? new Set();
|
|
1693
|
+
const optNodes = options.map((opt, oIdx) => {
|
|
1694
|
+
const isChecked = sel.has(opt);
|
|
1695
|
+
return h('button', {
|
|
1696
|
+
key: 'opt-' + oIdx,
|
|
1697
|
+
type: 'button',
|
|
1698
|
+
className: cls('agy-tv-q-opt', isChecked && 'selected'),
|
|
1699
|
+
onClick: (e) => {
|
|
1700
|
+
e.stopPropagation();
|
|
1701
|
+
toggleOption(i, opt, isMulti);
|
|
1702
|
+
},
|
|
1703
|
+
}, h('span', { className: cls('agy-tv-q-check', isMulti ? 'checkbox' : 'radio', isChecked && 'checked') }), h('span', { className: 'agy-tv-q-opt-text' }, opt));
|
|
1704
|
+
});
|
|
1705
|
+
qNodes.push(h('div', { key: 'q-' + i, className: 'agy-tv-q-item' }, h('div', { className: 'agy-tv-q-prompt' }, q.question || 'Please select:'), h('div', { className: 'agy-tv-q-opts' }, ...optNodes)));
|
|
1706
|
+
}
|
|
1707
|
+
const hasSelection = Object.values(selectedMap).some((s) => s.size > 0);
|
|
1708
|
+
return h('div', { className: 'agy-tv-question-wrap' }, ...qNodes, h('div', { className: 'agy-tv-q-footer' }, h('button', {
|
|
1709
|
+
type: 'button',
|
|
1710
|
+
className: cls('agy-tv-q-insert-btn', hasSelection && 'active'),
|
|
1711
|
+
disabled: !hasSelection,
|
|
1712
|
+
onClick: (e) => {
|
|
1713
|
+
e.stopPropagation();
|
|
1714
|
+
onInsert();
|
|
1715
|
+
},
|
|
1716
|
+
}, inserted ? '✓ 已填入对话框' : '💬 填入对话框 / Insert into Chat'), model.output ? h('div', { className: 'agy-tv-q-answered' }, `已记录: ${model.output}`) : null));
|
|
1717
|
+
}
|
|
1718
|
+
// ---- Image card renderer --------------------------------------------------
|
|
1719
|
+
function ImageCardBody({ model }) {
|
|
1720
|
+
const h = hx;
|
|
1721
|
+
const prompt = model.prompt || '';
|
|
1722
|
+
const name = model.imageName || 'image';
|
|
1723
|
+
const ratio = model.aspectRatio || '1:1';
|
|
1724
|
+
const path = model.imagePath || model.path || '';
|
|
1725
|
+
const mediaUrl = path ? (0, images_ts_1.buildMediaUrl)(path) : null;
|
|
1726
|
+
return h('div', { className: 'agy-tv-img-wrap' }, h('div', { className: 'agy-tv-img-meta' }, h('div', { className: 'agy-tv-img-prompt', title: prompt || name }, prompt ? `"${prompt}"` : name), h('div', { className: 'agy-tv-img-badges' }, h('span', { className: 'agy-tv-img-badge' }, ratio), ...(path ? [h('span', { key: 'path', className: 'agy-tv-img-badge', title: path }, path.split(/[/\\]/).pop())] : []))), mediaUrl ? h('div', {
|
|
1727
|
+
className: 'agy-tv-img-preview-box',
|
|
1728
|
+
onClick: (e) => {
|
|
1729
|
+
e.stopPropagation();
|
|
1730
|
+
(0, images_ts_1.openLightbox)(mediaUrl, prompt || name, path);
|
|
1731
|
+
},
|
|
1732
|
+
title: '点击放大预览 / Click to open lightbox'
|
|
1733
|
+
}, h('img', {
|
|
1734
|
+
className: 'agy-tv-img-thumb',
|
|
1735
|
+
src: mediaUrl,
|
|
1736
|
+
alt: prompt || name,
|
|
1737
|
+
loading: 'lazy',
|
|
1738
|
+
})) : null);
|
|
1739
|
+
}
|
|
1740
|
+
// ---- Card body renderer ---------------------------------------------------
|
|
1741
|
+
function CardBody({ model, openFile, cwd, home, inspect, t }) {
|
|
1742
|
+
const h = hx;
|
|
1743
|
+
const inspectNode = inspect !== undefined
|
|
1744
|
+
? h('div', { key: 'ins', style: { padding: '4px 8px 6px' } }, h('button', {
|
|
1745
|
+
type: 'button',
|
|
1746
|
+
className: 'agy-tv-inspect-btn',
|
|
1747
|
+
onClick: inspect,
|
|
1748
|
+
}, IconInspectOutline12(12), tr(t, 'code.inspect', 'Inspect')))
|
|
1749
|
+
: null;
|
|
1750
|
+
switch (model.kind) {
|
|
1751
|
+
case 'terminal': {
|
|
1752
|
+
const rawBody = model.output !== undefined && model.output !== '' ? trimTrailingBlankLines(model.output) : tr(t, 'tv.noOutput', '(no output)');
|
|
1753
|
+
const copyText = model.output !== undefined && model.output !== '' ? model.output : (model.command ?? '');
|
|
1754
|
+
return h('div', { className: 'agy-tv-card' }, h('div', { className: 'agy-tv-card-header' }, h('div', { className: 'agy-tv-card-header-left' }, h('span', { className: 'agy-tv-prompt-glyph' }, '❯'), ...(model.cwd ? [h('span', { key: 'cwd', className: 'agy-tv-card-cwd' }, model.cwd)] : []), h('span', { className: 'agy-tv-card-cmd', title: model.command }, model.command ?? '')), h(CopyButton, { text: copyText, t })), h('pre', { className: 'agy-tv-card-content' }, clip(rawBody, 16000)), inspectNode);
|
|
1755
|
+
}
|
|
1756
|
+
case 'diff': {
|
|
1757
|
+
const path = model.path ?? 'file';
|
|
1758
|
+
const pathLabel = relativize(path, cwd, home);
|
|
1759
|
+
const stat = diffStat(model);
|
|
1760
|
+
const copyText = model.newText ?? '';
|
|
1761
|
+
return h('div', { className: 'agy-tv-card' }, h('div', { className: 'agy-tv-card-header' }, h('div', { className: 'agy-tv-card-header-left' }, IconEditOutline16(13), h('span', { className: 'agy-tv-card-path' }, pathLabel), ...(stat ? [h('span', { key: 's', className: 'agy-tv-diff-stat' }, stat)] : [])), h(CopyButton, { text: copyText })), h(DiffBody, { model, openFile, cwd, home, t }), inspectNode);
|
|
1762
|
+
}
|
|
1763
|
+
case 'read': {
|
|
1764
|
+
const path = model.path ?? 'file';
|
|
1765
|
+
const pathLabel = relativize(path, cwd, home);
|
|
1766
|
+
const rawBody = model.output !== undefined && model.output !== '' ? trimTrailingBlankLines(model.output) : tr(t, 'tv.noOutput', '(no output)');
|
|
1767
|
+
return h('div', { className: 'agy-tv-card' }, h('div', { className: 'agy-tv-card-header' }, h('div', { className: 'agy-tv-card-header-left' }, IconBrowseOutline16(13), h('span', { className: 'agy-tv-card-path' }, pathLabel)), h(CopyButton, { text: model.output ?? '', t })), h('pre', { className: 'agy-tv-card-content' }, clip(rawBody, 16000)), inspectNode);
|
|
1768
|
+
}
|
|
1769
|
+
case 'search': {
|
|
1770
|
+
const rawBody = model.output !== undefined && model.output !== '' ? trimTrailingBlankLines(model.output) : tr(t, 'tv.noMatches', '(no matches)');
|
|
1771
|
+
return h('div', { className: 'agy-tv-card' }, h('div', { className: 'agy-tv-card-header' }, h('div', { className: 'agy-tv-card-header-left' }, IconSearchOutline16(13), h('span', { className: 'agy-tv-card-path' }, model.title)), h(CopyButton, { text: model.output ?? '', t })), h('pre', { className: 'agy-tv-card-content' }, clip(rawBody, 16000)), inspectNode);
|
|
1772
|
+
}
|
|
1773
|
+
case 'list': {
|
|
1774
|
+
const rawBody = model.output !== undefined && model.output !== '' ? trimTrailingBlankLines(model.output) : tr(t, 'tv.emptyDir', '(empty directory)');
|
|
1775
|
+
return h('div', { className: 'agy-tv-card' }, h('div', { className: 'agy-tv-card-header' }, h('div', { className: 'agy-tv-card-header-left' }, IconBrowseOutline16(13), h('span', { className: 'agy-tv-card-path' }, model.title)), h(CopyButton, { text: model.output ?? '', t })), h('pre', { className: 'agy-tv-card-content' }, clip(rawBody, 16000)), inspectNode);
|
|
1776
|
+
}
|
|
1777
|
+
case 'question': {
|
|
1778
|
+
return h('div', { className: 'agy-tv-card' }, h('div', { className: 'agy-tv-card-header' }, h('div', { className: 'agy-tv-card-header-left' }, IconQuestionOutline16(13), h('span', { className: 'agy-tv-card-path' }, model.title)), h(CopyButton, { text: model.output ?? (model.raw ?? '') })), h(QuestionCardBody, { model }), inspectNode);
|
|
1779
|
+
}
|
|
1780
|
+
case 'image': {
|
|
1781
|
+
return h('div', { className: 'agy-tv-card' }, h('div', { className: 'agy-tv-card-header' }, h('div', { className: 'agy-tv-card-header-left' }, IconImageOutline16(13), h('span', { className: 'agy-tv-card-path' }, model.imageName || 'Image')), h(CopyButton, { text: model.prompt || (model.raw ?? '') })), h(ImageCardBody, { model }), inspectNode);
|
|
1782
|
+
}
|
|
1783
|
+
default: {
|
|
1784
|
+
const copyText = model.output !== undefined && model.output !== '' ? model.output : (model.raw ?? '');
|
|
1785
|
+
const rawBody = model.output !== undefined && model.output !== ''
|
|
1786
|
+
? trimTrailingBlankLines(model.output)
|
|
1787
|
+
: (model.raw !== undefined && model.raw !== '' ? clip(model.raw, 4000) : tr(t, 'tv.noDetail', '(no detail)'));
|
|
1788
|
+
return h('div', { className: 'agy-tv-card' }, h('div', { className: 'agy-tv-card-header' }, h('div', { className: 'agy-tv-card-header-left' }, IconCodeOutline16(13), h('span', { className: 'agy-tv-card-path' }, model.tool || 'Tool')), h(CopyButton, { text: copyText, t })), h('pre', { className: 'agy-tv-card-content' }, clip(rawBody, 16000)), inspectNode);
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
// ---- Main ToolView Component ----------------------------------------------
|
|
1793
|
+
function AgyMirrorToolView(props) {
|
|
1794
|
+
const [open, setOpen] = useToggle(false);
|
|
1795
|
+
const h = hx;
|
|
1796
|
+
const { block, cwd, home, openFile, inspect, t } = props;
|
|
1797
|
+
const settle = isSettled(block);
|
|
1798
|
+
const err = isError(block);
|
|
1799
|
+
const model = mirrorCardModel(block, cwd, home);
|
|
1800
|
+
const state = !settle ? 'run' : err ? 'err' : 'ok';
|
|
1801
|
+
const title = displayTitle(model, t);
|
|
1802
|
+
const summary = displaySummary(model, cwd, home);
|
|
1803
|
+
const stat = diffStat(model);
|
|
1804
|
+
const preview = !open && model.kind !== 'diff' ? previewLine(model, cwd, home) : undefined;
|
|
1805
|
+
const hasFileLink = (model.kind === 'diff' || model.kind === 'read') && model.path && openFile !== undefined;
|
|
1806
|
+
const onFileClick = (e) => {
|
|
1807
|
+
e.stopPropagation();
|
|
1808
|
+
if (model.path && openFile) {
|
|
1809
|
+
if (model.location?.line !== undefined)
|
|
1810
|
+
openFile(model.path, { line: model.location.line });
|
|
1811
|
+
else
|
|
1812
|
+
openFile(model.path);
|
|
1813
|
+
}
|
|
1814
|
+
};
|
|
1815
|
+
const leading = open
|
|
1816
|
+
? h('span', { className: 'agy-tv-chevron-open' }, IconChevronDownOutline14(14))
|
|
1817
|
+
: h('span', { className: 'agy-tv-leading' }, h('span', { className: 'agy-tv-icon-idle' }, toolIconFor(model.kind, 14)), h('span', { className: 'agy-tv-chevron-hover' }, IconChevronDownOutline14(14)));
|
|
1818
|
+
const summaryNode = hasFileLink
|
|
1819
|
+
? h('button', {
|
|
1820
|
+
type: 'button',
|
|
1821
|
+
className: 'agy-tv-file-link',
|
|
1822
|
+
onClick: onFileClick,
|
|
1823
|
+
title: model.path,
|
|
1824
|
+
}, summary)
|
|
1825
|
+
: h('span', {
|
|
1826
|
+
className: cls('agy-tv-summary', err && 'agy-tv-summary-err'),
|
|
1827
|
+
title: summary,
|
|
1828
|
+
}, summary);
|
|
1829
|
+
const header = h('div', {
|
|
1830
|
+
className: 'agy-tv-row',
|
|
1831
|
+
role: 'button',
|
|
1832
|
+
tabIndex: 0,
|
|
1833
|
+
'aria-expanded': open,
|
|
1834
|
+
onClick: (e) => {
|
|
1835
|
+
e.stopPropagation();
|
|
1836
|
+
setOpen();
|
|
1837
|
+
},
|
|
1838
|
+
onKeyDown: (e) => {
|
|
1839
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
1840
|
+
e.preventDefault();
|
|
1841
|
+
setOpen();
|
|
1842
|
+
}
|
|
1843
|
+
},
|
|
1844
|
+
title: inspect !== undefined
|
|
1845
|
+
? (open ? tr(t, 'tv.hint.collapseInspect', 'Click to collapse · right-click to inspect') : tr(t, 'tv.hint.expandInspect', 'Click to expand · right-click to inspect'))
|
|
1846
|
+
: (open ? tr(t, 'tv.hint.collapse', 'Click to collapse') : tr(t, 'tv.hint.expand', 'Click to expand')),
|
|
1847
|
+
onContextMenu: inspect !== undefined ? (e) => { e.preventDefault(); inspect(); } : undefined,
|
|
1848
|
+
}, leading, h('span', { className: 'agy-tv-title' }, title), h('span', { className: 'agy-tv-sep', 'aria-hidden': true }), summaryNode, ...(stat !== null ? [h('span', { key: 'stat', className: 'agy-tv-diff-stat' }, stat)] : []), ...(preview !== undefined ? [h('span', { key: 'prev', className: 'agy-tv-preview', title: preview }, preview)] : []));
|
|
1849
|
+
return h('div', {
|
|
1850
|
+
className: cls('agy-tv-root', open && 'agy-tv-open'),
|
|
1851
|
+
'data-state': state,
|
|
1852
|
+
'data-kind': model.kind,
|
|
1853
|
+
}, header, ...(open ? [h('div', { key: 'wrap', className: 'agy-tv-body-wrap' }, h(CardBody, { model, openFile, cwd, home, inspect, t }))] : []));
|
|
1854
|
+
}
|
|
1855
|
+
// ---- Register into DSH slots ----------------------------------------------
|
|
1856
|
+
/** Extract the agy mirror cursor (and tool name) from a run_code program. */
|
|
1857
|
+
function parseAgyMirrorFromCode(argsRaw) {
|
|
1858
|
+
try {
|
|
1859
|
+
const parsed = JSON.parse(argsRaw);
|
|
1860
|
+
const code = typeof parsed?.code === 'string' ? parsed.code : '';
|
|
1861
|
+
const m = /tools\['agy_tool'\]\((\{.*?"step":\d+\})\)/.exec(code);
|
|
1862
|
+
if (!m)
|
|
1863
|
+
return null;
|
|
1864
|
+
const v = JSON.parse(m[1]);
|
|
1865
|
+
if (typeof v.run !== 'string' || typeof v.step !== 'number')
|
|
1866
|
+
return null;
|
|
1867
|
+
const tm = /replay recorded agy tool step \d+ \(([^)]+)\)/.exec(code);
|
|
1868
|
+
return { run: v.run, step: v.step, tool: tm?.[1] };
|
|
1869
|
+
}
|
|
1870
|
+
catch { /* not a mirror program */ }
|
|
1871
|
+
return null;
|
|
1872
|
+
}
|
|
1873
|
+
/** Project a run_code program into the fields the code card renders. */
|
|
1874
|
+
function runCodeModel(block) {
|
|
1875
|
+
const raw = block !== undefined ? parsedArgsRaw(block) : '{}';
|
|
1876
|
+
let code = raw;
|
|
1877
|
+
let description;
|
|
1878
|
+
try {
|
|
1879
|
+
const parsed = JSON.parse(raw);
|
|
1880
|
+
if (typeof parsed?.code === 'string')
|
|
1881
|
+
code = parsed.code;
|
|
1882
|
+
if (typeof parsed?.description === 'string' && parsed.description !== '')
|
|
1883
|
+
description = parsed.description;
|
|
1884
|
+
}
|
|
1885
|
+
catch { /* raw args are not JSON — show them verbatim */ }
|
|
1886
|
+
const firstLine = code.split('\n').find((line) => line.trim() !== '');
|
|
1887
|
+
const title = description
|
|
1888
|
+
?? (firstLine !== undefined && firstLine.trim() !== '' ? firstLine.trim().slice(0, 120) : 'run_code');
|
|
1889
|
+
return { code, title };
|
|
1890
|
+
}
|
|
1891
|
+
/**
|
|
1892
|
+
* Ordinary run_code toolview.
|
|
1893
|
+
*
|
|
1894
|
+
* The keyed `tool.call.toolview` registration for `run_code` REPLACES the
|
|
1895
|
+
* host's built-in code row rather than adding to it, so this branch owns the
|
|
1896
|
+
* icon, title, summary and expandable body for every non-mirror program. It
|
|
1897
|
+
* reuses the same styled vocabulary as the mirror card (`agy-tv-row` and
|
|
1898
|
+
* friends) so an ordinary Code Mode call reads exactly like a native tool row.
|
|
1899
|
+
*/
|
|
1900
|
+
function AgyCodeToolView(props) {
|
|
1901
|
+
const [open, setOpen] = useToggle(false);
|
|
1902
|
+
const h = hx;
|
|
1903
|
+
const { block, inspect, t } = props ?? {};
|
|
1904
|
+
const model = runCodeModel(block);
|
|
1905
|
+
const settle = block !== undefined && isSettled(block);
|
|
1906
|
+
const err = block !== undefined && isError(block);
|
|
1907
|
+
const state = !settle ? 'run' : err ? 'err' : 'ok';
|
|
1908
|
+
const translate = (key) => (typeof t === 'function' ? t(key) : key);
|
|
1909
|
+
const leading = open
|
|
1910
|
+
? h('span', { className: 'agy-tv-chevron-open' }, IconChevronDownOutline14(14))
|
|
1911
|
+
: h('span', { className: 'agy-tv-leading' }, h('span', { className: 'agy-tv-icon-idle' }, IconCodeOutline16(14)), h('span', { className: 'agy-tv-chevron-hover' }, IconChevronDownOutline14(14)));
|
|
1912
|
+
const header = h('div', {
|
|
1913
|
+
className: 'agy-tv-row',
|
|
1914
|
+
role: 'button',
|
|
1915
|
+
tabIndex: 0,
|
|
1916
|
+
'aria-expanded': open,
|
|
1917
|
+
onClick: (e) => {
|
|
1918
|
+
e.stopPropagation();
|
|
1919
|
+
setOpen();
|
|
1920
|
+
},
|
|
1921
|
+
onKeyDown: (e) => {
|
|
1922
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
1923
|
+
e.preventDefault();
|
|
1924
|
+
setOpen();
|
|
1925
|
+
}
|
|
1926
|
+
},
|
|
1927
|
+
title: inspect !== undefined
|
|
1928
|
+
? (open ? tr(t, 'tv.hint.collapseInspect', 'Click to collapse · right-click to inspect') : tr(t, 'tv.hint.expandInspect', 'Click to expand · right-click to inspect'))
|
|
1929
|
+
: (open ? tr(t, 'tv.hint.collapse', 'Click to collapse') : tr(t, 'tv.hint.expand', 'Click to expand')),
|
|
1930
|
+
onContextMenu: inspect !== undefined ? (e) => { e.preventDefault(); inspect(); } : undefined,
|
|
1931
|
+
}, leading, h('span', { className: 'agy-tv-title' }, translate('code.title')), h('span', { className: 'agy-tv-sep', 'aria-hidden': true }), h('span', { className: cls('agy-tv-summary', err && 'agy-tv-summary-err'), title: model.title }, model.title));
|
|
1932
|
+
const inspectNode = inspect !== undefined
|
|
1933
|
+
? h('div', { key: 'ins', style: { padding: '4px 8px 6px' } }, h('button', { type: 'button', className: 'agy-tv-inspect-btn', onClick: inspect }, IconInspectOutline12(12), translate('code.inspect')))
|
|
1934
|
+
: null;
|
|
1935
|
+
const body = h('div', { className: 'agy-tv-card' }, h('div', { className: 'agy-tv-card-header' }, h('div', { className: 'agy-tv-card-header-left' }, IconCodeOutline16(13), h('span', { className: 'agy-tv-card-path' }, model.title)), h(CopyButton, { text: model.code, labels: [translate('code.copy'), translate('code.copied')] })), h('pre', { className: 'agy-tv-card-content' }, clip(model.code, 16_000)), inspectNode);
|
|
1936
|
+
return h('div', {
|
|
1937
|
+
className: cls('agy-tv-root', open && 'agy-tv-open'),
|
|
1938
|
+
'data-state': state,
|
|
1939
|
+
'data-kind': 'code',
|
|
1940
|
+
}, header, ...(open ? [h('div', { key: 'wrap', className: 'agy-tv-body-wrap' }, body)] : []));
|
|
1941
|
+
}
|
|
1942
|
+
/**
|
|
1943
|
+
* run_code toolview: when the program is an agy mirror wrapper, render the
|
|
1944
|
+
* native Antigravity card instead of a raw code row; every other run_code
|
|
1945
|
+
* program renders through {@link AgyCodeToolView}.
|
|
1946
|
+
*/
|
|
1947
|
+
function AgyRunCodeToolView(props) {
|
|
1948
|
+
const typed = props;
|
|
1949
|
+
const block = typed?.block;
|
|
1950
|
+
const raw = block !== undefined ? parsedArgsRaw(block) : '{}';
|
|
1951
|
+
const mirror = parseAgyMirrorFromCode(raw);
|
|
1952
|
+
if (mirror === null)
|
|
1953
|
+
return AgyCodeToolView(typed);
|
|
1954
|
+
const synthetic = {
|
|
1955
|
+
...block,
|
|
1956
|
+
call: {
|
|
1957
|
+
...(block?.call ?? {}),
|
|
1958
|
+
name: 'agy_tool',
|
|
1959
|
+
argsRaw: JSON.stringify({
|
|
1960
|
+
run: mirror.run,
|
|
1961
|
+
step: mirror.step,
|
|
1962
|
+
...(mirror.tool !== undefined ? { tool: mirror.tool } : {}),
|
|
1963
|
+
}),
|
|
1964
|
+
},
|
|
1965
|
+
};
|
|
1966
|
+
return AgyMirrorToolView({ block: synthetic, t: typed?.t });
|
|
1967
|
+
}
|
|
1968
|
+
function installAgyToolView(ctx) {
|
|
1969
|
+
ctx.slots.inject('tool.call.toolview', () => {
|
|
1970
|
+
const d1 = ctx.slots.register(
|
|
1971
|
+
// locale: NS seats `t` so mirror cards localize titles/buttons like
|
|
1972
|
+
// the run_code view does (they used to hard-code 中文 + English).
|
|
1973
|
+
{ name: 'tool.call.toolview', key: 'agy_tool', id: 'agy-tool-view', label: 'Antigravity tool', locale: locales_ts_1.NS }, AgyMirrorToolView);
|
|
1974
|
+
// Code Mode wraps the mirror in run_code. This keyed registration REPLACES
|
|
1975
|
+
// the host's built-in code row (a keyed slot is a takeover, not an
|
|
1976
|
+
// addition), so AgyRunCodeToolView also owns the presentation of ordinary
|
|
1977
|
+
// non-mirror programs — hence the locale namespace for its title/copy labels.
|
|
1978
|
+
const d2 = ctx.slots.register({ name: 'tool.call.toolview', key: 'run_code', id: 'agy-run-code-view', label: 'Antigravity (code mode)', locale: locales_ts_1.NS }, AgyRunCodeToolView);
|
|
1979
|
+
return () => { d2(); d1(); };
|
|
1980
|
+
});
|
|
1981
|
+
}
|
|
1982
|
+
// ---- Official CSS Styles --------------------------------------------------
|
|
1983
|
+
const ROW_CSS = `
|
|
1984
|
+
/* Container */
|
|
1985
|
+
.agy-tv-root {
|
|
1986
|
+
display: flex;
|
|
1987
|
+
flex-direction: column;
|
|
1988
|
+
width: 100%;
|
|
1989
|
+
min-width: 0;
|
|
1990
|
+
position: relative;
|
|
1991
|
+
font-family: var(--dsw-font-family, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC", "Microsoft YaHei", sans-serif);
|
|
1992
|
+
margin: 2px 0;
|
|
1993
|
+
}
|
|
1994
|
+
|
|
1995
|
+
/* Collapsed Header Row */
|
|
1996
|
+
.agy-tv-row {
|
|
1997
|
+
display: flex;
|
|
1998
|
+
align-items: center;
|
|
1999
|
+
height: calc(24px + var(--dsh-content-font-delta, 0px));
|
|
2000
|
+
min-width: 0;
|
|
2001
|
+
position: relative;
|
|
2002
|
+
overflow: hidden;
|
|
2003
|
+
cursor: pointer;
|
|
2004
|
+
user-select: none;
|
|
2005
|
+
background: transparent;
|
|
2006
|
+
border: none;
|
|
2007
|
+
padding: 0;
|
|
2008
|
+
width: 100%;
|
|
2009
|
+
text-align: left;
|
|
2010
|
+
color: var(--dsw-alias-label-primary, #0f172a);
|
|
2011
|
+
}
|
|
2012
|
+
|
|
2013
|
+
/* Running sweep shimmer animation */
|
|
2014
|
+
.agy-tv-root[data-state="run"] .agy-tv-row:after {
|
|
2015
|
+
content: "";
|
|
2016
|
+
position: absolute;
|
|
2017
|
+
top: 0;
|
|
2018
|
+
bottom: 0;
|
|
2019
|
+
left: 0;
|
|
2020
|
+
width: 300px;
|
|
2021
|
+
background: linear-gradient(90deg, transparent 0%, color-mix(in srgb, var(--dsw-alias-bg-base, #ffffff) 60%, transparent) 55%, transparent 100%);
|
|
2022
|
+
pointer-events: none;
|
|
2023
|
+
animation: 2.6s ease-out infinite agy-tv-sweep;
|
|
2024
|
+
}
|
|
2025
|
+
@keyframes agy-tv-sweep {
|
|
2026
|
+
0% { left: -300px; }
|
|
2027
|
+
90%, 100% { left: 100%; }
|
|
2028
|
+
}
|
|
2029
|
+
|
|
2030
|
+
/* Leading Icon with Hover-to-Arrow transition */
|
|
2031
|
+
.agy-tv-leading {
|
|
2032
|
+
position: relative;
|
|
2033
|
+
flex: none;
|
|
2034
|
+
width: calc(16px + var(--dsh-content-font-delta, 0px));
|
|
2035
|
+
height: calc(16px + var(--dsh-content-font-delta, 0px));
|
|
2036
|
+
display: inline-flex;
|
|
2037
|
+
align-items: center;
|
|
2038
|
+
justify-content: center;
|
|
2039
|
+
margin-right: 6px;
|
|
2040
|
+
color: var(--dsw-alias-label-tertiary, #64748b);
|
|
2041
|
+
}
|
|
2042
|
+
.agy-tv-leading svg {
|
|
2043
|
+
width: calc(14px + var(--dsh-content-font-delta, 0px));
|
|
2044
|
+
height: calc(14px + var(--dsh-content-font-delta, 0px));
|
|
2045
|
+
}
|
|
2046
|
+
.agy-tv-icon-idle {
|
|
2047
|
+
display: inline-flex;
|
|
2048
|
+
opacity: 1;
|
|
2049
|
+
transition: opacity 0.1s ease;
|
|
2050
|
+
}
|
|
2051
|
+
.agy-tv-chevron-hover {
|
|
2052
|
+
position: absolute;
|
|
2053
|
+
inset: 0;
|
|
2054
|
+
margin: auto;
|
|
2055
|
+
opacity: 0;
|
|
2056
|
+
transition: opacity 0.1s ease;
|
|
2057
|
+
display: inline-flex;
|
|
2058
|
+
align-items: center;
|
|
2059
|
+
justify-content: center;
|
|
2060
|
+
color: var(--dsw-alias-label-secondary, #334155);
|
|
2061
|
+
}
|
|
2062
|
+
.agy-tv-row:hover .agy-tv-icon-idle {
|
|
2063
|
+
opacity: 0;
|
|
2064
|
+
}
|
|
2065
|
+
.agy-tv-row:hover .agy-tv-chevron-hover {
|
|
2066
|
+
opacity: 1;
|
|
2067
|
+
}
|
|
2068
|
+
.agy-tv-chevron-open {
|
|
2069
|
+
position: relative;
|
|
2070
|
+
flex: none;
|
|
2071
|
+
width: calc(16px + var(--dsh-content-font-delta, 0px));
|
|
2072
|
+
height: calc(16px + var(--dsh-content-font-delta, 0px));
|
|
2073
|
+
display: inline-flex;
|
|
2074
|
+
align-items: center;
|
|
2075
|
+
justify-content: center;
|
|
2076
|
+
margin-right: 6px;
|
|
2077
|
+
color: var(--dsw-alias-label-secondary, #334155);
|
|
2078
|
+
}
|
|
2079
|
+
.agy-tv-chevron-open svg {
|
|
2080
|
+
width: calc(14px + var(--dsh-content-font-delta, 0px));
|
|
2081
|
+
height: calc(14px + var(--dsh-content-font-delta, 0px));
|
|
2082
|
+
}
|
|
2083
|
+
|
|
2084
|
+
/* Title */
|
|
2085
|
+
.agy-tv-title {
|
|
2086
|
+
flex: none;
|
|
2087
|
+
font-size: var(--dsh-content-font-size-secondary, 13px);
|
|
2088
|
+
line-height: calc(24px + var(--dsh-content-font-delta, 0px));
|
|
2089
|
+
color: var(--dsw-alias-label-secondary, #334155);
|
|
2090
|
+
font-weight: 400;
|
|
2091
|
+
}
|
|
2092
|
+
|
|
2093
|
+
/* Dot separator */
|
|
2094
|
+
.agy-tv-sep {
|
|
2095
|
+
background: var(--dsw-alias-label-caption, #94a3b8);
|
|
2096
|
+
border-radius: 1px;
|
|
2097
|
+
flex: none;
|
|
2098
|
+
width: 2px;
|
|
2099
|
+
height: 2px;
|
|
2100
|
+
margin: 0 8px;
|
|
2101
|
+
}
|
|
2102
|
+
|
|
2103
|
+
/* Summary */
|
|
2104
|
+
.agy-tv-summary {
|
|
2105
|
+
text-overflow: ellipsis;
|
|
2106
|
+
white-space: nowrap;
|
|
2107
|
+
min-width: 0;
|
|
2108
|
+
font-size: var(--dsh-content-font-size-secondary, 13px);
|
|
2109
|
+
line-height: calc(24px + var(--dsh-content-font-delta, 0px));
|
|
2110
|
+
color: var(--dsw-alias-label-tertiary, #64748b);
|
|
2111
|
+
flex: auto;
|
|
2112
|
+
overflow: hidden;
|
|
2113
|
+
}
|
|
2114
|
+
.agy-tv-summary-err {
|
|
2115
|
+
color: var(--dsw-alias-state-error-primary, #dc2626);
|
|
2116
|
+
}
|
|
2117
|
+
|
|
2118
|
+
/* File link button */
|
|
2119
|
+
.agy-tv-file-link {
|
|
2120
|
+
text-overflow: ellipsis;
|
|
2121
|
+
white-space: nowrap;
|
|
2122
|
+
min-width: 0;
|
|
2123
|
+
font: inherit;
|
|
2124
|
+
text-align: left;
|
|
2125
|
+
font-size: var(--dsh-content-font-size-secondary, 13px);
|
|
2126
|
+
line-height: calc(24px + var(--dsh-content-font-delta, 0px));
|
|
2127
|
+
color: var(--dsw-alias-label-secondary, #334155);
|
|
2128
|
+
text-decoration: underline dotted;
|
|
2129
|
+
text-decoration-color: var(--dsw-alias-label-tertiary, #64748b);
|
|
2130
|
+
text-underline-offset: 3px;
|
|
2131
|
+
cursor: pointer;
|
|
2132
|
+
background: none;
|
|
2133
|
+
border: none;
|
|
2134
|
+
flex: 0 auto;
|
|
2135
|
+
margin: 0;
|
|
2136
|
+
padding: 0;
|
|
2137
|
+
text-decoration-thickness: 1px;
|
|
2138
|
+
overflow: hidden;
|
|
2139
|
+
}
|
|
2140
|
+
.agy-tv-file-link:hover {
|
|
2141
|
+
color: var(--dsw-alias-label-primary, #0f172a);
|
|
2142
|
+
text-decoration-color: currentColor;
|
|
2143
|
+
}
|
|
2144
|
+
|
|
2145
|
+
/* Diff stat badge (+10 -2) */
|
|
2146
|
+
.agy-tv-diff-stat {
|
|
2147
|
+
font-family: var(--ds-font-family-code, ui-monospace, monospace);
|
|
2148
|
+
font-size: calc(var(--dsh-content-font-size-secondary, 13px) - 2px);
|
|
2149
|
+
color: var(--dsw-alias-label-caption, #94a3b8);
|
|
2150
|
+
margin-left: 10px;
|
|
2151
|
+
flex: none;
|
|
2152
|
+
}
|
|
2153
|
+
|
|
2154
|
+
/* Preview text */
|
|
2155
|
+
.agy-tv-preview {
|
|
2156
|
+
font-family: var(--ds-font-family-code, ui-monospace, monospace);
|
|
2157
|
+
font-size: 11px;
|
|
2158
|
+
color: var(--dsw-alias-label-caption, #94a3b8);
|
|
2159
|
+
margin-left: 8px;
|
|
2160
|
+
overflow: hidden;
|
|
2161
|
+
text-overflow: ellipsis;
|
|
2162
|
+
white-space: nowrap;
|
|
2163
|
+
max-width: 35%;
|
|
2164
|
+
flex: none;
|
|
2165
|
+
}
|
|
2166
|
+
|
|
2167
|
+
/* Body wrap */
|
|
2168
|
+
.agy-tv-body-wrap {
|
|
2169
|
+
display: flex;
|
|
2170
|
+
flex-direction: column;
|
|
2171
|
+
}
|
|
2172
|
+
|
|
2173
|
+
/* Card container */
|
|
2174
|
+
.agy-tv-card {
|
|
2175
|
+
border: 0.5px solid var(--dsw-alias-border-l1, #e2e8f0);
|
|
2176
|
+
background: var(--dsw-alias-markdown-code-block, #f8fafc);
|
|
2177
|
+
font-family: var(--ds-font-family-code, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);
|
|
2178
|
+
font-size: 12px;
|
|
2179
|
+
line-height: 1.55;
|
|
2180
|
+
border-radius: 12px;
|
|
2181
|
+
margin: 4px 0 4px 4px;
|
|
2182
|
+
display: flex;
|
|
2183
|
+
flex-direction: column;
|
|
2184
|
+
overflow: hidden;
|
|
2185
|
+
}
|
|
2186
|
+
|
|
2187
|
+
/* Card Header */
|
|
2188
|
+
.agy-tv-card-header {
|
|
2189
|
+
display: flex;
|
|
2190
|
+
align-items: center;
|
|
2191
|
+
justify-content: space-between;
|
|
2192
|
+
gap: 12px;
|
|
2193
|
+
padding: 8px 12px;
|
|
2194
|
+
background: var(--dsw-alias-markdown-code-block-banner, rgba(0, 0, 0, 0.02));
|
|
2195
|
+
border-bottom: 0.5px solid var(--dsw-alias-border-l2, #e2e8f0);
|
|
2196
|
+
}
|
|
2197
|
+
.agy-tv-card-header-left {
|
|
2198
|
+
display: flex;
|
|
2199
|
+
align-items: baseline;
|
|
2200
|
+
gap: 8px;
|
|
2201
|
+
min-width: 0;
|
|
2202
|
+
flex: 1;
|
|
2203
|
+
overflow: hidden;
|
|
2204
|
+
}
|
|
2205
|
+
.agy-tv-prompt-glyph {
|
|
2206
|
+
color: var(--dsw-alias-label-caption, #94a3b8);
|
|
2207
|
+
flex: none;
|
|
2208
|
+
font-weight: 700;
|
|
2209
|
+
}
|
|
2210
|
+
.agy-tv-card-cwd {
|
|
2211
|
+
color: var(--dsw-alias-label-tertiary, #64748b);
|
|
2212
|
+
flex: none;
|
|
2213
|
+
overflow: hidden;
|
|
2214
|
+
text-overflow: ellipsis;
|
|
2215
|
+
white-space: nowrap;
|
|
2216
|
+
}
|
|
2217
|
+
.agy-tv-card-cmd {
|
|
2218
|
+
color: var(--dsw-alias-label-primary, #0f172a);
|
|
2219
|
+
overflow: hidden;
|
|
2220
|
+
text-overflow: ellipsis;
|
|
2221
|
+
white-space: pre;
|
|
2222
|
+
font-weight: 600;
|
|
2223
|
+
}
|
|
2224
|
+
.agy-tv-card-path {
|
|
2225
|
+
color: var(--dsw-alias-label-primary, #0f172a);
|
|
2226
|
+
font-weight: 600;
|
|
2227
|
+
overflow: hidden;
|
|
2228
|
+
text-overflow: ellipsis;
|
|
2229
|
+
white-space: nowrap;
|
|
2230
|
+
}
|
|
2231
|
+
|
|
2232
|
+
/* Copy button */
|
|
2233
|
+
.agy-tv-copy-btn {
|
|
2234
|
+
display: inline-flex;
|
|
2235
|
+
align-items: center;
|
|
2236
|
+
gap: 4px;
|
|
2237
|
+
background: transparent;
|
|
2238
|
+
border: none;
|
|
2239
|
+
padding: 2px 6px;
|
|
2240
|
+
margin: 0;
|
|
2241
|
+
color: var(--dsw-alias-label-secondary, #334155);
|
|
2242
|
+
cursor: pointer;
|
|
2243
|
+
font-size: 11px;
|
|
2244
|
+
line-height: 16px;
|
|
2245
|
+
border-radius: 4px;
|
|
2246
|
+
transition: background-color 0.1s ease, color 0.1s ease;
|
|
2247
|
+
flex: none;
|
|
2248
|
+
}
|
|
2249
|
+
.agy-tv-copy-btn:hover {
|
|
2250
|
+
background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.05));
|
|
2251
|
+
color: var(--dsw-alias-label-primary, #0f172a);
|
|
2252
|
+
}
|
|
2253
|
+
|
|
2254
|
+
/* Card scrollable content */
|
|
2255
|
+
.agy-tv-card-content {
|
|
2256
|
+
max-height: 260px;
|
|
2257
|
+
overflow-y: auto;
|
|
2258
|
+
overflow-x: auto;
|
|
2259
|
+
padding: 10px 14px;
|
|
2260
|
+
white-space: pre-wrap;
|
|
2261
|
+
word-break: break-word;
|
|
2262
|
+
color: var(--dsw-alias-label-secondary, #334155);
|
|
2263
|
+
scrollbar-width: thin;
|
|
2264
|
+
scrollbar-color: var(--dsw-alias-border-l3, rgba(0, 0, 0, 0.2)) transparent;
|
|
2265
|
+
}
|
|
2266
|
+
.agy-tv-card-content::-webkit-scrollbar {
|
|
2267
|
+
width: 6px;
|
|
2268
|
+
height: 6px;
|
|
2269
|
+
}
|
|
2270
|
+
.agy-tv-card-content::-webkit-scrollbar-thumb {
|
|
2271
|
+
background: var(--dsw-alias-border-l3, rgba(0, 0, 0, 0.18));
|
|
2272
|
+
border-radius: 6px;
|
|
2273
|
+
background-clip: padding-box;
|
|
2274
|
+
}
|
|
2275
|
+
.agy-tv-card-content::-webkit-scrollbar-track {
|
|
2276
|
+
background: transparent;
|
|
2277
|
+
margin: 6px 0;
|
|
2278
|
+
}
|
|
2279
|
+
|
|
2280
|
+
/* Diff lines */
|
|
2281
|
+
.agy-tv-diff-list {
|
|
2282
|
+
display: flex;
|
|
2283
|
+
flex-direction: column;
|
|
2284
|
+
padding: 6px 0;
|
|
2285
|
+
max-height: 280px;
|
|
2286
|
+
overflow-y: auto;
|
|
2287
|
+
overflow-x: auto;
|
|
2288
|
+
scrollbar-width: thin;
|
|
2289
|
+
scrollbar-color: var(--dsw-alias-border-l3, rgba(0, 0, 0, 0.2)) transparent;
|
|
2290
|
+
}
|
|
2291
|
+
.agy-tv-diff-list::-webkit-scrollbar {
|
|
2292
|
+
width: 6px;
|
|
2293
|
+
height: 6px;
|
|
2294
|
+
}
|
|
2295
|
+
.agy-tv-diff-list::-webkit-scrollbar-thumb {
|
|
2296
|
+
background: var(--dsw-alias-border-l3, rgba(0, 0, 0, 0.18));
|
|
2297
|
+
border-radius: 6px;
|
|
2298
|
+
}
|
|
2299
|
+
.agy-tv-diff-list::-webkit-scrollbar-track {
|
|
2300
|
+
background: transparent;
|
|
2301
|
+
margin: 4px 0;
|
|
2302
|
+
}
|
|
2303
|
+
.agy-tv-diff-line {
|
|
2304
|
+
display: flex;
|
|
2305
|
+
gap: 8px;
|
|
2306
|
+
padding: 0 12px;
|
|
2307
|
+
min-height: 20px;
|
|
2308
|
+
line-height: 20px;
|
|
2309
|
+
white-space: pre-wrap;
|
|
2310
|
+
word-break: break-word;
|
|
2311
|
+
}
|
|
2312
|
+
.agy-tv-diff-del {
|
|
2313
|
+
background: color-mix(in srgb, var(--dsw-alias-state-error-primary, #dc2626) 10%, transparent);
|
|
2314
|
+
color: var(--dsw-alias-state-error-primary, #dc2626);
|
|
2315
|
+
}
|
|
2316
|
+
.agy-tv-diff-add {
|
|
2317
|
+
background: color-mix(in srgb, var(--dsw-alias-state-success-primary, #059669) 10%, transparent);
|
|
2318
|
+
color: var(--dsw-alias-state-success-primary, #059669);
|
|
2319
|
+
}
|
|
2320
|
+
.agy-tv-diff-marker {
|
|
2321
|
+
flex: none;
|
|
2322
|
+
width: 14px;
|
|
2323
|
+
font-weight: 700;
|
|
2324
|
+
user-select: none;
|
|
2325
|
+
}
|
|
2326
|
+
|
|
2327
|
+
/* Inspect button */
|
|
2328
|
+
.agy-tv-inspect-btn {
|
|
2329
|
+
border: 0.5px solid var(--dsw-alias-border-l3, #cbd5e1);
|
|
2330
|
+
background: var(--dsw-alias-bg-base, #ffffff);
|
|
2331
|
+
color: var(--dsw-alias-label-secondary, #334155);
|
|
2332
|
+
cursor: pointer;
|
|
2333
|
+
opacity: 0;
|
|
2334
|
+
border-radius: 999px;
|
|
2335
|
+
align-self: flex-start;
|
|
2336
|
+
align-items: center;
|
|
2337
|
+
gap: 4px;
|
|
2338
|
+
margin: 4px 0 2px 4px;
|
|
2339
|
+
padding: 2px 8px;
|
|
2340
|
+
font-size: 11px;
|
|
2341
|
+
line-height: 16px;
|
|
2342
|
+
transition: opacity 0.1s;
|
|
2343
|
+
display: inline-flex;
|
|
2344
|
+
}
|
|
2345
|
+
.agy-tv-root:hover .agy-tv-inspect-btn,
|
|
2346
|
+
.agy-tv-inspect-btn:focus-visible {
|
|
2347
|
+
opacity: 1;
|
|
2348
|
+
}
|
|
2349
|
+
.agy-tv-inspect-btn:hover {
|
|
2350
|
+
background: var(--dsw-alias-interactive-bg-hover-solid, #f1f5f9);
|
|
2351
|
+
color: var(--dsw-alias-label-primary, #0f172a);
|
|
2352
|
+
}
|
|
2353
|
+
|
|
2354
|
+
/* Question Card Styles */
|
|
2355
|
+
.agy-tv-question-wrap {
|
|
2356
|
+
padding: 10px 14px;
|
|
2357
|
+
display: flex;
|
|
2358
|
+
flex-direction: column;
|
|
2359
|
+
gap: 10px;
|
|
2360
|
+
font-family: inherit;
|
|
2361
|
+
}
|
|
2362
|
+
.agy-tv-q-item {
|
|
2363
|
+
display: flex;
|
|
2364
|
+
flex-direction: column;
|
|
2365
|
+
gap: 6px;
|
|
2366
|
+
}
|
|
2367
|
+
.agy-tv-q-prompt {
|
|
2368
|
+
font-size: 13px;
|
|
2369
|
+
font-weight: 600;
|
|
2370
|
+
color: var(--dsw-alias-label-primary, #0f172a);
|
|
2371
|
+
}
|
|
2372
|
+
.agy-tv-q-opts {
|
|
2373
|
+
display: flex;
|
|
2374
|
+
flex-direction: column;
|
|
2375
|
+
gap: 6px;
|
|
2376
|
+
}
|
|
2377
|
+
.agy-tv-q-opt {
|
|
2378
|
+
display: flex;
|
|
2379
|
+
align-items: center;
|
|
2380
|
+
gap: 8px;
|
|
2381
|
+
padding: 6px 10px;
|
|
2382
|
+
border-radius: 6px;
|
|
2383
|
+
background: var(--dsw-alias-bg-l2, rgba(127, 127, 127, 0.08));
|
|
2384
|
+
border: 1px solid var(--dsw-alias-border-l2, rgba(127, 127, 127, 0.2));
|
|
2385
|
+
color: var(--dsw-alias-label-primary, #334155);
|
|
2386
|
+
cursor: pointer;
|
|
2387
|
+
text-align: left;
|
|
2388
|
+
font-size: 12.5px;
|
|
2389
|
+
transition: all 0.15s ease;
|
|
2390
|
+
}
|
|
2391
|
+
.agy-tv-q-opt:hover {
|
|
2392
|
+
background: var(--dsw-alias-bg-hover, rgba(127, 127, 127, 0.15));
|
|
2393
|
+
border-color: var(--dsw-alias-border-l3, rgba(127, 127, 127, 0.35));
|
|
2394
|
+
}
|
|
2395
|
+
.agy-tv-q-opt.selected {
|
|
2396
|
+
background: rgba(16, 185, 129, 0.12);
|
|
2397
|
+
border-color: #10b981;
|
|
2398
|
+
color: #10b981;
|
|
2399
|
+
}
|
|
2400
|
+
.agy-tv-q-check {
|
|
2401
|
+
width: 14px;
|
|
2402
|
+
height: 14px;
|
|
2403
|
+
border: 1.5px solid currentColor;
|
|
2404
|
+
display: inline-flex;
|
|
2405
|
+
align-items: center;
|
|
2406
|
+
justify-content: center;
|
|
2407
|
+
flex-shrink: 0;
|
|
2408
|
+
}
|
|
2409
|
+
.agy-tv-q-check.radio {
|
|
2410
|
+
border-radius: 50%;
|
|
2411
|
+
}
|
|
2412
|
+
.agy-tv-q-check.checkbox {
|
|
2413
|
+
border-radius: 3px;
|
|
2414
|
+
}
|
|
2415
|
+
.agy-tv-q-check.checked:after {
|
|
2416
|
+
content: "";
|
|
2417
|
+
width: 6px;
|
|
2418
|
+
height: 6px;
|
|
2419
|
+
background: currentColor;
|
|
2420
|
+
border-radius: inherit;
|
|
2421
|
+
}
|
|
2422
|
+
.agy-tv-q-footer {
|
|
2423
|
+
display: flex;
|
|
2424
|
+
align-items: center;
|
|
2425
|
+
justify-content: space-between;
|
|
2426
|
+
padding-top: 6px;
|
|
2427
|
+
border-top: 1px solid var(--dsw-alias-border-l2, rgba(127, 127, 127, 0.15));
|
|
2428
|
+
}
|
|
2429
|
+
.agy-tv-q-insert-btn {
|
|
2430
|
+
padding: 5px 12px;
|
|
2431
|
+
border-radius: 6px;
|
|
2432
|
+
font-size: 12px;
|
|
2433
|
+
font-weight: 600;
|
|
2434
|
+
background: rgba(16, 185, 129, 0.15);
|
|
2435
|
+
border: 1px solid #10b981;
|
|
2436
|
+
color: #10b981;
|
|
2437
|
+
cursor: pointer;
|
|
2438
|
+
transition: all 0.15s ease;
|
|
2439
|
+
}
|
|
2440
|
+
.agy-tv-q-insert-btn:disabled {
|
|
2441
|
+
opacity: 0.4;
|
|
2442
|
+
cursor: not-allowed;
|
|
2443
|
+
}
|
|
2444
|
+
.agy-tv-q-insert-btn:not(:disabled):hover {
|
|
2445
|
+
background: #10b981;
|
|
2446
|
+
color: #fff;
|
|
2447
|
+
}
|
|
2448
|
+
.agy-tv-q-answered {
|
|
2449
|
+
font-size: 11px;
|
|
2450
|
+
color: var(--dsw-alias-label-tertiary, #888);
|
|
2451
|
+
}
|
|
2452
|
+
|
|
2453
|
+
/* Image Card Styles */
|
|
2454
|
+
.agy-tv-img-wrap {
|
|
2455
|
+
padding: 10px 14px;
|
|
2456
|
+
display: flex;
|
|
2457
|
+
flex-direction: column;
|
|
2458
|
+
gap: 8px;
|
|
2459
|
+
}
|
|
2460
|
+
.agy-tv-img-meta {
|
|
2461
|
+
display: flex;
|
|
2462
|
+
align-items: center;
|
|
2463
|
+
justify-content: space-between;
|
|
2464
|
+
gap: 8px;
|
|
2465
|
+
}
|
|
2466
|
+
.agy-tv-img-prompt {
|
|
2467
|
+
font-size: 12.5px;
|
|
2468
|
+
color: var(--dsw-alias-label-primary, #334155);
|
|
2469
|
+
font-style: italic;
|
|
2470
|
+
overflow: hidden;
|
|
2471
|
+
text-overflow: ellipsis;
|
|
2472
|
+
white-space: nowrap;
|
|
2473
|
+
}
|
|
2474
|
+
.agy-tv-img-badges {
|
|
2475
|
+
display: flex;
|
|
2476
|
+
gap: 4px;
|
|
2477
|
+
flex-shrink: 0;
|
|
2478
|
+
}
|
|
2479
|
+
.agy-tv-img-badge {
|
|
2480
|
+
font-size: 11px;
|
|
2481
|
+
padding: 1px 6px;
|
|
2482
|
+
border-radius: 4px;
|
|
2483
|
+
background: var(--dsw-alias-bg-l2, rgba(127, 127, 127, 0.1));
|
|
2484
|
+
border: 1px solid var(--dsw-alias-border-l2, rgba(127, 127, 127, 0.2));
|
|
2485
|
+
color: var(--dsw-alias-label-secondary, #888);
|
|
2486
|
+
}
|
|
2487
|
+
.agy-tv-img-preview-box {
|
|
2488
|
+
max-height: 320px;
|
|
2489
|
+
overflow: hidden;
|
|
2490
|
+
border-radius: 6px;
|
|
2491
|
+
border: 1px solid var(--dsw-alias-border-l2, rgba(127, 127, 127, 0.2));
|
|
2492
|
+
display: flex;
|
|
2493
|
+
align-items: center;
|
|
2494
|
+
justify-content: center;
|
|
2495
|
+
background: repeating-conic-gradient(rgba(127,127,127,0.08) 0% 25%, transparent 0% 50%) 50% / 16px 16px;
|
|
2496
|
+
cursor: zoom-in;
|
|
2497
|
+
}
|
|
2498
|
+
.agy-tv-img-thumb {
|
|
2499
|
+
max-width: 100%;
|
|
2500
|
+
max-height: 320px;
|
|
2501
|
+
object-fit: contain;
|
|
2502
|
+
}
|
|
2503
|
+
`;
|
|
2504
|
+
// Insert toolview stylesheet into document
|
|
2505
|
+
const gDoc = globalThis.document;
|
|
2506
|
+
if (gDoc !== undefined && gDoc !== null) {
|
|
2507
|
+
const styleId = 'dsh-agy-link-toolview-css';
|
|
2508
|
+
if (gDoc.getElementById(styleId) === null) {
|
|
2509
|
+
const st = gDoc.createElement('style');
|
|
2510
|
+
st.id = styleId;
|
|
2511
|
+
st.textContent = ROW_CSS;
|
|
2512
|
+
const host = (gDoc.head ?? gDoc.documentElement);
|
|
2513
|
+
if (host)
|
|
2514
|
+
host.appendChild(st);
|
|
2515
|
+
}
|
|
2516
|
+
}
|
|
2517
|
+
};
|
|
2518
|
+
__modules["client/mermaid.js"] = function (require, exports, module) {
|
|
2519
|
+
"use strict";
|
|
2520
|
+
// Client-side mermaid rendering (dsh-agy-link).
|
|
2521
|
+
//
|
|
2522
|
+
// DSH's markdown pipeline (marked + shiki) has no mermaid grammar: a
|
|
2523
|
+
// ```mermaid fence falls through `highlightToHtml` as unknown language and
|
|
2524
|
+
// lands as a plain <pre> inside div.md-code-block, with the
|
|
2525
|
+
// fence language printed in the banner infostring. So the diagram source is
|
|
2526
|
+
// in the DOM — it just never gets drawn.
|
|
2527
|
+
//
|
|
2528
|
+
// Approach: non-destructive overlay. React owns the code-block subtree, so
|
|
2529
|
+
// we never rewrite data-code-block-content; we insert a sibling render
|
|
2530
|
+
// node and hide the source pre via a class. A MutationObserver re-tries
|
|
2531
|
+
// whenever React rebuilds the block.
|
|
2532
|
+
//
|
|
2533
|
+
// mermaid.js is NOT bundled (it would 8x client.js). It is lazy-loaded on
|
|
2534
|
+
// the first diagram; when that fails (offline / blocked CDN) the source
|
|
2535
|
+
// block is left visible. Failed loads are retried after a cooldown.
|
|
2536
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
2537
|
+
exports.MERMAID_CSS = void 0;
|
|
2538
|
+
exports.looksLikeMermaidSource = looksLikeMermaidSource;
|
|
2539
|
+
exports.isMermaidBlock = isMermaidBlock;
|
|
2540
|
+
exports.cleanupTempElements = cleanupTempElements;
|
|
2541
|
+
exports.installMermaidRendering = installMermaidRendering;
|
|
2542
|
+
exports.disposeMermaidRendering = disposeMermaidRendering;
|
|
2543
|
+
exports.isMermaidFence = isMermaidFence;
|
|
2544
|
+
exports.MERMAID_CSS = `
|
|
2545
|
+
div.md-code-block[data-agy-mermaid="done"] [data-code-block-content] {
|
|
2546
|
+
display: none !important;
|
|
2547
|
+
}
|
|
2548
|
+
div.md-code-block[data-agy-mermaid="render"] {
|
|
2549
|
+
border-color: var(--dsw-alias-border-l2, #e2e8f0);
|
|
2550
|
+
}
|
|
2551
|
+
div[data-agy-mermaid-svg] {
|
|
2552
|
+
padding: 12px 8px;
|
|
2553
|
+
overflow-x: auto;
|
|
2554
|
+
text-align: center;
|
|
2555
|
+
background: var(--dsw-alias-bg-base, #ffffff);
|
|
2556
|
+
}
|
|
2557
|
+
div[data-agy-mermaid-svg] svg {
|
|
2558
|
+
max-width: 100%;
|
|
2559
|
+
height: auto;
|
|
2560
|
+
}
|
|
2561
|
+
div[data-agy-mermaid-svg][data-agy-mermaid-error] {
|
|
2562
|
+
text-align: left;
|
|
2563
|
+
color: var(--dsw-alias-label-tertiary, #64748b);
|
|
2564
|
+
font-size: 12px;
|
|
2565
|
+
white-space: pre-wrap;
|
|
2566
|
+
padding: 10px 12px;
|
|
2567
|
+
}
|
|
2568
|
+
/* Prevent Mermaid's internal error DOM or leaked temporary nodes from escaping into document.body.
|
|
2569
|
+
The generic .error-icon/.error-text classes are scoped to OUR mounts and the
|
|
2570
|
+
leaked-body id shapes only — unqualified, they hid host UI using the same
|
|
2571
|
+
class names. */
|
|
2572
|
+
div[data-agy-mermaid-svg] .error-icon,
|
|
2573
|
+
div[data-agy-mermaid-svg] .error-text,
|
|
2574
|
+
body > [id^="dagy-mmd"] .error-icon,
|
|
2575
|
+
body > [id^="iagy-mmd"] .error-icon {
|
|
2576
|
+
display: none !important;
|
|
2577
|
+
}
|
|
2578
|
+
`;
|
|
2579
|
+
/**
|
|
2580
|
+
* The bundle ships mermaid.min.js and serves it from the plugin route. There
|
|
2581
|
+
* is deliberately NO public-CDN fallback: a floating-version, unsigned
|
|
2582
|
+
* script executing inside the DSH web origin (with reach into every local
|
|
2583
|
+
* HTTP API) is a supply-chain hazard that outweighs offline convenience.
|
|
2584
|
+
* When the local asset is missing the source block simply stays visible.
|
|
2585
|
+
*/
|
|
2586
|
+
const CDN_URLS = [
|
|
2587
|
+
'/plugins/agy-cli/mermaid.min.js',
|
|
2588
|
+
];
|
|
2589
|
+
let loadPromise = null;
|
|
2590
|
+
let loadFailedAt = 0;
|
|
2591
|
+
const LOAD_RETRY_MS = 60_000;
|
|
2592
|
+
let renderSeq = 0;
|
|
2593
|
+
function injectStyles() {
|
|
2594
|
+
if (typeof document === 'undefined')
|
|
2595
|
+
return;
|
|
2596
|
+
if (document.getElementById('agy-mermaid-css'))
|
|
2597
|
+
return;
|
|
2598
|
+
const st = document.createElement('style');
|
|
2599
|
+
st.id = 'agy-mermaid-css';
|
|
2600
|
+
st.textContent = exports.MERMAID_CSS;
|
|
2601
|
+
document.head.appendChild(st);
|
|
2602
|
+
}
|
|
2603
|
+
function loadScript(src) {
|
|
2604
|
+
return new Promise((resolve) => {
|
|
2605
|
+
const s = document.createElement('script');
|
|
2606
|
+
s.src = src;
|
|
2607
|
+
s.async = true;
|
|
2608
|
+
let settled = false;
|
|
2609
|
+
const done = (ok) => {
|
|
2610
|
+
if (settled)
|
|
2611
|
+
return;
|
|
2612
|
+
settled = true;
|
|
2613
|
+
s.removeEventListener('load', onLoad);
|
|
2614
|
+
s.removeEventListener('error', onError);
|
|
2615
|
+
// A timed-out script tag used to stay in the DOM: a slow response
|
|
2616
|
+
// would still execute later and race the fallback load.
|
|
2617
|
+
if (!ok)
|
|
2618
|
+
s.remove();
|
|
2619
|
+
resolve(ok);
|
|
2620
|
+
};
|
|
2621
|
+
const onLoad = () => done(true);
|
|
2622
|
+
const onError = () => done(false);
|
|
2623
|
+
s.addEventListener('load', onLoad);
|
|
2624
|
+
s.addEventListener('error', onError);
|
|
2625
|
+
document.head.appendChild(s);
|
|
2626
|
+
// A hung CDN must not wedge the observer thread.
|
|
2627
|
+
setTimeout(() => done(false), 15_000);
|
|
2628
|
+
});
|
|
2629
|
+
}
|
|
2630
|
+
async function loadMermaid() {
|
|
2631
|
+
if (typeof window === 'undefined')
|
|
2632
|
+
return null;
|
|
2633
|
+
if (window.mermaid !== undefined)
|
|
2634
|
+
return window.mermaid;
|
|
2635
|
+
if (loadPromise !== null)
|
|
2636
|
+
return loadPromise;
|
|
2637
|
+
// A failed CDN load is retried after a cooldown instead of never.
|
|
2638
|
+
if (Date.now() - loadFailedAt < LOAD_RETRY_MS)
|
|
2639
|
+
return null;
|
|
2640
|
+
loadPromise = (async () => {
|
|
2641
|
+
for (const url of CDN_URLS) {
|
|
2642
|
+
const ok = await loadScript(url);
|
|
2643
|
+
if (ok && window.mermaid !== undefined) {
|
|
2644
|
+
try {
|
|
2645
|
+
window.mermaid.initialize({
|
|
2646
|
+
startOnLoad: false,
|
|
2647
|
+
securityLevel: 'strict',
|
|
2648
|
+
theme: 'neutral',
|
|
2649
|
+
fontFamily: 'inherit',
|
|
2650
|
+
suppressErrorRendering: true,
|
|
2651
|
+
});
|
|
2652
|
+
}
|
|
2653
|
+
catch {
|
|
2654
|
+
// initialize is idempotent enough; keep going
|
|
2655
|
+
}
|
|
2656
|
+
return window.mermaid;
|
|
2657
|
+
}
|
|
2658
|
+
}
|
|
2659
|
+
loadFailedAt = Date.now();
|
|
2660
|
+
loadPromise = null;
|
|
2661
|
+
return null;
|
|
2662
|
+
})();
|
|
2663
|
+
return loadPromise;
|
|
2664
|
+
}
|
|
2665
|
+
/** Banner infostring text ("mermaid") identifies the fence language. */
|
|
2666
|
+
function fenceLang(block) {
|
|
2667
|
+
const info = block.querySelector('[data-code-block-banner] [class*="infostring"]')
|
|
2668
|
+
?? block.querySelector('[data-code-block-banner] [class*="language"]')
|
|
2669
|
+
?? block.querySelector('[data-code-block-banner]');
|
|
2670
|
+
const text = (info?.textContent ?? '').trim().toLowerCase();
|
|
2671
|
+
if (text !== '') {
|
|
2672
|
+
// The language is the FIRST token of the infostring; the banner may
|
|
2673
|
+
// carry UI text after it, so never return more than one token.
|
|
2674
|
+
return text.split(/\s+/)[0] ?? '';
|
|
2675
|
+
}
|
|
2676
|
+
// DSH renders UNKNOWN fence languages with a generic banner ("代码块")
|
|
2677
|
+
// and no infostring — the raw language then only survives on the code
|
|
2678
|
+
// element itself, if at all.
|
|
2679
|
+
const code = block.querySelector('[data-code-block-content] code');
|
|
2680
|
+
const cls = code?.getAttribute('class') ?? '';
|
|
2681
|
+
const m = cls.match(/language-([\w-]+)/);
|
|
2682
|
+
if (m?.[1])
|
|
2683
|
+
return m[1].toLowerCase();
|
|
2684
|
+
return (code?.getAttribute('data-language') ?? block.getAttribute('data-language') ?? '').toLowerCase();
|
|
2685
|
+
}
|
|
2686
|
+
// First-line shapes of the fence languages mermaid v11 can draw.
|
|
2687
|
+
const MERMAID_FIRST_LINE = /^(?:flowchart(?:\s+\w+)?|graph\s+(?:TB|TD|LR|RL|BT)|sequenceDiagram|classDiagram(?:-v2)?|stateDiagram(?:-v2)?|erDiagram|journey|gantt|pie|mindmap|timeline|gitGraph|sankey-beta|xychart-beta|block-beta|quadrantChart|requirementDiagram|radar-beta|C4(?:Context|Container|Component|Dynamic))\b/;
|
|
2688
|
+
/** Content sniff: unknown-language fences are the whole reason this module exists. */
|
|
2689
|
+
function looksLikeMermaidSource(source) {
|
|
2690
|
+
const line = source.split(/\r?\n/).find((l) => l.trim() !== '');
|
|
2691
|
+
return line !== undefined && MERMAID_FIRST_LINE.test(line.trim());
|
|
2692
|
+
}
|
|
2693
|
+
function isMermaidLang(lang) {
|
|
2694
|
+
return lang === 'mermaid' || lang === 'mermaidjs' || lang.startsWith('mermaid');
|
|
2695
|
+
}
|
|
2696
|
+
const KNOWN_NON_MERMAID = new Set([
|
|
2697
|
+
'typescript', 'ts', 'javascript', 'js', 'jsx', 'tsx',
|
|
2698
|
+
'python', 'py', 'java', 'c', 'cpp', 'c++', 'csharp', 'cs',
|
|
2699
|
+
'go', 'golang', 'rust', 'rs', 'php', 'ruby', 'rb', 'swift',
|
|
2700
|
+
'kotlin', 'kt', 'scala', 'shell', 'bash', 'sh', 'zsh',
|
|
2701
|
+
'sql', 'html', 'css', 'scss', 'less', 'json', 'yaml', 'yml',
|
|
2702
|
+
'xml', 'graphql', 'toml', 'ini', 'dockerfile', 'makefile',
|
|
2703
|
+
]);
|
|
2704
|
+
function isMermaidBlock(lang, source) {
|
|
2705
|
+
if (isMermaidLang(lang))
|
|
2706
|
+
return true;
|
|
2707
|
+
if (KNOWN_NON_MERMAID.has(lang))
|
|
2708
|
+
return false;
|
|
2709
|
+
return looksLikeMermaidSource(source);
|
|
2710
|
+
}
|
|
2711
|
+
function readSource(block) {
|
|
2712
|
+
const code = block.querySelector('[data-code-block-content] code')
|
|
2713
|
+
?? block.querySelector('[data-code-block-content] pre')
|
|
2714
|
+
?? block.querySelector('[data-code-block-content]');
|
|
2715
|
+
return (code?.textContent ?? '').trim();
|
|
2716
|
+
}
|
|
2717
|
+
function cleanupTempElements(id) {
|
|
2718
|
+
if (typeof document === 'undefined')
|
|
2719
|
+
return;
|
|
2720
|
+
if (id) {
|
|
2721
|
+
document.getElementById('d' + id)?.remove();
|
|
2722
|
+
document.getElementById('i' + id)?.remove();
|
|
2723
|
+
document.getElementById(id)?.remove();
|
|
2724
|
+
}
|
|
2725
|
+
const stray = document.querySelectorAll('body > [id^="dagy-mmd"], body > [id^="iagy-mmd"], body > [id^="d_agy-mmd"], body > [id^="i_agy-mmd"], body > [id^="agy-mmd-"]');
|
|
2726
|
+
for (let i = 0; i < stray.length; i++) {
|
|
2727
|
+
stray[i]?.remove();
|
|
2728
|
+
}
|
|
2729
|
+
}
|
|
2730
|
+
async function renderInto(block, source) {
|
|
2731
|
+
// Drop a previous overlay so a React rebuild can re-render cleanly.
|
|
2732
|
+
block.querySelector('[data-agy-mermaid-svg]')?.remove();
|
|
2733
|
+
const mount = document.createElement('div');
|
|
2734
|
+
mount.setAttribute('data-agy-mermaid-svg', '1');
|
|
2735
|
+
block.appendChild(mount);
|
|
2736
|
+
const api = await loadMermaid();
|
|
2737
|
+
if (api === null) {
|
|
2738
|
+
// Offline: keep the source visible and explain once.
|
|
2739
|
+
block.setAttribute('data-agy-mermaid', 'error');
|
|
2740
|
+
mount.setAttribute('data-agy-mermaid-error', '1');
|
|
2741
|
+
mount.textContent = 'Mermaid renderer unavailable (offline). Diagram source stays below.';
|
|
2742
|
+
return;
|
|
2743
|
+
}
|
|
2744
|
+
renderSeq += 1;
|
|
2745
|
+
const id = 'agy-mmd-' + renderSeq;
|
|
2746
|
+
try {
|
|
2747
|
+
// Pre-validate diagram syntax: parse throws without mutating DOM or rendering error fallback
|
|
2748
|
+
if (typeof api.parse === 'function') {
|
|
2749
|
+
await api.parse(source);
|
|
2750
|
+
}
|
|
2751
|
+
const { svg } = await api.render(id, source, mount);
|
|
2752
|
+
mount.innerHTML = svg;
|
|
2753
|
+
block.setAttribute('data-agy-mermaid', 'done');
|
|
2754
|
+
}
|
|
2755
|
+
catch (e) {
|
|
2756
|
+
block.setAttribute('data-agy-mermaid', 'error');
|
|
2757
|
+
mount.setAttribute('data-agy-mermaid-error', '1');
|
|
2758
|
+
mount.textContent = 'Mermaid render failed: ' + (e instanceof Error ? e.message : String(e));
|
|
2759
|
+
}
|
|
2760
|
+
finally {
|
|
2761
|
+
cleanupTempElements(id);
|
|
2762
|
+
}
|
|
2763
|
+
}
|
|
2764
|
+
// Streaming conversations mutate the DOM at high frequency; the scan itself
|
|
2765
|
+
// walks every code block, so coalesce mutation bursts into one pass.
|
|
2766
|
+
let scanTimer = null;
|
|
2767
|
+
function scheduleScan() {
|
|
2768
|
+
if (scanTimer !== null)
|
|
2769
|
+
return;
|
|
2770
|
+
scanTimer = setTimeout(() => {
|
|
2771
|
+
scanTimer = null;
|
|
2772
|
+
scan();
|
|
2773
|
+
}, 150);
|
|
2774
|
+
}
|
|
2775
|
+
function scan() {
|
|
2776
|
+
if (typeof document === 'undefined')
|
|
2777
|
+
return;
|
|
2778
|
+
cleanupTempElements();
|
|
2779
|
+
const blocks = document.querySelectorAll('.md-code-block');
|
|
2780
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
2781
|
+
const block = blocks[i];
|
|
2782
|
+
if (!block)
|
|
2783
|
+
continue;
|
|
2784
|
+
const state = block.getAttribute('data-agy-mermaid');
|
|
2785
|
+
// Error blocks stay source-visible; they are retried only when React
|
|
2786
|
+
// genuinely rebuilds the block (which wipes the state attribute).
|
|
2787
|
+
if (state === 'done' || state === 'busy' || state === 'error')
|
|
2788
|
+
continue;
|
|
2789
|
+
const lang = fenceLang(block);
|
|
2790
|
+
const source = readSource(block);
|
|
2791
|
+
if (source === '')
|
|
2792
|
+
continue;
|
|
2793
|
+
if (!isMermaidBlock(lang, source))
|
|
2794
|
+
continue;
|
|
2795
|
+
block.setAttribute('data-agy-mermaid', 'busy');
|
|
2796
|
+
void renderInto(block, source);
|
|
2797
|
+
}
|
|
2798
|
+
}
|
|
2799
|
+
let activeObserver = null;
|
|
2800
|
+
let domContentLoadedHandler = null;
|
|
2801
|
+
/**
|
|
2802
|
+
* Install the mermaid overlay. Returns a disposer that disconnects the
|
|
2803
|
+
* MutationObserver and cancels pending setup — wrap it in ctx.effect so a
|
|
2804
|
+
* plugin reload cannot stack observers over the same document.
|
|
2805
|
+
*/
|
|
2806
|
+
function installMermaidRendering() {
|
|
2807
|
+
if (typeof window === 'undefined' || typeof document === 'undefined')
|
|
2808
|
+
return () => { };
|
|
2809
|
+
// A previous install (plugin reload) must not keep observing forever.
|
|
2810
|
+
disposeMermaidRendering();
|
|
2811
|
+
injectStyles();
|
|
2812
|
+
cleanupTempElements();
|
|
2813
|
+
const observer = new MutationObserver(() => {
|
|
2814
|
+
scheduleScan();
|
|
2815
|
+
});
|
|
2816
|
+
activeObserver = observer;
|
|
2817
|
+
const setup = () => {
|
|
2818
|
+
if (document.body) {
|
|
2819
|
+
cleanupTempElements();
|
|
2820
|
+
observer.observe(document.body, { childList: true, subtree: true });
|
|
2821
|
+
scan();
|
|
2822
|
+
}
|
|
2823
|
+
};
|
|
2824
|
+
if (document.readyState === 'loading') {
|
|
2825
|
+
domContentLoadedHandler = setup;
|
|
2826
|
+
window.addEventListener('DOMContentLoaded', setup, { once: true });
|
|
2827
|
+
}
|
|
2828
|
+
else {
|
|
2829
|
+
setup();
|
|
2830
|
+
}
|
|
2831
|
+
return disposeMermaidRendering;
|
|
2832
|
+
}
|
|
2833
|
+
/** Disconnect the observer and cancel pending setup work. */
|
|
2834
|
+
function disposeMermaidRendering() {
|
|
2835
|
+
if (activeObserver !== null) {
|
|
2836
|
+
activeObserver.disconnect();
|
|
2837
|
+
activeObserver = null;
|
|
2838
|
+
}
|
|
2839
|
+
if (domContentLoadedHandler !== null) {
|
|
2840
|
+
window.removeEventListener('DOMContentLoaded', domContentLoadedHandler);
|
|
2841
|
+
domContentLoadedHandler = null;
|
|
2842
|
+
}
|
|
2843
|
+
if (scanTimer !== null) {
|
|
2844
|
+
clearTimeout(scanTimer);
|
|
2845
|
+
scanTimer = null;
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2848
|
+
/** Test seam: classify a fence language the way the scanner does. */
|
|
2849
|
+
function isMermaidFence(lang) {
|
|
2850
|
+
return isMermaidLang(lang.trim().toLowerCase());
|
|
2851
|
+
}
|
|
2852
|
+
};
|
|
2853
|
+
__modules["client/reasoning.js"] = function (require, exports, module) {
|
|
2854
|
+
"use strict";
|
|
2855
|
+
// Client-side reasoning presentation (dsh-agy-link).
|
|
2856
|
+
//
|
|
2857
|
+
// DSH web renders assistant reasoning as <div data-variant="think"> rows,
|
|
2858
|
+
// default collapsed. We must NOT rewrite React-managed thinkBody DOM:
|
|
2859
|
+
// clearing textContent destroys host-owned nodes and the block goes blank
|
|
2860
|
+
// after the turn finishes and React re-renders from the message model.
|
|
2861
|
+
//
|
|
2862
|
+
// The "show only the newest step, with a scrolling feel" behavior is NOT
|
|
2863
|
+
// implemented here. It is the host's own, driven by paragraph structure:
|
|
2864
|
+
// ReasoningRow's collapsed summary is `latestCompletedParagraphFirstLine`
|
|
2865
|
+
// while the row runs (first line of the newest completed paragraph) and
|
|
2866
|
+
// `firstLine` once it settles. The provider therefore emits one
|
|
2867
|
+
// blank-line-separated paragraph per step, opening with a `[agy 过程流]`
|
|
2868
|
+
// banner — the fold live-rolls to the newest step with zero client work,
|
|
2869
|
+
// and settles on the banner.
|
|
2870
|
+
//
|
|
2871
|
+
// What this file still owns:
|
|
2872
|
+
// - CSS-only styling of the think body (scrollable, no DOM surgery).
|
|
2873
|
+
// - While a row is running AND the reader has opened it, keep the body
|
|
2874
|
+
// pinned to the newest line (scrollTop = scrollHeight, `tail -f`).
|
|
2875
|
+
// It never opens a row by itself: forcing it open would defeat the
|
|
2876
|
+
// one-line fold the user asked for.
|
|
2877
|
+
//
|
|
2878
|
+
// Running is read from the host's own `data-state="running"` on the row
|
|
2879
|
+
// root (the official carrier — no spinner/class guessing), and the row is
|
|
2880
|
+
// never identified by `[data-message-id]`-style attributes the host does
|
|
2881
|
+
// not emit.
|
|
2882
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
2883
|
+
exports.installReasoningFollow = installReasoningFollow;
|
|
2884
|
+
exports.disposeReasoningFollow = disposeReasoningFollow;
|
|
2885
|
+
const REASONING_CSS = `
|
|
2886
|
+
/* Hover affordance on thinking disclosure rows */
|
|
2887
|
+
div[data-variant="think"] [data-disclosure-row]:hover [class*="iconIdle"],
|
|
2888
|
+
div[data-variant="think"] [class*="row"]:hover [class*="iconIdle"] {
|
|
2889
|
+
opacity: 0 !important;
|
|
2890
|
+
}
|
|
2891
|
+
div[data-variant="think"] [data-disclosure-row]:hover [class*="chevronHover"],
|
|
2892
|
+
div[data-variant="think"] [class*="row"]:hover [class*="chevronHover"] {
|
|
2893
|
+
opacity: 1 !important;
|
|
2894
|
+
}
|
|
2895
|
+
|
|
2896
|
+
/* Scrollable thinking body (does not touch DOM structure) */
|
|
2897
|
+
div[data-variant="think"] [class*="thinkBody"] {
|
|
2898
|
+
max-height: 360px;
|
|
2899
|
+
overflow-y: auto;
|
|
2900
|
+
overflow-x: hidden;
|
|
2901
|
+
white-space: pre-wrap;
|
|
2902
|
+
word-break: break-word;
|
|
2903
|
+
padding-right: 8px;
|
|
2904
|
+
scrollbar-width: thin;
|
|
2905
|
+
scrollbar-color: var(--dsw-alias-border-l3, rgba(0, 0, 0, 0.2)) transparent;
|
|
2906
|
+
}
|
|
2907
|
+
div[data-variant="think"] [class*="thinkBody"]::-webkit-scrollbar {
|
|
2908
|
+
width: 6px;
|
|
2909
|
+
height: 6px;
|
|
2910
|
+
}
|
|
2911
|
+
div[data-variant="think"] [class*="thinkBody"]::-webkit-scrollbar-thumb {
|
|
2912
|
+
background: var(--dsw-alias-border-l3, rgba(0, 0, 0, 0.18));
|
|
2913
|
+
border-radius: 6px;
|
|
2914
|
+
background-clip: padding-box;
|
|
2915
|
+
}
|
|
2916
|
+
div[data-variant="think"] [class*="thinkBody"]::-webkit-scrollbar-track {
|
|
2917
|
+
background: transparent;
|
|
2918
|
+
margin: 4px 0;
|
|
2919
|
+
}
|
|
2920
|
+
`;
|
|
2921
|
+
function injectStyles() {
|
|
2922
|
+
if (typeof document === 'undefined')
|
|
2923
|
+
return;
|
|
2924
|
+
const styleId = 'dsh-agy-link-reasoning-css';
|
|
2925
|
+
if (document.getElementById(styleId) === null) {
|
|
2926
|
+
const st = document.createElement('style');
|
|
2927
|
+
st.id = styleId;
|
|
2928
|
+
st.textContent = REASONING_CSS;
|
|
2929
|
+
const host = document.head ?? document.documentElement;
|
|
2930
|
+
if (host)
|
|
2931
|
+
host.appendChild(st);
|
|
2932
|
+
}
|
|
2933
|
+
}
|
|
2934
|
+
function thinkBodyOf(root) {
|
|
2935
|
+
return root.querySelector('[class*="thinkBody"]');
|
|
2936
|
+
}
|
|
2937
|
+
/**
|
|
2938
|
+
* One auto pass: every running think row whose body the reader has opened
|
|
2939
|
+
* gets pinned to its newest line. A collapsed row has no thinkBody mounted
|
|
2940
|
+
* (the host mounts it only when expanded), so the lookup itself is the
|
|
2941
|
+
* "did the reader open it" test — no click synthesis, no per-row bookkeeping.
|
|
2942
|
+
*
|
|
2943
|
+
* The pin only fires when the reader is already at the bottom, so scrolling
|
|
2944
|
+
* up to re-read an earlier step is never yanked back down.
|
|
2945
|
+
*/
|
|
2946
|
+
function followRunningReasoning() {
|
|
2947
|
+
if (typeof document === 'undefined')
|
|
2948
|
+
return;
|
|
2949
|
+
const rows = document.querySelectorAll('div[data-variant="think"]');
|
|
2950
|
+
for (let i = 0; i < rows.length; i++) {
|
|
2951
|
+
const root = rows[i];
|
|
2952
|
+
if (!root)
|
|
2953
|
+
continue;
|
|
2954
|
+
if (root.getAttribute('data-state') !== 'running')
|
|
2955
|
+
continue;
|
|
2956
|
+
const body = thinkBodyOf(root);
|
|
2957
|
+
if (body === null)
|
|
2958
|
+
continue;
|
|
2959
|
+
if (body.scrollTop + body.clientHeight < body.scrollHeight - 24)
|
|
2960
|
+
continue;
|
|
2961
|
+
body.scrollTop = body.scrollHeight;
|
|
2962
|
+
}
|
|
2963
|
+
}
|
|
2964
|
+
// Streaming conversations mutate the DOM at high frequency: coalesce
|
|
2965
|
+
// mutation bursts into one scan pass (mirrors the mermaid scanner's debounce).
|
|
2966
|
+
let scanTimer = null;
|
|
2967
|
+
function scheduleScan() {
|
|
2968
|
+
if (scanTimer !== null)
|
|
2969
|
+
return;
|
|
2970
|
+
scanTimer = setTimeout(() => {
|
|
2971
|
+
scanTimer = null;
|
|
2972
|
+
followRunningReasoning();
|
|
2973
|
+
}, 200);
|
|
2974
|
+
}
|
|
2975
|
+
let activeObserver = null;
|
|
2976
|
+
let domContentLoadedHandler = null;
|
|
2977
|
+
/**
|
|
2978
|
+
* Install the reasoning follow-along overlay. Returns a disposer that
|
|
2979
|
+
* disconnects the MutationObserver — wrap it in ctx.effect so a plugin
|
|
2980
|
+
* reload cannot stack observers.
|
|
2981
|
+
*/
|
|
2982
|
+
function installReasoningFollow() {
|
|
2983
|
+
if (typeof window === 'undefined' || typeof document === 'undefined')
|
|
2984
|
+
return () => { };
|
|
2985
|
+
disposeReasoningFollow();
|
|
2986
|
+
injectStyles();
|
|
2987
|
+
const observer = new MutationObserver(() => {
|
|
2988
|
+
scheduleScan();
|
|
2989
|
+
});
|
|
2990
|
+
activeObserver = observer;
|
|
2991
|
+
const setupObserver = () => {
|
|
2992
|
+
if (document.body) {
|
|
2993
|
+
observer.observe(document.body, { childList: true, subtree: true });
|
|
2994
|
+
followRunningReasoning();
|
|
2995
|
+
}
|
|
2996
|
+
};
|
|
2997
|
+
if (document.readyState === 'loading') {
|
|
2998
|
+
domContentLoadedHandler = setupObserver;
|
|
2999
|
+
window.addEventListener('DOMContentLoaded', setupObserver, { once: true });
|
|
3000
|
+
}
|
|
3001
|
+
else {
|
|
3002
|
+
setupObserver();
|
|
3003
|
+
}
|
|
3004
|
+
return disposeReasoningFollow;
|
|
3005
|
+
}
|
|
3006
|
+
/** Disconnect the observer and cancel pending work. */
|
|
3007
|
+
function disposeReasoningFollow() {
|
|
3008
|
+
if (activeObserver !== null) {
|
|
3009
|
+
activeObserver.disconnect();
|
|
3010
|
+
activeObserver = null;
|
|
3011
|
+
}
|
|
3012
|
+
if (domContentLoadedHandler !== null) {
|
|
3013
|
+
window.removeEventListener('DOMContentLoaded', domContentLoadedHandler);
|
|
3014
|
+
domContentLoadedHandler = null;
|
|
3015
|
+
}
|
|
3016
|
+
if (scanTimer !== null) {
|
|
3017
|
+
clearTimeout(scanTimer);
|
|
3018
|
+
scanTimer = null;
|
|
3019
|
+
}
|
|
3020
|
+
}
|
|
3021
|
+
};
|
|
3022
|
+
__modules["client/index.js"] = function (require, exports, module) {
|
|
3023
|
+
"use strict";
|
|
3024
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3025
|
+
exports.inject = exports.name = void 0;
|
|
3026
|
+
exports.apply = apply;
|
|
3027
|
+
const brand_icons_ts_1 = require("client/brand-icons.js");
|
|
3028
|
+
const toolview_ts_1 = require("client/toolview.js");
|
|
3029
|
+
const mermaid_ts_1 = require("client/mermaid.js");
|
|
3030
|
+
const locales_ts_1 = require("client/locales.js");
|
|
3031
|
+
const reasoning_ts_1 = require("client/reasoning.js");
|
|
3032
|
+
const images_ts_1 = require("client/images.js");
|
|
3033
|
+
const decisions_ts_1 = require("client/decisions.js");
|
|
3034
|
+
const R = require('react');
|
|
3035
|
+
const { createElement: h, useState, useEffect, useRef } = R;
|
|
3036
|
+
const win = globalThis;
|
|
3037
|
+
exports.name = 'dsh-agy-cli-client';
|
|
3038
|
+
// Cordis inject entries are hard dependencies: accessing a service not listed
|
|
3039
|
+
// here throws ("cannot get property X without inject"). locale must be declared
|
|
3040
|
+
// for ctx.locale to be readable (issue #24). dsh-web-app ships dsh-client-locale.
|
|
3041
|
+
exports.inject = ['slots', 'locale'];
|
|
3042
|
+
const base = win.location?.origin ?? '';
|
|
3043
|
+
// Module-level cache for instant remount rendering
|
|
3044
|
+
let statusCache = null;
|
|
3045
|
+
async function getStatus() {
|
|
3046
|
+
try {
|
|
3047
|
+
// A bounded fetch: the poll loop must never stack behind a hung request
|
|
3048
|
+
// (undici/browser defaults can park far longer than one poll period).
|
|
3049
|
+
const init = {};
|
|
3050
|
+
if (typeof AbortSignal !== 'undefined' && typeof AbortSignal.timeout === 'function') {
|
|
3051
|
+
init.signal = AbortSignal.timeout(5000);
|
|
3052
|
+
}
|
|
3053
|
+
const res = await win.fetch?.(base + '/plugins/agy-cli/status', init);
|
|
3054
|
+
if (!res || !res.ok)
|
|
3055
|
+
return null;
|
|
3056
|
+
const payload = (await res.json());
|
|
3057
|
+
statusCache = payload;
|
|
3058
|
+
return payload;
|
|
3059
|
+
}
|
|
3060
|
+
catch {
|
|
3061
|
+
return null;
|
|
3062
|
+
}
|
|
3063
|
+
}
|
|
3064
|
+
async function postJson(path, body) {
|
|
3065
|
+
try {
|
|
3066
|
+
const res = await win.fetch?.(base + path, {
|
|
3067
|
+
method: 'POST',
|
|
3068
|
+
headers: { 'Content-Type': 'application/json' },
|
|
3069
|
+
body: JSON.stringify(body),
|
|
3070
|
+
});
|
|
3071
|
+
if (!res)
|
|
3072
|
+
return null;
|
|
3073
|
+
try {
|
|
3074
|
+
const payload = await res.json();
|
|
3075
|
+
// Surface HTTP failure as ok:false so callers can distinguish
|
|
3076
|
+
// "saved" from "server refused" instead of always toasting success.
|
|
3077
|
+
if (!res.ok && payload !== null && typeof payload === 'object') {
|
|
3078
|
+
return { ok: false, ...payload };
|
|
3079
|
+
}
|
|
3080
|
+
return payload;
|
|
3081
|
+
}
|
|
3082
|
+
catch {
|
|
3083
|
+
return { ok: res.ok };
|
|
3084
|
+
}
|
|
3085
|
+
}
|
|
3086
|
+
catch {
|
|
3087
|
+
return null;
|
|
3088
|
+
}
|
|
3089
|
+
}
|
|
3090
|
+
// ---- dispatch reporting (Models page card) ----
|
|
3091
|
+
// The host renders `settings.models.provider-card` only for the directory row
|
|
3092
|
+
// whose `settingsNs` equals the key registered here, and it wraps every entry in
|
|
3093
|
+
// an error boundary that renders an EMPTY element when the card throws. So "the
|
|
3094
|
+
// host never dispatched this card" and "the card crashed on first render" are
|
|
3095
|
+
// the same pixels, from both sides. Reporting the dispatch itself — from inside
|
|
3096
|
+
// the render body, see the call site — is what tells them apart at
|
|
3097
|
+
// `/plugins/agy-cli/diagnose`. Throttled so re-renders (and StrictMode's double
|
|
3098
|
+
// render) cannot flood the endpoint.
|
|
3099
|
+
let lastDispatchReport = 0;
|
|
3100
|
+
function reportCardDispatch(props) {
|
|
3101
|
+
const now = Date.now();
|
|
3102
|
+
if (now - lastDispatchReport < 5_000)
|
|
3103
|
+
return;
|
|
3104
|
+
lastDispatchReport = now;
|
|
3105
|
+
const names = props !== null && typeof props === 'object' ? Object.keys(props) : [];
|
|
3106
|
+
void postJson('/plugins/agy-cli/card-ping', { props: names });
|
|
3107
|
+
}
|
|
3108
|
+
// ---- shared status load (no polling) ----
|
|
3109
|
+
// ONE module-level fetch serves every mounted consumer (the provider card in
|
|
3110
|
+
// the host's Models settings page). There is deliberately no timer: nothing on
|
|
3111
|
+
// screen needs a background refresh. The server answers `/status` by spawning
|
|
3112
|
+
// PowerShell (`execFileSync`) to read the Windows keyring, so any poll
|
|
3113
|
+
// competes with every other RPC and is the reason the page used to feel stuck.
|
|
3114
|
+
// The card fetches once per mount (opening the Models page), the local 30 s
|
|
3115
|
+
// tick below only redraws the countdown, and the refresh button fetches again.
|
|
3116
|
+
const statusListeners = new Set();
|
|
3117
|
+
let statusFetchSeq = 0;
|
|
3118
|
+
// Mount fetch and the refresh button overlap freely (a stuck request used to
|
|
3119
|
+
// let several more start), so entering this gate twice is the bug. A caller that
|
|
3120
|
+
// arrives while a fetch is running gets the running promise back instead of
|
|
3121
|
+
// `undefined`: returning early made the caller's `await` resolve instantly, so
|
|
3122
|
+
// the panel decided the load was over ("failed") while it was still in flight.
|
|
3123
|
+
let statusInFlight = null;
|
|
3124
|
+
function notifyStatus(st) {
|
|
3125
|
+
statusCache = st;
|
|
3126
|
+
for (const fn of [...statusListeners]) {
|
|
3127
|
+
try {
|
|
3128
|
+
fn(st);
|
|
3129
|
+
}
|
|
3130
|
+
catch { /* a dead consumer must not kill the load */ }
|
|
3131
|
+
}
|
|
3132
|
+
}
|
|
3133
|
+
async function loadStatusOnce() {
|
|
3134
|
+
if (statusInFlight !== null)
|
|
3135
|
+
return statusInFlight;
|
|
3136
|
+
const seq = ++statusFetchSeq;
|
|
3137
|
+
const run = (async () => {
|
|
3138
|
+
try {
|
|
3139
|
+
const st = await getStatus();
|
|
3140
|
+
// A slow response from an older fetch must never overwrite newer data.
|
|
3141
|
+
if (st !== null && seq === statusFetchSeq)
|
|
3142
|
+
notifyStatus(st);
|
|
3143
|
+
}
|
|
3144
|
+
finally {
|
|
3145
|
+
if (statusInFlight === run)
|
|
3146
|
+
statusInFlight = null;
|
|
3147
|
+
}
|
|
3148
|
+
})();
|
|
3149
|
+
statusInFlight = run;
|
|
3150
|
+
return run;
|
|
3151
|
+
}
|
|
3152
|
+
/**
|
|
3153
|
+
* Subscribe a component to the shared status poll. Returns the latest
|
|
3154
|
+
* snapshot plus a manual refresh that keeps the last good snapshot on a
|
|
3155
|
+
* transient failure (a failed refresh used to flip the whole panel back to
|
|
3156
|
+
* its loading state).
|
|
3157
|
+
*/
|
|
3158
|
+
/**
|
|
3159
|
+
* Subscribe a component to the shared status snapshot. Each mount triggers one
|
|
3160
|
+
* fetch (that is what "opening the panel" means), the returned refresh is the
|
|
3161
|
+
* button. A transient failure keeps the last good snapshot instead of flipping
|
|
3162
|
+
* the whole panel back to its loading state.
|
|
3163
|
+
*/
|
|
3164
|
+
function useAgyStatus() {
|
|
3165
|
+
const [status, setStatus] = useState(statusCache);
|
|
3166
|
+
const [loading, setLoading] = useState(statusCache === null);
|
|
3167
|
+
useEffect(() => {
|
|
3168
|
+
statusListeners.add(setStatus);
|
|
3169
|
+
void loadStatusOnce().then(() => { setLoading(false); });
|
|
3170
|
+
return () => {
|
|
3171
|
+
statusListeners.delete(setStatus);
|
|
3172
|
+
};
|
|
3173
|
+
}, []);
|
|
3174
|
+
const refresh = async () => { await loadStatusOnce(); };
|
|
3175
|
+
return { status, loading, refresh };
|
|
3176
|
+
}
|
|
3177
|
+
/**
|
|
3178
|
+
* Format reset timestamp into readable date/time + relative countdown.
|
|
3179
|
+
*/
|
|
3180
|
+
function formatQuotaWindow(t, resetTimeStr) {
|
|
3181
|
+
if (!resetTimeStr)
|
|
3182
|
+
return { resetText: '' };
|
|
3183
|
+
try {
|
|
3184
|
+
const d = new Date(resetTimeStr);
|
|
3185
|
+
const diffMs = d.getTime() - Date.now();
|
|
3186
|
+
if (diffMs <= 0)
|
|
3187
|
+
return { resetText: '' };
|
|
3188
|
+
const totalMins = Math.ceil(diffMs / 60000);
|
|
3189
|
+
const days = Math.floor(totalMins / 1440);
|
|
3190
|
+
const hours = Math.floor((totalMins % 1440) / 60);
|
|
3191
|
+
const mins = totalMins % 60;
|
|
3192
|
+
const isWeekly = days >= 1;
|
|
3193
|
+
const countdown = days > 0
|
|
3194
|
+
? `${days}${t('duration.day')} ${hours}${t('duration.hour')}`
|
|
3195
|
+
: hours > 0 ? `${hours}${t('duration.hour')} ${mins}${t('duration.minute')}` : `${mins}${t('duration.minute')}`;
|
|
3196
|
+
const hh = d.getHours().toString().padStart(2, '0');
|
|
3197
|
+
const mm = d.getMinutes().toString().padStart(2, '0');
|
|
3198
|
+
const resetText = isWeekly
|
|
3199
|
+
? `${d.getMonth() + 1}/${d.getDate()} ${hh}:${mm} (${countdown})`
|
|
3200
|
+
: `${hh}:${mm} (${countdown})`;
|
|
3201
|
+
return { resetText };
|
|
3202
|
+
}
|
|
3203
|
+
catch {
|
|
3204
|
+
return { resetText: '' };
|
|
3205
|
+
}
|
|
3206
|
+
}
|
|
3207
|
+
/** Inline brand logo (Gemini / Claude / OpenAI) as a crisp SVG mark. */
|
|
3208
|
+
function brandIcon(key, size = 13) {
|
|
3209
|
+
return h('svg', {
|
|
3210
|
+
viewBox: '0 0 24 24',
|
|
3211
|
+
width: size,
|
|
3212
|
+
height: size,
|
|
3213
|
+
'aria-hidden': true,
|
|
3214
|
+
style: { display: 'inline-block', verticalAlign: '-2px', flexShrink: 0 },
|
|
3215
|
+
}, h('path', { d: brand_icons_ts_1.BRAND_PATHS[key], fill: brand_icons_ts_1.BRAND_COLORS[key] }));
|
|
3216
|
+
}
|
|
3217
|
+
/** Clean outline/filled SVG icon (Lucide-style), zero emoji dependency. */
|
|
3218
|
+
function uiIcon(key, size = 12, color = 'currentColor') {
|
|
3219
|
+
const p = brand_icons_ts_1.UI_PATHS[key];
|
|
3220
|
+
const isFilled = key === 'star';
|
|
3221
|
+
return h('svg', {
|
|
3222
|
+
viewBox: '0 0 24 24',
|
|
3223
|
+
width: size,
|
|
3224
|
+
height: size,
|
|
3225
|
+
'aria-hidden': true,
|
|
3226
|
+
style: { display: 'inline-block', verticalAlign: '-1.5px', flexShrink: 0 },
|
|
3227
|
+
}, h('path', {
|
|
3228
|
+
d: p,
|
|
3229
|
+
fill: isFilled ? color : 'none',
|
|
3230
|
+
stroke: isFilled ? 'none' : color,
|
|
3231
|
+
'stroke-width': isFilled ? undefined : 2,
|
|
3232
|
+
'stroke-linecap': isFilled ? undefined : 'round',
|
|
3233
|
+
'stroke-linejoin': isFilled ? undefined : 'round',
|
|
3234
|
+
}));
|
|
3235
|
+
}
|
|
3236
|
+
const FAMILY_BRAND = {
|
|
3237
|
+
google: 'gemini',
|
|
3238
|
+
anthropic: 'claude',
|
|
3239
|
+
openai: 'openai',
|
|
3240
|
+
};
|
|
3241
|
+
let agyIconSeq = 0;
|
|
3242
|
+
/** Antigravity mark: gradient orb with an orbit ring (product has no public simple-icon). */
|
|
3243
|
+
function agyIcon(size = 14) {
|
|
3244
|
+
const gid = `agy-g${++agyIconSeq}`;
|
|
3245
|
+
return h('svg', {
|
|
3246
|
+
viewBox: '0 0 24 24',
|
|
3247
|
+
width: size,
|
|
3248
|
+
height: size,
|
|
3249
|
+
'aria-hidden': true,
|
|
3250
|
+
style: { display: 'inline-block', verticalAlign: '-2px', flexShrink: 0 },
|
|
3251
|
+
}, h('defs', null, h('linearGradient', { id: gid, x1: '0', y1: '0', x2: '1', y2: '1' }, h('stop', { offset: '0', 'stop-color': '#4C8DFF' }), h('stop', { offset: '1', 'stop-color': '#A96BFF' }))), h('circle', { cx: 12, cy: 12, r: 6, fill: `url(#${gid})` }), h('ellipse', {
|
|
3252
|
+
cx: 12, cy: 12, rx: 10.4, ry: 3.9,
|
|
3253
|
+
fill: 'none', stroke: `url(#${gid})`, 'stroke-width': 1.4,
|
|
3254
|
+
transform: 'rotate(-18 12 12)',
|
|
3255
|
+
}));
|
|
3256
|
+
}
|
|
3257
|
+
const GLOBAL_CSS = `
|
|
3258
|
+
@keyframes agy-spin {
|
|
3259
|
+
0% { transform: rotate(0deg); }
|
|
3260
|
+
100% { transform: rotate(360deg); }
|
|
3261
|
+
}
|
|
3262
|
+
|
|
3263
|
+
:root, body {
|
|
3264
|
+
--agy-font-family: var(--dsw-font-family, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC", "Microsoft YaHei", sans-serif);
|
|
3265
|
+
--agy-bg-panel: var(--dsw-alias-bg-layer-2, #ffffff);
|
|
3266
|
+
--agy-bg-card: var(--dsw-alias-bg-layer-2, #ffffff);
|
|
3267
|
+
--agy-bg-card-primary: #f0f7ff;
|
|
3268
|
+
--agy-bg-header: var(--dsw-alias-bg-layer-1, #f8fafc);
|
|
3269
|
+
--agy-bg-box: var(--dsw-alias-bg-layer-1, #f8fafc);
|
|
3270
|
+
--agy-bg-subbox: var(--dsw-alias-bg-layer-3, #f1f5f9);
|
|
3271
|
+
--agy-bg-input: var(--dsw-alias-bg-layer-1, #ffffff);
|
|
3272
|
+
--agy-bg-btn: var(--dsw-alias-bg-layer-3, #f1f5f9);
|
|
3273
|
+
--agy-bg-btn-hover: var(--dsw-alias-interactive-bg-hover, #e2e8f0);
|
|
3274
|
+
--agy-bg-btn-primary: var(--dsw-alias-state-business-primary, #2563eb);
|
|
3275
|
+
--agy-bg-progress-track: #e2e8f0;
|
|
3276
|
+
|
|
3277
|
+
--agy-border-card: var(--dsw-alias-border-l2, #e2e8f0);
|
|
3278
|
+
--agy-border-card-primary: #3b82f6;
|
|
3279
|
+
--agy-border-box: var(--dsw-alias-border-l1, #e2e8f0);
|
|
3280
|
+
--agy-border-subbox: var(--dsw-alias-border-l2, #cbd5e1);
|
|
3281
|
+
--agy-border-input: var(--dsw-alias-border-l2, #cbd5e1);
|
|
3282
|
+
--agy-border-input-focus: var(--dsw-alias-state-business-primary, #2563eb);
|
|
3283
|
+
--agy-border-btn: var(--dsw-alias-border-l2, #cbd5e1);
|
|
3284
|
+
--agy-border-btn-primary: #2563eb;
|
|
3285
|
+
--agy-border-divider: var(--dsw-alias-border-l2, #e2e8f0);
|
|
3286
|
+
|
|
3287
|
+
--agy-text-primary: var(--dsw-alias-label-primary, #0f172a);
|
|
3288
|
+
--agy-text-secondary: var(--dsw-alias-label-secondary, #334155);
|
|
3289
|
+
--agy-text-tertiary: var(--dsw-alias-label-tertiary, #64748b);
|
|
3290
|
+
--agy-text-muted: var(--dsw-alias-label-caption, #94a3b8);
|
|
3291
|
+
--agy-text-btn: var(--dsw-alias-label-primary, #0f172a);
|
|
3292
|
+
--agy-text-btn-primary: #ffffff;
|
|
3293
|
+
--agy-text-window-label: #2563eb;
|
|
3294
|
+
|
|
3295
|
+
--agy-shadow-card: 0 1px 3px rgba(0, 0, 0, 0.05);
|
|
3296
|
+
--agy-shadow-card-hover: 0 4px 12px rgba(0, 0, 0, 0.08);
|
|
3297
|
+
--agy-shadow-card-primary: 0 0 0 1px #3b82f6, 0 4px 14px rgba(37, 99, 235, 0.12);
|
|
3298
|
+
|
|
3299
|
+
/* Badges */
|
|
3300
|
+
--agy-badge-primary-bg: #dbeafe;
|
|
3301
|
+
--agy-badge-primary-text: #1e40af;
|
|
3302
|
+
--agy-badge-primary-border: #60a5fa;
|
|
3303
|
+
|
|
3304
|
+
--agy-badge-tag-bg: #f1f5f9;
|
|
3305
|
+
--agy-badge-tag-text: #334155;
|
|
3306
|
+
--agy-badge-tag-border: #cbd5e1;
|
|
3307
|
+
|
|
3308
|
+
--agy-badge-email-bg: #eff6ff;
|
|
3309
|
+
--agy-badge-email-text: #1d4ed8;
|
|
3310
|
+
--agy-badge-email-border: #bfdbfe;
|
|
3311
|
+
|
|
3312
|
+
--agy-badge-proxy-bg: #ecfdf5;
|
|
3313
|
+
--agy-badge-proxy-text: #047857;
|
|
3314
|
+
--agy-badge-proxy-border: #a7f3d0;
|
|
3315
|
+
|
|
3316
|
+
--agy-badge-ready-bg: #ecfdf5;
|
|
3317
|
+
--agy-badge-ready-text: #047857;
|
|
3318
|
+
--agy-badge-ready-border: #a7f3d0;
|
|
3319
|
+
|
|
3320
|
+
--agy-badge-unready-bg: #eff6ff;
|
|
3321
|
+
--agy-badge-unready-text: #1d4ed8;
|
|
3322
|
+
--agy-badge-unready-border: #bfdbfe;
|
|
3323
|
+
|
|
3324
|
+
/* Quota status pills */
|
|
3325
|
+
--agy-quota-high-bg: #ecfdf5;
|
|
3326
|
+
--agy-quota-high-text: #047857;
|
|
3327
|
+
--agy-quota-high-border: #a7f3d0;
|
|
3328
|
+
--agy-quota-med-bg: #fffbeb;
|
|
3329
|
+
--agy-quota-med-text: #b45309;
|
|
3330
|
+
--agy-quota-med-border: #fde68a;
|
|
3331
|
+
--agy-quota-low-bg: #fef2f2;
|
|
3332
|
+
--agy-quota-low-text: #b91c1c;
|
|
3333
|
+
--agy-quota-low-border: #fecaca;
|
|
3334
|
+
--agy-quota-none-bg: #f1f5f9;
|
|
3335
|
+
--agy-quota-none-text: #64748b;
|
|
3336
|
+
--agy-quota-none-border: #cbd5e1;
|
|
3337
|
+
|
|
3338
|
+
/* Danger button */
|
|
3339
|
+
--agy-danger-bg: #fef2f2;
|
|
3340
|
+
--agy-danger-text: #b91c1c;
|
|
3341
|
+
--agy-danger-border: #fecaca;
|
|
3342
|
+
--agy-danger-hover-bg: #fee2e2;
|
|
3343
|
+
|
|
3344
|
+
/* Warning banner */
|
|
3345
|
+
--agy-warn-bg: #fffbeb;
|
|
3346
|
+
--agy-warn-text: #92400e;
|
|
3347
|
+
--agy-warn-border: #fcd34d;
|
|
3348
|
+
--agy-warn-btn-bg: #fef3c7;
|
|
3349
|
+
--agy-warn-btn-text: #92400e;
|
|
3350
|
+
--agy-warn-btn-border: #f59e0b;
|
|
3351
|
+
|
|
3352
|
+
/* Toast notices */
|
|
3353
|
+
--agy-toast-success-bg: #ecfdf5;
|
|
3354
|
+
--agy-toast-success-text: #065f46;
|
|
3355
|
+
--agy-toast-success-border: #6ee7b7;
|
|
3356
|
+
--agy-toast-info-bg: #eff6ff;
|
|
3357
|
+
--agy-toast-info-text: #1e40af;
|
|
3358
|
+
--agy-toast-info-border: #93c5fd;
|
|
3359
|
+
--agy-toast-warn-bg: #fffbeb;
|
|
3360
|
+
--agy-toast-warn-text: #92400e;
|
|
3361
|
+
--agy-toast-warn-border: #fcd34d;
|
|
3362
|
+
--agy-toast-error-bg: #fef2f2;
|
|
3363
|
+
--agy-toast-error-text: #991b1b;
|
|
3364
|
+
--agy-toast-error-border: #fca5a5;
|
|
3365
|
+
|
|
3366
|
+
/* Segment control */
|
|
3367
|
+
--agy-seg-group-bg: #e2e8f0;
|
|
3368
|
+
--agy-seg-group-border: #cbd5e1;
|
|
3369
|
+
--agy-seg-btn-text: #475569;
|
|
3370
|
+
--agy-seg-btn-active-bg: var(--dsw-alias-state-business-primary, #2563eb);
|
|
3371
|
+
--agy-seg-btn-active-text: #ffffff;
|
|
3372
|
+
--agy-seg-danger-bg: #dc2626;
|
|
3373
|
+
--agy-seg-danger-text: #ffffff;
|
|
3374
|
+
--agy-seg-danger-border: #b91c1c;
|
|
3375
|
+
}
|
|
3376
|
+
|
|
3377
|
+
body[data-ds-dark-theme],
|
|
3378
|
+
body.dark,
|
|
3379
|
+
[data-theme="dark"] {
|
|
3380
|
+
--agy-bg-panel: var(--dsw-alias-bg-layer-2, #0f172a);
|
|
3381
|
+
--agy-bg-card: var(--dsw-alias-bg-layer-2, #1e293b);
|
|
3382
|
+
--agy-bg-card-primary: #111d33;
|
|
3383
|
+
--agy-bg-header: var(--dsw-alias-bg-layer-1, #1e293b);
|
|
3384
|
+
--agy-bg-box: var(--dsw-alias-bg-layer-1, #0f172a);
|
|
3385
|
+
--agy-bg-subbox: var(--dsw-alias-bg-layer-3, #090d16);
|
|
3386
|
+
--agy-bg-input: var(--dsw-alias-bg-layer-3, #090d16);
|
|
3387
|
+
--agy-bg-btn: var(--dsw-alias-bg-layer-3, #334155);
|
|
3388
|
+
--agy-bg-btn-hover: var(--dsw-alias-interactive-bg-hover, #475569);
|
|
3389
|
+
--agy-bg-btn-primary: var(--dsw-alias-state-business-primary, #2563eb);
|
|
3390
|
+
--agy-bg-progress-track: #1e293b;
|
|
3391
|
+
|
|
3392
|
+
--agy-border-card: var(--dsw-alias-border-l2, #334155);
|
|
3393
|
+
--agy-border-card-primary: #3b82f6;
|
|
3394
|
+
--agy-border-box: var(--dsw-alias-border-l1, #1e293b);
|
|
3395
|
+
--agy-border-subbox: var(--dsw-alias-border-l2, #334155);
|
|
3396
|
+
--agy-border-input: var(--dsw-alias-border-l2, #334155);
|
|
3397
|
+
--agy-border-input-focus: #3b82f6;
|
|
3398
|
+
--agy-border-btn: var(--dsw-alias-border-l2, #475569);
|
|
3399
|
+
--agy-border-btn-primary: #3b82f6;
|
|
3400
|
+
--agy-border-divider: var(--dsw-alias-border-l2, #334155);
|
|
3401
|
+
|
|
3402
|
+
--agy-text-primary: var(--dsw-alias-label-primary, #f8fafc);
|
|
3403
|
+
--agy-text-secondary: var(--dsw-alias-label-secondary, #cbd5e1);
|
|
3404
|
+
--agy-text-tertiary: var(--dsw-alias-label-tertiary, #94a3b8);
|
|
3405
|
+
--agy-text-muted: var(--dsw-alias-label-caption, #64748b);
|
|
3406
|
+
--agy-text-btn: var(--dsw-alias-label-primary, #ffffff);
|
|
3407
|
+
--agy-text-btn-primary: #ffffff;
|
|
3408
|
+
--agy-text-window-label: #93c5fd;
|
|
3409
|
+
|
|
3410
|
+
--agy-shadow-card: 0 2px 8px rgba(0, 0, 0, 0.3);
|
|
3411
|
+
--agy-shadow-card-hover: 0 6px 16px rgba(0, 0, 0, 0.4);
|
|
3412
|
+
--agy-shadow-card-primary: 0 0 16px rgba(59, 130, 246, 0.25);
|
|
3413
|
+
|
|
3414
|
+
/* Badges */
|
|
3415
|
+
--agy-badge-primary-bg: #1e3a8a;
|
|
3416
|
+
--agy-badge-primary-text: #93c5fd;
|
|
3417
|
+
--agy-badge-primary-border: #3b82f6;
|
|
3418
|
+
|
|
3419
|
+
--agy-badge-tag-bg: #0f172a;
|
|
3420
|
+
--agy-badge-tag-text: #cbd5e1;
|
|
3421
|
+
--agy-badge-tag-border: #334155;
|
|
3422
|
+
|
|
3423
|
+
--agy-badge-email-bg: #1e293b;
|
|
3424
|
+
--agy-badge-email-text: #93c5fd;
|
|
3425
|
+
--agy-badge-email-border: #3b82f6;
|
|
3426
|
+
|
|
3427
|
+
--agy-badge-proxy-bg: #064e3b;
|
|
3428
|
+
--agy-badge-proxy-text: #6ee7b7;
|
|
3429
|
+
--agy-badge-proxy-border: #059669;
|
|
3430
|
+
|
|
3431
|
+
--agy-badge-ready-bg: rgba(16, 185, 129, 0.15);
|
|
3432
|
+
--agy-badge-ready-text: #34d399;
|
|
3433
|
+
--agy-badge-ready-border: rgba(16, 185, 129, 0.35);
|
|
3434
|
+
|
|
3435
|
+
--agy-badge-unready-bg: rgba(59, 130, 246, 0.15);
|
|
3436
|
+
--agy-badge-unready-text: #93c5fd;
|
|
3437
|
+
--agy-badge-unready-border: rgba(59, 130, 246, 0.35);
|
|
3438
|
+
|
|
3439
|
+
/* Quota status pills */
|
|
3440
|
+
--agy-quota-high-bg: #064e3b;
|
|
3441
|
+
--agy-quota-high-text: #6ee7b7;
|
|
3442
|
+
--agy-quota-high-border: #059669;
|
|
3443
|
+
--agy-quota-med-bg: #451a03;
|
|
3444
|
+
--agy-quota-med-text: #fde68a;
|
|
3445
|
+
--agy-quota-med-border: #d97706;
|
|
3446
|
+
--agy-quota-low-bg: #450a0a;
|
|
3447
|
+
--agy-quota-low-text: #fca5a5;
|
|
3448
|
+
--agy-quota-low-border: #dc2626;
|
|
3449
|
+
--agy-quota-none-bg: #1e293b;
|
|
3450
|
+
--agy-quota-none-text: #94a3b8;
|
|
3451
|
+
--agy-quota-none-border: #475569;
|
|
3452
|
+
|
|
3453
|
+
/* Danger button */
|
|
3454
|
+
--agy-danger-bg: #450a0a;
|
|
3455
|
+
--agy-danger-text: #fca5a5;
|
|
3456
|
+
--agy-danger-border: #ef4444;
|
|
3457
|
+
--agy-danger-hover-bg: #5f1313;
|
|
3458
|
+
|
|
3459
|
+
/* Warning banner */
|
|
3460
|
+
--agy-warn-bg: #451a03;
|
|
3461
|
+
--agy-warn-text: #fde68a;
|
|
3462
|
+
--agy-warn-border: #d97706;
|
|
3463
|
+
--agy-warn-btn-bg: #78350f;
|
|
3464
|
+
--agy-warn-btn-text: #fde68a;
|
|
3465
|
+
--agy-warn-btn-border: #d97706;
|
|
3466
|
+
|
|
3467
|
+
/* Toast notices */
|
|
3468
|
+
--agy-toast-success-bg: #064e3b;
|
|
3469
|
+
--agy-toast-success-text: #6ee7b7;
|
|
3470
|
+
--agy-toast-success-border: #059669;
|
|
3471
|
+
--agy-toast-info-bg: #1e3a8a;
|
|
3472
|
+
--agy-toast-info-text: #93c5fd;
|
|
3473
|
+
--agy-toast-info-border: #3b82f6;
|
|
3474
|
+
--agy-toast-warn-bg: #451a03;
|
|
3475
|
+
--agy-toast-warn-text: #fde68a;
|
|
3476
|
+
--agy-toast-warn-border: #d97706;
|
|
3477
|
+
--agy-toast-error-bg: #450a0a;
|
|
3478
|
+
--agy-toast-error-text: #fca5a5;
|
|
3479
|
+
--agy-toast-error-border: #dc2626;
|
|
3480
|
+
|
|
3481
|
+
/* Segment control */
|
|
3482
|
+
--agy-seg-group-bg: #090d16;
|
|
3483
|
+
--agy-seg-group-border: #334155;
|
|
3484
|
+
--agy-seg-btn-text: #94a3b8;
|
|
3485
|
+
--agy-seg-btn-active-bg: var(--dsw-alias-state-business-primary, #2563eb);
|
|
3486
|
+
--agy-seg-btn-active-text: #ffffff;
|
|
3487
|
+
--agy-seg-danger-bg: #7f1d1d;
|
|
3488
|
+
--agy-seg-danger-text: #fca5a5;
|
|
3489
|
+
--agy-seg-danger-border: #ef4444;
|
|
3490
|
+
}
|
|
3491
|
+
|
|
3492
|
+
.agy-spinner {
|
|
3493
|
+
display: inline-block;
|
|
3494
|
+
width: 12px;
|
|
3495
|
+
height: 12px;
|
|
3496
|
+
border: 2px solid currentColor;
|
|
3497
|
+
border-top-color: transparent;
|
|
3498
|
+
border-radius: 50%;
|
|
3499
|
+
animation: agy-spin 0.75s linear infinite;
|
|
3500
|
+
vertical-align: -2px;
|
|
3501
|
+
margin-right: 5px;
|
|
3502
|
+
}
|
|
3503
|
+
.agy-pulse-dot {
|
|
3504
|
+
display: inline-block;
|
|
3505
|
+
width: 8px;
|
|
3506
|
+
height: 8px;
|
|
3507
|
+
border-radius: 50%;
|
|
3508
|
+
}
|
|
3509
|
+
.agy-card-hover {
|
|
3510
|
+
transition: border-color 0.2s ease, box-shadow 0.2s ease, transform 0.15s ease;
|
|
3511
|
+
}
|
|
3512
|
+
.agy-card-hover:hover {
|
|
3513
|
+
border-color: var(--agy-border-card-primary, #3b82f6) !important;
|
|
3514
|
+
box-shadow: var(--agy-shadow-card-hover) !important;
|
|
3515
|
+
}
|
|
3516
|
+
.agy-btn {
|
|
3517
|
+
transition: all 0.15s ease;
|
|
3518
|
+
user-select: none;
|
|
3519
|
+
}
|
|
3520
|
+
.agy-btn:hover:not(:disabled) {
|
|
3521
|
+
filter: brightness(1.08);
|
|
3522
|
+
transform: translateY(-0.5px);
|
|
3523
|
+
}
|
|
3524
|
+
.agy-btn:active:not(:disabled) {
|
|
3525
|
+
transform: translateY(0.5px);
|
|
3526
|
+
}
|
|
3527
|
+
.agy-progress-fill {
|
|
3528
|
+
transition: width 0.4s cubic-bezier(0.16, 1, 0.3, 1);
|
|
3529
|
+
}
|
|
3530
|
+
.agy-submodel-row {
|
|
3531
|
+
display: flex;
|
|
3532
|
+
align-items: center;
|
|
3533
|
+
justify-content: space-between;
|
|
3534
|
+
padding: 7px 10px;
|
|
3535
|
+
font-size: 11.5px;
|
|
3536
|
+
border-radius: 6px;
|
|
3537
|
+
background: var(--agy-bg-subbox);
|
|
3538
|
+
border: 1px solid var(--agy-border-box);
|
|
3539
|
+
margin: 4px 0;
|
|
3540
|
+
color: var(--agy-text-primary);
|
|
3541
|
+
}
|
|
3542
|
+
.agy-select {
|
|
3543
|
+
appearance: none;
|
|
3544
|
+
-webkit-appearance: none;
|
|
3545
|
+
font: inherit;
|
|
3546
|
+
font-size: 11.5px;
|
|
3547
|
+
font-weight: 600;
|
|
3548
|
+
color: var(--agy-text-primary);
|
|
3549
|
+
background-color: var(--agy-bg-subbox);
|
|
3550
|
+
border: 1px solid var(--agy-border-box);
|
|
3551
|
+
border-radius: 6px;
|
|
3552
|
+
padding: 4px 20px 4px 8px;
|
|
3553
|
+
max-width: 190px;
|
|
3554
|
+
cursor: pointer;
|
|
3555
|
+
background-image: linear-gradient(45deg, transparent 50%, currentColor 50%), linear-gradient(135deg, currentColor 50%, transparent 50%);
|
|
3556
|
+
background-position: calc(100% - 11px) calc(50% + 1px), calc(100% - 7px) calc(50% + 1px);
|
|
3557
|
+
background-size: 4px 4px, 4px 4px;
|
|
3558
|
+
background-repeat: no-repeat;
|
|
3559
|
+
}
|
|
3560
|
+
.agy-select:hover:not(:disabled) {
|
|
3561
|
+
border-color: var(--agy-border-box);
|
|
3562
|
+
filter: brightness(1.05);
|
|
3563
|
+
}
|
|
3564
|
+
.agy-select:disabled {
|
|
3565
|
+
opacity: 0.55;
|
|
3566
|
+
cursor: default;
|
|
3567
|
+
}
|
|
3568
|
+
.agy-checklist {
|
|
3569
|
+
display: flex;
|
|
3570
|
+
flex-wrap: wrap;
|
|
3571
|
+
gap: 3px 14px;
|
|
3572
|
+
max-height: 112px;
|
|
3573
|
+
overflow-y: auto;
|
|
3574
|
+
padding: 6px 8px;
|
|
3575
|
+
border-radius: 6px;
|
|
3576
|
+
background: var(--agy-bg-subbox);
|
|
3577
|
+
border: 1px solid var(--agy-border-box);
|
|
3578
|
+
}
|
|
3579
|
+
.agy-check-item {
|
|
3580
|
+
display: inline-flex;
|
|
3581
|
+
align-items: center;
|
|
3582
|
+
gap: 5px;
|
|
3583
|
+
font-size: 11.5px;
|
|
3584
|
+
color: var(--agy-text-primary);
|
|
3585
|
+
cursor: pointer;
|
|
3586
|
+
white-space: nowrap;
|
|
3587
|
+
}
|
|
3588
|
+
.agy-check-item input {
|
|
3589
|
+
cursor: pointer;
|
|
3590
|
+
margin: 0;
|
|
3591
|
+
}
|
|
3592
|
+
`;
|
|
3593
|
+
const S = {
|
|
3594
|
+
container: {
|
|
3595
|
+
lineHeight: 1.5,
|
|
3596
|
+
fontSize: '13px',
|
|
3597
|
+
fontFamily: 'var(--agy-font-family)',
|
|
3598
|
+
color: 'var(--agy-text-primary)',
|
|
3599
|
+
},
|
|
3600
|
+
headerCard: {
|
|
3601
|
+
display: 'flex',
|
|
3602
|
+
alignItems: 'center',
|
|
3603
|
+
justifyContent: 'space-between',
|
|
3604
|
+
padding: '12px 16px',
|
|
3605
|
+
background: 'var(--agy-bg-header)',
|
|
3606
|
+
border: '1px solid var(--agy-border-card)',
|
|
3607
|
+
borderRadius: '10px',
|
|
3608
|
+
marginBottom: '12px',
|
|
3609
|
+
color: 'var(--agy-text-primary)',
|
|
3610
|
+
boxShadow: 'var(--agy-shadow-card)',
|
|
3611
|
+
},
|
|
3612
|
+
badgePrimary: {
|
|
3613
|
+
display: 'inline-flex',
|
|
3614
|
+
alignItems: 'center',
|
|
3615
|
+
gap: '4px',
|
|
3616
|
+
background: 'var(--agy-badge-primary-bg)',
|
|
3617
|
+
color: 'var(--agy-badge-primary-text)',
|
|
3618
|
+
border: '1px solid var(--agy-badge-primary-border)',
|
|
3619
|
+
padding: '2px 8px',
|
|
3620
|
+
borderRadius: '5px',
|
|
3621
|
+
fontSize: '11px',
|
|
3622
|
+
fontWeight: 700,
|
|
3623
|
+
},
|
|
3624
|
+
badgeTag: {
|
|
3625
|
+
display: 'inline-flex',
|
|
3626
|
+
alignItems: 'center',
|
|
3627
|
+
gap: '4px',
|
|
3628
|
+
background: 'var(--agy-badge-tag-bg)',
|
|
3629
|
+
color: 'var(--agy-badge-tag-text)',
|
|
3630
|
+
border: '1px solid var(--agy-badge-tag-border)',
|
|
3631
|
+
padding: '2px 7px',
|
|
3632
|
+
borderRadius: '5px',
|
|
3633
|
+
fontSize: '11px',
|
|
3634
|
+
fontWeight: 600,
|
|
3635
|
+
},
|
|
3636
|
+
badgeReady: {
|
|
3637
|
+
display: 'inline-flex',
|
|
3638
|
+
alignItems: 'center',
|
|
3639
|
+
gap: '4px',
|
|
3640
|
+
background: 'var(--agy-badge-ready-bg)',
|
|
3641
|
+
color: 'var(--agy-badge-ready-text)',
|
|
3642
|
+
border: '1px solid var(--agy-badge-ready-border)',
|
|
3643
|
+
padding: '2px 8px',
|
|
3644
|
+
borderRadius: '5px',
|
|
3645
|
+
fontSize: '11px',
|
|
3646
|
+
fontWeight: 700,
|
|
3647
|
+
},
|
|
3648
|
+
badgeUnready: {
|
|
3649
|
+
display: 'inline-flex',
|
|
3650
|
+
alignItems: 'center',
|
|
3651
|
+
gap: '4px',
|
|
3652
|
+
background: 'var(--agy-badge-unready-bg)',
|
|
3653
|
+
color: 'var(--agy-badge-unready-text)',
|
|
3654
|
+
border: '1px solid var(--agy-badge-unready-border)',
|
|
3655
|
+
padding: '2px 8px',
|
|
3656
|
+
borderRadius: '5px',
|
|
3657
|
+
fontSize: '11px',
|
|
3658
|
+
fontWeight: 700,
|
|
3659
|
+
},
|
|
3660
|
+
card: {
|
|
3661
|
+
background: 'var(--agy-bg-card)',
|
|
3662
|
+
border: '1px solid var(--agy-border-card)',
|
|
3663
|
+
borderRadius: '10px',
|
|
3664
|
+
padding: '14px 16px',
|
|
3665
|
+
marginBottom: '12px',
|
|
3666
|
+
color: 'var(--agy-text-primary)',
|
|
3667
|
+
boxShadow: 'var(--agy-shadow-card)',
|
|
3668
|
+
},
|
|
3669
|
+
cardPrimary: {
|
|
3670
|
+
background: 'var(--agy-bg-card-primary)',
|
|
3671
|
+
border: '1.5px solid var(--agy-border-card-primary)',
|
|
3672
|
+
borderRadius: '10px',
|
|
3673
|
+
padding: '14px 16px',
|
|
3674
|
+
marginBottom: '12px',
|
|
3675
|
+
boxShadow: 'var(--agy-shadow-card-primary)',
|
|
3676
|
+
color: 'var(--agy-text-primary)',
|
|
3677
|
+
},
|
|
3678
|
+
quotaBox: {
|
|
3679
|
+
background: 'var(--agy-bg-box)',
|
|
3680
|
+
border: '1px solid var(--agy-border-box)',
|
|
3681
|
+
borderRadius: '8px',
|
|
3682
|
+
padding: '8px 10px',
|
|
3683
|
+
marginTop: '10px',
|
|
3684
|
+
},
|
|
3685
|
+
progressBarBg: {
|
|
3686
|
+
flex: '1',
|
|
3687
|
+
height: '8px',
|
|
3688
|
+
borderRadius: '4px',
|
|
3689
|
+
background: 'var(--agy-bg-progress-track)',
|
|
3690
|
+
border: '1px solid var(--agy-border-box)',
|
|
3691
|
+
overflow: 'hidden',
|
|
3692
|
+
margin: '0 10px',
|
|
3693
|
+
},
|
|
3694
|
+
btn: {
|
|
3695
|
+
display: 'inline-flex',
|
|
3696
|
+
alignItems: 'center',
|
|
3697
|
+
justifyContent: 'center',
|
|
3698
|
+
padding: '5px 12px',
|
|
3699
|
+
borderRadius: '6px',
|
|
3700
|
+
border: '1px solid var(--agy-border-btn)',
|
|
3701
|
+
background: 'var(--agy-bg-btn)',
|
|
3702
|
+
color: 'var(--agy-text-btn)',
|
|
3703
|
+
cursor: 'pointer',
|
|
3704
|
+
fontSize: '12px',
|
|
3705
|
+
fontWeight: 600,
|
|
3706
|
+
},
|
|
3707
|
+
btnPrimary: {
|
|
3708
|
+
display: 'inline-flex',
|
|
3709
|
+
alignItems: 'center',
|
|
3710
|
+
justifyContent: 'center',
|
|
3711
|
+
padding: '5px 13px',
|
|
3712
|
+
borderRadius: '6px',
|
|
3713
|
+
border: '1px solid var(--agy-border-btn-primary)',
|
|
3714
|
+
background: 'var(--agy-bg-btn-primary)',
|
|
3715
|
+
color: 'var(--agy-text-btn-primary)',
|
|
3716
|
+
cursor: 'pointer',
|
|
3717
|
+
fontSize: '12px',
|
|
3718
|
+
fontWeight: 700,
|
|
3719
|
+
boxShadow: '0 2px 6px rgba(37, 99, 235, 0.35)',
|
|
3720
|
+
},
|
|
3721
|
+
btnDanger: {
|
|
3722
|
+
display: 'inline-flex',
|
|
3723
|
+
alignItems: 'center',
|
|
3724
|
+
justifyContent: 'center',
|
|
3725
|
+
padding: '4px 9px',
|
|
3726
|
+
borderRadius: '6px',
|
|
3727
|
+
border: '1px solid var(--agy-danger-border)',
|
|
3728
|
+
background: 'var(--agy-danger-bg)',
|
|
3729
|
+
color: 'var(--agy-danger-text)',
|
|
3730
|
+
cursor: 'pointer',
|
|
3731
|
+
fontSize: '11px',
|
|
3732
|
+
fontWeight: 600,
|
|
3733
|
+
},
|
|
3734
|
+
input: {
|
|
3735
|
+
flex: '1',
|
|
3736
|
+
padding: '7px 12px',
|
|
3737
|
+
borderRadius: '6px',
|
|
3738
|
+
border: '1.5px solid var(--agy-border-input)',
|
|
3739
|
+
background: 'var(--agy-bg-input)',
|
|
3740
|
+
color: 'var(--agy-text-primary)',
|
|
3741
|
+
fontSize: '12.5px',
|
|
3742
|
+
outline: 'none',
|
|
3743
|
+
},
|
|
3744
|
+
muted: { color: 'var(--agy-text-tertiary)', fontSize: '11.5px' },
|
|
3745
|
+
noticeBanner: {
|
|
3746
|
+
display: 'flex',
|
|
3747
|
+
alignItems: 'center',
|
|
3748
|
+
justifyContent: 'space-between',
|
|
3749
|
+
padding: '10px 14px',
|
|
3750
|
+
borderRadius: '8px',
|
|
3751
|
+
marginBottom: '12px',
|
|
3752
|
+
fontSize: '12.5px',
|
|
3753
|
+
fontWeight: 500,
|
|
3754
|
+
},
|
|
3755
|
+
};
|
|
3756
|
+
function apply(ctx) {
|
|
3757
|
+
// locale is declared in inject — cordis guarantees ctx.locale (issue #24).
|
|
3758
|
+
const locale = ctx.locale;
|
|
3759
|
+
ctx.effect(() => {
|
|
3760
|
+
const disposePtBR = locale.addLanguage({ id: 'pt-BR', label: 'Português (Brasil)', fallback: 'en' });
|
|
3761
|
+
const disposeEs = locale.addLanguage({ id: 'es', label: 'Español', fallback: 'en' });
|
|
3762
|
+
// Untyped single-locale form: 'agy-link' is not in the host LocaleNamespaceMap merge table.
|
|
3763
|
+
const disposeZh = locale.register(locales_ts_1.NS, 'zh', locales_ts_1.zh);
|
|
3764
|
+
const disposeEn = locale.register(locales_ts_1.NS, 'en', locales_ts_1.en);
|
|
3765
|
+
const disposePtBRDictionary = locale.register(locales_ts_1.NS, 'pt-BR', locales_ts_1.ptBR);
|
|
3766
|
+
const disposeEsDictionary = locale.register(locales_ts_1.NS, 'es', locales_ts_1.es);
|
|
3767
|
+
return () => { disposeEsDictionary(); disposePtBRDictionary(); disposeEn(); disposeZh(); disposeEs(); disposePtBR(); };
|
|
3768
|
+
}, 'agy-cli: locale dictionaries');
|
|
3769
|
+
const useAgyTranslation = () => {
|
|
3770
|
+
const [, rerender] = useState(0);
|
|
3771
|
+
useEffect(() => locale.subscribe(() => rerender((revision) => revision + 1)), []);
|
|
3772
|
+
return locale.bind(locales_ts_1.NS);
|
|
3773
|
+
};
|
|
3774
|
+
const AgySettingsSection = (props) => {
|
|
3775
|
+
// Reported during render, not from an effect, and deliberately so: the
|
|
3776
|
+
// host wraps every slot entry in an error boundary that renders an EMPTY
|
|
3777
|
+
// element on a throw (`SlotErrorBoundary` → a bare anchor div). A card
|
|
3778
|
+
// that crashes on its first render therefore looks exactly like a card
|
|
3779
|
+
// the host never dispatched — an effect would never run in the crashing
|
|
3780
|
+
// case, which is the case worth distinguishing. Reporting at the top of
|
|
3781
|
+
// the body survives both; `reportCardDispatch` de-duplicates re-renders
|
|
3782
|
+
// and StrictMode's double render. See `/plugins/agy-cli/diagnose`.
|
|
3783
|
+
reportCardDispatch(props);
|
|
3784
|
+
const t = useAgyTranslation();
|
|
3785
|
+
const { status, loading, refresh } = useAgyStatus();
|
|
3786
|
+
const [loadingAction, setLoadingAction] = useState(null);
|
|
3787
|
+
const [toast, setToast] = useState(null);
|
|
3788
|
+
// Cooldown expiry and quota countdowns are Date.now()-derived while the
|
|
3789
|
+
// payload itself does NOT change when they lapse — without a wall-clock
|
|
3790
|
+
// tick the "cooldown" banner and the minutes-left froze forever.
|
|
3791
|
+
const [, setTimeTick] = useState(0);
|
|
3792
|
+
useEffect(() => {
|
|
3793
|
+
const timer = setInterval(() => setTimeTick((n) => n + 1), 30_000);
|
|
3794
|
+
return () => clearInterval(timer);
|
|
3795
|
+
}, []);
|
|
3796
|
+
const toastTimerRef = useRef(null);
|
|
3797
|
+
useEffect(() => () => {
|
|
3798
|
+
// Unmount must not leave a pending timer writing state into a dead
|
|
3799
|
+
// component; a second same-text toast also used to be cleared early
|
|
3800
|
+
// by the FIRST toast's leftover timer.
|
|
3801
|
+
if (toastTimerRef.current !== null)
|
|
3802
|
+
clearTimeout(toastTimerRef.current);
|
|
3803
|
+
}, []);
|
|
3804
|
+
// --- Collapse model -----------------------------------------------------
|
|
3805
|
+
// The host renders this card as a sibling of the provider editor and only
|
|
3806
|
+
// mounts that editor while the row is expanded (app.asar ModelsSection:
|
|
3807
|
+
// `renderSlot('settings.models.provider-card', {…})`, then
|
|
3808
|
+
// `open ? renderProviderEditor(…) : null`). The slot props carry no
|
|
3809
|
+
// "expanded" flag, so the editor's presence in the DOM is the only signal
|
|
3810
|
+
// available — and mirroring it is what makes 编辑 behave as the toggle for
|
|
3811
|
+
// this card too: expanding the row opens the panel, collapsing it closes
|
|
3812
|
+
// the panel. `manualOpen` lets the chevron override until the host state
|
|
3813
|
+
// changes again.
|
|
3814
|
+
//
|
|
3815
|
+
// Declared here, above the `status === null` early return, on purpose: a
|
|
3816
|
+
// hook that only runs once `/status` has answered changes this component's
|
|
3817
|
+
// hook order between the loading render and the loaded render, and React
|
|
3818
|
+
// answers that with "Rendered more hooks than during the previous render"
|
|
3819
|
+
// — the card painted the spinner, then threw on the re-render and the host
|
|
3820
|
+
// boundary replaced it with an empty element (the "only a title, nothing
|
|
3821
|
+
// else" card).
|
|
3822
|
+
const cardRef = useRef(null);
|
|
3823
|
+
const [hostOpen, setHostOpen] = useState(null);
|
|
3824
|
+
const [manualOpen, setManualOpen] = useState(null);
|
|
3825
|
+
useEffect(() => {
|
|
3826
|
+
const root = cardRef.current;
|
|
3827
|
+
if (root === null)
|
|
3828
|
+
return;
|
|
3829
|
+
const row = root.closest('li');
|
|
3830
|
+
const parent = root.parentElement;
|
|
3831
|
+
// Walk to the element whose NEXT sibling is the editor: our own root
|
|
3832
|
+
// when the slot renders it inline, otherwise the wrapper the slot core
|
|
3833
|
+
// put around it.
|
|
3834
|
+
const anchor = parent !== null && parent !== row ? parent : root;
|
|
3835
|
+
let previous = null;
|
|
3836
|
+
const probe = () => {
|
|
3837
|
+
const open = anchor.nextElementSibling !== null;
|
|
3838
|
+
if (open === previous)
|
|
3839
|
+
return;
|
|
3840
|
+
previous = open;
|
|
3841
|
+
setHostOpen(open);
|
|
3842
|
+
setManualOpen(null);
|
|
3843
|
+
};
|
|
3844
|
+
probe();
|
|
3845
|
+
const observer = new MutationObserver(probe);
|
|
3846
|
+
observer.observe(row ?? (parent ?? root), { childList: true });
|
|
3847
|
+
return () => observer.disconnect();
|
|
3848
|
+
}, []);
|
|
3849
|
+
const expanded = manualOpen ?? hostOpen ?? true;
|
|
3850
|
+
const showToast = (text, type = 'info') => {
|
|
3851
|
+
setToast({ text, type, id: Date.now() });
|
|
3852
|
+
if (toastTimerRef.current !== null)
|
|
3853
|
+
clearTimeout(toastTimerRef.current);
|
|
3854
|
+
toastTimerRef.current = setTimeout(() => {
|
|
3855
|
+
toastTimerRef.current = null;
|
|
3856
|
+
setToast((prev) => (prev !== null && prev.text === text ? null : prev));
|
|
3857
|
+
}, 6000);
|
|
3858
|
+
};
|
|
3859
|
+
const actionFailed = () => {
|
|
3860
|
+
showToast(t('api.genericError'), 'error');
|
|
3861
|
+
};
|
|
3862
|
+
const setCfg = async (key, value) => {
|
|
3863
|
+
setLoadingAction(`config:${key}`);
|
|
3864
|
+
const res = await postJson('/plugins/agy-cli/config', { key, value });
|
|
3865
|
+
if (res === null || res?.ok === false) {
|
|
3866
|
+
setLoadingAction(null);
|
|
3867
|
+
actionFailed();
|
|
3868
|
+
return;
|
|
3869
|
+
}
|
|
3870
|
+
await refresh();
|
|
3871
|
+
setLoadingAction(null);
|
|
3872
|
+
};
|
|
3873
|
+
const hiddenPendingRef = useRef(null);
|
|
3874
|
+
const toggleModelHidden = async (id, hide) => {
|
|
3875
|
+
// Merge against the PENDING set, not the last-rendered snapshot:
|
|
3876
|
+
// two rapid clicks used to read the same stale status and the
|
|
3877
|
+
// second silently overwrote the first (lost update).
|
|
3878
|
+
const next = new Set(hiddenPendingRef.current ?? status?.hiddenModels ?? []);
|
|
3879
|
+
if (hide)
|
|
3880
|
+
next.add(id);
|
|
3881
|
+
else
|
|
3882
|
+
next.delete(id);
|
|
3883
|
+
hiddenPendingRef.current = next;
|
|
3884
|
+
try {
|
|
3885
|
+
await setCfg('hiddenModels', Array.from(next));
|
|
3886
|
+
}
|
|
3887
|
+
finally {
|
|
3888
|
+
if (hiddenPendingRef.current === next)
|
|
3889
|
+
hiddenPendingRef.current = null;
|
|
3890
|
+
}
|
|
3891
|
+
};
|
|
3892
|
+
const refreshQuota = async () => {
|
|
3893
|
+
setLoadingAction('refresh:all');
|
|
3894
|
+
// Only a click that came after this call started is worth fetching.
|
|
3895
|
+
const seq = ++statusFetchSeq;
|
|
3896
|
+
const res = await postJson('/plugins/agy-cli/pool/refresh-quota', {});
|
|
3897
|
+
if (res === null || res?.ok === false) {
|
|
3898
|
+
setLoadingAction(null);
|
|
3899
|
+
actionFailed();
|
|
3900
|
+
return;
|
|
3901
|
+
}
|
|
3902
|
+
// The RPC already answered with the freshly fetched pool, so adopt it
|
|
3903
|
+
// (the click owns the sequence number; a concurrent mount fetch is
|
|
3904
|
+
// discarded) instead of asking `/status` again — that second round trip
|
|
3905
|
+
// re-reads the keyring and refetches the quotas just sent, and it is the
|
|
3906
|
+
// slow one the user feels. `/status` is still asked for everything the
|
|
3907
|
+
// pool does not carry (auth phase, catalog, overrides).
|
|
3908
|
+
const pool = res?.pool;
|
|
3909
|
+
if (pool !== undefined && pool !== null && statusCache !== null) {
|
|
3910
|
+
statusCache = { ...statusCache, pool };
|
|
3911
|
+
for (const fn of [...statusListeners]) {
|
|
3912
|
+
try {
|
|
3913
|
+
fn(statusCache);
|
|
3914
|
+
}
|
|
3915
|
+
catch { /* a dead consumer must not kill the load */ }
|
|
3916
|
+
}
|
|
3917
|
+
}
|
|
3918
|
+
const st = await getStatus();
|
|
3919
|
+
if (st !== null && seq === statusFetchSeq)
|
|
3920
|
+
notifyStatus(st);
|
|
3921
|
+
setLoadingAction(null);
|
|
3922
|
+
showToast(t('quota.refreshed'), 'success');
|
|
3923
|
+
};
|
|
3924
|
+
const authPhase = status?.auth?.phase ?? 'unknown';
|
|
3925
|
+
const pool = status?.pool;
|
|
3926
|
+
const catalogModels = status?.catalogModels ?? [];
|
|
3927
|
+
const hiddenModelSet = new Set(status?.hiddenModels ?? []);
|
|
3928
|
+
const accounts = pool?.accounts ?? [];
|
|
3929
|
+
// `accounts.length > 0` only proves the keyring entry exists, not that the
|
|
3930
|
+
// OAuth token behind it still works — the quota RPCs know that, so an
|
|
3931
|
+
// expired token must override an otherwise "ready" badge.
|
|
3932
|
+
const isAuthed = authPhase !== 'expired' && (authPhase === 'ok' || accounts.length > 0);
|
|
3933
|
+
const isBusy = loadingAction !== null;
|
|
3934
|
+
const renderSpinner = () => h('span', { className: 'agy-spinner' });
|
|
3935
|
+
const renderToastBanner = () => {
|
|
3936
|
+
if (!toast)
|
|
3937
|
+
return null;
|
|
3938
|
+
const typeStyles = {
|
|
3939
|
+
success: { background: 'var(--agy-toast-success-bg)', border: '1px solid var(--agy-toast-success-border)', color: 'var(--agy-toast-success-text)' },
|
|
3940
|
+
info: { background: 'var(--agy-toast-info-bg)', border: '1px solid var(--agy-toast-info-border)', color: 'var(--agy-toast-info-text)' },
|
|
3941
|
+
warn: { background: 'var(--agy-toast-warn-bg)', border: '1px solid var(--agy-toast-warn-border)', color: 'var(--agy-toast-warn-text)' },
|
|
3942
|
+
error: { background: 'var(--agy-toast-error-bg)', border: '1px solid var(--agy-toast-error-border)', color: 'var(--agy-toast-error-text)' },
|
|
3943
|
+
};
|
|
3944
|
+
const style = typeStyles[toast.type];
|
|
3945
|
+
return h('div', { style: { ...S.noticeBanner, ...style } }, h('div', { style: { display: 'flex', alignItems: 'center', gap: '8px', fontWeight: 600 } }, uiIcon(toast.type === 'success' ? 'check' : toast.type === 'error' || toast.type === 'warn' ? 'alert' : 'globe', 14, style.color), h('span', null, toast.text)), h('button', {
|
|
3946
|
+
type: 'button',
|
|
3947
|
+
style: { background: 'transparent', border: 'none', color: 'inherit', cursor: 'pointer', padding: '0 4px', opacity: 0.8, display: 'inline-flex', alignItems: 'center' },
|
|
3948
|
+
onClick: () => setToast(null),
|
|
3949
|
+
'aria-label': t('aria.closeToast'),
|
|
3950
|
+
}, uiIcon('x', 12)));
|
|
3951
|
+
};
|
|
3952
|
+
const renderQuotaBar = (label, familyKey, acc) => {
|
|
3953
|
+
const info = acc.quotas[familyKey];
|
|
3954
|
+
const cd = acc.cooldowns[familyKey];
|
|
3955
|
+
const inCooldown = cd && cd.cooldownUntil > Date.now();
|
|
3956
|
+
// 5-Hour limit
|
|
3957
|
+
const has5h = typeof info?.remainingFraction === 'number' && Number.isFinite(info.remainingFraction);
|
|
3958
|
+
const pct5h = has5h ? Math.max(0, Math.min(100, Math.round(info.remainingFraction * 100))) : -1;
|
|
3959
|
+
// Cooldown is a LOCAL heuristic — never overwrite the server-reported
|
|
3960
|
+
// fraction with 0% (a ghost cooldown used to show a 98%-full account as
|
|
3961
|
+
// empty). Surface it as a note next to the reset time instead.
|
|
3962
|
+
const cdNote = inCooldown ? t('account.localCooldown') : '';
|
|
3963
|
+
const w5h = formatQuotaWindow(t, info?.resetTime);
|
|
3964
|
+
// Weekly limit
|
|
3965
|
+
const hasWeekly = typeof info?.weeklyFraction === 'number' && Number.isFinite(info.weeklyFraction);
|
|
3966
|
+
const pctWeekly = hasWeekly ? Math.max(0, Math.min(100, Math.round(info.weeklyFraction * 100))) : -1;
|
|
3967
|
+
const wWeekly = formatQuotaWindow(t, info?.weeklyResetTime);
|
|
3968
|
+
const getColors = (pct) => {
|
|
3969
|
+
if (pct < 0) {
|
|
3970
|
+
return {
|
|
3971
|
+
bar: 'var(--agy-quota-none-border)',
|
|
3972
|
+
text: 'var(--agy-quota-none-text)',
|
|
3973
|
+
bg: 'var(--agy-quota-none-bg)',
|
|
3974
|
+
border: 'var(--agy-quota-none-border)',
|
|
3975
|
+
};
|
|
3976
|
+
}
|
|
3977
|
+
if (pct <= 20) {
|
|
3978
|
+
return {
|
|
3979
|
+
bar: 'linear-gradient(90deg, #dc2626, #ef4444)',
|
|
3980
|
+
text: 'var(--agy-quota-low-text)',
|
|
3981
|
+
bg: 'var(--agy-quota-low-bg)',
|
|
3982
|
+
border: 'var(--agy-quota-low-border)',
|
|
3983
|
+
};
|
|
3984
|
+
}
|
|
3985
|
+
if (pct <= 50) {
|
|
3986
|
+
return {
|
|
3987
|
+
bar: 'linear-gradient(90deg, #d97706, #f59e0b)',
|
|
3988
|
+
text: 'var(--agy-quota-med-text)',
|
|
3989
|
+
bg: 'var(--agy-quota-med-bg)',
|
|
3990
|
+
border: 'var(--agy-quota-med-border)',
|
|
3991
|
+
};
|
|
3992
|
+
}
|
|
3993
|
+
return {
|
|
3994
|
+
bar: 'linear-gradient(90deg, #059669, #10b981)',
|
|
3995
|
+
text: 'var(--agy-quota-high-text)',
|
|
3996
|
+
bg: 'var(--agy-quota-high-bg)',
|
|
3997
|
+
border: 'var(--agy-quota-high-border)',
|
|
3998
|
+
};
|
|
3999
|
+
};
|
|
4000
|
+
const c5h = getColors(pct5h);
|
|
4001
|
+
const cWeekly = getColors(pctWeekly);
|
|
4002
|
+
const renderLine = (windowName, percent, c, resetStr) => {
|
|
4003
|
+
return h('div', { style: { display: 'flex', alignItems: 'center', fontSize: '11.5px', margin: '4px 0' } }, h('span', {
|
|
4004
|
+
style: {
|
|
4005
|
+
color: 'var(--agy-text-window-label)',
|
|
4006
|
+
fontSize: '11px',
|
|
4007
|
+
fontWeight: 700,
|
|
4008
|
+
flexShrink: 0,
|
|
4009
|
+
// "5 小时额度" wrapped onto two lines inside the fixed
|
|
4010
|
+
// 52px column and pushed the bar off-centre; the label
|
|
4011
|
+
// sizes itself and never breaks mid-word now.
|
|
4012
|
+
whiteSpace: 'nowrap',
|
|
4013
|
+
paddingRight: '6px',
|
|
4014
|
+
},
|
|
4015
|
+
}, windowName), h('div', { style: S.progressBarBg }, h('div', {
|
|
4016
|
+
className: 'agy-progress-fill',
|
|
4017
|
+
style: {
|
|
4018
|
+
width: `${percent < 0 ? 100 : percent}%`,
|
|
4019
|
+
height: '100%',
|
|
4020
|
+
background: c.bar,
|
|
4021
|
+
borderRadius: '3px',
|
|
4022
|
+
},
|
|
4023
|
+
})), h('div', { style: { display: 'inline-flex', alignItems: 'center', gap: '8px', minWidth: '140px', justifyContent: 'flex-end', flexShrink: 0 } }, h('span', {
|
|
4024
|
+
style: {
|
|
4025
|
+
padding: '2px 7px',
|
|
4026
|
+
borderRadius: '4px',
|
|
4027
|
+
background: c.bg,
|
|
4028
|
+
color: c.text,
|
|
4029
|
+
border: `1px solid ${c.border}`,
|
|
4030
|
+
fontWeight: 700,
|
|
4031
|
+
fontSize: '11.5px',
|
|
4032
|
+
lineHeight: 1.2,
|
|
4033
|
+
},
|
|
4034
|
+
}, percent < 0 ? '—' : `${percent}%`), resetStr ? h('span', { style: { color: 'var(--agy-text-secondary)', fontSize: '11px', fontWeight: 500 } }, `↻ ${resetStr}`) : null));
|
|
4035
|
+
};
|
|
4036
|
+
return h('div', { style: { margin: '6px 0', padding: '8px 10px', background: 'var(--agy-bg-subbox)', borderRadius: '7px', border: '1px solid var(--agy-border-box)' } }, h('div', { style: { display: 'flex', alignItems: 'center', gap: '6px', fontWeight: 700, fontSize: '12.5px', marginBottom: '5px', color: 'var(--agy-text-primary)' } }, brandIcon(FAMILY_BRAND[familyKey], 14), h('span', null, label)), renderLine(t('quota.fiveHour'), pct5h, c5h, w5h.resetText ? w5h.resetText + cdNote : cdNote.replace(/^ · /, '')), renderLine(t('quota.weekly'), pctWeekly, cWeekly, wWeekly.resetText));
|
|
4037
|
+
};
|
|
4038
|
+
// Single account, no pool. The status endpoint always returns exactly one
|
|
4039
|
+
// account (the keyring identity), so there is no active/primary notion,
|
|
4040
|
+
// no per-account proxy override, and nothing to add or remove. The whole
|
|
4041
|
+
// multi-account UI (add flow, 设为主用, 代理, 删除, 号池调度, 清除冷却) was
|
|
4042
|
+
// wired to /plugins/agy-cli/pool/* routes this plugin never registered —
|
|
4043
|
+
// every one of those buttons could only ever fail with a generic error.
|
|
4044
|
+
// Shapes + restore procedure: docs/archived-panel-ui-20261020.md.
|
|
4045
|
+
const account = accounts[0];
|
|
4046
|
+
// The keyring OAuth token is refreshed by agy.exe itself; a 401 from the
|
|
4047
|
+
// quota RPCs therefore means "credentials lapsed", not "broken panel".
|
|
4048
|
+
// Say so instead of rendering an unexplained empty card.
|
|
4049
|
+
const quotaExpired = status?.quotaError === 'unauthorized';
|
|
4050
|
+
const expiredBanner = quotaExpired
|
|
4051
|
+
? h('div', {
|
|
4052
|
+
style: {
|
|
4053
|
+
background: 'var(--agy-warn-bg)',
|
|
4054
|
+
border: '1px solid var(--agy-warn-border)',
|
|
4055
|
+
borderRadius: '7px',
|
|
4056
|
+
padding: '8px 10px',
|
|
4057
|
+
color: 'var(--agy-warn-text)',
|
|
4058
|
+
fontSize: '12px',
|
|
4059
|
+
fontWeight: 600,
|
|
4060
|
+
marginBottom: '10px',
|
|
4061
|
+
display: 'flex',
|
|
4062
|
+
alignItems: 'center',
|
|
4063
|
+
gap: '6px',
|
|
4064
|
+
},
|
|
4065
|
+
}, uiIcon('alert', 13, 'var(--agy-warn-text)'), h('span', null, t('quota.expiredHint')))
|
|
4066
|
+
: null;
|
|
4067
|
+
const googleModels = account?.quotas.google?.models ?? [];
|
|
4068
|
+
const quotaCard = account
|
|
4069
|
+
? h('div', { className: 'agy-card-hover', style: S.card },
|
|
4070
|
+
// No identity header and no second refresh button here: the status
|
|
4071
|
+
// line above already carries account, state, clock and refresh, so
|
|
4072
|
+
// this card is quota data only.
|
|
4073
|
+
h('div', { style: S.quotaBox }, renderQuotaBar('Gemini', 'google', account), googleModels.length > 0 ? h('div', {
|
|
4074
|
+
style: { marginTop: '8px', paddingTop: '8px', borderTop: '1px solid var(--agy-border-box)' },
|
|
4075
|
+
}, h('div', { style: { color: 'var(--agy-text-secondary)', marginBottom: '6px', fontWeight: 600, fontSize: '11px' } }, t('account.models')), googleModels.map((model) => {
|
|
4076
|
+
const hasFrac = typeof model.remainingFraction === 'number' && Number.isFinite(model.remainingFraction);
|
|
4077
|
+
const pct = hasFrac ? Math.max(0, Math.min(100, Math.round(model.remainingFraction * 100))) : -1;
|
|
4078
|
+
const w = formatQuotaWindow(t, model.resetTime);
|
|
4079
|
+
const pColor = pct < 0 ? 'var(--agy-quota-none-text)' : pct <= 20 ? 'var(--agy-quota-low-text)' : pct <= 50 ? 'var(--agy-quota-med-text)' : 'var(--agy-quota-high-text)';
|
|
4080
|
+
return h('div', { key: model.modelId, className: 'agy-submodel-row' }, h('span', { style: { fontWeight: 500, color: 'var(--agy-text-primary)' } }, model.displayName || model.modelId), h('div', { style: { display: 'flex', alignItems: 'center', gap: '8px' } }, h('span', { style: { color: pColor, fontWeight: 700 } }, pct < 0 ? '—' : `${pct}%`), w.resetText ? h('span', { style: { color: 'var(--agy-text-tertiary)', fontSize: '10.5px' } }, `↻ ${w.resetText}`) : null));
|
|
4081
|
+
})) : null))
|
|
4082
|
+
: null;
|
|
4083
|
+
if (status === null) {
|
|
4084
|
+
// While the first `/status` is still running there is nothing to draw
|
|
4085
|
+
// yet; once it has answered (or failed) an empty snapshot must not
|
|
4086
|
+
// masquerade as "loading" forever. A failed load gets an actual retry
|
|
4087
|
+
// button — the old copy told the user to retry while offering nothing
|
|
4088
|
+
// to press, so the second open looked like a blank card.
|
|
4089
|
+
return h('div', { style: { ...S.container, minHeight: '120px', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '8px' } }, h('style', null, GLOBAL_CSS), loading
|
|
4090
|
+
? h('span', { style: S.muted }, [renderSpinner(), t('status.loading')])
|
|
4091
|
+
: [
|
|
4092
|
+
h('span', { key: 'msg', style: S.muted }, t('api.genericError')),
|
|
4093
|
+
h('button', {
|
|
4094
|
+
key: 'retry',
|
|
4095
|
+
type: 'button',
|
|
4096
|
+
style: S.btn,
|
|
4097
|
+
onClick: () => { setLoading(true); void loadStatusOnce().then(() => { setLoading(false); }); },
|
|
4098
|
+
}, t('status.retry')),
|
|
4099
|
+
]);
|
|
4100
|
+
}
|
|
4101
|
+
// Credential lifetime, not login-flow state. The keep-alive runs on the
|
|
4102
|
+
// host, so the only way to notice it failing is to show the clock the
|
|
4103
|
+
// user can watch: green while there is time, amber in the last minutes,
|
|
4104
|
+
// red once the credential is gone.
|
|
4105
|
+
const tokenSeconds = typeof status?.auth?.tokenExpiresInSeconds === 'number'
|
|
4106
|
+
? status.auth.tokenExpiresInSeconds
|
|
4107
|
+
: null;
|
|
4108
|
+
const keepaliveOn = status?.auth?.keepalive === true;
|
|
4109
|
+
// The host now reports why the last renewal attempt gave up. Without this
|
|
4110
|
+
// the panel said "renewing…" forever while agy.exe was in fact rejecting
|
|
4111
|
+
// our own argv and exiting before it ever touched the credential.
|
|
4112
|
+
const renewalReason = typeof status?.auth?.keepaliveLastReason === 'string'
|
|
4113
|
+
? status.auth.keepaliveLastReason
|
|
4114
|
+
: null;
|
|
4115
|
+
const renewalDetail = typeof status?.auth?.keepaliveLastDetail === 'string'
|
|
4116
|
+
? status.auth.keepaliveLastDetail
|
|
4117
|
+
: null;
|
|
4118
|
+
const renewalBroken = renewalReason === 'rejected' || renewalReason === 'failed' || renewalReason === 'not-rotated';
|
|
4119
|
+
const tokenLine = (() => {
|
|
4120
|
+
if (tokenSeconds === null)
|
|
4121
|
+
return null;
|
|
4122
|
+
const expired = tokenSeconds <= 0;
|
|
4123
|
+
const warned = !expired && tokenSeconds <= 300;
|
|
4124
|
+
// A broken renewal outranks the clock: it is the thing the user has to fix.
|
|
4125
|
+
const broken = renewalBroken && (expired || warned);
|
|
4126
|
+
const text = expired
|
|
4127
|
+
? (broken
|
|
4128
|
+
? t('auth.tokenRenewFailed')
|
|
4129
|
+
: keepaliveOn ? t('auth.tokenRenewing') : t('auth.tokenExpired'))
|
|
4130
|
+
: t('auth.tokenLeft').replace('{minutes}', String(Math.max(1, Math.round(tokenSeconds / 60))));
|
|
4131
|
+
return h('span', {
|
|
4132
|
+
style: {
|
|
4133
|
+
fontSize: '11.5px',
|
|
4134
|
+
fontWeight: 600,
|
|
4135
|
+
color: expired || broken ? 'var(--agy-warn-text)' : warned ? 'var(--agy-quota-med-text)' : 'var(--agy-text-tertiary)',
|
|
4136
|
+
},
|
|
4137
|
+
title: renewalDetail === null ? t('auth.tokenHint') : `${t('auth.tokenHint')}\n${renewalReason ?? ''}: ${renewalDetail}`,
|
|
4138
|
+
}, `${expired ? '⚠ ' : ''}${text}`);
|
|
4139
|
+
})();
|
|
4140
|
+
// One status line, not three stacked blocks. The host Models page already
|
|
4141
|
+
// prints the provider title ("Antigravity CLI") directly above this card,
|
|
4142
|
+
// so the identity + credential clock + readiness all live on the line but
|
|
4143
|
+
// one: account → state → "goes stale in N minutes" → refresh. Everything
|
|
4144
|
+
// below this line is quota data only.
|
|
4145
|
+
const accountAlias = account?.alias?.trim() ? account.alias : t('quota.accountUnknown');
|
|
4146
|
+
// --- Side-by-side fields ------------------------------------------------
|
|
4147
|
+
// Model and effort are "how the next turn runs". Both used to be pill
|
|
4148
|
+
// clouds with one button per option, so opening up another model family
|
|
4149
|
+
// (Pro, etc.) turned this block into a wall of pills that said nothing
|
|
4150
|
+
// about which one was actually in effect. Two native selects stay one line
|
|
4151
|
+
// tall for any catalog size; visibility is a scrollable checklist.
|
|
4152
|
+
const defaultModelId = status?.defaultModel ?? '';
|
|
4153
|
+
const defaultEffortId = status?.defaultEffort ?? '';
|
|
4154
|
+
const catalogModelIds = new Set(catalogModels.map((m) => m.id));
|
|
4155
|
+
const modelSelect = h('select', {
|
|
4156
|
+
className: 'agy-select',
|
|
4157
|
+
value: defaultModelId,
|
|
4158
|
+
disabled: loadingAction === 'config:defaultModel' || catalogModels.length === 0,
|
|
4159
|
+
title: defaultModelId,
|
|
4160
|
+
onChange: (event) => void setCfg('defaultModel', String(event?.target?.value ?? '')),
|
|
4161
|
+
}, [
|
|
4162
|
+
// Keep the configured model selectable even when the catalog snapshot
|
|
4163
|
+
// has not caught up (fresh login, empty quota answer): without this the
|
|
4164
|
+
// select would silently display the first catalog entry instead.
|
|
4165
|
+
defaultModelId !== '' && !catalogModelIds.has(defaultModelId)
|
|
4166
|
+
? h('option', { key: '__current', value: defaultModelId }, defaultModelId)
|
|
4167
|
+
: null,
|
|
4168
|
+
catalogModels.length === 0
|
|
4169
|
+
? h('option', { key: '__empty', value: defaultModelId }, defaultModelId === '' ? t('models.empty') : defaultModelId)
|
|
4170
|
+
: null,
|
|
4171
|
+
catalogModels.map((m) => h('option', { key: m.id, value: m.id }, m.name)),
|
|
4172
|
+
]);
|
|
4173
|
+
const effortSelect = h('select', {
|
|
4174
|
+
className: 'agy-select',
|
|
4175
|
+
value: defaultEffortId,
|
|
4176
|
+
disabled: loadingAction === 'config:defaultEffort',
|
|
4177
|
+
onChange: (event) => void setCfg('defaultEffort', String(event?.target?.value ?? '')),
|
|
4178
|
+
}, [
|
|
4179
|
+
h('option', { key: 'auto', value: '' }, t('effort.auto')),
|
|
4180
|
+
h('option', { key: 'low', value: 'low' }, t('effort.low')),
|
|
4181
|
+
h('option', { key: 'medium', value: 'medium' }, t('effort.medium')),
|
|
4182
|
+
h('option', { key: 'high', value: 'high' }, t('effort.high')),
|
|
4183
|
+
]);
|
|
4184
|
+
const visibilityList = catalogModels.length === 0
|
|
4185
|
+
? h('span', { style: S.muted }, t('models.empty'))
|
|
4186
|
+
: h('div', { className: 'agy-checklist' }, catalogModels.map((m) => h('label', { key: m.id, className: 'agy-check-item', title: m.id }, h('input', {
|
|
4187
|
+
type: 'checkbox',
|
|
4188
|
+
checked: !hiddenModelSet.has(m.id),
|
|
4189
|
+
disabled: loadingAction === 'config:hiddenModels',
|
|
4190
|
+
onChange: () => void toggleModelHidden(m.id, !hiddenModelSet.has(m.id)),
|
|
4191
|
+
}), h('span', null, m.name))));
|
|
4192
|
+
const settingsBlock = h('div', { style: { marginTop: '14px', paddingTop: '12px', borderTop: '1px solid var(--agy-border-divider)' } }, h('div', { style: { display: 'flex', gap: '18px', alignItems: 'center', flexWrap: 'wrap' } }, h('div', { style: { display: 'flex', alignItems: 'center', gap: '8px', minWidth: 0 } }, h('span', { style: { color: 'var(--agy-text-primary)', fontWeight: 600, fontSize: '12.5px', flexShrink: 0 } }, t('models.default')), modelSelect), h('div', { style: { display: 'flex', alignItems: 'center', gap: '8px', flexShrink: 0 } }, h('span', { style: { color: 'var(--agy-text-primary)', fontWeight: 600, fontSize: '12.5px' } }, t('effort.label')), effortSelect)), h('div', { style: { marginTop: '10px' } }, h('span', {
|
|
4193
|
+
style: { display: 'block', color: 'var(--agy-text-primary)', fontWeight: 600, fontSize: '12.5px', marginBottom: '6px', cursor: 'help' },
|
|
4194
|
+
title: t('models.hint'),
|
|
4195
|
+
}, t('models.label')), visibilityList));
|
|
4196
|
+
return h('div', { ref: cardRef, style: S.container }, h('style', null, GLOBAL_CSS), h('div', { style: S.headerCard }, h('div', { style: { display: 'flex', gap: '8px', alignItems: 'center', flexWrap: 'wrap', minWidth: 0 } }, h('span', { style: { fontWeight: 700, fontSize: '13.5px', color: 'var(--agy-text-primary)' }, title: accountAlias }, accountAlias), h('span', { style: isAuthed ? S.badgeReady : S.badgeUnready }, isAuthed ? t('status.ready') : t('status.needsAuth')), tokenLine), h('div', { style: { display: 'flex', alignItems: 'center', gap: '6px', flexShrink: 0 } }, h('button', {
|
|
4197
|
+
type: 'button',
|
|
4198
|
+
className: 'agy-btn',
|
|
4199
|
+
style: { ...S.btn, padding: '4px 7px', gap: '0', minWidth: '24px', justifyContent: 'center' },
|
|
4200
|
+
title: expanded ? t('panel.collapse') : t('panel.expand'),
|
|
4201
|
+
'aria-expanded': expanded ? 'true' : 'false',
|
|
4202
|
+
onClick: () => setManualOpen(!expanded),
|
|
4203
|
+
}, expanded ? '▾' : '▸'), h('button', {
|
|
4204
|
+
type: 'button',
|
|
4205
|
+
className: 'agy-btn',
|
|
4206
|
+
style: { ...S.btn, gap: '4px', flexShrink: 0 },
|
|
4207
|
+
disabled: isBusy,
|
|
4208
|
+
onClick: () => void refreshQuota(),
|
|
4209
|
+
}, loadingAction === 'refresh:all' ? [renderSpinner(), t('header.refreshing')] : [uiIcon('refresh', 12, 'var(--agy-text-btn)'), ` ${t('header.refreshQuota')}`]))), renderToastBanner(), expanded
|
|
4210
|
+
? h('div', { style: { display: 'contents' } }, expiredBanner, quotaCard, settingsBlock)
|
|
4211
|
+
: null);
|
|
4212
|
+
};
|
|
4213
|
+
// Antigravity's management surface lives INSIDE the host's Models settings
|
|
4214
|
+
// page: the provider row this plugin declares (`settingsNs:
|
|
4215
|
+
// 'dsh-agy-cli'` in src/index.ts) is the only entry point. The Models
|
|
4216
|
+
// section dispatches `settings.models.provider-card` on every card that
|
|
4217
|
+
// renders a directory row with `entryKey = row.entry.settingsNs`, and the
|
|
4218
|
+
// slot core renders the entry whose registered `key` equals that entryKey
|
|
4219
|
+
// verbatim — so this one key both selects the saved row and its first-run
|
|
4220
|
+
// setup card.
|
|
4221
|
+
//
|
|
4222
|
+
// NO registration in `conversation.session.header.actions` (a chat toolbar
|
|
4223
|
+
// badge) and NONE in `settings.section` (a nav entry of its own): the user's
|
|
4224
|
+
// directive is that this plugin shows up nowhere except the settings-model
|
|
4225
|
+
// page.
|
|
4226
|
+
ctx.slots.inject('settings.models.provider-card', () => {
|
|
4227
|
+
const dispose = ctx.slots.register({
|
|
4228
|
+
name: 'settings.models.provider-card',
|
|
4229
|
+
key: 'dsh-agy-cli',
|
|
4230
|
+
id: 'agy-cli-provider-card',
|
|
4231
|
+
}, AgySettingsSection);
|
|
4232
|
+
return dispose;
|
|
4233
|
+
});
|
|
4234
|
+
// Native tool-card rendering for the agy_tool mirror (DSH >= 0.1.5):
|
|
4235
|
+
// the browser conversation UI keys tool cards by wire name; registering
|
|
4236
|
+
// `tool.call.toolview` for `agy_tool` makes every mirrored agy step render
|
|
4237
|
+
// as a terminal / diff / read / search card instead of a generic text row.
|
|
4238
|
+
(0, toolview_ts_1.installAgyToolView)(ctx);
|
|
4239
|
+
// Keep an opened reasoning body pinned to its newest line while the row
|
|
4240
|
+
// runs. The fold itself (one-line "newest step" summary) is the host's,
|
|
4241
|
+
// driven by the provider's one-paragraph-per-step text. Installed through
|
|
4242
|
+
// ctx.effect so a plugin reload DISCONNECTS the old MutationObserver
|
|
4243
|
+
// instead of stacking one observer per reload.
|
|
4244
|
+
ctx.effect(() => (0, reasoning_ts_1.installReasoningFollow)());
|
|
4245
|
+
// Draw ```mermaid fences (DSH's shiki pass has no mermaid grammar and
|
|
4246
|
+
// leaves them as plain code blocks). Same lifecycle contract as above.
|
|
4247
|
+
ctx.effect(() => (0, mermaid_ts_1.installMermaidRendering)());
|
|
4248
|
+
// Local image display + fullscreen lightbox (recovered working tree).
|
|
4249
|
+
ctx.effect(() => (0, images_ts_1.installImageEnhancer)());
|
|
4250
|
+
// Interactive user decision & choice chips (recovered working tree).
|
|
4251
|
+
ctx.effect(() => (0, decisions_ts_1.installDecisionChips)());
|
|
4252
|
+
}
|
|
4253
|
+
};
|
|
4254
|
+
var module = { exports: {} }; var exports = module.exports;
|
|
4255
|
+
__modules["client/index.js"](__require, exports, module);
|
|
4256
|
+
exports.apply = module.exports.apply;
|
|
4257
|
+
exports.inject = module.exports.inject;
|
|
4258
|
+
exports.name = module.exports.name;
|
|
4259
|
+
return module.exports; } });
|