codexmate 0.1.23 → 0.1.25
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/cli.js +4 -1
- package/package.json +1 -1
- package/plugins/prompt-templates/computed.mjs +1 -1
- package/plugins/prompt-templates/index.mjs +3 -3
- package/plugins/prompt-templates/manifest.mjs +5 -3
- package/plugins/prompt-templates/methods.mjs +9 -2
- package/plugins/registry.mjs +5 -2
- package/plugins/text-tools/computed.mjs +23 -0
- package/plugins/text-tools/index.mjs +14 -0
- package/plugins/text-tools/manifest.mjs +20 -0
- package/plugins/text-tools/methods.mjs +24 -0
- package/plugins/text-tools/overview.mjs +12 -0
- package/plugins/text-tools/ownership.mjs +7 -0
- package/web-ui/app.js +5 -2
- package/web-ui/index.html +0 -1
- package/web-ui/logic.markdown-editor.mjs +5 -57
- package/web-ui/logic.prompts-change-axis.mjs +3 -88
- package/web-ui/logic.prompts-path-reference.mjs +118 -0
- package/web-ui/modules/app.methods.agents.mjs +1 -0
- package/web-ui/modules/app.methods.index.mjs +1 -1
- package/web-ui/modules/app.methods.navigation.mjs +79 -3
- package/web-ui/modules/app.methods.prompts-editor.mjs +140 -145
- package/web-ui/modules/app.methods.web-ui-preferences.mjs +7 -1
- package/web-ui/modules/i18n/locales/en.mjs +22 -1
- package/web-ui/modules/i18n/locales/ja.mjs +22 -1
- package/web-ui/modules/i18n/locales/vi.mjs +22 -1
- package/web-ui/modules/i18n/locales/zh-tw.mjs +22 -1
- package/web-ui/modules/i18n/locales/zh.mjs +22 -1
- package/web-ui/modules/plugins.computed.mjs +8 -2
- package/web-ui/modules/plugins.methods.mjs +8 -2
- package/web-ui/partials/index/layout-header.html +19 -4
- package/web-ui/partials/index/panel-plugins.html +45 -5
- package/web-ui/partials/index/panel-prompts.html +24 -31
- package/web-ui/res/web-ui-render.precompiled.js +142 -130
- package/web-ui/session-helpers.mjs +0 -1
- package/web-ui/source-bundle.cjs +1 -0
- package/web-ui/styles/prompts-editor.css +20 -91
package/cli.js
CHANGED
|
@@ -1098,7 +1098,10 @@ function normalizeWebUiPreferences(value = {}) {
|
|
|
1098
1098
|
skillsTargetApp: navigation.skillsTargetApp === 'claude' || navigation.skillsTargetApp === 'pi'
|
|
1099
1099
|
? navigation.skillsTargetApp
|
|
1100
1100
|
: 'codex',
|
|
1101
|
-
promptTemplatesMode: navigation.promptTemplatesMode === 'manage' ? 'manage' : 'compose'
|
|
1101
|
+
promptTemplatesMode: navigation.promptTemplatesMode === 'manage' ? 'manage' : 'compose',
|
|
1102
|
+
pluginsActiveId: typeof navigation.pluginsActiveId === 'string' && navigation.pluginsActiveId.trim()
|
|
1103
|
+
? navigation.pluginsActiveId.trim()
|
|
1104
|
+
: 'prompt-templates'
|
|
1102
1105
|
}
|
|
1103
1106
|
};
|
|
1104
1107
|
}
|
package/package.json
CHANGED
|
@@ -158,7 +158,7 @@ function localizeBuiltinPromptTemplate(item, t) {
|
|
|
158
158
|
|
|
159
159
|
import { pluginsRegistry } from '../registry.mjs';
|
|
160
160
|
|
|
161
|
-
export function
|
|
161
|
+
export function createPromptTemplatesComputed() {
|
|
162
162
|
return {
|
|
163
163
|
pluginsCatalog() {
|
|
164
164
|
return pluginsRegistry
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export { createPluginsMethods } from './methods.mjs';
|
|
2
|
-
export { createPluginsComputed } from './computed.mjs';
|
|
3
|
-
export { pluginMeta } from './manifest.mjs';
|
|
1
|
+
export { createPromptTemplatesMethods as createPluginsMethods } from './methods.mjs';
|
|
2
|
+
export { createPromptTemplatesComputed as createPluginsComputed } from './computed.mjs';
|
|
3
|
+
export { promptTemplatesPluginMeta as pluginMeta } from './manifest.mjs';
|
|
4
4
|
export {
|
|
5
5
|
readPromptTemplatesFromStorage,
|
|
6
6
|
persistPromptTemplatesToStorage,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { pluginOwnership } from './ownership.mjs';
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const promptTemplatesBaseMeta = {
|
|
4
4
|
id: 'prompt-templates',
|
|
5
5
|
title: 'Prompt Templates',
|
|
6
6
|
titleKey: 'plugins.catalog.promptTemplates.title',
|
|
@@ -11,8 +11,10 @@ const baseMeta = {
|
|
|
11
11
|
tone: 'configured'
|
|
12
12
|
};
|
|
13
13
|
|
|
14
|
-
export const
|
|
15
|
-
...
|
|
14
|
+
export const promptTemplatesPluginMeta = {
|
|
15
|
+
...promptTemplatesBaseMeta,
|
|
16
16
|
createdBy: pluginOwnership && typeof pluginOwnership.createdBy === 'string' ? pluginOwnership.createdBy : '',
|
|
17
17
|
maintainers: pluginOwnership && Array.isArray(pluginOwnership.maintainers) ? pluginOwnership.maintainers : []
|
|
18
18
|
};
|
|
19
|
+
|
|
20
|
+
export { promptTemplatesPluginMeta as pluginMeta };
|
|
@@ -79,7 +79,7 @@ function normalizePromptTemplateDraft(draft) {
|
|
|
79
79
|
};
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
export function
|
|
82
|
+
export function createPromptTemplatesMethods() {
|
|
83
83
|
return {
|
|
84
84
|
resetPromptComposerVarValues() {
|
|
85
85
|
this.promptComposerVarValuesRaw = {};
|
|
@@ -280,8 +280,15 @@ export function createPluginsMethods() {
|
|
|
280
280
|
selectPlugin(pluginId) {
|
|
281
281
|
const id = typeof pluginId === 'string' ? pluginId.trim() : '';
|
|
282
282
|
if (!id) return;
|
|
283
|
-
|
|
283
|
+
const entry = getPluginEntry(id);
|
|
284
|
+
if (!entry) return;
|
|
284
285
|
this.pluginsActiveId = id;
|
|
286
|
+
if (typeof this.saveNavState === 'function') {
|
|
287
|
+
this.saveNavState();
|
|
288
|
+
}
|
|
289
|
+
if (typeof entry.loadOverview === 'function' && typeof this.loadPluginsOverview === 'function') {
|
|
290
|
+
void Promise.resolve(this.loadPluginsOverview({ silent: true })).catch(() => {});
|
|
291
|
+
}
|
|
285
292
|
},
|
|
286
293
|
|
|
287
294
|
async loadPluginsOverview(options = {}) {
|
package/plugins/registry.mjs
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { promptTemplatesPluginMeta } from './prompt-templates/manifest.mjs';
|
|
2
2
|
import { loadPromptTemplatesOverview } from './prompt-templates/overview.mjs';
|
|
3
|
+
import { textToolsPluginMeta } from './text-tools/manifest.mjs';
|
|
4
|
+
import { loadTextToolsOverview } from './text-tools/overview.mjs';
|
|
3
5
|
|
|
4
6
|
export const pluginsRegistry = [
|
|
5
|
-
{ id:
|
|
7
|
+
{ id: promptTemplatesPluginMeta.id, meta: promptTemplatesPluginMeta, loadOverview: loadPromptTemplatesOverview },
|
|
8
|
+
{ id: textToolsPluginMeta.id, meta: textToolsPluginMeta, loadOverview: loadTextToolsOverview }
|
|
6
9
|
];
|
|
7
10
|
|
|
8
11
|
export function getFirstPluginId() {
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { textTools, DEFAULT_TEXT_TOOL_ID } from './index.mjs';
|
|
2
|
+
|
|
3
|
+
export function createTextToolsComputed() {
|
|
4
|
+
return {
|
|
5
|
+
textToolsList() {
|
|
6
|
+
const t = typeof this.t === 'function' ? this.t : null;
|
|
7
|
+
return textTools.map((tool) => ({
|
|
8
|
+
id: tool.id,
|
|
9
|
+
labelKey: tool.labelKey,
|
|
10
|
+
label: t ? t(tool.labelKey) : tool.labelKey
|
|
11
|
+
}));
|
|
12
|
+
},
|
|
13
|
+
|
|
14
|
+
textToolsOutput() {
|
|
15
|
+
const activeId = typeof this.textToolsActiveToolId === 'string' ? this.textToolsActiveToolId.trim() : '';
|
|
16
|
+
const tool = textTools.find((item) => item && item.id === activeId)
|
|
17
|
+
|| textTools.find((item) => item && item.id === DEFAULT_TEXT_TOOL_ID)
|
|
18
|
+
|| null;
|
|
19
|
+
const input = typeof this.textToolsInput === 'string' ? this.textToolsInput : '';
|
|
20
|
+
return tool && typeof tool.run === 'function' ? tool.run(input) : '';
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export const DEFAULT_TEXT_TOOL_ID = 'compress';
|
|
2
|
+
|
|
3
|
+
export function compressTextToSingleLine(input) {
|
|
4
|
+
if (typeof input !== 'string') return '';
|
|
5
|
+
return input.replace(/\s+/g, ' ').trim();
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const textTools = [
|
|
9
|
+
{
|
|
10
|
+
id: 'compress',
|
|
11
|
+
labelKey: 'plugins.textTools.tools.compress',
|
|
12
|
+
run: compressTextToSingleLine
|
|
13
|
+
}
|
|
14
|
+
];
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { textToolsPluginOwnership } from './ownership.mjs';
|
|
2
|
+
|
|
3
|
+
const textToolsBaseMeta = {
|
|
4
|
+
id: 'text-tools',
|
|
5
|
+
title: 'Text Tools',
|
|
6
|
+
titleKey: 'plugins.catalog.textTools.title',
|
|
7
|
+
description: 'Lightweight in-browser text utilities; the first tool compresses text into a single line.',
|
|
8
|
+
descriptionKey: 'plugins.catalog.textTools.description',
|
|
9
|
+
statusLabel: 'standard',
|
|
10
|
+
statusLabelKey: 'plugins.status.standard',
|
|
11
|
+
tone: 'configured'
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const textToolsPluginMeta = {
|
|
15
|
+
...textToolsBaseMeta,
|
|
16
|
+
createdBy: textToolsPluginOwnership && typeof textToolsPluginOwnership.createdBy === 'string' ? textToolsPluginOwnership.createdBy : '',
|
|
17
|
+
maintainers: textToolsPluginOwnership && Array.isArray(textToolsPluginOwnership.maintainers) ? textToolsPluginOwnership.maintainers : []
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export { textToolsPluginMeta as pluginMeta };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export function createTextToolsMethods() {
|
|
2
|
+
return {
|
|
3
|
+
async copyTextToolsOutput() {
|
|
4
|
+
const text = typeof this.textToolsOutput === 'string' ? this.textToolsOutput.trim() : '';
|
|
5
|
+
if (!text) {
|
|
6
|
+
this.showMessage(typeof this.t === 'function' ? this.t('toast.copy.empty') : 'Nothing to copy', 'info');
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
try {
|
|
10
|
+
if (navigator.clipboard && window.isSecureContext) {
|
|
11
|
+
await navigator.clipboard.writeText(text);
|
|
12
|
+
this.showMessage(typeof this.t === 'function' ? this.t('toast.copy.ok') : 'Copied', 'success');
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
} catch (_) {}
|
|
16
|
+
const ok = typeof this.fallbackCopyText === 'function' ? this.fallbackCopyText(text) : false;
|
|
17
|
+
if (ok) {
|
|
18
|
+
this.showMessage(typeof this.t === 'function' ? this.t('toast.copy.ok') : 'Copied', 'success');
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
this.showMessage(typeof this.t === 'function' ? this.t('toast.copy.fail') : 'Copy failed', 'error');
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { textTools, DEFAULT_TEXT_TOOL_ID } from './index.mjs';
|
|
2
|
+
|
|
3
|
+
export async function loadTextToolsOverview(ctx) {
|
|
4
|
+
const app = ctx && typeof ctx === 'object' ? ctx : {};
|
|
5
|
+
const activeId = typeof app.textToolsActiveToolId === 'string' ? app.textToolsActiveToolId.trim() : '';
|
|
6
|
+
const exists = textTools.some((tool) => tool && tool.id === activeId);
|
|
7
|
+
app.textToolsActiveToolId = exists ? activeId : DEFAULT_TEXT_TOOL_ID;
|
|
8
|
+
if (typeof app.textToolsInput !== 'string') {
|
|
9
|
+
app.textToolsInput = '';
|
|
10
|
+
}
|
|
11
|
+
return true;
|
|
12
|
+
}
|
package/web-ui/app.js
CHANGED
|
@@ -108,8 +108,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
108
108
|
sysChangeAxis: { mode: 'edit', ticks: [], truncated: false, totalLines: 0, added: 0, removed: 0 },
|
|
109
109
|
promptsMobileView: 'edit',
|
|
110
110
|
promptsPreviewLibsMissing: false,
|
|
111
|
-
|
|
112
|
-
|
|
111
|
+
promptsPathReferencePaths: {},
|
|
112
|
+
promptsPathReferenceLoading: false,
|
|
113
|
+
promptsPathReferenceCacheKey: '',
|
|
113
114
|
agentsPreviewHtml: '',
|
|
114
115
|
sysPromptPreviewHtml: '',
|
|
115
116
|
projectClaudeMdPath: '',
|
|
@@ -169,6 +170,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
169
170
|
pluginsActiveId: 'prompt-templates',
|
|
170
171
|
pluginsLoading: false,
|
|
171
172
|
pluginsError: '',
|
|
173
|
+
textToolsActiveToolId: '',
|
|
174
|
+
textToolsInput: '',
|
|
172
175
|
promptTemplatesListRaw: [],
|
|
173
176
|
promptTemplatesLoadedOnce: false,
|
|
174
177
|
promptTemplatesKeyword: '',
|
package/web-ui/index.html
CHANGED
|
@@ -8,7 +8,6 @@
|
|
|
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
11
|
<script src="/res/marked.min.js"></script>
|
|
13
12
|
<script src="/res/purify.min.js"></script>
|
|
14
13
|
<link rel="stylesheet" href="/web-ui/styles.css">
|
|
@@ -2,67 +2,15 @@
|
|
|
2
2
|
* Pure logic for the prompts-panel Markdown editor.
|
|
3
3
|
* No DOM access in this module so every function stays unit-testable.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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.
|
|
5
|
+
* Preview rendering delegates to the vendored marked + DOMPurify pair; raw
|
|
6
|
+
* markdown may embed arbitrary HTML, so sanitize is mandatory before the
|
|
7
|
+
* result reaches v-html.
|
|
11
8
|
*/
|
|
12
9
|
|
|
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
10
|
/**
|
|
63
11
|
* 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
|
-
*
|
|
12
|
+
* the UI can surface "libs missing" and "empty input" explicitly instead of
|
|
13
|
+
* faking a successful render.
|
|
66
14
|
*/
|
|
67
15
|
export function buildMarkdownPreviewHtml(text, marked, purify) {
|
|
68
16
|
const source = typeof text === 'string' ? text : '';
|
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Prompts-panel change axis: aggregates the
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* The diff engine itself is reused from logic.agents-diff.mjs (LCS for
|
|
7
|
-
* small inputs, window-sync for large ones) so hunk line numbers stay
|
|
8
|
-
* consistent with the red/green preview flow.
|
|
2
|
+
* Prompts-panel change axis: aggregates the rendered diff rows into
|
|
3
|
+
* clickable tick positions for the red/green diff preview view.
|
|
4
|
+
* Pure functions only.
|
|
9
5
|
*/
|
|
10
6
|
|
|
11
|
-
import { buildLineDiff } from './logic.agents-diff.mjs';
|
|
12
|
-
|
|
13
7
|
const AXIS_MAX_TICKS = 200;
|
|
14
8
|
const AXIS_MIN_TICK_HEIGHT_PERCENT = 1.2;
|
|
15
9
|
|
|
@@ -55,64 +49,6 @@ function capAxisTicks(runs, positioning) {
|
|
|
55
49
|
};
|
|
56
50
|
}
|
|
57
51
|
|
|
58
|
-
/**
|
|
59
|
-
* Build change-axis ticks for the editor view.
|
|
60
|
-
*
|
|
61
|
-
* @param {string} baseText saved original content
|
|
62
|
-
* @param {string} currentText current editor content
|
|
63
|
-
* @returns {{ ticks: Array<object>, totalLines: number, truncated: boolean,
|
|
64
|
-
* stats: { added: number, removed: number, unchanged: number } }}
|
|
65
|
-
*/
|
|
66
|
-
export function buildPromptsChangeAxis(baseText, currentText) {
|
|
67
|
-
const diff = buildLineDiff(baseText, currentText);
|
|
68
|
-
const totalLines = Math.max(1, diff.newLineCount);
|
|
69
|
-
const runs = [];
|
|
70
|
-
let run = null;
|
|
71
|
-
let newCursor = 0;
|
|
72
|
-
const flushRun = () => {
|
|
73
|
-
if (run) {
|
|
74
|
-
runs.push(run);
|
|
75
|
-
run = null;
|
|
76
|
-
}
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
for (const line of diff.lines) {
|
|
80
|
-
if (!line || !isChangedDiffType(line.type)) {
|
|
81
|
-
flushRun();
|
|
82
|
-
if (line && line.type === 'context' && typeof line.newNumber === 'number') {
|
|
83
|
-
newCursor = line.newNumber;
|
|
84
|
-
}
|
|
85
|
-
continue;
|
|
86
|
-
}
|
|
87
|
-
if (!run) {
|
|
88
|
-
run = {
|
|
89
|
-
anchor: line.type === 'add' && typeof line.newNumber === 'number'
|
|
90
|
-
? line.newNumber - 1
|
|
91
|
-
: newCursor,
|
|
92
|
-
startLine: line.type === 'add' && typeof line.newNumber === 'number'
|
|
93
|
-
? line.newNumber
|
|
94
|
-
: newCursor + 1,
|
|
95
|
-
added: 0,
|
|
96
|
-
removed: 0
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
if (line.type === 'add') {
|
|
100
|
-
run.added += 1;
|
|
101
|
-
} else {
|
|
102
|
-
run.removed += 1;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
flushRun();
|
|
106
|
-
|
|
107
|
-
const { ticks, truncated } = capAxisTicks(runs, { total: totalLines });
|
|
108
|
-
return {
|
|
109
|
-
ticks,
|
|
110
|
-
totalLines,
|
|
111
|
-
truncated,
|
|
112
|
-
stats: diff.stats
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
|
|
116
52
|
/**
|
|
117
53
|
* Build change-axis ticks for the diff preview view. Tick positions map to
|
|
118
54
|
* rendered row indexes so clicks scroll the diff list to the exact row.
|
|
@@ -164,24 +100,3 @@ export function buildPromptsDiffAxis(diffLines) {
|
|
|
164
100
|
};
|
|
165
101
|
}
|
|
166
102
|
|
|
167
|
-
/**
|
|
168
|
-
* Proportional scroll position for jumping to a line inside the editor
|
|
169
|
-
* textarea. The textarea scrolls internally (min-height contract in
|
|
170
|
-
* prompts-editor.css), so scrollTop maps linearly onto the line space.
|
|
171
|
-
*
|
|
172
|
-
* @param {number} scrollHeight textarea scrollHeight
|
|
173
|
-
* @param {number} clientHeight textarea clientHeight
|
|
174
|
-
* @param {number} lineIndex 1-based target line in the current content
|
|
175
|
-
* @param {number} totalLines total line count of the current content
|
|
176
|
-
* @returns {number} clamped scrollTop value
|
|
177
|
-
*/
|
|
178
|
-
export function computePromptsAxisScrollTop(scrollHeight, clientHeight, lineIndex, totalLines) {
|
|
179
|
-
const maxScroll = Math.max(0, (Number(scrollHeight) || 0) - (Number(clientHeight) || 0));
|
|
180
|
-
if (maxScroll <= 0) {
|
|
181
|
-
return 0;
|
|
182
|
-
}
|
|
183
|
-
const safeTotal = Math.max(1, Number(totalLines) || 1);
|
|
184
|
-
const safeLine = Math.max(1, Number(lineIndex) || 1);
|
|
185
|
-
const ratio = Math.min(1, Math.max(0, (safeLine - 1) / Math.max(1, safeTotal - 1)));
|
|
186
|
-
return Math.min(maxScroll, Math.round(maxScroll * ratio));
|
|
187
|
-
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prompts-panel "path reference" logic: build the dropdown entries that
|
|
3
|
+
* point to the OTHER instruction-file tabs' real absolute paths, and
|
|
4
|
+
* splice the rendered reference sentence into editor content at the caret.
|
|
5
|
+
*
|
|
6
|
+
* The inserted sentence is instruction-file CONTENT (not UI text), so it
|
|
7
|
+
* is intentionally a fixed English template, independent of the UI locale.
|
|
8
|
+
*
|
|
9
|
+
* Pure functions only: no Vue, no DOM, no api access.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export const PROMPT_PATH_REFERENCE_TEMPLATE = 'Always follow the rules defined in `{path}`.';
|
|
13
|
+
|
|
14
|
+
const PROMPT_PATH_REFERENCE_ENTRY_ORDER = ['codex', 'opencode', 'claudeGlobal', 'claudeProject', 'system'];
|
|
15
|
+
|
|
16
|
+
// promptsSubTab value -> entry id excluded from that tab's own dropdown.
|
|
17
|
+
const CURRENT_TAB_EXCLUDED_ENTRY = {
|
|
18
|
+
'codex': 'codex',
|
|
19
|
+
'opencode': 'opencode',
|
|
20
|
+
'claude-project': 'claudeProject',
|
|
21
|
+
'system': 'system'
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export function buildPromptPathReferenceSentence(path) {
|
|
25
|
+
const target = typeof path === 'string' ? path.trim() : '';
|
|
26
|
+
if (!target) {
|
|
27
|
+
return '';
|
|
28
|
+
}
|
|
29
|
+
return PROMPT_PATH_REFERENCE_TEMPLATE.replace('{path}', target);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Splice `text` into `content` at the given selection, replacing the
|
|
34
|
+
* selected range (same contract as typing over a selection). Out-of-range
|
|
35
|
+
* indices are clamped; invalid indices fall back to "insert at end".
|
|
36
|
+
*/
|
|
37
|
+
export function insertPromptPathReferenceText(content, selectionStart, selectionEnd, text) {
|
|
38
|
+
const source = typeof content === 'string' ? content : '';
|
|
39
|
+
const inserted = typeof text === 'string' ? text : '';
|
|
40
|
+
const fallback = source.length;
|
|
41
|
+
const rawStart = Number.isFinite(selectionStart) ? Number(selectionStart) : fallback;
|
|
42
|
+
const rawEnd = Number.isFinite(selectionEnd) ? Number(selectionEnd) : fallback;
|
|
43
|
+
const start = Math.min(Math.max(rawStart, 0), source.length);
|
|
44
|
+
const end = Math.min(Math.max(rawEnd, start), source.length);
|
|
45
|
+
return {
|
|
46
|
+
content: source.slice(0, start) + inserted + source.slice(end),
|
|
47
|
+
caret: start + inserted.length
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Build the dropdown option list for the prompts editors.
|
|
53
|
+
*
|
|
54
|
+
* config:
|
|
55
|
+
* - currentTab: promptsSubTab value ('codex' | 'opencode' | 'claude-project' | 'system')
|
|
56
|
+
* - paths: map entry id -> { path, exists, error } (server-resolved absolute paths)
|
|
57
|
+
* - t: i18n translator, signature t(key, params)
|
|
58
|
+
* - systemFile: file name shown for the Pi system-prompt entry (SYSTEM.md / APPEND_SYSTEM.md)
|
|
59
|
+
*
|
|
60
|
+
* Each option: { id, label, path, exists, disabled, disabledReason }.
|
|
61
|
+
* Missing/error entries stay in the list but render disabled, so a failed
|
|
62
|
+
* fetch is visible instead of silently hiding a tab.
|
|
63
|
+
*/
|
|
64
|
+
export function buildPromptPathReferenceOptions(config) {
|
|
65
|
+
const cfg = config || {};
|
|
66
|
+
const currentTab = CURRENT_TAB_EXCLUDED_ENTRY[cfg.currentTab] ? cfg.currentTab : 'codex';
|
|
67
|
+
const excludedEntry = CURRENT_TAB_EXCLUDED_ENTRY[currentTab];
|
|
68
|
+
const paths = cfg.paths || {};
|
|
69
|
+
const t = typeof cfg.t === 'function' ? cfg.t : ((key) => key);
|
|
70
|
+
const systemFile = typeof cfg.systemFile === 'string' && cfg.systemFile.trim()
|
|
71
|
+
? cfg.systemFile.trim()
|
|
72
|
+
: 'SYSTEM.md';
|
|
73
|
+
|
|
74
|
+
const labelKeys = {
|
|
75
|
+
codex: 'prompts.pathReference.codex',
|
|
76
|
+
opencode: 'prompts.pathReference.opencode',
|
|
77
|
+
claudeGlobal: 'prompts.pathReference.claudeGlobal',
|
|
78
|
+
claudeProject: 'prompts.pathReference.claudeProject',
|
|
79
|
+
system: 'prompts.pathReference.system'
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const options = [];
|
|
83
|
+
for (const id of PROMPT_PATH_REFERENCE_ENTRY_ORDER) {
|
|
84
|
+
if (id === excludedEntry) {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
const entry = paths[id] || null;
|
|
88
|
+
let label = id === 'system'
|
|
89
|
+
? t(labelKeys[id], { file: systemFile })
|
|
90
|
+
: t(labelKeys[id]);
|
|
91
|
+
let disabled = false;
|
|
92
|
+
let disabledReason = '';
|
|
93
|
+
if (id === 'claudeProject' && !(entry && entry.path)) {
|
|
94
|
+
disabled = true;
|
|
95
|
+
disabledReason = t('prompts.pathReference.projectMissing');
|
|
96
|
+
}
|
|
97
|
+
if (!entry) {
|
|
98
|
+
disabled = true;
|
|
99
|
+
if (!disabledReason) {
|
|
100
|
+
disabledReason = t('prompts.pathReference.notLoaded');
|
|
101
|
+
}
|
|
102
|
+
} else if (entry.error) {
|
|
103
|
+
disabled = true;
|
|
104
|
+
disabledReason = entry.error;
|
|
105
|
+
} else if (entry.path && entry.exists === false) {
|
|
106
|
+
label += ' · ' + t('common.notExistsWillCreateOnSave');
|
|
107
|
+
}
|
|
108
|
+
options.push({
|
|
109
|
+
id,
|
|
110
|
+
label,
|
|
111
|
+
path: entry && entry.path ? entry.path : '',
|
|
112
|
+
exists: entry ? !!entry.exists : false,
|
|
113
|
+
disabled,
|
|
114
|
+
disabledReason
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
return options;
|
|
118
|
+
}
|
|
@@ -1020,6 +1020,7 @@ export function createAgentsMethods(options = {}) {
|
|
|
1020
1020
|
},
|
|
1021
1021
|
|
|
1022
1022
|
loadPromptsTabContent() {
|
|
1023
|
+
if (typeof this.loadPromptsPathReferences === "function") this.loadPromptsPathReferences();
|
|
1023
1024
|
if (this.promptsSubTab === 'system') {
|
|
1024
1025
|
if (typeof this.loadSystemPrompt === 'function') this.loadSystemPrompt();
|
|
1025
1026
|
} else {
|