Skip to content

feat(workbench): Knowledge Workbench — new React/TS frontend + supporting backend - #188

Closed
KylinMountain wants to merge 87 commits into
mainfrom
feat/workbench-kimi-adoption
Closed

feat(workbench): Knowledge Workbench — new React/TS frontend + supporting backend#188
KylinMountain wants to merge 87 commits into
mainfrom
feat/workbench-kimi-adoption

Conversation

@KylinMountain

Copy link
Copy Markdown
Collaborator

What this is

Replaces the shipped plain-CSS/JSX Workbench (frontend/) with a React 18 + TypeScript + Vite + Tailwind + shadcn/Radix single-page app, wired to the real OpenKB REST API, with an Apple-design material/motion pass, light-default / dark-auto theming, and 中文 / English i18n. Adds the backend endpoints and runtime-config plumbing the new UI needs.

86 commits · 170 files · +21k / −4k. Squash-merge intended.

Frontend

  • Chat-first IA — Home launcher (recent sessions + grounded chat input with KB scope + slash-command generators /deck/skill/visualize), KB list with a real create-KB flow, KB detail (card-nav Overview: Index / Concepts / Entities / Summaries / Reports / Documents; opens index.md like a real wiki), per-KB settings gear.
  • Docked artifact panel — deck / graph / conversationally-generated output/**.html open in a sandboxed-iframe side panel (blob URL + bearer, never a token in a URL); chat shows compact "open" cards.
  • Interleaved chat trace — streamed narration + tool reads render as an ordered step trace (each read a click-through ✅ line) followed by the answer — live and on restore (the trace is persisted per turn, read-tool-only; sessions saved before this fall back to the flat text).
  • MarkdownView — fenced code, lazy Mermaid diagrams, [[wikilink|alias]], LaTeX math via KaTeX (bundled / self-contained), and --- rules.
  • Apple design — glass materials, critically-damped springs (motion), prefers-reduced-motion / -transparency, size-specific typography; a11y (Radix modal semantics for the settings sheet, labeled complementary region for the panel).
  • Settings — global-config editor (model / PageIndex threshold / wiki output language via a UN-official-languages combobox / global default credentials), an About tab, and theme + language toggles in the top-right chrome.
  • i18n — react-i18next, 8 lazily-loaded namespaces, 中文 default + English, plus a build guard (check-i18n.mjs) that fails on leftover UI CJK or zh/en key-set drift.
  • New deps pinned exactly (motion, mermaid, the i18next stack, katex).

Backend

  • entities added to GET /api/v1/list (+ get_kb_list).
  • New routers — GET /api/v1/graph/html (self-contained graph), GET /api/v1/output (path-guarded, output/-only HTML sink), GET/PATCH /api/v1/config (global defaults + credentials).
  • Runtime config unificationresolve_effective_config(kb_dir) layers defaults → ~/.config/openkb/global.yaml → KB .openkb/config.yaml for the global scalars, migrated across the load_config call-sites; RFC-7386 merge-patch with null-removal that preserves inheritance.
  • Chat — write-capable agent (emits an artifact SSE event on a write_file of output/**.html); per-turn trace persistence (ChatSession.assistant_traces), recording only read-tool calls so large generated file contents never hit the session JSON or the restore wire.
  • Global default credentials written to ~/.config/openkb/.env (0o600, atomic, under the global-config lock; the API key value is never returned by GET nor logged).

Testing

  • Backend — uv run pytest -q1175 passed; ruff check / ruff format clean.
  • Frontend — npm run build (tsc + vite) + the i18n:check guard green.
  • Manual browser verification across light/dark, both locales, and each surface (create-KB dialog, chat trace live + restore, KaTeX math + --- rules, settings credentials, artifact panel).
  • Each sub-project passed an opus whole-branch code review; findings were fixed and re-reviewed.

Notes

  • Configuration lives in files (global.yaml / per-KB config.yaml / .env), not startup input.
  • The local API is unauthenticated by default (opt-in bearer via OPENKB_API_TOKEN) — intended for local use.
  • Dependencies are pinned exactly (supply-chain caution).

https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG

…rtifact preview
Migrate ChatInput, ChatSession, and ArtifactCard off hardcoded
bg-white/bg-blue-*/border-black/text-neutral-* colors onto the Apple
token layer (.glass/.glass-2, border-[hsl(var(--glass-border))],
bg-accent-brand, text-foreground/text-muted-foreground). Fixes the
shared ChatInput box + slash-command menu rendering white against the
dark shell (flagged in Task 4). Source chips gain active:scale-[0.97]
+ transition duration-fast ease-out-apple press feedback.
Wrap the deck preview panel in motion AnimatePresence + a critically-
damped spring (bounce:0, 0.24s), degrading to an opacity-only cross-fade
under prefers-reduced-motion via useReducedMotion. The sandboxed iframe
(sandbox=allow-scripts, src={previewUrl} blob URL) and its blob-URL
revocation lifecycle are unchanged; the spring only wraps the panel.
Slash-command logic, streaming, citations, and session handling are
untouched. Semantic deck/skill/graph accent colors are preserved.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
KbDetail kept light-only semantic status colors (bg-*-50 / text-*-500 /
text-*-600) with no dark: pair, so red error cards and emerald/red status
pills rendered as bright light patches in dark mode. Add dark: variants
only (no light-mode, layout, or logic changes), matching the app-wide
pattern from ChatSession.tsx (red) and ArtifactCard.tsx StatusBadge
(emerald): dark:bg-red-500/10 dark:border-red-500/25 dark:text-red-400 and
dark:bg-emerald-500/10 dark:text-emerald-400.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
…el labeled region
KbSettingsSheet (modal): compose @radix-ui/react-dialog around the existing
motion.aside via asChild + forceMount so AnimatePresence keeps the D-era spring
and reduced-motion. Radix supplies focus trap, initial focus, and Escape-close;
add aria-modal + aria-labelledby (Dialog.Title on the heading) explicitly, and
drive focus return to the opening gear via on{Open,Close}AutoFocus (we open from
external state, so Radix's own triggerRef is null and would drop focus to body).
Scrim/backdrop-dismiss, config inherit/override, watch/recompile/lint, threshold
validation and all i18n preserved.
ArtifactPanel (non-modal docked): add role=complementary + aria-label (active
artifact label), aria-label on the header controls, and a one-time initial focus
to the panel on open. No focus trap and no background inert — Tab still flows out
to the chat. iframe/blob/new-tab/download, switcher, motion and i18n preserved.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
The KB list page had two "New knowledge base" controls (top-right button +
in-grid dashed card) and NEITHER had an onClick — the frontend never wired KB
creation. The sidebar's "+" was a third dead trigger.
- add createKb() -> POST /api/v1/init (api/kb.ts)
- new CreateKbDialog: Radix modal (Dialog.Trigger asChild for focus-trap/Escape/
focus-return), name-only field (model/creds inherit global defaults), inline
backend-error surfacing, success -> navigate to the new KB
- KbList: remove the dashed card; wire the single top-right button as the trigger
- AppSidebar: wire the "+" to the same dialog
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
Previously all streamed delta text (planning narration + the answer) was
concatenated into one blob and tool reads showed only as separate chips.
- chat.ts: fold SSE events into an ordered TurnStep[] preserving arrival order
(text segments interleaved with tool steps); tool_call marks the prior read
done; final reconciles the trailing text with the authoritative answer. The
flat `answer`/`sources`/`reading` fields are retained for restore/consumers.
- ChatSession.tsx: render steps in order — narration via MarkdownView, each read
as a status line (spinner -> green check), page reads stay click-through.
- drop `/compile` from the slash palette (runTurn never had a compile branch;
recompile lives on the KB gear) and remove its now-unused icon import + keys.
Chat is for Q&A + generation; KB-lifecycle actions live on the KB surface.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
…guage picker
Settings global config surface:
- backend: GET/PATCH /api/v1/config now also read/write global default credentials
(LLM_API_KEY / OPENAI_API_BASE) in ~/.config/openkb/.env, mirroring the per-KB
.env write (0o600, atomic, RFC-7386 null-removal, key value never returned/
logged), under the existing global-config lock. GlobalConfigResponse gains
openai_api_base + has_api_key; GlobalConfigPatchRequest gains api_key
(SecretStr) + openai_api_base. +6 tests.
- Settings.tsx: add a Default credentials section (masked key, set/unset hint,
deferred clear, API base) folded into the merge-patch save; remove the dead
brand/privacy footer card + its i18n key.
Wiki output language now offers the six official UN languages as a datalist
combobox (still free-text, so e.g. 日本語 works) in both the global Settings
and the per-KB gear. Also a one-line LanguageToggle doc-comment fix.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
…p the trace
Revisiting a saved session collapsed the interleaved trace back to one text
block, because only the flat answer was persisted. Now the ordered trace
(narration text + tool reads) is stored per turn and rebuilt on restore.
Backend:
- ChatSession gains `assistant_traces` (parallel to assistant_texts, 1:1 by
index); to_dict/load_session round-trip it; record_turn accepts a `trace` and
back-fills empty traces for older turns so a pre-existing session that gets a
new turn stays index-aligned. Old files without the key load as [] (no crash).
- iter_chat_turn_events accumulates the trace from delta/tool_call events (same
order the frontend folds live) and persists it via record_turn.
- ChatSessionLoadResponse returns assistant_traces (new ChatTraceStep model).
Frontend:
- loadSession exposes assistant_traces; ChatSession restore rebuilds steps via
stepsFromTrace (tool steps re-derived through the same read-only whitelist),
falling back to a single text step for trace-less (old / CLI) turns.
- fix a latent live bug: on `final`, do NOT overwrite the trailing text step
with final.answer (which is the full narration+answer concatenation) — that
duplicated narration once tool reads split the text; keep the streamed trace
and only inject the answer when nothing streamed.
Backward compatible: only sessions created after this change carry a trace;
older ones render their saved text as before.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
Review follow-up. The trace recorded every tool_call, including write_file,
whose `arguments` carries the entire generated file `content`. That bloated the
session JSON and was shipped to the browser on restore only to be dropped by
the frontend's read-only whitelist. Gate recording to _TRACE_READ_TOOLS
(read_file / get_page_content) — the only tools the UI renders — which also
fixes a live-vs-restore text-grouping divergence a dropped write_file caused.
Plus a tidy-up of the languages.ts doc comment (drop raw CJK).
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
…n MarkdownView
Answers that contain LaTeX (\[ … \] block, \( … \) inline) and `---` rules
showed the raw delimiters/dashes as literal text. Add math + horizontal-rule
support to the hand-rolled MarkdownView: block/inline math render via KaTeX
(bundled + self-contained, no CDN; malformed TeX falls back to a code box), and
a `---`/`***`/`___` line renders an <hr>. Adds katex (pinned exact, matching the
mermaid/motion convention) + @types/katex.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
The About tab linked a "Website openkb.ai" that does not exist yet; remove the
link entry and its i18n key (both locales). The Globe icon stays (still used by
the Vectify AI company link).
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
Comment threadtests/test_cli.py
"""

def test_query_resolves_model_from_global_config(self, kb_dir, monkeypatch, tmp_path):
import openkb.cli as cli_mod
Comment threadtests/test_config.py
import asyncio

import openkb.agent.query as q
import openkb.config as cfg
Comment threadopenkb/api_models.py

# Single source of truth for the writable config keys (derived from the model
# above so the two never drift).
_KB_CONFIG_WRITABLE_KEYS = set(_KbConfigWritable.model_fields)
… / junk)
GET /api/v1/page serves pages verbatim, so every concept/entity/summary page's
OKF `--- … ---` frontmatter leaked into the reader as junk metadata lines — and,
now that MarkdownView renders `---` as an <hr>, as a horizontal rule at the very
top. Strip a leading frontmatter block before rendering (reader only; chat
answers carry no frontmatter and are untouched). Second-review follow-up.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
@KylinMountain
KylinMountain deleted the feat/workbench-kimi-adoption branch July 20, 2026 04:58
@KylinMountain

Copy link
Copy Markdown
CollaboratorAuthor

Superseded by #189 — the branch was renamed feat/workbench-kimi-adoptionfeat/workbench-adoption, which auto-closed this PR. Same commits, same content.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@KylinMountain
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
feat(workbench): Knowledge Workbench — new React/TS frontend + supporting backend by KylinMountain · Pull Request #188 · VectifyAI/OpenKB · GitHub
Skip to content

feat(workbench): Knowledge Workbench — new React/TS frontend + supporting backend - #188

Closed
KylinMountain wants to merge 87 commits into
mainfrom
feat/workbench-kimi-adoption
Closed

feat(workbench): Knowledge Workbench — new React/TS frontend + supporting backend#188
KylinMountain wants to merge 87 commits into
mainfrom
feat/workbench-kimi-adoption

Conversation

@KylinMountain

Copy link
Copy Markdown
Collaborator

What this is

Replaces the shipped plain-CSS/JSX Workbench (frontend/) with a React 18 + TypeScript + Vite + Tailwind + shadcn/Radix single-page app, wired to the real OpenKB REST API, with an Apple-design material/motion pass, light-default / dark-auto theming, and 中文 / English i18n. Adds the backend endpoints and runtime-config plumbing the new UI needs.

86 commits · 170 files · +21k / −4k. Squash-merge intended.

Frontend

  • Chat-first IA — Home launcher (recent sessions + grounded chat input with KB scope + slash-command generators /deck/skill/visualize), KB list with a real create-KB flow, KB detail (card-nav Overview: Index / Concepts / Entities / Summaries / Reports / Documents; opens index.md like a real wiki), per-KB settings gear.
  • Docked artifact panel — deck / graph / conversationally-generated output/**.html open in a sandboxed-iframe side panel (blob URL + bearer, never a token in a URL); chat shows compact "open" cards.
  • Interleaved chat trace — streamed narration + tool reads render as an ordered step trace (each read a click-through ✅ line) followed by the answer — live and on restore (the trace is persisted per turn, read-tool-only; sessions saved before this fall back to the flat text).
  • MarkdownView — fenced code, lazy Mermaid diagrams, [[wikilink|alias]], LaTeX math via KaTeX (bundled / self-contained), and --- rules.
  • Apple design — glass materials, critically-damped springs (motion), prefers-reduced-motion / -transparency, size-specific typography; a11y (Radix modal semantics for the settings sheet, labeled complementary region for the panel).
  • Settings — global-config editor (model / PageIndex threshold / wiki output language via a UN-official-languages combobox / global default credentials), an About tab, and theme + language toggles in the top-right chrome.
  • i18n — react-i18next, 8 lazily-loaded namespaces, 中文 default + English, plus a build guard (check-i18n.mjs) that fails on leftover UI CJK or zh/en key-set drift.
  • New deps pinned exactly (motion, mermaid, the i18next stack, katex).

Backend

  • entities added to GET /api/v1/list (+ get_kb_list).
  • New routers — GET /api/v1/graph/html (self-contained graph), GET /api/v1/output (path-guarded, output/-only HTML sink), GET/PATCH /api/v1/config (global defaults + credentials).
  • Runtime config unificationresolve_effective_config(kb_dir) layers defaults → ~/.config/openkb/global.yaml → KB .openkb/config.yaml for the global scalars, migrated across the load_config call-sites; RFC-7386 merge-patch with null-removal that preserves inheritance.
  • Chat — write-capable agent (emits an artifact SSE event on a write_file of output/**.html); per-turn trace persistence (ChatSession.assistant_traces), recording only read-tool calls so large generated file contents never hit the session JSON or the restore wire.
  • Global default credentials written to ~/.config/openkb/.env (0o600, atomic, under the global-config lock; the API key value is never returned by GET nor logged).

Testing

  • Backend — uv run pytest -q1175 passed; ruff check / ruff format clean.
  • Frontend — npm run build (tsc + vite) + the i18n:check guard green.
  • Manual browser verification across light/dark, both locales, and each surface (create-KB dialog, chat trace live + restore, KaTeX math + --- rules, settings credentials, artifact panel).
  • Each sub-project passed an opus whole-branch code review; findings were fixed and re-reviewed.

Notes

  • Configuration lives in files (global.yaml / per-KB config.yaml / .env), not startup input.
  • The local API is unauthenticated by default (opt-in bearer via OPENKB_API_TOKEN) — intended for local use.
  • Dependencies are pinned exactly (supply-chain caution).

https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG

…rtifact preview
Migrate ChatInput, ChatSession, and ArtifactCard off hardcoded
bg-white/bg-blue-*/border-black/text-neutral-* colors onto the Apple
token layer (.glass/.glass-2, border-[hsl(var(--glass-border))],
bg-accent-brand, text-foreground/text-muted-foreground). Fixes the
shared ChatInput box + slash-command menu rendering white against the
dark shell (flagged in Task 4). Source chips gain active:scale-[0.97]
+ transition duration-fast ease-out-apple press feedback.
Wrap the deck preview panel in motion AnimatePresence + a critically-
damped spring (bounce:0, 0.24s), degrading to an opacity-only cross-fade
under prefers-reduced-motion via useReducedMotion. The sandboxed iframe
(sandbox=allow-scripts, src={previewUrl} blob URL) and its blob-URL
revocation lifecycle are unchanged; the spring only wraps the panel.
Slash-command logic, streaming, citations, and session handling are
untouched. Semantic deck/skill/graph accent colors are preserved.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
KbDetail kept light-only semantic status colors (bg-*-50 / text-*-500 /
text-*-600) with no dark: pair, so red error cards and emerald/red status
pills rendered as bright light patches in dark mode. Add dark: variants
only (no light-mode, layout, or logic changes), matching the app-wide
pattern from ChatSession.tsx (red) and ArtifactCard.tsx StatusBadge
(emerald): dark:bg-red-500/10 dark:border-red-500/25 dark:text-red-400 and
dark:bg-emerald-500/10 dark:text-emerald-400.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
…el labeled region
KbSettingsSheet (modal): compose @radix-ui/react-dialog around the existing
motion.aside via asChild + forceMount so AnimatePresence keeps the D-era spring
and reduced-motion. Radix supplies focus trap, initial focus, and Escape-close;
add aria-modal + aria-labelledby (Dialog.Title on the heading) explicitly, and
drive focus return to the opening gear via on{Open,Close}AutoFocus (we open from
external state, so Radix's own triggerRef is null and would drop focus to body).
Scrim/backdrop-dismiss, config inherit/override, watch/recompile/lint, threshold
validation and all i18n preserved.
ArtifactPanel (non-modal docked): add role=complementary + aria-label (active
artifact label), aria-label on the header controls, and a one-time initial focus
to the panel on open. No focus trap and no background inert — Tab still flows out
to the chat. iframe/blob/new-tab/download, switcher, motion and i18n preserved.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
The KB list page had two "New knowledge base" controls (top-right button +
in-grid dashed card) and NEITHER had an onClick — the frontend never wired KB
creation. The sidebar's "+" was a third dead trigger.
- add createKb() -> POST /api/v1/init (api/kb.ts)
- new CreateKbDialog: Radix modal (Dialog.Trigger asChild for focus-trap/Escape/
focus-return), name-only field (model/creds inherit global defaults), inline
backend-error surfacing, success -> navigate to the new KB
- KbList: remove the dashed card; wire the single top-right button as the trigger
- AppSidebar: wire the "+" to the same dialog
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
Previously all streamed delta text (planning narration + the answer) was
concatenated into one blob and tool reads showed only as separate chips.
- chat.ts: fold SSE events into an ordered TurnStep[] preserving arrival order
(text segments interleaved with tool steps); tool_call marks the prior read
done; final reconciles the trailing text with the authoritative answer. The
flat `answer`/`sources`/`reading` fields are retained for restore/consumers.
- ChatSession.tsx: render steps in order — narration via MarkdownView, each read
as a status line (spinner -> green check), page reads stay click-through.
- drop `/compile` from the slash palette (runTurn never had a compile branch;
recompile lives on the KB gear) and remove its now-unused icon import + keys.
Chat is for Q&A + generation; KB-lifecycle actions live on the KB surface.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
…guage picker
Settings global config surface:
- backend: GET/PATCH /api/v1/config now also read/write global default credentials
(LLM_API_KEY / OPENAI_API_BASE) in ~/.config/openkb/.env, mirroring the per-KB
.env write (0o600, atomic, RFC-7386 null-removal, key value never returned/
logged), under the existing global-config lock. GlobalConfigResponse gains
openai_api_base + has_api_key; GlobalConfigPatchRequest gains api_key
(SecretStr) + openai_api_base. +6 tests.
- Settings.tsx: add a Default credentials section (masked key, set/unset hint,
deferred clear, API base) folded into the merge-patch save; remove the dead
brand/privacy footer card + its i18n key.
Wiki output language now offers the six official UN languages as a datalist
combobox (still free-text, so e.g. 日本語 works) in both the global Settings
and the per-KB gear. Also a one-line LanguageToggle doc-comment fix.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
…p the trace
Revisiting a saved session collapsed the interleaved trace back to one text
block, because only the flat answer was persisted. Now the ordered trace
(narration text + tool reads) is stored per turn and rebuilt on restore.
Backend:
- ChatSession gains `assistant_traces` (parallel to assistant_texts, 1:1 by
index); to_dict/load_session round-trip it; record_turn accepts a `trace` and
back-fills empty traces for older turns so a pre-existing session that gets a
new turn stays index-aligned. Old files without the key load as [] (no crash).
- iter_chat_turn_events accumulates the trace from delta/tool_call events (same
order the frontend folds live) and persists it via record_turn.
- ChatSessionLoadResponse returns assistant_traces (new ChatTraceStep model).
Frontend:
- loadSession exposes assistant_traces; ChatSession restore rebuilds steps via
stepsFromTrace (tool steps re-derived through the same read-only whitelist),
falling back to a single text step for trace-less (old / CLI) turns.
- fix a latent live bug: on `final`, do NOT overwrite the trailing text step
with final.answer (which is the full narration+answer concatenation) — that
duplicated narration once tool reads split the text; keep the streamed trace
and only inject the answer when nothing streamed.
Backward compatible: only sessions created after this change carry a trace;
older ones render their saved text as before.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
Review follow-up. The trace recorded every tool_call, including write_file,
whose `arguments` carries the entire generated file `content`. That bloated the
session JSON and was shipped to the browser on restore only to be dropped by
the frontend's read-only whitelist. Gate recording to _TRACE_READ_TOOLS
(read_file / get_page_content) — the only tools the UI renders — which also
fixes a live-vs-restore text-grouping divergence a dropped write_file caused.
Plus a tidy-up of the languages.ts doc comment (drop raw CJK).
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
…n MarkdownView
Answers that contain LaTeX (\[ … \] block, \( … \) inline) and `---` rules
showed the raw delimiters/dashes as literal text. Add math + horizontal-rule
support to the hand-rolled MarkdownView: block/inline math render via KaTeX
(bundled + self-contained, no CDN; malformed TeX falls back to a code box), and
a `---`/`***`/`___` line renders an <hr>. Adds katex (pinned exact, matching the
mermaid/motion convention) + @types/katex.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
The About tab linked a "Website openkb.ai" that does not exist yet; remove the
link entry and its i18n key (both locales). The Globe icon stays (still used by
the Vectify AI company link).
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
Comment threadtests/test_cli.py
"""

def test_query_resolves_model_from_global_config(self, kb_dir, monkeypatch, tmp_path):
import openkb.cli as cli_mod
Comment threadtests/test_config.py
import asyncio

import openkb.agent.query as q
import openkb.config as cfg
Comment threadopenkb/api_models.py

# Single source of truth for the writable config keys (derived from the model
# above so the two never drift).
_KB_CONFIG_WRITABLE_KEYS = set(_KbConfigWritable.model_fields)
… / junk)
GET /api/v1/page serves pages verbatim, so every concept/entity/summary page's
OKF `--- … ---` frontmatter leaked into the reader as junk metadata lines — and,
now that MarkdownView renders `---` as an <hr>, as a horizontal rule at the very
top. Strip a leading frontmatter block before rendering (reader only; chat
answers carry no frontmatter and are untouched). Second-review follow-up.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
@KylinMountain
KylinMountain deleted the feat/workbench-kimi-adoption branch July 20, 2026 04:58
@KylinMountain

