@v587d/capital-generation 2.10.1 → 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 +16 -0
- package/README.md +19 -10
- package/chart-ui/package.json +1 -1
- package/config/client.js +36 -31
- package/config/client.src.cjs +185 -139
- package/config/package.json +1 -1
- 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 +2 -1
- package/selected-skills/README.md +3 -2
- package/selected-skills/filter.js +70 -0
- package/selected-skills/routing.js +23 -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
13
|
} = require('@deepseek-ai/dsh-client-ui-primitives')
|
|
13
14
|
const { SELECTED_SKILL_CATALOG, SELECTED_SKILL_TAG_LABELS } = require('../selected-skills/catalog.js')
|
|
14
|
-
const {
|
|
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,16 +97,17 @@ const zh = {
|
|
|
81
97
|
paddleocrLabel: 'PaddleOCR 文档解析 Token',
|
|
82
98
|
paddleocrHint: '配置 Token 后自动解析 PDF、图片。保存后新自动模式会话生效。',
|
|
83
99
|
selectedSkillsTitle: '精选 Skills',
|
|
84
|
-
selectedSkillsModeLabel: '
|
|
85
|
-
selectedSkillsModeHint: '
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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',
|
|
94
111
|
selectedSkillsDetailClose: '关闭',
|
|
95
112
|
selectedSkillsDocLoading: '原文读取中…',
|
|
96
113
|
selectedSkillsDocFailed: '原文读不到:这份快照没随包落地,或暂时读不出来。',
|
|
@@ -127,16 +144,17 @@ const en = {
|
|
|
127
144
|
paddleocrLabel: 'PaddleOCR document token',
|
|
128
145
|
paddleocrHint: 'Configure the token to parse PDFs and images automatically. Takes effect in new Auto mode sessions.',
|
|
129
146
|
selectedSkillsTitle: 'Selected Skills',
|
|
130
|
-
selectedSkillsModeLabel: '
|
|
131
|
-
selectedSkillsModeHint: 'When on,
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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.',
|
|
140
158
|
selectedSkillsDetailClose: 'Close',
|
|
141
159
|
selectedSkillsDocLoading: 'Loading the source…',
|
|
142
160
|
selectedSkillsDocFailed: 'The source could not be read: this snapshot is not in the installed package, or it cannot be opened right now.',
|
|
@@ -172,7 +190,6 @@ function installStyles() {
|
|
|
172
190
|
style.dataset.capitalConfig = 'true'
|
|
173
191
|
style.textContent = `
|
|
174
192
|
.capital-config-card { display: flex; flex-direction: column; gap: 12px; }
|
|
175
|
-
.capital-config-title { margin: 0; font-size: 14px; font-weight: 500; line-height: 20px; }
|
|
176
193
|
/* Declare display only when not hidden, or the hidden attribute loses to author display. */
|
|
177
194
|
.capital-config-panel { flex-direction: column; gap: 12px; }
|
|
178
195
|
.capital-config-panel:not([hidden]) { display: flex; }
|
|
@@ -188,25 +205,39 @@ function installStyles() {
|
|
|
188
205
|
.capital-config-secret-row label { flex: 0 1 auto; }
|
|
189
206
|
.capital-config-secret-row label ~ span { order: 1; margin-left: auto; }
|
|
190
207
|
.capital-config-field { display: flex; flex-direction: column; gap: 6px; padding: 12px 0; border-top: 0.5px solid var(--dsw-alias-border-l2); }
|
|
191
|
-
|
|
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-
|
|
202
|
-
.capital-config-
|
|
203
|
-
.capital-config-
|
|
204
|
-
.capital-config-
|
|
205
|
-
.capital-config-
|
|
206
|
-
.capital-config-
|
|
207
|
-
.capital-config-skill-
|
|
208
|
-
.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; }
|
|
209
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; }
|
|
210
241
|
/* Official Modal defaults to 380px; the div prefix outranks the hashed .dialog class. */
|
|
211
242
|
div.capital-config-skill-modal { width: min(820px, 100%); max-height: 100%; }
|
|
212
243
|
/* Scroll the body so the dialog's overflow:hidden cannot clip it. */
|
|
@@ -221,14 +252,6 @@ function installStyles() {
|
|
|
221
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; }
|
|
222
253
|
.capital-config-doc-link:hover { color: var(--dsw-alias-brand-primary); text-decoration: underline; }
|
|
223
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; }
|
|
224
|
-
.capital-config-selected-mode { margin-bottom: 12px; }
|
|
225
|
-
.capital-config-switch-row { align-items: center; gap: 12px; display: flex; }
|
|
226
|
-
.capital-config-switch-text { min-width: 0; flex: 1; display: flex; flex-direction: column; gap: 4px; }
|
|
227
|
-
.capital-config-switch-label { color: var(--dsw-alias-label-primary); font-size: 13px; font-weight: 500; line-height: 20px; }
|
|
228
|
-
.capital-config-hint { margin: 0; color: var(--dsw-alias-label-tertiary); font-size: 12px; line-height: 18px; }
|
|
229
|
-
.capital-config-skill-roles { display: flex; flex-wrap: wrap; gap: 4px; }
|
|
230
|
-
.capital-config-skill-role { padding: 1px 5px; border-radius: 4px; border: 0.5px solid var(--dsw-alias-border-l1); color: var(--dsw-alias-label-secondary); font-size: 11px; line-height: 16px; }
|
|
231
|
-
.capital-config-skill-when { margin: 0; color: var(--dsw-alias-label-tertiary); font-size: 11.5px; line-height: 17px; }
|
|
232
255
|
.capital-config-write-failed { margin: 0; color: var(--dsw-alias-state-error-primary); font-size: 12px; line-height: 18px; }
|
|
233
256
|
/* Own inline-flex group so the host's wrapping never splits the two links. */
|
|
234
257
|
.capital-config-plugin-links { flex: none; align-items: center; gap: 2px; display: inline-flex; }
|
|
@@ -537,27 +560,27 @@ function loadSkillDoc(name) {
|
|
|
537
560
|
return pending
|
|
538
561
|
}
|
|
539
562
|
|
|
540
|
-
|
|
563
|
+
function presetLabel(role, locale) {
|
|
564
|
+
return SELECTED_SKILL_PRESETS[role]?.[locale] ?? role
|
|
565
|
+
}
|
|
541
566
|
|
|
542
|
-
|
|
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
|
+
}
|
|
577
|
+
|
|
578
|
+
function SelectedSkillRow({ entry, state, disabled, onChange, t }) {
|
|
543
579
|
const locale = skillLocale()
|
|
544
580
|
const label = entry.label[locale]
|
|
545
581
|
const repositoryUrl = `https://github.com/${entry.owner}/${entry.repository}`
|
|
546
|
-
const copyTimer = useRef(null)
|
|
547
|
-
const [copied, setCopied] = useState(false)
|
|
548
582
|
const [detailOpen, setDetailOpen] = useState(false)
|
|
549
583
|
const [doc, setDoc] = useState(null)
|
|
550
|
-
useEffect(() => () => {
|
|
551
|
-
if (copyTimer.current !== null) clearTimeout(copyTimer.current)
|
|
552
|
-
}, [])
|
|
553
|
-
const copyName = async () => {
|
|
554
|
-
const accepted = await writeClipboard(`/${entry.name}`)
|
|
555
|
-
// Do not flip the label if the clipboard rejected it.
|
|
556
|
-
if (!accepted) return
|
|
557
|
-
setCopied(true)
|
|
558
|
-
if (copyTimer.current !== null) clearTimeout(copyTimer.current)
|
|
559
|
-
copyTimer.current = setTimeout(() => setCopied(false), COPIED_FLASH_MS)
|
|
560
|
-
}
|
|
561
584
|
const prefetchDoc = () => {
|
|
562
585
|
if (doc?.status === 'ready' || doc?.status === 'loading') return
|
|
563
586
|
if (skillDocs.has(entry.name)) {
|
|
@@ -578,48 +601,56 @@ function SelectedSkillRow({ entry, state, copyable, disabled, onChange, t }) {
|
|
|
578
601
|
: doc?.status === 'failed'
|
|
579
602
|
? h('p', { className: 'capital-config-skill-doc-state' }, t(doc.code === 'route_missing' ? 'selectedSkillsDocStale' : 'selectedSkillsDocFailed'))
|
|
580
603
|
: h('p', { className: 'capital-config-skill-doc-state' }, t('selectedSkillsDocLoading'))
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
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' },
|
|
584
609
|
h('div', { className: 'capital-config-skill-heading' },
|
|
585
|
-
h('
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
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),
|
|
618
646
|
),
|
|
619
647
|
h('p', { className: 'capital-config-skill-description' }, entry.description[locale]),
|
|
620
|
-
h('
|
|
621
|
-
|
|
622
|
-
h('
|
|
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
|
+
),
|
|
623
654
|
h('a', {
|
|
624
655
|
className: 'capital-config-skill-repo',
|
|
625
656
|
href: repositoryUrl,
|
|
@@ -627,8 +658,6 @@ function SelectedSkillRow({ entry, state, copyable, disabled, onChange, t }) {
|
|
|
627
658
|
rel: 'noopener noreferrer',
|
|
628
659
|
title: t('selectedSkillsOpenRepo'),
|
|
629
660
|
}, `${entry.owner}/${entry.repository}`, h(IconRightUpOutlineRegular, { size: 12 })),
|
|
630
|
-
h('div', { className: 'capital-config-skill-tags' }, entry.tags.map((tag) =>
|
|
631
|
-
h('span', { key: tag, className: 'capital-config-skill-tag' }, SELECTED_SKILL_TAG_LABELS[locale][tag] ?? tag))),
|
|
632
661
|
),
|
|
633
662
|
h(Switch, {
|
|
634
663
|
label,
|
|
@@ -654,7 +683,8 @@ function CapitalCard(props) {
|
|
|
654
683
|
const autoMode = state.selectedSkillsMode?.auto === true
|
|
655
684
|
const locale = skillLocale()
|
|
656
685
|
const [tab, setTab] = useState('sources')
|
|
657
|
-
const [
|
|
686
|
+
const [traitFilter, setTraitFilter] = useState(SELECTED_SKILL_FILTER_ALL)
|
|
687
|
+
const [presetFilter, setPresetFilter] = useState(SELECTED_SKILL_FILTER_ALL)
|
|
658
688
|
// Fetch on mount: tab labels carry numbers; hooks must precede the unavailable return.
|
|
659
689
|
useEffect(() => { props.ensureBundleRows() }, [])
|
|
660
690
|
// Pinned order is snapshotted on tab entry so rows never move under the cursor.
|
|
@@ -662,17 +692,11 @@ function CapitalCard(props) {
|
|
|
662
692
|
.filter((entry) => state.selectedSkills?.[entry.key]?.on === true)
|
|
663
693
|
.map((entry) => entry.key))
|
|
664
694
|
const [pinnedSkills, setPinnedSkills] = useState(enabledKeys)
|
|
665
|
-
useEffect(() => {
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
}
|
|
670
|
-
tagCounts.set('all', SELECTED_SKILL_CATALOG.length)
|
|
671
|
-
const tags = ['all', ...Array.from(tagCounts.keys())
|
|
672
|
-
.filter((tag) => tag !== 'all')
|
|
673
|
-
.sort((left, right) => tagCounts.get(right) - tagCounts.get(left))]
|
|
674
|
-
const visibleSkills = SELECTED_SKILL_CATALOG
|
|
675
|
-
.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 })
|
|
676
700
|
.sort((left, right) => (pinnedSkills.has(left.key) ? 0 : 1) - (pinnedSkills.has(right.key) ? 0 : 1))
|
|
677
701
|
const form = h(SettingsForm, {
|
|
678
702
|
labels: {
|
|
@@ -708,7 +732,7 @@ function CapitalCard(props) {
|
|
|
708
732
|
),
|
|
709
733
|
state.localFetch?.failed ? h('p', { role: 'status', className: 'capital-config-write-failed' }, t('saveFailed')) : null,
|
|
710
734
|
)
|
|
711
|
-
const modeSection = h('div', { className: 'capital-config-
|
|
735
|
+
const modeSection = h('div', { className: 'capital-config-selected-mode' },
|
|
712
736
|
h('div', { className: 'capital-config-switch-row' },
|
|
713
737
|
h('div', { className: 'capital-config-switch-text' },
|
|
714
738
|
h('span', { className: 'capital-config-switch-label' }, t('selectedSkillsModeLabel')),
|
|
@@ -723,37 +747,60 @@ function CapitalCard(props) {
|
|
|
723
747
|
),
|
|
724
748
|
state.selectedSkillsMode?.failed ? h('p', { role: 'status', className: 'capital-config-write-failed' }, t('saveFailed')) : null,
|
|
725
749
|
)
|
|
726
|
-
const
|
|
727
|
-
const
|
|
728
|
-
|
|
729
|
-
|
|
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,
|
|
730
772
|
type: 'button',
|
|
731
|
-
className: 'capital-config-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
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),
|
|
735
786
|
)
|
|
736
787
|
// No cap: the user builds the set; the role prompt caps only how many it lists.
|
|
737
|
-
const enabledSkills = SELECTED_SKILL_CATALOG.filter(
|
|
738
|
-
(entry) => state.selectedSkills?.[entry.key]?.on === true,
|
|
739
|
-
).length
|
|
740
788
|
const skillRows = visibleSkills.length > 0
|
|
741
789
|
? visibleSkills.map((entry) => h(SelectedSkillRow, {
|
|
742
790
|
key: entry.key,
|
|
743
|
-
entry: { ...entry, roles:
|
|
791
|
+
entry: { ...entry, roles: selectedSkillRoles(entry) },
|
|
744
792
|
state: state.selectedSkills?.[entry.key],
|
|
745
|
-
|
|
746
|
-
disabled: disabled || state.saving || autoMode,
|
|
793
|
+
disabled: disabled || state.saving,
|
|
747
794
|
onChange: (value) => props.toggleSelectedSkill(entry.key, value),
|
|
748
795
|
t,
|
|
749
796
|
}))
|
|
750
|
-
: [h('p', { key: 'empty', className: 'capital-config-
|
|
797
|
+
: [h('p', { key: 'empty', className: 'capital-config-selected-empty' }, t('selectedSkillsEmpty'))]
|
|
751
798
|
// No cap banner: the icon is the only explanation. Unknown count prints no number.
|
|
752
799
|
const withCount = (label, count) => (Number.isInteger(count) ? `${label} ${count}` : label)
|
|
753
800
|
const options = [
|
|
754
801
|
// A dirty draft while Save is off-screen must be announced on the tab.
|
|
755
802
|
{ value: 'sources', label: state.dirty ? t('titleUnsaved') : t('title') },
|
|
756
|
-
{ value: 'skills', label:
|
|
803
|
+
{ value: 'skills', label: t('selectedSkillsTitle') },
|
|
757
804
|
{ value: 'components', label: withCount(t('componentsTitle'), state.components?.count) },
|
|
758
805
|
]
|
|
759
806
|
const panel = (value, children) => h('section', {
|
|
@@ -780,13 +827,12 @@ function CapitalCard(props) {
|
|
|
780
827
|
},
|
|
781
828
|
}),
|
|
782
829
|
panel('sources', [form, localFetchSection]),
|
|
783
|
-
panel('skills',
|
|
784
|
-
modeSection
|
|
785
|
-
h('div', { className: 'capital-config-
|
|
830
|
+
panel('skills', autoMode
|
|
831
|
+
? [modeSection]
|
|
832
|
+
: [modeSection, h('div', { className: 'capital-config-skills-browser' },
|
|
786
833
|
skillsFilter,
|
|
787
834
|
h('div', { className: 'capital-config-selected-list' }, ...skillRows),
|
|
788
|
-
),
|
|
789
|
-
]),
|
|
835
|
+
)]),
|
|
790
836
|
// The panel is the host's rows list; keep the aria-controls target non-empty via sr-only.
|
|
791
837
|
panel('components', [h('span', { className: 'capital-config-sr-only' }, t('componentsHint'))]),
|
|
792
838
|
)
|
package/config/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Known limit: DNS resolves per request but the IP is not pinned to the connection,
|
|
4
4
|
* so a rebinding window remains (same trade-off as dsh-search-first).
|
|
5
5
|
*/
|
|
6
|
-
export declare const LOCAL_FETCH_CLIENT_VERSION = "2.10.
|
|
6
|
+
export declare const LOCAL_FETCH_CLIENT_VERSION = "2.10.2";
|
|
7
7
|
export declare const LOCAL_FETCH_MAX_URL_LENGTH = 2048;
|
|
8
8
|
export type LocalFetchErrorCode = 'ABORTED' | 'TIMEOUT' | 'INVALID_URL' | 'BLOCKED_URL' | 'DNS' | 'REDIRECT' | 'HTTP' | 'TOO_LARGE' | 'UNSUPPORTED_CONTENT_TYPE';
|
|
9
9
|
export declare class LocalFetchError extends Error {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* so a rebinding window remains (same trade-off as dsh-search-first).
|
|
5
5
|
*/
|
|
6
6
|
import { htmlToMarkdown, stripInvisibleText } from './html-markdown.js';
|
|
7
|
-
export const LOCAL_FETCH_CLIENT_VERSION = '2.10.
|
|
7
|
+
export const LOCAL_FETCH_CLIENT_VERSION = '2.10.2';
|
|
8
8
|
export const LOCAL_FETCH_MAX_URL_LENGTH = 2048;
|
|
9
9
|
export class LocalFetchError extends Error {
|
|
10
10
|
code;
|
|
@@ -35,7 +35,7 @@ export interface WindCallResult {
|
|
|
35
35
|
code?: WindErrorCode;
|
|
36
36
|
}
|
|
37
37
|
/** Must match `package.json` version; a test pins the two together. */
|
|
38
|
-
export declare const WIND_CLIENT_VERSION = "2.10.
|
|
38
|
+
export declare const WIND_CLIENT_VERSION = "2.10.2";
|
|
39
39
|
export interface WindClient {
|
|
40
40
|
callTool(toolName: string, args: Record<string, unknown>, signal?: AbortSignal): Promise<WindCallResult>;
|
|
41
41
|
}
|
|
@@ -19,7 +19,7 @@ export const WIND_SERVER_ENDPOINTS = {
|
|
|
19
19
|
analytics_data: 'https://mcp.wind.com.cn/vserver_analytics_data/mcp/',
|
|
20
20
|
};
|
|
21
21
|
/** Must match `package.json` version; a test pins the two together. */
|
|
22
|
-
export const WIND_CLIENT_VERSION = '2.10.
|
|
22
|
+
export const WIND_CLIENT_VERSION = '2.10.2';
|
|
23
23
|
function errorText(error) {
|
|
24
24
|
return error instanceof Error ? error.message : String(error);
|
|
25
25
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@v587d/capital-generation",
|
|
3
|
-
"version": "2.10.
|
|
3
|
+
"version": "2.10.2",
|
|
4
4
|
"description": "Capital Generation:面向中国散户的 AI 投资助手插件(自动模式 Agent Preset)",
|
|
5
5
|
"icon": "assets/cg-icon.png",
|
|
6
6
|
"author": "v587d",
|
|
@@ -108,6 +108,7 @@
|
|
|
108
108
|
"watchlist",
|
|
109
109
|
"preset",
|
|
110
110
|
"selected-skills/catalog.js",
|
|
111
|
+
"selected-skills/filter.js",
|
|
111
112
|
"selected-skills/routing.js",
|
|
112
113
|
"selected-skills/ROUTING.md",
|
|
113
114
|
"selected-skills/README.md",
|
|
@@ -29,8 +29,9 @@ for the user to review the source and decide whether it fits the task.
|
|
|
29
29
|
Tags describe what a skill helps with, not whether it is trustworthy or recommended.
|
|
30
30
|
`status`, `lastCommit`, `reviewBy` and `riskNote` are review metadata for this repository:
|
|
31
31
|
they drive the quarterly review cadence and the scan record, and they are deliberately not
|
|
32
|
-
rendered in the settings card, which shows the skill name, summary, capability tags
|
|
33
|
-
|
|
32
|
+
rendered in the settings card, which shows the skill name, summary, capability tags, the
|
|
33
|
+
preset that can load it, its trigger/keyword line and the upstream repository link. A tag
|
|
34
|
+
is not a guarantee of financial validity.
|
|
34
35
|
|
|
35
36
|
## Operating posture
|
|
36
37
|
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { SELECTED_SKILL_CATALOG } from './catalog.js'
|
|
2
|
+
import {
|
|
3
|
+
SELECTED_SKILL_PRESET_ORDER,
|
|
4
|
+
SELECTED_SKILL_ROLE_ALIASES,
|
|
5
|
+
SELECTED_SKILL_ROUTING,
|
|
6
|
+
} from './routing.js'
|
|
7
|
+
|
|
8
|
+
/** Both filter dimensions use this value for "no constraint". */
|
|
9
|
+
export const SELECTED_SKILL_FILTER_ALL = 'all'
|
|
10
|
+
|
|
11
|
+
/** Roles a skill can be loaded by; aliases resolve before matching. */
|
|
12
|
+
export function selectedSkillRoles(entry) {
|
|
13
|
+
return (SELECTED_SKILL_ROUTING[entry.name]?.roles ?? [])
|
|
14
|
+
.map((role) => SELECTED_SKILL_ROLE_ALIASES[role] ?? role)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Trait order is the full-catalog count, so chips never reorder while the user filters. */
|
|
18
|
+
export function selectedSkillTraitOrder(catalog = SELECTED_SKILL_CATALOG) {
|
|
19
|
+
const counts = new Map()
|
|
20
|
+
for (const entry of catalog) {
|
|
21
|
+
for (const tag of entry.tags) counts.set(tag, (counts.get(tag) ?? 0) + 1)
|
|
22
|
+
}
|
|
23
|
+
const traits = [...counts.keys()].sort((left, right) => counts.get(right) - counts.get(left))
|
|
24
|
+
return [SELECTED_SKILL_FILTER_ALL, ...traits]
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Total skills each preset can load; zero-count presets stay visible but disabled. */
|
|
28
|
+
export function selectedSkillPresetTotals(catalog = SELECTED_SKILL_CATALOG) {
|
|
29
|
+
const totals = new Map(SELECTED_SKILL_PRESET_ORDER.map((preset) => [preset, 0]))
|
|
30
|
+
for (const entry of catalog) {
|
|
31
|
+
for (const role of new Set(selectedSkillRoles(entry))) {
|
|
32
|
+
if (totals.has(role)) totals.set(role, totals.get(role) + 1)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return totals
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const matchesTrait = (entry, trait) => trait === SELECTED_SKILL_FILTER_ALL || entry.tags.includes(trait)
|
|
39
|
+
const matchesPreset = (entry, preset) => preset === SELECTED_SKILL_FILTER_ALL || selectedSkillRoles(entry).includes(preset)
|
|
40
|
+
|
|
41
|
+
/** Both dimensions intersect; either one set to `all` leaves the other as the only constraint. */
|
|
42
|
+
export function filterSelectedSkills({
|
|
43
|
+
trait = SELECTED_SKILL_FILTER_ALL,
|
|
44
|
+
preset = SELECTED_SKILL_FILTER_ALL,
|
|
45
|
+
catalog = SELECTED_SKILL_CATALOG,
|
|
46
|
+
} = {}) {
|
|
47
|
+
return catalog.filter((entry) => matchesTrait(entry, trait) && matchesPreset(entry, preset))
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Counts answer "how many rows will this chip show next", so each dimension is
|
|
52
|
+
* counted under the other dimension's current selection.
|
|
53
|
+
*/
|
|
54
|
+
export function selectedSkillFacetCounts({
|
|
55
|
+
trait = SELECTED_SKILL_FILTER_ALL,
|
|
56
|
+
preset = SELECTED_SKILL_FILTER_ALL,
|
|
57
|
+
catalog = SELECTED_SKILL_CATALOG,
|
|
58
|
+
} = {}) {
|
|
59
|
+
const traits = new Map([[SELECTED_SKILL_FILTER_ALL, 0]])
|
|
60
|
+
for (const entry of catalog.filter((item) => matchesPreset(item, preset))) {
|
|
61
|
+
traits.set(SELECTED_SKILL_FILTER_ALL, traits.get(SELECTED_SKILL_FILTER_ALL) + 1)
|
|
62
|
+
for (const tag of entry.tags) traits.set(tag, (traits.get(tag) ?? 0) + 1)
|
|
63
|
+
}
|
|
64
|
+
const presets = new Map([[SELECTED_SKILL_FILTER_ALL, 0]])
|
|
65
|
+
for (const entry of catalog.filter((item) => matchesTrait(item, trait))) {
|
|
66
|
+
presets.set(SELECTED_SKILL_FILTER_ALL, presets.get(SELECTED_SKILL_FILTER_ALL) + 1)
|
|
67
|
+
for (const role of new Set(selectedSkillRoles(entry))) presets.set(role, (presets.get(role) ?? 0) + 1)
|
|
68
|
+
}
|
|
69
|
+
return { traits, presets }
|
|
70
|
+
}
|