mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-16 13:03:31 +08:00
Add complete server-side implementation for the Claude Code Desktop App UI: - REST API: sessions, conversations, settings, models, scheduled tasks, search, agents, and status endpoints (9 modules, 30+ endpoints) - WebSocket: real-time chat streaming with state transitions, ping/pong, permission request forwarding, and stop generation support - Services: sessionService (JSONL read/write, CLI-compatible), settingsService (atomic writes), cronService, searchService (ripgrep), agentService (YAML management) - Middleware: CORS (localhost-only), auth, unified error handling - Tests: 180 tests (unit + E2E + business flow), all passing - Docs: PRD, UI design spec, server architecture design Non-invasive: all new code under src/server/, no changes to existing CLI code. CLI/UI data interop: reads/writes the same JSONL/JSON files as the CLI. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
68 lines
2.3 KiB
TypeScript
68 lines
2.3 KiB
TypeScript
/**
|
|
* Search REST API
|
|
*
|
|
* POST /api/search — 全局工作区搜索
|
|
* POST /api/search/sessions — 搜索会话历史
|
|
*/
|
|
|
|
import { SearchService } from '../services/searchService.js'
|
|
import { ApiError, errorResponse } from '../middleware/errorHandler.js'
|
|
|
|
const searchService = new SearchService()
|
|
|
|
export async function handleSearchApi(
|
|
req: Request,
|
|
_url: URL,
|
|
segments: string[],
|
|
): Promise<Response> {
|
|
try {
|
|
const method = req.method
|
|
const sub = segments[2] // 'sessions' | undefined
|
|
|
|
if (method !== 'POST') {
|
|
throw new ApiError(
|
|
405,
|
|
`Method ${method} not allowed. Use POST.`,
|
|
'METHOD_NOT_ALLOWED',
|
|
)
|
|
}
|
|
|
|
const body = await parseJsonBody(req)
|
|
const query = body.query as string
|
|
if (!query || typeof query !== 'string') {
|
|
throw ApiError.badRequest('Missing or invalid "query" in request body')
|
|
}
|
|
|
|
// ── POST /api/search/sessions ──────────────────────────────────────────
|
|
if (sub === 'sessions') {
|
|
const results = await searchService.searchSessions(query)
|
|
return Response.json({ results })
|
|
}
|
|
|
|
// ── POST /api/search ───────────────────────────────────────────────────
|
|
if (!sub) {
|
|
const results = await searchService.searchWorkspace(query, {
|
|
cwd: body.cwd as string | undefined,
|
|
maxResults: body.maxResults as number | undefined,
|
|
glob: body.glob as string | undefined,
|
|
caseSensitive: body.caseSensitive as boolean | undefined,
|
|
})
|
|
return Response.json({ results, total: results.length })
|
|
}
|
|
|
|
throw ApiError.notFound(`Unknown search endpoint: ${sub}`)
|
|
} catch (error) {
|
|
return errorResponse(error)
|
|
}
|
|
}
|
|
|
|
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
|
|
async function parseJsonBody(req: Request): Promise<Record<string, unknown>> {
|
|
try {
|
|
return (await req.json()) as Record<string, unknown>
|
|
} catch {
|
|
throw ApiError.badRequest('Invalid JSON body')
|
|
}
|
|
}
|