Copy link
Copy Markdown
CollaboratorAuthor

Superseded by #189 — the branch was renamed feat/workbench-kimi-adoptionfeat/workbench-adoption, which auto-closed this PR. Same commits, same content.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@KylinMountain
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat(workbench): Knowledge Workbench — new React/TS frontend + supporting backend by KylinMountain · Pull Request #188 · VectifyAI/OpenKB · GitHub
Skip to content

feat(workbench): Knowledge Workbench — new React/TS frontend + supporting backend - #188

Closed
KylinMountain wants to merge 87 commits into
mainfrom
feat/workbench-kimi-adoption
Closed

feat(workbench): Knowledge Workbench — new React/TS frontend + supporting backend#188
KylinMountain wants to merge 87 commits into
mainfrom
feat/workbench-kimi-adoption

Conversation

@KylinMountain

Copy link
Copy Markdown
Collaborator

What this is

Replaces the shipped plain-CSS/JSX Workbench (frontend/) with a React 18 + TypeScript + Vite + Tailwind + shadcn/Radix single-page app, wired to the real OpenKB REST API, with an Apple-design material/motion pass, light-default / dark-auto theming, and 中文 / English i18n. Adds the backend endpoints and runtime-config plumbing the new UI needs.

86 commits · 170 files · +21k / −4k. Squash-merge intended.

