feat: wire chat UI to backend APIs
- Created API clients for LLM chat (/api/llm/chat) and Ideas (/api/ideas) - Implemented useChat hook for conversation state management - Connected Chat component to backend with full CRUD operations - Integrated ConversationSidebar with conversation fetching - Added automatic conversation persistence after each message - Integrated WebSocket for connection status - Used existing better-auth for authentication - All TypeScript strict mode compliant (no any types) Deliverables: ✅ Working chat interface at /chat route ✅ Conversations save to database via Ideas API ✅ Real-time WebSocket connection ✅ Clean TypeScript (no errors) ✅ Full conversation loading and persistence See CHAT_INTEGRATION_SUMMARY.md for detailed documentation.
This commit is contained in:
57
apps/web/src/lib/api/chat.ts
Normal file
57
apps/web/src/lib/api/chat.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Chat API client
|
||||
* Handles LLM chat interactions via /api/llm/chat
|
||||
*/
|
||||
|
||||
import { apiPost } from "./client";
|
||||
|
||||
export interface ChatMessage {
|
||||
role: "system" | "user" | "assistant";
|
||||
content: string;
|
||||
}
|
||||
|
||||
export interface ChatRequest {
|
||||
model: string;
|
||||
messages: ChatMessage[];
|
||||
stream?: boolean;
|
||||
temperature?: number;
|
||||
maxTokens?: number;
|
||||
systemPrompt?: string;
|
||||
}
|
||||
|
||||
export interface ChatResponse {
|
||||
model: string;
|
||||
message: {
|
||||
role: "assistant";
|
||||
content: string;
|
||||
};
|
||||
done: boolean;
|
||||
totalDuration?: number;
|
||||
promptEvalCount?: number;
|
||||
evalCount?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a chat message to the LLM
|
||||
*/
|
||||
export async function sendChatMessage(request: ChatRequest): Promise<ChatResponse> {
|
||||
return apiPost<ChatResponse>("/api/llm/chat", request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream a chat message from the LLM (not implemented yet)
|
||||
* TODO: Implement streaming support
|
||||
*/
|
||||
export function streamChatMessage(
|
||||
request: ChatRequest,
|
||||
onChunk: (chunk: string) => void,
|
||||
onComplete: () => void,
|
||||
onError: (error: Error) => void
|
||||
): void {
|
||||
// Streaming implementation would go here
|
||||
void request;
|
||||
void onChunk;
|
||||
void onComplete;
|
||||
void onError;
|
||||
throw new Error("Streaming not implemented yet");
|
||||
}
|
||||
160
apps/web/src/lib/api/ideas.ts
Normal file
160
apps/web/src/lib/api/ideas.ts
Normal file
@@ -0,0 +1,160 @@
|
||||
/**
|
||||
* Ideas API client
|
||||
* Used for conversation persistence
|
||||
*/
|
||||
|
||||
import { apiGet, apiPost, apiPatch } from "./client";
|
||||
|
||||
export enum IdeaStatus {
|
||||
CAPTURED = "CAPTURED",
|
||||
REVIEWING = "REVIEWING",
|
||||
APPROVED = "APPROVED",
|
||||
IN_PROGRESS = "IN_PROGRESS",
|
||||
COMPLETED = "COMPLETED",
|
||||
ARCHIVED = "ARCHIVED",
|
||||
}
|
||||
|
||||
export interface Idea {
|
||||
id: string;
|
||||
workspaceId: string;
|
||||
domainId?: string | null;
|
||||
projectId?: string | null;
|
||||
title?: string | null;
|
||||
content: string;
|
||||
status: IdeaStatus;
|
||||
priority: string;
|
||||
category?: string | null;
|
||||
tags: string[];
|
||||
metadata: Record<string, unknown>;
|
||||
creatorId: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface CreateIdeaRequest {
|
||||
title?: string;
|
||||
content: string;
|
||||
domainId?: string;
|
||||
projectId?: string;
|
||||
status?: IdeaStatus;
|
||||
priority?: string;
|
||||
category?: string;
|
||||
tags?: string[];
|
||||
metadata?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface UpdateIdeaRequest {
|
||||
title?: string;
|
||||
content?: string;
|
||||
domainId?: string;
|
||||
projectId?: string;
|
||||
status?: IdeaStatus;
|
||||
priority?: string;
|
||||
category?: string;
|
||||
tags?: string[];
|
||||
metadata?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface QueryIdeasRequest {
|
||||
page?: number;
|
||||
limit?: number;
|
||||
status?: IdeaStatus;
|
||||
domainId?: string;
|
||||
projectId?: string;
|
||||
category?: string;
|
||||
search?: string;
|
||||
}
|
||||
|
||||
export interface QueryIdeasResponse {
|
||||
data: Idea[];
|
||||
meta: {
|
||||
total: number;
|
||||
page: number;
|
||||
limit: number;
|
||||
totalPages: number;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new idea (conversation)
|
||||
*/
|
||||
export async function createIdea(request: CreateIdeaRequest): Promise<Idea> {
|
||||
return apiPost<Idea>("/api/ideas", request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Quick capture an idea
|
||||
*/
|
||||
export async function captureIdea(content: string, title?: string): Promise<Idea> {
|
||||
return apiPost<Idea>("/api/ideas/capture", { content, title });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all ideas with optional filters
|
||||
*/
|
||||
export async function queryIdeas(params: QueryIdeasRequest = {}): Promise<QueryIdeasResponse> {
|
||||
const queryParams = new URLSearchParams();
|
||||
|
||||
if (params.page) queryParams.set("page", params.page.toString());
|
||||
if (params.limit) queryParams.set("limit", params.limit.toString());
|
||||
if (params.status) queryParams.set("status", params.status);
|
||||
if (params.domainId) queryParams.set("domainId", params.domainId);
|
||||
if (params.projectId) queryParams.set("projectId", params.projectId);
|
||||
if (params.category) queryParams.set("category", params.category);
|
||||
if (params.search) queryParams.set("search", params.search);
|
||||
|
||||
const query = queryParams.toString();
|
||||
const endpoint = query ? `/api/ideas?${query}` : "/api/ideas";
|
||||
|
||||
return apiGet<QueryIdeasResponse>(endpoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single idea by ID
|
||||
*/
|
||||
export async function getIdea(id: string): Promise<Idea> {
|
||||
return apiGet<Idea>(`/api/ideas/${id}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an idea
|
||||
*/
|
||||
export async function updateIdea(id: string, request: UpdateIdeaRequest): Promise<Idea> {
|
||||
return apiPatch<Idea>(`/api/ideas/${id}`, request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get conversations (ideas with category='conversation')
|
||||
*/
|
||||
export async function getConversations(params: Omit<QueryIdeasRequest, "category"> = {}): Promise<QueryIdeasResponse> {
|
||||
return queryIdeas({ ...params, category: "conversation" });
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a conversation
|
||||
*/
|
||||
export async function createConversation(
|
||||
title: string,
|
||||
content: string,
|
||||
projectId?: string
|
||||
): Promise<Idea> {
|
||||
return createIdea({
|
||||
title,
|
||||
content,
|
||||
projectId,
|
||||
category: "conversation",
|
||||
tags: ["chat"],
|
||||
metadata: { conversationType: "chat" },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update conversation content
|
||||
*/
|
||||
export async function updateConversation(
|
||||
id: string,
|
||||
content: string,
|
||||
title?: string
|
||||
): Promise<Idea> {
|
||||
return updateIdea(id, { content, title });
|
||||
}
|
||||
14
apps/web/src/lib/api/index.ts
Normal file
14
apps/web/src/lib/api/index.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* API Client Exports
|
||||
* Central export point for all API client modules
|
||||
*/
|
||||
|
||||
export * from "./client";
|
||||
export * from "./chat";
|
||||
export * from "./ideas";
|
||||
export * from "./tasks";
|
||||
export * from "./events";
|
||||
export * from "./knowledge";
|
||||
export * from "./domains";
|
||||
export * from "./teams";
|
||||
export * from "./personalities";
|
||||
Reference in New Issue
Block a user