@dipertq/dsh-openviking-status 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/lib/client.cjs +663 -16
- package/lib/client.cjs.map +1 -1
- package/lib/client.d.cts +224 -3
- package/lib/client.js +663 -16
- package/lib/client.js.map +1 -1
- package/lib/index.js +160 -2
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
package/lib/client.cjs
CHANGED
|
@@ -37,25 +37,37 @@ var client_exports = {};
|
|
|
37
37
|
__export(client_exports, {
|
|
38
38
|
COMMIT_THRESHOLD: () => COMMIT_THRESHOLD,
|
|
39
39
|
DEFAULT_OPENVIKING_ENDPOINT: () => DEFAULT_OPENVIKING_ENDPOINT,
|
|
40
|
+
ExtractionSection: () => ExtractionSection,
|
|
40
41
|
OpenVikingClient: () => OpenVikingClient,
|
|
41
42
|
OpenVikingSettingsSection: () => OpenVikingSettingsSection,
|
|
42
43
|
OpenVikingStatusChip: () => OpenVikingStatusChip,
|
|
43
44
|
OpenVikingStatusPopover: () => OpenVikingStatusPopover,
|
|
45
|
+
POLL_INTERVAL_ACTIVE_MS: () => POLL_INTERVAL_ACTIVE_MS,
|
|
46
|
+
POLL_INTERVAL_IDLE_MS: () => POLL_INTERVAL_IDLE_MS,
|
|
47
|
+
PROXY_OPENVIKING_ENDPOINT: () => PROXY_OPENVIKING_ENDPOINT,
|
|
48
|
+
TASKS_LIMIT_CAP: () => TASKS_LIMIT_CAP,
|
|
44
49
|
THEME: () => THEME,
|
|
50
|
+
WarningGlyph: () => WarningGlyph,
|
|
45
51
|
apply: () => apply,
|
|
46
52
|
chatNodesToText: () => chatNodesToText,
|
|
47
53
|
checkHealth: () => checkHealth,
|
|
48
54
|
commitSession: () => commitSession,
|
|
55
|
+
computeBreakdown: () => computeBreakdown,
|
|
49
56
|
defaultOpenVikingClient: () => defaultOpenVikingClient,
|
|
50
57
|
fetchSession: () => fetchSession,
|
|
58
|
+
formatBacklog: () => formatBacklog,
|
|
51
59
|
formatDaemonVersion: () => formatDaemonVersion,
|
|
60
|
+
formatDuration: () => formatDuration,
|
|
52
61
|
formatEndpoint: () => formatEndpoint,
|
|
62
|
+
formatExtractionSummary: () => formatExtractionSummary,
|
|
53
63
|
formatMemoryLeafName: () => formatMemoryLeafName,
|
|
54
64
|
formatPendingTokens: () => formatPendingTokens,
|
|
55
65
|
formatRelativeTime: () => formatRelativeTime,
|
|
56
66
|
formatStatusLabel: () => formatStatusLabel,
|
|
57
67
|
formatTooltipTitle: () => formatTooltipTitle,
|
|
58
68
|
getCategoryBadgeStyle: () => getCategoryBadgeStyle,
|
|
69
|
+
getChipDotState: () => getChipDotState,
|
|
70
|
+
getDotColorForState: () => getDotColorForState,
|
|
59
71
|
getProgressBarColor: () => getProgressBarColor,
|
|
60
72
|
getProgressBarPercent: () => getProgressBarPercent,
|
|
61
73
|
getSession: () => getSession,
|
|
@@ -64,9 +76,13 @@ __export(client_exports, {
|
|
|
64
76
|
inferCategory: () => inferCategory,
|
|
65
77
|
inject: () => inject,
|
|
66
78
|
name: () => name,
|
|
79
|
+
normalizeExecutionEvent: () => normalizeExecutionEvent,
|
|
80
|
+
normalizeExtractionTask: () => normalizeExtractionTask,
|
|
81
|
+
normalizeTaskList: () => normalizeTaskList,
|
|
67
82
|
parseRecalledMemories: () => parseRecalledMemories,
|
|
68
83
|
resolveApiKey: () => resolveApiKey,
|
|
69
84
|
resolveEndpoint: () => resolveEndpoint,
|
|
85
|
+
seedRunningTask: () => seedRunningTask,
|
|
70
86
|
themeVar: () => themeVar,
|
|
71
87
|
truncateSessionId: () => truncateSessionId
|
|
72
88
|
});
|
|
@@ -77,7 +93,73 @@ var import_react2 = __toESM(require("react"), 1);
|
|
|
77
93
|
var import_react_dom = __toESM(require("react-dom"), 1);
|
|
78
94
|
|
|
79
95
|
// src/client/api.ts
|
|
96
|
+
var TASKS_LIMIT_CAP = 200;
|
|
97
|
+
function normalizeExtractionTask(raw) {
|
|
98
|
+
if (!raw || typeof raw !== "object") return null;
|
|
99
|
+
const taskId = typeof raw.task_id === "string" && raw.task_id || typeof raw.id === "string" && raw.id || "";
|
|
100
|
+
if (!taskId) return null;
|
|
101
|
+
const statusRaw = typeof raw.status === "string" ? raw.status : "";
|
|
102
|
+
const status = statusRaw === "running" || statusRaw === "pending" || statusRaw === "completed" || statusRaw === "failed" ? statusRaw : "pending";
|
|
103
|
+
const result = raw.result ?? {};
|
|
104
|
+
const extracted = result.memories_extracted ?? {};
|
|
105
|
+
const numberOf = (v) => typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
106
|
+
const tokenUsageRaw = result.token_usage ?? raw.token_usage;
|
|
107
|
+
const tokenUsage = typeof tokenUsageRaw === "number" ? tokenUsageRaw : numberOf(
|
|
108
|
+
tokenUsageRaw?.total_tokens
|
|
109
|
+
);
|
|
110
|
+
const errorRaw = raw.error;
|
|
111
|
+
const errorMsg = typeof errorRaw === "string" ? errorRaw : typeof errorRaw?.message === "string" ? errorRaw.message : void 0;
|
|
112
|
+
return {
|
|
113
|
+
task_id: taskId,
|
|
114
|
+
status,
|
|
115
|
+
resource_id: typeof raw.resource_id === "string" ? raw.resource_id : void 0,
|
|
116
|
+
created_at: typeof raw.created_at === "string" ? raw.created_at : void 0,
|
|
117
|
+
updated_at: typeof raw.updated_at === "string" ? raw.updated_at : void 0,
|
|
118
|
+
memory_write: numberOf(extracted.memory_write ?? extracted.written),
|
|
119
|
+
memory_edit: numberOf(extracted.memory_edit ?? extracted.edited),
|
|
120
|
+
token_usage: tokenUsage,
|
|
121
|
+
error: errorMsg
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
function computeBreakdown(tasks) {
|
|
125
|
+
const b = {
|
|
126
|
+
running: 0,
|
|
127
|
+
pending: 0,
|
|
128
|
+
completed: 0,
|
|
129
|
+
failed: 0,
|
|
130
|
+
total: tasks.length,
|
|
131
|
+
firstRunning: null,
|
|
132
|
+
lastCompleted: null,
|
|
133
|
+
lastFailed: null
|
|
134
|
+
};
|
|
135
|
+
for (const t of tasks) {
|
|
136
|
+
if (t.status === "running") {
|
|
137
|
+
b.running += 1;
|
|
138
|
+
if (!b.firstRunning) b.firstRunning = t;
|
|
139
|
+
} else if (t.status === "pending") b.pending += 1;
|
|
140
|
+
else if (t.status === "completed") {
|
|
141
|
+
b.completed += 1;
|
|
142
|
+
if (!b.lastCompleted) b.lastCompleted = t;
|
|
143
|
+
} else if (t.status === "failed") {
|
|
144
|
+
b.failed += 1;
|
|
145
|
+
if (!b.lastFailed) b.lastFailed = t;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return b;
|
|
149
|
+
}
|
|
150
|
+
function normalizeExecutionEvent(raw) {
|
|
151
|
+
return {
|
|
152
|
+
seq: typeof raw.seq === "number" ? raw.seq : void 0,
|
|
153
|
+
recorded_at: typeof raw.recorded_at === "string" ? raw.recorded_at : void 0,
|
|
154
|
+
kind: typeof raw.kind === "string" ? raw.kind : void 0,
|
|
155
|
+
status: typeof raw.status === "string" ? raw.status : void 0,
|
|
156
|
+
stage: typeof raw.stage === "string" ? raw.stage : null,
|
|
157
|
+
operation: typeof raw.operation === "string" ? raw.operation : null,
|
|
158
|
+
error: typeof raw.error === "string" ? raw.error : null
|
|
159
|
+
};
|
|
160
|
+
}
|
|
80
161
|
var DEFAULT_OPENVIKING_ENDPOINT = "http://127.0.0.1:1933";
|
|
162
|
+
var PROXY_OPENVIKING_ENDPOINT = "/openviking-status/api";
|
|
81
163
|
function resolveEndpoint(endpoint) {
|
|
82
164
|
if (endpoint && endpoint.trim().length > 0) {
|
|
83
165
|
return endpoint.trim().replace(/\/+$/, "");
|
|
@@ -87,6 +169,7 @@ function resolveEndpoint(endpoint) {
|
|
|
87
169
|
if (typeof win.__OPENVIKING_ENDPOINT__ === "string" && win.__OPENVIKING_ENDPOINT__.trim()) {
|
|
88
170
|
return win.__OPENVIKING_ENDPOINT__.trim().replace(/\/+$/, "");
|
|
89
171
|
}
|
|
172
|
+
return PROXY_OPENVIKING_ENDPOINT;
|
|
90
173
|
}
|
|
91
174
|
if (typeof localStorage !== "undefined") {
|
|
92
175
|
try {
|
|
@@ -383,7 +466,9 @@ var OpenVikingClient = class {
|
|
|
383
466
|
const data = await res.json().catch(() => ({}));
|
|
384
467
|
return {
|
|
385
468
|
ok: data.ok === true,
|
|
386
|
-
error: typeof data.error === "string" ? data.error : void 0
|
|
469
|
+
error: typeof data.error === "string" ? data.error : void 0,
|
|
470
|
+
task_id: typeof data.task_id === "string" ? data.task_id : void 0,
|
|
471
|
+
resource_id: typeof data.resource_id === "string" ? data.resource_id : void 0
|
|
387
472
|
};
|
|
388
473
|
} catch (err) {
|
|
389
474
|
return {
|
|
@@ -415,7 +500,14 @@ var OpenVikingClient = class {
|
|
|
415
500
|
return { ok: false, error: String(errorMsg) };
|
|
416
501
|
}
|
|
417
502
|
this.resolvedSessionIds.set(sessionId.trim(), candidateId);
|
|
418
|
-
|
|
503
|
+
const okBody = await res.json().catch(() => ({}));
|
|
504
|
+
const container = okBody.result ?? okBody.data ?? okBody;
|
|
505
|
+
const taskId = container.task_id ?? container.id;
|
|
506
|
+
return {
|
|
507
|
+
ok: true,
|
|
508
|
+
task_id: typeof taskId === "string" && taskId.trim() ? taskId.trim() : void 0,
|
|
509
|
+
resource_id: candidateId
|
|
510
|
+
};
|
|
419
511
|
} catch (err) {
|
|
420
512
|
return {
|
|
421
513
|
ok: false,
|
|
@@ -425,7 +517,193 @@ var OpenVikingClient = class {
|
|
|
425
517
|
}
|
|
426
518
|
return { ok: false, error: lastError };
|
|
427
519
|
}
|
|
520
|
+
/**
|
|
521
|
+
* Список задач Phase 2 (Memory Extraction) текущей сессии со сводной
|
|
522
|
+
* разбивкой по статусам.
|
|
523
|
+
*
|
|
524
|
+
* Через прокси: один запрос `GET /tasks?session=<id>`, разбивка считается
|
|
525
|
+
* здесь. Напрямую: разрешаем `resource_id` через кандидатов и запрашиваем
|
|
526
|
+
* `GET /api/v1/tasks?resource_id=<id>&task_type=session_commit&limit=200`.
|
|
527
|
+
*/
|
|
528
|
+
async listTasks(sessionId) {
|
|
529
|
+
if (!sessionId || !sessionId.trim()) {
|
|
530
|
+
return { status: "missing" };
|
|
531
|
+
}
|
|
532
|
+
if (this.isProxy()) {
|
|
533
|
+
try {
|
|
534
|
+
const res = await fetch(
|
|
535
|
+
`${this.endpoint}/tasks?session=${encodeURIComponent(
|
|
536
|
+
sessionId.trim()
|
|
537
|
+
)}&limit=${TASKS_LIMIT_CAP}`,
|
|
538
|
+
{ method: "GET", headers: this.getHeaders() }
|
|
539
|
+
);
|
|
540
|
+
if (res.status === 401 || res.status === 403) {
|
|
541
|
+
return { status: "unauthorized" };
|
|
542
|
+
}
|
|
543
|
+
if (!res.ok) {
|
|
544
|
+
return { status: "error", detail: `HTTP ${res.status}` };
|
|
545
|
+
}
|
|
546
|
+
const data = await res.json();
|
|
547
|
+
if (data.status === "unauthorized") return { status: "unauthorized" };
|
|
548
|
+
if (data.status === "missing") return { status: "missing" };
|
|
549
|
+
if (data.status === "unreachable")
|
|
550
|
+
return {
|
|
551
|
+
status: "unreachable",
|
|
552
|
+
detail: typeof data.detail === "string" ? data.detail : void 0
|
|
553
|
+
};
|
|
554
|
+
if (data.status === "error")
|
|
555
|
+
return {
|
|
556
|
+
status: "error",
|
|
557
|
+
detail: typeof data.detail === "string" ? data.detail : void 0
|
|
558
|
+
};
|
|
559
|
+
const tasks = normalizeTaskList(data.tasks);
|
|
560
|
+
return { status: "ok", breakdown: computeBreakdown(tasks), tasks };
|
|
561
|
+
} catch (err) {
|
|
562
|
+
return {
|
|
563
|
+
status: "unreachable",
|
|
564
|
+
detail: err instanceof Error ? err.message : String(err)
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
const candidates = this.getCandidateSessionIds(sessionId);
|
|
569
|
+
let resourceId = null;
|
|
570
|
+
for (const candidateId of candidates) {
|
|
571
|
+
try {
|
|
572
|
+
const probe = await fetch(
|
|
573
|
+
`${this.endpoint}/api/v1/sessions/${encodeURIComponent(candidateId)}`,
|
|
574
|
+
{ method: "GET", headers: this.getHeaders() }
|
|
575
|
+
);
|
|
576
|
+
if (probe.status === 404) continue;
|
|
577
|
+
if (probe.status === 401 || probe.status === 403) {
|
|
578
|
+
return { status: "unauthorized" };
|
|
579
|
+
}
|
|
580
|
+
if (probe.ok) {
|
|
581
|
+
resourceId = candidateId;
|
|
582
|
+
this.resolvedSessionIds.set(sessionId.trim(), candidateId);
|
|
583
|
+
break;
|
|
584
|
+
}
|
|
585
|
+
} catch (err) {
|
|
586
|
+
return {
|
|
587
|
+
status: "unreachable",
|
|
588
|
+
detail: err instanceof Error ? err.message : String(err)
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
if (!resourceId) return { status: "missing" };
|
|
593
|
+
try {
|
|
594
|
+
const query = new URLSearchParams({
|
|
595
|
+
resource_id: resourceId,
|
|
596
|
+
task_type: "session_commit",
|
|
597
|
+
limit: String(TASKS_LIMIT_CAP)
|
|
598
|
+
});
|
|
599
|
+
const res = await fetch(
|
|
600
|
+
`${this.endpoint}/api/v1/tasks?${query.toString()}`,
|
|
601
|
+
{ method: "GET", headers: this.getHeaders() }
|
|
602
|
+
);
|
|
603
|
+
if (res.status === 401 || res.status === 403) {
|
|
604
|
+
return { status: "unauthorized" };
|
|
605
|
+
}
|
|
606
|
+
if (!res.ok) {
|
|
607
|
+
return { status: "error", detail: `HTTP ${res.status}` };
|
|
608
|
+
}
|
|
609
|
+
const data = await res.json();
|
|
610
|
+
const items = data?.items ?? data?.tasks ?? data?.result?.items ?? (Array.isArray(data) ? data : []);
|
|
611
|
+
const tasks = normalizeTaskList(items);
|
|
612
|
+
return { status: "ok", breakdown: computeBreakdown(tasks), tasks };
|
|
613
|
+
} catch (err) {
|
|
614
|
+
return {
|
|
615
|
+
status: "unreachable",
|
|
616
|
+
detail: err instanceof Error ? err.message : String(err)
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* Лениво загрузить одну задачу с лентой `execution_events` для «Show log».
|
|
622
|
+
*/
|
|
623
|
+
async fetchTaskEvents(taskId) {
|
|
624
|
+
if (!taskId || !taskId.trim()) {
|
|
625
|
+
return { status: "missing" };
|
|
626
|
+
}
|
|
627
|
+
const buildOk = (taskRaw) => {
|
|
628
|
+
const task = normalizeExtractionTask(taskRaw);
|
|
629
|
+
const eventsContainer = taskRaw.execution_events ?? {};
|
|
630
|
+
const rawEvents = eventsContainer.items ?? (Array.isArray(taskRaw.execution_events) ? taskRaw.execution_events : []);
|
|
631
|
+
const events = (Array.isArray(rawEvents) ? rawEvents : []).filter(
|
|
632
|
+
(e) => !!e && typeof e === "object"
|
|
633
|
+
).map(normalizeExecutionEvent);
|
|
634
|
+
if (!task) return { status: "error", detail: "malformed task" };
|
|
635
|
+
return { status: "ok", task, events };
|
|
636
|
+
};
|
|
637
|
+
if (this.isProxy()) {
|
|
638
|
+
try {
|
|
639
|
+
const res = await fetch(
|
|
640
|
+
`${this.endpoint}/task?id=${encodeURIComponent(
|
|
641
|
+
taskId.trim()
|
|
642
|
+
)}&events=1`,
|
|
643
|
+
{ method: "GET", headers: this.getHeaders() }
|
|
644
|
+
);
|
|
645
|
+
if (res.status === 401 || res.status === 403) {
|
|
646
|
+
return { status: "unauthorized" };
|
|
647
|
+
}
|
|
648
|
+
if (!res.ok) {
|
|
649
|
+
return { status: "error", detail: `HTTP ${res.status}` };
|
|
650
|
+
}
|
|
651
|
+
const data = await res.json();
|
|
652
|
+
if (data.status === "unauthorized") return { status: "unauthorized" };
|
|
653
|
+
if (data.status === "missing") return { status: "missing" };
|
|
654
|
+
if (data.status === "unreachable")
|
|
655
|
+
return {
|
|
656
|
+
status: "unreachable",
|
|
657
|
+
detail: typeof data.detail === "string" ? data.detail : void 0
|
|
658
|
+
};
|
|
659
|
+
if (data.status === "error")
|
|
660
|
+
return {
|
|
661
|
+
status: "error",
|
|
662
|
+
detail: typeof data.detail === "string" ? data.detail : void 0
|
|
663
|
+
};
|
|
664
|
+
return buildOk(data.task ?? {});
|
|
665
|
+
} catch (err) {
|
|
666
|
+
return {
|
|
667
|
+
status: "unreachable",
|
|
668
|
+
detail: err instanceof Error ? err.message : String(err)
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
try {
|
|
673
|
+
const res = await fetch(
|
|
674
|
+
`${this.endpoint}/api/v1/tasks/${encodeURIComponent(
|
|
675
|
+
taskId.trim()
|
|
676
|
+
)}?include_events=true`,
|
|
677
|
+
{ method: "GET", headers: this.getHeaders() }
|
|
678
|
+
);
|
|
679
|
+
if (res.status === 404) return { status: "missing" };
|
|
680
|
+
if (res.status === 401 || res.status === 403) {
|
|
681
|
+
return { status: "unauthorized" };
|
|
682
|
+
}
|
|
683
|
+
if (!res.ok) {
|
|
684
|
+
return { status: "error", detail: `HTTP ${res.status}` };
|
|
685
|
+
}
|
|
686
|
+
const data = await res.json();
|
|
687
|
+
const taskRaw = data?.result ?? data?.data ?? data;
|
|
688
|
+
return buildOk(taskRaw);
|
|
689
|
+
} catch (err) {
|
|
690
|
+
return {
|
|
691
|
+
status: "unreachable",
|
|
692
|
+
detail: err instanceof Error ? err.message : String(err)
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
}
|
|
428
696
|
};
|
|
697
|
+
function normalizeTaskList(raw) {
|
|
698
|
+
if (!Array.isArray(raw)) return [];
|
|
699
|
+
const tasks = [];
|
|
700
|
+
for (const entry of raw) {
|
|
701
|
+
if (!entry || typeof entry !== "object") continue;
|
|
702
|
+
const task = normalizeExtractionTask(entry);
|
|
703
|
+
if (task) tasks.push(task);
|
|
704
|
+
}
|
|
705
|
+
return tasks;
|
|
706
|
+
}
|
|
429
707
|
var defaultOpenVikingClient = new OpenVikingClient();
|
|
430
708
|
function checkHealth(endpoint, apiKey) {
|
|
431
709
|
const client = endpoint || apiKey ? new OpenVikingClient(endpoint, apiKey) : defaultOpenVikingClient;
|
|
@@ -799,6 +1077,39 @@ function dshIcon(name2) {
|
|
|
799
1077
|
return null;
|
|
800
1078
|
}
|
|
801
1079
|
}
|
|
1080
|
+
function formatDuration(startIso, endIso) {
|
|
1081
|
+
if (!startIso || !endIso) return void 0;
|
|
1082
|
+
const start = new Date(startIso).getTime();
|
|
1083
|
+
const end = new Date(endIso).getTime();
|
|
1084
|
+
if (isNaN(start) || isNaN(end) || end < start) return void 0;
|
|
1085
|
+
const ms = end - start;
|
|
1086
|
+
const sec = ms / 1e3;
|
|
1087
|
+
if (sec < 60) {
|
|
1088
|
+
return `${sec < 10 ? sec.toFixed(1) : Math.round(sec)}s`;
|
|
1089
|
+
}
|
|
1090
|
+
const min = Math.floor(sec / 60);
|
|
1091
|
+
const rem = Math.round(sec % 60);
|
|
1092
|
+
return `${min}m ${rem}s`;
|
|
1093
|
+
}
|
|
1094
|
+
function formatExtractionSummary(task, now = Date.now()) {
|
|
1095
|
+
const write = task.memory_write ?? 0;
|
|
1096
|
+
const edit = task.memory_edit ?? 0;
|
|
1097
|
+
const parts = [`${write} written, ${edit} edited`];
|
|
1098
|
+
const duration = formatDuration(task.created_at, task.updated_at);
|
|
1099
|
+
if (duration) parts.push(duration);
|
|
1100
|
+
const rel = formatRelativeTime(task.updated_at, now);
|
|
1101
|
+
if (rel) parts.push(rel);
|
|
1102
|
+
return parts.join(" \xB7 ");
|
|
1103
|
+
}
|
|
1104
|
+
function formatBacklog(breakdown) {
|
|
1105
|
+
if (!breakdown) return null;
|
|
1106
|
+
const { pending, failed } = breakdown;
|
|
1107
|
+
if (pending <= 0 && failed <= 0) return null;
|
|
1108
|
+
const parts = [];
|
|
1109
|
+
if (pending > 0) parts.push(`${pending} pending`);
|
|
1110
|
+
if (failed > 0) parts.push(`${failed} failed`);
|
|
1111
|
+
return parts.join(" / ");
|
|
1112
|
+
}
|
|
802
1113
|
function getProgressBarPercent(pendingTokens, threshold = COMMIT_THRESHOLD) {
|
|
803
1114
|
if (!threshold || threshold <= 0) return 0;
|
|
804
1115
|
const ratio = (pendingTokens || 0) / threshold;
|
|
@@ -845,10 +1156,10 @@ function formatMemoryLeafName(uri) {
|
|
|
845
1156
|
return segments[0] || clean;
|
|
846
1157
|
}
|
|
847
1158
|
function formatEndpoint(endpoint) {
|
|
848
|
-
if (!endpoint || !endpoint.trim()) {
|
|
1159
|
+
if (!endpoint || !endpoint.trim() || endpoint.startsWith("/")) {
|
|
849
1160
|
return "127.0.0.1:1933";
|
|
850
1161
|
}
|
|
851
|
-
return endpoint.trim().replace(/^https?:\/\//, "");
|
|
1162
|
+
return endpoint.trim().replace(/^https?:\/\//, "").replace(/\/+$/, "");
|
|
852
1163
|
}
|
|
853
1164
|
function truncateSessionId(id, maxLen = 16) {
|
|
854
1165
|
if (!id) return "";
|
|
@@ -865,6 +1176,229 @@ function handleEscapeKey(event, onClose) {
|
|
|
865
1176
|
}
|
|
866
1177
|
return false;
|
|
867
1178
|
}
|
|
1179
|
+
function ExtractionSection({
|
|
1180
|
+
breakdown,
|
|
1181
|
+
client
|
|
1182
|
+
}) {
|
|
1183
|
+
const [showLog, setShowLog] = (0, import_react.useState)(false);
|
|
1184
|
+
const [events, setEvents] = (0, import_react.useState)(null);
|
|
1185
|
+
const [logError, setLogError] = (0, import_react.useState)(null);
|
|
1186
|
+
const [loadingLog, setLoadingLog] = (0, import_react.useState)(false);
|
|
1187
|
+
if (!breakdown) return null;
|
|
1188
|
+
const isRunning = breakdown.running >= 1;
|
|
1189
|
+
const lastCompleted = breakdown.lastCompleted;
|
|
1190
|
+
const lastFailed = breakdown.lastFailed;
|
|
1191
|
+
const backlog = formatBacklog(breakdown);
|
|
1192
|
+
const failedActive = !isRunning && breakdown.failed >= 1;
|
|
1193
|
+
const logTaskId = (isRunning ? breakdown.firstRunning?.task_id : null) ?? (failedActive ? lastFailed?.task_id : null) ?? lastCompleted?.task_id ?? lastFailed?.task_id ?? null;
|
|
1194
|
+
const mutedStyle = { color: themeVar("labelTertiary") };
|
|
1195
|
+
const toggleLog = (0, import_react.useCallback)(async () => {
|
|
1196
|
+
const next = !showLog;
|
|
1197
|
+
setShowLog(next);
|
|
1198
|
+
if (next && events === null && logTaskId && client) {
|
|
1199
|
+
setLoadingLog(true);
|
|
1200
|
+
setLogError(null);
|
|
1201
|
+
const res = await client.fetchTaskEvents(logTaskId);
|
|
1202
|
+
setLoadingLog(false);
|
|
1203
|
+
if (res.status === "ok") {
|
|
1204
|
+
setEvents(res.events);
|
|
1205
|
+
} else {
|
|
1206
|
+
setLogError(
|
|
1207
|
+
res.status === "unauthorized" ? "No access to task log" : "Could not load log"
|
|
1208
|
+
);
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
}, [showLog, events, logTaskId, client]);
|
|
1212
|
+
if (!isRunning && !failedActive && !lastCompleted && !backlog) {
|
|
1213
|
+
return null;
|
|
1214
|
+
}
|
|
1215
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { "data-testid": "extraction-section", style: { marginBottom: "12px" }, children: [
|
|
1216
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1217
|
+
"div",
|
|
1218
|
+
{
|
|
1219
|
+
style: {
|
|
1220
|
+
marginBottom: "4px",
|
|
1221
|
+
color: themeVar("labelPrimary"),
|
|
1222
|
+
fontWeight: 500
|
|
1223
|
+
},
|
|
1224
|
+
children: "Memory Extraction"
|
|
1225
|
+
}
|
|
1226
|
+
),
|
|
1227
|
+
isRunning ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
|
|
1228
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
1229
|
+
"div",
|
|
1230
|
+
{
|
|
1231
|
+
style: {
|
|
1232
|
+
display: "flex",
|
|
1233
|
+
alignItems: "center",
|
|
1234
|
+
gap: "6px",
|
|
1235
|
+
marginBottom: "4px"
|
|
1236
|
+
},
|
|
1237
|
+
children: [
|
|
1238
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1239
|
+
"span",
|
|
1240
|
+
{
|
|
1241
|
+
"data-testid": "extraction-status-dot",
|
|
1242
|
+
"aria-hidden": "true",
|
|
1243
|
+
style: {
|
|
1244
|
+
width: "6px",
|
|
1245
|
+
height: "6px",
|
|
1246
|
+
borderRadius: "50%",
|
|
1247
|
+
background: themeVar("stateSuccess"),
|
|
1248
|
+
animation: "ov-pulse 1.2s ease-in-out infinite",
|
|
1249
|
+
flexShrink: 0
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
),
|
|
1253
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "data-testid": "extraction-running-label", children: "Extracting\u2026" })
|
|
1254
|
+
]
|
|
1255
|
+
}
|
|
1256
|
+
),
|
|
1257
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1258
|
+
"div",
|
|
1259
|
+
{
|
|
1260
|
+
"data-testid": "extraction-indeterminate-track",
|
|
1261
|
+
style: {
|
|
1262
|
+
width: "100%",
|
|
1263
|
+
height: "6px",
|
|
1264
|
+
borderRadius: "3px",
|
|
1265
|
+
backgroundColor: themeVar("insetSurface"),
|
|
1266
|
+
overflow: "hidden",
|
|
1267
|
+
position: "relative"
|
|
1268
|
+
},
|
|
1269
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1270
|
+
"div",
|
|
1271
|
+
{
|
|
1272
|
+
"data-testid": "extraction-indeterminate-fill",
|
|
1273
|
+
style: {
|
|
1274
|
+
position: "absolute",
|
|
1275
|
+
left: 0,
|
|
1276
|
+
top: 0,
|
|
1277
|
+
height: "100%",
|
|
1278
|
+
width: "40%",
|
|
1279
|
+
borderRadius: "3px",
|
|
1280
|
+
backgroundColor: themeVar("stateSuccess"),
|
|
1281
|
+
animation: "ov-indeterminate 1.2s ease-in-out infinite"
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
)
|
|
1285
|
+
}
|
|
1286
|
+
)
|
|
1287
|
+
] }) : failedActive && lastFailed ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
1288
|
+
"div",
|
|
1289
|
+
{
|
|
1290
|
+
"data-testid": "extraction-failed-line",
|
|
1291
|
+
style: {
|
|
1292
|
+
display: "flex",
|
|
1293
|
+
alignItems: "center",
|
|
1294
|
+
gap: "6px",
|
|
1295
|
+
color: themeVar("stateWarning")
|
|
1296
|
+
},
|
|
1297
|
+
children: [
|
|
1298
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1299
|
+
"span",
|
|
1300
|
+
{
|
|
1301
|
+
"aria-hidden": "true",
|
|
1302
|
+
style: { display: "inline-flex", flexShrink: 0 },
|
|
1303
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(WarningGlyph, { size: 12, testId: "extraction-warning-glyph" })
|
|
1304
|
+
}
|
|
1305
|
+
),
|
|
1306
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "data-testid": "extraction-failed-text", children: lastFailed.error || "extraction failed" })
|
|
1307
|
+
]
|
|
1308
|
+
}
|
|
1309
|
+
) : lastCompleted ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
1310
|
+
"div",
|
|
1311
|
+
{
|
|
1312
|
+
style: {
|
|
1313
|
+
display: "flex",
|
|
1314
|
+
alignItems: "center",
|
|
1315
|
+
gap: "6px"
|
|
1316
|
+
},
|
|
1317
|
+
children: [
|
|
1318
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1319
|
+
"span",
|
|
1320
|
+
{
|
|
1321
|
+
"data-testid": "extraction-status-dot",
|
|
1322
|
+
"aria-hidden": "true",
|
|
1323
|
+
style: {
|
|
1324
|
+
width: "6px",
|
|
1325
|
+
height: "6px",
|
|
1326
|
+
borderRadius: "50%",
|
|
1327
|
+
background: themeVar("stateSuccess"),
|
|
1328
|
+
flexShrink: 0
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
),
|
|
1332
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "data-testid": "extraction-last-line", children: formatExtractionSummary(lastCompleted) })
|
|
1333
|
+
]
|
|
1334
|
+
}
|
|
1335
|
+
) : null,
|
|
1336
|
+
backlog && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1337
|
+
"div",
|
|
1338
|
+
{
|
|
1339
|
+
"data-testid": "extraction-backlog-line",
|
|
1340
|
+
style: { ...mutedStyle, marginTop: "4px" },
|
|
1341
|
+
children: backlog
|
|
1342
|
+
}
|
|
1343
|
+
),
|
|
1344
|
+
logTaskId && client && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { marginTop: "6px" }, children: [
|
|
1345
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1346
|
+
"button",
|
|
1347
|
+
{
|
|
1348
|
+
type: "button",
|
|
1349
|
+
"data-testid": "extraction-show-log-btn",
|
|
1350
|
+
onClick: () => void toggleLog(),
|
|
1351
|
+
style: {
|
|
1352
|
+
background: "none",
|
|
1353
|
+
border: "none",
|
|
1354
|
+
padding: 0,
|
|
1355
|
+
cursor: "pointer",
|
|
1356
|
+
font: "inherit",
|
|
1357
|
+
color: themeVar("labelTertiary"),
|
|
1358
|
+
textDecoration: "underline"
|
|
1359
|
+
},
|
|
1360
|
+
"aria-expanded": showLog,
|
|
1361
|
+
children: showLog ? "Hide log" : "Show log"
|
|
1362
|
+
}
|
|
1363
|
+
),
|
|
1364
|
+
showLog && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
1365
|
+
"div",
|
|
1366
|
+
{
|
|
1367
|
+
"data-testid": "extraction-log",
|
|
1368
|
+
style: {
|
|
1369
|
+
marginTop: "6px",
|
|
1370
|
+
display: "flex",
|
|
1371
|
+
flexDirection: "column",
|
|
1372
|
+
gap: "2px",
|
|
1373
|
+
maxHeight: "120px",
|
|
1374
|
+
overflowY: "auto"
|
|
1375
|
+
},
|
|
1376
|
+
children: [
|
|
1377
|
+
loadingLog && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { "data-testid": "extraction-log-loading", style: mutedStyle, children: "Loading\u2026" }),
|
|
1378
|
+
logError && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { "data-testid": "extraction-log-error", style: mutedStyle, children: logError }),
|
|
1379
|
+
events && events.length === 0 && !loadingLog && !logError && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { "data-testid": "extraction-log-empty", style: mutedStyle, children: "No events" }),
|
|
1380
|
+
events?.map((ev, idx) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
1381
|
+
"div",
|
|
1382
|
+
{
|
|
1383
|
+
"data-testid": "extraction-log-event",
|
|
1384
|
+
style: {
|
|
1385
|
+
display: "flex",
|
|
1386
|
+
justifyContent: "space-between",
|
|
1387
|
+
gap: "8px"
|
|
1388
|
+
},
|
|
1389
|
+
children: [
|
|
1390
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: ev.status || ev.kind || "event" }),
|
|
1391
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: mutedStyle, children: formatRelativeTime(ev.recorded_at) || "" })
|
|
1392
|
+
]
|
|
1393
|
+
},
|
|
1394
|
+
`${ev.seq ?? idx}-${ev.recorded_at ?? idx}`
|
|
1395
|
+
))
|
|
1396
|
+
]
|
|
1397
|
+
}
|
|
1398
|
+
)
|
|
1399
|
+
] })
|
|
1400
|
+
] });
|
|
1401
|
+
}
|
|
868
1402
|
function OpenVikingStatusPopover({
|
|
869
1403
|
sessionId,
|
|
870
1404
|
health,
|
|
@@ -874,6 +1408,8 @@ function OpenVikingStatusPopover({
|
|
|
874
1408
|
endpoint,
|
|
875
1409
|
isCommitting = false,
|
|
876
1410
|
commitError = null,
|
|
1411
|
+
breakdown = null,
|
|
1412
|
+
client,
|
|
877
1413
|
onCommitNow,
|
|
878
1414
|
onClose,
|
|
879
1415
|
className,
|
|
@@ -954,7 +1490,7 @@ function OpenVikingStatusPopover({
|
|
|
954
1490
|
...style
|
|
955
1491
|
},
|
|
956
1492
|
children: [
|
|
957
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: `@keyframes ov-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }` }),
|
|
1493
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: `@keyframes ov-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } @keyframes ov-pulse { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: 0.35; transform: scale(0.72); } } @keyframes ov-indeterminate { 0% { left: -40%; } 100% { left: 100%; } }` }),
|
|
958
1494
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
959
1495
|
"div",
|
|
960
1496
|
{
|
|
@@ -975,7 +1511,7 @@ function OpenVikingStatusPopover({
|
|
|
975
1511
|
{
|
|
976
1512
|
"data-testid": "endpoint-label",
|
|
977
1513
|
style: { ...mutedStyle, ...monoStyle, fontWeight: 400 },
|
|
978
|
-
children: formatEndpoint(endpoint)
|
|
1514
|
+
children: formatEndpoint(health?.endpoint || endpoint)
|
|
979
1515
|
}
|
|
980
1516
|
)
|
|
981
1517
|
] }),
|
|
@@ -1151,7 +1687,7 @@ function OpenVikingStatusPopover({
|
|
|
1151
1687
|
marginBottom: "12px",
|
|
1152
1688
|
color: themeVar("labelTertiary")
|
|
1153
1689
|
},
|
|
1154
|
-
children: unauthorized ? "The daemon requires an API key.
|
|
1690
|
+
children: unauthorized ? "The daemon requires an API key. Configure it in DSH Settings \u2192 OpenViking." : "Session counters are unavailable right now."
|
|
1155
1691
|
}
|
|
1156
1692
|
),
|
|
1157
1693
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { marginBottom: "12px" }, children: [
|
|
@@ -1237,6 +1773,7 @@ function OpenVikingStatusPopover({
|
|
|
1237
1773
|
}
|
|
1238
1774
|
)
|
|
1239
1775
|
] }),
|
|
1776
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ExtractionSection, { breakdown, client }),
|
|
1240
1777
|
commitError && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1241
1778
|
"div",
|
|
1242
1779
|
{
|
|
@@ -1294,6 +1831,77 @@ function OpenVikingStatusPopover({
|
|
|
1294
1831
|
// src/client/OpenVikingStatusChip.tsx
|
|
1295
1832
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
1296
1833
|
var COMMIT_THRESHOLD = 2e4;
|
|
1834
|
+
var POLL_INTERVAL_IDLE_MS = 15e3;
|
|
1835
|
+
var POLL_INTERVAL_ACTIVE_MS = 2500;
|
|
1836
|
+
function getChipDotState({
|
|
1837
|
+
isOnline,
|
|
1838
|
+
sessionUnreadable,
|
|
1839
|
+
breakdown
|
|
1840
|
+
}) {
|
|
1841
|
+
if (!isOnline) return "offline";
|
|
1842
|
+
if (sessionUnreadable) return "session-unreadable";
|
|
1843
|
+
if (breakdown && breakdown.running >= 1) return "busy";
|
|
1844
|
+
if (breakdown && breakdown.failed >= 1) return "extraction-failed";
|
|
1845
|
+
return "online-idle";
|
|
1846
|
+
}
|
|
1847
|
+
function WarningGlyph({
|
|
1848
|
+
size = 11,
|
|
1849
|
+
testId = "extraction-warning-glyph"
|
|
1850
|
+
}) {
|
|
1851
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
1852
|
+
"svg",
|
|
1853
|
+
{
|
|
1854
|
+
"data-testid": testId,
|
|
1855
|
+
width: size,
|
|
1856
|
+
height: size,
|
|
1857
|
+
viewBox: "0 0 16 16",
|
|
1858
|
+
fill: "none",
|
|
1859
|
+
"aria-hidden": "true",
|
|
1860
|
+
style: { flexShrink: 0, display: "block" },
|
|
1861
|
+
children: [
|
|
1862
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "8", cy: "8", r: "6.5", stroke: "currentColor", strokeWidth: "1.5" }),
|
|
1863
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1864
|
+
"path",
|
|
1865
|
+
{
|
|
1866
|
+
d: "M8 5v3.5",
|
|
1867
|
+
stroke: "currentColor",
|
|
1868
|
+
strokeWidth: "1.5",
|
|
1869
|
+
strokeLinecap: "round"
|
|
1870
|
+
}
|
|
1871
|
+
),
|
|
1872
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "8", cy: "11", r: "0.9", fill: "currentColor" })
|
|
1873
|
+
]
|
|
1874
|
+
}
|
|
1875
|
+
);
|
|
1876
|
+
}
|
|
1877
|
+
function seedRunningTask(prev, taskId, resourceId) {
|
|
1878
|
+
const existing = prev && prev.status === "ok" ? prev.tasks : [];
|
|
1879
|
+
if (existing.some((t) => t.task_id === taskId)) {
|
|
1880
|
+
return prev;
|
|
1881
|
+
}
|
|
1882
|
+
const seeded = {
|
|
1883
|
+
task_id: taskId,
|
|
1884
|
+
status: "running",
|
|
1885
|
+
resource_id: resourceId,
|
|
1886
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1887
|
+
};
|
|
1888
|
+
const tasks = [seeded, ...existing];
|
|
1889
|
+
return { status: "ok", breakdown: computeBreakdown(tasks), tasks };
|
|
1890
|
+
}
|
|
1891
|
+
function getDotColorForState(state) {
|
|
1892
|
+
switch (state) {
|
|
1893
|
+
case "offline":
|
|
1894
|
+
return themeVar("stateError");
|
|
1895
|
+
case "session-unreadable":
|
|
1896
|
+
case "extraction-failed":
|
|
1897
|
+
return themeVar("stateWarning");
|
|
1898
|
+
case "busy":
|
|
1899
|
+
return themeVar("stateSuccess");
|
|
1900
|
+
case "online-idle":
|
|
1901
|
+
default:
|
|
1902
|
+
return themeVar("stateSuccess");
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1297
1905
|
function formatPendingTokens(pendingTokens) {
|
|
1298
1906
|
const k = Math.round((pendingTokens || 0) / 1e3);
|
|
1299
1907
|
return `${k}k pend`;
|
|
@@ -1396,6 +2004,7 @@ function StatusChipView({
|
|
|
1396
2004
|
initialHealth,
|
|
1397
2005
|
initialSessionData,
|
|
1398
2006
|
initialSessionRead,
|
|
2007
|
+
initialTasksRead,
|
|
1399
2008
|
initialOpen = false
|
|
1400
2009
|
}) {
|
|
1401
2010
|
const [health, setHealth] = (0, import_react2.useState)(
|
|
@@ -1404,6 +2013,9 @@ function StatusChipView({
|
|
|
1404
2013
|
const [sessionRead, setSessionRead] = (0, import_react2.useState)(
|
|
1405
2014
|
initialSessionRead ?? (initialSessionData ? { status: "ok", session: initialSessionData } : null)
|
|
1406
2015
|
);
|
|
2016
|
+
const [tasksRead, setTasksRead] = (0, import_react2.useState)(
|
|
2017
|
+
initialTasksRead ?? null
|
|
2018
|
+
);
|
|
1407
2019
|
const [isOpen, setIsOpen] = (0, import_react2.useState)(initialOpen);
|
|
1408
2020
|
const [isCommitting, setIsCommitting] = (0, import_react2.useState)(false);
|
|
1409
2021
|
const [commitError, setCommitError] = (0, import_react2.useState)(null);
|
|
@@ -1439,16 +2051,24 @@ function StatusChipView({
|
|
|
1439
2051
|
if (!healthRes.ok) {
|
|
1440
2052
|
return;
|
|
1441
2053
|
}
|
|
1442
|
-
|
|
2054
|
+
const [session, tasks] = await Promise.all([
|
|
2055
|
+
apiClient.readSession(sessionId),
|
|
2056
|
+
apiClient.listTasks(sessionId)
|
|
2057
|
+
]);
|
|
2058
|
+
setSessionRead(session);
|
|
2059
|
+
setTasksRead(tasks);
|
|
1443
2060
|
} catch {
|
|
1444
2061
|
setHealth({ ok: false });
|
|
1445
2062
|
}
|
|
1446
2063
|
}, [sessionId, apiClient]);
|
|
2064
|
+
const breakdown = tasksRead?.status === "ok" ? tasksRead.breakdown : null;
|
|
2065
|
+
const hasRunning = (breakdown?.running ?? 0) >= 1;
|
|
1447
2066
|
(0, import_react2.useEffect)(() => {
|
|
1448
2067
|
fetchStatus();
|
|
1449
|
-
const
|
|
2068
|
+
const interval = hasRunning ? POLL_INTERVAL_ACTIVE_MS : POLL_INTERVAL_IDLE_MS;
|
|
2069
|
+
const timer = setInterval(fetchStatus, interval);
|
|
1450
2070
|
return () => clearInterval(timer);
|
|
1451
|
-
}, [fetchStatus]);
|
|
2071
|
+
}, [fetchStatus, hasRunning]);
|
|
1452
2072
|
(0, import_react2.useEffect)(() => {
|
|
1453
2073
|
function handleClickOutside(event) {
|
|
1454
2074
|
if (popoverRef.current && !popoverRef.current.contains(event.target)) {
|
|
@@ -1478,6 +2098,11 @@ function StatusChipView({
|
|
|
1478
2098
|
keep_recent_count: 10
|
|
1479
2099
|
});
|
|
1480
2100
|
if (res.ok) {
|
|
2101
|
+
if (res.task_id) {
|
|
2102
|
+
setTasksRead(
|
|
2103
|
+
(prev) => seedRunningTask(prev, res.task_id, res.resource_id)
|
|
2104
|
+
);
|
|
2105
|
+
}
|
|
1481
2106
|
await fetchStatus();
|
|
1482
2107
|
onCommit?.();
|
|
1483
2108
|
} else {
|
|
@@ -1496,11 +2121,15 @@ function StatusChipView({
|
|
|
1496
2121
|
const sessionUnreadable = isOnline && sessionRead !== null && sessionRead.status !== "ok";
|
|
1497
2122
|
const pendingTokens = sessionData?.pending_tokens ?? 0;
|
|
1498
2123
|
const recalledCount = recalledResult.recalledCount;
|
|
1499
|
-
const
|
|
2124
|
+
const rawDotState = getChipDotState({
|
|
1500
2125
|
isOnline,
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
);
|
|
2126
|
+
sessionUnreadable,
|
|
2127
|
+
breakdown
|
|
2128
|
+
});
|
|
2129
|
+
const dotState = isCommitting && (rawDotState === "online-idle" || rawDotState === "busy") ? "busy" : rawDotState;
|
|
2130
|
+
const dotBusy = dotState === "busy";
|
|
2131
|
+
const dotFailed = dotState === "extraction-failed";
|
|
2132
|
+
const statusColor = getDotColorForState(dotState);
|
|
1504
2133
|
const tooltipTitle = formatTooltipTitle({
|
|
1505
2134
|
isOnline,
|
|
1506
2135
|
isCommitting,
|
|
@@ -1517,6 +2146,7 @@ function StatusChipView({
|
|
|
1517
2146
|
style: { minWidth: 0, display: "inline-flex", position: "relative" },
|
|
1518
2147
|
ref: popoverRef,
|
|
1519
2148
|
children: [
|
|
2149
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("style", { children: `@keyframes ov-pulse { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: 0.35; transform: scale(0.72); } }` }),
|
|
1520
2150
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
1521
2151
|
"button",
|
|
1522
2152
|
{
|
|
@@ -1547,17 +2177,32 @@ function StatusChipView({
|
|
|
1547
2177
|
title: tooltipTitle,
|
|
1548
2178
|
"aria-label": tooltipTitle,
|
|
1549
2179
|
children: [
|
|
1550
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
2180
|
+
dotFailed ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1551
2181
|
"span",
|
|
1552
2182
|
{
|
|
1553
2183
|
"data-testid": "status-dot",
|
|
2184
|
+
"data-dot-state": dotState,
|
|
2185
|
+
"aria-hidden": "true",
|
|
2186
|
+
style: {
|
|
2187
|
+
display: "inline-flex",
|
|
2188
|
+
color: statusColor,
|
|
2189
|
+
flexShrink: 0
|
|
2190
|
+
},
|
|
2191
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(WarningGlyph, { size: 11, testId: "chip-warning-glyph" })
|
|
2192
|
+
}
|
|
2193
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
2194
|
+
"span",
|
|
2195
|
+
{
|
|
2196
|
+
"data-testid": "status-dot",
|
|
2197
|
+
"data-dot-state": dotBusy ? "busy" : dotState,
|
|
1554
2198
|
"aria-hidden": "true",
|
|
1555
2199
|
style: {
|
|
1556
2200
|
width: "6px",
|
|
1557
2201
|
height: "6px",
|
|
1558
2202
|
borderRadius: "50%",
|
|
1559
2203
|
background: statusColor,
|
|
1560
|
-
flexShrink: 0
|
|
2204
|
+
flexShrink: 0,
|
|
2205
|
+
...dotBusy ? { animation: "ov-pulse 1.2s ease-in-out infinite" } : {}
|
|
1561
2206
|
}
|
|
1562
2207
|
}
|
|
1563
2208
|
),
|
|
@@ -1576,6 +2221,8 @@ function StatusChipView({
|
|
|
1576
2221
|
endpoint: apiClient.endpoint,
|
|
1577
2222
|
isCommitting,
|
|
1578
2223
|
commitError,
|
|
2224
|
+
breakdown,
|
|
2225
|
+
client: apiClient,
|
|
1579
2226
|
onCommitNow: handleCommitNow,
|
|
1580
2227
|
onClose: () => setIsOpen(false)
|
|
1581
2228
|
}
|