Frontend

  • Chat-first IA — Home launcher (recent sessions + grounded chat input with KB scope + slash-command generators /deck/skill/visualize), KB list with a real create-KB flow, KB detail (card-nav Overview: Index / Concepts / Entities / Summaries / Reports / Documents; opens index.md like a real wiki), per-KB settings gear.
  • Docked artifact panel — deck / graph / conversationally-generated output/**.html open in a sandboxed-iframe side panel (blob URL + bearer, never a token in a URL); chat shows compact "open" cards.
  • Interleaved chat trace — streamed narration + tool reads render as an ordered step trace (each read a click-through ✅ line) followed by the answer — live and on restore (the trace is persisted per turn, read-tool-only; sessions saved before this fall back to the flat text).
  • MarkdownView — fenced code, lazy Mermaid diagrams, [[wikilink|alias]], LaTeX math via KaTeX (bundled / self-contained), and --- rules.
  • Apple design — glass materials, critically-damped springs (motion), prefers-reduced-motion / -transparency, size-specific typography; a11y (Radix modal semantics for the settings sheet, labeled complementary region for the panel).
  • Settings — global-config editor (model / PageIndex threshold / wiki output language via a UN-official-languages combobox / global default credentials), an About tab, and theme + language toggles in the top-right chrome.
  • i18n — react-i18next, 8 lazily-loaded namespaces, 中文 default + English, plus a build guard (check-i18n.mjs) that fails on leftover UI CJK or zh/en key-set drift.
  • New deps pinned exactly (motion, mermaid, the i18next stack, katex).

Backend

  • entities added to GET /api/v1/list (+ get_kb_list).
  • New routers — GET /api/v1/graph/html (self-contained graph), GET /api/v1/output (path-guarded, output/-only HTML sink), GET/PATCH /api/v1/config (global defaults + credentials).
  • Runtime config unificationresolve_effective_config(kb_dir) layers defaults → ~/.config/openkb/global.yaml → KB .openkb/config.yaml for the global scalars, migrated across the load_config call-sites; RFC-7386 merge-patch with null-removal that preserves inheritance.
  • Chat — write-capable agent (emits an artifact SSE event on a write_file of output/**.html); per-turn trace persistence (ChatSession.assistant_traces), recording only read-tool calls so large generated file contents never hit the session JSON or the restore wire.
  • Global default credentials written to ~/.config/openkb/.env (0o600, atomic, under the global-config lock; the API key value is never returned by GET nor logged).

Testing

  • Backend — uv run pytest -q1175 passed; ruff check / ruff format clean.
  • Frontend — npm run build (tsc + vite) + the i18n:check guard green.
  • Manual browser verification across light/dark, both locales, and each surface (create-KB dialog, chat trace live + restore, KaTeX math + --- rules, settings credentials, artifact panel).
  • Each sub-project passed an opus whole-branch code review; findings were fixed and re-reviewed.

Notes

  • Configuration lives in files (global.yaml / per-KB config.yaml / .env), not startup input.
  • The local API is unauthenticated by default (opt-in bearer via OPENKB_API_TOKEN) — intended for local use.
  • Dependencies are pinned exactly (supply-chain caution).

https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG

…rtifact preview
Migrate ChatInput, ChatSession, and ArtifactCard off hardcoded
bg-white/bg-blue-*/border-black/text-neutral-* colors onto the Apple
token layer (.glass/.glass-2, border-[hsl(var(--glass-border))],
bg-accent-brand, text-foreground/text-muted-foreground). Fixes the
shared ChatInput box + slash-command menu rendering white against the
dark shell (flagged in Task 4). Source chips gain active:scale-[0.97]
+ transition duration-fast ease-out-apple press feedback.
Wrap the deck preview panel in motion AnimatePresence + a critically-
damped spring (bounce:0, 0.24s), degrading to an opacity-only cross-fade
under prefers-reduced-motion via useReducedMotion. The sandboxed iframe
(sandbox=allow-scripts, src={previewUrl} blob URL) and its blob-URL
revocation lifecycle are unchanged; the spring only wraps the panel.
Slash-command logic, streaming, citations, and session handling are
untouched. Semantic deck/skill/graph accent colors are preserved.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
KbDetail kept light-only semantic status colors (bg-*-50 / text-*-500 /
text-*-600) with no dark: pair, so red error cards and emerald/red status
pills rendered as bright light patches in dark mode. Add dark: variants
only (no light-mode, layout, or logic changes), matching the app-wide
pattern from ChatSession.tsx (red) and ArtifactCard.tsx StatusBadge
(emerald): dark:bg-red-500/10 dark:border-red-500/25 dark:text-red-400 and
dark:bg-emerald-500/10 dark:text-emerald-400.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
…el labeled region
KbSettingsSheet (modal): compose @radix-ui/react-dialog around the existing
motion.aside via asChild + forceMount so AnimatePresence keeps the D-era spring
and reduced-motion. Radix supplies focus trap, initial focus, and Escape-close;
add aria-modal + aria-labelledby (Dialog.Title on the heading) explicitly, and
drive focus return to the opening gear via on{Open,Close}AutoFocus (we open from
external state, so Radix's own triggerRef is null and would drop focus to body).
Scrim/backdrop-dismiss, config inherit/override, watch/recompile/lint, threshold
validation and all i18n preserved.
ArtifactPanel (non-modal docked): add role=complementary + aria-label (active
artifact label), aria-label on the header controls, and a one-time initial focus
to the panel on open. No focus trap and no background inert — Tab still flows out
to the chat. iframe/blob/new-tab/download, switcher, motion and i18n preserved.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
The KB list page had two "New knowledge base" controls (top-right button +
in-grid dashed card) and NEITHER had an onClick — the frontend never wired KB
creation. The sidebar's "+" was a third dead trigger.
- add createKb() -> POST /api/v1/init (api/kb.ts)
- new CreateKbDialog: Radix modal (Dialog.Trigger asChild for focus-trap/Escape/
focus-return), name-only field (model/creds inherit global defaults), inline
backend-error surfacing, success -> navigate to the new KB
- KbList: remove the dashed card; wire the single top-right button as the trigger
- AppSidebar: wire the "+" to the same dialog
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
Previously all streamed delta text (planning narration + the answer) was
concatenated into one blob and tool reads showed only as separate chips.
- chat.ts: fold SSE events into an ordered TurnStep[] preserving arrival order
(text segments interleaved with tool steps); tool_call marks the prior read
done; final reconciles the trailing text with the authoritative answer. The
flat `answer`/`sources`/`reading` fields are retained for restore/consumers.
- ChatSession.tsx: render steps in order — narration via MarkdownView, each read
as a status line (spinner -> green check), page reads stay click-through.
- drop `/compile` from the slash palette (runTurn never had a compile branch;
recompile lives on the KB gear) and remove its now-unused icon import + keys.
Chat is for Q&A + generation; KB-lifecycle actions live on the KB surface.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
…guage picker
Settings global config surface:
- backend: GET/PATCH /api/v1/config now also read/write global default credentials
(LLM_API_KEY / OPENAI_API_BASE) in ~/.config/openkb/.env, mirroring the per-KB
.env write (0o600, atomic, RFC-7386 null-removal, key value never returned/
logged), under the existing global-config lock. GlobalConfigResponse gains
openai_api_base + has_api_key; GlobalConfigPatchRequest gains api_key
(SecretStr) + openai_api_base. +6 tests.
- Settings.tsx: add a Default credentials section (masked key, set/unset hint,
deferred clear, API base) folded into the merge-patch save; remove the dead
brand/privacy footer card + its i18n key.
Wiki output language now offers the six official UN languages as a datalist
combobox (still free-text, so e.g. 日本語 works) in both the global Settings
and the per-KB gear. Also a one-line LanguageToggle doc-comment fix.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
…p the trace
Revisiting a saved session collapsed the interleaved trace back to one text
block, because only the flat answer was persisted. Now the ordered trace
(narration text + tool reads) is stored per turn and rebuilt on restore.
Backend:
- ChatSession gains `assistant_traces` (parallel to assistant_texts, 1:1 by
index); to_dict/load_session round-trip it; record_turn accepts a `trace` and
back-fills empty traces for older turns so a pre-existing session that gets a
new turn stays index-aligned. Old files without the key load as [] (no crash).
- iter_chat_turn_events accumulates the trace from delta/tool_call events (same
order the frontend folds live) and persists it via record_turn.
- ChatSessionLoadResponse returns assistant_traces (new ChatTraceStep model).
Frontend:
- loadSession exposes assistant_traces; ChatSession restore rebuilds steps via
stepsFromTrace (tool steps re-derived through the same read-only whitelist),
falling back to a single text step for trace-less (old / CLI) turns.
- fix a latent live bug: on `final`, do NOT overwrite the trailing text step
with final.answer (which is the full narration+answer concatenation) — that
duplicated narration once tool reads split the text; keep the streamed trace
and only inject the answer when nothing streamed.
Backward compatible: only sessions created after this change carry a trace;
older ones render their saved text as before.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
Review follow-up. The trace recorded every tool_call, including write_file,
whose `arguments` carries the entire generated file `content`. That bloated the
session JSON and was shipped to the browser on restore only to be dropped by
the frontend's read-only whitelist. Gate recording to _TRACE_READ_TOOLS
(read_file / get_page_content) — the only tools the UI renders — which also
fixes a live-vs-restore text-grouping divergence a dropped write_file caused.
Plus a tidy-up of the languages.ts doc comment (drop raw CJK).
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
…n MarkdownView
Answers that contain LaTeX (\[ … \] block, \( … \) inline) and `---` rules
showed the raw delimiters/dashes as literal text. Add math + horizontal-rule
support to the hand-rolled MarkdownView: block/inline math render via KaTeX
(bundled + self-contained, no CDN; malformed TeX falls back to a code box), and
a `---`/`***`/`___` line renders an <hr>. Adds katex (pinned exact, matching the
mermaid/motion convention) + @types/katex.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
The About tab linked a "Website openkb.ai" that does not exist yet; remove the
link entry and its i18n key (both locales). The Globe icon stays (still used by
the Vectify AI company link).
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
Comment threadtests/test_cli.py
"""

def test_query_resolves_model_from_global_config(self, kb_dir, monkeypatch, tmp_path):
import openkb.cli as cli_mod
Comment threadtests/test_config.py
import asyncio

import openkb.agent.query as q
import openkb.config as cfg
Comment threadopenkb/api_models.py

# Single source of truth for the writable config keys (derived from the model
# above so the two never drift).
_KB_CONFIG_WRITABLE_KEYS = set(_KbConfigWritable.model_fields)
… / junk)
GET /api/v1/page serves pages verbatim, so every concept/entity/summary page's
OKF `--- … ---` frontmatter leaked into the reader as junk metadata lines — and,
now that MarkdownView renders `---` as an <hr>, as a horizontal rule at the very
top. Strip a leading frontmatter block before rendering (reader only; chat
answers carry no frontmatter and are untouched). Second-review follow-up.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
@KylinMountain
KylinMountain deleted the feat/workbench-kimi-adoption branch July 20, 2026 04:58
@KylinMountain

Copy link
Copy Markdown
CollaboratorAuthor

Superseded by #189 — the branch was renamed feat/workbench-kimi-adoptionfeat/workbench-adoption, which auto-closed this PR. Same commits, same content.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@KylinMountain
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat(workbench): Knowledge Workbench — new React/TS frontend + supporting backend by KylinMountain · Pull Request #188 · VectifyAI/OpenKB · GitHub
Skip to content

feat(workbench): Knowledge Workbench — new React/TS frontend + supporting backend - #188

Closed
KylinMountain wants to merge 87 commits into
mainfrom
feat/workbench-kimi-adoption
Closed

feat(workbench): Knowledge Workbench — new React/TS frontend + supporting backend#188
KylinMountain wants to merge 87 commits into
mainfrom
feat/workbench-kimi-adoption

Conversation

@KylinMountain

Copy link
Copy Markdown
Collaborator

What this is

Replaces the shipped plain-CSS/JSX Workbench (frontend/) with a React 18 + TypeScript + Vite + Tailwind + shadcn/Radix single-page app, wired to the real OpenKB REST API, with an Apple-design material/motion pass, light-default / dark-auto theming, and 中文 / English i18n. Adds the backend endpoints and runtime-config plumbing the new UI needs.

86 commits · 170 files · +21k / −4k. Squash-merge intended.

Frontend

  • Chat-first IA — Home launcher (recent sessions + grounded chat input with KB scope + slash-command generators /deck/skill/visualize), KB list with a real create-KB flow, KB detail (card-nav Overview: Index / Concepts / Entities / Summaries / Reports / Documents; opens index.md like a real wiki), per-KB settings gear.
  • Docked artifact panel — deck / graph / conversationally-generated output/**.html open in a sandboxed-iframe side panel (blob URL + bearer, never a token in a URL); chat shows compact "open" cards.
  • Interleaved chat trace — streamed narration + tool reads render as an ordered step trace (each read a click-through ✅ line) followed by the answer — live and on restore (the trace is persisted per turn, read-tool-only; sessions saved before this fall back to the flat text).
  • MarkdownView — fenced code, lazy Mermaid diagrams, [[wikilink|alias]], LaTeX math via KaTeX (bundled / self-contained), and --- rules.
  • Apple design — glass materials, critically-damped springs (motion), prefers-reduced-motion / -transparency, size-specific typography; a11y (Radix modal semantics for the settings sheet, labeled complementary region for the panel).
  • Settings — global-config editor (model / PageIndex threshold / wiki output language via a UN-official-languages combobox / global default credentials), an About tab, and theme + language toggles in the top-right chrome.
  • i18n — react-i18next, 8 lazily-loaded namespaces, 中文 default + English, plus a build guard (check-i18n.mjs) that fails on leftover UI CJK or zh/en key-set drift.
  • New deps pinned exactly (motion, mermaid, the i18next stack, katex).

Backend

  • entities added to GET /api/v1/list (+ get_kb_list).
  • New routers — GET /api/v1/graph/html (self-contained graph), GET /api/v1/output (path-guarded, output/-only HTML sink), GET/PATCH /api/v1/config (global defaults + credentials).
  • Runtime config unificationresolve_effective_config(kb_dir) layers defaults → ~/.config/openkb/global.yaml → KB .openkb/config.yaml for the global scalars, migrated across the load_config call-sites; RFC-7386 merge-patch with null-removal that preserves inheritance.
  • Chat — write-capable agent (emits an artifact SSE event on a write_file of output/**.html); per-turn trace persistence (ChatSession.assistant_traces), recording only read-tool calls so large generated file contents never hit the session JSON or the restore wire.
  • Global default credentials written to ~/.config/openkb/.env (0o600, atomic, under the global-config lock; the API key value is never returned by GET nor logged).

Testing

  • Backend — uv run pytest -q1175 passed; ruff check / ruff format clean.
  • Frontend — npm run build (tsc + vite) + the i18n:check guard green.
  • Manual browser verification across light/dark, both locales, and each surface (create-KB dialog, chat trace live + restore, KaTeX math + --- rules, settings credentials, artifact panel).
  • Each sub-project passed an opus whole-branch code review; findings were fixed and re-reviewed.

Notes

  • Configuration lives in files (global.yaml / per-KB config.yaml / .env), not startup input.
  • The local API is unauthenticated by default (opt-in bearer via OPENKB_API_TOKEN) — intended for local use.
  • Dependencies are pinned exactly (supply-chain caution).

https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG

…rtifact preview
Migrate ChatInput, ChatSession, and ArtifactCard off hardcoded
bg-white/bg-blue-*/border-black/text-neutral-* colors onto the Apple
token layer (.glass/.glass-2, border-[hsl(var(--glass-border))],
bg-accent-brand, text-foreground/text-muted-foreground). Fixes the
shared ChatInput box + slash-command menu rendering white against the
dark shell (flagged in Task 4). Source chips gain active:scale-[0.97]
+ transition duration-fast ease-out-apple press feedback.
Wrap the deck preview panel in motion AnimatePresence + a critically-
damped spring (bounce:0, 0.24s), degrading to an opacity-only cross-fade
under prefers-reduced-motion via useReducedMotion. The sandboxed iframe
(sandbox=allow-scripts, src={previewUrl} blob URL) and its blob-URL
revocation lifecycle are unchanged; the spring only wraps the panel.
Slash-command logic, streaming, citations, and session handling are
untouched. Semantic deck/skill/graph accent colors are preserved.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
KbDetail kept light-only semantic status colors (bg-*-50 / text-*-500 /
text-*-600) with no dark: pair, so red error cards and emerald/red status
pills rendered as bright light patches in dark mode. Add dark: variants
only (no light-mode, layout, or logic changes), matching the app-wide
pattern from ChatSession.tsx (red) and ArtifactCard.tsx StatusBadge
(emerald): dark:bg-red-500/10 dark:border-red-500/25 dark:text-red-400 and
dark:bg-emerald-500/10 dark:text-emerald-400.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
…el labeled region
KbSettingsSheet (modal): compose @radix-ui/react-dialog around the existing
motion.aside via asChild + forceMount so AnimatePresence keeps the D-era spring
and reduced-motion. Radix supplies focus trap, initial focus, and Escape-close;
add aria-modal + aria-labelledby (Dialog.Title on the heading) explicitly, and
drive focus return to the opening gear via on{Open,Close}AutoFocus (we open from
external state, so Radix's own triggerRef is null and would drop focus to body).
Scrim/backdrop-dismiss, config inherit/override, watch/recompile/lint, threshold
validation and all i18n preserved.
ArtifactPanel (non-modal docked): add role=complementary + aria-label (active
artifact label), aria-label on the header controls, and a one-time initial focus
to the panel on open. No focus trap and no background inert — Tab still flows out
to the chat. iframe/blob/new-tab/download, switcher, motion and i18n preserved.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
The KB list page had two "New knowledge base" controls (top-right button +
in-grid dashed card) and NEITHER had an onClick — the frontend never wired KB
creation. The sidebar's "+" was a third dead trigger.
- add createKb() -> POST /api/v1/init (api/kb.ts)
- new CreateKbDialog: Radix modal (Dialog.Trigger asChild for focus-trap/Escape/
focus-return), name-only field (model/creds inherit global defaults), inline
backend-error surfacing, success -> navigate to the new KB
- KbList: remove the dashed card; wire the single top-right button as the trigger
- AppSidebar: wire the "+" to the same dialog
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
Previously all streamed delta text (planning narration + the answer) was
concatenated into one blob and tool reads showed only as separate chips.
- chat.ts: fold SSE events into an ordered TurnStep[] preserving arrival order
(text segments interleaved with tool steps); tool_call marks the prior read
done; final reconciles the trailing text with the authoritative answer. The
flat `answer`/`sources`/`reading` fields are retained for restore/consumers.
- ChatSession.tsx: render steps in order — narration via MarkdownView, each read
as a status line (spinner -> green check), page reads stay click-through.
- drop `/compile` from the slash palette (runTurn never had a compile branch;
recompile lives on the KB gear) and remove its now-unused icon import + keys.
Chat is for Q&A + generation; KB-lifecycle actions live on the KB surface.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
…guage picker
Settings global config surface:
- backend: GET/PATCH /api/v1/config now also read/write global default credentials
(LLM_API_KEY / OPENAI_API_BASE) in ~/.config/openkb/.env, mirroring the per-KB
.env write (0o600, atomic, RFC-7386 null-removal, key value never returned/
logged), under the existing global-config lock. GlobalConfigResponse gains
openai_api_base + has_api_key; GlobalConfigPatchRequest gains api_key
(SecretStr) + openai_api_base. +6 tests.
- Settings.tsx: add a Default credentials section (masked key, set/unset hint,
deferred clear, API base) folded into the merge-patch save; remove the dead
brand/privacy footer card + its i18n key.
Wiki output language now offers the six official UN languages as a datalist
combobox (still free-text, so e.g. 日本語 works) in both the global Settings
and the per-KB gear. Also a one-line LanguageToggle doc-comment fix.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
…p the trace
Revisiting a saved session collapsed the interleaved trace back to one text
block, because only the flat answer was persisted. Now the ordered trace
(narration text + tool reads) is stored per turn and rebuilt on restore.
Backend:
- ChatSession gains `assistant_traces` (parallel to assistant_texts, 1:1 by
index); to_dict/load_session round-trip it; record_turn accepts a `trace` and
back-fills empty traces for older turns so a pre-existing session that gets a
new turn stays index-aligned. Old files without the key load as [] (no crash).
- iter_chat_turn_events accumulates the trace from delta/tool_call events (same
order the frontend folds live) and persists it via record_turn.
- ChatSessionLoadResponse returns assistant_traces (new ChatTraceStep model).
Frontend:
- loadSession exposes assistant_traces; ChatSession restore rebuilds steps via
stepsFromTrace (tool steps re-derived through the same read-only whitelist),
falling back to a single text step for trace-less (old / CLI) turns.
- fix a latent live bug: on `final`, do NOT overwrite the trailing text step
with final.answer (which is the full narration+answer concatenation) — that
duplicated narration once tool reads split the text; keep the streamed trace
and only inject the answer when nothing streamed.
Backward compatible: only sessions created after this change carry a trace;
older ones render their saved text as before.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
Review follow-up. The trace recorded every tool_call, including write_file,
whose `arguments` carries the entire generated file `content`. That bloated the
session JSON and was shipped to the browser on restore only to be dropped by
the frontend's read-only whitelist. Gate recording to _TRACE_READ_TOOLS
(read_file / get_page_content) — the only tools the UI renders — which also
fixes a live-vs-restore text-grouping divergence a dropped write_file caused.
Plus a tidy-up of the languages.ts doc comment (drop raw CJK).
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
…n MarkdownView
Answers that contain LaTeX (\[ … \] block, \( … \) inline) and `---` rules
showed the raw delimiters/dashes as literal text. Add math + horizontal-rule
support to the hand-rolled MarkdownView: block/inline math render via KaTeX
(bundled + self-contained, no CDN; malformed TeX falls back to a code box), and
a `---`/`***`/`___` line renders an <hr>. Adds katex (pinned exact, matching the
mermaid/motion convention) + @types/katex.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
The About tab linked a "Website openkb.ai" that does not exist yet; remove the
link entry and its i18n key (both locales). The Globe icon stays (still used by
the Vectify AI company link).
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
Comment threadtests/test_cli.py
"""

def test_query_resolves_model_from_global_config(self, kb_dir, monkeypatch, tmp_path):
import openkb.cli as cli_mod
Comment threadtests/test_config.py
import asyncio

import openkb.agent.query as q
import openkb.config as cfg
Comment threadopenkb/api_models.py

# Single source of truth for the writable config keys (derived from the model
# above so the two never drift).
_KB_CONFIG_WRITABLE_KEYS = set(_KbConfigWritable.model_fields)
… / junk)
GET /api/v1/page serves pages verbatim, so every concept/entity/summary page's
OKF `--- … ---` frontmatter leaked into the reader as junk metadata lines — and,
now that MarkdownView renders `---` as an <hr>, as a horizontal rule at the very
top. Strip a leading frontmatter block before rendering (reader only; chat
answers carry no frontmatter and are untouched). Second-review follow-up.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
@KylinMountain
KylinMountain deleted the feat/workbench-kimi-adoption branch July 20, 2026 04:58
@KylinMountain

