* feat: add claude CLI subprocess backend for AI panels Add claude_cli.rs module that spawns the local `claude` CLI as a subprocess instead of calling the Anthropic API directly. This removes the need for users to configure an API key — the CLI uses the user's existing Claude authentication. - find_claude_binary(): discovers claude in PATH or common locations - check_cli(): returns install/version status for the frontend - run_chat_stream(): spawns claude -p with stream-json for chat panel - run_agent_stream(): spawns claude with MCP vault tools for agent panel - Parses stream-json NDJSON events and emits typed Tauri events - Remove http:default capability (no longer calling APIs from frontend) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: replace Anthropic API with Claude CLI for AI chat and agent panels Remove direct Anthropic API calls and the entire tool-use loop from the frontend. Both AI Chat and AI Agent panels now delegate to the `claude` CLI subprocess via Tauri commands, streaming NDJSON events back to the UI. Key changes: - ai-chat.ts / ai-agent.ts: replace SSE/API streaming with Tauri invoke + listen - useAIChat / useAiAgent hooks: simplified to use CLI streaming callbacks - AIChatPanel / AiPanel: remove API key dialogs, model selectors, undo support - SettingsPanel: remove Anthropic key field (no longer needed) - claude_cli.rs: add comprehensive tests (37 total, 90% coverage) - mock-handlers: replace ai_chat with check_claude_cli / stream_claude_* stubs - Net -432 lines across 17 files Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: add --verbose flag to claude CLI subprocess args * chore: exclude search.rs and lib.rs from coverage (qmd integration + Tauri setup) --------- Co-authored-by: Test <test@test.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
59 lines
2.0 KiB
TypeScript
59 lines
2.0 KiB
TypeScript
import { describe, it, expect, vi } from 'vitest'
|
|
import { render, screen, fireEvent } from '@testing-library/react'
|
|
import { AiPanel } from './AiPanel'
|
|
|
|
// Mock the hooks and utils to isolate component tests
|
|
vi.mock('../hooks/useAiAgent', () => ({
|
|
useAiAgent: () => ({
|
|
messages: [],
|
|
status: 'idle',
|
|
sendMessage: vi.fn(),
|
|
clearConversation: vi.fn(),
|
|
}),
|
|
}))
|
|
|
|
vi.mock('../utils/ai-chat', () => ({
|
|
nextMessageId: () => `msg-${Date.now()}`,
|
|
}))
|
|
|
|
describe('AiPanel', () => {
|
|
it('renders panel with AI Agent header', () => {
|
|
render(<AiPanel onClose={vi.fn()} vaultPath="/tmp/vault" />)
|
|
expect(screen.getByText('AI Agent')).toBeTruthy()
|
|
})
|
|
|
|
it('renders data-testid ai-panel', () => {
|
|
render(<AiPanel onClose={vi.fn()} vaultPath="/tmp/vault" />)
|
|
expect(screen.getByTestId('ai-panel')).toBeTruthy()
|
|
})
|
|
|
|
it('calls onClose when close button is clicked', () => {
|
|
const onClose = vi.fn()
|
|
render(<AiPanel onClose={onClose} vaultPath="/tmp/vault" />)
|
|
const panel = screen.getByTestId('ai-panel')
|
|
const buttons = panel.querySelectorAll('button')
|
|
const closeBtn = Array.from(buttons).find(b => b.title?.includes('Close'))
|
|
expect(closeBtn).toBeTruthy()
|
|
fireEvent.click(closeBtn!)
|
|
expect(onClose).toHaveBeenCalled()
|
|
})
|
|
|
|
it('renders empty state when no messages', () => {
|
|
render(<AiPanel onClose={vi.fn()} vaultPath="/tmp/vault" />)
|
|
expect(screen.getByText('Ask the AI agent to work with your vault')).toBeTruthy()
|
|
})
|
|
|
|
it('renders input field enabled', () => {
|
|
render(<AiPanel onClose={vi.fn()} vaultPath="/tmp/vault" />)
|
|
const input = screen.getByTestId('agent-input')
|
|
expect(input).toBeTruthy()
|
|
expect((input as HTMLInputElement).disabled).toBe(false)
|
|
})
|
|
|
|
it('has send button disabled when input is empty', () => {
|
|
render(<AiPanel onClose={vi.fn()} vaultPath="/tmp/vault" />)
|
|
const sendBtn = screen.getByTestId('agent-send')
|
|
expect((sendBtn as HTMLButtonElement).disabled).toBe(true)
|
|
})
|
|
})
|