mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:10:44 +00:00
fix(control-ui): filter archived sessions (#77132)
Summary: - Use sessions.list as the Control UI source of truth for available sessions. - Hide archived sessions by default and keep the Sessions filter UI explicit, compact, and reversible. - Preserve session-change behavior, checkpoint details, generated i18n output, and chat/session picker consistency. Validation: - pnpm ui:i18n:check - pnpm test ui/src/styles/components.test.ts ui/src/ui/views/sessions.test.ts ui/src/ui/app-render.helpers.node.test.ts - pnpm test ui/src/ui/controllers/sessions.test.ts ui/src/ui/app-gateway.sessions.node.test.ts ui/src/ui/views/sessions.test.ts ui/src/styles/components.test.ts ui/src/ui/app-render.helpers.node.test.ts - pnpm tsgo:test:ui - Blacksmith Testbox: pnpm check:changed -- <PR paths>
This commit is contained in:
@@ -466,6 +466,12 @@ Docs: https://docs.openclaw.ai
|
||||
- Control UI: allow deployments to configure grouped chat message max-width with a validated `gateway.controlUi.chatMessageMaxWidth` setting instead of patching bundled CSS after upgrades. Fixes #67935. Thanks @xiew4589-lang.
|
||||
- Control UI/Cron: ignore malformed persisted cron rows without valid payloads before they enter UI state and guard stale cron render paths, preventing blank Control UI sections after a bad cron snapshot. Fixes #55047 and #54439; supersedes #54550 and #54552.
|
||||
- Control UI/sessions: bound the default Sessions tab query to recent activity and fewer rows, avoiding expensive full-history loads while keeping filters editable. Fixes #76050. (#76051) Thanks @Neomail2.
|
||||
- Control UI/sessions: apply reliable `sessions.changed` snapshots in-place and refetch only for partial events, avoiding redundant `sessions.list` regeneration during active session updates.
|
||||
- Control UI/sessions: explain the Sessions filter controls with hover tooltips and raise the default list limit to 200 rows.
|
||||
- Control UI/sessions: expand compaction checkpoint details from checkpoint-bearing rows and keep token totals on one line.
|
||||
- Control UI/sessions: group Active and Limit filters together, streamline source toggles, and make the filter section collapsible.
|
||||
- Control UI/sessions: shorten filter tooltips and remove duplicate browser-native tooltip popovers.
|
||||
- Control UI/sessions: keep the expanded filter controls on one row on large screens.
|
||||
- Gateway/channels: cap startup fanout at four channel/account handoffs and recover from Bonjour ciao self-probe races, reducing Windows startup stalls with many Telegram accounts. Fixes #75687.
|
||||
- Gateway/sessions: keep `sessions.list` polling responsive on large session stores by reusing list-safe session cache/indexes and returning a lightweight compaction checkpoint preview instead of heavyweight summaries. Thanks @rolandrscheel.
|
||||
- Control UI/Gateway: keep long-running dashboard WebSocket sessions alive with protocol pings and keep Stop available after reconnect or reload by recovering session-scoped active-run abort state. Fixes #70991. Thanks @alexandre-leng.
|
||||
|
||||
@@ -953,7 +953,35 @@ async function formatGeneratedTypeScript(filePath: string, source: string): Prom
|
||||
rejectOnFailure: true,
|
||||
},
|
||||
);
|
||||
return result.stdout;
|
||||
return restoreReplacementCorruptedStringLiterals(source, result.stdout);
|
||||
}
|
||||
|
||||
function restoreReplacementCorruptedStringLiterals(source: string, formatted: string): string {
|
||||
if (!formatted.includes("\uFFFD") || source.includes("\uFFFD")) {
|
||||
return formatted;
|
||||
}
|
||||
|
||||
const stringLiteralPattern = /"(?:\\.|[^"\\])*"/gu;
|
||||
const sourceLiterals = [...source.matchAll(stringLiteralPattern)];
|
||||
const formattedLiterals = [...formatted.matchAll(stringLiteralPattern)];
|
||||
if (sourceLiterals.length !== formattedLiterals.length) {
|
||||
return formatted;
|
||||
}
|
||||
|
||||
let output = "";
|
||||
let cursor = 0;
|
||||
for (const [index, formattedLiteral] of formattedLiterals.entries()) {
|
||||
const replacement = sourceLiterals[index]?.[0];
|
||||
const literal = formattedLiteral[0];
|
||||
const start = formattedLiteral.index;
|
||||
if (replacement === undefined || start === undefined) {
|
||||
return formatted;
|
||||
}
|
||||
output += formatted.slice(cursor, start);
|
||||
output += literal.includes("\uFFFD") && !replacement.includes("\uFFFD") ? replacement : literal;
|
||||
cursor = start + literal.length;
|
||||
}
|
||||
return `${output}${formatted.slice(cursor)}`;
|
||||
}
|
||||
|
||||
type PendingPrompt = {
|
||||
|
||||
@@ -1023,8 +1023,7 @@ async function agentCommandInternal(
|
||||
allowTransientCooldownProbe: runOptions?.allowTransientCooldownProbe,
|
||||
sessionHasHistory:
|
||||
!isNewSession || (await attemptExecutionRuntime.sessionFileHasContent(sessionFile)),
|
||||
suppressPromptPersistenceOnRetry:
|
||||
isFallbackRetry && currentTurnUserMessagePersisted,
|
||||
suppressPromptPersistenceOnRetry: isFallbackRetry && currentTurnUserMessagePersisted,
|
||||
onUserMessagePersisted: () => {
|
||||
currentTurnUserMessagePersisted = true;
|
||||
},
|
||||
|
||||
@@ -391,9 +391,7 @@ describe("promptRemoteGatewayConfig", () => {
|
||||
const next = await promptRemoteGatewayConfig(cfg, prompter);
|
||||
|
||||
expect(next.gateway?.remote?.token).toBe("preexisting-remote-token");
|
||||
expect(text).not.toHaveBeenCalledWith(
|
||||
expect.objectContaining({ message: "Gateway token" }),
|
||||
);
|
||||
expect(text).not.toHaveBeenCalledWith(expect.objectContaining({ message: "Gateway token" }));
|
||||
});
|
||||
|
||||
it("keeps an existing remote gateway password when user confirms via masked-preview prompt", async () => {
|
||||
@@ -429,8 +427,6 @@ describe("promptRemoteGatewayConfig", () => {
|
||||
const next = await promptRemoteGatewayConfig(cfg, prompter);
|
||||
|
||||
expect(next.gateway?.remote?.password).toBe("preexisting-remote-password");
|
||||
expect(text).not.toHaveBeenCalledWith(
|
||||
expect.objectContaining({ message: "Gateway password" }),
|
||||
);
|
||||
expect(text).not.toHaveBeenCalledWith(expect.objectContaining({ message: "Gateway password" }));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-05-03T18:28:38.725Z",
|
||||
"fallbackKeys": [
|
||||
"sessionsView.activeTooltip",
|
||||
"sessionsView.globalTooltip",
|
||||
"sessionsView.hideFilters",
|
||||
"sessionsView.limitTooltip",
|
||||
"sessionsView.showArchived",
|
||||
"sessionsView.showArchivedTooltip",
|
||||
"sessionsView.showFilters",
|
||||
"sessionsView.sourceFilters",
|
||||
"sessionsView.unknownTooltip"
|
||||
],
|
||||
"generatedAt": "2026-05-04T05:56:58.631Z",
|
||||
"locale": "ar",
|
||||
"model": "gpt-5.5",
|
||||
"provider": "openai",
|
||||
"sourceHash": "d994d064e7880cb40c055c5201db0b9045c15f6abf37fac9b317e25e82b6b7b6",
|
||||
"totalKeys": 991,
|
||||
"translatedKeys": 991,
|
||||
"sourceHash": "66b99bdd39fc9dae0476c0e2fd2c0392f068bc3f7f91b532b04a106f14a3f3df",
|
||||
"totalKeys": 1001,
|
||||
"translatedKeys": 992,
|
||||
"workflow": 1
|
||||
}
|
||||
|
||||
@@ -451,6 +451,7 @@
|
||||
{"cache_key":"71fa5870018c723d0179b28b99583ceea6f765f50a78e9f2e368ba789ba33949","model":"gpt-5.5","provider":"openai","segment_id":"overview.palette.placeholder","source_path":"ui/src/i18n/locales/ar.ts","src_lang":"en","text":"Type a command…","text_hash":"96489e83623d94011df336e2a4d1a62eaf2b14913aecb4845bb11e13d88733e7","tgt_lang":"ar","translated":"اكتب أمرًا…","updated_at":"2026-04-29T17:38:06.592Z"}
|
||||
{"cache_key":"72388d2a619110968d6f8a2f5afc4668f1ad99eb8eb679baa8476c31e67d1a86","model":"gpt-5.5","provider":"openai","segment_id":"dreaming.stats.grounded","source_path":"ui/src/i18n/locales/ar.ts","src_lang":"en","text":"Grounded","text_hash":"5b6f73f04fe1a6af2dc43bebb45478862b0bd1fe079eed12f8bc2000a59bf68c","tgt_lang":"ar","translated":"مؤرض","updated_at":"2026-04-29T17:38:25.053Z"}
|
||||
{"cache_key":"723e0ef6630a94e28022dafbf14a431eba2e1bec2bad42011f383acd2e08ed5c","model":"gpt-5.5","provider":"openai","segment_id":"cron.quickCreate.namePlaceholder","source_path":"ui/src/i18n/locales/ar.ts","src_lang":"en","text":"e.g., Morning inbox check","text_hash":"149ef2da53b9dbcd4cb688e9d86fdb3780f50a88886ae841004fa3993ccd4e9f","tgt_lang":"ar","translated":"مثلاً، فحص البريد الوارد الصباحي","updated_at":"2026-04-29T20:14:54.627Z"}
|
||||
{"cache_key":"724ff4c9622ee7b4744f5527c6c5492e9197779bdac4b681b11e270501a1decc","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.filters","source_path":"ui/src/i18n/locales/ar.ts","src_lang":"en","text":"Filters","text_hash":"546ebb8eb993ea561029d9febd84c363bdb09010bb2cb915a8287762b76b9a64","tgt_lang":"ar","translated":"عوامل التصفية","updated_at":"2026-04-29T17:38:45.816Z"}
|
||||
{"cache_key":"725383f534697e9ecbb3f625af9a3a29d9091a464f1b6b04105eaf650726b32c","model":"gpt-5.5","provider":"openai","segment_id":"cron.errors.invalidRunTime","source_path":"ui/src/i18n/locales/ar.ts","src_lang":"en","text":"Invalid run time.","text_hash":"51465fa3cb94966411a49d8d1972fe997ac028fd249e05df55db8a2179975b48","tgt_lang":"ar","translated":"وقت التشغيل غير صالح.","updated_at":"2026-04-29T17:40:46.558Z"}
|
||||
{"cache_key":"73100cf996326ecfd714c375a917d94ef845d4fca16f760f840d8401a8ef54aa","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.loadingCheckpoints","source_path":"ui/src/i18n/locales/ar.ts","src_lang":"en","text":"Loading checkpoints…","text_hash":"28f4a96c140d1effc48388a1f67e650dfcf892df7003d38cd0ebeab22d65ba34","tgt_lang":"ar","translated":"جارٍ تحميل نقاط التحقق…","updated_at":"2026-04-29T20:14:44.891Z"}
|
||||
{"cache_key":"73d44e33e51a34c6665fa8783255ff3311fff166eb58493b4429fddc054773b7","model":"gpt-5.5","provider":"openai","segment_id":"agents.context.openFilesTab","source_path":"ui/src/i18n/locales/ar.ts","src_lang":"en","text":"Open Files tab","text_hash":"423a21a02bc6f7c21d6c85e30f0bc0827c497b6bc4123767375edd67f463c7bf","tgt_lang":"ar","translated":"فتح تبويب الملفات","updated_at":"2026-04-29T19:26:14.154Z"}
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-05-03T18:28:22.358Z",
|
||||
"fallbackKeys": [
|
||||
"sessionsView.activeTooltip",
|
||||
"sessionsView.globalTooltip",
|
||||
"sessionsView.hideFilters",
|
||||
"sessionsView.limitTooltip",
|
||||
"sessionsView.showArchived",
|
||||
"sessionsView.showArchivedTooltip",
|
||||
"sessionsView.showFilters",
|
||||
"sessionsView.sourceFilters",
|
||||
"sessionsView.unknownTooltip"
|
||||
],
|
||||
"generatedAt": "2026-05-04T05:56:56.445Z",
|
||||
"locale": "de",
|
||||
"model": "gpt-5.5",
|
||||
"provider": "openai",
|
||||
"sourceHash": "d994d064e7880cb40c055c5201db0b9045c15f6abf37fac9b317e25e82b6b7b6",
|
||||
"totalKeys": 991,
|
||||
"translatedKeys": 991,
|
||||
"sourceHash": "66b99bdd39fc9dae0476c0e2fd2c0392f068bc3f7f91b532b04a106f14a3f3df",
|
||||
"totalKeys": 1001,
|
||||
"translatedKeys": 992,
|
||||
"workflow": 1
|
||||
}
|
||||
|
||||
@@ -69,6 +69,7 @@
|
||||
{"cache_key":"180bef4481fcfd7f7aaedc2c8b7727dbbc66a3bdbb23614986a21d614d8589bc","model":"gpt-5.4","provider":"openai","segment_id":"cron.form.timezoneHelp","source_path":"ui/src/i18n/locales/de.ts","src_lang":"en","text":"Pick a common timezone or enter any valid IANA timezone.","text_hash":"a56f7de52b59658470a0ed6ae48112ff64e57b49b0e77e10d707d95b6822878b","tgt_lang":"de","translated":"Wähle eine gängige Zeitzone oder gib eine beliebige gültige IANA-Zeitzone ein.","updated_at":"2026-04-05T17:12:43.392Z"}
|
||||
{"cache_key":"181f6584bb9d14faec276e604018c371ebb2a4e5caf9c63bd442830b22ac9f31","model":"gpt-5.4","provider":"openai","segment_id":"common.hideAdvanced","source_path":"ui/src/i18n/locales/de.ts","src_lang":"en","text":"Hide Advanced","text_hash":"e6292a1e4e93ffea9b4e609d464a6c935bb10a8dafe6593795a9b43aed8ebcca","tgt_lang":"de","translated":"Erweitert ausblenden","updated_at":"2026-04-06T02:47:31.228Z"}
|
||||
{"cache_key":"182d7e4f797aff2943aed4f3accb1c8c454180d72d03b7317d657388214f18f8","model":"gpt-5.4","provider":"openai","segment_id":"overview.access.showToken","source_path":"ui/src/i18n/locales/de.ts","src_lang":"en","text":"Show token","text_hash":"2faef0ba40dc420f67de983b6c1be8f0f4b9b60f18409f2d2368b53b3c28a7bd","tgt_lang":"de","translated":"Token anzeigen","updated_at":"2026-04-20T06:26:17.877Z"}
|
||||
{"cache_key":"18397a1b25aee54adad75650a359aed3cf472aa25e28fb5e3ea0c0c56420938b","model":"gpt-5.4","provider":"openai","segment_id":"sessionsView.filters","source_path":"ui/src/i18n/locales/de.ts","src_lang":"en","text":"Filters","text_hash":"546ebb8eb993ea561029d9febd84c363bdb09010bb2cb915a8287762b76b9a64","tgt_lang":"de","translated":"Filter","updated_at":"2026-04-05T17:10:45.990Z"}
|
||||
{"cache_key":"183f83b1b860e812736b0dbea82a75738aaf9c9830630bda90d4d368544e7cbb","model":"gpt-5.4","provider":"openai","segment_id":"overview.stats.cron","source_path":"ui/src/i18n/locales/de.ts","src_lang":"en","text":"Cron","text_hash":"dd9d24965dbedc026915308732b77c1af68dcf52d3c0ca2421b1fdb0d197aca1","tgt_lang":"de","translated":"Cron","updated_at":"2026-04-06T02:59:27.518Z"}
|
||||
{"cache_key":"186e64f5c7705d37d6753c6235c2509a4e0f67fea0c9c04b9aaec80237f0f057","model":"gpt-5.4","provider":"openai","segment_id":"dreaming.phrases.weavingShortTerm","source_path":"ui/src/i18n/locales/de.ts","src_lang":"en","text":"weaving short-term into long-term…","text_hash":"1d64d672d34876489dc3885e05677abcae21d06bfa1d25ed87001721e441bd12","tgt_lang":"de","translated":"Kurzfristiges wird ins Langfristige eingewebt…","updated_at":"2026-04-06T02:48:28.029Z"}
|
||||
{"cache_key":"1967ab05b305f0272c9bdf6795a64b4edb4e63a3a6b9c9f08d96e8f58ce51376","model":"gpt-5.4","provider":"openai","segment_id":"cron.form.modelHelp","source_path":"ui/src/i18n/locales/de.ts","src_lang":"en","text":"Start typing to pick a known model, or enter a custom one.","text_hash":"6ebac6c51e0da79d2ad76fe3d1395dff0c7a51ec7aa0d6b39ac38b0ba9fd8724","tgt_lang":"de","translated":"Beginne zu tippen, um ein bekanntes Modell auszuwählen, oder gib ein benutzerdefiniertes ein.","updated_at":"2026-04-05T17:12:53.251Z"}
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-05-03T18:28:25.316Z",
|
||||
"fallbackKeys": [
|
||||
"sessionsView.activeTooltip",
|
||||
"sessionsView.globalTooltip",
|
||||
"sessionsView.hideFilters",
|
||||
"sessionsView.limitTooltip",
|
||||
"sessionsView.showArchived",
|
||||
"sessionsView.showArchivedTooltip",
|
||||
"sessionsView.showFilters",
|
||||
"sessionsView.sourceFilters",
|
||||
"sessionsView.unknownTooltip"
|
||||
],
|
||||
"generatedAt": "2026-05-04T05:56:56.952Z",
|
||||
"locale": "es",
|
||||
"model": "gpt-5.5",
|
||||
"provider": "openai",
|
||||
"sourceHash": "d994d064e7880cb40c055c5201db0b9045c15f6abf37fac9b317e25e82b6b7b6",
|
||||
"totalKeys": 991,
|
||||
"translatedKeys": 991,
|
||||
"sourceHash": "66b99bdd39fc9dae0476c0e2fd2c0392f068bc3f7f91b532b04a106f14a3f3df",
|
||||
"totalKeys": 1001,
|
||||
"translatedKeys": 992,
|
||||
"workflow": 1
|
||||
}
|
||||
|
||||
@@ -549,6 +549,7 @@
|
||||
{"cache_key":"ec05bc5196a50da0fb5e56605552e8aafe47454b47b090fa5ab25a70a1764aaa","model":"gpt-5.4","provider":"openai","segment_id":"nodes.binding.defaultBindingHint","source_path":"ui/src/i18n/locales/es.ts","src_lang":"en","text":"Used when agents do not override a node binding.","text_hash":"a61df1a47c1edd595446e4954df0f8a0a3f84ee01ad399ef66c92cf03a75826d","tgt_lang":"es","translated":"Se usa cuando los agentes no reemplazan una vinculación de nodo.","updated_at":"2026-04-06T02:48:58.952Z"}
|
||||
{"cache_key":"ec22fce103367ebd0215aea5541c1a56f4ce3f5412de22396ae8f885745074ea","model":"gpt-5.5","provider":"openai","segment_id":"cron.quickCreate.title","source_path":"ui/src/i18n/locales/es.ts","src_lang":"en","text":"New Automation","text_hash":"a3a41a0f86882e4c32217466e6386277710a4a39c703ad802bc9c7fb7363f776","tgt_lang":"es","translated":"Nueva automatización","updated_at":"2026-04-29T20:13:37.107Z"}
|
||||
{"cache_key":"ec771cf19645d1552acdaa429ea7298d97baa7e24b746c2f7f4440898c7869c5","model":"gpt-5.5","provider":"openai","segment_id":"cron.quickCreate.schedules.weekdays.label","source_path":"ui/src/i18n/locales/es.ts","src_lang":"en","text":"Weekdays","text_hash":"6f4b602bb52984dc6244023f94410f3711649f7fe93b8de47b3b3a608312f49e","tgt_lang":"es","translated":"Días laborables","updated_at":"2026-04-29T20:13:33.610Z"}
|
||||
{"cache_key":"edd0a477f8670af0902cff07bf114d01002a9baee19d53bf5c30cbc1a76ccc4d","model":"gpt-5.4","provider":"openai","segment_id":"sessionsView.filters","source_path":"ui/src/i18n/locales/es.ts","src_lang":"en","text":"Filters","text_hash":"546ebb8eb993ea561029d9febd84c363bdb09010bb2cb915a8287762b76b9a64","tgt_lang":"es","translated":"Filtros","updated_at":"2026-04-05T17:12:08.307Z"}
|
||||
{"cache_key":"edd42acdbffcecc9324d051a7447bdb04a0b0d11ec7776f228d80b3f925ae3fa","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.label","source_path":"ui/src/i18n/locales/es.ts","src_lang":"en","text":"Label","text_hash":"0e66373f45dcf3dd656151e519f7ee5e3d558d9c22cb87df339bbdd2b6c6a3c1","tgt_lang":"es","translated":"Etiqueta","updated_at":"2026-04-29T20:13:26.402Z"}
|
||||
{"cache_key":"ee4e2cc81c1938aac92acb4e4217750f3e62a60c0d741d0825e54290c82eaf3e","model":"gpt-5.4","provider":"openai","segment_id":"languages.th","source_path":"ui/src/i18n/locales/es.ts","src_lang":"en","text":"ไทย (Thai)","text_hash":"0339954ca7e472c2f007782682a76629a864d63d3e419430bb5f6c72c4c1c88d","tgt_lang":"es","translated":"ไทย (tailandés)","updated_at":"2026-04-23T06:30:10.624Z"}
|
||||
{"cache_key":"eecad09f8c66054f482c32cb351ef8b4a76ce2e033243067ffeb728f3bcd7734","model":"gpt-5.4","provider":"openai","segment_id":"usage.sessions.avg","source_path":"ui/src/i18n/locales/es.ts","src_lang":"en","text":"avg","text_hash":"ca5c8585b0760a760e0b887800360306b60288aa8581d4800ab42bc2c0d591a5","tgt_lang":"es","translated":"prom.","updated_at":"2026-04-05T17:12:30.161Z"}
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-05-03T18:29:08.558Z",
|
||||
"fallbackKeys": [
|
||||
"sessionsView.activeTooltip",
|
||||
"sessionsView.globalTooltip",
|
||||
"sessionsView.hideFilters",
|
||||
"sessionsView.limitTooltip",
|
||||
"sessionsView.showArchived",
|
||||
"sessionsView.showArchivedTooltip",
|
||||
"sessionsView.showFilters",
|
||||
"sessionsView.sourceFilters",
|
||||
"sessionsView.unknownTooltip"
|
||||
],
|
||||
"generatedAt": "2026-05-04T05:57:02.119Z",
|
||||
"locale": "fa",
|
||||
"model": "gpt-5.5",
|
||||
"provider": "openai",
|
||||
"sourceHash": "d994d064e7880cb40c055c5201db0b9045c15f6abf37fac9b317e25e82b6b7b6",
|
||||
"totalKeys": 991,
|
||||
"translatedKeys": 991,
|
||||
"sourceHash": "66b99bdd39fc9dae0476c0e2fd2c0392f068bc3f7f91b532b04a106f14a3f3df",
|
||||
"totalKeys": 1001,
|
||||
"translatedKeys": 992,
|
||||
"workflow": 1
|
||||
}
|
||||
|
||||
@@ -240,6 +240,7 @@
|
||||
{"cache_key":"404eca6904768d3467f8d6592e4895d61758269946bdfb7dee049829152f73b4","model":"gpt-5.5","provider":"openai","segment_id":"cron.quickCreate.promptPlaceholder","source_path":"ui/src/i18n/locales/fa.ts","src_lang":"en","text":"e.g., Check my inbox for urgent emails and summarize them...","text_hash":"f4675787351dcf3b421c7f187fc9e501f37cb0a5ca79ddcde938cf99efe2dac1","tgt_lang":"fa","translated":"مثلاً، صندوق ورودی من را برای ایمیلهای فوری بررسی کن و آنها را خلاصه کن...","updated_at":"2026-04-29T20:17:43.272Z"}
|
||||
{"cache_key":"407f619024266ae9f03ce10c0042977dce1cb99d21a4fb3e96da38c56f36d1f2","model":"gpt-5.5","provider":"openai","segment_id":"agentTools.builtIn","source_path":"ui/src/i18n/locales/fa.ts","src_lang":"en","text":"Built-in","text_hash":"1f43948106d1d47fef7b0afa5c60be05f7334dc4fe43a0b77d8716ef6ec22611","tgt_lang":"fa","translated":"داخلی","updated_at":"2026-04-29T17:41:37.156Z"}
|
||||
{"cache_key":"4094cac26f18845f2a9ba0cf36b9574366e3282fb79dde171dcf473dd79e1bdd","model":"gpt-5.5","provider":"openai","segment_id":"channels.nostr.usernameHelp","source_path":"ui/src/i18n/locales/fa.ts","src_lang":"en","text":"Short username (e.g., satoshi)","text_hash":"5e91f6b09039a459d4574c826d4280878ff019aeb382aa65e96c108472df0acf","tgt_lang":"fa","translated":"نام کاربری کوتاه (مثلاً satoshi)","updated_at":"2026-04-29T17:41:22.192Z"}
|
||||
{"cache_key":"40ba8bb8eebeb6c92b40d69cfad90e6c4a76f81444f748b01a124f1f0768929b","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.filters","source_path":"ui/src/i18n/locales/fa.ts","src_lang":"en","text":"Filters","text_hash":"546ebb8eb993ea561029d9febd84c363bdb09010bb2cb915a8287762b76b9a64","tgt_lang":"fa","translated":"فیلترها","updated_at":"2026-04-29T17:43:25.173Z"}
|
||||
{"cache_key":"40c32a2a822f0dbde6074ab1ef0d738fd75319ab0396eb71533f289639e6009a","model":"gpt-5.5","provider":"openai","segment_id":"cron.form.editJob","source_path":"ui/src/i18n/locales/fa.ts","src_lang":"en","text":"Edit Job","text_hash":"c492f013040b1041820951af390ee398a4cd71c47fe66908410f6cfe2055d01e","tgt_lang":"fa","translated":"ویرایش کار","updated_at":"2026-04-29T17:44:55.845Z"}
|
||||
{"cache_key":"40ffdd69ae7f63f4bcff764734d2e7f6c4ddf6cc82fe73dbe0d71aaf979ea8ac","model":"gpt-5.5","provider":"openai","segment_id":"debug.security.critical","source_path":"ui/src/i18n/locales/fa.ts","src_lang":"en","text":"{count} critical","text_hash":"97e8a7b9fe4cf2aec17af2d2f9e452ed4adef3ec84899cba45ec4b6c5045e1ec","tgt_lang":"fa","translated":"{count} بحرانی","updated_at":"2026-04-29T19:29:02.592Z"}
|
||||
{"cache_key":"4131cd39227df0fecfb126ca48a8d817d511b788c2a70b5dc7a5467b1818cf1b","model":"gpt-5.5","provider":"openai","segment_id":"debug.security.warnings","source_path":"ui/src/i18n/locales/fa.ts","src_lang":"en","text":"{count} warnings","text_hash":"20c152eb8c81aba048645d91a49f254c1f8cb41ed6e6290ac1e6c3bf4a94b913","tgt_lang":"fa","translated":"{count} هشدار","updated_at":"2026-04-29T19:29:02.592Z"}
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-05-03T18:28:35.244Z",
|
||||
"fallbackKeys": [
|
||||
"sessionsView.activeTooltip",
|
||||
"sessionsView.globalTooltip",
|
||||
"sessionsView.hideFilters",
|
||||
"sessionsView.limitTooltip",
|
||||
"sessionsView.showArchived",
|
||||
"sessionsView.showArchivedTooltip",
|
||||
"sessionsView.showFilters",
|
||||
"sessionsView.sourceFilters",
|
||||
"sessionsView.unknownTooltip"
|
||||
],
|
||||
"generatedAt": "2026-05-04T05:56:58.233Z",
|
||||
"locale": "fr",
|
||||
"model": "gpt-5.5",
|
||||
"provider": "openai",
|
||||
"sourceHash": "d994d064e7880cb40c055c5201db0b9045c15f6abf37fac9b317e25e82b6b7b6",
|
||||
"totalKeys": 991,
|
||||
"translatedKeys": 991,
|
||||
"sourceHash": "66b99bdd39fc9dae0476c0e2fd2c0392f068bc3f7f91b532b04a106f14a3f3df",
|
||||
"totalKeys": 1001,
|
||||
"translatedKeys": 992,
|
||||
"workflow": 1
|
||||
}
|
||||
|
||||
@@ -555,6 +555,7 @@
|
||||
{"cache_key":"a8795fe00724649ffd8ca87304895643d7ac3feed1fe665afd644d3e52144119","model":"gpt-5.4","provider":"openai","segment_id":"channels.nostr.displayName","source_path":"ui/src/i18n/locales/fr.ts","src_lang":"en","text":"Display Name","text_hash":"18d67c992b71ce69eb924554dbace110236c7e2db06effceb3d690b8cd64a671","tgt_lang":"fr","translated":"Nom d’affichage","updated_at":"2026-04-06T02:49:46.449Z"}
|
||||
{"cache_key":"a888baa6b2270129490da862e611512c2d1eb15ca5e1d70fe45109dcf19bdd18","model":"gpt-5.4","provider":"openai","segment_id":"overview.snapshot.uptime","source_path":"ui/src/i18n/locales/fr.ts","src_lang":"en","text":"Uptime","text_hash":"d63ab4711473b0398feb4b56622605d5d2ec7ecd3b1bb5070a7dd56de96aaf88","tgt_lang":"fr","translated":"Temps de fonctionnement","updated_at":"2026-04-05T17:13:59.772Z"}
|
||||
{"cache_key":"a8d9315c5ae546b9f2349ab8b24a1bfffc3bc1daa9053b436da242fe6a7a0a63","model":"gpt-5.4","provider":"openai","segment_id":"overview.access.togglePasswordVisibility","source_path":"ui/src/i18n/locales/fr.ts","src_lang":"en","text":"Toggle password visibility","text_hash":"1016c07b0f58d365790cc799fb215afd92fde1aeb5ac47cd17260e327465b2d6","tgt_lang":"fr","translated":"Basculer la visibilité du mot de passe","updated_at":"2026-04-20T06:26:41.876Z"}
|
||||
{"cache_key":"a915748ef5f8a7d8413e485610fc62bdc124f800a1fb5fbf97d74873b1cfd2a9","model":"gpt-5.4","provider":"openai","segment_id":"sessionsView.filters","source_path":"ui/src/i18n/locales/fr.ts","src_lang":"en","text":"Filters","text_hash":"546ebb8eb993ea561029d9febd84c363bdb09010bb2cb915a8287762b76b9a64","tgt_lang":"fr","translated":"Filtres","updated_at":"2026-04-05T17:14:09.807Z"}
|
||||
{"cache_key":"a9842d69515965223d3a93dec7e3e8cf887b9ede327390053fbfc7635d6a0863","model":"gpt-5.4","provider":"openai","segment_id":"agentTools.builtIn","source_path":"ui/src/i18n/locales/fr.ts","src_lang":"en","text":"Built-in","text_hash":"1f43948106d1d47fef7b0afa5c60be05f7334dc4fe43a0b77d8716ef6ec22611","tgt_lang":"fr","translated":"Intégré","updated_at":"2026-04-06T02:49:52.510Z"}
|
||||
{"cache_key":"a9c7c238b27d69675285eceedb5f3c95a9870227f8b46fa56c21e2b87b4a121e","model":"gpt-5.4","provider":"openai","segment_id":"dreaming.advanced.description","source_path":"ui/src/i18n/locales/fr.ts","src_lang":"en","text":"See what replayed from the daily log, what is waiting for promotion, and what already made it through.","text_hash":"db88d5beb64b2a10b51e81d01c279fa7a663905c2953c0615b48e5408393c311","tgt_lang":"fr","translated":"Voyez ce qui a été relu depuis le journal quotidien, ce qui attend une promotion et ce qui a déjà été validé.","updated_at":"2026-04-10T07:52:25.458Z"}
|
||||
{"cache_key":"aa64dd821827d39455444c72d04adfe4869a029057beb27b61c8d07eba2814e6","model":"gpt-5.4","provider":"openai","segment_id":"dreaming.stats.grounded","source_path":"ui/src/i18n/locales/fr.ts","src_lang":"en","text":"Grounded","text_hash":"5b6f73f04fe1a6af2dc43bebb45478862b0bd1fe079eed12f8bc2000a59bf68c","tgt_lang":"fr","translated":"Ancré","updated_at":"2026-04-08T22:27:49.211Z"}
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-05-03T18:28:50.810Z",
|
||||
"fallbackKeys": [
|
||||
"sessionsView.activeTooltip",
|
||||
"sessionsView.globalTooltip",
|
||||
"sessionsView.hideFilters",
|
||||
"sessionsView.limitTooltip",
|
||||
"sessionsView.showArchived",
|
||||
"sessionsView.showArchivedTooltip",
|
||||
"sessionsView.showFilters",
|
||||
"sessionsView.sourceFilters",
|
||||
"sessionsView.unknownTooltip"
|
||||
],
|
||||
"generatedAt": "2026-05-04T05:57:00.188Z",
|
||||
"locale": "id",
|
||||
"model": "gpt-5.5",
|
||||
"provider": "openai",
|
||||
"sourceHash": "d994d064e7880cb40c055c5201db0b9045c15f6abf37fac9b317e25e82b6b7b6",
|
||||
"totalKeys": 991,
|
||||
"translatedKeys": 991,
|
||||
"sourceHash": "66b99bdd39fc9dae0476c0e2fd2c0392f068bc3f7f91b532b04a106f14a3f3df",
|
||||
"totalKeys": 1001,
|
||||
"translatedKeys": 992,
|
||||
"workflow": 1
|
||||
}
|
||||
|
||||
@@ -623,6 +623,7 @@
|
||||
{"cache_key":"bef75a6124889b9bc1766ab134dbe40f63758827237042ebb2d84e21793fae34","model":"gpt-5.4","provider":"openai","segment_id":"cron.runs.deliveryUnknown","source_path":"ui/src/i18n/locales/id.ts","src_lang":"en","text":"Unknown","text_hash":"b764cdc0eab7137467211272fa539f1260d1bf2e71bcf6ff3bdc960f5c16aa14","tgt_lang":"id","translated":"Tidak diketahui","updated_at":"2026-04-05T17:16:01.471Z"}
|
||||
{"cache_key":"bf440e878311009b3d32a300e0ccc8b766f5cece3c21ce3fe18ab90bc1557e35","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.manual","source_path":"ui/src/i18n/locales/id.ts","src_lang":"en","text":"manual","text_hash":"36bde66f289a35683683b041c6d8f418a5f36607b547da25d00ad55891e80b88","tgt_lang":"id","translated":"manual","updated_at":"2026-04-29T20:16:03.576Z"}
|
||||
{"cache_key":"bf9f59cdf467d45d572604e15124bdcdea9734af5a916ce97071e041e24258ce","model":"gpt-5.4","provider":"openai","segment_id":"common.saving","source_path":"ui/src/i18n/locales/id.ts","src_lang":"en","text":"Saving…","text_hash":"23e39291d6135814ed7c936e278974544b0df5fbf0eb0427b6700979b7472a93","tgt_lang":"id","translated":"Menyimpan…","updated_at":"2026-04-06T02:50:40.390Z"}
|
||||
{"cache_key":"bfd1c5c6f72ea91777e8c37639788c1144bdfe7495ca2aec798bce8a5dae1ee9","model":"gpt-5.4","provider":"openai","segment_id":"sessionsView.filters","source_path":"ui/src/i18n/locales/id.ts","src_lang":"en","text":"Filters","text_hash":"546ebb8eb993ea561029d9febd84c363bdb09010bb2cb915a8287762b76b9a64","tgt_lang":"id","translated":"Filter","updated_at":"2026-04-05T17:15:25.362Z"}
|
||||
{"cache_key":"c0312d7af14a8cc2ffabcb46d9909f5fdda76be4eac7175c36a67bf4ab087025","model":"gpt-5.4","provider":"openai","segment_id":"usage.overview.errorsHint","source_path":"ui/src/i18n/locales/id.ts","src_lang":"en","text":"Total message and tool errors in range.","text_hash":"d99a4b10fb87bda650577c36cec57f531433cbee6046ebb8e614af9e2fffce28","tgt_lang":"id","translated":"Total kesalahan pesan dan alat dalam rentang.","updated_at":"2026-04-05T17:15:35.078Z"}
|
||||
{"cache_key":"c0b8bb54a1a3ed3e4ec20a71be07bdc6b4968cc07fe6e09940f0b214d65254da","model":"gpt-5.4","provider":"openai","segment_id":"cron.errors.scheduleAtInvalid","source_path":"ui/src/i18n/locales/id.ts","src_lang":"en","text":"Enter a valid date/time.","text_hash":"4878bf3e9a06845a2ac4fee29c4518ac244808363fc4fa23e04e929c6e4a0554","tgt_lang":"id","translated":"Masukkan tanggal/waktu yang valid.","updated_at":"2026-04-05T17:16:23.016Z"}
|
||||
{"cache_key":"c128d7134df55bcdab6c1237b0d934e3d269702e51220ebb626aa71bb80c02d4","model":"gpt-5.4","provider":"openai","segment_id":"dreaming.advanced.promotedDescription","source_path":"ui/src/i18n/locales/id.ts","src_lang":"en","text":"Items that already made it through promotion.","text_hash":"e64d609511dff83e5fe8d8906292d4f253e9aebe1e2787391dc02d7ce8d7234a","tgt_lang":"id","translated":"Item yang sudah berhasil melewati promosi.","updated_at":"2026-04-10T07:59:45.707Z"}
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-05-03T18:28:41.755Z",
|
||||
"fallbackKeys": [
|
||||
"sessionsView.activeTooltip",
|
||||
"sessionsView.globalTooltip",
|
||||
"sessionsView.hideFilters",
|
||||
"sessionsView.limitTooltip",
|
||||
"sessionsView.showArchived",
|
||||
"sessionsView.showArchivedTooltip",
|
||||
"sessionsView.showFilters",
|
||||
"sessionsView.sourceFilters",
|
||||
"sessionsView.unknownTooltip"
|
||||
],
|
||||
"generatedAt": "2026-05-04T05:56:59.016Z",
|
||||
"locale": "it",
|
||||
"model": "gpt-5.5",
|
||||
"provider": "openai",
|
||||
"sourceHash": "d994d064e7880cb40c055c5201db0b9045c15f6abf37fac9b317e25e82b6b7b6",
|
||||
"totalKeys": 991,
|
||||
"translatedKeys": 991,
|
||||
"sourceHash": "66b99bdd39fc9dae0476c0e2fd2c0392f068bc3f7f91b532b04a106f14a3f3df",
|
||||
"totalKeys": 1001,
|
||||
"translatedKeys": 992,
|
||||
"workflow": 1
|
||||
}
|
||||
|
||||
@@ -590,6 +590,7 @@
|
||||
{"cache_key":"9d6278f6a149c3bb0bb31b55a71803ade478e7011ac54497c1c042446ba48bd3","model":"gpt-5.5","provider":"openai","segment_id":"cron.errors.systemTextRequired","source_path":"ui/src/i18n/locales/it.ts","src_lang":"en","text":"System text is required.","text_hash":"7b13b35a0dabfa257fada59d07a81a0559c20e8a5049419e4969e2c538f110e5","tgt_lang":"it","translated":"Il testo di sistema è obbligatorio.","updated_at":"2026-04-29T17:39:49.212Z"}
|
||||
{"cache_key":"9d95011a47d3f9952be90336254662ccebac1f43ca0bada63718bb8508b8a196","model":"gpt-5.5","provider":"openai","segment_id":"cron.jobState.last","source_path":"ui/src/i18n/locales/it.ts","src_lang":"en","text":"Last","text_hash":"eb970eb0951c6cdeac1ec0cc723fc91e30b0c26ee6f3b5ee0e574db7f487dc55","tgt_lang":"it","translated":"Ultimo","updated_at":"2026-04-29T17:39:49.212Z"}
|
||||
{"cache_key":"9dd2619d6dcf0cc3308deae5739648da70375005709d2be19bad7b1b552bd771","model":"gpt-5.5","provider":"openai","segment_id":"agents.files.editFile","source_path":"ui/src/i18n/locales/it.ts","src_lang":"en","text":"Edit file","text_hash":"9608dd142d9b10d7799422fe96eab7262a5db3de6edbb1fde660d74ddaa2617b","tgt_lang":"it","translated":"Modifica file","updated_at":"2026-04-29T19:26:42.188Z"}
|
||||
{"cache_key":"9e9dd8b49a714df25c8cc09f0afda1f3e382d824026c11a559ce145d12761847","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.filters","source_path":"ui/src/i18n/locales/it.ts","src_lang":"en","text":"Filters","text_hash":"546ebb8eb993ea561029d9febd84c363bdb09010bb2cb915a8287762b76b9a64","tgt_lang":"it","translated":"Filtri","updated_at":"2026-04-29T17:38:27.250Z"}
|
||||
{"cache_key":"9ea9e3abf8ce9785ebf83e7570eacc98b05d9da920000f5c7c7ae6930d3a4d9f","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.selected","source_path":"ui/src/i18n/locales/it.ts","src_lang":"en","text":"{count} selected","text_hash":"529aacfdfd2b17bf9fe56ebad9a24339a2d1151327dd420c52c5f163aeb9acc6","tgt_lang":"it","translated":"{count} selezionati","updated_at":"2026-04-29T20:14:41.034Z"}
|
||||
{"cache_key":"9ebd7991e429843da1af7cfbb82fba12693658840e5c14e5040f40ddc8d3a04b","model":"gpt-5.5","provider":"openai","segment_id":"usage.cacheStatus.status.refreshing","source_path":"ui/src/i18n/locales/it.ts","src_lang":"en","text":"refreshing","text_hash":"0b61ac5d9426518ad7908a62037255c6881f9a5fa404ef3b99c24baa2111a174","tgt_lang":"it","translated":"aggiornamento in corso","updated_at":"2026-05-03T18:28:41.603Z"}
|
||||
{"cache_key":"9eeb15be964af258a78a5c23082db3fe3dd377c939f58f11915eb87237071806","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.loadingCheckpoints","source_path":"ui/src/i18n/locales/it.ts","src_lang":"en","text":"Loading checkpoints…","text_hash":"28f4a96c140d1effc48388a1f67e650dfcf892df7003d38cd0ebeab22d65ba34","tgt_lang":"it","translated":"Caricamento checkpoint…","updated_at":"2026-04-29T20:14:47.968Z"}
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-05-03T18:28:28.581Z",
|
||||
"fallbackKeys": [
|
||||
"sessionsView.activeTooltip",
|
||||
"sessionsView.globalTooltip",
|
||||
"sessionsView.hideFilters",
|
||||
"sessionsView.limitTooltip",
|
||||
"sessionsView.showArchived",
|
||||
"sessionsView.showArchivedTooltip",
|
||||
"sessionsView.showFilters",
|
||||
"sessionsView.sourceFilters",
|
||||
"sessionsView.unknownTooltip"
|
||||
],
|
||||
"generatedAt": "2026-05-04T05:56:57.392Z",
|
||||
"locale": "ja-JP",
|
||||
"model": "gpt-5.5",
|
||||
"provider": "openai",
|
||||
"sourceHash": "d994d064e7880cb40c055c5201db0b9045c15f6abf37fac9b317e25e82b6b7b6",
|
||||
"totalKeys": 991,
|
||||
"translatedKeys": 991,
|
||||
"sourceHash": "66b99bdd39fc9dae0476c0e2fd2c0392f068bc3f7f91b532b04a106f14a3f3df",
|
||||
"totalKeys": 1001,
|
||||
"translatedKeys": 992,
|
||||
"workflow": 1
|
||||
}
|
||||
|
||||
@@ -277,6 +277,7 @@
|
||||
{"cache_key":"4e049a994528b656da4c43f153fb597d557000f621960554fddbf517ba09ea17","model":"gpt-5.4","provider":"openai","segment_id":"usage.overview.avgSession","source_path":"ui/src/i18n/locales/ja-JP.ts","src_lang":"en","text":"avg session","text_hash":"a8ce1dc2f9461f5c3cf015b40c54888e55840ac786b8f878465ff1c77348a6df","tgt_lang":"ja-JP","translated":"平均セッション","updated_at":"2026-04-05T17:13:20.304Z"}
|
||||
{"cache_key":"4ee726808917d4ab2a629ca344d974b3c2ca818a5b9e6eb00dcd16aa5f1baa85","model":"gpt-5.4","provider":"openai","segment_id":"dreaming.phase.light","source_path":"ui/src/i18n/locales/ja-JP.ts","src_lang":"en","text":"Light","text_hash":"dbcd5e7bb7a0f538810de44c3efbd813037ee3fa358747bb71fa58e157af45f7","tgt_lang":"ja-JP","translated":"浅い","updated_at":"2026-04-10T07:59:01.981Z"}
|
||||
{"cache_key":"4ffeb0530ed210a7e62ebc63d338fb7f4b47db4749e09bcce83056b71830c3f7","model":"gpt-5.4","provider":"openai","segment_id":"usage.details.noMessages","source_path":"ui/src/i18n/locales/ja-JP.ts","src_lang":"en","text":"No messages","text_hash":"a06faf2668c28d0b26a3d89a7cb8751f4d952bc6f38ba9e0c202218269bdc659","tgt_lang":"ja-JP","translated":"メッセージがありません","updated_at":"2026-04-05T17:13:29.278Z"}
|
||||
{"cache_key":"504c6359edd3abe9067c8efb09402b2008b6cb89126b1bfca199907baec6755e","model":"gpt-5.4","provider":"openai","segment_id":"sessionsView.filters","source_path":"ui/src/i18n/locales/ja-JP.ts","src_lang":"en","text":"Filters","text_hash":"546ebb8eb993ea561029d9febd84c363bdb09010bb2cb915a8287762b76b9a64","tgt_lang":"ja-JP","translated":"フィルター","updated_at":"2026-04-05T17:13:06.557Z"}
|
||||
{"cache_key":"505f21a664756308c8fa44c0782c13542b9269dfc688cdbcab2e1af5be865ace","model":"gpt-5.4","provider":"openai","segment_id":"dreaming.diary.newer","source_path":"ui/src/i18n/locales/ja-JP.ts","src_lang":"en","text":"Newer","text_hash":"718c45696575a3aae41c3701a734767de3f3d1d7658c292804a6e3e90b1ce3a5","tgt_lang":"ja-JP","translated":"次へ","updated_at":"2026-04-06T02:49:20.465Z"}
|
||||
{"cache_key":"50b9b4718bf3e07611bf12a326e6bcb3739312ecb4a7d50600a31d134f4a4803","model":"gpt-5.4","provider":"openai","segment_id":"usage.filters.selectAll","source_path":"ui/src/i18n/locales/ja-JP.ts","src_lang":"en","text":"Select All","text_hash":"d1ec69e64b9609d089aae09f7adc5c566d2cd222f8d8325f0ab3b523f0ac2690","tgt_lang":"ja-JP","translated":"すべて選択","updated_at":"2026-04-05T17:13:06.557Z"}
|
||||
{"cache_key":"50f51132263d992e77b6c205759f1f40ebdbee08930a94979098611211820b9d","model":"gpt-5.4","provider":"openai","segment_id":"usage.sessions.copyName","source_path":"ui/src/i18n/locales/ja-JP.ts","src_lang":"en","text":"Copy session name","text_hash":"30a6a5c11915b5b6a99698ebe1cee13b7b84adcc45ccd0a827decce17ce45a2d","tgt_lang":"ja-JP","translated":"セッション名をコピー","updated_at":"2026-04-05T17:13:26.669Z"}
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-05-03T18:28:31.698Z",
|
||||
"fallbackKeys": [
|
||||
"sessionsView.activeTooltip",
|
||||
"sessionsView.globalTooltip",
|
||||
"sessionsView.hideFilters",
|
||||
"sessionsView.limitTooltip",
|
||||
"sessionsView.showArchived",
|
||||
"sessionsView.showArchivedTooltip",
|
||||
"sessionsView.showFilters",
|
||||
"sessionsView.sourceFilters",
|
||||
"sessionsView.unknownTooltip"
|
||||
],
|
||||
"generatedAt": "2026-05-04T05:56:57.843Z",
|
||||
"locale": "ko",
|
||||
"model": "gpt-5.5",
|
||||
"provider": "openai",
|
||||
"sourceHash": "d994d064e7880cb40c055c5201db0b9045c15f6abf37fac9b317e25e82b6b7b6",
|
||||
"totalKeys": 991,
|
||||
"translatedKeys": 991,
|
||||
"sourceHash": "66b99bdd39fc9dae0476c0e2fd2c0392f068bc3f7f91b532b04a106f14a3f3df",
|
||||
"totalKeys": 1001,
|
||||
"translatedKeys": 992,
|
||||
"workflow": 1
|
||||
}
|
||||
|
||||
@@ -651,6 +651,7 @@
|
||||
{"cache_key":"c33423236669e4bf11c80adda81ae32cd36bce5c890ad3c759451ab99ff26963","model":"gpt-5.4","provider":"openai","segment_id":"usage.export.json","source_path":"ui/src/i18n/locales/ko.ts","src_lang":"en","text":"JSON","text_hash":"db1a21a0bc2ef8fbe13ac4cf044e8c9116d29137d5ed8b916ab63dcb2d4290df","tgt_lang":"ko","translated":"JSON","updated_at":"2026-04-06T02:59:47.203Z"}
|
||||
{"cache_key":"c33b9f4c41efa629f253dba4459ed3d60d23353ecdbd1a69b5cef0af7f82629f","model":"gpt-5.4","provider":"openai","segment_id":"cron.form.nextHeartbeat","source_path":"ui/src/i18n/locales/ko.ts","src_lang":"en","text":"Next heartbeat","text_hash":"35e70a7ab8a0d3998180f789eecbec9bbcfe0520d436d8eb142ad6a8fbd55ec1","tgt_lang":"ko","translated":"다음 하트비트","updated_at":"2026-04-05T17:14:56.105Z"}
|
||||
{"cache_key":"c39a36a4dd2f76f335655974704e62edee9bee29156c044bfae69c7285ecad38","model":"gpt-5.4","provider":"openai","segment_id":"cron.form.exactTiming","source_path":"ui/src/i18n/locales/ko.ts","src_lang":"en","text":"Exact timing (no stagger)","text_hash":"02c679552df9fa650dcbc6302ae5f8e954f0303b05cf5b5bddcadf40d6892849","tgt_lang":"ko","translated":"정확한 타이밍(스태거 없음)","updated_at":"2026-04-05T17:15:04.550Z"}
|
||||
{"cache_key":"c3cfa6984c5322da0604d8682a42d0e38b87c09f6c7e2840c32c3736506267a8","model":"gpt-5.4","provider":"openai","segment_id":"sessionsView.filters","source_path":"ui/src/i18n/locales/ko.ts","src_lang":"en","text":"Filters","text_hash":"546ebb8eb993ea561029d9febd84c363bdb09010bb2cb915a8287762b76b9a64","tgt_lang":"ko","translated":"필터","updated_at":"2026-04-05T17:14:06.820Z"}
|
||||
{"cache_key":"c4021d1057371c8c447d74314118747655da5dd0db7e02a7d1cc316af9f4358c","model":"gpt-5.4","provider":"openai","segment_id":"usage.metrics.session","source_path":"ui/src/i18n/locales/ko.ts","src_lang":"en","text":"session","text_hash":"3f3af1ecebbd1410ab417ec0d27bbfcb5d340e177ae159b59fc8626c2dfd9175","tgt_lang":"ko","translated":"세션","updated_at":"2026-04-05T17:14:06.820Z"}
|
||||
{"cache_key":"c459f8514db55b3efbe5b2eb40f47d10349e04e745458b62425e34ab1e3b99b8","model":"gpt-5.4","provider":"openai","segment_id":"cron.errors.invalidRunTime","source_path":"ui/src/i18n/locales/ko.ts","src_lang":"en","text":"Invalid run time.","text_hash":"51465fa3cb94966411a49d8d1972fe997ac028fd249e05df55db8a2179975b48","tgt_lang":"ko","translated":"잘못된 실행 시간입니다.","updated_at":"2026-04-05T17:15:15.338Z"}
|
||||
{"cache_key":"c475af0ad025fa1fa926ddb2c0dfa7c246d22dba653f8b809d47e156db804fed","model":"gpt-5.4","provider":"openai","segment_id":"cron.form.timeoutPlaceholder","source_path":"ui/src/i18n/locales/ko.ts","src_lang":"en","text":"Optional, e.g. 90","text_hash":"6df8499092f2542448e280448a6915fe0d1b5354749ad0170108e193bfd23583","tgt_lang":"ko","translated":"선택 사항, 예: 90","updated_at":"2026-04-05T17:15:00.492Z"}
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-05-03T18:29:04.694Z",
|
||||
"fallbackKeys": [
|
||||
"sessionsView.activeTooltip",
|
||||
"sessionsView.globalTooltip",
|
||||
"sessionsView.hideFilters",
|
||||
"sessionsView.limitTooltip",
|
||||
"sessionsView.showArchived",
|
||||
"sessionsView.showArchivedTooltip",
|
||||
"sessionsView.showFilters",
|
||||
"sessionsView.sourceFilters",
|
||||
"sessionsView.unknownTooltip"
|
||||
],
|
||||
"generatedAt": "2026-05-04T05:57:01.663Z",
|
||||
"locale": "nl",
|
||||
"model": "gpt-5.5",
|
||||
"provider": "openai",
|
||||
"sourceHash": "d994d064e7880cb40c055c5201db0b9045c15f6abf37fac9b317e25e82b6b7b6",
|
||||
"totalKeys": 991,
|
||||
"translatedKeys": 991,
|
||||
"sourceHash": "66b99bdd39fc9dae0476c0e2fd2c0392f068bc3f7f91b532b04a106f14a3f3df",
|
||||
"totalKeys": 1001,
|
||||
"translatedKeys": 992,
|
||||
"workflow": 1
|
||||
}
|
||||
|
||||
@@ -690,6 +690,7 @@
|
||||
{"cache_key":"af416913453514b6c44e01158b501cd30718997d83cb976948a4bd9a0b88d9ef","model":"gpt-5.5","provider":"openai","segment_id":"cron.form.requiredSr","source_path":"ui/src/i18n/locales/nl.ts","src_lang":"en","text":"required","text_hash":"d0a3630555bbec7fc05a98d311c23b00fd1ab4d8296ac4a4125976d80b6a6959","tgt_lang":"nl","translated":"vereist","updated_at":"2026-04-29T17:41:38.443Z"}
|
||||
{"cache_key":"af430e4142abd05454225e4aea2127841b975530a67d897bfef2c1c00919fa69","model":"gpt-5.5","provider":"openai","segment_id":"usage.details.tokensWrittenToCache","source_path":"ui/src/i18n/locales/nl.ts","src_lang":"en","text":"Tokens written to cache","text_hash":"7abf026d6ca218c915b61286a73e94b7c71c6744b63702eab9bc41b4a3b20797","tgt_lang":"nl","translated":"Tokens geschreven naar cache","updated_at":"2026-04-29T17:41:14.532Z"}
|
||||
{"cache_key":"af7f75f9579a44c3c19e9b9398ef8a746de7efb1ae7677d346b3f3e6fd72c067","model":"gpt-5.5","provider":"openai","segment_id":"dreaming.scene.dedupeDiary","source_path":"ui/src/i18n/locales/nl.ts","src_lang":"en","text":"Dedupe Diary","text_hash":"805725ab08dda39943858e1ed241464dc23bc100fac04ce55d0f14a6009d06e4","tgt_lang":"nl","translated":"Dagboek dedupliceren","updated_at":"2026-04-29T17:40:35.784Z"}
|
||||
{"cache_key":"af9905b0a45706ec44bf0d16f7c455a37358f6fba470640c58e6c80536aca885","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.filters","source_path":"ui/src/i18n/locales/nl.ts","src_lang":"en","text":"Filters","text_hash":"546ebb8eb993ea561029d9febd84c363bdb09010bb2cb915a8287762b76b9a64","tgt_lang":"nl","translated":"Filters","updated_at":"2026-04-29T17:40:52.013Z"}
|
||||
{"cache_key":"afa20cd1986e25c5177d869a026e8b2efb251e02168d2862b284006c68217921","model":"gpt-5.5","provider":"openai","segment_id":"dreaming.advanced.originMixed","source_path":"ui/src/i18n/locales/nl.ts","src_lang":"en","text":"mixed","text_hash":"3f8fee624f43b2a9d685353269a0ab3eac785863ab6227636db1060fba1855e0","tgt_lang":"nl","translated":"gemengd","updated_at":"2026-04-29T17:40:40.727Z"}
|
||||
{"cache_key":"afd6aeecdb51c44532950b67c8a1721992d88cf986b4630d3e9b2ae9c5384a76","model":"gpt-5.5","provider":"openai","segment_id":"tabs.logs","source_path":"ui/src/i18n/locales/nl.ts","src_lang":"en","text":"Logs","text_hash":"ea2100dc89ae9fe21fa9b08ab1bf18662dca1e53a3eebd7d03afebcaf5d57515","tgt_lang":"nl","translated":"Logs","updated_at":"2026-04-29T17:40:10.194Z"}
|
||||
{"cache_key":"aff9f88e1f6394bdf7341a4e88927035576638ff0a0f1872fa734fbcf87ad798","model":"gpt-5.5","provider":"openai","segment_id":"tabs.chat","source_path":"ui/src/i18n/locales/nl.ts","src_lang":"en","text":"Chat","text_hash":"460b3a7da007b7af9d35bca54181dc91382263b2bf133ca214871ca1fed1fc1c","tgt_lang":"nl","translated":"Chat","updated_at":"2026-04-29T17:40:06.931Z"}
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-05-03T18:28:54.082Z",
|
||||
"fallbackKeys": [
|
||||
"sessionsView.activeTooltip",
|
||||
"sessionsView.globalTooltip",
|
||||
"sessionsView.hideFilters",
|
||||
"sessionsView.limitTooltip",
|
||||
"sessionsView.showArchived",
|
||||
"sessionsView.showArchivedTooltip",
|
||||
"sessionsView.showFilters",
|
||||
"sessionsView.sourceFilters",
|
||||
"sessionsView.unknownTooltip"
|
||||
],
|
||||
"generatedAt": "2026-05-04T05:57:00.556Z",
|
||||
"locale": "pl",
|
||||
"model": "gpt-5.5",
|
||||
"provider": "openai",
|
||||
"sourceHash": "d994d064e7880cb40c055c5201db0b9045c15f6abf37fac9b317e25e82b6b7b6",
|
||||
"totalKeys": 991,
|
||||
"translatedKeys": 991,
|
||||
"sourceHash": "66b99bdd39fc9dae0476c0e2fd2c0392f068bc3f7f91b532b04a106f14a3f3df",
|
||||
"totalKeys": 1001,
|
||||
"translatedKeys": 992,
|
||||
"workflow": 1
|
||||
}
|
||||
|
||||
@@ -397,6 +397,7 @@
|
||||
{"cache_key":"7ca9147599a71da8a098c7b4ff118ae61db0f144420bb088abc4e2ab5eeb1450","model":"gpt-5.4","provider":"openai","segment_id":"overview.connection.step4","source_path":"ui/src/i18n/locales/pl.ts","src_lang":"en","text":"Or generate a reusable token:","text_hash":"e9512b115cf5e0471b6c45328a8c304ae1a1b5541c3bd9bd26f3c7d2dcbed14b","tgt_lang":"pl","translated":"Lub wygeneruj token wielokrotnego użytku:","updated_at":"2026-04-05T17:16:31.522Z"}
|
||||
{"cache_key":"7d3a2b0aedc672a542e7f5d746ae27d622568da1f0224933b2f5573bcbbb2a3f","model":"gpt-5.4","provider":"openai","segment_id":"agentTools.channelSource","source_path":"ui/src/i18n/locales/pl.ts","src_lang":"en","text":"Channel: {id}","text_hash":"deeba4ed0001ba82ab20e37ea762c26095e52817c28b99b94e2e5026f88fee6c","tgt_lang":"pl","translated":"Kanał: {id}","updated_at":"2026-04-06T02:51:20.539Z"}
|
||||
{"cache_key":"7ddf8c65fe4362bcf9829450151550ac2d6fdc65d0d09a3153e0cc1ec5fe76da","model":"gpt-5.4","provider":"openai","segment_id":"dreaming.phrases.dreamingEmbeddings","source_path":"ui/src/i18n/locales/pl.ts","src_lang":"en","text":"dreaming in embeddings…","text_hash":"e17cd00c9abf4330434e5209a2fbb57d9ae277a90c390a0b42522fb836b54494","tgt_lang":"pl","translated":"śnienie w embeddingach…","updated_at":"2026-04-06T02:51:30.967Z"}
|
||||
{"cache_key":"7e3436fe7dcc4c9d10f62040ccd87b13d7c304954576bf29eaf85fe2e78aa506","model":"gpt-5.4","provider":"openai","segment_id":"sessionsView.filters","source_path":"ui/src/i18n/locales/pl.ts","src_lang":"en","text":"Filters","text_hash":"546ebb8eb993ea561029d9febd84c363bdb09010bb2cb915a8287762b76b9a64","tgt_lang":"pl","translated":"Filtry","updated_at":"2026-04-05T17:16:37.480Z"}
|
||||
{"cache_key":"7e3ac9f1f828422379fdf099f79967d48713995db760b0311ed55f41f784108e","model":"gpt-5.4","provider":"openai","segment_id":"chat.hideCronSessions","source_path":"ui/src/i18n/locales/pl.ts","src_lang":"en","text":"Hide cron sessions","text_hash":"32ac86b13fa25acc4626f518ba49fe9c6307d7bb1b518e05c7eaf4b327a85840","tgt_lang":"pl","translated":"Ukryj sesje Cron","updated_at":"2026-04-05T17:17:09.206Z"}
|
||||
{"cache_key":"7eb2f5906c58c6b726ef25bab1d3167824a49d085fee784f01c3deada07f0f75","model":"gpt-5.4","provider":"openai","segment_id":"usage.sessions.copy","source_path":"ui/src/i18n/locales/pl.ts","src_lang":"en","text":"Copy","text_hash":"e21f935f11d7e966dbbae78da9daa378fe8142a14e7c0cd7434183005faa6c5c","tgt_lang":"pl","translated":"Kopiuj","updated_at":"2026-04-05T17:16:59.033Z"}
|
||||
{"cache_key":"7eb45b77171bc76de41acab6576e7d918a673d3da94e6690cb5d7dd322bffb62","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.tokensBefore","source_path":"ui/src/i18n/locales/pl.ts","src_lang":"en","text":"{count} tokens before","text_hash":"375c48d7ec146984195cb4f88984b9184fb243f05e738cf7bd3896fabfe66976","tgt_lang":"pl","translated":"{count} tokenów przed","updated_at":"2026-04-29T20:16:06.818Z"}
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-05-03T18:28:19.439Z",
|
||||
"fallbackKeys": [
|
||||
"sessionsView.activeTooltip",
|
||||
"sessionsView.globalTooltip",
|
||||
"sessionsView.hideFilters",
|
||||
"sessionsView.limitTooltip",
|
||||
"sessionsView.showArchived",
|
||||
"sessionsView.showArchivedTooltip",
|
||||
"sessionsView.showFilters",
|
||||
"sessionsView.sourceFilters",
|
||||
"sessionsView.unknownTooltip"
|
||||
],
|
||||
"generatedAt": "2026-05-04T05:56:56.059Z",
|
||||
"locale": "pt-BR",
|
||||
"model": "gpt-5.5",
|
||||
"provider": "openai",
|
||||
"sourceHash": "d994d064e7880cb40c055c5201db0b9045c15f6abf37fac9b317e25e82b6b7b6",
|
||||
"totalKeys": 991,
|
||||
"translatedKeys": 991,
|
||||
"sourceHash": "66b99bdd39fc9dae0476c0e2fd2c0392f068bc3f7f91b532b04a106f14a3f3df",
|
||||
"totalKeys": 1001,
|
||||
"translatedKeys": 992,
|
||||
"workflow": 1
|
||||
}
|
||||
|
||||
@@ -399,6 +399,7 @@
|
||||
{"cache_key":"935616f2c011ebb986c74a2fc87862634e19d41d6300fca871ca56a175cb7a13","model":"gpt-5.4","provider":"openai","segment_id":"usage.mosaic.sat","source_path":"ui/src/i18n/locales/pt-BR.ts","src_lang":"en","text":"Sat","text_hash":"fdeb71b569e0034d827041c354d2a609ee60b2d3ab71eb0e390faa70c10e36e1","tgt_lang":"pt-BR","translated":"Sáb","updated_at":"2026-04-05T17:11:28.481Z"}
|
||||
{"cache_key":"942c70889f3af35ebedb038fc4eb7a371e58bd99ea75613a7b0b4d5b2ef78b80","model":"gpt-5.4","provider":"openai","segment_id":"cron.form.systemEvent","source_path":"ui/src/i18n/locales/pt-BR.ts","src_lang":"en","text":"Post message to main timeline","text_hash":"114ef03ed867cd1fabd71e0475822261a5baf3e84210260e8bed84ac005f0a3a","tgt_lang":"pt-BR","translated":"Publicar mensagem na linha do tempo principal","updated_at":"2026-04-05T17:11:52.042Z"}
|
||||
{"cache_key":"9441e66882e6d44d0519e0f37aba0e4a717fbc9d6ad464834e8dde613dc26d08","model":"gpt-5.4","provider":"openai","segment_id":"usage.filters.endDate","source_path":"ui/src/i18n/locales/pt-BR.ts","src_lang":"en","text":"End date","text_hash":"14303aa0c4a08d390e1180d9ed4ecbad43d4c4176d82ea8b8ae3f4b648b07380","tgt_lang":"pt-BR","translated":"Data de término","updated_at":"2026-04-05T17:10:40.263Z"}
|
||||
{"cache_key":"944820bd7b779650edc52744d74068450bc9eab3d9860cfb9fa56dff4f55fac1","model":"gpt-5.4","provider":"openai","segment_id":"sessionsView.filters","source_path":"ui/src/i18n/locales/pt-BR.ts","src_lang":"en","text":"Filters","text_hash":"546ebb8eb993ea561029d9febd84c363bdb09010bb2cb915a8287762b76b9a64","tgt_lang":"pt-BR","translated":"Filtros","updated_at":"2026-04-05T17:10:40.263Z"}
|
||||
{"cache_key":"94550816e6ca1cc607ac224085a048be1be4288b4a387002dee3c31b638f82bc","model":"gpt-5.4","provider":"openai","segment_id":"cron.jobs.direction","source_path":"ui/src/i18n/locales/pt-BR.ts","src_lang":"en","text":"Direction","text_hash":"9c8a9579abe55bdc8a7b97031705e2738d912de38a35262863d8f47e05d3d641","tgt_lang":"pt-BR","translated":"Direção","updated_at":"2026-04-05T17:11:32.154Z"}
|
||||
{"cache_key":"9484a8e7dd30002d167b7606e82cf73aa5ce2eda0d2f741446eb21f5563e9f1f","model":"gpt-5.4","provider":"openai","segment_id":"cron.form.cronOption","source_path":"ui/src/i18n/locales/pt-BR.ts","src_lang":"en","text":"Cron","text_hash":"dd9d24965dbedc026915308732b77c1af68dcf52d3c0ca2421b1fdb0d197aca1","tgt_lang":"pt-BR","translated":"Cron","updated_at":"2026-04-06T02:59:24.089Z"}
|
||||
{"cache_key":"94d32ff0dcc95e5dc6ea8a58e8d24dbfe90f079ba2b7e5f9604b8d9a495be61a","model":"gpt-5.4","provider":"openai","segment_id":"usage.overview.toolCalls","source_path":"ui/src/i18n/locales/pt-BR.ts","src_lang":"en","text":"Tool Calls","text_hash":"548ddc303bacce6b519d601219508cdbf5a27f81b466ccae5268286ae6c9fab9","tgt_lang":"pt-BR","translated":"Chamadas de ferramenta","updated_at":"2026-04-05T17:10:58.366Z"}
|
||||
|
||||
@@ -4166,6 +4166,13 @@
|
||||
"path": "ui/src/ui/views/overview.ts",
|
||||
"text": "openclaw doctor --generate-gateway-token"
|
||||
},
|
||||
{
|
||||
"count": 1,
|
||||
"kind": "html-attribute",
|
||||
"name": "aria-label",
|
||||
"path": "ui/src/ui/views/sessions.ts",
|
||||
"text": "Session filters"
|
||||
},
|
||||
{
|
||||
"count": 1,
|
||||
"kind": "html-text",
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-05-03T18:28:57.460Z",
|
||||
"fallbackKeys": [
|
||||
"sessionsView.activeTooltip",
|
||||
"sessionsView.globalTooltip",
|
||||
"sessionsView.hideFilters",
|
||||
"sessionsView.limitTooltip",
|
||||
"sessionsView.showArchived",
|
||||
"sessionsView.showArchivedTooltip",
|
||||
"sessionsView.showFilters",
|
||||
"sessionsView.sourceFilters",
|
||||
"sessionsView.unknownTooltip"
|
||||
],
|
||||
"generatedAt": "2026-05-04T05:57:00.916Z",
|
||||
"locale": "th",
|
||||
"model": "gpt-5.5",
|
||||
"provider": "openai",
|
||||
"sourceHash": "d994d064e7880cb40c055c5201db0b9045c15f6abf37fac9b317e25e82b6b7b6",
|
||||
"totalKeys": 991,
|
||||
"translatedKeys": 991,
|
||||
"sourceHash": "66b99bdd39fc9dae0476c0e2fd2c0392f068bc3f7f91b532b04a106f14a3f3df",
|
||||
"totalKeys": 1001,
|
||||
"translatedKeys": 992,
|
||||
"workflow": 1
|
||||
}
|
||||
|
||||
@@ -175,6 +175,7 @@
|
||||
{"cache_key":"3896f117ae78c4672515183a863fc9d72db1766358a503ba48d8abc877eee062","model":"gpt-5.4","provider":"openai","segment_id":"dreaming.trace.emptySignals","source_path":"ui/src/i18n/locales/th.ts","src_lang":"en","text":"No active signals.","text_hash":"0d9d086593baedf3d8af5a8f30c9bdb495209fdb3413e02f1e74c6f8ce77e876","tgt_lang":"th","translated":"ไม่มีสัญญาณที่ใช้งานอยู่","updated_at":"2026-04-23T06:27:13.277Z"}
|
||||
{"cache_key":"389a4cc41cd739a3a8ffdc7e42115917caf3aa70e42fac17fcee08dd6c4007cc","model":"gpt-5.4","provider":"openai","segment_id":"usage.filters.pin","source_path":"ui/src/i18n/locales/th.ts","src_lang":"en","text":"Pin","text_hash":"ff1cee74414621d812efa8f77a6024850158c209fba6158772088703c2a02ff9","tgt_lang":"th","translated":"ปักหมุด","updated_at":"2026-04-23T06:27:29.651Z"}
|
||||
{"cache_key":"390a25d8d33e52ff8a4c48188402249e740843eb50e362930242a7b41ae172d8","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.timeoutRetry","source_path":"ui/src/i18n/locales/th.ts","src_lang":"en","text":"timeout retry","text_hash":"79d153651a03220f4efa053666d2102b238e62f65f0d5358891699656eb5a0d4","tgt_lang":"th","translated":"ลองใหม่เมื่อหมดเวลา","updated_at":"2026-04-29T20:16:17.116Z"}
|
||||
{"cache_key":"398da2ff49ed8663b865024c57f60a706be89cd3789ecf90dfa223831e7e3454","model":"gpt-5.5","provider":"openai","segment_id":"cron.form.webhookHelp","source_path":"ui/src/i18n/locales/th.ts","src_lang":"en","text":"Send run summaries to a webhook endpoint.","text_hash":"cb5f366ea218ef2d0c803e1c814ed6cc24abd93701d5c5c87e9503869eb11070","tgt_lang":"th","translated":"ส่งสรุปผลการทำงานไปยังปลายทาง webhook","updated_at":"2026-05-04T06:00:00.000Z"}
|
||||
{"cache_key":"39a3998255817c48adfa4ade723397d3e9e0d8631135c6cd16a5d08c3dc4dafe","model":"gpt-5.4","provider":"openai","segment_id":"usage.daily.total","source_path":"ui/src/i18n/locales/th.ts","src_lang":"en","text":"Total","text_hash":"c9b3c38247f744e17dd26fda097d6a9ba9332586b6bdaa038bf8f313a863f2b8","tgt_lang":"th","translated":"รวม","updated_at":"2026-04-23T06:27:38.605Z"}
|
||||
{"cache_key":"39f8b3b0c8662478b0d414c27f010e55cc9142b41a210e64dcaa7fe29c6ac7c8","model":"gpt-5.4","provider":"openai","segment_id":"languages.ptBR","source_path":"ui/src/i18n/locales/th.ts","src_lang":"en","text":"Português (Brazilian Portuguese)","text_hash":"218d74650d53faa34f3263ebca533ed034422d1aec61d98ebd2ef353c0b9d492","tgt_lang":"th","translated":"Português (โปรตุเกสแบบบราซิล)","updated_at":"2026-04-23T06:28:14.367Z"}
|
||||
{"cache_key":"3a0210e94d14f26172b94e0d320019e09a831edd59eb95c2259b8965abc5d784","model":"gpt-5.4","provider":"openai","segment_id":"dreaming.diary.waitingHint","source_path":"ui/src/i18n/locales/th.ts","src_lang":"en","text":"Narrative entries will appear after the next dreaming cycle.","text_hash":"c183c67ee0ad3800a518c6eac25bb58b19d4c9f944a961f2c1e371f581a465cd","tgt_lang":"th","translated":"รายการบันทึกเชิงบรรยายจะแสดงหลังจากรอบการฝันถัดไป","updated_at":"2026-04-23T06:27:13.277Z"}
|
||||
@@ -660,6 +661,7 @@
|
||||
{"cache_key":"c26a4048ef7fddba423a40fc96a94dccb2646953c01cf5bddda426fc002a7008","model":"gpt-5.4","provider":"openai","segment_id":"overview.access.password","source_path":"ui/src/i18n/locales/th.ts","src_lang":"en","text":"Password (not stored)","text_hash":"a693085108fe8ddea3acb78ba8ac0c275e593fc85db1c526006247ceb1372dda","tgt_lang":"th","translated":"รหัสผ่าน (ไม่จัดเก็บ)","updated_at":"2026-04-23T06:26:22.932Z"}
|
||||
{"cache_key":"c2c3870589ba6d437dc17f13521dfee3e11c430212bee049bb4757c4e78a4837","model":"gpt-5.4","provider":"openai","segment_id":"usage.overview.toolResults","source_path":"ui/src/i18n/locales/th.ts","src_lang":"en","text":"tool results","text_hash":"a5594e12dfffd8e54c36d9b99bc31c7d41f0389d2251790338f34e836a3211fe","tgt_lang":"th","translated":"ผลลัพธ์ของ tool","updated_at":"2026-04-23T06:27:44.114Z"}
|
||||
{"cache_key":"c32c330e4f39e19226b60165c7e211f5c539de9543b6d34a2e6fefec07750717","model":"gpt-5.4","provider":"openai","segment_id":"cron.jobList.edit","source_path":"ui/src/i18n/locales/th.ts","src_lang":"en","text":"Edit","text_hash":"464c4ffd019e1e9691dcf0537c797353ef2b1c1d4833d3d463e5b74ae4547344","tgt_lang":"th","translated":"แก้ไข","updated_at":"2026-04-23T06:28:51.244Z"}
|
||||
{"cache_key":"c3a445b2b1b0a122417b66ff7add63b639705c4e771e246a69bec7acfaf06627","model":"gpt-5.4","provider":"openai","segment_id":"sessionsView.filters","source_path":"ui/src/i18n/locales/th.ts","src_lang":"en","text":"Filters","text_hash":"546ebb8eb993ea561029d9febd84c363bdb09010bb2cb915a8287762b76b9a64","tgt_lang":"th","translated":"ตัวกรอง","updated_at":"2026-04-23T06:27:29.651Z"}
|
||||
{"cache_key":"c44f7b80a319548d19de739c1956f5449e5e5e8e87e0d2728f152c7c39057f14","model":"gpt-5.4","provider":"openai","segment_id":"nav.collapse","source_path":"ui/src/i18n/locales/th.ts","src_lang":"en","text":"Collapse sidebar","text_hash":"aab31cde23ba9783050a754575b80c05e0e799b1542990b24b4b4bde2327e37e","tgt_lang":"th","translated":"ย่อแถบด้านข้าง","updated_at":"2026-04-23T06:26:03.424Z"}
|
||||
{"cache_key":"c4a21b12587ab2444055336a10450c0c02d43073dfce82b77cc3ceebecfb0f6c","model":"gpt-5.5","provider":"openai","segment_id":"chat.openCommandPalette","source_path":"ui/src/i18n/locales/th.ts","src_lang":"en","text":"Open command palette","text_hash":"c022b19a38a632d9f0981df1407ed11743b7fd8a80b159b76a7cf78ad61a43b1","tgt_lang":"th","translated":"เปิดแถบคำสั่ง","updated_at":"2026-04-29T20:16:17.117Z"}
|
||||
{"cache_key":"c4b196fe79b21964fd255fb4916cb850f8c480d03e9de5ea6495e92e2eb5e828","model":"gpt-5.5","provider":"openai","segment_id":"lazyView.errorTitle","source_path":"ui/src/i18n/locales/th.ts","src_lang":"en","text":"Panel failed to load","text_hash":"f8c9d26f13962ea24220d44bb42badfec39d7f37b22dffdbb75a67c873cc044d","tgt_lang":"th","translated":"โหลดแผงไม่สำเร็จ","updated_at":"2026-04-27T12:13:28.547Z"}
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-05-03T18:28:44.586Z",
|
||||
"fallbackKeys": [
|
||||
"sessionsView.activeTooltip",
|
||||
"sessionsView.globalTooltip",
|
||||
"sessionsView.hideFilters",
|
||||
"sessionsView.limitTooltip",
|
||||
"sessionsView.showArchived",
|
||||
"sessionsView.showArchivedTooltip",
|
||||
"sessionsView.showFilters",
|
||||
"sessionsView.sourceFilters",
|
||||
"sessionsView.unknownTooltip"
|
||||
],
|
||||
"generatedAt": "2026-05-04T05:56:59.387Z",
|
||||
"locale": "tr",
|
||||
"model": "gpt-5.5",
|
||||
"provider": "openai",
|
||||
"sourceHash": "d994d064e7880cb40c055c5201db0b9045c15f6abf37fac9b317e25e82b6b7b6",
|
||||
"totalKeys": 991,
|
||||
"translatedKeys": 991,
|
||||
"sourceHash": "66b99bdd39fc9dae0476c0e2fd2c0392f068bc3f7f91b532b04a106f14a3f3df",
|
||||
"totalKeys": 1001,
|
||||
"translatedKeys": 992,
|
||||
"workflow": 1
|
||||
}
|
||||
|
||||
@@ -250,6 +250,7 @@
|
||||
{"cache_key":"4c63c479a4885ef3bc77cdf94fb219a23bb1bb0f769444ba6e539b3ce2a7aa61","model":"gpt-5.4","provider":"openai","segment_id":"languages.tr","source_path":"ui/src/i18n/locales/tr.ts","src_lang":"en","text":"Türkçe (Turkish)","text_hash":"d7ba05ad20ad9e92b3f8b724f1c164bd0db7173a9f9fa9f961f5b588c413c0d4","tgt_lang":"tr","translated":"Türkçe (Türkçe)","updated_at":"2026-04-05T17:15:57.661Z"}
|
||||
{"cache_key":"4c9e54e204f210245167a9ea9054f76d3beaa8571ce329c614bbd284916685dd","model":"gpt-5.4","provider":"openai","segment_id":"dreaming.phrases.simmeringIdeas","source_path":"ui/src/i18n/locales/tr.ts","src_lang":"en","text":"simmering half-formed ideas…","text_hash":"bb9432dfcd536797972bc477a1cc8e154d4b639552bdb67b9be0ee1517e6037b","tgt_lang":"tr","translated":"yarı şekillenmiş fikirler demleniyor…","updated_at":"2026-04-06T02:50:36.506Z"}
|
||||
{"cache_key":"4cc21ca774eb668391fe67ab660ab29b7dbd5fbe2efa1e6f78a3c4bdec42e0a8","model":"gpt-5.4","provider":"openai","segment_id":"usage.details.collapseAll","source_path":"ui/src/i18n/locales/tr.ts","src_lang":"en","text":"Collapse All","text_hash":"55988e28a4e8720a588c5c53fd47616d929a404d3d2af7e6f8ba313dce6dc3e4","tgt_lang":"tr","translated":"Tümünü Daralt","updated_at":"2026-04-05T17:15:44.742Z"}
|
||||
{"cache_key":"4cd6fd7b3041566a2df0f571e51c4b1a3a623e2c42c24300ebe0b4dab5f6b653","model":"gpt-5.4","provider":"openai","segment_id":"sessionsView.filters","source_path":"ui/src/i18n/locales/tr.ts","src_lang":"en","text":"Filters","text_hash":"546ebb8eb993ea561029d9febd84c363bdb09010bb2cb915a8287762b76b9a64","tgt_lang":"tr","translated":"Filtreler","updated_at":"2026-04-05T17:15:14.133Z"}
|
||||
{"cache_key":"4d58b946022f634546d176ca6f35ddb7b475999aa684085ae46f3a296bba354b","model":"gpt-5.4","provider":"openai","segment_id":"channels.generic.subtitle","source_path":"ui/src/i18n/locales/tr.ts","src_lang":"en","text":"Channel status and configuration.","text_hash":"af598d2e3f8e7a9dcacdc23e2865c738ceced7ac9c98bb19ff0fde64e76d5be0","tgt_lang":"tr","translated":"Kanal durumu ve yapılandırması.","updated_at":"2026-04-06T02:50:07.323Z"}
|
||||
{"cache_key":"4d69b13b725892190573e55a9f0ec2afd4f6ca51f0af0c17fb959e584ff03e25","model":"gpt-5.4","provider":"openai","segment_id":"dreaming.advanced.title","source_path":"ui/src/i18n/locales/tr.ts","src_lang":"en","text":"Daily Log Replay","text_hash":"aafb35de5bb78185d5268c25978163b98291c650afcd56df7ab95ec773c3c988","tgt_lang":"tr","translated":"Günlük Kayıt Tekrarı","updated_at":"2026-04-10T07:52:37.528Z"}
|
||||
{"cache_key":"4ef3226510a1d1e465493c29b51264bb69f8252c52f90731ce5e9bac20f93ad3","model":"gpt-5.4","provider":"openai","segment_id":"chat.showCronSessions","source_path":"ui/src/i18n/locales/tr.ts","src_lang":"en","text":"Show cron sessions","text_hash":"0cc0314eb8ffe4f1b14e774b3eec8f0433cc0ab073f396ca789d6ee35cb37385","tgt_lang":"tr","translated":"Cron oturumlarını göster","updated_at":"2026-04-05T17:15:52.927Z"}
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-05-03T18:28:47.748Z",
|
||||
"fallbackKeys": [
|
||||
"sessionsView.activeTooltip",
|
||||
"sessionsView.globalTooltip",
|
||||
"sessionsView.hideFilters",
|
||||
"sessionsView.limitTooltip",
|
||||
"sessionsView.showArchived",
|
||||
"sessionsView.showArchivedTooltip",
|
||||
"sessionsView.showFilters",
|
||||
"sessionsView.sourceFilters",
|
||||
"sessionsView.unknownTooltip"
|
||||
],
|
||||
"generatedAt": "2026-05-04T05:56:59.815Z",
|
||||
"locale": "uk",
|
||||
"model": "gpt-5.5",
|
||||
"provider": "openai",
|
||||
"sourceHash": "d994d064e7880cb40c055c5201db0b9045c15f6abf37fac9b317e25e82b6b7b6",
|
||||
"totalKeys": 991,
|
||||
"translatedKeys": 991,
|
||||
"sourceHash": "66b99bdd39fc9dae0476c0e2fd2c0392f068bc3f7f91b532b04a106f14a3f3df",
|
||||
"totalKeys": 1001,
|
||||
"translatedKeys": 992,
|
||||
"workflow": 1
|
||||
}
|
||||
|
||||
@@ -98,6 +98,7 @@
|
||||
{"cache_key":"1c4884f073580a600a2ee56a7033f53bc908c3f9b17156152b7ec3219532f8af","model":"gpt-5.4","provider":"openai","segment_id":"overview.connection.tailscaleDocsTitle","source_path":"ui/src/i18n/locales/uk.ts","src_lang":"en","text":"Tailscale Serve docs (opens in new tab)","text_hash":"2ba82f0f0c4820765bb47b714ebdba4b491e2729f2b415496411900a702d4fdb","tgt_lang":"uk","translated":"Документація щодо Tailscale Serve (відкривається в новій вкладці)","updated_at":"2026-04-20T06:26:53.139Z"}
|
||||
{"cache_key":"1c941be301f363ff9dcef04704d6a6486d2a105c96abe75372fd2e086d073e48","model":"gpt-5.4","provider":"openai","segment_id":"cron.runs.allDelivery","source_path":"ui/src/i18n/locales/uk.ts","src_lang":"en","text":"All delivery","text_hash":"41ae1c2395e52fa33ba7df91afec0e316cd9e36a74a39b87a825f65a7dce707b","tgt_lang":"uk","translated":"Уся доставка","updated_at":"2026-04-05T17:23:29.777Z"}
|
||||
{"cache_key":"1cccc505aa43a900279be9c21babef4eed902580c54613950a634d543a328c1b","model":"gpt-5.4","provider":"openai","segment_id":"common.probeOk","source_path":"ui/src/i18n/locales/uk.ts","src_lang":"en","text":"Probe ok","text_hash":"c3d8dac3db6b4f2768483a199b2c0784645995f63459d91e8d0bddee2f6993c7","tgt_lang":"uk","translated":"Перевірку пройдено","updated_at":"2026-04-06T02:50:29.304Z"}
|
||||
{"cache_key":"1cf7a6c657246ab71e39911b627e4794d74f2f6a5fd0d96120e99194c63f69a1","model":"gpt-5.4","provider":"openai","segment_id":"sessionsView.filters","source_path":"ui/src/i18n/locales/uk.ts","src_lang":"en","text":"Filters","text_hash":"546ebb8eb993ea561029d9febd84c363bdb09010bb2cb915a8287762b76b9a64","tgt_lang":"uk","translated":"Фільтри","updated_at":"2026-04-05T17:22:39.086Z"}
|
||||
{"cache_key":"1d21fb4e720fdf0aa7968a0a2064e3aa82c1d87de5307b0491231305b6bff293","model":"gpt-5.4","provider":"openai","segment_id":"dreaming.trace.shortTerm","source_path":"ui/src/i18n/locales/uk.ts","src_lang":"en","text":"Short-term","text_hash":"5bb852d4225d676aa64e8933284475ce54fd35d9535b4f5b4b37c42245112df0","tgt_lang":"uk","translated":"Короткострокові","updated_at":"2026-04-08T18:39:02.532Z"}
|
||||
{"cache_key":"1d6a807ba1fe5f29292c1e1138ea743313e908cdcf2d4f768099e626257ae3d4","model":"gpt-5.4","provider":"openai","segment_id":"overview.cards.modelAuthAttentionExpiredDesc","source_path":"ui/src/i18n/locales/uk.ts","src_lang":"en","text":"{providers} — re-authenticate with openclaw models auth","text_hash":"e883d59961999e0948fec3d4acbf11dc7c55a16beb27d313a0ce6cceb7ad2cb8","tgt_lang":"uk","translated":"{providers} — повторно виконайте авторизацію через openclaw models auth","updated_at":"2026-04-15T05:45:16.519Z"}
|
||||
{"cache_key":"1d926c6ca1ca8542c8ccbd82545ba6a7f1e0e14f35417b613a89c4c486272d3c","model":"gpt-5.4","provider":"openai","segment_id":"cron.jobList.disable","source_path":"ui/src/i18n/locales/uk.ts","src_lang":"en","text":"Disable","text_hash":"b7e3e4aa4257b9a11a82f59faf34c8450ca10d4116885b0a29fedf60842d81d5","tgt_lang":"uk","translated":"Вимкнути","updated_at":"2026-04-05T17:23:50.170Z"}
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-05-03T18:29:00.531Z",
|
||||
"fallbackKeys": [
|
||||
"sessionsView.activeTooltip",
|
||||
"sessionsView.globalTooltip",
|
||||
"sessionsView.hideFilters",
|
||||
"sessionsView.limitTooltip",
|
||||
"sessionsView.showArchived",
|
||||
"sessionsView.showArchivedTooltip",
|
||||
"sessionsView.showFilters",
|
||||
"sessionsView.sourceFilters",
|
||||
"sessionsView.unknownTooltip"
|
||||
],
|
||||
"generatedAt": "2026-05-04T05:57:01.264Z",
|
||||
"locale": "vi",
|
||||
"model": "gpt-5.5",
|
||||
"provider": "openai",
|
||||
"sourceHash": "d994d064e7880cb40c055c5201db0b9045c15f6abf37fac9b317e25e82b6b7b6",
|
||||
"totalKeys": 991,
|
||||
"translatedKeys": 991,
|
||||
"sourceHash": "66b99bdd39fc9dae0476c0e2fd2c0392f068bc3f7f91b532b04a106f14a3f3df",
|
||||
"totalKeys": 1001,
|
||||
"translatedKeys": 992,
|
||||
"workflow": 1
|
||||
}
|
||||
|
||||
@@ -200,6 +200,7 @@
|
||||
{"cache_key":"338e95308eade034cfdd794e21694ade0cf0333ccfdd28ae3269ccd1065fd708","model":"gpt-5.5","provider":"openai","segment_id":"cron.quickCreate.schedules.everyEvening.description","source_path":"ui/src/i18n/locales/vi.ts","src_lang":"en","text":"Daily at 6:00 PM","text_hash":"260b9ed9fe7fac30932e89488f121facfb1cd04b8ecda5ca2ece9a207ad6662a","tgt_lang":"vi","translated":"Hằng ngày lúc 6:00 CH","updated_at":"2026-04-29T20:16:47.550Z"}
|
||||
{"cache_key":"339644c0fa5c48da3c660f5fe04ff24adb36d70238e191220037cc256d7957a9","model":"gpt-5.5","provider":"openai","segment_id":"dreaming.phrases.consolidatingMemories","source_path":"ui/src/i18n/locales/vi.ts","src_lang":"en","text":"consolidating memories…","text_hash":"89baaaae1f0e1ad3d02d40be2987273190f86bf34e8a27dd35c8e7faa76e2841","tgt_lang":"vi","translated":"đang hợp nhất ký ức…","updated_at":"2026-04-29T17:40:41.874Z"}
|
||||
{"cache_key":"33a2e759514cde43ae2fac511a1b6b9f8950815ab7a2ea14f35134817ab95755","model":"gpt-5.5","provider":"openai","segment_id":"login.toggleTokenVisibility","source_path":"ui/src/i18n/locales/vi.ts","src_lang":"en","text":"Toggle token visibility","text_hash":"81fc4b962be0e4a4748879f1645272c8f2302e101c59544f1fac347b3f892f26","tgt_lang":"vi","translated":"Bật/tắt hiển thị token","updated_at":"2026-04-29T17:41:23.093Z"}
|
||||
{"cache_key":"34203a416c3659071700de15490dfc95fc7eeb44d583ca410b044f46ee46b5f9","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.filters","source_path":"ui/src/i18n/locales/vi.ts","src_lang":"en","text":"Filters","text_hash":"546ebb8eb993ea561029d9febd84c363bdb09010bb2cb915a8287762b76b9a64","tgt_lang":"vi","translated":"Bộ lọc","updated_at":"2026-04-29T17:40:44.671Z"}
|
||||
{"cache_key":"345cbb35705a925d8869f7e21af9f90364253bae766d59a44eaa42be9de03e7a","model":"gpt-5.5","provider":"openai","segment_id":"languages.es","source_path":"ui/src/i18n/locales/vi.ts","src_lang":"en","text":"Español (Spanish)","text_hash":"b785e11e822c061a3a5368c55fbeb3f436766ef1e9b3448a605083d0b06ecddb","tgt_lang":"vi","translated":"Español (Tiếng Tây Ban Nha)","updated_at":"2026-04-29T17:41:29.250Z"}
|
||||
{"cache_key":"3485543d6cc5f1756ffcd246aca399c782b456447d7f070cb47472f3c9df9164","model":"gpt-5.5","provider":"openai","segment_id":"agents.files.liveDraftPreview","source_path":"ui/src/i18n/locales/vi.ts","src_lang":"en","text":"Live Draft Preview","text_hash":"eb6b2fefeacd2aac68f7ea96e616e8ba9eefd3d7c74a0e100bdcafe2d515052f","tgt_lang":"vi","translated":"Bản xem trước bản nháp trực tiếp","updated_at":"2026-04-29T19:28:16.225Z"}
|
||||
{"cache_key":"349bd72e0e1c90712d49edf439cfb885b7c4a961c36217a7f4fe8c8c94d09e07","model":"gpt-5.5","provider":"openai","segment_id":"agents.files.updated","source_path":"ui/src/i18n/locales/vi.ts","src_lang":"en","text":"Updated {time}","text_hash":"2f87419441e6111b4d62893d3c4ef5ddeb2c8e1af82fabab6132856faf77f907","tgt_lang":"vi","translated":"Đã cập nhật {time}","updated_at":"2026-04-29T19:28:16.225Z"}
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-05-03T18:28:13.442Z",
|
||||
"fallbackKeys": [
|
||||
"sessionsView.activeTooltip",
|
||||
"sessionsView.globalTooltip",
|
||||
"sessionsView.hideFilters",
|
||||
"sessionsView.limitTooltip",
|
||||
"sessionsView.showArchived",
|
||||
"sessionsView.showArchivedTooltip",
|
||||
"sessionsView.showFilters",
|
||||
"sessionsView.sourceFilters",
|
||||
"sessionsView.unknownTooltip"
|
||||
],
|
||||
"generatedAt": "2026-05-04T05:56:55.059Z",
|
||||
"locale": "zh-CN",
|
||||
"model": "gpt-5.5",
|
||||
"provider": "openai",
|
||||
"sourceHash": "d994d064e7880cb40c055c5201db0b9045c15f6abf37fac9b317e25e82b6b7b6",
|
||||
"totalKeys": 991,
|
||||
"translatedKeys": 991,
|
||||
"sourceHash": "66b99bdd39fc9dae0476c0e2fd2c0392f068bc3f7f91b532b04a106f14a3f3df",
|
||||
"totalKeys": 1001,
|
||||
"translatedKeys": 992,
|
||||
"workflow": 1
|
||||
}
|
||||
|
||||
@@ -153,6 +153,7 @@
|
||||
{"cache_key":"416f556b264c42fccba8bab1e8877917155eb16207a0c60dab0a045873f9010c","model":"gpt-5.5","provider":"openai","segment_id":"lazyView.retry","source_path":"ui/src/i18n/locales/zh-CN.ts","src_lang":"en","text":"Retry","text_hash":"942087cc2d41e01304b7195558d093d10c72af8e838c7556d6a02d471ee71852","tgt_lang":"zh-CN","translated":"重试","updated_at":"2026-04-27T12:10:49.684Z"}
|
||||
{"cache_key":"41c33a4091090aafbe2f6303f026cf661ff8c8c4d6bce6c2c04fca72763a3291","model":"gpt-5.4","provider":"openai","segment_id":"usage.details.tool","source_path":"ui/src/i18n/locales/zh-CN.ts","src_lang":"en","text":"Tool","text_hash":"2e53bdcd0740867b597599e733c04a994f55fb17c89a61595183a001742e5705","tgt_lang":"zh-CN","translated":"工具","updated_at":"2026-04-05T17:11:02.649Z"}
|
||||
{"cache_key":"4297c339c27b767ffb25c04b0402467d032c04e4272e369c33dad1b2fad9462d","model":"gpt-5.4","provider":"openai","segment_id":"common.lastStart","source_path":"ui/src/i18n/locales/zh-CN.ts","src_lang":"en","text":"Last start","text_hash":"37a1eec0a7895251539d960c0ee5951c83da27223bdf5223c8440a4a48e061ef","tgt_lang":"zh-CN","translated":"上次启动","updated_at":"2026-04-06T02:47:28.112Z"}
|
||||
{"cache_key":"42a2f0a683536f3df65516b4b660a06bfa4c1abbdf46a00f1d0cfed59261d7ed","model":"gpt-5.4","provider":"openai","segment_id":"sessionsView.filters","source_path":"ui/src/i18n/locales/zh-CN.ts","src_lang":"en","text":"Filters","text_hash":"546ebb8eb993ea561029d9febd84c363bdb09010bb2cb915a8287762b76b9a64","tgt_lang":"zh-CN","translated":"筛选","updated_at":"2026-04-05T17:10:36.565Z"}
|
||||
{"cache_key":"43e22bbbc8ae9553cb8c30edcc49eed0b287b681446a2ba39e5f6a6da386f268","model":"gpt-5.4","provider":"openai","segment_id":"usage.mosaic.sun","source_path":"ui/src/i18n/locales/zh-CN.ts","src_lang":"en","text":"Sun","text_hash":"db18f17fe532007616d0d0fcc303281c35aafc940b13e6af55e63f8fed304718","tgt_lang":"zh-CN","translated":"周日","updated_at":"2026-04-05T17:11:05.447Z"}
|
||||
{"cache_key":"440f26448eacf4104c8e58d2fc5ad9ea4c20591f2a35e7f6052aafa8734a0099","model":"gpt-5.4","provider":"openai","segment_id":"cron.jobs.schedule","source_path":"ui/src/i18n/locales/zh-CN.ts","src_lang":"en","text":"Schedule","text_hash":"f4830a1dae2980447c716bd4b5779b7013575ef09f70ef4731457218792487b3","tgt_lang":"zh-CN","translated":"计划","updated_at":"2026-04-05T17:11:07.427Z"}
|
||||
{"cache_key":"441e3efbef12124ea08db6950e26ab0ea5ce908493e8509ff849988de1e4d9ac","model":"gpt-5.4","provider":"openai","segment_id":"channels.nostr.bannerHelp","source_path":"ui/src/i18n/locales/zh-CN.ts","src_lang":"en","text":"HTTPS URL to a banner image","text_hash":"5feb792028cf20b11294d2bed052e34770970d0a8a991fdc8eeb39045a9c42ca","tgt_lang":"zh-CN","translated":"横幅图片的 HTTPS URL","updated_at":"2026-04-06T02:47:39.053Z"}
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-05-03T18:28:16.548Z",
|
||||
"fallbackKeys": [
|
||||
"sessionsView.activeTooltip",
|
||||
"sessionsView.globalTooltip",
|
||||
"sessionsView.hideFilters",
|
||||
"sessionsView.limitTooltip",
|
||||
"sessionsView.showArchived",
|
||||
"sessionsView.showArchivedTooltip",
|
||||
"sessionsView.showFilters",
|
||||
"sessionsView.sourceFilters",
|
||||
"sessionsView.unknownTooltip"
|
||||
],
|
||||
"generatedAt": "2026-05-04T05:56:55.670Z",
|
||||
"locale": "zh-TW",
|
||||
"model": "gpt-5.5",
|
||||
"provider": "openai",
|
||||
"sourceHash": "d994d064e7880cb40c055c5201db0b9045c15f6abf37fac9b317e25e82b6b7b6",
|
||||
"totalKeys": 991,
|
||||
"translatedKeys": 991,
|
||||
"sourceHash": "66b99bdd39fc9dae0476c0e2fd2c0392f068bc3f7f91b532b04a106f14a3f3df",
|
||||
"totalKeys": 1001,
|
||||
"translatedKeys": 992,
|
||||
"workflow": 1
|
||||
}
|
||||
|
||||
@@ -524,6 +524,7 @@
|
||||
{"cache_key":"b269cb408b9d173f35cea21df10a8df5141c31a54bd05a857aae267a431f892f","model":"gpt-5.4","provider":"openai","segment_id":"usage.filters.sessionsCount","source_path":"ui/src/i18n/locales/zh-TW.ts","src_lang":"en","text":"{count} sessions","text_hash":"27de9b3be346a2abd2cb67f9f93abfe8100d7ce996e1204b75fc84670c7818e6","tgt_lang":"zh-TW","translated":"{count} 個工作階段","updated_at":"2026-04-05T17:10:38.462Z"}
|
||||
{"cache_key":"b27fdf8fe6eda22093c13a6518f4bccb13f1a2ecfbdcd979b22db0c46a227a11","model":"gpt-5.5","provider":"openai","segment_id":"cron.quickCreate.whatHint","source_path":"ui/src/i18n/locales/zh-TW.ts","src_lang":"en","text":"Describe the task in natural language. The agent will run this prompt each time.","text_hash":"740434f6a8f3a54a5bbe7362b393c2d4c4a25789d52c76dddb57c96c25432f0e","tgt_lang":"zh-TW","translated":"以自然語言描述任務。代理程式每次都會執行此提示。","updated_at":"2026-04-29T20:12:36.404Z"}
|
||||
{"cache_key":"b3810becb628fd4a8565e19b7dfbfb4dc1ba31f48692f496a7ffbf0e5ee6a120","model":"gpt-5.4","provider":"openai","segment_id":"cron.runs.selectJobHint","source_path":"ui/src/i18n/locales/zh-TW.ts","src_lang":"en","text":"Select a job to inspect run history.","text_hash":"cd1410f81b92c15d46b317f73d250066fbcaf4dc1f9e1978309f36ab21f17135","tgt_lang":"zh-TW","translated":"選擇一個工作以檢視執行記錄。","updated_at":"2026-04-05T17:11:25.473Z"}
|
||||
{"cache_key":"b3d605bc167ceb0536d65bec566f2397c44a2faf8e77ae98e248f4ef9cd1276e","model":"gpt-5.4","provider":"openai","segment_id":"sessionsView.filters","source_path":"ui/src/i18n/locales/zh-TW.ts","src_lang":"en","text":"Filters","text_hash":"546ebb8eb993ea561029d9febd84c363bdb09010bb2cb915a8287762b76b9a64","tgt_lang":"zh-TW","translated":"篩選條件","updated_at":"2026-04-05T17:10:31.705Z"}
|
||||
{"cache_key":"b44759978cb549a8f29d8066804c61830059f0b604049eba195f1d4334d26092","model":"gpt-5.4","provider":"openai","segment_id":"cron.form.timezonePlaceholder","source_path":"ui/src/i18n/locales/zh-TW.ts","src_lang":"en","text":"America/Los_Angeles","text_hash":"2d4bbedff807854084b7855fd6e0d49ab55b41e8c9395debd40d0e8e1d3390cf","tgt_lang":"zh-TW","translated":"America/Los_Angeles","updated_at":"2026-04-06T02:59:17.485Z"}
|
||||
{"cache_key":"b4cb2889b6ce8d4b3933053db7ac9586bfa640f87e40933783e388ed8e9d9c26","model":"gpt-5.4","provider":"openai","segment_id":"usage.metrics.tokens","source_path":"ui/src/i18n/locales/zh-TW.ts","src_lang":"en","text":"Tokens","text_hash":"a039dfb9628b53ddaebcfe8ef0793e3fdf19867601295f00d192acef59050869","tgt_lang":"zh-TW","translated":"Token","updated_at":"2026-04-05T17:10:31.705Z"}
|
||||
{"cache_key":"b5a677e7111689d2cac8ff845e690ca219e4fc96480dab57613facb3dc16376a","model":"gpt-5.5","provider":"openai","segment_id":"cron.quickCreate.delivery.silent.label","source_path":"ui/src/i18n/locales/zh-TW.ts","src_lang":"en","text":"Silent","text_hash":"ddbcf06726488a43af36838754808ac5041b05ab6434735615979d820725b56f","tgt_lang":"zh-TW","translated":"靜默","updated_at":"2026-04-29T20:12:31.105Z"}
|
||||
|
||||
@@ -159,8 +159,22 @@ export const ar: TranslationMap = {
|
||||
store: "المخزن: {path}",
|
||||
active: "نشط",
|
||||
limit: "الحد",
|
||||
filters: "عوامل التصفية",
|
||||
showFilters: "Show filters",
|
||||
hideFilters: "Hide filters",
|
||||
sourceFilters: "Session source filters",
|
||||
global: "عام",
|
||||
unknown: "غير معروف",
|
||||
showArchived: "Show archived",
|
||||
activeTooltip:
|
||||
"Only request sessions updated within the last N minutes. Clear the field, or show archived sessions, to remove the active-time cutoff.",
|
||||
limitTooltip:
|
||||
"Maximum rows to request from the Gateway. Higher limits can make large stores slower.",
|
||||
globalTooltip:
|
||||
"Include the special global session bucket shared outside a specific agent or chat.",
|
||||
unknownTooltip: "Include the special unknown session bucket for legacy or unresolved traffic.",
|
||||
showArchivedTooltip:
|
||||
"Include explicitly archived rows and older store-backed sessions by removing the active-time cutoff.",
|
||||
minutesPlaceholder: "دقيقة",
|
||||
searchPlaceholder: "تصفية حسب المفتاح أو الوكيل أو التسمية أو النوع…",
|
||||
selected: "تم تحديد {count}",
|
||||
|
||||
@@ -163,8 +163,22 @@ export const de: TranslationMap = {
|
||||
store: "Speicher: {path}",
|
||||
active: "Aktiv",
|
||||
limit: "Limit",
|
||||
filters: "Filter",
|
||||
showFilters: "Show filters",
|
||||
hideFilters: "Hide filters",
|
||||
sourceFilters: "Session source filters",
|
||||
global: "Global",
|
||||
unknown: "Unbekannt",
|
||||
showArchived: "Show archived",
|
||||
activeTooltip:
|
||||
"Only request sessions updated within the last N minutes. Clear the field, or show archived sessions, to remove the active-time cutoff.",
|
||||
limitTooltip:
|
||||
"Maximum rows to request from the Gateway. Higher limits can make large stores slower.",
|
||||
globalTooltip:
|
||||
"Include the special global session bucket shared outside a specific agent or chat.",
|
||||
unknownTooltip: "Include the special unknown session bucket for legacy or unresolved traffic.",
|
||||
showArchivedTooltip:
|
||||
"Include explicitly archived rows and older store-backed sessions by removing the active-time cutoff.",
|
||||
minutesPlaceholder: "Min.",
|
||||
searchPlaceholder: "Nach Schlüssel, Agent, Label, Art filtern…",
|
||||
selected: "{count} ausgewählt",
|
||||
|
||||
@@ -158,8 +158,18 @@ export const en: TranslationMap = {
|
||||
store: "Store: {path}",
|
||||
active: "Active",
|
||||
limit: "Limit",
|
||||
filters: "Filters",
|
||||
showFilters: "Show filters",
|
||||
hideFilters: "Hide filters",
|
||||
sourceFilters: "Session source filters",
|
||||
global: "Global",
|
||||
unknown: "Unknown",
|
||||
showArchived: "Show archived",
|
||||
activeTooltip: "Updated in the last N minutes.",
|
||||
limitTooltip: "Max sessions to load.",
|
||||
globalTooltip: "Include global sessions.",
|
||||
unknownTooltip: "Include unknown sessions.",
|
||||
showArchivedTooltip: "Include archived sessions.",
|
||||
minutesPlaceholder: "min",
|
||||
searchPlaceholder: "Filter by key, agent, label, kind…",
|
||||
selected: "{count} selected",
|
||||
|
||||
@@ -160,8 +160,22 @@ export const es: TranslationMap = {
|
||||
store: "Almacén: {path}",
|
||||
active: "Activo",
|
||||
limit: "Límite",
|
||||
filters: "Filtros",
|
||||
showFilters: "Show filters",
|
||||
hideFilters: "Hide filters",
|
||||
sourceFilters: "Session source filters",
|
||||
global: "Global",
|
||||
unknown: "Desconocido",
|
||||
showArchived: "Show archived",
|
||||
activeTooltip:
|
||||
"Only request sessions updated within the last N minutes. Clear the field, or show archived sessions, to remove the active-time cutoff.",
|
||||
limitTooltip:
|
||||
"Maximum rows to request from the Gateway. Higher limits can make large stores slower.",
|
||||
globalTooltip:
|
||||
"Include the special global session bucket shared outside a specific agent or chat.",
|
||||
unknownTooltip: "Include the special unknown session bucket for legacy or unresolved traffic.",
|
||||
showArchivedTooltip:
|
||||
"Include explicitly archived rows and older store-backed sessions by removing the active-time cutoff.",
|
||||
minutesPlaceholder: "min",
|
||||
searchPlaceholder: "Filtrar por clave, agente, etiqueta, tipo…",
|
||||
selected: "{count} seleccionados",
|
||||
|
||||
@@ -161,8 +161,22 @@ export const fa: TranslationMap = {
|
||||
store: "ذخیرهگاه: {path}",
|
||||
active: "فعال",
|
||||
limit: "حد",
|
||||
filters: "فیلترها",
|
||||
showFilters: "Show filters",
|
||||
hideFilters: "Hide filters",
|
||||
sourceFilters: "Session source filters",
|
||||
global: "سراسری",
|
||||
unknown: "نامشخص",
|
||||
showArchived: "Show archived",
|
||||
activeTooltip:
|
||||
"Only request sessions updated within the last N minutes. Clear the field, or show archived sessions, to remove the active-time cutoff.",
|
||||
limitTooltip:
|
||||
"Maximum rows to request from the Gateway. Higher limits can make large stores slower.",
|
||||
globalTooltip:
|
||||
"Include the special global session bucket shared outside a specific agent or chat.",
|
||||
unknownTooltip: "Include the special unknown session bucket for legacy or unresolved traffic.",
|
||||
showArchivedTooltip:
|
||||
"Include explicitly archived rows and older store-backed sessions by removing the active-time cutoff.",
|
||||
minutesPlaceholder: "دقیقه",
|
||||
searchPlaceholder: "فیلتر بر اساس کلید، عامل، برچسب، نوع…",
|
||||
selected: "{count} انتخابشده",
|
||||
|
||||
@@ -162,8 +162,22 @@ export const fr: TranslationMap = {
|
||||
store: "Stockage : {path}",
|
||||
active: "Actif",
|
||||
limit: "Limite",
|
||||
filters: "Filtres",
|
||||
showFilters: "Show filters",
|
||||
hideFilters: "Hide filters",
|
||||
sourceFilters: "Session source filters",
|
||||
global: "Global",
|
||||
unknown: "Inconnu",
|
||||
showArchived: "Show archived",
|
||||
activeTooltip:
|
||||
"Only request sessions updated within the last N minutes. Clear the field, or show archived sessions, to remove the active-time cutoff.",
|
||||
limitTooltip:
|
||||
"Maximum rows to request from the Gateway. Higher limits can make large stores slower.",
|
||||
globalTooltip:
|
||||
"Include the special global session bucket shared outside a specific agent or chat.",
|
||||
unknownTooltip: "Include the special unknown session bucket for legacy or unresolved traffic.",
|
||||
showArchivedTooltip:
|
||||
"Include explicitly archived rows and older store-backed sessions by removing the active-time cutoff.",
|
||||
minutesPlaceholder: "min",
|
||||
searchPlaceholder: "Filtrer par clé, agent, libellé, type…",
|
||||
selected: "{count} sélectionné(s)",
|
||||
|
||||
@@ -160,8 +160,22 @@ export const id: TranslationMap = {
|
||||
store: "Penyimpanan: {path}",
|
||||
active: "Aktif",
|
||||
limit: "Batas",
|
||||
filters: "Filter",
|
||||
showFilters: "Show filters",
|
||||
hideFilters: "Hide filters",
|
||||
sourceFilters: "Session source filters",
|
||||
global: "Global",
|
||||
unknown: "Tidak diketahui",
|
||||
showArchived: "Show archived",
|
||||
activeTooltip:
|
||||
"Only request sessions updated within the last N minutes. Clear the field, or show archived sessions, to remove the active-time cutoff.",
|
||||
limitTooltip:
|
||||
"Maximum rows to request from the Gateway. Higher limits can make large stores slower.",
|
||||
globalTooltip:
|
||||
"Include the special global session bucket shared outside a specific agent or chat.",
|
||||
unknownTooltip: "Include the special unknown session bucket for legacy or unresolved traffic.",
|
||||
showArchivedTooltip:
|
||||
"Include explicitly archived rows and older store-backed sessions by removing the active-time cutoff.",
|
||||
minutesPlaceholder: "mnt",
|
||||
searchPlaceholder: "Filter menurut kunci, agen, label, jenis…",
|
||||
selected: "{count} dipilih",
|
||||
|
||||
@@ -160,8 +160,22 @@ export const it: TranslationMap = {
|
||||
store: "Archivio: {path}",
|
||||
active: "Attivo",
|
||||
limit: "Limite",
|
||||
filters: "Filtri",
|
||||
showFilters: "Show filters",
|
||||
hideFilters: "Hide filters",
|
||||
sourceFilters: "Session source filters",
|
||||
global: "Globale",
|
||||
unknown: "Sconosciuto",
|
||||
showArchived: "Show archived",
|
||||
activeTooltip:
|
||||
"Only request sessions updated within the last N minutes. Clear the field, or show archived sessions, to remove the active-time cutoff.",
|
||||
limitTooltip:
|
||||
"Maximum rows to request from the Gateway. Higher limits can make large stores slower.",
|
||||
globalTooltip:
|
||||
"Include the special global session bucket shared outside a specific agent or chat.",
|
||||
unknownTooltip: "Include the special unknown session bucket for legacy or unresolved traffic.",
|
||||
showArchivedTooltip:
|
||||
"Include explicitly archived rows and older store-backed sessions by removing the active-time cutoff.",
|
||||
minutesPlaceholder: "min",
|
||||
searchPlaceholder: "Filtra per chiave, agente, etichetta, tipo…",
|
||||
selected: "{count} selezionati",
|
||||
|
||||
@@ -163,8 +163,22 @@ export const ja_JP: TranslationMap = {
|
||||
store: "ストア: {path}",
|
||||
active: "有効",
|
||||
limit: "制限",
|
||||
filters: "フィルター",
|
||||
showFilters: "Show filters",
|
||||
hideFilters: "Hide filters",
|
||||
sourceFilters: "Session source filters",
|
||||
global: "グローバル",
|
||||
unknown: "不明",
|
||||
showArchived: "Show archived",
|
||||
activeTooltip:
|
||||
"Only request sessions updated within the last N minutes. Clear the field, or show archived sessions, to remove the active-time cutoff.",
|
||||
limitTooltip:
|
||||
"Maximum rows to request from the Gateway. Higher limits can make large stores slower.",
|
||||
globalTooltip:
|
||||
"Include the special global session bucket shared outside a specific agent or chat.",
|
||||
unknownTooltip: "Include the special unknown session bucket for legacy or unresolved traffic.",
|
||||
showArchivedTooltip:
|
||||
"Include explicitly archived rows and older store-backed sessions by removing the active-time cutoff.",
|
||||
minutesPlaceholder: "分",
|
||||
searchPlaceholder: "キー、エージェント、ラベル、種類で絞り込み…",
|
||||
selected: "{count} 件選択中",
|
||||
|
||||
@@ -159,8 +159,22 @@ export const ko: TranslationMap = {
|
||||
store: "저장소: {path}",
|
||||
active: "활성",
|
||||
limit: "제한",
|
||||
filters: "필터",
|
||||
showFilters: "Show filters",
|
||||
hideFilters: "Hide filters",
|
||||
sourceFilters: "Session source filters",
|
||||
global: "전역",
|
||||
unknown: "알 수 없음",
|
||||
showArchived: "Show archived",
|
||||
activeTooltip:
|
||||
"Only request sessions updated within the last N minutes. Clear the field, or show archived sessions, to remove the active-time cutoff.",
|
||||
limitTooltip:
|
||||
"Maximum rows to request from the Gateway. Higher limits can make large stores slower.",
|
||||
globalTooltip:
|
||||
"Include the special global session bucket shared outside a specific agent or chat.",
|
||||
unknownTooltip: "Include the special unknown session bucket for legacy or unresolved traffic.",
|
||||
showArchivedTooltip:
|
||||
"Include explicitly archived rows and older store-backed sessions by removing the active-time cutoff.",
|
||||
minutesPlaceholder: "분",
|
||||
searchPlaceholder: "키, 에이전트, 레이블, 종류로 필터링…",
|
||||
selected: "{count}개 선택됨",
|
||||
|
||||
@@ -162,8 +162,22 @@ export const nl: TranslationMap = {
|
||||
store: "Opslag: {path}",
|
||||
active: "Actief",
|
||||
limit: "Limiet",
|
||||
filters: "Filters",
|
||||
showFilters: "Show filters",
|
||||
hideFilters: "Hide filters",
|
||||
sourceFilters: "Session source filters",
|
||||
global: "Globaal",
|
||||
unknown: "Onbekend",
|
||||
showArchived: "Show archived",
|
||||
activeTooltip:
|
||||
"Only request sessions updated within the last N minutes. Clear the field, or show archived sessions, to remove the active-time cutoff.",
|
||||
limitTooltip:
|
||||
"Maximum rows to request from the Gateway. Higher limits can make large stores slower.",
|
||||
globalTooltip:
|
||||
"Include the special global session bucket shared outside a specific agent or chat.",
|
||||
unknownTooltip: "Include the special unknown session bucket for legacy or unresolved traffic.",
|
||||
showArchivedTooltip:
|
||||
"Include explicitly archived rows and older store-backed sessions by removing the active-time cutoff.",
|
||||
minutesPlaceholder: "min",
|
||||
searchPlaceholder: "Filter op sleutel, agent, label, type…",
|
||||
selected: "{count} geselecteerd",
|
||||
|
||||
@@ -161,8 +161,22 @@ export const pl: TranslationMap = {
|
||||
store: "Magazyn: {path}",
|
||||
active: "Aktywny",
|
||||
limit: "Limit",
|
||||
filters: "Filtry",
|
||||
showFilters: "Show filters",
|
||||
hideFilters: "Hide filters",
|
||||
sourceFilters: "Session source filters",
|
||||
global: "Globalne",
|
||||
unknown: "Nieznane",
|
||||
showArchived: "Show archived",
|
||||
activeTooltip:
|
||||
"Only request sessions updated within the last N minutes. Clear the field, or show archived sessions, to remove the active-time cutoff.",
|
||||
limitTooltip:
|
||||
"Maximum rows to request from the Gateway. Higher limits can make large stores slower.",
|
||||
globalTooltip:
|
||||
"Include the special global session bucket shared outside a specific agent or chat.",
|
||||
unknownTooltip: "Include the special unknown session bucket for legacy or unresolved traffic.",
|
||||
showArchivedTooltip:
|
||||
"Include explicitly archived rows and older store-backed sessions by removing the active-time cutoff.",
|
||||
minutesPlaceholder: "min",
|
||||
searchPlaceholder: "Filtruj według klucza, agenta, etykiety, rodzaju…",
|
||||
selected: "Wybrano: {count}",
|
||||
|
||||
@@ -160,8 +160,22 @@ export const pt_BR: TranslationMap = {
|
||||
store: "Armazenamento: {path}",
|
||||
active: "Ativo",
|
||||
limit: "Limite",
|
||||
filters: "Filtros",
|
||||
showFilters: "Show filters",
|
||||
hideFilters: "Hide filters",
|
||||
sourceFilters: "Session source filters",
|
||||
global: "Global",
|
||||
unknown: "Desconhecido",
|
||||
showArchived: "Show archived",
|
||||
activeTooltip:
|
||||
"Only request sessions updated within the last N minutes. Clear the field, or show archived sessions, to remove the active-time cutoff.",
|
||||
limitTooltip:
|
||||
"Maximum rows to request from the Gateway. Higher limits can make large stores slower.",
|
||||
globalTooltip:
|
||||
"Include the special global session bucket shared outside a specific agent or chat.",
|
||||
unknownTooltip: "Include the special unknown session bucket for legacy or unresolved traffic.",
|
||||
showArchivedTooltip:
|
||||
"Include explicitly archived rows and older store-backed sessions by removing the active-time cutoff.",
|
||||
minutesPlaceholder: "min",
|
||||
searchPlaceholder: "Filtrar por chave, agente, rótulo, tipo…",
|
||||
selected: "{count} selecionado(s)",
|
||||
|
||||
@@ -158,8 +158,22 @@ export const th: TranslationMap = {
|
||||
store: "ที่เก็บ: {path}",
|
||||
active: "ใช้งานอยู่",
|
||||
limit: "ขีดจำกัด",
|
||||
filters: "ตัวกรอง",
|
||||
showFilters: "Show filters",
|
||||
hideFilters: "Hide filters",
|
||||
sourceFilters: "Session source filters",
|
||||
global: "ส่วนกลาง",
|
||||
unknown: "ไม่ทราบ",
|
||||
showArchived: "Show archived",
|
||||
activeTooltip:
|
||||
"Only request sessions updated within the last N minutes. Clear the field, or show archived sessions, to remove the active-time cutoff.",
|
||||
limitTooltip:
|
||||
"Maximum rows to request from the Gateway. Higher limits can make large stores slower.",
|
||||
globalTooltip:
|
||||
"Include the special global session bucket shared outside a specific agent or chat.",
|
||||
unknownTooltip: "Include the special unknown session bucket for legacy or unresolved traffic.",
|
||||
showArchivedTooltip:
|
||||
"Include explicitly archived rows and older store-backed sessions by removing the active-time cutoff.",
|
||||
minutesPlaceholder: "นาที",
|
||||
searchPlaceholder: "กรองตามคีย์, agent, ป้ายกำกับ, ชนิด…",
|
||||
selected: "เลือกแล้ว {count} รายการ",
|
||||
|
||||
@@ -162,8 +162,22 @@ export const tr: TranslationMap = {
|
||||
store: "Depo: {path}",
|
||||
active: "Etkin",
|
||||
limit: "Sınır",
|
||||
filters: "Filtreler",
|
||||
showFilters: "Show filters",
|
||||
hideFilters: "Hide filters",
|
||||
sourceFilters: "Session source filters",
|
||||
global: "Genel",
|
||||
unknown: "Bilinmiyor",
|
||||
showArchived: "Show archived",
|
||||
activeTooltip:
|
||||
"Only request sessions updated within the last N minutes. Clear the field, or show archived sessions, to remove the active-time cutoff.",
|
||||
limitTooltip:
|
||||
"Maximum rows to request from the Gateway. Higher limits can make large stores slower.",
|
||||
globalTooltip:
|
||||
"Include the special global session bucket shared outside a specific agent or chat.",
|
||||
unknownTooltip: "Include the special unknown session bucket for legacy or unresolved traffic.",
|
||||
showArchivedTooltip:
|
||||
"Include explicitly archived rows and older store-backed sessions by removing the active-time cutoff.",
|
||||
minutesPlaceholder: "dk",
|
||||
searchPlaceholder: "Anahtara, ajana, etikete, türe göre filtrele…",
|
||||
selected: "{count} seçildi",
|
||||
|
||||
@@ -161,8 +161,22 @@ export const uk: TranslationMap = {
|
||||
store: "Сховище: {path}",
|
||||
active: "Активно",
|
||||
limit: "Обмеження",
|
||||
filters: "Фільтри",
|
||||
showFilters: "Show filters",
|
||||
hideFilters: "Hide filters",
|
||||
sourceFilters: "Session source filters",
|
||||
global: "Глобально",
|
||||
unknown: "Невідомо",
|
||||
showArchived: "Show archived",
|
||||
activeTooltip:
|
||||
"Only request sessions updated within the last N minutes. Clear the field, or show archived sessions, to remove the active-time cutoff.",
|
||||
limitTooltip:
|
||||
"Maximum rows to request from the Gateway. Higher limits can make large stores slower.",
|
||||
globalTooltip:
|
||||
"Include the special global session bucket shared outside a specific agent or chat.",
|
||||
unknownTooltip: "Include the special unknown session bucket for legacy or unresolved traffic.",
|
||||
showArchivedTooltip:
|
||||
"Include explicitly archived rows and older store-backed sessions by removing the active-time cutoff.",
|
||||
minutesPlaceholder: "хв",
|
||||
searchPlaceholder: "Фільтрувати за ключем, агентом, міткою, типом…",
|
||||
selected: "Вибрано: {count}",
|
||||
|
||||
@@ -160,8 +160,22 @@ export const vi: TranslationMap = {
|
||||
store: "Kho lưu trữ: {path}",
|
||||
active: "Đang hoạt động",
|
||||
limit: "Giới hạn",
|
||||
filters: "Bộ lọc",
|
||||
showFilters: "Show filters",
|
||||
hideFilters: "Hide filters",
|
||||
sourceFilters: "Session source filters",
|
||||
global: "Toàn cục",
|
||||
unknown: "Không rõ",
|
||||
showArchived: "Show archived",
|
||||
activeTooltip:
|
||||
"Only request sessions updated within the last N minutes. Clear the field, or show archived sessions, to remove the active-time cutoff.",
|
||||
limitTooltip:
|
||||
"Maximum rows to request from the Gateway. Higher limits can make large stores slower.",
|
||||
globalTooltip:
|
||||
"Include the special global session bucket shared outside a specific agent or chat.",
|
||||
unknownTooltip: "Include the special unknown session bucket for legacy or unresolved traffic.",
|
||||
showArchivedTooltip:
|
||||
"Include explicitly archived rows and older store-backed sessions by removing the active-time cutoff.",
|
||||
minutesPlaceholder: "phút",
|
||||
searchPlaceholder: "Lọc theo khóa, agent, nhãn, loại…",
|
||||
selected: "Đã chọn {count}",
|
||||
|
||||
@@ -158,8 +158,22 @@ export const zh_CN: TranslationMap = {
|
||||
store: "存储:{path}",
|
||||
active: "活跃",
|
||||
limit: "限制",
|
||||
filters: "筛选",
|
||||
showFilters: "Show filters",
|
||||
hideFilters: "Hide filters",
|
||||
sourceFilters: "Session source filters",
|
||||
global: "全局",
|
||||
unknown: "未知",
|
||||
showArchived: "Show archived",
|
||||
activeTooltip:
|
||||
"Only request sessions updated within the last N minutes. Clear the field, or show archived sessions, to remove the active-time cutoff.",
|
||||
limitTooltip:
|
||||
"Maximum rows to request from the Gateway. Higher limits can make large stores slower.",
|
||||
globalTooltip:
|
||||
"Include the special global session bucket shared outside a specific agent or chat.",
|
||||
unknownTooltip: "Include the special unknown session bucket for legacy or unresolved traffic.",
|
||||
showArchivedTooltip:
|
||||
"Include explicitly archived rows and older store-backed sessions by removing the active-time cutoff.",
|
||||
minutesPlaceholder: "分钟",
|
||||
searchPlaceholder: "按密钥、代理、标签、类型筛选…",
|
||||
selected: "已选择 {count} 项",
|
||||
|
||||
@@ -158,8 +158,22 @@ export const zh_TW: TranslationMap = {
|
||||
store: "儲存位置:{path}",
|
||||
active: "啟用中",
|
||||
limit: "限制",
|
||||
filters: "篩選條件",
|
||||
showFilters: "Show filters",
|
||||
hideFilters: "Hide filters",
|
||||
sourceFilters: "Session source filters",
|
||||
global: "全域",
|
||||
unknown: "未知",
|
||||
showArchived: "Show archived",
|
||||
activeTooltip:
|
||||
"Only request sessions updated within the last N minutes. Clear the field, or show archived sessions, to remove the active-time cutoff.",
|
||||
limitTooltip:
|
||||
"Maximum rows to request from the Gateway. Higher limits can make large stores slower.",
|
||||
globalTooltip:
|
||||
"Include the special global session bucket shared outside a specific agent or chat.",
|
||||
unknownTooltip: "Include the special unknown session bucket for legacy or unresolved traffic.",
|
||||
showArchivedTooltip:
|
||||
"Include explicitly archived rows and older store-backed sessions by removing the active-time cutoff.",
|
||||
minutesPlaceholder: "分鐘",
|
||||
searchPlaceholder: "依金鑰、代理程式、標籤、類型篩選…",
|
||||
selected: "已選取 {count} 個",
|
||||
|
||||
@@ -217,9 +217,7 @@ img.chat-avatar {
|
||||
border-radius: var(--radius-lg);
|
||||
padding: 10px 14px;
|
||||
box-shadow: none;
|
||||
transition:
|
||||
background var(--duration-fast) ease-out,
|
||||
border-color var(--duration-fast) ease-out;
|
||||
transition: border-color var(--duration-fast) ease-out;
|
||||
width: auto;
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
@@ -347,7 +345,7 @@ img.chat-avatar {
|
||||
}
|
||||
|
||||
.chat-bubble:hover {
|
||||
background: var(--bg-hover);
|
||||
border-color: color-mix(in srgb, var(--accent) 28%, var(--border));
|
||||
}
|
||||
|
||||
.chat-bubble--tool-shell:hover {
|
||||
@@ -366,7 +364,7 @@ img.chat-avatar {
|
||||
}
|
||||
|
||||
.chat-group.user .chat-bubble:hover {
|
||||
background: color-mix(in srgb, var(--accent) 20%, transparent);
|
||||
border-color: color-mix(in srgb, var(--accent) 32%, transparent);
|
||||
}
|
||||
|
||||
/* Streaming animation */
|
||||
|
||||
@@ -2309,6 +2309,10 @@
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.sessions-table {
|
||||
min-width: 1240px;
|
||||
}
|
||||
|
||||
.data-table th {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
@@ -2380,6 +2384,15 @@
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
|
||||
.session-data-row--expandable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.session-data-row--expandable:focus-visible {
|
||||
outline: 1px solid var(--border-strong);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
.data-table tbody tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
@@ -2419,6 +2432,11 @@
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
|
||||
.session-token-cell {
|
||||
min-width: 112px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Checkbox column */
|
||||
.data-table-checkbox-col {
|
||||
width: var(--data-table-checkbox-col-width);
|
||||
@@ -2579,6 +2597,284 @@ td.data-table-key-col {
|
||||
accent-color: var(--accent);
|
||||
}
|
||||
|
||||
.sessions-filter-panel {
|
||||
margin-bottom: 12px;
|
||||
border: 1px solid color-mix(in srgb, var(--border) 82%, transparent);
|
||||
border-radius: var(--radius-md);
|
||||
background: color-mix(in srgb, var(--bg-muted) 58%, transparent);
|
||||
}
|
||||
|
||||
.sessions-filter-panel__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
.sessions-filter-panel__title {
|
||||
padding-left: 4px;
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.02em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.sessions-filter-panel__toggle {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
min-height: 28px;
|
||||
padding: 4px 8px;
|
||||
color: var(--muted);
|
||||
background: transparent;
|
||||
border: 1px solid transparent;
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
color var(--duration-fast) ease,
|
||||
background-color var(--duration-fast) ease,
|
||||
border-color var(--duration-fast) ease;
|
||||
}
|
||||
|
||||
.sessions-filter-panel__toggle:hover {
|
||||
color: var(--text);
|
||||
background: color-mix(in srgb, var(--bg-hover) 72%, transparent);
|
||||
border-color: color-mix(in srgb, var(--border) 74%, transparent);
|
||||
}
|
||||
|
||||
.sessions-filter-panel__toggle:focus-visible {
|
||||
box-shadow: var(--focus-ring);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.sessions-filter-panel__toggle svg {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
.sessions-filter-bar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 0 6px 6px;
|
||||
}
|
||||
|
||||
.session-filter-primary-row,
|
||||
.session-filter-toggle-group {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.session-filter-field,
|
||||
.session-filter-check {
|
||||
position: relative;
|
||||
min-height: 34px;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid color-mix(in srgb, var(--border) 76%, transparent);
|
||||
border-radius: var(--radius-sm);
|
||||
background: color-mix(in srgb, var(--card) 88%, transparent);
|
||||
}
|
||||
|
||||
.session-filter-field {
|
||||
display: inline-grid;
|
||||
grid-template-columns: auto minmax(54px, max-content);
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 4px 8px 4px 10px;
|
||||
}
|
||||
|
||||
.session-filter-label,
|
||||
.session-filter-check__label {
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.session-filter-input {
|
||||
width: 62px;
|
||||
box-sizing: border-box;
|
||||
padding: 5px 7px;
|
||||
color: var(--text);
|
||||
background: var(--bg-elevated);
|
||||
border: 1px solid transparent;
|
||||
border-radius: calc(var(--radius-sm) - 2px);
|
||||
font-size: 12px;
|
||||
line-height: 1.2;
|
||||
outline: none;
|
||||
transition:
|
||||
border-color var(--duration-fast) ease,
|
||||
box-shadow var(--duration-fast) ease,
|
||||
opacity var(--duration-fast) ease;
|
||||
}
|
||||
|
||||
.session-filter-input--limit {
|
||||
width: 56px;
|
||||
}
|
||||
|
||||
.session-filter-input:focus {
|
||||
border-color: var(--border-strong);
|
||||
box-shadow: var(--focus-ring);
|
||||
}
|
||||
|
||||
.session-filter-input:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.48;
|
||||
}
|
||||
|
||||
.session-filter-check {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 6px 10px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
transition:
|
||||
border-color var(--duration-fast) ease,
|
||||
background-color var(--duration-fast) ease,
|
||||
color var(--duration-fast) ease;
|
||||
}
|
||||
|
||||
.session-filter-check:hover {
|
||||
border-color: color-mix(in srgb, var(--border-strong) 74%, transparent);
|
||||
background: color-mix(in srgb, var(--bg-hover) 72%, transparent);
|
||||
}
|
||||
|
||||
.session-filter-check--active {
|
||||
border-color: color-mix(in srgb, var(--accent) 64%, var(--border));
|
||||
color: var(--text);
|
||||
background: color-mix(in srgb, var(--accent-subtle) 72%, var(--card));
|
||||
}
|
||||
|
||||
.session-filter-field[data-tooltip]::before,
|
||||
.session-filter-check[data-tooltip]::before,
|
||||
.session-filter-field[data-tooltip]::after,
|
||||
.session-filter-check[data-tooltip]::after {
|
||||
position: absolute;
|
||||
z-index: 20;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition:
|
||||
opacity var(--duration-fast) ease,
|
||||
transform var(--duration-fast) ease;
|
||||
}
|
||||
|
||||
.session-filter-field[data-tooltip]::before,
|
||||
.session-filter-check[data-tooltip]::before {
|
||||
content: "";
|
||||
bottom: calc(100% + 4px);
|
||||
left: 18px;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: var(--text);
|
||||
transform: translateY(3px) rotate(45deg);
|
||||
}
|
||||
|
||||
.session-filter-field[data-tooltip]::after,
|
||||
.session-filter-check[data-tooltip]::after {
|
||||
content: attr(data-tooltip);
|
||||
bottom: calc(100% + 8px);
|
||||
left: 0;
|
||||
width: max-content;
|
||||
max-width: min(320px, calc(100vw - 32px));
|
||||
padding: 7px 9px;
|
||||
color: var(--card);
|
||||
background: var(--text);
|
||||
border-radius: var(--radius-sm);
|
||||
box-shadow: var(--shadow-md);
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
line-height: 1.35;
|
||||
white-space: normal;
|
||||
transform: translateY(3px);
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
.session-filter-field[data-tooltip]:hover::before,
|
||||
.session-filter-field[data-tooltip]:hover::after,
|
||||
.session-filter-check[data-tooltip]:hover::before,
|
||||
.session-filter-check[data-tooltip]:hover::after {
|
||||
opacity: 1;
|
||||
transform: translateY(0) rotate(45deg);
|
||||
}
|
||||
|
||||
.session-filter-field[data-tooltip]:hover::after,
|
||||
.session-filter-check[data-tooltip]:hover::after {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.session-filter-check__input {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0 0 0 0);
|
||||
clip-path: inset(50%);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.session-filter-check__mark {
|
||||
display: inline-grid;
|
||||
place-items: center;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
flex: 0 0 auto;
|
||||
color: color-mix(in srgb, var(--muted) 48%, transparent);
|
||||
opacity: 0.42;
|
||||
transition:
|
||||
color var(--duration-fast) ease,
|
||||
opacity var(--duration-fast) ease;
|
||||
}
|
||||
|
||||
.session-filter-check__mark svg {
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
stroke-width: 3;
|
||||
}
|
||||
|
||||
.session-filter-check__input:checked + .session-filter-check__mark {
|
||||
color: var(--accent);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.session-filter-check__input:focus-visible + .session-filter-check__mark {
|
||||
box-shadow: var(--focus-ring);
|
||||
}
|
||||
|
||||
.session-filter-check__input:checked ~ .session-filter-check__label {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.sessions-filter-bar {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.sessions-filter-bar,
|
||||
.session-filter-primary-row,
|
||||
.session-filter-toggle-group,
|
||||
.session-filter-field,
|
||||
.session-filter-check {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.session-filter-field {
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
}
|
||||
}
|
||||
|
||||
/* ===========================================
|
||||
Log Stream
|
||||
=========================================== */
|
||||
|
||||
@@ -24,3 +24,13 @@ describe("agent fallback chip styles", () => {
|
||||
expect(css).toContain(".agent-chip-input .chip-remove:disabled");
|
||||
});
|
||||
});
|
||||
|
||||
describe("sessions filter styles", () => {
|
||||
it("keeps the expanded sessions filters on one row until the mobile breakpoint", () => {
|
||||
const css = readComponentsCss();
|
||||
|
||||
expect(css).toContain(".sessions-filter-bar {\n display: flex;\n flex-wrap: wrap;");
|
||||
expect(css).toContain("@media (max-width: 760px)");
|
||||
expect(css).toContain(".sessions-filter-bar {\n flex-direction: column;");
|
||||
});
|
||||
});
|
||||
|
||||
11
ui/src/ui/app-defaults.test.ts
Normal file
11
ui/src/ui/app-defaults.test.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { DEFAULT_SESSIONS_FILTERS } from "./app-defaults.ts";
|
||||
|
||||
describe("app defaults", () => {
|
||||
it("defaults session list requests to a broader but bounded result set", () => {
|
||||
expect(DEFAULT_SESSIONS_FILTERS).toEqual({
|
||||
activeMinutes: "120",
|
||||
limit: "200",
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -10,6 +10,11 @@ export const DEFAULT_LOG_LEVEL_FILTERS: Record<LogLevel, boolean> = {
|
||||
fatal: true,
|
||||
};
|
||||
|
||||
export const DEFAULT_SESSIONS_FILTERS = {
|
||||
activeMinutes: "120",
|
||||
limit: "200",
|
||||
} as const;
|
||||
|
||||
export const DEFAULT_CRON_FORM: CronFormState = {
|
||||
name: "",
|
||||
description: "",
|
||||
|
||||
@@ -113,7 +113,29 @@ function createHost() {
|
||||
}
|
||||
|
||||
describe("handleGatewayEvent sessions.changed", () => {
|
||||
it("reloads sessions when the gateway pushes a sessions.changed event", () => {
|
||||
it("applies reliable session change snapshots without refetching the list", () => {
|
||||
loadSessionsMock.mockReset();
|
||||
applySessionsChangedEventMock.mockReset().mockReturnValue({ applied: true, change: "updated" });
|
||||
const host = createHost();
|
||||
const payload = {
|
||||
sessionKey: "agent:main:main",
|
||||
sessionId: "sess-main",
|
||||
kind: "direct",
|
||||
reason: "patch",
|
||||
};
|
||||
|
||||
handleGatewayEvent(host, {
|
||||
type: "event",
|
||||
event: "sessions.changed",
|
||||
payload,
|
||||
seq: 1,
|
||||
});
|
||||
|
||||
expect(applySessionsChangedEventMock).toHaveBeenCalledWith(host, payload);
|
||||
expect(loadSessionsMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("reloads sessions when a change event cannot be applied locally", () => {
|
||||
loadSessionsMock.mockReset();
|
||||
applySessionsChangedEventMock.mockReset().mockReturnValue({ applied: false });
|
||||
const host = createHost();
|
||||
@@ -121,11 +143,10 @@ describe("handleGatewayEvent sessions.changed", () => {
|
||||
handleGatewayEvent(host, {
|
||||
type: "event",
|
||||
event: "sessions.changed",
|
||||
payload: { sessionKey: "agent:main:main", reason: "patch" },
|
||||
payload: { sessionKey: "agent:main:main", reason: "cleanup" },
|
||||
seq: 1,
|
||||
});
|
||||
|
||||
expect(loadSessionsMock).toHaveBeenCalledTimes(1);
|
||||
expect(loadSessionsMock).toHaveBeenCalledWith(host);
|
||||
});
|
||||
|
||||
|
||||
@@ -755,8 +755,8 @@ function handleGatewayEventUnsafe(host: GatewayHost, evt: GatewayEventFrame) {
|
||||
}
|
||||
|
||||
if (evt.event === "sessions.changed") {
|
||||
applySessionsChangedEvent(host as unknown as SessionsState, evt.payload);
|
||||
if (isChatTurnSessionChangedPayload(evt.payload)) {
|
||||
const result = applySessionsChangedEvent(host as unknown as SessionsState, evt.payload);
|
||||
if (result.applied || isChatTurnSessionChangedPayload(evt.payload)) {
|
||||
return;
|
||||
}
|
||||
void loadSessions(host as unknown as SessionsState);
|
||||
|
||||
@@ -133,6 +133,7 @@ function createChatSessionState(overrides: Partial<AppViewState> = {}) {
|
||||
client: { request: vi.fn() },
|
||||
sessionsLoading: false,
|
||||
sessionsError: null,
|
||||
sessionsShowArchived: false,
|
||||
sessionsResult: {
|
||||
ts: 0,
|
||||
path: "",
|
||||
@@ -533,12 +534,10 @@ describe("resolveSessionOptionGroups", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps scoped fallbacks for active grouped sessions without useful row metadata", () => {
|
||||
it("does not synthesize active grouped sessions without a listed row", () => {
|
||||
const sessionKey = "agent:main:subagent:4f2146de-887b-4176-9abe-91140082959b";
|
||||
|
||||
expect(labelsForSessionOptions({ sessionKey })).toContain(
|
||||
"subagent:4f2146de-887b-4176-9abe-91140082959b",
|
||||
);
|
||||
expect(labelsForSessionOptions({ sessionKey })).toEqual([]);
|
||||
expect(
|
||||
labelsForSessionOptions({
|
||||
sessionKey,
|
||||
@@ -622,6 +621,7 @@ describe("createChatSession", () => {
|
||||
limit: 0,
|
||||
includeGlobal: true,
|
||||
includeUnknown: true,
|
||||
showArchived: false,
|
||||
},
|
||||
);
|
||||
expect(state.sessionKey).toBe("agent:ops:dashboard:new-chat");
|
||||
@@ -810,6 +810,7 @@ describe("switchChatSession", () => {
|
||||
limit: 0,
|
||||
includeGlobal: true,
|
||||
includeUnknown: true,
|
||||
showArchived: false,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -650,6 +650,7 @@ export async function createChatSession(state: AppViewState) {
|
||||
limit: 0,
|
||||
includeGlobal: true,
|
||||
includeUnknown: true,
|
||||
showArchived: Boolean(state.sessionsShowArchived),
|
||||
},
|
||||
);
|
||||
if (
|
||||
@@ -680,6 +681,7 @@ async function refreshSessionOptions(state: AppViewState) {
|
||||
limit: 0,
|
||||
includeGlobal: true,
|
||||
includeUnknown: true,
|
||||
showArchived: Boolean(state.sessionsShowArchived),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1680,6 +1680,8 @@ export function renderApp(state: AppViewState) {
|
||||
limit: state.sessionsFilterLimit,
|
||||
includeGlobal: state.sessionsIncludeGlobal,
|
||||
includeUnknown: state.sessionsIncludeUnknown,
|
||||
showArchived: state.sessionsShowArchived,
|
||||
filtersCollapsed: state.sessionsFiltersCollapsed,
|
||||
basePath: state.basePath,
|
||||
searchQuery: state.sessionsSearchQuery,
|
||||
agentIdentityById: state.agentIdentityById,
|
||||
@@ -1698,6 +1700,19 @@ export function renderApp(state: AppViewState) {
|
||||
state.sessionsFilterLimit = next.limit;
|
||||
state.sessionsIncludeGlobal = next.includeGlobal;
|
||||
state.sessionsIncludeUnknown = next.includeUnknown;
|
||||
state.sessionsShowArchived = next.showArchived;
|
||||
state.sessionsSelectedKeys = new Set();
|
||||
state.sessionsPage = 0;
|
||||
void loadSessions(state, {
|
||||
activeMinutes: Number(next.activeMinutes) || 0,
|
||||
limit: Number(next.limit) || 0,
|
||||
includeGlobal: next.includeGlobal,
|
||||
includeUnknown: next.includeUnknown,
|
||||
showArchived: next.showArchived,
|
||||
});
|
||||
},
|
||||
onToggleFiltersCollapsed: () => {
|
||||
state.sessionsFiltersCollapsed = !state.sessionsFiltersCollapsed;
|
||||
},
|
||||
onSearchChange: (q) => {
|
||||
state.sessionsSearchQuery = q;
|
||||
|
||||
@@ -260,6 +260,8 @@ export type AppViewState = {
|
||||
sessionsFilterLimit: string;
|
||||
sessionsIncludeGlobal: boolean;
|
||||
sessionsIncludeUnknown: boolean;
|
||||
sessionsShowArchived: boolean;
|
||||
sessionsFiltersCollapsed: boolean;
|
||||
sessionsHideCron: boolean;
|
||||
sessionsSearchQuery: string;
|
||||
sessionsSortColumn: "key" | "kind" | "updated" | "tokens";
|
||||
|
||||
@@ -25,7 +25,11 @@ import {
|
||||
type ChatInputHistoryKeyInput,
|
||||
type ChatInputHistoryKeyResult,
|
||||
} from "./app-chat.ts";
|
||||
import { DEFAULT_CRON_FORM, DEFAULT_LOG_LEVEL_FILTERS } from "./app-defaults.ts";
|
||||
import {
|
||||
DEFAULT_CRON_FORM,
|
||||
DEFAULT_LOG_LEVEL_FILTERS,
|
||||
DEFAULT_SESSIONS_FILTERS,
|
||||
} from "./app-defaults.ts";
|
||||
import type { EventLogEntry } from "./app-events.ts";
|
||||
import { connectGateway as connectGatewayInternal } from "./app-gateway.ts";
|
||||
import {
|
||||
@@ -370,10 +374,12 @@ export class OpenClawApp extends LitElement {
|
||||
@state() sessionsLoading = false;
|
||||
@state() sessionsResult: SessionsListResult | null = null;
|
||||
@state() sessionsError: string | null = null;
|
||||
@state() sessionsFilterActive = "120";
|
||||
@state() sessionsFilterLimit = "50";
|
||||
@state() sessionsFilterActive = DEFAULT_SESSIONS_FILTERS.activeMinutes;
|
||||
@state() sessionsFilterLimit = DEFAULT_SESSIONS_FILTERS.limit;
|
||||
@state() sessionsIncludeGlobal = true;
|
||||
@state() sessionsIncludeUnknown = false;
|
||||
@state() sessionsShowArchived = false;
|
||||
@state() sessionsFiltersCollapsed = false;
|
||||
@state() sessionsHideCron = true;
|
||||
@state() sessionsSearchQuery = "";
|
||||
@state() sessionsSortColumn: "key" | "kind" | "updated" | "tokens" = "updated";
|
||||
|
||||
@@ -77,6 +77,7 @@ async function refreshSessionOptions(state: AppViewState) {
|
||||
limit: 0,
|
||||
includeGlobal: true,
|
||||
includeUnknown: true,
|
||||
showArchived: Boolean(state.sessionsShowArchived),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -539,7 +540,9 @@ export function resolveSessionOptionGroups(
|
||||
}
|
||||
addOption(row.key);
|
||||
}
|
||||
addOption(sessionKey);
|
||||
if (byKey.has(sessionKey)) {
|
||||
addOption(sessionKey);
|
||||
}
|
||||
|
||||
for (const group of groups.values()) {
|
||||
const counts = new Map<string, number>();
|
||||
|
||||
@@ -29,6 +29,7 @@ function createState(request: RequestFn, overrides: Partial<SessionsState> = {})
|
||||
sessionsFilterLimit: "0",
|
||||
sessionsIncludeGlobal: true,
|
||||
sessionsIncludeUnknown: true,
|
||||
sessionsShowArchived: false,
|
||||
sessionsExpandedCheckpointKey: null,
|
||||
sessionsCheckpointItemsByKey: {},
|
||||
sessionsCheckpointLoadingKey: null,
|
||||
@@ -249,6 +250,160 @@ describe("deleteSessionsAndRefresh", () => {
|
||||
});
|
||||
|
||||
describe("loadSessions", () => {
|
||||
it("hides explicitly archived sessions by default", async () => {
|
||||
const request = vi.fn(async (method: string) => {
|
||||
if (method !== "sessions.list") {
|
||||
throw new Error(`unexpected method: ${method}`);
|
||||
}
|
||||
return {
|
||||
ts: 1,
|
||||
path: "(multiple)",
|
||||
count: 2,
|
||||
defaults: { modelProvider: null, model: null, contextTokens: null },
|
||||
sessions: [
|
||||
{ key: "agent:main:main", kind: "direct", updatedAt: 2 },
|
||||
{
|
||||
key: "agent:main:subagent:archived",
|
||||
kind: "direct",
|
||||
updatedAt: 1,
|
||||
status: "done",
|
||||
archived: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
const state = createState(request);
|
||||
|
||||
await loadSessions(state);
|
||||
|
||||
expect(state.sessionsResult?.sessions.map((session) => session.key)).toEqual([
|
||||
"agent:main:main",
|
||||
]);
|
||||
expect(state.sessionsResult?.count).toBe(1);
|
||||
});
|
||||
|
||||
it("includes explicitly archived sessions when explicitly shown", async () => {
|
||||
const request = vi.fn(async (method: string) => {
|
||||
if (method !== "sessions.list") {
|
||||
throw new Error(`unexpected method: ${method}`);
|
||||
}
|
||||
return {
|
||||
ts: 1,
|
||||
path: "(multiple)",
|
||||
count: 2,
|
||||
defaults: { modelProvider: null, model: null, contextTokens: null },
|
||||
sessions: [
|
||||
{ key: "agent:main:main", kind: "direct", updatedAt: 2 },
|
||||
{
|
||||
key: "agent:main:subagent:archived",
|
||||
kind: "direct",
|
||||
updatedAt: 1,
|
||||
status: "done",
|
||||
archived: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
const state = createState(request, { sessionsShowArchived: true });
|
||||
|
||||
await loadSessions(state);
|
||||
|
||||
expect(state.sessionsResult?.sessions.map((session) => session.key)).toEqual([
|
||||
"agent:main:main",
|
||||
"agent:main:subagent:archived",
|
||||
]);
|
||||
expect(state.sessionsResult?.count).toBe(2);
|
||||
});
|
||||
|
||||
it("keeps terminal non-archived sessions visible by default", async () => {
|
||||
const request = vi.fn(async (method: string) => {
|
||||
if (method !== "sessions.list") {
|
||||
throw new Error(`unexpected method: ${method}`);
|
||||
}
|
||||
return {
|
||||
ts: 1,
|
||||
path: "(multiple)",
|
||||
count: 2,
|
||||
defaults: { modelProvider: null, model: null, contextTokens: null },
|
||||
sessions: [
|
||||
{ key: "agent:main:main", kind: "direct", updatedAt: 2 },
|
||||
{
|
||||
key: "agent:main:subagent:done",
|
||||
kind: "direct",
|
||||
updatedAt: 1,
|
||||
status: "done",
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
const state = createState(request);
|
||||
|
||||
await loadSessions(state);
|
||||
|
||||
expect(state.sessionsResult?.sessions.map((session) => session.key)).toEqual([
|
||||
"agent:main:main",
|
||||
"agent:main:subagent:done",
|
||||
]);
|
||||
expect(state.sessionsResult?.count).toBe(2);
|
||||
});
|
||||
|
||||
it("omits the active-window cutoff when archived sessions are shown", async () => {
|
||||
const request = vi.fn(async (method: string) => {
|
||||
if (method !== "sessions.list") {
|
||||
throw new Error(`unexpected method: ${method}`);
|
||||
}
|
||||
return {
|
||||
ts: 1,
|
||||
path: "(multiple)",
|
||||
count: 0,
|
||||
defaults: { modelProvider: null, model: null, contextTokens: null },
|
||||
sessions: [],
|
||||
};
|
||||
});
|
||||
const state = createState(request, {
|
||||
sessionsFilterActive: "120",
|
||||
sessionsFilterLimit: "50",
|
||||
sessionsShowArchived: true,
|
||||
});
|
||||
|
||||
await loadSessions(state);
|
||||
|
||||
expect(request).toHaveBeenCalledWith("sessions.list", {
|
||||
limit: 50,
|
||||
includeGlobal: true,
|
||||
includeUnknown: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("applies the active-window cutoff while archived sessions are hidden", async () => {
|
||||
const request = vi.fn(async (method: string) => {
|
||||
if (method !== "sessions.list") {
|
||||
throw new Error(`unexpected method: ${method}`);
|
||||
}
|
||||
return {
|
||||
ts: 1,
|
||||
path: "(multiple)",
|
||||
count: 0,
|
||||
defaults: { modelProvider: null, model: null, contextTokens: null },
|
||||
sessions: [],
|
||||
};
|
||||
});
|
||||
const state = createState(request, {
|
||||
sessionsFilterActive: "120",
|
||||
sessionsFilterLimit: "50",
|
||||
sessionsShowArchived: false,
|
||||
});
|
||||
|
||||
await loadSessions(state);
|
||||
|
||||
expect(request).toHaveBeenCalledWith("sessions.list", {
|
||||
activeMinutes: 120,
|
||||
limit: 50,
|
||||
includeGlobal: true,
|
||||
includeUnknown: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("coalesces overlapping refreshes instead of dropping the latest request", async () => {
|
||||
let resolveFirst: () => void = () => undefined;
|
||||
const firstBlocker = new Promise<void>((resolve) => {
|
||||
@@ -272,7 +427,7 @@ describe("loadSessions", () => {
|
||||
ts: 2,
|
||||
path: "(multiple)",
|
||||
count: 0,
|
||||
defaults: {},
|
||||
defaults: { modelProvider: null, model: null, contextTokens: null },
|
||||
sessions: [],
|
||||
};
|
||||
});
|
||||
@@ -391,6 +546,127 @@ describe("loadSessions", () => {
|
||||
});
|
||||
|
||||
describe("applySessionsChangedEvent", () => {
|
||||
it("removes deleted sessions instead of keeping archived rows visible", () => {
|
||||
const state = createState(async () => undefined, {
|
||||
sessionsResult: {
|
||||
ts: 1,
|
||||
path: "(multiple)",
|
||||
count: 2,
|
||||
defaults: { modelProvider: null, model: null, contextTokens: null },
|
||||
sessions: [
|
||||
{ key: "agent:main:main", kind: "direct", updatedAt: 1 },
|
||||
{ key: "agent:main:old", kind: "direct", updatedAt: 1 },
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const applied = applySessionsChangedEvent(state, {
|
||||
sessionKey: "agent:main:old",
|
||||
reason: "delete",
|
||||
ts: 2,
|
||||
});
|
||||
|
||||
expect(applied).toEqual({ applied: true, change: "deleted" });
|
||||
expect(state.sessionsResult?.sessions.map((session) => session.key)).toEqual([
|
||||
"agent:main:main",
|
||||
]);
|
||||
expect(state.sessionsResult?.count).toBe(1);
|
||||
});
|
||||
|
||||
it("does not synthesize new sessions from partial events without a store-backed row", () => {
|
||||
const state = createState(async () => undefined, {
|
||||
sessionsResult: {
|
||||
ts: 1,
|
||||
path: "(multiple)",
|
||||
count: 0,
|
||||
defaults: { modelProvider: null, model: null, contextTokens: null },
|
||||
sessions: [],
|
||||
},
|
||||
});
|
||||
|
||||
const applied = applySessionsChangedEvent(state, {
|
||||
sessionKey: "agent:main:ephemeral",
|
||||
reason: "message",
|
||||
ts: 2,
|
||||
});
|
||||
|
||||
expect(applied).toEqual({ applied: false });
|
||||
expect(state.sessionsResult?.sessions).toEqual([]);
|
||||
});
|
||||
|
||||
it("applies partial events only to existing source-of-truth rows", () => {
|
||||
const state = createState(async () => undefined, {
|
||||
sessionsResult: {
|
||||
ts: 1,
|
||||
path: "(multiple)",
|
||||
count: 1,
|
||||
defaults: { modelProvider: null, model: null, contextTokens: null },
|
||||
sessions: [{ key: "agent:main:main", kind: "direct", updatedAt: 1 }],
|
||||
},
|
||||
});
|
||||
|
||||
const applied = applySessionsChangedEvent(state, {
|
||||
sessionKey: "agent:main:main",
|
||||
reason: "message",
|
||||
ts: 2,
|
||||
});
|
||||
|
||||
expect(applied).toEqual({ applied: true, change: "updated" });
|
||||
expect(state.sessionsResult?.sessions).toEqual([
|
||||
{ key: "agent:main:main", kind: "direct", updatedAt: 1 },
|
||||
]);
|
||||
});
|
||||
|
||||
it("drops rows that become explicitly archived while archived sessions are hidden", () => {
|
||||
const state = createState(async () => undefined, {
|
||||
sessionsResult: {
|
||||
ts: 1,
|
||||
path: "(multiple)",
|
||||
count: 1,
|
||||
defaults: { modelProvider: null, model: null, contextTokens: null },
|
||||
sessions: [{ key: "agent:main:subagent:done", kind: "direct", updatedAt: 1 }],
|
||||
},
|
||||
});
|
||||
|
||||
const applied = applySessionsChangedEvent(state, {
|
||||
sessionKey: "agent:main:subagent:done",
|
||||
sessionId: "sess-done",
|
||||
status: "done",
|
||||
archived: true,
|
||||
ts: 2,
|
||||
});
|
||||
|
||||
expect(applied).toEqual({ applied: true, change: "deleted" });
|
||||
expect(state.sessionsResult?.sessions).toEqual([]);
|
||||
});
|
||||
|
||||
it("keeps terminal status updates visible while archived sessions are hidden", () => {
|
||||
const state = createState(async () => undefined, {
|
||||
sessionsResult: {
|
||||
ts: 1,
|
||||
path: "(multiple)",
|
||||
count: 1,
|
||||
defaults: { modelProvider: null, model: null, contextTokens: null },
|
||||
sessions: [{ key: "agent:main:subagent:done", kind: "direct", updatedAt: 1 }],
|
||||
},
|
||||
});
|
||||
|
||||
const applied = applySessionsChangedEvent(state, {
|
||||
sessionKey: "agent:main:subagent:done",
|
||||
sessionId: "sess-done",
|
||||
status: "done",
|
||||
ts: 2,
|
||||
});
|
||||
|
||||
expect(applied).toEqual({ applied: true, change: "updated" });
|
||||
expect(state.sessionsResult?.sessions).toMatchObject([
|
||||
{
|
||||
key: "agent:main:subagent:done",
|
||||
status: "done",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("updates fresh context usage from websocket event payloads", () => {
|
||||
const state = createState(async () => undefined, {
|
||||
sessionsResult: {
|
||||
@@ -413,6 +689,7 @@ describe("applySessionsChangedEvent", () => {
|
||||
|
||||
const applied = applySessionsChangedEvent(state, {
|
||||
sessionKey: "agent:main:main",
|
||||
sessionId: "sess-main",
|
||||
ts: 2,
|
||||
totalTokens: 190_000,
|
||||
totalTokensFresh: true,
|
||||
@@ -453,6 +730,7 @@ describe("applySessionsChangedEvent", () => {
|
||||
|
||||
applySessionsChangedEvent(state, {
|
||||
sessionKey: "agent:main:main",
|
||||
sessionId: "sess-main",
|
||||
totalTokensFresh: false,
|
||||
contextTokens: 200_000,
|
||||
});
|
||||
@@ -497,7 +775,7 @@ describe("applySessionsChangedEvent", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("reports when websocket event payloads insert new rows", () => {
|
||||
it("reports when reliable websocket event payloads insert new rows", () => {
|
||||
const state = createState(async () => undefined, {
|
||||
sessionsResult: {
|
||||
ts: 1,
|
||||
@@ -510,6 +788,7 @@ describe("applySessionsChangedEvent", () => {
|
||||
|
||||
const applied = applySessionsChangedEvent(state, {
|
||||
sessionKey: "agent:main:new",
|
||||
sessionId: "sess-new",
|
||||
ts: 2,
|
||||
kind: "direct",
|
||||
updatedAt: 2,
|
||||
|
||||
@@ -23,6 +23,7 @@ export type SessionsState = {
|
||||
sessionsFilterLimit: string;
|
||||
sessionsIncludeGlobal: boolean;
|
||||
sessionsIncludeUnknown: boolean;
|
||||
sessionsShowArchived: boolean;
|
||||
sessionsExpandedCheckpointKey: string | null;
|
||||
sessionsCheckpointItemsByKey: Record<string, SessionCompactionCheckpoint[]>;
|
||||
sessionsCheckpointLoadingKey: string | null;
|
||||
@@ -35,6 +36,7 @@ type LoadSessionsOverrides = {
|
||||
limit?: number;
|
||||
includeGlobal?: boolean;
|
||||
includeUnknown?: boolean;
|
||||
showArchived?: boolean;
|
||||
};
|
||||
|
||||
type CreateSessionParams = {
|
||||
@@ -79,6 +81,7 @@ const SESSION_EVENT_ROW_FIELDS = [
|
||||
"spawnedBy",
|
||||
"startedAt",
|
||||
"status",
|
||||
"archived",
|
||||
"subject",
|
||||
"surface",
|
||||
"systemSent",
|
||||
@@ -127,6 +130,29 @@ function normalizeSessionKind(value: unknown): GatewaySessionRow["kind"] | undef
|
||||
: undefined;
|
||||
}
|
||||
|
||||
export function isArchivedSessionRow(row: GatewaySessionRow): boolean {
|
||||
return row.archived === true;
|
||||
}
|
||||
|
||||
function filterAvailableSessionRows(
|
||||
rows: GatewaySessionRow[],
|
||||
options: { showArchived: boolean },
|
||||
): GatewaySessionRow[] {
|
||||
return rows.filter((row) => row.key && (options.showArchived || !isArchivedSessionRow(row)));
|
||||
}
|
||||
|
||||
function projectSessionsResultForAvailability(
|
||||
result: SessionsListResult,
|
||||
options: { showArchived: boolean },
|
||||
): SessionsListResult {
|
||||
const sessions = filterAvailableSessionRows(result.sessions, options);
|
||||
return {
|
||||
...result,
|
||||
count: sessions.length,
|
||||
sessions,
|
||||
};
|
||||
}
|
||||
|
||||
function compareSessionRowsByUpdatedAt(a: GatewaySessionRow, b: GatewaySessionRow): number {
|
||||
return (b.updatedAt ?? 0) - (a.updatedAt ?? 0);
|
||||
}
|
||||
@@ -240,7 +266,7 @@ async function runCompactionMutation<T>(
|
||||
|
||||
export type SessionsChangedApplyResult =
|
||||
| { applied: false }
|
||||
| { applied: true; change: "inserted" | "updated" };
|
||||
| { applied: true; change: "deleted" | "inserted" | "updated" };
|
||||
|
||||
export function applySessionsChangedEvent(
|
||||
state: SessionsState,
|
||||
@@ -262,7 +288,24 @@ export function applySessionsChangedEvent(
|
||||
|
||||
const previousRows = state.sessionsResult.sessions;
|
||||
const existingIndex = previousRows.findIndex((row) => row.key === key);
|
||||
if (payload.reason === "delete") {
|
||||
if (existingIndex < 0) {
|
||||
return { applied: false };
|
||||
}
|
||||
state.sessionsResult = {
|
||||
...state.sessionsResult,
|
||||
count: Math.max(0, state.sessionsResult.count - 1),
|
||||
sessions: previousRows.filter((row) => row.key !== key),
|
||||
};
|
||||
invalidateCheckpointCacheForKey(state, key);
|
||||
return { applied: true, change: "deleted" };
|
||||
}
|
||||
const existing = existingIndex >= 0 ? previousRows[existingIndex] : undefined;
|
||||
const hasReliableSource =
|
||||
existingIndex >= 0 || eventSession !== null || typeof source.sessionId === "string";
|
||||
if (!hasReliableSource) {
|
||||
return { applied: false };
|
||||
}
|
||||
const previousCheckpointSignature = checkpointSummarySignature(existing);
|
||||
const fallbackKind = normalizeSessionKind(source.kind) ?? existing?.kind ?? "unknown";
|
||||
const nextRow: GatewaySessionRow = {
|
||||
@@ -285,6 +328,18 @@ export function applySessionsChangedEvent(
|
||||
if (nextRow.totalTokensFresh === false && !hasOwn(source, "totalTokens")) {
|
||||
delete nextRow.totalTokens;
|
||||
}
|
||||
if (!state.sessionsShowArchived && isArchivedSessionRow(nextRow)) {
|
||||
if (existingIndex < 0) {
|
||||
return { applied: false };
|
||||
}
|
||||
state.sessionsResult = {
|
||||
...state.sessionsResult,
|
||||
count: Math.max(0, state.sessionsResult.count - 1),
|
||||
sessions: previousRows.filter((row) => row.key !== key),
|
||||
};
|
||||
invalidateCheckpointCacheForKey(state, key);
|
||||
return { applied: true, change: "deleted" };
|
||||
}
|
||||
|
||||
const nextRows =
|
||||
existingIndex >= 0
|
||||
@@ -366,7 +421,10 @@ async function loadSessionsOnce(
|
||||
);
|
||||
const includeGlobal = overrides?.includeGlobal ?? state.sessionsIncludeGlobal;
|
||||
const includeUnknown = overrides?.includeUnknown ?? state.sessionsIncludeUnknown;
|
||||
const activeMinutes = overrides?.activeMinutes ?? toNumber(state.sessionsFilterActive, 0);
|
||||
const showArchived = overrides?.showArchived ?? state.sessionsShowArchived;
|
||||
const activeMinutes = showArchived
|
||||
? 0
|
||||
: (overrides?.activeMinutes ?? toNumber(state.sessionsFilterActive, 0));
|
||||
const limit = overrides?.limit ?? toNumber(state.sessionsFilterLimit, 0);
|
||||
const params: Record<string, unknown> = {
|
||||
includeGlobal,
|
||||
@@ -380,15 +438,15 @@ async function loadSessionsOnce(
|
||||
}
|
||||
const res = await client.request<SessionsListResult | undefined>("sessions.list", params);
|
||||
if (res) {
|
||||
state.sessionsResult = res;
|
||||
const nextKeys = new Set(res.sessions.map((row) => row.key));
|
||||
state.sessionsResult = projectSessionsResultForAvailability(res, { showArchived });
|
||||
const nextKeys = new Set(state.sessionsResult.sessions.map((row) => row.key));
|
||||
for (const key of Object.keys(state.sessionsCheckpointItemsByKey)) {
|
||||
if (!nextKeys.has(key)) {
|
||||
invalidateCheckpointCacheForKey(state, key);
|
||||
}
|
||||
}
|
||||
let expandedNeedsRefetch = false;
|
||||
for (const row of res.sessions) {
|
||||
for (const row of state.sessionsResult.sessions) {
|
||||
const previous = previousRows.get(row.key);
|
||||
if (checkpointSummarySignature(previous) !== checkpointSummarySignature(row)) {
|
||||
invalidateCheckpointCacheForKey(state, row.key);
|
||||
|
||||
@@ -441,6 +441,7 @@ export type GatewaySessionRow = {
|
||||
totalTokens?: number;
|
||||
totalTokensFresh?: boolean;
|
||||
status?: SessionRunStatus;
|
||||
archived?: boolean;
|
||||
hasActiveRun?: boolean;
|
||||
subagentRunState?: SubagentRunState;
|
||||
hasActiveSubagentRun?: boolean;
|
||||
|
||||
@@ -34,6 +34,8 @@ function buildProps(result: SessionsListResult): SessionsProps {
|
||||
limit: "120",
|
||||
includeGlobal: false,
|
||||
includeUnknown: false,
|
||||
showArchived: false,
|
||||
filtersCollapsed: false,
|
||||
basePath: "",
|
||||
searchQuery: "",
|
||||
agentIdentityById: {},
|
||||
@@ -48,6 +50,7 @@ function buildProps(result: SessionsListResult): SessionsProps {
|
||||
checkpointBusyKey: null,
|
||||
checkpointErrorByKey: {},
|
||||
onFiltersChange: () => undefined,
|
||||
onToggleFiltersCollapsed: () => undefined,
|
||||
onSearchChange: () => undefined,
|
||||
onSortChange: () => undefined,
|
||||
onPageChange: () => undefined,
|
||||
@@ -66,6 +69,123 @@ function buildProps(result: SessionsListResult): SessionsProps {
|
||||
}
|
||||
|
||||
describe("sessions view", () => {
|
||||
it("renders an explicit archived-session toggle", async () => {
|
||||
const container = document.createElement("div");
|
||||
const onFiltersChange = vi.fn();
|
||||
render(
|
||||
renderSessions({
|
||||
...buildProps(buildMultiResult([])),
|
||||
onFiltersChange,
|
||||
}),
|
||||
container,
|
||||
);
|
||||
await Promise.resolve();
|
||||
|
||||
const archivedToggle = container.querySelector(
|
||||
".session-archive-toggle input",
|
||||
) as HTMLInputElement | null;
|
||||
expect(archivedToggle?.checked).toBe(false);
|
||||
|
||||
archivedToggle!.checked = true;
|
||||
archivedToggle!.dispatchEvent(new Event("change", { bubbles: true }));
|
||||
|
||||
expect(onFiltersChange).toHaveBeenCalledWith({
|
||||
activeMinutes: "",
|
||||
limit: "120",
|
||||
includeGlobal: false,
|
||||
includeUnknown: false,
|
||||
showArchived: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("uses one short styled tooltip per session filter", async () => {
|
||||
const container = document.createElement("div");
|
||||
render(renderSessions(buildProps(buildMultiResult([]))), container);
|
||||
await Promise.resolve();
|
||||
|
||||
const filters = container.querySelector(".sessions-filter-bar");
|
||||
const activeField = filters
|
||||
?.querySelector<HTMLInputElement>(".session-filter-input--minutes")
|
||||
?.closest("label");
|
||||
const limitField = filters
|
||||
?.querySelector<HTMLInputElement>(".session-filter-input--limit")
|
||||
?.closest("label");
|
||||
const globalToggle = filters
|
||||
?.querySelector<HTMLInputElement>(".session-filter-check__input[name=includeGlobal]")
|
||||
?.closest("label");
|
||||
const unknownToggle = filters
|
||||
?.querySelector<HTMLInputElement>(".session-filter-check__input[name=includeUnknown]")
|
||||
?.closest("label");
|
||||
const archivedToggle = filters
|
||||
?.querySelector<HTMLInputElement>(".session-filter-check__input[name=showArchived]")
|
||||
?.closest("label");
|
||||
|
||||
expect(activeField?.getAttribute("data-tooltip")).toBe("Updated in the last N minutes.");
|
||||
expect(limitField?.getAttribute("data-tooltip")).toBe("Max sessions to load.");
|
||||
expect(globalToggle?.getAttribute("data-tooltip")).toBe("Include global sessions.");
|
||||
expect(unknownToggle?.getAttribute("data-tooltip")).toBe("Include unknown sessions.");
|
||||
expect(archivedToggle?.getAttribute("data-tooltip")).toBe("Include archived sessions.");
|
||||
expect(
|
||||
Array.from(filters?.querySelectorAll("[title]") ?? []).map((node) => node.className),
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
it("keeps active and limit together and renders streamlined source toggles", async () => {
|
||||
const container = document.createElement("div");
|
||||
render(
|
||||
renderSessions({
|
||||
...buildProps(buildMultiResult([])),
|
||||
activeMinutes: "120",
|
||||
limit: "200",
|
||||
includeGlobal: true,
|
||||
}),
|
||||
container,
|
||||
);
|
||||
await Promise.resolve();
|
||||
|
||||
const primaryRow = container.querySelector(".session-filter-primary-row");
|
||||
expect(primaryRow?.querySelector(".session-filter-input--minutes")?.closest("label")).toBe(
|
||||
primaryRow?.firstElementChild,
|
||||
);
|
||||
expect(primaryRow?.querySelector(".session-filter-input--limit")?.closest("label")).toBe(
|
||||
primaryRow?.lastElementChild,
|
||||
);
|
||||
|
||||
const toggleGroup = container.querySelector(".session-filter-toggle-group");
|
||||
expect(toggleGroup?.getAttribute("role")).toBe("group");
|
||||
expect(toggleGroup?.getAttribute("aria-label")).toBe("Session source filters");
|
||||
expect(toggleGroup?.querySelectorAll(".session-filter-check")).toHaveLength(3);
|
||||
expect(
|
||||
toggleGroup
|
||||
?.querySelector<HTMLInputElement>(".session-filter-check__input[name=includeGlobal]")
|
||||
?.closest("label")
|
||||
?.classList.contains("session-filter-check--active"),
|
||||
).toBe(true);
|
||||
expect(toggleGroup?.querySelector(".session-filter-check__box")).toBeNull();
|
||||
});
|
||||
|
||||
it("collapses the whole session filter section from the header", async () => {
|
||||
const container = document.createElement("div");
|
||||
const onToggleFiltersCollapsed = vi.fn();
|
||||
render(
|
||||
renderSessions({
|
||||
...buildProps(buildMultiResult([])),
|
||||
filtersCollapsed: true,
|
||||
onToggleFiltersCollapsed,
|
||||
}),
|
||||
container,
|
||||
);
|
||||
await Promise.resolve();
|
||||
|
||||
const toggle = container.querySelector<HTMLButtonElement>(".sessions-filter-panel__toggle");
|
||||
expect(toggle?.getAttribute("aria-expanded")).toBe("false");
|
||||
expect(container.querySelector(".sessions-filter-bar")).toBeNull();
|
||||
|
||||
toggle?.click();
|
||||
|
||||
expect(onToggleFiltersCollapsed).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("renders and patches provider-owned thinking ids", async () => {
|
||||
const container = document.createElement("div");
|
||||
const onPatch = vi.fn();
|
||||
@@ -261,6 +381,80 @@ describe("sessions view", () => {
|
||||
expect(text).not.toContain("Object (telegram)");
|
||||
});
|
||||
|
||||
it("expands checkpoint details from row activation when checkpoints exist", async () => {
|
||||
const container = document.createElement("div");
|
||||
const onToggleCheckpointDetails = vi.fn();
|
||||
render(
|
||||
renderSessions({
|
||||
...buildProps(
|
||||
buildResult({
|
||||
key: "agent:main:main",
|
||||
kind: "direct",
|
||||
updatedAt: Date.now(),
|
||||
totalTokens: 123456,
|
||||
contextTokens: 200000,
|
||||
compactionCheckpointCount: 1,
|
||||
latestCompactionCheckpoint: {
|
||||
checkpointId: "checkpoint-1",
|
||||
createdAt: Date.now(),
|
||||
reason: "manual",
|
||||
},
|
||||
}),
|
||||
),
|
||||
onToggleCheckpointDetails,
|
||||
}),
|
||||
container,
|
||||
);
|
||||
await Promise.resolve();
|
||||
|
||||
const row = container.querySelector("tbody tr.session-data-row");
|
||||
row?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||
|
||||
expect(onToggleCheckpointDetails).toHaveBeenCalledWith("agent:main:main");
|
||||
const tokenCell = container.querySelector(".session-token-cell");
|
||||
expect(tokenCell?.textContent?.trim()).toBe("123456 / 200000");
|
||||
});
|
||||
|
||||
it("does not expand checkpoint details when the row has none or a nested control was used", async () => {
|
||||
const container = document.createElement("div");
|
||||
const onToggleCheckpointDetails = vi.fn();
|
||||
render(
|
||||
renderSessions({
|
||||
...buildProps(
|
||||
buildMultiResult([
|
||||
{
|
||||
key: "agent:main:with-checkpoint",
|
||||
kind: "direct",
|
||||
updatedAt: 20,
|
||||
compactionCheckpointCount: 1,
|
||||
latestCompactionCheckpoint: {
|
||||
checkpointId: "checkpoint-1",
|
||||
createdAt: 20,
|
||||
reason: "manual",
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "agent:main:no-checkpoint",
|
||||
kind: "direct",
|
||||
updatedAt: 10,
|
||||
compactionCheckpointCount: 0,
|
||||
},
|
||||
]),
|
||||
),
|
||||
onToggleCheckpointDetails,
|
||||
}),
|
||||
container,
|
||||
);
|
||||
await Promise.resolve();
|
||||
|
||||
const rows = container.querySelectorAll("tbody tr.session-data-row");
|
||||
const checkbox = rows[0]?.querySelector("input[type=checkbox]");
|
||||
checkbox?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||
rows[1]?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||
|
||||
expect(onToggleCheckpointDetails).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("filters rows by agent identity name", async () => {
|
||||
const container = document.createElement("div");
|
||||
render(
|
||||
|
||||
@@ -22,6 +22,8 @@ export type SessionsProps = {
|
||||
limit: string;
|
||||
includeGlobal: boolean;
|
||||
includeUnknown: boolean;
|
||||
showArchived: boolean;
|
||||
filtersCollapsed: boolean;
|
||||
basePath: string;
|
||||
searchQuery: string;
|
||||
agentIdentityById: Record<string, AgentIdentityResult>;
|
||||
@@ -40,7 +42,9 @@ export type SessionsProps = {
|
||||
limit: string;
|
||||
includeGlobal: boolean;
|
||||
includeUnknown: boolean;
|
||||
showArchived: boolean;
|
||||
}) => void;
|
||||
onToggleFiltersCollapsed: () => void;
|
||||
onSearchChange: (query: string) => void;
|
||||
onSortChange: (column: "key" | "kind" | "updated" | "tokens", dir: "asc" | "desc") => void;
|
||||
onPageChange: (page: number) => void;
|
||||
@@ -254,6 +258,44 @@ function formatCheckpointDelta(checkpoint: SessionCompactionCheckpoint): string
|
||||
return t("sessionsView.tokenDeltaUnavailable");
|
||||
}
|
||||
|
||||
function isRowControlTarget(target: EventTarget | null): boolean {
|
||||
return (
|
||||
target instanceof Element &&
|
||||
Boolean(target.closest("a, button, input, label, select, textarea"))
|
||||
);
|
||||
}
|
||||
|
||||
function renderFilterToggle(params: {
|
||||
name: string;
|
||||
checked: boolean;
|
||||
label: string;
|
||||
title: string;
|
||||
extraClass?: string;
|
||||
onChange: (checked: boolean) => void;
|
||||
}) {
|
||||
const className = [
|
||||
"session-filter-check",
|
||||
"session-filter-toggle",
|
||||
params.extraClass ?? "",
|
||||
params.checked ? "session-filter-check--active" : "",
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" ");
|
||||
return html`
|
||||
<label class=${className} data-tooltip=${params.title}>
|
||||
<input
|
||||
name=${params.name}
|
||||
class="session-filter-check__input"
|
||||
type="checkbox"
|
||||
.checked=${params.checked}
|
||||
@change=${(e: Event) => params.onChange((e.target as HTMLInputElement).checked)}
|
||||
/>
|
||||
<span class="session-filter-check__mark" aria-hidden="true">${icons.check}</span>
|
||||
<span class="session-filter-check__label">${params.label}</span>
|
||||
</label>
|
||||
`;
|
||||
}
|
||||
|
||||
export function renderSessions(props: SessionsProps) {
|
||||
const rawRows = props.result?.sessions ?? [];
|
||||
const filtered = filterRows(rawRows, props.searchQuery, props.agentIdentityById);
|
||||
@@ -262,6 +304,16 @@ export function renderSessions(props: SessionsProps) {
|
||||
const totalPages = Math.max(1, Math.ceil(totalRows / props.pageSize));
|
||||
const page = Math.min(props.page, totalPages - 1);
|
||||
const paginated = paginateRows(sorted, page, props.pageSize);
|
||||
const activeTooltip = t("sessionsView.activeTooltip");
|
||||
const limitTooltip = t("sessionsView.limitTooltip");
|
||||
const globalTooltip = t("sessionsView.globalTooltip");
|
||||
const unknownTooltip = t("sessionsView.unknownTooltip");
|
||||
const showArchivedTooltip = t("sessionsView.showArchivedTooltip");
|
||||
const filtersExpanded = !props.filtersCollapsed;
|
||||
const filterPanelTitle = t("sessionsView.filters");
|
||||
const filterToggleLabel = filtersExpanded
|
||||
? t("sessionsView.hideFilters")
|
||||
: t("sessionsView.showFilters");
|
||||
|
||||
const sortHeader = (
|
||||
col: "key" | "kind" | "updated" | "tokens",
|
||||
@@ -299,64 +351,114 @@ export function renderSessions(props: SessionsProps) {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="filters" style="margin-bottom: 12px;">
|
||||
<label class="field-inline">
|
||||
<span>${t("sessionsView.active")}</span>
|
||||
<input
|
||||
style="width: 72px;"
|
||||
placeholder=${t("sessionsView.minutesPlaceholder")}
|
||||
.value=${props.activeMinutes}
|
||||
@input=${(e: Event) =>
|
||||
props.onFiltersChange({
|
||||
activeMinutes: (e.target as HTMLInputElement).value,
|
||||
limit: props.limit,
|
||||
includeGlobal: props.includeGlobal,
|
||||
includeUnknown: props.includeUnknown,
|
||||
})}
|
||||
/>
|
||||
</label>
|
||||
<label class="field-inline">
|
||||
<span>${t("sessionsView.limit")}</span>
|
||||
<input
|
||||
style="width: 64px;"
|
||||
.value=${props.limit}
|
||||
@input=${(e: Event) =>
|
||||
props.onFiltersChange({
|
||||
activeMinutes: props.activeMinutes,
|
||||
limit: (e.target as HTMLInputElement).value,
|
||||
includeGlobal: props.includeGlobal,
|
||||
includeUnknown: props.includeUnknown,
|
||||
})}
|
||||
/>
|
||||
</label>
|
||||
<label class="field-inline checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
.checked=${props.includeGlobal}
|
||||
@change=${(e: Event) =>
|
||||
props.onFiltersChange({
|
||||
activeMinutes: props.activeMinutes,
|
||||
limit: props.limit,
|
||||
includeGlobal: (e.target as HTMLInputElement).checked,
|
||||
includeUnknown: props.includeUnknown,
|
||||
})}
|
||||
/>
|
||||
<span>${t("sessionsView.global")}</span>
|
||||
</label>
|
||||
<label class="field-inline checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
.checked=${props.includeUnknown}
|
||||
@change=${(e: Event) =>
|
||||
props.onFiltersChange({
|
||||
activeMinutes: props.activeMinutes,
|
||||
limit: props.limit,
|
||||
includeGlobal: props.includeGlobal,
|
||||
includeUnknown: (e.target as HTMLInputElement).checked,
|
||||
})}
|
||||
/>
|
||||
<span>${t("sessionsView.unknown")}</span>
|
||||
</label>
|
||||
<div class="sessions-filter-panel">
|
||||
<div class="sessions-filter-panel__header">
|
||||
<div class="sessions-filter-panel__title">${filterPanelTitle}</div>
|
||||
<button
|
||||
class="sessions-filter-panel__toggle"
|
||||
type="button"
|
||||
aria-expanded=${String(filtersExpanded)}
|
||||
aria-controls="sessions-filter-bar"
|
||||
@click=${props.onToggleFiltersCollapsed}
|
||||
>
|
||||
${filtersExpanded ? icons.chevronDown : icons.chevronRight}
|
||||
<span>${filterToggleLabel}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
${filtersExpanded
|
||||
? html`
|
||||
<div
|
||||
id="sessions-filter-bar"
|
||||
class="sessions-filter-bar"
|
||||
aria-label="Session filters"
|
||||
>
|
||||
<div class="session-filter-primary-row">
|
||||
<label class="session-filter-field" data-tooltip=${activeTooltip}>
|
||||
<span class="session-filter-label">${t("sessionsView.active")}</span>
|
||||
<input
|
||||
class="session-filter-input session-filter-input--minutes"
|
||||
placeholder=${t("sessionsView.minutesPlaceholder")}
|
||||
.value=${props.activeMinutes}
|
||||
?disabled=${props.showArchived}
|
||||
@input=${(e: Event) =>
|
||||
props.onFiltersChange({
|
||||
activeMinutes: (e.target as HTMLInputElement).value,
|
||||
limit: props.limit,
|
||||
includeGlobal: props.includeGlobal,
|
||||
includeUnknown: props.includeUnknown,
|
||||
showArchived: props.showArchived,
|
||||
})}
|
||||
/>
|
||||
</label>
|
||||
<label class="session-filter-field" data-tooltip=${limitTooltip}>
|
||||
<span class="session-filter-label">${t("sessionsView.limit")}</span>
|
||||
<input
|
||||
class="session-filter-input session-filter-input--limit"
|
||||
.value=${props.limit}
|
||||
@input=${(e: Event) =>
|
||||
props.onFiltersChange({
|
||||
activeMinutes: props.activeMinutes,
|
||||
limit: (e.target as HTMLInputElement).value,
|
||||
includeGlobal: props.includeGlobal,
|
||||
includeUnknown: props.includeUnknown,
|
||||
showArchived: props.showArchived,
|
||||
})}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="session-filter-toggle-group"
|
||||
role="group"
|
||||
aria-label=${t("sessionsView.sourceFilters")}
|
||||
>
|
||||
${renderFilterToggle({
|
||||
name: "includeGlobal",
|
||||
checked: props.includeGlobal,
|
||||
label: t("sessionsView.global"),
|
||||
title: globalTooltip,
|
||||
onChange: (checked) =>
|
||||
props.onFiltersChange({
|
||||
activeMinutes: props.activeMinutes,
|
||||
limit: props.limit,
|
||||
includeGlobal: checked,
|
||||
includeUnknown: props.includeUnknown,
|
||||
showArchived: props.showArchived,
|
||||
}),
|
||||
})}
|
||||
${renderFilterToggle({
|
||||
name: "includeUnknown",
|
||||
checked: props.includeUnknown,
|
||||
label: t("sessionsView.unknown"),
|
||||
title: unknownTooltip,
|
||||
onChange: (checked) =>
|
||||
props.onFiltersChange({
|
||||
activeMinutes: props.activeMinutes,
|
||||
limit: props.limit,
|
||||
includeGlobal: props.includeGlobal,
|
||||
includeUnknown: checked,
|
||||
showArchived: props.showArchived,
|
||||
}),
|
||||
})}
|
||||
${renderFilterToggle({
|
||||
name: "showArchived",
|
||||
checked: props.showArchived,
|
||||
label: t("sessionsView.showArchived"),
|
||||
title: showArchivedTooltip,
|
||||
extraClass: "session-archive-toggle",
|
||||
onChange: (checked) =>
|
||||
props.onFiltersChange({
|
||||
activeMinutes: props.activeMinutes,
|
||||
limit: props.limit,
|
||||
includeGlobal: props.includeGlobal,
|
||||
includeUnknown: props.includeUnknown,
|
||||
showArchived: checked,
|
||||
}),
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
: nothing}
|
||||
</div>
|
||||
|
||||
${props.error
|
||||
@@ -396,7 +498,7 @@ export function renderSessions(props: SessionsProps) {
|
||||
: nothing}
|
||||
|
||||
<div class="data-table-container">
|
||||
<table class="data-table">
|
||||
<table class="data-table sessions-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="data-table-checkbox-col">
|
||||
@@ -495,9 +597,11 @@ function renderRows(row: GatewaySessionRow, props: SessionsProps) {
|
||||
const reasoningLevels = withCurrentOption(REASONING_LEVELS, reasoning);
|
||||
const latestCheckpoint = row.latestCompactionCheckpoint;
|
||||
const checkpointCount = row.compactionCheckpointCount ?? 0;
|
||||
const hasCheckpoints = checkpointCount > 0 || Boolean(latestCheckpoint);
|
||||
const isExpanded = props.expandedCheckpointKey === row.key;
|
||||
const checkpointItems = props.checkpointItemsByKey[row.key] ?? [];
|
||||
const checkpointError = props.checkpointErrorByKey[row.key];
|
||||
const detailsId = `session-checkpoints-${encodeURIComponent(row.key)}`;
|
||||
const displayName = normalizeOptionalString(row.displayName) ?? null;
|
||||
const trimmedLabel = normalizeOptionalString(row.label) ?? "";
|
||||
const showDisplayName = Boolean(
|
||||
@@ -528,9 +632,41 @@ function renderRows(row: GatewaySessionRow, props: SessionsProps) {
|
||||
: row.kind === "global"
|
||||
? "data-table-badge--global"
|
||||
: "data-table-badge--unknown";
|
||||
const rowClass = [
|
||||
"session-data-row",
|
||||
hasCheckpoints ? "session-data-row--expandable" : "",
|
||||
isExpanded ? "session-data-row--expanded" : "",
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" ");
|
||||
const activateCheckpointDetails = () => {
|
||||
if (hasCheckpoints) {
|
||||
props.onToggleCheckpointDetails(row.key);
|
||||
}
|
||||
};
|
||||
|
||||
return [
|
||||
html`<tr>
|
||||
html`<tr
|
||||
class=${rowClass}
|
||||
tabindex=${hasCheckpoints ? "0" : nothing}
|
||||
aria-expanded=${hasCheckpoints ? String(isExpanded) : nothing}
|
||||
aria-controls=${hasCheckpoints ? detailsId : nothing}
|
||||
@click=${(e: MouseEvent) => {
|
||||
if (!hasCheckpoints || isRowControlTarget(e.target)) {
|
||||
return;
|
||||
}
|
||||
activateCheckpointDetails();
|
||||
}}
|
||||
@keydown=${(e: KeyboardEvent) => {
|
||||
if (!hasCheckpoints || isRowControlTarget(e.target)) {
|
||||
return;
|
||||
}
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
e.preventDefault();
|
||||
activateCheckpointDetails();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<td class="data-table-checkbox-col">
|
||||
<input
|
||||
type="checkbox"
|
||||
@@ -588,7 +724,7 @@ function renderRows(row: GatewaySessionRow, props: SessionsProps) {
|
||||
<span class="data-table-badge ${badgeClass}">${row.kind}</span>
|
||||
</td>
|
||||
<td>${updated}</td>
|
||||
<td>${formatSessionTokens(row)}</td>
|
||||
<td class="session-token-cell">${formatSessionTokens(row)}</td>
|
||||
<td>
|
||||
<div style="display: grid; gap: 6px;">
|
||||
<span class="muted" style="font-size: 12px;">
|
||||
@@ -606,13 +742,21 @@ function renderRows(row: GatewaySessionRow, props: SessionsProps) {
|
||||
</span>
|
||||
`
|
||||
: nothing}
|
||||
<button
|
||||
class="btn btn--sm"
|
||||
?disabled=${props.checkpointLoadingKey === row.key}
|
||||
@click=${() => props.onToggleCheckpointDetails(row.key)}
|
||||
>
|
||||
${isExpanded ? t("sessionsView.hideCheckpoints") : t("sessionsView.showCheckpoints")}
|
||||
</button>
|
||||
${hasCheckpoints
|
||||
? html`
|
||||
<button
|
||||
class="btn btn--sm session-checkpoint-toggle"
|
||||
?disabled=${props.checkpointLoadingKey === row.key}
|
||||
aria-expanded=${String(isExpanded)}
|
||||
aria-controls=${detailsId}
|
||||
@click=${() => props.onToggleCheckpointDetails(row.key)}
|
||||
>
|
||||
${isExpanded
|
||||
? t("sessionsView.hideCheckpoints")
|
||||
: t("sessionsView.showCheckpoints")}
|
||||
</button>
|
||||
`
|
||||
: nothing}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
@@ -686,9 +830,9 @@ function renderRows(row: GatewaySessionRow, props: SessionsProps) {
|
||||
</select>
|
||||
</td>
|
||||
</tr>`,
|
||||
...(isExpanded
|
||||
...(isExpanded && hasCheckpoints
|
||||
? [
|
||||
html`<tr>
|
||||
html`<tr id=${detailsId} class="session-checkpoint-details-row">
|
||||
<td colspan="11" style="padding: 0;">
|
||||
<div
|
||||
style="padding: 14px 16px; border-top: 1px solid var(--border); background: var(--surface-2, rgba(127, 127, 127, 0.05));"
|
||||
|
||||
Reference in New Issue
Block a user