Copy link
Copy Markdown
CollaboratorAuthor

Superseded by #189 — the branch was renamed feat/workbench-kimi-adoptionfeat/workbench-adoption, which auto-closed this PR. Same commits, same content.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@KylinMountain
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' feat(workbench): Knowledge Workbench — new React/TS frontend + supporting backend by KylinMountain · Pull Request #188 · VectifyAI/OpenKB · GitHub
Skip to content

feat(workbench): Knowledge Workbench — new React/TS frontend + supporting backend - #188

Closed
KylinMountain wants to merge 87 commits into
mainfrom
feat/workbench-kimi-adoption
Closed

feat(workbench): Knowledge Workbench — new React/TS frontend + supporting backend#188
KylinMountain wants to merge 87 commits into
mainfrom
feat/workbench-kimi-adoption

Conversation

@KylinMountain

Copy link
Copy Markdown
Collaborator

What this is

Replaces the shipped plain-CSS/JSX Workbench (frontend/) with a React 18 + TypeScript + Vite + Tailwind + shadcn/Radix single-page app, wired to the real OpenKB REST API, with an Apple-design material/motion pass, light-default / dark-auto theming, and 中文 / English i18n. Adds the backend endpoints and runtime-config plumbing the new UI needs.

86 commits · 170 files · +21k / −4k. Squash-merge intended.

