@polderlabs/bizar 4.5.2 → 4.7.1
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/bizar-dash/dist/assets/main-DHZmbnxQ.js +361 -0
- package/bizar-dash/dist/assets/main-DHZmbnxQ.js.map +1 -0
- package/bizar-dash/dist/assets/main-DX_Jh8Wc.css +1 -0
- package/bizar-dash/dist/assets/{mobile-lbH6szyX.js → mobile-BK8-ythT.js} +18 -18
- package/bizar-dash/dist/assets/mobile-BK8-ythT.js.map +1 -0
- package/bizar-dash/dist/assets/{mobile-BRhoDOUz.js → mobile-Chvf9u_B.js} +1 -1
- package/bizar-dash/dist/assets/{mobile-BRhoDOUz.js.map → mobile-Chvf9u_B.js.map} +1 -1
- package/bizar-dash/dist/index.html +3 -3
- package/bizar-dash/dist/mobile.html +2 -2
- package/bizar-dash/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/bizar-dash/skills/publishing/SKILL.md +146 -0
- package/bizar-dash/src/server/api.mjs +8 -0
- package/bizar-dash/src/server/backup-store.mjs +525 -0
- package/bizar-dash/src/server/digest-store.mjs +558 -0
- package/bizar-dash/src/server/lib/rate-limit.mjs +122 -0
- package/bizar-dash/src/server/logger.mjs +71 -0
- package/bizar-dash/src/server/memory-lightrag.mjs +45 -6
- package/bizar-dash/src/server/metrics.mjs +193 -0
- package/bizar-dash/src/server/routes/backup.mjs +112 -0
- package/bizar-dash/src/server/routes/chat.mjs +20 -3
- package/bizar-dash/src/server/routes/digests.mjs +82 -0
- package/bizar-dash/src/server/routes/lightrag.mjs +3 -2
- package/bizar-dash/src/server/routes/memory.mjs +5 -4
- package/bizar-dash/src/server/routes/misc.mjs +2 -1
- package/bizar-dash/src/server/routes/overview.mjs +2 -1
- package/bizar-dash/src/server/routes-v2/events.mjs +14 -0
- package/bizar-dash/src/server/schedules-runner.mjs +126 -0
- package/bizar-dash/src/server/server.mjs +79 -0
- package/bizar-dash/src/web/App.tsx +8 -1
- package/bizar-dash/src/web/components/BackupRestore.tsx +330 -0
- package/bizar-dash/src/web/components/SearchModal.tsx +6 -3
- package/bizar-dash/src/web/components/VirtualList.tsx +53 -0
- package/bizar-dash/src/web/components/chat/ChatThread.tsx +17 -11
- package/bizar-dash/src/web/components/chat/Composer.tsx +2 -0
- package/bizar-dash/src/web/hooks/useI18n.ts +13 -0
- package/bizar-dash/src/web/lib/i18n.ts +25 -0
- package/bizar-dash/src/web/locales/en.json +52 -0
- package/bizar-dash/src/web/main.tsx +5 -0
- package/bizar-dash/src/web/styles/main.css +70 -8
- package/bizar-dash/src/web/views/Activity.tsx +35 -18
- package/bizar-dash/src/web/views/Agents.tsx +57 -42
- package/bizar-dash/src/web/views/Artifacts.tsx +38 -25
- package/bizar-dash/src/web/views/Chat.tsx +8 -0
- package/bizar-dash/src/web/views/History.tsx +94 -76
- package/bizar-dash/src/web/views/MiniMaxUsage.tsx +21 -4
- package/bizar-dash/src/web/views/Mods.tsx +30 -17
- package/bizar-dash/src/web/views/Overview.tsx +19 -9
- package/bizar-dash/src/web/views/Providers.tsx +16 -16
- package/bizar-dash/src/web/views/Schedules.tsx +33 -15
- package/bizar-dash/src/web/views/Settings.tsx +97 -1745
- package/bizar-dash/src/web/views/Skills.tsx +4 -1
- package/bizar-dash/src/web/views/Tasks.tsx +11 -2
- package/bizar-dash/src/web/views/memory/ConfigPanel.tsx +8 -2
- package/bizar-dash/src/web/views/memory/LightragPanel.tsx +3 -0
- package/bizar-dash/src/web/views/memory/ObsidianPanel.tsx +12 -4
- package/bizar-dash/src/web/views/memory/SemanticSearchPanel.tsx +3 -0
- package/bizar-dash/src/web/views/settings/ActivitySection.tsx +205 -0
- package/bizar-dash/src/web/views/settings/AgentSection.tsx +294 -0
- package/bizar-dash/src/web/views/settings/AuthSection.tsx +159 -0
- package/bizar-dash/src/web/views/settings/BackupSection.tsx +16 -0
- package/bizar-dash/src/web/views/settings/EnvVarsSection.tsx +16 -0
- package/bizar-dash/src/web/views/settings/GeneralSection.tsx +105 -0
- package/bizar-dash/src/web/views/settings/HeadroomSection.tsx +39 -0
- package/bizar-dash/src/web/views/settings/MemorySection.tsx +16 -0
- package/bizar-dash/src/web/views/settings/NetworkSection.tsx +87 -0
- package/bizar-dash/src/web/views/settings/NotificationsSection.tsx +34 -0
- package/bizar-dash/src/web/views/settings/ProvidersSection.tsx +16 -0
- package/bizar-dash/src/web/views/settings/SkillsSection.tsx +16 -0
- package/bizar-dash/src/web/views/settings/SystemLlmSection.tsx +81 -0
- package/bizar-dash/src/web/views/settings/ThemeSection.tsx +168 -0
- package/bizar-dash/src/web/views/settings/UpdatesSection.tsx +256 -0
- package/bizar-dash/tests/a11y.test.tsx +206 -0
- package/bizar-dash/tests/backup-restore.test.mjs +217 -0
- package/bizar-dash/tests/backup-restore.test.tsx +123 -0
- package/bizar-dash/tests/backup-store.test.mjs +300 -0
- package/bizar-dash/tests/cli-bugfixes.test.mjs +4 -4
- package/bizar-dash/tests/cli-error-visibility.test.mjs +153 -0
- package/bizar-dash/tests/cli-refactor.test.mjs +184 -0
- package/bizar-dash/tests/components/Button.test.tsx +41 -0
- package/bizar-dash/tests/components/Card.test.tsx +42 -0
- package/bizar-dash/tests/components/Modal.test.tsx +104 -0
- package/bizar-dash/tests/components/Spinner.test.tsx +32 -0
- package/bizar-dash/tests/components/StatusBadge.test.tsx +35 -0
- package/bizar-dash/tests/components/Toast.test.tsx +108 -0
- package/bizar-dash/tests/digest-generation.test.mjs +191 -0
- package/bizar-dash/tests/digest-store.test.mjs +264 -0
- package/bizar-dash/tests/hooks/useModal.test.tsx +84 -0
- package/bizar-dash/tests/hooks/useToast.test.tsx +50 -0
- package/bizar-dash/tests/lib/i18n.test.ts +46 -0
- package/bizar-dash/tests/lib/utils.test.ts +194 -0
- package/bizar-dash/tests/logger.test.mjs +207 -0
- package/bizar-dash/tests/metrics.test.mjs +183 -0
- package/bizar-dash/tests/rate-limit.test.mjs +298 -0
- package/bizar-dash/tests/server-bugfixes.test.mjs +2 -2
- package/bizar-dash/tests/setup.ts +7 -0
- package/bizar-dash/vitest.config.ts +13 -0
- package/cli/artifact-cli.mjs +605 -0
- package/cli/artifact-render.mjs +621 -0
- package/cli/artifact-server.mjs +847 -0
- package/cli/artifact.mjs +38 -2096
- package/cli/bg.mjs +5 -13
- package/cli/bin.mjs +221 -1350
- package/cli/commands/artifact.mjs +20 -0
- package/cli/commands/dash.mjs +160 -0
- package/cli/commands/headroom.mjs +204 -0
- package/cli/commands/install.mjs +169 -0
- package/cli/commands/memory.mjs +25 -0
- package/cli/commands/minimax.mjs +285 -0
- package/cli/commands/mod.mjs +185 -0
- package/cli/commands/service.mjs +65 -0
- package/cli/commands/usage.mjs +109 -0
- package/cli/commands/util.mjs +459 -0
- package/cli/digest.mjs +149 -0
- package/cli/doctor.mjs +1 -13
- package/cli/provision.mjs +2 -13
- package/cli/service-controller.mjs +1 -11
- package/cli/service.mjs +1 -11
- package/cli/utils.mjs +41 -1
- package/package.json +6 -1
- package/bizar-dash/dist/assets/main-B4OfGAwz.js +0 -361
- package/bizar-dash/dist/assets/main-B4OfGAwz.js.map +0 -1
- package/bizar-dash/dist/assets/main-DAlLdW8I.css +0 -1
- package/bizar-dash/dist/assets/mobile-lbH6szyX.js.map +0 -1
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// src/web/components/VirtualList.tsx — hand-rolled virtual scrolling (no external deps).
|
|
2
|
+
import React, { useState, useRef, useEffect, useMemo } from 'react';
|
|
3
|
+
|
|
4
|
+
export function VirtualList<T>({
|
|
5
|
+
items,
|
|
6
|
+
renderItem,
|
|
7
|
+
itemHeight = 80,
|
|
8
|
+
height = 400,
|
|
9
|
+
overscan = 5,
|
|
10
|
+
className,
|
|
11
|
+
}: {
|
|
12
|
+
items: T[];
|
|
13
|
+
renderItem: (item: T, index: number) => React.ReactNode;
|
|
14
|
+
itemHeight?: number;
|
|
15
|
+
height?: number;
|
|
16
|
+
overscan?: number;
|
|
17
|
+
className?: string;
|
|
18
|
+
}) {
|
|
19
|
+
const [scrollTop, setScrollTop] = useState(0);
|
|
20
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
21
|
+
|
|
22
|
+
const visibleRange = useMemo(() => {
|
|
23
|
+
const start = Math.max(0, Math.floor(scrollTop / itemHeight) - overscan);
|
|
24
|
+
const end = Math.min(
|
|
25
|
+
items.length,
|
|
26
|
+
Math.ceil((scrollTop + height) / itemHeight) + overscan,
|
|
27
|
+
);
|
|
28
|
+
return { start, end };
|
|
29
|
+
}, [scrollTop, itemHeight, height, overscan, items.length]);
|
|
30
|
+
|
|
31
|
+
const totalHeight = items.length * itemHeight;
|
|
32
|
+
const offsetY = visibleRange.start * itemHeight;
|
|
33
|
+
const visibleItems = items.slice(visibleRange.start, visibleRange.end);
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<div
|
|
37
|
+
ref={containerRef}
|
|
38
|
+
onScroll={(e) => setScrollTop(e.currentTarget.scrollTop)}
|
|
39
|
+
style={{ height, overflowY: 'auto' }}
|
|
40
|
+
className={className}
|
|
41
|
+
>
|
|
42
|
+
<div style={{ height: totalHeight, position: 'relative' }}>
|
|
43
|
+
<div style={{ transform: `translateY(${offsetY}px)` }}>
|
|
44
|
+
{visibleItems.map((item, i) => (
|
|
45
|
+
<div key={visibleRange.start + i} style={{ height: itemHeight }}>
|
|
46
|
+
{renderItem(item, visibleRange.start + i)}
|
|
47
|
+
</div>
|
|
48
|
+
))}
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
@@ -8,6 +8,7 @@ import { useEffect, useRef } from 'react';
|
|
|
8
8
|
import { LoadingSkeleton } from './LoadingSkeleton';
|
|
9
9
|
import { WelcomeScreen } from './WelcomeScreen';
|
|
10
10
|
import { MessageBubble } from './MessageBubble';
|
|
11
|
+
import { VirtualList } from '../VirtualList';
|
|
11
12
|
|
|
12
13
|
interface Message {
|
|
13
14
|
role?: string;
|
|
@@ -105,17 +106,22 @@ export function ChatThread({
|
|
|
105
106
|
|
|
106
107
|
return (
|
|
107
108
|
<div className="chat-thread legacy" ref={innerRef}>
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
109
|
+
<VirtualList
|
|
110
|
+
items={messages}
|
|
111
|
+
itemHeight={120}
|
|
112
|
+
height={800}
|
|
113
|
+
className="chat-thread-virtual"
|
|
114
|
+
renderItem={(m, i) => (
|
|
115
|
+
<MessageBubble
|
|
116
|
+
message={m}
|
|
117
|
+
pinned={pinned.has(i)}
|
|
118
|
+
onCopy={() => onCopy(m)}
|
|
119
|
+
onDelete={() => onDelete(i)}
|
|
120
|
+
onTogglePin={() => onTogglePin(i)}
|
|
121
|
+
onRegenerate={() => onRegenerate(String(m.ts ?? i))}
|
|
122
|
+
/>
|
|
123
|
+
)}
|
|
124
|
+
/>
|
|
119
125
|
</div>
|
|
120
126
|
);
|
|
121
127
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// src/web/hooks/useI18n.ts — React hook for translations.
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { t, setLocale, getLocale } from '../lib/i18n';
|
|
4
|
+
|
|
5
|
+
export function useI18n() {
|
|
6
|
+
const [, force] = useState(0);
|
|
7
|
+
return {
|
|
8
|
+
t,
|
|
9
|
+
setLocale,
|
|
10
|
+
locale: getLocale(),
|
|
11
|
+
rerender: () => force((n) => n + 1),
|
|
12
|
+
};
|
|
13
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// src/web/lib/i18n.ts — tiny key-based translation system.
|
|
2
|
+
export type Locale = 'en' | string;
|
|
3
|
+
export type Translations = Record<string, string>;
|
|
4
|
+
|
|
5
|
+
let currentLocale: Locale = 'en';
|
|
6
|
+
const translations: Record<Locale, Translations> = { en: {} };
|
|
7
|
+
|
|
8
|
+
export function setLocale(locale: Locale, dict?: Translations) {
|
|
9
|
+
currentLocale = locale;
|
|
10
|
+
if (dict) translations[locale] = dict;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function t(key: string, vars?: Record<string, string | number>): string {
|
|
14
|
+
let s = translations[currentLocale]?.[key] || translations['en']?.[key] || key;
|
|
15
|
+
if (vars) {
|
|
16
|
+
for (const [k, v] of Object.entries(vars)) {
|
|
17
|
+
s = s.replace(new RegExp(`\\{${k}\\}`, 'g'), String(v));
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return s;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function getLocale(): Locale {
|
|
24
|
+
return currentLocale;
|
|
25
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"settings.title": "Settings",
|
|
3
|
+
"settings.general": "General",
|
|
4
|
+
"settings.envVars": "Environment Variables",
|
|
5
|
+
"settings.providers": "Providers",
|
|
6
|
+
"settings.theme": "Theme",
|
|
7
|
+
"settings.themeMode": "Mode",
|
|
8
|
+
"settings.themeAccent": "Accent",
|
|
9
|
+
"settings.layout": "Layout",
|
|
10
|
+
"settings.notifications": "Notifications",
|
|
11
|
+
"settings.activity": "Activity",
|
|
12
|
+
"settings.save": "Save",
|
|
13
|
+
"settings.reset": "Reset",
|
|
14
|
+
"settings.reload": "Reload",
|
|
15
|
+
"settings.dashboard": "Dashboard",
|
|
16
|
+
"settings.background": "Background Agents",
|
|
17
|
+
"settings.systemLlm": "System LLM API",
|
|
18
|
+
"settings.headroom": "Headroom",
|
|
19
|
+
"settings.updates": "Updates",
|
|
20
|
+
"settings.about": "About",
|
|
21
|
+
"settings.auth": "Authentication",
|
|
22
|
+
"settings.agents": "Agent Behavior",
|
|
23
|
+
"settings.service": "Service",
|
|
24
|
+
"settings.tailscale": "Tailscale Serve",
|
|
25
|
+
"chat.title": "Chat",
|
|
26
|
+
"chat.newSession": "New session",
|
|
27
|
+
"chat.sendMessage": "Send message",
|
|
28
|
+
"chat.placeholder": "Type your message…",
|
|
29
|
+
"chat.export": "Export transcript",
|
|
30
|
+
"chat.rename": "Rename session",
|
|
31
|
+
"chat.delete": "Delete session",
|
|
32
|
+
"memory.title": "Memory",
|
|
33
|
+
"memory.overview": "Overview",
|
|
34
|
+
"memory.lightrag": "LightRAG",
|
|
35
|
+
"memory.obsidian": "Obsidian Vault",
|
|
36
|
+
"memory.gitSync": "Git Sync",
|
|
37
|
+
"memory.semantic": "Semantic Search",
|
|
38
|
+
"memory.config": "Config",
|
|
39
|
+
"memory.refresh": "Refresh memory",
|
|
40
|
+
"tasks.title": "Tasks",
|
|
41
|
+
"tasks.create": "Create task",
|
|
42
|
+
"tasks.complete": "Mark complete",
|
|
43
|
+
"tasks.delete": "Delete task",
|
|
44
|
+
"activity.title": "Activity",
|
|
45
|
+
"activity.live": "Live",
|
|
46
|
+
"activity.paused": "Paused",
|
|
47
|
+
"activity.refresh": "Refresh",
|
|
48
|
+
"history.title": "History",
|
|
49
|
+
"history.export": "Export",
|
|
50
|
+
"overview.title": "Overview",
|
|
51
|
+
"overview.submit": "Submit to Odin"
|
|
52
|
+
}
|
|
@@ -3,6 +3,11 @@ import { StrictMode, useEffect, useState } from 'react';
|
|
|
3
3
|
import { createRoot } from 'react-dom/client';
|
|
4
4
|
import { App } from './App';
|
|
5
5
|
import { MobileApp } from './MobileApp';
|
|
6
|
+
import en from './locales/en.json';
|
|
7
|
+
import { setLocale } from './lib/i18n';
|
|
8
|
+
|
|
9
|
+
// Initialize i18n
|
|
10
|
+
setLocale('en', en);
|
|
6
11
|
// Import mobile CSS so the MobileApp renders with styles when served
|
|
7
12
|
// from index.html (e.g. when the auto-redirect to /m is bypassed).
|
|
8
13
|
import './styles/main.css';
|
|
@@ -101,6 +101,41 @@ pre,
|
|
|
101
101
|
border: 0;
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
/* v4.8.0 — WCAG 2.2 AA: skip-to-main-content link. Visually hidden
|
|
105
|
+
until focused (keyboard tab), then pops in at the top-left so
|
|
106
|
+
users can jump past the topbar + sidebar into the active view.
|
|
107
|
+
Anchors must be focusable by default; we just hide them visually. */
|
|
108
|
+
.skip-to-main {
|
|
109
|
+
position: absolute;
|
|
110
|
+
top: -100px;
|
|
111
|
+
left: var(--space-2);
|
|
112
|
+
z-index: 9999;
|
|
113
|
+
padding: 10px 14px;
|
|
114
|
+
background: var(--accent);
|
|
115
|
+
color: #ffffff;
|
|
116
|
+
border-radius: var(--radius-sm);
|
|
117
|
+
font-size: 13px;
|
|
118
|
+
font-weight: 600;
|
|
119
|
+
text-decoration: none;
|
|
120
|
+
box-shadow: var(--shadow-2);
|
|
121
|
+
transition: top 120ms var(--ease);
|
|
122
|
+
}
|
|
123
|
+
.skip-to-main:focus,
|
|
124
|
+
.skip-to-main:focus-visible {
|
|
125
|
+
top: var(--space-2);
|
|
126
|
+
outline: 2px solid var(--accent-2);
|
|
127
|
+
outline-offset: 2px;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/* v4.8.0 — Make <main> programmatically focusable for the skip link
|
|
131
|
+
without a visible focus ring (it has no interactive content). */
|
|
132
|
+
#main-content:focus {
|
|
133
|
+
outline: none;
|
|
134
|
+
}
|
|
135
|
+
#main-content:focus-visible {
|
|
136
|
+
outline: none;
|
|
137
|
+
}
|
|
138
|
+
|
|
104
139
|
/* ─── Theme tokens ─────────────────────────────────────────────────── */
|
|
105
140
|
|
|
106
141
|
:root {
|
|
@@ -258,10 +293,10 @@ pre,
|
|
|
258
293
|
--accent-glow: rgba(124, 58, 237, 0.18);
|
|
259
294
|
--accent-soft: rgba(124, 58, 237, 0.06);
|
|
260
295
|
|
|
261
|
-
--success: #
|
|
262
|
-
--warning: #
|
|
263
|
-
--error: #
|
|
264
|
-
--info: #
|
|
296
|
+
--success: #047857;
|
|
297
|
+
--warning: #b45309;
|
|
298
|
+
--error: #b91c1c;
|
|
299
|
+
--info: #1d4ed8;
|
|
265
300
|
--success-soft: rgba(5, 150, 105, 0.12);
|
|
266
301
|
--error-soft: rgba(220, 38, 38, 0.08);
|
|
267
302
|
--warning-soft: rgba(217, 119, 6, 0.12);
|
|
@@ -281,10 +316,12 @@ pre,
|
|
|
281
316
|
/* v3.21.x — Surface and semantic tokens (light theme overrides). */
|
|
282
317
|
--surface-1: color-mix(in oklab, var(--bg) 92%, var(--text) 8%);
|
|
283
318
|
--surface-2: color-mix(in oklab, var(--bg) 84%, var(--text) 16%);
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
--
|
|
287
|
-
--
|
|
319
|
+
/* v4.8.0 — Darken light-theme status colors so they pass WCAG AA (4.5:1)
|
|
320
|
+
against the light surface (#f7f8fa). */
|
|
321
|
+
--success: oklch(0.48 0.14 145);
|
|
322
|
+
--warning: oklch(0.55 0.15 70);
|
|
323
|
+
--error: oklch(0.48 0.20 25);
|
|
324
|
+
--info: oklch(0.48 0.16 235);
|
|
288
325
|
--bg-1: #f4f4f5;
|
|
289
326
|
--bg-2: #e4e4e7;
|
|
290
327
|
|
|
@@ -945,6 +982,17 @@ pre,
|
|
|
945
982
|
box-shadow: 0 0 0 3px var(--accent-bg);
|
|
946
983
|
}
|
|
947
984
|
|
|
985
|
+
/* v4.8.0 — Placeholder color must pass AA (4.5:1) against input
|
|
986
|
+
backgrounds on both themes. Default browser placeholder is too
|
|
987
|
+
light on dark and too dark on light, so we pin to --text-dim
|
|
988
|
+
which is computed to ~9:1 (dark) / ~6:1 (light). */
|
|
989
|
+
.input::placeholder,
|
|
990
|
+
.textarea::placeholder,
|
|
991
|
+
.select::placeholder {
|
|
992
|
+
color: var(--text-dim);
|
|
993
|
+
opacity: 1;
|
|
994
|
+
}
|
|
995
|
+
|
|
948
996
|
.textarea.invalid {
|
|
949
997
|
border-color: var(--error);
|
|
950
998
|
}
|
|
@@ -5004,6 +5052,12 @@ kbd {
|
|
|
5004
5052
|
cursor: pointer;
|
|
5005
5053
|
user-select: none;
|
|
5006
5054
|
transition: background var(--motion-fast) var(--ease);
|
|
5055
|
+
background: transparent;
|
|
5056
|
+
border: 0;
|
|
5057
|
+
color: inherit;
|
|
5058
|
+
text-align: left;
|
|
5059
|
+
width: 100%;
|
|
5060
|
+
font: inherit;
|
|
5007
5061
|
}
|
|
5008
5062
|
|
|
5009
5063
|
.mods-registry-head:hover {
|
|
@@ -5214,6 +5268,14 @@ kbd {
|
|
|
5214
5268
|
cursor: pointer;
|
|
5215
5269
|
user-select: none;
|
|
5216
5270
|
padding: 4px 0;
|
|
5271
|
+
background: transparent;
|
|
5272
|
+
border: 0;
|
|
5273
|
+
color: inherit;
|
|
5274
|
+
text-align: left;
|
|
5275
|
+
font: inherit;
|
|
5276
|
+
}
|
|
5277
|
+
button.mod-instructions-head {
|
|
5278
|
+
width: 100%;
|
|
5217
5279
|
}
|
|
5218
5280
|
.mod-instructions-head:hover { color: var(--accent); }
|
|
5219
5281
|
.mod-instructions-spacer { flex: 1; }
|
|
@@ -35,6 +35,7 @@ import { Button } from '../components/Button';
|
|
|
35
35
|
import { Card, CardTitle } from '../components/Card';
|
|
36
36
|
import { EmptyState } from '../components/EmptyState';
|
|
37
37
|
import { Spinner } from '../components/Spinner';
|
|
38
|
+
import { VirtualList } from '../components/VirtualList';
|
|
38
39
|
import { useToast } from '../components/Toast';
|
|
39
40
|
import { useModal } from '../components/Modal';
|
|
40
41
|
import { api } from '../lib/api';
|
|
@@ -776,23 +777,29 @@ export function Activity({ snapshot, refreshSnapshot }: Props) {
|
|
|
776
777
|
<p>No events yet.</p>
|
|
777
778
|
</div>
|
|
778
779
|
) : (
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
780
|
+
<VirtualList
|
|
781
|
+
items={[...events].reverse()}
|
|
782
|
+
itemHeight={40}
|
|
783
|
+
height={400}
|
|
784
|
+
className="tl-stream-virtual"
|
|
785
|
+
renderItem={(ev) => {
|
|
786
|
+
const Icon =
|
|
787
|
+
ev.author ? Bot : ev.kind === 'task' ? CheckSquare : ev.kind === 'bg' ? Cpu : ActivityIcon;
|
|
788
|
+
return (
|
|
789
|
+
<div className="tl-stream-event">
|
|
790
|
+
<span className="tl-stream-event-time">
|
|
791
|
+
{new Date(ev.ts).toLocaleTimeString('en-GB', { hour12: false })}
|
|
792
|
+
</span>
|
|
793
|
+
<span className="tl-stream-event-icon" style={{ color: statusColor(ev.kind) }}>
|
|
794
|
+
<Icon size={12} />
|
|
795
|
+
</span>
|
|
796
|
+
<span className="tl-stream-event-text">
|
|
797
|
+
{ev.author || ev.text || ev.kind}
|
|
798
|
+
</span>
|
|
799
|
+
</div>
|
|
800
|
+
);
|
|
801
|
+
}}
|
|
802
|
+
/>
|
|
796
803
|
)}
|
|
797
804
|
</div>
|
|
798
805
|
</aside>
|
|
@@ -1102,7 +1109,9 @@ export function Activity({ snapshot, refreshSnapshot }: Props) {
|
|
|
1102
1109
|
</Button>
|
|
1103
1110
|
</div>
|
|
1104
1111
|
<div className="tl-form-row">
|
|
1112
|
+
<label htmlFor="bg-message-input" className="sr-only">Send a message to this bg session</label>
|
|
1105
1113
|
<input
|
|
1114
|
+
id="bg-message-input"
|
|
1106
1115
|
className="input"
|
|
1107
1116
|
placeholder="Send a message to this bg session…"
|
|
1108
1117
|
value={bgMessage}
|
|
@@ -1110,6 +1119,7 @@ export function Activity({ snapshot, refreshSnapshot }: Props) {
|
|
|
1110
1119
|
onKeyDown={(e) => {
|
|
1111
1120
|
if (e.key === 'Enter') onSendBgMessage();
|
|
1112
1121
|
}}
|
|
1122
|
+
aria-label="Send a message to this bg session"
|
|
1113
1123
|
/>
|
|
1114
1124
|
<Button variant="primary" size="sm" disabled={!bgMessage.trim()} onClick={onSendBgMessage}>
|
|
1115
1125
|
<Send size={12} /> Send
|
|
@@ -1120,18 +1130,22 @@ export function Activity({ snapshot, refreshSnapshot }: Props) {
|
|
|
1120
1130
|
|
|
1121
1131
|
{selectedItem.kind !== 'bg' && (
|
|
1122
1132
|
<div className="tl-detail-create">
|
|
1123
|
-
<
|
|
1133
|
+
<label className="field-label" htmlFor="followup-task-title">Create follow-up task</label>
|
|
1124
1134
|
<input
|
|
1135
|
+
id="followup-task-title"
|
|
1125
1136
|
className="input"
|
|
1126
1137
|
placeholder="Task title"
|
|
1127
1138
|
value={taskTitle}
|
|
1128
1139
|
onChange={(e) => setTaskTitle(e.target.value)}
|
|
1129
1140
|
/>
|
|
1130
1141
|
<div className="tl-form-row">
|
|
1142
|
+
<label htmlFor="followup-task-priority" className="sr-only">Task priority</label>
|
|
1131
1143
|
<select
|
|
1144
|
+
id="followup-task-priority"
|
|
1132
1145
|
className="select"
|
|
1133
1146
|
value={taskPriority}
|
|
1134
1147
|
onChange={(e) => setTaskPriority(e.target.value)}
|
|
1148
|
+
aria-label="Task priority"
|
|
1135
1149
|
>
|
|
1136
1150
|
<option value="low">Low</option>
|
|
1137
1151
|
<option value="normal">Normal</option>
|
|
@@ -1173,7 +1187,9 @@ export function Activity({ snapshot, refreshSnapshot }: Props) {
|
|
|
1173
1187
|
))}
|
|
1174
1188
|
</ul>
|
|
1175
1189
|
<div className="comment-input-row">
|
|
1190
|
+
<label htmlFor="comment-input" className="sr-only">Add a comment</label>
|
|
1176
1191
|
<input
|
|
1192
|
+
id="comment-input"
|
|
1177
1193
|
className="input"
|
|
1178
1194
|
placeholder="Add a comment…"
|
|
1179
1195
|
value={commentText}
|
|
@@ -1181,6 +1197,7 @@ export function Activity({ snapshot, refreshSnapshot }: Props) {
|
|
|
1181
1197
|
onKeyDown={(e) => {
|
|
1182
1198
|
if (e.key === 'Enter' && !e.shiftKey) onAddComment();
|
|
1183
1199
|
}}
|
|
1200
|
+
aria-label="Add a comment"
|
|
1184
1201
|
/>
|
|
1185
1202
|
<Button
|
|
1186
1203
|
variant="secondary"
|
|
@@ -144,16 +144,18 @@ export function Agents({ snapshot, refreshSnapshot }: Props) {
|
|
|
144
144
|
width: 640,
|
|
145
145
|
children: (
|
|
146
146
|
<div className="agent-form">
|
|
147
|
-
<label className="field-label">Name (a-z, 0-9, dashes)</label>
|
|
147
|
+
<label className="field-label" htmlFor="agent-new-name">Name (a-z, 0-9, dashes)</label>
|
|
148
148
|
<input
|
|
149
|
+
id="agent-new-name"
|
|
149
150
|
ref={(el) => (nameEl = el)}
|
|
150
151
|
className="input"
|
|
151
152
|
type="text"
|
|
152
153
|
placeholder="my-agent"
|
|
153
154
|
autoFocus
|
|
154
155
|
/>
|
|
155
|
-
<label className="field-label">Description</label>
|
|
156
|
+
<label className="field-label" htmlFor="agent-new-desc">Description</label>
|
|
156
157
|
<input
|
|
158
|
+
id="agent-new-desc"
|
|
157
159
|
ref={(el) => { descEl = el; }}
|
|
158
160
|
className="input"
|
|
159
161
|
type="text"
|
|
@@ -161,8 +163,8 @@ export function Agents({ snapshot, refreshSnapshot }: Props) {
|
|
|
161
163
|
/>
|
|
162
164
|
<div className="task-form-row">
|
|
163
165
|
<div className="task-form-field">
|
|
164
|
-
<label className="field-label">Model</label>
|
|
165
|
-
<select ref={(el) => (modelEl = el)} className="select" defaultValue="">
|
|
166
|
+
<label className="field-label" htmlFor="agent-new-model">Model</label>
|
|
167
|
+
<select id="agent-new-model" ref={(el) => (modelEl = el)} className="select" defaultValue="">
|
|
166
168
|
<option value="">(provider default)</option>
|
|
167
169
|
{MODELS.map((m) => (
|
|
168
170
|
<option key={m} value={m}>{m}</option>
|
|
@@ -170,22 +172,22 @@ export function Agents({ snapshot, refreshSnapshot }: Props) {
|
|
|
170
172
|
</select>
|
|
171
173
|
</div>
|
|
172
174
|
<div className="task-form-field">
|
|
173
|
-
<label className="field-label">Mode</label>
|
|
174
|
-
<select ref={(el) => (modeEl = el)} className="select" defaultValue="subagent">
|
|
175
|
+
<label className="field-label" htmlFor="agent-new-mode">Mode</label>
|
|
176
|
+
<select id="agent-new-mode" ref={(el) => (modeEl = el)} className="select" defaultValue="subagent">
|
|
175
177
|
<option value="primary">primary</option>
|
|
176
178
|
<option value="subagent">subagent</option>
|
|
177
179
|
<option value="all">all</option>
|
|
178
180
|
</select>
|
|
179
181
|
</div>
|
|
180
182
|
<div className="task-form-field" style={{ flex: '0 0 80px' }}>
|
|
181
|
-
<label className="field-label">Color</label>
|
|
182
|
-
<input ref={(el) => (colorEl = el)} className="input" type="color" defaultValue="#8b5cf6" />
|
|
183
|
+
<label className="field-label" htmlFor="agent-new-color">Color</label>
|
|
184
|
+
<input id="agent-new-color" ref={(el) => (colorEl = el)} className="input" type="color" defaultValue="#8b5cf6" />
|
|
183
185
|
</div>
|
|
184
186
|
</div>
|
|
185
187
|
<div className="task-form-row">
|
|
186
188
|
<div className="task-form-field" style={{ flex: 1 }}>
|
|
187
|
-
<label className="field-label">Category</label>
|
|
188
|
-
<select ref={(el) => (categoryEl = el)} className="select" defaultValue="">
|
|
189
|
+
<label className="field-label" htmlFor="agent-new-category">Category</label>
|
|
190
|
+
<select id="agent-new-category" ref={(el) => (categoryEl = el)} className="select" defaultValue="">
|
|
189
191
|
<option value="">(none)</option>
|
|
190
192
|
{CATEGORIES.map((c) => (
|
|
191
193
|
<option key={c.id} value={c.id}>{c.label}</option>
|
|
@@ -193,21 +195,24 @@ export function Agents({ snapshot, refreshSnapshot }: Props) {
|
|
|
193
195
|
</select>
|
|
194
196
|
</div>
|
|
195
197
|
<div className="task-form-field" style={{ flex: 2 }}>
|
|
196
|
-
<label className="field-label">Tags (comma-separated)</label>
|
|
197
|
-
<input ref={(el) => (tagsEl = el)} className="input" type="text" placeholder="reasoning, code, planning" />
|
|
198
|
+
<label className="field-label" htmlFor="agent-new-tags">Tags (comma-separated)</label>
|
|
199
|
+
<input id="agent-new-tags" ref={(el) => (tagsEl = el)} className="input" type="text" placeholder="reasoning, code, planning" />
|
|
198
200
|
</div>
|
|
199
201
|
</div>
|
|
200
|
-
<
|
|
201
|
-
|
|
202
|
-
{
|
|
203
|
-
|
|
204
|
-
<
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
202
|
+
<fieldset>
|
|
203
|
+
<legend className="field-label" style={{ padding: 0 }}>Tools</legend>
|
|
204
|
+
<div ref={(el) => (toolsContainer = el)} className="agent-tools">
|
|
205
|
+
{TOOL_OPTIONS.map((t) => (
|
|
206
|
+
<label key={t} className="checkbox-row">
|
|
207
|
+
<input type="checkbox" value={t} aria-label={t} />
|
|
208
|
+
<span>{t}</span>
|
|
209
|
+
</label>
|
|
210
|
+
))}
|
|
211
|
+
</div>
|
|
212
|
+
</fieldset>
|
|
213
|
+
<label className="field-label" htmlFor="agent-new-prompt">System prompt</label>
|
|
210
214
|
<textarea
|
|
215
|
+
id="agent-new-prompt"
|
|
211
216
|
ref={(el) => (promptEl = el)}
|
|
212
217
|
className="textarea"
|
|
213
218
|
rows={6}
|
|
@@ -280,8 +285,9 @@ export function Agents({ snapshot, refreshSnapshot }: Props) {
|
|
|
280
285
|
<div className="muted">
|
|
281
286
|
File: <code>{full.path}</code>
|
|
282
287
|
</div>
|
|
283
|
-
<label className="field-label">Description</label>
|
|
288
|
+
<label className="field-label" htmlFor="agent-edit-desc">Description</label>
|
|
284
289
|
<input
|
|
290
|
+
id="agent-edit-desc"
|
|
285
291
|
ref={(el) => { descEl = el; }}
|
|
286
292
|
className="input"
|
|
287
293
|
type="text"
|
|
@@ -289,8 +295,8 @@ export function Agents({ snapshot, refreshSnapshot }: Props) {
|
|
|
289
295
|
/>
|
|
290
296
|
<div className="task-form-row">
|
|
291
297
|
<div className="task-form-field">
|
|
292
|
-
<label className="field-label">Model</label>
|
|
293
|
-
<select ref={(el) => (modelEl = el)} className="select" defaultValue={full.model}>
|
|
298
|
+
<label className="field-label" htmlFor="agent-edit-model">Model</label>
|
|
299
|
+
<select id="agent-edit-model" ref={(el) => (modelEl = el)} className="select" defaultValue={full.model}>
|
|
294
300
|
<option value="">(provider default)</option>
|
|
295
301
|
{MODELS.map((m) => (
|
|
296
302
|
<option key={m} value={m}>{m}</option>
|
|
@@ -298,22 +304,22 @@ export function Agents({ snapshot, refreshSnapshot }: Props) {
|
|
|
298
304
|
</select>
|
|
299
305
|
</div>
|
|
300
306
|
<div className="task-form-field">
|
|
301
|
-
<label className="field-label">Mode</label>
|
|
302
|
-
<select ref={(el) => (modeEl = el)} className="select" defaultValue={full.mode || 'subagent'}>
|
|
307
|
+
<label className="field-label" htmlFor="agent-edit-mode">Mode</label>
|
|
308
|
+
<select id="agent-edit-mode" ref={(el) => (modeEl = el)} className="select" defaultValue={full.mode || 'subagent'}>
|
|
303
309
|
<option value="primary">primary</option>
|
|
304
310
|
<option value="subagent">subagent</option>
|
|
305
311
|
<option value="all">all</option>
|
|
306
312
|
</select>
|
|
307
313
|
</div>
|
|
308
314
|
<div className="task-form-field" style={{ flex: '0 0 80px' }}>
|
|
309
|
-
<label className="field-label">Color</label>
|
|
310
|
-
<input ref={(el) => (colorEl = el)} className="input" type="color" defaultValue={full.color || '#8b5cf6'} />
|
|
315
|
+
<label className="field-label" htmlFor="agent-edit-color">Color</label>
|
|
316
|
+
<input id="agent-edit-color" ref={(el) => (colorEl = el)} className="input" type="color" defaultValue={full.color || '#8b5cf6'} />
|
|
311
317
|
</div>
|
|
312
318
|
</div>
|
|
313
319
|
<div className="task-form-row">
|
|
314
320
|
<div className="task-form-field" style={{ flex: 1 }}>
|
|
315
|
-
<label className="field-label">Category</label>
|
|
316
|
-
<select ref={(el) => (categoryEl = el)} className="select" defaultValue={full.category || ''}>
|
|
321
|
+
<label className="field-label" htmlFor="agent-edit-category">Category</label>
|
|
322
|
+
<select id="agent-edit-category" ref={(el) => (categoryEl = el)} className="select" defaultValue={full.category || ''}>
|
|
317
323
|
<option value="">(none)</option>
|
|
318
324
|
{CATEGORIES.map((c) => (
|
|
319
325
|
<option key={c.id} value={c.id}>{c.label}</option>
|
|
@@ -321,8 +327,9 @@ export function Agents({ snapshot, refreshSnapshot }: Props) {
|
|
|
321
327
|
</select>
|
|
322
328
|
</div>
|
|
323
329
|
<div className="task-form-field" style={{ flex: 2 }}>
|
|
324
|
-
<label className="field-label">Tags (comma-separated)</label>
|
|
330
|
+
<label className="field-label" htmlFor="agent-edit-tags">Tags (comma-separated)</label>
|
|
325
331
|
<input
|
|
332
|
+
id="agent-edit-tags"
|
|
326
333
|
ref={(el) => (tagsEl = el)}
|
|
327
334
|
className="input"
|
|
328
335
|
type="text"
|
|
@@ -330,17 +337,20 @@ export function Agents({ snapshot, refreshSnapshot }: Props) {
|
|
|
330
337
|
/>
|
|
331
338
|
</div>
|
|
332
339
|
</div>
|
|
333
|
-
<
|
|
334
|
-
|
|
335
|
-
{
|
|
336
|
-
|
|
337
|
-
<
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
340
|
+
<fieldset>
|
|
341
|
+
<legend className="field-label" style={{ padding: 0 }}>Tools</legend>
|
|
342
|
+
<div ref={(el) => (toolsContainer = el)} className="agent-tools">
|
|
343
|
+
{TOOL_OPTIONS.map((t) => (
|
|
344
|
+
<label key={t} className="checkbox-row">
|
|
345
|
+
<input type="checkbox" value={t} aria-label={t} defaultChecked={full.tools?.includes(t)} />
|
|
346
|
+
<span>{t}</span>
|
|
347
|
+
</label>
|
|
348
|
+
))}
|
|
349
|
+
</div>
|
|
350
|
+
</fieldset>
|
|
351
|
+
<label className="field-label" htmlFor="agent-edit-prompt">System prompt</label>
|
|
343
352
|
<textarea
|
|
353
|
+
id="agent-edit-prompt"
|
|
344
354
|
ref={(el) => (promptEl = el)}
|
|
345
355
|
className="textarea"
|
|
346
356
|
rows={8}
|
|
@@ -483,15 +493,20 @@ export function Agents({ snapshot, refreshSnapshot }: Props) {
|
|
|
483
493
|
</div>
|
|
484
494
|
<div className="view-actions">
|
|
485
495
|
<div className="search-input">
|
|
496
|
+
<label htmlFor="agents-search" className="sr-only">Search agents</label>
|
|
486
497
|
<input
|
|
498
|
+
id="agents-search"
|
|
487
499
|
className="input"
|
|
488
500
|
type="text"
|
|
489
501
|
placeholder="Search…"
|
|
490
502
|
value={search}
|
|
491
503
|
onChange={(e) => setSearch(e.target.value)}
|
|
504
|
+
aria-label="Search agents"
|
|
492
505
|
/>
|
|
493
506
|
</div>
|
|
507
|
+
<label htmlFor="agents-category-filter" className="sr-only">Filter by category</label>
|
|
494
508
|
<select
|
|
509
|
+
id="agents-category-filter"
|
|
495
510
|
className="select select-sm"
|
|
496
511
|
value={categoryFilter}
|
|
497
512
|
onChange={(e) => setCategoryFilter(e.target.value)}
|