@lijian-ui/dsh-file-manager 0.2.1 → 0.2.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/assets/mermaid.min.js +3587 -0
- package/lib/client.js +87 -123
- package/lib/client.js.map +1 -1
- package/lib/tsconfig.client.tsbuildinfo +1 -1
- package/lib/types/client/index.d.ts +0 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/picker/FilePickerModal.d.ts +8 -6
- package/lib/types/client/picker/FilePickerModal.d.ts.map +1 -1
- package/lib/types/client/preview/PreviewPanel.d.ts.map +1 -1
- package/lib/types/client/reference.d.ts +6 -10
- package/lib/types/client/reference.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/client/DockItem.tsx +1 -1
- package/src/client/components/ExplorerPanel.tsx +1 -1
- package/src/client/components/ScmPanel.tsx +4 -4
- package/src/client/index.ts +3 -14
- package/src/client/layout.ts +2 -2
- package/src/client/picker/FilePickerModal.tsx +62 -43
- package/src/client/preview/PreviewPanel.tsx +1 -0
- package/src/client/preview/content.tsx +1 -1
- package/src/client/reference.ts +13 -8
- package/src/client/styles/picker.module.css +9 -9
- package/src/client/styles/preview.module.css +15 -10
- package/src/client/styles/tokens.module.css +52 -89
- package/src/client/styles/chip.module.css +0 -38
|
@@ -33,17 +33,13 @@ export interface ConversationService {
|
|
|
33
33
|
export interface FileReference {
|
|
34
34
|
/** Workspace-relative path (what the agent will read). */
|
|
35
35
|
path: string;
|
|
36
|
-
/** First line of the referenced range (1-based, inclusive). */
|
|
37
|
-
startLine
|
|
38
|
-
/** Last line of the referenced range (1-based, inclusive). */
|
|
39
|
-
endLine
|
|
36
|
+
/** First line of the referenced range (1-based, inclusive). Omit for whole-file references. */
|
|
37
|
+
startLine?: number;
|
|
38
|
+
/** Last line of the referenced range (1-based, inclusive). Omit for whole-file references. */
|
|
39
|
+
endLine?: number;
|
|
40
40
|
}
|
|
41
|
-
/** The `@path:lines` model form (what the codec serializes to on submit). */
|
|
42
|
-
export declare function formatFileRefToken(ref:
|
|
43
|
-
path: string;
|
|
44
|
-
startLine: number;
|
|
45
|
-
endLine: number;
|
|
46
|
-
}): string;
|
|
41
|
+
/** The `@path` / `@path:lines` model form (what the codec serializes to on submit). */
|
|
42
|
+
export declare function formatFileRefToken(ref: FileReference): string;
|
|
47
43
|
/** The DRAFT-REV+CAS span the conversation machine expects for chip inserts. */
|
|
48
44
|
export interface RefSpan {
|
|
49
45
|
start: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reference.d.ts","sourceRoot":"","sources":["../../../src/client/reference.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,wCAAwC,CAAA;AAEtF;;;;;;;;;;GAUG;AAEH;;;;GAIG;AACH,UAAU,UAAU;IAClB,KAAK,EAAE;QAAE,WAAW,IAAI;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAA;IAC7D,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CAC9B;AACD,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE;QAAE,GAAG,CAAC,IAAI,EAAE,aAAa,GAAG,UAAU,CAAA;KAAE,CAAA;CAChD;AAED,4DAA4D;AAC5D,MAAM,WAAW,aAAa;IAC5B,0DAA0D;IAC1D,IAAI,EAAE,MAAM,CAAA;IACZ,+
|
|
1
|
+
{"version":3,"file":"reference.d.ts","sourceRoot":"","sources":["../../../src/client/reference.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,wCAAwC,CAAA;AAEtF;;;;;;;;;;GAUG;AAEH;;;;GAIG;AACH,UAAU,UAAU;IAClB,KAAK,EAAE;QAAE,WAAW,IAAI;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAA;IAC7D,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CAC9B;AACD,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE;QAAE,GAAG,CAAC,IAAI,EAAE,aAAa,GAAG,UAAU,CAAA;KAAE,CAAA;CAChD;AAED,4DAA4D;AAC5D,MAAM,WAAW,aAAa;IAC5B,0DAA0D;IAC1D,IAAI,EAAE,MAAM,CAAA;IACZ,+FAA+F;IAC/F,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,8FAA8F;IAC9F,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AA2BD,uFAAuF;AACvF,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAI7D;AAYD,gFAAgF;AAChF,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,MAAM,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;IACX,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,4EAA4E;AAC5E,wBAAgB,aAAa,CAAC,GAAG,EAAE,aAAa,EAAE,SAAS,EAAE,SAAS,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAezG;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,aAAa,EAClB,SAAS,EAAE,SAAS,GAAG,SAAS,EAChC,GAAG,EAAE,aAAa,EAClB,IAAI,EAAE,OAAO,GACZ,OAAO,CAmBT;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CACrC,GAAG,EAAE,aAAa,EAClB,SAAS,EAAE,SAAS,GAAG,SAAS,EAChC,GAAG,EAAE,aAAa,GACjB,OAAO,CAcT"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lijian-ui/dsh-file-manager",
|
|
3
3
|
"description": "DSH web GUI right-panel system: a pixel-faithful re-implementation of FileManager's Explorer + Preview columns (file tree, filename search, git changes, multi-tab preview of 10+ formats) driven by real host-side fs/git services, hot-pluggable via a profile bundle without any dsh source changes",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.3",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"types": "lib/types/index.d.ts",
|
package/src/client/DockItem.tsx
CHANGED
|
@@ -14,7 +14,7 @@ function ensureStyle(): void {
|
|
|
14
14
|
if (document.getElementById(STYLE_ID) !== null) return
|
|
15
15
|
const tag = document.createElement('style')
|
|
16
16
|
tag.id = STYLE_ID
|
|
17
|
-
tag.textContent = '.dsh-dock-item{width:32px;height:32px;border:none;border-radius:9999px;background
|
|
17
|
+
tag.textContent = '.dsh-dock-item{width:32px;height:32px;border:none;border-radius:9999px;background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-base);cursor:pointer;position:relative;transition:transform .15s ease,background .18s ease;display:inline-flex;align-items:center;justify-content:center}.dsh-dock-item:hover{transform:scale(1.15);background:color-mix(in srgb,var(--dsw-alias-label-primary) 85%,var(--dsw-alias-bg-base))}.dsh-dock-item-active{background:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-bg-base)}.dsh-dock-tooltip{position:absolute;top:calc(100% + 6px);left:50%;transform:translateX(-50%);padding:2px 8px;font-size:12px;line-height:1.4;white-space:nowrap;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-3);border:1px solid var(--dsw-alias-border-l2);border-radius:6px;opacity:0;pointer-events:none;transition:opacity .15s}.dsh-dock-item:hover .dsh-dock-tooltip{opacity:1}'
|
|
18
18
|
document.head.appendChild(tag)
|
|
19
19
|
}
|
|
20
20
|
|
|
@@ -267,7 +267,7 @@ function SearchArea({
|
|
|
267
267
|
<div className={explorerCss.searchArea}>
|
|
268
268
|
<div
|
|
269
269
|
className={`${explorerCss.searchBox}${searchFocus ? ` ${explorerCss.searchAreaFocus}` : ''}`}
|
|
270
|
-
style={{ borderColor: searchFocus ? 'var(--
|
|
270
|
+
style={{ borderColor: searchFocus ? 'var(--dsw-alias-state-business-primary)' : undefined }}
|
|
271
271
|
>
|
|
272
272
|
<span className={explorerCss.searchIcon}><SearchIcon size={14} /></span>
|
|
273
273
|
<input
|
|
@@ -130,7 +130,7 @@ export function ScmPanel({ stores }: { stores: PanelStores }): JSX.Element {
|
|
|
130
130
|
</span>
|
|
131
131
|
<span className={scmCss.sectionTitle}>{t('scm.changes')}</span>
|
|
132
132
|
{status.branch !== '' && (
|
|
133
|
-
<span className={scmCss.branchName} style={{ fontSize: 11, color: 'var(--
|
|
133
|
+
<span className={scmCss.branchName} style={{ fontSize: 11, color: 'var(--dsw-alias-label-tertiary)', display: 'flex', alignItems: 'center', gap: 4 }}>
|
|
134
134
|
<BranchIcon size={12} />
|
|
135
135
|
{status.branch}
|
|
136
136
|
</span>
|
|
@@ -161,7 +161,7 @@ export function ScmPanel({ stores }: { stores: PanelStores }): JSX.Element {
|
|
|
161
161
|
type="button"
|
|
162
162
|
className={scmCss.sectionAction}
|
|
163
163
|
title={t('scm.viewList')}
|
|
164
|
-
style={{ color: state.viewMode === 'list' ? 'var(--
|
|
164
|
+
style={{ color: state.viewMode === 'list' ? 'var(--dsw-alias-brand-primary)' : undefined }}
|
|
165
165
|
onClick={() => scm.setViewMode('list')}
|
|
166
166
|
>
|
|
167
167
|
<ListIcon size={13} />
|
|
@@ -170,7 +170,7 @@ export function ScmPanel({ stores }: { stores: PanelStores }): JSX.Element {
|
|
|
170
170
|
type="button"
|
|
171
171
|
className={scmCss.sectionAction}
|
|
172
172
|
title={t('scm.viewTree')}
|
|
173
|
-
style={{ color: state.viewMode === 'tree' ? 'var(--
|
|
173
|
+
style={{ color: state.viewMode === 'tree' ? 'var(--dsw-alias-brand-primary)' : undefined }}
|
|
174
174
|
onClick={() => scm.setViewMode('tree')}
|
|
175
175
|
>
|
|
176
176
|
<TreeIcon size={13} />
|
|
@@ -359,7 +359,7 @@ function DirNode({
|
|
|
359
359
|
<ChevronRightIcon size={13} />
|
|
360
360
|
</span>
|
|
361
361
|
<FileTypeIcon name={label} isDir expanded={expanded} />
|
|
362
|
-
<span style={{ fontSize: 13, color: 'var(--
|
|
362
|
+
<span style={{ fontSize: 13, color: 'var(--dsw-alias-label-primary)' }}>{label}</span>
|
|
363
363
|
</div>
|
|
364
364
|
{expanded &&
|
|
365
365
|
rows.map((row) => (
|
package/src/client/index.ts
CHANGED
|
@@ -37,7 +37,6 @@ import { handleFileRefClick } from './chat/file-ref.ts'
|
|
|
37
37
|
import { appendToDraft, appendFileReferenceChip } from './reference.ts'
|
|
38
38
|
import { registerFileReferenceSource } from './file-source.ts'
|
|
39
39
|
import type { FileReferenceSelection } from './preview/selection.ts'
|
|
40
|
-
import './styles/chip.module.css'
|
|
41
40
|
import { FilePanelDockItem } from './FilePanelDockItem.tsx'
|
|
42
41
|
|
|
43
42
|
declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
@@ -223,9 +222,8 @@ export function apply(ctx: ClientContext): void {
|
|
|
223
222
|
let lastPreviewOpen = false
|
|
224
223
|
|
|
225
224
|
// The Explorer row '@' button: insert a file-reference chip for files
|
|
226
|
-
// (path
|
|
227
|
-
//
|
|
228
|
-
// range; failures fall back to plain text.
|
|
225
|
+
// (plain `@<rel-path>`, no line range) or plain `@<rel-path>/` text for
|
|
226
|
+
// folders.
|
|
229
227
|
const onReference = (path: string, isDir: boolean): void => {
|
|
230
228
|
const sessionId = ctx.sessions.list.getSnapshot().current as SessionId | undefined
|
|
231
229
|
if (sessionId === undefined) {
|
|
@@ -236,16 +234,7 @@ export function apply(ctx: ClientContext): void {
|
|
|
236
234
|
appendToDraft(ctx, sessionId, `@${path}/ `)
|
|
237
235
|
return
|
|
238
236
|
}
|
|
239
|
-
|
|
240
|
-
if (!result.ok) {
|
|
241
|
-
appendToDraft(ctx, sessionId, `@${path} `)
|
|
242
|
-
return
|
|
243
|
-
}
|
|
244
|
-
const text = result.value.content
|
|
245
|
-
const trimmed = text.endsWith('\n') ? text.slice(0, -1) : text
|
|
246
|
-
const lines = trimmed === '' ? 1 : trimmed.split('\n').length
|
|
247
|
-
appendFileReferenceChip(ctx, sessionId, { path, startLine: 1, endLine: lines })
|
|
248
|
-
})
|
|
237
|
+
appendFileReferenceChip(ctx, sessionId, { path })
|
|
249
238
|
}
|
|
250
239
|
|
|
251
240
|
// The preview "添加到对话" action: insert a file-reference chip
|
package/src/client/layout.ts
CHANGED
|
@@ -165,7 +165,7 @@ export class PanelLayoutController {
|
|
|
165
165
|
previewCol.style.overflow = 'hidden'
|
|
166
166
|
previewCol.style.display = 'flex'
|
|
167
167
|
previewCol.style.flexDirection = 'column'
|
|
168
|
-
previewCol.style.borderLeft = '1px solid var(--
|
|
168
|
+
previewCol.style.borderLeft = '1px solid var(--dsw-alias-bg-layer-3)'
|
|
169
169
|
|
|
170
170
|
const explorerCol = document.createElement('div')
|
|
171
171
|
explorerCol.dataset.filemgrExplorerCol = ''
|
|
@@ -174,7 +174,7 @@ export class PanelLayoutController {
|
|
|
174
174
|
explorerCol.style.overflow = 'hidden'
|
|
175
175
|
explorerCol.style.display = 'flex'
|
|
176
176
|
explorerCol.style.flexDirection = 'column'
|
|
177
|
-
explorerCol.style.borderLeft = '1px solid var(--
|
|
177
|
+
explorerCol.style.borderLeft = '1px solid var(--dsw-alias-bg-layer-3)'
|
|
178
178
|
|
|
179
179
|
frame.appendChild(previewCol)
|
|
180
180
|
frame.appendChild(explorerCol)
|
|
@@ -4,13 +4,15 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Trigger: typing `@` (with nothing after it) in the composer opens this
|
|
6
6
|
* modal directly — no inline trigger menu (the shell's trigger menu is a
|
|
7
|
-
* flat candidate list that cannot host a tree).
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* flat candidate list that cannot host a tree). On open the just-typed `@`
|
|
8
|
+
* is stripped from the composer draft, which permanently silences the
|
|
9
|
+
* shell's own draft-driven `@` menu while the picker is up. The modal owns
|
|
10
|
+
* a recursive tree with lazy expansion, a workspace filename search,
|
|
11
|
+
* per-row checkboxes for multi-selection, and a chip row of the picked
|
|
12
|
+
* paths.
|
|
10
13
|
*
|
|
11
|
-
* Insertion
|
|
12
|
-
*
|
|
13
|
-
* the stray `@`.
|
|
14
|
+
* Insertion lands the picked references at the recorded anchor position
|
|
15
|
+
* (`@a.ts @src/ @b.md`); cancel drops the stray `@`.
|
|
14
16
|
* @module dsh-filemgr/client/picker/FilePickerModal
|
|
15
17
|
*/
|
|
16
18
|
|
|
@@ -399,6 +401,28 @@ export function FilePickerHost({ ctx, api }: { ctx: ClientContext; api: PanelApi
|
|
|
399
401
|
initialDir: '',
|
|
400
402
|
span: { start: mention.start, end: mention.start + 1 },
|
|
401
403
|
})
|
|
404
|
+
// Strip the just-typed `@` from the composer draft. Ordering note: this
|
|
405
|
+
// listener runs on the textarea (target phase) BEFORE the shell's
|
|
406
|
+
// root-delegated handler commits the keystroke into the machine, so a
|
|
407
|
+
// synchronous strip would read a STALE snapshot (guard fails, `@`
|
|
408
|
+
// survives). Defer to a microtask — by then the machine has committed
|
|
409
|
+
// the draft with the `@`, the guard holds, and the shell's draft-driven
|
|
410
|
+
// `@` menu loses its anchor for good. insert() re-checks the anchor
|
|
411
|
+
// idempotently in case this race ever lands the other way.
|
|
412
|
+
queueMicrotask(() => {
|
|
413
|
+
try {
|
|
414
|
+
const actx = ctx.sessions.scope(sessionId)
|
|
415
|
+
const conversation = (ctx as unknown as { conversation?: ConversationService }).conversation
|
|
416
|
+
if (actx === undefined || conversation === undefined) return
|
|
417
|
+
const shellInput = conversation.input.for(actx)
|
|
418
|
+
const snap = shellInput.state.getSnapshot()
|
|
419
|
+
if (snap.draft.charAt(mention.start) === '@') {
|
|
420
|
+
shellInput.setDraft(snap.draft.slice(0, mention.start) + snap.draft.slice(mention.start + 1))
|
|
421
|
+
}
|
|
422
|
+
} catch {
|
|
423
|
+
// Best-effort: insert() re-checks the anchor idempotently.
|
|
424
|
+
}
|
|
425
|
+
})
|
|
402
426
|
}
|
|
403
427
|
const attach = (): void => {
|
|
404
428
|
for (const ta of document.querySelectorAll<HTMLTextAreaElement>('textarea')) {
|
|
@@ -421,58 +445,53 @@ export function FilePickerHost({ ctx, api }: { ctx: ClientContext; api: PanelApi
|
|
|
421
445
|
/**
|
|
422
446
|
* Insert the picked references into the composer draft as file-reference
|
|
423
447
|
* chips (one `slash/input-insert-reference` bail per file; the `file`
|
|
424
|
-
* source codec serializes each to `@path
|
|
425
|
-
* back to plain `@path/ ` text since
|
|
426
|
-
* Multiple selections land as
|
|
448
|
+
* source codec serializes each to `@path` on submit). Whole-file picks
|
|
449
|
+
* carry no line range. Folders fall back to plain `@path/ ` text since
|
|
450
|
+
* the chip has no directory concept. Multiple selections land as
|
|
451
|
+
* adjacent chips. Since open stripped the bare `@` from the draft, every
|
|
452
|
+
* chip inserts at the recorded anchor as a zero-length span.
|
|
427
453
|
*/
|
|
428
454
|
const insert = async (paths: PickedPath[]): Promise<void> => {
|
|
429
455
|
const current = filePicker.getSnapshot()
|
|
430
456
|
if (!current.open || current.req === null || paths.length === 0) return
|
|
431
|
-
const { sessionId,
|
|
457
|
+
const { sessionId, span } = current.req
|
|
432
458
|
try {
|
|
433
459
|
const actx = ctx.sessions.scope(sessionId)
|
|
434
460
|
if (actx === undefined) return
|
|
435
461
|
const conversation = (ctx as unknown as { conversation?: ConversationService }).conversation
|
|
436
462
|
if (conversation === undefined) return
|
|
437
463
|
const shell = conversation.input.for(actx)
|
|
438
|
-
//
|
|
439
|
-
//
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
const lines = trimmed === '' ? 1 : trimmed.split('\n').length
|
|
447
|
-
return { pick, lines }
|
|
448
|
-
}))
|
|
449
|
-
// Walk in original order. The first chip replaces the `@` token; the
|
|
450
|
-
// rest insert at the current cursor (0-length span) so the machine
|
|
451
|
-
// appends each one in turn.
|
|
452
|
-
let cursor = span.end
|
|
453
|
-
let firstSpan = true
|
|
454
|
-
for (const { pick, lines } of lineCounts) {
|
|
455
|
-
if (pick.isDir) {
|
|
456
|
-
appendToDraft(ctx, sessionId, `@${pick.path}/ `)
|
|
457
|
-
continue
|
|
464
|
+
// Idempotent anchor cleanup: the open-time microtask strip usually
|
|
465
|
+
// removed the bare `@` already; if that race ever landed the other way
|
|
466
|
+
// (or was skipped), remove it now so no stray `@` survives next to the
|
|
467
|
+
// inserted chips.
|
|
468
|
+
{
|
|
469
|
+
const snap0 = shell.state.getSnapshot()
|
|
470
|
+
if (snap0.draft.charAt(span.start) === '@') {
|
|
471
|
+
shell.setDraft(snap0.draft.slice(0, span.start) + snap0.draft.slice(span.start + 1))
|
|
458
472
|
}
|
|
459
|
-
|
|
473
|
+
}
|
|
474
|
+
// Walk in REVERSE pick order, every file chip at the SAME zero-length
|
|
475
|
+
// anchor: each machine insert splices the chip's DISPLAY TEXT (`@label`)
|
|
476
|
+
// plus a gap at span.start — the chip look itself is a minted scan
|
|
477
|
+
// decoration, never special characters — pushing earlier-inserted text
|
|
478
|
+
// to the right, so the draft reads in original pick order with no
|
|
479
|
+
// cursor bookkeeping. (A fixed advance of 2 would land the next chip
|
|
480
|
+
// mid-text of the previous one.) Folders keep the plain-text append at
|
|
481
|
+
// the draft tail.
|
|
482
|
+
for (let i = paths.length - 1; i >= 0; i--) {
|
|
483
|
+
const pick = paths[i]
|
|
484
|
+
if (pick.isDir) continue
|
|
460
485
|
const snap = shell.state.getSnapshot()
|
|
461
|
-
|
|
462
|
-
? { start: span.start, end: span.end, draftRev: snap.draftRev }
|
|
463
|
-
: { start: cursor, end: cursor, draftRev: snap.draftRev }
|
|
464
|
-
const ok = insertFileReference(
|
|
486
|
+
insertFileReference(
|
|
465
487
|
ctx,
|
|
466
488
|
sessionId,
|
|
467
|
-
{ path: pick.path
|
|
468
|
-
|
|
489
|
+
{ path: pick.path },
|
|
490
|
+
{ start: span.start, end: span.start, draftRev: snap.draftRev },
|
|
469
491
|
)
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
// trailing space; the cursor advances by 2 for the new characters.
|
|
474
|
-
cursor += 2
|
|
475
|
-
}
|
|
492
|
+
}
|
|
493
|
+
for (const pick of paths) {
|
|
494
|
+
if (pick.isDir) appendToDraft(ctx, sessionId, `@${pick.path}/ `)
|
|
476
495
|
}
|
|
477
496
|
filePicker.close()
|
|
478
497
|
} catch (error) {
|
|
@@ -246,7 +246,7 @@ function HtmlViewer({
|
|
|
246
246
|
}): JSX.Element {
|
|
247
247
|
const srcDoc = useMemo(() => {
|
|
248
248
|
// Base styles so the embedded page inherits the theme background.
|
|
249
|
-
return `<!doctype html><html><head><meta charset="utf-8"><style>body{margin:0;font-family:-apple-system,"system-ui","Segoe UI",Roboto,"PingFang SC",sans-serif;color
|
|
249
|
+
return `<!doctype html><html><head><meta charset="utf-8"><style>body{margin:0;font-family:-apple-system,"system-ui","Segoe UI",Roboto,"PingFang SC",sans-serif;color:var(--dsw-alias-label-primary)}@media (prefers-color-scheme:dark){body{color:var(--dsw-alias-label-secondary)}}</style></head><body>${content}</body></html>`
|
|
250
250
|
}, [content])
|
|
251
251
|
if (sourceMode && onContentChange !== undefined) {
|
|
252
252
|
return (
|
package/src/client/reference.ts
CHANGED
|
@@ -29,10 +29,10 @@ export interface ConversationService {
|
|
|
29
29
|
export interface FileReference {
|
|
30
30
|
/** Workspace-relative path (what the agent will read). */
|
|
31
31
|
path: string
|
|
32
|
-
/** First line of the referenced range (1-based, inclusive). */
|
|
33
|
-
startLine
|
|
34
|
-
/** Last line of the referenced range (1-based, inclusive). */
|
|
35
|
-
endLine
|
|
32
|
+
/** First line of the referenced range (1-based, inclusive). Omit for whole-file references. */
|
|
33
|
+
startLine?: number
|
|
34
|
+
/** Last line of the referenced range (1-based, inclusive). Omit for whole-file references. */
|
|
35
|
+
endLine?: number
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
/** Basename of a `/`-separated relative path ('' when empty). */
|
|
@@ -56,11 +56,13 @@ function formatRefLabel(ref: FileReference): string {
|
|
|
56
56
|
const basename = basenameOf(ref.path)
|
|
57
57
|
const dirname = dirnameOf(ref.path)
|
|
58
58
|
const name = dirname === '' ? basename : `${basename} - ${dirname}`
|
|
59
|
+
if (ref.startLine === undefined || ref.endLine === undefined) return name
|
|
59
60
|
return ref.endLine > ref.startLine ? `${name} ${ref.startLine}-${ref.endLine}` : `${name} ${ref.startLine}`
|
|
60
61
|
}
|
|
61
62
|
|
|
62
|
-
/** The `@path:lines` model form (what the codec serializes to on submit). */
|
|
63
|
-
export function formatFileRefToken(ref:
|
|
63
|
+
/** The `@path` / `@path:lines` model form (what the codec serializes to on submit). */
|
|
64
|
+
export function formatFileRefToken(ref: FileReference): string {
|
|
65
|
+
if (ref.startLine === undefined || ref.endLine === undefined) return `@${ref.path}`
|
|
64
66
|
const range = ref.endLine > ref.startLine ? `${ref.startLine}-${ref.endLine}` : `${ref.startLine}`
|
|
65
67
|
return `@${ref.path}:${range}`
|
|
66
68
|
}
|
|
@@ -71,6 +73,8 @@ interface ConversationReference {
|
|
|
71
73
|
ref: string
|
|
72
74
|
label: string
|
|
73
75
|
clipboardText: string
|
|
76
|
+
/** Official chip appearance: renders the reference icon over the `@` glyph. */
|
|
77
|
+
appearance: 'file'
|
|
74
78
|
}
|
|
75
79
|
|
|
76
80
|
/** The DRAFT-REV+CAS span the conversation machine expects for chip inserts. */
|
|
@@ -86,7 +90,7 @@ export function appendToDraft(ctx: ClientContext, sessionId: SessionId | undefin
|
|
|
86
90
|
if (sessionId === undefined) return false
|
|
87
91
|
const actx = ctx.sessions.scope(sessionId)
|
|
88
92
|
if (actx === undefined) return false
|
|
89
|
-
const conversation = (ctx as unknown as {
|
|
93
|
+
const conversation = (ctx as unknown as { get(name: string): unknown }).get('conversation') as ConversationService | undefined
|
|
90
94
|
if (conversation === undefined) return false
|
|
91
95
|
const input = conversation.input.for(actx as unknown as ClientContext)
|
|
92
96
|
const draft = input.state.getSnapshot().draft
|
|
@@ -122,6 +126,7 @@ export function insertFileReference(
|
|
|
122
126
|
ref: formatFileRefToken(ref),
|
|
123
127
|
label: formatRefLabel(ref),
|
|
124
128
|
clipboardText: formatFileRefToken(ref),
|
|
129
|
+
appearance: 'file',
|
|
125
130
|
}
|
|
126
131
|
return (actx as unknown as {
|
|
127
132
|
bail(target: unknown, event: string, payload: unknown): boolean
|
|
@@ -148,7 +153,7 @@ export function appendFileReferenceChip(
|
|
|
148
153
|
if (sessionId === undefined) return false
|
|
149
154
|
const actx = ctx.sessions.scope(sessionId)
|
|
150
155
|
if (actx === undefined) return false
|
|
151
|
-
const conversation = (ctx as unknown as {
|
|
156
|
+
const conversation = (ctx as unknown as { get(name: string): unknown }).get('conversation') as ConversationService | undefined
|
|
152
157
|
if (conversation === undefined) return false
|
|
153
158
|
const shell = conversation.input.for(actx as unknown as ClientContext)
|
|
154
159
|
const snap = shell.state.getSnapshot()
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
.overlay {
|
|
9
9
|
position: fixed;
|
|
10
10
|
inset: 0;
|
|
11
|
-
background:
|
|
11
|
+
background: var(--dsw-alias-bg-mask-1);
|
|
12
12
|
display: flex;
|
|
13
13
|
align-items: center;
|
|
14
14
|
justify-content: center;
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
background: var(--aion-bg-base);
|
|
25
25
|
border: 1px solid var(--aion-border-base);
|
|
26
26
|
border-radius: 10px;
|
|
27
|
-
box-shadow:
|
|
27
|
+
box-shadow: var(--dsw-shadow-lv3);
|
|
28
28
|
overflow: hidden;
|
|
29
29
|
font-family: var(--aion-font-sans);
|
|
30
30
|
color: var(--aion-text-primary);
|
|
@@ -158,11 +158,11 @@
|
|
|
158
158
|
}
|
|
159
159
|
|
|
160
160
|
.rowSelected {
|
|
161
|
-
background:
|
|
161
|
+
background: color-mix(in srgb, var(--dsw-alias-state-business-primary) 12%, transparent);
|
|
162
162
|
}
|
|
163
163
|
|
|
164
164
|
.rowSelected:hover {
|
|
165
|
-
background:
|
|
165
|
+
background: color-mix(in srgb, var(--dsw-alias-state-business-primary) 18%, transparent);
|
|
166
166
|
}
|
|
167
167
|
|
|
168
168
|
.chev {
|
|
@@ -220,7 +220,7 @@
|
|
|
220
220
|
height: 16px;
|
|
221
221
|
border: 1px solid var(--aion-border-base);
|
|
222
222
|
border-radius: 4px;
|
|
223
|
-
color:
|
|
223
|
+
color: var(--dsw-alias-label-primary-foreground);
|
|
224
224
|
flex-shrink: 0;
|
|
225
225
|
background: transparent;
|
|
226
226
|
}
|
|
@@ -294,7 +294,7 @@
|
|
|
294
294
|
}
|
|
295
295
|
|
|
296
296
|
.chipX:hover {
|
|
297
|
-
background:
|
|
297
|
+
background: var(--dsw-alias-interactive-bg-hover);
|
|
298
298
|
color: var(--aion-text-primary);
|
|
299
299
|
}
|
|
300
300
|
|
|
@@ -325,8 +325,8 @@
|
|
|
325
325
|
height: 32px;
|
|
326
326
|
padding: 0 16px;
|
|
327
327
|
border: none;
|
|
328
|
-
background: var(--
|
|
329
|
-
color:
|
|
328
|
+
background: var(--dsw-alias-button-primary-fill);
|
|
329
|
+
color: var(--dsw-alias-label-primary-foreground);
|
|
330
330
|
border-radius: 6px;
|
|
331
331
|
font-size: 13px;
|
|
332
332
|
font-weight: 500;
|
|
@@ -335,7 +335,7 @@
|
|
|
335
335
|
}
|
|
336
336
|
|
|
337
337
|
.insertBtn:hover:not(:disabled) {
|
|
338
|
-
|
|
338
|
+
background: var(--dsw-alias-button-primary-hover);
|
|
339
339
|
}
|
|
340
340
|
|
|
341
341
|
.insertBtn:disabled {
|
|
@@ -504,25 +504,30 @@
|
|
|
504
504
|
border-radius: 4px;
|
|
505
505
|
}
|
|
506
506
|
|
|
507
|
-
/* The "添加到对话" floating action over a preview selection.
|
|
507
|
+
/* The "添加到对话" floating action over a preview selection.
|
|
508
|
+
* Mirrors the official primitives Button primary variant (.sm capsule):
|
|
509
|
+
* geometry h28/r14/p10, fill = button-primary-fill, text = label-primary-foreground,
|
|
510
|
+
* hover = button-primary-hover, shadow = --dsw-shadow-lv1 (floating layer). */
|
|
508
511
|
.addToChat {
|
|
509
512
|
position: fixed;
|
|
510
513
|
z-index: 1000;
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
514
|
+
display: inline-flex;
|
|
515
|
+
align-items: center;
|
|
516
|
+
height: 28px;
|
|
517
|
+
padding: 0 10px;
|
|
518
|
+
background: var(--dsw-alias-button-primary-fill);
|
|
519
|
+
color: var(--dsw-alias-label-primary-foreground);
|
|
520
|
+
border-radius: 14px;
|
|
521
|
+
font-size: 12px;
|
|
522
|
+
line-height: 18px;
|
|
518
523
|
cursor: pointer;
|
|
519
|
-
box-shadow: var(--
|
|
524
|
+
box-shadow: var(--dsw-shadow-lv1);
|
|
520
525
|
user-select: none;
|
|
521
526
|
white-space: nowrap;
|
|
522
527
|
}
|
|
523
528
|
|
|
524
529
|
.addToChat:hover {
|
|
525
|
-
|
|
530
|
+
background: var(--dsw-alias-button-primary-hover);
|
|
526
531
|
}
|
|
527
532
|
|
|
528
533
|
/* Code viewer shell: line gutter + code body in one scroll container. */
|