Frontend

  • Chat-first IA — Home launcher (recent sessions + grounded chat input with KB scope + slash-command generators /deck/skill/visualize), KB list with a real create-KB flow, KB detail (card-nav Overview: Index / Concepts / Entities / Summaries / Reports / Documents; opens index.md like a real wiki), per-KB settings gear.
  • Docked artifact panel — deck / graph / conversationally-generated output/**.html open in a sandboxed-iframe side panel (blob URL + bearer, never a token in a URL); chat shows compact "open" cards.
  • Interleaved chat trace — streamed narration + tool reads render as an ordered step trace (each read a click-through ✅ line) followed by the answer — live and on restore (the trace is persisted per turn, read-tool-only; sessions saved before this fall back to the flat text).
  • MarkdownView — fenced code, lazy Mermaid diagrams, [[wikilink|alias]], LaTeX math via KaTeX (bundled / self-contained), and --- rules.
  • Apple design — glass materials, critically-damped springs (motion), prefers-reduced-motion / -transparency, size-specific typography; a11y (Radix modal semantics for the settings sheet, labeled complementary region for the panel).
  • Settings — global-config editor (model / PageIndex threshold / wiki output language via a UN-official-languages combobox / global default credentials), an About tab, and theme + language toggles in the top-right chrome.
  • i18n — react-i18next, 8 lazily-loaded namespaces, 中文 default + English, plus a build guard (check-i18n.mjs) that fails on leftover UI CJK or zh/en key-set drift.
  • New deps pinned exactly (motion, mermaid, the i18next stack, katex).

Backend

  • entities added to GET /api/v1/list (+ get_kb_list).
  • New routers — GET /api/v1/graph/html (self-contained graph), GET /api/v1/output (path-guarded, output/-only HTML sink), GET/PATCH /api/v1/config (global defaults + credentials).
  • Runtime config unificationresolve_effective_config(kb_dir) layers defaults → ~/.config/openkb/global.yaml → KB .openkb/config.yaml for the global scalars, migrated across the load_config call-sites; RFC-7386 merge-patch with null-removal that preserves inheritance.
  • Chat — write-capable agent (emits an artifact SSE event on a write_file of output/**.html); per-turn trace persistence (ChatSession.assistant_traces), recording only read-tool calls so large generated file contents never hit the session JSON or the restore wire.
  • Global default credentials written to ~/.config/openkb/.env (0o600, atomic, under the global-config lock; the API key value is never returned by GET nor logged).

Testing

  • Backend — uv run pytest -q1175 passed; ruff check / ruff format clean.
  • Frontend — npm run build (tsc + vite) + the i18n:check guard green.
  • Manual browser verification across light/dark, both locales, and each surface (create-KB dialog, chat trace live + restore, KaTeX math + --- rules, settings credentials, artifact panel).
  • Each sub-project passed an opus whole-branch code review; findings were fixed and re-reviewed.

Notes

  • Configuration lives in files (global.yaml / per-KB config.yaml / .env), not startup input.
  • The local API is unauthenticated by default (opt-in bearer via OPENKB_API_TOKEN) — intended for local use.
  • Dependencies are pinned exactly (supply-chain caution).

https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG

…rtifact preview
Migrate ChatInput, ChatSession, and ArtifactCard off hardcoded
bg-white/bg-blue-*/border-black/text-neutral-* colors onto the Apple
token layer (.glass/.glass-2, border-[hsl(var(--glass-border))],
bg-accent-brand, text-foreground/text-muted-foreground). Fixes the
shared ChatInput box + slash-command menu rendering white against the
dark shell (flagged in Task 4). Source chips gain active:scale-[0.97]
+ transition duration-fast ease-out-apple press feedback.
Wrap the deck preview panel in motion AnimatePresence + a critically-
damped spring (bounce:0, 0.24s), degrading to an opacity-only cross-fade
under prefers-reduced-motion via useReducedMotion. The sandboxed iframe
(sandbox=allow-scripts, src={previewUrl} blob URL) and its blob-URL
revocation lifecycle are unchanged; the spring only wraps the panel.
Slash-command logic, streaming, citations, and session handling are
untouched. Semantic deck/skill/graph accent colors are preserved.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
KbDetail kept light-only semantic status colors (bg-*-50 / text-*-500 /
text-*-600) with no dark: pair, so red error cards and emerald/red status
pills rendered as bright light patches in dark mode. Add dark: variants
only (no light-mode, layout, or logic changes), matching the app-wide
pattern from ChatSession.tsx (red) and ArtifactCard.tsx StatusBadge
(emerald): dark:bg-red-500/10 dark:border-red-500/25 dark:text-red-400 and
dark:bg-emerald-500/10 dark:text-emerald-400.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
…el labeled region
KbSettingsSheet (modal): compose @radix-ui/react-dialog around the existing
motion.aside via asChild + forceMount so AnimatePresence keeps the D-era spring
and reduced-motion. Radix supplies focus trap, initial focus, and Escape-close;
add aria-modal + aria-labelledby (Dialog.Title on the heading) explicitly, and
drive focus return to the opening gear via on{Open,Close}AutoFocus (we open from
external state, so Radix's own triggerRef is null and would drop focus to body).
Scrim/backdrop-dismiss, config inherit/override, watch/recompile/lint, threshold
validation and all i18n preserved.
ArtifactPanel (non-modal docked): add role=complementary + aria-label (active
artifact label), aria-label on the header controls, and a one-time initial focus
to the panel on open. No focus trap and no background inert — Tab still flows out
to the chat. iframe/blob/new-tab/download, switcher, motion and i18n preserved.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
The KB list page had two "New knowledge base" controls (top-right button +
in-grid dashed card) and NEITHER had an onClick — the frontend never wired KB
creation. The sidebar's "+" was a third dead trigger.
- add createKb() -> POST /api/v1/init (api/kb.ts)
- new CreateKbDialog: Radix modal (Dialog.Trigger asChild for focus-trap/Escape/
focus-return), name-only field (model/creds inherit global defaults), inline
backend-error surfacing, success -> navigate to the new KB
- KbList: remove the dashed card; wire the single top-right button as the trigger
- AppSidebar: wire the "+" to the same dialog
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
Previously all streamed delta text (planning narration + the answer) was
concatenated into one blob and tool reads showed only as separate chips.
- chat.ts: fold SSE events into an ordered TurnStep[] preserving arrival order
(text segments interleaved with tool steps); tool_call marks the prior read
done; final reconciles the trailing text with the authoritative answer. The
flat `answer`/`sources`/`reading` fields are retained for restore/consumers.
- ChatSession.tsx: render steps in order — narration via MarkdownView, each read
as a status line (spinner -> green check), page reads stay click-through.
- drop `/compile` from the slash palette (runTurn never had a compile branch;
recompile lives on the KB gear) and remove its now-unused icon import + keys.
Chat is for Q&A + generation; KB-lifecycle actions live on the KB surface.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
…guage picker
Settings global config surface:
- backend: GET/PATCH /api/v1/config now also read/write global default credentials
(LLM_API_KEY / OPENAI_API_BASE) in ~/.config/openkb/.env, mirroring the per-KB
.env write (0o600, atomic, RFC-7386 null-removal, key value never returned/
logged), under the existing global-config lock. GlobalConfigResponse gains
openai_api_base + has_api_key; GlobalConfigPatchRequest gains api_key
(SecretStr) + openai_api_base. +6 tests.
- Settings.tsx: add a Default credentials section (masked key, set/unset hint,
deferred clear, API base) folded into the merge-patch save; remove the dead
brand/privacy footer card + its i18n key.
Wiki output language now offers the six official UN languages as a datalist
combobox (still free-text, so e.g. 日本語 works) in both the global Settings
and the per-KB gear. Also a one-line LanguageToggle doc-comment fix.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
…p the trace
Revisiting a saved session collapsed the interleaved trace back to one text
block, because only the flat answer was persisted. Now the ordered trace
(narration text + tool reads) is stored per turn and rebuilt on restore.
Backend:
- ChatSession gains `assistant_traces` (parallel to assistant_texts, 1:1 by
index); to_dict/load_session round-trip it; record_turn accepts a `trace` and
back-fills empty traces for older turns so a pre-existing session that gets a
new turn stays index-aligned. Old files without the key load as [] (no crash).
- iter_chat_turn_events accumulates the trace from delta/tool_call events (same
order the frontend folds live) and persists it via record_turn.
- ChatSessionLoadResponse returns assistant_traces (new ChatTraceStep model).
Frontend:
- loadSession exposes assistant_traces; ChatSession restore rebuilds steps via
stepsFromTrace (tool steps re-derived through the same read-only whitelist),
falling back to a single text step for trace-less (old / CLI) turns.
- fix a latent live bug: on `final`, do NOT overwrite the trailing text step
with final.answer (which is the full narration+answer concatenation) — that
duplicated narration once tool reads split the text; keep the streamed trace
and only inject the answer when nothing streamed.
Backward compatible: only sessions created after this change carry a trace;
older ones render their saved text as before.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
Review follow-up. The trace recorded every tool_call, including write_file,
whose `arguments` carries the entire generated file `content`. That bloated the
session JSON and was shipped to the browser on restore only to be dropped by
the frontend's read-only whitelist. Gate recording to _TRACE_READ_TOOLS
(read_file / get_page_content) — the only tools the UI renders — which also
fixes a live-vs-restore text-grouping divergence a dropped write_file caused.
Plus a tidy-up of the languages.ts doc comment (drop raw CJK).
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
…n MarkdownView
Answers that contain LaTeX (\[ … \] block, \( … \) inline) and `---` rules
showed the raw delimiters/dashes as literal text. Add math + horizontal-rule
support to the hand-rolled MarkdownView: block/inline math render via KaTeX
(bundled + self-contained, no CDN; malformed TeX falls back to a code box), and
a `---`/`***`/`___` line renders an <hr>. Adds katex (pinned exact, matching the
mermaid/motion convention) + @types/katex.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
The About tab linked a "Website openkb.ai" that does not exist yet; remove the
link entry and its i18n key (both locales). The Globe icon stays (still used by
the Vectify AI company link).
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
Comment threadtests/test_cli.py
"""

def test_query_resolves_model_from_global_config(self, kb_dir, monkeypatch, tmp_path):
import openkb.cli as cli_mod
Comment threadtests/test_config.py
import asyncio

import openkb.agent.query as q
import openkb.config as cfg
Comment threadopenkb/api_models.py

# Single source of truth for the writable config keys (derived from the model
# above so the two never drift).
_KB_CONFIG_WRITABLE_KEYS = set(_KbConfigWritable.model_fields)
… / junk)
GET /api/v1/page serves pages verbatim, so every concept/entity/summary page's
OKF `--- … ---` frontmatter leaked into the reader as junk metadata lines — and,
now that MarkdownView renders `---` as an <hr>, as a horizontal rule at the very
top. Strip a leading frontmatter block before rendering (reader only; chat
answers carry no frontmatter and are untouched). Second-review follow-up.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
@KylinMountain
KylinMountain deleted the feat/workbench-kimi-adoption branch July 20, 2026 04:58
@KylinMountain

Copy link
Copy Markdown
CollaboratorAuthor

Superseded by #189 — the branch was renamed feat/workbench-kimi-adoptionfeat/workbench-adoption, which auto-closed this PR. Same commits, same content.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@KylinMountain
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat(workbench): Knowledge Workbench — new React/TS frontend + supporting backend by KylinMountain · Pull Request #188 · VectifyAI/OpenKB · GitHub
Skip to content

feat(workbench): Knowledge Workbench — new React/TS frontend + supporting backend - #188

Closed
KylinMountain wants to merge 87 commits into
mainfrom
feat/workbench-kimi-adoption
Closed

feat(workbench): Knowledge Workbench — new React/TS frontend + supporting backend#188
KylinMountain wants to merge 87 commits into
mainfrom
feat/workbench-kimi-adoption

Conversation

@KylinMountain

Copy link
Copy Markdown
Collaborator

What this is

Replaces the shipped plain-CSS/JSX Workbench (frontend/) with a React 18 + TypeScript + Vite + Tailwind + shadcn/Radix single-page app, wired to the real OpenKB REST API, with an Apple-design material/motion pass, light-default / dark-auto theming, and 中文 / English i18n. Adds the backend endpoints and runtime-config plumbing the new UI needs.

86 commits · 170 files · +21k / −4k. Squash-merge intended.

Frontend

  • Chat-first IA — Home launcher (recent sessions + grounded chat input with KB scope + slash-command generators /deck/skill/visualize), KB list with a real create-KB flow, KB detail (card-nav Overview: Index / Concepts / Entities / Summaries / Reports / Documents; opens index.md like a real wiki), per-KB settings gear.
  • Docked artifact panel — deck / graph / conversationally-generated output/**.html open in a sandboxed-iframe side panel (blob URL + bearer, never a token in a URL); chat shows compact "open" cards.
  • Interleaved chat trace — streamed narration + tool reads render as an ordered step trace (each read a click-through ✅ line) followed by the answer — live and on restore (the trace is persisted per turn, read-tool-only; sessions saved before this fall back to the flat text).
  • MarkdownView — fenced code, lazy Mermaid diagrams, [[wikilink|alias]], LaTeX math via KaTeX (bundled / self-contained), and --- rules.
  • Apple design — glass materials, critically-damped springs (motion), prefers-reduced-motion / -transparency, size-specific typography; a11y (Radix modal semantics for the settings sheet, labeled complementary region for the panel).
  • Settings — global-config editor (model / PageIndex threshold / wiki output language via a UN-official-languages combobox / global default credentials), an About tab, and theme + language toggles in the top-right chrome.
  • i18n — react-i18next, 8 lazily-loaded namespaces, 中文 default + English, plus a build guard (check-i18n.mjs) that fails on leftover UI CJK or zh/en key-set drift.
  • New deps pinned exactly (motion, mermaid, the i18next stack, katex).

Backend

  • entities added to GET /api/v1/list (+ get_kb_list).
  • New routers — GET /api/v1/graph/html (self-contained graph), GET /api/v1/output (path-guarded, output/-only HTML sink), GET/PATCH /api/v1/config (global defaults + credentials).
  • Runtime config unificationresolve_effective_config(kb_dir) layers defaults → ~/.config/openkb/global.yaml → KB .openkb/config.yaml for the global scalars, migrated across the load_config call-sites; RFC-7386 merge-patch with null-removal that preserves inheritance.
  • Chat — write-capable agent (emits an artifact SSE event on a write_file of output/**.html); per-turn trace persistence (ChatSession.assistant_traces), recording only read-tool calls so large generated file contents never hit the session JSON or the restore wire.
  • Global default credentials written to ~/.config/openkb/.env (0o600, atomic, under the global-config lock; the API key value is never returned by GET nor logged).

Testing

  • Backend — uv run pytest -q1175 passed; ruff check / ruff format clean.
  • Frontend — npm run build (tsc + vite) + the i18n:check guard green.
  • Manual browser verification across light/dark, both locales, and each surface (create-KB dialog, chat trace live + restore, KaTeX math + --- rules, settings credentials, artifact panel).
  • Each sub-project passed an opus whole-branch code review; findings were fixed and re-reviewed.

Notes

  • Configuration lives in files (global.yaml / per-KB config.yaml / .env), not startup input.
  • The local API is unauthenticated by default (opt-in bearer via OPENKB_API_TOKEN) — intended for local use.
  • Dependencies are pinned exactly (supply-chain caution).

https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG

…rtifact preview
Migrate ChatInput, ChatSession, and ArtifactCard off hardcoded
bg-white/bg-blue-*/border-black/text-neutral-* colors onto the Apple
token layer (.glass/.glass-2, border-[hsl(var(--glass-border))],
bg-accent-brand, text-foreground/text-muted-foreground). Fixes the
shared ChatInput box + slash-command menu rendering white against the
dark shell (flagged in Task 4). Source chips gain active:scale-[0.97]
+ transition duration-fast ease-out-apple press feedback.
Wrap the deck preview panel in motion AnimatePresence + a critically-
damped spring (bounce:0, 0.24s), degrading to an opacity-only cross-fade
under prefers-reduced-motion via useReducedMotion. The sandboxed iframe
(sandbox=allow-scripts, src={previewUrl} blob URL) and its blob-URL
revocation lifecycle are unchanged; the spring only wraps the panel.
Slash-command logic, streaming, citations, and session handling are
untouched. Semantic deck/skill/graph accent colors are preserved.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
KbDetail kept light-only semantic status colors (bg-*-50 / text-*-500 /
text-*-600) with no dark: pair, so red error cards and emerald/red status
pills rendered as bright light patches in dark mode. Add dark: variants
only (no light-mode, layout, or logic changes), matching the app-wide
pattern from ChatSession.tsx (red) and ArtifactCard.tsx StatusBadge
(emerald): dark:bg-red-500/10 dark:border-red-500/25 dark:text-red-400 and
dark:bg-emerald-500/10 dark:text-emerald-400.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
…el labeled region
KbSettingsSheet (modal): compose @radix-ui/react-dialog around the existing
motion.aside via asChild + forceMount so AnimatePresence keeps the D-era spring
and reduced-motion. Radix supplies focus trap, initial focus, and Escape-close;
add aria-modal + aria-labelledby (Dialog.Title on the heading) explicitly, and
drive focus return to the opening gear via on{Open,Close}AutoFocus (we open from
external state, so Radix's own triggerRef is null and would drop focus to body).
Scrim/backdrop-dismiss, config inherit/override, watch/recompile/lint, threshold
validation and all i18n preserved.
ArtifactPanel (non-modal docked): add role=complementary + aria-label (active
artifact label), aria-label on the header controls, and a one-time initial focus
to the panel on open. No focus trap and no background inert — Tab still flows out
to the chat. iframe/blob/new-tab/download, switcher, motion and i18n preserved.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
The KB list page had two "New knowledge base" controls (top-right button +
in-grid dashed card) and NEITHER had an onClick — the frontend never wired KB
creation. The sidebar's "+" was a third dead trigger.
- add createKb() -> POST /api/v1/init (api/kb.ts)
- new CreateKbDialog: Radix modal (Dialog.Trigger asChild for focus-trap/Escape/
focus-return), name-only field (model/creds inherit global defaults), inline
backend-error surfacing, success -> navigate to the new KB
- KbList: remove the dashed card; wire the single top-right button as the trigger
- AppSidebar: wire the "+" to the same dialog
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
Previously all streamed delta text (planning narration + the answer) was
concatenated into one blob and tool reads showed only as separate chips.
- chat.ts: fold SSE events into an ordered TurnStep[] preserving arrival order
(text segments interleaved with tool steps); tool_call marks the prior read
done; final reconciles the trailing text with the authoritative answer. The
flat `answer`/`sources`/`reading` fields are retained for restore/consumers.
- ChatSession.tsx: render steps in order — narration via MarkdownView, each read
as a status line (spinner -> green check), page reads stay click-through.
- drop `/compile` from the slash palette (runTurn never had a compile branch;
recompile lives on the KB gear) and remove its now-unused icon import + keys.
Chat is for Q&A + generation; KB-lifecycle actions live on the KB surface.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
…guage picker
Settings global config surface:
- backend: GET/PATCH /api/v1/config now also read/write global default credentials
(LLM_API_KEY / OPENAI_API_BASE) in ~/.config/openkb/.env, mirroring the per-KB
.env write (0o600, atomic, RFC-7386 null-removal, key value never returned/
logged), under the existing global-config lock. GlobalConfigResponse gains
openai_api_base + has_api_key; GlobalConfigPatchRequest gains api_key
(SecretStr) + openai_api_base. +6 tests.
- Settings.tsx: add a Default credentials section (masked key, set/unset hint,
deferred clear, API base) folded into the merge-patch save; remove the dead
brand/privacy footer card + its i18n key.
Wiki output language now offers the six official UN languages as a datalist
combobox (still free-text, so e.g. 日本語 works) in both the global Settings
and the per-KB gear. Also a one-line LanguageToggle doc-comment fix.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
…p the trace
Revisiting a saved session collapsed the interleaved trace back to one text
block, because only the flat answer was persisted. Now the ordered trace
(narration text + tool reads) is stored per turn and rebuilt on restore.
Backend:
- ChatSession gains `assistant_traces` (parallel to assistant_texts, 1:1 by
index); to_dict/load_session round-trip it; record_turn accepts a `trace` and
back-fills empty traces for older turns so a pre-existing session that gets a
new turn stays index-aligned. Old files without the key load as [] (no crash).
- iter_chat_turn_events accumulates the trace from delta/tool_call events (same
order the frontend folds live) and persists it via record_turn.
- ChatSessionLoadResponse returns assistant_traces (new ChatTraceStep model).
Frontend:
- loadSession exposes assistant_traces; ChatSession restore rebuilds steps via
stepsFromTrace (tool steps re-derived through the same read-only whitelist),
falling back to a single text step for trace-less (old / CLI) turns.
- fix a latent live bug: on `final`, do NOT overwrite the trailing text step
with final.answer (which is the full narration+answer concatenation) — that
duplicated narration once tool reads split the text; keep the streamed trace
and only inject the answer when nothing streamed.
Backward compatible: only sessions created after this change carry a trace;
older ones render their saved text as before.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
Review follow-up. The trace recorded every tool_call, including write_file,
whose `arguments` carries the entire generated file `content`. That bloated the
session JSON and was shipped to the browser on restore only to be dropped by
the frontend's read-only whitelist. Gate recording to _TRACE_READ_TOOLS
(read_file / get_page_content) — the only tools the UI renders — which also
fixes a live-vs-restore text-grouping divergence a dropped write_file caused.
Plus a tidy-up of the languages.ts doc comment (drop raw CJK).
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
…n MarkdownView
Answers that contain LaTeX (\[ … \] block, \( … \) inline) and `---` rules
showed the raw delimiters/dashes as literal text. Add math + horizontal-rule
support to the hand-rolled MarkdownView: block/inline math render via KaTeX
(bundled + self-contained, no CDN; malformed TeX falls back to a code box), and
a `---`/`***`/`___` line renders an <hr>. Adds katex (pinned exact, matching the
mermaid/motion convention) + @types/katex.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
The About tab linked a "Website openkb.ai" that does not exist yet; remove the
link entry and its i18n key (both locales). The Globe icon stays (still used by
the Vectify AI company link).
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
Comment threadtests/test_cli.py
"""

def test_query_resolves_model_from_global_config(self, kb_dir, monkeypatch, tmp_path):
import openkb.cli as cli_mod
Comment threadtests/test_config.py
import asyncio

import openkb.agent.query as q
import openkb.config as cfg
Comment threadopenkb/api_models.py

# Single source of truth for the writable config keys (derived from the model
# above so the two never drift).
_KB_CONFIG_WRITABLE_KEYS = set(_KbConfigWritable.model_fields)
… / junk)
GET /api/v1/page serves pages verbatim, so every concept/entity/summary page's
OKF `--- … ---` frontmatter leaked into the reader as junk metadata lines — and,
now that MarkdownView renders `---` as an <hr>, as a horizontal rule at the very
top. Strip a leading frontmatter block before rendering (reader only; chat
answers carry no frontmatter and are untouched). Second-review follow-up.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
@KylinMountain
KylinMountain deleted the feat/workbench-kimi-adoption branch July 20, 2026 04:58
@KylinMountain

