@cychien/ezdesign 0.0.0-stage → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/shell.js ADDED
@@ -0,0 +1,4644 @@
1
+ "use strict";
2
+ (() => {
3
+ // node_modules/.pnpm/@hugeicons+core-free-icons@4.2.3/node_modules/@hugeicons/core-free-icons/dist/esm/Cancel01Icon.js
4
+ var Cancel01Icon = [
5
+ ["path", { d: "M18 6L6.00081 17.9992M17.9992 18L6 6.00085", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "1.5", key: "0" }]
6
+ ];
7
+ var Cancel01Icon_default = Cancel01Icon;
8
+
9
+ // node_modules/.pnpm/@hugeicons+core-free-icons@4.2.3/node_modules/@hugeicons/core-free-icons/dist/esm/AlignSelectionIcon.js
10
+ var AlignSelectionIcon = [
11
+ ["path", { d: "M20 6V18M18 4H6M18 20H6M4 18V6", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "1.5", key: "0" }],
12
+ ["path", { d: "M22 4C22 5.10457 21.1046 6 20 6C18.8954 6 18 5.10457 18 4C18 2.89543 18.8954 2 20 2C21.1046 2 22 2.89543 22 4Z", stroke: "currentColor", strokeWidth: "1.5", key: "1" }],
13
+ ["path", { d: "M6 4C6 5.10457 5.10457 6 4 6C2.89543 6 2 5.10457 2 4C2 2.89543 2.89543 2 4 2C5.10457 2 6 2.89543 6 4Z", stroke: "currentColor", strokeWidth: "1.5", key: "2" }],
14
+ ["path", { d: "M22 20C22 21.1046 21.1046 22 20 22C18.8954 22 18 21.1046 18 20C18 18.8954 18.8954 18 20 18C21.1046 18 22 18.8954 22 20Z", stroke: "currentColor", strokeWidth: "1.5", key: "3" }],
15
+ ["path", { d: "M6 20C6 21.1046 5.10457 22 4 22C2.89543 22 2 21.1046 2 20C2 18.8954 2.89543 18 4 18C5.10457 18 6 18.8954 6 20Z", stroke: "currentColor", strokeWidth: "1.5", key: "4" }]
16
+ ];
17
+ var AlignSelectionIcon_default = AlignSelectionIcon;
18
+
19
+ // node_modules/.pnpm/@hugeicons+core-free-icons@4.2.3/node_modules/@hugeicons/core-free-icons/dist/esm/ChevronDownIcon.js
20
+ var ChevronDownIcon = [
21
+ ["path", { d: "M18 9.00005C18 9.00005 13.5811 15 12 15C10.4188 15 6 9 6 9", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "1.5", key: "0" }]
22
+ ];
23
+ var ChevronDownIcon_default = ChevronDownIcon;
24
+
25
+ // node_modules/.pnpm/@hugeicons+core-free-icons@4.2.3/node_modules/@hugeicons/core-free-icons/dist/esm/ChevronRightIcon.js
26
+ var ChevronRightIcon = [
27
+ ["path", { d: "M9.00005 18C9.00005 18 15 13.5811 15 12C15 10.4188 9 6 9 6", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "1.5", key: "0" }]
28
+ ];
29
+ var ChevronRightIcon_default = ChevronRightIcon;
30
+
31
+ // node_modules/.pnpm/@hugeicons+core-free-icons@4.2.3/node_modules/@hugeicons/core-free-icons/dist/esm/MagicWand01Icon.js
32
+ var MagicWand01Icon = [
33
+ ["path", { d: "M13.9258 12.7775L11.7775 10.6292C11.4847 10.3364 11.3383 10.19 11.1803 10.1117C10.8798 9.96277 10.527 9.96277 10.2264 10.1117C10.0685 10.19 9.92207 10.3364 9.62923 10.6292C9.33638 10.9221 9.18996 11.0685 9.11169 11.2264C8.96277 11.527 8.96277 11.8798 9.11169 12.1803C9.18996 12.3383 9.33638 12.4847 9.62923 12.7775L11.7775 14.9258M13.9258 12.7775L20.3708 19.2225C20.6636 19.5153 20.81 19.6617 20.8883 19.8197C21.0372 20.1202 21.0372 20.473 20.8883 20.7736C20.81 20.9315 20.6636 21.0779 20.3708 21.3708C20.0779 21.6636 19.9315 21.81 19.7736 21.8883C19.473 22.0372 19.1202 22.0372 18.8197 21.8883C18.6617 21.81 18.5153 21.6636 18.2225 21.3708L11.7775 14.9258M13.9258 12.7775L11.7775 14.9258", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "1.5", key: "0" }],
34
+ ["path", { d: "M17 2L17.2948 2.7966C17.6813 3.84117 17.8746 4.36345 18.2556 4.74445C18.6366 5.12545 19.1588 5.31871 20.2034 5.70523L21 6L20.2034 6.29477C19.1588 6.68129 18.6366 6.87456 18.2556 7.25555C17.8746 7.63655 17.6813 8.15883 17.2948 9.2034L17 10L16.7052 9.2034C16.3187 8.15884 16.1254 7.63655 15.7444 7.25555C15.3634 6.87455 14.8412 6.68129 13.7966 6.29477L13 6L13.7966 5.70523C14.8412 5.31871 15.3634 5.12545 15.7444 4.74445C16.1254 4.36345 16.3187 3.84117 16.7052 2.7966L17 2Z", stroke: "currentColor", strokeLinejoin: "round", strokeWidth: "1.5", key: "1" }],
35
+ ["path", { d: "M6 4L6.22108 4.59745C6.51097 5.38087 6.65592 5.77259 6.94167 6.05834C7.22741 6.34408 7.61913 6.48903 8.40255 6.77892L9 7L8.40255 7.22108C7.61913 7.51097 7.22741 7.65592 6.94166 7.94167C6.65592 8.22741 6.51097 8.61913 6.22108 9.40255L6 10L5.77892 9.40255C5.48903 8.61913 5.34408 8.22741 5.05833 7.94167C4.77259 7.65592 4.38087 7.51097 3.59745 7.22108L3 7L3.59745 6.77892C4.38087 6.48903 4.77259 6.34408 5.05833 6.05833C5.34408 5.77259 5.48903 5.38087 5.77892 4.59745L6 4Z", stroke: "currentColor", strokeLinejoin: "round", strokeWidth: "1.5", key: "2" }]
36
+ ];
37
+ var MagicWand01Icon_default = MagicWand01Icon;
38
+
39
+ // node_modules/.pnpm/@hugeicons+core-free-icons@4.2.3/node_modules/@hugeicons/core-free-icons/dist/esm/SparklesIcon.js
40
+ var SparklesIcon = [
41
+ ["path", { d: "M15 2L15.5387 4.39157C15.9957 6.42015 17.5798 8.00431 19.6084 8.46127L22 9L19.6084 9.53873C17.5798 9.99569 15.9957 11.5798 15.5387 13.6084L15 16L14.4613 13.6084C14.0043 11.5798 12.4202 9.99569 10.3916 9.53873L8 9L10.3916 8.46127C12.4201 8.00431 14.0043 6.42015 14.4613 4.39158L15 2Z", stroke: "currentColor", strokeLinejoin: "round", strokeWidth: "1.5", key: "0" }],
42
+ ["path", { d: "M7 12L7.38481 13.7083C7.71121 15.1572 8.84275 16.2888 10.2917 16.6152L12 17L10.2917 17.3848C8.84275 17.7112 7.71121 18.8427 7.38481 20.2917L7 22L6.61519 20.2917C6.28879 18.8427 5.15725 17.7112 3.70827 17.3848L2 17L3.70827 16.6152C5.15725 16.2888 6.28879 15.1573 6.61519 13.7083L7 12Z", stroke: "currentColor", strokeLinejoin: "round", strokeWidth: "1.5", key: "1" }]
43
+ ];
44
+ var SparklesIcon_default = SparklesIcon;
45
+
46
+ // node_modules/.pnpm/@hugeicons+core-free-icons@4.2.3/node_modules/@hugeicons/core-free-icons/dist/esm/ArrowRight02Icon.js
47
+ var ArrowRight02Icon = [
48
+ ["path", { d: "M18.5 12L4.99997 12", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "1.5", key: "0" }],
49
+ ["path", { d: "M13 18C13 18 19 13.5811 19 12C19 10.4188 13 6 13 6", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "1.5", key: "1" }]
50
+ ];
51
+ var ArrowRight02Icon_default = ArrowRight02Icon;
52
+
53
+ // node_modules/.pnpm/@hugeicons+core-free-icons@4.2.3/node_modules/@hugeicons/core-free-icons/dist/esm/Grid02Icon.js
54
+ var Grid02Icon = [
55
+ ["path", { d: "M6 4V20", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "1.5", key: "0" }],
56
+ ["path", { d: "M18 4V20", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "1.5", key: "1" }],
57
+ ["path", { d: "M21 7L3 7", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "1.5", key: "2" }],
58
+ ["path", { d: "M21 17L3 17", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "1.5", key: "3" }]
59
+ ];
60
+ var Grid02Icon_default = Grid02Icon;
61
+
62
+ // node_modules/.pnpm/@hugeicons+core-free-icons@4.2.3/node_modules/@hugeicons/core-free-icons/dist/esm/ChatGptIcon.js
63
+ var ChatGptIcon = [
64
+ ["path", { d: "M11.7453 14.85L6.90436 12V7C6.90436 4.79086 8.72949 3 10.9809 3C12.3782 3 13.6113 3.6898 14.3458 4.74128", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "1.5", key: "0" }],
65
+ ["path", { d: "M9.59961 19.1791C10.3266 20.2757 11.5866 21.0008 13.0192 21.0008C15.2707 21.0008 17.0958 19.21 17.0958 17.0008V12.0008L12.1612 9.0957", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "1.5", key: "1" }],
66
+ ["path", { d: "M9.45166 13.5L9.45123 7.66938L13.8642 5.16938C15.814 4.06481 18.3072 4.72031 19.4329 6.63348C20.1593 7.86806 20.1388 9.32466 19.5089 10.4995", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "1.5", key: "2" }],
67
+ ["path", { d: "M4.48963 13.4993C3.8595 14.6742 3.83887 16.131 4.56539 17.3657C5.6911 19.2789 8.18428 19.9344 10.1341 18.8298L14.5471 16.3298L14.643 10.7344", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "1.5", key: "3" }],
68
+ ["path", { d: "M17.0959 17.6309C18.4415 17.5734 19.7295 16.8634 20.4529 15.634C21.5786 13.7209 20.9106 11.2745 18.9608 10.1699L14.5478 7.66992L9.48907 10.4255", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "1.5", key: "4" }],
69
+ ["path", { d: "M6.90454 6.36938C5.55865 6.42662 4.27032 7.13672 3.54684 8.3663C2.42113 10.2795 3.08917 12.7258 5.03896 13.8304L9.45196 16.3304L14.5 13.5807", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "1.5", key: "5" }]
70
+ ];
71
+ var ChatGptIcon_default = ChatGptIcon;
72
+
73
+ // node_modules/.pnpm/@hugeicons+core-free-icons@4.2.3/node_modules/@hugeicons/core-free-icons/dist/esm/CheckIcon.js
74
+ var CheckIcon = [
75
+ ["path", { d: "M5 13.2592L7.58583 15.9568C8.2525 16.6523 8.58583 17.0001 9.00004 17.0001C9.41425 17.0001 9.74759 16.6523 10.4143 15.9568L19 7.00006", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "1.5", key: "0" }]
76
+ ];
77
+ var CheckIcon_default = CheckIcon;
78
+
79
+ // node_modules/.pnpm/@hugeicons+core-free-icons@4.2.3/node_modules/@hugeicons/core-free-icons/dist/esm/ClaudeIcon.js
80
+ var ClaudeIcon = [
81
+ ["path", { d: "M13 12L18.5 5M7.63965 3L12.5 12L13.6865 3M4.48381 6.71679L11.9872 12M3 12L11.9872 12.473M12.2244 13.177L7 20M4.84194 16.8682L11.2824 12.9758M11.5 21L12.665 13.177M21 14L13.1846 12.668M21 10.5788L13 12.3223M16.779 19.646L12.8876 13.3772M19.3566 18.207L13.313 12.9893", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "1.5", key: "0" }]
82
+ ];
83
+ var ClaudeIcon_default = ClaudeIcon;
84
+
85
+ // node_modules/.pnpm/@hugeicons+core-free-icons@4.2.3/node_modules/@hugeicons/core-free-icons/dist/esm/CursorMagicSelection02Icon.js
86
+ var CursorMagicSelection02Icon = [
87
+ ["path", { d: "M6.99069 4.56404C5.14721 3.92359 4.06068 3.76816 3.41442 4.41442C2.49121 5.33764 3.20405 7.15934 4.62973 10.8028L6.99069 16.8363C8.35243 20.3163 9.03329 22.0563 10.1133 21.9986C11.1934 21.9409 11.6886 20.125 12.6791 16.4933C12.974 15.4119 13.1215 14.8712 13.4963 14.4963C13.8712 14.1215 14.4119 13.974 15.4933 13.6791C19.125 12.6886 20.9409 12.1934 20.9986 11.1133C21.0428 10.287 20.0346 9.69434 18 8.85164", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "1.5", key: "0" }],
88
+ ["path", { d: "M11.4999 4C12.1 5.62152 13.3784 6.89998 14.9999 7.5C13.3784 8.10002 12.1 9.37848 11.4999 11C10.8999 9.37848 9.62146 8.10002 7.99994 7.5C9.62146 6.89998 10.8999 5.62152 11.4999 4Z", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "1.5", key: "1" }],
89
+ ["path", { d: "M15.7499 2C15.9642 2.57911 16.4208 3.03571 16.9999 3.25C16.4208 3.46429 15.9642 3.92089 15.7499 4.5C15.5356 3.92089 15.0791 3.46429 14.4999 3.25C15.0791 3.03571 15.5356 2.57911 15.7499 2Z", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "1.5", key: "2" }]
90
+ ];
91
+ var CursorMagicSelection02Icon_default = CursorMagicSelection02Icon;
92
+
93
+ // node_modules/.pnpm/@hugeicons+core-free-icons@4.2.3/node_modules/@hugeicons/core-free-icons/dist/esm/File02Icon.js
94
+ var File02Icon = [
95
+ ["path", { d: "M8 17H16", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "1.5", key: "0" }],
96
+ ["path", { d: "M8 13H12", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "1.5", key: "1" }],
97
+ ["path", { d: "M13 2.5V3C13 5.82843 13 7.24264 13.8787 8.12132C14.7574 9 16.1716 9 19 9H19.5M20 10.6569V14C20 17.7712 20 19.6569 18.8284 20.8284C17.6569 22 15.7712 22 12 22C8.22876 22 6.34315 22 5.17157 20.8284C4 19.6569 4 17.7712 4 14V9.45584C4 6.21082 4 4.58831 4.88607 3.48933C5.06508 3.26731 5.26731 3.06508 5.48933 2.88607C6.58831 2 8.21082 2 11.4558 2C12.1614 2 12.5141 2 12.8372 2.11401C12.9044 2.13772 12.9702 2.165 13.0345 2.19575C13.3436 2.34355 13.593 2.593 14.0919 3.09188L18.8284 7.82843C19.4065 8.40649 19.6955 8.69552 19.8478 9.06306C20 9.4306 20 9.83935 20 10.6569Z", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "1.5", key: "2" }]
98
+ ];
99
+ var File02Icon_default = File02Icon;
100
+
101
+ // node_modules/.pnpm/@hugeicons+core-free-icons@4.2.3/node_modules/@hugeicons/core-free-icons/dist/esm/KeyboardIcon.js
102
+ var KeyboardIcon = [
103
+ ["path", { d: "M14.5 7H9.5C6.21252 7 4.56878 7 3.46243 7.90796C3.25989 8.07418 3.07418 8.25989 2.90796 8.46243C2 9.56878 2 11.2125 2 14.5C2 17.7875 2 19.4312 2.90796 20.5376C3.07418 20.7401 3.25989 20.9258 3.46243 21.092C4.56878 22 6.21252 22 9.5 22H14.5C17.7875 22 19.4312 22 20.5376 21.092C20.7401 20.9258 20.9258 20.7401 21.092 20.5376C22 19.4312 22 17.7875 22 14.5C22 11.2125 22 9.56878 21.092 8.46243C20.9258 8.25989 20.7401 8.07418 20.5376 7.90796C19.4312 7 17.7875 7 14.5 7Z", stroke: "currentColor", strokeLinecap: "round", strokeWidth: "1.5", key: "0" }],
104
+ ["path", { d: "M12 7V5C12 4.44772 12.4477 4 13 4C13.5523 4 14 3.55228 14 3V2", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "1.5", key: "1" }],
105
+ ["path", { d: "M7 12L8 12", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "1.5", key: "2" }],
106
+ ["path", { d: "M11.5 12L12.5 12", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "1.5", key: "3" }],
107
+ ["path", { d: "M16 12L17 12", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "1.5", key: "4" }],
108
+ ["path", { d: "M7 17L17 17", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "1.5", key: "5" }]
109
+ ];
110
+ var KeyboardIcon_default = KeyboardIcon;
111
+
112
+ // node_modules/.pnpm/@hugeicons+core-free-icons@4.2.3/node_modules/@hugeicons/core-free-icons/dist/esm/Select01Icon.js
113
+ var Select01Icon = [
114
+ ["path", { d: "M5 2V8M2 5H8", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "1.5", key: "0" }],
115
+ ["path", { d: "M12 5H15M12 22H15M18 5H18.5C20.433 5 22 6.567 22 8.5V9M22 18V18.5C22 20.433 20.433 22 18.5 22H18M9 22H8.5C6.567 22 5 20.433 5 18.5L5 18M22 12V15M5 12L5 15", stroke: "currentColor", strokeLinecap: "round", strokeWidth: "1.5", key: "1" }]
116
+ ];
117
+ var Select01Icon_default = Select01Icon;
118
+
119
+ // node_modules/.pnpm/@hugeicons+core-free-icons@4.2.3/node_modules/@hugeicons/core-free-icons/dist/esm/Navigation03Icon.js
120
+ var Navigation03Icon = [
121
+ ["path", { d: "M11.922 4.79004C16.6963 3.16245 19.0834 2.34866 20.3674 3.63261C21.6513 4.91656 20.8375 7.30371 19.21 12.078L18.1016 15.3292C16.8517 18.9958 16.2267 20.8291 15.1964 20.9808C14.9195 21.0216 14.6328 20.9971 14.3587 20.9091C13.3395 20.5819 12.8007 18.6489 11.7231 14.783C11.4841 13.9255 11.3646 13.4967 11.0924 13.1692C11.0134 13.0742 10.9258 12.9866 10.8308 12.9076C10.5033 12.6354 10.0745 12.5159 9.21705 12.2769C5.35111 11.1993 3.41814 10.6605 3.0909 9.64127C3.00292 9.36724 2.97837 9.08053 3.01916 8.80355C3.17088 7.77332 5.00419 7.14834 8.6708 5.89838L11.922 4.79004Z", stroke: "currentColor", strokeWidth: "1.5", key: "0" }]
122
+ ];
123
+ var Navigation03Icon_default = Navigation03Icon;
124
+
125
+ // node_modules/.pnpm/@hugeicons+core-free-icons@4.2.3/node_modules/@hugeicons/core-free-icons/dist/esm/BubbleChatAddIcon.js
126
+ var BubbleChatAddIcon = [
127
+ ["path", { d: "M21.5 12C21.5 17.2467 17.2467 21.5 12 21.5C10.3719 21.5 8.8394 21.0904 7.5 20.3687C5.63177 19.362 4.37462 20.2979 3.26592 20.4658C3.09774 20.4913 2.93024 20.4302 2.80997 20.31C2.62741 20.1274 2.59266 19.8451 2.6935 19.6074C3.12865 18.5818 3.5282 16.6382 2.98341 15C2.6698 14.057 2.5 13.0483 2.5 12C2.5 6.75329 6.75329 2.5 12 2.5C17.2467 2.5 21.5 6.75329 21.5 12Z", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "1.5", key: "0" }],
128
+ ["path", { d: "M15.5 12H8.5M12 8.5V15.5", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "1.5", key: "1" }]
129
+ ];
130
+ var BubbleChatAddIcon_default = BubbleChatAddIcon;
131
+
132
+ // node_modules/.pnpm/@hugeicons+core-free-icons@4.2.3/node_modules/@hugeicons/core-free-icons/dist/esm/BubbleChatOutcomeIcon.js
133
+ var BubbleChatOutcomeIcon = [
134
+ ["path", { d: "M21.5 12C21.5 17.2467 17.2467 21.5 12 21.5C10.3719 21.5 8.8394 21.0904 7.5 20.3687C5.63177 19.362 4.37462 20.2979 3.26592 20.4658C3.09774 20.4913 2.93024 20.4302 2.80997 20.31C2.62741 20.1274 2.59266 19.8451 2.6935 19.6074C3.12865 18.5818 3.5282 16.6382 2.98341 15C2.6698 14.057 2.5 13.0483 2.5 12C2.5 6.75329 6.75329 2.5 12 2.5C17.2467 2.5 21.5 6.75329 21.5 12Z", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "1.5", key: "0" }],
135
+ ["path", { d: "M12.5 9C12.5 9 15.5 11.2095 15.5 12C15.5 12.7906 12.5 15 12.5 15M15 12L8.5 12", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "1.5", key: "1" }]
136
+ ];
137
+ var BubbleChatOutcomeIcon_default = BubbleChatOutcomeIcon;
138
+
139
+ // node_modules/.pnpm/@hugeicons+core-free-icons@4.2.3/node_modules/@hugeicons/core-free-icons/dist/esm/Edit02Icon.js
140
+ var Edit02Icon = [
141
+ ["path", { d: "M14.0737 3.88545C14.8189 3.07808 15.1915 2.6744 15.5874 2.43893C16.5427 1.87076 17.7191 1.85309 18.6904 2.39232C19.0929 2.6158 19.4769 3.00812 20.245 3.79276C21.0131 4.5774 21.3972 4.96972 21.6159 5.38093C22.1438 6.37312 22.1265 7.57479 21.5703 8.5507C21.3398 8.95516 20.9446 9.33578 20.1543 10.097L10.7506 19.1543C9.25288 20.5969 8.504 21.3182 7.56806 21.6837C6.63212 22.0493 5.6032 22.0224 3.54536 21.9686L3.26538 21.9613C2.63891 21.9449 2.32567 21.9367 2.14359 21.73C1.9615 21.5234 1.98636 21.2043 2.03608 20.5662L2.06308 20.2197C2.20301 18.4235 2.27297 17.5255 2.62371 16.7182C2.97444 15.9109 3.57944 15.2555 4.78943 13.9445L14.0737 3.88545Z", stroke: "currentColor", strokeLinejoin: "round", strokeWidth: "1.5", key: "0" }],
142
+ ["path", { d: "M13 4L20 11", stroke: "currentColor", strokeLinejoin: "round", strokeWidth: "1.5", key: "1" }],
143
+ ["path", { d: "M14 22L22 22", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "1.5", key: "2" }]
144
+ ];
145
+ var Edit02Icon_default = Edit02Icon;
146
+
147
+ // src/acp-ask.ts
148
+ function answeredField(field, answered) {
149
+ return answered.has(field.key) || (field.kind === "select" || field.kind === "multiselect") && !!field.custom && answered.has(field.custom.key);
150
+ }
151
+ function askComplete(fields, answered) {
152
+ return answered.size > 0 && fields.every((f) => f.optional || answeredField(f, answered));
153
+ }
154
+ function askSendsOnPick(fields, typedCustom) {
155
+ return !typedCustom && fields.length === 1 && fields[0]?.kind === "select";
156
+ }
157
+
158
+ // src/agents.ts
159
+ var AGENT_PROFILES = [
160
+ {
161
+ id: "claude",
162
+ name: "Claude Code",
163
+ command: "npx -y @agentclientprotocol/claude-agent-acp",
164
+ binary: "claude",
165
+ brand: "claude"
166
+ },
167
+ {
168
+ id: "codex",
169
+ name: "Codex",
170
+ command: "npx -y @agentclientprotocol/codex-acp",
171
+ binary: "codex",
172
+ brand: "openai"
173
+ },
174
+ { id: "gemini", name: "Gemini CLI", command: "gemini --experimental-acp", binary: "gemini" }
175
+ ];
176
+ function agentProfileFor(command) {
177
+ return AGENT_PROFILES.find((p) => p.command === command);
178
+ }
179
+ function agentBrandFor(command) {
180
+ return agentProfileFor(command)?.brand;
181
+ }
182
+
183
+ // src/acp-config.ts
184
+ function configLabel(option) {
185
+ switch (option.category) {
186
+ case "model":
187
+ return "\u6A21\u578B";
188
+ case "thought_level":
189
+ return "\u601D\u8003\u5F37\u5EA6";
190
+ case "mode":
191
+ return "\u6A21\u5F0F";
192
+ default:
193
+ return option.name;
194
+ }
195
+ }
196
+ function configValues(option) {
197
+ if (option.type !== "select") return [];
198
+ return option.options.flatMap((o) => "options" in o ? o.options : [o]);
199
+ }
200
+ function configValueName(option, value) {
201
+ if (typeof value === "boolean") return value ? "\u958B\u555F" : "\u95DC\u9589";
202
+ return configValues(option).find((o) => o.value === value)?.name ?? value;
203
+ }
204
+ function shortConfigValueName(name) {
205
+ const paren = name.indexOf(" (");
206
+ return paren > 0 ? name.slice(0, paren) : name;
207
+ }
208
+
209
+ // src/client/draft.ts
210
+ var NBSP = "\xA0";
211
+ var refMarker = (n) => `[ref ${n}]`;
212
+ var refChipText = (n, chosen = false) => `${chosen ? "\u63A2\u7D22\u6A23\u5F0F" : "\u9078\u53D6\u5143\u7D20"} ${n}`;
213
+ var fileMarker = (n) => `[file ${n}]`;
214
+ var skillMarker = (n) => `[skill ${n}]`;
215
+ var MARKER = /\[(ref|file|skill) (\d+)\]/g;
216
+ function splitComment(text) {
217
+ const out = [];
218
+ let at2 = 0;
219
+ for (const match of text.matchAll(MARKER)) {
220
+ if (match.index > at2) out.push({ t: "text", v: text.slice(at2, match.index) });
221
+ out.push({ t: match[1], n: Number(match[2]) });
222
+ at2 = match.index + match[0].length;
223
+ }
224
+ if (at2 < text.length) out.push({ t: "text", v: text.slice(at2) });
225
+ return out;
226
+ }
227
+ function draftText(body) {
228
+ const out = [];
229
+ let file = 0;
230
+ let skill = 0;
231
+ for (const node of body) {
232
+ if (node.t === "text") out.push(node.v);
233
+ else if (node.t === "ref" && node.anchor) out.push(refMarker(node.n));
234
+ else if (node.t === "file" && node.id) out.push(fileMarker(++file));
235
+ else if (node.t === "skill") out.push(skillMarker(++skill));
236
+ }
237
+ return out.join("").replaceAll(NBSP, " ").trim();
238
+ }
239
+ function bodyFromComment(text, refs, files, skills = []) {
240
+ const byNumber = new Map(refs.map((r) => [r.n, r]));
241
+ const placedRefs = /* @__PURE__ */ new Set();
242
+ const placedFiles = /* @__PURE__ */ new Set();
243
+ const body = [];
244
+ for (const part of splitComment(text)) {
245
+ if (part.t === "text") {
246
+ body.push({ t: "text", v: part.v });
247
+ continue;
248
+ }
249
+ if (part.t === "ref") {
250
+ const ref = byNumber.get(part.n);
251
+ if (!ref) {
252
+ body.push({ t: "text", v: refMarker(part.n) });
253
+ continue;
254
+ }
255
+ placedRefs.add(part.n);
256
+ body.push({ t: "ref", n: ref.n, anchor: ref.anchor, label: ref.label });
257
+ continue;
258
+ }
259
+ if (part.t === "skill") {
260
+ const name = skills[part.n - 1];
261
+ body.push(name ? { t: "skill", name } : { t: "text", v: skillMarker(part.n) });
262
+ continue;
263
+ }
264
+ const file = files[part.n - 1];
265
+ if (!file) {
266
+ body.push({ t: "text", v: fileMarker(part.n) });
267
+ continue;
268
+ }
269
+ placedFiles.add(part.n);
270
+ body.push({ t: "file", id: file.id, name: file.name });
271
+ }
272
+ const trailing = [];
273
+ for (const ref of refs) {
274
+ if (!placedRefs.has(ref.n)) trailing.push({ t: "ref", ...ref });
275
+ }
276
+ files.forEach((file, i) => {
277
+ if (!placedFiles.has(i + 1)) trailing.push({ t: "file", id: file.id, name: file.name });
278
+ });
279
+ for (const node of trailing) body.push({ t: "text", v: NBSP }, node);
280
+ body.push({ t: "text", v: NBSP });
281
+ return normalizeDraft(body);
282
+ }
283
+ function draftSkills(body) {
284
+ return body.flatMap((node) => node.t === "skill" ? [node.name] : []);
285
+ }
286
+ function draftRefs(body) {
287
+ const out = [];
288
+ for (const node of body) {
289
+ if (node.t === "ref" && node.anchor) {
290
+ out.push({
291
+ n: node.n,
292
+ anchor: node.anchor,
293
+ label: node.label,
294
+ ...node.variant ? { variant: node.variant } : {}
295
+ });
296
+ }
297
+ }
298
+ return out;
299
+ }
300
+ function nextRefNumber(body) {
301
+ let max = 0;
302
+ for (const node of body) {
303
+ if (node.t === "ref" && node.anchor && node.n > max) max = node.n;
304
+ }
305
+ return max + 1;
306
+ }
307
+ function draftFileIds(body) {
308
+ return body.flatMap((n) => n.t === "file" && n.id ? [n.id] : []);
309
+ }
310
+ function draftPendingNames(body) {
311
+ return body.flatMap((n) => n.t === "file" && !n.id ? [n.name] : []);
312
+ }
313
+ function hasPendingRef(body) {
314
+ return body.some((n) => n.t === "ref" && !n.anchor);
315
+ }
316
+ function resolveDraftRef(body, ref) {
317
+ const n = nextRefNumber(body);
318
+ let done3 = false;
319
+ return body.map((node) => {
320
+ if (done3 || node.t !== "ref" || node.anchor) return node;
321
+ done3 = true;
322
+ return { t: "ref", n, anchor: ref.anchor, label: ref.label };
323
+ });
324
+ }
325
+ function dropDraftRef(body) {
326
+ let done3 = false;
327
+ return normalizeDraft(
328
+ body.filter((node) => {
329
+ if (done3 || node.t !== "ref" || node.anchor) return true;
330
+ done3 = true;
331
+ return false;
332
+ })
333
+ );
334
+ }
335
+ function restorableBody(body) {
336
+ return normalizeDraft(body.filter((n) => !(n.t === "file" && !n.id)));
337
+ }
338
+ function normalizeDraft(body) {
339
+ const out = [];
340
+ for (const node of body) {
341
+ if (node.t !== "text") {
342
+ out.push(node);
343
+ continue;
344
+ }
345
+ if (!node.v) continue;
346
+ const last = out[out.length - 1];
347
+ if (last?.t === "text") out[out.length - 1] = { t: "text", v: last.v + node.v };
348
+ else out.push(node);
349
+ }
350
+ return out;
351
+ }
352
+ function draftBelongsHere(draft, page) {
353
+ return draft.subject?.page === page;
354
+ }
355
+ var GRACE_MS = 24 * 60 * 6e4;
356
+
357
+ // src/client/icon.ts
358
+ var SVG_NS = "http://www.w3.org/2000/svg";
359
+ var kebab = (key) => key.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);
360
+ function icon(node, size = 16) {
361
+ const svg = document.createElementNS(SVG_NS, "svg");
362
+ svg.setAttribute("viewBox", "0 0 24 24");
363
+ svg.setAttribute("width", String(size));
364
+ svg.setAttribute("height", String(size));
365
+ svg.setAttribute("fill", "none");
366
+ svg.setAttribute("aria-hidden", "true");
367
+ svg.setAttribute("data-ez-ui", "");
368
+ for (const [tag, attrs] of node) {
369
+ const child = document.createElementNS(SVG_NS, tag);
370
+ for (const [key, value] of Object.entries(attrs)) {
371
+ if (key === "key") continue;
372
+ child.setAttribute(kebab(key), String(value));
373
+ }
374
+ svg.appendChild(child);
375
+ }
376
+ return svg;
377
+ }
378
+
379
+ // src/client/slash.ts
380
+ var MAX_QUERY = 24;
381
+ var BREAK = /[\s ]/;
382
+ function detectSlash(before, trigger = "/") {
383
+ const start = before.lastIndexOf(trigger);
384
+ if (start === -1) return null;
385
+ const prev = start === 0 ? "" : before[start - 1];
386
+ if (prev && !BREAK.test(prev)) return null;
387
+ const query = before.slice(start + 1);
388
+ if (query.length > MAX_QUERY || BREAK.test(query)) return null;
389
+ return { start, query };
390
+ }
391
+ function filterCommands(commands, query) {
392
+ const available = commands.filter((c) => c.enabled?.() ?? true);
393
+ const q = query.trim().toLowerCase();
394
+ if (!q) return available;
395
+ return available.filter(
396
+ (c) => [c.id, c.label, ...c.keywords].some((term) => term.toLowerCase().includes(q))
397
+ );
398
+ }
399
+ function attachSlashMenu(editable, {
400
+ mk,
401
+ commands,
402
+ triggerChar = "/",
403
+ title = "\u6307\u4EE4",
404
+ load
405
+ }) {
406
+ const doc = editable.ownerDocument;
407
+ const view = doc.defaultView;
408
+ let menu = null;
409
+ let trigger = null;
410
+ let shown = [];
411
+ let active = 0;
412
+ let loaded = null;
413
+ let loading = false;
414
+ function readTrigger() {
415
+ const selection = doc.getSelection();
416
+ if (!selection?.isCollapsed || selection.rangeCount === 0) return null;
417
+ const range = selection.getRangeAt(0);
418
+ const node = range.startContainer;
419
+ if (!editable.contains(node) || node.nodeType !== Node.TEXT_NODE) return null;
420
+ const text = node;
421
+ const hit = detectSlash((text.nodeValue ?? "").slice(0, range.startOffset), triggerChar);
422
+ return hit ? { node: text, start: hit.start, query: hit.query } : null;
423
+ }
424
+ function slashRect() {
425
+ if (!trigger?.node.isConnected) return null;
426
+ const range = doc.createRange();
427
+ range.setStart(trigger.node, trigger.start);
428
+ range.setEnd(trigger.node, Math.min(trigger.node.length, trigger.start + 1));
429
+ return range.getBoundingClientRect();
430
+ }
431
+ function place() {
432
+ if (!menu || !view) return;
433
+ const at2 = slashRect();
434
+ if (!at2) return;
435
+ const box = menu.getBoundingClientRect();
436
+ const left = Math.max(8, Math.min(at2.left, view.innerWidth - box.width - 8));
437
+ const below = at2.bottom + 6;
438
+ const above = at2.top - box.height - 6;
439
+ const crowded = below + box.height > view.innerHeight - 8 || at2.top > view.innerHeight * 0.6;
440
+ const top = crowded && above >= 8 ? above : Math.min(below, view.innerHeight - box.height - 8);
441
+ menu.style.left = `${left}px`;
442
+ menu.style.top = `${top}px`;
443
+ }
444
+ function paintActive() {
445
+ const list = menu?.querySelector(".ez-slash-list");
446
+ if (!list) return;
447
+ [...list.children].forEach((item, i) => {
448
+ item.toggleAttribute("data-active", i === active);
449
+ item.setAttribute("aria-selected", String(i === active));
450
+ });
451
+ }
452
+ function render2() {
453
+ if (!menu) return;
454
+ menu.textContent = "";
455
+ const headEl = mk("div", "ez-slash-head");
456
+ headEl.textContent = title;
457
+ const list = mk("div", "ez-slash-list");
458
+ list.setAttribute("role", "listbox");
459
+ menu.append(headEl, list);
460
+ shown.forEach((command, i) => {
461
+ const item = mk("div", "ez-slash-item");
462
+ item.setAttribute("role", "option");
463
+ const label = mk("span", "ez-slash-label");
464
+ label.textContent = command.label;
465
+ if (command.icon) item.append(icon(command.icon, 16));
466
+ item.append(label);
467
+ const hintText = typeof command.hint === "function" ? command.hint() : command.hint;
468
+ if (hintText) {
469
+ const hint = mk("span", "ez-slash-hint");
470
+ hint.textContent = hintText;
471
+ item.append(hint);
472
+ }
473
+ item.addEventListener("mousedown", (e) => {
474
+ e.preventDefault();
475
+ choose(command);
476
+ });
477
+ item.addEventListener("mousemove", () => {
478
+ if (active === i) return;
479
+ active = i;
480
+ paintActive();
481
+ });
482
+ list.appendChild(item);
483
+ });
484
+ paintActive();
485
+ }
486
+ function onSelectionChange() {
487
+ if (!readTrigger()) close();
488
+ }
489
+ function onBlur() {
490
+ close();
491
+ }
492
+ function open() {
493
+ menu = mk("div", "ez-slash-menu");
494
+ doc.body.appendChild(menu);
495
+ doc.addEventListener("selectionchange", onSelectionChange);
496
+ editable.addEventListener("blur", onBlur);
497
+ }
498
+ function close() {
499
+ if (!menu) return false;
500
+ menu.remove();
501
+ menu = null;
502
+ trigger = null;
503
+ doc.removeEventListener("selectionchange", onSelectionChange);
504
+ editable.removeEventListener("blur", onBlur);
505
+ return true;
506
+ }
507
+ function choose(command) {
508
+ const at2 = trigger;
509
+ close();
510
+ if (at2?.node.isConnected) {
511
+ const end = Math.min(at2.node.length, at2.start + 1 + at2.query.length);
512
+ const cut = doc.createRange();
513
+ cut.setStart(at2.node, at2.start);
514
+ cut.setEnd(at2.node, end);
515
+ cut.deleteContents();
516
+ const caret = doc.createRange();
517
+ caret.setStart(at2.node, at2.start);
518
+ caret.collapse(true);
519
+ const selection = doc.getSelection();
520
+ selection?.removeAllRanges();
521
+ selection?.addRange(caret);
522
+ editable.dispatchEvent(new InputEvent("input", { bubbles: true }));
523
+ }
524
+ editable.focus();
525
+ command.run();
526
+ }
527
+ function sync() {
528
+ const hit = readTrigger();
529
+ if (!hit) {
530
+ close();
531
+ return;
532
+ }
533
+ if (load && !loaded && !loading) {
534
+ loading = true;
535
+ void Promise.resolve(load()).then((result) => {
536
+ loaded = result;
537
+ }).catch(() => {
538
+ loaded = [];
539
+ }).finally(() => {
540
+ loading = false;
541
+ if (readTrigger()) sync();
542
+ });
543
+ }
544
+ const matches = filterCommands(load ? loaded ?? [] : commands, hit.query);
545
+ if (matches.length === 0) {
546
+ close();
547
+ return;
548
+ }
549
+ const queryChanged = trigger?.query !== hit.query;
550
+ trigger = hit;
551
+ shown = matches;
552
+ if (!menu) open();
553
+ if (queryChanged) active = 0;
554
+ active = Math.min(active, shown.length - 1);
555
+ render2();
556
+ place();
557
+ }
558
+ editable.addEventListener("input", (e) => {
559
+ if (e.isComposing) return;
560
+ sync();
561
+ });
562
+ editable.addEventListener("keydown", (e) => {
563
+ if (!menu) return;
564
+ if (e.key === "ArrowDown" || e.key === "ArrowUp") {
565
+ e.preventDefault();
566
+ e.stopPropagation();
567
+ const step = e.key === "ArrowDown" ? 1 : shown.length - 1;
568
+ active = (active + step) % shown.length;
569
+ paintActive();
570
+ return;
571
+ }
572
+ if (e.key === "Enter" || e.key === "Tab") {
573
+ if (e.metaKey || e.ctrlKey) return;
574
+ e.preventDefault();
575
+ e.stopPropagation();
576
+ const command = shown[active];
577
+ if (command) choose(command);
578
+ return;
579
+ }
580
+ if (e.key === "Escape") {
581
+ e.preventDefault();
582
+ e.stopPropagation();
583
+ close();
584
+ }
585
+ });
586
+ return { close };
587
+ }
588
+
589
+ // src/client/attach.ts
590
+ var MAX_BYTES = 8 * 1024 * 1024;
591
+ var CHIP_ATTR = "data-ez-chip";
592
+ var REF_ATTR = "data-ez-ref";
593
+ var SKILL_ATTR = "data-ez-skill";
594
+ function attachify({
595
+ api: api2,
596
+ mk,
597
+ className,
598
+ placeholder,
599
+ onChange,
600
+ commands,
601
+ skills
602
+ }) {
603
+ const editable = mk("div", className);
604
+ editable.setAttribute("contenteditable", "plaintext-only");
605
+ editable.setAttribute("role", "textbox");
606
+ editable.setAttribute("aria-multiline", "true");
607
+ editable.setAttribute("aria-label", placeholder);
608
+ editable.dataset.placeholder = placeholder;
609
+ const field = mk("div", "ez-attach-field");
610
+ const error = mk("div", "ez-attach-error");
611
+ const wrap = mk("div", "ez-attach");
612
+ error.hidden = true;
613
+ field.append(editable);
614
+ const picker = mk("input", "ez-file-picker");
615
+ picker.type = "file";
616
+ picker.multiple = true;
617
+ picker.hidden = true;
618
+ picker.addEventListener("change", () => {
619
+ for (const file of take(picker.files ?? void 0)) add(file);
620
+ picker.value = "";
621
+ });
622
+ wrap.append(field, error, picker);
623
+ const slash = attachSlashMenu(editable, {
624
+ mk,
625
+ commands: [
626
+ {
627
+ id: "file",
628
+ label: "File",
629
+ hint: "\u9644\u52A0\u6A94\u6848",
630
+ keywords: ["file", "attach", "upload", "\u6A94\u6848", "\u9644\u4EF6", "\u5716\u7247"],
631
+ icon: File02Icon_default,
632
+ run: () => picker.click()
633
+ },
634
+ ...commands ?? []
635
+ ]
636
+ });
637
+ const skillMenu = attachSlashMenu(editable, {
638
+ mk,
639
+ triggerChar: "$",
640
+ title: "Skills",
641
+ commands: [],
642
+ load: skills
643
+ });
644
+ const uploading = /* @__PURE__ */ new Set();
645
+ let errorTimer = 0;
646
+ const chips = () => [...editable.querySelectorAll(`[${CHIP_ATTR}]`)];
647
+ function showError(message) {
648
+ error.textContent = message;
649
+ error.hidden = false;
650
+ clearTimeout(errorTimer);
651
+ errorTimer = setTimeout(() => {
652
+ error.hidden = true;
653
+ }, 6e3);
654
+ }
655
+ function paintEmpty() {
656
+ const empty = !editable.textContent?.trim() && chips().length === 0;
657
+ editable.toggleAttribute("data-empty", empty);
658
+ }
659
+ function changed() {
660
+ paintEmpty();
661
+ onChange?.();
662
+ }
663
+ function del(id) {
664
+ fetch(`${api2}/attachments/${id}`, { method: "DELETE" }).catch(() => {
665
+ });
666
+ }
667
+ function makeChip(name, attr, value, glyph) {
668
+ const chip = mk("span", "ez-chip");
669
+ chip.setAttribute("contenteditable", "false");
670
+ chip.setAttribute(attr, value);
671
+ if (glyph) chip.append(icon(glyph, 11));
672
+ chip.append(mk("span", "ez-chip-name"));
673
+ chip.querySelector(".ez-chip-name").textContent = name;
674
+ if (attr === REF_ATTR) chip.classList.add("ez-chip-ref");
675
+ if (attr === SKILL_ATTR) chip.classList.add("ez-chip-skill");
676
+ if (!value) chip.classList.add("ez-chip-pending");
677
+ return chip;
678
+ }
679
+ const fileChip = (name, id = "") => makeChip(name, CHIP_ATTR, id, File02Icon_default);
680
+ const refChip = (ref, placeholder2) => {
681
+ const chip = makeChip(
682
+ ref ? refChipText(ref.n, !!ref.variant) : placeholder2,
683
+ REF_ATTR,
684
+ ref ? JSON.stringify(ref) : "",
685
+ ref?.variant ? MagicWand01Icon_default : AlignSelectionIcon_default
686
+ );
687
+ if (ref?.variant) chip.classList.add("ez-chip-chosen");
688
+ const title = ref?.variant ? [ref.variant.name, ref.label].filter(Boolean).join(" \xB7 ") : ref?.label ?? "";
689
+ if (title) chip.title = title;
690
+ return chip;
691
+ };
692
+ const skillChip = (name) => makeChip(`$${name}`, SKILL_ATTR, name, null);
693
+ const pendingRef = () => editable.querySelector(`[${REF_ATTR}=""]`);
694
+ function placeCaretAfter(node) {
695
+ const caret = document.createRange();
696
+ caret.setStartAfter(node);
697
+ caret.collapse(true);
698
+ const selection = document.getSelection();
699
+ selection?.removeAllRanges();
700
+ selection?.addRange(caret);
701
+ }
702
+ function insertAtCaret(node) {
703
+ const selection = document.getSelection();
704
+ const live = selection?.rangeCount ? selection.getRangeAt(0) : null;
705
+ const range = live && editable.contains(live.commonAncestorContainer) ? live : null;
706
+ if (range) {
707
+ range.deleteContents();
708
+ range.insertNode(node);
709
+ } else {
710
+ editable.appendChild(node);
711
+ }
712
+ const after = document.createTextNode(NBSP);
713
+ node.parentNode?.insertBefore(after, node.nextSibling);
714
+ placeCaretAfter(after);
715
+ }
716
+ function add(file) {
717
+ if (file.size > MAX_BYTES) {
718
+ showError(`${file.name || "\u9019\u500B\u6A94\u6848"} \u8D85\u904E\u55AE\u6A94 8 MB \u4E0A\u9650`);
719
+ return;
720
+ }
721
+ const chip = fileChip(file.name || "\u8CBC\u4E0A\u7684\u5716\u7247");
722
+ insertAtCaret(chip);
723
+ uploading.add(chip);
724
+ changed();
725
+ void upload(chip, file);
726
+ }
727
+ async function upload(chip, file) {
728
+ try {
729
+ const res = await fetch(`${api2}/attachments`, {
730
+ method: "POST",
731
+ headers: {
732
+ "content-type": file.type || "application/octet-stream",
733
+ // A filename is free to hold bytes no header may carry.
734
+ "x-ez-name": encodeURIComponent(file.name || "image.png")
735
+ },
736
+ body: file
737
+ });
738
+ if (!res.ok) {
739
+ const body = await res.json().catch(() => null);
740
+ throw new Error(body?.error ?? `HTTP ${res.status}`);
741
+ }
742
+ const saved = await res.json();
743
+ if (!editable.contains(chip)) {
744
+ del(saved.id);
745
+ return;
746
+ }
747
+ chip.setAttribute(CHIP_ATTR, saved.id);
748
+ chip.querySelector(".ez-chip-name").textContent = saved.name;
749
+ chip.classList.remove("ez-chip-pending");
750
+ } catch (err) {
751
+ chip.remove();
752
+ showError(`\u300C${file.name || "\u6A94\u6848"}\u300D\u4E0A\u50B3\u5931\u6557\uFF1A${err.message}`);
753
+ } finally {
754
+ uploading.delete(chip);
755
+ changed();
756
+ }
757
+ }
758
+ function take(list) {
759
+ return list ? [...list] : [];
760
+ }
761
+ function beginRef(label) {
762
+ if (hasPendingRef(snapshot2())) return false;
763
+ insertAtCaret(refChip(null, label));
764
+ changed();
765
+ return true;
766
+ }
767
+ function resolveRef(ref) {
768
+ const chip = pendingRef();
769
+ if (!chip) return;
770
+ const numbered = { ...ref, n: nextRefNumber(snapshot2()) };
771
+ chip.setAttribute(REF_ATTR, JSON.stringify(numbered));
772
+ chip.querySelector(".ez-chip-name").textContent = refChipText(numbered.n, !!numbered.variant);
773
+ if (numbered.label) chip.title = numbered.label;
774
+ chip.classList.remove("ez-chip-pending");
775
+ if (chip.nextSibling) placeCaretAfter(chip.nextSibling);
776
+ changed();
777
+ }
778
+ function insertRef(ref) {
779
+ const numbered = { ...ref, n: nextRefNumber(snapshot2()) };
780
+ insertAtCaret(refChip(numbered, numbered.label));
781
+ changed();
782
+ }
783
+ function cancelRef() {
784
+ const chip = pendingRef();
785
+ if (!chip) return;
786
+ const after = chip.nextSibling;
787
+ chip.remove();
788
+ if (after?.nodeType === Node.TEXT_NODE && after.nodeValue?.startsWith(NBSP)) {
789
+ after.nodeValue = after.nodeValue.slice(1);
790
+ if (!after.nodeValue) after.parentNode?.removeChild(after);
791
+ }
792
+ changed();
793
+ }
794
+ function snapshot2() {
795
+ const out = [];
796
+ collectNodes(editable, out);
797
+ return out;
798
+ }
799
+ function restore(body) {
800
+ editable.textContent = "";
801
+ const nodes = restorableBody(body);
802
+ for (const node of nodes) {
803
+ if (node.t === "text") editable.appendChild(document.createTextNode(node.v));
804
+ else if (node.t === "file") editable.appendChild(fileChip(node.name, node.id));
805
+ else if (node.t === "skill") editable.appendChild(skillChip(node.name));
806
+ else {
807
+ const ref = node.anchor ? {
808
+ n: node.n,
809
+ anchor: node.anchor,
810
+ label: node.label,
811
+ ...node.variant ? { variant: node.variant } : {}
812
+ } : null;
813
+ editable.appendChild(refChip(ref, node.label));
814
+ }
815
+ }
816
+ if (nodes[nodes.length - 1]?.t !== "text") {
817
+ editable.appendChild(document.createTextNode(NBSP));
818
+ }
819
+ if (editable.lastChild) placeCaretAfter(editable.lastChild);
820
+ changed();
821
+ }
822
+ editable.addEventListener("paste", (e) => {
823
+ const data = e.clipboardData;
824
+ const files = take(data?.files);
825
+ if (files.length === 0) return;
826
+ if (!data?.getData("text/plain")) e.preventDefault();
827
+ e.stopPropagation();
828
+ for (const file of files) add(file);
829
+ });
830
+ const isChipNode = (node) => node instanceof HTMLElement && (node.hasAttribute(CHIP_ATTR) || node.hasAttribute(REF_ATTR) || node.hasAttribute(SKILL_ATTR));
831
+ editable.addEventListener("beforeinput", (e) => {
832
+ if (e.inputType !== "deleteContentBackward") return;
833
+ const selection = document.getSelection();
834
+ if (!selection?.isCollapsed || selection.rangeCount === 0) return;
835
+ const range = selection.getRangeAt(0);
836
+ const node = range.startContainer;
837
+ if (node.nodeType !== Node.TEXT_NODE) return;
838
+ const text = node;
839
+ if (text.nodeValue !== NBSP || range.startOffset !== 1) return;
840
+ const before = text.previousSibling;
841
+ if (!isChipNode(before)) return;
842
+ e.preventDefault();
843
+ text.remove();
844
+ placeCaretAfter(before);
845
+ changed();
846
+ });
847
+ editable.addEventListener("input", changed);
848
+ const dragging = (e) => e.dataTransfer?.types.includes("Files") ?? false;
849
+ let depth = 0;
850
+ function endDrag() {
851
+ depth = 0;
852
+ field.removeAttribute("data-drop");
853
+ }
854
+ field.addEventListener("dragenter", (e) => {
855
+ if (!dragging(e)) return;
856
+ e.preventDefault();
857
+ depth++;
858
+ field.toggleAttribute("data-drop", true);
859
+ });
860
+ field.addEventListener("dragover", (e) => {
861
+ if (!dragging(e)) return;
862
+ e.preventDefault();
863
+ if (e.dataTransfer) e.dataTransfer.dropEffect = "copy";
864
+ });
865
+ field.addEventListener("dragleave", (e) => {
866
+ if (!dragging(e)) return;
867
+ if (--depth <= 0) endDrag();
868
+ });
869
+ field.addEventListener("drop", (e) => {
870
+ const files = take(e.dataTransfer?.files);
871
+ endDrag();
872
+ if (files.length === 0) return;
873
+ e.preventDefault();
874
+ e.stopPropagation();
875
+ if (document.activeElement !== editable) editable.focus();
876
+ for (const file of files) add(file);
877
+ });
878
+ paintEmpty();
879
+ return {
880
+ wrap,
881
+ field,
882
+ editable,
883
+ // All three read the same single walk, so the text a comment reads as, the
884
+ // files it carries and the elements it points at can never disagree about
885
+ // order - and all three are testable without a browser.
886
+ text: () => draftText(snapshot2()),
887
+ skills: () => draftSkills(snapshot2()),
888
+ insertSkill(name) {
889
+ insertAtCaret(skillChip(name));
890
+ changed();
891
+ },
892
+ ids: () => draftFileIds(snapshot2()),
893
+ refs: () => draftRefs(snapshot2()),
894
+ pending: () => [...uploading].filter((c) => editable.contains(c)).length,
895
+ pendingNames: () => draftPendingNames(snapshot2()),
896
+ // Either menu, whichever is up - at most one can be, since a trigger only
897
+ // counts at the start of the word being typed.
898
+ closeSlash: () => slash.close() || skillMenu.close(),
899
+ beginRef,
900
+ resolveRef,
901
+ insertRef,
902
+ cancelRef,
903
+ snapshot: snapshot2,
904
+ restore,
905
+ reset() {
906
+ slash.close();
907
+ uploading.clear();
908
+ editable.textContent = "";
909
+ changed();
910
+ },
911
+ discard() {
912
+ slash.close();
913
+ for (const id of chips().map((c) => c.getAttribute(CHIP_ATTR))) if (id) del(id);
914
+ uploading.clear();
915
+ editable.textContent = "";
916
+ changed();
917
+ }
918
+ };
919
+ }
920
+ function collectNodes(node, out) {
921
+ for (const child of node.childNodes) {
922
+ if (child.nodeType === Node.TEXT_NODE) {
923
+ out.push({ t: "text", v: child.nodeValue ?? "" });
924
+ continue;
925
+ }
926
+ if (!(child instanceof HTMLElement)) continue;
927
+ const name = child.querySelector(".ez-chip-name")?.textContent ?? "";
928
+ if (child.hasAttribute(REF_ATTR)) {
929
+ const ref = parseRef(child.getAttribute(REF_ATTR));
930
+ out.push({
931
+ t: "ref",
932
+ n: ref?.n ?? 0,
933
+ anchor: ref?.anchor ?? null,
934
+ label: ref?.label ?? name,
935
+ ...ref?.variant ? { variant: ref.variant } : {}
936
+ });
937
+ continue;
938
+ }
939
+ if (child.hasAttribute(CHIP_ATTR)) {
940
+ out.push({ t: "file", id: child.getAttribute(CHIP_ATTR) ?? "", name });
941
+ continue;
942
+ }
943
+ if (child.hasAttribute(SKILL_ATTR)) {
944
+ out.push({ t: "skill", name: child.getAttribute(SKILL_ATTR) ?? "" });
945
+ continue;
946
+ }
947
+ if (child.tagName === "BR") {
948
+ out.push({ t: "text", v: "\n" });
949
+ continue;
950
+ }
951
+ const prev = lastText(out);
952
+ if (prev !== void 0 && !prev.endsWith("\n")) out.push({ t: "text", v: "\n" });
953
+ collectNodes(child, out);
954
+ }
955
+ }
956
+ function lastText(out) {
957
+ for (let i = out.length - 1; i >= 0; i--) {
958
+ const node = out[i];
959
+ if (node?.t === "text") return node.v;
960
+ }
961
+ return void 0;
962
+ }
963
+ function parseRef(raw) {
964
+ if (!raw) return null;
965
+ try {
966
+ return JSON.parse(raw);
967
+ } catch {
968
+ return null;
969
+ }
970
+ }
971
+
972
+ // src/client/confirm-skip.ts
973
+ function confirmSkipKey(question) {
974
+ return `ez-confirm-skip:${question}`;
975
+ }
976
+ function confirmSkipped(store, question) {
977
+ try {
978
+ return store?.getItem(confirmSkipKey(question)) === "1";
979
+ } catch {
980
+ return false;
981
+ }
982
+ }
983
+ function rememberConfirm(store, question, answer) {
984
+ if (!answer.ok || !answer.skip) return;
985
+ try {
986
+ store?.setItem(confirmSkipKey(question), "1");
987
+ } catch {
988
+ }
989
+ }
990
+
991
+ // src/client/page-storage.ts
992
+ var SHELL_OWNED_PREFIXES = ["ezdesign:", "ez-update-dismissed", "ez-confirm-skip:"];
993
+ var MAX_RECORDS_PER_STORE = 1e3;
994
+ var MAX_BYTES2 = 15e5;
995
+ var COLLECT_TIMEOUT_MS = 1500;
996
+ function storageEntries(store) {
997
+ const out = [];
998
+ for (let i = 0; i < store.length; i++) {
999
+ const name = store.key(i);
1000
+ if (name === null) continue;
1001
+ if (SHELL_OWNED_PREFIXES.some((p) => name.startsWith(p))) continue;
1002
+ out.push({ name, value: store.getItem(name) ?? "" });
1003
+ }
1004
+ return out;
1005
+ }
1006
+ var done = (request) => new Promise((ok, fail) => {
1007
+ request.onsuccess = () => ok(request.result);
1008
+ request.onerror = () => fail(request.error);
1009
+ });
1010
+ function keyPathOf(path) {
1011
+ if (typeof path === "string") return { keyPath: path };
1012
+ if (Array.isArray(path)) return { keyPathArray: [...path] };
1013
+ return {};
1014
+ }
1015
+ function jsonSafe(value) {
1016
+ try {
1017
+ JSON.stringify(value);
1018
+ return true;
1019
+ } catch {
1020
+ return false;
1021
+ }
1022
+ }
1023
+ async function readDatabase(factory, name) {
1024
+ const request = factory.open(name);
1025
+ request.onupgradeneeded = () => request.transaction?.abort();
1026
+ const db = await done(request);
1027
+ try {
1028
+ const stores = [];
1029
+ for (const storeName of [...db.objectStoreNames]) {
1030
+ const store = db.transaction(storeName, "readonly").objectStore(storeName);
1031
+ const [keys, values] = await Promise.all([
1032
+ done(store.getAllKeys(null, MAX_RECORDS_PER_STORE)),
1033
+ done(store.getAll(null, MAX_RECORDS_PER_STORE))
1034
+ ]);
1035
+ const inline = store.keyPath !== null;
1036
+ const records = values.flatMap((value, i) => {
1037
+ const record = inline ? { value } : { key: keys[i], value };
1038
+ return jsonSafe(record) ? [record] : [];
1039
+ });
1040
+ const indexes = [...store.indexNames].map((indexName) => {
1041
+ const index = store.index(indexName);
1042
+ return {
1043
+ name: indexName,
1044
+ ...keyPathOf(index.keyPath),
1045
+ multiEntry: index.multiEntry,
1046
+ unique: index.unique
1047
+ };
1048
+ });
1049
+ stores.push({
1050
+ name: storeName,
1051
+ ...keyPathOf(store.keyPath),
1052
+ autoIncrement: store.autoIncrement,
1053
+ records,
1054
+ indexes
1055
+ });
1056
+ }
1057
+ return { name, version: db.version, stores };
1058
+ } finally {
1059
+ db.close();
1060
+ }
1061
+ }
1062
+ async function readIndexedDb(factory) {
1063
+ const listed = await factory.databases();
1064
+ const out = [];
1065
+ for (const { name } of listed) {
1066
+ if (!name) continue;
1067
+ try {
1068
+ out.push(await readDatabase(factory, name));
1069
+ } catch {
1070
+ }
1071
+ }
1072
+ return out;
1073
+ }
1074
+ async function collectPageStorage(win) {
1075
+ let local;
1076
+ let session;
1077
+ let factory;
1078
+ try {
1079
+ local = storageEntries(win.localStorage);
1080
+ session = storageEntries(win.sessionStorage);
1081
+ factory = win.indexedDB;
1082
+ } catch {
1083
+ return null;
1084
+ }
1085
+ const timedOut = new Promise((ok) => setTimeout(() => ok([]), COLLECT_TIMEOUT_MS));
1086
+ const indexedDB = await Promise.race([readIndexedDb(factory).catch(() => []), timedOut]);
1087
+ const storage = { localStorage: local, sessionStorage: session, indexedDB };
1088
+ return JSON.stringify(storage).length > MAX_BYTES2 ? { ...storage, indexedDB: [] } : storage;
1089
+ }
1090
+
1091
+ // src/client/usage-note.ts
1092
+ function windowName(minutes) {
1093
+ if (minutes === 7 * 24 * 60) return "1 \u9031";
1094
+ if (minutes % (24 * 60) === 0) return `${minutes / (24 * 60)} \u5929`;
1095
+ if (minutes % 60 === 0) return `${minutes / 60} \u5C0F\u6642`;
1096
+ return `${minutes} \u5206\u9418`;
1097
+ }
1098
+ var DAY_MS = 24 * 60 * 60 * 1e3;
1099
+ function startOfDay(at2) {
1100
+ return new Date(at2.getFullYear(), at2.getMonth(), at2.getDate()).getTime();
1101
+ }
1102
+ function clockTime(resetsAt, now) {
1103
+ const when = new Date(resetsAt * 1e3);
1104
+ const time = when.toLocaleTimeString(void 0, {
1105
+ hour: "2-digit",
1106
+ minute: "2-digit",
1107
+ hourCycle: "h23"
1108
+ });
1109
+ const days = Math.round((startOfDay(when) - startOfDay(new Date(now))) / DAY_MS);
1110
+ if (days <= 0) return time;
1111
+ if (days === 1) return `\u660E\u5929 ${time}`;
1112
+ return `${when.getMonth() + 1}/${when.getDate()} ${time}`;
1113
+ }
1114
+ function resetTime(window2, now) {
1115
+ return window2.resetsAt !== void 0 && window2.resetsAt * 1e3 > now ? clockTime(window2.resetsAt, now) : "";
1116
+ }
1117
+ function tightestWindow(usage) {
1118
+ const windows = usage?.windows ?? [];
1119
+ const account = windows.filter((w) => !w.model);
1120
+ return (account.length ? account : windows).sort((a, b) => a.windowMinutes - b.windowMinutes)[0];
1121
+ }
1122
+ function usageNote(usage, now = Date.now()) {
1123
+ const window2 = tightestWindow(usage);
1124
+ if (!window2) return "";
1125
+ const percent = Math.round(window2.remaining * 100);
1126
+ const when = resetTime(window2, now);
1127
+ return when ? `\u5269 ${percent}% \u76F4\u5230 ${when}` : `\u5269 ${percent}%`;
1128
+ }
1129
+ var LOW = 0.15;
1130
+ function usageRows(usage, now = Date.now()) {
1131
+ return (usage?.windows ?? []).map((window2) => {
1132
+ const when = resetTime(window2, now);
1133
+ const percent = Math.round(window2.remaining * 100);
1134
+ return {
1135
+ label: window2.model ? `${windowName(window2.windowMinutes)} \xB7 ${window2.model}` : windowName(window2.windowMinutes),
1136
+ percent,
1137
+ used: 100 - percent,
1138
+ when: when ? `${when} \u91CD\u7F6E` : "",
1139
+ low: window2.remaining < LOW
1140
+ };
1141
+ });
1142
+ }
1143
+ function planName(plan) {
1144
+ if (!plan) return "";
1145
+ return plan.charAt(0).toUpperCase() + plan.slice(1);
1146
+ }
1147
+
1148
+ // src/client/devices.ts
1149
+ var DESKTOP = {
1150
+ id: "desktop",
1151
+ name: "\u96FB\u8166",
1152
+ width: 1440,
1153
+ height: 788,
1154
+ fluid: true
1155
+ };
1156
+ var TABLET = { id: "tablet", name: "\u5E73\u677F", width: 1112, height: 740 };
1157
+ var TABLET_PORTRAIT = {
1158
+ id: "tablet-portrait",
1159
+ name: "\u76F4\u7ACB\u5E73\u677F",
1160
+ width: 834,
1161
+ height: 1018
1162
+ };
1163
+ var MOBILE = { id: "mobile", name: "\u624B\u6A5F", width: 375, height: 629 };
1164
+ var DEVICES = [MOBILE, TABLET, DESKTOP];
1165
+ var CANVAS_DEVICES = [MOBILE, TABLET_PORTRAIT, TABLET, DESKTOP];
1166
+ var CANVAS_DEFAULT = [DESKTOP.id, TABLET.id, MOBILE.id];
1167
+ function canvasLimit(devices, gap) {
1168
+ if (devices.length === 0) return 1;
1169
+ const widths = devices.map((d) => d.width);
1170
+ return Math.max(...widths) + Math.min(...widths) + gap;
1171
+ }
1172
+ function canvasRows(devices, gap, limit) {
1173
+ if (devices.length === 0) return [];
1174
+ const order = [...devices].sort((a, b) => a.width - b.width);
1175
+ const rows = [];
1176
+ for (const device of order) {
1177
+ const row = rows[rows.length - 1];
1178
+ const grown = row ? rowWidth([...row, device], gap) : 0;
1179
+ if (row && grown <= limit) row.push(device);
1180
+ else rows.push([device]);
1181
+ }
1182
+ return rows;
1183
+ }
1184
+ function rowWidth(row, gap) {
1185
+ return row.reduce((w, d) => w + d.width, 0) + gap * Math.max(0, row.length - 1);
1186
+ }
1187
+ var CANVAS_GAP = 14;
1188
+ var CANVAS_ROW_GAP = 26;
1189
+ function deviceById(id) {
1190
+ return DEVICES.find((d) => d.id === id) ?? DESKTOP;
1191
+ }
1192
+ var MIN_ZOOM = 0.2;
1193
+ function fitWidth(width, boxWidth) {
1194
+ if (!(boxWidth > 0) || !(width > 0)) return 1;
1195
+ return scale(boxWidth / width, 1);
1196
+ }
1197
+ var CANVAS_ZOOM = 1;
1198
+ function scale(raw, max) {
1199
+ return Math.min(Math.max(Math.floor(raw * 100) / 100, MIN_ZOOM), max);
1200
+ }
1201
+ function deviceLabel(device) {
1202
+ return `${device.name} \xB7 ${device.width}\xD7${device.height}`;
1203
+ }
1204
+
1205
+ // src/client/canvas-layout.ts
1206
+ function defaultLayout(devices) {
1207
+ const limit = canvasLimit(CANVAS_DEVICES, CANVAS_GAP);
1208
+ return canvasRows(devices, CANVAS_GAP, limit).map((row) => row.map((d) => d.id));
1209
+ }
1210
+ function sanitizeLayout(raw, known) {
1211
+ if (!Array.isArray(raw)) return null;
1212
+ const seen = /* @__PURE__ */ new Set();
1213
+ const rows = [];
1214
+ for (const entry of raw) {
1215
+ if (!Array.isArray(entry)) continue;
1216
+ const row = [];
1217
+ for (const id of entry) {
1218
+ if (typeof id !== "string" || !known.includes(id) || seen.has(id)) continue;
1219
+ seen.add(id);
1220
+ row.push(id);
1221
+ }
1222
+ if (row.length) rows.push(row);
1223
+ }
1224
+ return rows.length ? rows : null;
1225
+ }
1226
+ function toggleDevice(layout2, id) {
1227
+ const on = layout2.some((row) => row.includes(id));
1228
+ if (!on) return [...layout2, [id]];
1229
+ if (layout2.reduce((n, row) => n + row.length, 0) === 1) return layout2;
1230
+ return layout2.map((row) => row.filter((x) => x !== id)).filter((row) => row.length);
1231
+ }
1232
+ function rowGeometry(rows, m) {
1233
+ const heights = rows.map((row) => m.head + Math.max(...row.map((d) => d.height)));
1234
+ const tops = [];
1235
+ let y = 0;
1236
+ for (const h2 of heights) {
1237
+ tops.push(y);
1238
+ y += h2 + m.rowGap;
1239
+ }
1240
+ return {
1241
+ tops,
1242
+ heights,
1243
+ width: rows.reduce((w, row) => Math.max(w, rowWidth(row, m.gap)), 0),
1244
+ height: rows.length ? y - m.rowGap : 0
1245
+ };
1246
+ }
1247
+ function planCanvas(rows, m) {
1248
+ const geo = rowGeometry(rows, m);
1249
+ const cards = [];
1250
+ rows.forEach((row, i) => {
1251
+ let x = 0;
1252
+ for (const d of row) {
1253
+ cards.push({ id: d.id, x, y: geo.tops[i] });
1254
+ x += d.width + m.gap;
1255
+ }
1256
+ });
1257
+ return { cards, width: geo.width, height: geo.height };
1258
+ }
1259
+ function seamY(geo, rowGap, k) {
1260
+ if (k === 0) return geo.tops[0] ?? 0;
1261
+ if (k === geo.tops.length) return geo.height;
1262
+ return geo.tops[k] - rowGap / 2;
1263
+ }
1264
+ function columnCard(row, gap, x) {
1265
+ let cx = 0;
1266
+ for (let i = 0; i < row.length; i++) {
1267
+ const d = row[i];
1268
+ if (i === row.length - 1 || x < cx + d.width + gap / 2) return { device: d, x: cx };
1269
+ cx += d.width + gap;
1270
+ }
1271
+ return { device: row[0], x: 0 };
1272
+ }
1273
+ function dropTarget(rows, m, point, snap) {
1274
+ if (rows.length === 0) return { kind: "row", at: 0 };
1275
+ const geo = rowGeometry(rows, m);
1276
+ let at2 = 0;
1277
+ let nearest = Infinity;
1278
+ for (let k = 0; k <= rows.length; k++) {
1279
+ const dist = Math.abs(point.y - seamY(geo, m.rowGap, k));
1280
+ if (dist < nearest) {
1281
+ at2 = k;
1282
+ nearest = dist;
1283
+ }
1284
+ }
1285
+ if (nearest <= snap) return { kind: "row", at: at2 };
1286
+ const row = geo.tops.findIndex((top, i) => point.y >= top && point.y <= top + geo.heights[i]);
1287
+ if (row < 0) return { kind: "row", at: point.y < 0 ? 0 : rows.length };
1288
+ const col = columnCard(rows[row], m.gap, point.x);
1289
+ if (point.y > geo.tops[row] + m.head + col.device.height) return { kind: "row", at: row + 1 };
1290
+ let x = 0;
1291
+ let index = 0;
1292
+ for (const d of rows[row]) {
1293
+ if (point.x > x + d.width / 2) index++;
1294
+ x += d.width + m.gap;
1295
+ }
1296
+ return { kind: "slot", row, index };
1297
+ }
1298
+ function applyMove(layout2, id, target2) {
1299
+ const fromRow = layout2.findIndex((row) => row.includes(id));
1300
+ if (fromRow < 0) return layout2;
1301
+ const fromIndex = layout2[fromRow].indexOf(id);
1302
+ const next = layout2.map((row) => row.filter((x) => x !== id));
1303
+ if (target2.kind === "slot") {
1304
+ const row = next[target2.row];
1305
+ if (!row) return layout2;
1306
+ const index = target2.row === fromRow && target2.index > fromIndex ? target2.index - 1 : target2.index;
1307
+ row.splice(Math.min(index, row.length), 0, id);
1308
+ } else {
1309
+ next.splice(Math.min(target2.at, next.length), 0, [id]);
1310
+ }
1311
+ const moved = next.filter((row) => row.length);
1312
+ return sameLayout(moved, layout2) ? layout2 : moved;
1313
+ }
1314
+ function sameLayout(a, b) {
1315
+ return a.length === b.length && a.every((row, i) => row.length === b[i].length && row.every((id, j) => id === b[i][j]));
1316
+ }
1317
+ function indicatorRect(rows, m, target2, point) {
1318
+ const geo = rowGeometry(rows, m);
1319
+ if (rows.length === 0) return { x: 0, y: 0, width: 0, height: 0 };
1320
+ if (target2.kind === "row") {
1321
+ if (target2.at === 0) {
1322
+ const col2 = columnCard(rows[0], m.gap, point.x);
1323
+ return { x: col2.x, y: 0, width: col2.device.width, height: 0 };
1324
+ }
1325
+ const i = Math.min(target2.at, rows.length) - 1;
1326
+ const col = columnCard(rows[i], m.gap, point.x);
1327
+ return {
1328
+ x: col.x,
1329
+ y: geo.tops[i] + m.head + col.device.height + m.rowGap / 2,
1330
+ width: col.device.width,
1331
+ height: 0
1332
+ };
1333
+ }
1334
+ let x = 0;
1335
+ for (const d of rows[target2.row].slice(0, target2.index)) x += d.width + m.gap;
1336
+ return {
1337
+ x: x - m.gap / 2,
1338
+ y: geo.tops[target2.row],
1339
+ width: 0,
1340
+ height: geo.heights[target2.row]
1341
+ };
1342
+ }
1343
+
1344
+ // src/client/markdown.ts
1345
+ var BULLET = /^\s{0,3}[-*+]\s+(.*)$/;
1346
+ var NUMBERED = /^\s{0,3}\d{1,3}[.)]\s+(.*)$/;
1347
+ var HEADING = /^(#{1,4})\s+(.*)$/;
1348
+ var FENCE = /^\s{0,3}```/;
1349
+ var INLINE = /(`+)([^`]+?)\1|\*\*([^*\n]+?)\*\*|__([^_\n]+?)__|\*([^*\s][^*\n]*?)\*|_([^_\s][^_\n]*?)_|\[([^\]\n]+)\]\(([^)\s]+)\)/;
1350
+ function parseInline(text) {
1351
+ const out = [];
1352
+ const pushText = (chunk) => {
1353
+ const lines = chunk.split("\n");
1354
+ lines.forEach((line, i) => {
1355
+ if (line) out.push({ t: "text", text: line });
1356
+ if (i < lines.length - 1) out.push({ t: "break" });
1357
+ });
1358
+ };
1359
+ let at2 = 0;
1360
+ const inline = new RegExp(INLINE.source, "g");
1361
+ for (; ; ) {
1362
+ const m = inline.exec(text);
1363
+ if (!m) break;
1364
+ if (m.index > at2) pushText(text.slice(at2, m.index));
1365
+ at2 = m.index + m[0].length;
1366
+ if (m[2] !== void 0) out.push({ t: "code", text: m[2].trim() });
1367
+ else if (m[3] !== void 0 || m[4] !== void 0) {
1368
+ out.push({ t: "strong", children: parseInline(m[3] ?? m[4]) });
1369
+ } else if (m[5] !== void 0 || m[6] !== void 0) {
1370
+ out.push({ t: "em", children: parseInline(m[5] ?? m[6]) });
1371
+ } else if (m[7] !== void 0 && m[8] !== void 0) {
1372
+ if (/^https?:\/\//.test(m[8])) out.push({ t: "link", text: m[7], href: m[8] });
1373
+ else pushText(m[0]);
1374
+ }
1375
+ }
1376
+ if (at2 < text.length) pushText(text.slice(at2));
1377
+ return out;
1378
+ }
1379
+ function parseMarkdown(text) {
1380
+ const blocks = [];
1381
+ const lines = text.split("\n");
1382
+ let i = 0;
1383
+ while (i < lines.length) {
1384
+ const line = lines[i];
1385
+ if (!line.trim()) {
1386
+ i++;
1387
+ continue;
1388
+ }
1389
+ if (FENCE.test(line)) {
1390
+ const body = [];
1391
+ i++;
1392
+ while (i < lines.length && !FENCE.test(lines[i])) body.push(lines[i++]);
1393
+ i++;
1394
+ blocks.push({ t: "fence", text: body.join("\n") });
1395
+ continue;
1396
+ }
1397
+ const heading = HEADING.exec(line);
1398
+ if (heading) {
1399
+ blocks.push({ t: "heading", level: heading[1].length, children: parseInline(heading[2]) });
1400
+ i++;
1401
+ continue;
1402
+ }
1403
+ const bullet = BULLET.exec(line);
1404
+ const numbered = bullet ? null : NUMBERED.exec(line);
1405
+ if (bullet || numbered) {
1406
+ const ordered = Boolean(numbered);
1407
+ const items = [];
1408
+ while (i < lines.length) {
1409
+ const m = ordered ? NUMBERED.exec(lines[i]) : BULLET.exec(lines[i]);
1410
+ if (!m) break;
1411
+ const parts2 = [m[1]];
1412
+ i++;
1413
+ while (i < lines.length && lines[i].trim() && !BULLET.test(lines[i]) && !NUMBERED.test(lines[i]) && /^\s{2,}/.test(lines[i])) {
1414
+ parts2.push(lines[i].trim());
1415
+ i++;
1416
+ }
1417
+ items.push(parseInline(parts2.join(" ")));
1418
+ }
1419
+ blocks.push({ t: "list", ordered, items });
1420
+ continue;
1421
+ }
1422
+ const parts = [];
1423
+ while (i < lines.length) {
1424
+ const l = lines[i];
1425
+ if (!l.trim() || FENCE.test(l) || HEADING.test(l) || BULLET.test(l) || NUMBERED.test(l)) {
1426
+ break;
1427
+ }
1428
+ parts.push(l);
1429
+ i++;
1430
+ }
1431
+ blocks.push({ t: "p", children: parseInline(parts.join("\n")) });
1432
+ }
1433
+ return blocks;
1434
+ }
1435
+ function renderInline(into, nodes) {
1436
+ for (const node of nodes) {
1437
+ switch (node.t) {
1438
+ case "text":
1439
+ into.append(node.text);
1440
+ break;
1441
+ case "break":
1442
+ into.append(document.createElement("br"));
1443
+ break;
1444
+ case "code": {
1445
+ const el = document.createElement("code");
1446
+ el.className = "ez-md-code";
1447
+ el.textContent = node.text;
1448
+ into.append(el);
1449
+ break;
1450
+ }
1451
+ case "strong":
1452
+ case "em": {
1453
+ const el = document.createElement(node.t === "strong" ? "strong" : "em");
1454
+ renderInline(el, node.children);
1455
+ into.append(el);
1456
+ break;
1457
+ }
1458
+ case "link": {
1459
+ const el = document.createElement("a");
1460
+ el.href = node.href;
1461
+ el.textContent = node.text;
1462
+ el.target = "_blank";
1463
+ el.rel = "noreferrer noopener";
1464
+ into.append(el);
1465
+ break;
1466
+ }
1467
+ }
1468
+ }
1469
+ }
1470
+ function markdownEl(text, className) {
1471
+ const box = document.createElement("div");
1472
+ box.className = className;
1473
+ for (const block of parseMarkdown(text)) {
1474
+ switch (block.t) {
1475
+ case "p": {
1476
+ const el = document.createElement("p");
1477
+ renderInline(el, block.children);
1478
+ box.append(el);
1479
+ break;
1480
+ }
1481
+ case "heading": {
1482
+ const el = document.createElement(block.level <= 2 ? "h5" : "h6");
1483
+ renderInline(el, block.children);
1484
+ box.append(el);
1485
+ break;
1486
+ }
1487
+ case "list": {
1488
+ const el = document.createElement(block.ordered ? "ol" : "ul");
1489
+ for (const item of block.items) {
1490
+ const li = document.createElement("li");
1491
+ renderInline(li, item);
1492
+ el.append(li);
1493
+ }
1494
+ box.append(el);
1495
+ break;
1496
+ }
1497
+ case "fence": {
1498
+ const pre = document.createElement("pre");
1499
+ pre.className = "ez-md-pre";
1500
+ pre.textContent = block.text;
1501
+ box.append(pre);
1502
+ break;
1503
+ }
1504
+ }
1505
+ }
1506
+ return box;
1507
+ }
1508
+
1509
+ // src/client/nav.ts
1510
+ function reduceNav(state, e) {
1511
+ switch (e.t) {
1512
+ case "request": {
1513
+ const push = e.url === state.url ? [] : [{ do: "push", url: e.url }];
1514
+ return { state: { url: e.url }, effects: [...push, { do: "navigate", url: e.url }] };
1515
+ }
1516
+ case "moved":
1517
+ if (e.url === state.url) return { state, effects: [] };
1518
+ return {
1519
+ state: { url: e.url },
1520
+ effects: [
1521
+ { do: "push", url: e.url },
1522
+ { do: "navigate", url: e.url, except: e.from }
1523
+ ]
1524
+ };
1525
+ case "loaded":
1526
+ if (e.url === state.url) return { state, effects: [] };
1527
+ return {
1528
+ state: { url: e.url },
1529
+ effects: [
1530
+ { do: "replace", url: e.url },
1531
+ { do: "navigate", url: e.url, except: e.from }
1532
+ ]
1533
+ };
1534
+ case "pop":
1535
+ if (e.url === state.url) return { state, effects: [] };
1536
+ return { state: { url: e.url }, effects: [{ do: "navigate", url: e.url }] };
1537
+ }
1538
+ }
1539
+
1540
+ // src/client/thread.ts
1541
+ function threadOrder(entries) {
1542
+ const answers = /* @__PURE__ */ new Map();
1543
+ for (const entry of entries) {
1544
+ if (entry.role === "user" || !entry.batchId) continue;
1545
+ const group = answers.get(entry.batchId);
1546
+ if (group) group.push(entry);
1547
+ else answers.set(entry.batchId, [entry]);
1548
+ }
1549
+ const out = [];
1550
+ const placed = /* @__PURE__ */ new Set();
1551
+ for (const entry of entries) {
1552
+ if (placed.has(entry)) continue;
1553
+ if (entry.role !== "user" && entry.batchId && answers.has(entry.batchId)) continue;
1554
+ out.push(entry);
1555
+ placed.add(entry);
1556
+ if (entry.role !== "user" || !entry.batchId) continue;
1557
+ for (const answer of answers.get(entry.batchId) ?? []) {
1558
+ out.push(answer);
1559
+ placed.add(answer);
1560
+ }
1561
+ answers.delete(entry.batchId);
1562
+ }
1563
+ for (const group of answers.values()) out.push(...group);
1564
+ return out;
1565
+ }
1566
+
1567
+ // src/client/pick.ts
1568
+ var ARM_TIMEOUT_MS = 1500;
1569
+ var MAX_RETURNS = 2;
1570
+ function modLabel(userAgent) {
1571
+ return /Mac|iP(hone|ad|od)/.test(userAgent) ? "\u2318" : "Ctrl";
1572
+ }
1573
+ var at = (frame) => frame ? { frame } : {};
1574
+ function standDown(state, keep) {
1575
+ return keep ? [{ do: "disarm-others", id: state.id, keep }] : [];
1576
+ }
1577
+ function elsewhere(state, frame) {
1578
+ return Boolean(frame && state.frame && state.frame !== frame);
1579
+ }
1580
+ var done2 = (effects) => ({ state: null, effects });
1581
+ function banner(state) {
1582
+ if (!state) return { do: "banner", text: null };
1583
+ if (state.phase === "returning") {
1584
+ return { do: "banner", text: `\u56DE\u5230 ${state.draft?.subject?.page ?? ""} \u7E7C\u7E8C\u7559\u8A00\u2026` };
1585
+ }
1586
+ return { do: "banner", text: null };
1587
+ }
1588
+ function reducePick(state, e) {
1589
+ switch (e.t) {
1590
+ case "arm": {
1591
+ if (state) return { state, effects: [] };
1592
+ const next = {
1593
+ id: e.id,
1594
+ host: e.host,
1595
+ phase: "picking",
1596
+ armed: false,
1597
+ frame: null,
1598
+ draft: null,
1599
+ ref: null,
1600
+ page: null,
1601
+ armedAt: e.now,
1602
+ returns: 0
1603
+ };
1604
+ return {
1605
+ state: next,
1606
+ effects: [{ do: "arm-overlay", id: e.id, host: e.host }, banner(next)]
1607
+ };
1608
+ }
1609
+ case "armed": {
1610
+ if (!state) {
1611
+ const next = {
1612
+ id: e.id,
1613
+ host: e.host,
1614
+ phase: "picking",
1615
+ armed: true,
1616
+ frame: e.frame ?? null,
1617
+ draft: null,
1618
+ ref: null,
1619
+ page: null,
1620
+ armedAt: e.now,
1621
+ returns: 0
1622
+ };
1623
+ return { state: next, effects: [banner(next)] };
1624
+ }
1625
+ if (state.id !== e.id) return { state, effects: [] };
1626
+ if (elsewhere(state, e.frame)) return { state, effects: [] };
1627
+ return { state: { ...state, armed: true }, effects: [] };
1628
+ }
1629
+ case "draft":
1630
+ if (!state || state.id !== e.id || elsewhere(state, e.frame)) return { state, effects: [] };
1631
+ return { state: { ...state, draft: e.draft }, effects: [] };
1632
+ case "picked": {
1633
+ if (!state || state.id !== e.id || elsewhere(state, e.frame)) return { state, effects: [] };
1634
+ const owner = state.frame ?? e.frame ?? null;
1635
+ if (state.host === "note") {
1636
+ return done2([{ do: "insert-note", ref: e.ref }, ...standDown(state, owner), banner(null)]);
1637
+ }
1638
+ const draft = state.draft;
1639
+ if (!draft?.subject) return done2([banner(null)]);
1640
+ const filled = { ...draft, body: resolveDraftRef(draft.body, e.ref) };
1641
+ if (draftBelongsHere(filled, e.page)) {
1642
+ return done2([{ do: "restore", draft: filled }, banner(null)]);
1643
+ }
1644
+ const next = {
1645
+ ...state,
1646
+ phase: "returning",
1647
+ frame: owner,
1648
+ draft: filled,
1649
+ ref: e.ref,
1650
+ page: e.page,
1651
+ returns: state.returns + 1
1652
+ };
1653
+ return {
1654
+ state: next,
1655
+ effects: [{ do: "navigate", page: draft.subject.page, ...at(owner) }, banner(next)]
1656
+ };
1657
+ }
1658
+ case "draft-done":
1659
+ if (!state || state.id !== e.id) return { state, effects: [] };
1660
+ return done2([banner(null)]);
1661
+ case "cancelled": {
1662
+ if (!state || state.id !== e.id || elsewhere(state, e.frame)) return { state, effects: [] };
1663
+ if (e.resumed || state.host === "note") {
1664
+ return done2([...standDown(state, state.frame ?? e.frame ?? null), banner(null)]);
1665
+ }
1666
+ const draft = state.draft;
1667
+ if (!draft?.subject) return done2([banner(null)]);
1668
+ const cleared = { ...draft, body: dropDraftRef(draft.body) };
1669
+ const owner = state.frame ?? e.frame ?? null;
1670
+ if (state.page && draftBelongsHere(cleared, state.page)) {
1671
+ return done2([{ do: "restore", draft: cleared, ...at(owner) }, banner(null)]);
1672
+ }
1673
+ const next = {
1674
+ ...state,
1675
+ phase: "returning",
1676
+ frame: owner,
1677
+ draft: cleared,
1678
+ ref: null,
1679
+ returns: state.returns + 1
1680
+ };
1681
+ return {
1682
+ state: next,
1683
+ effects: [{ do: "navigate", page: draft.subject.page, ...at(owner) }, banner(next)]
1684
+ };
1685
+ }
1686
+ case "ready": {
1687
+ if (!state) return { state, effects: [] };
1688
+ if (elsewhere(state, e.frame)) return { state, effects: [] };
1689
+ const here = { ...state, page: e.page };
1690
+ if (state.phase === "returning") {
1691
+ if (draftBelongsHere(state.draft, e.page)) {
1692
+ return done2([{ do: "restore", draft: state.draft, ...at(state.frame) }, banner(null)]);
1693
+ }
1694
+ if (state.returns >= MAX_RETURNS) {
1695
+ return done2([
1696
+ { do: "banner", text: `\u56DE\u4E0D\u5230 ${state.draft?.subject?.page ?? ""}\uFF0C\u9019\u5247\u7559\u8A00\u6C92\u6709\u4FDD\u7559` }
1697
+ ]);
1698
+ }
1699
+ const next2 = { ...here, returns: state.returns + 1 };
1700
+ return {
1701
+ state: next2,
1702
+ effects: [
1703
+ { do: "navigate", page: state.draft.subject.page, ...at(state.frame) },
1704
+ banner(next2)
1705
+ ]
1706
+ };
1707
+ }
1708
+ const next = { ...here, armed: false, armedAt: e.now };
1709
+ return {
1710
+ state: next,
1711
+ effects: [
1712
+ {
1713
+ do: "arm-overlay",
1714
+ id: state.id,
1715
+ host: state.host,
1716
+ ...state.draft?.subject ? { returnTo: state.draft.subject.page } : {},
1717
+ ...at(e.frame ?? state.frame)
1718
+ },
1719
+ banner(next)
1720
+ ]
1721
+ };
1722
+ }
1723
+ case "abort": {
1724
+ if (!state) return { state, effects: [] };
1725
+ return done2([{ do: "abort-overlay", id: state.id, ...at(state.frame) }, banner(null)]);
1726
+ }
1727
+ case "expired": {
1728
+ if (!state || state.id !== e.id || elsewhere(state, e.frame)) return { state, effects: [] };
1729
+ return done2([
1730
+ { do: "banner", text: "\u9019\u5247\u7559\u8A00\u64F1\u7F6E\u592A\u4E45\uFF0C\u9644\u52A0\u7684\u6A94\u6848\u5DF2\u7D93\u88AB\u6E05\u6389\u4E86\uFF0C\u8ACB\u91CD\u65B0\u7559\u4E00\u6B21" }
1731
+ ]);
1732
+ }
1733
+ case "tick": {
1734
+ if (!state || state.armed || state.phase !== "picking") return { state, effects: [] };
1735
+ if (e.now - state.armedAt < ARM_TIMEOUT_MS) return { state, effects: [] };
1736
+ return done2([{ do: "banner", text: "\u9019\u4E00\u9801\u9084\u6C92\u6E96\u5099\u597D\uFF0C\u6C92\u8FA6\u6CD5\u6311\u9078\u5143\u7D20" }]);
1737
+ }
1738
+ }
1739
+ }
1740
+
1741
+ // src/client/sidebar-width.ts
1742
+ var SIDEBAR_DEFAULT = 344;
1743
+ var SIDEBAR_MIN = 344;
1744
+ var SIDEBAR_MAX = 480;
1745
+ var STAGE_MIN = 480;
1746
+ function maxSidebarWidth(viewportWidth) {
1747
+ return Math.max(SIDEBAR_MIN, Math.min(SIDEBAR_MAX, viewportWidth - STAGE_MIN));
1748
+ }
1749
+ function clampSidebarWidth(width, viewportWidth) {
1750
+ return Math.round(Math.min(Math.max(width, SIDEBAR_MIN), maxSidebarWidth(viewportWidth)));
1751
+ }
1752
+
1753
+ // src/client/shell.ts
1754
+ var PREFIX = (() => {
1755
+ const script = document.currentScript;
1756
+ try {
1757
+ return new URL(script.src).pathname.replace(/\/shell\.js$/, "");
1758
+ } catch {
1759
+ return "/__ezdesign";
1760
+ }
1761
+ })();
1762
+ var API = `${PREFIX}/api`;
1763
+ var MOD_LABEL = modLabel(navigator.userAgent);
1764
+ var PAGE_PATH = new URLSearchParams(location.search).get("path") || "/";
1765
+ var ANNOTATE_MODES = [
1766
+ {
1767
+ id: "element",
1768
+ label: "\u5143\u7D20",
1769
+ hint: "\u9EDE\u4E00\u4E0B\u5143\u7D20\u6216\u53CD\u767D\u6587\u5B57\u4F86\u7559\u8A00",
1770
+ key: "E",
1771
+ svg: CursorMagicSelection02Icon_default
1772
+ },
1773
+ {
1774
+ id: "region",
1775
+ label: "\u7BC4\u570D",
1776
+ hint: "\u62D6\u66F3\u6846\u51FA\u4E00\u584A\u7BC4\u570D\u4F86\u7559\u8A00\uFF0C\u6846\u5230\u7684\u5143\u7D20\u90FD\u6703\u5E36\u7D66 agent",
1777
+ key: "R",
1778
+ svg: Select01Icon_default
1779
+ }
1780
+ ];
1781
+ var expandedBatches = /* @__PURE__ */ new Set();
1782
+ var ITEM_LIMIT = 3;
1783
+ var EDIT_ICON = 10;
1784
+ var TICK_ICON = 14;
1785
+ var snapshot = null;
1786
+ var annotateMode = "off";
1787
+ var deviceId = DESKTOP.id;
1788
+ var multi = false;
1789
+ var layout = defaultLayout(CANVAS_DEVICES.filter((d) => CANVAS_DEFAULT.includes(d.id)));
1790
+ function shownDevices() {
1791
+ const on = CANVAS_DEVICES.filter((d) => layout.some((row) => row.includes(d.id)));
1792
+ return on.length ? on : [CANVAS_DEVICES[0]];
1793
+ }
1794
+ var canvasDevice = (id) => CANVAS_DEVICES.find((d) => d.id === id) ?? DESKTOP;
1795
+ var canvasLayout = () => layout.map((row) => row.map(canvasDevice));
1796
+ var VIEW_KEY = "ezdesign:view";
1797
+ function loadView() {
1798
+ try {
1799
+ const stored = JSON.parse(localStorage.getItem(VIEW_KEY) ?? "null");
1800
+ if (typeof stored?.device === "string") deviceId = deviceById(stored.device).id;
1801
+ multi = Boolean(stored?.multi);
1802
+ const arranged = sanitizeLayout(
1803
+ stored?.layout,
1804
+ CANVAS_DEVICES.map((d) => d.id)
1805
+ );
1806
+ if (arranged) layout = arranged;
1807
+ else if (Array.isArray(stored?.shown)) {
1808
+ const kept = CANVAS_DEVICES.filter((d) => stored.shown.includes(d.id));
1809
+ if (kept.length) layout = defaultLayout(kept);
1810
+ }
1811
+ } catch {
1812
+ }
1813
+ }
1814
+ function saveView() {
1815
+ try {
1816
+ localStorage.setItem(VIEW_KEY, JSON.stringify({ device: deviceId, multi, layout }));
1817
+ } catch {
1818
+ }
1819
+ }
1820
+ loadView();
1821
+ var root = document.getElementById("ez-shell");
1822
+ function h(tag, className, text) {
1823
+ const node = document.createElement(tag);
1824
+ if (className) node.className = className;
1825
+ if (text !== void 0) node.textContent = text;
1826
+ return node;
1827
+ }
1828
+ var brand = h("div", "ez-brand", "ezdesign");
1829
+ var version = h("span", "ez-version");
1830
+ var updatePill = h("button", "ez-update-flag");
1831
+ updatePill.setAttribute("aria-haspopup", "menu");
1832
+ updatePill.setAttribute("aria-expanded", "false");
1833
+ var updateMenu = h("div", "ez-menu");
1834
+ updateMenu.setAttribute("role", "menu");
1835
+ var updateNow = h("button", "ez-menu-item");
1836
+ updateNow.setAttribute("role", "menuitem");
1837
+ updateMenu.append(updateNow);
1838
+ var updateHint = h("div", "ez-update-hint");
1839
+ updateHint.hidden = true;
1840
+ updateHint.append(updatePill, updateMenu);
1841
+ var updateMenuOpen = false;
1842
+ function paintUpdateMenu() {
1843
+ updateHint.toggleAttribute("data-open", updateMenuOpen);
1844
+ updatePill.setAttribute("aria-expanded", String(updateMenuOpen));
1845
+ }
1846
+ updatePill.onclick = () => {
1847
+ updateMenuOpen = !updateMenuOpen;
1848
+ paintUpdateMenu();
1849
+ };
1850
+ updateNow.onclick = () => {
1851
+ updateMenuOpen = false;
1852
+ paintUpdateMenu();
1853
+ void startUpdate();
1854
+ };
1855
+ document.addEventListener("click", (e) => {
1856
+ if (updateMenuOpen && e.target instanceof Node && !updateHint.contains(e.target)) {
1857
+ updateMenuOpen = false;
1858
+ paintUpdateMenu();
1859
+ }
1860
+ });
1861
+ var target = h("span", "ez-target");
1862
+ var agentStatus = h("div", "ez-badge");
1863
+ var agentPill = h("button", "ez-agent-pill");
1864
+ agentPill.setAttribute("aria-haspopup", "menu");
1865
+ agentPill.setAttribute("aria-expanded", "false");
1866
+ var agentMenu = h("div", "ez-menu ez-menu-up");
1867
+ agentMenu.setAttribute("role", "menu");
1868
+ agentMenu.setAttribute("aria-label", "Agent");
1869
+ var agentWrap = h("div", "ez-agent");
1870
+ agentWrap.hidden = true;
1871
+ agentWrap.append(agentPill, agentMenu);
1872
+ var plain = (key) => (e) => !e.metaKey && !e.ctrlKey && e.key.toLowerCase() === key.toLowerCase();
1873
+ var cmd = (key) => (e) => (e.metaKey || e.ctrlKey) && e.key === key;
1874
+ function isTyping(target2) {
1875
+ if (!(target2 instanceof HTMLElement)) return false;
1876
+ return target2.isContentEditable || target2 instanceof HTMLInputElement || target2 instanceof HTMLTextAreaElement;
1877
+ }
1878
+ var SHORTCUTS = [
1879
+ ...ANNOTATE_MODES.map((m) => ({
1880
+ keys: m.key,
1881
+ label: m.label,
1882
+ match: plain(m.key),
1883
+ run: () => sendMode(annotateMode === m.id ? "off" : m.id)
1884
+ })),
1885
+ ...DEVICES.map((d, i) => ({
1886
+ keys: String(i + 1),
1887
+ label: deviceLabel(d),
1888
+ match: plain(String(i + 1)),
1889
+ run: () => setDevice(d.id)
1890
+ })),
1891
+ {
1892
+ keys: "4",
1893
+ label: "\u6240\u6709\u5C3A\u5BF8\u6392\u6210\u4E00\u5F35\u756B\u5E03",
1894
+ match: plain("4"),
1895
+ run: () => setMulti(!multi)
1896
+ },
1897
+ {
1898
+ keys: "N",
1899
+ label: "\u5BEB\u88DC\u5145\u8AAA\u660E",
1900
+ match: plain("n"),
1901
+ run: () => note.focus()
1902
+ },
1903
+ {
1904
+ keys: "\u2318 \u21B5",
1905
+ label: "\u9001\u51FA\u7D66 agent",
1906
+ match: cmd("Enter"),
1907
+ run: () => void sendBatch(),
1908
+ whileTyping: true
1909
+ },
1910
+ {
1911
+ keys: `${MOD_LABEL} .`,
1912
+ label: "\u4E2D\u6B62 agent \u9019\u4E00\u8F2A",
1913
+ match: cmd("."),
1914
+ run: () => void cancelTurn(),
1915
+ /** The reason to stop the agent usually arrives while the next batch is being
1916
+ * typed - watching it head the wrong way is what prompts it. */
1917
+ whileTyping: true
1918
+ },
1919
+ {
1920
+ keys: "Esc",
1921
+ label: "\u95DC\u6389\u8F38\u5165\u6846\uFF0C\u518D\u4E00\u6B21\u96E2\u958B\u6A19\u8A3B\u6A21\u5F0F",
1922
+ match: plain("Escape"),
1923
+ /** Blurring first is what makes the rest of the table reachable again: every
1924
+ * modifier-free key is withheld while a field has focus, so without this the
1925
+ * keyboard has no way back out of the composer. */
1926
+ run: () => {
1927
+ if (keysPinned) {
1928
+ pinKeys(false);
1929
+ return;
1930
+ }
1931
+ if (abortPick("escape")) return;
1932
+ const active = document.activeElement;
1933
+ if (isTyping(active)) {
1934
+ if (active instanceof HTMLElement) active.blur();
1935
+ return;
1936
+ }
1937
+ escapeAnnotating();
1938
+ },
1939
+ whileTyping: true
1940
+ }
1941
+ ];
1942
+ function runShortcut(e, typing) {
1943
+ for (const s of SHORTCUTS) {
1944
+ if (typing && !s.whileTyping) continue;
1945
+ if (!s.match(e)) continue;
1946
+ s.run();
1947
+ return true;
1948
+ }
1949
+ return false;
1950
+ }
1951
+ var keysBtn = h("button", "ez-keys");
1952
+ keysBtn.append(icon(KeyboardIcon_default, 14));
1953
+ keysBtn.title = "\u5FEB\u6377\u9375";
1954
+ keysBtn.setAttribute("aria-label", "\u5FEB\u6377\u9375");
1955
+ keysBtn.setAttribute("aria-expanded", "false");
1956
+ var keysPinned = false;
1957
+ var keysHover = false;
1958
+ var keysCloseTimer = 0;
1959
+ var KEYS_CLOSE_MS = 140;
1960
+ function paintKeys() {
1961
+ const open = keysPinned || keysHover;
1962
+ keysWrap.toggleAttribute("data-open", open);
1963
+ keysBtn.setAttribute("aria-expanded", String(open));
1964
+ }
1965
+ function pinKeys(next) {
1966
+ keysPinned = next;
1967
+ paintKeys();
1968
+ }
1969
+ keysBtn.onclick = () => pinKeys(!keysPinned);
1970
+ var keysPanel = h("div", "ez-keys-panel");
1971
+ for (const s of SHORTCUTS) {
1972
+ const row = h("div", "ez-keys-row");
1973
+ row.append(h("kbd", "ez-kbd", s.keys), h("span", "ez-keys-label", s.label));
1974
+ keysPanel.appendChild(row);
1975
+ }
1976
+ var keysWrap = h("div", "ez-keys-wrap");
1977
+ keysWrap.append(keysBtn, keysPanel);
1978
+ keysWrap.addEventListener("pointerenter", () => {
1979
+ window.clearTimeout(keysCloseTimer);
1980
+ keysHover = true;
1981
+ paintKeys();
1982
+ });
1983
+ keysWrap.addEventListener("pointerleave", () => {
1984
+ window.clearTimeout(keysCloseTimer);
1985
+ keysCloseTimer = window.setTimeout(() => {
1986
+ keysHover = false;
1987
+ paintKeys();
1988
+ }, KEYS_CLOSE_MS);
1989
+ });
1990
+ document.addEventListener("click", (e) => {
1991
+ if (keysPinned && e.target instanceof Node && !keysWrap.contains(e.target)) pinKeys(false);
1992
+ });
1993
+ var headRow = h("div", "ez-head-row");
1994
+ headRow.append(brand, version, updateHint, h("div", "ez-spacer"), keysWrap, agentStatus);
1995
+ var deviceName = h("button", "ez-seg-label");
1996
+ deviceName.onclick = () => setDevice(deviceId);
1997
+ var deviceCaret = h("button", "ez-seg-caret");
1998
+ deviceCaret.append(icon(ChevronDownIcon_default, 12));
1999
+ deviceCaret.setAttribute("aria-haspopup", "menu");
2000
+ deviceCaret.setAttribute("aria-expanded", "false");
2001
+ var deviceMenu = h("div", "ez-menu");
2002
+ deviceMenu.setAttribute("role", "menu");
2003
+ deviceMenu.setAttribute("aria-label", "\u9810\u89BD\u5C3A\u5BF8");
2004
+ var deviceItems = DEVICES.map((d) => {
2005
+ const item = h("button", "ez-menu-item");
2006
+ item.setAttribute("role", "menuitemradio");
2007
+ item.dataset.device = d.id;
2008
+ item.title = deviceLabel(d);
2009
+ item.append(h("span", "ez-menu-name", d.name), menuCheck());
2010
+ item.onclick = () => {
2011
+ closeDeviceMenu();
2012
+ setDevice(d.id);
2013
+ };
2014
+ deviceMenu.appendChild(item);
2015
+ return item;
2016
+ });
2017
+ var deviceGroup = h("div", "ez-seg-item ez-seg-device");
2018
+ deviceGroup.append(deviceName, deviceCaret, deviceMenu);
2019
+ var deviceMenuOpen = false;
2020
+ function paintDeviceMenu() {
2021
+ deviceGroup.toggleAttribute("data-open", deviceMenuOpen);
2022
+ deviceCaret.setAttribute("aria-expanded", String(deviceMenuOpen));
2023
+ }
2024
+ function closeDeviceMenu() {
2025
+ if (!deviceMenuOpen) return;
2026
+ deviceMenuOpen = false;
2027
+ paintDeviceMenu();
2028
+ }
2029
+ function openDeviceMenu() {
2030
+ if (deviceMenuOpen) return;
2031
+ deviceMenuOpen = true;
2032
+ paintDeviceMenu();
2033
+ deviceItems.find((i) => i.dataset.device === deviceId)?.focus();
2034
+ }
2035
+ deviceCaret.onclick = () => {
2036
+ if (deviceMenuOpen) closeDeviceMenu();
2037
+ else openDeviceMenu();
2038
+ };
2039
+ deviceMenu.addEventListener("keydown", (e) => {
2040
+ const at2 = deviceItems.indexOf(document.activeElement);
2041
+ if (e.key === "ArrowDown" || e.key === "ArrowUp") {
2042
+ const step = e.key === "ArrowDown" ? 1 : -1;
2043
+ deviceItems[(at2 + step + deviceItems.length) % deviceItems.length]?.focus();
2044
+ e.preventDefault();
2045
+ return;
2046
+ }
2047
+ if (e.key === "Escape") {
2048
+ closeDeviceMenu();
2049
+ deviceCaret.focus();
2050
+ e.preventDefault();
2051
+ }
2052
+ });
2053
+ document.addEventListener("click", (e) => {
2054
+ if (e.target instanceof Node && !deviceGroup.contains(e.target)) closeDeviceMenu();
2055
+ });
2056
+ deviceGroup.addEventListener("focusout", (e) => {
2057
+ const to = e.relatedTarget;
2058
+ if (to instanceof Node && deviceGroup.contains(to)) return;
2059
+ closeDeviceMenu();
2060
+ });
2061
+ var multiBtn = h("button", "ez-seg-item ez-seg-multi", "\u5168\u90E8\u5C3A\u5BF8");
2062
+ multiBtn.title = "\u6240\u6709\u5C3A\u5BF8\u6392\u6210\u4E00\u5F35\u756B\u5E03\uFF0C\u62D6\u66F3\u80CC\u666F\u79FB\u52D5\uFF084\uFF09";
2063
+ multiBtn.onclick = () => setMulti(!multi);
2064
+ var deviceRow = h("div", "ez-seg");
2065
+ deviceRow.append(deviceGroup, multiBtn);
2066
+ function paintControls() {
2067
+ const device = deviceById(deviceId);
2068
+ deviceName.textContent = device.name;
2069
+ deviceName.title = `${deviceLabel(device)}\uFF081 / 2 / 3\uFF09`;
2070
+ deviceCaret.title = "\u63DB\u4E00\u500B\u5C3A\u5BF8";
2071
+ for (const item of deviceItems) {
2072
+ const on = !multi && item.dataset.device === deviceId;
2073
+ item.setAttribute("aria-checked", String(on));
2074
+ item.toggleAttribute("data-current", on);
2075
+ }
2076
+ deviceGroup.classList.toggle("ez-on", !multi);
2077
+ multiBtn.classList.toggle("ez-on", multi);
2078
+ multiBtn.setAttribute("aria-pressed", String(multi));
2079
+ paintShown();
2080
+ }
2081
+ var annotateGroup = h("div", "ez-annotate-group");
2082
+ var annotateBtns = ANNOTATE_MODES.map((m) => {
2083
+ const btn = h("button", `ez-tool-btn ez-mode-${m.id}`);
2084
+ btn.append(icon(m.svg, 15), h("span", void 0, m.label));
2085
+ btn.title = `${m.hint}\uFF08${m.key}\uFF09\u30FBEsc \u96E2\u958B`;
2086
+ btn.onclick = () => sendMode(annotateMode === m.id ? "off" : m.id);
2087
+ annotateGroup.appendChild(btn);
2088
+ return { id: m.id, btn };
2089
+ });
2090
+ function paintAnnotateMode() {
2091
+ for (const { id, btn } of annotateBtns) {
2092
+ const on = annotateMode === id;
2093
+ btn.classList.toggle("ez-on", on);
2094
+ btn.setAttribute("aria-pressed", String(on));
2095
+ }
2096
+ }
2097
+ var sideHead = h("div", "ez-side-head");
2098
+ sideHead.append(headRow, target, deviceRow, annotateGroup);
2099
+ var banner2 = h("div", "ez-banner");
2100
+ banner2.hidden = true;
2101
+ var updateCard = h("section", "ez-update");
2102
+ updateCard.hidden = true;
2103
+ var UPDATE_DISMISSED_KEY = "ez-update-dismissed";
2104
+ function updateOfferKey(u) {
2105
+ return `v${u.latest}`;
2106
+ }
2107
+ function readUpdateDismissed() {
2108
+ try {
2109
+ return localStorage.getItem(UPDATE_DISMISSED_KEY);
2110
+ } catch {
2111
+ return null;
2112
+ }
2113
+ }
2114
+ function setUpdateDismissed(key) {
2115
+ try {
2116
+ if (key) localStorage.setItem(UPDATE_DISMISSED_KEY, key);
2117
+ else localStorage.removeItem(UPDATE_DISMISSED_KEY);
2118
+ } catch {
2119
+ }
2120
+ render();
2121
+ }
2122
+ var updating = false;
2123
+ async function startUpdate() {
2124
+ if (updating) return;
2125
+ updating = true;
2126
+ setUpdateDismissed(null);
2127
+ try {
2128
+ await api("/update", { method: "POST" });
2129
+ } finally {
2130
+ updating = false;
2131
+ render();
2132
+ }
2133
+ }
2134
+ function updateTitle(u) {
2135
+ switch (u.phase) {
2136
+ case "installing":
2137
+ return `\u6B63\u5728\u66F4\u65B0\u5230 v${u.latest}`;
2138
+ case "handing-over":
2139
+ return `\u6B63\u5728\u5207\u63DB\u5230 v${u.latest}`;
2140
+ case "failed":
2141
+ return "\u66F4\u65B0\u5931\u6557";
2142
+ default:
2143
+ return `\u6709\u65B0\u7248\u672C v${u.latest}`;
2144
+ }
2145
+ }
2146
+ function updateView(u) {
2147
+ if (!updating || u.phase !== "available" && u.phase !== "failed") return u;
2148
+ const { error: _error, ...rest } = u;
2149
+ return { ...rest, phase: "installing" };
2150
+ }
2151
+ function paintUpdate(s) {
2152
+ const u = s.update && updateView(s.update);
2153
+ const dismissible = u?.phase === "available" || u?.phase === "failed";
2154
+ const dismissed = !!u && dismissible && readUpdateDismissed() === updateOfferKey(u);
2155
+ updateHint.hidden = !dismissed;
2156
+ if (dismissed) {
2157
+ updatePill.textContent = `v${u.latest} \u53EF\u66F4\u65B0`;
2158
+ updateNow.textContent = `\u66F4\u65B0\u5230 v${u.latest}`;
2159
+ updateNow.disabled = updating;
2160
+ } else if (updateMenuOpen) {
2161
+ updateMenuOpen = false;
2162
+ }
2163
+ paintUpdateMenu();
2164
+ updateCard.hidden = !u || !!dismissed;
2165
+ updateCard.textContent = "";
2166
+ if (!u || dismissed) return;
2167
+ updateCard.append(h("div", "ez-update-title", updateTitle(u)));
2168
+ const busy = u.phase !== "available" && u.phase !== "failed";
2169
+ const lines = h("ul", "ez-update-lines");
2170
+ const line = (text, cls = "") => lines.append(h("li", cls || void 0, text));
2171
+ const working = s.acp?.state === "working";
2172
+ if (busy) {
2173
+ if (u.phase === "installing") line("\u4E0B\u8F09\u4E26\u5B89\u88DD\u65B0\u7248\u672C\u2026");
2174
+ if (u.phase === "handing-over") line("\u65B0\u7684 daemon \u63A5\u624B\u4E2D\uFF0C\u756B\u9762\u6703\u81EA\u52D5\u91CD\u65B0\u8F09\u5165");
2175
+ } else {
2176
+ if (s.acp) line("\u66F4\u65B0\u5B8C\u6210\u5F8C\uFF0C\u6703\u958B\u555F\u65B0 session");
2177
+ if (u.phase === "failed" && u.error) line(u.error, "ez-update-error");
2178
+ }
2179
+ if (lines.childElementCount) updateCard.append(lines);
2180
+ if (!busy) {
2181
+ const actions = h("div", "ez-update-actions");
2182
+ const go = h("button", "ez-update-go");
2183
+ go.append(
2184
+ h(
2185
+ "span",
2186
+ "ez-update-label",
2187
+ u.phase === "failed" ? "\u91CD\u8A66" : working ? "\u4ECD\u8981\u66F4\u65B0" : "\u7ACB\u5373\u66F4\u65B0"
2188
+ )
2189
+ );
2190
+ go.disabled = updating;
2191
+ go.onclick = () => void startUpdate();
2192
+ actions.append(go);
2193
+ updateCard.append(actions);
2194
+ const dismiss = h("button", "ez-update-x");
2195
+ dismiss.append(icon(Cancel01Icon_default, 13));
2196
+ dismiss.title = "\u7A0D\u5F8C\u518D\u8AAA\uFF08\u7248\u865F\u65C1\u7684\u63D0\u793A\u6703\u7559\u8457\uFF09";
2197
+ dismiss.setAttribute("aria-label", dismiss.title);
2198
+ dismiss.onclick = () => setUpdateDismissed(updateOfferKey(u));
2199
+ updateCard.append(dismiss);
2200
+ }
2201
+ }
2202
+ var stageWrap = h("div", "ez-stage-wrap");
2203
+ var stage = h("div", "ez-stage");
2204
+ var canvas = h("div", "ez-canvas");
2205
+ stage.appendChild(canvas);
2206
+ var strip = h("div", "ez-strip");
2207
+ strip.hidden = true;
2208
+ function stripNotice(text) {
2209
+ strip.hidden = false;
2210
+ const pill = h("div", "ez-strip-pill");
2211
+ pill.append(icon(MagicWand01Icon_default, 14), h("div", "ez-strip-pending", text));
2212
+ strip.replaceChildren(pill);
2213
+ setTimeout(() => {
2214
+ if (!liveExplores().length) strip.hidden = true;
2215
+ }, 4e3);
2216
+ }
2217
+ var posted = /* @__PURE__ */ new Map();
2218
+ function liveExplores() {
2219
+ return snapshot?.explores ?? [];
2220
+ }
2221
+ function paintVariants() {
2222
+ const rounds = liveExplores();
2223
+ for (const round of rounds) {
2224
+ const variant = round.variants.find((v) => v.id === round.selected);
2225
+ const html = variant?.html ?? null;
2226
+ if (posted.get(round.id) === (variant?.id ?? null)) continue;
2227
+ posted.set(round.id, variant?.id ?? null);
2228
+ broadcast({ type: "ez:variant", exploreId: round.id, anchor: round.anchor, html });
2229
+ }
2230
+ for (const id of [...posted.keys()]) {
2231
+ if (rounds.some((r) => r.id === id)) continue;
2232
+ posted.delete(id);
2233
+ broadcast({ type: "ez:variant", exploreId: id, anchor: null, html: null });
2234
+ }
2235
+ }
2236
+ function seedVariants(id) {
2237
+ for (const round of liveExplores()) {
2238
+ const variant = round.variants.find((v) => v.id === round.selected);
2239
+ toFrame(id, {
2240
+ type: "ez:variant",
2241
+ exploreId: round.id,
2242
+ anchor: round.anchor,
2243
+ html: variant?.html ?? null
2244
+ });
2245
+ }
2246
+ }
2247
+ function select(round, variantId) {
2248
+ void api("/explore/select", {
2249
+ method: "POST",
2250
+ body: JSON.stringify({ id: round.id, variantId })
2251
+ });
2252
+ }
2253
+ var stripDrawn = null;
2254
+ function paintStrip() {
2255
+ const rounds = liveExplores();
2256
+ strip.hidden = rounds.length === 0;
2257
+ const signature = JSON.stringify([currentChatId(), snapshot?.agentBusy ?? false, rounds]);
2258
+ if (signature === stripDrawn) return;
2259
+ stripDrawn = signature;
2260
+ strip.replaceChildren(...rounds.map((round) => roundPill(round)));
2261
+ }
2262
+ function currentChatId() {
2263
+ return snapshot?.chats?.find((c) => c.current)?.id ?? null;
2264
+ }
2265
+ async function adoptVariant(round, variant) {
2266
+ const ref = {
2267
+ anchor: round.anchor,
2268
+ label: round.label,
2269
+ variant: { name: variant.name, html: variant.html }
2270
+ };
2271
+ const held = exploreHold?.round === round.id ? exploreHold : null;
2272
+ if (held) held.leaving = true;
2273
+ const res = await api("/explore/exit", {
2274
+ method: "POST",
2275
+ body: JSON.stringify({ id: round.id, variantId: variant.id })
2276
+ });
2277
+ if (!res.ok) {
2278
+ if (held) held.leaving = false;
2279
+ return;
2280
+ }
2281
+ if (held) held.answer = ref;
2282
+ else {
2283
+ noteAttach.insertRef(ref);
2284
+ noteAttach.editable.focus();
2285
+ }
2286
+ }
2287
+ var exploreHold = null;
2288
+ function endExploreIn(ref) {
2289
+ const held = exploreHold;
2290
+ exploreHold = null;
2291
+ if (held && frames.has(held.frame)) {
2292
+ if (held.mode !== "off") toFrame(held.frame, { type: "ez:set-mode", mode: held.mode });
2293
+ const body = ref ? [...held.draft.body, { t: "ref", n: nextRefNumber(held.draft.body), ...ref }] : held.draft.body;
2294
+ toFrame(held.frame, { type: "ez:restore", draft: { ...held.draft, body } });
2295
+ return;
2296
+ }
2297
+ if (!ref) return;
2298
+ noteAttach.insertRef(ref);
2299
+ noteAttach.editable.focus();
2300
+ }
2301
+ function settleHold() {
2302
+ const held = exploreHold;
2303
+ if (!held?.round) return;
2304
+ if (liveExplores().some((e) => e.id === held.round)) {
2305
+ held.seen = true;
2306
+ return;
2307
+ }
2308
+ if (!held.seen) return;
2309
+ if (held.leaving && held.answer === void 0) return;
2310
+ endExploreIn(held.answer ?? void 0);
2311
+ }
2312
+ async function leaveRound(round) {
2313
+ const held = exploreHold?.round === round.id ? exploreHold : null;
2314
+ if (held) held.leaving = true;
2315
+ const res = await api("/explore/exit", {
2316
+ method: "POST",
2317
+ body: JSON.stringify({ id: round.id })
2318
+ });
2319
+ if (!held) return;
2320
+ if (res.ok) held.answer = null;
2321
+ else held.leaving = false;
2322
+ }
2323
+ var stripAsk = null;
2324
+ function closeStripAsk() {
2325
+ const close = stripAsk;
2326
+ stripAsk = null;
2327
+ close?.();
2328
+ }
2329
+ function askOnStrip(actions, offer, run) {
2330
+ if (confirmSkipped(localStorage, offer.key)) {
2331
+ run();
2332
+ return;
2333
+ }
2334
+ closeStripAsk();
2335
+ const card = h("div", "ez-strip-ask");
2336
+ card.setAttribute("role", "dialog");
2337
+ card.setAttribute("aria-label", offer.go);
2338
+ const skip = h("input");
2339
+ skip.type = "checkbox";
2340
+ const skipLabel = h("label", "ez-strip-ask-skip");
2341
+ skipLabel.append(skip, h("span", void 0, "\u4E0D\u8981\u518D\u63D0\u793A"));
2342
+ const go = h("button", "ez-strip-ask-go", offer.go);
2343
+ const no = h("button", "ez-strip-ask-no", "\u53D6\u6D88");
2344
+ const row = h("div", "ez-strip-ask-actions");
2345
+ row.append(go, no);
2346
+ card.append(h("div", void 0, offer.body), row, skipLabel);
2347
+ const outside = (e) => {
2348
+ if (e.target instanceof Node && !card.contains(e.target)) done3(false);
2349
+ };
2350
+ const done3 = (ok) => {
2351
+ document.removeEventListener("pointerdown", outside, true);
2352
+ card.remove();
2353
+ stripAsk = null;
2354
+ rememberConfirm(localStorage, offer.key, { ok, skip: skip.checked });
2355
+ if (ok) run();
2356
+ };
2357
+ go.onclick = () => done3(true);
2358
+ no.onclick = () => done3(false);
2359
+ card.onkeydown = (e) => {
2360
+ if (e.key !== "Escape") return;
2361
+ e.stopPropagation();
2362
+ done3(false);
2363
+ };
2364
+ skip.addEventListener("change", () => go.focus());
2365
+ stripAsk = () => {
2366
+ document.removeEventListener("pointerdown", outside, true);
2367
+ card.remove();
2368
+ };
2369
+ document.addEventListener("pointerdown", outside, true);
2370
+ actions.append(card);
2371
+ go.focus();
2372
+ }
2373
+ function roundPill(round) {
2374
+ const pill = h("div", "ez-strip-pill");
2375
+ pill.title = round.direction ? `${round.label}\uFF1A${round.direction}` : round.label;
2376
+ pill.setAttribute("role", "group");
2377
+ pill.setAttribute("aria-label", `\u63A2\u7D22 ${round.label}`);
2378
+ pill.append(icon(MagicWand01Icon_default, 14));
2379
+ const chips = h("div", "ez-strip-chips");
2380
+ const original = h("button", `ez-strip-chip${round.selected === null ? " ez-on" : ""}`, "\u539F\u672C");
2381
+ original.title = "\u9019\u500B\u5143\u7D20\u539F\u672C\u7684\u6A23\u5B50";
2382
+ original.onclick = () => select(round, null);
2383
+ chips.append(original);
2384
+ for (const variant of round.variants) {
2385
+ const on = variant.id === round.selected;
2386
+ const chip = h("button", `ez-strip-chip${on ? " ez-on" : ""}`, variant.name);
2387
+ chip.title = variant.note ? `${variant.name}\uFF1A${variant.note}` : variant.name;
2388
+ chip.onclick = () => select(round, variant.id);
2389
+ chips.append(chip);
2390
+ }
2391
+ if (round.status === "generating") {
2392
+ chips.append(h("div", "ez-strip-pending", round.variants.length ? "\u9084\u5728\u60F3\u2026" : "\u6B63\u5728\u7522\u751F\u2026"));
2393
+ }
2394
+ pill.append(chips);
2395
+ const actions = h("div", "ez-strip-actions");
2396
+ const chosen = round.variants.find((v) => v.id === round.selected);
2397
+ const adopt = h("button", "ez-strip-action", "\u9078\u64C7");
2398
+ adopt.disabled = !chosen;
2399
+ adopt.title = chosen ? `\u5E36\u300C${chosen.name}\u300D\u56DE\u4E3B\u5C0D\u8A71\uFF0C\u4EA4\u7D66 agent \u5BE6\u4F5C\u9032\u5C08\u6848` : "\u5148\u6311\u4E00\u500B\u8B8A\u9AD4\uFF0C\u624D\u6709\u6771\u897F\u53EF\u4EE5\u5E36\u56DE\u53BB";
2400
+ adopt.onclick = () => askOnStrip(
2401
+ actions,
2402
+ {
2403
+ key: "explore-adopt",
2404
+ body: "\u96E2\u958B\u9019\u4E00\u8F2A\u63A2\u7D22\u56DE\u5230\u4E3B\u5C0D\u8A71\u3002\u4F60\u9078\u7684\u7248\u672C\u6703\u9644\u5728\u8A0A\u606F\u4E0A\uFF0C\u4EA4\u7D66 agent \u5BE6\u4F5C\u9032\u5C08\u6848\u3002",
2405
+ go: "\u9078\u64C7"
2406
+ },
2407
+ () => void adoptVariant(round, chosen)
2408
+ );
2409
+ actions.append(adopt);
2410
+ const quit = h("button", "ez-strip-action", "\u9000\u51FA\u63A2\u7D22");
2411
+ quit.title = "\u7D50\u675F\u9019\u4E00\u8F2A\u63A2\u7D22\uFF0C\u56DE\u5230\u4E3B\u5C0D\u8A71";
2412
+ quit.onclick = () => askOnStrip(
2413
+ actions,
2414
+ {
2415
+ key: "explore-exit",
2416
+ body: "\u96E2\u958B\u9019\u4E00\u8F2A\u63A2\u7D22\u56DE\u5230\u4E3B\u5C0D\u8A71\uFF0C\u9801\u9762\u56DE\u5230\u539F\u672C\u7684\u6A23\u5B50\uFF0C\u9019\u4E9B\u8B8A\u9AD4\u4E0D\u6703\u7559\u4E0B\u3002",
2417
+ go: "\u9000\u51FA\u63A2\u7D22"
2418
+ },
2419
+ () => void leaveRound(round)
2420
+ );
2421
+ actions.append(quit);
2422
+ pill.append(actions);
2423
+ return pill;
2424
+ }
2425
+ var shownBtn = h("button", "ez-shown-btn");
2426
+ shownBtn.append(icon(Grid02Icon_default, 14));
2427
+ shownBtn.title = "\u9078\u64C7\u756B\u5E03\u4E0A\u8981\u986F\u793A\u7684\u5C3A\u5BF8";
2428
+ shownBtn.setAttribute("aria-label", "\u9078\u64C7\u756B\u5E03\u4E0A\u8981\u986F\u793A\u7684\u5C3A\u5BF8");
2429
+ shownBtn.setAttribute("aria-haspopup", "menu");
2430
+ shownBtn.setAttribute("aria-expanded", "false");
2431
+ var shownMenu = h("div", "ez-menu ez-menu-right");
2432
+ shownMenu.setAttribute("role", "menu");
2433
+ shownMenu.setAttribute("aria-label", "\u756B\u5E03\u4E0A\u7684\u5C3A\u5BF8");
2434
+ var shownItems = CANVAS_DEVICES.map((d) => {
2435
+ const item = h("button", "ez-menu-item");
2436
+ item.setAttribute("role", "menuitemcheckbox");
2437
+ item.dataset.device = d.id;
2438
+ item.title = deviceLabel(d);
2439
+ const tick = h("span", "ez-menu-tick");
2440
+ tick.append(icon(CheckIcon_default, 14));
2441
+ item.append(tick, h("span", "ez-menu-name", d.name));
2442
+ item.onclick = () => toggleShown(d.id);
2443
+ shownMenu.appendChild(item);
2444
+ return item;
2445
+ });
2446
+ var shownWrap = h("div", "ez-shown");
2447
+ shownWrap.append(shownBtn, shownMenu);
2448
+ stageWrap.append(stage, shownWrap, strip);
2449
+ var shownMenuOpen = false;
2450
+ function paintShown() {
2451
+ shownWrap.toggleAttribute("data-open", shownMenuOpen);
2452
+ shownWrap.toggleAttribute("data-hidden", !multi);
2453
+ shownBtn.setAttribute("aria-expanded", String(shownMenuOpen));
2454
+ const on = shownDevices();
2455
+ for (const item of shownItems) {
2456
+ const checked = on.some((d) => d.id === item.dataset.device);
2457
+ item.setAttribute("aria-checked", String(checked));
2458
+ item.classList.toggle("ez-on", checked);
2459
+ item.toggleAttribute("disabled", checked && on.length === 1);
2460
+ }
2461
+ }
2462
+ function toggleShown(id) {
2463
+ const next = toggleDevice(layout, id);
2464
+ if (next === layout) return;
2465
+ layout = next;
2466
+ saveView();
2467
+ paintShown();
2468
+ if (!multi) return;
2469
+ const mounted = frames.get(id);
2470
+ if (mounted) {
2471
+ abortPick("mode");
2472
+ mounted.card.remove();
2473
+ frames.delete(id);
2474
+ if (popupFrame === id) setPopupFrame(null);
2475
+ } else {
2476
+ const device = canvasDevice(id);
2477
+ const holder = document.createDocumentFragment();
2478
+ const frame = mountFrame(id, device, holder);
2479
+ const placed = planCanvas(canvasLayout(), canvasMetrics()).cards.find((c) => c.id === id);
2480
+ if (placed) {
2481
+ frame.card.style.left = `${placed.x}px`;
2482
+ frame.card.style.top = `${placed.y}px`;
2483
+ }
2484
+ paintFrame(frame, device, device.height, CANVAS_ZOOM, false);
2485
+ canvas.appendChild(holder);
2486
+ }
2487
+ settleFrames();
2488
+ }
2489
+ shownBtn.onclick = () => {
2490
+ shownMenuOpen = !shownMenuOpen;
2491
+ paintShown();
2492
+ };
2493
+ document.addEventListener("click", (e) => {
2494
+ if (e.target instanceof Node && !shownWrap.contains(e.target) && shownMenuOpen) {
2495
+ shownMenuOpen = false;
2496
+ paintShown();
2497
+ }
2498
+ });
2499
+ shownWrap.addEventListener("keydown", (e) => {
2500
+ if (e.key !== "Escape" || !shownMenuOpen) return;
2501
+ shownMenuOpen = false;
2502
+ paintShown();
2503
+ shownBtn.focus();
2504
+ e.preventDefault();
2505
+ });
2506
+ var SINGLE = "single";
2507
+ var frames = /* @__PURE__ */ new Map();
2508
+ var popupFrame = null;
2509
+ function setPopupFrame(next) {
2510
+ popupFrame = next;
2511
+ for (const f of frames.values()) {
2512
+ if (f.id !== next) toFrame(f.id, { type: "ez:hold", held: next !== null });
2513
+ }
2514
+ }
2515
+ var nav = { url: PAGE_PATH };
2516
+ function dispatchNav(e) {
2517
+ const { state, effects } = reduceNav(nav, e);
2518
+ const moved = state.url !== nav.url;
2519
+ nav = state;
2520
+ for (const effect of effects) applyNavEffect(effect);
2521
+ if (moved) paintPage();
2522
+ }
2523
+ function applyNavEffect(effect) {
2524
+ switch (effect.do) {
2525
+ // The address bar carries the live page, not the one the session started
2526
+ // on: the shell mounts its frames from the query string, so a reload three
2527
+ // pages deep has to come back to the third page and not the first. The
2528
+ // state carries it too, because on the way back that is all there is - the
2529
+ // frames that were on those pages are long gone.
2530
+ case "push":
2531
+ history.pushState({ ezUrl: effect.url }, "", shellUrl(effect.url));
2532
+ return;
2533
+ case "replace":
2534
+ history.replaceState({ ezUrl: effect.url }, "", shellUrl(effect.url));
2535
+ return;
2536
+ case "navigate":
2537
+ for (const f of frames.values()) {
2538
+ if (f.id !== effect.except) navigateFrame(f, effect.url);
2539
+ }
2540
+ return;
2541
+ }
2542
+ }
2543
+ function shellUrl(page) {
2544
+ const url = new URL(location.href);
2545
+ url.searchParams.set("path", page);
2546
+ return url;
2547
+ }
2548
+ history.replaceState({ ezUrl: nav.url }, "", shellUrl(nav.url));
2549
+ window.addEventListener("popstate", (e) => {
2550
+ const state = e.state;
2551
+ const path = new URLSearchParams(location.search).get("path");
2552
+ dispatchNav({ t: "pop", url: state?.ezUrl ?? path ?? "/" });
2553
+ });
2554
+ function paintPage() {
2555
+ const origin = snapshot?.targetOrigin.replace(/^https?:\/\//, "") ?? "";
2556
+ target.textContent = `${origin}${nav.url}`;
2557
+ document.title = `ezdesign \xB7 ${nav.url}`;
2558
+ }
2559
+ function mountFrame(id, device, into) {
2560
+ const card = h("div", "ez-card");
2561
+ const head = h("div", "ez-card-head");
2562
+ head.title = "\u62D6\u66F3\u8ABF\u6574\u6392\u5217";
2563
+ const box = h("div", "ez-screen");
2564
+ const wrap = h("div", "ez-frame-wrap");
2565
+ const iframe = h("iframe", "ez-frame");
2566
+ iframe.src = nav.url;
2567
+ wrap.appendChild(iframe);
2568
+ box.appendChild(wrap);
2569
+ card.append(head, box);
2570
+ into.appendChild(card);
2571
+ const frame = { id, card, head, box, wrap, iframe, device, zoom: 0 };
2572
+ labelFrame(frame, device);
2573
+ frames.set(id, frame);
2574
+ head.addEventListener("pointerdown", (e) => beginCardDrag(e, frame));
2575
+ return frame;
2576
+ }
2577
+ function labelFrame(frame, device) {
2578
+ const label = deviceLabel(device);
2579
+ frame.head.textContent = label;
2580
+ frame.iframe.title = label;
2581
+ }
2582
+ function frameIdOf(source) {
2583
+ if (!source) return null;
2584
+ for (const f of frames.values()) if (f.iframe.contentWindow === source) return f.id;
2585
+ return null;
2586
+ }
2587
+ function toFrame(id, message) {
2588
+ frames.get(id)?.iframe.contentWindow?.postMessage(message, location.origin);
2589
+ }
2590
+ function broadcast(message) {
2591
+ for (const f of frames.values()) f.iframe.contentWindow?.postMessage(message, location.origin);
2592
+ }
2593
+ function navigateFrame(frame, page) {
2594
+ frame.iframe.contentWindow?.location.replace(page);
2595
+ }
2596
+ function stageBox() {
2597
+ const cs = getComputedStyle(stage);
2598
+ return {
2599
+ width: stage.clientWidth - parseFloat(cs.paddingLeft) - parseFloat(cs.paddingRight),
2600
+ height: stage.clientHeight - parseFloat(cs.paddingTop) - parseFloat(cs.paddingBottom)
2601
+ };
2602
+ }
2603
+ function paintFrame(frame, device, height, zoom, fluid) {
2604
+ const { card, box, wrap } = frame;
2605
+ card.style.width = fluid ? "100%" : "";
2606
+ card.style.height = fluid ? "100%" : "";
2607
+ box.style.width = fluid ? "100%" : `${Math.round(device.width * zoom)}px`;
2608
+ box.style.height = fluid ? "100%" : `${Math.round(height * zoom)}px`;
2609
+ wrap.style.width = fluid ? "100%" : `${device.width}px`;
2610
+ wrap.style.height = fluid ? "100%" : `${height}px`;
2611
+ wrap.style.transform = fluid || zoom === 1 ? "" : `scale(${zoom})`;
2612
+ const applied = fluid ? 1 : zoom;
2613
+ if (frame.device.id !== device.id || frame.zoom !== applied) {
2614
+ if (frame.device.id !== device.id) labelFrame(frame, device);
2615
+ frame.device = device;
2616
+ frame.zoom = applied;
2617
+ toFrame(frame.id, {
2618
+ type: "ez:viewport",
2619
+ preset: device.id,
2620
+ zoom: applied,
2621
+ scoped: multi
2622
+ });
2623
+ }
2624
+ }
2625
+ function paintFrames() {
2626
+ if (multi) {
2627
+ const plan = planCanvas(canvasLayout(), canvasMetrics());
2628
+ canvas.style.width = `${plan.width}px`;
2629
+ canvas.style.height = `${plan.height}px`;
2630
+ for (const placed of plan.cards) {
2631
+ const frame2 = frames.get(placed.id);
2632
+ if (!frame2) continue;
2633
+ frame2.card.style.left = `${placed.x}px`;
2634
+ frame2.card.style.top = `${placed.y}px`;
2635
+ const d = canvasDevice(placed.id);
2636
+ paintFrame(frame2, d, d.height, CANVAS_ZOOM, false);
2637
+ }
2638
+ return;
2639
+ }
2640
+ const frame = frames.get(SINGLE);
2641
+ if (!frame) return;
2642
+ const device = deviceById(deviceId);
2643
+ const fluid = Boolean(device.fluid);
2644
+ canvas.style.width = fluid ? "100%" : "";
2645
+ canvas.style.height = fluid ? "100%" : "";
2646
+ const box = stageBox();
2647
+ const zoom = fluid ? 1 : fitWidth(device.width, box.width);
2648
+ paintFrame(frame, device, zoom > 0 ? box.height / zoom : device.height, zoom, fluid);
2649
+ }
2650
+ var settleTimer = 0;
2651
+ function settleFrames() {
2652
+ clearTimeout(settleTimer);
2653
+ canvas.removeAttribute("data-settle");
2654
+ const before = /* @__PURE__ */ new Map();
2655
+ for (const f of frames.values()) before.set(f.id, f.card.getBoundingClientRect());
2656
+ for (const f of frames.values()) f.card.style.transform = "";
2657
+ paintFrames();
2658
+ const moved = [];
2659
+ for (const f of frames.values()) {
2660
+ const was = before.get(f.id);
2661
+ if (!was) continue;
2662
+ const now = f.card.getBoundingClientRect();
2663
+ const dx = was.x - now.x;
2664
+ const dy = was.y - now.y;
2665
+ if (!dx && !dy) continue;
2666
+ f.card.style.transform = `translate3d(${dx}px, ${dy}px, 0)`;
2667
+ moved.push(f);
2668
+ }
2669
+ if (!moved.length) return;
2670
+ void canvas.offsetWidth;
2671
+ canvas.setAttribute("data-settle", "");
2672
+ for (const f of moved) f.card.style.transform = "";
2673
+ settleTimer = window.setTimeout(() => canvas.removeAttribute("data-settle"), 240);
2674
+ }
2675
+ function paintStageClasses() {
2676
+ stage.classList.toggle("ez-multi", multi);
2677
+ stage.classList.toggle("ez-framed", multi || !deviceById(deviceId).fluid);
2678
+ }
2679
+ function rebuildStage() {
2680
+ abortPick("mode");
2681
+ popupFrame = null;
2682
+ canvas.replaceChildren();
2683
+ frames.clear();
2684
+ if (multi) {
2685
+ for (const row of canvasLayout()) {
2686
+ for (const d of row) mountFrame(d.id, d, canvas);
2687
+ }
2688
+ } else {
2689
+ const device = deviceById(deviceId);
2690
+ mountFrame(SINGLE, device, canvas);
2691
+ }
2692
+ paintFrames();
2693
+ }
2694
+ paintControls();
2695
+ paintAnnotateMode();
2696
+ paintStageClasses();
2697
+ rebuildStage();
2698
+ var sidebar = h("aside", "ez-sidebar");
2699
+ var queueSection = h("section", "ez-section ez-queue-section");
2700
+ var queueList = h("ul", "ez-queue");
2701
+ var queueScroll = h("div", "ez-fade ez-queue-scroll");
2702
+ queueScroll.appendChild(queueList);
2703
+ var sendBtn = h("button", "ez-send");
2704
+ var sendLabel = h("span", void 0, "\u9001\u51FA\u7D66 agent");
2705
+ sendBtn.title = "\u9001\u51FA\u7D66 agent\uFF08\u2318/Ctrl + Enter\uFF09";
2706
+ sendBtn.append(icon(Navigation03Icon_default, 15), sendLabel);
2707
+ sendBtn.onclick = () => void sendBatch();
2708
+ var noteAttach = attachify({
2709
+ api: API,
2710
+ mk: h,
2711
+ className: "ez-note",
2712
+ placeholder: "\u6574\u9AD4\u60F3\u6CD5\u6216\u88DC\u5145\u8AAA\u660E\uFF0C\u8F38\u5165 / \u7528\u6307\u4EE4\uFF08\u9078\u586B\uFF09",
2713
+ onChange: () => paintSendState(),
2714
+ commands: [
2715
+ {
2716
+ id: "element",
2717
+ label: "Element",
2718
+ hint: "\u9078\u53D6\u9801\u9762\u5143\u7D20\u6216\u7BC4\u570D",
2719
+ keywords: ["element", "pick", "ref", "reference", "\u5143\u7D20", "\u6307\u5B9A", "\u53C3\u8003", "\u6846\u9078"],
2720
+ icon: AlignSelectionIcon_default,
2721
+ // The pointer is in the other document, so this can only ask. The answer
2722
+ // comes back as a message and lands here via the pick reducer.
2723
+ run: () => startPick("note")
2724
+ },
2725
+ {
2726
+ id: "new",
2727
+ label: "New chat",
2728
+ hint: "\u958B\u555F\u65B0\u5C0D\u8A71",
2729
+ keywords: ["new", "chat", "clear", "reset", "context", "\u65B0\u5C0D\u8A71", "\u6E05\u9664", "\u91CD\u8A2D"],
2730
+ icon: BubbleChatAddIcon_default,
2731
+ // Only ACP mode has a context this can throw away. A CLI-driven agent owns
2732
+ // its own session, and clearing it is a thing the user does in the terminal.
2733
+ enabled: () => !!snapshot?.acp,
2734
+ // The note being typed survives: the slash menu takes only the `/new` out,
2735
+ // and the draft is what the fresh session is about to be asked.
2736
+ run: () => void newChat()
2737
+ },
2738
+ {
2739
+ id: "resume",
2740
+ label: "Resume",
2741
+ hint: "\u56DE\u5230\u4E4B\u524D\u7684\u5C0D\u8A71",
2742
+ keywords: ["resume", "chat", "history", "past", "\u5C0D\u8A71", "\u7D00\u9304", "\u4E4B\u524D", "\u56DE\u5230"],
2743
+ icon: BubbleChatOutcomeIcon_default,
2744
+ // Nothing to resume onto without an agent, and nothing to resume *to*
2745
+ // while this review has only ever had the one conversation.
2746
+ enabled: () => !!snapshot?.acp && (snapshot.chats?.length ?? 0) > 1,
2747
+ run: () => void openResume()
2748
+ }
2749
+ ],
2750
+ skills: () => fetchSkills()
2751
+ });
2752
+ var note = noteAttach.editable;
2753
+ note.title = "\u5BEB\u88DC\u5145\u8AAA\u660E\uFF08N\uFF09";
2754
+ var configPill = h("button", "ez-config-pill");
2755
+ configPill.setAttribute("aria-haspopup", "menu");
2756
+ configPill.setAttribute("aria-expanded", "false");
2757
+ var configMenu = h("div", "ez-menu ez-menu-up");
2758
+ configMenu.setAttribute("role", "menu");
2759
+ var configWrap = h("div", "ez-config");
2760
+ configWrap.hidden = true;
2761
+ configWrap.append(configPill, configMenu);
2762
+ var limitPill = h("button", "ez-limit");
2763
+ limitPill.type = "button";
2764
+ limitPill.setAttribute("aria-describedby", "ez-usage-card");
2765
+ var limitCard = h("div", "ez-usage-card");
2766
+ limitCard.id = "ez-usage-card";
2767
+ limitCard.setAttribute("role", "tooltip");
2768
+ var limitWrap = h("div", "ez-usage");
2769
+ limitWrap.hidden = true;
2770
+ limitWrap.append(limitPill, limitCard);
2771
+ var confirmTitle = h("div", "ez-notice-title");
2772
+ var confirmBody = h("div", "ez-notice-body");
2773
+ var confirmSkip = h("input");
2774
+ confirmSkip.type = "checkbox";
2775
+ confirmSkip.id = "ez-confirm-skip";
2776
+ var confirmSkipLabel = h("label", "ez-notice-skip");
2777
+ confirmSkipLabel.htmlFor = confirmSkip.id;
2778
+ confirmSkipLabel.append(confirmSkip, h("span", "", "\u4EE5\u5F8C\u4E0D\u518D\u63D0\u9192"));
2779
+ var confirmGo = h("button", "ez-notice-go");
2780
+ var confirmNo = h("button", "ez-notice-no", "\u53D6\u6D88");
2781
+ var confirmActions = h("div", "ez-notice-actions");
2782
+ confirmActions.append(confirmGo, confirmNo);
2783
+ var confirmCard = h("section", "ez-notice ez-confirm");
2784
+ confirmCard.hidden = true;
2785
+ confirmCard.append(confirmTitle, confirmBody, confirmActions, confirmSkipLabel);
2786
+ var resumeTitle = h("div", "ez-notice-title", "\u56DE\u5230\u4E4B\u524D\u7684\u5C0D\u8A71");
2787
+ var resumeList = h("div", "ez-notice-list");
2788
+ resumeList.setAttribute("role", "menu");
2789
+ var resumeClose = h("button", "ez-notice-x");
2790
+ resumeClose.title = "\u95DC\u9589";
2791
+ resumeClose.setAttribute("aria-label", "\u95DC\u9589");
2792
+ resumeClose.append(icon(Cancel01Icon_default, 13));
2793
+ var resumeCard = h("section", "ez-notice ez-resume");
2794
+ resumeCard.hidden = true;
2795
+ resumeCard.append(resumeClose, resumeTitle, resumeList);
2796
+ var resumeRows = [];
2797
+ function pointAtResumeRow(row) {
2798
+ for (const other of resumeRows) other.toggleAttribute("data-active", other === row);
2799
+ }
2800
+ function closeResume() {
2801
+ resumeCard.hidden = true;
2802
+ }
2803
+ async function openResume() {
2804
+ resumeList.textContent = "";
2805
+ resumeCard.hidden = false;
2806
+ const res = await api("/acp/chats");
2807
+ if (!res.ok) {
2808
+ closeResume();
2809
+ return;
2810
+ }
2811
+ const { chats } = await res.json();
2812
+ resumeList.textContent = "";
2813
+ resumeRows = chats.map((chat) => {
2814
+ const row = h("button", "ez-notice-row");
2815
+ row.setAttribute("role", "menuitemradio");
2816
+ row.setAttribute("aria-checked", String(chat.current));
2817
+ if (chat.current) row.dataset.current = "";
2818
+ row.append(
2819
+ icon(ArrowRight02Icon_default, 12),
2820
+ h("span", "ez-notice-row-name", chat.title || chatTime(chat.startedAt)),
2821
+ h("span", "ez-notice-row-when", chat.title ? chatTime(chat.startedAt) : "")
2822
+ );
2823
+ row.addEventListener("pointerenter", () => pointAtResumeRow(row));
2824
+ row.addEventListener("focus", () => pointAtResumeRow(row));
2825
+ row.title = chat.entries ? `${chat.entries} \u5247` : "\u5C1A\u7121\u5167\u5BB9";
2826
+ row.onclick = () => {
2827
+ closeResume();
2828
+ if (!chat.current) void switchChat(chat.id);
2829
+ };
2830
+ resumeList.append(row);
2831
+ return row;
2832
+ });
2833
+ resumeCard.hidden = chats.length === 0;
2834
+ resumeRows[0]?.focus();
2835
+ }
2836
+ resumeCard.onkeydown = (e) => {
2837
+ if (walkMenu(e, resumeRows)) return;
2838
+ if (e.key === "Escape") {
2839
+ e.stopPropagation();
2840
+ closeResume();
2841
+ }
2842
+ };
2843
+ resumeClose.onclick = () => closeResume();
2844
+ var notices = h("div", "ez-notices");
2845
+ notices.append(banner2, updateCard, resumeCard, confirmCard);
2846
+ var answerConfirm = null;
2847
+ function closeConfirm(ok, offer) {
2848
+ const answer = answerConfirm;
2849
+ answerConfirm = null;
2850
+ confirmCard.hidden = true;
2851
+ if (offer && offer.skippable !== false) {
2852
+ rememberConfirm(localStorage, offer.key, { ok, skip: confirmSkip.checked });
2853
+ }
2854
+ answer?.(ok);
2855
+ }
2856
+ function askConfirm(offer) {
2857
+ const skippable = offer.skippable !== false;
2858
+ if (skippable && confirmSkipped(localStorage, offer.key)) return Promise.resolve(true);
2859
+ confirmSkipLabel.hidden = !skippable;
2860
+ answerConfirm?.(false);
2861
+ answerConfirm = null;
2862
+ confirmTitle.textContent = offer.title;
2863
+ confirmBody.textContent = offer.body;
2864
+ confirmGo.textContent = offer.go;
2865
+ confirmSkip.checked = false;
2866
+ confirmCard.hidden = false;
2867
+ confirmGo.onclick = () => closeConfirm(true, offer);
2868
+ confirmNo.onclick = () => closeConfirm(false, offer);
2869
+ confirmCard.onkeydown = (e) => {
2870
+ if (e.key === "Escape") {
2871
+ e.stopPropagation();
2872
+ closeConfirm(false, offer);
2873
+ }
2874
+ };
2875
+ confirmGo.focus();
2876
+ return new Promise((resolve) => {
2877
+ answerConfirm = resolve;
2878
+ });
2879
+ }
2880
+ var harnessBtn = h("button", "ez-harness");
2881
+ harnessBtn.type = "button";
2882
+ harnessBtn.setAttribute("role", "switch");
2883
+ harnessBtn.setAttribute("aria-checked", "false");
2884
+ var HARNESS_HINT = "\u5BE6\u9A57\u6027\u529F\u80FD\uFF1A\u958B\u555F\u5F8C\uFF0Cagent \u6703\u5148\u4E86\u89E3\u4F60\u7684\u7522\u54C1\u3001\u6574\u7406\u51FA\u8A2D\u8A08\u898F\u7BC4\uFF0C\u4E4B\u5F8C\u90FD\u7167\u898F\u7BC4\u8A2D\u8A08";
2885
+ var RUNNING_HINT = "agent \u6B63\u5728\u57F7\u884C\uFF0C\u9019\u4E00\u8F2A\u7D50\u675F\u5F8C\u624D\u80FD\u8ABF\u6574";
2886
+ harnessBtn.title = HARNESS_HINT;
2887
+ var harnessMark = h("span", "ez-harness-mark");
2888
+ harnessMark.append(icon(SparklesIcon_default, 12));
2889
+ harnessBtn.append(
2890
+ harnessMark,
2891
+ h("span", "ez-harness-label", "\u589E\u5F37\u8A2D\u8A08"),
2892
+ h("span", "ez-switch"),
2893
+ h("span", "ez-harness-tag", "\u5BE6\u9A57")
2894
+ );
2895
+ harnessBtn.onclick = () => void toggleHarness();
2896
+ var EFFORTS = [
2897
+ { level: "low", label: "\u4F4E", hint: "\u4F4E\uFF1A\u53EA\u770B\u8A2D\u8A08\u898F\u7BC4\uFF0C\u76F4\u63A5\u5BE6\u4F5C\uFF0C\u4E0D\u622A\u5716\u4E5F\u4E0D\u53E6\u5916\u6AA2\u67E5" },
2898
+ { level: "medium", label: "\u4E2D", hint: "\u4E2D\uFF1A\u5B8C\u6574\u8A2D\u8A08\u6D41\u7A0B\uFF0C\u4F46\u4E0D\u53E6\u5916\u8ACB\u4EBA\u6AA2\u67E5" },
2899
+ { level: "high", label: "\u9AD8", hint: "\u9AD8\uFF1A\u5B8C\u6574\u8A2D\u8A08\u6D41\u7A0B\uFF0C\u6700\u5F8C\u518D\u7368\u7ACB\u6AA2\u67E5\u4E00\u6B21" }
2900
+ ];
2901
+ var effortGroup = h("label", "ez-effort");
2902
+ effortGroup.hidden = true;
2903
+ var effortTrack = h("span", "ez-effort-track");
2904
+ effortTrack.setAttribute("aria-hidden", "true");
2905
+ effortTrack.append(h("span", "ez-effort-fill"), h("span", "ez-effort-thumb"));
2906
+ var effortSlider = h("input", "ez-effort-input");
2907
+ effortSlider.type = "range";
2908
+ effortSlider.min = "0";
2909
+ effortSlider.max = String(EFFORTS.length - 1);
2910
+ effortSlider.step = "1";
2911
+ effortSlider.setAttribute("aria-label", "\u8A2D\u8A08\u529B\u5EA6");
2912
+ var effortValue = h("span", "ez-effort-value");
2913
+ var effortControl = h("span", "ez-effort-slider");
2914
+ effortControl.append(effortTrack, effortSlider);
2915
+ effortGroup.append(h("span", "ez-effort-label", "\u529B\u5EA6"), effortControl, effortValue);
2916
+ function showEffort(at2) {
2917
+ const effort = EFFORTS[at2];
2918
+ effortSlider.value = String(at2);
2919
+ effortControl.style.setProperty("--at", String(at2 / (EFFORTS.length - 1)));
2920
+ effortSlider.setAttribute("aria-valuetext", effort.label);
2921
+ effortValue.textContent = effort.label;
2922
+ effortGroup.title = effort.hint;
2923
+ }
2924
+ effortSlider.oninput = () => showEffort(turnRunning() ? currentEffort() : Number(effortSlider.value));
2925
+ effortSlider.onchange = () => void setEffort(EFFORTS[Number(effortSlider.value)].level);
2926
+ async function setEffort(level) {
2927
+ if (!snapshot?.acp || turnRunning() || snapshot.designEffort === level) return;
2928
+ await api("/harness/effort", { method: "POST", body: JSON.stringify({ effort: level }) });
2929
+ }
2930
+ var harnessPending = false;
2931
+ var HARNESS_FILE = { product: "PRODUCT.md", design: "DESIGN.md" };
2932
+ async function toggleHarness() {
2933
+ if (harnessPending || !snapshot?.acp || turnRunning()) return;
2934
+ const on = !snapshot.designHarness;
2935
+ harnessPending = true;
2936
+ paintHarness();
2937
+ try {
2938
+ const res = await api("/harness", { method: "POST", body: JSON.stringify({ on }) });
2939
+ if (res.status !== 409) return;
2940
+ const { missing } = await res.json();
2941
+ if (!missing?.length) return;
2942
+ const files = missing.map((f) => HARNESS_FILE[f] ?? f).join(" \u8207 ");
2943
+ const ok = await askConfirm({
2944
+ key: "design-harness-create",
2945
+ title: "\u555F\u7528\u589E\u5F37\u8A2D\u8A08",
2946
+ body: `\u589E\u5F37\u8A2D\u8A08\u6703\u5EFA\u7ACB ${files}\uFF0C\u662F\u5426\u7E7C\u7E8C\uFF1F`,
2947
+ go: "\u7E7C\u7E8C",
2948
+ skippable: false
2949
+ });
2950
+ if (!ok) return;
2951
+ await reportPageStorage();
2952
+ await api("/harness", { method: "POST", body: JSON.stringify({ on: true, create: true }) });
2953
+ } finally {
2954
+ harnessPending = false;
2955
+ paintHarness();
2956
+ }
2957
+ }
2958
+ function paintHarness() {
2959
+ const s = snapshot;
2960
+ harnessRow.hidden = !s?.acp;
2961
+ harnessBtn.setAttribute("aria-checked", String(!!s?.designHarness));
2962
+ if (harnessPending) harnessBtn.setAttribute("aria-busy", "true");
2963
+ else harnessBtn.removeAttribute("aria-busy");
2964
+ const running = turnRunning();
2965
+ harnessBtn.disabled = s?.state === "ended";
2966
+ harnessBtn.title = running ? RUNNING_HINT : HARNESS_HINT;
2967
+ harnessBtn.toggleAttribute("aria-readonly", running);
2968
+ effortGroup.hidden = !s?.designHarness;
2969
+ effortSlider.disabled = s?.state === "ended";
2970
+ effortSlider.toggleAttribute("aria-readonly", running);
2971
+ if (running) effortHeld = false;
2972
+ if (!effortHeld) showEffort(currentEffort());
2973
+ if (running) effortGroup.title = RUNNING_HINT;
2974
+ }
2975
+ function turnRunning() {
2976
+ return snapshot?.acp?.state === "working";
2977
+ }
2978
+ function currentEffort() {
2979
+ return EFFORTS.findIndex((e) => e.level === (snapshot?.designEffort ?? "medium"));
2980
+ }
2981
+ var effortHeld = false;
2982
+ effortSlider.onpointerdown = () => effortHeld = true;
2983
+ window.addEventListener("pointerup", () => effortHeld = false);
2984
+ var controlRow = h("div", "ez-control-row");
2985
+ controlRow.hidden = true;
2986
+ controlRow.append(agentWrap, configWrap, limitWrap);
2987
+ var harnessRow = h("div", "ez-harness-row");
2988
+ harnessRow.hidden = true;
2989
+ harnessRow.append(harnessBtn, effortGroup);
2990
+ queueSection.append(queueScroll, harnessRow, controlRow, noteAttach.wrap, sendBtn);
2991
+ var editAttach = attachify({
2992
+ api: API,
2993
+ mk: h,
2994
+ className: "ez-qi-input",
2995
+ // Worded as the page popup's, which asks the same question of the same kind of
2996
+ // text, and puts the keys in the field rather than on a line of their own.
2997
+ placeholder: `\u60F3\u600E\u9EBC\u8ABF\u6574\uFF1F\u8F38\u5165 / \u7528\u6307\u4EE4\uFF08${MOD_LABEL}+Enter \u5132\u5B58\uFF09`,
2998
+ // The box grows with what is typed into it, and the queue's height budget is
2999
+ // what decides whether the row it grew into is still whole on screen.
3000
+ onChange: () => {
3001
+ paintEditState();
3002
+ capQueue();
3003
+ }
3004
+ });
3005
+ editAttach.wrap.addEventListener(
3006
+ "keydown",
3007
+ (e) => {
3008
+ if (e.key === "Escape") {
3009
+ e.preventDefault();
3010
+ e.stopPropagation();
3011
+ if (!editAttach.closeSlash()) closeEdit("cancelled");
3012
+ return;
3013
+ }
3014
+ if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
3015
+ e.preventDefault();
3016
+ e.stopPropagation();
3017
+ void saveEdit();
3018
+ }
3019
+ },
3020
+ true
3021
+ );
3022
+ var forkBar = h("nav", "ez-fork-bar");
3023
+ forkBar.hidden = true;
3024
+ forkBar.setAttribute("aria-label", "\u76EE\u524D\u7684\u5C0D\u8A71\u4F4D\u7F6E");
3025
+ var forkRoot = h("button", "ez-fork-root");
3026
+ var forkChip = h("button", "ez-fork-here");
3027
+ var forkName = h("span", "ez-fork-name");
3028
+ forkChip.append(icon(MagicWand01Icon_default, 13), forkName);
3029
+ function rootOf(chat, chats) {
3030
+ let at2 = chat;
3031
+ for (let hops = 0; at2.parentChatId && hops < 32; hops += 1) {
3032
+ const parent = chats.find((c) => c.id === at2.parentChatId);
3033
+ if (!parent) break;
3034
+ at2 = parent;
3035
+ }
3036
+ return at2;
3037
+ }
3038
+ function chatName(chat, isRoot) {
3039
+ if (chat.parentChatId) return chat.title ?? "\u5206\u652F\u5C0D\u8A71";
3040
+ return isRoot ? "\u4E3B\u5C0D\u8A71" : chat.title ?? "\u5C0D\u8A71";
3041
+ }
3042
+ function chatDetail(chat) {
3043
+ return chat.detail ?? chatTime(chat.startedAt);
3044
+ }
3045
+ function branchesOf(s) {
3046
+ const all = [...s?.chats ?? []].reverse();
3047
+ const current = all.find((c) => c.current);
3048
+ const here = current ? rootOf(current, all) : void 0;
3049
+ return all.filter((c) => c.parentChatId && (here ? c.parentChatId === here.id : false));
3050
+ }
3051
+ function branchPicker(trigger, menuClass) {
3052
+ const menu = h("div", `ez-menu ${menuClass}`);
3053
+ menu.setAttribute("role", "menu");
3054
+ menu.setAttribute("aria-label", "\u5207\u63DB\u5230\u67D0\u500B\u5C0D\u8A71");
3055
+ menu.hidden = true;
3056
+ let rows = [];
3057
+ let open = false;
3058
+ function pointAt(row) {
3059
+ for (const other of rows) other.toggleAttribute("data-active", other === row);
3060
+ }
3061
+ function close(focusTrigger = false) {
3062
+ open = false;
3063
+ menu.hidden = true;
3064
+ trigger.setAttribute("aria-expanded", "false");
3065
+ if (focusTrigger) trigger.focus();
3066
+ }
3067
+ function show() {
3068
+ menu.textContent = "";
3069
+ rows = branchesOf(snapshot).map((chat) => {
3070
+ const row = h("button", "ez-notice-row ez-fork-row");
3071
+ row.setAttribute("role", "menuitemradio");
3072
+ row.setAttribute("aria-checked", String(chat.current));
3073
+ if (chat.current) row.dataset.current = "";
3074
+ row.append(
3075
+ icon(ArrowRight02Icon_default, 12),
3076
+ h("span", "ez-notice-row-name", chatName(chat, false)),
3077
+ h("span", "ez-notice-row-when", chatDetail(chat))
3078
+ );
3079
+ row.addEventListener("pointerenter", () => pointAt(row));
3080
+ row.addEventListener("focus", () => pointAt(row));
3081
+ row.onclick = () => {
3082
+ close();
3083
+ if (!chat.current) void switchChat(chat.id);
3084
+ };
3085
+ menu.append(row);
3086
+ return row;
3087
+ });
3088
+ open = true;
3089
+ menu.hidden = false;
3090
+ trigger.setAttribute("aria-expanded", "true");
3091
+ (rows.find((r) => "current" in r.dataset) ?? rows[0])?.focus();
3092
+ }
3093
+ trigger.setAttribute("aria-haspopup", "menu");
3094
+ trigger.setAttribute("aria-expanded", "false");
3095
+ trigger.onclick = () => open ? close() : show();
3096
+ const holdFocus = (e) => e.preventDefault();
3097
+ trigger.addEventListener("mousedown", holdFocus);
3098
+ menu.addEventListener("mousedown", holdFocus);
3099
+ menu.onkeydown = (e) => {
3100
+ if (walkMenu(e, rows)) return;
3101
+ if (e.key === "Escape") {
3102
+ e.stopPropagation();
3103
+ close(true);
3104
+ }
3105
+ };
3106
+ menu.addEventListener("focusout", () => {
3107
+ queueMicrotask(() => {
3108
+ if (open && !menu.contains(document.activeElement) && document.activeElement !== trigger) {
3109
+ close();
3110
+ }
3111
+ });
3112
+ });
3113
+ return { menu, close, isOpen: () => open };
3114
+ }
3115
+ var BRANCHES_REACHABLE = false;
3116
+ var forkPicker = BRANCHES_REACHABLE ? branchPicker(forkChip, "ez-fork-menu") : null;
3117
+ if (!forkPicker) {
3118
+ forkChip.disabled = true;
3119
+ forkChip.setAttribute("aria-current", "page");
3120
+ }
3121
+ var forkMore = h("button", "ez-fork-more");
3122
+ forkMore.append(h("span", void 0, "\u5206\u652F\u5C0D\u8A71"), icon(ChevronDownIcon_default, 12));
3123
+ forkMore.title = "\u9019\u689D\u4E3B\u7DDA\u5E95\u4E0B\u7684\u5206\u652F\u5C0D\u8A71";
3124
+ var morePicker = BRANCHES_REACHABLE ? branchPicker(forkMore, "ez-fork-more-menu") : null;
3125
+ async function leaveBranch(toChatId) {
3126
+ const ok = await askConfirm({
3127
+ // Keyed on the act. The body says one clause more when a turn is running,
3128
+ // but it is the same decision either way, so answering it once answers it.
3129
+ key: "leave-branch",
3130
+ title: "\u78BA\u5B9A\u8981\u56DE\u4E3B\u5C0D\u8A71\uFF1F",
3131
+ body: snapshot?.agentBusy ? "\u56DE\u53BB\u6703\u7ACB\u523B\u505C\u4E0B agent\uFF0C\u9019\u4E00\u8F2A\u63A2\u7D22\u5C31\u6B64\u7D50\u675F\uFF0C\u4E4B\u5F8C\u7121\u6CD5\u518D\u56DE\u5230\u9019\u500B\u5C0D\u8A71\u3002" : "\u9019\u4E00\u8F2A\u63A2\u7D22\u5C31\u6B64\u7D50\u675F\uFF0C\u4E4B\u5F8C\u7121\u6CD5\u518D\u56DE\u5230\u9019\u500B\u5C0D\u8A71\u3002",
3132
+ go: "\u56DE\u4E3B\u5C0D\u8A71"
3133
+ });
3134
+ if (!ok) return;
3135
+ await switchChat(toChatId);
3136
+ }
3137
+ forkRoot.onclick = () => {
3138
+ const chats = [...snapshot?.chats ?? []].reverse();
3139
+ const current = chats.find((c) => c.current);
3140
+ if (!current) return;
3141
+ const root2 = rootOf(current, chats);
3142
+ if (!root2.current) void leaveBranch(root2.id);
3143
+ };
3144
+ var shownChatId = null;
3145
+ function paintFork(s) {
3146
+ const chats = s.chats ?? [];
3147
+ const current = chats.find((c) => c.current);
3148
+ const onBranch2 = !!current?.parentChatId;
3149
+ const branches = morePicker ? branchesOf(s).length : 0;
3150
+ forkBar.hidden = !onBranch2 && !branches;
3151
+ forkMore.hidden = onBranch2 || !branches;
3152
+ if (forkMore.hidden && morePicker?.isOpen()) morePicker.close();
3153
+ if (!onBranch2) {
3154
+ if (forkPicker?.isOpen()) forkPicker.close();
3155
+ forkSep.hidden = true;
3156
+ forkChip.hidden = true;
3157
+ if (current) {
3158
+ const name = chatName(current, true);
3159
+ forkRoot.textContent = name;
3160
+ forkRoot.disabled = true;
3161
+ forkRoot.setAttribute("aria-current", "page");
3162
+ forkRoot.removeAttribute("title");
3163
+ }
3164
+ shownChatId = current?.id ?? null;
3165
+ return;
3166
+ }
3167
+ forkSep.hidden = false;
3168
+ forkChip.hidden = false;
3169
+ forkRoot.disabled = false;
3170
+ forkRoot.removeAttribute("aria-current");
3171
+ const here = chatName(current, false);
3172
+ const root2 = rootOf(current, chats);
3173
+ const rootName = chatName(root2, true);
3174
+ forkRoot.textContent = rootName;
3175
+ forkRoot.title = `\u56DE\u5230${rootName}`;
3176
+ forkName.textContent = here;
3177
+ forkChip.title = forkPicker ? `${here} \xB7 \u9EDE\u4E00\u4E0B\u5207\u63DB\u5C0D\u8A71` : here;
3178
+ }
3179
+ function pushThread(s) {
3180
+ const chats = s.chats ?? [];
3181
+ const current = chats.find((c) => c.current);
3182
+ if (!current || current.id === shownChatId) return;
3183
+ const was = shownChatId;
3184
+ shownChatId = current.id;
3185
+ if (!was) return;
3186
+ const deeper = current.parentChatId === was;
3187
+ const back = chats.find((c) => c.id === was)?.parentChatId === current.id;
3188
+ convScroll.removeAttribute("data-enter");
3189
+ void convScroll.offsetWidth;
3190
+ convScroll.dataset.enter = deeper ? "deeper" : back ? "back" : "swap";
3191
+ }
3192
+ var convSection = h("section", "ez-section ez-conv-section");
3193
+ var convList = h("div", "ez-conv");
3194
+ var convScroll = h("div", "ez-fade ez-conv-scroll");
3195
+ convScroll.appendChild(convList);
3196
+ var forkSep = h("span", "ez-fork-sep");
3197
+ forkSep.append(icon(ChevronRightIcon_default, 10));
3198
+ forkBar.append(forkRoot, forkSep, forkChip);
3199
+ if (forkPicker) forkBar.append(forkPicker.menu);
3200
+ if (morePicker) forkBar.append(forkMore, morePicker.menu);
3201
+ convSection.append(forkBar, convScroll, notices);
3202
+ var resizer = h("div", "ez-resizer");
3203
+ resizer.tabIndex = 0;
3204
+ resizer.setAttribute("role", "separator");
3205
+ resizer.setAttribute("aria-orientation", "vertical");
3206
+ resizer.setAttribute("aria-valuemin", String(SIDEBAR_MIN));
3207
+ resizer.setAttribute("aria-label", "\u8ABF\u6574\u5074\u908A\u6B04\u5BEC\u5EA6");
3208
+ resizer.title = "\u62D6\u66F3\u8ABF\u6574\u5BEC\u5EA6\uFF0C\u96D9\u64CA\u9084\u539F";
3209
+ sidebar.append(resizer, sideHead, convSection, queueSection);
3210
+ root.append(stageWrap, sidebar);
3211
+ stage.addEventListener("pointerdown", (e) => {
3212
+ if (e.button !== 0) return;
3213
+ const panX = stage.scrollWidth - stage.clientWidth;
3214
+ const panY = stage.scrollHeight - stage.clientHeight;
3215
+ if (panX <= 0 && panY <= 0) return;
3216
+ const startX = e.clientX;
3217
+ const startY = e.clientY;
3218
+ const startLeft = stage.scrollLeft;
3219
+ const startTop = stage.scrollTop;
3220
+ stage.setPointerCapture(e.pointerId);
3221
+ document.body.classList.add("ez-panning");
3222
+ const move = (ev) => {
3223
+ stage.scrollLeft = startLeft - (ev.clientX - startX);
3224
+ stage.scrollTop = startTop - (ev.clientY - startY);
3225
+ };
3226
+ stage.addEventListener("pointermove", move);
3227
+ stage.addEventListener(
3228
+ "lostpointercapture",
3229
+ () => {
3230
+ stage.removeEventListener("pointermove", move);
3231
+ document.body.classList.remove("ez-panning");
3232
+ },
3233
+ { once: true }
3234
+ );
3235
+ });
3236
+ function cardHead() {
3237
+ for (const f of frames.values()) {
3238
+ const head = f.box.offsetTop;
3239
+ if (head > 0) return head;
3240
+ }
3241
+ return 19;
3242
+ }
3243
+ function canvasMetrics() {
3244
+ return { gap: CANVAS_GAP, rowGap: CANVAS_ROW_GAP, head: cardHead() };
3245
+ }
3246
+ var dropHint = h("div", "ez-drop-hint");
3247
+ var DRAG_SLOP = 4;
3248
+ var ROW_SNAP = 30;
3249
+ var PAN_EDGE = 44;
3250
+ var PAN_SPEED = 18;
3251
+ function beginCardDrag(e, frame) {
3252
+ if (!multi || e.button !== 0) return;
3253
+ e.preventDefault();
3254
+ e.stopPropagation();
3255
+ const handle = e.currentTarget;
3256
+ handle.setPointerCapture(e.pointerId);
3257
+ const startX = e.clientX;
3258
+ const startY = e.clientY;
3259
+ let clientX = startX;
3260
+ let clientY = startY;
3261
+ const grabbed = frame.card.getBoundingClientRect();
3262
+ const grabX = startX - grabbed.left;
3263
+ const grabY = startY - grabbed.top;
3264
+ let dragging = false;
3265
+ let cancelled = false;
3266
+ let landing = null;
3267
+ let raf = 0;
3268
+ const rows = canvasLayout();
3269
+ const metrics = canvasMetrics();
3270
+ let homeX = 0;
3271
+ let homeY = 0;
3272
+ let originX = 0;
3273
+ let originY = 0;
3274
+ let scrollX = 0;
3275
+ let scrollY = 0;
3276
+ let stageEdge;
3277
+ let drawn = "";
3278
+ const follow = () => {
3279
+ const x = clientX - (originX - (stage.scrollLeft - scrollX));
3280
+ const y = clientY - (originY - (stage.scrollTop - scrollY));
3281
+ frame.card.style.transform = `translate3d(${x - grabX - homeX}px, ${y - grabY - homeY}px, 0)`;
3282
+ const target2 = dropTarget(rows, metrics, { x, y }, ROW_SNAP);
3283
+ const next = applyMove(layout, frame.id, target2);
3284
+ if (next === layout) {
3285
+ landing = null;
3286
+ drawn = "";
3287
+ dropHint.removeAttribute("data-on");
3288
+ return;
3289
+ }
3290
+ landing = next;
3291
+ const line = indicatorRect(rows, metrics, target2, { x, y });
3292
+ const key = `${line.x}:${line.y}:${line.width}:${line.height}`;
3293
+ if (key === drawn) return;
3294
+ drawn = key;
3295
+ dropHint.setAttribute("data-on", "");
3296
+ dropHint.toggleAttribute("data-flat", line.height === 0);
3297
+ dropHint.style.left = `${line.x}px`;
3298
+ dropHint.style.top = `${line.y}px`;
3299
+ dropHint.style.width = line.width ? `${line.width}px` : "";
3300
+ dropHint.style.height = line.height ? `${line.height}px` : "";
3301
+ };
3302
+ const creep = (depth) => Math.round(PAN_SPEED * Math.min(1, depth / PAN_EDGE));
3303
+ const tick = () => {
3304
+ let dx = 0;
3305
+ let dy = 0;
3306
+ if (clientX < stageEdge.left + PAN_EDGE) dx = -creep(stageEdge.left + PAN_EDGE - clientX);
3307
+ else if (clientX > stageEdge.right - PAN_EDGE) dx = creep(clientX - stageEdge.right + PAN_EDGE);
3308
+ if (clientY < stageEdge.top + PAN_EDGE) dy = -creep(stageEdge.top + PAN_EDGE - clientY);
3309
+ else if (clientY > stageEdge.bottom - PAN_EDGE)
3310
+ dy = creep(clientY - stageEdge.bottom + PAN_EDGE);
3311
+ if (dx) stage.scrollLeft += dx;
3312
+ if (dy) stage.scrollTop += dy;
3313
+ follow();
3314
+ raf = requestAnimationFrame(tick);
3315
+ };
3316
+ const onKey = (ev) => {
3317
+ if (ev.key !== "Escape") return;
3318
+ ev.preventDefault();
3319
+ ev.stopPropagation();
3320
+ cancelled = true;
3321
+ handle.releasePointerCapture(e.pointerId);
3322
+ };
3323
+ const lift = () => {
3324
+ dragging = true;
3325
+ homeX = frame.card.offsetLeft;
3326
+ homeY = frame.card.offsetTop;
3327
+ const origin = canvas.getBoundingClientRect();
3328
+ originX = origin.left;
3329
+ originY = origin.top;
3330
+ scrollX = stage.scrollLeft;
3331
+ scrollY = stage.scrollTop;
3332
+ stageEdge = stage.getBoundingClientRect();
3333
+ document.body.classList.add("ez-arranging");
3334
+ frame.card.classList.add("ez-card-drag");
3335
+ canvas.appendChild(dropHint);
3336
+ document.addEventListener("keydown", onKey, true);
3337
+ raf = requestAnimationFrame(tick);
3338
+ };
3339
+ const putDown = () => {
3340
+ cancelAnimationFrame(raf);
3341
+ if (!cancelled) follow();
3342
+ document.removeEventListener("keydown", onKey, true);
3343
+ document.body.classList.remove("ez-arranging");
3344
+ frame.card.classList.remove("ez-card-drag");
3345
+ dropHint.removeAttribute("data-on");
3346
+ dropHint.remove();
3347
+ if (!cancelled && landing) {
3348
+ layout = landing;
3349
+ saveView();
3350
+ }
3351
+ settleFrames();
3352
+ };
3353
+ const move = (ev) => {
3354
+ clientX = ev.clientX;
3355
+ clientY = ev.clientY;
3356
+ if (!dragging && Math.hypot(clientX - startX, clientY - startY) >= DRAG_SLOP) lift();
3357
+ };
3358
+ handle.addEventListener("pointermove", move);
3359
+ handle.addEventListener(
3360
+ "lostpointercapture",
3361
+ () => {
3362
+ handle.removeEventListener("pointermove", move);
3363
+ if (dragging) putDown();
3364
+ },
3365
+ { once: true }
3366
+ );
3367
+ }
3368
+ var WIDTH_KEY = "ezdesign:sidebar-width";
3369
+ var maxSidebar = () => maxSidebarWidth(innerWidth);
3370
+ var clampSidebar = (w) => clampSidebarWidth(w, innerWidth);
3371
+ var preferredWidth = SIDEBAR_DEFAULT;
3372
+ try {
3373
+ const stored = Number(localStorage.getItem(WIDTH_KEY));
3374
+ if (Number.isFinite(stored) && stored > 0) preferredWidth = stored;
3375
+ } catch {
3376
+ }
3377
+ function paintSidebarWidth() {
3378
+ const width = clampSidebar(preferredWidth);
3379
+ sidebar.style.width = `${width}px`;
3380
+ resizer.setAttribute("aria-valuenow", String(width));
3381
+ resizer.setAttribute("aria-valuemax", String(maxSidebar()));
3382
+ paintFrames();
3383
+ }
3384
+ function resizeSidebar(width) {
3385
+ preferredWidth = clampSidebar(width);
3386
+ paintSidebarWidth();
3387
+ capQueue();
3388
+ }
3389
+ function persistSidebarWidth() {
3390
+ try {
3391
+ localStorage.setItem(WIDTH_KEY, String(preferredWidth));
3392
+ } catch {
3393
+ }
3394
+ }
3395
+ paintSidebarWidth();
3396
+ addEventListener("resize", () => {
3397
+ paintSidebarWidth();
3398
+ paintFrames();
3399
+ capQueue();
3400
+ });
3401
+ resizer.addEventListener("pointerdown", (e) => {
3402
+ if (e.button !== 0) return;
3403
+ const startX = e.clientX;
3404
+ const startWidth = sidebar.getBoundingClientRect().width;
3405
+ resizer.setPointerCapture(e.pointerId);
3406
+ document.body.classList.add("ez-resizing");
3407
+ const move = (ev) => resizeSidebar(startWidth + startX - ev.clientX);
3408
+ resizer.addEventListener("pointermove", move);
3409
+ resizer.addEventListener(
3410
+ "lostpointercapture",
3411
+ () => {
3412
+ resizer.removeEventListener("pointermove", move);
3413
+ document.body.classList.remove("ez-resizing");
3414
+ persistSidebarWidth();
3415
+ },
3416
+ { once: true }
3417
+ );
3418
+ });
3419
+ resizer.addEventListener("dblclick", () => {
3420
+ resizeSidebar(SIDEBAR_DEFAULT);
3421
+ persistSidebarWidth();
3422
+ });
3423
+ resizer.addEventListener("keydown", (e) => {
3424
+ const step = e.shiftKey ? 48 : 16;
3425
+ if (e.key === "ArrowLeft") resizeSidebar(sidebar.getBoundingClientRect().width + step);
3426
+ else if (e.key === "ArrowRight") resizeSidebar(sidebar.getBoundingClientRect().width - step);
3427
+ else return;
3428
+ persistSidebarWidth();
3429
+ e.preventDefault();
3430
+ });
3431
+ function sendMode(mode) {
3432
+ abortPick("mode");
3433
+ broadcast({ type: "ez:set-mode", mode });
3434
+ }
3435
+ var pickState = null;
3436
+ var pickSeq = 0;
3437
+ var PICKING_LABEL = "\u9078\u53D6\u4E2D\u2026";
3438
+ var onBranch = false;
3439
+ function paintVeil() {
3440
+ const state = pickState ? "pick" : onBranch ? "explore" : "";
3441
+ if (state) stage.dataset.ezVeil = state;
3442
+ else delete stage.dataset.ezVeil;
3443
+ }
3444
+ function dispatchPick(e) {
3445
+ const was = pickState;
3446
+ const out = reducePick(pickState, e);
3447
+ pickState = out.state;
3448
+ for (const effect of out.effects) applyPickEffect(effect);
3449
+ paintVeil();
3450
+ if (was && !pickState && !out.effects.some((x) => x.do === "insert-note")) {
3451
+ noteAttach.cancelRef();
3452
+ paintSendState();
3453
+ }
3454
+ }
3455
+ function pickFrames(frame) {
3456
+ if (!frame) return [...frames.values()];
3457
+ const one = frames.get(frame);
3458
+ return one ? [one] : [];
3459
+ }
3460
+ function applyPickEffect(effect) {
3461
+ switch (effect.do) {
3462
+ case "arm-overlay":
3463
+ if (effect.host === "note") {
3464
+ noteAttach.beginRef(PICKING_LABEL);
3465
+ paintSendState();
3466
+ }
3467
+ for (const f of pickFrames(effect.frame)) {
3468
+ toFrame(f.id, {
3469
+ type: "ez:pick",
3470
+ pickId: effect.id,
3471
+ host: effect.host,
3472
+ ...effect.returnTo ? { returnTo: effect.returnTo } : {}
3473
+ });
3474
+ }
3475
+ return;
3476
+ case "abort-overlay":
3477
+ for (const f of pickFrames(effect.frame)) {
3478
+ toFrame(f.id, { type: "ez:pick-abort", pickId: effect.id });
3479
+ }
3480
+ return;
3481
+ case "disarm-others":
3482
+ for (const f of frames.values()) {
3483
+ if (f.id !== effect.keep) toFrame(f.id, { type: "ez:pick-abort", pickId: effect.id });
3484
+ }
3485
+ return;
3486
+ case "restore":
3487
+ for (const f of pickFrames(effect.frame)) {
3488
+ toFrame(f.id, { type: "ez:restore", draft: effect.draft });
3489
+ }
3490
+ return;
3491
+ case "navigate":
3492
+ for (const f of pickFrames(effect.frame)) navigateFrame(f, effect.page);
3493
+ return;
3494
+ case "insert-note":
3495
+ noteAttach.resolveRef(effect.ref);
3496
+ note.focus();
3497
+ paintSendState();
3498
+ return;
3499
+ case "banner":
3500
+ pickNotice = effect.text;
3501
+ paintBanner();
3502
+ return;
3503
+ }
3504
+ }
3505
+ function startPick(host) {
3506
+ dispatchPick({
3507
+ t: "arm",
3508
+ id: `s${++pickSeq}-${Date.now().toString(36)}`,
3509
+ host,
3510
+ now: Date.now()
3511
+ });
3512
+ }
3513
+ function abortPick(reason) {
3514
+ if (!pickState) return false;
3515
+ dispatchPick({ t: "abort", reason });
3516
+ return true;
3517
+ }
3518
+ setInterval(() => {
3519
+ if (pickState) dispatchPick({ t: "tick", now: Date.now() });
3520
+ }, 500);
3521
+ function setDevice(id) {
3522
+ if (!multi && id === deviceId) return;
3523
+ const remount = multi;
3524
+ deviceId = id;
3525
+ multi = false;
3526
+ saveView();
3527
+ paintControls();
3528
+ paintStageClasses();
3529
+ if (remount) rebuildStage();
3530
+ else paintFrames();
3531
+ }
3532
+ function setMulti(next) {
3533
+ if (multi === next) return;
3534
+ multi = next;
3535
+ saveView();
3536
+ paintControls();
3537
+ paintStageClasses();
3538
+ rebuildStage();
3539
+ }
3540
+ async function reportPageStorage() {
3541
+ const win = frames.values().next().value?.iframe.contentWindow;
3542
+ const storage = win ? await collectPageStorage(win).catch(() => null) : null;
3543
+ if (!storage) return;
3544
+ await api("/page-state", { method: "POST", body: JSON.stringify(storage) }).catch(() => {
3545
+ });
3546
+ }
3547
+ async function api(path, init) {
3548
+ return fetch(`${API}${path}`, {
3549
+ headers: { "content-type": "application/json" },
3550
+ ...init
3551
+ });
3552
+ }
3553
+ var sending = false;
3554
+ function canSend() {
3555
+ if (sending || noteAttach.pending() > 0) return false;
3556
+ return snapshot?.state !== "ended";
3557
+ }
3558
+ function paintSendState() {
3559
+ sendBtn.disabled = !canSend();
3560
+ }
3561
+ var LIMIT_BEAT_MS = 12e4;
3562
+ function refreshLimit() {
3563
+ if (document.visibilityState !== "visible" || !snapshot?.acp) return;
3564
+ void api("/acp/limit", { method: "POST" }).catch(() => {
3565
+ });
3566
+ }
3567
+ window.addEventListener("focus", refreshLimit);
3568
+ document.addEventListener("visibilitychange", refreshLimit);
3569
+ setInterval(refreshLimit, LIMIT_BEAT_MS);
3570
+ async function sendBatch() {
3571
+ if (!canSend()) return;
3572
+ const count = snapshot?.annotations.length ?? 0;
3573
+ const attachments = noteAttach.ids();
3574
+ const references = noteAttach.refs();
3575
+ const text = noteAttach.text();
3576
+ const skills = noteAttach.skills();
3577
+ if (count === 0 && !text && attachments.length === 0 && references.length === 0 && !skills.length) {
3578
+ return;
3579
+ }
3580
+ abortPick("sent");
3581
+ sending = true;
3582
+ sendBtn.disabled = true;
3583
+ try {
3584
+ await reportPageStorage();
3585
+ const res = await api("/send", {
3586
+ method: "POST",
3587
+ body: JSON.stringify({
3588
+ note: text,
3589
+ attachments,
3590
+ references,
3591
+ ...skills.length ? { skills } : {}
3592
+ })
3593
+ });
3594
+ if (res.ok) noteAttach.reset();
3595
+ } finally {
3596
+ sending = false;
3597
+ paintSendState();
3598
+ }
3599
+ }
3600
+ var editing = null;
3601
+ var editSaving = false;
3602
+ var editSave = null;
3603
+ function paintEditState() {
3604
+ if (!editing || !editSave) return;
3605
+ const empty = !editAttach.text() && !editAttach.ids().length && !editAttach.refs().length;
3606
+ editSave.disabled = editSaving || empty || editAttach.pending() > 0;
3607
+ }
3608
+ function openEdit(a) {
3609
+ if (editing?.id === a.id) return;
3610
+ closeEdit("switched");
3611
+ editing = {
3612
+ id: a.id,
3613
+ owned: new Set((a.attachments ?? []).map((f) => f.id))
3614
+ };
3615
+ render();
3616
+ editAttach.editable.focus();
3617
+ editAttach.restore(bodyFromComment(a.comment, a.references ?? [], a.attachments ?? []));
3618
+ paintEditState();
3619
+ }
3620
+ function closeEdit(reason) {
3621
+ const at2 = editing;
3622
+ if (!at2) return;
3623
+ editing = null;
3624
+ editSaving = false;
3625
+ if (reason === "cancelled" || reason === "switched") {
3626
+ for (const id of editAttach.ids()) {
3627
+ if (!at2.owned.has(id)) void api(`/attachments/${id}`, { method: "DELETE" });
3628
+ }
3629
+ }
3630
+ editAttach.reset();
3631
+ editSave = null;
3632
+ if (reason !== "gone") render();
3633
+ }
3634
+ async function saveEdit() {
3635
+ const at2 = editing;
3636
+ if (!at2 || editSaving || editSave?.disabled !== false) return;
3637
+ editSaving = true;
3638
+ paintEditState();
3639
+ try {
3640
+ const res = await api(`/annotations/${at2.id}`, {
3641
+ method: "PATCH",
3642
+ body: JSON.stringify({
3643
+ comment: editAttach.text(),
3644
+ attachments: editAttach.ids(),
3645
+ references: editAttach.refs()
3646
+ })
3647
+ });
3648
+ if (!res.ok) return;
3649
+ closeEdit("saved");
3650
+ } finally {
3651
+ editSaving = false;
3652
+ paintEditState();
3653
+ }
3654
+ }
3655
+ async function cancelTurn() {
3656
+ const acp = snapshot?.acp;
3657
+ if (acp?.state !== "working" || acp.cancelling) return;
3658
+ await api("/acp/cancel", { method: "POST" });
3659
+ }
3660
+ async function newChat() {
3661
+ if (!snapshot?.acp) return;
3662
+ await api("/acp/new", { method: "POST" });
3663
+ }
3664
+ function brandMark(brand2, size) {
3665
+ if (!brand2) return [];
3666
+ const mark = h("span", "ez-brand-mark");
3667
+ mark.append(icon(brand2 === "claude" ? ClaudeIcon_default : ChatGptIcon_default, size));
3668
+ return [mark];
3669
+ }
3670
+ function menuCheck() {
3671
+ const check = h("span", "ez-menu-check");
3672
+ check.append(icon(CheckIcon_default, 14));
3673
+ return check;
3674
+ }
3675
+ function placeMenu(pill, menu) {
3676
+ const row = pill.offsetParent;
3677
+ if (!row) return;
3678
+ const rightmost = Math.max(0, row.clientWidth - menu.offsetWidth);
3679
+ menu.style.left = `${Math.min(pill.offsetLeft, rightmost)}px`;
3680
+ }
3681
+ function walkMenu(e, rows) {
3682
+ if (e.key !== "ArrowDown" && e.key !== "ArrowUp") return false;
3683
+ if (!rows.length) return false;
3684
+ const down = e.key === "ArrowDown";
3685
+ const at2 = rows.indexOf(document.activeElement);
3686
+ const next = at2 === -1 ? down ? 0 : rows.length - 1 : (at2 + (down ? 1 : -1) + rows.length) % rows.length;
3687
+ rows[next]?.focus();
3688
+ e.preventDefault();
3689
+ return true;
3690
+ }
3691
+ var agentMenuOpen = false;
3692
+ var agentsDrawn = null;
3693
+ var agents = [];
3694
+ var agentRows = [];
3695
+ function paintAgentMenuState() {
3696
+ agentWrap.toggleAttribute("data-open", agentMenuOpen);
3697
+ agentPill.setAttribute("aria-expanded", String(agentMenuOpen));
3698
+ }
3699
+ function closeAgentMenu() {
3700
+ if (!agentMenuOpen) return;
3701
+ agentMenuOpen = false;
3702
+ paintAgentMenuState();
3703
+ }
3704
+ agentPill.onclick = () => {
3705
+ agentMenuOpen = !agentMenuOpen;
3706
+ paintAgentMenuState();
3707
+ if (!agentMenuOpen) return;
3708
+ placeMenu(agentPill, agentMenu);
3709
+ void loadAgents().then(() => placeMenu(agentPill, agentMenu));
3710
+ };
3711
+ document.addEventListener("click", (e) => {
3712
+ if (e.target instanceof Node && !agentWrap.contains(e.target)) closeAgentMenu();
3713
+ });
3714
+ agentMenu.addEventListener("keydown", (e) => {
3715
+ if (walkMenu(e, agentRows)) return;
3716
+ if (e.key !== "Escape") return;
3717
+ closeAgentMenu();
3718
+ agentPill.focus();
3719
+ e.preventDefault();
3720
+ });
3721
+ function runningAgentId() {
3722
+ return agentProfileFor(snapshot?.acp?.agent ?? "")?.id;
3723
+ }
3724
+ async function loadAgents() {
3725
+ const res = await api("/acp/agents");
3726
+ if (!res.ok) return;
3727
+ agents = (await res.json()).agents;
3728
+ paintAgents();
3729
+ }
3730
+ async function switchAgent(agent) {
3731
+ closeAgentMenu();
3732
+ if (agent.id === runningAgentId()) return;
3733
+ const ok = await askConfirm({
3734
+ // Keyed on the act, not on which agent: the reason is the same whichever
3735
+ // way the switch goes, so answering it once answers it for all of them.
3736
+ key: "switch-agent",
3737
+ title: `\u78BA\u5B9A\u8981\u6539\u7528 ${agent.name}\uFF1F`,
3738
+ body: "\u5207\u63DB Agent \u6703\u958B\u555F\u65B0\u5C0D\u8A71\uFF0C\u907A\u5931\u73FE\u6709\u4E0A\u4E0B\u6587\u3002",
3739
+ go: `\u6539\u7528 ${agent.name}`
3740
+ });
3741
+ if (!ok) return;
3742
+ await api("/acp/agent", {
3743
+ method: "POST",
3744
+ body: JSON.stringify({ id: agent.id })
3745
+ });
3746
+ }
3747
+ function paintAgents() {
3748
+ const command = snapshot?.acp?.agent ?? "";
3749
+ const runningId = runningAgentId();
3750
+ const shown = agents.filter((a) => a.installed || a.id === runningId);
3751
+ const signature = JSON.stringify([shown, command]);
3752
+ if (signature === agentsDrawn) return;
3753
+ agentsDrawn = signature;
3754
+ agentPill.textContent = "";
3755
+ agentPill.append(
3756
+ ...brandMark(agentBrandFor(command), 14),
3757
+ h("span", "ez-agent-name", agentProfileFor(command)?.name ?? "Agent"),
3758
+ icon(ChevronDownIcon_default, 12)
3759
+ );
3760
+ agentMenu.textContent = "";
3761
+ agentRows = shown.map((agent) => {
3762
+ const on = agent.id === runningId;
3763
+ const row = h("button", "ez-menu-item");
3764
+ row.setAttribute("role", "menuitemradio");
3765
+ row.setAttribute("aria-checked", String(on));
3766
+ if (on) row.dataset.current = "";
3767
+ const brand2 = AGENT_PROFILES.find((p) => p.id === agent.id)?.brand;
3768
+ row.append(...brandMark(brand2, 15), h("span", "ez-menu-name", agent.name), menuCheck());
3769
+ row.onclick = () => void switchAgent(agent);
3770
+ agentMenu.append(row);
3771
+ return row;
3772
+ });
3773
+ }
3774
+ async function fetchSkills() {
3775
+ if (!snapshot?.acp) return [];
3776
+ const res = await api("/acp/skills");
3777
+ if (!res.ok) return [];
3778
+ const { skills } = await res.json();
3779
+ return skills.map((skill) => ({
3780
+ id: skill.name,
3781
+ label: skill.name,
3782
+ keywords: [skill.description],
3783
+ run: () => noteAttach.insertSkill(skill.name)
3784
+ }));
3785
+ }
3786
+ function chatTime(at2) {
3787
+ const when = new Date(at2);
3788
+ const clock = `${when.getHours()}:${String(when.getMinutes()).padStart(2, "0")}`;
3789
+ const today = /* @__PURE__ */ new Date();
3790
+ const sameDay = when.getFullYear() === today.getFullYear() && when.getMonth() === today.getMonth() && when.getDate() === today.getDate();
3791
+ return sameDay ? clock : `${when.getMonth() + 1}/${when.getDate()} ${clock}`;
3792
+ }
3793
+ async function switchChat(id) {
3794
+ await api("/acp/chat", { method: "POST", body: JSON.stringify({ id }) });
3795
+ }
3796
+ var configMenuOpen = false;
3797
+ var configDrawn = null;
3798
+ function paintConfigMenuState() {
3799
+ configWrap.toggleAttribute("data-open", configMenuOpen);
3800
+ configPill.setAttribute("aria-expanded", String(configMenuOpen));
3801
+ }
3802
+ function closeConfigMenu() {
3803
+ if (!configMenuOpen) return;
3804
+ configMenuOpen = false;
3805
+ paintConfigMenuState();
3806
+ }
3807
+ configPill.onclick = () => {
3808
+ configMenuOpen = !configMenuOpen;
3809
+ paintConfigMenuState();
3810
+ if (configMenuOpen) placeMenu(configPill, configMenu);
3811
+ };
3812
+ document.addEventListener("click", (e) => {
3813
+ if (e.target instanceof Node && !configWrap.contains(e.target)) closeConfigMenu();
3814
+ });
3815
+ configWrap.addEventListener("focusout", (e) => {
3816
+ const to = e.relatedTarget;
3817
+ if (to instanceof Node && configWrap.contains(to)) return;
3818
+ closeConfigMenu();
3819
+ });
3820
+ configMenu.addEventListener("keydown", (e) => {
3821
+ if (walkMenu(e, [...configMenu.querySelectorAll(".ez-menu-item")])) return;
3822
+ if (e.key !== "Escape") return;
3823
+ closeConfigMenu();
3824
+ configPill.focus();
3825
+ e.preventDefault();
3826
+ });
3827
+ async function setConfig(configId, value) {
3828
+ closeConfigMenu();
3829
+ await api("/acp/config", {
3830
+ method: "POST",
3831
+ body: JSON.stringify({ configId, value })
3832
+ });
3833
+ }
3834
+ function pillOption(options) {
3835
+ return options.find((o) => o.category === "model") ?? options[0];
3836
+ }
3837
+ function paintLimit(acp) {
3838
+ const limit = acp?.limit;
3839
+ const line = usageNote(limit);
3840
+ limitWrap.hidden = !line;
3841
+ if (!line) return;
3842
+ limitPill.textContent = line;
3843
+ const plan = planName(limit?.plan);
3844
+ const head = h("div", "ez-usage-head");
3845
+ head.append(h("span", "ez-usage-title", "\u7528\u91CF"));
3846
+ if (plan) head.append(h("span", "ez-usage-plan", plan));
3847
+ limitCard.replaceChildren(head);
3848
+ for (const row of usageRows(limit)) {
3849
+ const top = h("div", "ez-usage-line");
3850
+ top.append(
3851
+ h("span", "ez-usage-label", row.label),
3852
+ h("span", "ez-usage-left", `\u5269 ${row.percent}%`)
3853
+ );
3854
+ const fill = h("i");
3855
+ fill.style.width = `${row.used}%`;
3856
+ const bar = h("div", "ez-usage-bar");
3857
+ bar.append(fill);
3858
+ const item = h("div", "ez-usage-row");
3859
+ if (row.low) item.dataset.low = "";
3860
+ item.append(top, bar);
3861
+ if (row.when) item.append(h("div", "ez-usage-when", row.when));
3862
+ limitCard.append(item);
3863
+ }
3864
+ }
3865
+ function paintConfig(acp) {
3866
+ const options = (acp?.configOptions ?? []).filter((o) => !HIDDEN_CATEGORIES.has(o.category ?? ""));
3867
+ configWrap.hidden = options.length === 0;
3868
+ if (options.length === 0) {
3869
+ closeConfigMenu();
3870
+ configDrawn = null;
3871
+ return;
3872
+ }
3873
+ const settable = acp?.state === "idle" || acp?.state === "working";
3874
+ configPill.disabled = !settable;
3875
+ if (!settable) closeConfigMenu();
3876
+ const signature = JSON.stringify([options, settable]);
3877
+ if (signature === configDrawn) return;
3878
+ configDrawn = signature;
3879
+ const named = pillOption(options);
3880
+ const label = named && named.type === "select" ? shortConfigValueName(configValueName(named, named.currentValue)) : named && configLabel(named) || "";
3881
+ configPill.textContent = "";
3882
+ configPill.append(h("span", "ez-config-name", label), icon(ChevronDownIcon_default, 12));
3883
+ const full = named && named.type === "select" ? configValueName(named, named.currentValue) : "";
3884
+ const detail = named && named.type === "select" ? configValues(named).find((o) => o.value === named.currentValue)?.description : void 0;
3885
+ configPill.title = [named ? configLabel(named) : "", full, detail].filter(Boolean).join(" \xB7 ");
3886
+ configMenu.textContent = "";
3887
+ for (const option of [...options].sort(byCategory)) {
3888
+ const group = h("div", "ez-menu-group");
3889
+ group.setAttribute("role", "group");
3890
+ group.setAttribute("aria-label", configLabel(option));
3891
+ if (option.type === "boolean") {
3892
+ group.append(
3893
+ configRow(option, !option.currentValue, configLabel(option), option, option.currentValue)
3894
+ );
3895
+ } else {
3896
+ group.append(h("div", "ez-menu-label", configLabel(option)));
3897
+ for (const value of configValues(option)) {
3898
+ group.append(
3899
+ configRow(option, value.value, value.name, value, value.value === option.currentValue)
3900
+ );
3901
+ }
3902
+ }
3903
+ configMenu.append(group);
3904
+ }
3905
+ if (configMenuOpen) placeMenu(configPill, configMenu);
3906
+ }
3907
+ var HIDDEN_CATEGORIES = /* @__PURE__ */ new Set(["mode", "model_config"]);
3908
+ var CATEGORY_ORDER = ["model", "thought_level"];
3909
+ function byCategory(a, b) {
3910
+ const rank = (o) => {
3911
+ const at2 = CATEGORY_ORDER.indexOf(o.category ?? "");
3912
+ return at2 === -1 ? CATEGORY_ORDER.length : at2;
3913
+ };
3914
+ return rank(a) - rank(b);
3915
+ }
3916
+ function resolvedName(option, value, name, describes) {
3917
+ const resolved = describes.description;
3918
+ if (option.type !== "select" || value !== "default" || !/default/i.test(name) || !resolved) {
3919
+ return name;
3920
+ }
3921
+ return `${name.replace(/\s*\([^)]*\)\s*$/, "")} (${resolved.replace(/\s*\(([^)]*)\)/g, " \xB7 $1")})`;
3922
+ }
3923
+ function configRow(option, value, name, describes, on) {
3924
+ const row = h("button", "ez-menu-item");
3925
+ row.setAttribute("role", option.type === "boolean" ? "menuitemcheckbox" : "menuitemradio");
3926
+ row.setAttribute("aria-checked", String(on));
3927
+ if (on) row.dataset.current = "";
3928
+ row.append(h("span", "ez-menu-name", resolvedName(option, value, name, describes)), menuCheck());
3929
+ if (describes.description) row.title = describes.description;
3930
+ row.onclick = () => void setConfig(option.id, value);
3931
+ return row;
3932
+ }
3933
+ var queueDrawn = null;
3934
+ function paintQueue(annotations) {
3935
+ if (editing && !annotations.some((a) => a.id === editing?.id)) closeEdit("gone");
3936
+ const signature = JSON.stringify([editing?.id ?? null, annotations]);
3937
+ if (signature === queueDrawn) return;
3938
+ queueDrawn = signature;
3939
+ queueList.textContent = "";
3940
+ annotations.forEach((a, i) => {
3941
+ const li = h("li", "ez-queue-item");
3942
+ const head = h("div", "ez-qi-head");
3943
+ head.append(h("span", "ez-qi-num", String(i + 1)), h("span", "ez-qi-label", annotationLabel(a)));
3944
+ const vp = a.anchor.viewport;
3945
+ if (vp?.width && vp.preset !== "desktop") {
3946
+ head.append(h("span", "ez-qi-vp", `${vp.width}px`));
3947
+ }
3948
+ const acts = h("div", "ez-qi-acts");
3949
+ const open = editing?.id === a.id;
3950
+ const primary = h("button", `ez-qi-act ${open ? "ez-qi-tick" : "ez-qi-edit"}`);
3951
+ primary.append(
3952
+ open ? icon(CheckIcon_default, TICK_ICON) : icon(Edit02Icon_default, EDIT_ICON)
3953
+ );
3954
+ primary.title = open ? `\u5132\u5B58\uFF08${MOD_LABEL}+Enter\uFF09` : "\u6539\u9019\u5247\u6A19\u8A3B\uFF08\u9001\u51FA\u524D\u90FD\u9084\u80FD\u6539\uFF09";
3955
+ primary.onclick = open ? () => void saveEdit() : () => openEdit(a);
3956
+ const dismiss = h("button", "ez-qi-act ez-qi-del");
3957
+ dismiss.append(icon(Cancel01Icon_default, 13));
3958
+ dismiss.title = open ? "\u53D6\u6D88\uFF08Esc\uFF09" : "\u79FB\u9664\u9019\u5247\u6A19\u8A3B";
3959
+ dismiss.onclick = open ? () => closeEdit("cancelled") : () => void api(`/annotations/${a.id}`, { method: "DELETE" });
3960
+ acts.append(primary, dismiss);
3961
+ head.append(acts);
3962
+ if (open) {
3963
+ li.dataset.editing = "";
3964
+ editSave = primary;
3965
+ li.append(head, editAttach.wrap);
3966
+ queueList.appendChild(li);
3967
+ paintEditState();
3968
+ return;
3969
+ }
3970
+ li.append(head);
3971
+ const row = commentEl(
3972
+ a.comment,
3973
+ a.references,
3974
+ a.attachments?.map((f) => f.name),
3975
+ "ez-qi-comment"
3976
+ );
3977
+ if (a.comment || a.references?.length || a.attachments?.length) li.append(row.box);
3978
+ const files = fileChips(row.unplacedFiles);
3979
+ if (files) li.append(files);
3980
+ queueList.appendChild(li);
3981
+ });
3982
+ capQueue();
3983
+ }
3984
+ function annotationLabel(a) {
3985
+ const parts = [];
3986
+ if (a.anchor.source) parts.push(a.anchor.source);
3987
+ else if (a.anchor.components?.length) parts.push(`<${a.anchor.components[0]}>`);
3988
+ if (a.anchor.section) parts.push(a.anchor.section);
3989
+ if (a.anchor.text) parts.push(`"${a.anchor.text.slice(0, 32)}"`);
3990
+ return parts.join(" \xB7 ") || a.anchor.page || "";
3991
+ }
3992
+ function buildSaid(entry, isUser) {
3993
+ const said = h("div", isUser ? "ez-bubble" : "ez-said");
3994
+ const items = entry.items ?? [];
3995
+ if (items.length) {
3996
+ const key = entry.batchId ?? String(entry.ts);
3997
+ const expanded = expandedBatches.has(key);
3998
+ const collapsed = items.length > ITEM_LIMIT + 1 && !expanded;
3999
+ const shown = collapsed ? items.slice(0, ITEM_LIMIT) : items;
4000
+ const list = h("ol", "ez-bubble-list");
4001
+ shown.forEach((item, i) => {
4002
+ const li = h("li", "ez-bubble-item");
4003
+ const body = h("div", "ez-bi-body");
4004
+ const rendered = commentEl(
4005
+ item.comment,
4006
+ item.references,
4007
+ item.attachments,
4008
+ void 0,
4009
+ entry.skills
4010
+ );
4011
+ if (item.where) li.title = item.where;
4012
+ if (rendered.box.childNodes.length) body.append(rendered.box);
4013
+ const files = fileChips(rendered.unplacedFiles);
4014
+ if (files) body.append(files);
4015
+ li.append(h("span", "ez-bi-num", `${i + 1}.`), body);
4016
+ list.appendChild(li);
4017
+ });
4018
+ said.appendChild(list);
4019
+ if (collapsed) {
4020
+ const more = h("button", "ez-bubble-more", `\u9084\u6709 ${items.length - ITEM_LIMIT} \u5247`);
4021
+ more.onclick = () => {
4022
+ expandedBatches.add(key);
4023
+ render();
4024
+ };
4025
+ said.appendChild(more);
4026
+ }
4027
+ }
4028
+ if (!isUser && entry.text) {
4029
+ said.appendChild(markdownEl(entry.text, "ez-md"));
4030
+ return said;
4031
+ }
4032
+ const note2 = commentEl(
4033
+ entry.text,
4034
+ entry.references,
4035
+ entry.attachments,
4036
+ items.length ? "ez-bubble-note" : void 0,
4037
+ entry.skills
4038
+ );
4039
+ if (entry.text || entry.references?.length || entry.attachments?.length) {
4040
+ said.appendChild(note2.box);
4041
+ }
4042
+ const noteFiles = fileChips(note2.unplacedFiles);
4043
+ if (noteFiles) said.appendChild(noteFiles);
4044
+ return said;
4045
+ }
4046
+ function chipEl(name, glyph, kind, title) {
4047
+ const chip = h("span", kind ? `ez-chip ez-chip-${kind}` : "ez-chip");
4048
+ if (glyph) chip.append(icon(glyph, 11));
4049
+ chip.append(h("span", "ez-chip-name", name));
4050
+ if (title) chip.title = title;
4051
+ return chip;
4052
+ }
4053
+ var refChipEl = (n, label) => chipEl(refChipText(n), AlignSelectionIcon_default, "ref", label);
4054
+ var skillChipEl = (name) => chipEl(`$${name}`, null, "skill");
4055
+ function fileChips(names) {
4056
+ if (!names.length) return null;
4057
+ const row = h("div", "ez-file-chips");
4058
+ for (const name of names) row.appendChild(chipEl(name, File02Icon_default));
4059
+ return row;
4060
+ }
4061
+ function commentEl(text, refs, files, className, skills) {
4062
+ const box = h("div", className);
4063
+ const numbered = new Map(
4064
+ (refs ?? []).flatMap((r) => typeof r === "string" ? [] : [[r.n, r.label]])
4065
+ );
4066
+ const names = files ?? [];
4067
+ const placedRefs = /* @__PURE__ */ new Set();
4068
+ const placedFiles = /* @__PURE__ */ new Set();
4069
+ for (const part of splitComment(text)) {
4070
+ if (part.t === "text") {
4071
+ box.appendChild(document.createTextNode(part.v));
4072
+ continue;
4073
+ }
4074
+ if (part.t === "skill") {
4075
+ const named = skills?.[part.n - 1];
4076
+ box.appendChild(
4077
+ named === void 0 ? document.createTextNode(skillMarker(part.n)) : skillChipEl(named)
4078
+ );
4079
+ continue;
4080
+ }
4081
+ if (part.t === "file") {
4082
+ const name = names[part.n - 1];
4083
+ if (name === void 0) {
4084
+ box.appendChild(document.createTextNode(fileMarker(part.n)));
4085
+ continue;
4086
+ }
4087
+ placedFiles.add(part.n);
4088
+ box.appendChild(chipEl(name, File02Icon_default));
4089
+ continue;
4090
+ }
4091
+ const label = numbered.get(part.n);
4092
+ if (label === void 0) {
4093
+ box.appendChild(document.createTextNode(refMarker(part.n)));
4094
+ continue;
4095
+ }
4096
+ placedRefs.add(part.n);
4097
+ box.appendChild(refChipEl(part.n, label));
4098
+ }
4099
+ for (const r of refs ?? []) {
4100
+ if (typeof r === "string") {
4101
+ box.appendChild(chipEl(r, AlignSelectionIcon_default, "ref"));
4102
+ } else if (!placedRefs.has(r.n)) {
4103
+ box.appendChild(refChipEl(r.n, r.label));
4104
+ }
4105
+ }
4106
+ return {
4107
+ box,
4108
+ unplacedFiles: names.filter((_, i) => !placedFiles.has(i + 1))
4109
+ };
4110
+ }
4111
+ var QUEUE_VISIBLE = 2;
4112
+ function capQueue() {
4113
+ const items = [...queueList.children];
4114
+ queueScroll.toggleAttribute("data-filled", items.length > 0);
4115
+ if (items.length > QUEUE_VISIBLE) {
4116
+ const gap = parseFloat(getComputedStyle(queueList).rowGap) || 0;
4117
+ const budget = items.slice(0, QUEUE_VISIBLE).reduce((sum, li) => sum + li.offsetHeight, 0) + gap * (QUEUE_VISIBLE - 1);
4118
+ const open = items.find((li) => li.dataset.editing !== void 0);
4119
+ queueList.style.maxHeight = `${Math.max(budget, open?.offsetHeight ?? 0)}px`;
4120
+ } else {
4121
+ queueList.style.maxHeight = "";
4122
+ }
4123
+ if (editing) {
4124
+ queueList.querySelector("[data-editing]")?.scrollIntoView({ block: "nearest" });
4125
+ }
4126
+ paintQueueFades();
4127
+ }
4128
+ function watchFades(wrapper, scroller) {
4129
+ const paint = () => {
4130
+ const slack = scroller.scrollHeight - scroller.clientHeight;
4131
+ wrapper.toggleAttribute("data-more-above", scroller.scrollTop > 1);
4132
+ wrapper.toggleAttribute("data-more-below", scroller.scrollTop < slack - 1);
4133
+ };
4134
+ scroller.addEventListener("scroll", paint);
4135
+ return paint;
4136
+ }
4137
+ var paintQueueFades = watchFades(queueScroll, queueList);
4138
+ var paintConvFades = watchFades(convScroll, convList);
4139
+ var pickNotice = null;
4140
+ function bannerText() {
4141
+ if (snapshot?.state === "ended") {
4142
+ return snapshot.endedBy === "agent" ? "Agent \u5DF2\u7D50\u675F\u9019\u6B21 review\u3002\u8981\u7E7C\u7E8C\u7684\u8A71\uFF0C\u8ACB agent \u91CD\u65B0\u958B\u555F session" : "Review \u5DF2\u7D50\u675F";
4143
+ }
4144
+ return pickNotice;
4145
+ }
4146
+ function paintBanner() {
4147
+ const text = bannerText();
4148
+ banner2.hidden = !text;
4149
+ if (text) banner2.textContent = text;
4150
+ }
4151
+ function render() {
4152
+ if (!snapshot) return;
4153
+ const s = snapshot;
4154
+ paintPage();
4155
+ const ended = s.state === "ended";
4156
+ if (ended) abortPick("ended");
4157
+ paintBanner();
4158
+ paintUpdate(s);
4159
+ for (const { btn } of annotateBtns) btn.disabled = ended;
4160
+ paintSendState();
4161
+ paintConfig(s.acp);
4162
+ paintLimit(s.acp);
4163
+ paintStrip();
4164
+ paintVariants();
4165
+ settleHold();
4166
+ paintFork(s);
4167
+ pushThread(s);
4168
+ broadcast({ type: "ez:can-explore", on: s.canExplore === true });
4169
+ onBranch = !!s.chats?.find((c) => c.current)?.parentChatId;
4170
+ paintVeil();
4171
+ broadcast({ type: "ez:in-explore", on: onBranch });
4172
+ agentWrap.hidden = !s.acp;
4173
+ paintHarness();
4174
+ controlRow.hidden = agentWrap.hidden && configWrap.hidden;
4175
+ if (s.acp && !agents.length) void loadAgents();
4176
+ paintAgents();
4177
+ if (s.acp?.cancelling) {
4178
+ agentStatus.className = "ez-badge ez-working";
4179
+ agentStatus.textContent = "\u6B63\u5728\u4E2D\u6B62";
4180
+ } else if (s.agentBusy) {
4181
+ agentStatus.className = "ez-badge ez-working";
4182
+ agentStatus.textContent = "Agent \u4FEE\u6539\u4E2D";
4183
+ } else if (s.acp?.state === "starting") {
4184
+ agentStatus.className = "ez-badge ez-working";
4185
+ agentStatus.textContent = "Agent \u6E96\u5099\u4E2D";
4186
+ } else if (s.agentOnline) {
4187
+ agentStatus.className = "ez-badge ez-online";
4188
+ agentStatus.textContent = "Agent \u5DF2\u9023\u7DDA";
4189
+ } else {
4190
+ agentStatus.className = "ez-badge";
4191
+ agentStatus.textContent = "Agent \u672A\u9023\u7DDA";
4192
+ }
4193
+ paintQueue(s.annotations);
4194
+ convList.textContent = "";
4195
+ let prevRole = null;
4196
+ let livePlaced = false;
4197
+ for (const entry of threadOrder(s.conversation)) {
4198
+ if (entry.role === "system") {
4199
+ convList.appendChild(h("div", "ez-msg-system", entry.text));
4200
+ prevRole = null;
4201
+ continue;
4202
+ }
4203
+ const grouped = entry.role === prevRole;
4204
+ const item = h("div", `ez-msg ez-msg-${entry.role}${grouped ? " ez-msg-cont" : ""}`);
4205
+ const isUser = entry.role === "user";
4206
+ item.append(buildSaid(entry, isUser));
4207
+ convList.appendChild(item);
4208
+ prevRole = entry.role;
4209
+ if (s.agentBusy && isUser && entry.batchId && entry.batchId === s.activeBatchId) {
4210
+ convList.appendChild(liveTurnEl(s, prevRole));
4211
+ livePlaced = true;
4212
+ prevRole = "agent";
4213
+ }
4214
+ }
4215
+ const opening = s.acp?.state === "starting" && !!s.agentQueued;
4216
+ const waiting = s.agentBusy || opening;
4217
+ if (waiting && !livePlaced) convList.appendChild(liveTurnEl(s, prevRole));
4218
+ if (s.acp?.ask) convList.appendChild(acpAskEl(s.acp.ask));
4219
+ if (s.acp?.state === "exited" && s.acp.error) {
4220
+ convList.appendChild(
4221
+ h("div", "ez-msg-system ez-msg-system-plain", `agent \u5DF2\u96E2\u7DDA\uFF1A${s.acp.error}`)
4222
+ );
4223
+ }
4224
+ convList.scrollTop = convList.scrollHeight;
4225
+ paintConvFades();
4226
+ }
4227
+ function liveTurnEl(s, prevRole) {
4228
+ const row = h("div", `ez-msg ez-msg-agent${prevRole === "agent" ? " ez-msg-cont" : ""}`);
4229
+ const dots = h("div", "ez-thinking");
4230
+ dots.setAttribute("role", "status");
4231
+ const opening = s.acp?.state === "starting";
4232
+ dots.setAttribute("aria-label", s.agentProgress ?? (opening ? "Agent \u6E96\u5099\u4E2D" : "Agent \u4FEE\u6539\u4E2D"));
4233
+ for (let i = 0; i < 3; i++) dots.appendChild(h("span", "ez-dot"));
4234
+ if (s.agentProgress) dots.appendChild(h("span", "ez-progress", s.agentProgress));
4235
+ const turn = h("div", "ez-acp-turn");
4236
+ if (s.acp && s.acp.state === "working") turn.append(acpFeedEl(s.acp));
4237
+ turn.append(dots);
4238
+ row.append(turn);
4239
+ return row;
4240
+ }
4241
+ function acpFeedEl(acp) {
4242
+ const box = h("div", "ez-acp-feed");
4243
+ const latest = acp.feed.at(-1);
4244
+ for (const item of acp.feed) {
4245
+ switch (item.kind) {
4246
+ case "say":
4247
+ box.appendChild(markdownEl(item.text, "ez-md ez-acp-say"));
4248
+ break;
4249
+ case "thought":
4250
+ if (item === latest) box.appendChild(h("div", "ez-acp-thought", item.text.slice(-160)));
4251
+ break;
4252
+ case "tool": {
4253
+ const line = h("div", `ez-acp-tool ez-acp-tool-${item.status}`);
4254
+ line.append(h("span", "ez-acp-dot"), h("span", "ez-acp-text", item.title));
4255
+ box.appendChild(line);
4256
+ break;
4257
+ }
4258
+ case "plan":
4259
+ for (const entry of item.entries) {
4260
+ const line = h("div", `ez-acp-plan ez-acp-plan-${entry.status}`);
4261
+ line.append(h("span", "ez-acp-tick"), h("span", "ez-acp-text", entry.content));
4262
+ box.appendChild(line);
4263
+ }
4264
+ break;
4265
+ }
4266
+ }
4267
+ return box;
4268
+ }
4269
+ function acpAskEl(ask) {
4270
+ const card = h("div", "ez-acp-ask");
4271
+ if (ask.title) card.append(h("div", "ez-acp-ask-title", ask.title));
4272
+ const values = /* @__PURE__ */ new Map();
4273
+ const choicesOnly = ask.fields.every((f) => f.kind === "select");
4274
+ const customKeys = new Set(
4275
+ ask.fields.flatMap(
4276
+ (f) => (f.kind === "select" || f.kind === "multiselect") && f.custom ? [f.custom.key] : []
4277
+ )
4278
+ );
4279
+ const typedCustom = () => [...values.keys()].some((k) => customKeys.has(k));
4280
+ const sendsOnPick = () => askSendsOnPick(ask.fields, typedCustom());
4281
+ let settled = false;
4282
+ const send = (body) => {
4283
+ if (settled) return;
4284
+ settled = true;
4285
+ for (const c of card.querySelectorAll("button, input, textarea")) {
4286
+ c.disabled = true;
4287
+ }
4288
+ void api("/acp/answer", { method: "POST", body: JSON.stringify({ id: ask.id, ...body }) });
4289
+ };
4290
+ const complete = () => askComplete(ask.fields, new Set(values.keys()));
4291
+ const submit = () => {
4292
+ if (complete()) send({ answers: Object.fromEntries(values) });
4293
+ };
4294
+ const submitBtn = h("button", "ez-acp-opt ez-acp-opt-primary", "\u9001\u51FA");
4295
+ submitBtn.onclick = submit;
4296
+ const refresh = () => {
4297
+ submitBtn.hidden = sendsOnPick();
4298
+ submitBtn.disabled = !complete();
4299
+ };
4300
+ const changed = (how) => {
4301
+ if (how === "pick" && sendsOnPick()) submit();
4302
+ else refresh();
4303
+ };
4304
+ const mixed = ask.fields.some((f) => f.optional) && ask.fields.some((f) => !f.optional);
4305
+ ask.fields.forEach((field, i) => {
4306
+ let labelId;
4307
+ if (field.text || mixed && field.optional) {
4308
+ const label = h(
4309
+ "div",
4310
+ "ez-acp-ask-q",
4311
+ `${field.text ?? ""}${mixed && field.optional ? "\uFF08\u9078\u586B\uFF09" : ""}`
4312
+ );
4313
+ labelId = `${ask.id}-q${i}`;
4314
+ label.id = labelId;
4315
+ card.append(label);
4316
+ }
4317
+ card.append(acpFieldEl(field, values, changed, submit, labelId));
4318
+ });
4319
+ if (ask.kind === "question" || !choicesOnly || !askSendsOnPick(ask.fields, false)) {
4320
+ const actions = h("div", "ez-acp-ask-actions");
4321
+ if (ask.kind === "question") {
4322
+ const skip = h("button", "ez-acp-opt", "\u7565\u904E");
4323
+ skip.onclick = () => send({ decline: true });
4324
+ actions.append(skip);
4325
+ }
4326
+ actions.append(submitBtn);
4327
+ card.append(actions);
4328
+ }
4329
+ refresh();
4330
+ return card;
4331
+ }
4332
+ function acpFieldEl(field, values, changed, submit, labelId) {
4333
+ const labelled = (el, role) => {
4334
+ if (role) el.setAttribute("role", role);
4335
+ if (labelId) el.setAttribute("aria-labelledby", labelId);
4336
+ return el;
4337
+ };
4338
+ const optionButton = (option) => {
4339
+ const btn = h("button", `ez-acp-opt${option.hint ? ` ez-acp-opt-${option.hint}` : ""}`);
4340
+ btn.append(h("span", void 0, option.name));
4341
+ if (option.description) btn.append(h("span", "ez-acp-opt-desc", option.description));
4342
+ btn.title = option.description ?? "";
4343
+ return btn;
4344
+ };
4345
+ const chord = (e) => {
4346
+ if ((e.metaKey || e.ctrlKey) && e.key === "Enter") {
4347
+ e.preventDefault();
4348
+ submit();
4349
+ }
4350
+ };
4351
+ const textField = (key, opts) => {
4352
+ const area = h("textarea", "ez-acp-field");
4353
+ area.rows = 1;
4354
+ if (opts.max !== void 0) area.maxLength = opts.max;
4355
+ if (opts.placeholder) area.placeholder = opts.placeholder;
4356
+ const grow = () => {
4357
+ area.style.height = "auto";
4358
+ area.style.height = `${area.scrollHeight}px`;
4359
+ };
4360
+ if (opts.initial !== void 0) {
4361
+ area.value = opts.initial;
4362
+ values.set(key, opts.initial);
4363
+ }
4364
+ area.oninput = () => {
4365
+ if (area.value) values.set(key, area.value);
4366
+ else values.delete(key);
4367
+ grow();
4368
+ opts.onInput?.();
4369
+ changed("edit");
4370
+ };
4371
+ area.onkeydown = chord;
4372
+ queueMicrotask(grow);
4373
+ return area;
4374
+ };
4375
+ const withCustom = (row, clearPick) => {
4376
+ if (field.kind !== "select" && field.kind !== "multiselect") return row;
4377
+ const custom = field.custom;
4378
+ if (!custom) return row;
4379
+ const group = h("div", "ez-acp-group");
4380
+ const box = textField(custom.key, {
4381
+ placeholder: field.kind === "multiselect" ? "\u5176\u4ED6\u7B54\u6848\uFF08\u9078\u586B\uFF09" : "\u81EA\u5DF1\u56DE\u7B54\uFF0C\u6216\u88DC\u5145\u8AAA\u660E\uFF08\u9078\u586B\uFF09",
4382
+ onInput: () => {
4383
+ if (field.kind === "select" && box.value) clearPick();
4384
+ }
4385
+ });
4386
+ group.append(row, box);
4387
+ return group;
4388
+ };
4389
+ switch (field.kind) {
4390
+ case "select": {
4391
+ const row = labelled(h("div", "ez-acp-ask-options"), "radiogroup");
4392
+ const clearPick = () => {
4393
+ values.delete(field.key);
4394
+ for (const b of row.querySelectorAll("button")) b.classList.remove("ez-on");
4395
+ };
4396
+ const group = withCustom(row, clearPick);
4397
+ for (const option of field.options) {
4398
+ const btn = optionButton(option);
4399
+ if (option.id === field.default) {
4400
+ values.set(field.key, option.id);
4401
+ btn.classList.add("ez-on");
4402
+ }
4403
+ btn.onclick = () => {
4404
+ clearPick();
4405
+ values.set(field.key, option.id);
4406
+ btn.classList.add("ez-on");
4407
+ if (field.custom) {
4408
+ const box = group.querySelector("textarea");
4409
+ if (box) box.value = "";
4410
+ values.delete(field.custom.key);
4411
+ }
4412
+ changed("pick");
4413
+ };
4414
+ row.append(btn);
4415
+ }
4416
+ return group;
4417
+ }
4418
+ case "multiselect": {
4419
+ const row = labelled(h("div", "ez-acp-ask-options"), "group");
4420
+ const picked = new Set(field.default ?? []);
4421
+ const sync = () => {
4422
+ if (picked.size) values.set(field.key, [...picked]);
4423
+ else values.delete(field.key);
4424
+ const full = field.max !== void 0 && picked.size >= field.max;
4425
+ for (const b of row.querySelectorAll("button")) {
4426
+ const on = picked.has(b.dataset.id ?? "");
4427
+ b.classList.toggle("ez-on", on);
4428
+ b.setAttribute("aria-pressed", String(on));
4429
+ b.disabled = full && !on;
4430
+ }
4431
+ };
4432
+ for (const option of field.options) {
4433
+ const btn = optionButton(option);
4434
+ btn.dataset.id = option.id;
4435
+ btn.onclick = () => {
4436
+ if (picked.has(option.id)) picked.delete(option.id);
4437
+ else picked.add(option.id);
4438
+ sync();
4439
+ changed("pick");
4440
+ };
4441
+ row.append(btn);
4442
+ }
4443
+ sync();
4444
+ return withCustom(row, () => {
4445
+ });
4446
+ }
4447
+ case "boolean": {
4448
+ const row = labelled(h("div", "ez-acp-ask-options"), "radiogroup");
4449
+ for (const [value, name] of [
4450
+ [true, "\u662F"],
4451
+ [false, "\u5426"]
4452
+ ]) {
4453
+ const btn = optionButton({ id: String(value), name });
4454
+ if (field.default === value) {
4455
+ values.set(field.key, value);
4456
+ btn.classList.add("ez-on");
4457
+ }
4458
+ btn.onclick = () => {
4459
+ values.set(field.key, value);
4460
+ for (const b of row.querySelectorAll("button")) b.classList.remove("ez-on");
4461
+ btn.classList.add("ez-on");
4462
+ changed("pick");
4463
+ };
4464
+ row.append(btn);
4465
+ }
4466
+ return row;
4467
+ }
4468
+ case "number": {
4469
+ const input = labelled(h("input", "ez-acp-field ez-acp-field-number"));
4470
+ input.type = "number";
4471
+ if (field.min !== void 0) input.min = String(field.min);
4472
+ if (field.max !== void 0) input.max = String(field.max);
4473
+ input.step = field.integer ? "1" : "any";
4474
+ if (field.default !== void 0) {
4475
+ input.value = String(field.default);
4476
+ values.set(field.key, field.default);
4477
+ }
4478
+ input.oninput = () => {
4479
+ const n = input.value === "" ? NaN : Number(input.value);
4480
+ if (Number.isFinite(n)) values.set(field.key, n);
4481
+ else values.delete(field.key);
4482
+ changed("edit");
4483
+ };
4484
+ input.onkeydown = chord;
4485
+ return input;
4486
+ }
4487
+ case "text":
4488
+ return labelled(
4489
+ textField(field.key, {
4490
+ ...field.max !== void 0 ? { max: field.max } : {},
4491
+ ...field.format ? { placeholder: field.format } : {},
4492
+ ...field.default !== void 0 ? { initial: field.default } : {}
4493
+ })
4494
+ );
4495
+ }
4496
+ }
4497
+ window.addEventListener("message", (e) => {
4498
+ if (e.origin !== location.origin) return;
4499
+ const from = frameIdOf(e.source);
4500
+ if (!from) return;
4501
+ const data = e.data;
4502
+ if (data?.type === "ez:wheel") {
4503
+ stage.scrollLeft += data.dx ?? 0;
4504
+ stage.scrollTop += data.dy ?? 0;
4505
+ }
4506
+ if (data?.type === "ez:scroll" && typeof data.ratio === "number" && multi && (!popupFrame || from === popupFrame)) {
4507
+ for (const f of frames.values()) {
4508
+ if (f.id !== from) toFrame(f.id, { type: "ez:scroll-to", ratio: data.ratio });
4509
+ }
4510
+ }
4511
+ if (data?.type === "ez:popup") {
4512
+ if (data.open) setPopupFrame(from);
4513
+ else if (from === popupFrame) setPopupFrame(null);
4514
+ }
4515
+ if (data?.type === "ez:mode") {
4516
+ annotateMode = data.mode ?? "off";
4517
+ paintAnnotateMode();
4518
+ for (const f of frames.values()) {
4519
+ if (f.id !== from) toFrame(f.id, { type: "ez:set-mode", mode: annotateMode });
4520
+ }
4521
+ }
4522
+ if (data?.type === "ez:navigate" && data.url) {
4523
+ dispatchNav({ t: "request", url: data.url });
4524
+ }
4525
+ if (data?.type === "ez:page" && data.url) {
4526
+ dispatchNav({ t: "moved", url: data.url, from });
4527
+ }
4528
+ if (data?.type === "ez:explore" && data.anchor && data.capture) {
4529
+ void (async () => {
4530
+ await reportPageStorage();
4531
+ const res = await api("/explore/start", {
4532
+ method: "POST",
4533
+ body: JSON.stringify({
4534
+ anchor: data.anchor,
4535
+ capture: data.capture,
4536
+ direction: data.direction ?? "",
4537
+ attachments: data.attachments ?? [],
4538
+ references: data.references ?? []
4539
+ })
4540
+ });
4541
+ if (!res.ok) {
4542
+ stripNotice("\u9019\u500B agent \u73FE\u5728\u7121\u6CD5\u57F7\u884C\u63A2\u7D22");
4543
+ endExploreIn();
4544
+ return;
4545
+ }
4546
+ const { exploreId } = await res.json();
4547
+ if (exploreHold && exploreId) exploreHold = { ...exploreHold, round: exploreId };
4548
+ })();
4549
+ }
4550
+ if (data?.type === "ez:ready") {
4551
+ dispatchNav({ t: "loaded", url: data.url ?? data.page ?? "/", from });
4552
+ toFrame(from, { type: "ez:set-mode", mode: annotateMode });
4553
+ toFrame(from, { type: "ez:can-explore", on: snapshot?.canExplore === true });
4554
+ toFrame(from, {
4555
+ type: "ez:in-explore",
4556
+ on: !!snapshot?.chats?.find((c) => c.current)?.parentChatId
4557
+ });
4558
+ seedVariants(from);
4559
+ const frame = frames.get(from);
4560
+ if (frame) {
4561
+ toFrame(from, {
4562
+ type: "ez:viewport",
4563
+ preset: frame.device.id,
4564
+ zoom: frame.zoom,
4565
+ scoped: multi
4566
+ });
4567
+ }
4568
+ if (popupFrame && popupFrame !== from) toFrame(from, { type: "ez:hold", held: true });
4569
+ else if (popupFrame === from) setPopupFrame(null);
4570
+ dispatchPick({
4571
+ t: "ready",
4572
+ page: data.page ?? "/",
4573
+ now: Date.now(),
4574
+ frame: from
4575
+ });
4576
+ }
4577
+ if (data?.type === "ez:explore-held" && from && data.draft) {
4578
+ exploreHold = { frame: from, draft: data.draft, mode: annotateMode };
4579
+ }
4580
+ if (data?.type === "ez:pick-armed" && data.pickId) {
4581
+ dispatchPick({
4582
+ t: "armed",
4583
+ id: data.pickId,
4584
+ host: data.host ?? "popup",
4585
+ now: Date.now(),
4586
+ frame: from
4587
+ });
4588
+ }
4589
+ if (data?.type === "ez:draft" && data.pickId && data.draft) {
4590
+ dispatchPick({
4591
+ t: "draft",
4592
+ id: data.pickId,
4593
+ draft: data.draft,
4594
+ frame: from
4595
+ });
4596
+ }
4597
+ if (data?.type === "ez:picked" && data.pickId && data.ref) {
4598
+ dispatchPick({
4599
+ t: "picked",
4600
+ id: data.pickId,
4601
+ ref: data.ref,
4602
+ page: data.page ?? "/",
4603
+ frame: from
4604
+ });
4605
+ }
4606
+ if (data?.type === "ez:draft-done" && data.pickId) {
4607
+ dispatchPick({ t: "draft-done", id: data.pickId });
4608
+ }
4609
+ if (data?.type === "ez:draft-expired" && data.pickId) {
4610
+ dispatchPick({ t: "expired", id: data.pickId, frame: from });
4611
+ }
4612
+ if (data?.type === "ez:pick-cancelled" && data.pickId) {
4613
+ dispatchPick({
4614
+ t: "cancelled",
4615
+ id: data.pickId,
4616
+ resumed: Boolean(data.resumed),
4617
+ frame: from
4618
+ });
4619
+ }
4620
+ if (data?.type === "ez:key") {
4621
+ if (data.chord) runShortcut(data.chord, false);
4622
+ }
4623
+ });
4624
+ function escapeAnnotating() {
4625
+ if (annotateMode === "off") return;
4626
+ broadcast({ type: "ez:escape" });
4627
+ }
4628
+ document.addEventListener("keydown", (e) => {
4629
+ if (runShortcut(e, isTyping(e.target))) e.preventDefault();
4630
+ });
4631
+ var servedBy = null;
4632
+ var events = new EventSource(`${API}/events`);
4633
+ events.onmessage = (e) => {
4634
+ snapshot = JSON.parse(e.data);
4635
+ servedBy ?? (servedBy = snapshot.version);
4636
+ if (snapshot.version !== servedBy) {
4637
+ location.reload();
4638
+ return;
4639
+ }
4640
+ version.textContent = `v${snapshot.version}`;
4641
+ render();
4642
+ };
4643
+ paintPage();
4644
+ })();