dsh-context-domains 0.3.6 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/client.js CHANGED
@@ -35,6 +35,21 @@ window.__ModuleLoader__.load({
35
35
  .cdx-dot{width:6px;height:6px;border-radius:50%;flex:0 0 6px;background:var(--dsw-alias-state-idle-primary)}
36
36
  .cdx-chip[aria-checked="true"] .cdx-dot{background:var(--dsw-alias-state-success-primary);
37
37
  box-shadow:0 0 0 3px color-mix(in srgb, var(--dsw-alias-state-success-primary) 22%, transparent)}
38
+ /* 压缩阈值计数器:条的最右边,左减右加、中间是数字(单位 10 万 token) */
39
+ .cdx-budget{display:inline-flex;align-items:center;flex:0 0 auto;gap:1px;height:22px;margin-left:8px;padding:0 3px;
40
+ border:1px solid var(--dsw-alias-border-l1);border-radius:999px;
41
+ background:color-mix(in srgb, var(--dsw-alias-bg-layer-1) 72%, transparent);
42
+ font-variant-numeric:tabular-nums;user-select:none}
43
+ .cdx-budget-btn{display:grid;place-items:center;width:17px;height:17px;padding:0;border:0;border-radius:50%;
44
+ background:transparent;font:inherit;font-size:12px;line-height:1;cursor:pointer;
45
+ color:var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary))}
46
+ .cdx-budget-btn:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover, rgba(127,127,127,.14));
47
+ color:var(--dsw-alias-label-primary)}
48
+ .cdx-budget-btn:disabled{opacity:.3;cursor:default}
49
+ .cdx-budget-num{min-width:17px;text-align:center;font-size:11.5px;font-weight:600;
50
+ color:var(--dsw-alias-label-primary);cursor:default}
51
+ .cdx-budget[data-busy="true"]{opacity:.55}
52
+ .cdx-budget[data-degraded="true"] .cdx-budget-num{color:var(--dsw-alias-label-warning, #e8a33d)}
38
53
  .cdx-more{flex:0 0 auto;width:22px;height:22px;border-radius:6px;border:0;background:transparent;
39
54
  color:var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary));cursor:pointer;line-height:1}
