Skip to content

feat: add Fresh 2 web frontend - #6

Merged
g-eoj merged 7 commits into
mainfrom
feat/add-web-frontend
Feb 24, 2026
Merged

feat: add Fresh 2 web frontend#6
g-eoj merged 7 commits into
mainfrom
feat/add-web-frontend

Conversation

@g-eoj

@g-eoj g-eoj commented Feb 24, 2026

Copy link
Copy Markdown
Owner

Summary

  • Chat island — SSE streaming from Python backend, message history, auto-expanding textarea, keyboard shortcuts (Enter to submit, Shift+Enter for newline, ArrowUp/Down for history navigation)
  • ControlsPanel island — collapsible sidebar; section open/close state persisted to localStorage; AI settings (baseline + dynamic thinking effort, forget); search settings (references required, bookmark group filtering)
  • HealthStatus component — modal showing backend/vLLM reachability and env var status pulled from /api/health
  • Bookmark management — groups with inline URL add, checkbox filtering, drag-to-copy references from answers
  • API routes/api/config (reads librarian.config.json for port), /api/health (env var status from env.schema.json)
  • Styling — Tailwind v4 + DaisyUI + Typography; CSS custom properties for theming; custom scrollbar; animated collapsible sections; star-shaped range slider thumb

Changes vs initial code

  • Fixed ControlSection TODO: onToggle now syncs DOM open state back to signal; section state persisted via createPersistedSignal
  • Removed broken Ctrl+Z (was overriding textarea undo) and Ctrl+R (couldn't prevent browser refresh) shortcuts
  • Fixed misleading marked sanitization comment — marked does not sanitize HTML; content comes from the trusted local backend
  • Bumped all dependencies to latest: preact 10.28.4, @preact/signals 2.8.1, tailwindcss 4.2.1, vite 7.3.1, marked 17.0.3, daisyui 5.5.19

Test plan

  • deno task dev starts the Vite dev server
  • Chat input submits on Enter, newline on Shift+Enter
  • ArrowUp/Down cycles through query history
  • Controls panel collapses/expands and section state persists on refresh
  • Bookmark groups can be created, populated, and used to filter search
  • Health modal shows correct status for backend and env vars
  • Forget button clears session and message history

🤖 Generated with Claude Code

g-eoj and others added 2 commits February 24, 2026 15:11
Islands architecture with Preact + Vite + Tailwind v4:

- Chat island: SSE streaming from Python backend, message history,
  keyboard shortcuts (Enter submit, Shift+Enter newline, ArrowUp/Down
  history navigation)
- ControlsPanel island: collapsible sidebar with persisted open state,
  AI settings (thinking effort), search settings, bookmark groups
- HealthStatus component: modal showing backend/vLLM reachability
  and env var status
- Bookmark management: groups with drag-to-add URLs, checkbox filtering
- API routes: /api/config (port config), /api/health (env var status)
- Global CSS with custom properties for theming, custom scrollbar,
  animated collapsible sections, star-thumb range slider

Dependencies bumped to latest: preact 10.28.4, @preact/signals 2.8.1,
tailwindcss 4.2.1, vite 7.3.1, marked 17.0.3, daisyui 5.5.19.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
No DaisyUI component classes are used anywhere — all styling is custom
CSS and Tailwind utilities.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Feb 24, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds a complete Fresh 2 web frontend for the Librarian RAG application with SSE streaming chat, collapsible controls panel, bookmark management, and health monitoring.

Key additions:

  • Chat island with SSE streaming from Python backend, auto-expanding textarea, keyboard shortcuts (Enter/Shift+Enter, ArrowUp/Down for history)
  • ControlsPanel with AI settings (thinking effort controls, session reset) and search settings (references required, bookmark filtering)
  • Health modal showing backend/vLLM reachability and environment variable status
  • Bookmark groups with inline URL addition, checkbox filtering, and drag-to-copy references
  • API routes for config and health checks
  • Tailwind v4 + DaisyUI styling with custom theming and scrollbars

Issues found:

  • SSE JSON parsing in Chat.tsx lacks error handling and will crash on malformed data
  • Minor typo in ControlsPanel description

Dependencies updated:
All dependencies bumped to latest versions (preact 10.28.4, tailwindcss 4.2.1, marked 17.0.3, etc.)

Confidence Score: 4/5

  • Safe to merge after addressing JSON parsing error handling in SSE stream
  • Well-structured implementation with good separation of concerns, proper state management, and comprehensive features. One critical issue: missing error handling in SSE JSON parsing could crash the chat on malformed backend data. Minor typo also present.
  • Pay close attention to web/islands/Chat.tsx for the SSE parsing fix

Important Files Changed

Filename Overview
web/islands/Chat.tsx Core chat island with SSE streaming, history navigation, and keyboard shortcuts. SSE parsing lacks error handling for malformed JSON.
web/islands/ControlsPanel.tsx Collapsible settings panel with AI and search controls. Minor typo in description ("Effects" should be "Affects").
web/utils/appState.ts State management with localStorage persistence using signals. Clean implementation of createPersistedSignal helper.
web/components/HealthStatus.tsx Health check modal that fetches backend and vLLM status, plus environment variable validation. Clean implementation with proper error handling.
web/components/Answer.tsx Renders markdown answers with draggable reference links. Uses dangerouslySetInnerHTML with trusted local backend content (documented).
web/routes/api/health.ts API route that reads env schema and checks environment variable status. Proper error handling for missing schema.

Sequence Diagram

sequenceDiagram
    participant User
    participant Chat as Chat Island
    participant Backend as Python Backend
    participant vLLM
    participant Health as Health Modal
    participant Controls as ControlsPanel
    
    User->>Chat: Submit query (Enter)
    Chat->>Backend: POST /api/query (SSE)
    Backend->>vLLM: Process query
    Backend-->>Chat: event: node (RouterNode)
    Backend-->>Chat: event: node (ResearchNode)
    Backend-->>Chat: event: answer + references
    Chat->>Chat: Update messageHistory
    Chat->>User: Display answer + references
    
    User->>Controls: Click health status
    Controls->>Health: Open modal
    Health->>Backend: GET /api/health
    Backend->>vLLM: Check health
    Backend-->>Health: Service status
    Health->>Chat: GET /api/health (frontend)
    Chat-->>Health: Env var status
    Health->>User: Show health details
    
    User->>Controls: Configure bookmarks
    Controls->>Controls: Update localStorage
    User->>Chat: Submit filtered query
    Chat->>Backend: POST with allowed_sites
    Backend-->>Chat: Filtered results
Loading

Last reviewed commit: 2bbc5cf

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

24 files reviewed, 3 comments

Edit Code Review Agent Settings | Greptile

Comment thread web/islands/Chat.tsx Outdated
if (line.startsWith("event:")) {
eventType = line.slice(6).trim();
} else if (line.startsWith("data:")) {
const data = JSON.parse(line.slice(5).trim());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrap in try-catch to prevent crashes from malformed SSE data

Suggested change
const data = JSON.parse(line.slice(5).trim());
try {
const data = JSON.parse(line.slice(5).trim());

Comment thread web/islands/Chat.tsx
Comment on lines +126 to +156
for (const line of lines) {
if (line.startsWith("event:")) {
eventType = line.slice(6).trim();
} else if (line.startsWith("data:")) {
const data = JSON.parse(line.slice(5).trim());
if (eventType === "node") {
currentNode.value = data.node_type;
} else if (eventType === "answer") {
messageHistory.value = [
...messageHistory.value,
{
query: userQuery,
answer: String(data.answer),
references: data.references || [],
},
];
currentNode.value = null;
currentQuery.value = null;
} else if (eventType === "error") {
messageHistory.value = [
...messageHistory.value,
{
query: userQuery,
answer: `Error: ${data.message}`,
references: [],
},
];
currentNode.value = null;
currentQuery.value = null;
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JSON parsing can crash if SSE data is malformed. Wrap the parse and data handling in a try-catch:

for (const line of lines) {
  if (line.startsWith("event:")) {
    eventType = line.slice(6).trim();
  } else if (line.startsWith("data:")) {
    try {
      const data = JSON.parse(line.slice(5).trim());
      if (eventType === "node") {
        currentNode.value = data.node_type;
      } else if (eventType === "answer") {
        // ... rest of answer handling
      } else if (eventType === "error") {
        // ... rest of error handling
      }
    } catch (parseErr) {
      console.error("Failed to parse SSE data:", parseErr);
      // Continue processing other lines
    }
  }
}

Comment thread web/islands/ControlsPanel.tsx Outdated
<HealthStatus />
<ControlRow
label="Baseline Thinking Effort"
description="Effects the time it takes to answer queries."

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Effects" should be "Affects"

Suggested change
description="Effects the time it takes to answer queries."
description="Affects the time it takes to answer queries."

g-eoj and others added 4 commits February 24, 2026 15:25
- Fix "Effects" → "Affects" in thinking effort description
- Wrap SSE JSON.parse in try-catch so malformed data is skipped
  rather than crashing the stream handler

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
nodeModulesDir=manual requires an explicit install step so transitive
npm dependencies are present in node_modules before type-checking.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Prose line wrapping in markdown adds no value. Exclude *.md from
deno fmt so README can be written with natural line lengths.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Feb 24, 2026

Copy link
Copy Markdown

Greptile Summary

This PR introduces a comprehensive Fresh 2 web frontend for the Librarian chatbot, replacing the previous minimal setup with a full-featured interface. The implementation uses Preact signals for reactive state management, SSE streaming for real-time responses, and localStorage for session persistence.

Key changes:

  • Chat interface — SSE streaming from Python backend with real-time node updates, keyboard navigation (Enter to submit, Shift+Enter for newline, ArrowUp/Down for history), auto-expanding textarea
  • Controls sidebar — Collapsible panel with AI settings (thinking effort controls, session clearing) and search settings (reference requirements, bookmark group filtering with checkbox UI)
  • Health monitoring — Modal showing backend/vLLM reachability and environment variable status
  • Bookmark management — Groups with inline URL input, drag-to-copy references, localStorage persistence
  • API routes/api/config reads librarian.config.json for port configuration, /api/health checks environment variables against env.schema.json
  • Dependencies — Fresh 2.2.0, Vite 7.3.1, Tailwind v4.2.1, Preact 10.28.4, marked 17.0.3
  • CI updates — Added deno install step before running checks

Issues found:

  • Error handling in Chat.tsx uses implicit any type without type guard (syntax issue)

Confidence Score: 4/5

  • Safe to merge with one minor TypeScript error handling fix needed
  • The implementation is well-structured with proper SSE error handling (including try-catch for malformed JSON), localStorage persistence, and clean separation of concerns. One syntax issue with error type checking in Chat.tsx needs fixing. All components follow good practices with proper cleanup, the state management is solid, and the CI pipeline is properly configured.
  • web/islands/Chat.tsx requires a minor type guard fix for error handling

Important Files Changed

Filename Overview
web/islands/Chat.tsx SSE streaming chat interface with message history, keyboard shortcuts, and error handling
web/islands/ControlsPanel.tsx Collapsible controls sidebar with AI and search settings sections
web/utils/appState.ts Global signal-based state management with localStorage persistence
web/components/Controls.tsx Reusable control components (sections, toggles, sliders, inputs)
web/components/HealthStatus.tsx Modal displaying backend/vLLM health and environment variable status
web/routes/api/health.ts API route serving environment variable status from env.schema.json
web/routes/api/config.ts API route serving librarian.config.json
web/deno.json Deno configuration with Fresh 2, Vite, and updated dependencies

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    User[User Browser]
    Chat[Chat Island]
    Controls[ControlsPanel Island]
    AppState[appState.ts<br/>Persisted Signals]
    
    User -->|Enter query| Chat
    User -->|Adjust settings| Controls
    
    Chat -->|Read/Write| AppState
    Controls -->|Read/Write| AppState
    
    Chat -->|SSE stream| Backend[Python Backend<br/>localhost:8001]
    Chat -->|GET /api/config| ConfigAPI[config.ts route]
    Controls -->|GET /api/health| HealthAPI[health.ts route]
    
    Backend -->|node events| Chat
    Backend -->|answer events| Chat
    Backend -->|error events| Chat
    
    ConfigAPI -->|Read| Config[librarian.config.json]
    HealthAPI -->|Read| EnvSchema[env.schema.json]
    HealthAPI -->|Check env vars| Deno[Deno.env]
    
    AppState -->|Persist| LocalStorage[(localStorage)]
    
    Chat -->|Render| Answer[Answer Component<br/>marked parse]
    Chat -->|Render| Loading[LoadingIndicator]
    
    Controls -->|Manage| Bookmarks[Bookmark Groups]
    Bookmarks -->|Filter search| Backend
Loading

Last reviewed commit: f0611ac

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

26 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment thread web/islands/Chat.tsx Outdated
Comment on lines +163 to +164
} catch (err) {
if (err.name !== "AbortError") {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

err is implicitly any, needs type guard

Suggested change
} catch (err) {
if (err.name !== "AbortError") {
} catch (err) {
if (err instanceof Error && err.name !== "AbortError") {

Accessing .name/.message on an untyped catch variable is unsafe.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@g-eoj
g-eoj merged commit 8933059 into main Feb 24, 2026
2 checks passed
Sign up for free to 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