@songsid/agend 2.2.0-beta.8 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -24
- package/README.zh-TW.md +40 -24
- package/dist/agent-endpoint.js +3 -0
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/agent-whats-new.js +7 -1
- package/dist/agent-whats-new.js.map +1 -1
- package/dist/auth-api.d.ts +3 -1
- package/dist/auth-api.js +15 -1
- package/dist/auth-api.js.map +1 -1
- package/dist/backend/antigravity.d.ts +33 -0
- package/dist/backend/antigravity.js +126 -18
- package/dist/backend/antigravity.js.map +1 -1
- package/dist/backend/claude-code.d.ts +3 -0
- package/dist/backend/claude-code.js +9 -0
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/codex-metadata.d.ts +33 -0
- package/dist/backend/codex-metadata.js +122 -18
- package/dist/backend/codex-metadata.js.map +1 -1
- package/dist/backend/codex.d.ts +3 -0
- package/dist/backend/codex.js +17 -1
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/effort-metadata.d.ts +37 -1
- package/dist/backend/effort-metadata.js +86 -15
- package/dist/backend/effort-metadata.js.map +1 -1
- package/dist/backend/grok.d.ts +4 -0
- package/dist/backend/grok.js +14 -0
- package/dist/backend/grok.js.map +1 -1
- package/dist/backend/kiro.d.ts +18 -1
- package/dist/backend/kiro.js +35 -8
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/launch-effort.d.ts +61 -0
- package/dist/backend/launch-effort.js +170 -0
- package/dist/backend/launch-effort.js.map +1 -0
- package/dist/backend/muse.d.ts +17 -0
- package/dist/backend/muse.js +86 -16
- package/dist/backend/muse.js.map +1 -1
- package/dist/backend/types.d.ts +40 -1
- package/dist/backend/types.js +12 -1
- package/dist/backend/types.js.map +1 -1
- package/dist/backend-command.d.ts +91 -0
- package/dist/backend-command.js +122 -0
- package/dist/backend-command.js.map +1 -0
- package/dist/channel/adapters/discord.d.ts +12 -0
- package/dist/channel/adapters/discord.js +49 -2
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/adapters/telegram.d.ts +18 -0
- package/dist/channel/adapters/telegram.js +24 -3
- package/dist/channel/adapters/telegram.js.map +1 -1
- package/dist/channel/mcp-tools.js +2 -0
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/channel/tool-router.d.ts +2 -0
- package/dist/channel/tool-router.js +1 -1
- package/dist/channel/tool-router.js.map +1 -1
- package/dist/channel/types.d.ts +16 -0
- package/dist/cli.js +124 -24
- package/dist/cli.js.map +1 -1
- package/dist/command-table.d.ts +2 -2
- package/dist/command-table.js +5 -2
- package/dist/command-table.js.map +1 -1
- package/dist/compact-watch.d.ts +86 -0
- package/dist/compact-watch.js +288 -0
- package/dist/compact-watch.js.map +1 -0
- package/dist/config-validator.js +39 -0
- package/dist/config-validator.js.map +1 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.js +2 -1
- package/dist/config.js.map +1 -1
- package/dist/daemon.d.ts +13 -1
- package/dist/daemon.js +160 -13
- package/dist/daemon.js.map +1 -1
- package/dist/delivery-outbox.d.ts +8 -1
- package/dist/delivery-outbox.js +6 -2
- package/dist/delivery-outbox.js.map +1 -1
- package/dist/doctor.js +7 -0
- package/dist/doctor.js.map +1 -1
- package/dist/fleet-context.d.ts +7 -0
- package/dist/fleet-manager.d.ts +160 -10
- package/dist/fleet-manager.js +837 -121
- package/dist/fleet-manager.js.map +1 -1
- package/dist/forged-envelope-scan.d.ts +4 -3
- package/dist/forged-envelope-scan.js +4 -3
- package/dist/forged-envelope-scan.js.map +1 -1
- package/dist/general-knowledge/skills/model-discovery/SKILL.md +4 -1
- package/dist/install-lock.d.ts +18 -1
- package/dist/install-lock.js +30 -3
- package/dist/install-lock.js.map +1 -1
- package/dist/instance-avatar.d.ts +5 -4
- package/dist/instance-avatar.js +12 -8
- package/dist/instance-avatar.js.map +1 -1
- package/dist/instance-lifecycle.js +3 -1
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/instructions.js +2 -2
- package/dist/instructions.js.map +1 -1
- package/dist/kiro-compact-pane.d.ts +38 -0
- package/dist/kiro-compact-pane.js +57 -0
- package/dist/kiro-compact-pane.js.map +1 -0
- package/dist/kiro-log-gate.d.ts +42 -0
- package/dist/kiro-log-gate.js +39 -0
- package/dist/kiro-log-gate.js.map +1 -0
- package/dist/launchd-handoff.d.ts +43 -0
- package/dist/launchd-handoff.js +56 -0
- package/dist/launchd-handoff.js.map +1 -0
- package/dist/locale.js +202 -30
- package/dist/locale.js.map +1 -1
- package/dist/log-service.js +10 -2
- package/dist/log-service.js.map +1 -1
- package/dist/log-steps.d.ts +26 -0
- package/dist/log-steps.js +215 -3
- package/dist/log-steps.js.map +1 -1
- package/dist/login-shell.d.ts +21 -0
- package/dist/login-shell.js +98 -0
- package/dist/login-shell.js.map +1 -0
- package/dist/outbound-handlers.d.ts +27 -0
- package/dist/outbound-handlers.js +120 -36
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +12 -0
- package/dist/outbound-schemas.js +15 -4
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/provider-probe.d.ts +29 -0
- package/dist/provider-probe.js +198 -2
- package/dist/provider-probe.js.map +1 -1
- package/dist/public-web-gateway.d.ts +2 -0
- package/dist/public-web-gateway.js +11 -1
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/quickstart-api.d.ts +1 -1
- package/dist/quickstart-api.js +8 -6
- package/dist/quickstart-api.js.map +1 -1
- package/dist/quickstart.d.ts +12 -0
- package/dist/quickstart.js +36 -6
- package/dist/quickstart.js.map +1 -1
- package/dist/reply-buttons-controller.d.ts +86 -5
- package/dist/reply-buttons-controller.js +271 -7
- package/dist/reply-buttons-controller.js.map +1 -1
- package/dist/reply-buttons.d.ts +48 -3
- package/dist/reply-buttons.js +105 -10
- package/dist/reply-buttons.js.map +1 -1
- package/dist/reply-polls.d.ts +85 -0
- package/dist/reply-polls.js +154 -0
- package/dist/reply-polls.js.map +1 -0
- package/dist/restart-guard.d.ts +6 -3
- package/dist/restart-guard.js +6 -3
- package/dist/restart-guard.js.map +1 -1
- package/dist/restart-progress.d.ts +0 -2
- package/dist/restart-progress.js +0 -2
- package/dist/restart-progress.js.map +1 -1
- package/dist/sd-notify.d.ts +22 -1
- package/dist/sd-notify.js +63 -10
- package/dist/sd-notify.js.map +1 -1
- package/dist/service-activation.d.ts +15 -7
- package/dist/service-activation.js +42 -26
- package/dist/service-activation.js.map +1 -1
- package/dist/service-installer.d.ts +24 -0
- package/dist/service-installer.js +49 -1
- package/dist/service-installer.js.map +1 -1
- package/dist/service-plan.d.ts +11 -0
- package/dist/service-plan.js +23 -0
- package/dist/service-plan.js.map +1 -1
- package/dist/systemd-peer-bus.d.ts +95 -0
- package/dist/systemd-peer-bus.js +430 -0
- package/dist/systemd-peer-bus.js.map +1 -0
- package/dist/systemd-runtime.d.ts +27 -1
- package/dist/systemd-runtime.js +69 -7
- package/dist/systemd-runtime.js.map +1 -1
- package/dist/tool-permissions.js +3 -3
- package/dist/tool-permissions.js.map +1 -1
- package/dist/topic-commands.d.ts +68 -2
- package/dist/topic-commands.js +141 -27
- package/dist/topic-commands.js.map +1 -1
- package/dist/transcript-jsonl.d.ts +7 -2
- package/dist/transcript-jsonl.js +13 -7
- package/dist/transcript-jsonl.js.map +1 -1
- package/dist/transcript-sources.js +6 -1
- package/dist/transcript-sources.js.map +1 -1
- package/dist/types.d.ts +8 -0
- package/dist/ui/chat-commands.js +1 -0
- package/dist/ui/chat-render.js +55 -10
- package/dist/ui/chat-store.js +69 -5
- package/dist/ui/chat-strings.js +28 -16
- package/dist/ui/chat-thread.js +132 -14
- package/dist/ui/chat-tour.js +17 -14
- package/dist/ui/panel-chat.js +16 -10
- package/dist/ui/panel-fleet.js +15 -2
- package/dist/ui/panel-log.js +55 -15
- package/dist/ui/panel-needs.js +10 -7
- package/dist/ui/panel-settings.js +58 -7
- package/dist/ui/settings-dialogs.js +11 -6
- package/dist/ui/settings-model.js +3 -1
- package/dist/ui/settings-strings.js +24 -10
- package/dist/ui/settings-token.js +7 -0
- package/dist/ui/settings-wizard.js +5 -3
- package/dist/ui/shared/app-i18n.js +1 -1
- package/dist/ui/shared/app-stream.js +4 -1
- package/dist/ui/shared/app.css +52 -10
- package/dist/ui/shared/header-tools.js +34 -14
- package/dist/ui/shared/instance-switch.js +5 -2
- package/dist/ui/shared/panel-view.js +15 -4
- package/dist/ui/shared/signin.js +15 -9
- package/dist/ui/shared/ui-icons.js +1 -0
- package/dist/ui/shared/view-strings.js +10 -2
- package/dist/ui/signin.html +8 -3
- package/dist/unrecognised-prompt.js +2 -2
- package/dist/unrecognised-prompt.js.map +1 -1
- package/dist/update-check.d.ts +31 -0
- package/dist/update-check.js +47 -0
- package/dist/update-check.js.map +1 -1
- package/dist/update-launch.d.ts +48 -1
- package/dist/update-launch.js +159 -5
- package/dist/update-launch.js.map +1 -1
- package/dist/usage/providers.d.ts +28 -8
- package/dist/usage/providers.js +96 -41
- package/dist/usage/providers.js.map +1 -1
- package/dist/view-api.js +26 -9
- package/dist/view-api.js.map +1 -1
- package/dist/web-api.d.ts +44 -2
- package/dist/web-api.js +265 -57
- package/dist/web-api.js.map +1 -1
- package/dist/web-chat-history.d.ts +26 -1
- package/dist/web-chat-history.js +47 -8
- package/dist/web-chat-history.js.map +1 -1
- package/dist/web-commands.d.ts +39 -3
- package/dist/web-commands.js +22 -5
- package/dist/web-commands.js.map +1 -1
- package/dist/web-login.d.ts +14 -0
- package/dist/web-login.js +4 -2
- package/dist/web-login.js.map +1 -1
- package/dist/web-prefs.d.ts +18 -0
- package/dist/web-prefs.js +36 -4
- package/dist/web-prefs.js.map +1 -1
- package/dist/web-profile.d.ts +32 -0
- package/dist/web-profile.js +132 -0
- package/dist/web-profile.js.map +1 -0
- package/dist/web-session.d.ts +14 -0
- package/dist/web-session.js +10 -0
- package/dist/web-session.js.map +1 -1
- package/dist/web-upload.d.ts +119 -4
- package/dist/web-upload.js +252 -39
- package/dist/web-upload.js.map +1 -1
- package/launcher/install-admission.cjs +61 -6
- package/package.json +3 -3
package/dist/ui/chat-strings.js
CHANGED
|
@@ -3,23 +3,27 @@ import { register } from "/assets/app-i18n.js";
|
|
|
3
3
|
|
|
4
4
|
register("chat", {
|
|
5
5
|
en: {
|
|
6
|
-
cmdConfirm: "Clear", cmdRunning: "Running /{0}…", cmdNotCommand: "Not a command here — Enter sends it as a message.", cmdDismiss: "Dismiss", cmdCommands: "Commands",
|
|
6
|
+
cmdConfirm: "Clear", cmdConfirmBackend: "Switch and restart", cmdRunning: "Running /{0}…", cmdNotCommand: "Not a command here — Enter sends it as a message.", cmdDismiss: "Dismiss", cmdCommands: "Commands",
|
|
7
|
+
compactRunning: "Compacting…", compactDone: "✅ Conversation compacted", compactUnknown: "This backend does not report when compaction ends.",
|
|
8
|
+
compactTimeout: "No sign that compaction finished within {0} minutes — check the agent's own chat.",
|
|
9
|
+
compactLost: "Can't follow this compaction: the command did not reach the agent, or the agent stopped or changed backend.",
|
|
10
|
+
compactNotCompacted: "kiro did not compact the conversation: it was too short, or the compaction was stopped.",
|
|
7
11
|
cmdChoose: "Choose", cmdCurrent: "current", chipCompact: "Compact", chipClear: "Clear…", chipContext: "Context {0}% used",
|
|
8
12
|
chipModel: "Model: {0} — choose another", chipEffort: "Effort: {0} — choose another",
|
|
9
13
|
cmd_ctx: "Context use, model and effort", cmd_compact: "Compact the conversation", cmd_clear: "Clear the conversation (asks first)",
|
|
10
|
-
cmd_model: "Show or switch the model", cmd_effort: "Show or set the reasoning effort", cmd_cancel: "Stop the current reply",
|
|
14
|
+
cmd_model: "Show or switch the model", cmd_backend: "Switch the CLI backend, then model and effort (restarts)", cmd_effort: "Show or set the reasoning effort", cmd_cancel: "Stop the current reply",
|
|
11
15
|
cmd_btw: "A side question that does not interrupt", cmd_steer: "Send into the current turn", cmd_pause: "Pause the instance",
|
|
12
16
|
cmd_wake: "Wake a paused instance", cmd_save: "Save the conversation to a file",
|
|
13
17
|
tourTitle: "Welcome to web chat", tourStep: "{0} of {1}", tourBack: "Back", tourNext: "Next", tourDone: "Got it", tourSkip: "Skip",
|
|
14
18
|
tour1: "Pick an instance in the list to chat with it. On a phone, ☰ opens the list.",
|
|
15
|
-
tour2: "
|
|
19
|
+
tour2: "This is the agent's conversation. If it also has a Telegram or Discord chat, web echo (Settings → Web) decides whether what you send here shows there too.",
|
|
16
20
|
tour3: "Attach files and images with 📎, or paste or drop them on the chat.",
|
|
17
21
|
tour4: "While the agent works, Stop reply appears in the message box: it interrupts the current reply. The instance itself keeps running.",
|
|
18
22
|
tour5: "A “needs you” badge means the agent is waiting on its terminal, for example a permission question. Answer it from the buttons in its chat.",
|
|
19
23
|
a11yWorking: "{0} started working", a11yIdle: "{0} finished", a11yReplied: "{0} replied", conversationWith: "Conversation with {0}",
|
|
20
24
|
stopping: "Stopping {0}…", awaiting: "{0} is waiting for your input", approxNote: "Read from the terminal, so approximate", elapsedTitle: "Time since this page saw it start (approximate)",
|
|
21
25
|
dropHere: "Drop files to attach them", asText: "As text", pasteAttached: "A long paste ({0} characters) was attached as a text file", pasteKeptAsText: "No room for another attachment: the long paste stays as text",
|
|
22
|
-
send: "Send", composerPlaceholder: "Message {0}…", composerHint: "Enter to send · Shift+Enter for a new line
|
|
26
|
+
send: "Send", composerPlaceholder: "Message {0}…", composerHint: "Enter to send · Shift+Enter for a new line", composerMore: "/ for commands · Esc stops the reply", attachTitle: "Attach files (any type — or paste / drop them)",
|
|
23
27
|
pvPreview: "Preview", pvStop: "Stop", pvDownload: "Download", pvMenu: "Preview options", pvHtml: "HTML",
|
|
24
28
|
pvAllowButton: "Allow previews", pvAllow: "Allow HTML previews — they may be able to send data out", pvDisallow: "Stop allowing HTML previews",
|
|
25
29
|
pvConfirm: "Allow HTML previews?\n\nThis applies on every device where you are signed in. A preview runs the agent's HTML in an isolated frame. It cannot use your login, but it may be able to send data out, and it can slow or freeze this tab. You still click Preview each time.",
|
|
@@ -32,6 +36,7 @@ register("chat", {
|
|
|
32
36
|
attGone: "This file could not be found again after AgEnD restarted (moved, deleted or changed). Ask the agent to send it again.", attUnavailable: "unavailable", yesterdayAt: "Yesterday {0}",
|
|
33
37
|
tcShow: "Show", tcHide: "Hide", tcDownload: "Download", tcShowAll: "Show all {0} lines", tcOver: "This file is over 1 MiB; download it to read it.", tcPlain: "Too large to format here: shown as plain text.",
|
|
34
38
|
rbGroup: "Choices", rbChosen: "Chosen by {0}", rbExpired: "Expired — reply in text",
|
|
39
|
+
pollGroup: "Poll", pollAnswered: "🗳 {0} answered", pollClosed: "🗳 Closed · {0} answered", pollLeft: "Time left", pollVoters: "Who voted",
|
|
35
40
|
pvOpenPanel: "Open in panel", pvInPanel: "Shown in the panel.", pvPanel: "HTML preview", pvSource: "From {0}, {1} — show the message",
|
|
36
41
|
pvPage: "Page", pvCode: "Code", pvReload: "Reload", pvClose: "Close the panel", pvBack: "Back to the chat", pvResize: "Resize the preview panel",
|
|
37
42
|
pvNewer: "A newer version is in the reply from {0}.", pvNewerShow: "Show it", pvPanelIdle: "Click Preview to run it here.",
|
|
@@ -65,17 +70,18 @@ register("chat", {
|
|
|
65
70
|
pvFail_unknown: "an unexpected error",
|
|
66
71
|
pvServer_notOffered: "Previews are not available on this page.",
|
|
67
72
|
previews: "Allow HTML previews",
|
|
73
|
+
clicked: "Clicked:", clickShow: "Show the message these buttons were on",
|
|
68
74
|
copy: "Copy", copyMessage: "Copy this reply", copied: "Copied", copyFailed: "Copy failed", wrap: "Wrap", showAll: "Show all {0} lines", showMore: "Show more", showLess: "Show less", jumpLatest: "Latest", newBelow: "{0} new",
|
|
69
75
|
disconnected: "disconnected", tick_queued: "Waiting behind another message", tick_processing: "Handed to the agent", tick_delivered: "The agent has it", tick_failed: "Not delivered", tick_cancelled: "Dropped by Stop",
|
|
70
76
|
promptAnswering: "Answering…", promptGone: "This prompt is no longer open", promptFor: "{0}: {1}",
|
|
71
77
|
working: "{0} is working…", stuck: "{0} looks stuck", stopReply: "Stop reply", stopReplyTitle: "Stop the current reply (like Esc) and drop the messages still waiting. The instance keeps running.", stopSent: "Stopped",
|
|
72
|
-
file_empty: "empty file", file_too_large: "over
|
|
73
|
-
noMessages: "
|
|
74
|
-
pick: "
|
|
78
|
+
file_empty: "empty file", file_too_large: "over {0} MB", file_too_large_public: "over {0} MB — the most the public link carries in one upload", file_too_many: "at most {0} files per message", file_too_much: "the files together would be over {0} MB", removeFile: "Remove", notSent: "Not sent:", putBack: "Put back", discard: "Discard",
|
|
79
|
+
noMessages: "Type a task below.",
|
|
80
|
+
pick: "Choose an agent to start chatting.", noAgents: "Create an agent to start chatting.", newAgent: "New agent", notFound: "No instance called “{0}”", notFoundHint: "It may have been deleted or renamed.", backToList: "Choose an agent",
|
|
75
81
|
details: "Instance details", start: "Start instance", stopInstance: "Stop instance", restart: "Restart instance", delete: "Delete instance…",
|
|
76
82
|
started: "{0} started", stoppedInst: "{0} stopped", restarted: "{0} restarted", instanceDeleted: "{0} was deleted",
|
|
77
83
|
deleteTitle: "Delete {0}?", deleteHelp: "This removes the instance from the fleet and stops it. Its working directory is kept. Type “delete {0}” to confirm.", deleteGo: "Delete", cancel: "Cancel",
|
|
78
|
-
loadFailed: "Failed to load", tour: "
|
|
84
|
+
loadFailed: "Failed to load", tour: "Chat tour", tourBtnTitle: "Show the chat tour on an agent's chat", tourNoAgent: "Choose an agent before opening the chat tour.",
|
|
79
85
|
dName: "Name", dStatus: "Status", dDisplay: "Display name", dDescription: "Description", dDirectory: "Directory", dBackend: "Backend", dModel: "Model", dEffort: "Effort",
|
|
80
86
|
dCost: "Cost", dCostNote: "(session accumulated)", dContext: "Context", dRate5: "Rate 5h", dRate7: "Rate 7d", dActivity: "Recent activity", dInstance: "Instance", dRuntime: "Runtime",
|
|
81
87
|
// #1523 N2: Details (the Fleet side of one instance).
|
|
@@ -87,23 +93,27 @@ register("chat", {
|
|
|
87
93
|
configured: "(configured)", fleetDefault: "(fleet default)",
|
|
88
94
|
},
|
|
89
95
|
"zh-TW": {
|
|
90
|
-
cmdConfirm: "清空", cmdRunning: "執行 /{0} 中…", cmdNotCommand: "這裡沒有這個指令——按 Enter 會當成一般訊息送出。", cmdDismiss: "關閉", cmdCommands: "指令",
|
|
96
|
+
cmdConfirm: "清空", cmdConfirmBackend: "切換並重新啟動", cmdRunning: "執行 /{0} 中…", cmdNotCommand: "這裡沒有這個指令——按 Enter 會當成一般訊息送出。", cmdDismiss: "關閉", cmdCommands: "指令",
|
|
97
|
+
compactRunning: "壓縮中…", compactDone: "✅ 對話已壓縮", compactUnknown: "這個後端不會回報壓縮何時完成。",
|
|
98
|
+
compactTimeout: "{0} 分鐘內沒有看到壓縮完成的紀錄,請到代理自己的對話確認。",
|
|
99
|
+
compactLost: "無法再追蹤這次壓縮:指令沒有送到代理,或代理已停止、換了後端。",
|
|
100
|
+
compactNotCompacted: "kiro 沒有壓縮對話:對話太短,或壓縮被中止了。",
|
|
91
101
|
cmdChoose: "選擇", cmdCurrent: "目前", chipCompact: "壓縮", chipClear: "清空…", chipContext: "已用 {0}% 的 context",
|
|
92
102
|
chipModel: "模型:{0}——選擇其他", chipEffort: "推理強度:{0}——選擇其他",
|
|
93
103
|
cmd_ctx: "context 用量、模型與推理強度", cmd_compact: "壓縮對話", cmd_clear: "清空對話(會先確認)",
|
|
94
|
-
cmd_model: "查看或切換模型", cmd_effort: "查看或設定推理強度", cmd_cancel: "停止目前的回覆",
|
|
104
|
+
cmd_model: "查看或切換模型", cmd_backend: "切換 CLI backend,再選模型和推理強度(會重新啟動)", cmd_effort: "查看或設定推理強度", cmd_cancel: "停止目前的回覆",
|
|
95
105
|
cmd_btw: "不打斷目前工作的旁問", cmd_steer: "送進目前這一輪", cmd_pause: "暫停 instance",
|
|
96
106
|
cmd_wake: "喚醒已暫停的 instance", cmd_save: "把對話存成檔案",
|
|
97
107
|
tourTitle: "歡迎使用網頁聊天", tourStep: "{0} / {1}", tourBack: "上一步", tourNext: "下一步", tourDone: "知道了", tourSkip: "略過",
|
|
98
108
|
tour1: "在清單裡選一個 instance,就能跟它對話。手機上請按 ☰ 打開清單。",
|
|
99
|
-
tour2: "
|
|
109
|
+
tour2: "這是這個 Agent 的對話。如果它也有 Telegram 或 Discord 聊天,在這裡送出的訊息是否也出現在那裡,由「設定 → 網頁」的網頁轉發決定。",
|
|
100
110
|
tour3: "按 📎 附上檔案和圖片,也可以直接貼上或拖到聊天裡。",
|
|
101
111
|
tour4: "Agent 工作時,輸入框會出現「中止回覆」:中斷目前的回覆,instance 本身會繼續運行。",
|
|
102
112
|
tour5: "看到「等你回應」標記,表示 Agent 在終端機上等你,例如權限詢問。到它的聊天裡按按鈕回應即可。",
|
|
103
113
|
a11yWorking: "{0} 開始工作", a11yIdle: "{0} 已完成", a11yReplied: "{0} 已回覆", conversationWith: "與 {0} 的對話",
|
|
104
114
|
stopping: "正在中止 {0}…", awaiting: "{0} 正在等你回應", approxNote: "從終端畫面判讀,僅供參考", elapsedTitle: "從這個頁面看到它開始工作起算(約略)",
|
|
105
115
|
dropHere: "放開即可附加檔案", asText: "改為文字", pasteAttached: "貼上的長文字({0} 個字元)已改為文字檔附加", pasteKeptAsText: "附件已滿,貼上的長文字保留為一般文字",
|
|
106
|
-
send: "送出", composerPlaceholder: "傳訊息給 {0}…", composerHint: "Enter 送出 · Shift+Enter 換行
|
|
116
|
+
send: "送出", composerPlaceholder: "傳訊息給 {0}…", composerHint: "Enter 送出 · Shift+Enter 換行", composerMore: "/ 叫出指令 · Esc 中止回覆", attachTitle: "附加檔案(任何類型,也可以貼上或拖曳)",
|
|
107
117
|
pvPreview: "預覽", pvStop: "停止", pvDownload: "下載", pvMenu: "預覽選項", pvHtml: "HTML",
|
|
108
118
|
pvAllowButton: "允許預覽", pvAllow: "允許預覽 HTML(預覽可能把資料傳出去)", pvDisallow: "不再允許預覽 HTML",
|
|
109
119
|
pvConfirm: "要允許預覽 HTML 嗎?\n\n這會套用到你登入的每一台裝置。預覽會在隔離的框架裡執行 agent 的 HTML。它無法使用你的登入,但可能把資料傳出去,也可能讓這個分頁變慢或卡住。每次仍需要你按「預覽」。",
|
|
@@ -116,6 +126,7 @@ register("chat", {
|
|
|
116
126
|
attGone: "AgEnD 重新啟動後找不到這個檔案(已移動、刪除或變更)。請 agent 再傳一次。", attUnavailable: "無法取得", yesterdayAt: "昨天 {0}",
|
|
117
127
|
tcShow: "顯示", tcHide: "收起", tcDownload: "下載", tcShowAll: "顯示全部 {0} 行", tcOver: "這個檔案超過 1 MiB,請下載後閱讀。", tcPlain: "內容太大,無法在這裡排版,改以純文字顯示。",
|
|
118
128
|
rbGroup: "選項", rbChosen: "{0} 已選擇", rbExpired: "已逾時,請直接回覆文字",
|
|
129
|
+
pollGroup: "投票", pollAnswered: "🗳 已有 {0} 人作答", pollClosed: "🗳 已截止 · {0} 人作答", pollLeft: "剩餘時間", pollVoters: "投票名單",
|
|
119
130
|
pvOpenPanel: "在面板中開啟", pvInPanel: "已在面板中顯示。", pvPanel: "HTML 預覽", pvSource: "來自 {0},{1}——顯示該訊息",
|
|
120
131
|
pvPage: "頁面", pvCode: "程式碼", pvReload: "重新載入", pvClose: "關閉面板", pvBack: "回到對話", pvResize: "調整預覽面板寬度",
|
|
121
132
|
pvNewer: "{0} 的回覆裡有較新的版本。", pvNewerShow: "顯示", pvPanelIdle: "按「預覽」在這裡執行。",
|
|
@@ -148,17 +159,18 @@ register("chat", {
|
|
|
148
159
|
pvFail_unknown: "發生了未預期的錯誤",
|
|
149
160
|
pvServer_notOffered: "這個頁面不提供預覽。",
|
|
150
161
|
previews: "允許預覽 HTML",
|
|
162
|
+
clicked: "點選了:", clickShow: "顯示這些按鈕所在的訊息",
|
|
151
163
|
copy: "複製", copyMessage: "複製這則回覆", copied: "已複製", copyFailed: "複製失敗", wrap: "換行", showAll: "展開全部 {0} 行", showMore: "顯示更多", showLess: "收合", jumpLatest: "最新", newBelow: "{0} 則新訊息",
|
|
152
164
|
disconnected: "已斷線", tick_queued: "排在前一則訊息之後", tick_processing: "已交給 agent", tick_delivered: "agent 已收到", tick_failed: "未送達", tick_cancelled: "已被中止取消",
|
|
153
165
|
promptAnswering: "回覆中…", promptGone: "這個提示已經結束", promptFor: "{0}:{1}",
|
|
154
166
|
working: "{0} 處理中…", stuck: "{0} 似乎卡住了", stopReply: "中止回覆", stopReplyTitle: "中止目前的回覆(同 Esc),並取消還在排隊的訊息。instance 會繼續運行。", stopSent: "已中止",
|
|
155
|
-
file_empty: "空檔案", file_too_large: "超過
|
|
156
|
-
noMessages: "
|
|
157
|
-
pick: "選一個
|
|
167
|
+
file_empty: "空檔案", file_too_large: "超過 {0} MB", file_too_large_public: "超過 {0} MB,這是公開連結單次上傳的上限", file_too_many: "每則訊息最多 {0} 個檔案", file_too_much: "檔案合計會超過 {0} MB", removeFile: "移除", notSent: "未送出:", putBack: "放回輸入框", discard: "捨棄",
|
|
168
|
+
noMessages: "直接在下方輸入任務。",
|
|
169
|
+
pick: "選一個 Agent 開始聊天。", noAgents: "先建立一個 Agent,再開始聊天。", newAgent: "新增 Agent", notFound: "沒有叫「{0}」的 instance", notFoundHint: "它可能已被刪除或改名。", backToList: "選擇 Agent",
|
|
158
170
|
details: "Instance 詳細資料", start: "啟動 instance", stopInstance: "停止 instance", restart: "重新啟動 instance", delete: "刪除 instance…",
|
|
159
171
|
started: "{0} 已啟動", stoppedInst: "{0} 已停止", restarted: "{0} 已重新啟動", instanceDeleted: "{0} 已刪除",
|
|
160
172
|
deleteTitle: "要刪除 {0} 嗎?", deleteHelp: "這會把 instance 從 fleet 移除並停止它,工作目錄會保留。請輸入「delete {0}」確認。", deleteGo: "刪除", cancel: "取消",
|
|
161
|
-
loadFailed: "載入失敗", tour: "
|
|
173
|
+
loadFailed: "載入失敗", tour: "聊天導覽", tourBtnTitle: "在 Agent 的聊天上開啟聊天導覽", tourNoAgent: "請先選一個 Agent,再開啟聊天導覽。",
|
|
162
174
|
dName: "名稱", dStatus: "狀態", dDisplay: "顯示名稱", dDescription: "說明", dDirectory: "目錄", dBackend: "Backend", dModel: "模型", dEffort: "Effort",
|
|
163
175
|
dCost: "花費", dCostNote: "(本次 session 累計)", dContext: "Context", dRate5: "5 小時額度", dRate7: "7 天額度", dActivity: "最近活動", dInstance: "Instance", dRuntime: "執行環境",
|
|
164
176
|
dConfig: "設定", dBinding: "綁定", dBindingGeneral: "{0} 的 General topic", dBindingTopic: "{0},topic {1}", dBindingNoTopic: "{0},尚未綁定 topic",
|
package/dist/ui/chat-thread.js
CHANGED
|
@@ -31,6 +31,12 @@ const COPY = '<svg class="icon" width="14" height="14" viewBox="0 0 24 24" fill=
|
|
|
31
31
|
/** Escapes & " ' < >: safe for text and inside a quoted attribute. */
|
|
32
32
|
export function escAttr(s) { return String(s == null ? "" : s).replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'").replace(/</g, "<").replace(/>/g, ">"); }
|
|
33
33
|
export const msgKey = (x) => `${x.boot}-${x.id}`;
|
|
34
|
+
/**
|
|
35
|
+
* Board 00ef2430: a thread opens with only its newest messages drawn — enough for a screen — so switching chats paints
|
|
36
|
+
* at once; the older ones are drawn after that, a chunk at a time between frames, the reader's place kept.
|
|
37
|
+
*/
|
|
38
|
+
export const THREAD_TAIL = 24;
|
|
39
|
+
export const THREAD_CHUNK = 40;
|
|
34
40
|
|
|
35
41
|
/**
|
|
36
42
|
* Bind a renderer to `list` (the thread element) inside `scroller`. opts: { t, tf, isUser(x), onJump({show, unseen}),
|
|
@@ -47,6 +53,10 @@ export function createThread(list, scroller, opts) {
|
|
|
47
53
|
// moves down or stays), and those must not count as the reader leaving the bottom. Reaching the bottom pins again.
|
|
48
54
|
let pinned = true;
|
|
49
55
|
let lastTop = 0;
|
|
56
|
+
// The oldest message drawn (by key): everything after it is drawn, so new messages never push the window. Null: none
|
|
57
|
+
// drawn yet — the next render draws the newest THREAD_TAIL and the rest follows (grow).
|
|
58
|
+
let firstKey = null;
|
|
59
|
+
let growTimer = null, growFrame = null, disposed = false;
|
|
50
60
|
const cardRefresh = new WeakMap(); // card node → re-read availability (weak: a card that left takes its source with it)
|
|
51
61
|
const tr = opts.t, trf = opts.tf;
|
|
52
62
|
const stopIn = (node) => (P() ? P().stopIn(node) : []);
|
|
@@ -76,7 +86,20 @@ export function createThread(list, scroller, opts) {
|
|
|
76
86
|
const ticks = u && x.delivery ? R().deliveryHtml(x.delivery, TICKS()) : "";
|
|
77
87
|
const tools = u ? "" : `<div class="msg-tools"><button type="button" class="chip-btn icon-only" data-act="copyMsg" data-arg="${escAttr(msgKey(x))}" title="${escAttr(tr("chat.copyMessage"))}" aria-label="${escAttr(tr("chat.copyMessage"))}">${COPY}</button></div>`;
|
|
78
88
|
const buttons = x.role === "agent" ? replyButtonsHtml(x.buttons) : "";
|
|
79
|
-
|
|
89
|
+
// A person's click on a reply button shows as the button's label, never the line the agent was given (its value).
|
|
90
|
+
const clicked = x.role === "user" && x.click && typeof x.click.label === "string";
|
|
91
|
+
return `<div class="msg ${u ? "user" : "agent"}"><div class="meta"><span class="sender">${escAttr(x.sender)}</span>${time}${ticks}</div><div class="body">${clicked ? clickHtml(x.click) : `<div class="md">${R().renderMarkdown(x.text, { htmlCards: x.role === "agent", emojis: opts.emojis, mentions: opts.mentions })}</div>`}${R().attachmentsHtml(x.attachments, { htmlCards: x.role === "agent", goneTitle: tr("chat.attGone"), goneLabel: tr("chat.attUnavailable"), inlinePreviews: !PUBLIC_LINK() })}${buttons}</div>${tools}</div>`;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* A button click as a chip: 🔘 and the label (escaped: the label is the agent's text). With `of` (live, while its
|
|
95
|
+
* reply is in this thread) it is a button that shows that reply; restored from disk, plain.
|
|
96
|
+
*/
|
|
97
|
+
function clickHtml(click) {
|
|
98
|
+
const inner = `<span class="click-mark" aria-hidden="true">🔘</span><span class="sr-only">${escAttr(tr("chat.clicked"))} </span>${escAttr(click.label)}`;
|
|
99
|
+
const of = typeof click.of === "string" && /^[0-9a-f]{32}$/.test(click.of) ? click.of : null;
|
|
100
|
+
return `<div class="click-chip">${of
|
|
101
|
+
? `<button type="button" class="click-link" data-act="showButtons" data-arg="${escAttr(of)}" title="${escAttr(tr("chat.clickShow"))}">${inner}</button>`
|
|
102
|
+
: `<span class="click-text">${inner}</span>`}</div>`;
|
|
80
103
|
}
|
|
81
104
|
/**
|
|
82
105
|
* #1672: a status line (another instance's message, a daemon notice) is muted, read-only plain text — never Markdown,
|
|
@@ -99,6 +122,7 @@ export function createThread(list, scroller, opts) {
|
|
|
99
122
|
*/
|
|
100
123
|
function replyButtonsHtml(b) {
|
|
101
124
|
if (!b || typeof b.id !== "string" || !Array.isArray(b.labels) || !b.labels.length) return "";
|
|
125
|
+
if (b.poll && typeof b.poll === "object") return pollHtml(b);
|
|
102
126
|
const busy = !!(opts.replyButtonBusy && opts.replyButtonBusy(b.id));
|
|
103
127
|
const off = b.state !== "open" || busy;
|
|
104
128
|
const items = b.labels.map((label, i) => {
|
|
@@ -108,12 +132,51 @@ export function createThread(list, scroller, opts) {
|
|
|
108
132
|
const note = b.state === "chosen" ? trf("chat.rbChosen", b.by || "") : b.state === "expired" ? tr("chat.rbExpired") : "";
|
|
109
133
|
return `<div class="rb-row" role="group" aria-label="${escAttr(tr("chat.rbGroup"))}">${items}${note ? `<span class="rb-note">${escAttr(note)}</span>` : ""}</div>`;
|
|
110
134
|
}
|
|
135
|
+
/**
|
|
136
|
+
* #1823: a poll. Anyone here may click while it is open; the page marks its own vote (opts.pollMine), which no one else
|
|
137
|
+
* sees. Counts only when the server sends them (revealed: live, or closed); names only at close with show_voters. The
|
|
138
|
+
* time left is not in the html (a node is kept by its html): pollLeft writes it, every second.
|
|
139
|
+
*/
|
|
140
|
+
function pollHtml(b) {
|
|
141
|
+
const p = b.poll, closed = b.state === "closed";
|
|
142
|
+
const busy = !!(opts.replyButtonBusy && opts.replyButtonBusy(b.id));
|
|
143
|
+
const mine = (opts.pollMine && opts.pollMine(b.id)) || [];
|
|
144
|
+
const counts = Array.isArray(p.counts) && p.counts.length === b.labels.length ? p.counts : null;
|
|
145
|
+
const items = b.labels.map((label, i) => {
|
|
146
|
+
const me = !closed && mine.includes(i);
|
|
147
|
+
const tally = counts ? ` · ${counts[i]}${closed && p.answered ? ` (${Math.round((counts[i] / p.answered) * 100)}%)` : ""}` : "";
|
|
148
|
+
return `<button type="button" class="btn btn-sm rb-btn${me ? " chosen" : ""}" data-act="replyButton" data-arg="${escAttr(`${b.id}:${i}`)}" aria-pressed="${me ? "true" : "false"}"${closed || busy ? " disabled" : ""}>${escAttr(`${me ? "✓ " : ""}${label}${tally}`)}</button>`;
|
|
149
|
+
}).join("");
|
|
150
|
+
const status = closed
|
|
151
|
+
? escAttr(trf("chat.pollClosed", p.answered))
|
|
152
|
+
: `${escAttr(trf("chat.pollAnswered", p.answered))} · ⏳ <span class="poll-left" data-deadline="${escAttr(p.deadline)}" title="${escAttr(tr("chat.pollLeft"))}"></span>`;
|
|
153
|
+
const voters = closed && Array.isArray(p.voters)
|
|
154
|
+
? `<ul class="poll-voters" aria-label="${escAttr(tr("chat.pollVoters"))}">${b.labels.map((label, i) => `<li>${escAttr(`${label}: ${(p.voters[i] || []).join(", ") || "—"}`)}</li>`).join("")}</ul>`
|
|
155
|
+
: "";
|
|
156
|
+
return `<div class="rb-row poll" role="group" aria-label="${escAttr(tr("chat.pollGroup"))}">${items}<span class="rb-note">${status}</span></div>${voters}`;
|
|
157
|
+
}
|
|
158
|
+
/** #1823: the time left on this node's open polls, now. */
|
|
159
|
+
function pollLeft(node) {
|
|
160
|
+
for (const el of node.querySelectorAll(".poll-left[data-deadline]")) {
|
|
161
|
+
const v = R().pollRemaining(Number(el.dataset.deadline) - (opts.serverNow ? opts.serverNow() : Date.now()));
|
|
162
|
+
if (el.textContent !== v) el.textContent = v;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
// #1823: while an open poll is on screen, its time left is rewritten every second.
|
|
166
|
+
let pollTick = null;
|
|
167
|
+
function armPolls() {
|
|
168
|
+
const open = nodes.size > 0 && !!list.querySelector(".poll-left[data-deadline]");
|
|
169
|
+
if (open && !pollTick) pollTick = setInterval(() => pollLeft(list), 1000);
|
|
170
|
+
else if (!open && pollTick) { clearInterval(pollTick); pollTick = null; }
|
|
171
|
+
}
|
|
172
|
+
|
|
111
173
|
function msgNode(html, x, stoppedKeys) {
|
|
112
174
|
const tpl = list.ownerDocument.createElement("template");
|
|
113
175
|
tpl.innerHTML = html;
|
|
114
176
|
const node = tpl.content.firstElementChild;
|
|
115
177
|
decorateCode(node);
|
|
116
178
|
timeLabel(node);
|
|
179
|
+
pollLeft(node);
|
|
117
180
|
if (x) decorateHtmlCards(node, x, stoppedKeys || []);
|
|
118
181
|
if (x) decorateTextCards(node, x);
|
|
119
182
|
return node;
|
|
@@ -388,6 +451,44 @@ export function createThread(list, scroller, opts) {
|
|
|
388
451
|
const ro = typeof ResizeObserver === "function" ? new ResizeObserver(resized) : null;
|
|
389
452
|
if (ro) { ro.observe(list); ro.observe(scroller); }
|
|
390
453
|
|
|
454
|
+
/** Where drawing starts in `msgs`: at the oldest drawn message, or (none yet) the newest THREAD_TAIL. */
|
|
455
|
+
function startIndex() {
|
|
456
|
+
const i = firstKey === null ? -1 : msgs.findIndex((x) => msgKey(x) === firstKey);
|
|
457
|
+
// Whatever is drawn stays drawn: the list may have reordered (merged across fleet restarts) or lost its oldest to
|
|
458
|
+
// the cap — so the window starts at the earlier of the oldest drawn key and the first message still drawn.
|
|
459
|
+
const still = nodes.size ? msgs.findIndex((x) => nodes.has(msgKey(x))) : -1;
|
|
460
|
+
const at = [i, still].filter((n) => n >= 0);
|
|
461
|
+
return at.length ? Math.min(...at) : Math.max(0, msgs.length - THREAD_TAIL);
|
|
462
|
+
}
|
|
463
|
+
/** Draw the next chunk of older messages, keeping the reader where they were; then the next, until all are drawn. */
|
|
464
|
+
function grow() {
|
|
465
|
+
growTimer = growFrame = null;
|
|
466
|
+
if (disposed) return; // the chat left: nothing more is drawn
|
|
467
|
+
const at = startIndex();
|
|
468
|
+
if (at <= 0) return;
|
|
469
|
+
firstKey = msgKey(msgs[Math.max(0, at - THREAD_CHUNK)]);
|
|
470
|
+
const fromBottom = scroller.scrollHeight - scroller.scrollTop;
|
|
471
|
+
draw({ older: true }); // drawn above the reader: never "new below" (render alone counts those)
|
|
472
|
+
if (pinned) toBottom();
|
|
473
|
+
else { scroller.scrollTop = scroller.scrollHeight - fromBottom; lastTop = scroller.scrollTop; }
|
|
474
|
+
scheduleGrow();
|
|
475
|
+
}
|
|
476
|
+
/** After the next frame has painted (the click's own frame stays light), draw a chunk more. */
|
|
477
|
+
function scheduleGrow() {
|
|
478
|
+
if (growTimer !== null || growFrame !== null || startIndex() <= 0) return;
|
|
479
|
+
const later = () => { growFrame = null; growTimer = setTimeout(grow, 0); };
|
|
480
|
+
if (typeof requestAnimationFrame === "function") growFrame = requestAnimationFrame(later); else later();
|
|
481
|
+
}
|
|
482
|
+
/** Everything drawn now (a remembered scroll position, a message to show that is not drawn yet). */
|
|
483
|
+
function growAll() {
|
|
484
|
+
if (startIndex() <= 0) return;
|
|
485
|
+
if (growFrame !== null && typeof cancelAnimationFrame === "function") cancelAnimationFrame(growFrame);
|
|
486
|
+
if (growTimer !== null) clearTimeout(growTimer);
|
|
487
|
+
growTimer = growFrame = null;
|
|
488
|
+
firstKey = msgs.length ? msgKey(msgs[0]) : null;
|
|
489
|
+
draw({ older: true });
|
|
490
|
+
}
|
|
491
|
+
|
|
391
492
|
/** Draw `next` (this instance's messages). `restore`: the scroll position to return to (null = the bottom). */
|
|
392
493
|
function render(next, renderOpts = {}) {
|
|
393
494
|
const stick = pinned || nearBottom();
|
|
@@ -395,37 +496,51 @@ export function createThread(list, scroller, opts) {
|
|
|
395
496
|
if (!msgs.length) {
|
|
396
497
|
for (const v of nodes.values()) stopIn(v.node);
|
|
397
498
|
nodes.clear();
|
|
499
|
+
firstKey = null;
|
|
398
500
|
armDay();
|
|
399
501
|
list.textContent = "";
|
|
400
502
|
opts.onEmpty(true);
|
|
401
503
|
return;
|
|
402
504
|
}
|
|
403
505
|
opts.onEmpty(false);
|
|
404
|
-
|
|
506
|
+
// A position to return to that is not the bottom needs the messages above it: all of them, at once (as before).
|
|
507
|
+
if ("restore" in renderOpts && renderOpts.restore != null) firstKey = msgKey(msgs[0]);
|
|
508
|
+
const added = draw({});
|
|
509
|
+
if (firstKey === null || startIndex() > 0) { firstKey = msgKey(msgs[startIndex()]); scheduleGrow(); }
|
|
510
|
+
if ("restore" in renderOpts) {
|
|
511
|
+
unseen = 0;
|
|
512
|
+
if (renderOpts.restore == null) toBottom();
|
|
513
|
+
else { scroller.scrollTop = renderOpts.restore; pinned = nearBottom(); lastTop = scroller.scrollTop; }
|
|
514
|
+
} else if (stick) toBottom();
|
|
515
|
+
else unseen += added;
|
|
516
|
+
reportJump();
|
|
517
|
+
}
|
|
518
|
+
/** Put the messages from startIndex() on in the list (nodes reused when unchanged). Returns how many are new. */
|
|
519
|
+
function draw(drawOpts) {
|
|
520
|
+
const shown = msgs.slice(startIndex());
|
|
521
|
+
const keep = new Set(shown.map(msgKey));
|
|
405
522
|
for (const [k, v] of nodes) if (!keep.has(k)) { stopIn(v.node); v.node.remove(); nodes.delete(k); }
|
|
406
523
|
let prev = null, added = 0;
|
|
407
|
-
for (const x of
|
|
408
|
-
const key = msgKey(x)
|
|
524
|
+
for (const x of shown) {
|
|
525
|
+
const key = msgKey(x);
|
|
409
526
|
let have = nodes.get(key);
|
|
527
|
+
// Filling in older messages: a node drawn from this very message object is current — not drawn again.
|
|
528
|
+
if (drawOpts.older && have && have.x === x) { prev = have.node; continue; }
|
|
529
|
+
const html = msgHtml(x);
|
|
410
530
|
if (!have || have.html !== html) {
|
|
411
531
|
const stopped = have ? stopIn(have.node) : [];
|
|
412
532
|
const node = msgNode(html, x, stopped);
|
|
413
533
|
if (have) have.node.replaceWith(node); else added++;
|
|
414
|
-
have = { html, node }; nodes.set(key, have);
|
|
415
|
-
} else timeLabel(have.node);
|
|
534
|
+
have = { html, node, x }; nodes.set(key, have);
|
|
535
|
+
} else { have.x = x; timeLabel(have.node); }
|
|
416
536
|
const want = prev ? prev.nextSibling : list.firstChild;
|
|
417
537
|
// A node already in the list is being moved: its preview stops first. A new node (still in its template) has none.
|
|
418
538
|
if (have.node !== want) { if (have.node.parentNode === list) stopIn(have.node); list.insertBefore(have.node, want); }
|
|
419
539
|
prev = have.node;
|
|
420
540
|
}
|
|
421
541
|
armDay();
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
if (renderOpts.restore == null) toBottom();
|
|
425
|
-
else { scroller.scrollTop = renderOpts.restore; pinned = nearBottom(); lastTop = scroller.scrollTop; }
|
|
426
|
-
} else if (stick) toBottom();
|
|
427
|
-
else unseen += added;
|
|
428
|
-
reportJump();
|
|
542
|
+
armPolls();
|
|
543
|
+
return added;
|
|
429
544
|
}
|
|
430
545
|
|
|
431
546
|
function onScroll() {
|
|
@@ -440,6 +555,7 @@ export function createThread(list, scroller, opts) {
|
|
|
440
555
|
function jumpLatest() { toBottom(); unseen = 0; reportJump(); }
|
|
441
556
|
/** Show one message (the panel's "from …" link, #1481): scrolled into view and briefly marked. */
|
|
442
557
|
function reveal(key) {
|
|
558
|
+
if (!nodes.has(key) && msgs.some((x) => msgKey(x) === key)) growAll(); // an older message not drawn yet
|
|
443
559
|
const have = nodes.get(key); if (!have) return false;
|
|
444
560
|
if (typeof have.node.scrollIntoView === "function") have.node.scrollIntoView({ block: "center" });
|
|
445
561
|
have.node.classList.add("flash");
|
|
@@ -489,15 +605,17 @@ export function createThread(list, scroller, opts) {
|
|
|
489
605
|
else if (act === "replyButton") {
|
|
490
606
|
const m = /^([0-9a-f]{32}):(\d{1,2})$/.exec(el.dataset.arg || "");
|
|
491
607
|
if (m && opts.clickReplyButton) opts.clickReplyButton(m[1], Number(m[2]));
|
|
492
|
-
}
|
|
608
|
+
} else if (act === "showButtons") { const x = msgs.find(y => y.buttons && y.buttons.id === el.dataset.arg); if (x) reveal(msgKey(x)); }
|
|
493
609
|
}
|
|
494
610
|
|
|
495
611
|
/** The thread goes (the chat left, or another instance opened): every preview in it stops first. */
|
|
496
612
|
function dispose() {
|
|
613
|
+
disposed = true;
|
|
497
614
|
if (ro) ro.disconnect();
|
|
498
615
|
if (P()) P().stopAll("leave");
|
|
499
616
|
nodes.clear();
|
|
500
617
|
armDay();
|
|
618
|
+
armPolls();
|
|
501
619
|
}
|
|
502
620
|
|
|
503
621
|
return { render, onScroll, onClick, jumpLatest, reveal, refreshCards, dispose, nodes, get unseen() { return unseen; } };
|
package/dist/ui/chat-tour.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
// #1366: the
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
1
|
+
// #1366: the chat tour, in the app shell (#1408). Since #1854 (rank 6) it never starts by itself: a first visit opens
|
|
2
|
+
// on a quiet page, and Settings → This device → Chat tour starts it on an agent's chat. It walks only the steps whose
|
|
3
|
+
// target is on the page. Got it, Skip or Esc ends it and records it (agend_tour_done, kept as before: no new key). A
|
|
4
|
+
// card that outlines one thing at a time: no overlay, nothing blocked, and no style attribute — the outline is a class
|
|
5
|
+
// (#1300).
|
|
5
6
|
//
|
|
6
7
|
// Esc order: the phone drawer covers the card, so it closes first (the shell); then the tour; only then may Esc reach
|
|
7
8
|
// the chat and stop a reply. The Esc that closed the tour is spent: its key repeats (a held key) must not go on to stop
|
|
@@ -24,6 +25,8 @@ const STEPS = [
|
|
|
24
25
|
: document.querySelector(".badge-await") || byId("instanceList")) },
|
|
25
26
|
];
|
|
26
27
|
let at = -1, spot = null, returnTo = null, escHeld = false, installed = false;
|
|
28
|
+
/** The steps of the running tour: those whose target was on the page when it started (#1854: target-aware). */
|
|
29
|
+
let steps = STEPS;
|
|
27
30
|
|
|
28
31
|
export function tourDone() { try { return localStorage.getItem(TOUR_KEY) === "1"; } catch { return true; } }
|
|
29
32
|
export function tourStep() { return at; }
|
|
@@ -32,13 +35,15 @@ export function startTour() {
|
|
|
32
35
|
// Replayed from the drawer: the drawer closes first (focus goes back to ☰), so the card is seen.
|
|
33
36
|
closeDrawer();
|
|
34
37
|
if (at < 0) returnTo = document.activeElement;
|
|
38
|
+
steps = STEPS.filter((s) => s.spot());
|
|
39
|
+
if (!steps.length) return;
|
|
35
40
|
show(0);
|
|
36
41
|
}
|
|
37
42
|
|
|
38
43
|
/** The current step's outline follows a redraw (a new badge, Send swapped for Stop reply) — same card, same focus. */
|
|
39
44
|
export function refreshTourSpot() {
|
|
40
45
|
if (at < 0) return;
|
|
41
|
-
const next =
|
|
46
|
+
const next = steps[at].spot() || null;
|
|
42
47
|
if (next === spot && (!next || next.classList.contains("tour-spot"))) return;
|
|
43
48
|
if (spot) spot.classList.remove("tour-spot");
|
|
44
49
|
spot = next;
|
|
@@ -46,10 +51,10 @@ export function refreshTourSpot() {
|
|
|
46
51
|
}
|
|
47
52
|
|
|
48
53
|
function show(i) {
|
|
49
|
-
if (i < 0 || i >=
|
|
54
|
+
if (i < 0 || i >= steps.length) return;
|
|
50
55
|
at = i;
|
|
51
56
|
if (spot) spot.classList.remove("tour-spot");
|
|
52
|
-
spot =
|
|
57
|
+
spot = steps[i].spot() || null;
|
|
53
58
|
if (spot) spot.classList.add("tour-spot");
|
|
54
59
|
let card = byId("tour");
|
|
55
60
|
if (!card) {
|
|
@@ -60,18 +65,18 @@ function show(i) {
|
|
|
60
65
|
}
|
|
61
66
|
card.textContent = "";
|
|
62
67
|
const title = document.createElement("h2"); title.id = "tourTitle"; title.textContent = t("chat.tourTitle");
|
|
63
|
-
const text = document.createElement("p"); text.className = "tour-text"; text.setAttribute("aria-live", "polite"); text.textContent = t(
|
|
68
|
+
const text = document.createElement("p"); text.className = "tour-text"; text.setAttribute("aria-live", "polite"); text.textContent = t(steps[i].text);
|
|
64
69
|
const foot = document.createElement("div"); foot.className = "tour-foot";
|
|
65
|
-
const count = document.createElement("span"); count.className = "tour-count"; count.textContent = t("chat.tourStep", i + 1,
|
|
70
|
+
const count = document.createElement("span"); count.className = "tour-count"; count.textContent = t("chat.tourStep", i + 1, steps.length);
|
|
66
71
|
const button = (label, act, arg, cls) => {
|
|
67
72
|
const b = document.createElement("button"); b.type = "button"; b.className = `btn btn-sm ${cls}`; b.dataset.tour = act;
|
|
68
73
|
if (arg != null) b.dataset.arg = String(arg);
|
|
69
74
|
b.textContent = t(label); return b;
|
|
70
75
|
};
|
|
71
76
|
foot.append(count);
|
|
72
|
-
if (i <
|
|
77
|
+
if (i < steps.length - 1) foot.append(button("chat.tourSkip", "end", null, "btn-ghost"));
|
|
73
78
|
if (i > 0) foot.append(button("chat.tourBack", "move", -1, "btn-ghost"));
|
|
74
|
-
const next = i <
|
|
79
|
+
const next = i < steps.length - 1 ? button("chat.tourNext", "move", 1, "btn-primary") : button("chat.tourDone", "end", null, "btn-primary");
|
|
75
80
|
foot.append(next);
|
|
76
81
|
card.append(title, text, foot);
|
|
77
82
|
next.focus();
|
|
@@ -105,12 +110,10 @@ export function onTourKey(e) {
|
|
|
105
110
|
if (at >= 0) { e.preventDefault(); e.stopPropagation(); endTour(); escHeld = true; }
|
|
106
111
|
}
|
|
107
112
|
|
|
108
|
-
/** Once per page: the key handler
|
|
113
|
+
/** Once per page: the key handler and the language follow-up. Nothing is shown: the tour is started by a person (#1854). */
|
|
109
114
|
export function installTour() {
|
|
110
115
|
if (installed) return;
|
|
111
116
|
installed = true;
|
|
112
117
|
document.addEventListener("keydown", onTourKey, true);
|
|
113
118
|
onLang(() => { if (at >= 0) show(at); });
|
|
114
|
-
// Shown first on the chat (it points at the chat's own controls), never over View or another panel.
|
|
115
|
-
if (!tourDone() && /^\/ui(\/chat\/|$)/.test(location.pathname)) setTimeout(startTour, 0);
|
|
116
119
|
}
|
package/dist/ui/panel-chat.js
CHANGED
|
@@ -33,7 +33,6 @@ import { createMentionIndex } from "./chat-mentions.js";
|
|
|
33
33
|
import { PreviewPanel, claimPanel, openPanel, panelClaims, panelStore, panelVersion, shownKey } from "./preview-panel.js";
|
|
34
34
|
import { needsArgument, paletteFor, parseCommandLine } from "./chat-commands.js";
|
|
35
35
|
import { installTour, refreshTourSpot } from "./chat-tour.js";
|
|
36
|
-
import { FirstRunCard } from "./first-run.js";
|
|
37
36
|
import { LightboxHost, openLightbox } from "./image-lightbox.js";
|
|
38
37
|
|
|
39
38
|
const R = () => globalThis.AgendChatRender;
|
|
@@ -148,14 +147,15 @@ export function ChatPanel({ route, navKey }) {
|
|
|
148
147
|
}, [name, !!inst]);
|
|
149
148
|
useEffect(() => { setTitle(name || t("app.chat")); }, [name, navKey]);
|
|
150
149
|
if (!name) {
|
|
151
|
-
// #
|
|
150
|
+
// #1854 (rank 7): one instruction — pick an agent, or with none at all, the one way to make one. Connecting a chat
|
|
151
|
+
// app (the first-run card, #1519 P7) is offered in Settings, not here: it competed with the first agent.
|
|
152
152
|
const none = app.ready && !app.instances.length;
|
|
153
153
|
return html`<div class="panel p-chat"><${PanelHeader} title=${t("app.chat")} />
|
|
154
|
-
<div class="panel-body center"
|
|
155
|
-
${none ? html`<${Empty} icon="bot" title=${t("
|
|
156
|
-
action=${html`<button type="button" class="btn btn-primary" onClick=${requestNewInstance}><${Icon} name="plus" size=${16} />${t("
|
|
157
|
-
: html`<${Empty} icon="chat" title=${t("chat.pick")}
|
|
158
|
-
action=${html`<button type="button" class="btn only-narrow" onClick=${openDrawer}>${t("chat.backToList")}</button>`} />`}</div></div
|
|
154
|
+
<div class="panel-body center">
|
|
155
|
+
${none ? html`<${Empty} icon="bot" title=${t("chat.noAgents")}
|
|
156
|
+
action=${html`<button type="button" class="btn btn-primary" onClick=${requestNewInstance}><${Icon} name="plus" size=${16} />${t("chat.newAgent")}</button>`} />`
|
|
157
|
+
: html`<${Empty} icon="chat" title=${t("chat.pick")}
|
|
158
|
+
action=${html`<button type="button" class="btn only-narrow" onClick=${openDrawer}>${t("chat.backToList")}</button>`} />`}</div></div>`;
|
|
159
159
|
}
|
|
160
160
|
if (!app.ready) return html`<div class="panel p-chat"><${PanelHeader} title=${name} /><div class="panel-body"><${Skeleton} lines=${5} /></div></div>`;
|
|
161
161
|
if (!inst) return html`<${NotFound} name=${name} />`;
|
|
@@ -252,6 +252,8 @@ function Thread({ name, th }) {
|
|
|
252
252
|
setPreviewOptIn, copyText, download: downloadHtml, toggleWrap,
|
|
253
253
|
clickReplyButton: (id, index) => store.clickReplyButton(name, id, index), // #1266
|
|
254
254
|
replyButtonBusy: (id) => store.state.rbBusy.has(id),
|
|
255
|
+
pollMine: (id) => store.state.pollMine[id], // #1823: this device's own vote
|
|
256
|
+
serverNow: () => Date.now() + store.state.clockSkew, // #1823: the deadline is the server's
|
|
255
257
|
panel: { shown: shownKey, open: (spec, o) => openPanel(spec, o), version: panelVersion, claim: claimPanel, claims: panelClaims }, // #1481, #1704
|
|
256
258
|
openImage: openLightbox, // an image, full size, in the page
|
|
257
259
|
emojis: (id) => !!emojiIndex && emojiIndex.known(id), // #1630: the fleet's server emoji
|
|
@@ -286,7 +288,7 @@ function Thread({ name, th }) {
|
|
|
286
288
|
}, [name]);
|
|
287
289
|
return html`<div class="scroller" ref=${scroller}>
|
|
288
290
|
<div class="thread" ref=${list} role="log" aria-live="off" aria-label=${t("chat.conversationWith", name)}></div>
|
|
289
|
-
${empty ? html`<${Empty} icon="chat" title=${t("chat.noMessages")}
|
|
291
|
+
${empty ? html`<${Empty} icon="chat" title=${t("chat.noMessages")} />` : null}
|
|
290
292
|
${jump.show ? html`<button type="button" class="jump-latest" onClick=${() => th.current && th.current.jumpLatest()}>
|
|
291
293
|
<${Icon} name="down" size=${16} />${jump.unseen > 0 ? t("chat.newBelow", jump.unseen) : t("chat.jumpLatest")}</button>` : null}
|
|
292
294
|
</div>`;
|
|
@@ -305,7 +307,7 @@ function Dock({ name, inst, lease, exec, awaiting }) {
|
|
|
305
307
|
<button type="button" class="btn btn-primary btn-sm" onClick=${() => store.putBack(name)}>${t("chat.putBack")}</button>
|
|
306
308
|
<button type="button" class="btn btn-sm" onClick=${() => store.discardFailed(name)}>${t("chat.discard")}</button></div>` : null}
|
|
307
309
|
<${Composer} name=${name} lease=${lease} busy=${R().isBusy(exec)} />
|
|
308
|
-
<p class="hint">${t("chat.composerHint")}</p>
|
|
310
|
+
<p class="hint" title=${t("chat.composerMore")}>${t("chat.composerHint")}</p>
|
|
309
311
|
</div></div>`;
|
|
310
312
|
}
|
|
311
313
|
|
|
@@ -320,6 +322,10 @@ function CommandCard({ name, lease }) {
|
|
|
320
322
|
aria-label=${t("chat.cmdDismiss")} onClick=${() => store.dismissCommand(name)}><${Icon} name="close" size=${14} /></button>`}</div>
|
|
321
323
|
${c.error ? html`<p class="cmd-text">${c.error}</p>` : null}
|
|
322
324
|
${c.text ? html`<pre class="cmd-text">${c.text}</pre>` : null}
|
|
325
|
+
${c.compact ? html`<p class="cmd-text cmd-phase">${c.compact === "running" ? t("chat.compactRunning")
|
|
326
|
+
: c.compact === "done" ? t("chat.compactDone") : c.compact === "timeout" ? t("chat.compactTimeout", "10")
|
|
327
|
+
: c.compact === "lost" ? t("chat.compactLost") : c.compact === "not_compacted" ? t("chat.compactNotCompacted") : t("chat.compactUnknown")}</p>` : null}
|
|
328
|
+
${c.choices && c.choices.prompt ? html`<pre class="cmd-text">${c.choices.prompt}</pre>` : null}
|
|
323
329
|
${c.choices ? html`<div class="cmd-choices" role="group" aria-label=${t("chat.cmdChoose")}>${c.choices.options.map((o) => html`<button key=${o.id} type="button"
|
|
324
330
|
class=${`btn btn-sm${o.id === c.choices.current ? " current" : ""}`} aria-current=${o.id === c.choices.current ? "true" : undefined} onClick=${() => run(o.id)}>${o.label}</button>`)}</div>` : null}
|
|
325
331
|
</div>`;
|
|
@@ -431,7 +437,7 @@ function Composer({ name, lease, busy }) {
|
|
|
431
437
|
${files.length ? html`<div class="pending-files">${files.map((f, i) => html`<${FileChip} key=${`${f.name}-${i}`} f=${f} i=${i} name=${name} />`)}</div>` : null}
|
|
432
438
|
<div class="composer">
|
|
433
439
|
<button id="attachBtn" type="button" class="icon-btn" title=${t("chat.attachTitle")} aria-label=${t("chat.attachTitle")} onClick=${() => fileIn.current && fileIn.current.click()}><${Icon} name="attach" /></button>
|
|
434
|
-
<input ref=${fileIn} type="file" multiple hidden
|
|
440
|
+
<input ref=${fileIn} type="file" multiple hidden
|
|
435
441
|
onChange=${(e) => { store.addFiles(name, e.target.files); e.target.value = ""; }} />
|
|
436
442
|
<textarea id="msgIn" ref=${input} rows="1" value=${value} placeholder=${t("chat.composerPlaceholder", name)} aria-label=${t("chat.composerPlaceholder", name)}
|
|
437
443
|
autocomplete="off" aria-controls=${palette ? "cmdPalette" : undefined} aria-expanded=${palette ? "true" : "false"}
|