From 494fc39c657bbbbdccbde812e329b98b14d319ba Mon Sep 17 00:00:00 2001 From: Gabriel Bo Date: Tue, 3 Mar 2026 18:54:35 -0800 Subject: [PATCH] fixing ui + adding diff fonts + fixing merge conflicts --- docs/index.md | 130 +++---- docs/stylesheets/extra.css | 407 +++++++++------------ frontend/index.html | 3 + frontend/src/components/Chat/InputArea.tsx | 21 +- frontend/src/components/Chat/MicButton.tsx | 105 ++++-- frontend/src/index.css | 6 +- frontend/src/lib/store.ts | 2 + frontend/src/pages/SettingsPage.tsx | 51 ++- mkdocs.yml | 2 +- 9 files changed, 367 insertions(+), 360 deletions(-) diff --git a/docs/index.md b/docs/index.md index 291dbf5d..d1d36d18 100644 --- a/docs/index.md +++ b/docs/index.md @@ -5,9 +5,7 @@ hide: - navigation --- -# OpenJarvis - -**Programming abstractions for on-device AI.** +# _Programming abstractions_ for on-device AI OpenJarvis is a modular framework for building, running, and learning from local AI systems. It provides composable abstractions across **five pillars** with a cross-cutting trace-driven learning system: @@ -29,55 +27,55 @@ Everything runs on your hardware. Cloud APIs are optional. --- - Intelligence (the model), Agents (agentic harness), Tools (MCP-based tool system with storage), Engine (inference runtime), and Learning (trace-driven improvement) — each with a clear ABC interface and decorator-based registry. + Intelligence, Agents, Tools, Engine, and Learning — each with a clear ABC interface and decorator-based registry. - **5 Engine Backends** --- - Ollama, vLLM, SGLang, llama.cpp, and cloud (OpenAI/Anthropic/Google). All implement the same `InferenceEngine` ABC with `generate()`, `stream()`, `list_models()`, and `health()`. + Ollama, vLLM, SGLang, llama.cpp, and cloud (OpenAI/Anthropic/Google). Same `InferenceEngine` ABC. - **5 Memory Backends** --- - SQLite/FTS5 (default, zero-dependency), FAISS, ColBERTv2, BM25, and Hybrid (reciprocal rank fusion). Document chunking, indexing, and context injection built in. + SQLite/FTS5, FAISS, ColBERTv2, BM25, and Hybrid (reciprocal rank fusion). Zero-dependency default. - **Hardware-Aware** --- - Auto-detects GPU vendor, model, and VRAM via `nvidia-smi`, `rocm-smi`, and `system_profiler`. Recommends the optimal engine for your hardware automatically. + Auto-detects GPU vendor, model, and VRAM. Recommends the optimal engine for your hardware. - **Offline-First** --- - All core functionality works without a network connection. Cloud API backends are optional extras for when you need them. + All core functionality works without a network connection. Cloud APIs are optional extras. - **OpenAI-Compatible API** --- - `jarvis serve` starts a FastAPI server with `POST /v1/chat/completions`, `GET /v1/models`, and SSE streaming. Drop-in replacement for OpenAI-compatible clients. + `jarvis serve` starts a FastAPI server with SSE streaming. Drop-in replacement for OpenAI clients. - **Trace-Driven Learning** --- - Every agent interaction is recorded as a trace. The learning system improves Intelligence (SFT weight updates) and Agents (system prompt, tool selection, retry logic). Pluggable policies: heuristic, trace-driven, SFT, agent advisor, ICL updater, GRPO. + Every interaction is traced. The learning system improves models (SFT) and agents (prompt, tools, logic). - **Python SDK** --- - The `Jarvis` class provides a high-level sync API. Three lines of code to ask a question. Full access to agents, tools, memory, and model routing. + The `Jarvis` class: three lines of code to ask a question. Full access to agents, tools, memory, and routing. - **CLI-First** --- - `jarvis ask`, `jarvis serve`, `jarvis memory`, `jarvis bench`, `jarvis telemetry` — every capability is accessible from the command line with rich terminal output. + `jarvis ask`, `jarvis serve`, `jarvis memory`, `jarvis bench` — every capability from the command line. @@ -85,86 +83,70 @@ Everything runs on your hardware. Cloud APIs are optional. ## Quick Start -
+=== "Browser App" -### Browser App + Run the full chat UI locally with one script: -Run the full chat UI locally with one script: + ```bash + git clone https://github.com/HazyResearch/OpenJarvis.git + cd OpenJarvis + ./scripts/quickstart.sh + ``` -```bash -git clone https://github.com/HazyResearch/OpenJarvis.git -cd OpenJarvis -./scripts/quickstart.sh -``` + This installs dependencies, starts Ollama + a local model, launches the backend + and frontend, and opens `http://localhost:5173` in your browser. -This installs dependencies, starts Ollama + a local model, launches the backend -and frontend, and opens `http://localhost:5173` in your browser. +=== "Desktop App" -
+ Download the native desktop app — it bundles Ollama and the Python backend + so everything works out of the box. -
+ [Download for macOS (Apple Silicon)](https://github.com/HazyResearch/OpenJarvis/releases/latest/download/OpenJarvis_aarch64.dmg){ .md-button .md-button--primary } -### Desktop App + Also available for [macOS (Intel)](https://github.com/HazyResearch/OpenJarvis/releases/latest/download/OpenJarvis_x64.dmg), [Windows](https://github.com/HazyResearch/OpenJarvis/releases/latest/download/OpenJarvis_x64-setup.exe), [Linux (DEB)](https://github.com/HazyResearch/OpenJarvis/releases/latest/download/OpenJarvis_amd64.deb), and [Linux (RPM)](https://github.com/HazyResearch/OpenJarvis/releases/latest/download/OpenJarvis_amd64.rpm). See the [Downloads](downloads.md) page for details. -Download the native desktop app — it bundles Ollama and the Python backend -so everything works out of the box. +=== "Python SDK" -[Download for macOS (Apple Silicon)](https://github.com/HazyResearch/OpenJarvis/releases/latest/download/OpenJarvis_aarch64.dmg){ .md-button .md-button--primary } + ```python + from openjarvis import Jarvis -Also available for [macOS (Intel)](https://github.com/HazyResearch/OpenJarvis/releases/latest/download/OpenJarvis_x64.dmg), [Windows](https://github.com/HazyResearch/OpenJarvis/releases/latest/download/OpenJarvis_x64-setup.exe), [Linux (DEB)](https://github.com/HazyResearch/OpenJarvis/releases/latest/download/OpenJarvis_amd64.deb), and [Linux (RPM)](https://github.com/HazyResearch/OpenJarvis/releases/latest/download/OpenJarvis_amd64.rpm). See the [Downloads](downloads.md) page for details. + j = Jarvis() + response = j.ask("Explain quicksort in two sentences.") + print(response) + j.close() + ``` -
+ For more control, use `ask_full()` to get usage stats, model info, and tool results: -
+ ```python + result = j.ask_full( + "What is 2 + 2?", + agent="orchestrator", + tools=["calculator"], + ) + print(result["content"]) # "4" + print(result["tool_results"]) # [{tool_name: "calculator", ...}] + ``` -### Python SDK +=== "CLI" -```python -from openjarvis import Jarvis + ```bash + # Ask a question + jarvis ask "What is the capital of France?" -j = Jarvis() -response = j.ask("Explain quicksort in two sentences.") -print(response) -j.close() -``` + # Use an agent with tools + jarvis ask --agent orchestrator --tools calculator,think "What is 137 * 42?" -For more control, use `ask_full()` to get usage stats, model info, and tool results: + # Start the API server + jarvis serve --port 8000 -```python -result = j.ask_full( - "What is 2 + 2?", - agent="orchestrator", - tools=["calculator"], -) -print(result["content"]) # "4" -print(result["tool_results"]) # [{tool_name: "calculator", ...}] -``` + # Index documents and search memory + jarvis memory index ./docs/ + jarvis memory search "configuration options" -
- -
- -### CLI - -```bash -# Ask a question -jarvis ask "What is the capital of France?" - -# Use an agent with tools -jarvis ask --agent orchestrator --tools calculator,think "What is 137 * 42?" - -# Start the API server -jarvis serve --port 8000 - -# Index documents and search memory -jarvis memory index ./docs/ -jarvis memory search "configuration options" - -# Run inference benchmarks -jarvis bench run --json -``` - -
+ # Run inference benchmarks + jarvis bench run --json + ``` --- diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css index 2c94a5a3..9b8fe137 100644 --- a/docs/stylesheets/extra.css +++ b/docs/stylesheets/extra.css @@ -1,200 +1,89 @@ /* ── Fonts ─────────────────────────────────────────────────────────── */ -@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap'); +@import url('https://fonts.googleapis.com/css2?family=Merriweather:ital,wght@0,300;0,400;0,700;0,900;1,300;1,400;1,700&family=Inter:wght@400;500;600;700&display=swap'); :root { - --md-text-font: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; + --md-text-font: "Merriweather", Georgia, "Times New Roman", serif; --md-code-font: "JetBrains Mono", "Fira Code", "SF Mono", monospace; } /* ── Color Overrides (Light) ──────────────────────────────────────── */ [data-md-color-scheme="default"] { - --md-primary-fg-color: #1e293b; - --md-primary-fg-color--light: #334155; - --md-primary-fg-color--dark: #0f172a; - --md-accent-fg-color: #3b82f6; - --md-accent-fg-color--transparent: rgba(59, 130, 246, 0.1); + --md-primary-fg-color: #1a1a2e; + --md-primary-fg-color--light: #2d2d44; + --md-primary-fg-color--dark: #0d0d1a; + --md-accent-fg-color: #4361ee; + --md-accent-fg-color--transparent: rgba(67, 97, 238, 0.08); --md-default-bg-color: #ffffff; - --md-default-fg-color: #1e293b; - --md-default-fg-color--light: #475569; - --md-typeset-a-color: #2563eb; + --md-default-fg-color: #1a1a2e; + --md-default-fg-color--light: #555577; + --md-typeset-a-color: #4361ee; } /* ── Color Overrides (Dark) ───────────────────────────────────────── */ [data-md-color-scheme="slate"] { - --md-primary-fg-color: #0f172a; - --md-primary-fg-color--light: #1e293b; - --md-primary-fg-color--dark: #020617; - --md-accent-fg-color: #60a5fa; - --md-accent-fg-color--transparent: rgba(96, 165, 250, 0.1); - --md-default-bg-color: #0f172a; - --md-default-fg-color: #e2e8f0; - --md-default-fg-color--light: #94a3b8; - --md-typeset-a-color: #60a5fa; - --md-code-bg-color: #1e293b; + --md-primary-fg-color: #0d0d1a; + --md-primary-fg-color--light: #1a1a2e; + --md-primary-fg-color--dark: #050510; + --md-accent-fg-color: #7b8cde; + --md-accent-fg-color--transparent: rgba(123, 140, 222, 0.1); + --md-default-bg-color: #0d0d1a; + --md-default-fg-color: #e0e0ec; + --md-default-fg-color--light: #8888aa; + --md-typeset-a-color: #7b8cde; + --md-code-bg-color: #161628; } -/* ── Hero Section ─────────────────────────────────────────────────── */ -.hero { - padding: 4rem 0 3rem; - text-align: center; - position: relative; +/* ── Global Typography ───────────────────────────────────────────── */ +.md-typeset { + font-size: 0.92rem; + line-height: 1.8; } -.hero h1 { - font-size: 3.2rem; - font-weight: 800; - letter-spacing: -0.03em; - line-height: 1.1; - margin-bottom: 1rem; - background: linear-gradient(135deg, #1e293b 0%, #3b82f6 100%); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; +/* Nav and UI chrome use a clean sans-serif */ +.md-header, +.md-tabs, +.md-nav, +.md-footer, +.md-search__input, +.md-source { + font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; } -[data-md-color-scheme="slate"] .hero h1 { - background: linear-gradient(135deg, #e2e8f0 0%, #60a5fa 100%); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; +.md-nav__link, +.md-tabs__link { + font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-weight: 500; } -.hero .hero-tagline { - font-size: 1.25rem; +/* ── Hero Section (DSPy-style: clean, italic emphasis) ───────────── */ +.md-typeset h1 { + font-size: 2.6rem; + font-weight: 900; + letter-spacing: -0.02em; + line-height: 1.15; + margin-bottom: 0.5rem; + color: var(--md-default-fg-color); +} + +.md-typeset h1 em { + font-style: italic; + color: var(--md-accent-fg-color); +} + +.hero-tagline { + font-size: 1.15rem; color: var(--md-default-fg-color--light); max-width: 640px; - margin: 0 auto 2rem; - line-height: 1.6; + line-height: 1.7; font-weight: 400; -} - -.hero-buttons { - display: flex; - gap: 1rem; - justify-content: center; - flex-wrap: wrap; - margin-bottom: 2rem; -} - -.hero-buttons a { - display: inline-flex; - align-items: center; - gap: 0.5rem; - padding: 0.75rem 1.75rem; - border-radius: 8px; - font-weight: 600; - font-size: 0.95rem; - text-decoration: none; - transition: all 0.2s ease; -} - -.hero-buttons .btn-primary { - background: #3b82f6; - color: #fff; - box-shadow: 0 2px 8px rgba(59, 130, 246, 0.3); -} - -.hero-buttons .btn-primary:hover { - background: #2563eb; - box-shadow: 0 4px 16px rgba(59, 130, 246, 0.4); - transform: translateY(-1px); -} - -.hero-buttons .btn-secondary { - background: transparent; - color: var(--md-default-fg-color); - border: 1.5px solid var(--md-default-fg-color--light); -} - -.hero-buttons .btn-secondary:hover { - border-color: var(--md-accent-fg-color); - color: var(--md-accent-fg-color); - transform: translateY(-1px); -} - -/* ── Feature Cards ────────────────────────────────────────────────── */ -.md-typeset .grid.cards > ol, -.md-typeset .grid.cards > ul { - gap: 1rem; -} - -.md-typeset .grid.cards > ol > li, -.md-typeset .grid.cards > ul > li { - border: 1px solid rgba(0, 0, 0, 0.08); - border-radius: 12px; - padding: 1.5rem; - transition: all 0.25s ease; - background: var(--md-default-bg-color); -} - -[data-md-color-scheme="slate"] .md-typeset .grid.cards > ol > li, -[data-md-color-scheme="slate"] .md-typeset .grid.cards > ul > li { - border-color: rgba(255, 255, 255, 0.08); - background: #1e293b; -} - -.md-typeset .grid.cards > ol > li:hover, -.md-typeset .grid.cards > ul > li:hover { - border-color: var(--md-accent-fg-color); - box-shadow: 0 4px 20px rgba(59, 130, 246, 0.1); - transform: translateY(-2px); -} - -.md-typeset .grid.cards > ol > li > :first-child, -.md-typeset .grid.cards > ul > li > :first-child { - font-weight: 600; - font-size: 1.05rem; -} - -/* ── Code Blocks ──────────────────────────────────────────────────── */ -.md-typeset code { - border-radius: 4px; - font-size: 0.85em; -} - -.md-typeset pre { - border-radius: 10px; - border: 1px solid rgba(0, 0, 0, 0.06); -} - -[data-md-color-scheme="slate"] .md-typeset pre { - border-color: rgba(255, 255, 255, 0.06); -} - -.md-typeset pre > code { - font-size: 0.85rem; - line-height: 1.65; -} - -/* ── Tabs ─────────────────────────────────────────────────────────── */ -.md-typeset .tabbed-labels > label { - font-weight: 500; - font-size: 0.9rem; -} - -/* ── Navigation ───────────────────────────────────────────────────── */ -.md-tabs { - background: var(--md-primary-fg-color); -} - -.md-header { - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08); -} - -/* ── Admonitions ──────────────────────────────────────────────────── */ -.md-typeset .admonition, -.md-typeset details { - border-radius: 8px; - border-width: 1px; - border-left-width: 4px; - box-shadow: none; + margin-bottom: 1.5rem; } /* ── Section Headers ──────────────────────────────────────────────── */ .md-typeset h2 { font-weight: 700; - font-size: 1.6rem; - letter-spacing: -0.02em; + font-size: 1.5rem; + letter-spacing: -0.015em; margin-top: 3rem; padding-top: 1.5rem; border-top: 1px solid rgba(0, 0, 0, 0.06); @@ -211,8 +100,93 @@ } .md-typeset h3 { + font-weight: 700; + font-size: 1.1rem; + letter-spacing: -0.01em; +} + +.md-typeset h4 { font-weight: 600; - font-size: 1.15rem; + font-size: 0.95rem; +} + +/* ── Feature Cards (simplified, DSPy-like) ───────────────────────── */ +.md-typeset .grid.cards > ol, +.md-typeset .grid.cards > ul { + gap: 0.75rem; +} + +.md-typeset .grid.cards > ol > li, +.md-typeset .grid.cards > ul > li { + border: 1px solid rgba(0, 0, 0, 0.08); + border-radius: 8px; + padding: 1.25rem 1.5rem; + transition: border-color 0.2s ease; + background: var(--md-default-bg-color); +} + +[data-md-color-scheme="slate"] .md-typeset .grid.cards > ol > li, +[data-md-color-scheme="slate"] .md-typeset .grid.cards > ul > li { + border-color: rgba(255, 255, 255, 0.08); + background: #161628; +} + +.md-typeset .grid.cards > ol > li:hover, +.md-typeset .grid.cards > ul > li:hover { + border-color: var(--md-accent-fg-color); +} + +.md-typeset .grid.cards > ol > li > :first-child, +.md-typeset .grid.cards > ul > li > :first-child { + font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-weight: 600; + font-size: 0.95rem; +} + +/* ── Code Blocks ──────────────────────────────────────────────────── */ +.md-typeset code { + border-radius: 4px; + font-size: 0.82em; +} + +.md-typeset pre { + border-radius: 8px; + border: 1px solid rgba(0, 0, 0, 0.06); +} + +[data-md-color-scheme="slate"] .md-typeset pre { + border-color: rgba(255, 255, 255, 0.06); +} + +.md-typeset pre > code { + font-size: 0.82rem; + line-height: 1.7; +} + +/* ── Tabs ─────────────────────────────────────────────────────────── */ +.md-typeset .tabbed-labels > label { + font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-weight: 500; + font-size: 0.85rem; +} + +/* ── Navigation ───────────────────────────────────────────────────── */ +.md-tabs { + background: var(--md-primary-fg-color); +} + +.md-header { + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06); +} + +/* ── Admonitions ──────────────────────────────────────────────────── */ +.md-typeset .admonition, +.md-typeset details { + border-radius: 8px; + border-width: 1px; + border-left-width: 4px; + box-shadow: none; + font-size: 0.88rem; } /* ── Footer ───────────────────────────────────────────────────────── */ @@ -220,14 +194,15 @@ background: var(--md-primary-fg-color--dark); } -/* ── Pill Badges (used on landing page) ───────────────────────────── */ +/* ── Pill Badges ─────────────────────────────────────────────────── */ .pill { display: inline-block; - padding: 0.3rem 0.9rem; + padding: 0.25rem 0.8rem; border-radius: 100px; - font-size: 0.8rem; + font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-size: 0.75rem; font-weight: 600; - letter-spacing: 0.02em; + letter-spacing: 0.03em; background: var(--md-accent-fg-color--transparent); color: var(--md-accent-fg-color); margin-bottom: 1rem; @@ -236,27 +211,13 @@ /* ── Divider ──────────────────────────────────────────────────────── */ .md-typeset hr { border-color: rgba(0, 0, 0, 0.06); - margin: 3rem 0; + margin: 2.5rem 0; } [data-md-color-scheme="slate"] .md-typeset hr { border-color: rgba(255, 255, 255, 0.06); } -/* ── Responsive ───────────────────────────────────────────────────── */ -@media (max-width: 768px) { - .hero h1 { - font-size: 2.2rem; - } - .hero .hero-tagline { - font-size: 1.05rem; - } - .hero-buttons { - flex-direction: column; - align-items: center; - } -} - /* ── Inline Code ─────────────────────────────────────────────────── */ .md-typeset :not(pre) > code { transition: background 0.15s ease; @@ -271,17 +232,21 @@ transition: color 0.15s ease; } +.md-typeset a:hover { + text-decoration: underline; +} + /* ── Content Width ───────────────────────────────────────────────── */ .md-content__inner { - max-width: 52rem; + max-width: 50rem; } /* ── Tables ──────────────────────────────────────────────────────── */ .md-typeset table:not([class]) { - border-radius: 8px; + border-radius: 6px; overflow: hidden; border: 1px solid rgba(0, 0, 0, 0.08); - font-size: 0.85rem; + font-size: 0.83rem; } [data-md-color-scheme="slate"] .md-typeset table:not([class]) { @@ -289,58 +254,28 @@ } .md-typeset table:not([class]) th { + font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-weight: 600; - font-size: 0.8rem; + font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.04em; } -/* ── Quick-start section cards ────────────────────────────────────── */ -.quick-start-section { - border: 1px solid rgba(0, 0, 0, 0.08); - border-left: 4px solid var(--md-accent-fg-color); - border-radius: 10px; - padding: 1.5rem 2rem 1.25rem; - margin-bottom: 1.5rem; - background: var(--md-default-bg-color); - transition: border-color 0.2s ease, box-shadow 0.2s ease; +/* ── Quick Start Tabs (replaces old card sections) ───────────────── */ +.md-typeset .tabbed-set { + margin-top: 1rem; } -.quick-start-section:hover { - border-color: rgba(0, 0, 0, 0.12); - border-left-color: var(--md-accent-fg-color); - box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04); +.md-typeset .tabbed-content { + padding-top: 0.5rem; } -[data-md-color-scheme="slate"] .quick-start-section { - border-color: rgba(255, 255, 255, 0.08); - border-left-color: var(--md-accent-fg-color); - background: #1e293b; -} - -[data-md-color-scheme="slate"] .quick-start-section:hover { - border-color: rgba(255, 255, 255, 0.14); - border-left-color: var(--md-accent-fg-color); - box-shadow: 0 2px 12px rgba(0, 0, 0, 0.2); -} - -.quick-start-section h3 { - font-size: 1.3rem !important; - font-weight: 700 !important; - margin-top: 0 !important; - margin-bottom: 0.75rem; - padding-bottom: 0.5rem; - border-bottom: 1px solid rgba(0, 0, 0, 0.06); -} - -[data-md-color-scheme="slate"] .quick-start-section h3 { - border-bottom-color: rgba(255, 255, 255, 0.06); -} - -.quick-start-section p:last-child { - margin-bottom: 0; -} - -.quick-start-section pre:last-child { - margin-bottom: 0; +/* ── Responsive ───────────────────────────────────────────────────── */ +@media (max-width: 768px) { + .md-typeset h1 { + font-size: 1.8rem; + } + .hero-tagline { + font-size: 1rem; + } } diff --git a/frontend/index.html b/frontend/index.html index 61c096b8..67861cb4 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -7,6 +7,9 @@ + + + OpenJarvis diff --git a/frontend/src/components/Chat/InputArea.tsx b/frontend/src/components/Chat/InputArea.tsx index 384d1f0a..8acc118a 100644 --- a/frontend/src/components/Chat/InputArea.tsx +++ b/frontend/src/components/Chat/InputArea.tsx @@ -17,6 +17,7 @@ export function InputArea() { const selectedModel = useAppStore((s) => s.selectedModel); const streamState = useAppStore((s) => s.streamState); const messages = useAppStore((s) => s.messages); + const speechEnabled = useAppStore((s) => s.settings.speechEnabled); const createConversation = useAppStore((s) => s.createConversation); const addMessage = useAppStore((s) => s.addMessage); const updateLastAssistant = useAppStore((s) => s.updateLastAssistant); @@ -25,6 +26,13 @@ export function InputArea() { const { state: speechState, available: speechAvailable, startRecording, stopRecording } = useSpeech(); + const micDisabled = !speechEnabled || !speechAvailable || streamState.isStreaming; + const micReason: 'not-enabled' | 'no-backend' | 'streaming' | undefined = + !speechEnabled ? 'not-enabled' + : !speechAvailable ? 'no-backend' + : streamState.isStreaming ? 'streaming' + : undefined; + const handleMicClick = useCallback(async () => { if (speechState === 'recording') { try { @@ -257,13 +265,12 @@ export function InputArea() { ) : (
- {speechAvailable && ( - - )} + + {showTooltip && isInactive && ( +
+ {tooltipText} +
)} - +
); } diff --git a/frontend/src/index.css b/frontend/src/index.css index 972eb393..68e7a965 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -138,7 +138,9 @@ } body { - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + font-family: "Merriweather", Georgia, "Times New Roman", serif; + font-weight: 400; + line-height: 1.7; background-color: var(--color-bg); color: var(--color-text); -webkit-font-smoothing: antialiased; @@ -234,7 +236,7 @@ /* Markdown prose styling */ .prose { - line-height: 1.65; + line-height: 1.75; font-size: 0.9375rem; } diff --git a/frontend/src/lib/store.ts b/frontend/src/lib/store.ts index c73b5b15..a263da5e 100644 --- a/frontend/src/lib/store.ts +++ b/frontend/src/lib/store.ts @@ -51,6 +51,7 @@ interface Settings { defaultAgent: string; temperature: number; maxTokens: number; + speechEnabled: boolean; } function loadSettings(): Settings { @@ -62,6 +63,7 @@ function loadSettings(): Settings { defaultAgent: '', temperature: 0.7, maxTokens: 4096, + speechEnabled: false, }; try { const raw = localStorage.getItem(SETTINGS_KEY); diff --git a/frontend/src/pages/SettingsPage.tsx b/frontend/src/pages/SettingsPage.tsx index 82155399..82c9a8e2 100644 --- a/frontend/src/pages/SettingsPage.tsx +++ b/frontend/src/pages/SettingsPage.tsx @@ -13,9 +13,10 @@ import { Download, Upload, Trash2, + Mic, } from 'lucide-react'; import { useAppStore, type ThemeMode } from '../lib/store'; -import { checkHealth } from '../lib/api'; +import { checkHealth, fetchSpeechHealth } from '../lib/api'; function Section({ title, children }: { title: string; children: React.ReactNode }) { return ( @@ -57,10 +58,14 @@ export function SettingsPage() { const conversations = useAppStore((s) => s.conversations); const serverInfo = useAppStore((s) => s.serverInfo); const [healthy, setHealthy] = useState(null); + const [speechBackendAvailable, setSpeechBackendAvailable] = useState(null); const [saved, setSaved] = useState(false); useEffect(() => { checkHealth().then(setHealthy); + fetchSpeechHealth() + .then((h) => setSpeechBackendAvailable(h.available)) + .catch(() => setSpeechBackendAvailable(false)); }, []); const showSaved = () => { @@ -225,6 +230,50 @@ export function SettingsPage() { + {/* Speech */} +
+ + + + +
+ + + {speechBackendAvailable === null ? 'Checking...' + : speechBackendAvailable ? 'Available' + : 'Not configured'} + +
+
+ {!speechBackendAvailable && speechBackendAvailable !== null && ( +
+ Set up a speech backend to use voice input. + See the documentation for details. +
+ )} +
+ {/* Data */}
diff --git a/mkdocs.yml b/mkdocs.yml index c4c5ec11..073f0049 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -12,7 +12,7 @@ theme: name: material language: en font: - text: Inter + text: Merriweather code: JetBrains Mono features: - navigation.tabs