@v587d/capital-generation 2.10.0 → 2.10.2
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/CHANGELOG.md +56 -0
- package/README.md +20 -10
- package/chart-ui/package.json +1 -1
- package/config/client.js +36 -29
- package/config/client.src.cjs +228 -142
- package/config/index.js +8 -2
- package/config/package.json +1 -1
- package/lib/agents/selected-skills-provider.d.ts +29 -10
- package/lib/agents/selected-skills-provider.js +262 -139
- package/lib/agents/skill-usage.d.ts +22 -0
- package/lib/agents/skill-usage.js +92 -0
- package/lib/capital-settings.d.ts +12 -1
- package/lib/capital-settings.js +23 -3
- package/lib/index.d.ts +6 -3
- package/lib/index.js +10 -19
- package/lib/root-extras-entry.d.ts +2 -5
- package/lib/root-extras-entry.js +1 -3
- package/lib/selected-skills-entry.d.ts +14 -0
- package/lib/selected-skills-entry.js +43 -0
- package/lib/web-retriever/local-fetch.d.ts +1 -1
- package/lib/web-retriever/local-fetch.js +1 -1
- package/lib/web-retriever/wind-client.d.ts +1 -1
- package/lib/web-retriever/wind-client.js +1 -1
- package/package.json +11 -2
- package/preset/capital-generation/agent.patch.yml +34 -16
- package/preset/capital-generation/skills/capital-data-query/SKILL.md +19 -0
- package/preset/capital-generation/skills/capital-equity-research/SKILL.md +4 -0
- package/preset/capital-generation/skills/capital-event-impact/SKILL.md +3 -0
- package/preset/capital-generation/skills/capital-orchestration/SKILL.md +7 -9
- package/preset/capital-generation/skills/capital-portfolio-review/SKILL.md +3 -0
- package/preset/capital-generation/skills/capital-web-evidence/SKILL.md +1 -0
- package/preset/capital-generation/skills/capital-web-protocol/SKILL.md +3 -0
- package/selected-skills/README.md +8 -6
- package/selected-skills/ROUTING.md +88 -0
- package/selected-skills/catalog.js +32 -121
- package/selected-skills/filter.js +70 -0
- package/selected-skills/routing.js +1059 -0
- package/watchlist/package.json +1 -1
package/config/client.src.cjs
CHANGED
|
@@ -5,13 +5,26 @@ const {
|
|
|
5
5
|
SettingsSecretField,
|
|
6
6
|
SettingsFormModel,
|
|
7
7
|
SegmentedControl,
|
|
8
|
+
Pill,
|
|
8
9
|
Switch,
|
|
10
|
+
Tag,
|
|
9
11
|
Modal,
|
|
10
|
-
writeClipboard,
|
|
11
12
|
IconRightUpOutlineRegular,
|
|
12
|
-
IconWarningTriangleOutlineRegular,
|
|
13
13
|
} = require('@deepseek-ai/dsh-client-ui-primitives')
|
|
14
|
-
const { SELECTED_SKILL_CATALOG, SELECTED_SKILL_TAG_LABELS
|
|
14
|
+
const { SELECTED_SKILL_CATALOG, SELECTED_SKILL_TAG_LABELS } = require('../selected-skills/catalog.js')
|
|
15
|
+
const {
|
|
16
|
+
SELECTED_SKILL_FILTER_ALL,
|
|
17
|
+
filterSelectedSkills,
|
|
18
|
+
selectedSkillFacetCounts,
|
|
19
|
+
selectedSkillPresetTotals,
|
|
20
|
+
selectedSkillRoles,
|
|
21
|
+
selectedSkillTraitOrder,
|
|
22
|
+
} = require('../selected-skills/filter.js')
|
|
23
|
+
const {
|
|
24
|
+
SELECTED_SKILL_PRESETS,
|
|
25
|
+
SELECTED_SKILL_PRESET_ORDER,
|
|
26
|
+
SELECTED_SKILL_ROUTING,
|
|
27
|
+
} = require('../selected-skills/routing.js')
|
|
15
28
|
|
|
16
29
|
/**
|
|
17
30
|
* Two different strings: `ENTRY_ID` is the settings namespace (= profile entry
|
|
@@ -51,6 +64,9 @@ const DOC_URLS = {
|
|
|
51
64
|
/** Render order = array order; one list drives status, refs, drafts and event refresh. */
|
|
52
65
|
const CREDENTIAL_FIELDS = ['fuyao', 'ftshare', 'anysearch', 'wind', 'paddleocr']
|
|
53
66
|
const SELECTED_SKILL_FIELDS = SELECTED_SKILL_CATALOG.map(({ key }) => key)
|
|
67
|
+
// Stable orders: facet counts change with the other dimension, but chips must not jump under the cursor.
|
|
68
|
+
const SELECTED_SKILL_TRAITS = selectedSkillTraitOrder()
|
|
69
|
+
const SELECTED_SKILL_PRESET_TOTALS = selectedSkillPresetTotals()
|
|
54
70
|
const DEFAULT_REFS = {
|
|
55
71
|
fuyao: FUYAO_DEFAULT_REF,
|
|
56
72
|
ftshare: FTSHARE_DEFAULT_REF,
|
|
@@ -81,14 +97,17 @@ const zh = {
|
|
|
81
97
|
paddleocrLabel: 'PaddleOCR 文档解析 Token',
|
|
82
98
|
paddleocrHint: '配置 Token 后自动解析 PDF、图片。保存后新自动模式会话生效。',
|
|
83
99
|
selectedSkillsTitle: '精选 Skills',
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
100
|
+
selectedSkillsModeLabel: '智能装载 Skills',
|
|
101
|
+
selectedSkillsModeHint: '开启后,由大模型根据用户提问,智能装载合适的精选「Skills」。',
|
|
102
|
+
selectedSkillsFilterLabel: '筛选精选 Skills',
|
|
103
|
+
selectedSkillsTraitGroup: 'Skill 特点',
|
|
104
|
+
selectedSkillsPresetGroup: '适用预设',
|
|
105
|
+
selectedSkillsAll: '全部',
|
|
106
|
+
selectedSkillsEmpty: '没有符合当前筛选条件的 Skill。',
|
|
107
|
+
selectedSkillsWhen: '何时使用',
|
|
108
|
+
selectedSkillsKeywords: '关键词',
|
|
109
|
+
selectedSkillsOpenDetail: '查看 Skill 原文',
|
|
110
|
+
selectedSkillsPresetUnavailable: '该预设不加载精选 Skills',
|
|
92
111
|
selectedSkillsDetailClose: '关闭',
|
|
93
112
|
selectedSkillsDocLoading: '原文读取中…',
|
|
94
113
|
selectedSkillsDocFailed: '原文读不到:这份快照没随包落地,或暂时读不出来。',
|
|
@@ -125,12 +144,17 @@ const en = {
|
|
|
125
144
|
paddleocrLabel: 'PaddleOCR document token',
|
|
126
145
|
paddleocrHint: 'Configure the token to parse PDFs and images automatically. Takes effect in new Auto mode sessions.',
|
|
127
146
|
selectedSkillsTitle: 'Selected Skills',
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
147
|
+
selectedSkillsModeLabel: 'Auto-load Skills',
|
|
148
|
+
selectedSkillsModeHint: 'When on, the model picks the relevant selected Skills for each question.',
|
|
149
|
+
selectedSkillsFilterLabel: 'Filter selected Skills',
|
|
150
|
+
selectedSkillsTraitGroup: 'Skill traits',
|
|
151
|
+
selectedSkillsPresetGroup: 'Preset',
|
|
152
|
+
selectedSkillsAll: 'All',
|
|
153
|
+
selectedSkillsEmpty: 'No skills match the current filters.',
|
|
154
|
+
selectedSkillsWhen: 'Use when',
|
|
155
|
+
selectedSkillsKeywords: 'Keywords',
|
|
156
|
+
selectedSkillsOpenDetail: 'Open skill source',
|
|
157
|
+
selectedSkillsPresetUnavailable: 'This preset does not load selected Skills.',
|
|
134
158
|
selectedSkillsDetailClose: 'Close',
|
|
135
159
|
selectedSkillsDocLoading: 'Loading the source…',
|
|
136
160
|
selectedSkillsDocFailed: 'The source could not be read: this snapshot is not in the installed package, or it cannot be opened right now.',
|
|
@@ -166,7 +190,6 @@ function installStyles() {
|
|
|
166
190
|
style.dataset.capitalConfig = 'true'
|
|
167
191
|
style.textContent = `
|
|
168
192
|
.capital-config-card { display: flex; flex-direction: column; gap: 12px; }
|
|
169
|
-
.capital-config-title { margin: 0; font-size: 14px; font-weight: 500; line-height: 20px; }
|
|
170
193
|
/* Declare display only when not hidden, or the hidden attribute loses to author display. */
|
|
171
194
|
.capital-config-panel { flex-direction: column; gap: 12px; }
|
|
172
195
|
.capital-config-panel:not([hidden]) { display: flex; }
|
|
@@ -182,25 +205,39 @@ function installStyles() {
|
|
|
182
205
|
.capital-config-secret-row label { flex: 0 1 auto; }
|
|
183
206
|
.capital-config-secret-row label ~ span { order: 1; margin-left: auto; }
|
|
184
207
|
.capital-config-field { display: flex; flex-direction: column; gap: 6px; padding: 12px 0; border-top: 0.5px solid var(--dsw-alias-border-l2); }
|
|
185
|
-
|
|
186
|
-
.capital-config-
|
|
187
|
-
.capital-config-
|
|
188
|
-
.capital-config-
|
|
189
|
-
.capital-config-
|
|
190
|
-
.capital-config-
|
|
191
|
-
.capital-config-
|
|
192
|
-
.capital-config-
|
|
193
|
-
.capital-config-
|
|
194
|
-
.capital-config-
|
|
195
|
-
.capital-config-
|
|
196
|
-
.capital-config-
|
|
197
|
-
.capital-config-
|
|
198
|
-
.capital-config-
|
|
199
|
-
.capital-config-
|
|
200
|
-
.capital-config-
|
|
201
|
-
.capital-config-skill-
|
|
202
|
-
.capital-config-skill-
|
|
208
|
+
.capital-config-selected-mode { padding: 12px 14px; border: 0.5px solid var(--dsw-alias-border-l1); border-radius: 10px; background: var(--dsw-alias-bg-layer-2); }
|
|
209
|
+
.capital-config-switch-row { align-items: center; gap: 12px; display: flex; }
|
|
210
|
+
.capital-config-switch-text { min-width: 0; flex: 1; display: flex; flex-direction: column; gap: 4px; }
|
|
211
|
+
.capital-config-switch-label { color: var(--dsw-alias-label-primary); font-size: 13px; font-weight: 500; line-height: 20px; }
|
|
212
|
+
.capital-config-hint { margin: 0; color: var(--dsw-alias-label-tertiary); font-size: 12px; line-height: 18px; }
|
|
213
|
+
.capital-config-skills-browser { display: flex; flex-direction: column; gap: 16px; }
|
|
214
|
+
.capital-config-filters { display: flex; flex-direction: column; gap: 10px; padding: 12px 14px; border: 0.5px solid var(--dsw-alias-border-l1); border-radius: 10px; background: var(--dsw-alias-bg-layer-2); }
|
|
215
|
+
.capital-config-filter-row { display: flex; align-items: flex-start; gap: 12px; }
|
|
216
|
+
.capital-config-filter-label { flex: 0 0 auto; min-width: 64px; padding-top: 3px; color: var(--dsw-alias-label-tertiary); font-size: 11.5px; font-weight: 500; line-height: 18px; }
|
|
217
|
+
.capital-config-filter-options { display: flex; flex: 1 1 auto; flex-wrap: wrap; gap: 4px; min-width: 0; }
|
|
218
|
+
.capital-config-filter-pill { height: 22px; padding: 0 7px; gap: 5px; font-size: 11.5px; line-height: 16px; }
|
|
219
|
+
.capital-config-filter-pill:disabled { opacity: 0.45; cursor: not-allowed; }
|
|
220
|
+
.capital-config-filter-pill:disabled:hover { background: var(--dsw-alias-bg-layer-2); }
|
|
221
|
+
.capital-config-filter-count { color: var(--dsw-alias-label-tertiary); font-size: 10px; line-height: 14px; font-variant-numeric: tabular-nums; }
|
|
222
|
+
.capital-config-filter-pill[aria-pressed="true"] .capital-config-filter-count { color: inherit; opacity: 0.72; }
|
|
223
|
+
.capital-config-selected-list { display: flex; flex-direction: column; border-top: 0.5px solid var(--dsw-alias-border-l1); }
|
|
224
|
+
.capital-config-skill-item { border-bottom: 0.5px solid var(--dsw-alias-border-l1); }
|
|
225
|
+
.capital-config-skill-row { display: flex; align-items: flex-start; gap: 16px; padding: 14px 2px; }
|
|
226
|
+
.capital-config-skill-main { min-width: 0; flex: 1 1 auto; display: flex; flex-direction: column; gap: 6px; }
|
|
227
|
+
.capital-config-skill-heading { min-width: 0; display: flex; flex-wrap: wrap; align-items: center; gap: 6px 8px; }
|
|
228
|
+
.capital-config-skill-name { padding: 0; border: 0; border-radius: 4px; background: transparent; color: var(--dsw-alias-label-primary); font: inherit; font-size: 13px; font-weight: 500; line-height: 20px; text-align: left; cursor: pointer; }
|
|
229
|
+
.capital-config-skill-name:hover { color: var(--dsw-alias-brand-primary); }
|
|
230
|
+
.capital-config-skill-name:focus-visible { outline: var(--dsw-focus-ring-width, 2px) solid var(--dsw-focus-ring-color, var(--dsw-alias-brand-primary)); outline-offset: 2px; }
|
|
231
|
+
.capital-config-skill-tags, .capital-config-skill-presets { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 4px; }
|
|
232
|
+
.capital-config-skill-tag { font-weight: 400; }
|
|
233
|
+
.capital-config-skill-preset { font-weight: 500; }
|
|
203
234
|
.capital-config-skill-description { margin: 0; color: var(--dsw-alias-label-secondary); font-size: 12px; line-height: 18px; }
|
|
235
|
+
.capital-config-skill-meta { display: grid; grid-template-columns: max-content minmax(0, 1fr); column-gap: 10px; row-gap: 2px; margin: 0; color: var(--dsw-alias-label-secondary); font-size: 12px; line-height: 18px; }
|
|
236
|
+
.capital-config-skill-meta dt { color: var(--dsw-alias-label-tertiary); font-weight: 500; white-space: nowrap; }
|
|
237
|
+
.capital-config-skill-meta dd { min-width: 0; margin: 0; }
|
|
238
|
+
.capital-config-skill-repo { display: inline-flex; align-items: center; gap: 3px; width: max-content; max-width: 100%; margin-top: 2px; color: var(--dsw-alias-label-tertiary); font-size: 11px; line-height: 16px; text-decoration: none; }
|
|
239
|
+
.capital-config-skill-repo:hover { color: var(--dsw-alias-brand-primary); text-decoration: underline; }
|
|
240
|
+
.capital-config-selected-empty { margin: 0; padding: 28px 0; color: var(--dsw-alias-label-tertiary); font-size: 12px; line-height: 18px; text-align: center; }
|
|
204
241
|
/* Official Modal defaults to 380px; the div prefix outranks the hashed .dialog class. */
|
|
205
242
|
div.capital-config-skill-modal { width: min(820px, 100%); max-height: 100%; }
|
|
206
243
|
/* Scroll the body so the dialog's overflow:hidden cannot clip it. */
|
|
@@ -215,12 +252,6 @@ function installStyles() {
|
|
|
215
252
|
.capital-config-doc-link { display: inline-flex; align-items: center; gap: 3px; color: var(--dsw-alias-label-tertiary); font-size: 12px; line-height: 20px; white-space: nowrap; text-decoration: none; }
|
|
216
253
|
.capital-config-doc-link:hover { color: var(--dsw-alias-brand-primary); text-decoration: underline; }
|
|
217
254
|
.capital-config-doc-link:focus-visible { border-radius: 3px; outline: var(--dsw-focus-ring-width, 2px) solid var(--dsw-focus-ring-color, var(--dsw-alias-brand-primary)); outline-offset: 2px; }
|
|
218
|
-
.capital-config-switch-row { align-items: center; gap: 12px; display: flex; }
|
|
219
|
-
.capital-config-switch-text { min-width: 0; flex: 1; display: flex; flex-direction: column; gap: 4px; }
|
|
220
|
-
.capital-config-switch-label { color: var(--dsw-alias-label-primary); font-size: 13px; font-weight: 500; line-height: 20px; }
|
|
221
|
-
.capital-config-hint { margin: 0; color: var(--dsw-alias-label-tertiary); font-size: 12px; line-height: 18px; }
|
|
222
|
-
/* --dsw-alias-label-error is undefined in the theme, so state tokens only. */
|
|
223
|
-
.capital-config-skill-cap-icon { display: inline-flex; align-items: center; flex: none; padding: 2px; margin: -2px; color: var(--dsw-alias-state-warn-primary); cursor: help; }
|
|
224
255
|
.capital-config-write-failed { margin: 0; color: var(--dsw-alias-state-error-primary); font-size: 12px; line-height: 18px; }
|
|
225
256
|
/* Own inline-flex group so the host's wrapping never splits the two links. */
|
|
226
257
|
.capital-config-plugin-links { flex: none; align-items: center; gap: 2px; display: inline-flex; }
|
|
@@ -241,6 +272,8 @@ class CapitalCardController {
|
|
|
241
272
|
this.localFetchFailed = false
|
|
242
273
|
this.selectedSkillWriting = new Set()
|
|
243
274
|
this.selectedSkillFailed = new Set()
|
|
275
|
+
this.selectedSkillsModeWriting = false
|
|
276
|
+
this.selectedSkillsModeFailed = false
|
|
244
277
|
// Row count is external: null = unknown, never 0; fetch only after the card is shown.
|
|
245
278
|
this.bundleRows = null
|
|
246
279
|
this.rowsRequested = false
|
|
@@ -285,6 +318,11 @@ class CapitalCardController {
|
|
|
285
318
|
writing: this.selectedSkillWriting.has(field),
|
|
286
319
|
failed: this.selectedSkillFailed.has(field),
|
|
287
320
|
}])),
|
|
321
|
+
selectedSkillsMode: {
|
|
322
|
+
auto: snapshot.value?.selectedSkillsMode === 'auto',
|
|
323
|
+
writing: this.selectedSkillsModeWriting,
|
|
324
|
+
failed: this.selectedSkillsModeFailed,
|
|
325
|
+
},
|
|
288
326
|
// `count === null` means unknown, not zero: the card decides whether to draw it.
|
|
289
327
|
components: { count: this.bundleRows },
|
|
290
328
|
}
|
|
@@ -362,6 +400,24 @@ class CapitalCardController {
|
|
|
362
400
|
this.store.set(this.projection())
|
|
363
401
|
}
|
|
364
402
|
|
|
403
|
+
async toggleSelectedSkillsMode(auto) {
|
|
404
|
+
if (this.selectedSkillsModeWriting) return
|
|
405
|
+
const snapshot = this.scope.getSnapshot()
|
|
406
|
+
if (snapshot.status !== 'ready' || !snapshot.writable) return
|
|
407
|
+
this.selectedSkillsModeWriting = true
|
|
408
|
+
this.selectedSkillsModeFailed = false
|
|
409
|
+
this.store.set(this.projection())
|
|
410
|
+
try {
|
|
411
|
+
await this.scope.mutate([{ op: 'set', path: ['selectedSkillsMode'], value: auto ? 'auto' : 'manual' }])
|
|
412
|
+
} catch {
|
|
413
|
+
// The landed check below reports the failure.
|
|
414
|
+
}
|
|
415
|
+
this.selectedSkillsModeWriting = false
|
|
416
|
+
const landed = (this.scope.getSnapshot().value?.selectedSkillsMode === 'auto') === auto
|
|
417
|
+
if (!landed) this.selectedSkillsModeFailed = true
|
|
418
|
+
this.store.set(this.projection())
|
|
419
|
+
}
|
|
420
|
+
|
|
365
421
|
async toggleSelectedSkill(field, value) {
|
|
366
422
|
if (!SELECTED_SKILL_FIELDS.includes(field) || this.selectedSkillWriting.has(field)) return
|
|
367
423
|
const snapshot = this.scope.getSnapshot()
|
|
@@ -421,6 +477,7 @@ class CapitalCardController {
|
|
|
421
477
|
...this.model.actions(),
|
|
422
478
|
toggleLocalFetch: (value) => this.toggleLocalFetch(value),
|
|
423
479
|
toggleSelectedSkill: (field, value) => this.toggleSelectedSkill(field, value),
|
|
480
|
+
toggleSelectedSkillsMode: (auto) => this.toggleSelectedSkillsMode(auto),
|
|
424
481
|
ensureBundleRows: () => this.ensureBundleRows(),
|
|
425
482
|
}
|
|
426
483
|
}
|
|
@@ -503,27 +560,27 @@ function loadSkillDoc(name) {
|
|
|
503
560
|
return pending
|
|
504
561
|
}
|
|
505
562
|
|
|
506
|
-
|
|
563
|
+
function presetLabel(role, locale) {
|
|
564
|
+
return SELECTED_SKILL_PRESETS[role]?.[locale] ?? role
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/** Routing text stays one sentence; split the keyword tail so both lines share a type scale. */
|
|
568
|
+
function splitUseWhen(value, locale) {
|
|
569
|
+
const marker = locale === 'en' ? 'Triggers on ' : ';关键词:'
|
|
570
|
+
const index = value.lastIndexOf(marker)
|
|
571
|
+
if (index <= 0) return { when: value, keywords: '' }
|
|
572
|
+
return {
|
|
573
|
+
when: value.slice(0, index).trim(),
|
|
574
|
+
keywords: value.slice(index + marker.length).trim().replace(locale === 'en' ? /\.$/ : /。$/, ''),
|
|
575
|
+
}
|
|
576
|
+
}
|
|
507
577
|
|
|
508
|
-
function SelectedSkillRow({ entry, state, disabled,
|
|
578
|
+
function SelectedSkillRow({ entry, state, disabled, onChange, t }) {
|
|
509
579
|
const locale = skillLocale()
|
|
510
580
|
const label = entry.label[locale]
|
|
511
581
|
const repositoryUrl = `https://github.com/${entry.owner}/${entry.repository}`
|
|
512
|
-
const copyTimer = useRef(null)
|
|
513
|
-
const [copied, setCopied] = useState(false)
|
|
514
582
|
const [detailOpen, setDetailOpen] = useState(false)
|
|
515
583
|
const [doc, setDoc] = useState(null)
|
|
516
|
-
useEffect(() => () => {
|
|
517
|
-
if (copyTimer.current !== null) clearTimeout(copyTimer.current)
|
|
518
|
-
}, [])
|
|
519
|
-
const copyName = async () => {
|
|
520
|
-
const accepted = await writeClipboard(entry.name)
|
|
521
|
-
// Do not flip the label if the clipboard rejected it.
|
|
522
|
-
if (!accepted) return
|
|
523
|
-
setCopied(true)
|
|
524
|
-
if (copyTimer.current !== null) clearTimeout(copyTimer.current)
|
|
525
|
-
copyTimer.current = setTimeout(() => setCopied(false), COPIED_FLASH_MS)
|
|
526
|
-
}
|
|
527
584
|
const prefetchDoc = () => {
|
|
528
585
|
if (doc?.status === 'ready' || doc?.status === 'loading') return
|
|
529
586
|
if (skillDocs.has(entry.name)) {
|
|
@@ -544,45 +601,56 @@ function SelectedSkillRow({ entry, state, disabled, capReached = false, capToolt
|
|
|
544
601
|
: doc?.status === 'failed'
|
|
545
602
|
? h('p', { className: 'capital-config-skill-doc-state' }, t(doc.code === 'route_missing' ? 'selectedSkillsDocStale' : 'selectedSkillsDocFailed'))
|
|
546
603
|
: h('p', { className: 'capital-config-skill-doc-state' }, t('selectedSkillsDocLoading'))
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
604
|
+
const { when, keywords } = splitUseWhen(SELECTED_SKILL_ROUTING[entry.name]?.useWhen?.[locale] ?? '', locale)
|
|
605
|
+
const roles = entry.roles ?? []
|
|
606
|
+
return h('div', { className: 'capital-config-skill-item' },
|
|
607
|
+
h('div', { className: 'capital-config-skill-row' },
|
|
608
|
+
h('div', { className: 'capital-config-skill-main' },
|
|
550
609
|
h('div', { className: 'capital-config-skill-heading' },
|
|
551
|
-
h('
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
610
|
+
h('button', {
|
|
611
|
+
type: 'button',
|
|
612
|
+
className: 'capital-config-skill-name',
|
|
613
|
+
title: t('selectedSkillsOpenDetail'),
|
|
614
|
+
'aria-haspopup': 'dialog',
|
|
615
|
+
// Prefetch on hover / focus so the modal opens with content; click is the action.
|
|
616
|
+
onPointerEnter: prefetchDoc,
|
|
617
|
+
onFocus: prefetchDoc,
|
|
618
|
+
onClick: () => {
|
|
619
|
+
prefetchDoc()
|
|
620
|
+
setDetailOpen(true)
|
|
621
|
+
},
|
|
622
|
+
}, label),
|
|
623
|
+
h('div', { className: 'capital-config-skill-tags', role: 'group', 'aria-label': t('selectedSkillsTraitGroup') },
|
|
624
|
+
entry.tags.map((tag) => h(Tag, {
|
|
625
|
+
key: tag,
|
|
626
|
+
tone: 'neutral',
|
|
627
|
+
className: 'capital-config-skill-tag',
|
|
628
|
+
}, SELECTED_SKILL_TAG_LABELS[locale][tag] ?? tag))),
|
|
629
|
+
roles.length > 0 ? h('div', { className: 'capital-config-skill-presets', role: 'group', 'aria-label': t('selectedSkillsPresetGroup') },
|
|
630
|
+
roles.map((role) => h(Tag, {
|
|
631
|
+
key: role,
|
|
632
|
+
tone: 'info',
|
|
633
|
+
className: 'capital-config-skill-preset',
|
|
634
|
+
},
|
|
635
|
+
h('span', { className: 'capital-config-sr-only' }, `${t('selectedSkillsPresetGroup')}:`),
|
|
636
|
+
presetLabel(role, locale)))) : null,
|
|
637
|
+
h(Modal, {
|
|
638
|
+
open: detailOpen,
|
|
639
|
+
onClose: () => setDetailOpen(false),
|
|
640
|
+
title: label,
|
|
641
|
+
description: entry.name,
|
|
642
|
+
closeLabel: t('selectedSkillsDetailClose'),
|
|
643
|
+
className: 'capital-config-skill-modal',
|
|
644
|
+
contentClassName: 'capital-config-skill-modal-content',
|
|
645
|
+
}, detailContent),
|
|
584
646
|
),
|
|
585
647
|
h('p', { className: 'capital-config-skill-description' }, entry.description[locale]),
|
|
648
|
+
h('dl', { className: 'capital-config-skill-meta' },
|
|
649
|
+
h('dt', { key: 'when-label' }, t('selectedSkillsWhen')),
|
|
650
|
+
h('dd', { key: 'when' }, when),
|
|
651
|
+
keywords ? h('dt', { key: 'keywords-label' }, t('selectedSkillsKeywords')) : null,
|
|
652
|
+
keywords ? h('dd', { key: 'keywords' }, keywords) : null,
|
|
653
|
+
),
|
|
586
654
|
h('a', {
|
|
587
655
|
className: 'capital-config-skill-repo',
|
|
588
656
|
href: repositoryUrl,
|
|
@@ -590,22 +658,11 @@ function SelectedSkillRow({ entry, state, disabled, capReached = false, capToolt
|
|
|
590
658
|
rel: 'noopener noreferrer',
|
|
591
659
|
title: t('selectedSkillsOpenRepo'),
|
|
592
660
|
}, `${entry.owner}/${entry.repository}`, h(IconRightUpOutlineRegular, { size: 12 })),
|
|
593
|
-
h('div', { className: 'capital-config-skill-tags' }, entry.tags.map((tag) =>
|
|
594
|
-
h('span', { key: tag, className: 'capital-config-skill-tag' }, SELECTED_SKILL_TAG_LABELS[locale][tag] ?? tag))),
|
|
595
661
|
),
|
|
596
|
-
// Capped rows: reason on the icon before the switch; the icon ignores custom
|
|
597
|
-
// props, so title / role sit on its wrapper.
|
|
598
|
-
capReached && state?.on !== true ? h('span', {
|
|
599
|
-
className: 'capital-config-skill-cap-icon',
|
|
600
|
-
title: capTooltip,
|
|
601
|
-
role: 'img',
|
|
602
|
-
'aria-label': capTooltip,
|
|
603
|
-
}, h(IconWarningTriangleOutlineRegular, { size: 14 })) : null,
|
|
604
662
|
h(Switch, {
|
|
605
663
|
label,
|
|
606
664
|
checked: state?.on === true,
|
|
607
|
-
|
|
608
|
-
disabled: disabled || state?.writing === true || (capReached && state?.on !== true),
|
|
665
|
+
disabled: disabled || state?.writing === true,
|
|
609
666
|
onChange,
|
|
610
667
|
}),
|
|
611
668
|
),
|
|
@@ -623,9 +680,11 @@ function CapitalCard(props) {
|
|
|
623
680
|
const t = props.t
|
|
624
681
|
const state = props.useCapitalCard((snapshot) => snapshot)
|
|
625
682
|
const disabled = !state.writable
|
|
683
|
+
const autoMode = state.selectedSkillsMode?.auto === true
|
|
626
684
|
const locale = skillLocale()
|
|
627
685
|
const [tab, setTab] = useState('sources')
|
|
628
|
-
const [
|
|
686
|
+
const [traitFilter, setTraitFilter] = useState(SELECTED_SKILL_FILTER_ALL)
|
|
687
|
+
const [presetFilter, setPresetFilter] = useState(SELECTED_SKILL_FILTER_ALL)
|
|
629
688
|
// Fetch on mount: tab labels carry numbers; hooks must precede the unavailable return.
|
|
630
689
|
useEffect(() => { props.ensureBundleRows() }, [])
|
|
631
690
|
// Pinned order is snapshotted on tab entry so rows never move under the cursor.
|
|
@@ -633,17 +692,11 @@ function CapitalCard(props) {
|
|
|
633
692
|
.filter((entry) => state.selectedSkills?.[entry.key]?.on === true)
|
|
634
693
|
.map((entry) => entry.key))
|
|
635
694
|
const [pinnedSkills, setPinnedSkills] = useState(enabledKeys)
|
|
636
|
-
useEffect(() => {
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
}
|
|
641
|
-
tagCounts.set('all', SELECTED_SKILL_CATALOG.length)
|
|
642
|
-
const tags = ['all', ...Array.from(tagCounts.keys())
|
|
643
|
-
.filter((tag) => tag !== 'all')
|
|
644
|
-
.sort((left, right) => tagCounts.get(right) - tagCounts.get(left))]
|
|
645
|
-
const visibleSkills = SELECTED_SKILL_CATALOG
|
|
646
|
-
.filter((entry) => tagFilter === 'all' || entry.tags.includes(tagFilter))
|
|
695
|
+
useEffect(() => {
|
|
696
|
+
if (state.available && !autoMode) setPinnedSkills(enabledKeys())
|
|
697
|
+
}, [state.available, autoMode])
|
|
698
|
+
const facetCounts = selectedSkillFacetCounts({ trait: traitFilter, preset: presetFilter })
|
|
699
|
+
const visibleSkills = filterSelectedSkills({ trait: traitFilter, preset: presetFilter })
|
|
647
700
|
.sort((left, right) => (pinnedSkills.has(left.key) ? 0 : 1) - (pinnedSkills.has(right.key) ? 0 : 1))
|
|
648
701
|
const form = h(SettingsForm, {
|
|
649
702
|
labels: {
|
|
@@ -679,42 +732,75 @@ function CapitalCard(props) {
|
|
|
679
732
|
),
|
|
680
733
|
state.localFetch?.failed ? h('p', { role: 'status', className: 'capital-config-write-failed' }, t('saveFailed')) : null,
|
|
681
734
|
)
|
|
682
|
-
const
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
735
|
+
const modeSection = h('div', { className: 'capital-config-selected-mode' },
|
|
736
|
+
h('div', { className: 'capital-config-switch-row' },
|
|
737
|
+
h('div', { className: 'capital-config-switch-text' },
|
|
738
|
+
h('span', { className: 'capital-config-switch-label' }, t('selectedSkillsModeLabel')),
|
|
739
|
+
h('p', { className: 'capital-config-hint' }, t('selectedSkillsModeHint')),
|
|
740
|
+
),
|
|
741
|
+
h(Switch, {
|
|
742
|
+
label: t('selectedSkillsModeLabel'),
|
|
743
|
+
checked: autoMode,
|
|
744
|
+
disabled: disabled || state.saving || state.selectedSkillsMode?.writing === true,
|
|
745
|
+
onChange: props.toggleSelectedSkillsMode,
|
|
746
|
+
}),
|
|
747
|
+
),
|
|
748
|
+
state.selectedSkillsMode?.failed ? h('p', { role: 'status', className: 'capital-config-write-failed' }, t('saveFailed')) : null,
|
|
749
|
+
)
|
|
750
|
+
const traitLabels = SELECTED_SKILL_TAG_LABELS[locale]
|
|
751
|
+
const filterRow = (label, pills) => h('div', { className: 'capital-config-filter-row' },
|
|
752
|
+
h('span', { className: 'capital-config-filter-label' }, label),
|
|
753
|
+
h('div', { className: 'capital-config-filter-options', role: 'group', 'aria-label': label }, ...pills),
|
|
754
|
+
)
|
|
755
|
+
const traitPills = SELECTED_SKILL_TRAITS.map((trait) => h(Pill, {
|
|
756
|
+
key: trait,
|
|
757
|
+
type: 'button',
|
|
758
|
+
className: 'capital-config-filter-pill',
|
|
759
|
+
active: traitFilter === trait,
|
|
760
|
+
'aria-pressed': traitFilter === trait ? 'true' : 'false',
|
|
761
|
+
onClick: () => setTraitFilter(trait),
|
|
762
|
+
},
|
|
763
|
+
trait === SELECTED_SKILL_FILTER_ALL ? t('selectedSkillsAll') : (traitLabels[trait] ?? trait),
|
|
764
|
+
h('span', { className: 'capital-config-filter-count' }, String(facetCounts.traits.get(trait) ?? 0))))
|
|
765
|
+
const presetPills = [SELECTED_SKILL_FILTER_ALL, ...SELECTED_SKILL_PRESET_ORDER].map((preset) => {
|
|
766
|
+
const total = preset === SELECTED_SKILL_FILTER_ALL
|
|
767
|
+
? SELECTED_SKILL_CATALOG.length
|
|
768
|
+
: SELECTED_SKILL_PRESET_TOTALS.get(preset) ?? 0
|
|
769
|
+
const unavailable = preset !== SELECTED_SKILL_FILTER_ALL && total === 0
|
|
770
|
+
return h(Pill, {
|
|
771
|
+
key: preset,
|
|
686
772
|
type: 'button',
|
|
687
|
-
className: 'capital-config-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
773
|
+
className: 'capital-config-filter-pill',
|
|
774
|
+
active: presetFilter === preset,
|
|
775
|
+
'aria-pressed': presetFilter === preset ? 'true' : 'false',
|
|
776
|
+
disabled: unavailable,
|
|
777
|
+
title: unavailable ? t('selectedSkillsPresetUnavailable') : undefined,
|
|
778
|
+
onClick: () => setPresetFilter(preset),
|
|
779
|
+
},
|
|
780
|
+
preset === SELECTED_SKILL_FILTER_ALL ? t('selectedSkillsAll') : presetLabel(preset, locale),
|
|
781
|
+
h('span', { className: 'capital-config-filter-count' }, String(facetCounts.presets.get(preset) ?? 0)))
|
|
782
|
+
})
|
|
783
|
+
const skillsFilter = h('div', { className: 'capital-config-filters', role: 'group', 'aria-label': t('selectedSkillsFilterLabel') },
|
|
784
|
+
filterRow(t('selectedSkillsTraitGroup'), traitPills),
|
|
785
|
+
filterRow(t('selectedSkillsPresetGroup'), presetPills),
|
|
691
786
|
)
|
|
692
|
-
//
|
|
693
|
-
const enabledSkills = SELECTED_SKILL_CATALOG.filter(
|
|
694
|
-
(entry) => state.selectedSkills?.[entry.key]?.on === true,
|
|
695
|
-
).length
|
|
696
|
-
const capReached = enabledSkills >= SELECTED_SKILLS_MAX_ENABLED
|
|
697
|
-
// Build the shared tooltip once; per-row drift would show different numbers.
|
|
698
|
-
const capTooltip = t('selectedSkillsCapTooltip')
|
|
699
|
-
.replace('{count}', `${enabledSkills}/${SELECTED_SKILLS_MAX_ENABLED}`)
|
|
787
|
+
// No cap: the user builds the set; the role prompt caps only how many it lists.
|
|
700
788
|
const skillRows = visibleSkills.length > 0
|
|
701
789
|
? visibleSkills.map((entry) => h(SelectedSkillRow, {
|
|
702
790
|
key: entry.key,
|
|
703
|
-
entry,
|
|
791
|
+
entry: { ...entry, roles: selectedSkillRoles(entry) },
|
|
704
792
|
state: state.selectedSkills?.[entry.key],
|
|
705
793
|
disabled: disabled || state.saving,
|
|
706
|
-
capReached,
|
|
707
|
-
capTooltip,
|
|
708
794
|
onChange: (value) => props.toggleSelectedSkill(entry.key, value),
|
|
709
795
|
t,
|
|
710
796
|
}))
|
|
711
|
-
: [h('p', { key: 'empty', className: 'capital-config-
|
|
797
|
+
: [h('p', { key: 'empty', className: 'capital-config-selected-empty' }, t('selectedSkillsEmpty'))]
|
|
712
798
|
// No cap banner: the icon is the only explanation. Unknown count prints no number.
|
|
713
799
|
const withCount = (label, count) => (Number.isInteger(count) ? `${label} ${count}` : label)
|
|
714
800
|
const options = [
|
|
715
801
|
// A dirty draft while Save is off-screen must be announced on the tab.
|
|
716
802
|
{ value: 'sources', label: state.dirty ? t('titleUnsaved') : t('title') },
|
|
717
|
-
{ value: 'skills', label:
|
|
803
|
+
{ value: 'skills', label: t('selectedSkillsTitle') },
|
|
718
804
|
{ value: 'components', label: withCount(t('componentsTitle'), state.components?.count) },
|
|
719
805
|
]
|
|
720
806
|
const panel = (value, children) => h('section', {
|
|
@@ -741,12 +827,12 @@ function CapitalCard(props) {
|
|
|
741
827
|
},
|
|
742
828
|
}),
|
|
743
829
|
panel('sources', [form, localFetchSection]),
|
|
744
|
-
panel('skills',
|
|
745
|
-
|
|
830
|
+
panel('skills', autoMode
|
|
831
|
+
? [modeSection]
|
|
832
|
+
: [modeSection, h('div', { className: 'capital-config-skills-browser' },
|
|
746
833
|
skillsFilter,
|
|
747
834
|
h('div', { className: 'capital-config-selected-list' }, ...skillRows),
|
|
748
|
-
),
|
|
749
|
-
]),
|
|
835
|
+
)]),
|
|
750
836
|
// The panel is the host's rows list; keep the aria-controls target non-empty via sr-only.
|
|
751
837
|
panel('components', [h('span', { className: 'capital-config-sr-only' }, t('componentsHint'))]),
|
|
752
838
|
)
|
package/config/index.js
CHANGED
|
@@ -59,15 +59,21 @@ const PaddleOcrSchema = z.object({
|
|
|
59
59
|
|
|
60
60
|
const SelectedSkillsShape = Object.fromEntries(SELECTED_SKILL_CATALOG.map((entry) => [
|
|
61
61
|
entry.key,
|
|
62
|
-
z.boolean().default(false).volatile().description(`Enable ${entry.name} for the
|
|
62
|
+
z.boolean().default(false).volatile().description(`Enable ${entry.name} for the Capital role(s) that own it (experimental; default off)`),
|
|
63
63
|
]))
|
|
64
64
|
const SelectedSkillsSchema = z.object(SelectedSkillsShape)
|
|
65
65
|
|
|
66
66
|
const SELECTED_SKILL_DEFAULTS = Object.fromEntries(SELECTED_SKILL_CATALOG.map((entry) => [entry.key, false]))
|
|
67
67
|
|
|
68
|
+
const SelectedSkillsModeSchema = z.union(['manual', 'auto'])
|
|
69
|
+
.default('manual')
|
|
70
|
+
.volatile()
|
|
71
|
+
.description('manual = 只用用户勾选;auto = 按角色开放全部方法,运行期忽略勾选')
|
|
72
|
+
|
|
68
73
|
export const Config = z.object({
|
|
69
74
|
customPersona: z.string().default('').volatile().description('自动模式附加人设文本'),
|
|
70
|
-
|
|
75
|
+
selectedSkillsMode: SelectedSkillsModeSchema,
|
|
76
|
+
selectedSkills: SelectedSkillsSchema.default(SELECTED_SKILL_DEFAULTS).description('Explicitly selected community methods; default off, each visible only to the Capital role(s) that own it'),
|
|
71
77
|
fuyaoCredentialRef: z.string().default('FUYAO_API_KEY').volatile().description('Fuyao credentials 引用名'),
|
|
72
78
|
ftshareCredentialRef: z.string().default('FTSHARE_API_KEY').volatile().description('FTShare(非凸科技)credentials 引用名'),
|
|
73
79
|
retriever: z.object({
|