dsh-plugin-capabilities 0.3.1 → 0.3.3
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/lib/client.js +43 -8
- package/lib/client.js.map +2 -2
- package/package.json +1 -1
- package/src/client/MarketTab.tsx +21 -0
- package/src/client/SkillsTab.tsx +33 -10
- package/src/client/css.ts +13 -5
- package/src/client/locales.ts +4 -0
- package/src/client/primitives.d.ts +6 -0
package/package.json
CHANGED
package/src/client/MarketTab.tsx
CHANGED
|
@@ -260,6 +260,9 @@ export function MarketTab(props: { t: Translate; market: MarketInjected; mcp: Mc
|
|
|
260
260
|
open={detailRepo !== null || detailServer !== null}
|
|
261
261
|
onClose={() => setDetail(null)}
|
|
262
262
|
title={detailRepo !== null ? title(detailRepo) : detailServer !== null ? title(detailServer) : ''}
|
|
263
|
+
closeLabel={t('close')}
|
|
264
|
+
className="dpc-marketDialog"
|
|
265
|
+
contentClassName="dpc-marketContent"
|
|
263
266
|
footer={
|
|
264
267
|
<>
|
|
265
268
|
{detailRepo !== null && (
|
|
@@ -296,6 +299,24 @@ export function MarketTab(props: { t: Translate; market: MarketInjected; mcp: Mc
|
|
|
296
299
|
</>
|
|
297
300
|
}
|
|
298
301
|
>
|
|
302
|
+
{(detailRepo !== null || detailServer !== null) && (
|
|
303
|
+
<div className="dpc-detailTagsRow">
|
|
304
|
+
{detailRepo !== null && detailRepo.skillCount !== undefined && (
|
|
305
|
+
<span className="dpc-tag">{t('marketSkillCount').replace('{n}', String(detailRepo.skillCount))}</span>
|
|
306
|
+
)}
|
|
307
|
+
{detailRepo !== null && detailRepo.installedId !== null && (
|
|
308
|
+
<span className="dpc-tag" data-kind="source">{t('marketInstalled')}</span>
|
|
309
|
+
)}
|
|
310
|
+
{detailServer !== null && (
|
|
311
|
+
<>
|
|
312
|
+
<span className="dpc-tag">{detailServer.transport}</span>
|
|
313
|
+
{detailServer.runtime !== undefined && <span className="dpc-tag">{detailServer.runtime}</span>}
|
|
314
|
+
{detailServer.category !== undefined && <span className="dpc-tag">{detailServer.category}</span>}
|
|
315
|
+
{detailServer.installed && <span className="dpc-tag" data-kind="source">{t('marketInstalled')}</span>}
|
|
316
|
+
</>
|
|
317
|
+
)}
|
|
318
|
+
</div>
|
|
319
|
+
)}
|
|
299
320
|
{detailRepo !== null && (
|
|
300
321
|
<>
|
|
301
322
|
<p className="dpc-detailDesc">{longText(detailRepo)}</p>
|
package/src/client/SkillsTab.tsx
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { useEffect, useState } from 'react'
|
|
7
7
|
import type { ReactElement } from 'react'
|
|
8
|
-
import { Button, IconRefreshOutline14, IconSkillOutline16, Modal, StateDot } from '@deepseek-ai/dsh-client-ui-primitives'
|
|
8
|
+
import { Button, IconRefreshOutline14, IconSkillOutline16, MarkdownText, Modal, StateDot } from '@deepseek-ai/dsh-client-ui-primitives'
|
|
9
9
|
import { CSS } from './css.ts'
|
|
10
10
|
import type { OpenTarget, Translate } from './index.ts'
|
|
11
11
|
import type { RootRowView } from './MarketTab.tsx'
|
|
@@ -77,6 +77,8 @@ export function SkillsTab(props: { t: Translate; injected: SkillsInjected }): Re
|
|
|
77
77
|
const { t, injected } = props
|
|
78
78
|
const [skills, setSkills] = useState<SkillRowView[] | null>(null)
|
|
79
79
|
const [editor, setEditor] = useState<EditorState | null>(null)
|
|
80
|
+
/** Content pane of the editor modal: rendered Markdown vs the raw editor. */
|
|
81
|
+
const [preview, setPreview] = useState(false)
|
|
80
82
|
const [confirmName, setConfirmName] = useState<string | null>(null)
|
|
81
83
|
const [busy, setBusy] = useState(false)
|
|
82
84
|
const [outcome, setOutcome] = useState<{ ok: boolean; text: string } | null>(null)
|
|
@@ -106,6 +108,7 @@ export function SkillsTab(props: { t: Translate; injected: SkillsInjected }): Re
|
|
|
106
108
|
|
|
107
109
|
const openCreate = (): void => {
|
|
108
110
|
setFormError(null)
|
|
111
|
+
setPreview(false)
|
|
109
112
|
setEditor({ mode: 'create', name: '', description: '', whenToUse: '', modelInvocable: true, userInvocable: true, content: '' })
|
|
110
113
|
}
|
|
111
114
|
|
|
@@ -114,6 +117,9 @@ export function SkillsTab(props: { t: Translate; injected: SkillsInjected }): Re
|
|
|
114
117
|
setFormError(null)
|
|
115
118
|
try {
|
|
116
119
|
const body = await injected.get(skill.name)
|
|
120
|
+
// Read-only sources open on the rendered preview; editable ones on the
|
|
121
|
+
// plain-text editor. The toggle in the content row flips either way.
|
|
122
|
+
setPreview(!skill.editable)
|
|
117
123
|
setEditor({
|
|
118
124
|
mode: skill.editable ? 'edit' : 'view',
|
|
119
125
|
name: skill.name,
|
|
@@ -485,15 +491,32 @@ export function SkillsTab(props: { t: Translate; injected: SkillsInjected }): Re
|
|
|
485
491
|
{t('userInvocable')}
|
|
486
492
|
</label>
|
|
487
493
|
</div>
|
|
488
|
-
<
|
|
489
|
-
<
|
|
490
|
-
|
|
491
|
-
className="dpc-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
494
|
+
<div className="dpc-label">
|
|
495
|
+
<div className="dpc-cardRow">
|
|
496
|
+
<span>{t('skillContent')}</span>
|
|
497
|
+
<span className="dpc-spacer" />
|
|
498
|
+
<div className="dpc-segments" role="tablist" aria-label={t('skillContent')}>
|
|
499
|
+
<button type="button" role="tab" aria-selected={preview} className="dpc-segment" data-active={preview ? 'true' : undefined} onClick={() => setPreview(true)}>
|
|
500
|
+
{t('skillPreview')}
|
|
501
|
+
</button>
|
|
502
|
+
<button type="button" role="tab" aria-selected={!preview} className="dpc-segment" data-active={!preview ? 'true' : undefined} onClick={() => setPreview(false)}>
|
|
503
|
+
{readOnly ? t('skillPlainText') : t('edit')}
|
|
504
|
+
</button>
|
|
505
|
+
</div>
|
|
506
|
+
</div>
|
|
507
|
+
{preview
|
|
508
|
+
? (typeof MarkdownText === 'function'
|
|
509
|
+
? <div className="dpc-mdPreview"><MarkdownText text={editor.content} /></div>
|
|
510
|
+
: <textarea className="dpc-textarea" value={editor.content} readOnly />)
|
|
511
|
+
: (
|
|
512
|
+
<textarea
|
|
513
|
+
className="dpc-textarea"
|
|
514
|
+
value={editor.content}
|
|
515
|
+
readOnly={readOnly}
|
|
516
|
+
onChange={(event) => setEditor({ ...editor, content: event.target.value })}
|
|
517
|
+
/>
|
|
518
|
+
)}
|
|
519
|
+
</div>
|
|
497
520
|
{formError !== null && <p className="dpc-formError">{formError}</p>}
|
|
498
521
|
<div className="dpc-cardRow">
|
|
499
522
|
<span className="dpc-spacer" />
|
package/src/client/css.ts
CHANGED
|
@@ -46,7 +46,7 @@ export const CSS = `
|
|
|
46
46
|
.dpc-label{display:flex;flex-direction:column;gap:4px;font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary)}
|
|
47
47
|
.dpc-label>span:first-child{color:var(--dsw-alias-label-tertiary)}
|
|
48
48
|
.dpc-input,.dpc-textarea,.dpc-select{width:100%;box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:7px 10px;outline:none;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font:inherit;font-size:13px}
|
|
49
|
-
.dpc-textarea{min-height:
|
|
49
|
+
.dpc-textarea{min-height:320px;resize:vertical;font-family:var(--ds-font-family-code);line-height:1.5}
|
|
50
50
|
.dpc-textarea[data-short='true']{min-height:96px}
|
|
51
51
|
.dpc-input:focus-visible,.dpc-textarea:focus-visible,.dpc-select:focus-visible{border-color:var(--dsw-alias-state-business-primary);box-shadow:0 0 0 2px color-mix(in srgb,var(--dsw-alias-state-business-primary) 18%,transparent)}
|
|
52
52
|
.dpc-checks{display:flex;gap:16px;font-size:13px;line-height:20px}
|
|
@@ -59,8 +59,9 @@ export const CSS = `
|
|
|
59
59
|
/* Editor dialogs (new skill / server): 640px wide so markdown bodies and
|
|
60
60
|
command/arg/env lines stop wrapping mid-token; the content column scrolls
|
|
61
61
|
on short viewports instead of clipping past the dialog edge. */
|
|
62
|
-
.dpc-modalForm.dpc-modalForm{width:min(
|
|
62
|
+
.dpc-modalForm.dpc-modalForm{width:min(760px,100%)}
|
|
63
63
|
.dpc-modalScroll.dpc-modalScroll{max-height:calc(100vh - 160px);overflow-y:auto}
|
|
64
|
+
.dpc-mdPreview{min-height:320px;max-height:60vh;overflow-y:auto;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:8px 12px;background:var(--dsw-alias-bg-layer-1);font-size:13px}
|
|
64
65
|
.dpc-importScroll{display:flex;flex-direction:column;gap:14px;max-height:min(400px,52vh);overflow-y:auto;padding:2px 4px 2px 2px}
|
|
65
66
|
.dpc-importGroup{display:flex;flex-direction:column;gap:8px}
|
|
66
67
|
.dpc-importHead{display:flex;align-items:center;gap:8px;padding:0 2px}
|
|
@@ -108,14 +109,21 @@ export const CSS = `
|
|
|
108
109
|
.dpc-format .dpc-form{margin-top:8px}
|
|
109
110
|
.dpc-formatHint{margin:2px 0 0;font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary)}
|
|
110
111
|
.dpc-code{margin:4px 0 8px;border:1px solid var(--dsw-alias-border-l2);border-radius:6px;padding:8px 10px;overflow-x:auto;background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);font-family:var(--ds-font-family-code);font-size:11px;line-height:17px;white-space:pre}
|
|
111
|
-
/* Market detail modal.
|
|
112
|
+
/* Market detail modal. The host dialog card is a fixed 380px column with no
|
|
113
|
+
scroll, which the long-form intros turn into a clipped strip — widen it
|
|
114
|
+
(doubled selector outranks the module class regardless of style order),
|
|
115
|
+
cap the height, and scroll inside the content region. */
|
|
116
|
+
.dpc-marketDialog.dpc-marketDialog{width:min(720px,100%);max-height:calc(100vh - 48px)}
|
|
117
|
+
.dpc-marketContent{overflow-y:auto;min-height:0}
|
|
118
|
+
.dpc-detailTagsRow{display:flex;flex-wrap:wrap;gap:4px;margin:0 0 8px}
|
|
112
119
|
.dpc-detailDesc{margin:0;font-size:13px;line-height:20px}
|
|
113
|
-
.dpc-detailSection{margin-top:
|
|
120
|
+
.dpc-detailSection{margin-top:14px;display:flex;flex-direction:column;gap:6px}
|
|
114
121
|
.dpc-detailLabel{font-size:11px;line-height:16px;color:var(--dsw-alias-label-secondary)}
|
|
115
122
|
.dpc-detailTags{display:flex;flex-wrap:wrap;gap:4px}
|
|
116
|
-
.dpc-skillList{margin:0;padding:0;list-style:none;display:
|
|
123
|
+
.dpc-skillList{margin:0;padding:0;list-style:none;display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:4px 18px}
|
|
117
124
|
.dpc-skillItem{display:flex;gap:8px;align-items:baseline;font-size:12px;line-height:18px;min-width:0}
|
|
118
125
|
.dpc-skillName{font-family:var(--ds-font-family-code);color:var(--dsw-alias-label-primary);white-space:nowrap;flex:none}
|
|
119
126
|
.dpc-skillDesc{color:var(--dsw-alias-label-secondary);min-width:0}
|
|
127
|
+
@media(max-width:560px){.dpc-skillList{grid-template-columns:minmax(0,1fr)}}
|
|
120
128
|
@media(max-width:680px){.dpc-cards{grid-template-columns:minmax(0,1fr)}}
|
|
121
129
|
`
|
package/src/client/locales.ts
CHANGED
|
@@ -14,6 +14,8 @@ export const zh = {
|
|
|
14
14
|
skillDescription: '描述',
|
|
15
15
|
skillWhenToUse: '使用时机(可选)',
|
|
16
16
|
skillContent: '正文(Markdown)',
|
|
17
|
+
skillPreview: 'Markdown 预览',
|
|
18
|
+
skillPlainText: '纯文本',
|
|
17
19
|
modelInvocable: '允许模型调用',
|
|
18
20
|
userInvocable: '允许用户 / 调用',
|
|
19
21
|
save: '保存',
|
|
@@ -145,6 +147,8 @@ export const en = {
|
|
|
145
147
|
skillDescription: 'Description',
|
|
146
148
|
skillWhenToUse: 'When to use (optional)',
|
|
147
149
|
skillContent: 'Body (Markdown)',
|
|
150
|
+
skillPreview: 'Markdown preview',
|
|
151
|
+
skillPlainText: 'Plain text',
|
|
148
152
|
modelInvocable: 'Model-invocable',
|
|
149
153
|
userInvocable: 'User-invocable (/)',
|
|
150
154
|
save: 'Save',
|
|
@@ -40,6 +40,12 @@ declare module '@deepseek-ai/dsh-client-ui-primitives' {
|
|
|
40
40
|
className?: string | undefined
|
|
41
41
|
}): ReactElement
|
|
42
42
|
|
|
43
|
+
/** Untrusted-Markdown renderer over the app's own mdast pipeline. */
|
|
44
|
+
export function MarkdownText(props: {
|
|
45
|
+
text: string
|
|
46
|
+
streaming?: boolean | undefined
|
|
47
|
+
}): ReactElement
|
|
48
|
+
|
|
43
49
|
export function IconRefreshOutline14(props: {
|
|
44
50
|
className?: string | undefined
|
|
45
51
|
size?: number | undefined
|