@polderlabs/bizar 2.6.1 → 3.0.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/cli/bin.mjs +158 -130
- package/cli/plan.test.mjs +2331 -0
- package/cli/service.mjs +309 -0
- package/package.json +19 -27
- package/cli/dashboard/api.mjs +0 -473
- package/cli/dashboard/browser.mjs +0 -40
- package/cli/dashboard/server.mjs +0 -366
- package/cli/dashboard/state.mjs +0 -438
- package/cli/dashboard/tasks-store.mjs +0 -203
- package/cli/dashboard/watcher.mjs +0 -81
- package/cli/dashboard.mjs +0 -97
- package/dist/assets/index-BVvY22Gt.css +0 -1
- package/dist/assets/index-CO3c8O32.js +0 -285
- package/dist/assets/index-CO3c8O32.js.map +0 -1
- package/dist/index.html +0 -18
- package/src/App.tsx +0 -233
- package/src/components/Button.tsx +0 -55
- package/src/components/Card.tsx +0 -40
- package/src/components/EmptyState.tsx +0 -30
- package/src/components/Modal.tsx +0 -137
- package/src/components/Spinner.tsx +0 -19
- package/src/components/StatusBadge.tsx +0 -25
- package/src/components/Tag.tsx +0 -28
- package/src/components/Toast.tsx +0 -142
- package/src/components/Topbar.tsx +0 -88
- package/src/index.html +0 -17
- package/src/lib/api.ts +0 -71
- package/src/lib/markdown.tsx +0 -59
- package/src/lib/types.ts +0 -200
- package/src/lib/utils.ts +0 -79
- package/src/lib/ws.ts +0 -132
- package/src/main.tsx +0 -12
- package/src/styles/main.css +0 -2324
- package/src/views/Agents.tsx +0 -199
- package/src/views/Chat.tsx +0 -255
- package/src/views/Config.tsx +0 -250
- package/src/views/Overview.tsx +0 -267
- package/src/views/Plans.tsx +0 -667
- package/src/views/Projects.tsx +0 -155
- package/src/views/Settings.tsx +0 -253
- package/src/views/Tasks.tsx +0 -567
- package/tsconfig.json +0 -23
- package/vite.config.ts +0 -24
package/src/components/Toast.tsx
DELETED
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
// src/components/Toast.tsx — toast context, provider hook, container.
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
createContext,
|
|
5
|
-
useCallback,
|
|
6
|
-
useContext,
|
|
7
|
-
useMemo,
|
|
8
|
-
useRef,
|
|
9
|
-
useState,
|
|
10
|
-
} from 'react';
|
|
11
|
-
import { CheckCircle2, AlertTriangle, AlertCircle, Info, X } from 'lucide-react';
|
|
12
|
-
|
|
13
|
-
export type ToastKind = 'info' | 'success' | 'error' | 'warning';
|
|
14
|
-
|
|
15
|
-
export type Toast = {
|
|
16
|
-
id: number;
|
|
17
|
-
kind: ToastKind;
|
|
18
|
-
message: string;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
export type ToastApi = {
|
|
22
|
-
show: (message: string, kind?: ToastKind, durationMs?: number) => void;
|
|
23
|
-
info: (message: string, durationMs?: number) => void;
|
|
24
|
-
success: (message: string, durationMs?: number) => void;
|
|
25
|
-
error: (message: string, durationMs?: number) => void;
|
|
26
|
-
warning: (message: string, durationMs?: number) => void;
|
|
27
|
-
dismiss: (id: number) => void;
|
|
28
|
-
toasts: Toast[];
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
const ToastContext = createContext<ToastApi | null>(null);
|
|
32
|
-
|
|
33
|
-
export function useToast(): ToastApi {
|
|
34
|
-
const ctx = useContext(ToastContext);
|
|
35
|
-
if (!ctx) {
|
|
36
|
-
// Fallback no-op so tests/Storybook don't crash
|
|
37
|
-
return {
|
|
38
|
-
show: () => undefined,
|
|
39
|
-
info: () => undefined,
|
|
40
|
-
success: () => undefined,
|
|
41
|
-
error: () => undefined,
|
|
42
|
-
warning: () => undefined,
|
|
43
|
-
dismiss: () => undefined,
|
|
44
|
-
toasts: [],
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
return ctx;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const ICONS: Record<ToastKind, typeof Info> = {
|
|
51
|
-
info: Info,
|
|
52
|
-
success: CheckCircle2,
|
|
53
|
-
error: AlertCircle,
|
|
54
|
-
warning: AlertTriangle,
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
export function ToastProvider({ children }: { children: React.ReactNode }) {
|
|
58
|
-
const [toasts, setToasts] = useState<Toast[]>([]);
|
|
59
|
-
const idRef = useRef(1);
|
|
60
|
-
const timersRef = useRef<Map<number, ReturnType<typeof setTimeout>>>(new Map());
|
|
61
|
-
|
|
62
|
-
const dismiss = useCallback((id: number) => {
|
|
63
|
-
setToasts((cur) => cur.filter((t) => t.id !== id));
|
|
64
|
-
const timer = timersRef.current.get(id);
|
|
65
|
-
if (timer) {
|
|
66
|
-
clearTimeout(timer);
|
|
67
|
-
timersRef.current.delete(id);
|
|
68
|
-
}
|
|
69
|
-
}, []);
|
|
70
|
-
|
|
71
|
-
const show = useCallback<ToastApi['show']>(
|
|
72
|
-
(message, kind = 'info', durationMs = 4000) => {
|
|
73
|
-
const id = idRef.current++;
|
|
74
|
-
setToasts((cur) => [...cur, { id, kind, message }]);
|
|
75
|
-
if (durationMs > 0) {
|
|
76
|
-
const timer = setTimeout(() => dismiss(id), durationMs);
|
|
77
|
-
timersRef.current.set(id, timer);
|
|
78
|
-
}
|
|
79
|
-
},
|
|
80
|
-
[dismiss],
|
|
81
|
-
);
|
|
82
|
-
|
|
83
|
-
const api = useMemo<ToastApi>(
|
|
84
|
-
() => ({
|
|
85
|
-
show,
|
|
86
|
-
info: (m, d) => show(m, 'info', d),
|
|
87
|
-
success: (m, d) => show(m, 'success', d),
|
|
88
|
-
error: (m, d) => show(m, 'error', d),
|
|
89
|
-
warning: (m, d) => show(m, 'warning', d),
|
|
90
|
-
dismiss,
|
|
91
|
-
toasts,
|
|
92
|
-
}),
|
|
93
|
-
[show, dismiss, toasts],
|
|
94
|
-
);
|
|
95
|
-
|
|
96
|
-
return (
|
|
97
|
-
<ToastContext.Provider value={api}>
|
|
98
|
-
{children}
|
|
99
|
-
<ToastContainer toasts={toasts} onDismiss={dismiss} />
|
|
100
|
-
</ToastContext.Provider>
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
export function ToastContainer({
|
|
105
|
-
toasts,
|
|
106
|
-
onDismiss,
|
|
107
|
-
}: {
|
|
108
|
-
toasts: Toast[];
|
|
109
|
-
onDismiss: (id: number) => void;
|
|
110
|
-
}) {
|
|
111
|
-
return (
|
|
112
|
-
<div className="toast-stack" aria-live="polite">
|
|
113
|
-
{toasts.map((t) => (
|
|
114
|
-
<ToastItem key={t.id} toast={t} onDismiss={onDismiss} />
|
|
115
|
-
))}
|
|
116
|
-
</div>
|
|
117
|
-
);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function ToastItem({
|
|
121
|
-
toast,
|
|
122
|
-
onDismiss,
|
|
123
|
-
}: {
|
|
124
|
-
toast: Toast;
|
|
125
|
-
onDismiss: (id: number) => void;
|
|
126
|
-
}) {
|
|
127
|
-
const Icon = ICONS[toast.kind];
|
|
128
|
-
return (
|
|
129
|
-
<div className={`toast toast-${toast.kind}`} role="status">
|
|
130
|
-
<Icon size={16} className="toast-icon" />
|
|
131
|
-
<span className="toast-message">{toast.message}</span>
|
|
132
|
-
<button
|
|
133
|
-
type="button"
|
|
134
|
-
className="toast-close"
|
|
135
|
-
aria-label="Dismiss"
|
|
136
|
-
onClick={() => onDismiss(toast.id)}
|
|
137
|
-
>
|
|
138
|
-
<X size={14} />
|
|
139
|
-
</button>
|
|
140
|
-
</div>
|
|
141
|
-
);
|
|
142
|
-
}
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
// src/components/Topbar.tsx — header with brand, tab nav, ws status.
|
|
2
|
-
|
|
3
|
-
import type { ReactNode } from 'react';
|
|
4
|
-
import {
|
|
5
|
-
LayoutDashboard,
|
|
6
|
-
MessageSquare,
|
|
7
|
-
Bot,
|
|
8
|
-
Map,
|
|
9
|
-
Folder,
|
|
10
|
-
CheckSquare,
|
|
11
|
-
Settings2,
|
|
12
|
-
Sliders,
|
|
13
|
-
type LucideIcon,
|
|
14
|
-
} from 'lucide-react';
|
|
15
|
-
import { cn } from '../lib/utils';
|
|
16
|
-
import type { WsStatus } from '../lib/types';
|
|
17
|
-
|
|
18
|
-
export type TabDef = {
|
|
19
|
-
id: string;
|
|
20
|
-
label: string;
|
|
21
|
-
icon: LucideIcon;
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
export const TABS: TabDef[] = [
|
|
25
|
-
{ id: 'overview', label: 'Overview', icon: LayoutDashboard },
|
|
26
|
-
{ id: 'chat', label: 'Chat', icon: MessageSquare },
|
|
27
|
-
{ id: 'agents', label: 'Agents', icon: Bot },
|
|
28
|
-
{ id: 'plans', label: 'Plans', icon: Map },
|
|
29
|
-
{ id: 'projects', label: 'Projects', icon: Folder },
|
|
30
|
-
{ id: 'tasks', label: 'Tasks', icon: CheckSquare },
|
|
31
|
-
{ id: 'config', label: 'Config', icon: Settings2 },
|
|
32
|
-
{ id: 'settings', label: 'Settings', icon: Sliders },
|
|
33
|
-
];
|
|
34
|
-
|
|
35
|
-
export type TopbarProps = {
|
|
36
|
-
activeTab: string;
|
|
37
|
-
onTabChange: (id: string) => void;
|
|
38
|
-
wsStatus: WsStatus;
|
|
39
|
-
version: string;
|
|
40
|
-
rightSlot?: ReactNode;
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
export function Topbar({
|
|
44
|
-
activeTab,
|
|
45
|
-
onTabChange,
|
|
46
|
-
wsStatus,
|
|
47
|
-
version,
|
|
48
|
-
rightSlot,
|
|
49
|
-
}: TopbarProps) {
|
|
50
|
-
return (
|
|
51
|
-
<header className="topbar">
|
|
52
|
-
<div className="brand">
|
|
53
|
-
<span className="brand-logo" aria-hidden>
|
|
54
|
-
🪩
|
|
55
|
-
</span>
|
|
56
|
-
<span className="brand-title">Bizar</span>
|
|
57
|
-
<span className="brand-version">{version}</span>
|
|
58
|
-
</div>
|
|
59
|
-
<nav className="tabs" role="tablist">
|
|
60
|
-
{TABS.map((tab) => {
|
|
61
|
-
const Icon = tab.icon;
|
|
62
|
-
const active = tab.id === activeTab;
|
|
63
|
-
return (
|
|
64
|
-
<button
|
|
65
|
-
key={tab.id}
|
|
66
|
-
type="button"
|
|
67
|
-
role="tab"
|
|
68
|
-
aria-selected={active}
|
|
69
|
-
className={cn('tab', active && 'tab-active')}
|
|
70
|
-
onClick={() => onTabChange(tab.id)}
|
|
71
|
-
title={`${tab.label} (${TABS.findIndex((t) => t.id === tab.id) + 1})`}
|
|
72
|
-
>
|
|
73
|
-
<Icon size={14} className="tab-icon" />
|
|
74
|
-
<span className="tab-label">{tab.label}</span>
|
|
75
|
-
</button>
|
|
76
|
-
);
|
|
77
|
-
})}
|
|
78
|
-
</nav>
|
|
79
|
-
<div className="topbar-right">
|
|
80
|
-
{rightSlot}
|
|
81
|
-
<div className={cn('ws-status', `ws-${wsStatus}`)} title={`WebSocket: ${wsStatus}`}>
|
|
82
|
-
<span className="ws-dot" />
|
|
83
|
-
<span className="ws-label">{wsStatus}</span>
|
|
84
|
-
</div>
|
|
85
|
-
</div>
|
|
86
|
-
</header>
|
|
87
|
-
);
|
|
88
|
-
}
|
package/src/index.html
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8" />
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
-
<title>Bizar Dashboard</title>
|
|
7
|
-
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🪩</text></svg>" />
|
|
8
|
-
<link rel="preconnect" href="https://rsms.me/" />
|
|
9
|
-
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
10
|
-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
11
|
-
<link rel="stylesheet" href="https://rsms.me/inter/inter.css" />
|
|
12
|
-
</head>
|
|
13
|
-
<body>
|
|
14
|
-
<div id="root"></div>
|
|
15
|
-
<script type="module" src="/main.tsx"></script>
|
|
16
|
-
</body>
|
|
17
|
-
</html>
|
package/src/lib/api.ts
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
// src/lib/api.ts — REST client. Single instance, type-safe wrappers.
|
|
2
|
-
|
|
3
|
-
export class ApiError extends Error {
|
|
4
|
-
status: number;
|
|
5
|
-
data: unknown;
|
|
6
|
-
constructor(message: string, status: number, data?: unknown) {
|
|
7
|
-
super(message);
|
|
8
|
-
this.name = 'ApiError';
|
|
9
|
-
this.status = status;
|
|
10
|
-
this.data = data;
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
class ApiClient {
|
|
15
|
-
base = '/api';
|
|
16
|
-
|
|
17
|
-
async get<T>(path: string): Promise<T> {
|
|
18
|
-
return this.req<T>('GET', path);
|
|
19
|
-
}
|
|
20
|
-
async post<T>(path: string, body?: unknown): Promise<T> {
|
|
21
|
-
return this.req<T>('POST', path, body);
|
|
22
|
-
}
|
|
23
|
-
async put<T>(path: string, body?: unknown): Promise<T> {
|
|
24
|
-
return this.req<T>('PUT', path, body);
|
|
25
|
-
}
|
|
26
|
-
async patch<T>(path: string, body?: unknown): Promise<T> {
|
|
27
|
-
return this.req<T>('PATCH', path, body);
|
|
28
|
-
}
|
|
29
|
-
async del<T = unknown>(path: string): Promise<T> {
|
|
30
|
-
return this.req<T>('DELETE', path);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
private async req<T>(method: string, path: string, body?: unknown): Promise<T> {
|
|
34
|
-
const opts: RequestInit = {
|
|
35
|
-
method,
|
|
36
|
-
headers: { 'Content-Type': 'application/json' },
|
|
37
|
-
};
|
|
38
|
-
if (body !== undefined && body !== null) {
|
|
39
|
-
opts.body = typeof body === 'string' ? body : JSON.stringify(body);
|
|
40
|
-
}
|
|
41
|
-
const r = await fetch(this.base + path, opts);
|
|
42
|
-
const ct = r.headers.get('content-type') || '';
|
|
43
|
-
if (ct.includes('application/json')) {
|
|
44
|
-
const data = await r.json();
|
|
45
|
-
if (!r.ok) {
|
|
46
|
-
const msg =
|
|
47
|
-
(data && typeof data === 'object' && 'message' in data
|
|
48
|
-
? (data as { message?: string }).message
|
|
49
|
-
: undefined) || `${method} ${path}: ${r.status}`;
|
|
50
|
-
throw new ApiError(msg, r.status, data);
|
|
51
|
-
}
|
|
52
|
-
return data as T;
|
|
53
|
-
}
|
|
54
|
-
const text = await r.text();
|
|
55
|
-
if (!r.ok) {
|
|
56
|
-
throw new ApiError(
|
|
57
|
-
`${method} ${path}: ${r.status} — ${text.slice(0, 200)}`,
|
|
58
|
-
r.status,
|
|
59
|
-
text,
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
|
-
try {
|
|
63
|
-
return JSON.parse(text) as T;
|
|
64
|
-
} catch {
|
|
65
|
-
return text as unknown as T;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export const api = new ApiClient();
|
|
71
|
-
export { ApiClient };
|
package/src/lib/markdown.tsx
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
// src/lib/markdown.ts — pretty JSON syntax highlighter for the JSON tree panel.
|
|
2
|
-
// Replaces the old utils.highlightJSON; we render to React nodes, not strings.
|
|
3
|
-
|
|
4
|
-
import { Fragment } from 'react';
|
|
5
|
-
|
|
6
|
-
type Token = { kind: 'key' | 'string' | 'number' | 'boolean' | 'null' | 'punct' | 'space'; value: string };
|
|
7
|
-
|
|
8
|
-
function tokenize(json: string): Token[] {
|
|
9
|
-
const out: Token[] = [];
|
|
10
|
-
const re = /("(?:\\.|[^"\\])*")(\s*:)?|(-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)|(\btrue\b|\bfalse\b)|(\bnull\b)|([{}\[\],])|(\s+)/g;
|
|
11
|
-
let m: RegExpExecArray | null;
|
|
12
|
-
while ((m = re.exec(json)) !== null) {
|
|
13
|
-
if (m[1] !== undefined) {
|
|
14
|
-
out.push({ kind: m[2] ? 'key' : 'string', value: m[1] });
|
|
15
|
-
if (m[2]) out.push({ kind: 'punct', value: m[2] });
|
|
16
|
-
} else if (m[3] !== undefined) {
|
|
17
|
-
out.push({ kind: 'number', value: m[3] });
|
|
18
|
-
} else if (m[4] !== undefined) {
|
|
19
|
-
out.push({ kind: 'boolean', value: m[4] });
|
|
20
|
-
} else if (m[5] !== undefined) {
|
|
21
|
-
out.push({ kind: 'null', value: m[5] });
|
|
22
|
-
} else if (m[6] !== undefined) {
|
|
23
|
-
out.push({ kind: 'punct', value: m[6] });
|
|
24
|
-
} else if (m[7] !== undefined) {
|
|
25
|
-
out.push({ kind: 'space', value: m[7] });
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
return out;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const COLOR: Record<Token['kind'], string | undefined> = {
|
|
32
|
-
key: 'var(--syntax-key)',
|
|
33
|
-
string: 'var(--syntax-string)',
|
|
34
|
-
number: 'var(--syntax-number)',
|
|
35
|
-
boolean: 'var(--syntax-boolean)',
|
|
36
|
-
null: 'var(--syntax-null)',
|
|
37
|
-
punct: undefined,
|
|
38
|
-
space: undefined,
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
/** Render highlighted JSON as React fragments. */
|
|
42
|
-
export function JsonHighlight({ value }: { value: unknown }) {
|
|
43
|
-
const json = JSON.stringify(value, null, 2);
|
|
44
|
-
if (json === undefined) return null;
|
|
45
|
-
const tokens = tokenize(json);
|
|
46
|
-
return (
|
|
47
|
-
<>
|
|
48
|
-
{tokens.map((t, i) => {
|
|
49
|
-
const color = COLOR[t.kind];
|
|
50
|
-
if (!color) return <Fragment key={i}>{t.value}</Fragment>;
|
|
51
|
-
return (
|
|
52
|
-
<span key={i} style={{ color }}>
|
|
53
|
-
{t.value}
|
|
54
|
-
</span>
|
|
55
|
-
);
|
|
56
|
-
})}
|
|
57
|
-
</>
|
|
58
|
-
);
|
|
59
|
-
}
|
package/src/lib/types.ts
DELETED
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
// src/lib/types.ts — TypeScript types for the Bizar dashboard.
|
|
2
|
-
// All shapes here mirror the JSON returned by cli/dashboard/api.mjs.
|
|
3
|
-
|
|
4
|
-
export type ThemeName = 'dark' | 'light' | 'system';
|
|
5
|
-
|
|
6
|
-
export type Agent = {
|
|
7
|
-
name: string;
|
|
8
|
-
description: string;
|
|
9
|
-
model: string;
|
|
10
|
-
mode: string;
|
|
11
|
-
file: string;
|
|
12
|
-
path: string;
|
|
13
|
-
mtime: number;
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
export type Project = {
|
|
17
|
-
name: string;
|
|
18
|
-
path: string;
|
|
19
|
-
projectMdSize: number;
|
|
20
|
-
hindsightCount: number;
|
|
21
|
-
mtime: number;
|
|
22
|
-
active: boolean;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
export type Plan = {
|
|
26
|
-
slug: string;
|
|
27
|
-
title: string;
|
|
28
|
-
status: string;
|
|
29
|
-
source: 'worktree' | 'global' | string;
|
|
30
|
-
elementCount: number | null;
|
|
31
|
-
commentCount: number | null;
|
|
32
|
-
mtime: number;
|
|
33
|
-
planUrl: string | null;
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
export type CanvasElement = {
|
|
37
|
-
id: string;
|
|
38
|
-
type: string;
|
|
39
|
-
title?: string;
|
|
40
|
-
content?: string;
|
|
41
|
-
x: number;
|
|
42
|
-
y: number;
|
|
43
|
-
width: number;
|
|
44
|
-
height: number;
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
export type CanvasConnection = {
|
|
48
|
-
id: string;
|
|
49
|
-
fromElementId?: string;
|
|
50
|
-
from?: string;
|
|
51
|
-
toElementId?: string;
|
|
52
|
-
to?: string;
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
export type CanvasComment = {
|
|
56
|
-
id: string;
|
|
57
|
-
elementId?: string;
|
|
58
|
-
text: string;
|
|
59
|
-
author: string;
|
|
60
|
-
created: string;
|
|
61
|
-
thread?: { author: string; text: string }[];
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
export type CanvasViewport = { x: number; y: number; zoom: number };
|
|
65
|
-
|
|
66
|
-
export type Canvas = {
|
|
67
|
-
schemaVersion?: number;
|
|
68
|
-
title: string;
|
|
69
|
-
elements: CanvasElement[];
|
|
70
|
-
connections: CanvasConnection[];
|
|
71
|
-
comments: CanvasComment[];
|
|
72
|
-
viewport: CanvasViewport;
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
export type ConfigResponse = {
|
|
76
|
-
path: string;
|
|
77
|
-
data: unknown;
|
|
78
|
-
raw: string;
|
|
79
|
-
exists: boolean;
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
export type Settings = {
|
|
83
|
-
theme: ThemeName;
|
|
84
|
-
defaultAgent: string;
|
|
85
|
-
defaultModel: string;
|
|
86
|
-
notifications: {
|
|
87
|
-
onAgentComplete: boolean;
|
|
88
|
-
onPlanApproval: boolean;
|
|
89
|
-
};
|
|
90
|
-
about: {
|
|
91
|
-
version: string;
|
|
92
|
-
homepage: string;
|
|
93
|
-
license: string;
|
|
94
|
-
};
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
export type SettingsResponse = {
|
|
98
|
-
path: string;
|
|
99
|
-
data: Settings;
|
|
100
|
-
exists: boolean;
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
export type OverviewCounts = {
|
|
104
|
-
agents: number;
|
|
105
|
-
plans: number;
|
|
106
|
-
projects: number;
|
|
107
|
-
sessions: number;
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
export type OverviewVersions = {
|
|
111
|
-
node: string;
|
|
112
|
-
platform: string;
|
|
113
|
-
projectRoot: string;
|
|
114
|
-
bizarRoot: string;
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
export type ActivityItem = {
|
|
118
|
-
ts: string;
|
|
119
|
-
kind: string;
|
|
120
|
-
[k: string]: unknown;
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
export type Overview = {
|
|
124
|
-
counts: OverviewCounts;
|
|
125
|
-
recentActivity: ActivityItem[];
|
|
126
|
-
versions: OverviewVersions;
|
|
127
|
-
generatedAt: string;
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
export type ChatMessage = {
|
|
131
|
-
role: 'user' | 'assistant' | 'system' | string;
|
|
132
|
-
content?: string;
|
|
133
|
-
message?: string;
|
|
134
|
-
agent?: string;
|
|
135
|
-
ts?: string | number;
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
export type ChatSession = {
|
|
139
|
-
id: string;
|
|
140
|
-
file: string;
|
|
141
|
-
mtime: number;
|
|
142
|
-
size: number;
|
|
143
|
-
};
|
|
144
|
-
|
|
145
|
-
export type ChatResponse = {
|
|
146
|
-
messages: ChatMessage[];
|
|
147
|
-
sessions: ChatSession[];
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
export type Task = {
|
|
151
|
-
id: string;
|
|
152
|
-
title: string;
|
|
153
|
-
description: string;
|
|
154
|
-
status: 'queued' | 'doing' | 'done' | string;
|
|
155
|
-
tags: string[];
|
|
156
|
-
priority: 'low' | 'normal' | 'high' | string;
|
|
157
|
-
createdAt: string;
|
|
158
|
-
updatedAt: string;
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
export type Snapshot = {
|
|
162
|
-
overview: Overview;
|
|
163
|
-
agents: Agent[];
|
|
164
|
-
plans: Plan[];
|
|
165
|
-
projects: Project[];
|
|
166
|
-
config: ConfigResponse;
|
|
167
|
-
settings: SettingsResponse;
|
|
168
|
-
tasks: Task[];
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
export type WsStatus = 'connecting' | 'connected' | 'disconnected';
|
|
172
|
-
|
|
173
|
-
export type WsMessage =
|
|
174
|
-
| { type: 'snapshot'; ts: number; data: Snapshot }
|
|
175
|
-
| { type: 'change'; event: string; path: string; ts: number }
|
|
176
|
-
| { type: 'tasks:change'; task: Task }
|
|
177
|
-
| { type: 'tasks:delete'; id: string }
|
|
178
|
-
| { type: 'settings:change'; settings: Settings }
|
|
179
|
-
| { type: 'pong'; ts: number }
|
|
180
|
-
| { type: 'ping' }
|
|
181
|
-
| { type: 'refresh' };
|
|
182
|
-
|
|
183
|
-
/** Resolve a theme to the actual key applied to <html data-theme="..."> */
|
|
184
|
-
export function applyTheme(theme: ThemeName): 'dark' | 'light' {
|
|
185
|
-
const resolved: 'dark' | 'light' =
|
|
186
|
-
theme === 'system'
|
|
187
|
-
? typeof window !== 'undefined' &&
|
|
188
|
-
window.matchMedia('(prefers-color-scheme: light)').matches
|
|
189
|
-
? 'light'
|
|
190
|
-
: 'dark'
|
|
191
|
-
: theme;
|
|
192
|
-
if (typeof document !== 'undefined') {
|
|
193
|
-
if (resolved === 'light') {
|
|
194
|
-
document.documentElement.setAttribute('data-theme', 'light');
|
|
195
|
-
} else {
|
|
196
|
-
document.documentElement.removeAttribute('data-theme');
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
return resolved;
|
|
200
|
-
}
|
package/src/lib/utils.ts
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
// src/lib/utils.ts — tiny shared helpers.
|
|
2
|
-
|
|
3
|
-
export function cn(...parts: (string | false | null | undefined)[]): string {
|
|
4
|
-
return parts.filter(Boolean).join(' ');
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
/** Compact relative time. */
|
|
8
|
-
export function formatRelative(ts: number | string | Date | undefined | null): string {
|
|
9
|
-
if (!ts && ts !== 0) return '';
|
|
10
|
-
const t = typeof ts === 'number' ? ts : new Date(ts).getTime();
|
|
11
|
-
if (Number.isNaN(t)) return '';
|
|
12
|
-
const diff = Date.now() - t;
|
|
13
|
-
if (diff < 0) return 'just now';
|
|
14
|
-
if (diff < 60_000) return 'just now';
|
|
15
|
-
if (diff < 3_600_000) return `${Math.floor(diff / 60_000)}m ago`;
|
|
16
|
-
if (diff < 86_400_000) return `${Math.floor(diff / 3_600_000)}h ago`;
|
|
17
|
-
if (diff < 7 * 86_400_000) return `${Math.floor(diff / 86_400_000)}d ago`;
|
|
18
|
-
return new Date(t).toLocaleDateString();
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/** Full localized timestamp. */
|
|
22
|
-
export function formatTime(ts: number | string | Date | undefined | null): string {
|
|
23
|
-
if (!ts) return '';
|
|
24
|
-
const d = typeof ts === 'number' ? new Date(ts) : new Date(ts);
|
|
25
|
-
if (Number.isNaN(d.getTime())) return '';
|
|
26
|
-
return d.toLocaleString();
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/** Trailing-suffix truncation. */
|
|
30
|
-
export function truncate(s: string | undefined | null, max = 160): string {
|
|
31
|
-
if (!s) return '';
|
|
32
|
-
return s.length > max ? s.slice(0, max) + '…' : s;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/** Debounce. */
|
|
36
|
-
export function debounce<T extends (...args: never[]) => void>(
|
|
37
|
-
fn: T,
|
|
38
|
-
ms = 200,
|
|
39
|
-
): (...args: Parameters<T>) => void {
|
|
40
|
-
let timer: ReturnType<typeof setTimeout> | null = null;
|
|
41
|
-
return (...args: Parameters<T>) => {
|
|
42
|
-
if (timer) clearTimeout(timer);
|
|
43
|
-
timer = setTimeout(() => fn(...args), ms);
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/** Color helpers (status badges). */
|
|
48
|
-
export const priorityColors: Record<string, string> = {
|
|
49
|
-
low: 'var(--text-dim)',
|
|
50
|
-
normal: 'var(--info)',
|
|
51
|
-
high: 'var(--error)',
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
export const statusBadgeKind = (
|
|
55
|
-
status: string,
|
|
56
|
-
): 'success' | 'warning' | 'error' | 'info' | 'accent' | '' => {
|
|
57
|
-
switch (status) {
|
|
58
|
-
case 'approved':
|
|
59
|
-
case 'done':
|
|
60
|
-
return 'success';
|
|
61
|
-
case 'draft':
|
|
62
|
-
case 'queued':
|
|
63
|
-
return '';
|
|
64
|
-
case 'in-progress':
|
|
65
|
-
case 'doing':
|
|
66
|
-
return 'info';
|
|
67
|
-
case 'rejected':
|
|
68
|
-
return 'error';
|
|
69
|
-
default:
|
|
70
|
-
return 'accent';
|
|
71
|
-
}
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
/** Cheap hash for change-detection on text (e.g. raw JSON). */
|
|
75
|
-
export function hashText(s: string): string {
|
|
76
|
-
let h = 5381;
|
|
77
|
-
for (let i = 0; i < s.length; i++) h = ((h << 5) + h) ^ s.charCodeAt(i);
|
|
78
|
-
return (h >>> 0).toString(36);
|
|
79
|
-
}
|