@songsid/agend 2.3.3-beta.4 → 2.3.3-beta.5
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/dist/agent-cli.js +13 -112
- package/dist/agent-cli.js.map +1 -1
- package/dist/agent-endpoint.d.ts +0 -1
- package/dist/agent-endpoint.js +1 -9
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/agent-whats-new.d.ts +3 -6
- package/dist/agent-whats-new.js +7 -18
- package/dist/agent-whats-new.js.map +1 -1
- package/dist/auth-api.d.ts +0 -7
- package/dist/auth-api.js +1 -15
- package/dist/auth-api.js.map +1 -1
- package/dist/backend/codex.d.ts +1 -0
- package/dist/backend/codex.js +106 -15
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/types.d.ts +17 -0
- package/dist/backend/types.js.map +1 -1
- package/dist/bundled-skills.d.ts +4 -6
- package/dist/bundled-skills.js +10 -14
- package/dist/bundled-skills.js.map +1 -1
- package/dist/channel/mcp-server.js +5 -5
- package/dist/channel/mcp-server.js.map +1 -1
- package/dist/channel/mcp-tools.d.ts +1 -3
- package/dist/channel/mcp-tools.js +4 -58
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/cli.js +3 -1
- package/dist/cli.js.map +1 -1
- package/dist/config-validator.js +6 -3
- package/dist/config-validator.js.map +1 -1
- package/dist/daemon.d.ts +11 -3
- package/dist/daemon.js +59 -107
- package/dist/daemon.js.map +1 -1
- package/dist/dashboard-live.d.ts +1 -1
- package/dist/dashboard-live.js +3 -3
- package/dist/dashboard-live.js.map +1 -1
- package/dist/dashboard-permissions.d.ts +1 -1
- package/dist/dashboard-permissions.js +1 -3
- package/dist/dashboard-permissions.js.map +1 -1
- package/dist/fleet-manager.d.ts +26 -44
- package/dist/fleet-manager.js +70 -226
- package/dist/fleet-manager.js.map +1 -1
- package/dist/general-knowledge/skills/credential-profiles/SKILL.md +11 -1
- package/dist/general-knowledge/skills/session-management/SKILL.md +6 -0
- package/dist/general-knowledge/skills/web-appearance/SKILL.md +13 -204
- package/dist/general-knowledge/skills/web-dashboard/SKILL.md +26 -63
- package/dist/instance-lifecycle.js +1 -1
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/locale.js +2 -0
- package/dist/locale.js.map +1 -1
- package/dist/outbound-handlers.js +19 -7
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +0 -62
- package/dist/outbound-schemas.js +2 -41
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/public-web-gateway.js +0 -9
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/public-web-link.d.ts +2 -0
- package/dist/public-web-link.js +5 -0
- package/dist/public-web-link.js.map +1 -1
- package/dist/search/search-docs.d.ts +1 -1
- package/dist/search/search-docs.js +2 -2
- package/dist/search/search-docs.js.map +1 -1
- package/dist/settings-api.d.ts +6 -0
- package/dist/settings-api.js +14 -2
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-change.js +3 -1
- package/dist/settings-change.js.map +1 -1
- package/dist/tool-permissions.js +3 -10
- package/dist/tool-permissions.js.map +1 -1
- package/dist/topic-commands.d.ts +6 -2
- package/dist/topic-commands.js +2 -0
- package/dist/topic-commands.js.map +1 -1
- package/dist/types.d.ts +2 -1
- package/dist/ui/chat-ambient.js +4 -4
- package/dist/ui/chat-commands.js +3 -2
- package/dist/ui/chat-store.js +4 -1
- package/dist/ui/chat-strings.js +6 -42
- package/dist/ui/chat-thread.js +6 -44
- package/dist/ui/panel-chat.js +21 -50
- package/dist/ui/preview-panel.js +3 -15
- package/dist/ui/search-dialog.js +30 -14
- package/dist/ui/settings-appearance.js +1 -3
- package/dist/ui/settings-guests.js +13 -4
- package/dist/ui/settings-learn.js +1 -1
- package/dist/ui/settings-mods.js +5 -12
- package/dist/ui/settings-registry.js +0 -1
- package/dist/ui/settings-strings.js +6 -6
- package/dist/ui/shared/app-prefs.js +17 -17
- package/dist/ui/shared/app-shell.js +12 -8
- package/dist/ui/shared/app-store.js +6 -2
- package/dist/ui/shared/app.css +4 -36
- package/dist/ui/shared/app.js +5 -4
- package/dist/ui/shared/instance-nav.js +9 -9
- package/dist/ui/shared/instance-switch.js +8 -2
- package/dist/ui/shared/panel-view.js +22 -8
- package/dist/ui/shared/view-strings.js +6 -0
- package/dist/usage/usage-api.d.ts +3 -0
- package/dist/usage/usage-api.js +5 -1
- package/dist/usage/usage-api.js.map +1 -1
- package/dist/view-api.d.ts +2 -0
- package/dist/view-api.js +64 -0
- package/dist/view-api.js.map +1 -1
- package/dist/web-api.d.ts +9 -17
- package/dist/web-api.js +255 -371
- package/dist/web-api.js.map +1 -1
- package/dist/web-appearance-tools.d.ts +0 -2
- package/dist/web-appearance-tools.js +2 -5
- package/dist/web-appearance-tools.js.map +1 -1
- package/dist/web-appearance-view.d.ts +0 -6
- package/dist/web-appearance-view.js +0 -1
- package/dist/web-appearance-view.js.map +1 -1
- package/dist/web-appearance.d.ts +14 -7
- package/dist/web-appearance.js +5 -6
- package/dist/web-appearance.js.map +1 -1
- package/dist/web-asset-parts.d.ts +1 -17
- package/dist/web-asset-parts.js +1 -15
- package/dist/web-asset-parts.js.map +1 -1
- package/dist/web-auth.js +1 -7
- package/dist/web-auth.js.map +1 -1
- package/dist/web-chat-history.d.ts +3 -7
- package/dist/web-chat-history.js +10 -17
- package/dist/web-chat-history.js.map +1 -1
- package/dist/web-commands.d.ts +9 -1
- package/dist/web-commands.js +13 -0
- package/dist/web-commands.js.map +1 -1
- package/dist/web-experimental.d.ts +2 -11
- package/dist/web-experimental.js +8 -36
- package/dist/web-experimental.js.map +1 -1
- package/dist/web-guest-access.d.ts +40 -2
- package/dist/web-guest-access.js +50 -14
- package/dist/web-guest-access.js.map +1 -1
- package/dist/web-mod-bundle.d.ts +2 -45
- package/dist/web-mod-bundle.js +15 -63
- package/dist/web-mod-bundle.js.map +1 -1
- package/dist/web-mod-store.d.ts +3 -48
- package/dist/web-mod-store.js +30 -97
- package/dist/web-mod-store.js.map +1 -1
- package/dist/web-prefs.d.ts +13 -0
- package/dist/web-prefs.js +38 -0
- package/dist/web-prefs.js.map +1 -1
- package/dist/web-preview.d.ts +1 -11
- package/dist/web-preview.js +6 -20
- package/dist/web-preview.js.map +1 -1
- package/dist/web-session.d.ts +11 -3
- package/dist/web-session.js +22 -11
- package/dist/web-session.js.map +1 -1
- package/dist/web-settings-patch.d.ts +1 -0
- package/dist/web-settings-patch.js +6 -3
- package/dist/web-settings-patch.js.map +1 -1
- package/dist/web-settings-tools.d.ts +0 -13
- package/dist/web-settings-tools.js +12 -84
- package/dist/web-settings-tools.js.map +1 -1
- package/package.json +1 -1
- package/dist/general-knowledge/skills/web-appearance/SKILL.canvas-worker-off.md +0 -376
- package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +0 -672
- package/dist/ui/app-effects.js +0 -251
- package/dist/ui/chat-effect-posters.js +0 -42
- package/dist/ui/chat-effects.js +0 -167
- package/dist/ui/effect-trial-controller.js +0 -124
- package/dist/ui/effect-trial-offer.js +0 -2
- package/dist/ui/effect-trial-panel.js +0 -40
- package/dist/ui/effects-waker.js +0 -2
- package/dist/ui/settings-effects.js +0 -202
- package/dist/ui/settings-owner-effects.js +0 -82
- package/dist/ui/shared/effect-app-runtime.js +0 -299
- package/dist/ui/shared/effect-baseline.js +0 -64
- package/dist/ui/shared/effect-chat-runtime.js +0 -162
- package/dist/ui/shared/effect-cursor.js +0 -126
- package/dist/ui/shared/effect-display-metadata.js +0 -39
- package/dist/ui/shared/effect-frame.js +0 -146
- package/dist/ui/shared/effect-geometry.js +0 -137
- package/dist/ui/shared/effect-host-style.js +0 -10
- package/dist/ui/shared/effect-owner-regions.js +0 -74
- package/dist/ui/shared/effect-owner-source.js +0 -122
- package/dist/ui/shared/effect-policy.js +0 -151
- package/dist/ui/shared/effect-poster-reader.js +0 -98
- package/dist/ui/shared/effect-posters.js +0 -117
- package/dist/ui/shared/effect-profiler.js +0 -184
- package/dist/ui/shared/effect-profiles.js +0 -65
- package/dist/ui/shared/effect-registry.js +0 -545
- package/dist/ui/shared/effect-runtime-loader.js +0 -236
- package/dist/ui/shared/effect-safety.js +0 -153
- package/dist/ui/shared/effect-stock-snapshot.js +0 -43
- package/dist/ui/shared/effect-stock-trust.js +0 -217
- package/dist/ui/shared/effect-trial-shadow.js +0 -62
- package/dist/ui/shared/effect-trial-source.js +0 -103
- package/dist/ui/shared/effect-trial-stage.js +0 -170
- package/dist/ui/shared/effect-trial.css +0 -40
- package/dist/ui/shared/feature-flags.js +0 -127
- package/dist/ui/shared/owner-effect-prefs.js +0 -117
- package/dist/web-effect-bootstrap.d.ts +0 -12
- package/dist/web-effect-bootstrap.js +0 -49
- package/dist/web-effect-bootstrap.js.map +0 -1
- package/dist/web-effect-bundle.d.ts +0 -28
- package/dist/web-effect-bundle.js +0 -171
- package/dist/web-effect-bundle.js.map +0 -1
- package/dist/web-effect-canonical.d.ts +0 -8
- package/dist/web-effect-canonical.js +0 -27
- package/dist/web-effect-canonical.js.map +0 -1
- package/dist/web-effect-library.d.ts +0 -114
- package/dist/web-effect-library.js +0 -530
- package/dist/web-effect-library.js.map +0 -1
- package/dist/web-effect-playback.d.ts +0 -30
- package/dist/web-effect-playback.js +0 -71
- package/dist/web-effect-playback.js.map +0 -1
- package/dist/web-effect-poster.d.ts +0 -8
- package/dist/web-effect-poster.js +0 -52
- package/dist/web-effect-poster.js.map +0 -1
- package/dist/web-effect-profiles.d.ts +0 -118
- package/dist/web-effect-profiles.js +0 -181
- package/dist/web-effect-profiles.js.map +0 -1
- package/dist/web-effect-service.d.ts +0 -93
- package/dist/web-effect-service.js +0 -335
- package/dist/web-effect-service.js.map +0 -1
- package/dist/web-effect-tools.d.ts +0 -48
- package/dist/web-effect-tools.js +0 -129
- package/dist/web-effect-tools.js.map +0 -1
- package/dist/web-effect-trials.d.ts +0 -86
- package/dist/web-effect-trials.js +0 -204
- package/dist/web-effect-trials.js.map +0 -1
- package/dist/web-effect-upload.d.ts +0 -27
- package/dist/web-effect-upload.js +0 -185
- package/dist/web-effect-upload.js.map +0 -1
- package/dist/web-effect-worker.d.ts +0 -8
- package/dist/web-effect-worker.js +0 -170
- package/dist/web-effect-worker.js.map +0 -1
- package/dist/web-effect.d.ts +0 -103
- package/dist/web-effect.js +0 -153
- package/dist/web-effect.js.map +0 -1
- package/dist/web-experimental-http.d.ts +0 -2
- package/dist/web-experimental-http.js +0 -12
- package/dist/web-experimental-http.js.map +0 -1
- package/dist/web-mod-agent.d.ts +0 -50
- package/dist/web-mod-agent.js +0 -207
- package/dist/web-mod-agent.js.map +0 -1
package/dist/ui/chat-strings.js
CHANGED
|
@@ -17,26 +17,7 @@ register("chat", {
|
|
|
17
17
|
reply: "Reply", replyingTo: "Replying to {0}", replyCancel: "Cancel the reply (Esc)", replyHidden: "an earlier message", replyPhoto: "[Photo]", replyFile: "[File]",
|
|
18
18
|
replyGone: "That message is no longer in this chat.", replyJump: "Show the message this replies to", copyText: "Copy text", copyLink: "Copy link", msgMenu: "Message",
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
effectTrialReason_localAdmin: "Sign in as an admin outside the public link to Try.",
|
|
22
|
-
effectTrialReason_chatChanged: "Open the chat that offered this trial.",
|
|
23
|
-
effectTrialReason_busy: "Stop the current trial before starting another.",
|
|
24
|
-
effectTrialReason_safe: "Turn off safe mode to Try.",
|
|
25
|
-
effectTrialReason_effectsOff: "Turn on effects to Try.",
|
|
26
|
-
effectTrialReason_reducedMotion: "Effects follow this device’s reduced-motion setting.",
|
|
27
|
-
effectTrialReason_saveData: "Your device requests reduced data use.",
|
|
28
|
-
effectTrialReason_transport: "The isolated effect player is unavailable. Reload this page.",
|
|
29
|
-
effectTrialReason_unsupported: "Canvas Worker playback is unavailable in this browser.",
|
|
30
|
-
effectTrialReason_inactive: "Return to this page to Try.",
|
|
31
|
-
effectTrialReason_dialog: "Close the other menu or dialog to Try.",
|
|
32
|
-
effectTrialReason_expired: "This trial offer has expired.",
|
|
33
|
-
effectTrialSignIn: "Sign in here",
|
|
34
|
-
effectTrialStopOther: "Stop current trial",
|
|
35
|
-
effectTrialSettings: "Open playback settings",
|
|
36
|
-
effectTrialPlayMotion: "Play effects anyway",
|
|
37
|
-
effectTrial: "Effect trial", effectTrialLocal: "A sample on this page. Nothing is applied or saved.",
|
|
38
|
-
effectTrialStopped: "Try stopped", effectTrialStart: "Try", effectTrialExpired: "This trial offer has expired.", effectTrialUnavailable: "This trial is no longer available.",
|
|
39
|
-
cmdConfirm: "Clear", cmdConfirmBackend: "Switch and restart", cmdRunning: "Running /{0}…", cmdNotCommand: "Not a command here — Enter sends it as a message.", cmdDismiss: "Dismiss", cmdCommands: "Commands",
|
|
20
|
+
cmdConfirm: "Clear", cmdConfirmBackend: "Switch and restart", cmdRunning: "Running /{0}…", cmdNotCommand: "Not a command here — Enter sends it as a message.", cmdNotGuests: "Not one a guest can run here.", cmdDismiss: "Dismiss", cmdCommands: "Commands",
|
|
40
21
|
compactRunning: "Compacting…", compactDone: "✅ Conversation compacted", compactUnknown: "This backend does not report when compaction ends.",
|
|
41
22
|
compactTimeout: "No sign that compaction finished within {0} minutes — check the agent's own chat.",
|
|
42
23
|
compactLost: "Can't follow this compaction: the command did not reach the agent, or the agent stopped or changed backend.",
|
|
@@ -57,7 +38,7 @@ register("chat", {
|
|
|
57
38
|
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)",
|
|
58
39
|
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",
|
|
59
40
|
send: "Send", composerPlaceholder: "Message {0}…", composerHint: "Enter to send · Shift+Enter for a new line", composerHintTouch: "Return adds a line · tap Send to send", composerMore: "/ for commands · Esc stops the reply", guestMore: "Esc stops the reply",
|
|
60
|
-
guestNote: "You're a guest: the agent sees your messages as a guest's (recognition only).", guestNoCommands: "Guests can't
|
|
41
|
+
guestNote: "You're a guest: the agent sees your messages as a guest's (recognition only).", guestNoCommands: "Guests can't send that — send it without the leading /.", guestCommandNotAllowed: "{0} isn't available to guests. Only this channel's admins can run it.", attachTitle: "Attach files (any type — or paste / drop them)",
|
|
61
42
|
pvPreview: "Preview", pvStop: "Stop", pvDownload: "Download", pvMenu: "Preview options", pvHtml: "HTML",
|
|
62
43
|
pvAllowButton: "Allow previews", pvAllow: "Allow HTML previews — they may be able to send data out", pvDisallow: "Stop allowing HTML previews",
|
|
63
44
|
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.",
|
|
@@ -115,6 +96,7 @@ register("chat", {
|
|
|
115
96
|
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",
|
|
116
97
|
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", putBack: "Put back",
|
|
117
98
|
noMessages: "Type a task below.",
|
|
99
|
+
guestAllHidden: "Nothing here you can see yet", guestAllHiddenHint: "This agent's recent activity is between agents, which guests don't see. Its chat appears here when someone talks in its channel.",
|
|
118
100
|
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",
|
|
119
101
|
details: "Instance details", fullWidth: "Full width", start: "Start instance", stopInstance: "Stop instance", restart: "Restart instance", delete: "Delete instance…",
|
|
120
102
|
started: "{0} started", stoppedInst: "{0} stopped", restarted: "{0} restarted", instanceDeleted: "{0} was deleted",
|
|
@@ -158,26 +140,7 @@ register("chat", {
|
|
|
158
140
|
reply: "回覆", replyingTo: "回覆 {0}", replyCancel: "取消回覆(Esc)", replyHidden: "之前的一則訊息", replyPhoto: "[圖片]", replyFile: "[檔案]",
|
|
159
141
|
replyGone: "那則訊息已不在這個聊天裡。", replyJump: "顯示被回覆的訊息", copyText: "複製文字", copyLink: "複製連結", msgMenu: "訊息",
|
|
160
142
|
|
|
161
|
-
|
|
162
|
-
effectTrialReason_localAdmin: "請在公開連結以外的儀表板以管理員登入後試播。",
|
|
163
|
-
effectTrialReason_chatChanged: "請開啟發出試播邀請的聊天。",
|
|
164
|
-
effectTrialReason_busy: "請先停止目前的試播。",
|
|
165
|
-
effectTrialReason_safe: "請先關閉安全模式。",
|
|
166
|
-
effectTrialReason_effectsOff: "請先開啟效果。",
|
|
167
|
-
effectTrialReason_reducedMotion: "效果跟隨這台裝置的「減少動態效果」設定。",
|
|
168
|
-
effectTrialReason_saveData: "裝置目前要求節省數據。",
|
|
169
|
-
effectTrialReason_transport: "隔離效果播放器目前無法使用。請重新載入頁面。",
|
|
170
|
-
effectTrialReason_unsupported: "此瀏覽器無法播放 Canvas Worker 效果。",
|
|
171
|
-
effectTrialReason_inactive: "請回到本頁後試播。",
|
|
172
|
-
effectTrialReason_dialog: "請先關閉其他選單或對話框。",
|
|
173
|
-
effectTrialReason_expired: "試播邀請已過期。",
|
|
174
|
-
effectTrialSignIn: "在此登入",
|
|
175
|
-
effectTrialStopOther: "停止目前試播",
|
|
176
|
-
effectTrialSettings: "開啟播放設定",
|
|
177
|
-
effectTrialPlayMotion: "仍然播放效果",
|
|
178
|
-
effectTrial: "效果試播", effectTrialLocal: "僅在本頁試播,不會套用或儲存。",
|
|
179
|
-
effectTrialStopped: "試播已停止", effectTrialStart: "試播", effectTrialExpired: "試播邀請已過期。", effectTrialUnavailable: "此試播已無法使用。",
|
|
180
|
-
cmdConfirm: "清空", cmdConfirmBackend: "切換並重新啟動", cmdRunning: "執行 /{0} 中…", cmdNotCommand: "這裡沒有這個指令——按 Enter 會當成一般訊息送出。", cmdDismiss: "關閉", cmdCommands: "指令",
|
|
143
|
+
cmdConfirm: "清空", cmdConfirmBackend: "切換並重新啟動", cmdRunning: "執行 /{0} 中…", cmdNotCommand: "這裡沒有這個指令——按 Enter 會當成一般訊息送出。", cmdNotGuests: "訪客在這裡不能使用這個指令。", cmdDismiss: "關閉", cmdCommands: "指令",
|
|
181
144
|
compactRunning: "壓縮中…", compactDone: "✅ 對話已壓縮", compactUnknown: "這個後端不會回報壓縮何時完成。",
|
|
182
145
|
compactTimeout: "{0} 分鐘內沒有看到壓縮完成的紀錄,請到代理自己的對話確認。",
|
|
183
146
|
compactLost: "無法再追蹤這次壓縮:指令沒有送到代理,或代理已停止、換了後端。",
|
|
@@ -198,7 +161,7 @@ register("chat", {
|
|
|
198
161
|
stopping: "正在中止 {0}…", awaiting: "{0} 正在等你回應", approxNote: "從終端畫面判讀,僅供參考", elapsedTitle: "從這個頁面看到它開始工作起算(約略)",
|
|
199
162
|
dropHere: "放開即可附加檔案", asText: "改為文字", pasteAttached: "貼上的長文字({0} 個字元)已改為文字檔附加", pasteKeptAsText: "附件已滿,貼上的長文字保留為一般文字",
|
|
200
163
|
send: "送出", composerPlaceholder: "傳訊息給 {0}…", composerHint: "Enter 送出 · Shift+Enter 換行", composerHintTouch: "Return 換行 · 按「送出」傳送", composerMore: "/ 叫出指令 · Esc 中止回覆", guestMore: "Esc 中止回覆",
|
|
201
|
-
guestNote: "你是訪客:agent 會把你的訊息視為訪客的訊息(僅供辨識)。", guestNoCommands: "
|
|
164
|
+
guestNote: "你是訪客:agent 會把你的訊息視為訪客的訊息(僅供辨識)。", guestNoCommands: "訪客不能這樣送出,請去掉開頭的 / 再送出。", guestCommandNotAllowed: "訪客不能使用 {0},只有這個頻道的管理員可以執行。", attachTitle: "附加檔案(任何類型,也可以貼上或拖曳)",
|
|
202
165
|
pvPreview: "預覽", pvStop: "停止", pvDownload: "下載", pvMenu: "預覽選項", pvHtml: "HTML",
|
|
203
166
|
pvAllowButton: "允許預覽", pvAllow: "允許預覽 HTML(預覽可能把資料傳出去)", pvDisallow: "不再允許預覽 HTML",
|
|
204
167
|
pvConfirm: "要允許預覽 HTML 嗎?\n\n這會套用到你登入的每一台裝置。預覽會在隔離的框架裡執行 agent 的 HTML。它無法使用你的登入,但可能把資料傳出去,也可能讓這個分頁變慢或卡住。每次仍需要你按「預覽」。",
|
|
@@ -254,6 +217,7 @@ register("chat", {
|
|
|
254
217
|
working: "{0} 處理中…", stuck: "{0} 似乎卡住了", stopReply: "中止回覆", stopReplyTitle: "中止目前的回覆(同 Esc),並取消還在排隊的訊息。instance 會繼續運行。", stopSent: "已中止",
|
|
255
218
|
file_empty: "空檔案", file_too_large: "超過 {0} MB", file_too_large_public: "超過 {0} MB,這是公開連結單次上傳的上限", file_too_many: "每則訊息最多 {0} 個檔案", file_too_much: "檔案合計會超過 {0} MB", removeFile: "移除", putBack: "放回輸入框",
|
|
256
219
|
noMessages: "直接在下方輸入任務。",
|
|
220
|
+
guestAllHidden: "這裡還沒有你能看到的訊息", guestAllHiddenHint: "這個 agent 最近只和其他 agent 往來,訪客看不到這些。有人在它的頻道說話後,對話會出現在這裡。",
|
|
257
221
|
pick: "選一個 Agent 開始聊天。", noAgents: "先建立一個 Agent,再開始聊天。", newAgent: "新增 Agent", notFound: "沒有叫「{0}」的 instance", notFoundHint: "它可能已被刪除或改名。", backToList: "選擇 Agent",
|
|
258
222
|
details: "Instance 詳細資料", fullWidth: "全寬", start: "啟動 instance", stopInstance: "停止 instance", restart: "重新啟動 instance", delete: "刪除 instance…",
|
|
259
223
|
started: "{0} 已啟動", stoppedInst: "{0} 已停止", restarted: "{0} 已重新啟動", instanceDeleted: "{0} 已刪除",
|
package/dist/ui/chat-thread.js
CHANGED
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
// Unrelated updates (a new message, another message's tick) stop nothing (#1306 §6.2, #1408 §4).
|
|
9
9
|
import "./chat-render.js";
|
|
10
10
|
import "./preview.js";
|
|
11
|
-
import { canvasWorkerEnabled, onCanvasWorkerChange, onCanvasMetadataChange, readCanvasTrialMessage } from "/assets/feature-flags.js";
|
|
12
11
|
import { loadHtmlAttachment } from "./html-attachment.js";
|
|
13
12
|
import { chatTime, chatTimeFull, onDayChange } from "./chat-time.js";
|
|
14
13
|
import { textLinkSegments } from "/assets/app-route.js";
|
|
@@ -120,7 +119,7 @@ export function createThread(list, scroller, opts) {
|
|
|
120
119
|
if (el.textContent !== v) el.textContent = v;
|
|
121
120
|
}
|
|
122
121
|
// #1770: "today" ends at the fleet's midnight; the labels are rewritten then (and on every render), while there are any.
|
|
123
|
-
let offDay = null
|
|
122
|
+
let offDay = null;
|
|
124
123
|
function armDay() {
|
|
125
124
|
if (nodes.size && !offDay) offDay = onDayChange(() => { for (const v of nodes.values()) timeLabel(v.node); });
|
|
126
125
|
else if (!nodes.size && offDay) { offDay(); offDay = null; }
|
|
@@ -133,8 +132,6 @@ export function createThread(list, scroller, opts) {
|
|
|
133
132
|
// is kept by: it is written into the node (timeLabels) — crossing midnight relabels in place and reloads no preview.
|
|
134
133
|
const time = x.ts ? `<span class="time" data-ts="${escAttr(x.ts)}" title="${escAttr(chatTimeFull(x.ts))}"></span>` : `<span class="time"></span>`;
|
|
135
134
|
if (x.outgoing) return outgoingHtml(x);
|
|
136
|
-
const trial = readCanvasTrialMessage(x);
|
|
137
|
-
if (trial) return canvasWorkerEnabled() ? trialHtml(trial, time, x) : "";
|
|
138
135
|
if (x.role === "status") return statusHtml(x, time);
|
|
139
136
|
const u = opts.isUser(x);
|
|
140
137
|
// renderMarkdown escapes the whole text before it adds any tag of its own (chat-render.js).
|
|
@@ -213,26 +210,7 @@ export function createThread(list, scroller, opts) {
|
|
|
213
210
|
? `<button type="button" class="click-link" data-act="showButtons" data-arg="${escAttr(of)}" title="${escAttr(tr("chat.clickShow"))}">${inner}</button>`
|
|
214
211
|
: `<span class="click-text">${inner}</span>`}</div>`;
|
|
215
212
|
}
|
|
216
|
-
|
|
217
|
-
const trialReasons = new Set(["checking", "localAdmin", "chatChanged", "busy", "safe", "effectsOff", "reducedMotion", "saveData", "unsupported", "transport", "inactive", "dialog", "expired"]);
|
|
218
|
-
function trialStatus(message) {
|
|
219
|
-
const reason = opts.effectTrialStatus?.(message);
|
|
220
|
-
return reason === "ready" || trialReasons.has(reason) ? reason : opts.canTryEffect?.(message) === true ? "ready" : "localAdmin";
|
|
221
|
-
}
|
|
222
|
-
function trialHtml(offer, time, message) {
|
|
223
|
-
const reason = trialNow() >= offer.expiresAt ? "expired" : PUBLIC_LINK() ? "localAdmin" : trialStatus(message);
|
|
224
|
-
const rawFailure = opts.effectTrialFailure?.(message), failure = typeof rawFailure === "string" ? rawFailure.replace(/[\u0000-\u001f\u007f]/g, "").slice(0, 80) : null;
|
|
225
|
-
const available = reason === "ready", fix = !PUBLIC_LINK() && !available && reason !== "expired" && reason !== "checking" && typeof opts.fixEffectTrial === "function";
|
|
226
|
-
const label = available ? "chat.effectTrialStart" : reason === "busy" ? "chat.effectTrialStopOther" : reason === "localAdmin" ? "chat.effectTrialSignIn" : reason === "reducedMotion" ? "chat.effectTrialPlayMotion" : "chat.effectTrialSettings";
|
|
227
|
-
const button = available || fix ? `<button type="button" class="btn btn-sm effect-trial-start" data-act="effectTrial" data-arg="${escAttr(msgKey(message))}">${escAttr(tr(label))}</button>` : "";
|
|
228
|
-
return `<div class="msg status-line"><div class="meta">${time}</div><div class="body"><div class="effect-trial-offer" data-trust="thread-control"><strong>${escAttr(tr("chat.effectTrial"))}: ${escAttr(offer.title)}</strong><div>${escAttr(tr(available ? "chat.effectTrialLocal" : "chat.effectTrialReason_" + reason))}</div>${failure ? `<div class="effect-trial-failure" role="status">${escAttr(tr("chat.effectTrialStopped"))}: ${escAttr(failure)}</div>` : ""}${button}</div></div></div>`;
|
|
229
|
-
}
|
|
230
|
-
function armTrials() {
|
|
231
|
-
if (trialTimer !== null) clearTimeout(trialTimer); trialTimer = null;
|
|
232
|
-
if (!canvasWorkerEnabled()) return;
|
|
233
|
-
const now = trialNow(), next = msgs.map(x => readCanvasTrialMessage(x)).filter(x => x && x.expiresAt > now).reduce((n, x) => Math.min(n, x.expiresAt), Infinity);
|
|
234
|
-
if (Number.isFinite(next)) trialTimer = setTimeout(() => { trialTimer = null; render(msgs); }, Math.max(1, Math.min(2147483647, next - now)));
|
|
235
|
-
}
|
|
213
|
+
|
|
236
214
|
/**
|
|
237
215
|
* #1672: a status line (another instance's message, a daemon notice) is muted, read-only plain text — never Markdown,
|
|
238
216
|
* a preview card, a button or a person's bubble. Checked before isUser, which reads the sender: a fleet name need not
|
|
@@ -668,7 +646,6 @@ export function createThread(list, scroller, opts) {
|
|
|
668
646
|
function render(next, renderOpts = {}) {
|
|
669
647
|
const stick = pinned || nearBottom();
|
|
670
648
|
msgs = next || [];
|
|
671
|
-
armTrials();
|
|
672
649
|
if (!msgs.length) {
|
|
673
650
|
for (const v of nodes.values()) stopIn(v.node);
|
|
674
651
|
nodes.clear();
|
|
@@ -693,11 +670,7 @@ export function createThread(list, scroller, opts) {
|
|
|
693
670
|
}
|
|
694
671
|
/** Put the messages from startIndex() on in the list (nodes reused when unchanged). Returns how many are new. */
|
|
695
672
|
function draw(drawOpts) {
|
|
696
|
-
const shown = msgs.slice(startIndex())
|
|
697
|
-
// Volatile host offer records are hidden off and while their on-only parser is loading.
|
|
698
|
-
const hostOffer = message?.role === "status" && message.sender === "agend" && Object.hasOwn(message, "effectTrial") && message.effectTrial != null;
|
|
699
|
-
return !hostOffer || canvasWorkerEnabled() && readCanvasTrialMessage(message) !== undefined;
|
|
700
|
-
});
|
|
673
|
+
const shown = msgs.slice(startIndex());
|
|
701
674
|
const keep = new Set(shown.map(msgKey));
|
|
702
675
|
for (const [k, v] of nodes) if (!keep.has(k)) { stopIn(v.node); v.node.remove(); nodes.delete(k); }
|
|
703
676
|
let prev = null, added = 0;
|
|
@@ -711,7 +684,7 @@ export function createThread(list, scroller, opts) {
|
|
|
711
684
|
const stopped = have ? stopIn(have.node) : [];
|
|
712
685
|
const node = msgNode(html, x, stopped);
|
|
713
686
|
if (have) have.node.replaceWith(node); else added++;
|
|
714
|
-
have = { html, node, x
|
|
687
|
+
have = { html, node, x }; nodes.set(key, have);
|
|
715
688
|
} else { have.x = x; timeLabel(have.node); }
|
|
716
689
|
const want = prev ? prev.nextSibling : list.firstChild;
|
|
717
690
|
// A node already in the list is being moved: its preview stops first. A new node (still in its template) has none.
|
|
@@ -811,13 +784,6 @@ export function createThread(list, scroller, opts) {
|
|
|
811
784
|
else if (act === "replyButton") {
|
|
812
785
|
const m = /^([0-9a-f]{32}):(\d{1,2})$/.exec(el.dataset.arg || "");
|
|
813
786
|
if (m && opts.clickReplyButton) opts.clickReplyButton(m[1], Number(m[2]));
|
|
814
|
-
} else if (act === "effectTrial") {
|
|
815
|
-
// Only the current host-produced keyed card and a real local click can request admission. No token is read from DOM.
|
|
816
|
-
const key = el.dataset.arg, row = msgs.find(x => msgKey(x) === key), owner = nodes.get(key), node = owner?.node, offer = readCanvasTrialMessage(row);
|
|
817
|
-
if (PUBLIC_LINK() || e.defaultPrevented || e.isTrusted !== true || owner?.trialButton !== el || el.className !== "btn btn-sm effect-trial-start" || !node?.contains(el) || el.closest(".effect-trial-offer")?.getAttribute("data-trust") !== "thread-control"
|
|
818
|
-
|| !offer || trialNow() >= offer.expiresAt) return;
|
|
819
|
-
if (trialStatus(row) === "ready" && opts.canTryEffect?.(row) === true) opts.startEffectTrial?.(row);
|
|
820
|
-
else opts.fixEffectTrial?.(row, trialStatus(row));
|
|
821
787
|
} else if (act === "showButtons") { const x = msgs.find(y => y.buttons && y.buttons.id === el.dataset.arg); if (x) reveal(msgKey(x)); }
|
|
822
788
|
else if (act === "reply") { const x = msgs.find(y => msgKey(y) === el.dataset.arg); if (x) opts.startReply?.(x); }
|
|
823
789
|
else if (act === "jumpQuote") void jumpTo(el.dataset.arg);
|
|
@@ -841,7 +807,7 @@ export function createThread(list, scroller, opts) {
|
|
|
841
807
|
/** The thread goes (the chat left, or another instance opened): every preview in it stops first. */
|
|
842
808
|
/**
|
|
843
809
|
* #2053: kept for a later visit — the chat left as it always did (every preview stops: "leave"), but its drawn nodes
|
|
844
|
-
* stay. Nothing of it runs meanwhile: no timer (day labels, polls,
|
|
810
|
+
* stay. Nothing of it runs meanwhile: no timer (day labels, polls, filling in), no size observer; the
|
|
845
811
|
* caller drops its subscriptions. Shown again: attach, then render — only what changed is drawn.
|
|
846
812
|
*/
|
|
847
813
|
function park() {
|
|
@@ -850,7 +816,6 @@ export function createThread(list, scroller, opts) {
|
|
|
850
816
|
if (P()) P().stopAll("leave");
|
|
851
817
|
for (const node of list.querySelectorAll(".html-card[data-att], .text-card[data-att]")) { const f = cardDrop.get(node); if (f) f(); }
|
|
852
818
|
if (ro) { ro.disconnect(); ro = null; }
|
|
853
|
-
if (trialTimer !== null) clearTimeout(trialTimer); trialTimer = null;
|
|
854
819
|
if (growFrame !== null && typeof cancelAnimationFrame === "function") cancelAnimationFrame(growFrame);
|
|
855
820
|
if (growTimer !== null) clearTimeout(growTimer);
|
|
856
821
|
growTimer = growFrame = null;
|
|
@@ -868,12 +833,9 @@ export function createThread(list, scroller, opts) {
|
|
|
868
833
|
if (ro) { ro.observe(list); ro.observe(sc); }
|
|
869
834
|
}
|
|
870
835
|
/** `own`: only this thread's previews stop (a thread let go while another chat is in view); else every one, as leaving. */
|
|
871
|
-
const redrawFeature = () => { if (!disposed && !parked) render(msgs); };
|
|
872
|
-
const featureOff = onCanvasWorkerChange(redrawFeature), metadataOff = onCanvasMetadataChange(redrawFeature);
|
|
873
836
|
function dispose(own = false) {
|
|
874
|
-
disposed = true;
|
|
837
|
+
disposed = true;
|
|
875
838
|
if (!parked) watchReader(scroller, false);
|
|
876
|
-
if (trialTimer !== null) clearTimeout(trialTimer); trialTimer = null;
|
|
877
839
|
if (ro) ro.disconnect();
|
|
878
840
|
if (P()) { if (own) P().stopIn(list); else P().stopAll("leave"); }
|
|
879
841
|
nodes.clear();
|
package/dist/ui/panel-chat.js
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
// right, the agent's at full width, and one rounded composer pinned below with attach, Stop reply and Send inside it.
|
|
7
7
|
// The instance's own actions (details, start, stop, restart, delete) are in the header's ⋯ menu, and only the ones
|
|
8
8
|
// that apply to its state are offered (#1408 rough edges 4 and 7).
|
|
9
|
-
import { html,
|
|
9
|
+
import { html, useEffect, useLayoutEffect, useRef, useState } from "/assets/app-html.js";
|
|
10
10
|
import { lang, t } from "/assets/app-i18n.js";
|
|
11
|
-
import { appStore, createStore, guestPage, useStore } from "/assets/app-store.js";
|
|
11
|
+
import { appStore, createStore, guestPage, guestTrusted, useStore } from "/assets/app-store.js";
|
|
12
12
|
import { ComposerAuth } from "/assets/web-authorize.js";
|
|
13
13
|
import { useLease } from "/assets/app-ctx.js";
|
|
14
14
|
import { modelText, namesOf, namesStore, navStore as instanceNavStore } from "/assets/instance-nav.js";
|
|
@@ -80,12 +80,7 @@ import { PreviewPanel, claimPanel, openPanel, panelClaims, panelStore, panelVers
|
|
|
80
80
|
import { needsArgument, paletteFor, parseCommandLine } from "./chat-commands.js";
|
|
81
81
|
import { installTour, refreshTourSpot } from "./chat-tour.js";
|
|
82
82
|
import { LightboxHost, openLightbox } from "./image-lightbox.js";
|
|
83
|
-
import { canvasWorkerMenuAvailable, experimentalComponent, useCanvasWorker } from "/assets/feature-flags.js";
|
|
84
|
-
const ChatEffectPosters = experimentalComponent(() => import("./chat-effect-posters.js"), m => m.ChatEffectPosters);
|
|
85
|
-
const ChatEffects = experimentalComponent(() => import("./chat-effects.js"), m => m.ChatEffects);
|
|
86
|
-
import { setFollowsReducedMotion } from "/assets/fx-motion.js";
|
|
87
83
|
import { AmbientMotionNote, ChatAmbient, ambientMenuItems } from "./chat-ambient.js";
|
|
88
|
-
import { startEffectsWaker, getAppEffects, wakeEffects, effectsLoaded, onEffectsAwake, chatHasEffects } from "/assets/feature-flags.js";
|
|
89
84
|
/** The composer's textarea (#2342: text put back into it is a step Ctrl+Z goes back from). */
|
|
90
85
|
const composerField = () => (typeof document !== "undefined" ? document.getElementById("msgIn") : null);
|
|
91
86
|
|
|
@@ -149,9 +144,6 @@ export function boot({ stream, boot: bootData, deps = {} }) {
|
|
|
149
144
|
}
|
|
150
145
|
// #1604: the preview opt-in and Tour live in Settings → This device now (and on each preview card's menu).
|
|
151
146
|
installTour();
|
|
152
|
-
// #2118: viewing current in-scope Chat effects needs no management or personal-slot authority. #2193: the runtime
|
|
153
|
-
// itself loads only when there is something to play (effects-waker.js).
|
|
154
|
-
startEffectsWaker({ store });
|
|
155
147
|
return store;
|
|
156
148
|
}
|
|
157
149
|
|
|
@@ -261,9 +253,6 @@ function ChatView({ name, inst, lease, navKey, exec, awaiting }) {
|
|
|
261
253
|
const themeKey = JSON.stringify(theme);
|
|
262
254
|
const card = useCard(name, inst.card_v);
|
|
263
255
|
const [dialog, setDialog] = useState(null); // "delete" | null
|
|
264
|
-
const [canvasPlayback, setCanvasPlayback] = useState(null);
|
|
265
|
-
const publishCanvasPlayback = useCallback(value => setCanvasPlayback(previous =>
|
|
266
|
-
!previous && !value.playing || previous?.name === value.name && previous.playing === value.playing ? previous : value), []);
|
|
267
256
|
const [wrap, setWrap] = useState(codeWrap);
|
|
268
257
|
const wide = useStore(chatWide).on;
|
|
269
258
|
const view = useRef(null), split = useRef(null), thread = useRef(null), bg = useRef(null), effectSurface = useRef(null);
|
|
@@ -312,9 +301,6 @@ function ChatView({ name, inst, lease, navKey, exec, awaiting }) {
|
|
|
312
301
|
else if (r && r.held) toast(r.message);
|
|
313
302
|
else toast(t(verb === "start" ? "chat.started" : verb === "stop" ? "chat.stoppedInst" : "chat.restarted", name));
|
|
314
303
|
};
|
|
315
|
-
// #2193: this chat's Canvas Worker placements (its status row's effect_chat_v): none, no effects code runs for it.
|
|
316
|
-
const cwEnabled = useCanvasWorker();
|
|
317
|
-
const fxHere = cwEnabled && chatHasEffects(name);
|
|
318
304
|
const items = [
|
|
319
305
|
// #1523 N2: Details is a page now (the Fleet side of this instance), not a dialog.
|
|
320
306
|
{ key: "details", label: t("chat.details"), icon: "info", onSelect: () => navigate(detailsPath(name)) },
|
|
@@ -328,7 +314,6 @@ function ChatView({ name, inst, lease, navKey, exec, awaiting }) {
|
|
|
328
314
|
// #2117: its ambient — why it is not showing here (with the way to change that), and a person's Clear.
|
|
329
315
|
...ambientMenuItems(name, { publicLink: document.body.dataset.publicLink === "1", clear: clearAmbient }),
|
|
330
316
|
narrowScreen() ? null : { key: "fullWidth", checkbox: true, checked: wide, label: t("chat.fullWidth"), onSelect: () => setChatWide(!wide) },
|
|
331
|
-
cwEnabled && canvasWorkerMenuAvailable(window) ? { key: "effects", label: t("cwControls.title"), onSelect: () => setDialog("effects") } : null,
|
|
332
317
|
{ key: "delete", label: t("chat.delete"), icon: "trash", danger: true, onSelect: () => setDialog("delete") },
|
|
333
318
|
];
|
|
334
319
|
const cls = statusClass(inst, exec, awaiting);
|
|
@@ -341,14 +326,14 @@ function ChatView({ name, inst, lease, navKey, exec, awaiting }) {
|
|
|
341
326
|
${inst.model ? html`<button type="button" class="hd-chip" title=${t("chat.chipModel", modelText(inst))} aria-label=${t("chat.chipModel", modelText(inst))} onClick=${() => pick("model")}>${modelText(inst)}</button>` : null}
|
|
342
327
|
${inst.effort ? html`<button type="button" class="hd-chip" title=${t("chat.chipEffort", inst.effort)} aria-label=${t("chat.chipEffort", inst.effort)} onClick=${() => pick("effort")}>${inst.effort}</button>` : null}`;
|
|
343
328
|
return html`<div class=${`panel p-chat${wrap ? " wrap-code" : ""}`} data-width=${wide ? "full" : undefined} ref=${view}>
|
|
344
|
-
<${PanelHeader} title=${headerName(namesOf(inst || { name }, names.primary))} sub=${sub} nav=${guest ?
|
|
329
|
+
<${PanelHeader} title=${headerName(namesOf(inst || { name }, names.primary))} sub=${sub} nav=${guest ? (guestTrusted() ? html`<${InstanceSwitch} name=${name} current="chat" guest=${true} />` : null) : html`<${InstanceSwitch} name=${name} current="chat" />`}><${CardMenu} card=${card} /><${HeaderTools} navKey=${navKey} />${guest ? null : html`<${Menu} items=${items} label=${t("app.more")} />`}</${PanelHeader}>
|
|
345
330
|
<${ChatCard} key=${name} name=${name} card=${card} />
|
|
346
331
|
<${AgentWidget} slot="chat_header" widget=${widgetFor(name, "chat_header")} binds=${instanceBinds(inst, exec, awaiting)} />
|
|
347
332
|
<div class="chat-split" ref=${split}>
|
|
348
333
|
<div ref=${effectSurface} class=${`chat-main${theme && theme.background ? " has-th-bg" : ""}`}>
|
|
349
334
|
${theme && theme.background ? html`<img ref=${bg} class=${`th-bg th-bg-${theme.background.fit}`} src=${themeImageUrl(name, theme.background.image)} alt="" aria-hidden="true" decoding="async" />` : null}
|
|
350
335
|
<${ChatAmbient} name=${name} surface=${effectSurface} part="layer" />
|
|
351
|
-
<${AmbientMotionNote} name=${name}
|
|
336
|
+
<${AmbientMotionNote} name=${name} />
|
|
352
337
|
<${Thread} name=${name} th=${thread} />
|
|
353
338
|
<${Dock} name=${name} inst=${inst} lease=${lease} exec=${exec} awaiting=${awaiting} workingLabel=${theme ? theme.workingLabel : undefined} />
|
|
354
339
|
<${ChatAmbient} name=${name} surface=${effectSurface} part="front" />
|
|
@@ -423,20 +408,11 @@ function threadEntry(name, doc) {
|
|
|
423
408
|
list.setAttribute("aria-label", t("chat.conversationWith", name));
|
|
424
409
|
const e = { list, sink: null, lang: lang(), thread: null };
|
|
425
410
|
const thread = createThread(list, doc.createElement("div"), {
|
|
426
|
-
t, tf: t, isUser: (x) => store.isUser(x, name), onJump: (j) => e.sink?.setJump(j),
|
|
411
|
+
t, tf: t, isUser: (x) => store.isUser(x, name), onJump: (j) => e.sink?.setJump(j),
|
|
412
|
+
// #2848: which empty — "hidden" (a guest's chat whose every message is hidden from it) differs from a plain empty
|
|
413
|
+
// chat, so the explanation shows when the history answer comes after the first (empty) draw.
|
|
414
|
+
onEmpty: (v) => e.sink?.setEmpty(v && store.allHidden(name) ? "hidden" : v),
|
|
427
415
|
setPreviewOptIn, copyText, download: downloadHtml, toggleWrap,
|
|
428
|
-
// #2193: an offer shown while the effects runtime sleeps wakes it; until it answers, the offer is being checked.
|
|
429
|
-
effectTrialStatus: x => { const fx = getAppEffects(); if (fx) return fx.trialStatus(x); if (!effectsLoaded()) { void wakeEffects(); return "checking"; } return "localAdmin"; },
|
|
430
|
-
effectTrialFailure: x => getAppEffects()?.trialFailure(x),
|
|
431
|
-
fixEffectTrial: (x, reason) => {
|
|
432
|
-
if (reason === "reducedMotion") setFollowsReducedMotion(false);
|
|
433
|
-
else if (reason === "busy") getAppEffects()?.stopTrial();
|
|
434
|
-
else if (reason === "transport") location.reload();
|
|
435
|
-
else if (reason === "localAdmin") location.href = "/signin?next=" + encodeURIComponent(location.pathname + location.search);
|
|
436
|
-
else navigate("/settings/look");
|
|
437
|
-
},
|
|
438
|
-
canTryEffect: x => getAppEffects()?.canTry(x) === true,
|
|
439
|
-
startEffectTrial: x => { const seq = navStore.get().seq; void getAppEffects()?.startTrial(x).then(ok => { if (!ok && e.sink && navStore.get().seq === seq && document.hasFocus?.() === true && document.visibilityState === "visible") toast(t("chat.effectTrialUnavailable")); }); },
|
|
440
416
|
clickReplyButton: (id, index) => store.clickReplyButton(name, id, index), // #1266
|
|
441
417
|
retryOutgoing: (clientId) => store.retryOutgoing(name, clientId), // a failed send's Retry and Delete
|
|
442
418
|
deleteOutgoing: (clientId) => store.deleteOutgoing(name, clientId),
|
|
@@ -472,17 +448,6 @@ function follow(name, thread) {
|
|
|
472
448
|
else if (kind === "sent") thread.jumpLatest();
|
|
473
449
|
}));
|
|
474
450
|
offs.push(P() ? P().onChange(() => thread.refreshCards()) : () => {});
|
|
475
|
-
// #2193: the runtime may start later (an offer shown wakes it): its trial updates are followed from then.
|
|
476
|
-
let trialOff = null, trialOwner = null;
|
|
477
|
-
const watchTrial = () => {
|
|
478
|
-
const next = getAppEffects();
|
|
479
|
-
if (next === trialOwner) return;
|
|
480
|
-
trialOff?.(); trialOff = null; trialOwner = next;
|
|
481
|
-
trialOff = next?.subscribeTrial(() => thread.render(store.threadRows(name))) ?? null;
|
|
482
|
-
};
|
|
483
|
-
watchTrial();
|
|
484
|
-
offs.push(onEffectsAwake(() => { watchTrial(); thread.render(store.threadRows(name)); }));
|
|
485
|
-
offs.push(() => { trialOff?.(); trialOff = null; trialOwner = null; });
|
|
486
451
|
offs.push(panelStore.subscribe(() => thread.refreshCards())); // a card shows whether the panel has its block
|
|
487
452
|
return () => { for (const off of offs) off(); };
|
|
488
453
|
}
|
|
@@ -548,7 +513,8 @@ function Thread({ name, th }) {
|
|
|
548
513
|
}, [name]);
|
|
549
514
|
return html`<div class="scroller" ref=${scroller}>
|
|
550
515
|
<div class="thread-host" ref=${host}></div>
|
|
551
|
-
${empty ? html`<${Empty} icon="chat" title=${t("chat.
|
|
516
|
+
${empty === "hidden" ? html`<${Empty} icon="chat" title=${t("chat.guestAllHidden")} hint=${t("chat.guestAllHiddenHint")} />`
|
|
517
|
+
: empty ? html`<${Empty} icon="chat" title=${t("chat.noMessages")} />` : null}
|
|
552
518
|
<div class="jump-dock">${jump.show ? html`<button data-trust="thread-control" type="button" class="jump-latest" onClick=${() => th.current && th.current.jumpLatest()}>
|
|
553
519
|
<${Icon} name="down" size=${16} />${jump.unseen > 0 ? t("chat.newBelow", jump.unseen) : t("chat.jumpLatest")}</button>` : null}</div>
|
|
554
520
|
</div>`;
|
|
@@ -677,10 +643,12 @@ function Composer({ name, label = name, lease, busy }) {
|
|
|
677
643
|
const files = s.pendingFiles[name] || [];
|
|
678
644
|
const reply = s.replies[name]; // #2272: the message this one will reply to
|
|
679
645
|
const content = !!value.trim() || files.length > 0;
|
|
680
|
-
// A file waiting: what goes is a message with its file, never a command (no palette, no command line).
|
|
681
|
-
//
|
|
646
|
+
// A file waiting: what goes is a message with its file, never a command (no palette, no command line). #2855: a guest's
|
|
647
|
+
// palette offers only what it may run on this agent — the table's "anyone" commands (its status frame's `commands`).
|
|
682
648
|
const guest = guestPage();
|
|
683
|
-
const
|
|
649
|
+
const app = useStore(appStore);
|
|
650
|
+
const guestAllowed = guest ? (app.instances.find((i) => i.name === name)?.commands ?? []) : null;
|
|
651
|
+
const palette = files.length || closedFor === value || (guest && !guestAllowed.length) ? null : paletteFor(value, guestAllowed || undefined);
|
|
684
652
|
const pick = palette && palette.length ? palette[Math.min(sel, palette.length - 1)] : null;
|
|
685
653
|
// Grow with the text, up to a limit: set through the CSSOM, never a style attribute (#1300). Empty, it is one line
|
|
686
654
|
// (#2478 — a long name's placeholder made the box two lines tall).
|
|
@@ -707,9 +675,12 @@ function Composer({ name, label = name, lease, busy }) {
|
|
|
707
675
|
else complete(pick);
|
|
708
676
|
return;
|
|
709
677
|
}
|
|
710
|
-
// #1920: a guest's "/…" would be refused as a command — said here, the draft kept to edit.
|
|
711
|
-
if (guest && /^\s*\//.test(value)) { toast(t("chat.guestNoCommands"), false); return; }
|
|
712
678
|
const cmd = files.length ? null : parseCommandLine(value);
|
|
679
|
+
// #1920/#2855: a guest's "/…" that is not one it may run here is refused here, why said, the draft kept to edit — a
|
|
680
|
+
// known command: only the channel's admins run it; anything else: it would not go as a message either.
|
|
681
|
+
if (guest && /^\s*\//.test(value) && !(cmd && guestAllowed.includes(cmd.command))) {
|
|
682
|
+
toast(cmd ? t("chat.guestCommandNotAllowed", `/${cmd.command}`) : t("chat.guestNoCommands"), false); return;
|
|
683
|
+
}
|
|
713
684
|
if (cmd && needsArgument(cmd.command) && !cmd.args) { recordEdit(input.current); setValue(`/${cmd.command} `); return; }
|
|
714
685
|
if (cmd) { runLine(cmd); return; }
|
|
715
686
|
store.send(name);
|
|
@@ -743,7 +714,7 @@ function Composer({ name, label = name, lease, busy }) {
|
|
|
743
714
|
${palette.length ? palette.map((c) => html`<li key=${c.name} id=${optId(c)} role="option" aria-selected=${c === pick ? "true" : "false"}
|
|
744
715
|
class=${c === pick ? "on" : ""} onMouseDown=${(e) => { e.preventDefault(); complete(c); }}>
|
|
745
716
|
<span class="cmd-name mono">/${c.name}${c.arg ? html` <span class="cmd-arg">${c.arg}</span>` : null}</span><span class="cmd-desc">${t(`chat.cmd_${c.name}`)}</span></li>`)
|
|
746
|
-
: html`<li class="cmd-none" role="option" aria-selected="false">${t("chat.cmdNotCommand")}</li>`}
|
|
717
|
+
: html`<li class="cmd-none" role="option" aria-selected="false">${t(guest ? "chat.cmdNotGuests" : "chat.cmdNotCommand")}</li>`}
|
|
747
718
|
</ul>` : null}
|
|
748
719
|
${reply ? html`<div class="reply-bar" data-trust="composer" role="status">
|
|
749
720
|
<span class="reply-mark" aria-hidden="true"><svg class="icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 14 4 9l5-5"/><path d="M4 9h10.5a5.5 5.5 0 0 1 0 11H11"/></svg></span><span class="reply-label">${t("chat.replyingTo", reply.sender)}</span>
|
package/dist/ui/preview-panel.js
CHANGED
|
@@ -16,8 +16,6 @@ import { Icon } from "/assets/ui-icons.js";
|
|
|
16
16
|
import "./chat-render.js";
|
|
17
17
|
import "./preview.js";
|
|
18
18
|
import { loadHtmlAttachment } from "./html-attachment.js";
|
|
19
|
-
import { experimentalComponent, useCanvasWorker } from "/assets/feature-flags.js";
|
|
20
|
-
const EffectTrialPanel = experimentalComponent(() => import("./effect-trial-panel.js"), m => m.EffectTrialPanel);
|
|
21
19
|
import { chatTime, onDayChange } from "./chat-time.js";
|
|
22
20
|
|
|
23
21
|
const R = () => globalThis.AgendChatRender;
|
|
@@ -47,19 +45,13 @@ export const frameKey = (key) => `panel:${key}`;
|
|
|
47
45
|
/** Show `spec` in the panel; `run`: start it there (the card's "Open in panel" click). The block shown before stops. */
|
|
48
46
|
function retire(open) {
|
|
49
47
|
if (!open) return;
|
|
50
|
-
if (open.
|
|
51
|
-
else if (P()) P().stop(frameKey(open.key), "stopped", "");
|
|
48
|
+
if (P()) P().stop(frameKey(open.key), "stopped", "");
|
|
52
49
|
}
|
|
53
50
|
export function openPanel(spec, { run = false } = {}) {
|
|
54
51
|
const old = state.open;
|
|
55
52
|
set({ open: { ...spec, kind: "html" }, run: run ? ++runs : 0 });
|
|
56
53
|
retire(old); // State changes first: cleanup reentry cannot close a newer panel.
|
|
57
54
|
}
|
|
58
|
-
/** A checked controller handle enters the existing component slot; no HTML run token is issued. */
|
|
59
|
-
export function openTrialPanel(spec) {
|
|
60
|
-
const old = state.open;
|
|
61
|
-
set({ open: { ...spec, kind: "trial" }, run: 0 }); retire(old);
|
|
62
|
-
}
|
|
63
55
|
/** Close publishes the empty slot before old-owner callbacks can reenter. */
|
|
64
56
|
export function closePanel() {
|
|
65
57
|
const old = state.open; if (!old) return;
|
|
@@ -80,7 +72,7 @@ export function htmlTitle(code) {
|
|
|
80
72
|
* without one, the newest block does. `msgs` in order; `keyOf` makes a message's key.
|
|
81
73
|
*/
|
|
82
74
|
export function newerVersion(open, msgs, keyOf) {
|
|
83
|
-
if (!open ||
|
|
75
|
+
if (!open || !Array.isArray(msgs)) return null;
|
|
84
76
|
const at = msgs.findIndex(x => keyOf(x) === open.msgKey);
|
|
85
77
|
if (at < 0) return null;
|
|
86
78
|
// An attached file: a later agent reply attaching a file of the same name is its newer version. Its HTML is read
|
|
@@ -145,8 +137,7 @@ export function PreviewPanel({ name, split, msgs, subscribe, keyOf, reveal, down
|
|
|
145
137
|
const pending = useRef(0); // #1704: the card's click, waiting for this device's Allow (the st.run it came with)
|
|
146
138
|
const ownAllow = useRef(0); // while this panel's own Allow commits its choice: the click it answers (not cancelled by it)
|
|
147
139
|
const holder = useRef(null), divider = useRef(null), panel = useRef(null);
|
|
148
|
-
const
|
|
149
|
-
const open = st.open?.kind !== "trial" && st.open?.instance === name ? st.open : null;
|
|
140
|
+
const open = st.open?.instance === name ? st.open : null;
|
|
150
141
|
|
|
151
142
|
useEffect(() => panelStore.subscribe(setSt), []);
|
|
152
143
|
// This device's choice (here or in another tab) decides whether Preview is offered: re-read it when it changes.
|
|
@@ -245,9 +236,6 @@ export function PreviewPanel({ name, split, msgs, subscribe, keyOf, reveal, down
|
|
|
245
236
|
// Esc in the panel closes it (and is not the chat's "stop the reply").
|
|
246
237
|
const onPanelKey = (e) => { if (e.key === "Escape" && !e.isComposing) { e.preventDefault(); closePanel(); } };
|
|
247
238
|
|
|
248
|
-
if (trial) return html`<${EffectTrialPanel} binding=${trial.binding} title=${trial.handle.reply.offer.title}
|
|
249
|
-
current=${trial.handle.current} ownerCss=${trial.ownerCss} onStage=${trial.onStage}
|
|
250
|
-
onStop=${trial.handle.stop} onClose=${() => { if (panelStore.get().open === trial) closePanel(); }} />`;
|
|
251
239
|
if (!open) return null;
|
|
252
240
|
const going = run.state === "starting" || run.state === "running";
|
|
253
241
|
const a = P().availability();
|
package/dist/ui/search-dialog.js
CHANGED
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
// ↑↓ and Enter, Esc or a click outside to close; filters folded under one toggle. A chat row still in the agent's
|
|
4
4
|
// history opens there, at the message (#m=); anything else — a chat row that has left the history, a message between
|
|
5
5
|
// agents, a task — opens here as the index's copy (GET /ui/search/context). Text only: no innerHTML, no style attribute.
|
|
6
|
+
// #2855: a trusted guest's — the chats it can see only (the server's rule): no settings, teams, sources or "only mine".
|
|
6
7
|
import { html, useEffect, useLayoutEffect, useMemo, useRef, useState } from "/assets/app-html.js";
|
|
7
8
|
import { t, register } from "/assets/app-i18n.js";
|
|
8
9
|
import { recordEdit } from "/assets/text-undo.js";
|
|
9
|
-
import { appStore, useStore } from "/assets/app-store.js";
|
|
10
|
+
import { appStore, guestPage, useStore } from "/assets/app-store.js";
|
|
10
11
|
import { navigate } from "/assets/app-nav.js";
|
|
11
12
|
import { chatPath, fleetPath } from "/assets/app-route.js";
|
|
12
13
|
import { namesOf, namesStore } from "/assets/instance-nav.js";
|
|
@@ -28,6 +29,8 @@ register("search", {
|
|
|
28
29
|
building: "Still indexing — some older results may be missing.",
|
|
29
30
|
limited: "Words of 1–2 characters alone search the newest 5,000 entries only — add a longer word to search everything.",
|
|
30
31
|
unavailable: "Search is off or not available (web.search in fleet.yaml).", busy: "Search is busy — try again in a moment.",
|
|
32
|
+
guestPlaceholder: "Search your chats…", guestHint: "Find something said in the chats you can see: a word or two. Short words (1–2 characters) work too.",
|
|
33
|
+
tooMany: "Too many searches. Try again in {0} seconds.",
|
|
31
34
|
signedOut: "Signed out — sign in again to search.", failed: "Search failed — try again.",
|
|
32
35
|
keys: "↑↓ move · Enter open · Esc close", results: "Results", back: "Results",
|
|
33
36
|
gone: "This message is no longer in the chat's history — this is the copy kept by search.",
|
|
@@ -47,6 +50,8 @@ register("search", {
|
|
|
47
50
|
building: "索引仍在建立中,較舊的結果可能還沒出現。",
|
|
48
51
|
limited: "只有 1–2 個字的短詞時,只會搜尋最新的 5,000 筆;加上較長的詞即可搜尋全部。",
|
|
49
52
|
unavailable: "搜尋已關閉或無法使用(fleet.yaml 的 web.search)。", busy: "搜尋忙碌中,請稍後再試。",
|
|
53
|
+
guestPlaceholder: "搜尋你的聊天…", guestHint: "找你看得到的聊天裡說過的事:輸入一兩個詞。1–2 個字的短詞也可以。",
|
|
54
|
+
tooMany: "搜尋太多次了,請 {0} 秒後再試。",
|
|
50
55
|
signedOut: "已登出,請重新登入後再搜尋。", failed: "搜尋失敗,請再試一次。",
|
|
51
56
|
keys: "↑↓ 移動 · Enter 開啟 · Esc 關閉", results: "結果", back: "結果",
|
|
52
57
|
gone: "這則訊息已不在聊天紀錄中,以下是搜尋保留的副本。",
|
|
@@ -144,7 +149,10 @@ export function jumpToMessage(instance, key, w = window) {
|
|
|
144
149
|
w.dispatchEvent(new Event("hashchange"));
|
|
145
150
|
}
|
|
146
151
|
|
|
147
|
-
const errorText = (status) => (status === 404 ? t("search.unavailable") : status === 503 ? t("search.busy") : status === 401 ? t("search.signedOut")
|
|
152
|
+
const errorText = (status, retryS) => (status === 404 ? t("search.unavailable") : status === 503 ? t("search.busy") : status === 401 ? t("search.signedOut")
|
|
153
|
+
: status === 429 ? t("search.tooMany", retryS || 60) : t("search.failed"));
|
|
154
|
+
/** #2855: a refused guest search's "try again in" (seconds), from its answer. */
|
|
155
|
+
const retryOf = async (r) => { try { const b = await r.json(); return { error: b && b.error, s: Number(b && b.retry_after_s) || 0 }; } catch { return { error: null, s: 0 }; } };
|
|
148
156
|
|
|
149
157
|
/** #2347 §6: how many settings the dialog lists above the messages (the Settings page's own search lists them all). */
|
|
150
158
|
const SETTINGS_SHOWN = 5;
|
|
@@ -154,7 +162,8 @@ export function SearchDialog({ preset = {}, onClose }) {
|
|
|
154
162
|
const app = useStore(appStore);
|
|
155
163
|
const names = useStore(namesStore);
|
|
156
164
|
const [q, setQ] = useState(preset.q || "");
|
|
157
|
-
const
|
|
165
|
+
const guest = guestPage();
|
|
166
|
+
const [f, setF] = useState({ instance: preset.instance || "", team: "", since: "any", sources: guest ? ["chat"] : [...SOURCES], mine: false });
|
|
158
167
|
const [showFilters, setShowFilters] = useState(!!preset.instance);
|
|
159
168
|
const [teams, setTeams] = useState(null);
|
|
160
169
|
const [res, setRes] = useState({ status: "idle" });
|
|
@@ -193,8 +202,15 @@ export function SearchDialog({ preset = {}, onClose }) {
|
|
|
193
202
|
const ac = typeof AbortController === "function" ? new AbortController() : null;
|
|
194
203
|
const timer = setTimeout(async () => {
|
|
195
204
|
try {
|
|
196
|
-
const
|
|
205
|
+
const ask = () => fetch(url, { headers: { Accept: "application/json" }, ...(ac ? { signal: ac.signal } : {}) });
|
|
206
|
+
let r = await ask();
|
|
197
207
|
if (mine !== seq.current) return;
|
|
208
|
+
if (r.status === 429) {
|
|
209
|
+
// #2855: a guest's searches run one at a time — the one before this may still be finishing: once more, shortly.
|
|
210
|
+
const why = await retryOf(r);
|
|
211
|
+
if (why.error === "guest_search_running") { await new Promise((ok) => setTimeout(ok, 400)); if (mine !== seq.current) return; r = await ask(); if (mine !== seq.current) return; }
|
|
212
|
+
if (r.status === 429) { const again = why.error === "guest_search_running" ? await retryOf(r) : why; setRes({ status: "error", error: errorText(429, again.s) }); return; }
|
|
213
|
+
}
|
|
198
214
|
if (!r.ok) { setRes({ status: "error", error: errorText(r.status) }); return; }
|
|
199
215
|
const body = await r.json();
|
|
200
216
|
if (mine !== seq.current) return;
|
|
@@ -209,7 +225,7 @@ export function SearchDialog({ preset = {}, onClose }) {
|
|
|
209
225
|
|
|
210
226
|
// Teams, once the filters are first shown.
|
|
211
227
|
useEffect(() => {
|
|
212
|
-
if (!showFilters || teams) return;
|
|
228
|
+
if (guest || !showFilters || teams) return;
|
|
213
229
|
fetch("/ui/teams", { headers: { Accept: "application/json" } }).then(r => (r.ok ? r.json() : { teams: {} })).then(
|
|
214
230
|
(b) => setTeams(Object.keys((b && b.teams) || {}).sort()), () => setTeams([]));
|
|
215
231
|
}, [showFilters, teams]);
|
|
@@ -219,9 +235,9 @@ export function SearchDialog({ preset = {}, onClose }) {
|
|
|
219
235
|
// While a new query runs, the last answer stays (dimmed) rather than flashing empty.
|
|
220
236
|
const groups = useMemo(() => groupHits(res.hits || []), [res]);
|
|
221
237
|
// #2347 §6: the settings matching the query, from the page's own registry (no request), above the messages.
|
|
222
|
-
const settings = useMemo(() => (q.trim() ? searchSettings(q).slice(0, SETTINGS_SHOWN) : []), [q]);
|
|
238
|
+
const settings = useMemo(() => (q.trim() && !guest ? searchSettings(q).slice(0, SETTINGS_SHOWN) : []), [q]);
|
|
223
239
|
const flat = useMemo(() => [...settings.map((entry) => ({ setting: entry })), ...flatHits(groups)], [settings, groups]);
|
|
224
|
-
const activeFilters = (f.instance ? 1 : 0) + (f.team ? 1 : 0) + (f.since !== "any" ? 1 : 0) + (f.sources.length < SOURCES.length ? 1 : 0) + (f.mine ? 1 : 0);
|
|
240
|
+
const activeFilters = (f.instance ? 1 : 0) + (f.team ? 1 : 0) + (f.since !== "any" ? 1 : 0) + (!guest && f.sources.length < SOURCES.length ? 1 : 0) + (f.mine ? 1 : 0);
|
|
225
241
|
|
|
226
242
|
// The active row stays in view.
|
|
227
243
|
useEffect(() => {
|
|
@@ -239,7 +255,7 @@ export function SearchDialog({ preset = {}, onClose }) {
|
|
|
239
255
|
// Any failure — the request, or a body that is not JSON — ends as an error, never "Loading…" for good.
|
|
240
256
|
fetch(`/ui/search/context?key=${encodeURIComponent(hit.key)}`, { headers: { Accept: "application/json" } })
|
|
241
257
|
.then(async (r) => {
|
|
242
|
-
if (!r.ok) { setDetail(d => (d && d.hit === hit ? { hit, status: "error", error: r.status === 404 ? t("search.notFound") : errorText(r.status) } : d)); return; }
|
|
258
|
+
if (!r.ok) { const s = r.status === 429 ? (await retryOf(r)).s : 0; setDetail(d => (d && d.hit === hit ? { hit, status: "error", error: r.status === 404 ? t("search.notFound") : errorText(r.status, s) } : d)); return; }
|
|
243
259
|
const body = await r.json();
|
|
244
260
|
setDetail(d => (d && d.hit === hit ? { hit, status: "ok", ctx: body } : d));
|
|
245
261
|
})
|
|
@@ -266,7 +282,7 @@ export function SearchDialog({ preset = {}, onClose }) {
|
|
|
266
282
|
<div class="search-box">
|
|
267
283
|
<div class="search-head">
|
|
268
284
|
<${Icon} name="search" />
|
|
269
|
-
<input ref=${input} class="search-input" type="search" value=${q} placeholder=${t("search.placeholder")} aria-label=${t("search.label")}
|
|
285
|
+
<input ref=${input} class="search-input" type="search" value=${q} placeholder=${t(guest ? "search.guestPlaceholder" : "search.placeholder")} aria-label=${t("search.label")}
|
|
270
286
|
role="combobox" aria-expanded=${flat.length && !detail ? "true" : "false"} aria-controls="search-results" aria-autocomplete="list"
|
|
271
287
|
aria-activedescendant=${activeId} autocomplete="off" spellcheck=${false} enterkeyhint="search"
|
|
272
288
|
onInput=${(e) => { setQ(e.target.value); setDetail(null); }} />
|
|
@@ -288,24 +304,24 @@ export function SearchDialog({ preset = {}, onClose }) {
|
|
|
288
304
|
${["any", "day", "week", "month"].map(k => html`<button type="button" class=${`chip-btn${f.since === k ? " on" : ""}`} aria-pressed=${f.since === k ? "true" : "false"}
|
|
289
305
|
onClick=${() => setFilter({ since: k })}>${k === "any" ? t("search.anyTime") : t(`search.${k}`)}</button>`)}
|
|
290
306
|
</div>
|
|
291
|
-
|
|
307
|
+
${guest ? null : html`<div class="search-chips" role="group" aria-label=${t("search.sources")}>
|
|
292
308
|
${SOURCES.map(s => html`<button type="button" class=${`chip-btn${f.sources.includes(s) ? " on" : ""}`} aria-pressed=${f.sources.includes(s) ? "true" : "false"}
|
|
293
309
|
onClick=${() => toggleSource(s)}>${t(`search.src_${s}`)}</button>`)}
|
|
294
310
|
<label class="search-mine"><input type="checkbox" checked=${f.mine} onChange=${(e) => setFilter({ mine: e.target.checked })} />${t("search.mine")}</label>
|
|
295
|
-
</div
|
|
311
|
+
</div>`}
|
|
296
312
|
</div>` : null}
|
|
297
313
|
<div class="search-body" ref=${list}>
|
|
298
314
|
${detail ? html`<${Detail} detail=${detail} q=${q} label=${label} onBack=${() => { setDetail(null); input.current && input.current.focus(); }} onClose=${onClose} />`
|
|
299
|
-
: html`<${Results} q=${q} res=${res} groups=${groups} settings=${settings} active=${active} setActive=${setActive} open=${open} label=${label} filtered=${activeFilters > 0}
|
|
315
|
+
: html`<${Results} guest=${guest} q=${q} res=${res} groups=${groups} settings=${settings} active=${active} setActive=${setActive} open=${open} label=${label} filtered=${activeFilters > 0}
|
|
300
316
|
recent=${recent} pick=${(s) => { recordEdit(input.current); setQ(s); input.current && input.current.focus(); }} clearRecent=${() => { forgetRecent(); setRecent([]); }} />`}
|
|
301
317
|
</div>
|
|
302
318
|
<div class="search-foot" aria-hidden="true">${t("search.keys")}</div>
|
|
303
319
|
</div></dialog>`;
|
|
304
320
|
}
|
|
305
321
|
|
|
306
|
-
function Results({ q, res, groups, settings = [], active, setActive, open, label, filtered, recent, pick, clearRecent }) {
|
|
322
|
+
function Results({ guest = false, q, res, groups, settings = [], active, setActive, open, label, filtered, recent, pick, clearRecent }) {
|
|
307
323
|
if (!q.trim()) {
|
|
308
|
-
return html`<div class="search-empty"><p class="search-hint">${t("search.hint")}</p>
|
|
324
|
+
return html`<div class="search-empty"><p class="search-hint">${t(guest ? "search.guestHint" : "search.hint")}</p>
|
|
309
325
|
${recent.length ? html`<div class="search-recent"><div class="search-recent-head"><span>${t("search.recent")}</span>
|
|
310
326
|
<button type="button" class="chip-btn" onClick=${clearRecent}>${t("search.clearRecent")}</button></div>
|
|
311
327
|
<ul>${recent.map(s => html`<li><button type="button" class="search-recent-item" onClick=${() => pick(s)}><${Icon} name="clock" size=${14} /><span>${s}</span></button></li>`)}</ul></div>` : null}</div>`;
|