Copy link
Copy Markdown
CollaboratorAuthor

Superseded by #189 — the branch was renamed feat/workbench-kimi-adoptionfeat/workbench-adoption, which auto-closed this PR. Same commits, same content.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@KylinMountain
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat(workbench): Knowledge Workbench — new React/TS frontend + supporting backend by KylinMountain · Pull Request #188 · VectifyAI/OpenKB · GitHub
Skip to content

feat(workbench): Knowledge Workbench — new React/TS frontend + supporting backend - #188

Closed
KylinMountain wants to merge 87 commits into
mainfrom
feat/workbench-kimi-adoption
Closed

feat(workbench): Knowledge Workbench — new React/TS frontend + supporting backend#188
KylinMountain wants to merge 87 commits into
mainfrom
feat/workbench-kimi-adoption

Conversation

@KylinMountain

Copy link
Copy Markdown
Collaborator

What this is

Replaces the shipped plain-CSS/JSX Workbench (frontend/) with a React 18 + TypeScript + Vite + Tailwind + shadcn/Radix single-page app, wired to the real OpenKB REST API, with an Apple-design material/motion pass, light-default / dark-auto theming, and 中文 / English i18n. Adds the backend endpoints and runtime-config plumbing the new UI needs.

86 commits · 170 files · +21k / −4k. Squash-merge intended.

