codexmate 0.1.21 → 0.1.22
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/package.json +1 -1
- package/web-ui/app.js +14 -0
- package/web-ui/index.html +3 -0
- package/web-ui/logic.markdown-editor.mjs +77 -0
- package/web-ui/modules/app.computed.session.mjs +33 -13
- package/web-ui/modules/app.methods.index.mjs +2 -0
- package/web-ui/modules/app.methods.prompts-editor.mjs +153 -0
- package/web-ui/modules/app.methods.session-actions.mjs +37 -4
- package/web-ui/modules/i18n/locales/en.mjs +8 -0
- package/web-ui/modules/i18n/locales/ja.mjs +8 -0
- package/web-ui/modules/i18n/locales/vi.mjs +8 -0
- package/web-ui/modules/i18n/locales/zh-tw.mjs +8 -0
- package/web-ui/modules/i18n/locales/zh.mjs +8 -0
- package/web-ui/partials/index/panel-prompts.html +61 -15
- package/web-ui/res/highlight.min.js +1244 -0
- package/web-ui/res/marked.min.js +6 -0
- package/web-ui/res/purify.min.js +3 -0
- package/web-ui/res/web-ui-render.precompiled.js +191 -21
- package/web-ui/styles/prompts-editor.css +320 -0
- package/web-ui/styles.css +1 -0
package/package.json
CHANGED
package/web-ui/app.js
CHANGED
|
@@ -102,6 +102,14 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
102
102
|
showConfigTemplateModal: false,
|
|
103
103
|
showAgentsModal: false,
|
|
104
104
|
promptsSubTab: 'codex',
|
|
105
|
+
promptsPreviewEnabled: true,
|
|
106
|
+
promptsPreviewCollapsed: false,
|
|
107
|
+
promptsMobileView: 'edit',
|
|
108
|
+
promptsPreviewLibsMissing: false,
|
|
109
|
+
agentsHighlightHtml: '',
|
|
110
|
+
sysPromptHighlightHtml: '',
|
|
111
|
+
agentsPreviewHtml: '',
|
|
112
|
+
sysPromptPreviewHtml: '',
|
|
105
113
|
projectClaudeMdPath: '',
|
|
106
114
|
promptHistoryVisible: false,
|
|
107
115
|
promptHistoryLoading: false,
|
|
@@ -812,6 +820,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
812
820
|
if (typeof this.loadPromptsTabContent === 'function') this.loadPromptsTabContent();
|
|
813
821
|
}
|
|
814
822
|
},
|
|
823
|
+
agentsContent() {
|
|
824
|
+
if (typeof this.schedulePromptsEditorRefresh === 'function') this.schedulePromptsEditorRefresh('agents');
|
|
825
|
+
},
|
|
826
|
+
sysPromptContent() {
|
|
827
|
+
if (typeof this.schedulePromptsEditorRefresh === 'function') this.schedulePromptsEditorRefresh('sys');
|
|
828
|
+
},
|
|
815
829
|
projectClaudeMdPath(newPath) {
|
|
816
830
|
if (typeof this.persistWebUiPreferences === 'function') {
|
|
817
831
|
this.persistWebUiPreferences({ projectClaudeMdPath: newPath || '' });
|
package/web-ui/index.html
CHANGED
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
<link rel="apple-touch-icon" href="/res/logo-pack.webp">
|
|
9
9
|
<script src="/res/vue.runtime.global.prod.js"></script>
|
|
10
10
|
<script src="/res/json5.min.js"></script>
|
|
11
|
+
<script src="/res/highlight.min.js"></script>
|
|
12
|
+
<script src="/res/marked.min.js"></script>
|
|
13
|
+
<script src="/res/purify.min.js"></script>
|
|
11
14
|
<link rel="stylesheet" href="/web-ui/styles.css">
|
|
12
15
|
</head>
|
|
13
16
|
<body>
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure logic for the prompts-panel Markdown editor.
|
|
3
|
+
* No DOM access in this module so every function stays unit-testable.
|
|
4
|
+
*
|
|
5
|
+
* Syntax highlighting delegates to the vendored highlight.js UMD build
|
|
6
|
+
* (web-ui/res/highlight.min.js, "markdown" grammar is included in the
|
|
7
|
+
* common build) and the rendered highlight markup is layered behind a
|
|
8
|
+
* transparent textarea. Preview rendering delegates to the vendored
|
|
9
|
+
* marked + DOMPurify pair; raw markdown may embed arbitrary HTML, so
|
|
10
|
+
* sanitize is mandatory before the result reaches v-html.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const HIGHLIGHT_MAX_CHARS = 200000;
|
|
14
|
+
const HTML_ESCAPE_MAP = {
|
|
15
|
+
'&': '&',
|
|
16
|
+
'<': '<',
|
|
17
|
+
'>': '>',
|
|
18
|
+
'"': '"',
|
|
19
|
+
"'": '''
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
let hasWarnedHighlightFallback = false;
|
|
24
|
+
|
|
25
|
+
export function escapeHtmlText(text) {
|
|
26
|
+
return String(text === null || text === undefined ? '' : text)
|
|
27
|
+
.replace(/[&<>"']/g, (ch) => HTML_ESCAPE_MAP[ch]);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function plainHighlightFallback(text) {
|
|
31
|
+
if (!hasWarnedHighlightFallback) {
|
|
32
|
+
hasWarnedHighlightFallback = true;
|
|
33
|
+
// eslint-disable-next-line no-console
|
|
34
|
+
console.warn('prompts editor: highlight.js unavailable, editor falls back to plain text rendering');
|
|
35
|
+
}
|
|
36
|
+
return escapeHtmlText(text) + '\n';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Produce the highlighted HTML layer for a markdown source string.
|
|
41
|
+
* highlight.js escapes HTML entities itself; the trailing newline keeps
|
|
42
|
+
* the <pre> backdrop visually aligned with the textarea's final line.
|
|
43
|
+
* Returns escaped plain text when hljs is missing or the input exceeds
|
|
44
|
+
* the highlight budget (observable degradation, never a fake success).
|
|
45
|
+
*/
|
|
46
|
+
export function highlightMarkdownText(text, hljs) {
|
|
47
|
+
const source = typeof text === 'string' ? text : '';
|
|
48
|
+
if (!hljs || typeof hljs.highlight !== 'function' || typeof hljs.getLanguage !== 'function' || !hljs.getLanguage('markdown')) {
|
|
49
|
+
return plainHighlightFallback(source);
|
|
50
|
+
}
|
|
51
|
+
if (source.length > HIGHLIGHT_MAX_CHARS) {
|
|
52
|
+
return plainHighlightFallback(source);
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
return hljs.highlight(source, { language: 'markdown', ignoreUnescapedHTML: true }).value + '\n';
|
|
56
|
+
} catch (e) {
|
|
57
|
+
return plainHighlightFallback(source);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Render a markdown preview. Returns a state object instead of raw HTML so
|
|
64
|
+
* the UI can surface "libs missing" and "empty input" explicitly instead
|
|
65
|
+
* of faking a successful render.
|
|
66
|
+
*/
|
|
67
|
+
export function buildMarkdownPreviewHtml(text, marked, purify) {
|
|
68
|
+
const source = typeof text === 'string' ? text : '';
|
|
69
|
+
if (!source.trim()) {
|
|
70
|
+
return { empty: true, html: '' };
|
|
71
|
+
}
|
|
72
|
+
if (!marked || typeof marked.parse !== 'function' || !purify || typeof purify.sanitize !== 'function') {
|
|
73
|
+
return { unavailable: true, html: '' };
|
|
74
|
+
}
|
|
75
|
+
const rawHtml = marked.parse(source, { gfm: true, breaks: false });
|
|
76
|
+
return { html: purify.sanitize(String(rawHtml)) };
|
|
77
|
+
}
|
|
@@ -112,30 +112,39 @@ function extractWorkspaceArtifacts(line, target) {
|
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
function buildWorkspaceBriefText(summary, labels = {}) {
|
|
115
|
+
function buildWorkspaceBriefText(summary, labels = {}, metricLabels = {}) {
|
|
116
116
|
if (!summary || !summary.available) return '';
|
|
117
117
|
const label = (key, fallback) => (labels && labels[key]) || fallback;
|
|
118
|
+
const metricLabel = (key, fallback) => (metricLabels && metricLabels[key]) || fallback;
|
|
118
119
|
const lines = [
|
|
119
120
|
`# ${label('title', 'Session workspace brief')}`,
|
|
120
121
|
'',
|
|
121
122
|
`${label('source', 'Source')}: ${summary.sourceLabel || summary.source || '-'}`,
|
|
122
123
|
`${label('messages', 'Messages')}: ${summary.messageCount}`,
|
|
124
|
+
`${metricLabel('userLabel', 'User')}: ${summary.roleCounts.user}`,
|
|
125
|
+
`${metricLabel('assistantLabel', 'Assistant')}: ${summary.roleCounts.assistant}`,
|
|
126
|
+
`${metricLabel('commandsLabel', 'Commands')}: ${summary.commands.length}`,
|
|
127
|
+
`${metricLabel('artifactsLabel', 'Artifacts')}: ${summary.files.length + summary.links.length}`,
|
|
128
|
+
`${metricLabel('risksLabel', 'Risks')}: ${summary.risks.length + summary.nextSteps.length}`,
|
|
123
129
|
`${label('path', 'Path')}: ${summary.cwd || '-'}`,
|
|
124
130
|
''
|
|
125
131
|
];
|
|
126
132
|
const sections = [
|
|
127
|
-
['signals', label('signals', 'Signals'), summary.signals],
|
|
128
|
-
['commands', label('commands', 'Reusable commands'), summary.commands],
|
|
129
|
-
['files', label('files', 'Files'), summary.files],
|
|
130
|
-
['links', label('links', 'Links'), summary.links],
|
|
131
|
-
['risks', label('risks', 'Risks / blockers'), summary.risks],
|
|
132
|
-
['nextSteps', label('nextSteps', 'Next steps'), summary.nextSteps]
|
|
133
|
+
['signals', label('signals', 'Signals'), summary.signals, label('noneSignals', 'No strong signals yet')],
|
|
134
|
+
['commands', label('commands', 'Reusable commands'), summary.commands, label('noneCommands', 'No command signals')],
|
|
135
|
+
['files', label('files', 'Files'), summary.files, label('noneFiles', 'No file signals')],
|
|
136
|
+
['links', label('links', 'Links'), summary.links, label('noneLinks', 'No link signals')],
|
|
137
|
+
['risks', label('risks', 'Risks / blockers'), summary.risks, label('noneRisks', 'No risks or todos detected')],
|
|
138
|
+
['nextSteps', label('nextSteps', 'Next steps'), summary.nextSteps, label('noneRisks', 'No risks or todos detected')]
|
|
133
139
|
];
|
|
134
|
-
for (const [, sectionTitle, items] of sections) {
|
|
135
|
-
if (!Array.isArray(items) || !items.length) continue;
|
|
140
|
+
for (const [, sectionTitle, items, emptyText] of sections) {
|
|
136
141
|
lines.push(`## ${sectionTitle}`);
|
|
137
|
-
|
|
138
|
-
|
|
142
|
+
if (Array.isArray(items) && items.length) {
|
|
143
|
+
for (const item of items) {
|
|
144
|
+
lines.push(`- ${item}`);
|
|
145
|
+
}
|
|
146
|
+
} else {
|
|
147
|
+
lines.push(`- ${emptyText}`);
|
|
139
148
|
}
|
|
140
149
|
lines.push('');
|
|
141
150
|
}
|
|
@@ -231,7 +240,13 @@ export function buildSessionWorkspaceSummary(session, messages, options = {}) {
|
|
|
231
240
|
risks,
|
|
232
241
|
nextSteps
|
|
233
242
|
};
|
|
234
|
-
summary.briefText = buildWorkspaceBriefText(summary, options.briefLabels || {}
|
|
243
|
+
summary.briefText = buildWorkspaceBriefText(summary, options.briefLabels || {}, {
|
|
244
|
+
userLabel: options.userLabel,
|
|
245
|
+
assistantLabel: options.assistantLabel,
|
|
246
|
+
commandsLabel: options.commandsLabel,
|
|
247
|
+
artifactsLabel: options.artifactsLabel,
|
|
248
|
+
risksLabel: options.risksLabel
|
|
249
|
+
});
|
|
235
250
|
return summary;
|
|
236
251
|
}
|
|
237
252
|
|
|
@@ -474,7 +489,12 @@ export function createSessionComputed() {
|
|
|
474
489
|
files: t ? t('sessions.workspace.files') : 'Files',
|
|
475
490
|
links: t ? t('sessions.workspace.links') : 'Links',
|
|
476
491
|
risks: t ? t('sessions.workspace.risks') : 'Risks / blockers',
|
|
477
|
-
nextSteps: t ? t('sessions.workspace.nextSteps') : 'Next steps'
|
|
492
|
+
nextSteps: t ? t('sessions.workspace.nextSteps') : 'Next steps',
|
|
493
|
+
noneSignals: t ? t('sessions.workspace.empty') : 'No strong signals yet',
|
|
494
|
+
noneCommands: t ? t('sessions.workspace.noneCommands') : 'No command signals',
|
|
495
|
+
noneFiles: t ? t('sessions.workspace.noneFiles') : 'No file signals',
|
|
496
|
+
noneLinks: t ? t('sessions.workspace.noneLinks') : 'No link signals',
|
|
497
|
+
noneRisks: t ? t('sessions.workspace.noneRisks') : 'No risks or todos detected'
|
|
478
498
|
}
|
|
479
499
|
});
|
|
480
500
|
},
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
} from './app.constants.mjs';
|
|
14
14
|
import { createAgentsMethods } from './app.methods.agents.mjs';
|
|
15
15
|
import { createSystemPromptMethods } from './app.methods.system-prompt.mjs';
|
|
16
|
+
import { createPromptsEditorMethods } from './app.methods.prompts-editor.mjs';
|
|
16
17
|
import { createClaudeConfigMethods } from './app.methods.claude-config.mjs';
|
|
17
18
|
import { createCodexConfigMethods } from './app.methods.codex-config.mjs';
|
|
18
19
|
import { createInstallMethods } from './app.methods.install.mjs';
|
|
@@ -51,6 +52,7 @@ import {
|
|
|
51
52
|
export function createAppMethods() {
|
|
52
53
|
return {
|
|
53
54
|
...createI18nMethods(),
|
|
55
|
+
...createPromptsEditorMethods(),
|
|
54
56
|
...createWebhookMethods(),
|
|
55
57
|
...createStartupClaudeMethods({
|
|
56
58
|
api,
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prompts-panel Markdown editor methods: overlay highlight sync,
|
|
3
|
+
* mobile view switching, and the live split preview (marked + DOMPurify,
|
|
4
|
+
* debounced). One implementation is shared by the agentsContent editor
|
|
5
|
+
* and the sysPromptContent editor.
|
|
6
|
+
*
|
|
7
|
+
* State contract (declared in app.js data()):
|
|
8
|
+
* promptsPreviewEnabled, promptsPreviewCollapsed, promptsMobileView,
|
|
9
|
+
* promptsPreviewLibsMissing, agentsHighlightHtml, sysPromptHighlightHtml,
|
|
10
|
+
* agentsPreviewHtml, sysPromptPreviewHtml
|
|
11
|
+
* Template contract:
|
|
12
|
+
* refs promptsAgentsTextarea / promptsSysTextarea on the two textareas,
|
|
13
|
+
* refs promptsAgentsHighlight / promptsSysHighlight on the backdrop <pre>.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
buildMarkdownPreviewHtml,
|
|
18
|
+
highlightMarkdownText
|
|
19
|
+
} from '../logic.markdown-editor.mjs';
|
|
20
|
+
|
|
21
|
+
const PROMPTS_HIGHLIGHT_DEBOUNCE_MS = 150;
|
|
22
|
+
const PROMPTS_PREVIEW_DEBOUNCE_MS = 300;
|
|
23
|
+
|
|
24
|
+
const PROMPTS_EDITOR_META = {
|
|
25
|
+
agents: {
|
|
26
|
+
contentField: 'agentsContent',
|
|
27
|
+
highlightField: 'agentsHighlightHtml',
|
|
28
|
+
previewField: 'agentsPreviewHtml',
|
|
29
|
+
textareaRef: 'promptsAgentsTextarea',
|
|
30
|
+
highlightRef: 'promptsAgentsHighlight'
|
|
31
|
+
},
|
|
32
|
+
sys: {
|
|
33
|
+
contentField: 'sysPromptContent',
|
|
34
|
+
highlightField: 'sysPromptHighlightHtml',
|
|
35
|
+
previewField: 'sysPromptPreviewHtml',
|
|
36
|
+
textareaRef: 'promptsSysTextarea',
|
|
37
|
+
highlightRef: 'promptsSysHighlight'
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
function resolvePromptsHljs() {
|
|
42
|
+
if (typeof window !== 'undefined' && window.hljs) {
|
|
43
|
+
return window.hljs;
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function resolvePromptsPreviewLibs() {
|
|
49
|
+
if (typeof window === 'undefined') {
|
|
50
|
+
return { marked: null, purify: null };
|
|
51
|
+
}
|
|
52
|
+
return { marked: window.marked || null, purify: window.DOMPurify || null };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function createPromptsEditorMethods() {
|
|
56
|
+
return {
|
|
57
|
+
promptsEditorMeta(key) {
|
|
58
|
+
return PROMPTS_EDITOR_META[key] || null;
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
refreshPromptsHighlight(key) {
|
|
63
|
+
const meta = this.promptsEditorMeta(key);
|
|
64
|
+
if (!meta) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
this[meta.highlightField] = highlightMarkdownText(this[meta.contentField], resolvePromptsHljs());
|
|
68
|
+
this.syncPromptsOverlayScroll(key);
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
schedulePromptsHighlight(key) {
|
|
72
|
+
const meta = this.promptsEditorMeta(key);
|
|
73
|
+
if (!meta) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (!this._promptsHighlightTimers) {
|
|
77
|
+
this._promptsHighlightTimers = {};
|
|
78
|
+
}
|
|
79
|
+
if (this._promptsHighlightTimers[key]) {
|
|
80
|
+
clearTimeout(this._promptsHighlightTimers[key]);
|
|
81
|
+
}
|
|
82
|
+
this._promptsHighlightTimers[key] = setTimeout(() => {
|
|
83
|
+
this._promptsHighlightTimers[key] = null;
|
|
84
|
+
this.refreshPromptsHighlight(key);
|
|
85
|
+
}, PROMPTS_HIGHLIGHT_DEBOUNCE_MS);
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
syncPromptsOverlayScroll(key) {
|
|
89
|
+
const meta = this.promptsEditorMeta(key);
|
|
90
|
+
if (!meta || typeof this.$refs !== 'object' || !this.$refs) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const textarea = this.$refs[meta.textareaRef];
|
|
94
|
+
const backdrop = this.$refs[meta.highlightRef];
|
|
95
|
+
if (!textarea || !backdrop) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
backdrop.scrollTop = textarea.scrollTop;
|
|
99
|
+
backdrop.scrollLeft = textarea.scrollLeft;
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
refreshPromptsPreview(key) {
|
|
103
|
+
const meta = this.promptsEditorMeta(key);
|
|
104
|
+
if (!meta) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (!this.promptsPreviewEnabled) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const libs = resolvePromptsPreviewLibs();
|
|
111
|
+
const state = buildMarkdownPreviewHtml(this[meta.contentField], libs.marked, libs.purify);
|
|
112
|
+
if (state.unavailable) {
|
|
113
|
+
this.promptsPreviewLibsMissing = true;
|
|
114
|
+
this[meta.previewField] = '';
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
this.promptsPreviewLibsMissing = false;
|
|
118
|
+
this[meta.previewField] = state.html;
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
schedulePromptsPreview(key) {
|
|
122
|
+
const meta = this.promptsEditorMeta(key);
|
|
123
|
+
if (!meta) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (!this._promptsPreviewTimers) {
|
|
127
|
+
this._promptsPreviewTimers = {};
|
|
128
|
+
}
|
|
129
|
+
if (this._promptsPreviewTimers[key]) {
|
|
130
|
+
clearTimeout(this._promptsPreviewTimers[key]);
|
|
131
|
+
}
|
|
132
|
+
this._promptsPreviewTimers[key] = setTimeout(() => {
|
|
133
|
+
this._promptsPreviewTimers[key] = null;
|
|
134
|
+
this.refreshPromptsPreview(key);
|
|
135
|
+
}, PROMPTS_PREVIEW_DEBOUNCE_MS);
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
schedulePromptsEditorRefresh(key) {
|
|
139
|
+
this.schedulePromptsHighlight(key);
|
|
140
|
+
this.schedulePromptsPreview(key);
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
setPromptsMobileView(view) {
|
|
144
|
+
if (view === 'edit' || view === 'preview') {
|
|
145
|
+
this.promptsMobileView = view;
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
togglePromptsPreviewCollapsed() {
|
|
150
|
+
this.promptsPreviewCollapsed = !this.promptsPreviewCollapsed;
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
}
|
|
@@ -174,28 +174,61 @@ export function createSessionActionMethods(options = {}) {
|
|
|
174
174
|
|
|
175
175
|
async copySessionWorkspaceBrief() {
|
|
176
176
|
const summary = this.activeSessionWorkspaceSummary;
|
|
177
|
-
const
|
|
177
|
+
const brief = summary && typeof summary.briefText === 'string'
|
|
178
178
|
? summary.briefText.trim()
|
|
179
179
|
: '';
|
|
180
|
-
if (!
|
|
180
|
+
if (!brief) {
|
|
181
181
|
this.showMessage(this.t('sessions.workspace.copy.empty'), 'error');
|
|
182
182
|
return;
|
|
183
183
|
}
|
|
184
|
+
const session = this.activeSession;
|
|
185
|
+
let exported;
|
|
186
|
+
let exportResult;
|
|
187
|
+
try {
|
|
188
|
+
const res = await api('export-session', {
|
|
189
|
+
source: session.source,
|
|
190
|
+
sessionId: session.sessionId,
|
|
191
|
+
filePath: session.filePath
|
|
192
|
+
});
|
|
193
|
+
if (!res || typeof res !== 'object' || (typeof res.error === 'string' && res.error.trim())) {
|
|
194
|
+
this.showMessage((res && res.error) || this.t('toast.copy.fail'), 'error');
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
exported = typeof res.content === 'string' ? res.content.trim() : '';
|
|
198
|
+
if (!exported) {
|
|
199
|
+
this.showMessage(this.t('toast.copy.fail'), 'error');
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
exportResult = res;
|
|
203
|
+
} catch (e) {
|
|
204
|
+
this.showMessage(this.t('toast.copy.fail'), 'error');
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
const text = `${brief}\n\n---\n\n${exported}`;
|
|
184
208
|
const ok = this.fallbackCopyText(text);
|
|
185
209
|
if (ok) {
|
|
186
|
-
this.
|
|
210
|
+
this.notifyBriefCopied(exportResult);
|
|
187
211
|
return;
|
|
188
212
|
}
|
|
189
213
|
try {
|
|
190
214
|
if (navigator.clipboard && window.isSecureContext) {
|
|
191
215
|
await navigator.clipboard.writeText(text);
|
|
192
|
-
this.
|
|
216
|
+
this.notifyBriefCopied(exportResult);
|
|
193
217
|
return;
|
|
194
218
|
}
|
|
195
219
|
} catch (_) {}
|
|
196
220
|
this.showMessage(this.t('toast.copy.fail'), 'error');
|
|
197
221
|
},
|
|
198
222
|
|
|
223
|
+
notifyBriefCopied(exportResult) {
|
|
224
|
+
if (exportResult && exportResult.truncated) {
|
|
225
|
+
const maxLabel = exportResult.maxMessages === 'all' ? 'all' : exportResult.maxMessages;
|
|
226
|
+
this.showMessage(`已复制工作简报(会话全文已截断:最多 ${maxLabel} 条消息)`, 'info');
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
this.showMessage(this.t('sessions.workspace.copy.success'), 'success');
|
|
230
|
+
},
|
|
231
|
+
|
|
199
232
|
getSessionExportKey(session) {
|
|
200
233
|
return `${session.source || 'unknown'}:${session.sessionId || ''}:${session.filePath || ''}`;
|
|
201
234
|
},
|
|
@@ -233,6 +233,14 @@ const en = Object.freeze({
|
|
|
233
233
|
'prompts.subTab.project': 'CLAUDE.md',
|
|
234
234
|
'prompts.subTab.system': 'System Prompt (Pi)',
|
|
235
235
|
'prompts.subTab.presets': 'Preset pool',
|
|
236
|
+
'prompts.editor.toolbar': 'Markdown toolbar',
|
|
237
|
+
'prompts.editor.collapsePreview': 'Collapse preview',
|
|
238
|
+
'prompts.editor.expandPreview': 'Expand preview',
|
|
239
|
+
'prompts.editor.previewToggle': 'Live preview',
|
|
240
|
+
'prompts.editor.edit': 'Edit',
|
|
241
|
+
'prompts.editor.preview': 'Preview',
|
|
242
|
+
'prompts.editor.previewUnavailable': 'Preview components missing (marked / DOMPurify), please refresh the page',
|
|
243
|
+
'prompts.editor.previewEmpty': 'Nothing to preview yet',
|
|
236
244
|
'prompts.presets.title': 'Presets',
|
|
237
245
|
'prompts.presets.hint': 'Manage presets.',
|
|
238
246
|
'prompts.presets.namePlaceholder': 'Preset name',
|
|
@@ -234,6 +234,14 @@ const ja = Object.freeze({
|
|
|
234
234
|
'prompts.subTab.project': 'CLAUDE.md',
|
|
235
235
|
'prompts.subTab.system': 'System Prompt (Pi)',
|
|
236
236
|
'prompts.subTab.presets': 'プリセット',
|
|
237
|
+
'prompts.editor.toolbar': 'Markdown ツールバー',
|
|
238
|
+
'prompts.editor.collapsePreview': 'プレビューを折りたたむ',
|
|
239
|
+
'prompts.editor.expandPreview': 'プレビューを展開',
|
|
240
|
+
'prompts.editor.previewToggle': 'ライブプレビュー',
|
|
241
|
+
'prompts.editor.edit': '編集',
|
|
242
|
+
'prompts.editor.preview': 'プレビュー',
|
|
243
|
+
'prompts.editor.previewUnavailable': 'プレビュー用コンポーネント(marked / DOMPurify)が読み込まれていません。ページを再読み込みしてください',
|
|
244
|
+
'prompts.editor.previewEmpty': 'プレビューする内容がありません',
|
|
237
245
|
'prompts.presets.title': 'プリセット',
|
|
238
246
|
'prompts.presets.hint': 'プリセットを管理します。',
|
|
239
247
|
'prompts.presets.namePlaceholder': 'プリセット名',
|
|
@@ -251,6 +251,14 @@ const vi = Object.freeze({
|
|
|
251
251
|
'prompts.subTab.project': 'CLAUDE.md',
|
|
252
252
|
'prompts.subTab.system': 'System Prompt (Pi)',
|
|
253
253
|
'prompts.subTab.presets': 'Kho preset',
|
|
254
|
+
'prompts.editor.toolbar': 'Thanh công cụ Markdown',
|
|
255
|
+
'prompts.editor.collapsePreview': 'Thu gọn bản xem trước',
|
|
256
|
+
'prompts.editor.expandPreview': 'Mở rộng bản xem trước',
|
|
257
|
+
'prompts.editor.previewToggle': 'Xem trước trực tiếp',
|
|
258
|
+
'prompts.editor.edit': 'Chỉnh sửa',
|
|
259
|
+
'prompts.editor.preview': 'Xem trước',
|
|
260
|
+
'prompts.editor.previewUnavailable': 'Thư viện xem trước (marked / DOMPurify) chưa được tải, hãy tải lại trang',
|
|
261
|
+
'prompts.editor.previewEmpty': 'Chưa có nội dung để xem trước',
|
|
254
262
|
'prompts.presets.title': 'Preset',
|
|
255
263
|
'prompts.presets.hint': 'Quản lý preset.',
|
|
256
264
|
'prompts.presets.namePlaceholder': 'Tên preset',
|
|
@@ -233,6 +233,14 @@ const zhTw = Object.freeze({
|
|
|
233
233
|
'prompts.subTab.project': 'CLAUDE.md',
|
|
234
234
|
'prompts.subTab.system': 'System Prompt (Pi)',
|
|
235
235
|
'prompts.subTab.presets': '預設池',
|
|
236
|
+
'prompts.editor.toolbar': 'Markdown 工具列',
|
|
237
|
+
'prompts.editor.collapsePreview': '收起預覽',
|
|
238
|
+
'prompts.editor.expandPreview': '展開預覽',
|
|
239
|
+
'prompts.editor.previewToggle': '即時預覽',
|
|
240
|
+
'prompts.editor.edit': '編輯',
|
|
241
|
+
'prompts.editor.preview': '預覽',
|
|
242
|
+
'prompts.editor.previewUnavailable': '預覽元件未載入(marked / DOMPurify 缺失),請重新整理頁面',
|
|
243
|
+
'prompts.editor.previewEmpty': '尚無內容可預覽',
|
|
236
244
|
'prompts.presets.title': '預設池',
|
|
237
245
|
'prompts.presets.hint': '管理預設。',
|
|
238
246
|
'prompts.presets.namePlaceholder': '預設名稱',
|
|
@@ -233,6 +233,14 @@ const zh = Object.freeze({
|
|
|
233
233
|
'prompts.subTab.project': 'CLAUDE.md',
|
|
234
234
|
'prompts.subTab.system': 'System Prompt (Pi)',
|
|
235
235
|
'prompts.subTab.presets': '预设池',
|
|
236
|
+
'prompts.editor.toolbar': 'Markdown 工具栏',
|
|
237
|
+
'prompts.editor.collapsePreview': '收起预览',
|
|
238
|
+
'prompts.editor.expandPreview': '展开预览',
|
|
239
|
+
'prompts.editor.previewToggle': '实时预览',
|
|
240
|
+
'prompts.editor.edit': '编辑',
|
|
241
|
+
'prompts.editor.preview': '预览',
|
|
242
|
+
'prompts.editor.previewUnavailable': '预览组件未加载(marked / DOMPurify 缺失),请刷新页面',
|
|
243
|
+
'prompts.editor.previewEmpty': '暂无内容可预览',
|
|
236
244
|
'prompts.presets.title': '预设池',
|
|
237
245
|
'prompts.presets.hint': '管理预设。',
|
|
238
246
|
'prompts.presets.namePlaceholder': '预设名称',
|
|
@@ -201,17 +201,40 @@
|
|
|
201
201
|
</div>
|
|
202
202
|
</div>
|
|
203
203
|
</div>
|
|
204
|
-
<div
|
|
204
|
+
<div class="prompts-md-toolbar" role="toolbar" :aria-label="t('prompts.editor.toolbar')">
|
|
205
|
+
<div class="prompts-md-toolbar-group prompts-md-toolbar-group--view">
|
|
206
|
+
<div v-if="promptsPreviewEnabled" class="prompts-mobile-view-switch" role="tablist" :aria-label="t('prompts.editor.previewToggle')">
|
|
207
|
+
<button type="button" role="tab" :aria-selected="promptsMobileView === 'edit'" :class="['prompts-mobile-view-btn', { active: promptsMobileView === 'edit' }]" @click="setPromptsMobileView('edit')">{{ t('prompts.editor.edit') }}</button>
|
|
208
|
+
<button type="button" role="tab" :aria-selected="promptsMobileView === 'preview'" :class="['prompts-mobile-view-btn', { active: promptsMobileView === 'preview' }]" @click="setPromptsMobileView('preview')">{{ t('prompts.editor.preview') }}</button>
|
|
209
|
+
</div>
|
|
210
|
+
</div>
|
|
211
|
+
</div>
|
|
212
|
+
<div :class="['editor-frame prompts-editor-frame', { 'editor-frame--loading': agentsLoading, 'prompts-editor-frame--split': promptsPreviewEnabled, 'prompts-editor-frame--preview-collapsed': promptsPreviewCollapsed, 'prompts-editor-frame--mobile-preview': promptsPreviewEnabled && promptsMobileView === 'preview' }]">
|
|
213
|
+
<button v-if="promptsPreviewEnabled" type="button" class="prompts-split-collapse-btn" :class="{ 'prompts-split-collapse-btn--collapsed': promptsPreviewCollapsed }" :title="promptsPreviewCollapsed ? t('prompts.editor.expandPreview') : t('prompts.editor.collapsePreview')" :aria-label="promptsPreviewCollapsed ? t('prompts.editor.expandPreview') : t('prompts.editor.collapsePreview')" @click="togglePromptsPreviewCollapsed()">
|
|
214
|
+
<svg class="prompts-split-collapse-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 6l6 6-6 6"/></svg>
|
|
215
|
+
</button>
|
|
205
216
|
<div v-if="agentsLoading" class="editor-skeleton">
|
|
206
217
|
<div class="skeleton-line" v-for="i in 6" :key="i"></div>
|
|
207
218
|
</div>
|
|
208
|
-
<
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
219
|
+
<div class="prompts-editor-columns">
|
|
220
|
+
<div class="prompts-edit-pane">
|
|
221
|
+
<pre ref="promptsAgentsHighlight" class="prompts-highlight-backdrop" aria-hidden="true" v-html="agentsHighlightHtml"></pre>
|
|
222
|
+
<textarea
|
|
223
|
+
ref="promptsAgentsTextarea"
|
|
224
|
+
v-model="agentsContent"
|
|
225
|
+
class="form-input template-editor"
|
|
226
|
+
spellcheck="false"
|
|
227
|
+
:readonly="agentsLoading || agentsSaving || agentsDiffVisible"
|
|
228
|
+
@input="onAgentsContentInput"
|
|
229
|
+
@scroll="syncPromptsOverlayScroll('agents')"
|
|
230
|
+
:placeholder="t(promptsSubTab === 'claude-project' ? 'modal.agents.placeholder.claudeProject' : 'modal.agents.placeholder')"></textarea>
|
|
231
|
+
</div>
|
|
232
|
+
<div v-if="promptsPreviewEnabled" class="prompts-preview-pane">
|
|
233
|
+
<div v-if="promptsPreviewLibsMissing" class="state-message error">{{ t('prompts.editor.previewUnavailable') }}</div>
|
|
234
|
+
<div v-else-if="!agentsPreviewHtml" class="state-message">{{ t('prompts.editor.previewEmpty') }}</div>
|
|
235
|
+
<div v-else class="prompts-preview-body" v-html="agentsPreviewHtml"></div>
|
|
236
|
+
</div>
|
|
237
|
+
</div>
|
|
215
238
|
</div>
|
|
216
239
|
<div v-if="promptsContextHint" :class="['prompts-context-hint', { 'prompts-context-hint--warn': promptsContextHint.warn }]">
|
|
217
240
|
{{ promptsContextHint.text }}
|
|
@@ -320,16 +343,39 @@
|
|
|
320
343
|
</div>
|
|
321
344
|
</div>
|
|
322
345
|
</div>
|
|
323
|
-
<div
|
|
346
|
+
<div class="prompts-md-toolbar" role="toolbar" :aria-label="t('prompts.editor.toolbar')">
|
|
347
|
+
<div class="prompts-md-toolbar-group prompts-md-toolbar-group--view">
|
|
348
|
+
<div v-if="promptsPreviewEnabled" class="prompts-mobile-view-switch" role="tablist" :aria-label="t('prompts.editor.previewToggle')">
|
|
349
|
+
<button type="button" role="tab" :aria-selected="promptsMobileView === 'edit'" :class="['prompts-mobile-view-btn', { active: promptsMobileView === 'edit' }]" @click="setPromptsMobileView('edit')">{{ t('prompts.editor.edit') }}</button>
|
|
350
|
+
<button type="button" role="tab" :aria-selected="promptsMobileView === 'preview'" :class="['prompts-mobile-view-btn', { active: promptsMobileView === 'preview' }]" @click="setPromptsMobileView('preview')">{{ t('prompts.editor.preview') }}</button>
|
|
351
|
+
</div>
|
|
352
|
+
</div>
|
|
353
|
+
</div>
|
|
354
|
+
<div :class="['editor-frame prompts-editor-frame', { 'editor-frame--loading': sysPromptLoading, 'prompts-editor-frame--split': promptsPreviewEnabled, 'prompts-editor-frame--preview-collapsed': promptsPreviewCollapsed, 'prompts-editor-frame--mobile-preview': promptsPreviewEnabled && promptsMobileView === 'preview' }]">
|
|
355
|
+
<button v-if="promptsPreviewEnabled" type="button" class="prompts-split-collapse-btn" :class="{ 'prompts-split-collapse-btn--collapsed': promptsPreviewCollapsed }" :title="promptsPreviewCollapsed ? t('prompts.editor.expandPreview') : t('prompts.editor.collapsePreview')" :aria-label="promptsPreviewCollapsed ? t('prompts.editor.expandPreview') : t('prompts.editor.collapsePreview')" @click="togglePromptsPreviewCollapsed()">
|
|
356
|
+
<svg class="prompts-split-collapse-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 6l6 6-6 6"/></svg>
|
|
357
|
+
</button>
|
|
324
358
|
<div v-if="sysPromptLoading" class="editor-skeleton">
|
|
325
359
|
<div class="skeleton-line" v-for="i in 6" :key="i"></div>
|
|
326
360
|
</div>
|
|
327
|
-
<
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
361
|
+
<div class="prompts-editor-columns">
|
|
362
|
+
<div class="prompts-edit-pane">
|
|
363
|
+
<pre ref="promptsSysHighlight" class="prompts-highlight-backdrop" aria-hidden="true" v-html="sysPromptHighlightHtml"></pre>
|
|
364
|
+
<textarea
|
|
365
|
+
ref="promptsSysTextarea"
|
|
366
|
+
v-model="sysPromptContent"
|
|
367
|
+
class="form-input template-editor"
|
|
368
|
+
spellcheck="false"
|
|
369
|
+
:readonly="sysPromptLoading || sysPromptSaving || sysPromptDiffVisible"
|
|
370
|
+
@scroll="syncPromptsOverlayScroll('sys')"
|
|
371
|
+
:placeholder="t('sysPrompt.placeholder')"></textarea>
|
|
372
|
+
</div>
|
|
373
|
+
<div v-if="promptsPreviewEnabled" class="prompts-preview-pane">
|
|
374
|
+
<div v-if="promptsPreviewLibsMissing" class="state-message error">{{ t('prompts.editor.previewUnavailable') }}</div>
|
|
375
|
+
<div v-else-if="!sysPromptPreviewHtml" class="state-message">{{ t('prompts.editor.previewEmpty') }}</div>
|
|
376
|
+
<div v-else class="prompts-preview-body" v-html="sysPromptPreviewHtml"></div>
|
|
377
|
+
</div>
|
|
378
|
+
</div>
|
|
333
379
|
</div>
|
|
334
380
|
<div v-if="sysPromptContextHint()" :class="['prompts-context-hint', { 'prompts-context-hint--warn': sysPromptContextHint().warn }]">
|
|
335
381
|
{{ sysPromptContextHint().text }}
|