@nexus-ai-fs/tui 0.9.18
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 +30 -0
- package/package.json +48 -0
- package/src/app.tsx +349 -0
- package/src/index.tsx +137 -0
- package/src/opentui-env.d.ts +61 -0
- package/src/panels/access/access-panel.tsx +597 -0
- package/src/panels/access/alert-list.tsx +77 -0
- package/src/panels/access/constraint-creator.tsx +128 -0
- package/src/panels/access/constraint-list.tsx +72 -0
- package/src/panels/access/credential-list.tsx +68 -0
- package/src/panels/access/delegation-chain-view.tsx +110 -0
- package/src/panels/access/delegation-completer.tsx +120 -0
- package/src/panels/access/delegation-creator.tsx +237 -0
- package/src/panels/access/delegation-list.tsx +74 -0
- package/src/panels/access/fraud-score-view.tsx +94 -0
- package/src/panels/access/manifest-creator.tsx +167 -0
- package/src/panels/access/manifest-list.tsx +105 -0
- package/src/panels/access/namespace-config-view.tsx +525 -0
- package/src/panels/access/permission-checker.tsx +231 -0
- package/src/panels/agents/agent-status-view.tsx +196 -0
- package/src/panels/agents/agents-panel.tsx +493 -0
- package/src/panels/agents/delegation-list.tsx +154 -0
- package/src/panels/agents/inbox-view.tsx +96 -0
- package/src/panels/agents/trajectories-tab.tsx +40 -0
- package/src/panels/api-console/api-console-panel.tsx +189 -0
- package/src/panels/api-console/codegen-viewer.tsx +36 -0
- package/src/panels/api-console/codegen.ts +112 -0
- package/src/panels/api-console/endpoint-list.tsx +57 -0
- package/src/panels/api-console/request-builder.tsx +69 -0
- package/src/panels/api-console/response-viewer.tsx +54 -0
- package/src/panels/connectors/available-tab.tsx +357 -0
- package/src/panels/connectors/connector-row.tsx +121 -0
- package/src/panels/connectors/connectors-panel.tsx +88 -0
- package/src/panels/connectors/error-parser.ts +116 -0
- package/src/panels/connectors/mounted-tab.tsx +179 -0
- package/src/panels/connectors/skills-tab.tsx +235 -0
- package/src/panels/connectors/template-generator.ts +211 -0
- package/src/panels/connectors/write-tab.tsx +514 -0
- package/src/panels/events/audit-tab.tsx +69 -0
- package/src/panels/events/audit-trail.tsx +75 -0
- package/src/panels/events/connector-detail.tsx +49 -0
- package/src/panels/events/connector-list.tsx +73 -0
- package/src/panels/events/connectors-tab.tsx +92 -0
- package/src/panels/events/event-replay.tsx +80 -0
- package/src/panels/events/events-panel.tsx +414 -0
- package/src/panels/events/events-tab.tsx +212 -0
- package/src/panels/events/lock-list.tsx +54 -0
- package/src/panels/events/locks-tab.tsx +103 -0
- package/src/panels/events/mcl-replay.tsx +77 -0
- package/src/panels/events/mcl-tab.tsx +83 -0
- package/src/panels/events/operations-tab-wrapper.tsx +62 -0
- package/src/panels/events/operations-tab.tsx +41 -0
- package/src/panels/events/replay-tab.tsx +76 -0
- package/src/panels/events/secrets-audit.tsx +64 -0
- package/src/panels/events/secrets-tab.tsx +75 -0
- package/src/panels/events/subscription-list.tsx +54 -0
- package/src/panels/events/subscriptions-tab.tsx +82 -0
- package/src/panels/files/file-aspects.tsx +93 -0
- package/src/panels/files/file-editor.tsx +160 -0
- package/src/panels/files/file-explorer-keybindings.ts +468 -0
- package/src/panels/files/file-explorer-panel.tsx +545 -0
- package/src/panels/files/file-lineage.tsx +163 -0
- package/src/panels/files/file-list-item.tsx +28 -0
- package/src/panels/files/file-metadata.tsx +62 -0
- package/src/panels/files/file-preview.tsx +108 -0
- package/src/panels/files/file-schema.tsx +89 -0
- package/src/panels/files/file-tree-node.tsx +44 -0
- package/src/panels/files/file-tree.tsx +169 -0
- package/src/panels/files/share-links-tab.tsx +33 -0
- package/src/panels/files/uploads-tab.tsx +45 -0
- package/src/panels/payments/approval-list.tsx +83 -0
- package/src/panels/payments/balance-card.tsx +43 -0
- package/src/panels/payments/budget-card.tsx +70 -0
- package/src/panels/payments/payments-panel.tsx +451 -0
- package/src/panels/payments/policy-list.tsx +64 -0
- package/src/panels/payments/reservation-list.tsx +78 -0
- package/src/panels/payments/transaction-list.tsx +103 -0
- package/src/panels/payments/transfer-form.tsx +109 -0
- package/src/panels/search/column-search.tsx +79 -0
- package/src/panels/search/knowledge-view.tsx +100 -0
- package/src/panels/search/memory-list.tsx +197 -0
- package/src/panels/search/playbook-list.tsx +77 -0
- package/src/panels/search/rlm-answer-view.tsx +105 -0
- package/src/panels/search/search-panel.tsx +405 -0
- package/src/panels/search/search-results.tsx +116 -0
- package/src/panels/stack/stack-panel.tsx +474 -0
- package/src/panels/versions/conflicts-tab.tsx +59 -0
- package/src/panels/versions/entry-detail.tsx +89 -0
- package/src/panels/versions/transaction-actions.tsx +34 -0
- package/src/panels/versions/transaction-list.tsx +90 -0
- package/src/panels/versions/versions-panel.tsx +276 -0
- package/src/panels/workflows/execution-list.tsx +102 -0
- package/src/panels/workflows/scheduler-view.tsx +135 -0
- package/src/panels/workflows/workflow-list.tsx +88 -0
- package/src/panels/workflows/workflows-panel.tsx +295 -0
- package/src/panels/zones/brick-detail.tsx +136 -0
- package/src/panels/zones/brick-list.tsx +56 -0
- package/src/panels/zones/cache-tab.tsx +118 -0
- package/src/panels/zones/drift-view.tsx +97 -0
- package/src/panels/zones/mcp-mounts-tab.tsx +38 -0
- package/src/panels/zones/memories-tab.tsx +37 -0
- package/src/panels/zones/reindex-status.tsx +84 -0
- package/src/panels/zones/workspaces-tab.tsx +37 -0
- package/src/panels/zones/zone-list.tsx +73 -0
- package/src/panels/zones/zones-panel.tsx +559 -0
- package/src/services/command-runner.ts +303 -0
- package/src/shared/accessibility-announcements.ts +44 -0
- package/src/shared/action-registry.ts +466 -0
- package/src/shared/brick-states.ts +91 -0
- package/src/shared/command-palette.ts +35 -0
- package/src/shared/components/announcement-bar.tsx +30 -0
- package/src/shared/components/app-confirm-dialog.tsx +29 -0
- package/src/shared/components/breadcrumb.tsx +21 -0
- package/src/shared/components/brick-gate.tsx +60 -0
- package/src/shared/components/command-output.tsx +95 -0
- package/src/shared/components/command-palette.tsx +97 -0
- package/src/shared/components/confirm-dialog.tsx +61 -0
- package/src/shared/components/diff-viewer.tsx +219 -0
- package/src/shared/components/empty-state.tsx +36 -0
- package/src/shared/components/error-bar.tsx +60 -0
- package/src/shared/components/error-boundary.tsx +53 -0
- package/src/shared/components/help-overlay.tsx +99 -0
- package/src/shared/components/identity-switcher.tsx +168 -0
- package/src/shared/components/loading-indicator.tsx +40 -0
- package/src/shared/components/pagination-bar.tsx +68 -0
- package/src/shared/components/pre-connection-screen.tsx +398 -0
- package/src/shared/components/scroll-indicator.tsx +46 -0
- package/src/shared/components/side-nav-utils.ts +68 -0
- package/src/shared/components/side-nav.tsx +287 -0
- package/src/shared/components/spinner.tsx +26 -0
- package/src/shared/components/status-bar.tsx +117 -0
- package/src/shared/components/styled-text.tsx +72 -0
- package/src/shared/components/sub-tab-bar-utils.ts +100 -0
- package/src/shared/components/sub-tab-bar.tsx +40 -0
- package/src/shared/components/tab-bar-utils.ts +36 -0
- package/src/shared/components/tab-bar.tsx +50 -0
- package/src/shared/components/text-input.tsx +73 -0
- package/src/shared/components/tooltip.tsx +53 -0
- package/src/shared/components/virtual-list.tsx +93 -0
- package/src/shared/components/welcome-screen.tsx +111 -0
- package/src/shared/hooks/use-api.ts +10 -0
- package/src/shared/hooks/use-brick-available.ts +42 -0
- package/src/shared/hooks/use-confirm.ts +66 -0
- package/src/shared/hooks/use-connection-state.ts +67 -0
- package/src/shared/hooks/use-copy.ts +31 -0
- package/src/shared/hooks/use-fresh-server.ts +62 -0
- package/src/shared/hooks/use-keyboard.ts +58 -0
- package/src/shared/hooks/use-list-navigation.ts +106 -0
- package/src/shared/hooks/use-swr.ts +117 -0
- package/src/shared/hooks/use-tab-fallback.ts +32 -0
- package/src/shared/hooks/use-text-input.ts +113 -0
- package/src/shared/hooks/use-visible-tabs.ts +61 -0
- package/src/shared/lib/circular-buffer.ts +82 -0
- package/src/shared/lib/clipboard.ts +14 -0
- package/src/shared/nav-items.ts +73 -0
- package/src/shared/navigation.ts +110 -0
- package/src/shared/status-breadcrumb.ts +74 -0
- package/src/shared/syntax-style.ts +3 -0
- package/src/shared/tab-visibility.ts +15 -0
- package/src/shared/text-style.ts +23 -0
- package/src/shared/theme.ts +179 -0
- package/src/shared/utils/format-size.ts +20 -0
- package/src/shared/utils/format-text.ts +10 -0
- package/src/shared/utils/format-time.ts +72 -0
- package/src/shared/utils/lru-cache.ts +75 -0
- package/src/stores/access-store-types.ts +154 -0
- package/src/stores/access-store.ts +674 -0
- package/src/stores/agents-store.ts +404 -0
- package/src/stores/announcement-store.ts +46 -0
- package/src/stores/api-console-store.ts +476 -0
- package/src/stores/connectors-store.ts +434 -0
- package/src/stores/create-api-action.ts +140 -0
- package/src/stores/delegation-store.ts +300 -0
- package/src/stores/error-store.ts +102 -0
- package/src/stores/events-store.ts +163 -0
- package/src/stores/files-store.ts +630 -0
- package/src/stores/first-run-store.ts +34 -0
- package/src/stores/global-store.ts +255 -0
- package/src/stores/infra-store.ts +461 -0
- package/src/stores/knowledge-store.ts +358 -0
- package/src/stores/lineage-store.ts +126 -0
- package/src/stores/mcp-store.ts +147 -0
- package/src/stores/payments-store.ts +545 -0
- package/src/stores/search-store-types.ts +155 -0
- package/src/stores/search-store.ts +656 -0
- package/src/stores/share-link-store.ts +151 -0
- package/src/stores/stack-store.ts +352 -0
- package/src/stores/ui-store.ts +161 -0
- package/src/stores/upload-store.ts +131 -0
- package/src/stores/versions-store.ts +355 -0
- package/src/stores/workflows-store.ts +402 -0
- package/src/stores/workspace-store.ts +185 -0
- package/src/stores/zones-store.ts +378 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transaction list: displays audit transaction records with status, flow,
|
|
3
|
+
* pagination controls, and integrity verification result.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from "react";
|
|
7
|
+
import type { TransactionRecord, IntegrityResult } from "../../stores/payments-store.js";
|
|
8
|
+
import { LoadingIndicator } from "../../shared/components/loading-indicator.js";
|
|
9
|
+
import { EmptyState } from "../../shared/components/empty-state.js";
|
|
10
|
+
|
|
11
|
+
interface TransactionListProps {
|
|
12
|
+
readonly transactions: readonly TransactionRecord[];
|
|
13
|
+
readonly selectedIndex: number;
|
|
14
|
+
readonly loading: boolean;
|
|
15
|
+
readonly hasMore: boolean;
|
|
16
|
+
readonly hasPrev: boolean;
|
|
17
|
+
readonly currentPage: number;
|
|
18
|
+
readonly integrityResult: IntegrityResult | null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function shortId(id: string): string {
|
|
22
|
+
if (id.length <= 12) return id;
|
|
23
|
+
return `${id.slice(0, 8)}..`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function formatTimestamp(ts: string): string {
|
|
27
|
+
try {
|
|
28
|
+
return new Date(ts).toLocaleString();
|
|
29
|
+
} catch {
|
|
30
|
+
return ts;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function formatAmount(amount: string, currency: string): string {
|
|
35
|
+
return `${amount} ${currency}`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function TransactionList({
|
|
39
|
+
transactions,
|
|
40
|
+
selectedIndex,
|
|
41
|
+
loading,
|
|
42
|
+
hasMore,
|
|
43
|
+
hasPrev,
|
|
44
|
+
currentPage,
|
|
45
|
+
integrityResult,
|
|
46
|
+
}: TransactionListProps): React.ReactNode {
|
|
47
|
+
if (loading) {
|
|
48
|
+
return <LoadingIndicator message="Loading transactions..." />;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (transactions.length === 0) {
|
|
52
|
+
return <EmptyState message="No transactions yet." hint="Press t to create a transfer." />;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const selectedTx = transactions[selectedIndex];
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<box height="100%" width="100%" flexDirection="column">
|
|
59
|
+
{/* Header */}
|
|
60
|
+
<box height={1} width="100%">
|
|
61
|
+
<text>{" ID DATE AMOUNT STATUS FLOW"}</text>
|
|
62
|
+
</box>
|
|
63
|
+
<box height={1} width="100%">
|
|
64
|
+
<text>{" ---------- ------------------- -------------- --------- --------------------"}</text>
|
|
65
|
+
</box>
|
|
66
|
+
|
|
67
|
+
{/* Rows */}
|
|
68
|
+
<scrollbox flexGrow={1} width="100%">
|
|
69
|
+
{transactions.map((tx, i) => {
|
|
70
|
+
const isSelected = i === selectedIndex;
|
|
71
|
+
const prefix = isSelected ? "> " : " ";
|
|
72
|
+
const flow = `${shortId(tx.buyer_agent_id)}->${shortId(tx.seller_agent_id)}`;
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<box key={tx.id} height={1} width="100%">
|
|
76
|
+
<text>
|
|
77
|
+
{`${prefix}${shortId(tx.id).padEnd(10)} ${formatTimestamp(tx.created_at).padEnd(19)} ${formatAmount(tx.amount, tx.currency).padEnd(14)} ${tx.status.padEnd(9)} ${flow}`}
|
|
78
|
+
</text>
|
|
79
|
+
</box>
|
|
80
|
+
);
|
|
81
|
+
})}
|
|
82
|
+
</scrollbox>
|
|
83
|
+
|
|
84
|
+
{/* Integrity verification result */}
|
|
85
|
+
{integrityResult && selectedTx && integrityResult.record_id === selectedTx.id && (
|
|
86
|
+
<box height={1} width="100%">
|
|
87
|
+
<text>
|
|
88
|
+
{integrityResult.is_valid
|
|
89
|
+
? `Integrity OK: ${shortId(integrityResult.record_hash)} (valid)`
|
|
90
|
+
: `INTEGRITY FAIL: ${shortId(integrityResult.record_hash)} (TAMPERED)`}
|
|
91
|
+
</text>
|
|
92
|
+
</box>
|
|
93
|
+
)}
|
|
94
|
+
|
|
95
|
+
{/* Pagination status */}
|
|
96
|
+
<box height={1} width="100%">
|
|
97
|
+
<text>
|
|
98
|
+
{` Page ${currentPage}${hasMore ? "+" : ""} ${hasPrev ? "[p:prev]" : ""} ${hasMore ? "[n:next]" : "(end)"} ${transactions.length} shown`}
|
|
99
|
+
</text>
|
|
100
|
+
</box>
|
|
101
|
+
</box>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transfer form overlay: three-field form (to, amount, memo) for credit transfers.
|
|
3
|
+
*
|
|
4
|
+
* Tab cycles between fields, Enter submits, Escape cancels.
|
|
5
|
+
* Uses the same input-mode pattern as search-panel.tsx.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React, { useState, useCallback } from "react";
|
|
9
|
+
import { useKeyboard } from "../../shared/hooks/use-keyboard.js";
|
|
10
|
+
|
|
11
|
+
type TransferField = "to" | "amount" | "memo";
|
|
12
|
+
|
|
13
|
+
const FIELD_ORDER: readonly TransferField[] = ["to", "amount", "memo"];
|
|
14
|
+
const FIELD_LABELS: Readonly<Record<TransferField, string>> = {
|
|
15
|
+
to: "To",
|
|
16
|
+
amount: "Amount",
|
|
17
|
+
memo: "Memo",
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
interface TransferFormProps {
|
|
21
|
+
readonly onSubmit: (to: string, amount: string, memo: string) => void;
|
|
22
|
+
readonly onCancel: () => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function TransferForm({
|
|
26
|
+
onSubmit,
|
|
27
|
+
onCancel,
|
|
28
|
+
}: TransferFormProps): React.ReactNode {
|
|
29
|
+
const [activeField, setActiveField] = useState<TransferField>("to");
|
|
30
|
+
const [fields, setFields] = useState<Readonly<Record<TransferField, string>>>({
|
|
31
|
+
to: "",
|
|
32
|
+
amount: "",
|
|
33
|
+
memo: "",
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const handleUnhandledKey = useCallback(
|
|
37
|
+
(keyName: string) => {
|
|
38
|
+
if (keyName.length === 1) {
|
|
39
|
+
setFields((prev) => ({ ...prev, [activeField]: prev[activeField] + keyName }));
|
|
40
|
+
} else if (keyName === "space") {
|
|
41
|
+
setFields((prev) => ({ ...prev, [activeField]: prev[activeField] + " " }));
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
[activeField],
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
useKeyboard(
|
|
48
|
+
{
|
|
49
|
+
tab: () => {
|
|
50
|
+
const currentIdx = FIELD_ORDER.indexOf(activeField);
|
|
51
|
+
const nextIdx = (currentIdx + 1) % FIELD_ORDER.length;
|
|
52
|
+
const nextField = FIELD_ORDER[nextIdx];
|
|
53
|
+
if (nextField) {
|
|
54
|
+
setActiveField(nextField);
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
backspace: () => {
|
|
58
|
+
setFields((prev) => ({
|
|
59
|
+
...prev,
|
|
60
|
+
[activeField]: prev[activeField].slice(0, -1),
|
|
61
|
+
}));
|
|
62
|
+
},
|
|
63
|
+
return: () => {
|
|
64
|
+
const to = fields.to.trim();
|
|
65
|
+
const amount = fields.amount.trim();
|
|
66
|
+
const memo = fields.memo.trim();
|
|
67
|
+
if (to && amount) {
|
|
68
|
+
onSubmit(to, amount, memo);
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
escape: () => {
|
|
72
|
+
onCancel();
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
handleUnhandledKey,
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<box
|
|
80
|
+
height="100%"
|
|
81
|
+
width="100%"
|
|
82
|
+
flexDirection="column"
|
|
83
|
+
borderStyle="single"
|
|
84
|
+
>
|
|
85
|
+
<box height={1} width="100%">
|
|
86
|
+
<text>{"--- Transfer Credits ---"}</text>
|
|
87
|
+
</box>
|
|
88
|
+
|
|
89
|
+
{FIELD_ORDER.map((field) => {
|
|
90
|
+
const isActive = field === activeField;
|
|
91
|
+
const label = FIELD_LABELS[field];
|
|
92
|
+
const value = fields[field];
|
|
93
|
+
const cursor = isActive ? "\u2588" : "";
|
|
94
|
+
const prefix = isActive ? "> " : " ";
|
|
95
|
+
return (
|
|
96
|
+
<box key={field} height={1} width="100%">
|
|
97
|
+
<text>{`${prefix}${label}: ${value}${cursor}`}</text>
|
|
98
|
+
</box>
|
|
99
|
+
);
|
|
100
|
+
})}
|
|
101
|
+
|
|
102
|
+
<box height={1} width="100%" marginTop={1}>
|
|
103
|
+
<text>
|
|
104
|
+
{"Tab:next field Enter:submit Escape:cancel Backspace:delete"}
|
|
105
|
+
</text>
|
|
106
|
+
</box>
|
|
107
|
+
</box>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Column search results sub-view.
|
|
3
|
+
* Displays datasets matching a column name query via the knowledge store.
|
|
4
|
+
* Issue #2930.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React from "react";
|
|
8
|
+
|
|
9
|
+
interface ColumnResult {
|
|
10
|
+
readonly entityUrn: string;
|
|
11
|
+
readonly columnName: string;
|
|
12
|
+
readonly columnType: string;
|
|
13
|
+
readonly path?: string | null;
|
|
14
|
+
readonly schema?: {
|
|
15
|
+
readonly columns?: readonly { name: string; type: string }[];
|
|
16
|
+
readonly format?: string;
|
|
17
|
+
readonly row_count?: number;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface ColumnSearchProps {
|
|
22
|
+
readonly results: readonly ColumnResult[];
|
|
23
|
+
readonly loading: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function formatUrn(urn: string): string {
|
|
27
|
+
// urn:nexus:file:zone:hash → show shortened hash
|
|
28
|
+
const parts = urn.split(":");
|
|
29
|
+
const hash = parts[parts.length - 1] ?? urn;
|
|
30
|
+
const zone = parts.length >= 4 ? parts[3] : "";
|
|
31
|
+
return zone ? `${zone}:${hash.slice(0, 8)}…` : hash.slice(0, 12) + "…";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function ColumnSearch({
|
|
35
|
+
results,
|
|
36
|
+
loading,
|
|
37
|
+
}: ColumnSearchProps): React.ReactNode {
|
|
38
|
+
if (loading) {
|
|
39
|
+
return <text>Searching columns...</text>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (results.length === 0) {
|
|
43
|
+
return (
|
|
44
|
+
<text>
|
|
45
|
+
No column matches. Press / and type a column name to search.
|
|
46
|
+
</text>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<box flexDirection="column" height="100%" width="100%">
|
|
52
|
+
<text>
|
|
53
|
+
{" COLUMN TYPE FILE FORMAT ROWS OTHER COLUMNS"}
|
|
54
|
+
</text>
|
|
55
|
+
<text>
|
|
56
|
+
{" ────────────────── ─────────── ────────────────────────────────────── ────── ──── ─────────────────────────"}
|
|
57
|
+
</text>
|
|
58
|
+
{results.slice(0, 30).map((r, i) => {
|
|
59
|
+
const format = r.schema?.format ?? "?";
|
|
60
|
+
const rows = r.schema?.row_count != null ? String(r.schema.row_count) : "?";
|
|
61
|
+
const filePath = r.path ?? "(unknown)";
|
|
62
|
+
const otherCols = (r.schema?.columns ?? [])
|
|
63
|
+
.filter((c) => c.name !== r.columnName)
|
|
64
|
+
.map((c) => c.name)
|
|
65
|
+
.join(", ");
|
|
66
|
+
return (
|
|
67
|
+
<text key={i}>
|
|
68
|
+
{` ${r.columnName.padEnd(20)} ${r.columnType.padEnd(12)} ${filePath.padEnd(40)} ${format.padEnd(7)} ${rows.padEnd(5)} ${otherCols}`}
|
|
69
|
+
</text>
|
|
70
|
+
);
|
|
71
|
+
})}
|
|
72
|
+
{results.length > 30 && (
|
|
73
|
+
<text>
|
|
74
|
+
{` ... and ${results.length - 30} more results`}
|
|
75
|
+
</text>
|
|
76
|
+
)}
|
|
77
|
+
</box>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Knowledge graph view: entity detail (as dict) and neighbors list with depth info.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import React from "react";
|
|
6
|
+
import type { KnowledgeEntity, NeighborEntry } from "../../stores/search-store.js";
|
|
7
|
+
|
|
8
|
+
interface KnowledgeViewProps {
|
|
9
|
+
readonly entity: KnowledgeEntity | null;
|
|
10
|
+
readonly neighbors: readonly NeighborEntry[];
|
|
11
|
+
readonly knowledgeSearchResult: KnowledgeEntity | null;
|
|
12
|
+
readonly loading: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function formatEntityDict(entity: KnowledgeEntity): string {
|
|
16
|
+
const entries = Object.entries(entity);
|
|
17
|
+
if (entries.length === 0) return "{}";
|
|
18
|
+
return entries
|
|
19
|
+
.map(([key, value]) => `${key}: ${JSON.stringify(value)}`)
|
|
20
|
+
.join(", ");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function truncateValue(value: unknown, maxLen: number): string {
|
|
24
|
+
const str = JSON.stringify(value);
|
|
25
|
+
if (str.length <= maxLen) return str;
|
|
26
|
+
return `${str.slice(0, maxLen - 3)}...`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function KnowledgeView({
|
|
30
|
+
entity,
|
|
31
|
+
neighbors,
|
|
32
|
+
knowledgeSearchResult,
|
|
33
|
+
loading,
|
|
34
|
+
}: KnowledgeViewProps): React.ReactNode {
|
|
35
|
+
if (loading) {
|
|
36
|
+
return (
|
|
37
|
+
<box height="100%" width="100%" justifyContent="center" alignItems="center">
|
|
38
|
+
<text>Loading knowledge graph...</text>
|
|
39
|
+
</box>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!entity && !knowledgeSearchResult) {
|
|
44
|
+
return (
|
|
45
|
+
<box height="100%" width="100%" justifyContent="center" alignItems="center">
|
|
46
|
+
<text>Search or select a result to explore the knowledge graph</text>
|
|
47
|
+
</box>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<scrollbox height="100%" width="100%">
|
|
53
|
+
{/* Entity detail */}
|
|
54
|
+
{entity && (
|
|
55
|
+
<>
|
|
56
|
+
<box height={1} width="100%">
|
|
57
|
+
<text>--- Entity Detail ---</text>
|
|
58
|
+
</box>
|
|
59
|
+
{Object.entries(entity).map(([key, value]) => (
|
|
60
|
+
<box key={key} height={1} width="100%">
|
|
61
|
+
<text>{` ${key}: ${truncateValue(value, 60)}`}</text>
|
|
62
|
+
</box>
|
|
63
|
+
))}
|
|
64
|
+
</>
|
|
65
|
+
)}
|
|
66
|
+
|
|
67
|
+
{/* Neighbors */}
|
|
68
|
+
{neighbors.length > 0 && (
|
|
69
|
+
<>
|
|
70
|
+
<box height={1} width="100%" marginTop={1}>
|
|
71
|
+
<text>{`--- Neighbors (${neighbors.length}) ---`}</text>
|
|
72
|
+
</box>
|
|
73
|
+
{neighbors.map((n, i) => {
|
|
74
|
+
const entitySummary = formatEntityDict(n.entity);
|
|
75
|
+
const pathStr = n.path.join(" -> ");
|
|
76
|
+
return (
|
|
77
|
+
<box key={i} height={1} width="100%">
|
|
78
|
+
<text>{` [depth=${n.depth}] ${truncateValue(entitySummary, 50)} path: ${pathStr}`}</text>
|
|
79
|
+
</box>
|
|
80
|
+
);
|
|
81
|
+
})}
|
|
82
|
+
</>
|
|
83
|
+
)}
|
|
84
|
+
|
|
85
|
+
{/* Knowledge search result (single entity) */}
|
|
86
|
+
{!entity && knowledgeSearchResult && (
|
|
87
|
+
<>
|
|
88
|
+
<box height={1} width="100%">
|
|
89
|
+
<text>--- Graph Search Result ---</text>
|
|
90
|
+
</box>
|
|
91
|
+
{Object.entries(knowledgeSearchResult).map(([key, value]) => (
|
|
92
|
+
<box key={key} height={1} width="100%">
|
|
93
|
+
<text>{` ${key}: ${truncateValue(value, 60)}`}</text>
|
|
94
|
+
</box>
|
|
95
|
+
))}
|
|
96
|
+
</>
|
|
97
|
+
)}
|
|
98
|
+
</scrollbox>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory list: displays memory dicts from search results.
|
|
3
|
+
*
|
|
4
|
+
* When a memory has version history loaded, an expanded section
|
|
5
|
+
* appears below that memory row showing each version.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React from "react";
|
|
9
|
+
import type { Memory, MemoryHistory, MemoryDiff } from "../../stores/search-store.js";
|
|
10
|
+
import { truncateText } from "../../shared/utils/format-text.js";
|
|
11
|
+
|
|
12
|
+
interface MemoryListProps {
|
|
13
|
+
readonly memories: readonly Memory[];
|
|
14
|
+
readonly selectedIndex: number;
|
|
15
|
+
readonly loading: boolean;
|
|
16
|
+
readonly memoryHistory: MemoryHistory | null;
|
|
17
|
+
readonly memoryHistoryLoading: boolean;
|
|
18
|
+
readonly memoryDiff: MemoryDiff | null;
|
|
19
|
+
readonly memoryDiffLoading: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function shortId(id: unknown): string {
|
|
23
|
+
const str = String(id ?? "");
|
|
24
|
+
if (str.length <= 12) return str;
|
|
25
|
+
return `${str.slice(0, 8)}..`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function getMemoryField(memory: Memory, field: string): unknown {
|
|
29
|
+
return (memory as Record<string, unknown>)[field];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function formatTimestamp(ts: string): string {
|
|
33
|
+
if (!ts) return "";
|
|
34
|
+
// Show date and time, truncated for terminal width
|
|
35
|
+
return ts.slice(0, 19).replace("T", " ");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function VersionHistorySection({
|
|
39
|
+
history,
|
|
40
|
+
historyLoading,
|
|
41
|
+
}: {
|
|
42
|
+
readonly history: MemoryHistory | null;
|
|
43
|
+
readonly historyLoading: boolean;
|
|
44
|
+
}): React.ReactNode {
|
|
45
|
+
if (historyLoading) {
|
|
46
|
+
return (
|
|
47
|
+
<box height={1} width="100%" marginLeft={4}>
|
|
48
|
+
<text>{"Loading version history..."}</text>
|
|
49
|
+
</box>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!history) return null;
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<box width="100%" flexDirection="column" marginLeft={4}>
|
|
57
|
+
<box height={1} width="100%">
|
|
58
|
+
<text>{`Version History (current: v${history.current_version})`}</text>
|
|
59
|
+
</box>
|
|
60
|
+
<box height={1} width="100%">
|
|
61
|
+
<text>{" VER STATUS CREATED AT"}</text>
|
|
62
|
+
</box>
|
|
63
|
+
<box height={1} width="100%">
|
|
64
|
+
<text>{" --- ---------- -------------------"}</text>
|
|
65
|
+
</box>
|
|
66
|
+
{history.versions.map((v) => {
|
|
67
|
+
const isCurrent = v.version === history.current_version;
|
|
68
|
+
const marker = isCurrent ? " *" : " ";
|
|
69
|
+
const ver = String(v.version).padStart(3);
|
|
70
|
+
const status = v.status.padEnd(10);
|
|
71
|
+
const created = formatTimestamp(v.created_at);
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<box key={v.version} height={1} width="100%">
|
|
75
|
+
<text>{` ${marker}${ver} ${status} ${created}`}</text>
|
|
76
|
+
</box>
|
|
77
|
+
);
|
|
78
|
+
})}
|
|
79
|
+
</box>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function DiffSection({
|
|
84
|
+
diff,
|
|
85
|
+
diffLoading,
|
|
86
|
+
}: {
|
|
87
|
+
readonly diff: MemoryDiff | null;
|
|
88
|
+
readonly diffLoading: boolean;
|
|
89
|
+
}): React.ReactNode {
|
|
90
|
+
if (diffLoading) {
|
|
91
|
+
return (
|
|
92
|
+
<box height={1} width="100%" marginLeft={4}>
|
|
93
|
+
<text>{"Loading diff..."}</text>
|
|
94
|
+
</box>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (!diff) return null;
|
|
99
|
+
|
|
100
|
+
const lines = diff.diff.split("\n");
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<box width="100%" flexDirection="column" marginLeft={4}>
|
|
104
|
+
<box height={1} width="100%">
|
|
105
|
+
<text>{`Diff v${diff.v1} -> v${diff.v2} (${diff.mode})`}</text>
|
|
106
|
+
</box>
|
|
107
|
+
{lines.map((line, i) => (
|
|
108
|
+
<box key={i} height={1} width="100%">
|
|
109
|
+
<text>{` ${line}`}</text>
|
|
110
|
+
</box>
|
|
111
|
+
))}
|
|
112
|
+
</box>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function MemoryList({
|
|
117
|
+
memories,
|
|
118
|
+
selectedIndex,
|
|
119
|
+
loading,
|
|
120
|
+
memoryHistory,
|
|
121
|
+
memoryHistoryLoading,
|
|
122
|
+
memoryDiff,
|
|
123
|
+
memoryDiffLoading,
|
|
124
|
+
}: MemoryListProps): React.ReactNode {
|
|
125
|
+
if (loading) {
|
|
126
|
+
return (
|
|
127
|
+
<box height="100%" width="100%" justifyContent="center" alignItems="center">
|
|
128
|
+
<text>Loading memories...</text>
|
|
129
|
+
</box>
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (memories.length === 0) {
|
|
134
|
+
return (
|
|
135
|
+
<box height="100%" width="100%" justifyContent="center" alignItems="center">
|
|
136
|
+
<text>No memories found</text>
|
|
137
|
+
</box>
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Determine which memory has history expanded (matches memoryHistory.memory_id)
|
|
142
|
+
const expandedMemoryId = memoryHistory?.memory_id ?? null;
|
|
143
|
+
|
|
144
|
+
return (
|
|
145
|
+
<box height="100%" width="100%" flexDirection="column">
|
|
146
|
+
{/* Header */}
|
|
147
|
+
<box height={1} width="100%">
|
|
148
|
+
<text>{`Memories: ${memories.length}`}</text>
|
|
149
|
+
</box>
|
|
150
|
+
<box height={1} width="100%">
|
|
151
|
+
<text>{" ID TYPE CONTENT"}</text>
|
|
152
|
+
</box>
|
|
153
|
+
<box height={1} width="100%">
|
|
154
|
+
<text>{" ------------ --------- ------------------------------------------------"}</text>
|
|
155
|
+
</box>
|
|
156
|
+
|
|
157
|
+
{/* Rows */}
|
|
158
|
+
<scrollbox flexGrow={1} width="100%">
|
|
159
|
+
{memories.map((m, i) => {
|
|
160
|
+
const isSelected = i === selectedIndex;
|
|
161
|
+
const memId = String(getMemoryField(m, "memory_id") ?? "");
|
|
162
|
+
const hasHistory = memId === expandedMemoryId;
|
|
163
|
+
const historyIndicator = hasHistory ? " [H]" : "";
|
|
164
|
+
const prefix = isSelected ? "> " : " ";
|
|
165
|
+
const memoryIdDisplay = shortId(getMemoryField(m, "memory_id")).padEnd(12);
|
|
166
|
+
const memType = String(getMemoryField(m, "type") ?? "unknown").padEnd(9);
|
|
167
|
+
const content = truncateText(
|
|
168
|
+
String(getMemoryField(m, "content") ?? JSON.stringify(m)),
|
|
169
|
+
44,
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
return (
|
|
173
|
+
<React.Fragment key={i}>
|
|
174
|
+
<box height={1} width="100%">
|
|
175
|
+
<text>
|
|
176
|
+
{`${prefix}${memoryIdDisplay} ${memType} ${content}${historyIndicator}`}
|
|
177
|
+
</text>
|
|
178
|
+
</box>
|
|
179
|
+
{isSelected && (hasHistory || memoryHistoryLoading) && (
|
|
180
|
+
<VersionHistorySection
|
|
181
|
+
history={memoryHistory}
|
|
182
|
+
historyLoading={memoryHistoryLoading}
|
|
183
|
+
/>
|
|
184
|
+
)}
|
|
185
|
+
{isSelected && (memoryDiff !== null || memoryDiffLoading) && (
|
|
186
|
+
<DiffSection
|
|
187
|
+
diff={memoryDiff}
|
|
188
|
+
diffLoading={memoryDiffLoading}
|
|
189
|
+
/>
|
|
190
|
+
)}
|
|
191
|
+
</React.Fragment>
|
|
192
|
+
);
|
|
193
|
+
})}
|
|
194
|
+
</scrollbox>
|
|
195
|
+
</box>
|
|
196
|
+
);
|
|
197
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Playbook list: displays playbook records with name, scope, tags, usage, and success rate.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import React from "react";
|
|
6
|
+
import type { PlaybookRecord } from "../../stores/search-store.js";
|
|
7
|
+
import { truncateText } from "../../shared/utils/format-text.js";
|
|
8
|
+
|
|
9
|
+
interface PlaybookListProps {
|
|
10
|
+
readonly playbooks: readonly PlaybookRecord[];
|
|
11
|
+
readonly selectedIndex: number;
|
|
12
|
+
readonly loading: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function formatRate(rate: number | null): string {
|
|
16
|
+
if (rate === null || rate === undefined) return "-";
|
|
17
|
+
return `${(rate * 100).toFixed(0)}%`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function PlaybookList({
|
|
21
|
+
playbooks,
|
|
22
|
+
selectedIndex,
|
|
23
|
+
loading,
|
|
24
|
+
}: PlaybookListProps): React.ReactNode {
|
|
25
|
+
if (loading) {
|
|
26
|
+
return (
|
|
27
|
+
<box height="100%" width="100%" justifyContent="center" alignItems="center">
|
|
28
|
+
<text>Loading playbooks...</text>
|
|
29
|
+
</box>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (playbooks.length === 0) {
|
|
34
|
+
return (
|
|
35
|
+
<box height="100%" width="100%" justifyContent="center" alignItems="center">
|
|
36
|
+
<text>No playbooks found</text>
|
|
37
|
+
</box>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<box height="100%" width="100%" flexDirection="column">
|
|
43
|
+
{/* Header */}
|
|
44
|
+
<box height={1} width="100%">
|
|
45
|
+
<text>{`Playbooks: ${playbooks.length}`}</text>
|
|
46
|
+
</box>
|
|
47
|
+
<box height={1} width="100%">
|
|
48
|
+
<text>{" NAME SCOPE VIS VER USED RATE"}</text>
|
|
49
|
+
</box>
|
|
50
|
+
<box height={1} width="100%">
|
|
51
|
+
<text>{" ---------------------------- --------- ------ --- ---- ----"}</text>
|
|
52
|
+
</box>
|
|
53
|
+
|
|
54
|
+
{/* Rows */}
|
|
55
|
+
<scrollbox flexGrow={1} width="100%">
|
|
56
|
+
{playbooks.map((p, i) => {
|
|
57
|
+
const isSelected = i === selectedIndex;
|
|
58
|
+
const prefix = isSelected ? "> " : " ";
|
|
59
|
+
const name = truncateText(p.name, 28).padEnd(28);
|
|
60
|
+
const scope = truncateText(p.scope, 9).padEnd(9);
|
|
61
|
+
const vis = truncateText(p.visibility, 6).padEnd(6);
|
|
62
|
+
const ver = String(p.version).padEnd(3);
|
|
63
|
+
const used = String(p.usage_count).padEnd(4);
|
|
64
|
+
const rate = formatRate(p.success_rate);
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<box key={p.playbook_id} height={1} width="100%">
|
|
68
|
+
<text>
|
|
69
|
+
{`${prefix}${name} ${scope} ${vis} ${ver} ${used} ${rate}`}
|
|
70
|
+
</text>
|
|
71
|
+
</box>
|
|
72
|
+
);
|
|
73
|
+
})}
|
|
74
|
+
</scrollbox>
|
|
75
|
+
</box>
|
|
76
|
+
);
|
|
77
|
+
}
|