Frontend

  • Chat-first IA — Home launcher (recent sessions + grounded chat input with KB scope + slash-command generators /deck/skill/visualize), KB list with a real create-KB flow, KB detail (card-nav Overview: Index / Concepts / Entities / Summaries / Reports / Documents; opens index.md like a real wiki), per-KB settings gear.
  • Docked artifact panel — deck / graph / conversationally-generated output/**.html open in a sandboxed-iframe side panel (blob URL + bearer, never a token in a URL); chat shows compact "open" cards.
  • Interleaved chat trace — streamed narration + tool reads render as an ordered step trace (each read a click-through ✅ line) followed by the answer — live and on restore (the trace is persisted per turn, read-tool-only; sessions saved before this fall back to the flat text).
  • MarkdownView — fenced code, lazy Mermaid diagrams, [[wikilink|alias]], LaTeX math via KaTeX (bundled / self-contained), and --- rules.
  • Apple design — glass materials, critically-damped springs (motion), prefers-reduced-motion / -transparency, size-specific typography; a11y (Radix modal semantics for the settings sheet, labeled complementary region for the panel).
  • Settings — global-config editor (model / PageIndex threshold / wiki output language via a UN-official-languages combobox / global default credentials), an About tab, and theme + language toggles in the top-right chrome.
  • i18n — react-i18next, 8 lazily-loaded namespaces, 中文 default + English, plus a build guard (check-i18n.mjs) that fails on leftover UI CJK or zh/en key-set drift.
  • New deps pinned exactly (motion, mermaid, the i18next stack, katex).

Backend

  • entities added to GET /api/v1/list (+ get_kb_list).
  • New routers — GET /api/v1/graph/html (self-contained graph), GET /api/v1/output (path-guarded, output/-only HTML sink), GET/PATCH /api/v1/config (global defaults + credentials).
  • Runtime config unificationresolve_effective_config(kb_dir) layers defaults → ~/.config/openkb/global.yaml → KB .openkb/config.yaml for the global scalars, migrated across the load_config call-sites; RFC-7386 merge-patch with null-removal that preserves inheritance.
  • Chat — write-capable agent (emits an artifact SSE event on a write_file of output/**.html); per-turn trace persistence (ChatSession.assistant_traces), recording only read-tool calls so large generated file contents never hit the session JSON or the restore wire.
  • Global default credentials written to ~/.config/openkb/.env (0o600, atomic, under the global-config lock; the API key value is never returned by GET nor logged).

Testing

  • Backend — uv run pytest -q1175 passed; ruff check / ruff format clean.
  • Frontend — npm run build (tsc + vite) + the i18n:check guard green.
  • Manual browser verification across light/dark, both locales, and each surface (create-KB dialog, chat trace live + restore, KaTeX math + --- rules, settings credentials, artifact panel).
  • Each sub-project passed an opus whole-branch code review; findings were fixed and re-reviewed.

Notes

  • Configuration lives in files (global.yaml / per-KB config.yaml / .env), not startup input.
  • The local API is unauthenticated by default (opt-in bearer via OPENKB_API_TOKEN) — intended for local use.
  • Dependencies are pinned exactly (supply-chain caution).

https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG

…rtifact preview
Migrate ChatInput, ChatSession, and ArtifactCard off hardcoded
bg-white/bg-blue-*/border-black/text-neutral-* colors onto the Apple
token layer (.glass/.glass-2, border-[hsl(var(--glass-border))],
bg-accent-brand, text-foreground/text-muted-foreground). Fixes the
shared ChatInput box + slash-command menu rendering white against the
dark shell (flagged in Task 4). Source chips gain active:scale-[0.97]
+ transition duration-fast ease-out-apple press feedback.
Wrap the deck preview panel in motion AnimatePresence + a critically-
damped spring (bounce:0, 0.24s), degrading to an opacity-only cross-fade
under prefers-reduced-motion via useReducedMotion. The sandboxed iframe
(sandbox=allow-scripts, src={previewUrl} blob URL) and its blob-URL
revocation lifecycle are unchanged; the spring only wraps the panel.
Slash-command logic, streaming, citations, and session handling are
untouched. Semantic deck/skill/graph accent colors are preserved.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
KbDetail kept light-only semantic status colors (bg-*-50 / text-*-500 /
text-*-600) with no dark: pair, so red error cards and emerald/red status
pills rendered as bright light patches in dark mode. Add dark: variants
only (no light-mode, layout, or logic changes), matching the app-wide
pattern from ChatSession.tsx (red) and ArtifactCard.tsx StatusBadge
(emerald): dark:bg-red-500/10 dark:border-red-500/25 dark:text-red-400 and
dark:bg-emerald-500/10 dark:text-emerald-400.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
…el labeled region
KbSettingsSheet (modal): compose @radix-ui/react-dialog around the existing
motion.aside via asChild + forceMount so AnimatePresence keeps the D-era spring
and reduced-motion. Radix supplies focus trap, initial focus, and Escape-close;
add aria-modal + aria-labelledby (Dialog.Title on the heading) explicitly, and
drive focus return to the opening gear via on{Open,Close}AutoFocus (we open from
external state, so Radix's own triggerRef is null and would drop focus to body).
Scrim/backdrop-dismiss, config inherit/override, watch/recompile/lint, threshold
validation and all i18n preserved.
ArtifactPanel (non-modal docked): add role=complementary + aria-label (active
artifact label), aria-label on the header controls, and a one-time initial focus
to the panel on open. No focus trap and no background inert — Tab still flows out
to the chat. iframe/blob/new-tab/download, switcher, motion and i18n preserved.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
The KB list page had two "New knowledge base" controls (top-right button +
in-grid dashed card) and NEITHER had an onClick — the frontend never wired KB
creation. The sidebar's "+" was a third dead trigger.
- add createKb() -> POST /api/v1/init (api/kb.ts)
- new CreateKbDialog: Radix modal (Dialog.Trigger asChild for focus-trap/Escape/
focus-return), name-only field (model/creds inherit global defaults), inline
backend-error surfacing, success -> navigate to the new KB
- KbList: remove the dashed card; wire the single top-right button as the trigger
- AppSidebar: wire the "+" to the same dialog
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
Previously all streamed delta text (planning narration + the answer) was
concatenated into one blob and tool reads showed only as separate chips.
- chat.ts: fold SSE events into an ordered TurnStep[] preserving arrival order
(text segments interleaved with tool steps); tool_call marks the prior read
done; final reconciles the trailing text with the authoritative answer. The
flat `answer`/`sources`/`reading` fields are retained for restore/consumers.
- ChatSession.tsx: render steps in order — narration via MarkdownView, each read
as a status line (spinner -> green check), page reads stay click-through.
- drop `/compile` from the slash palette (runTurn never had a compile branch;
recompile lives on the KB gear) and remove its now-unused icon import + keys.
Chat is for Q&A + generation; KB-lifecycle actions live on the KB surface.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
…guage picker
Settings global config surface:
- backend: GET/PATCH /api/v1/config now also read/write global default credentials
(LLM_API_KEY / OPENAI_API_BASE) in ~/.config/openkb/.env, mirroring the per-KB
.env write (0o600, atomic, RFC-7386 null-removal, key value never returned/
logged), under the existing global-config lock. GlobalConfigResponse gains
openai_api_base + has_api_key; GlobalConfigPatchRequest gains api_key
(SecretStr) + openai_api_base. +6 tests.
- Settings.tsx: add a Default credentials section (masked key, set/unset hint,
deferred clear, API base) folded into the merge-patch save; remove the dead
brand/privacy footer card + its i18n key.
Wiki output language now offers the six official UN languages as a datalist
combobox (still free-text, so e.g. 日本語 works) in both the global Settings
and the per-KB gear. Also a one-line LanguageToggle doc-comment fix.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
…p the trace
Revisiting a saved session collapsed the interleaved trace back to one text
block, because only the flat answer was persisted. Now the ordered trace
(narration text + tool reads) is stored per turn and rebuilt on restore.
Backend:
- ChatSession gains `assistant_traces` (parallel to assistant_texts, 1:1 by
index); to_dict/load_session round-trip it; record_turn accepts a `trace` and
back-fills empty traces for older turns so a pre-existing session that gets a
new turn stays index-aligned. Old files without the key load as [] (no crash).
- iter_chat_turn_events accumulates the trace from delta/tool_call events (same
order the frontend folds live) and persists it via record_turn.
- ChatSessionLoadResponse returns assistant_traces (new ChatTraceStep model).
Frontend:
- loadSession exposes assistant_traces; ChatSession restore rebuilds steps via
stepsFromTrace (tool steps re-derived through the same read-only whitelist),
falling back to a single text step for trace-less (old / CLI) turns.
- fix a latent live bug: on `final`, do NOT overwrite the trailing text step
with final.answer (which is the full narration+answer concatenation) — that
duplicated narration once tool reads split the text; keep the streamed trace
and only inject the answer when nothing streamed.
Backward compatible: only sessions created after this change carry a trace;
older ones render their saved text as before.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
Review follow-up. The trace recorded every tool_call, including write_file,
whose `arguments` carries the entire generated file `content`. That bloated the
session JSON and was shipped to the browser on restore only to be dropped by
the frontend's read-only whitelist. Gate recording to _TRACE_READ_TOOLS
(read_file / get_page_content) — the only tools the UI renders — which also
fixes a live-vs-restore text-grouping divergence a dropped write_file caused.
Plus a tidy-up of the languages.ts doc comment (drop raw CJK).
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
…n MarkdownView
Answers that contain LaTeX (\[ … \] block, \( … \) inline) and `---` rules
showed the raw delimiters/dashes as literal text. Add math + horizontal-rule
support to the hand-rolled MarkdownView: block/inline math render via KaTeX
(bundled + self-contained, no CDN; malformed TeX falls back to a code box), and
a `---`/`***`/`___` line renders an <hr>. Adds katex (pinned exact, matching the
mermaid/motion convention) + @types/katex.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
The About tab linked a "Website openkb.ai" that does not exist yet; remove the
link entry and its i18n key (both locales). The Globe icon stays (still used by
the Vectify AI company link).
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
Comment threadtests/test_cli.py
"""

def test_query_resolves_model_from_global_config(self, kb_dir, monkeypatch, tmp_path):
import openkb.cli as cli_mod
Comment threadtests/test_config.py
import asyncio

import openkb.agent.query as q
import openkb.config as cfg
Comment threadopenkb/api_models.py

# Single source of truth for the writable config keys (derived from the model
# above so the two never drift).
_KB_CONFIG_WRITABLE_KEYS = set(_KbConfigWritable.model_fields)
… / junk)
GET /api/v1/page serves pages verbatim, so every concept/entity/summary page's
OKF `--- … ---` frontmatter leaked into the reader as junk metadata lines — and,
now that MarkdownView renders `---` as an <hr>, as a horizontal rule at the very
top. Strip a leading frontmatter block before rendering (reader only; chat
answers carry no frontmatter and are untouched). Second-review follow-up.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
@KylinMountain
KylinMountain deleted the feat/workbench-kimi-adoption branch July 20, 2026 04:58
@KylinMountain

Copy link
Copy Markdown
CollaboratorAuthor

Superseded by #189 — the branch was renamed feat/workbench-kimi-adoptionfeat/workbench-adoption, which auto-closed this PR. Same commits, same content.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@KylinMountain
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); feat(workbench): Knowledge Workbench — new React/TS frontend + supporting backend by KylinMountain · Pull Request #188 · VectifyAI/OpenKB · GitHub
Skip to content

feat(workbench): Knowledge Workbench — new React/TS frontend + supporting backend - #188

Closed
KylinMountain wants to merge 87 commits into
mainfrom
feat/workbench-kimi-adoption
Closed

feat(workbench): Knowledge Workbench — new React/TS frontend + supporting backend#188
KylinMountain wants to merge 87 commits into
mainfrom
feat/workbench-kimi-adoption

Conversation

@KylinMountain

Copy link
Copy Markdown
Collaborator

What this is

Replaces the shipped plain-CSS/JSX Workbench (frontend/) with a React 18 + TypeScript + Vite + Tailwind + shadcn/Radix single-page app, wired to the real OpenKB REST API, with an Apple-design material/motion pass, light-default / dark-auto theming, and 中文 / English i18n. Adds the backend endpoints and runtime-config plumbing the new UI needs.

86 commits · 170 files · +21k / −4k. Squash-merge intended.

Frontend

  • Chat-first IA — Home launcher (recent sessions + grounded chat input with KB scope + slash-command generators /deck/skill/visualize), KB list with a real create-KB flow, KB detail (card-nav Overview: Index / Concepts / Entities / Summaries / Reports / Documents; opens index.md like a real wiki), per-KB settings gear.
  • Docked artifact panel — deck / graph / conversationally-generated output/**.html open in a sandboxed-iframe side panel (blob URL + bearer, never a token in a URL); chat shows compact "open" cards.
  • Interleaved chat trace — streamed narration + tool reads render as an ordered step trace (each read a click-through ✅ line) followed by the answer — live and on restore (the trace is persisted per turn, read-tool-only; sessions saved before this fall back to the flat text).
  • MarkdownView — fenced code, lazy Mermaid diagrams, [[wikilink|alias]], LaTeX math via KaTeX (bundled / self-contained), and --- rules.
  • Apple design — glass materials, critically-damped springs (motion), prefers-reduced-motion / -transparency, size-specific typography; a11y (Radix modal semantics for the settings sheet, labeled complementary region for the panel).
  • Settings — global-config editor (model / PageIndex threshold / wiki output language via a UN-official-languages combobox / global default credentials), an About tab, and theme + language toggles in the top-right chrome.
  • i18n — react-i18next, 8 lazily-loaded namespaces, 中文 default + English, plus a build guard (check-i18n.mjs) that fails on leftover UI CJK or zh/en key-set drift.
  • New deps pinned exactly (motion, mermaid, the i18next stack, katex).

Backend

  • entities added to GET /api/v1/list (+ get_kb_list).
  • New routers — GET /api/v1/graph/html (self-contained graph), GET /api/v1/output (path-guarded, output/-only HTML sink), GET/PATCH /api/v1/config (global defaults + credentials).
  • Runtime config unificationresolve_effective_config(kb_dir) layers defaults → ~/.config/openkb/global.yaml → KB .openkb/config.yaml for the global scalars, migrated across the load_config call-sites; RFC-7386 merge-patch with null-removal that preserves inheritance.
  • Chat — write-capable agent (emits an artifact SSE event on a write_file of output/**.html); per-turn trace persistence (ChatSession.assistant_traces), recording only read-tool calls so large generated file contents never hit the session JSON or the restore wire.
  • Global default credentials written to ~/.config/openkb/.env (0o600, atomic, under the global-config lock; the API key value is never returned by GET nor logged).

Testing

  • Backend — uv run pytest -q1175 passed; ruff check / ruff format clean.
  • Frontend — npm run build (tsc + vite) + the i18n:check guard green.
  • Manual browser verification across light/dark, both locales, and each surface (create-KB dialog, chat trace live + restore, KaTeX math + --- rules, settings credentials, artifact panel).
  • Each sub-project passed an opus whole-branch code review; findings were fixed and re-reviewed.

Notes

  • Configuration lives in files (global.yaml / per-KB config.yaml / .env), not startup input.
  • The local API is unauthenticated by default (opt-in bearer via OPENKB_API_TOKEN) — intended for local use.
  • Dependencies are pinned exactly (supply-chain caution).

https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG

…rtifact preview
Migrate ChatInput, ChatSession, and ArtifactCard off hardcoded
bg-white/bg-blue-*/border-black/text-neutral-* colors onto the Apple
token layer (.glass/.glass-2, border-[hsl(var(--glass-border))],
bg-accent-brand, text-foreground/text-muted-foreground). Fixes the
shared ChatInput box + slash-command menu rendering white against the
dark shell (flagged in Task 4). Source chips gain active:scale-[0.97]
+ transition duration-fast ease-out-apple press feedback.
Wrap the deck preview panel in motion AnimatePresence + a critically-
damped spring (bounce:0, 0.24s), degrading to an opacity-only cross-fade
under prefers-reduced-motion via useReducedMotion. The sandboxed iframe
(sandbox=allow-scripts, src={previewUrl} blob URL) and its blob-URL
revocation lifecycle are unchanged; the spring only wraps the panel.
Slash-command logic, streaming, citations, and session handling are
untouched. Semantic deck/skill/graph accent colors are preserved.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
KbDetail kept light-only semantic status colors (bg-*-50 / text-*-500 /
text-*-600) with no dark: pair, so red error cards and emerald/red status
pills rendered as bright light patches in dark mode. Add dark: variants
only (no light-mode, layout, or logic changes), matching the app-wide
pattern from ChatSession.tsx (red) and ArtifactCard.tsx StatusBadge
(emerald): dark:bg-red-500/10 dark:border-red-500/25 dark:text-red-400 and
dark:bg-emerald-500/10 dark:text-emerald-400.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
…el labeled region
KbSettingsSheet (modal): compose @radix-ui/react-dialog around the existing
motion.aside via asChild + forceMount so AnimatePresence keeps the D-era spring
and reduced-motion. Radix supplies focus trap, initial focus, and Escape-close;
add aria-modal + aria-labelledby (Dialog.Title on the heading) explicitly, and
drive focus return to the opening gear via on{Open,Close}AutoFocus (we open from
external state, so Radix's own triggerRef is null and would drop focus to body).
Scrim/backdrop-dismiss, config inherit/override, watch/recompile/lint, threshold
validation and all i18n preserved.
ArtifactPanel (non-modal docked): add role=complementary + aria-label (active
artifact label), aria-label on the header controls, and a one-time initial focus
to the panel on open. No focus trap and no background inert — Tab still flows out
to the chat. iframe/blob/new-tab/download, switcher, motion and i18n preserved.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
The KB list page had two "New knowledge base" controls (top-right button +
in-grid dashed card) and NEITHER had an onClick — the frontend never wired KB
creation. The sidebar's "+" was a third dead trigger.
- add createKb() -> POST /api/v1/init (api/kb.ts)
- new CreateKbDialog: Radix modal (Dialog.Trigger asChild for focus-trap/Escape/
focus-return), name-only field (model/creds inherit global defaults), inline
backend-error surfacing, success -> navigate to the new KB
- KbList: remove the dashed card; wire the single top-right button as the trigger
- AppSidebar: wire the "+" to the same dialog
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
Previously all streamed delta text (planning narration + the answer) was
concatenated into one blob and tool reads showed only as separate chips.
- chat.ts: fold SSE events into an ordered TurnStep[] preserving arrival order
(text segments interleaved with tool steps); tool_call marks the prior read
done; final reconciles the trailing text with the authoritative answer. The
flat `answer`/`sources`/`reading` fields are retained for restore/consumers.
- ChatSession.tsx: render steps in order — narration via MarkdownView, each read
as a status line (spinner -> green check), page reads stay click-through.
- drop `/compile` from the slash palette (runTurn never had a compile branch;
recompile lives on the KB gear) and remove its now-unused icon import + keys.
Chat is for Q&A + generation; KB-lifecycle actions live on the KB surface.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
…guage picker
Settings global config surface:
- backend: GET/PATCH /api/v1/config now also read/write global default credentials
(LLM_API_KEY / OPENAI_API_BASE) in ~/.config/openkb/.env, mirroring the per-KB
.env write (0o600, atomic, RFC-7386 null-removal, key value never returned/
logged), under the existing global-config lock. GlobalConfigResponse gains
openai_api_base + has_api_key; GlobalConfigPatchRequest gains api_key
(SecretStr) + openai_api_base. +6 tests.
- Settings.tsx: add a Default credentials section (masked key, set/unset hint,
deferred clear, API base) folded into the merge-patch save; remove the dead
brand/privacy footer card + its i18n key.
Wiki output language now offers the six official UN languages as a datalist
combobox (still free-text, so e.g. 日本語 works) in both the global Settings
and the per-KB gear. Also a one-line LanguageToggle doc-comment fix.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
…p the trace
Revisiting a saved session collapsed the interleaved trace back to one text
block, because only the flat answer was persisted. Now the ordered trace
(narration text + tool reads) is stored per turn and rebuilt on restore.
Backend:
- ChatSession gains `assistant_traces` (parallel to assistant_texts, 1:1 by
index); to_dict/load_session round-trip it; record_turn accepts a `trace` and
back-fills empty traces for older turns so a pre-existing session that gets a
new turn stays index-aligned. Old files without the key load as [] (no crash).
- iter_chat_turn_events accumulates the trace from delta/tool_call events (same
order the frontend folds live) and persists it via record_turn.
- ChatSessionLoadResponse returns assistant_traces (new ChatTraceStep model).
Frontend:
- loadSession exposes assistant_traces; ChatSession restore rebuilds steps via
stepsFromTrace (tool steps re-derived through the same read-only whitelist),
falling back to a single text step for trace-less (old / CLI) turns.
- fix a latent live bug: on `final`, do NOT overwrite the trailing text step
with final.answer (which is the full narration+answer concatenation) — that
duplicated narration once tool reads split the text; keep the streamed trace
and only inject the answer when nothing streamed.
Backward compatible: only sessions created after this change carry a trace;
older ones render their saved text as before.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
Review follow-up. The trace recorded every tool_call, including write_file,
whose `arguments` carries the entire generated file `content`. That bloated the
session JSON and was shipped to the browser on restore only to be dropped by
the frontend's read-only whitelist. Gate recording to _TRACE_READ_TOOLS
(read_file / get_page_content) — the only tools the UI renders — which also
fixes a live-vs-restore text-grouping divergence a dropped write_file caused.
Plus a tidy-up of the languages.ts doc comment (drop raw CJK).
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
…n MarkdownView
Answers that contain LaTeX (\[ … \] block, \( … \) inline) and `---` rules
showed the raw delimiters/dashes as literal text. Add math + horizontal-rule
support to the hand-rolled MarkdownView: block/inline math render via KaTeX
(bundled + self-contained, no CDN; malformed TeX falls back to a code box), and
a `---`/`***`/`___` line renders an <hr>. Adds katex (pinned exact, matching the
mermaid/motion convention) + @types/katex.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
The About tab linked a "Website openkb.ai" that does not exist yet; remove the
link entry and its i18n key (both locales). The Globe icon stays (still used by
the Vectify AI company link).
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
Comment threadtests/test_cli.py
"""

def test_query_resolves_model_from_global_config(self, kb_dir, monkeypatch, tmp_path):
import openkb.cli as cli_mod
Comment threadtests/test_config.py
import asyncio

import openkb.agent.query as q
import openkb.config as cfg
Comment threadopenkb/api_models.py

# Single source of truth for the writable config keys (derived from the model
# above so the two never drift).
_KB_CONFIG_WRITABLE_KEYS = set(_KbConfigWritable.model_fields)
… / junk)
GET /api/v1/page serves pages verbatim, so every concept/entity/summary page's
OKF `--- … ---` frontmatter leaked into the reader as junk metadata lines — and,
now that MarkdownView renders `---` as an <hr>, as a horizontal rule at the very
top. Strip a leading frontmatter block before rendering (reader only; chat
answers carry no frontmatter and are untouched). Second-review follow-up.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
@KylinMountain
KylinMountain deleted the feat/workbench-kimi-adoption branch July 20, 2026 04:58
@KylinMountain

Copy link
Copy Markdown
CollaboratorAuthor

Superseded by #189 — the branch was renamed feat/workbench-kimi-adoptionfeat/workbench-adoption, which auto-closed this PR. Same commits, same content.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@KylinMountain