40
55
  .cdx-more:hover{background:var(--dsw-alias-interactive-bg-hover, rgba(127,127,127,.12));
@@ -58,51 +73,221 @@ window.__ModuleLoader__.load({
58
73
  .cdx-fallback{display:flex;align-items:center;gap:3px;max-width:42vw;overflow-x:auto;scrollbar-width:none}
59
74
  .cdx-fallback::-webkit-scrollbar{height:0}
60
75
 
61
- .cdx-set{display:flex;gap:16px;min-height:380px;color:var(--dsw-alias-label-primary);container-type:inline-size}
62
- .cdx-set-list{width:215px;flex:0 0 215px;display:flex;flex-direction:column;gap:3px}
63
- .cdx-set-list-hd{display:flex;align-items:center;gap:8px;margin-bottom:6px;font-size:11.5px;
76
+ /* ---------- 设置面板:左列领域清单 / 右列编辑器(视觉稿来自 OpenDesign cdx-settings-v2) ---------- */
77
+ .cdx-set,.cdx-set *,.cdx-scrim,.cdx-scrim *{box-sizing:border-box}
78
+ .cdx-set{display:flex;height:clamp(440px,68vh,690px);min-height:400px;overflow:hidden;
79
+ border:1px solid var(--dsw-alias-border-l1);border-radius:14px;background:var(--dsw-alias-bg-layer-1);
80
+ color:var(--dsw-alias-label-primary);container-type:inline-size}
81
+ /* 左列 200px */
82
+ .cdx-side{display:flex;flex:0 0 200px;flex-direction:column;width:200px;
83
+ background:color-mix(in srgb, var(--dsw-alias-bg-base) 55%, var(--dsw-alias-bg-layer-1));
84
+ border-right:1px solid var(--dsw-alias-border-l1)}
85
+ .cdx-side-hd{display:flex;flex:0 0 auto;align-items:center;justify-content:space-between;gap:8px;
86
+ height:48px;padding:0 12px;border-bottom:1px solid var(--dsw-alias-border-l1)}
87
+ .cdx-side-count{font-size:11px;letter-spacing:.04em;white-space:nowrap;
88
+ color:var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary))}
89
+ .cdx-side-actions{display:flex;align-items:center;gap:4px}
90
+ .cdx-side-list{flex:1 1 auto;min-height:0;display:flex;flex-direction:column;gap:2px;
91
+ margin:0;padding:8px 8px 0;overflow-y:auto;list-style:none;
92
+ scrollbar-width:thin;scrollbar-color:var(--dsw-alias-scrollbar-bg-l2, rgba(127,127,127,.35)) transparent}
93
+ .cdx-side-list::-webkit-scrollbar{width:6px}
94
+ .cdx-side-list::-webkit-scrollbar-thumb{background:var(--dsw-alias-scrollbar-bg-l2, rgba(127,127,127,.35));border-radius:3px}
95
+ .cdx-side-list::-webkit-scrollbar-thumb:hover{background:var(--dsw-alias-scrollbar-hover-l2, rgba(127,127,127,.55))}
96
+ .cdx-domain{position:relative;display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;
97
+ gap:8px;width:100%;min-height:56px;padding:8px 8px 8px 12px;border:0;border-radius:8px;
98
+ background:transparent;color:inherit;font:inherit;text-align:left;cursor:pointer;
99
+ transition:background-color .14s ease}
100
+ .cdx-domain:hover{background:var(--dsw-alias-interactive-bg-hover, rgba(127,127,127,.12))}
101
+ .cdx-domain[aria-selected="true"]{background:var(--dsw-alias-interactive-bg-active, rgba(127,127,127,.18))}
102
+ .cdx-domain-mark{position:absolute;top:10px;bottom:10px;left:0;width:2px;border-radius:2px;background:transparent}
103
+ .cdx-domain[aria-selected="true"] .cdx-domain-mark{background:var(--dsw-alias-brand-primary, #5686fe)}
104
+ .cdx-domain-body{display:flex;flex-direction:column;gap:2px;min-width:0}
105
+ .cdx-domain-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
106
+ font-size:13px;font-weight:500;color:var(--dsw-alias-label-secondary)}
107
+ .cdx-domain[aria-selected="true"] .cdx-domain-name{color:var(--dsw-alias-label-primary)}
108
+ .cdx-domain-meta{display:flex;align-items:center;gap:4px;font-size:11px;
109
+ color:var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary))}
110
+ .cdx-domain-badge{flex:0 0 auto;height:16px;padding:0 4px;font-size:11px;line-height:14px;border-radius:4px;
111
+ color:var(--dsw-alias-state-success-primary);
112
+ border:1px solid color-mix(in srgb, var(--dsw-alias-state-success-primary) 42%, transparent)}
113
+ .cdx-domain-ratio{flex:0 0 auto;white-space:nowrap;font-variant-numeric:tabular-nums;font-size:11px;
114
+ font-family:var(--dsh-font-family-code, ui-monospace, SFMono-Regular, Menlo, monospace);
115
+ color:var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary))}
116
+ .cdx-side-foot{flex:0 0 auto;margin:0;padding:12px;font-size:11px;line-height:1.5;
117
+ color:var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary));
118
+ border-top:1px solid var(--dsw-alias-border-l1)}
119
+ /* 右列 */
120
+ .cdx-main{display:flex;flex:1 1 auto;flex-direction:column;gap:16px;min-width:0;padding:16px}
121
+ .cdx-field{display:flex;flex-direction:column;gap:8px}
122
+ .cdx-field-lbl{font-size:11px;letter-spacing:.04em;
123
+ color:var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary))}
124
+ .cdx-input{width:100%;height:32px;padding:0 8px;font:inherit;font-size:12px;outline:none;
125
+ color:var(--dsw-alias-label-primary);
126
+ background:var(--dsw-alias-bg-layer-2);
127
+ border:1px solid var(--dsw-alias-border-l2);border-radius:6px;
128
+ transition:border-color .14s ease, box-shadow .14s ease}
129
+ .cdx-input::placeholder{color:var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary))}
130
+ .cdx-input:focus{border-color:var(--dsw-alias-brand-primary, #5686fe);
131
+ box-shadow:0 0 0 3px color-mix(in srgb, var(--dsw-alias-brand-primary, #5686fe) 18%, transparent)}
132
+ .cdx-input-area{height:56px;padding:8px;line-height:1.5;resize:none}
133
+ /* 默认启用开关行 */
134
+ .cdx-switchrow{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:8px 0;
135
+ border-top:1px solid var(--dsw-alias-border-l1);border-bottom:1px solid var(--dsw-alias-border-l1)}
136
+ .cdx-switchrow-text{display:flex;flex-direction:column;gap:2px;min-width:0}
137
+ .cdx-switchrow-lbl{font-size:12px;color:var(--dsw-alias-label-primary)}
138
+ .cdx-switchrow-hint{font-size:11px;color:var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary))}
139
+ .cdx-switch{position:relative;flex:0 0 auto;width:32px;height:18px;padding:0;cursor:pointer;
140
+ background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:999px;
141
+ transition:background-color .14s ease, border-color .14s ease}
142
+ .cdx-switch::after{content:"";position:absolute;top:2px;left:2px;width:12px;height:12px;border-radius:50%;
143
+ background:var(--dsw-alias-switch-thumb, var(--dsw-alias-label-tertiary, #888));
144
+ transition:transform .14s ease, background-color .14s ease}
145
+ .cdx-switch[aria-checked="true"]{border-color:var(--dsw-alias-brand-primary);
146
+ background:var(--dsw-alias-brand-primary)}
147
+ .cdx-switch[aria-checked="true"]::after{background:var(--dsw-alias-label-primary-foreground);transform:translateX(14px)}
148
+ .cdx-switch:focus-visible{outline:none;
149
+ box-shadow:0 0 0 3px color-mix(in srgb, var(--dsw-alias-brand-primary, #5686fe) 20%, transparent)}
150
+ .cdx-switch:disabled{cursor:default;opacity:.6}
151
+ /* 成员区 */
152
+ .cdx-members{display:flex;flex:1 1 auto;flex-direction:column;gap:4px;min-height:0}
153
+ .cdx-search{display:flex;align-items:center;height:32px;border-radius:8px;
154
+ background:var(--dsw-alias-bg-layer-2);
155
+ border:1px solid var(--dsw-alias-border-l2);
156
+ transition:border-color .14s ease, box-shadow .14s ease}
157
+ .cdx-search:focus-within{border-color:var(--dsw-alias-brand-primary, #5686fe);
158
+ box-shadow:0 0 0 3px color-mix(in srgb, var(--dsw-alias-brand-primary, #5686fe) 18%, transparent)}
159
+ .cdx-search-input{flex:1 1 auto;min-width:0;height:100%;padding:0 8px;font:inherit;font-size:12px;
160
+ color:var(--dsw-alias-label-primary);background:transparent;border:0;outline:none;appearance:none}
161
+ .cdx-search-input::placeholder{color:var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary))}
162
+ .cdx-search-input::-webkit-search-cancel-button{display:none}
163
+ .cdx-search-meta{flex:0 0 auto;padding:0 8px;font-size:11px;white-space:nowrap;font-variant-numeric:tabular-nums;
164
+ color:var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary));
165
+ border-left:1px solid var(--dsw-alias-border-l1)}
166
+ .cdx-filters{display:flex;align-items:center;justify-content:space-between;gap:8px;row-gap:6px;
167
+ flex-wrap:wrap;padding:4px 0}
168
+ .cdx-filters-right{display:flex;align-items:center;gap:4px;min-width:0}
169
+ .cdx-filters-lbl{font-size:11px;white-space:nowrap;
170
+ color:var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary))}
171
+ .cdx-seg{display:inline-flex;gap:2px;padding:2px;border-radius:8px;
172
+ background:var(--dsw-alias-bg-layer-2);
173
+ border:1px solid var(--dsw-alias-border-l1)}
174
+ .cdx-seg-btn{height:22px;padding:0 6px;font:inherit;font-size:11px;white-space:nowrap;cursor:pointer;
175
+ color:var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary));
176
+ background:transparent;border:0;border-radius:6px;transition:background-color .14s ease, color .14s ease}
177
+ .cdx-seg-btn:hover{background:var(--dsw-alias-interactive-bg-hover, rgba(127,127,127,.12));
178
+ color:var(--dsw-alias-label-primary)}
179
+ .cdx-seg-btn[aria-pressed="true"]{
180
+ background:color-mix(in srgb, var(--dsw-alias-brand-primary) 22%, transparent);
181
+ color:var(--dsw-alias-label-primary);font-weight:600}
182
+ .cdx-seg-btn:focus-visible{outline:none;
183
+ box-shadow:0 0 0 2px color-mix(in srgb, var(--dsw-alias-brand-primary, #5686fe) 22%, transparent)}
184
+ .cdx-list{display:grid;grid-template-columns:1fr 1fr;column-gap:8px;align-content:start;
185
+ flex:1 1 auto;min-height:160px;max-height:260px;padding:4px 4px 4px 0;
186
+ overflow-y:auto;overscroll-behavior:contain;
187
+ border-top:1px solid var(--dsw-alias-border-l1);
188
+ scrollbar-width:thin;scrollbar-color:var(--dsw-alias-scrollbar-bg-l2, rgba(127,127,127,.35)) transparent}
189
+ .cdx-list-one{grid-template-columns:1fr}
190
+ .cdx-list::-webkit-scrollbar{width:6px}
191
+ .cdx-list::-webkit-scrollbar-thumb{background:var(--dsw-alias-scrollbar-bg-l2, rgba(127,127,127,.35));border-radius:3px}
192
+ .cdx-list::-webkit-scrollbar-thumb:hover{background:var(--dsw-alias-scrollbar-hover-l2, rgba(127,127,127,.55))}
193
+ .cdx-member{position:relative;display:flex;align-items:center;gap:8px;height:26px;min-width:0;
194
+ padding:0 4px;border-radius:6px;cursor:pointer;transition:background-color .14s ease}
195
+ .cdx-member:hover{background:var(--dsw-alias-interactive-bg-hover, rgba(127,127,127,.12))}
196
+ .cdx-member input{position:absolute;width:13px;height:13px;margin:0;opacity:0;pointer-events:none}
197
+ .cdx-check{position:relative;flex:0 0 auto;width:13px;height:13px;border-radius:4px;
198
+ background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);
199
+ transition:background-color .14s ease, border-color .14s ease}
200
+ .cdx-check::after{content:"";position:absolute;top:1px;left:3.5px;width:4px;height:7px;
201
+ border-right:1.5px solid var(--dsw-alias-label-primary-foreground, #fff);
202
+ border-bottom:1.5px solid var(--dsw-alias-label-primary-foreground, #fff);
203
+ opacity:0;transform:rotate(40deg) scale(.5);transition:opacity .14s ease, transform .14s ease}
204
+ .cdx-member input:checked + .cdx-check{
205
+ background:var(--dsw-alias-brand-primary, #5686fe);border-color:var(--dsw-alias-brand-primary, #5686fe)}
206
+ .cdx-member input:checked + .cdx-check::after{opacity:1;transform:rotate(40deg) scale(1)}
207
+ .cdx-member input:focus-visible + .cdx-check{border-color:var(--dsw-alias-brand-primary, #5686fe);
208
+ box-shadow:0 0 0 2px color-mix(in srgb, var(--dsw-alias-brand-primary, #5686fe) 22%, transparent)}
209
+ .cdx-member-name{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1;
210
+ font-family:var(--dsh-font-family-code, ui-monospace, SFMono-Regular, Menlo, monospace);font-size:11px;
64
211
  color:var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary))}
65
- .cdx-btn{font:inherit;font-size:12px;padding:5px 10px;border-radius:7px;cursor:pointer;white-space:nowrap;
66
- border:1px solid var(--dsw-alias-border-l2);background:transparent;color:var(--dsw-alias-label-primary)}
67
- .cdx-btn:hover{background:var(--dsw-alias-interactive-bg-hover, rgba(127,127,127,.12))}
68
- .cdx-btn-primary{background:var(--dsw-alias-button-primary-fill, #111);color:var(--dsw-alias-label-primary-foreground, #fff);
69
- border-color:transparent;font-weight:600}
70
- .cdx-dcard{display:flex;align-items:center;gap:9px;padding:8px 9px;border-radius:9px;cursor:pointer;
71
- border:1px solid transparent;background:transparent;color:inherit;text-align:left;font:inherit}
72
- .cdx-dcard:hover{background:var(--dsw-alias-interactive-bg-hover, rgba(127,127,127,.12))}
73
- .cdx-dcard[aria-selected="true"]{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-border-l2)}
74
- .cdx-dcard-ico{width:24px;height:24px;flex:0 0 24px;border-radius:7px;display:grid;place-items:center;font-size:12px;
75
- background:var(--dsw-alias-bg-layer-2)}
76
- .cdx-dcard-n{font-size:12.5px;display:block}
77
- .cdx-dcard-m{font-size:10.5px;display:block;color:var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary))}
78
- .cdx-badge{margin-left:auto;flex:0 0 auto;white-space:nowrap;font-size:9.5px;padding:0 5px;border-radius:4px;
79
- color:var(--dsw-alias-state-success-primary);background:color-mix(in srgb, var(--dsw-alias-state-success-primary) 14%, transparent)}
80
- .cdx-set-edit{flex:1 1 auto;min-width:0;display:flex;flex-direction:column;gap:12px}
81
- .cdx-field{display:flex;gap:12px;align-items:flex-start}
82
- .cdx-field>label{width:74px;flex:0 0 74px;font-size:11.5px;padding-top:6px;
212
+ .cdx-member input:checked ~ .cdx-member-name{color:var(--dsw-alias-label-secondary)}
213
+ /* 空态 */
214
+ .cdx-empty{grid-column:1 / -1;display:flex;flex-direction:column;align-items:center;justify-content:center;
215
+ gap:8px;min-height:200px;padding:16px;text-align:center}
216
+ .cdx-empty-text{margin:0;font-size:11px;
83
217
  color:var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary))}
84
- .cdx-input{flex:1 1 auto;min-width:0;font:inherit;font-size:12.5px;padding:6px 9px;border-radius:8px;
85
- border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary)}
86
- .cdx-mem-hd{display:flex;align-items:center;gap:8px}
87
- .cdx-grid{display:grid;grid-template-columns:1fr 1fr;gap:3px 12px}
88
- .cdx-mem{display:flex;align-items:center;gap:8px;padding:4px 6px;border-radius:7px;font-size:12px;cursor:pointer;
89
- color:var(--dsw-alias-label-secondary)}
90
- .cdx-mem:hover{background:var(--dsw-alias-interactive-bg-hover, rgba(127,127,127,.12))}
91
- .cdx-mem input{appearance:none;width:13px;height:13px;margin:0;border-radius:4px;flex:0 0 13px;position:relative;
92
- border:1.5px solid var(--dsw-alias-label-tertiary, #888);background:transparent}
93
- .cdx-mem input:checked{background:var(--dsw-alias-brand-primary, #5686fe);border-color:var(--dsw-alias-brand-primary, #5686fe)}
94
- .cdx-mem input:checked::after{content:"";position:absolute;left:3px;top:0;width:4px;height:7px;
95
- border:2px solid #fff;border-top:0;border-left:0;transform:rotate(42deg)}
96
- .cdx-mem .nm{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
97
- font-family:var(--dsh-font-family-code, ui-monospace, SFMono-Regular, Menlo, monospace);font-size:11.5px}
98
- .cdx-empty{padding:20px 8px;font-size:12px;color:var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary))}
99
218
  .cdx-hint{font-size:11px;color:var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary))}
100
- .cdx-scroll{max-height:250px;overflow:auto;padding-right:4px}
101
- @container (max-width: 720px){
102
- .cdx-grid{grid-template-columns:1fr}
103
- .cdx-field{flex-wrap:wrap;gap:4px}
104
- .cdx-field>label{width:100%;flex:1 1 100%;padding-top:0}
105
- .cdx-set-list{width:180px;flex:0 0 180px}
219
+ .cdx-empty-btn{height:24px;padding:0 8px;font:inherit;font-size:11px;cursor:pointer;border-radius:6px;
220
+ color:var(--dsw-alias-label-secondary);background:transparent;border:1px solid var(--dsw-alias-border-l2)}
221
+ .cdx-empty-btn:hover{background:var(--dsw-alias-interactive-bg-hover, rgba(127,127,127,.12));
222
+ color:var(--dsw-alias-label-primary)}
223
+ /* 底部操作 */
224
+ .cdx-main-foot{display:flex;flex:0 0 auto;align-items:center;gap:8px;margin-top:auto;padding-top:12px;
225
+ border-top:1px solid var(--dsw-alias-border-l1)}
226
+ .cdx-btn{display:inline-flex;align-items:center;justify-content:center;gap:4px;height:26px;padding:0 8px;
227
+ font:inherit;font-size:11px;white-space:nowrap;cursor:pointer;border-radius:6px;
228
+ color:var(--dsw-alias-label-secondary);
229
+ background:var(--dsw-alias-bg-layer-2);
230
+ border:1px solid var(--dsw-alias-border-l2);
231
+ transition:background-color .14s ease, border-color .14s ease, color .14s ease, transform .14s ease}
232
+ .cdx-btn:hover{background:var(--dsw-alias-interactive-bg-hover, rgba(127,127,127,.12));
233
+ color:var(--dsw-alias-label-primary)}
234
+ .cdx-btn:active{transform:translateY(1px)}
235
+ .cdx-btn:disabled{opacity:.5;cursor:default}
236
+ .cdx-btn-primary{height:36px;font-size:13px;font-weight:600;border-color:transparent;
237
+ color:var(--dsw-alias-label-primary-foreground, #fff);background:var(--dsw-alias-brand-primary, #5686fe)}
238
+ .cdx-btn-primary:hover{filter:brightness(1.08);color:var(--dsw-alias-label-primary-foreground, #fff);
239
+ background:var(--dsw-alias-brand-primary, #5686fe)}
240
+ .cdx-btn-danger{height:30px;padding:0 12px;background:transparent;border-color:var(--dsw-alias-border-l2);
241
+ color:var(--dsw-alias-label-error)}
242
+ .cdx-btn-danger:hover{color:var(--dsw-alias-label-error);border-color:var(--dsw-alias-label-error);
243
+ background:color-mix(in srgb, var(--dsw-alias-label-error) 12%, transparent)}
244
+ .cdx-iconbtn{display:grid;place-items:center;width:24px;height:24px;padding:0;font:inherit;font-size:12px;
245
+ line-height:1;cursor:pointer;border-radius:6px;background:transparent;border:1px solid transparent;
246
+ color:var(--dsw-alias-label-secondary);transition:background-color .14s ease, border-color .14s ease, color .14s ease}
247
+ .cdx-iconbtn:hover{background:var(--dsw-alias-interactive-bg-hover, rgba(127,127,127,.12));
248
+ border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-brand-primary, #5686fe)}
249
+ .cdx-iconbtn-accent{color:var(--dsw-alias-brand-primary, #5686fe)}
250
+ /* AI 归类弹窗 */
251
+ .cdx-scrim{position:fixed;inset:0;z-index:1200;display:grid;place-items:center;padding:24px;
252
+ background:var(--dsw-alias-bg-mask-1, rgba(0,0,0,.4));
253
+ -webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px)}
254
+ .cdx-modal{display:flex;flex-direction:column;gap:12px;width:520px;max-width:100%;max-height:86vh;
255
+ padding:16px;box-sizing:border-box;border-radius:14px;border:1px solid var(--dsw-alias-border-l2);
256
+ background:var(--dsw-alias-bg-layer-1);
257
+ color:var(--dsw-alias-label-primary);box-shadow:0 28px 72px rgba(0,0,0,.42)}
258
+ .cdx-modal-hd{display:flex;align-items:center;justify-content:space-between;gap:8px}
259
+ .cdx-modal-title{margin:0;font-size:15px;font-weight:600;color:var(--dsw-alias-label-primary)}
260
+ .cdx-modal-desc{margin:0;font-size:11px;line-height:1.6;
261
+ color:var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary))}
262
+ .cdx-prompt{display:flex;flex-direction:column;overflow:hidden;cursor:copy;border-radius:8px;
263
+ background:color-mix(in srgb, var(--dsw-alias-bg-base) 55%, var(--dsw-alias-bg-layer-1));
264
+ border:1px solid var(--dsw-alias-border-l2);transition:border-color .14s ease}
265
+ .cdx-prompt:hover{border-color:var(--dsw-alias-brand-primary, #5686fe)}
266
+ .cdx-prompt:focus-visible{outline:none;border-color:var(--dsw-alias-brand-primary, #5686fe);
267
+ box-shadow:0 0 0 3px color-mix(in srgb, var(--dsw-alias-brand-primary, #5686fe) 18%, transparent)}
268
+ .cdx-prompt-text{max-height:248px;margin:0;padding:12px;overflow:auto;white-space:pre-wrap;
269
+ overflow-wrap:break-word;font-family:var(--dsh-font-family-code, ui-monospace, SFMono-Regular, Menlo, monospace);
270
+ font-size:11px;line-height:1.7;color:var(--dsw-alias-label-secondary);scrollbar-width:thin}
271
+ .cdx-prompt-text::-webkit-scrollbar{width:6px}
272
+ .cdx-prompt-text::-webkit-scrollbar-thumb{background:var(--dsw-alias-scrollbar-bg-l2, rgba(127,127,127,.35));border-radius:3px}
273
+ .cdx-prompt-hint{align-self:flex-end;height:20px;margin:6px 6px 0;padding:0 4px;font-size:11px;line-height:18px;
274
+ white-space:nowrap;pointer-events:none;opacity:0;border-radius:6px;transition:opacity .14s ease;
275
+ color:var(--dsw-alias-label-secondary);
276
+ background:var(--dsw-alias-bg-layer-2);
277
+ border:1px solid var(--dsw-alias-border-l2)}
278
+ .cdx-prompt:hover .cdx-prompt-hint,
279
+ .cdx-prompt:focus-visible .cdx-prompt-hint,
280
+ .cdx-prompt[data-copied="true"] .cdx-prompt-hint{opacity:1}
281
+ .cdx-prompt[data-copied="true"] .cdx-prompt-hint{color:var(--dsw-alias-state-success-primary);
282
+ border-color:color-mix(in srgb, var(--dsw-alias-state-success-primary) 45%, transparent)}
283
+ .cdx-modal-foot{display:flex}
284
+ .cdx-modal-foot .cdx-btn{width:100%}
285
+ /* 窄面板:左列收窄、成员列表单列 */
286
+ @container (max-width: 520px){
287
+ .cdx-side{flex-basis:172px;width:172px}
288
+ .cdx-list{grid-template-columns:1fr}
289
+ .cdx-main{gap:12px;padding:12px}
290
+ .cdx-filters-lbl{display:none}
106
291
  }
107
292
  @media (prefers-reduced-motion: reduce){.cdx-chip{transition:none}}
108
293
  `
@@ -276,6 +461,66 @@ window.__ModuleLoader__.load({
276
461
  return { skills, tools, total: skills + tools }
277
462
  }
278
463
 
464
+ /* ------------------------------------------------- 压缩阈值计数器 */
465
+ // 条的最右边:左减右加,中间是数字,单位 10 万 token(2 = 20 万,最低值)。
466
+ // 值是全局的(压缩引擎按 preset 挂一个,多会话共用),所以条上改了立刻对全场生效。
467
+ function CompactionCounter({ info }) {
468
+ const [busy, setBusy] = React.useState(false)
469
+ if (info === null || info === undefined) return null
470
+ const step = info.stepTokens > 0 ? info.stepTokens : 100000
471
+ const min = Math.round((info.minTokens ?? 200000) / step)
472
+ const max = Math.round((info.maxTokens ?? 10000000) / step)
473
+ const value = Math.round((info.budgetTokens ?? min * step) / step)
474
+ const wan = Math.round((info.budgetTokens ?? 0) / 10000)
475
+ const clamped = Array.isArray(info.targets) && info.targets.some((target) => target.clamped === true)
476
+ const degraded = Array.isArray(info.degraded) && info.degraded.length > 0
477
+
478
+ const move = async (delta) => {
479
+ const next = Math.min(max, Math.max(min, value + delta))
480
+ if (busy || next === value) return
481
+ setBusy(true)
482
+ try {
483
+ await call('/compaction', { tokens: next })
484
+ broadcast()
485
+ } catch (error) {
486
+ console.warn('[context-domains] compaction budget failed', error)
487
+ } finally {
488
+ setBusy(false)
489
+ }
490
+ }
491
+
492
+ const title = `上下文压缩阈值 ${wan} 万 token`
493
+ + `(${value} × ${step / 10000} 万,最低 ${min})\n`
494
+ + `全局生效:所有会话共用这一个数;改了下一轮就用新值,不用重启。\n`
495
+ + (clamped ? '当前模型窗口比这个数小,实际按窗口上限压。\n' : '')
496
+ + (degraded
497
+ ? `降级:${info.degraded.map((item) => item.reason).join(';')}`
498
+ : `已接管 ${info.engines} 个 compaction 引擎`)
499
+
500
+ return h(
501
+ 'div',
502
+ {
503
+ className: 'cdx-budget',
504
+ role: 'group',
505
+ 'aria-label': '上下文压缩阈值',
506
+ 'data-busy': busy ? 'true' : 'false',
507
+ 'data-degraded': degraded ? 'true' : 'false',
508
+ title,
509
+ },
510
+ h('button', {
511
+ className: 'cdx-budget-btn', type: 'button', 'aria-label': '降低压缩阈值 10 万 token',
512
+ disabled: busy || value <= min,
513
+ onClick: () => void move(-1),
514
+ }, '−'),
515
+ h('span', { className: 'cdx-budget-num', 'aria-live': 'polite' }, String(value)),
516
+ h('button', {
517
+ className: 'cdx-budget-btn', type: 'button', 'aria-label': '提高压缩阈值 10 万 token',
518
+ disabled: busy || value >= max,
519
+ onClick: () => void move(1),
520
+ }, '+'),
521
+ )
522
+ }
523
+
279
524
  /* ------------------------------------------------------------- 领域条 */
280
525
  function DomainBar(props) {
281
526
  const sessionId = props.sessionId
@@ -299,7 +544,11 @@ window.__ModuleLoader__.load({
299
544
  }
300
545
 
301
546
  if (!owned) return h('span', { className: 'cdx-slot', 'aria-hidden': 'true' })
302
- if (!snapshot || snapshot.domains.length === 0) return null
547
+ if (!snapshot) return null
548
+ // 一个领域都没有也留着条:压缩阈值计数器挂在同一条上(老 Host 没有这个块就照旧隐藏)
549
+ const compaction = snapshot.compaction ?? null
550
+ if (snapshot.domains.length === 0 && compaction === null) return null
551
+ const counter = h(CompactionCounter, { info: compaction })
303
552
  const openDomain = openId === null ? null : snapshot.domains.find((d) => d.id === openId)
304
553
 
305
554
  const chipList = snapshot.domains.map((domain) =>
@@ -368,7 +617,7 @@ window.__ModuleLoader__.load({
368
617
  React.Fragment,
369
618
  null,
370
619
  h('span', { ref: anchorRef, className: 'cdx-slot', 'aria-hidden': 'true' }),
371
- h('div', { className: 'cdx-fallback', role: 'group', 'aria-label': '上下文领域' }, chipList),
620
+ h('div', { className: 'cdx-fallback', role: 'group', 'aria-label': '上下文领域' }, chipList, counter),
372
621
  popover,
373
622
  )
374
623
  }
@@ -388,6 +637,7 @@ window.__ModuleLoader__.load({
388
637
  title: '点领域开关;在 设置 → 上下文领域 里管理领域',
389
638
  },
390
639
  h('div', { className: 'cdx-bar-chips' }, chipList),
640
+ counter,
391
641
  ),
392
642
  ),
393
643
  popover,
@@ -395,14 +645,37 @@ window.__ModuleLoader__.load({
395
645
  }
396
646
 
397
647
  /* -------------------------------------------------------------- 设置 */
648
+ // 页签与显示范围都记住:重新打开设置页不至于又回到「全部」再翻一遍
649
+ let lastMemberTab = 'all'
650
+ let lastMemberFilter = 'all'
651
+
652
+ /** 给用户抄的 prompt:自动化建域走的是同一套 HTTP 契约,这里只负责把话写顺。 */
653
+ const AI_PROMPT = '读 GET /context-domains/api/catalog,把里面的 skill 和 tool 按用途归成 6–8 个领域:\n'
654
+ + '名字 ≤6 字,一个领域可以同时装 skill 与 tool,成员不要重复。\n'
655
+ + '先给我一份归类清单,我确认后你用 POST /context-domains/api/domains\n'
656
+ + '(op=create, name, description, members:[{type,name}])逐条建好。'
657
+
398
658
  function DomainsSettings() {
399
659
  const [domains, setDomains] = React.useState([])
400
660
  const [catalog, setCatalog] = React.useState({ skills: [], tools: [], suggestions: [], unassigned: { skills: [], tools: [] } })
401
661
  const [selectedId, setSelectedId] = React.useState(null)
402
662
  const [query, setQuery] = React.useState('')
663
+ const [tab, setTab] = React.useState(lastMemberTab)
664
+ const [filter, setFilter] = React.useState(lastMemberFilter)
665
+ const [aiOpen, setAiOpen] = React.useState(false)
666
+ const [copied, setCopied] = React.useState(false)
403
667
  const [error, setError] = React.useState(null)
404
668
  const [busy, setBusy] = React.useState(false)
405
669
 
670
+ const pickTab = (next) => {
671
+ lastMemberTab = next
672
+ setTab(next)
673
+ }
674
+ const pickFilter = (next) => {
675
+ lastMemberFilter = next
676
+ setFilter(next)
677
+ }
678
+
406
679
  const load = React.useCallback(async () => {
407
680
  try {
408
681
  const [domainValue, catalogValue] = await Promise.all([call('/domains'), call('/catalog')])
@@ -423,6 +696,19 @@ window.__ModuleLoader__.load({
423
696
  }, [domains, selectedId])
424
697
 
425
698
  const selected = domains.find((domain) => domain.id === selectedId) ?? null
699
+
700
+ React.useEffect(() => {
701
+ if (!aiOpen) return undefined
702
+ const onKey = (event) => {
703
+ if (event.key !== 'Escape') return
704
+ // 只关这一层:宿主设置弹窗也监听 Esc,别连带把设置页关掉
705
+ event.stopPropagation()
706
+ event.stopImmediatePropagation?.()
707
+ setAiOpen(false)
708
+ }
709
+ document.addEventListener('keydown', onKey, true)
710
+ return () => document.removeEventListener('keydown', onKey, true)
711
+ }, [aiOpen])
426
712
  const run = async (fn) => {
427
713
  setBusy(true)
428
714
  try {
@@ -433,8 +719,9 @@ window.__ModuleLoader__.load({
433
719
  setBusy(false)
434
720
  }
435
721
  }
722
+ // 新建即默认启用:用户建领域的意图就是「要用它」,默认值别再让人多勾一次
436
723
  const create = (draft) => run(async () => {
437
- const created = await call('/domains', { op: 'create', ...draft })
724
+ const created = await call('/domains', { op: 'create', defaultOn: true, ...draft })
438
725
  await load()
439
726
  setSelectedId(created.id)
440
727
  })
@@ -456,8 +743,6 @@ window.__ModuleLoader__.load({
456
743
  skill: catalog.skills.filter((item) => match(item.name)),
457
744
  tool: catalog.tools.filter((item) => match(item.name)),
458
745
  }
459
- const memberNames = (type) => (selected?.members ?? []).filter((m) => m.type === type).map((m) => m.name)
460
-
461
746
  const setMember = (type, name, on) => {
462
747
  if (!selected) return
463
748
  const next = [...(selected.members ?? [])]
@@ -467,31 +752,181 @@ window.__ModuleLoader__.load({
467
752
  void update({ members: next })
468
753
  }
469
754
 
470
- const section = (type, label) => {
471
- const names = pool[type].map((item) => item.name)
472
- if (names.length === 0) return null
473
- return h(
474
- React.Fragment,
475
- { key: type },
476
- h('div', { className: 'cdx-grp', style: { borderTop: 'none', paddingLeft: 0 } },
477
- label, h('span', { className: 'n' }, String(memberNames(type).length))),
478
- h(
755
+ const copyPrompt = async () => {
756
+ try {
757
+ await navigator.clipboard.writeText(AI_PROMPT)
758
+ } catch {
759
+ const area = document.createElement('textarea')
760
+ area.value = AI_PROMPT
761
+ area.style.position = 'fixed'
762
+ area.style.opacity = '0'
763
+ document.body.appendChild(area)
764
+ area.select()
765
+ document.execCommand?.('copy')
766
+ area.remove()
767
+ }
768
+ setCopied(true)
769
+ setTimeout(() => setCopied(false), 1600)
770
+ }
771
+
772
+ /** 分段控件:类型页签与显示范围共用,选中态用 aria-pressed 表达。 */
773
+ const segmented = (label, items, active, onPick) =>
774
+ h(
775
+ 'div',
776
+ { className: 'cdx-seg', role: 'group', 'aria-label': label },
777
+ items.map(([id, text]) =>
778
+ h(
779
+ 'button',
780
+ {
781
+ key: id,
782
+ className: 'cdx-seg-btn',
783
+ type: 'button',
784
+ 'aria-pressed': active === id ? 'true' : 'false',
785
+ onClick: () => onPick(id),
786
+ },
787
+ text,
788
+ ),
789
+ ),
790
+ )
791
+
792
+ // 成员行:技能在前、工具在后。类型由页签表达,列表本身保持无装饰(视觉稿的做法),
793
+ // 顺序本身就是「混装」的暗示。
794
+ const rows = (tab === 'all' ? ['skill', 'tool'] : [tab]).flatMap((type) =>
795
+ pool[type]
796
+ .filter((item) => {
797
+ if (filter === 'all') return true
798
+ const on = memberSet.has(`${type}:${item.name}`)
799
+ return filter === 'picked' ? on : !on
800
+ })
801
+ .map((item) => ({ type, name: item.name })),
802
+ )
803
+
804
+ const emptyText = () => {
805
+ if (keywords !== '') return `没有匹配「${query.trim()}」的成员`
806
+ if (filter === 'picked') return '还没有已选成员'
807
+ if (filter === 'unpicked') return '本领域成员已全部选中'
808
+ if (tab === 'skill') return '本领域没有技能成员'
809
+ if (tab === 'tool') return '本领域没有工具成员'
810
+ return '还没有可用的成员'
811
+ }
812
+
813
+ const clearFilters = () => {
814
+ setQuery('')
815
+ pickTab('all')
816
+ pickFilter('all')
817
+ }
818
+
819
+ const memberList = rows.length === 0
820
+ ? h(
479
821
  'div',
480
- { className: 'cdx-grid' },
481
- names.map((name) =>
822
+ { className: 'cdx-empty' },
823
+ h('p', { className: 'cdx-empty-text' }, emptyText()),
824
+ h('button', { className: 'cdx-empty-btn', type: 'button', onClick: clearFilters }, '清除筛选'),
825
+ )
826
+ : rows.map((row) =>
827
+ h(
828
+ 'label',
829
+ { className: 'cdx-member', key: `${row.type}:${row.name}`, title: row.name },
830
+ h('input', {
831
+ type: 'checkbox',
832
+ checked: memberSet.has(`${row.type}:${row.name}`),
833
+ disabled: busy,
834
+ onChange: (event) => setMember(row.type, row.name, event.target.checked),
835
+ }),
836
+ h('span', { className: 'cdx-check', 'aria-hidden': 'true' }),
837
+ h('span', { className: 'cdx-member-name' }, row.name),
838
+ ),
839
+ )
840
+
841
+ // AI 弹窗:portal 到 body 且 z-index 高过宿主设置弹窗(1000/1100 的 fixed 层),
842
+ // 否则会被压在那个对话框下面 —— 表现就是「点了 AI 什么都不出现」。
843
+ const aiDialog = aiOpen
844
+ ? portal(
845
+ h(
846
+ 'div',
847
+ {
848
+ className: 'cdx-scrim',
849
+ role: 'presentation',
850
+ onMouseDown: (event) => {
851
+ event.stopPropagation()
852
+ if (event.target === event.currentTarget) setAiOpen(false)
853
+ },
854
+ onClick: (event) => event.stopPropagation(),
855
+ },
482
856
  h(
483
- 'label',
484
- { className: 'cdx-mem', key: name },
485
- h('input', {
486
- type: 'checkbox',
487
- checked: memberSet.has(`${type}:${name}`),
488
- disabled: busy,
489
- onChange: (event) => setMember(type, name, event.target.checked),
490
- }),
491
- h('span', { className: 'nm', title: name }, name),
857
+ 'div',
858
+ {
859
+ className: 'cdx-modal', role: 'dialog', 'aria-modal': 'true', 'aria-label': '让 AI 帮忙归类',
860
+ onMouseDown: (event) => event.stopPropagation(),
861
+ },
862
+ h(
863
+ 'div',
864
+ { className: 'cdx-modal-hd' },
865
+ h('h2', { className: 'cdx-modal-title' }, '让 AI 帮忙归类'),
866
+ h('button', {
867
+ className: 'cdx-iconbtn', type: 'button', 'aria-label': '关闭弹窗',
868
+ onClick: () => setAiOpen(false),
869
+ }, '✕'),
870
+ ),
871
+ h('p', { className: 'cdx-modal-desc' },
872
+ '把下面这段发给会话里的 AI:它会自己读目录、按用途归类,先给你清单,确认后再建域。'),
873
+ h(
874
+ 'div',
875
+ {
876
+ className: 'cdx-prompt',
877
+ role: 'button',
878
+ tabIndex: 0,
879
+ title: '点一下复制',
880
+ 'data-copied': copied ? 'true' : 'false',
881
+ 'aria-label': 'prompt 文本,点击复制',
882
+ onClick: () => void copyPrompt(),
883
+ onKeyDown: (event) => {
884
+ if (event.key === 'Enter' || event.key === ' ') {
885
+ event.preventDefault()
886
+ void copyPrompt()
887
+ }
888
+ },
889
+ },
890
+ h('span', { className: 'cdx-prompt-hint', 'aria-live': 'polite' }, copied ? '已复制' : '点击复制'),
891
+ h('pre', { className: 'cdx-prompt-text' }, AI_PROMPT),
892
+ ),
893
+ h(
894
+ 'div',
895
+ { className: 'cdx-modal-foot' },
896
+ h('button', {
897
+ className: 'cdx-btn cdx-btn-primary', type: 'button',
898
+ onClick: () => void copyPrompt(),
899
+ }, copied ? '已复制' : '复制 prompt'),
900
+ ),
492
901
  ),
493
902
  ),
903
+ )
904
+ : null
905
+
906
+ const domainRow = (domain) => {
907
+ const c = counts(domain)
908
+ return h(
909
+ 'button',
910
+ {
911
+ key: domain.id,
912
+ className: 'cdx-domain',
913
+ type: 'button',
914
+ 'aria-selected': selectedId === domain.id ? 'true' : 'false',
915
+ onClick: () => setSelectedId(domain.id),
916
+ },
917
+ h('span', { className: 'cdx-domain-mark', 'aria-hidden': 'true' }),
918
+ h(
919
+ 'span',
920
+ { className: 'cdx-domain-body' },
921
+ h('span', { className: 'cdx-domain-name' }, domain.name),
922
+ h(
923
+ 'span',
924
+ { className: 'cdx-domain-meta' },
925
+ `${c.total} 项`,
926
+ domain.defaultOn ? h('span', { className: 'cdx-domain-badge' }, '默认') : null,
927
+ ),
494
928
  ),
929
+ h('span', { className: 'cdx-domain-ratio' }, `${c.skills}/${c.tools}`),
495
930
  )
496
931
  }
497
932
 
@@ -499,94 +934,110 @@ window.__ModuleLoader__.load({
499
934
  'div',
500
935
  { className: 'cdx-set' },
501
936
  h(
502
- 'div',
503
- { className: 'cdx-set-list' },
937
+ 'aside',
938
+ { className: 'cdx-side' },
939
+ h(
940
+ 'header',
941
+ { className: 'cdx-side-hd' },
942
+ h('span', { className: 'cdx-side-count' }, `${domains.length} 个领域`),
943
+ h(
944
+ 'div',
945
+ { className: 'cdx-side-actions' },
946
+ h('button', {
947
+ className: 'cdx-btn', type: 'button', disabled: busy,
948
+ onClick: () => void create({ name: '新领域', members: [] }),
949
+ }, '+ 新建'),
950
+ h('button', {
951
+ className: 'cdx-iconbtn cdx-iconbtn-accent', type: 'button',
952
+ title: '让 AI 帮忙归类成员(点开有可复制的 prompt)',
953
+ 'aria-label': '让 AI 帮忙归类成员',
954
+ onClick: () => setAiOpen(true),
955
+ }, '✦'),
956
+ ),
957
+ ),
504
958
  h(
505
959
  'div',
506
- { className: 'cdx-set-list-hd' },
507
- h('span', null, `${domains.length} 个领域`),
508
- h('span', { style: { flex: '1 1 auto' } }),
509
- h('button', { className: 'cdx-btn', type: 'button', disabled: busy, onClick: () => void create({ name: '新领域', members: [] }) }, '+ 新建'),
960
+ { className: 'cdx-side-list', role: 'list' },
961
+ domains.length === 0
962
+ ? h(
963
+ 'div',
964
+ { className: 'cdx-empty' },
965
+ h('p', { className: 'cdx-empty-text' }, '还没有领域,可以从建议开始'),
966
+ (catalog.suggestions ?? []).map((s) =>
967
+ h('button', {
968
+ key: s.name, className: 'cdx-empty-btn', type: 'button', disabled: busy,
969
+ onClick: () => void create({ name: s.name, members: s.members }),
970
+ }, `${s.name} · ${s.members.length} 项`),
971
+ ),
972
+ )
973
+ : domains.map(domainRow),
510
974
  ),
511
- domains.map((domain) => {
512
- const c = counts(domain)
513
- return h(
514
- 'button',
515
- {
516
- key: domain.id,
517
- className: 'cdx-dcard',
518
- type: 'button',
519
- 'aria-selected': selectedId === domain.id ? 'true' : 'false',
520
- onClick: () => setSelectedId(domain.id),
521
- },
522
- h('span', { className: 'cdx-dcard-ico' }, domain.icon ?? '◈'),
523
- h(
524
- 'span',
525
- null,
526
- h('span', { className: 'cdx-dcard-n' }, domain.name),
527
- h('span', { className: 'cdx-dcard-m' }, `${c.total} 项`),
528
- ),
529
- domain.defaultOn ? h('span', { className: 'cdx-badge' }, '默认') : null,
530
- )
531
- }),
532
- domains.length === 0
533
- ? h('div', { className: 'cdx-empty' }, '还没有领域。可以从下面的建议开始:',
534
- h('div', { style: { display: 'flex', flexWrap: 'wrap', gap: '6px', marginTop: '8px' } },
535
- (catalog.suggestions ?? []).map((s) => h('button', {
536
- key: s.name, className: 'cdx-btn', type: 'button', disabled: busy,
537
- onClick: () => void create({ name: s.name, members: s.members }),
538
- }, `${s.name} (${s.members.length})`))))
539
- : null,
540
- h('div', { className: 'cdx-hint', style: { marginTop: '8px' } },
975
+ h('p', { className: 'cdx-side-foot' },
541
976
  `未归入领域:${catalog.unassigned?.skills?.length ?? 0} skill · ${catalog.unassigned?.tools?.length ?? 0} tool`),
542
977
  ),
543
978
  h(
544
979
  'div',
545
- { className: 'cdx-set-edit' },
546
- error ? h('div', { className: 'cdx-empty', style: { color: 'var(--dsw-alias-state-error-primary)' } }, error) : null,
980
+ { className: 'cdx-main' },
981
+ error
982
+ ? h('div', { className: 'cdx-empty-text', style: { color: 'var(--dsw-alias-label-error)' } }, error)
983
+ : null,
547
984
  selected === null
548
- ? h('div', { className: 'cdx-empty' }, '从左侧选一个领域,或新建一个。领域只做归纳:一个领域里可以同时装 skill 和 tool。')
985
+ ? h('div', { className: 'cdx-empty', style: { minHeight: '0', flex: '1 1 auto' } },
986
+ h('p', { className: 'cdx-empty-text' }, '从左侧选一个领域,或新建一个。领域只做归纳:一个领域里可以同时装 skill 和 tool。'))
549
987
  : h(
550
988
  React.Fragment,
551
989
  null,
552
- h('div', { className: 'cdx-field' }, h('label', null, '名称'),
990
+ h('label', { className: 'cdx-field' },
991
+ h('span', { className: 'cdx-field-lbl' }, '名称'),
553
992
  h('input', {
554
- className: 'cdx-input', defaultValue: selected.name, key: `${selected.id}:name`,
993
+ className: 'cdx-input', defaultValue: selected.name, key: `${selected.id}:name`, spellCheck: false,
555
994
  onBlur: (event) => {
556
995
  const value = event.target.value.trim()
557
996
  if (value !== '' && value !== selected.name) void update({ name: value })
558
997
  },
559
998
  })),
560
- h('div', { className: 'cdx-field' }, h('label', null, '说明'),
561
- h('input', {
562
- className: 'cdx-input', defaultValue: selected.description ?? '', key: `${selected.id}:desc`,
999
+ h('label', { className: 'cdx-field' },
1000
+ h('span', { className: 'cdx-field-lbl' }, '说明'),
1001
+ h('textarea', {
1002
+ className: 'cdx-input cdx-input-area', rows: 2, spellCheck: false,
1003
+ defaultValue: selected.description ?? '', key: `${selected.id}:desc`,
563
1004
  placeholder: '这个领域装了什么,什么时候要开',
564
1005
  onBlur: (event) => void update({ description: event.target.value }),
565
1006
  })),
566
- h('div', { className: 'cdx-field' }, h('label', null, '新会话默认'),
567
- h('label', { className: 'cdx-mem', style: { paddingLeft: 0 } },
1007
+ h('div', { className: 'cdx-switchrow' },
1008
+ h('span', { className: 'cdx-switchrow-text' },
1009
+ h('span', { className: 'cdx-switchrow-lbl' }, '默认启用'),
1010
+ h('span', { className: 'cdx-switchrow-hint' }, '勾上 = 新会话自动点亮,会话里仍可随时关')),
1011
+ h('button', {
1012
+ className: 'cdx-switch', type: 'button', role: 'switch',
1013
+ 'aria-checked': selected.defaultOn ? 'true' : 'false', 'aria-label': '默认启用',
1014
+ disabled: busy,
1015
+ onClick: () => void update({ defaultOn: !selected.defaultOn }),
1016
+ })),
1017
+ h('div', { className: 'cdx-members' },
1018
+ h('div', { className: 'cdx-search' },
568
1019
  h('input', {
569
- type: 'checkbox', checked: selected.defaultOn, disabled: busy,
570
- onChange: (event) => void update({ defaultOn: event.target.checked }),
1020
+ className: 'cdx-search-input', type: 'search', placeholder: '搜索成员名称',
1021
+ 'aria-label': '搜索成员', value: query,
1022
+ onChange: (event) => setQuery(event.target.value),
571
1023
  }),
572
- h('span', { className: 'nm' }, '新会话自动点亮,仍可在会话里改'))),
573
- h('div', { className: 'cdx-mem-hd' },
574
- h('input', {
575
- className: 'cdx-input', placeholder: '搜索成员…', value: query,
576
- onChange: (event) => setQuery(event.target.value),
577
- }),
578
- h('span', { className: 'cdx-hint' }, `已选 ${memberSet.size} 项`)),
579
- h('div', { className: 'cdx-scroll' }, section('skill', '技能'), section('tool', '工具')),
580
- h('div', { className: 'cdx-field', style: { marginTop: '6px' } }, h('label', null, '为什么混装'),
581
- h('div', { style: { flex: 1, fontSize: '12px', color: 'var(--dsw-alias-label-tertiary)' } },
582
- '有的产品是「一个 skill 带一个专用 tool」,拆成两个领域反而要记两次。',
583
- h('br'),
584
- h('span', { className: 'cdx-hint' }, '对用户是一个开关;加载时才按类型分别处理(skill 进目录、tool 进工具表)。'))),
585
- h('div', { style: { display: 'flex', gap: '8px', marginTop: '8px' } },
586
- h('button', { className: 'cdx-btn', type: 'button', disabled: busy, onClick: () => void remove() }, '删除领域'),
587
- h('span', { className: 'cdx-hint', style: { alignSelf: 'center' } },
588
- '保存即时生效:新会话立刻按新领域组装,已有会话在下一轮生效。'))),
1024
+ h('span', { className: 'cdx-search-meta' }, `已选 ${memberSet.size} 项`)),
1025
+ h('div', { className: 'cdx-filters' },
1026
+ segmented('成员类型', [['all', '全部'], ['skill', '技能'], ['tool', '工具']], tab, pickTab),
1027
+ h('div', { className: 'cdx-filters-right' },
1028
+ h('span', { className: 'cdx-filters-lbl' }, '显示'),
1029
+ segmented('显示范围',
1030
+ [['all', '全部成员'], ['picked', '已选'], ['unpicked', '未选']], filter, pickFilter))),
1031
+ h('div', {
1032
+ className: tab === 'all' ? 'cdx-list' : 'cdx-list cdx-list-one',
1033
+ role: 'group', 'aria-label': '成员列表',
1034
+ }, memberList)),
1035
+ h('div', { className: 'cdx-main-foot' },
1036
+ h('button', { className: 'cdx-btn cdx-btn-danger', type: 'button', disabled: busy, onClick: () => void remove() }, '删除领域'),
1037
+ h('span', { className: 'cdx-hint' }, '保存即时生效,下一轮生效')),
1038
+ ),
589
1039
  ),
1040
+ aiDialog,
590
1041
  )
591
1042
  }
592
1043