pi-roundtable-web 0.24.2 → 0.25.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.
Files changed (148) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +51 -9
  3. package/dist/chunk-action-menu-qe839g4b.js +1 -0
  4. package/dist/chunk-agent-0pm9p3nn.js +1 -0
  5. package/dist/chunk-arranged-rooms-enpfswmg.js +1 -0
  6. package/dist/chunk-character-creator-6yf29xwx.js +1 -0
  7. package/dist/chunk-codeeditor-gdbdg7rp.js +2 -0
  8. package/dist/chunk-confirm-xrw7d732.js +1 -0
  9. package/dist/chunk-connectors-anhghy3k.js +1 -0
  10. package/dist/chunk-conversations-ry228aes.js +1 -0
  11. package/dist/chunk-drag-q4fx9b22.js +1 -0
  12. package/dist/chunk-entry-he1embgp.js +1 -0
  13. package/dist/chunk-folder-list-5cp8stna.js +1 -0
  14. package/dist/chunk-folds-hff104tk.js +1 -0
  15. package/dist/chunk-group-scene-h228jdat.js +1 -0
  16. package/dist/chunk-index-0p5h2kdz.js +2 -0
  17. package/dist/chunk-index-0pt08yd9.js +1 -0
  18. package/dist/chunk-index-12bw9yj8.js +2 -0
  19. package/dist/chunk-index-18gxy79n.js +47 -0
  20. package/dist/{chunk-index-mpm90318.js → chunk-index-27qca4mr.js} +2 -2
  21. package/dist/chunk-index-2ad17eh4.js +2 -0
  22. package/dist/chunk-index-31wh5twj.js +52 -0
  23. package/dist/chunk-index-4dy94pbz.js +2 -0
  24. package/dist/chunk-index-4ecjtdy8.js +13 -0
  25. package/dist/chunk-index-4v1b7ks9.js +2 -0
  26. package/dist/chunk-index-5d8yjj4m.js +2 -0
  27. package/dist/chunk-index-89ajmxh7.js +2 -0
  28. package/dist/chunk-index-9b1kt7qw.css +1 -0
  29. package/dist/chunk-index-9jqcskb4.js +14 -0
  30. package/dist/chunk-index-b9ny4623.js +2 -0
  31. package/dist/chunk-index-bvfqjh77.js +2 -0
  32. package/dist/chunk-index-d5abvq7v.js +2 -0
  33. package/dist/chunk-index-e1dr6g10.js +2 -0
  34. package/dist/chunk-index-gpkzhq1g.js +2 -0
  35. package/dist/chunk-index-hk3xyy20.js +2 -0
  36. package/dist/chunk-index-jm23vn03.js +4 -0
  37. package/dist/chunk-index-k7gh0jvz.js +2 -0
  38. package/dist/{chunk-index-6t68gbex.js → chunk-index-krtk6nn2.js} +2 -2
  39. package/dist/chunk-index-ky42ev0b.js +2 -0
  40. package/dist/chunk-index-pa2yyqh0.js +2 -0
  41. package/dist/chunk-index-pcvt668q.js +2 -0
  42. package/dist/chunk-index-pqw27eye.js +1 -0
  43. package/dist/chunk-index-s681nny9.js +2 -0
  44. package/dist/chunk-index-wmbmmrd4.js +302 -0
  45. package/dist/chunk-index-ymbjrsp6.js +2 -0
  46. package/dist/chunk-index-zfd00hwb.js +2 -0
  47. package/dist/chunk-lang-css-t85r8rz0.js +1 -0
  48. package/dist/{chunk-lang-dockerfile-ey0nvhhm.js → chunk-lang-dockerfile-d5mzxs60.js} +1 -1
  49. package/dist/{chunk-lang-go-403qsw9f.js → chunk-lang-go-830fmhba.js} +3 -3
  50. package/dist/chunk-lang-html-n6jparge.js +1 -0
  51. package/dist/chunk-lang-javascript-xj00cp97.js +1 -0
  52. package/dist/chunk-lang-json-5eaarz7j.js +1 -0
  53. package/dist/chunk-lang-markdown-zxx8eke8.js +6 -0
  54. package/dist/{chunk-lang-python-9jk5a49y.js → chunk-lang-python-fjw84w10.js} +2 -2
  55. package/dist/chunk-lang-rust-5rbp2zdk.js +1 -0
  56. package/dist/{chunk-lang-shell-cgq6q0kj.js → chunk-lang-shell-vzagpym3.js} +1 -1
  57. package/dist/chunk-lang-sql-0c2w9c2y.js +2 -0
  58. package/dist/{chunk-lang-toml-xh45nm68.js → chunk-lang-toml-yr9fv6eb.js} +1 -1
  59. package/dist/chunk-lang-typescript-8f6yywt3.js +1 -0
  60. package/dist/{chunk-lang-xml-1mh879j9.js → chunk-lang-xml-eze08p42.js} +1 -1
  61. package/dist/{chunk-lang-yaml-jac98srt.js → chunk-lang-yaml-c4b6ft3y.js} +1 -1
  62. package/dist/chunk-layout-actions-bv0chj41.js +1 -0
  63. package/dist/chunk-menu-items-jrwxycr1.js +2 -0
  64. package/dist/chunk-notes-9b7pgqfe.js +1 -0
  65. package/dist/chunk-overview-8yw71esz.js +1 -0
  66. package/dist/chunk-palette-frms7axe.js +1 -0
  67. package/dist/chunk-placement-items-fvfy9m0y.js +1 -0
  68. package/dist/chunk-prompt-history-yrqehads.js +1 -0
  69. package/dist/chunk-prompts-g6609tqx.js +1 -0
  70. package/dist/chunk-room-confirms-0a2y0925.js +1 -0
  71. package/dist/chunk-room-menu-g11q97br.js +1 -0
  72. package/dist/chunk-rooms-menu-button-h9qge48n.js +1 -0
  73. package/dist/chunk-schedules-ezmacrbj.js +1 -0
  74. package/dist/chunk-seat-menu-jv4s7hmj.js +1 -0
  75. package/dist/chunk-sheet-hosts-b0azyfhb.js +1 -0
  76. package/dist/chunk-skill-editor-h6k6whwg.js +8 -0
  77. package/dist/chunk-skills-gqcfhq56.js +1 -0
  78. package/dist/chunk-switcher-list-mcpamqnj.js +1 -0
  79. package/dist/chunk-table-s8cde1xz.js +1 -0
  80. package/dist/chunk-table-seats-wqkjn8bc.js +1 -0
  81. package/dist/chunk-table2d-4sje1fr0.js +1 -0
  82. package/dist/chunk-table3d-h64skazp.js +4220 -0
  83. package/dist/chunk-transcript-c59c6mpm.js +1 -0
  84. package/dist/chunk-workspace-0cyspkk4.js +1 -0
  85. package/dist/chunk-zh-tw-nc93jm10.js +1 -0
  86. package/dist/index.html +1 -1
  87. package/dist/sw.js +1 -1
  88. package/package.json +3 -3
  89. package/src/api-types.ts +214 -2
  90. package/src/app/api-http.ts +66 -0
  91. package/src/app/app.ts +18 -1
  92. package/src/app/card-log.ts +6 -0
  93. package/src/app/layout-routes.ts +239 -0
  94. package/src/app/layout.ts +133 -0
  95. package/src/app/prompt-routes.ts +167 -0
  96. package/src/app/room-log.ts +7 -0
  97. package/src/app/rooms.ts +23 -0
  98. package/src/app/routes.ts +155 -131
  99. package/src/app/skill-doc-routes.ts +122 -0
  100. package/src/app/team-routes.ts +457 -0
  101. package/src/app/threads.ts +14 -0
  102. package/src/i18n/zh-tw.ts +177 -10
  103. package/src/layout-order.ts +210 -0
  104. package/src/push/mutes.ts +17 -0
  105. package/src/web-plugin.ts +25 -0
  106. package/dist/chunk-action-menu-7v5fce2v.js +0 -1
  107. package/dist/chunk-agent-2rw4ydw2.js +0 -1
  108. package/dist/chunk-agent-sheet-2q24dkkf.js +0 -3
  109. package/dist/chunk-character-creator-8q6ympxj.js +0 -1
  110. package/dist/chunk-codeeditor-fgmq298t.js +0 -2
  111. package/dist/chunk-connectors-mr53r7fh.js +0 -1
  112. package/dist/chunk-conversations-4vy2z6w9.js +0 -1
  113. package/dist/chunk-entry-jg1eem82.js +0 -1
  114. package/dist/chunk-group-scene-na95b1te.js +0 -1
  115. package/dist/chunk-index-11rjg8a8.js +0 -1
  116. package/dist/chunk-index-1y50d6sj.js +0 -2
  117. package/dist/chunk-index-5y2r0eaj.js +0 -47
  118. package/dist/chunk-index-7fphxxzd.js +0 -14
  119. package/dist/chunk-index-80f1zkp6.css +0 -1
  120. package/dist/chunk-index-87h45frg.js +0 -2
  121. package/dist/chunk-index-bnwmssyn.js +0 -2
  122. package/dist/chunk-index-ckgbszwt.js +0 -51
  123. package/dist/chunk-index-fmjjekhd.js +0 -2
  124. package/dist/chunk-index-t1m65yn6.js +0 -13
  125. package/dist/chunk-index-t711t17m.js +0 -2
  126. package/dist/chunk-index-txv7qvyy.js +0 -2
  127. package/dist/chunk-index-wr3dg24d.js +0 -2
  128. package/dist/chunk-index-yx683d5n.js +0 -2
  129. package/dist/chunk-lang-css-fkdg3x8x.js +0 -1
  130. package/dist/chunk-lang-html-v8h9g977.js +0 -1
  131. package/dist/chunk-lang-javascript-ykb2vhaa.js +0 -1
  132. package/dist/chunk-lang-json-2rze3rpy.js +0 -1
  133. package/dist/chunk-lang-markdown-6w2htnfc.js +0 -6
  134. package/dist/chunk-lang-rust-24fggcqx.js +0 -1
  135. package/dist/chunk-lang-sql-gjywb9ke.js +0 -2
  136. package/dist/chunk-lang-typescript-qh6rebyb.js +0 -1
  137. package/dist/chunk-menu-items-zjpyj51b.js +0 -2
  138. package/dist/chunk-notes-sjba7kw3.js +0 -1
  139. package/dist/chunk-overview-26r77kb6.js +0 -1
  140. package/dist/chunk-room-confirms-851184ep.js +0 -1
  141. package/dist/chunk-room-menu-8y9c026y.js +0 -1
  142. package/dist/chunk-skills-56s4hdbn.js +0 -1
  143. package/dist/chunk-table-qykpvp32.js +0 -1
  144. package/dist/chunk-table2d-k60ressj.js +0 -1
  145. package/dist/chunk-table3d-bv2ajk39.js +0 -4220
  146. package/dist/chunk-transcript-vekz1mh6.js +0 -1
  147. package/dist/chunk-workspace-hevefd0d.js +0 -1
  148. package/dist/chunk-zh-tw-5r2wjre0.js +0 -1
