codexmate 0.0.37 → 0.0.39
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/analytics-export-args.js +68 -0
- package/cli/builtin-proxy.js +626 -207
- package/cli/openai-bridge.js +541 -210
- package/cli/session-usage.js +187 -1
- package/cli.js +84 -2
- package/package.json +1 -1
- package/web-ui/app.js +12 -3
- package/web-ui/modules/app.computed.main-tabs.mjs +37 -30
- package/web-ui/modules/app.methods.claude-config.mjs +111 -9
- package/web-ui/modules/app.methods.openclaw-editing.mjs +48 -0
- package/web-ui/modules/app.methods.openclaw-persist.mjs +13 -7
- package/web-ui/modules/app.methods.providers.mjs +36 -10
- package/web-ui/modules/app.methods.runtime.mjs +76 -1
- package/web-ui/modules/app.methods.startup-claude.mjs +1 -0
- package/web-ui/modules/config-mode.computed.mjs +3 -3
- package/web-ui/modules/i18n.dict.mjs +13 -0
- package/web-ui/modules/i18n.mjs +65 -16
- package/web-ui/modules/skills.methods.mjs +1 -1
- package/web-ui/partials/index/layout-header.html +16 -46
- package/web-ui/partials/index/modal-openclaw-config.html +135 -71
- package/web-ui/partials/index/modal-webhook.html +8 -8
- package/web-ui/partials/index/modals-basic.html +56 -16
- package/web-ui/partials/index/panel-config-claude.html +20 -20
- package/web-ui/partials/index/panel-config-codex.html +5 -5
- package/web-ui/partials/index/panel-config-openclaw.html +70 -64
- package/web-ui/partials/index/panel-dashboard.html +62 -77
- package/web-ui/partials/index/panel-settings.html +28 -7
- package/web-ui/partials/index/panel-trash.html +14 -14
- package/web-ui/res/web-ui-render.precompiled.js +846 -539
- package/web-ui/styles/controls-forms.css +6 -0
- package/web-ui/styles/dashboard.css +46 -14
- package/web-ui/styles/layout-shell.css +45 -0
- package/web-ui/styles/navigation-panels.css +3 -3
- package/web-ui/styles/openclaw-structured.css +383 -33
- package/web-ui/styles/responsive.css +68 -0
- package/web-ui/styles/sessions-usage.css +105 -9
- package/web-ui/styles/settings-panel.css +4 -0
- package/web-ui/partials/index/panel-config-codex.html.bak +0 -337
package/cli/session-usage.js
CHANGED
|
@@ -113,6 +113,192 @@ async function listSessionUsageCore(params = {}, deps = {}) {
|
|
|
113
113
|
return normalizedSessions.filter(Boolean);
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
+
function readNonNegativeInteger(value) {
|
|
117
|
+
const numeric = Number(value);
|
|
118
|
+
if (!Number.isFinite(numeric) || numeric < 0) {
|
|
119
|
+
return 0;
|
|
120
|
+
}
|
|
121
|
+
return Math.floor(numeric);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function parseUsageExportDate(value, boundary) {
|
|
125
|
+
if (value === undefined || value === null || value === '') {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
if (value instanceof Date) {
|
|
129
|
+
const time = value.getTime();
|
|
130
|
+
return Number.isFinite(time) ? time : NaN;
|
|
131
|
+
}
|
|
132
|
+
const raw = String(value).trim();
|
|
133
|
+
if (!raw) {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
const dateOnly = raw.match(/^(\d{4})-(\d{2})-(\d{2})$/);
|
|
137
|
+
if (dateOnly) {
|
|
138
|
+
const year = Number(dateOnly[1]);
|
|
139
|
+
const month = Number(dateOnly[2]) - 1;
|
|
140
|
+
const day = Number(dateOnly[3]);
|
|
141
|
+
const start = Date.UTC(year, month, day);
|
|
142
|
+
const normalized = new Date(start);
|
|
143
|
+
if (!Number.isFinite(start)
|
|
144
|
+
|| normalized.getUTCFullYear() !== year
|
|
145
|
+
|| normalized.getUTCMonth() !== month
|
|
146
|
+
|| normalized.getUTCDate() !== day) {
|
|
147
|
+
return NaN;
|
|
148
|
+
}
|
|
149
|
+
return boundary === 'end' ? start + 24 * 60 * 60 * 1000 : start;
|
|
150
|
+
}
|
|
151
|
+
const parsed = Date.parse(raw);
|
|
152
|
+
return Number.isFinite(parsed) ? parsed : NaN;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function formatUsageExportDay(timestamp) {
|
|
156
|
+
return new Date(timestamp).toISOString().slice(0, 10);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function normalizeUsageExportFormat(value) {
|
|
160
|
+
const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
161
|
+
return normalized === 'json' ? 'json' : 'csv';
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function normalizeUsageExportModelFilters(params = {}) {
|
|
165
|
+
const raw = [];
|
|
166
|
+
const push = (value) => {
|
|
167
|
+
if (Array.isArray(value)) {
|
|
168
|
+
value.forEach(push);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (typeof value !== 'string') {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
value.split(',').forEach((item) => {
|
|
175
|
+
const normalized = item.trim().toLowerCase();
|
|
176
|
+
if (normalized) raw.push(normalized);
|
|
177
|
+
});
|
|
178
|
+
};
|
|
179
|
+
push(params.model);
|
|
180
|
+
push(params.models);
|
|
181
|
+
// API-facing alias: callers may pass modelType when they reuse usage filters
|
|
182
|
+
// outside the CLI flag surface.
|
|
183
|
+
push(params.modelType);
|
|
184
|
+
return [...new Set(raw)];
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function sessionMatchesUsageExportModelFilters(session, filters) {
|
|
188
|
+
if (!filters.length) {
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
const models = [];
|
|
192
|
+
if (typeof session.model === 'string') models.push(session.model);
|
|
193
|
+
if (Array.isArray(session.models)) models.push(...session.models.filter(item => typeof item === 'string'));
|
|
194
|
+
const normalizedModels = models.map(item => item.trim().toLowerCase()).filter(Boolean);
|
|
195
|
+
return filters.some(filter => normalizedModels.some(model => model === filter || model.includes(filter)));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function escapeUsageCsvCell(value) {
|
|
199
|
+
const raw = value === undefined || value === null ? '' : String(value);
|
|
200
|
+
if (!/[",\n\r]/.test(raw)) {
|
|
201
|
+
return raw;
|
|
202
|
+
}
|
|
203
|
+
return `"${raw.replace(/"/g, '""')}"`;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function serializeUsageExportRowsToCsv(rows) {
|
|
207
|
+
const columns = ['date', 'model', 'tokens', 'sessions'];
|
|
208
|
+
const lines = [columns.join(',')];
|
|
209
|
+
for (const row of rows) {
|
|
210
|
+
lines.push(columns.map(column => escapeUsageCsvCell(row[column])).join(','));
|
|
211
|
+
}
|
|
212
|
+
return lines.join('\r\n') + '\r\n';
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function buildUsageExportRows(sessions = [], params = {}) {
|
|
216
|
+
const fromTime = parseUsageExportDate(params.from ?? params.startDate, 'start');
|
|
217
|
+
const toTime = parseUsageExportDate(params.to ?? params.endDate, 'end');
|
|
218
|
+
if (Number.isNaN(fromTime)) {
|
|
219
|
+
return { error: 'Invalid from date' };
|
|
220
|
+
}
|
|
221
|
+
if (Number.isNaN(toTime)) {
|
|
222
|
+
return { error: 'Invalid to date' };
|
|
223
|
+
}
|
|
224
|
+
if (fromTime !== null && toTime !== null && fromTime >= toTime) {
|
|
225
|
+
return { error: 'from date must be before to date' };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const modelFilters = normalizeUsageExportModelFilters(params);
|
|
229
|
+
const groups = new Map();
|
|
230
|
+
for (const session of Array.isArray(sessions) ? sessions : []) {
|
|
231
|
+
if (!session || typeof session !== 'object' || Array.isArray(session)) {
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
if (!sessionMatchesUsageExportModelFilters(session, modelFilters)) {
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
const timestamp = Date.parse(session.updatedAt || session.createdAt || '');
|
|
238
|
+
if (!Number.isFinite(timestamp)) {
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
if (fromTime !== null && timestamp < fromTime) {
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
if (toTime !== null && timestamp >= toTime) {
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
const model = typeof session.model === 'string' && session.model.trim()
|
|
248
|
+
? session.model.trim()
|
|
249
|
+
: (Array.isArray(session.models) && typeof session.models[0] === 'string' ? session.models[0].trim() : 'unknown');
|
|
250
|
+
if (!model) {
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
const date = formatUsageExportDay(timestamp);
|
|
254
|
+
const key = `${date}\u0000${model}`;
|
|
255
|
+
const current = groups.get(key) || { date, model, tokens: 0, sessions: 0 };
|
|
256
|
+
current.tokens += readNonNegativeInteger(session.totalTokens ?? session.tokens);
|
|
257
|
+
current.sessions += 1;
|
|
258
|
+
groups.set(key, current);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const rows = [...groups.values()].sort((a, b) => {
|
|
262
|
+
const dateCompare = a.date.localeCompare(b.date);
|
|
263
|
+
if (dateCompare !== 0) return dateCompare;
|
|
264
|
+
return a.model.localeCompare(b.model);
|
|
265
|
+
});
|
|
266
|
+
return { rows };
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
async function exportSessionUsageCore(params = {}, deps = {}) {
|
|
270
|
+
const listSessionUsage = typeof deps.listSessionUsage === 'function'
|
|
271
|
+
? deps.listSessionUsage
|
|
272
|
+
: (options) => listSessionUsageCore(options, deps);
|
|
273
|
+
const sessions = Array.isArray(params.sessions)
|
|
274
|
+
? params.sessions
|
|
275
|
+
: await listSessionUsage({
|
|
276
|
+
source: params.source,
|
|
277
|
+
limit: params.limit,
|
|
278
|
+
forceRefresh: !!params.forceRefresh
|
|
279
|
+
});
|
|
280
|
+
const built = buildUsageExportRows(sessions, params);
|
|
281
|
+
if (built.error) {
|
|
282
|
+
return { error: built.error };
|
|
283
|
+
}
|
|
284
|
+
const format = normalizeUsageExportFormat(params.format);
|
|
285
|
+
const rows = built.rows;
|
|
286
|
+
const content = format === 'json'
|
|
287
|
+
? JSON.stringify({ rows }, null, 2) + '\n'
|
|
288
|
+
: serializeUsageExportRowsToCsv(rows);
|
|
289
|
+
const extension = format === 'json' ? 'json' : 'csv';
|
|
290
|
+
return {
|
|
291
|
+
format,
|
|
292
|
+
mimeType: format === 'json' ? 'application/json' : 'text/csv',
|
|
293
|
+
fileName: `usage-export.${extension}`,
|
|
294
|
+
rows,
|
|
295
|
+
content
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
|
|
116
299
|
module.exports = {
|
|
117
|
-
listSessionUsageCore
|
|
300
|
+
listSessionUsageCore,
|
|
301
|
+
buildUsageExportRows,
|
|
302
|
+
exportSessionUsageCore,
|
|
303
|
+
serializeUsageExportRowsToCsv
|
|
118
304
|
};
|
package/cli.js
CHANGED
|
@@ -162,7 +162,8 @@ const {
|
|
|
162
162
|
extractSessionDetailPreviewFromTailText,
|
|
163
163
|
extractSessionDetailPreviewFromFileFast
|
|
164
164
|
} = require('./lib/cli-sessions');
|
|
165
|
-
const { listSessionUsageCore } = require('./cli/session-usage');
|
|
165
|
+
const { listSessionUsageCore, exportSessionUsageCore } = require('./cli/session-usage');
|
|
166
|
+
const { parseAnalyticsExportArgs } = require('./cli/analytics-export-args');
|
|
166
167
|
const {
|
|
167
168
|
readBundledWebUiCss,
|
|
168
169
|
readBundledWebUiHtml,
|
|
@@ -2080,12 +2081,22 @@ function addProviderToConfig(params = {}) {
|
|
|
2080
2081
|
const name = typeof params.name === 'string' ? params.name.trim() : '';
|
|
2081
2082
|
const url = typeof params.url === 'string' ? params.url.trim() : '';
|
|
2082
2083
|
const key = typeof params.key === 'string' ? params.key.trim() : '';
|
|
2084
|
+
const requireModel = !!params.requireModel;
|
|
2085
|
+
const fallbackModel = (() => {
|
|
2086
|
+
if (requireModel) return '';
|
|
2087
|
+
const list = readModels();
|
|
2088
|
+
return Array.isArray(list) && typeof list[0] === 'string' ? list[0].trim() : '';
|
|
2089
|
+
})();
|
|
2090
|
+
const model = typeof params.model === 'string' && params.model.trim()
|
|
2091
|
+
? params.model.trim()
|
|
2092
|
+
: fallbackModel;
|
|
2083
2093
|
const useTransform = !!params.useTransform;
|
|
2084
2094
|
const allowManaged = !!params.allowManaged;
|
|
2085
2095
|
const normalizedUrl = normalizeBaseUrl(url);
|
|
2086
2096
|
|
|
2087
2097
|
if (!name) return { error: '名称不能为空' };
|
|
2088
2098
|
if (!url) return { error: 'URL 不能为空' };
|
|
2099
|
+
if (!model) return { error: '模型名称不能为空' };
|
|
2089
2100
|
if (!isValidProviderName(name)) {
|
|
2090
2101
|
return { error: '名称仅支持字母/数字/._-' };
|
|
2091
2102
|
}
|
|
@@ -2162,6 +2173,7 @@ function addProviderToConfig(params = {}) {
|
|
|
2162
2173
|
`wire_api = "responses"`,
|
|
2163
2174
|
`requires_openai_auth = ${requiresOpenaiAuth ? 'true' : 'false'}`,
|
|
2164
2175
|
`preferred_auth_method = "${safeKey}"`,
|
|
2176
|
+
`models = [{ id = "${escapeTomlBasicString(model)}", name = "${escapeTomlBasicString(model)}" }]`,
|
|
2165
2177
|
...extraLines,
|
|
2166
2178
|
`request_max_retries = 4`,
|
|
2167
2179
|
`stream_max_retries = 10`,
|
|
@@ -2172,6 +2184,13 @@ function addProviderToConfig(params = {}) {
|
|
|
2172
2184
|
|
|
2173
2185
|
try {
|
|
2174
2186
|
writeConfig(newContent);
|
|
2187
|
+
const models = readModels();
|
|
2188
|
+
if (!models.includes(model)) {
|
|
2189
|
+
writeModels([...models, model]);
|
|
2190
|
+
}
|
|
2191
|
+
const currentModels = readCurrentModels();
|
|
2192
|
+
currentModels[name] = model;
|
|
2193
|
+
writeCurrentModels(currentModels);
|
|
2175
2194
|
} catch (e) {
|
|
2176
2195
|
return { error: `写入配置失败: ${e.message}` };
|
|
2177
2196
|
}
|
|
@@ -5204,6 +5223,12 @@ async function listSessionUsage(params = {}) {
|
|
|
5204
5223
|
});
|
|
5205
5224
|
}
|
|
5206
5225
|
|
|
5226
|
+
async function exportSessionUsage(params = {}) {
|
|
5227
|
+
return exportSessionUsageCore(params, {
|
|
5228
|
+
listSessionUsage
|
|
5229
|
+
});
|
|
5230
|
+
}
|
|
5231
|
+
|
|
5207
5232
|
function listSessionPaths(params = {}) {
|
|
5208
5233
|
const source = typeof params.source === 'string' ? params.source.trim().toLowerCase() : '';
|
|
5209
5234
|
if (source && source !== 'codex' && source !== 'claude' && source !== 'gemini' && source !== 'codebuddy' && source !== 'all') {
|
|
@@ -9796,6 +9821,47 @@ async function cmdExportSession(args = []) {
|
|
|
9796
9821
|
console.log();
|
|
9797
9822
|
}
|
|
9798
9823
|
|
|
9824
|
+
function printAnalyticsUsage() {
|
|
9825
|
+
console.log('\n用法:');
|
|
9826
|
+
console.log(' codexmate analytics export [--format csv|json] [--from YYYY-MM-DD] [--to YYYY-MM-DD] [--model <MODEL>] [--source <codex|claude|gemini|codebuddy|all>] [--output <PATH|->] [-o <PATH|->]');
|
|
9827
|
+
console.log('');
|
|
9828
|
+
}
|
|
9829
|
+
|
|
9830
|
+
async function cmdAnalytics(args = []) {
|
|
9831
|
+
const subcommand = args[0];
|
|
9832
|
+
if (subcommand !== 'export') {
|
|
9833
|
+
printAnalyticsUsage();
|
|
9834
|
+
process.exit(subcommand ? 1 : 0);
|
|
9835
|
+
}
|
|
9836
|
+
const parsed = parseAnalyticsExportArgs(args.slice(1));
|
|
9837
|
+
if (parsed.options.help) {
|
|
9838
|
+
printAnalyticsUsage();
|
|
9839
|
+
process.exit(0);
|
|
9840
|
+
}
|
|
9841
|
+
if (parsed.error) {
|
|
9842
|
+
console.error('错误:', parsed.error);
|
|
9843
|
+
printAnalyticsUsage();
|
|
9844
|
+
process.exit(1);
|
|
9845
|
+
}
|
|
9846
|
+
|
|
9847
|
+
const result = await exportSessionUsage(parsed.options);
|
|
9848
|
+
if (result && result.error) {
|
|
9849
|
+
console.error('导出失败:', result.error);
|
|
9850
|
+
process.exit(1);
|
|
9851
|
+
}
|
|
9852
|
+
const output = parsed.options.output || (result && result.fileName) || `usage-export.${parsed.options.format}`;
|
|
9853
|
+
if (output === '-') {
|
|
9854
|
+
process.stdout.write(result && result.content ? result.content : '');
|
|
9855
|
+
return;
|
|
9856
|
+
}
|
|
9857
|
+
const outputPath = path.resolve(process.cwd(), output);
|
|
9858
|
+
ensureDir(path.dirname(outputPath));
|
|
9859
|
+
fs.writeFileSync(outputPath, result && result.content ? result.content : '', 'utf-8');
|
|
9860
|
+
console.log(`\n✓ Usage 已导出: ${outputPath}`);
|
|
9861
|
+
console.log(` 格式: ${result.format}; rows: ${Array.isArray(result.rows) ? result.rows.length : 0}`);
|
|
9862
|
+
console.log();
|
|
9863
|
+
}
|
|
9864
|
+
|
|
9799
9865
|
function parseStartOptions(args = []) {
|
|
9800
9866
|
const options = { host: '', noBrowser: false };
|
|
9801
9867
|
if (!Array.isArray(args)) {
|
|
@@ -10818,7 +10884,7 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
|
|
|
10818
10884
|
result = buildConfigTemplateDiff(params || {});
|
|
10819
10885
|
break;
|
|
10820
10886
|
case 'add-provider':
|
|
10821
|
-
result = addProviderToConfig(params || {});
|
|
10887
|
+
result = addProviderToConfig({ ...(params || {}), requireModel: true });
|
|
10822
10888
|
break;
|
|
10823
10889
|
case 'update-provider':
|
|
10824
10890
|
result = updateProviderInConfig(params || {});
|
|
@@ -11077,6 +11143,20 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
|
|
|
11077
11143
|
}
|
|
11078
11144
|
}
|
|
11079
11145
|
break;
|
|
11146
|
+
case 'export-sessions-usage':
|
|
11147
|
+
{
|
|
11148
|
+
const usageParams = isPlainObject(params) ? params : {};
|
|
11149
|
+
const source = typeof usageParams.source === 'string' ? usageParams.source.trim().toLowerCase() : '';
|
|
11150
|
+
if (source && source !== 'codex' && source !== 'claude' && source !== 'gemini' && source !== 'codebuddy' && source !== 'all') {
|
|
11151
|
+
result = { error: 'Invalid source. Must be codex, claude, gemini, codebuddy, or all' };
|
|
11152
|
+
} else {
|
|
11153
|
+
result = await exportSessionUsage({
|
|
11154
|
+
...usageParams,
|
|
11155
|
+
source: source || 'all'
|
|
11156
|
+
});
|
|
11157
|
+
}
|
|
11158
|
+
}
|
|
11159
|
+
break;
|
|
11080
11160
|
case 'list-session-paths':
|
|
11081
11161
|
{
|
|
11082
11162
|
const source = typeof params.source === 'string' ? params.source.trim().toLowerCase() : '';
|
|
@@ -15960,6 +16040,7 @@ function printMainHelp() {
|
|
|
15960
16040
|
console.log(' codexmate delete-model <模型> 删除模型');
|
|
15961
16041
|
console.log(' codexmate workflow <list|get|validate|run|runs> MCP 工作流中心');
|
|
15962
16042
|
console.log(' codexmate task <plan|run|runs|queue|retry|cancel|logs> 本地任务编排');
|
|
16043
|
+
console.log(' codexmate analytics export [--format csv|json] [--from YYYY-MM-DD] [--to YYYY-MM-DD] [--model <MODEL>] [--output <PATH|->] [-o <PATH|->] 导出 Usage 数据');
|
|
15963
16044
|
console.log(' codexmate run [--host <HOST>] [--no-browser] 启动 Web 界面');
|
|
15964
16045
|
console.log(' codexmate update [--check] 检查并快速更新工具');
|
|
15965
16046
|
console.log(' codexmate codex [参数...] [--follow-up <文本>|--queued-follow-up <文本> 可重复] 等同于 codex --yolo');
|
|
@@ -16052,6 +16133,7 @@ async function main() {
|
|
|
16052
16133
|
case 'proxy': await cmdProxy(args.slice(1)); break;
|
|
16053
16134
|
case 'workflow': await cmdWorkflow(args.slice(1)); break;
|
|
16054
16135
|
case 'task': await cmdTask(args.slice(1)); break;
|
|
16136
|
+
case 'analytics': await cmdAnalytics(args.slice(1)); break;
|
|
16055
16137
|
case 'run': cmdStart(parseStartOptions(args.slice(1))); break;
|
|
16056
16138
|
case 'update': await cmdToolUpdate(args.slice(1)); break;
|
|
16057
16139
|
case 'start':
|
package/package.json
CHANGED
package/web-ui/app.js
CHANGED
|
@@ -62,11 +62,13 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
62
62
|
messageType: '',
|
|
63
63
|
showAddModal: false,
|
|
64
64
|
showEditModal: false,
|
|
65
|
+
showAddProviderKey: false,
|
|
65
66
|
showEditProviderKey: false,
|
|
66
67
|
showModelModal: false,
|
|
67
68
|
showModelListModal: false,
|
|
68
69
|
showClaudeConfigModal: false,
|
|
69
70
|
showEditConfigModal: false,
|
|
71
|
+
showAddClaudeConfigKey: false,
|
|
70
72
|
showEditClaudeConfigKey: false,
|
|
71
73
|
showOpenclawConfigModal: false,
|
|
72
74
|
showConfigTemplateModal: false,
|
|
@@ -268,7 +270,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
268
270
|
installRegistryPreset: 'default',
|
|
269
271
|
installRegistryCustom: '',
|
|
270
272
|
installStatusTargets: null,
|
|
271
|
-
newProvider: { name: '', url: '', key: '',
|
|
273
|
+
newProvider: { name: '', url: '', key: '', model: '', useTransform: false },
|
|
272
274
|
resetConfigLoading: false,
|
|
273
275
|
editingProvider: { name: '', url: '', key: '', readOnly: false, nonEditable: false },
|
|
274
276
|
newModelName: '',
|
|
@@ -293,7 +295,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
293
295
|
currentOpenclawConfig: '',
|
|
294
296
|
openclawConfigs: {
|
|
295
297
|
'默认配置': {
|
|
296
|
-
content: DEFAULT_OPENCLAW_TEMPLATE
|
|
298
|
+
content: DEFAULT_OPENCLAW_TEMPLATE,
|
|
299
|
+
isDefault: true
|
|
297
300
|
}
|
|
298
301
|
},
|
|
299
302
|
openclawEditing: { name: '', content: '', lockName: false },
|
|
@@ -343,6 +346,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
343
346
|
overrideModels: true,
|
|
344
347
|
showKey: false
|
|
345
348
|
},
|
|
349
|
+
openclawAccordionStep: 1,
|
|
350
|
+
openclawValidation: {
|
|
351
|
+
providerName: { valid: true, message: '' },
|
|
352
|
+
modelId: { valid: true, message: '' }
|
|
353
|
+
},
|
|
346
354
|
openclawAgentsList: [],
|
|
347
355
|
openclawProviders: [],
|
|
348
356
|
openclawMissingProviders: [],
|
|
@@ -567,7 +575,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
567
575
|
: { content: DEFAULT_OPENCLAW_TEMPLATE };
|
|
568
576
|
const normalized = {
|
|
569
577
|
'默认配置': {
|
|
570
|
-
content: typeof defaultEntry.content === 'string' ? defaultEntry.content : DEFAULT_OPENCLAW_TEMPLATE
|
|
578
|
+
content: typeof defaultEntry.content === 'string' ? defaultEntry.content : DEFAULT_OPENCLAW_TEMPLATE,
|
|
579
|
+
isDefault: true
|
|
571
580
|
}
|
|
572
581
|
};
|
|
573
582
|
for (const [name, value] of Object.entries(source)) {
|
|
@@ -43,93 +43,97 @@ function readTaskOrchestrationDraftMetrics(taskOrchestration) {
|
|
|
43
43
|
};
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
function
|
|
46
|
+
function translateTaskText(t, key, fallback, params = null) {
|
|
47
|
+
return typeof t === 'function' ? t(key, params) : fallback;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function createTaskDraftChecklist(metrics, t = null) {
|
|
47
51
|
const workflowReady = metrics.engine !== 'workflow' || metrics.workflowCount > 0;
|
|
48
52
|
const scopeReady = metrics.hasNotes || !metrics.allowWrite;
|
|
49
53
|
const previewReady = metrics.hasPlan && metrics.planIssues.length === 0;
|
|
50
54
|
return [
|
|
51
55
|
{
|
|
52
56
|
key: 'target',
|
|
53
|
-
label: '目标',
|
|
57
|
+
label: translateTaskText(t, 'orchestration.readiness.target.label', '目标'),
|
|
54
58
|
done: metrics.hasTarget,
|
|
55
|
-
detail: metrics.hasTarget ? '已写目标' : '还没写目标'
|
|
59
|
+
detail: metrics.hasTarget ? translateTaskText(t, 'orchestration.readiness.target.done', '已写目标') : translateTaskText(t, 'orchestration.readiness.target.missing', '还没写目标')
|
|
56
60
|
},
|
|
57
61
|
{
|
|
58
62
|
key: 'engine',
|
|
59
|
-
label: metrics.engine === 'workflow' ? 'Workflow' : '执行策略',
|
|
63
|
+
label: metrics.engine === 'workflow' ? 'Workflow' : translateTaskText(t, 'orchestration.readiness.engine.label', '执行策略'),
|
|
60
64
|
done: workflowReady,
|
|
61
65
|
detail: metrics.engine === 'workflow'
|
|
62
|
-
? (metrics.workflowCount > 0 ? `已选 ${metrics.workflowCount} 个 Workflow
|
|
63
|
-
: '使用 Codex 规划节点'
|
|
66
|
+
? (metrics.workflowCount > 0 ? translateTaskText(t, 'orchestration.readiness.workflow.done', `已选 ${metrics.workflowCount} 个 Workflow`, { count: metrics.workflowCount }) : translateTaskText(t, 'orchestration.readiness.workflow.missing', '还没选 Workflow ID'))
|
|
67
|
+
: translateTaskText(t, 'orchestration.readiness.engine.codex', '使用 Codex 规划节点')
|
|
64
68
|
},
|
|
65
69
|
{
|
|
66
70
|
key: 'scope',
|
|
67
|
-
label: '边界',
|
|
71
|
+
label: translateTaskText(t, 'orchestration.readiness.scope.label', '边界'),
|
|
68
72
|
done: scopeReady,
|
|
69
73
|
detail: metrics.hasNotes
|
|
70
|
-
? '已补充说明'
|
|
71
|
-
: (metrics.allowWrite ? '建议补说明后再写入' : '当前是只读,可直接试')
|
|
74
|
+
? translateTaskText(t, 'orchestration.readiness.scope.done', '已补充说明')
|
|
75
|
+
: (metrics.allowWrite ? translateTaskText(t, 'orchestration.readiness.scope.writeHint', '建议补说明后再写入') : translateTaskText(t, 'orchestration.readiness.scope.readonlyHint', '当前是只读,可直接试'))
|
|
72
76
|
},
|
|
73
77
|
{
|
|
74
78
|
key: 'preview',
|
|
75
|
-
label: '预览',
|
|
79
|
+
label: translateTaskText(t, 'orchestration.readiness.preview.label', '预览'),
|
|
76
80
|
done: previewReady,
|
|
77
81
|
detail: !metrics.hasPlan
|
|
78
|
-
? '还没生成计划'
|
|
79
|
-
: (metrics.planIssues.length > 0 ? `有 ${metrics.planIssues.length}
|
|
82
|
+
? translateTaskText(t, 'orchestration.readiness.preview.missing', '还没生成计划')
|
|
83
|
+
: (metrics.planIssues.length > 0 ? translateTaskText(t, 'orchestration.readiness.preview.blocked', `有 ${metrics.planIssues.length} 个阻塞项`, { count: metrics.planIssues.length }) : translateTaskText(t, 'orchestration.readiness.preview.ready', `计划可用,${metrics.planNodeCount} 个节点`, { count: metrics.planNodeCount }))
|
|
80
84
|
}
|
|
81
85
|
];
|
|
82
86
|
}
|
|
83
87
|
|
|
84
|
-
function createTaskDraftReadiness(metrics) {
|
|
88
|
+
function createTaskDraftReadiness(metrics, t = null) {
|
|
85
89
|
if (!metrics.hasTarget) {
|
|
86
90
|
return {
|
|
87
91
|
tone: 'neutral',
|
|
88
|
-
title: '先写目标',
|
|
89
|
-
summary: '先把想完成的结果写清楚,再让编排器拆节点。'
|
|
92
|
+
title: translateTaskText(t, 'orchestration.readiness.empty.title', '先写目标'),
|
|
93
|
+
summary: translateTaskText(t, 'orchestration.readiness.empty.summary', '先把想完成的结果写清楚,再让编排器拆节点。')
|
|
90
94
|
};
|
|
91
95
|
}
|
|
92
96
|
if (metrics.engine === 'workflow' && metrics.workflowCount === 0) {
|
|
93
97
|
return {
|
|
94
98
|
tone: 'warn',
|
|
95
|
-
title: '缺少 Workflow',
|
|
96
|
-
summary: '你已经选了 Workflow 模式,但还没指定可复用流程。'
|
|
99
|
+
title: translateTaskText(t, 'orchestration.readiness.workflow.title', '缺少 Workflow'),
|
|
100
|
+
summary: translateTaskText(t, 'orchestration.readiness.workflow.summary', '你已经选了 Workflow 模式,但还没指定可复用流程。')
|
|
97
101
|
};
|
|
98
102
|
}
|
|
99
103
|
if (!metrics.hasPlan) {
|
|
100
104
|
return {
|
|
101
105
|
tone: 'warn',
|
|
102
|
-
title: '建议先预览',
|
|
103
|
-
summary: '草稿已成形,先生成一次计划,确认节点和依赖再执行。'
|
|
106
|
+
title: translateTaskText(t, 'orchestration.readiness.preview.title', '建议先预览'),
|
|
107
|
+
summary: translateTaskText(t, 'orchestration.readiness.preview.summary', '草稿已成形,先生成一次计划,确认节点和依赖再执行。')
|
|
104
108
|
};
|
|
105
109
|
}
|
|
106
110
|
if (metrics.planIssues.length > 0) {
|
|
107
111
|
return {
|
|
108
112
|
tone: 'error',
|
|
109
|
-
title: '预览有阻塞',
|
|
110
|
-
summary: `当前计划里还有 ${metrics.planIssues.length}
|
|
113
|
+
title: translateTaskText(t, 'orchestration.readiness.blocked.title', '预览有阻塞'),
|
|
114
|
+
summary: translateTaskText(t, 'orchestration.readiness.blocked.summary', `当前计划里还有 ${metrics.planIssues.length} 个阻塞项,先处理它们。`, { count: metrics.planIssues.length })
|
|
111
115
|
};
|
|
112
116
|
}
|
|
113
117
|
if (metrics.planWarnings.length > 0) {
|
|
114
118
|
return {
|
|
115
119
|
tone: 'warn',
|
|
116
|
-
title: '可以执行,但有提醒',
|
|
117
|
-
summary: `计划已生成,但还有 ${metrics.planWarnings.length}
|
|
120
|
+
title: translateTaskText(t, 'orchestration.readiness.warn.title', '可以执行,但有提醒'),
|
|
121
|
+
summary: translateTaskText(t, 'orchestration.readiness.warn.summary', `计划已生成,但还有 ${metrics.planWarnings.length} 条提醒值得先看一眼。`, { count: metrics.planWarnings.length })
|
|
118
122
|
};
|
|
119
123
|
}
|
|
120
124
|
if (metrics.dryRun) {
|
|
121
125
|
return {
|
|
122
126
|
tone: 'success',
|
|
123
|
-
title: '适合先预演',
|
|
124
|
-
summary: '现在可以安全地跑一次仅预演,先看结果再决定是否真实执行。'
|
|
127
|
+
title: translateTaskText(t, 'orchestration.readiness.dryRun.title', '适合先预演'),
|
|
128
|
+
summary: translateTaskText(t, 'orchestration.readiness.dryRun.summary', '现在可以安全地跑一次仅预演,先看结果再决定是否真实执行。')
|
|
125
129
|
};
|
|
126
130
|
}
|
|
127
131
|
return {
|
|
128
132
|
tone: 'success',
|
|
129
|
-
title: '可以执行',
|
|
133
|
+
title: translateTaskText(t, 'orchestration.readiness.ready.title', '可以执行'),
|
|
130
134
|
summary: metrics.followUpCount > 0
|
|
131
|
-
? `主目标和收尾动作都已具备,可以直接执行或入队。`
|
|
132
|
-
: '主目标已经够清楚了,可以直接执行或入队。'
|
|
135
|
+
? translateTaskText(t, 'orchestration.readiness.ready.withFollowUps', `主目标和收尾动作都已具备,可以直接执行或入队。`)
|
|
136
|
+
: translateTaskText(t, 'orchestration.readiness.ready.summary', '主目标已经够清楚了,可以直接执行或入队。')
|
|
133
137
|
};
|
|
134
138
|
}
|
|
135
139
|
|
|
@@ -144,6 +148,7 @@ export function createMainTabsComputed() {
|
|
|
144
148
|
if (this.mainTab === 'market') return this.t('kicker.market');
|
|
145
149
|
if (this.mainTab === 'plugins') return this.t('kicker.plugins');
|
|
146
150
|
if (this.mainTab === 'docs') return this.t('kicker.docs');
|
|
151
|
+
if (this.mainTab === 'trash') return this.t('kicker.trash');
|
|
147
152
|
return this.t('kicker.settings');
|
|
148
153
|
},
|
|
149
154
|
mainTabTitle() {
|
|
@@ -155,6 +160,7 @@ export function createMainTabsComputed() {
|
|
|
155
160
|
if (this.mainTab === 'market') return this.t('title.market');
|
|
156
161
|
if (this.mainTab === 'plugins') return this.t('title.plugins');
|
|
157
162
|
if (this.mainTab === 'docs') return this.t('title.docs');
|
|
163
|
+
if (this.mainTab === 'trash') return this.t('settings.trash.title');
|
|
158
164
|
return this.t('title.settings');
|
|
159
165
|
},
|
|
160
166
|
mainTabSubtitle() {
|
|
@@ -166,6 +172,7 @@ export function createMainTabsComputed() {
|
|
|
166
172
|
if (this.mainTab === 'market') return this.t('subtitle.market');
|
|
167
173
|
if (this.mainTab === 'plugins') return this.t('subtitle.plugins');
|
|
168
174
|
if (this.mainTab === 'docs') return this.t('subtitle.docs');
|
|
175
|
+
if (this.mainTab === 'trash') return this.t('settings.trash.meta');
|
|
169
176
|
return this.t('subtitle.settings');
|
|
170
177
|
},
|
|
171
178
|
taskOrchestrationSelectedRun() {
|
|
@@ -196,10 +203,10 @@ export function createMainTabsComputed() {
|
|
|
196
203
|
return readTaskOrchestrationDraftMetrics(this.taskOrchestration);
|
|
197
204
|
},
|
|
198
205
|
taskOrchestrationDraftChecklist() {
|
|
199
|
-
return createTaskDraftChecklist(this.taskOrchestrationDraftMetrics);
|
|
206
|
+
return createTaskDraftChecklist(this.taskOrchestrationDraftMetrics, this.t && this.t.bind(this));
|
|
200
207
|
},
|
|
201
208
|
taskOrchestrationDraftReadiness() {
|
|
202
|
-
return createTaskDraftReadiness(this.taskOrchestrationDraftMetrics);
|
|
209
|
+
return createTaskDraftReadiness(this.taskOrchestrationDraftMetrics, this.t && this.t.bind(this));
|
|
203
210
|
}
|
|
204
211
|
};
|
|
205
212
|
}
|