dsh-vscode-mode 0.1.7 → 0.1.11
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/README.md +12 -0
- package/lib/client.js +725 -89
- package/lib/client.js.map +1 -1
- package/lib/index.js +1003 -94
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/capture.ts +44 -8
- package/src/client/index.ts +11 -2
- package/src/client/monaco/diffRender.ts +17 -2
- package/src/client/monaco/loader.ts +56 -9
- package/src/client/state/records.ts +5 -2
- package/src/client/state/regions.ts +26 -20
- package/src/client/styles/editor.css +16 -2
- package/src/client/styles/mcp.css +87 -0
- package/src/client/ui/DiffBox.ts +1 -0
- package/src/client/ui/EditorView.ts +70 -8
- package/src/client/ui/McpSettings.ts +233 -0
- package/src/index.ts +4 -2
- package/src/mcp.ts +123 -0
- package/src/mcpIsolation.ts +160 -0
- package/src/mcpProject.ts +258 -0
- package/src/model.ts +66 -34
- package/src/revert.ts +10 -3
- package/src/rpc.ts +44 -0
- package/src/shared/diff.ts +201 -0
- package/src/shared/mcp.ts +53 -0
- package/src/shared/rpc.ts +21 -0
- package/src/shared/types.ts +15 -0
- package/src/store.ts +29 -31
package/src/capture.ts
CHANGED
|
@@ -3,13 +3,41 @@
|
|
|
3
3
|
* 迁移自原 src/index.ts 的 tools/result 处理,语义不改。
|
|
4
4
|
* 作者 ddj 2026-08-20
|
|
5
5
|
*/
|
|
6
|
-
import type { DiffRecord } from './shared/types.js'
|
|
6
|
+
import type { DiffRecord, Hunk } from './shared/types.js'
|
|
7
|
+
import { annotateHunks, fingerprint } from './shared/diff.js'
|
|
7
8
|
import type { Ctx, Session } from './store.js'
|
|
8
9
|
import { archiveRecords, loadBucket, saveBucket } from './store.js'
|
|
9
10
|
import { fileMaxBatch, prune, recordResolved } from './model.js'
|
|
10
11
|
import type { Registry } from './registry.js'
|
|
11
12
|
import { cwdOf } from './registry.js'
|
|
12
13
|
|
|
14
|
+
/** 校验并提取工具 result meta 中的文件 hunk。 */
|
|
15
|
+
function validHunks(raw: unknown): Hunk[] {
|
|
16
|
+
if (!Array.isArray(raw)) return []
|
|
17
|
+
const hunks: Hunk[] = []
|
|
18
|
+
for (const item of raw) {
|
|
19
|
+
if (!item || typeof item !== 'object') continue
|
|
20
|
+
const hunk = item as Record<string, unknown>
|
|
21
|
+
if (typeof hunk.newText !== 'string') continue
|
|
22
|
+
if (hunk.oldText !== null && typeof hunk.oldText !== 'string') continue
|
|
23
|
+
hunks.push({ oldText: hunk.oldText as string | null, newText: hunk.newText })
|
|
24
|
+
}
|
|
25
|
+
return hunks
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** 已有文件 write 的元数据缺失时,用权威整文件快照保底。 */
|
|
29
|
+
function fallbackHunks(toolName: string, args: Record<string, unknown>, before: string | null, after: string | null, create: boolean): Hunk[] {
|
|
30
|
+
if (create) return [{ oldText: null, newText: after ?? (typeof args.content === 'string' ? args.content : '') }]
|
|
31
|
+
if (toolName === 'edit' && typeof args.old_string === 'string' && typeof args.new_string === 'string') {
|
|
32
|
+
return [{ oldText: args.old_string, newText: args.new_string }]
|
|
33
|
+
}
|
|
34
|
+
if (toolName === 'write' && before !== null && after !== null && before !== after) {
|
|
35
|
+
return [{ oldText: before, newText: after }]
|
|
36
|
+
}
|
|
37
|
+
if (toolName === 'write' && after !== null) return [{ oldText: null, newText: after }]
|
|
38
|
+
return []
|
|
39
|
+
}
|
|
40
|
+
|
|
13
41
|
/**
|
|
14
42
|
* 捕获一次工具执行结果(tools/result):edit/write 成功时构造记录、
|
|
15
43
|
* 递增文件批次、融合归档被后续修改取代的旧差异、落盘。
|
|
@@ -28,21 +56,29 @@ export async function captureToolResult(ctx: Ctx, registry: Registry, exec: any,
|
|
|
28
56
|
const cwd = cwdOf(session)
|
|
29
57
|
if (!cwd) return
|
|
30
58
|
const value = result.value
|
|
31
|
-
const args = exec.arguments || {}
|
|
59
|
+
const args = (exec.arguments || {}) as Record<string, unknown>
|
|
32
60
|
const path = typeof value.path === 'string' ? value.path : args.file_path
|
|
33
|
-
if (!path) return
|
|
34
|
-
const metaDiffs: DiffRecord['hunks'] = Array.isArray(result.meta?.diffs) ? result.meta.diffs : []
|
|
61
|
+
if (typeof path !== 'string' || !path) return
|
|
35
62
|
const before = typeof value.before === 'string' ? value.before : null
|
|
63
|
+
const after = typeof value.after === 'string' ? value.after : (typeof args.content === 'string' ? args.content : null)
|
|
36
64
|
const create = exec.name === 'write' && value.before === null
|
|
37
|
-
const callHunk = exec.name === 'edit' && typeof args.old_string === 'string'
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
65
|
+
const callHunk = exec.name === 'edit' && typeof args.old_string === 'string' && typeof args.new_string === 'string'
|
|
66
|
+
? { oldText: args.old_string, newText: args.new_string }
|
|
67
|
+
: null
|
|
68
|
+
const metaHunks = validHunks(result.meta?.diffs)
|
|
69
|
+
const rawHunks = metaHunks.length ? metaHunks : fallbackHunks(exec.name, args, before, after, create)
|
|
70
|
+
const hunks = annotateHunks(rawHunks, before, after)
|
|
71
|
+
if (!hunks.length || typeof exec.callId !== 'string') return
|
|
41
72
|
const record: DiffRecord = {
|
|
42
73
|
callId: exec.callId,
|
|
43
74
|
toolName: exec.name,
|
|
44
75
|
path,
|
|
45
76
|
before,
|
|
77
|
+
after,
|
|
78
|
+
baseFingerprint: fingerprint(before),
|
|
79
|
+
afterFingerprint: fingerprint(after),
|
|
80
|
+
legacy: false,
|
|
81
|
+
conflict: false,
|
|
46
82
|
create,
|
|
47
83
|
callHunk,
|
|
48
84
|
hunks,
|
package/src/client/index.ts
CHANGED
|
@@ -15,8 +15,9 @@ import React from 'react'
|
|
|
15
15
|
import './styles/editor.css'
|
|
16
16
|
import { EditorView } from './ui/EditorView.js'
|
|
17
17
|
import { DiffBadge } from './ui/DiffBadge.js'
|
|
18
|
+
import { McpSettings } from './ui/McpSettings.js'
|
|
18
19
|
|
|
19
|
-
export const inject = ['slots', 'timer']
|
|
20
|
+
export const inject = ['slots', 'timer', 'locale', 'connection', 'remote']
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
23
|
* 装配客户端:注册中央编辑区视图与 header 差异角标。
|
|
@@ -41,4 +42,12 @@ export function apply(ctx: any): void {
|
|
|
41
42
|
{ name: 'conversation.session.header.utilities', id: 'edrv-diff-badge', order: 90, label: '差异' },
|
|
42
43
|
(props: unknown) => React.createElement(DiffBadge, Object.assign({}, props)),
|
|
43
44
|
))
|
|
44
|
-
|
|
45
|
+
|
|
46
|
+
// 设置页中的 VSCodeMode 专属页签,MCP 管理作为该页签的内部子 Tab。
|
|
47
|
+
ctx.slots.inject('settings.section', () => ctx.slots.register({
|
|
48
|
+
name: 'settings.section',
|
|
49
|
+
id: 'vscode-mode',
|
|
50
|
+
order: 30,
|
|
51
|
+
label: 'VSCodeMode',
|
|
52
|
+
}, () => React.createElement(McpSettings)))
|
|
53
|
+
}
|
|
@@ -86,8 +86,15 @@ export function createDiffRenderer(log) {
|
|
|
86
86
|
const lineH = editor.getOption(monaco.editor.EditorOption.lineHeight) || 20
|
|
87
87
|
let li = null
|
|
88
88
|
try { li = editor.getLayoutInfo() } catch (e) { /* ignore */ }
|
|
89
|
-
const
|
|
89
|
+
const rawLeft = li ? li.decorationsLeft : 0
|
|
90
90
|
const width = li && li.decorationsWidth > 0 ? li.decorationsWidth : 16
|
|
91
|
+
// decorationsLeft 是 Monaco 内部坐标,需限制在宿主根节点内,避免窄窗口/边框
|
|
92
|
+
// 或布局尚未完成时把减号绘制到编辑器左、右边界之外。
|
|
93
|
+
const maxLeft = Math.max(0, root.clientWidth - width)
|
|
94
|
+
// 字符本身有少量字体侧向留白;给标记列留出 4px 内缩,避免减号半个
|
|
95
|
+
// 字符落到 Monaco 根节点左边框外,同时仍限制右侧不越界。
|
|
96
|
+
const markerInset = 12
|
|
97
|
+
const left = Math.max(0, Math.min(maxLeft, rawLeft + markerInset))
|
|
91
98
|
const lineCount = editor.getModel().getLineCount()
|
|
92
99
|
let itemIdx = 0
|
|
93
100
|
let zi = 0
|
|
@@ -100,7 +107,8 @@ export function createDiffRenderer(log) {
|
|
|
100
107
|
for (let i = 0; i < z.n; i++) {
|
|
101
108
|
const item = document.createElement('div')
|
|
102
109
|
item.className = 'edrv-minus-item'
|
|
103
|
-
|
|
110
|
+
// 使用 CSS 线段代替文本字符,避免不同字体的侧向留白把 “-” 绘制到边界外。
|
|
111
|
+
item.textContent = ''
|
|
104
112
|
item.style.width = width + 'px'
|
|
105
113
|
item.style.height = lineH + 'px'
|
|
106
114
|
item.dataset.zone = String(zi)
|
|
@@ -130,7 +138,14 @@ export function createDiffRenderer(log) {
|
|
|
130
138
|
const edRect = root.getBoundingClientRect()
|
|
131
139
|
const st = editor.getScrollTop() || 0
|
|
132
140
|
const items = overlay.querySelectorAll('.edrv-minus-item')
|
|
141
|
+
let correctedLeft = left
|
|
142
|
+
try {
|
|
143
|
+
const layout = editor.getLayoutInfo()
|
|
144
|
+
const markerWidth = layout.decorationsWidth > 0 ? layout.decorationsWidth : width
|
|
145
|
+
correctedLeft = Math.max(0, Math.min(Math.max(0, root.clientWidth - markerWidth), (layout.decorationsLeft || 0) + 12))
|
|
146
|
+
} catch (e) { /* 保留首次布局计算结果 */ }
|
|
133
147
|
for (const item of items) {
|
|
148
|
+
item.style.left = correctedLeft + 'px'
|
|
134
149
|
const zIndex = Number(item.dataset.zone)
|
|
135
150
|
const rIndex = Number(item.dataset.row)
|
|
136
151
|
const z = createdZones[zIndex]
|
|
@@ -7,6 +7,46 @@
|
|
|
7
7
|
|
|
8
8
|
export const MONACO_BASE = '/edrv/vendor/monaco/vs'
|
|
9
9
|
let monacoPromise = null
|
|
10
|
+
let monacoStage = { phase: 'idle', progress: 0, message: '准备加载 Monaco…' }
|
|
11
|
+
const stageListeners = new Set()
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Monaco 加载阶段:这是阶段进度而非网络字节进度,避免误导用户。
|
|
15
|
+
* @author ddj 2026年08月22号
|
|
16
|
+
*/
|
|
17
|
+
export const MONACO_STAGES = {
|
|
18
|
+
loader: { progress: 18, message: '加载 Monaco 引导模块…' },
|
|
19
|
+
core: { progress: 72, message: '加载编辑器核心模块…' },
|
|
20
|
+
ready: { progress: 100, message: 'Monaco 编辑器已就绪' },
|
|
21
|
+
error: { progress: 0, message: 'Monaco 编辑器加载失败' },
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 发布 Monaco 加载阶段,监听器异常不得影响编辑器加载。
|
|
26
|
+
* @author ddj 2026年08月22号
|
|
27
|
+
* @param phase 阶段名
|
|
28
|
+
* @param progress 阶段百分比
|
|
29
|
+
* @param message 用户可读状态
|
|
30
|
+
*/
|
|
31
|
+
function publishStage(phase, progress, message) {
|
|
32
|
+
monacoStage = { phase, progress, message }
|
|
33
|
+
for (const listener of stageListeners) {
|
|
34
|
+
try { listener(monacoStage) } catch (error) { /* UI 回调异常忽略 */ }
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 订阅 Monaco 加载阶段。
|
|
40
|
+
* @author ddj 2026年08月22号
|
|
41
|
+
* @param listener 阶段回调
|
|
42
|
+
* @returns 取消订阅函数
|
|
43
|
+
*/
|
|
44
|
+
function subscribeStage(listener) {
|
|
45
|
+
if (typeof listener !== 'function') return () => {}
|
|
46
|
+
stageListeners.add(listener)
|
|
47
|
+
listener(monacoStage)
|
|
48
|
+
return () => stageListeners.delete(listener)
|
|
49
|
+
}
|
|
10
50
|
|
|
11
51
|
/** 扩展名 → Monaco language id(常见语言;未知回退 plaintext)。 */
|
|
12
52
|
export const LANG_BY_EXT = {
|
|
@@ -40,19 +80,26 @@ export function langOf(path) {
|
|
|
40
80
|
* @author ddj 2026年08月20号
|
|
41
81
|
* @returns Promise<object> window.monaco
|
|
42
82
|
*/
|
|
43
|
-
export function loadMonaco() {
|
|
83
|
+
export function loadMonaco(onProgress) {
|
|
84
|
+
const unsubscribe = subscribeStage(onProgress)
|
|
44
85
|
if (!monacoPromise) {
|
|
86
|
+
publishStage('loader', MONACO_STAGES.loader.progress, MONACO_STAGES.loader.message)
|
|
45
87
|
monacoPromise = new Promise((resolve, reject) => {
|
|
88
|
+
const fail = (error) => {
|
|
89
|
+
monacoPromise = null
|
|
90
|
+
publishStage('error', MONACO_STAGES.error.progress, MONACO_STAGES.error.message)
|
|
91
|
+
reject(error)
|
|
92
|
+
}
|
|
46
93
|
const boot = () => {
|
|
47
94
|
try {
|
|
48
95
|
window.require.config({ paths: { vs: MONACO_BASE } })
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
96
|
+
publishStage('core', MONACO_STAGES.core.progress, MONACO_STAGES.core.message)
|
|
97
|
+
window.require(['vs/editor/editor.main'], () => {
|
|
98
|
+
publishStage('ready', MONACO_STAGES.ready.progress, MONACO_STAGES.ready.message)
|
|
99
|
+
resolve(window.monaco)
|
|
100
|
+
}, (err) => fail(new Error('Monaco 模块加载失败:' + String(err))))
|
|
53
101
|
} catch (error) {
|
|
54
|
-
|
|
55
|
-
reject(error)
|
|
102
|
+
fail(error)
|
|
56
103
|
}
|
|
57
104
|
}
|
|
58
105
|
const existing = document.querySelector('script[data-edrv-monaco-loader]')
|
|
@@ -62,10 +109,10 @@ export function loadMonaco() {
|
|
|
62
109
|
s.src = MONACO_BASE + '/loader.js'
|
|
63
110
|
s.dataset.edrvMonacoLoader = '1'
|
|
64
111
|
s.onload = boot
|
|
65
|
-
s.onerror = () =>
|
|
112
|
+
s.onerror = () => fail(new Error('Monaco loader 加载失败'))
|
|
66
113
|
document.head.appendChild(s)
|
|
67
114
|
}
|
|
68
115
|
})
|
|
69
116
|
}
|
|
70
|
-
return monacoPromise
|
|
117
|
+
return monacoPromise.finally(unsubscribe)
|
|
71
118
|
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* 作者 ddj 2026-08-20
|
|
5
5
|
*/
|
|
6
6
|
import type { CallHunk, Hunk, RecordView } from '../../shared/types.js'
|
|
7
|
+
import { preciseHunk } from '../../shared/diff.js'
|
|
7
8
|
|
|
8
9
|
/** 差异决策状态。 */
|
|
9
10
|
export const ST = { PENDING: 'pending', ACCEPTED: 'accepted', REJECTED: 'rejected' } as const
|
|
@@ -36,7 +37,9 @@ export function statusAt(rec: RecordView | undefined, idx: number): Status {
|
|
|
36
37
|
/** hunk 是否为空差异(old===new,无实际内容变化,不可操作)。create 记录不视为空差异。 */
|
|
37
38
|
export function noopHunk(rec: RecordView | undefined, h: Hunk | CallHunk | null | undefined): boolean {
|
|
38
39
|
if (!rec || rec.create === true) return false
|
|
39
|
-
const precise = rec
|
|
40
|
+
const precise = rec && h && rec.hunks.indexOf(h as Hunk) >= 0
|
|
41
|
+
? preciseHunk(rec, rec.hunks.indexOf(h as Hunk))
|
|
42
|
+
: h
|
|
40
43
|
const oldText = precise?.oldText ?? null
|
|
41
44
|
const newText = precise?.newText ?? null
|
|
42
45
|
return oldText !== null && oldText === newText
|
|
@@ -44,7 +47,7 @@ export function noopHunk(rec: RecordView | undefined, h: Hunk | CallHunk | null
|
|
|
44
47
|
|
|
45
48
|
/** 记录是否仍有待处理差异(superseded / 全部已决策 = false)。 */
|
|
46
49
|
export function isRecPending(rec: RecordView): boolean {
|
|
47
|
-
if (!rec || rec.superseded === true) return false
|
|
50
|
+
if (!rec || rec.superseded === true || rec.conflict === true) return false
|
|
48
51
|
const perHunk = Array.isArray(rec.decisions?.perHunk) ? rec.decisions.perHunk : []
|
|
49
52
|
if (perHunk.length) {
|
|
50
53
|
// 只认存在至少一个"非空差异且未决策"的 hunk
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* 作者 ddj 2026-08-20
|
|
5
5
|
*/
|
|
6
6
|
import type { Hunk, RecordView } from '../../shared/types.js'
|
|
7
|
+
import { locateHunks, lineBefore, preciseHunk, splitLines } from '../../shared/diff.js'
|
|
7
8
|
import { ST, noopHunk, statusAt } from './records.js'
|
|
8
9
|
import type { Status } from './records.js'
|
|
9
10
|
|
|
@@ -25,9 +26,7 @@ export interface Region {
|
|
|
25
26
|
|
|
26
27
|
/** 统计 index 之前(不含)的换行数 → 0 基行号。 */
|
|
27
28
|
export function countLinesBefore(text: string, index: number): number {
|
|
28
|
-
|
|
29
|
-
for (let i = 0; i < index; i++) if (text.charCodeAt(i) === 10) n++
|
|
30
|
-
return n
|
|
29
|
+
return lineBefore(text, index)
|
|
31
30
|
}
|
|
32
31
|
|
|
33
32
|
/**
|
|
@@ -51,29 +50,36 @@ export function trimCommonLines(oldLines: string[], newLines: string[]): { oldLi
|
|
|
51
50
|
export function diffRegions(records: RecordView[], content: string | null): Region[] {
|
|
52
51
|
const regions: Region[] = []
|
|
53
52
|
if (content === null) return regions
|
|
54
|
-
const lines = content
|
|
53
|
+
const lines = splitLines(content)
|
|
55
54
|
for (const rec of records) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
regions.push({ callId: rec.callId, idx: i, start: 1, end: lines.length, oldLines: [], newLines: lines.slice(), whole: true, status, create: true, rec, superseded: rec.superseded === true })
|
|
62
|
-
continue
|
|
55
|
+
if (rec.create) {
|
|
56
|
+
for (let i = 0; i < rec.hunks.length; i++) {
|
|
57
|
+
const h = preciseHunk(rec, i)
|
|
58
|
+
if (!h || noopHunk(rec, h)) continue
|
|
59
|
+
regions.push({ callId: rec.callId, idx: i, start: 1, end: Math.max(1, lines.length), oldLines: [], newLines: lines.slice(), whole: true, status: statusAt(rec, i), create: true, rec, superseded: rec.superseded === true })
|
|
63
60
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
61
|
+
continue
|
|
62
|
+
}
|
|
63
|
+
const entries: Array<{ idx: number; hunk: Hunk }> = []
|
|
64
|
+
for (let i = 0; i < rec.hunks.length; i++) {
|
|
65
|
+
const hunk = preciseHunk(rec, i)
|
|
66
|
+
if (hunk && !noopHunk(rec, hunk)) entries.push({ idx: i, hunk })
|
|
67
|
+
}
|
|
68
|
+
const locations = locateHunks(content, entries.map((entry) => entry.hunk))
|
|
69
|
+
for (let i = 0; i < entries.length; i++) {
|
|
70
|
+
const entry = entries[i]
|
|
71
|
+
const location = locations[i]
|
|
72
|
+
const status = statusAt(rec, entry.idx)
|
|
73
|
+
if (!location.matched) {
|
|
74
|
+
regions.push({ callId: rec.callId, idx: entry.idx, stale: true, status, create: false, oldLines: entry.hunk.oldText === null ? [] : entry.hunk.oldText.split('\n'), newLines: entry.hunk.newText ? entry.hunk.newText.split('\n') : [], rec, superseded: rec.superseded === true })
|
|
69
75
|
continue
|
|
70
76
|
}
|
|
71
|
-
const start = countLinesBefore(content,
|
|
72
|
-
const oldLines =
|
|
73
|
-
const newLines = newText.split('\n')
|
|
77
|
+
const start = countLinesBefore(content, location.start) + 1
|
|
78
|
+
const oldLines = entry.hunk.oldText === null ? [] : entry.hunk.oldText.split('\n')
|
|
79
|
+
const newLines = entry.hunk.newText.split('\n')
|
|
74
80
|
const trimmed = trimCommonLines(oldLines, newLines)
|
|
75
81
|
const regionStart = start + trimmed.shift
|
|
76
|
-
regions.push({ callId: rec.callId, idx:
|
|
82
|
+
regions.push({ callId: rec.callId, idx: entry.idx, start: regionStart, end: regionStart + trimmed.newLines.length, oldLines: trimmed.oldLines, newLines: trimmed.newLines, status, create: false, rec, superseded: rec.superseded === true })
|
|
77
83
|
}
|
|
78
84
|
}
|
|
79
85
|
regions.sort((a, b) => (a.start ?? Infinity) - (b.start ?? Infinity))
|
|
@@ -40,8 +40,9 @@
|
|
|
40
40
|
[data-edrv-view] .edrv-del-zone { background: #fdeaea; border-top: 1px solid rgba(217,83,79,.35); border-bottom: 1px solid rgba(217,83,79,.35); box-shadow: inset 0 0 0 1px rgba(217,83,79,.12); }
|
|
41
41
|
[data-edrv-view] .edrv-del-row { white-space: pre; font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 12.5px; line-height: 20px; color: #c0392b; }
|
|
42
42
|
[data-edrv-view] .edrv-del-text { display: inline-block; white-space: pre; }
|
|
43
|
-
[data-edrv-view] .edrv-minus-overlay { position: absolute; left: 0; top: 0; pointer-events: none; z-index: 6; }
|
|
44
|
-
[data-edrv-view] .edrv-minus-item { position: absolute;
|
|
43
|
+
[data-edrv-view] .edrv-minus-overlay { position: absolute; left: 0; top: 0; width: 100%; height: 100%; max-width: 100%; overflow: visible; pointer-events: none; z-index: 6; }
|
|
44
|
+
[data-edrv-view] .edrv-minus-item { position: absolute; box-sizing: border-box; max-width: 100%; overflow: hidden; }
|
|
45
|
+
[data-edrv-view] .edrv-minus-item::before { content: ''; position: absolute; left: 4px; top: 50%; width: 8px; height: 2px; transform: translateY(-50%); border-radius: 2px; background: #d9534f; }
|
|
45
46
|
[data-edrv-view] .edrv-monaco-host { flex: 1; min-height: 0; position: relative; }
|
|
46
47
|
[data-edrv-view] .edrv-monaco-host > div { position: absolute; inset: 0; }
|
|
47
48
|
[data-edrv-view] .edrv-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px; height: 100%; color: var(--dsw-alias-label-tertiary, #777); font-size: 13px; }
|
|
@@ -83,5 +84,18 @@
|
|
|
83
84
|
[data-edrv-view] .edrv-launch-path { flex: 1; min-width: 0; font-size: 12px; color: var(--dsw-alias-label-primary, #eee); word-break: break-all; }
|
|
84
85
|
[data-edrv-view] .edrv-launch-cnt { font-size: 11px; color: var(--dsw-alias-label-tertiary, #888); white-space: nowrap; }
|
|
85
86
|
[data-edrv-view] .edrv-launch-arch { display: flex; flex-direction: column; gap: 6px; }
|
|
87
|
+
[data-edrv-view] .monaco-editor { overflow: hidden !important; }
|
|
88
|
+
[data-edrv-view] .monaco-editor .sticky-widget { z-index: 30 !important; background: var(--dsw-alias-bg-base, #f8f8f8) !important; }
|
|
89
|
+
[data-edrv-view] .monaco-editor .sticky-widget .sticky-line-content,
|
|
90
|
+
[data-edrv-view] .monaco-editor .sticky-widget .sticky-line-number { background: var(--dsw-alias-bg-base, #f8f8f8) !important; }
|
|
86
91
|
[data-edrv-view] .monaco-editor, [data-edrv-view] .monaco-editor .margin, [data-edrv-view] .monaco-editor-background { background: transparent !important; }
|
|
87
92
|
[data-edrv-view] .monaco-editor .scrollbar .slider { background: rgba(128,128,128,.3) !important; }
|
|
93
|
+
[data-edrv-view] .edrv-loading { gap: 8px; padding: 20px; }
|
|
94
|
+
[data-edrv-view] .edrv-loading-title { color: var(--dsw-alias-label-primary, #1f2933); font-size: 13px; }
|
|
95
|
+
[data-edrv-view] .edrv-progress { width: min(360px, 72%); height: 6px; overflow: hidden; border-radius: 999px; background: var(--dsw-alias-border-l1, #e0e6e8); }
|
|
96
|
+
[data-edrv-view] .edrv-progress-value { height: 100%; border-radius: inherit; background: var(--dsw-alias-brand-primary, #0f9d58); transition: width .24s ease; }
|
|
97
|
+
[data-edrv-view] .edrv-loading-percent { color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 11px; font-variant-numeric: tabular-nums; }
|
|
98
|
+
@media (prefers-reduced-motion: reduce) {
|
|
99
|
+
[data-edrv-view] .edrv-progress-value { transition: none; }
|
|
100
|
+
}
|
|
101
|
+
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/* dsh-vscode-mode MCP 管理界面样式。
|
|
2
|
+
全部使用 DSH 真实主题变量(--dsw-alias-*),不再用不存在的 fg-* 变量。 */
|
|
3
|
+
.vsm-mcp-page{display:flex;flex-direction:column;gap:18px;width:100%;max-width:920px;box-sizing:border-box;color:var(--dsw-alias-label-primary,#e6e6e6);padding-bottom:32px}
|
|
4
|
+
|
|
5
|
+
/* 顶部:标题 + 副标题 + 添加按钮 */
|
|
6
|
+
.vsm-mcp-header{display:flex;align-items:flex-start;justify-content:space-between;gap:20px}
|
|
7
|
+
.vsm-mcp-header h2{margin:0 0 4px;font-size:20px;font-weight:600;line-height:1.35;color:var(--dsw-alias-label-primary,#e6e6e6)}
|
|
8
|
+
.vsm-mcp-header p{margin:0;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:13px;line-height:1.5}
|
|
9
|
+
.vsm-primary{border:0;border-radius:6px;padding:8px 16px;background:var(--dsw-alias-button-primary-fill,#6d55ff);color:var(--dsw-alias-button-contrast-fill,#fff);font-size:13px;font-weight:500;cursor:pointer;white-space:nowrap}
|
|
10
|
+
.vsm-primary:hover{background:var(--dsw-alias-button-primary-hover,#5a46e6)}
|
|
11
|
+
.vsm-primary:disabled{opacity:.55;cursor:default}
|
|
12
|
+
|
|
13
|
+
/* 子 Tab:我的 MCP / MCP 市场 */
|
|
14
|
+
.vsm-mcp-tabs{display:flex;gap:24px;border-bottom:1px solid var(--dsw-alias-border-l2,#3a3a3a)}
|
|
15
|
+
.vsm-mcp-tabs button{border:0;background:none;color:var(--dsw-alias-label-tertiary,#9aa5b1);padding:9px 2px 11px;cursor:pointer;font-size:14px;position:relative}
|
|
16
|
+
.vsm-mcp-tabs button:hover{color:var(--dsw-alias-label-secondary,#c8c8c8)}
|
|
17
|
+
.vsm-mcp-tabs button.active{color:var(--dsw-alias-label-primary,#fff);font-weight:500}
|
|
18
|
+
.vsm-mcp-tabs button.active::after{content:"";position:absolute;left:0;right:0;bottom:-1px;height:2px;background:var(--dsw-alias-brand-primary,#6d55ff);border-radius:2px}
|
|
19
|
+
|
|
20
|
+
/* 服务卡片 */
|
|
21
|
+
.vsm-mcp-card{border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:10px;padding:14px 16px;margin-bottom:10px;background:var(--dsw-alias-bg-layer-2,transparent);transition:border-color .15s}
|
|
22
|
+
.vsm-mcp-card:hover{border-color:var(--dsw-alias-border-l3,#555)}
|
|
23
|
+
.vsm-mcp-card-head{display:flex;justify-content:space-between;align-items:center;gap:12px}
|
|
24
|
+
.vsm-mcp-title{display:flex;align-items:center;gap:9px;font-weight:600;font-size:14px;color:var(--dsw-alias-label-primary,#e6e6e6);min-width:0}
|
|
25
|
+
.vsm-mcp-source{font-size:11px;font-weight:400;padding:2px 7px;border-radius:4px;background:var(--dsw-alias-bg-mask-2,#ffffff22);color:var(--dsw-alias-label-tertiary,#9aa5b1);flex-shrink:0}
|
|
26
|
+
.vsm-mcp-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0;background:var(--dsw-alias-state-error-primary,#e05252)}
|
|
27
|
+
.vsm-mcp-dot.connected{background:var(--dsw-alias-state-success-primary,#38bd75)}
|
|
28
|
+
.vsm-mcp-dot.connecting{background:var(--dsw-alias-state-warn-primary,#e5b93f)}
|
|
29
|
+
.vsm-mcp-dot.disabled{background:var(--dsw-alias-label-tertiary,#9aa5b1)}
|
|
30
|
+
.vsm-mcp-actions{display:flex;align-items:center;gap:4px;flex-shrink:0}
|
|
31
|
+
.vsm-mcp-actions button{border:0;background:none;color:var(--dsw-alias-label-tertiary,#9aa5b1);cursor:pointer;font-size:15px;width:28px;height:28px;border-radius:6px;line-height:1}
|
|
32
|
+
.vsm-mcp-actions button:hover{color:var(--dsw-alias-label-primary,#fff);background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}
|
|
33
|
+
.vsm-mcp-actions button.vsm-danger:hover{color:var(--dsw-alias-state-error-primary,#f07878);background:var(--dsw-alias-interactive-bg-hover-danger,#ff000014)}
|
|
34
|
+
.vsm-switch{font-size:14px !important}
|
|
35
|
+
.vsm-switch.on{color:var(--dsw-alias-state-success-primary,#38bd75)}
|
|
36
|
+
.vsm-mcp-meta{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1);margin:9px 0}
|
|
37
|
+
.vsm-mcp-error{color:var(--dsw-alias-state-error-primary,#f07878);font-size:12px;margin:8px 0}
|
|
38
|
+
.vsm-mcp-banner{padding:9px 12px;border:1px solid var(--dsw-alias-state-error-secondary,#d9534f55);border-radius:6px;background:var(--dsw-alias-state-error-secondary,#d9534f1a);margin-bottom:10px}
|
|
39
|
+
|
|
40
|
+
/* 工具 chips */
|
|
41
|
+
.vsm-mcp-tools{display:flex;flex-wrap:wrap;gap:6px}
|
|
42
|
+
.vsm-mcp-chip{font:12px var(--ds-font-family-code,ui-monospace,monospace);padding:4px 8px;border-radius:5px;background:var(--dsw-alias-bg-mask-2,#ffffff14);color:var(--dsw-alias-label-secondary,#c8c8c8);border:1px solid var(--dsw-alias-border-l1,#333)}
|
|
43
|
+
|
|
44
|
+
/* 空态 / 加载态 */
|
|
45
|
+
.vsm-mcp-empty{text-align:center;padding:56px 20px;color:var(--dsw-alias-label-tertiary,#9aa5b1);border:1px dashed var(--dsw-alias-border-l2,#3a3a3a);border-radius:8px;font-size:13px}
|
|
46
|
+
|
|
47
|
+
/* 项目搜索下拉框 */
|
|
48
|
+
.vsm-project-picker{position:relative;width:100%;z-index:4}
|
|
49
|
+
.vsm-project-picker-trigger{width:100%;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 12px;border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:8px;background:var(--dsw-alias-bg-layer-1,transparent);color:var(--dsw-alias-label-primary,#e6e6e6);cursor:pointer;text-align:left}
|
|
50
|
+
.vsm-project-picker-trigger:hover{border-color:var(--dsw-alias-brand-primary,#6d55ff)}
|
|
51
|
+
.vsm-project-picker-label{display:flex;flex-direction:column;min-width:0;font-size:13px;font-weight:500}
|
|
52
|
+
.vsm-project-picker-label small{margin-top:3px;color:var(--dsw-alias-label-tertiary,#9aa5b1);font:11px var(--ds-font-family-code,ui-monospace,monospace);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
53
|
+
.vsm-project-picker-arrow{color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:16px}
|
|
54
|
+
.vsm-project-picker-menu{position:absolute;top:calc(100% + 6px);left:0;right:0;padding:8px;border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:9px;background:var(--dsw-alias-bg-overlay,#202020);box-shadow:0 12px 36px #0008}
|
|
55
|
+
.vsm-project-picker-search{width:100%;box-sizing:border-box;padding:8px 10px;border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;background:var(--dsw-alias-bg-layer-1,#2b2b2b);color:var(--dsw-alias-label-primary,#eee);outline:none}
|
|
56
|
+
.vsm-project-picker-search:focus{border-color:var(--dsw-alias-brand-primary,#6d55ff)}
|
|
57
|
+
.vsm-project-picker-options{max-height:260px;overflow:auto;margin-top:6px}
|
|
58
|
+
.vsm-project-option{display:flex;width:100%;flex-direction:column;gap:3px;padding:8px 9px;border:0;border-radius:6px;background:none;color:var(--dsw-alias-label-primary,#eee);cursor:pointer;text-align:left}
|
|
59
|
+
.vsm-project-option:hover,.vsm-project-option.highlight{background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}
|
|
60
|
+
.vsm-project-option.selected{color:var(--dsw-alias-brand-primary,#6d55ff)}
|
|
61
|
+
.vsm-project-option small{overflow:hidden;color:var(--dsw-alias-label-tertiary,#9aa5b1);font:11px var(--ds-font-family-code,ui-monospace,monospace);text-overflow:ellipsis;white-space:nowrap}
|
|
62
|
+
.vsm-project-picker-empty{padding:18px 8px;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:12px;text-align:center}
|
|
63
|
+
|
|
64
|
+
/* 项目级 MCP 分组 */
|
|
65
|
+
.vsm-project{border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:12px;margin-bottom:14px;overflow:hidden;background:var(--dsw-alias-bg-layer-1,transparent)}
|
|
66
|
+
.vsm-project-head{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 16px;border-bottom:1px solid var(--dsw-alias-border-l1,#333);background:var(--dsw-alias-bg-mask-1,#ffffff0a)}
|
|
67
|
+
.vsm-project-title{display:flex;align-items:center;gap:10px;min-width:0}
|
|
68
|
+
.vsm-project-icon{color:var(--dsw-alias-brand-primary,#6d55ff);font-size:15px;flex-shrink:0}
|
|
69
|
+
.vsm-project-name{font-size:14px;font-weight:600;color:var(--dsw-alias-label-primary,#e6e6e6);display:block}
|
|
70
|
+
.vsm-project-path{font-size:11px;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-family:var(--ds-font-family-code,ui-monospace,monospace);display:block;margin-top:2px;word-break:break-all}
|
|
71
|
+
.vsm-primary.vsm-small{padding:5px 12px;font-size:12px}
|
|
72
|
+
.vsm-project-empty{text-align:center;padding:26px 16px;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:12px}
|
|
73
|
+
.vsm-project .vsm-mcp-card{border:0;border-radius:0;margin:0;border-bottom:1px solid var(--dsw-alias-border-l1,#333);background:transparent}
|
|
74
|
+
.vsm-project .vsm-mcp-card:last-child{border-bottom:0}
|
|
75
|
+
.vsm-project .vsm-mcp-banner{border-radius:0;margin:0}
|
|
76
|
+
|
|
77
|
+
/* 新增 MCP 弹窗 */
|
|
78
|
+
.vsm-mcp-modal{position:fixed;inset:0;background:var(--dsw-alias-bg-mask-drop,#000000aa);display:flex;align-items:center;justify-content:center;z-index:50}
|
|
79
|
+
.vsm-mcp-dialog{width:min(560px,calc(100vw - 40px));max-height:88vh;overflow:auto;padding:22px 24px;border-radius:12px;background:var(--dsw-alias-bg-overlay,#202020);border:1px solid var(--dsw-alias-border-l2,#444);box-shadow:0 16px 60px #000c;color:var(--dsw-alias-label-primary,#e6e6e6)}
|
|
80
|
+
.vsm-mcp-dialog h3{margin:0 0 14px;font-size:16px;font-weight:600}
|
|
81
|
+
.vsm-mcp-dialog label{display:flex;flex-direction:column;gap:6px;margin:12px 0;font-size:12px;color:var(--dsw-alias-label-secondary,#aaa)}
|
|
82
|
+
.vsm-mcp-dialog input,.vsm-mcp-dialog select,.vsm-mcp-dialog textarea{font:inherit;color:var(--dsw-alias-label-primary,#eee);background:var(--dsw-alias-bg-layer-1,#2b2b2b);border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;padding:8px 10px}
|
|
83
|
+
.vsm-mcp-dialog input:focus,.vsm-mcp-dialog select:focus,.vsm-mcp-dialog textarea:focus{outline:none;border-color:var(--dsw-alias-brand-primary,#6d55ff)}
|
|
84
|
+
.vsm-mcp-dialog textarea{min-height:64px;resize:vertical;font-family:var(--ds-font-family-code,ui-monospace,monospace)}
|
|
85
|
+
.vsm-mcp-dialog-actions{display:flex;justify-content:flex-end;gap:10px;margin-top:20px}
|
|
86
|
+
.vsm-mcp-dialog-actions button{padding:8px 14px;border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;background:none;color:var(--dsw-alias-label-primary,#eee);cursor:pointer;font-size:13px}
|
|
87
|
+
.vsm-mcp-dialog-actions button:hover{background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}
|
package/src/client/ui/DiffBox.ts
CHANGED
|
@@ -37,6 +37,7 @@ export function DiffBox(props) {
|
|
|
37
37
|
})
|
|
38
38
|
const stale = staleRegions.map((r) => React.createElement('div', { key: 'stale' + callIdAttr(r.callId, r.idx), className: 'edrv-diffrow edrv-diffrow-stale', title: '差异无法定位(文件可能已被手动修改)' },
|
|
39
39
|
React.createElement('span', { className: 'edrv-diffrow-l' }, 'L' + (r.start ?? '?') + ' 无法定位'),
|
|
40
|
+
React.createElement('span', { className: 'edrv-diffrow-tag' }, '冲突/需刷新'),
|
|
40
41
|
React.createElement('span', { className: 'edrv-diffrow-tag' }, badgeOf(r.status))))
|
|
41
42
|
let othersEl = null
|
|
42
43
|
if (otherFiles.length) {
|
|
@@ -35,6 +35,8 @@ export function EditorView(props) {
|
|
|
35
35
|
const [content, setContent] = React.useState(null)
|
|
36
36
|
const [status, setStatus] = React.useState('')
|
|
37
37
|
const [error, setError] = React.useState(null)
|
|
38
|
+
const [loadError, setLoadError] = React.useState(null)
|
|
39
|
+
const [loadStage, setLoadStage] = React.useState({ progress: 0, message: '准备加载编辑器…' })
|
|
38
40
|
const [openInput, setOpenInput] = React.useState(false)
|
|
39
41
|
const [pathDraft, setPathDraft] = React.useState('')
|
|
40
42
|
const [cursor, setCursor] = React.useState('')
|
|
@@ -118,11 +120,27 @@ export function EditorView(props) {
|
|
|
118
120
|
|
|
119
121
|
const loadContent = (path, sid) => {
|
|
120
122
|
const seq = ++loadSeqRef.current
|
|
123
|
+
setLoadError(null)
|
|
124
|
+
setLoadStage({ progress: monaco ? 72 : 12, message: '读取文件内容…' })
|
|
121
125
|
rpc('edrv.read', { sessionId: sid, path }).then((res) => {
|
|
122
126
|
if (seq !== loadSeqRef.current || path !== active) return
|
|
123
|
-
if (res && res.ok) {
|
|
124
|
-
|
|
125
|
-
|
|
127
|
+
if (res && res.ok) {
|
|
128
|
+
setContent(res.content)
|
|
129
|
+
setLoadStage((prev) => ({ progress: Math.max(84, prev.progress), message: '文件已读取,准备创建编辑器…' }))
|
|
130
|
+
setStatus('已加载')
|
|
131
|
+
} else {
|
|
132
|
+
const message = res?.error ? String(res.error) : '读取失败'
|
|
133
|
+
setLoadError(message)
|
|
134
|
+
setError(message)
|
|
135
|
+
setStatus('读取失败')
|
|
136
|
+
}
|
|
137
|
+
}).catch((e) => {
|
|
138
|
+
if (seq !== loadSeqRef.current) return
|
|
139
|
+
const message = 'read异常:' + String(e)
|
|
140
|
+
setLoadError(message)
|
|
141
|
+
setError(message)
|
|
142
|
+
setStatus('读取失败')
|
|
143
|
+
})
|
|
126
144
|
}
|
|
127
145
|
|
|
128
146
|
// 挂载轮询 + 事件订阅
|
|
@@ -173,14 +191,27 @@ export function EditorView(props) {
|
|
|
173
191
|
|
|
174
192
|
React.useEffect(() => {
|
|
175
193
|
if (!active) return
|
|
176
|
-
setContent(null)
|
|
194
|
+
setContent(null)
|
|
195
|
+
setLoadError(null)
|
|
196
|
+
setLoadStage({ progress: 10, message: '准备读取文件…' })
|
|
197
|
+
setStatus('加载中…')
|
|
177
198
|
loadContent(active, sessionId)
|
|
178
199
|
}, [active, sessionId])
|
|
179
200
|
|
|
180
201
|
React.useEffect(() => {
|
|
181
202
|
if (monaco || monacoErr) return
|
|
182
203
|
let alive = true
|
|
183
|
-
|
|
204
|
+
const onProgress = (stage) => {
|
|
205
|
+
if (!alive) return
|
|
206
|
+
const progress = stage.phase === 'ready' ? 70 : Math.round(10 + stage.progress * 0.6)
|
|
207
|
+
setLoadStage((prev) => ({ progress: Math.max(prev.progress, progress), message: stage.message }))
|
|
208
|
+
}
|
|
209
|
+
loadMonaco(onProgress).then((m) => { if (alive) setMonaco(m) }).catch((e) => {
|
|
210
|
+
if (alive) {
|
|
211
|
+
setMonacoErr(String(e?.message ?? e))
|
|
212
|
+
setStatus('Monaco 不可用')
|
|
213
|
+
}
|
|
214
|
+
})
|
|
184
215
|
return () => { alive = false }
|
|
185
216
|
}, [monaco, monacoErr])
|
|
186
217
|
|
|
@@ -235,12 +266,14 @@ export function EditorView(props) {
|
|
|
235
266
|
const ed = editorRef.current
|
|
236
267
|
const model = getModel(active, content)
|
|
237
268
|
if (ed.getModel() !== model) ed.setModel(model)
|
|
269
|
+
setLoadStage((prev) => ({ progress: Math.max(96, prev.progress), message: '创建编辑器视图…' }))
|
|
238
270
|
}, [monaco, active, content])
|
|
239
271
|
|
|
240
272
|
// 行内差异自绘(decorations / view zones / minus overlay)→ diffRenderer
|
|
241
273
|
React.useEffect(() => {
|
|
242
274
|
if (!monaco || !editorRef.current || !active || content === null) return
|
|
243
275
|
diffRendererRef.current.render(monaco, editorRef.current, pendingRegions, sessionId)
|
|
276
|
+
setLoadStage({ progress: 100, message: '编辑器已就绪' })
|
|
244
277
|
}, [monaco, active, content, pendingRegions])
|
|
245
278
|
|
|
246
279
|
React.useEffect(() => () => {
|
|
@@ -525,19 +558,48 @@ export function EditorView(props) {
|
|
|
525
558
|
|
|
526
559
|
const otherFiles = sum.pendingFiles.filter((f) => f.path !== active)
|
|
527
560
|
|
|
561
|
+
/**
|
|
562
|
+
* 渲染编辑器/文件加载进度面板。
|
|
563
|
+
* @author ddj 2026年08月22号
|
|
564
|
+
* @param message 当前加载阶段
|
|
565
|
+
* @param progress 阶段进度
|
|
566
|
+
* @param retry 可选重试回调
|
|
567
|
+
* @returns 加载面板 React 元素
|
|
568
|
+
*/
|
|
569
|
+
const loadingBody = (message, progress, retry) => React.createElement('div', { className: 'edrv-empty edrv-loading', 'aria-live': 'polite' },
|
|
570
|
+
React.createElement('div', { className: 'edrv-loading-title' }, message),
|
|
571
|
+
React.createElement('div', {
|
|
572
|
+
className: 'edrv-progress',
|
|
573
|
+
role: 'progressbar',
|
|
574
|
+
'aria-label': '编辑器加载进度',
|
|
575
|
+
'aria-valuemin': 0,
|
|
576
|
+
'aria-valuemax': 100,
|
|
577
|
+
'aria-valuenow': progress,
|
|
578
|
+
}, React.createElement('div', { className: 'edrv-progress-value', style: { width: Math.max(0, Math.min(100, progress)) + '%' } })),
|
|
579
|
+
React.createElement('div', { className: 'edrv-loading-percent' }, Math.round(progress) + '%'),
|
|
580
|
+
retry ? React.createElement('button', { className: 'edrv-pill edrv-pill-ghost', onClick: retry }, '重试') : null)
|
|
581
|
+
|
|
528
582
|
let body
|
|
529
583
|
if (!monaco && !monacoErr) {
|
|
530
|
-
body =
|
|
584
|
+
body = loadingBody(loadStage.message, loadStage.progress)
|
|
531
585
|
} else if (monacoErr) {
|
|
532
586
|
body = React.createElement('div', { className: 'edrv-empty' },
|
|
533
587
|
React.createElement('div', null, 'Monaco 编辑器加载失败:' + String(monacoErr)),
|
|
534
|
-
React.createElement('div', { style: { fontSize: 11 } }, '请确认插件包 assets/vendor/monaco 完整(/edrv/vendor 路由可达)')
|
|
588
|
+
React.createElement('div', { style: { fontSize: 11 } }, '请确认插件包 assets/vendor/monaco 完整(/edrv/vendor 路由可达)'),
|
|
589
|
+
React.createElement('button', { className: 'edrv-pill edrv-pill-ghost', onClick: () => { setMonacoErr(null); setLoadStage({ progress: 0, message: '准备重试 Monaco…' }) } }, '重试'))
|
|
535
590
|
} else if (!active) {
|
|
536
591
|
body = React.createElement('div', { className: 'edrv-empty' },
|
|
537
592
|
React.createElement('div', null, '暂无打开的文件'),
|
|
538
593
|
React.createElement('div', { style: { fontSize: 12 } }, '使用右上搜索框 (Ctrl+P) 打开工作区文件;agent 修改文件后顶部会出现差异角标'))
|
|
594
|
+
} else if (content === null && loadError) {
|
|
595
|
+
body = loadingBody('文件加载失败:' + loadError, 0, () => {
|
|
596
|
+
setLoadError(null)
|
|
597
|
+
setContent(null)
|
|
598
|
+
setLoadStage({ progress: 10, message: '重新读取文件…' })
|
|
599
|
+
loadContent(active, sessionId)
|
|
600
|
+
})
|
|
539
601
|
} else if (content === null) {
|
|
540
|
-
body =
|
|
602
|
+
body = loadingBody(loadStage.message || '读取文件内容…', loadStage.progress)
|
|
541
603
|
} else {
|
|
542
604
|
body = React.createElement('div', { className: 'edrv-monaco-host' },
|
|
543
605
|
React.createElement('div', { ref: ensureEditor }))
|