package/src/i18n/zh-tw.ts CHANGED
@@ -280,6 +280,15 @@ export const ZH_TW: Readonly<Record<string, string>> = {
280
280
  "Reply to the agent that started this thread.": "回覆開啟這個討論串的夥伴。",
281
281
  "Yolo mode": "Yolo 模式",
282
282
  "No agents are seated yet.": "圓桌上還沒有夥伴入座。",
283
+ "Who's at the table": "誰上桌",
284
+ "Off the table": "離席中",
285
+ "Leave the table": "離席",
286
+ "{name} left the table": "{name}已離席",
287
+ "The table is empty": "圓桌空了",
288
+ "Every agent has left the table. Choose who sits down.":
289
+ "所有夥伴都離席了。選擇誰要上桌。",
290
+ "An agent who leaves the table is muted: its room stops pushing to you. A card that waits for you is always pushed.":
291
+ "離席的夥伴會被靜音:它的聊天室不再推播給你。等你回應的卡片仍會推播。",
283
292
 
284
293
  // 通知
285
294
  "A file is no longer available. Attach it again.":
@@ -318,8 +327,8 @@ export const ZH_TW: Readonly<Record<string, string>> = {
318
327
  // 夥伴
319
328
  "Class, colors and parts. The picture here, in the rooms and in Discord all follow it.":
320
329
  "職業、配色與部件。這裡、聊天室與 Discord 的圖像都會跟著改變。",
321
- "Follow the assistant (automatic)": "跟隨助理(自動)",
322
- "Follow the assistant": "跟隨助理",
330
+ "Follow the assistant (automatic)": "跟隨夥伴(自動)",
331
+ "Follow the assistant": "跟隨夥伴",
323
332
  Look: "造型",
324
333
  Name: "名稱",
325
334
  Prompt: "提示詞",
@@ -338,7 +347,7 @@ export const ZH_TW: Readonly<Record<string, string>> = {
338
347
  Unreachable: "無法連線",
339
348
  "Upstream gateways": "上游閘道",
340
349
  "Upstream MCP gateways and virtual servers used by the assistant.":
341
- "助理使用的上游 MCP 閘道與虛擬伺服器。",
350
+ "夥伴使用的上游 MCP 閘道與虛擬伺服器。",
342
351
  "Used by": "使用者",
343
352
  "Virtual servers": "虛擬伺服器",
344
353
 
@@ -349,7 +358,7 @@ export const ZH_TW: Readonly<Record<string, string>> = {
349
358
  running: "進行中",
350
359
  "Owner channels": "擁有者頻道",
351
360
  "Direct messages and channels where the owner talks to the assistant.":
352
- "擁有者與助理交談的私訊與頻道。",
361
+ "擁有者與夥伴交談的私訊與頻道。",
353
362
  "Agent channels": "夥伴頻道",
354
363
  "The agent server's one-to-one channels.": "夥伴伺服器的一對一頻道。",
355
364
  "Group channels": "群組頻道",
@@ -386,12 +395,12 @@ export const ZH_TW: Readonly<Record<string, string>> = {
386
395
  "Search notes": "搜尋記憶",
387
396
  Text: "內容",
388
397
  "What the assistant remembers. A change applies from the next turn.":
389
- "助理記得的內容。變更會從下一個回合起生效。",
398
+ "夥伴記得的內容。變更會從下一個回合起生效。",
390
399
  "Whose notes": "誰的記憶",
391
400
  Core: "核心",
392
401
  "Carried into every turn.": "每個回合都會帶入。",
393
402
  Note: "備忘",
394
- "Searched when the assistant needs them.": "助理需要時才會搜尋。",
403
+ "Searched when the assistant needs them.": "夥伴需要時才會搜尋。",
395
404
  Events: "事件",
396
405
  "Carried into every turn until their date.": "到期日之前每個回合都會帶入。",
397
406
 
@@ -406,7 +415,7 @@ export const ZH_TW: Readonly<Record<string, string>> = {
406
415
  "Conversations opened through remote MCP; after deletion the session ID cannot be resumed.":
407
416
  "透過遠端 MCP 開啟的對話;刪除後無法再用該工作階段 ID 接續。",
408
417
  "Direct messages and channels where you mention the assistant. Start over archives; delete removes every archive permanently.":
409
- "你在私訊與頻道中提及助理的對話。重新開始會封存;刪除會永久移除所有封存。",
418
+ "你在私訊與頻道中提及夥伴的對話。重新開始會封存;刪除會永久移除所有封存。",
410
419
  "Enabled by {name} at {time}": "由{name}於 {time} 啟用",
411
420
  idle: "閒置",
412
421
  "Just compacted (limit {limit})": "剛完成壓縮(上限 {limit})",
@@ -496,6 +505,61 @@ export const ZH_TW: Readonly<Record<string, string>> = {
496
505
  "Could not copy.": "無法複製。",
497
506
  "Could not share.": "無法分享。",
498
507
 
508
+ // 新增、封存與群組
509
+ "New agent": "新增夥伴",
510
+ "New group": "新增群組",
511
+ Archived: "已封存",
512
+ Archive: "封存",
513
+ "Archive {name}": "封存{name}",
514
+ "Archive {name}?": "要封存{name}嗎?",
515
+ "Archive agent": "封存夥伴",
516
+ "Archive group": "封存群組",
517
+ "Archived {name}.": "已封存{name}。",
518
+ "Restored {name}.": "已還原{name}。",
519
+ "Deleted {name}.": "已刪除{name}。",
520
+ "Delete {name}?": "要刪除{name}嗎?",
521
+ "Made {name}.": "已建立{name}。",
522
+ "Make agent": "建立夥伴",
523
+ "Make group": "建立群組",
524
+ "Making…": "建立中…",
525
+ "Permanent name": "固定名稱",
526
+ "Lowercase letters, digits and dashes, up to 32. It names the room and cannot be changed.":
527
+ "小寫英文字母、數字與連字號,最多 32 個字元。它是聊天室的名稱,建立後無法更改。",
528
+ "Its job, its sources, its limits and how it reports.":
529
+ "它的工作、資料來源、限制,以及怎麼回報。",
530
+ "Opening task": "開場任務",
531
+ "Optional. The agent starts on it as soon as it is made.":
532
+ "可不填。夥伴建立後會立刻開始做。",
533
+ "Your look": "你選的外觀",
534
+ "Suggested from its role": "依角色建議的外觀",
535
+ "Choose a look": "選擇外觀",
536
+ "Use this look": "使用這個外觀",
537
+ "Edit group": "編輯群組",
538
+ "Name, members and host": "名稱、成員與主持人",
539
+ Members: "成員",
540
+ "Pick 2 to 6 agents. They take turns in the order picked.":
541
+ "選 2 到 6 位夥伴。他們依選取的順序輪流發言。",
542
+ Host: "主持人",
543
+ "It stops answering. The history is kept and it can be restored.":
544
+ "它會停止回應。歷史會保留,之後可以還原。",
545
+ "An archived agent stops answering and its schedules are cancelled. Its history is kept, and you can restore it from Archived.":
546
+ "封存後的夥伴不再回應,排程也會取消。歷史會保留,之後可以從「已封存」還原。",
547
+ "{name} stops answering and its schedules are cancelled. A group loses it as a member. The history is kept, and Archived in the room list restores it.":
548
+ "{name}會停止回應,排程也會取消;群組會少掉這位成員。歷史會保留,可以從聊天室列表的「已封存」還原。",
549
+ "Archived agents and groups no longer answer. Their history is kept until you delete them.":
550
+ "已封存的夥伴與群組不再回應。歷史會保留,直到你刪除它們。",
551
+ "Nothing is archived.": "沒有已封存的項目。",
552
+ "{name} and its messages in this app are deleted for good, and the name can be used again. This cannot be undone.":
553
+ "{name}和它在這個應用程式裡的訊息會永久刪除,名稱可以再使用。此動作無法復原。",
554
+ "What the agent can load when a task calls for it. It applies from its next turn.":
555
+ "夥伴在任務需要時可以載入的技能。下一輪對話起生效。",
556
+ "Its file cannot be read.": "讀不到它的檔案。",
557
+ "Built in: every agent carries it.": "內建:每位夥伴都有。",
558
+ "This host has no skills.": "這個主機沒有技能。",
559
+ "Give add or remove.": "請給 add 或 remove。",
560
+ "members must be a list of text.": "members 必須是文字清單。",
561
+ "Give displayName, members or host.": "請給 displayName、members 或 host。",
562
+
499
563
  // 聊天室列表
500
564
  Agents: "夥伴",
501
565
  "Dispatch threads": "派工討論串",
@@ -578,7 +642,7 @@ export const ZH_TW: Readonly<Record<string, string>> = {
578
642
 
579
643
  // 技能
580
644
  "All agents": "所有夥伴",
581
- "Assistant-written": "助理撰寫",
645
+ "Assistant-written": "夥伴撰寫",
582
646
  "Built-in": "內建",
583
647
  Carriers: "搭載對象",
584
648
  "File missing": "檔案遺失",
@@ -608,7 +672,7 @@ export const ZH_TW: Readonly<Record<string, string>> = {
608
672
  "Tool failed": "工具執行失敗",
609
673
  "Tool result": "工具結果",
610
674
  User: "使用者",
611
- Assistant: "助理",
675
+ Assistant: "夥伴",
612
676
  "Summary of earlier conversation": "先前對話的摘要",
613
677
 
614
678
  // 伺服器回應
@@ -654,6 +718,46 @@ export const ZH_TW: Readonly<Record<string, string>> = {
654
718
  "before and limit must be whole numbers.": "before 與 limit 必須是整數。",
655
719
  "Give yolo or longRunning.": "請提供 yolo 或 longRunning。",
656
720
  "muted must be true or false.": "muted 必須是 true 或 false。",
721
+ // 分組與置頂
722
+ "Give the folder a name of 1 to 40 characters.": "分組名稱要 1 到 40 個字。",
723
+ "There is no such folder.": "找不到這個分組。",
724
+ "A folder of that name exists.": "已經有同名的分組。",
725
+ "There are 50 folders already.": "分組已達 50 個上限。",
726
+ "before must be a key or null.": "before 必須是聊天室代號或 null。",
727
+ "name must be text.": "name 必須是文字。",
728
+ "Give a name or a place.": "請提供名稱或位置。",
729
+ "A thread stays under its room.": "派工串跟著原本的聊天室。",
730
+ "Home stays at the top.": "首頁固定在最上面。",
731
+ "pinned must be true or false.": "pinned 必須是 true 或 false。",
732
+ "folder must be a folder id or null.": "folder 必須是分組 ID 或 null。",
733
+ "Add a folder": "新增分組",
734
+ "New folder…": "新增分組…",
735
+ "Folder name": "分組名稱",
736
+ "Rename folder": "重新命名分組",
737
+ "Delete folder": "刪除分組",
738
+ "Delete the folder {name}? Its rooms move to Ungrouped; no room is deleted.":
739
+ "刪除分組「{name}」?裡面的聊天室會移到未分組,聊天室本身不會刪除。",
740
+ "Folder menu": "分組選單",
741
+ "Folders and order": "分組與排序",
742
+ "Move to folder…": "移到分組…",
743
+ "No rooms in this folder yet.": "這個分組還沒有聊天室。",
744
+ "Collapse {name}": "收合「{name}」",
745
+ "Expand {name}": "展開「{name}」",
746
+ "{count} room": "{count} 個聊天室",
747
+ "{count} rooms": "{count} 個聊天室",
748
+ Ungrouped: "未分組",
749
+ Pinned: "置頂",
750
+ Pin: "置頂",
751
+ Unpin: "取消置頂",
752
+ Arrange: "整理",
753
+ "Keep this room at the top of the list.": "把這個聊天室固定在清單最上面。",
754
+ "Put this room in a folder, or take it out.":
755
+ "把這個聊天室放進分組,或移出分組。",
756
+ "Move up": "上移",
757
+ "Move down": "下移",
758
+ "Mute the whole folder": "靜音整個分組",
759
+ "Unmute the whole folder": "取消靜音整個分組",
760
+ "Some notifications are off": "部分通知已關閉",
657
761
  "This pane is not served.": "沒有提供這個頁面。",
658
762
  "The schedule id is a number.": "排程 ID 必須是數字。",
659
763
  "There is no such schedule.": "找不到這個排程。",
@@ -736,7 +840,7 @@ export const ZH_TW: Readonly<Record<string, string>> = {
736
840
  "character.class.paladin": "聖騎士",
737
841
  "character.class.archivist": "檔案管理員",
738
842
  // 職業適合的工作
739
- "character.jobs.wizard": "協調者、規劃者、主要助理",
843
+ "character.jobs.wizard": "協調者、規劃者、主要夥伴",
740
844
  "character.jobs.knight": "審查者、品質把關、守護者",
741
845
  "character.jobs.scholar": "研究員、文獻回顧",
742
846
  "character.jobs.smith": "程式開發、建置、重構",
@@ -854,4 +958,67 @@ export const ZH_TW: Readonly<Record<string, string>> = {
854
958
  "character.accessory.glasses": "眼鏡",
855
959
  "character.accessory.freckles": "雀斑",
856
960
  "character.accessory.scar": "疤痕",
961
+
962
+ // 主要提示詞與技能內容的編輯
963
+ "Main prompts": "主要提示詞",
964
+ "Edit the main prompts": "編輯主要提示詞",
965
+ "The texts the assistant and the agents start from, with a diff to read before you save and a way back to the file's text.":
966
+ "夥伴們開始工作時所依據的文字;儲存前可檢視差異,也能回到檔案裡的版本。",
967
+ "The texts the assistant and the agents start from. A text you save here replaces the one from the file from the next turn on, until you go back to the file's.":
968
+ "夥伴們開始工作時所依據的文字。你在這裡儲存的文字,從下一回合起取代檔案裡的版本,直到你回到檔案版本為止。",
969
+ "Shared prompt": "共用提示詞",
970
+ "Guest prompt": "訪客提示詞",
971
+ "The base every agent's prompt starts with, the main assistant's included.":
972
+ "每位夥伴的提示詞都以它開頭,主要夥伴也不例外。",
973
+ "Used instead of the shared prompt when the person speaking is not the owner.":
974
+ "說話的人不是擁有者時,改用它取代共用提示詞。",
975
+ "No prompts to edit.": "沒有可編輯的提示詞。",
976
+ "Differs from the file version": "與檔案版本不同",
977
+ "File version cannot be read": "讀不到檔案版本",
978
+ "The file version of this prompt cannot be read, so it cannot be changed here until it can: {reason}":
979
+ "這份提示詞的檔案版本讀不到,在讀得到之前無法在這裡修改:{reason}",
980
+ "Set by {name}, {time}": "由{name}設定,{time}",
981
+ "Revert to the file version": "還原為檔案版本",
982
+ "Revert {name} to the file version?": "要把{name}還原為檔案版本嗎?",
983
+ "Back to the file version.": "已還原為檔案版本。",
984
+ "The text you saved is dropped and the text from the file applies from the next turn. It stays in the history, so you can bring it back.":
985
+ "你儲存的文字會被移除,從下一回合起改用檔案裡的文字。它仍留在歷史中,之後可以再取回。",
986
+ History: "歷史",
987
+ "History of the prompt": "提示詞的歷史",
988
+ "History of {name}": "{name}的歷史",
989
+ "No changes have been kept yet.": "還沒有留下任何更動紀錄。",
990
+ "Back to the file version": "已回到檔案版本",
991
+ "This change": "這次更動",
992
+ "It goes into the editor, where you read it over and save it to put it in force.":
993
+ "它會放進編輯器;檢視差異並儲存後才會生效。",
994
+ "Use this text": "使用這段文字",
995
+ "New skill": "新增技能",
996
+ "Create skill": "建立技能",
997
+ "Skill created.": "已建立技能。",
998
+ "This skill cannot be edited here.": "這個技能無法在這裡編輯。",
999
+ "This skill changed while you were editing it. The changes below are against the version there now; save again to replace it, or go back to edit.":
1000
+ "這個技能在你編輯時被更動了。下方的差異是對照目前的版本;再儲存一次會取代它,或回去繼續編輯。",
1001
+ "Frontmatter holds the name, which cannot change, and a description that starts with “Use when”. The rest is the skill.":
1002
+ "Frontmatter 含有名稱(不能更改)與以「Use when」開頭的描述,其餘就是技能內容。",
1003
+ "Registered skills and their carriers. A skill an agent wrote can be edited here; the others are read-only.":
1004
+ "已登錄的技能與攜帶者。夥伴撰寫的技能可以在這裡編輯,其他的唯讀。",
1005
+ "Built in with the release; it changes with the release.":
1006
+ "隨版本內建;要隨版本更新才會改變。",
1007
+ "From the package {package}; it changes when the package is upgraded.":
1008
+ "來自套件 {package};套件升級時才會改變。",
1009
+ "Linked from {repo}; change it in its repository.":
1010
+ "連結自 {repo};請到它的儲存庫修改。",
1011
+ // 主要提示詞與技能內容的編輯:伺服器的拒絕訊息
1012
+ "This host has no prompts to edit.": "這個主機沒有可編輯的提示詞。",
1013
+ "There is no such prompt.": "沒有這份提示詞。",
1014
+ "base must be the text the prompt was edited from.":
1015
+ "base 必須是編輯所依據的提示詞文字。",
1016
+ "base must be the text the prompt was reverted from.":
1017
+ "base 必須是還原所依據的提示詞文字。",
1018
+ "This prompt changed while you were editing it.":
1019
+ "這份提示詞在你編輯時被更動了。",
1020
+ "baseHash must be the hash the skill was read with.":
1021
+ "baseHash 必須是讀取技能時的雜湊值。",
1022
+ "This skill changed while you were editing it.":
1023
+ "這個技能在你編輯時被更動了。",
857
1024
  };
@@ -0,0 +1,210 @@
1
+ // The arrangement of the room list as plain functions over `LayoutView`. The server applies them to
2
+ // the stored layout under a lock and the page applies the same ones at once (optimistic), so a move
3
+ // means the same on both sides. Nothing here touches a database or the DOM.
4
+
5
+ import type { FolderView, LayoutView } from "./api-types.ts";
6
+
7
+ /** The most folders there can be. */
8
+ export const MAX_FOLDERS = 50;
9
+ /** The most code points of a folder's name. */
10
+ export const FOLDER_NAME_MAX = 40;
11
+
12
+ export const EMPTY_LAYOUT: LayoutView = { pins: [], folders: [] };
13
+
14
+ /** Why an edit of the layout was refused. */
15
+ export type LayoutProblem = "bad-name" | "no-folder" | "duplicate" | "full";
16
+
17
+ export class LayoutError extends Error {
18
+ constructor(readonly code: LayoutProblem) {
19
+ super(code);
20
+ this.name = "LayoutError";
21
+ }
22
+ }
23
+
24
+ /**
25
+ * `list` with `item` standing before `before`, or last when `before` is none or not in the list (a
26
+ * neighbour that was deleted meanwhile). `item` is taken out of its old place first; before itself
27
+ * it stays where it is.
28
+ */
29
+ export function moveBefore<T>(
30
+ list: readonly T[],
31
+ item: T,
32
+ before: T | null | undefined,
33
+ ): T[] {
34
+ if (before === item && list.includes(item)) return [...list];
35
+ const rest = list.filter((one) => one !== item);
36
+ const at =
37
+ before === null || before === undefined ? -1 : rest.indexOf(before);
38
+ if (at < 0) return [...rest, item];
39
+ return [...rest.slice(0, at), item, ...rest.slice(at)];
40
+ }
41
+
42
+ /**
43
+ * Where moving `item` one step goes in the list as it is shown: the neighbour it then stands
44
+ * before (`null` = last). Undefined when it is not in the list or already at that end. Reading the
45
+ * neighbour from what is shown keeps a step right when the stored list holds rooms that are hidden.
46
+ */
47
+ export function nudgeBefore(
48
+ shownList: readonly string[],
49
+ item: string,
50
+ direction: "up" | "down",
51
+ ): { before: string | null } | undefined {
52
+ const at = shownList.indexOf(item);
53
+ if (at < 0) return undefined;
54
+ if (direction === "up")
55
+ return at === 0 ? undefined : { before: shownList[at - 1] ?? null };
56
+ return at === shownList.length - 1
57
+ ? undefined
58
+ : { before: shownList[at + 2] ?? null };
59
+ }
60
+
61
+ /** A folder's name as stored: trimmed, 1 to 40 code points, without control characters. */
62
+ export function folderNameOf(raw: string): string {
63
+ const name = raw.trim();
64
+ const length = Array.from(name).length;
65
+ if (length < 1 || length > FOLDER_NAME_MAX || /\p{Cc}/u.test(name))
66
+ throw new LayoutError("bad-name");
67
+ return name;
68
+ }
69
+
70
+ const same = (a: string, b: string): boolean =>
71
+ a.toLocaleLowerCase() === b.toLocaleLowerCase();
72
+
73
+ function folderIndex(layout: LayoutView, id: string): number {
74
+ const at = layout.folders.findIndex((folder) => folder.id === id);
75
+ if (at < 0) throw new LayoutError("no-folder");
76
+ return at;
77
+ }
78
+
79
+ const withFolders = (
80
+ layout: LayoutView,
81
+ folders: FolderView[],
82
+ ): LayoutView => ({ pins: layout.pins, folders });
83
+
84
+ /** The layout with a new last folder. */
85
+ export function createFolder(
86
+ layout: LayoutView,
87
+ id: string,
88
+ rawName: string,
89
+ ): LayoutView {
90
+ const name = folderNameOf(rawName);
91
+ if (layout.folders.some((folder) => same(folder.name, name)))
92
+ throw new LayoutError("duplicate");
93
+ if (layout.folders.length >= MAX_FOLDERS) throw new LayoutError("full");
94
+ return withFolders(layout, [...layout.folders, { id, name, rooms: [] }]);
95
+ }
96
+
97
+ export function renameFolder(
98
+ layout: LayoutView,
99
+ id: string,
100
+ rawName: string,
101
+ ): LayoutView {
102
+ const at = folderIndex(layout, id);
103
+ const name = folderNameOf(rawName);
104
+ if (layout.folders.some((f) => f.id !== id && same(f.name, name)))
105
+ throw new LayoutError("duplicate");
106
+ return withFolders(
107
+ layout,
108
+ layout.folders.map((folder, i) =>
109
+ i === at ? { ...folder, name } : folder,
110
+ ),
111
+ );
112
+ }
113
+
114
+ /** The folder moved before another, or last. */
115
+ export function placeFolder(
116
+ layout: LayoutView,
117
+ id: string,
118
+ before: string | null,
119
+ ): LayoutView {
120
+ const at = folderIndex(layout, id);
121
+ const moving = layout.folders[at] as FolderView;
122
+ const ids = moveBefore(
123
+ layout.folders.map((folder) => folder.id),
124
+ id,
125
+ before,
126
+ );
127
+ return withFolders(
128
+ layout,
129
+ ids.map((one) =>
130
+ one === id
131
+ ? moving
132
+ : (layout.folders.find((folder) => folder.id === one) as FolderView),
133
+ ),
134
+ );
135
+ }
136
+
137
+ /** The layout without the folder; its rooms are in no folder. */
138
+ export function removeFolder(layout: LayoutView, id: string): LayoutView {
139
+ folderIndex(layout, id);
140
+ return withFolders(
141
+ layout,
142
+ layout.folders.filter((folder) => folder.id !== id),
143
+ );
144
+ }
145
+
146
+ /** The layout without a room: not pinned and in no folder. For a room that is gone for good. */
147
+ export const forgetRoom = (layout: LayoutView, room: string): LayoutView => ({
148
+ pins: layout.pins.filter((one) => one !== room),
149
+ folders: layout.folders.map((folder) =>
150
+ folder.rooms.includes(room)
151
+ ? { ...folder, rooms: folder.rooms.filter((one) => one !== room) }
152
+ : folder,
153
+ ),
154
+ });
155
+
156
+ /**
157
+ * A room into a folder before another room of it (or last), or into none with `folder` null. A
158
+ * room is in one place, so it leaves its pin and its old folder.
159
+ */
160
+ export function placeRoom(
161
+ layout: LayoutView,
162
+ room: string,
163
+ folder: string | null,
164
+ before: string | null,
165
+ ): LayoutView {
166
+ if (folder !== null) folderIndex(layout, folder);
167
+ const clear = forgetRoom(layout, room);
168
+ if (folder === null) return clear;
169
+ return withFolders(
170
+ clear,
171
+ clear.folders.map((one) =>
172
+ one.id === folder
173
+ ? { ...one, rooms: moveBefore(one.rooms, room, before) }
174
+ : one,
175
+ ),
176
+ );
177
+ }
178
+
179
+ /** A room pinned before another pinned room (or last), or unpinned. A pinned room leaves its folder. */
180
+ export function setPinned(
181
+ layout: LayoutView,
182
+ room: string,
183
+ pinned: boolean,
184
+ before: string | null,
185
+ ): LayoutView {
186
+ const clear = forgetRoom(layout, room);
187
+ if (!pinned) return clear;
188
+ return {
189
+ pins: moveBefore(
190
+ layout.pins.filter((one) => one !== room),
191
+ room,
192
+ before,
193
+ ),
194
+ folders: clear.folders,
195
+ };
196
+ }
197
+
198
+ /** The layout with only the rooms `known` accepts, for a reader who must not see a deleted agent's room. */
199
+ export function shown(
200
+ layout: LayoutView,
201
+ known: (room: string) => boolean,
202
+ ): LayoutView {
203
+ return {
204
+ pins: layout.pins.filter(known),
205
+ folders: layout.folders.map((folder) => ({
206
+ ...folder,
207
+ rooms: folder.rooms.filter(known),
208
+ })),
209
+ };
210
+ }
package/src/push/mutes.ts CHANGED
@@ -38,6 +38,23 @@ export class PushMutes {
38
38
  return new Set(rows.map((row) => row.room));
39
39
  }
40
40
 
41
+ /** Mutes or unmutes every room in the list as one step; a room already so is left as it is. */
42
+ async setMany(
43
+ rooms: readonly string[],
44
+ muted: boolean,
45
+ by: string,
46
+ ): Promise<void> {
47
+ if (rooms.length === 0) return;
48
+ await this.#sql.begin(async (tx) => {
49
+ if (muted)
50
+ await tx`
51
+ INSERT INTO web_push_muted_rooms ${tx(rooms.map((room) => ({ room, muted_by: by })))}
52
+ ON CONFLICT (room) DO NOTHING`;
53
+ else
54
+ await tx`DELETE FROM web_push_muted_rooms WHERE room IN ${tx(rooms)}`;
55
+ });
56
+ }
57
+
41
58
  /** Mutes or unmutes a room; doing either twice leaves it as it is. */
42
59
  async set(room: string, muted: boolean, by: string): Promise<void> {
43
60
  if (muted)
package/src/web-plugin.ts CHANGED
@@ -6,8 +6,10 @@ import {
6
6
  IDENTITY,
7
7
  MEMORY,
8
8
  MODES,
9
+ PROMPTS,
9
10
  type RoundtablePlugin,
10
11
  SCHEDULES,
12
+ SKILLS,
11
13
  } from "pi-roundtable";
12
14
  import { DISCORD } from "pi-roundtable/discord";
13
15
  import { APP_MIGRATIONS, OwnerApp } from "./app/app.ts";
@@ -124,6 +126,24 @@ async function build(settings: ResolvedOptions, context: Context) {
124
126
  // Absent on a host without the registry: the console then lists conversations found by name only.
125
127
  const registry = services.find(CONVERSATIONS);
126
128
  const identity = services.get(IDENTITY);
129
+ // An agent's skills are chosen from what the Skills pane lists when the host supplies it, else from the addon's own catalog.
130
+ const skillRegistry = services.find(SKILLS);
131
+ const skills = skillRegistry
132
+ ? {
133
+ catalog: () =>
134
+ features?.skills
135
+ ? features.skills.catalog()
136
+ : skillRegistry.catalog(),
137
+ attach: (
138
+ agent: string,
139
+ add: readonly string[],
140
+ remove: readonly string[],
141
+ ) => skillRegistry.attach(agent, add, remove),
142
+ documents: skillRegistry,
143
+ }
144
+ : undefined;
145
+ // The prompts plugin is always on; a host that replaced it may leave it out.
146
+ const prompts = services.find(PROMPTS);
127
147
  const listeners: (() => void)[] = [];
128
148
  const changed = () => {
129
149
  for (const listener of listeners) listener();
@@ -188,6 +208,9 @@ async function build(settings: ResolvedOptions, context: Context) {
188
208
  const app =
189
209
  rooms && vapid
190
210
  ? new OwnerApp({
211
+ ...(settings.presentation
212
+ ? { presentation: settings.presentation }
213
+ : {}),
191
214
  push: {
192
215
  vapid,
193
216
  subject: settings.push.subject,
@@ -217,6 +240,8 @@ async function build(settings: ResolvedOptions, context: Context) {
217
240
  : {}),
218
241
  database: context.database(),
219
242
  agents: services.get(AGENTS),
243
+ ...(skills ? { skills } : {}),
244
+ ...(prompts ? { prompts } : {}),
220
245
  ...(workspace ? { workspace } : {}),
221
246
  modes: services.get(MODES),
222
247
  identity,
@@ -1 +0,0 @@
1
- import{o,yt,c,r,m,e,i,l,W,pt,En}from"./chunk-index-ckgbszwt.js";var k=()=>typeof matchMedia==="function"&&matchMedia("(max-width: 719px)").matches,x={rtMenu:!0},g=()=>Boolean(window.history.state?.rtMenu),E=8,y=(n)=>n?.isConnected?n:null;function b(n){return n.items.map((t)=>i("button",{type:"button",role:t.checked===void 0?"menuitem":"menuitemcheckbox",...t.checked===void 0?{}:{"aria-checked":t.checked},className:"menu-item menu-link","data-tone":t.tone,onClick:()=>n.onChoose(t),children:[t.icon?e("span",{className:"menu-icon","data-tone":t.tone,children:e(l,{name:t.icon,size:18})}):null,e("span",{className:"menu-text",children:e("strong",{children:t.label})}),t.checked?e(l,{name:"check",size:18,className:"menu-check"}):null]},t.id))}function H({menu:n}){let t=c(void 0);if(n)t.current=n;let u=t.current,f=c(!1),v=c(void 0),p=n?.serial;return o(()=>{if(p===void 0)return;return window.history.pushState(x,"",window.location.href),f.current=!0,()=>{if(f.current)f.current=!1,window.history.back()}},[p]),o(()=>{let h=()=>{if(!f.current||g())return;f.current=!1,pt();let M=v.current;v.current=void 0,M?.()};return window.addEventListener("popstate",h),()=>window.removeEventListener("popstate",h)},[]),e(W,{open:n!==void 0,title:u?.title??"",onClose:pt,restoreFocus:()=>y(u?.source??null),children:u?e("div",{className:"menu action-items",role:"menu","aria-label":u.title,children:e(b,{items:u.items,onChoose:(h)=>{if(!f.current){pt(),h.run();return}v.current=h.run,window.history.back()}})}):null})}function T({menu:n}){let t=c(null),[u,f]=r(n.at),{source:v}=n,p=(s)=>{y(v)?.focus(),pt(),s?.()},h=c(p);h.current=p,yt(()=>{let s=t.current;if(!s)return;let a=s.getBoundingClientRect();f({x:Math.max(E,Math.min(n.at.x,window.innerWidth-a.width-E)),y:Math.max(E,Math.min(n.at.y,window.innerHeight-a.height-E))}),s.querySelector('[role^="menuitem"]')?.focus()},[n.serial]),o(()=>{let s=(d)=>{if(d.target instanceof Node&&t.current?.contains(d.target))return;pt()},a=()=>pt();return document.addEventListener("pointerdown",s,!0),document.addEventListener("wheel",s,{capture:!0,passive:!0}),document.addEventListener("touchmove",s,{capture:!0,passive:!0}),window.addEventListener("resize",a),()=>{document.removeEventListener("pointerdown",s,!0),document.removeEventListener("wheel",s,!0),document.removeEventListener("touchmove",s,!0),window.removeEventListener("resize",a)}},[]);let M=(s)=>{let a=[...t.current?.querySelectorAll('[role^="menuitem"]')??[]],d=a.indexOf(document.activeElement),w=(L)=>{s.preventDefault(),a[(L+a.length)%a.length]?.focus()};switch(s.key){case"Escape":s.preventDefault(),s.stopPropagation(),h.current();return;case"ArrowDown":return w(d+1);case"ArrowUp":return w(d<0?-1:d-1);case"Tab":return w(s.shiftKey?d<0?-1:d-1:d+1);case"Home":return w(0);case"End":return w(-1)}};return e("div",{ref:t,className:"action-popover",role:"menu","aria-label":n.title,tabIndex:-1,style:{left:`${u.x}px`,top:`${u.y}px`},onKeyDown:M,children:e(b,{items:n.items,onChoose:(s)=>p(s.run)})})}function I(){let n=En(),t=c(void 0);if(n&&t.current?.serial!==n.serial)t.current={serial:n.serial,sheet:k()};let u=Boolean(n&&t.current?.sheet);return i(m,{children:[e(H,{menu:u?n:void 0}),n&&!u?e(T,{menu:n},n.serial):null]})}export{I as ActionMenuHost};
@@ -1 +0,0 @@
1
- import{o,e,re,f,P,Re}from"./chunk-index-ckgbszwt.js";function g({name:r}){let m=f((i)=>i.listed),t=f((i)=>Object.values(i.rooms).find((n)=>n.view?.kind==="agent"&&n.view.name===r)?.view?.key,r);return o(()=>{if(t)re("room",t,{replace:!0}),Re(r);else if(m)re("rooms",void 0,{replace:!0})},[t,m,r]),e("div",{className:"page",children:e(P,{})})}export{g as AgentPage};
@@ -1,3 +0,0 @@
1
- import{Q,Ge,ie,o,Oe,q,c,r,t,s,e,i,l,f,ae,W,P,S,y,d,b,vt,on,Cn,An}from"./chunk-index-ckgbszwt.js";import{lt}from"./chunk-index-mpm90318.js";import{st}from"./chunk-index-87h45frg.js";function U(n,m){let a=n.split(`
2
- `),k=m.split(`
3
- `),h=0;while(h<a.length&&h<k.length&&a[h]===k[h])h++;let g=0;while(g<a.length-h&&g<k.length-h&&a[a.length-1-g]===k[k.length-1-g])g++;let N=a.slice(h,a.length-g),w=k.slice(h,k.length-g),u=a.slice(0,h).map((v)=>({kind:"same",text:v}));return u.push(...me(N,w)),u.push(...a.slice(a.length-g).map((v)=>({kind:"same",text:v}))),u}function me(n,m){let a=(v)=>v.map((L)=>({kind:"del",text:L})),k=(v)=>v.map((L)=>({kind:"add",text:L}));if(n.length===0)return k(m);if(m.length===0)return a(n);if(n.length*m.length>4000000)return[...a(n),...k(m)];let h=m.length+1,g=new Uint32Array((n.length+1)*h);for(let v=n.length-1;v>=0;v--)for(let L=m.length-1;L>=0;L--)g[v*h+L]=n[v]===m[L]?(g[(v+1)*h+L+1]??0)+1:Math.max(g[(v+1)*h+L]??0,g[v*h+L+1]??0);let N=[],w=0,u=0;while(w<n.length&&u<m.length)if(n[w]===m[u])N.push({kind:"same",text:n[w]}),w++,u++;else if((g[(w+1)*h+u]??0)>=(g[w*h+u+1]??0))N.push({kind:"del",text:n[w]}),w++;else N.push({kind:"add",text:m[u]}),u++;return N.push(...a(n.slice(w)),...k(m.slice(u))),N}function X(n){return{added:n.filter((m)=>m.kind==="add").length,removed:n.filter((m)=>m.kind==="del").length}}var ce=ie(()=>(Q("8q6ympxj"),import("./chunk-character-creator-8q6ympxj.js")).then((n)=>({default:n.CharacterCreator}))),fe=["default","off","minimal","low","medium","high","xhigh"],ee={default:s("Follow the assistant (automatic)"),off:s("Off"),minimal:s("Minimal"),low:s("Low"),medium:s("Medium"),high:s("High"),xhigh:s("Very high")},C="default",E=(n)=>t(ee[n]??n),J=(n)=>({displayName:n.displayName,prompt:n.prompt,model:n.model??C,thinking:n.thinking??C});function Y(n,m){return{...m.displayName!==n.displayName?{displayName:m.displayName}:{},...m.prompt!==n.prompt?{prompt:m.prompt}:{},...m.model!==(n.model??C)?{model:m.model}:{},...m.thinking!==(n.thinking??C)?{thinking:m.thinking}:{}}}function Ee(){let n=An(),m=c(n);if(n)m.current=n;let a=m.current,k=n!==void 0;if(o(()=>{if(!k)return;let h=window.location.href,g=()=>{if(window.location.href!==h)on()};return window.addEventListener("popstate",g),window.addEventListener("hashchange",g),()=>{window.removeEventListener("popstate",g),window.removeEventListener("hashchange",g)}},[k]),!a)return null;return e(W,{open:n!==void 0,title:t("Edit agent"),size:"wide",onClose:on,children:e(ue,{name:a.name,opened:a.id},a.name)})}function ue({name:n,opened:m}){let[a,k]=r(),[h,g]=r([]),[N,w]=r(),[u,v]=r(),[L,R]=r(!1),[te,x]=r(!1),[ne,F]=r(!1),se=vt(a?.room??""),M=c(null),T=a!==void 0,V=f((p)=>a?p.rooms[a.room]?.view?.muted===!0:!1,a?.room??""),[oe,H]=r(!1),O=Oe(),B=Oe(),K=Oe();o(()=>{if(!T||!Cn()?.look)return;M.current?.scrollIntoView({block:"start"}),M.current?.focus({preventScroll:!0})},[T,m]);let _=c({});_.current={profile:a,draft:u},o(()=>{let p=!0;return Promise.all([d.agent(n),d.models()]).then(([D,re])=>{if(!p)return;let{profile:G,draft:I}=_.current,de=G&&I&&Object.keys(Y(G,I)).length>0;k(D),g(re.models),v(de?I:J(D))},(D)=>p&&w(b(D))),()=>{p=!1}},[n,m]);let A=q(()=>a&&u?Y(a,u):{},[a,u]);if(N)return e(S,{message:N});if(!a||!u)return e(P,{});let j=Object.keys(A).length>0,le=Array.from(new Set([C,...h,u.model])),z=async()=>{R(!0);try{let p=await d.updateAgent(n,A);k(p),v(J(p)),x(!1),y(t("Saved."))}catch(p){y(b(p),"danger")}finally{R(!1)}};return i("div",{className:"stack agent-sheet",children:[i("header",{className:"profile-head",children:[e(ae,{spec:a.character,name:a.name,size:72,state:se,label:a.displayName}),i("div",{children:[e("h3",{children:a.displayName}),e("p",{className:"meta",children:t("Runs on {model}, thinking {thinking}",{model:a.effective.model,thinking:E(a.effective.thinking)})})]})]}),e("ul",{className:"menu",children:e("li",{children:i("label",{className:"menu-item menu-toggle",htmlFor:K,children:[e("span",{className:"menu-icon",children:e(l,{name:V?"bell-off":"bell"})}),i("span",{className:"menu-text",children:[e("strong",{children:t("Notifications")}),e("span",{className:"meta",children:t("Push this room's posts to every owner's devices. A card that waits for you is always pushed.")})]}),e(lt,{id:K,checked:!V,label:t("Notifications"),busy:oe,onChange:async(p)=>{H(!0),await st(a.room,!p),H(!1)}})]})})}),te?e(ge,{profile:a,draft:u,saving:L,onBack:()=>x(!1),onSave:z}):i("form",{className:"stack form",onSubmit:(p)=>{if(p.preventDefault(),!j)return;if(A.prompt!==void 0)x(!0);else z()},children:[i("label",{htmlFor:B,children:[t("Name"),e("input",{id:B,value:u.displayName,onChange:(p)=>v({...u,displayName:p.target.value})})]}),i("div",{className:"field",children:[i("div",{className:"field-head",children:[e("label",{htmlFor:O,children:t("Prompt")}),i("button",{type:"button",className:"secondary compact","aria-haspopup":"dialog",onClick:()=>F(!0),children:[e(pe,{}),t("Full screen")]})]}),e("textarea",{id:O,rows:8,value:u.prompt,onChange:(p)=>v({...u,prompt:p.target.value})})]}),e(Z,{label:t("Model"),value:u.model,onChange:(p)=>v({...u,model:p}),options:le.map((p)=>({value:p,label:p===C?t("Follow the assistant"):p}))}),e(Z,{label:t("Thinking level"),value:u.thinking,onChange:(p)=>v({...u,thinking:p}),options:fe.map((p)=>({value:p,label:t(ee[p])}))}),e("div",{className:"actions",children:e("button",{type:"submit",disabled:!j||L,children:L?t("Saving…"):A.prompt!==void 0?t("Review changes"):t("Save")})})]}),i("section",{className:"card stack",ref:M,tabIndex:-1,children:[e("h3",{children:t("Look")}),e("p",{className:"meta",children:t("Class, colors and parts. The picture here, in the rooms and in Discord all follow it.")}),e(Ge,{fallback:e(P,{}),children:e(ce,{name:a.name,displayName:a.displayName,prompt:a.prompt,character:a.character,onSaved:(p)=>k(p)})})]}),e(W,{open:ne,title:t("Prompt"),size:"full",onClose:()=>F(!1),children:e("textarea",{className:"prompt-full","aria-label":t("Prompt"),value:u.prompt,onChange:(p)=>v({...u,prompt:p.target.value})})})]})}var pe=()=>e("svg",{viewBox:"0 0 24 24",width:16,height:16,fill:"none",stroke:"currentColor",strokeWidth:1.8,strokeLinecap:"round",strokeLinejoin:"round","aria-hidden":"true",focusable:"false",children:e("path",{d:"M4 9V4h5M20 9V4h-5M4 15v5h5M20 15v5h-5"})});function Z(n){return i("fieldset",{className:"field",children:[e("legend",{children:n.label}),e("div",{className:"option-chips",children:n.options.map((m)=>e("button",{type:"button","aria-pressed":m.value===n.value,className:"option-chip",onClick:()=>n.onChange(m.value),children:m.label},m.value))})]})}var he={same:" ",add:"+",del:"−"};function ge(n){let{profile:m,draft:a}=n,k=c(null);o(()=>k.current?.focus(),[]);let h=q(()=>U(m.prompt,a.prompt),[m.prompt,a.prompt]),g=X(h),N=[];if(a.displayName!==m.displayName)N.push({label:t("Name"),before:m.displayName,after:a.displayName});let w=(u)=>u===C?t("Follow the assistant"):u;if(a.model!==(m.model??C))N.push({label:t("Model"),before:w(m.model??C),after:w(a.model)});if(a.thinking!==(m.thinking??C))N.push({label:t("Thinking level"),before:E(m.thinking??C),after:E(a.thinking)});return i("section",{ref:k,className:"stack review",tabIndex:-1,"aria-label":t("Review changes"),children:[e("h3",{children:t("Review changes")}),N.length?e("ul",{className:"review-fields",children:N.map((u)=>i("li",{children:[e("strong",{children:u.label}),e("span",{className:"review-before",children:u.before}),e("span",{"aria-hidden":"true",children:"→"}),e("span",{className:"review-after",children:u.after})]},u.label))}):null,i("div",{className:"field",children:[i("span",{className:"field-label",children:[t("Prompt")," ·"," ",t("Lines: {added} added, {removed} removed",{added:g.added,removed:g.removed})]}),e("pre",{className:"diff",children:h.map((u,v)=>i("span",{className:"diff-line","data-kind":u.kind,children:[e("span",{className:"diff-mark","aria-hidden":"true",children:he[u.kind]}),u.kind==="add"||u.kind==="del"?i("span",{className:"visually-hidden",children:[u.kind==="add"?t("Added"):t("Removed"),":"," "]}):null,u.text||" "]},v))})]}),i("div",{className:"actions",children:[e("button",{type:"button",className:"secondary",disabled:n.saving,onClick:n.onBack,children:t("Back to editing")}),e("button",{type:"button",disabled:n.saving,onClick:n.onSave,children:n.saving?t("Saving…"):t("Save")})]})]})}export{Ee as AgentSheetHost};
@@ -1 +0,0 @@
1
- import{_e,rt,X,Lt,jt}from"./chunk-index-5y2r0eaj.js";import{ce,an,o,Oe,q,c,r,t,s,m,e,i,Ct,bt,ae,W,y,ht,d,b,Pe,Me,ge}from"./chunk-index-ckgbszwt.js";var le={"hat.none":"No hat","hat.goggcap":"Leather cap","hat.tallcap":"Burgundian felt cap","hat.softcap":"Soft cap","hat.hardhat":"Iron skullcap","hat.fcap":"Hunter's cap","hat.winghelm":"Winged helm","hat.helm":"Helm","hat.mortar":"Biretta","hat.beret":"Chaperon","hat.goggles":"Pointed cap","hat.tricorn":"Pilgrim hat","hat.sailor":"Straw hat","hat.kettle":"Kettle hat","hat.cavalier":"Feathered hat","hat.toque":"Linen cook's cap","hair.none":"No hair","beard.none":"No beard","accessory.none":"No accessory","prop.beads":"Prayer beads","prop.marotte":"Marotte","prop.map":"Chart","prop.flask":"Alchemist's phial","prop.telescope":"Astrolabe","prop.wrench":"Dividers","outfit.surplice":"Surplice","outfit.overalls":"Work jerkin","outfit.armor":"Mail hauberk","outfit.apron":"Smith's apron","outfit.chef":"Cook's tunic","outfit.coat":"Doublet","outfit.vest":"Pourpoint","outfit.sailor":"Mariner's tunic","hair.bob":"Bob","hair.bun":"Bun","hair.ponytail":"Ponytail"},U=(a)=>a.charAt(0).toUpperCase()+a.slice(1);function N(a,l){let h=`character.${a}.${l}`,p=t(h);if(p!==h)return p;if(a==="class")return X.classes[l]?.label??U(l);return le[`${a}.${l}`]??U(l)}function V(a){let l=`character.jobs.${a}`,h=t(l);return h!==l?h:X.classes[a]?.jobs??""}var H=[{field:"hat",title:s("Hat"),ids:X.hats},{field:"outfit",title:s("Outfit"),ids:X.outfits},{field:"prop",title:s("Prop"),ids:X.props},{field:"hair",title:s("Hair"),ids:X.hair},{field:"beard",title:s("Beard"),ids:["none",...X.beards]},{field:"accessory",title:s("Accessory"),ids:["none",...X.accessories]}],de=new Set(["class","colors","palette","build","skin","hairColor",...H.map((a)=>a.field)]),Z=Object.keys(X.palettes),x={idle:s("Idle"),thinking:s("Thinking"),working:s("Working"),waiting:s("Waiting for you"),done:s("Done"),error:s("Error"),sleepy:s("Sleepy")},ue={slim:s("Slim"),regular:s("Regular"),stout:s("Stout")};function ee(a){return{class:a.class,outfit:a.outfit,hat:a.hat,prop:a.prop,hair:a.hair,beard:a.beard,accessory:a.accessory,build:a.build,skin:a.skin,hairColor:a.hairColor,colors:{...a.colors}}}var Q=(a)=>JSON.stringify(ee(a)),v=(a)=>a[Math.floor(Math.random()*a.length)];function fe(){return{class:v(_e),outfit:v(X.outfits),hat:v(X.hats),prop:v(X.props),hair:v(X.hair),beard:v(["none",...X.beards]),accessory:v(["none",...X.accessories]),build:v(["slim","regular","stout"]),skin:v([0,1,2,3,4,5]),hairColor:v(X.hairColors),palette:v(Z)}}var te=ce(function(l){return i("button",{type:"button",className:"creator-tile","aria-pressed":l.pressed,title:l.hint?`${l.label} — ${l.hint}`:l.label,onClick:l.onPick,...l.data,children:[e(ae,{spec:l.spec,size:l.size,live:!1}),e("span",{children:l.label})]})});function me(a,l,h){let p=JSON.stringify({...a,[l]:0});return q(()=>h.map((u)=>({...a,[l]:u})),[p,l,h])}function he(){let a=c(null),[l,h]=r();return o(()=>{let p=a.current;if(!p)return;let u=()=>{let S=p.scrollLeft>1,P=p.scrollLeft+p.clientWidth<p.scrollWidth-1;h(S&&P?"both":P?"end":S?"start":void 0)};u(),p.addEventListener("scroll",u,{passive:!0});let g=typeof ResizeObserver>"u"?void 0:new ResizeObserver(u);return g?.observe(p),()=>{p.removeEventListener("scroll",u),g?.disconnect()}},[]),[a,l]}function pe(a){let{part:l}=a,h=me(a.shown,l.field,l.ids),[p,u]=he();return e("div",{className:"creator-strip",ref:p,"data-more":u,children:l.ids.map((g,S)=>e(te,{data:{[`data-${l.field}`]:g},spec:h[S],size:44,label:N(l.field,g),pressed:a.draft[l.field]===g,onPick:()=>a.onPick(g)},g))})}function B(a){return e("button",{type:"button",className:"creator-swatch","aria-pressed":a.pressed,"aria-label":a.label,title:a.label,style:{background:a.background},onClick:a.onPick,...a.data})}function L(a){return i("div",{className:"creator-field","data-field":a.field,children:[a.title?e("h3",{children:a.title}):null,a.children,a.error?e("p",{className:"field-error",role:"alert",children:a.error}):null]})}function He(a){let[l,h]=r(a.character),p=an(l),[u,g]=r({}),[S,P]=r(!1),[T,re]=r("idle"),[K,E]=r(),[R,M]=r(),O=Pe("(max-width: 719px)"),z=c(null),I=Oe(),ie=Me(l),ne=bt(),w=Q(l)!==Q(a.character);o(()=>h(a.character),[a.character]),o(()=>{if(!w)return;let n=(k)=>k.preventDefault(),f=(k)=>{if(k.button!==0||k.metaKey||k.ctrlKey||k.shiftKey||k.altKey)return;let A=k.target instanceof Element?k.target.closest("a"):null;if(!A||A.target||A.hasAttribute("download"))return;if(z.current?.contains(A))return;k.preventDefault(),k.stopPropagation(),E({kind:"link",anchor:A})},F=Ct((k)=>E({kind:"navigate",proceed:k}));return window.addEventListener("beforeunload",n),document.addEventListener("click",f,!0),()=>{F(),window.removeEventListener("beforeunload",n),document.removeEventListener("click",f,!0)}},[w]);let C=(n)=>{g({}),h((f)=>Lt(n,f))},oe=async()=>{P(!0),g({});try{let n=await d.setCharacter(a.name,ee(l));a.onSaved(n),y(t("Saved."))}catch(n){let f=n instanceof ht&&n.field?n.field.split(".")[0]:void 0,F=f&&de.has(f)?f:"*";g({[F]:b(n)})}finally{P(!1)}},se=async()=>{let n=K;if(h(a.character),g({}),n?.kind==="link")setTimeout(()=>n.anchor.click(),0);else if(n?.kind==="navigate")n.proceed()},_=e(L,{field:"class",title:t("Class"),error:u.class,children:e("div",{className:"creator-grid",children:_e.map((n)=>e(be,{id:n,base:p,pressed:l.class===n,onPick:()=>C({class:n,colors:l.colors})},n))})}),j=i(L,{field:"colors",title:t("Colors"),error:u.colors??u.palette,children:[i("fieldset",{className:"creator-swatches",children:[e("legend",{className:"visually-hidden",children:t("Palette")}),Z.map((n)=>{let f=X.palettes[n];return e(B,{data:{"data-palette":n},label:N("palette",n),pressed:l.palette===n,background:`linear-gradient(135deg, ${f.primary} 52%, ${f.trim} 52%)`,onPick:()=>C({palette:n})},n)})]}),e("div",{className:"creator-custom",children:["primary","secondary","trim"].map((n)=>i("label",{children:[e("span",{children:n==="primary"?t("Primary (the accent)"):n==="secondary"?t("Secondary"):t("Trim")}),e("input",{type:"color",value:l.colors[n],onChange:(f)=>C({colors:{[n]:f.target.value}})})]},n))})]}),G=H.map((n)=>e(L,{field:n.field,title:t(n.title),error:u[n.field],children:e(pe,{part:n,draft:l,shown:p,onPick:(f)=>C({[n.field]:f})})},n.field)),J=i(m,{children:[e(L,{field:"build",title:t("Build"),error:u.build,children:i("fieldset",{className:"segmented",children:[e("legend",{className:"visually-hidden",children:t("Build")}),["slim","regular","stout"].map((n)=>i("label",{"data-checked":l.build===n,children:[e("input",{type:"radio",name:`${I}-build`,checked:l.build===n,onChange:()=>C({build:n})}),Y(n)]},n))]})}),e(L,{field:"skin",title:t("Skin tone"),error:u.skin,children:i("fieldset",{className:"creator-swatches",children:[e("legend",{className:"visually-hidden",children:t("Skin tone")}),(ne??X.skins).map((n,f)=>e(B,{data:{"data-skin":String(f)},label:t("Skin tone {n}",{n:f+1}),pressed:l.skin===f,background:n,onPick:()=>C({skin:f})},f))]})}),e(L,{field:"hairColor",title:t("Hair color"),error:u.hairColor,children:i("fieldset",{className:"creator-swatches",children:[e("legend",{className:"visually-hidden",children:t("Hair color")}),X.hairColors.map((n,f)=>e(B,{data:{"data-hair-color":n},label:t("Hair color {n}",{n:f+1}),pressed:l.hairColor===n,background:n,onPick:()=>C({hairColor:n})},n)),e("input",{type:"color","aria-label":t("Hair color"),value:l.hairColor,onChange:(n)=>C({hairColor:n.target.value})})]})})]}),D=[{id:"class",title:t("Class"),summary:N("class",l.class),error:u.class,node:_},{id:"colors",title:t("Colors"),summary:l.palette?N("palette",l.palette):l.colors.primary,error:u.colors??u.palette,node:j},...H.map((n,f)=>({id:n.field,title:t(n.title),summary:N(n.field,l[n.field]),error:u[n.field],node:G[f]})),{id:"body",title:t("Body"),summary:Y(l.build),error:u.build??u.skin??u.hairColor,node:J}];return i("form",{ref:z,className:"creator",style:ie,"aria-labelledby":I,onSubmit:(n)=>{if(n.preventDefault(),w&&!S)oe()},children:[e("h2",{id:I,className:"visually-hidden",children:t("Character of {name}",{name:a.displayName})}),i("div",{className:"creator-preview",children:[e(ae,{spec:l,name:a.name,state:T,size:176,label:t("{name}, {state}",{name:a.displayName,state:t(x[T])})}),e("button",{type:"button",className:"secondary","data-action":"cycle",onClick:()=>re((n)=>rt[(rt.indexOf(n)+1)%rt.length]??"idle"),children:t("Preview states")}),e("span",{className:"meta","aria-live":"polite",children:t(x[T])})]}),i("div",{className:"creator-body",children:[u["*"]?e("p",{className:"field-error",role:"alert","data-field":"*",children:u["*"]}):null,O?e("ul",{className:"creator-sections",children:D.map((n)=>e("li",{children:i("button",{type:"button",className:"creator-section secondary",onClick:()=>M(n.id),children:[e("strong",{children:n.title}),e("span",{className:"meta",children:n.summary}),n.error?e("span",{className:"field-error",role:"alert",children:n.error}):null]})},n.id))}):i(m,{children:[_,j,G,J]}),i("div",{className:"actions creator-actions",children:[e("button",{type:"button",className:"secondary","data-action":"suggest",onClick:()=>{g({}),h(jt({name:a.name,displayName:a.displayName,prompt:a.prompt}))},children:t("Suggest from role")}),e("button",{type:"button",className:"secondary","data-action":"randomize",onClick:()=>C(fe()),children:t("Randomize")}),e("button",{type:"button",className:"secondary","data-action":"cancel",onClick:()=>{if(w)E({kind:"cancel"})},children:t("Cancel")}),e("button",{type:"submit","data-action":"save",disabled:!w||S,children:S?t("Saving…"):t("Save")})]})]}),e(W,{open:O&&R!==void 0,title:D.find((n)=>n.id===R)?.title??"",onClose:()=>M(void 0),children:e("div",{className:"stack",children:D.find((n)=>n.id===R)?.node})}),e(ge,{open:K!==void 0,title:t("Discard your changes?"),confirmLabel:t("Discard"),cancelLabel:t("Keep editing"),tone:"danger",onClose:()=>E(void 0),onConfirm:se,children:e("p",{children:t("The new look for {name} has not been saved.",{name:a.displayName})})})]})}var Y=(a)=>t(ue[a]);function be(a){let l=a.base.colors,h=q(()=>Lt({class:a.id,colors:l}),[a.id,l]);return e(te,{data:{"data-class":a.id},spec:h,size:56,label:N("class",a.id),hint:V(a.id),pressed:a.pressed,onPick:a.onPick})}export{He as CharacterCreator};