Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions tests/unit/web-ui-behavior-parity.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,9 @@ test('captured bundled app skeleton only exposes expected data key drift versus
'showEditClaudeConfigKey',
'showEditProviderKey',
'toolConfigPermissionSaving',
'toolConfigPermissions'
'toolConfigPermissions',
'promptsHint',
'promptsSubTab'
] : [
'appVersion',
'appLatestVersion',
Expand Down Expand Up @@ -413,7 +415,9 @@ test('captured bundled app skeleton only exposes expected data key drift versus
'showAddClaudeConfigKey',
'showAddProviderKey',
'showEditClaudeConfigKey',
'showEditProviderKey'
'showEditProviderKey',
'promptsHint',
'promptsSubTab'
];
const allowedMissingCurrentKeys = [
'localProxyRunning',
Expand Down Expand Up @@ -579,6 +583,8 @@ test('captured bundled app skeleton only exposes expected data key drift versus
'resetTaskOrchestrationDraft',
'appendTaskWorkflowId',
'openClaudeMdEditor',
'switchPromptsSubTab',
'loadPromptsContent',
'saveNavState',
'isLocalBridgeExcluded',
'loadLocalBridgeExcluded',
Expand Down
17 changes: 16 additions & 1 deletion web-ui/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ document.addEventListener('DOMContentLoaded', () => {
showOpenclawConfigModal: false,
showConfigTemplateModal: false,
showAgentsModal: false,
promptsSubTab: 'codex',
promptsHint: '',
showSkillsModal: false,
showHealthCheckModal: false,
showCodexBridgePoolModal: false,
Expand Down Expand Up @@ -486,7 +488,7 @@ document.addEventListener('DOMContentLoaded', () => {
}
{
const NAV_STATE_STORAGE_KEY = 'codexmateNavState.v1';
const mainTabSet = new Set(['dashboard', 'config', 'sessions', 'usage', 'orchestration', 'market', 'plugins', 'docs', 'settings', 'trash']);
const mainTabSet = new Set(['dashboard', 'config', 'sessions', 'usage', 'orchestration', 'market', 'plugins', 'docs', 'settings', 'trash', 'prompts']);
let restored = null;
try {
const raw = localStorage.getItem(NAV_STATE_STORAGE_KEY) || '';
Expand Down Expand Up @@ -710,6 +712,19 @@ document.addEventListener('DOMContentLoaded', () => {
this.clearSessionTimelineRefs();
},

watch: {
mainTab(newTab) {
if (newTab === 'prompts' && typeof this.loadPromptsContent === 'function') {
this.loadPromptsContent();
}
},
promptsSubTab() {
if (this.mainTab === 'prompts' && typeof this.loadPromptsContent === 'function') {
this.loadPromptsContent();
}
}
},

computed: createAppComputed(),
methods: createAppMethods()
};
Expand Down
1 change: 1 addition & 0 deletions web-ui/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
<!-- @include ./partials/index/panel-trash.html -->
<!-- @include ./partials/index/panel-market.html -->
<!-- @include ./partials/index/panel-plugins.html -->
<!-- @include ./partials/index/panel-prompts.html -->
<!-- @include ./partials/index/layout-footer.html -->
<!-- @include ./partials/index/modals-basic.html -->
<!-- @include ./partials/index/modal-webhook.html -->
Expand Down
3 changes: 3 additions & 0 deletions web-ui/modules/app.computed.main-tabs.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ export function createMainTabsComputed() {
if (this.mainTab === 'plugins') return this.t('kicker.plugins');
if (this.mainTab === 'docs') return this.t('kicker.docs');
if (this.mainTab === 'trash') return this.t('kicker.trash');
if (this.mainTab === 'prompts') return this.t('kicker.prompts');
return this.t('kicker.settings');
},
mainTabTitle() {
Expand All @@ -163,6 +164,7 @@ export function createMainTabsComputed() {
if (this.mainTab === 'plugins') return this.t('title.plugins');
if (this.mainTab === 'docs') return this.t('title.docs');
if (this.mainTab === 'trash') return this.t('settings.trash.title');
if (this.mainTab === 'prompts') return this.t('title.prompts');
return this.t('title.settings');
},
mainTabSubtitle() {
Expand All @@ -175,6 +177,7 @@ export function createMainTabsComputed() {
if (this.mainTab === 'plugins') return this.t('subtitle.plugins');
if (this.mainTab === 'docs') return this.t('subtitle.docs');
if (this.mainTab === 'trash') return this.t('settings.trash.meta');
if (this.mainTab === 'prompts') return this.t('subtitle.prompts');
return this.t('subtitle.settings');
},
taskOrchestrationSelectedRun() {
Expand Down
26 changes: 25 additions & 1 deletion web-ui/modules/app.computed.session.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -777,7 +777,7 @@ export function createSessionComputed() {
? this.sessionUsageDaily
: null;
if (!daily || !Array.isArray(daily.rows) || daily.rows.length === 0) {
return { points: [], labels: [], linePath: '', areaPath: '', width: 800, maxTokens: 0 };
return { points: [], labels: [], linePath: '', areaPath: '', width: 800, maxTokens: 0, yTicks: [] };
}

const rows = daily.rows;
Expand Down Expand Up @@ -806,6 +806,29 @@ export function createSessionComputed() {
const selectedKey = this.sessionsUsageSelectedDayKey;
const selectedPoint = points.find(p => p.key === selectedKey) || points[points.length - 1] || null;

const yTicks = [];
if (maxTokens > 0) {
const rawStep = maxTokens / 4;
const magnitude = Math.pow(10, Math.floor(Math.log10(rawStep)));
const residual = rawStep / magnitude;
const niceStep = residual <= 1.5 ? magnitude : (residual <= 3.5 ? 2 * magnitude : 5 * magnitude);
const tickCount = Math.ceil(maxTokens / niceStep);
for (let i = 0; i <= tickCount; i++) {
const value = i * niceStep;
if (value > maxTokens + niceStep * 0.5) break;
const normalized = value / maxTokens;
const y = padding.top + chartHeight - (normalized * chartHeight);
yTicks.push({
value,
label: formatCompactUsageSummaryNumber(value),
y,
percent: (((height - y) / height) * 100).toFixed(1)
});
}
} else {
yTicks.push({ value: 0, label: '0', y: padding.top + chartHeight, percent: (((height - (padding.top + chartHeight)) / height) * 100).toFixed(1) });
}

return {
points,
labels: rows.map((row, index) => ({
Expand All @@ -817,6 +840,7 @@ export function createSessionComputed() {
width,
height,
maxTokens,
yTicks,
hoverX: selectedPoint ? selectedPoint.x : 0,
hoverY: selectedPoint ? selectedPoint.y : 0
};
Expand Down
55 changes: 54 additions & 1 deletion web-ui/modules/app.methods.agents.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -640,12 +640,65 @@ export function createAgentsMethods(options = {}) {
? 'CLAUDE.md 已保存'
: (this.agentsContext === 'openclaw' ? 'OpenClaw AGENTS.md 已保存' : 'AGENTS.md 已保存'));
this.showMessage(successLabel, 'success');
this.closeAgentsModal({ force: true });
if (this.mainTab === 'prompts') {
this.loadPromptsContent();
} else {
this.closeAgentsModal({ force: true });
}
} catch (e) {
this.showMessage(this.t('toast.save.fail'), 'error');
} finally {
this.agentsSaving = false;
}
},

switchPromptsSubTab(subTab) {
const normalized = subTab === 'claude-md' ? 'claude-md' : 'codex';
if (this.promptsSubTab === normalized) {
this.loadPromptsContent();
return;
}
this.promptsSubTab = normalized;
},

async loadPromptsContent() {
const requestToken = issueLatestRequestToken(this, '_agentsOpenRequestToken');
this.agentsLoading = true;
this.resetAgentsDiffState();
try {
const isClaude = this.promptsSubTab === 'claude-md';
const action = isClaude ? 'get-claude-md-file' : 'get-agents-file';
const res = await api(action);
if (!isLatestRequestToken(this, '_agentsOpenRequestToken', requestToken)) {
return;
}
if (res.error) {
this.showMessage(res.error, 'error');
return;
}
this.agentsContent = res.content || '';
this.agentsOriginalContent = this.agentsContent;
this.agentsPath = res.path || '';
this.agentsExists = !!res.exists;
this.agentsLineEnding = res.lineEnding === '\r\n' ? '\r\n' : '\n';
this.agentsContext = isClaude ? 'claude-md' : 'codex';
const t = typeof this.t === 'function' ? this.t : null;
const tr = (key, fallback) => (t ? t(key) : fallback);
if (isClaude) {
this.promptsHint = tr('modal.agents.hint.claudeMd', '保存后会写入 ~/.claude/CLAUDE.md。');
} else {
this.promptsHint = tr('modal.agents.hint.default', '保存后会写入目标 AGENTS.md(与 config.toml 同级)。');
}
} catch (e) {
if (!isLatestRequestToken(this, '_agentsOpenRequestToken', requestToken)) {
return;
}
this.showMessage(this.t('toast.load.fail'), 'error');
} finally {
if (isLatestRequestToken(this, '_agentsOpenRequestToken', requestToken)) {
this.agentsLoading = false;
}
}
}
};
}
3 changes: 2 additions & 1 deletion web-ui/modules/app.methods.navigation.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
'plugins',
'docs',
'settings',
'trash'
'trash',
'prompts'
]);
const loadDoctorOverview = async (vm, options = {}) => {
if (!vm || typeof vm !== 'object') return false;
Expand Down
6 changes: 6 additions & 0 deletions web-ui/modules/app.methods.session-actions.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,12 @@ export function createSessionActionMethods(options = {}) {
this.showMessage(this.t('toast.copy.fail'), 'error');
},

openSessionLink(session) {
const url = this.buildSessionStandaloneUrl(session);
if (!url) { this.showMessage('无法生成链接', 'error'); return; }
window.open(url, '_blank', 'noopener');
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
},

getSessionFilePath(session) {
const filePath = typeof session?.filePath === 'string' ? session.filePath.trim() : '';
return filePath;
Expand Down
24 changes: 19 additions & 5 deletions web-ui/modules/i18n/locales/en.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ const en = Object.freeze({
'common.apply': 'Apply',
'common.applying': 'Applying...',
'common.confirming': 'Confirming...',
'common.preview': 'Preview',
'common.previewing': 'Previewing...',
'common.refreshFromText': 'Refresh from text',
'common.backToEdit': 'Back to edit',
'common.selectAll': 'Select all',
Expand Down Expand Up @@ -129,12 +131,14 @@ const en = Object.freeze({
'tab.market': 'Skills',
'tab.plugins': 'Plugins',
'tab.settings': 'Settings',
'tab.prompts': 'Prompts',

// Side rail section titles
'side.overview': 'Overview',
'side.docs': 'Docs',
'side.config': 'Config',
'side.sessions': 'Sessions',
'side.prompts': 'Prompts',
'side.plugins': 'Plugins',
'side.system': 'System',
'side.orchestration': 'Tasks',
Expand Down Expand Up @@ -164,6 +168,10 @@ const en = Object.freeze({
'side.config.openclaw.meta': 'JSON5 / AGENTS',
'side.sessions.browser': 'Session Browser',
'side.sessions.browser.meta': 'Browse / Export / Cleanup',
'side.prompts.agents': 'AGENTS.md',
'side.prompts.agents.meta': 'Codex prompt file',
'side.prompts.claude': 'CLAUDE.md',
'side.prompts.claude.meta': 'Claude prompt file',
'side.plugins.tools': 'Prompt Tools',
'side.plugins.tools.meta': 'Templates / Variables',
'side.plugins.templatesCount': '{count} templates',
Expand All @@ -182,6 +190,7 @@ const en = Object.freeze({
'kicker.docs': 'Docs',
'kicker.trash': 'Trash',
'kicker.settings': 'Settings',
'kicker.prompts': 'Prompts',

'title.dashboard': 'Dashboard / Doctor',
'title.config': 'Local Configuration Console',
Expand All @@ -192,6 +201,7 @@ const en = Object.freeze({
'title.plugins': 'Plugins & Templates',
'title.docs': 'CLI Install & Docs',
'title.settings': 'System & Data Settings',
'title.prompts': 'Prompt Files Editor',

'subtitle.dashboard': 'Aggregate status and diagnostics.',
'subtitle.config': 'Manage local configs and models.',
Expand All @@ -202,6 +212,9 @@ const en = Object.freeze({
'subtitle.plugins': 'Manage reusable prompt templates and plugins.',
'subtitle.docs': 'CLI install commands and troubleshooting.',
'subtitle.settings': 'Manage downloads, directories, and trash.',
'subtitle.prompts': 'Edit AGENTS.md and CLAUDE.md.',
'prompts.subTab.codex': 'AGENTS.md (Codex)',
'prompts.subTab.claude': 'CLAUDE.md (Claude)',
'dashboard.doctor.title': 'Doctor',
'dashboard.doctor.runChecks': 'Run checks',
'dashboard.doctor.checking': 'Checking...',
Expand Down Expand Up @@ -468,7 +481,7 @@ const en = Object.freeze({
'diff.hint.busy': 'Generating diff or applying. Actions are temporarily disabled.',
'diff.hint.failedBack': 'Diff preview failed. Go back to edit and retry.',
'diff.hint.noChangesBack': 'No changes detected. Go back to edit or cancel.',
'diff.hint.previewMode': 'Preview mode. Click “Apply” to write or “Back to edit” to continue.',
'diff.hint.previewMode': 'Preview mode. Click “Save” to write or “Back to edit” to continue.',

'modal.agents.export': 'Export',
'modal.agents.copy': 'Copy',
Expand All @@ -477,13 +490,13 @@ const en = Object.freeze({
'modal.agents.targetFile': 'Target file',
'modal.agents.contentLabel': 'AGENTS.md content',
'modal.agents.placeholder': 'Edit AGENTS.md here',
'modal.agents.unsaved.previewModeHint': 'Preview mode: changes are not saved until you click “Apply”.',
'modal.agents.unsaved.previewModeHint': 'Preview mode: changes are not saved until you click “Save”.',
'modal.agents.unsaved.detectedHint': 'Unsaved changes detected: save before closing or applying.',
'modal.agents.hint.shortcuts': 'Shortcut: Esc (back to edit in preview, close in edit).',
'modal.agents.hint.twoStepSave': 'Two-step save: “Confirm” to preview diff, then “Apply” to save.',
'modal.agents.hint.twoStepSave': 'Two-step save: “Preview” to review diff, then “Save” to write.',
'diff.tooLargeSkip': 'Content too large. Line diff preview skipped.',
'diff.viewHint.preview': 'Preview mode. Click “Apply” to save or “Back to edit” to continue.',
'diff.viewHint.truncated': 'Preview skipped due to size. Click “Apply” to save or “Back to edit” to continue.',
'diff.viewHint.preview': 'Preview mode. Click “Save” to write or “Back to edit” to continue.',
'diff.viewHint.truncated': 'Preview skipped due to size. Click “Save” to write or “Back to edit” to continue.',

// Skills modal
'modal.skills.title': 'Skills manager',
Expand Down Expand Up @@ -624,6 +637,7 @@ const en = Object.freeze({
'sessions.preview.importNative.failedWithReason': 'Import to native failed: {reason}',
'sessions.preview.copyLink': 'Copy link',
'sessions.preview.copyPath': 'Copy path',
'sessions.preview.openLink': 'Open link',
'sessions.preview.loadingBody': 'Loading session content...',
'sessions.preview.emptyMsgs': 'No messages to display',
'sessions.preview.rendering': 'Rendering session content...',
Expand Down
Loading
Loading