create-featurebased-architecture 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +29 -9
  2. package/dist/index.js +4 -2
  3. package/package.json +2 -2
  4. package/templates/ollama-chatbot-backend/.env.example +9 -0
  5. package/templates/ollama-chatbot-backend/README.md +132 -0
  6. package/templates/ollama-chatbot-backend/package.json +21 -0
  7. package/templates/ollama-chatbot-backend/src/config/database.ts +4 -0
  8. package/templates/ollama-chatbot-backend/src/config/env.ts +8 -0
  9. package/templates/ollama-chatbot-backend/src/config/index.ts +3 -0
  10. package/templates/ollama-chatbot-backend/src/config/ollama.ts +14 -0
  11. package/templates/ollama-chatbot-backend/src/features/chat/controller.ts +49 -0
  12. package/templates/ollama-chatbot-backend/src/features/chat/index.ts +5 -0
  13. package/templates/ollama-chatbot-backend/src/features/chat/routes.ts +7 -0
  14. package/templates/ollama-chatbot-backend/src/features/chat/schema.ts +13 -0
  15. package/templates/ollama-chatbot-backend/src/features/chat/service.ts +91 -0
  16. package/templates/ollama-chatbot-backend/src/features/chat/types.ts +22 -0
  17. package/templates/ollama-chatbot-backend/src/features/conversations/controller.ts +114 -0
  18. package/templates/ollama-chatbot-backend/src/features/conversations/index.ts +6 -0
  19. package/templates/ollama-chatbot-backend/src/features/conversations/repository.ts +61 -0
  20. package/templates/ollama-chatbot-backend/src/features/conversations/routes.ts +11 -0
  21. package/templates/ollama-chatbot-backend/src/features/conversations/schema.ts +9 -0
  22. package/templates/ollama-chatbot-backend/src/features/conversations/service.ts +28 -0
  23. package/templates/ollama-chatbot-backend/src/features/conversations/types.ts +23 -0
  24. package/templates/ollama-chatbot-backend/src/index.ts +22 -0
  25. package/templates/ollama-chatbot-backend/src/routes/index.ts +10 -0
  26. package/templates/ollama-chatbot-backend/src/shared/index.ts +2 -0
  27. package/templates/ollama-chatbot-backend/src/shared/types/index.ts +16 -0
  28. package/templates/ollama-chatbot-backend/src/shared/utils/index.ts +1 -0
  29. package/templates/ollama-chatbot-backend/src/shared/utils/response.ts +10 -0
  30. package/templates/ollama-chatbot-backend/tsconfig.json +22 -0
  31. package/templates/ollama-chatbot-frontend/.env.example +1 -0
  32. package/templates/ollama-chatbot-frontend/README.md +65 -0
  33. package/templates/ollama-chatbot-frontend/index.html +12 -0
  34. package/templates/ollama-chatbot-frontend/package.json +23 -0
  35. package/templates/ollama-chatbot-frontend/src/App.tsx +17 -0
  36. package/templates/ollama-chatbot-frontend/src/config/env.ts +1 -0
  37. package/templates/ollama-chatbot-frontend/src/config/index.ts +1 -0
  38. package/templates/ollama-chatbot-frontend/src/features/chat/components/ChatPage.tsx +94 -0
  39. package/templates/ollama-chatbot-frontend/src/features/chat/components/index.ts +1 -0
  40. package/templates/ollama-chatbot-frontend/src/features/chat/hooks/index.ts +1 -0
  41. package/templates/ollama-chatbot-frontend/src/features/chat/hooks/useChat.ts +149 -0
  42. package/templates/ollama-chatbot-frontend/src/features/chat/index.ts +4 -0
  43. package/templates/ollama-chatbot-frontend/src/features/chat/services/chatService.ts +81 -0
  44. package/templates/ollama-chatbot-frontend/src/features/chat/services/index.ts +1 -0
  45. package/templates/ollama-chatbot-frontend/src/features/chat/types.ts +33 -0
  46. package/templates/ollama-chatbot-frontend/src/index.css +281 -0
  47. package/templates/ollama-chatbot-frontend/src/main.tsx +13 -0
  48. package/templates/ollama-chatbot-frontend/src/shared/components/Sidebar.tsx +56 -0
  49. package/templates/ollama-chatbot-frontend/src/shared/components/index.ts +1 -0
  50. package/templates/ollama-chatbot-frontend/tsconfig.json +27 -0
  51. package/templates/ollama-chatbot-frontend/tsconfig.node.json +11 -0
  52. package/templates/ollama-chatbot-frontend/vite.config.ts +12 -0
@@ -0,0 +1,11 @@
1
+ import { Hono } from "hono";
2
+ import { conversationController } from "./controller";
3
+
4
+ export const conversationRoutes = new Hono();
5
+
6
+ conversationRoutes.get("/", conversationController.getAll);
7
+ conversationRoutes.get("/:id", conversationController.getById);
8
+ conversationRoutes.get("/:id/messages", conversationController.getMessages);
9
+ conversationRoutes.post("/", conversationController.create);
10
+ conversationRoutes.put("/:id", conversationController.updateTitle);
11
+ conversationRoutes.delete("/:id", conversationController.delete);
@@ -0,0 +1,9 @@
1
+ import { z } from "zod";
2
+
3
+ export const createConversationSchema = z.object({
4
+ title: z.string().min(1, "Title is required").max(200, "Title too long"),
5
+ });
6
+
7
+ export const conversationIdSchema = z.object({
8
+ id: z.string().uuid("Invalid conversation ID"),
9
+ });
@@ -0,0 +1,28 @@
1
+ import { conversationRepository } from "./repository";
2
+ import type { Conversation, Message, CreateConversationDto } from "./types";
3
+
4
+ export const conversationService = {
5
+ async getAllConversations(): Promise<Conversation[]> {
6
+ return conversationRepository.findAll();
7
+ },
8
+
9
+ async getConversationById(id: string): Promise<Conversation | null> {
10
+ return conversationRepository.findById(id);
11
+ },
12
+
13
+ async createConversation(data: CreateConversationDto): Promise<Conversation> {
14
+ return conversationRepository.create(data);
15
+ },
16
+
17
+ async updateConversationTitle(id: string, title: string): Promise<Conversation | null> {
18
+ return conversationRepository.updateTitle(id, title);
19
+ },
20
+
21
+ async deleteConversation(id: string): Promise<boolean> {
22
+ return conversationRepository.delete(id);
23
+ },
24
+
25
+ async getConversationMessages(conversationId: string): Promise<Message[]> {
26
+ return conversationRepository.getMessages(conversationId);
27
+ },
28
+ };
@@ -0,0 +1,23 @@
1
+ export interface Conversation {
2
+ id: string;
3
+ title: string;
4
+ created_at: Date;
5
+ updated_at: Date;
6
+ }
7
+
8
+ export interface Message {
9
+ id: string;
10
+ conversation_id: string;
11
+ role: "user" | "assistant" | "system";
12
+ content: string;
13
+ created_at: Date;
14
+ }
15
+
16
+ export interface CreateConversationDto {
17
+ title: string;
18
+ }
19
+
20
+ export interface AddMessageDto {
21
+ role: "user" | "assistant" | "system";
22
+ content: string;
23
+ }
@@ -0,0 +1,22 @@
1
+ import { Hono } from "hono";
2
+ import { cors } from "hono/cors";
3
+ import { logger } from "hono/logger";
4
+ import { env } from "./config/env";
5
+ import { appRouter } from "./routes";
6
+
7
+ const app = new Hono();
8
+
9
+ // Middleware
10
+ app.use("*", logger());
11
+ app.use("*", cors());
12
+
13
+ // Routes
14
+ app.route("/api", appRouter);
15
+
16
+ // Health check
17
+ app.get("/health", (c) => c.json({ status: "ok", timestamp: new Date().toISOString() }));
18
+
19
+ export default {
20
+ port: env.PORT,
21
+ fetch: app.fetch,
22
+ };
@@ -0,0 +1,10 @@
1
+ import { Hono } from "hono";
2
+ import { chatRoutes } from "../features/chat/routes";
3
+ import { conversationRoutes } from "../features/conversations/routes";
4
+
5
+ export const appRouter = new Hono();
6
+
7
+ appRouter.route("/chat", chatRoutes);
8
+ appRouter.route("/conversations", conversationRoutes);
9
+
10
+ appRouter.get("/", (c) => c.json({ message: "Ollama Chatbot API" }));
@@ -0,0 +1,2 @@
1
+ export * from "./types";
2
+ export * from "./utils";
@@ -0,0 +1,16 @@
1
+ // Shared types across features
2
+ export interface ApiResponse<T> {
3
+ success: boolean;
4
+ data?: T;
5
+ error?: string;
6
+ message?: string;
7
+ }
8
+
9
+ export interface PaginatedResponse<T> extends ApiResponse<T[]> {
10
+ pagination: {
11
+ page: number;
12
+ limit: number;
13
+ total: number;
14
+ totalPages: number;
15
+ };
16
+ }
@@ -0,0 +1 @@
1
+ export * from "./response";
@@ -0,0 +1,10 @@
1
+ import type { Context } from "hono";
2
+ import type { ApiResponse } from "../types";
3
+
4
+ export function successResponse<T>(c: Context, data: T, message?: string, status = 200) {
5
+ return c.json<ApiResponse<T>>({ success: true, data, message }, status);
6
+ }
7
+
8
+ export function errorResponse(c: Context, error: string, status = 400) {
9
+ return c.json<ApiResponse<never>>({ success: false, error }, status);
10
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "strict": true,
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true,
9
+ "forceConsistentCasingInFileNames": true,
10
+ "outDir": "./dist",
11
+ "rootDir": "./src",
12
+ "baseUrl": ".",
13
+ "paths": {
14
+ "@/*": ["src/*"],
15
+ "@/features/*": ["src/features/*"],
16
+ "@/shared/*": ["src/shared/*"],
17
+ "@/config/*": ["src/config/*"]
18
+ }
19
+ },
20
+ "include": ["src/**/*"],
21
+ "exclude": ["node_modules", "dist"]
22
+ }
@@ -0,0 +1 @@
1
+ VITE_API_URL=http://localhost:3000/api
@@ -0,0 +1,65 @@
1
+ # {{PROJECT_NAME}}
2
+
3
+ An Ollama Chatbot frontend with feature-based architecture using React, Vite, and react-icons.
4
+
5
+ ## Project Structure
6
+
7
+ ```
8
+ src/
9
+ ├── config/ # Configuration
10
+ ├── features/
11
+ │ └── chat/
12
+ │ ├── components/
13
+ │ ├── hooks/
14
+ │ ├── services/
15
+ │ ├── types.ts
16
+ │ └── index.ts
17
+ ├── shared/
18
+ │ └── components/
19
+ ├── App.tsx
20
+ ├── main.tsx
21
+ └── index.css
22
+ ```
23
+
24
+ ## Getting Started
25
+
26
+ ```bash
27
+ # Install dependencies
28
+ bun install
29
+
30
+ # Copy environment file
31
+ cp .env.example .env
32
+
33
+ # Run development server
34
+ bun run dev
35
+ ```
36
+
37
+ ## Environment Variables
38
+
39
+ | Variable | Description |
40
+ |----------|-------------|
41
+ | `VITE_API_URL` | Backend API URL (default: http://localhost:3000/api) |
42
+
43
+ ## Features
44
+
45
+ - Real-time chat with streaming responses
46
+ - Conversation history
47
+ - Create new conversations
48
+ - Delete conversations
49
+ - Dark theme UI
50
+
51
+ ## Backend
52
+
53
+ This frontend requires the **Ollama Chatbot Backend** to be running. Generate both templates and run the backend first.
54
+
55
+ ## Customization
56
+
57
+ ### Changing the Model
58
+
59
+ The model is configured in the backend. Update the `.env` file in your backend project:
60
+
61
+ ```env
62
+ OLLAMA_MODEL=llama3:70b-cloud
63
+ ```
64
+
65
+ Or pass a specific model per request in the frontend by modifying the `sendMessage` call in `useChat.ts`.
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>{{PROJECT_NAME}} - Chatbot</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="/src/main.tsx"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "{{PROJECT_NAME}}",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "scripts": {
6
+ "dev": "bunx --bun vite",
7
+ "build": "bunx --bun vite build",
8
+ "preview": "bunx --bun vite preview"
9
+ },
10
+ "dependencies": {
11
+ "react": "^18.2.0",
12
+ "react-dom": "^18.2.0",
13
+ "react-router-dom": "^6.20.0",
14
+ "react-icons": "^5.0.0"
15
+ },
16
+ "devDependencies": {
17
+ "@types/react": "^18.2.0",
18
+ "@types/react-dom": "^18.2.0",
19
+ "@vitejs/plugin-react": "^4.2.0",
20
+ "typescript": "^5.0.0",
21
+ "vite": "^5.0.0"
22
+ }
23
+ }
@@ -0,0 +1,17 @@
1
+ import { Routes, Route } from "react-router-dom";
2
+ import { ChatPage } from "./features/chat";
3
+ import { Sidebar } from "./shared/components";
4
+
5
+ export function App() {
6
+ return (
7
+ <div className="app-container">
8
+ <Sidebar />
9
+ <main className="main-content">
10
+ <Routes>
11
+ <Route path="/" element={<ChatPage />} />
12
+ <Route path="/chat/:conversationId" element={<ChatPage />} />
13
+ </Routes>
14
+ </main>
15
+ </div>
16
+ );
17
+ }
@@ -0,0 +1 @@
1
+ export const API_URL = import.meta.env.VITE_API_URL || "http://localhost:3000/api";
@@ -0,0 +1 @@
1
+ export * from "./env";
@@ -0,0 +1,94 @@
1
+ import { useState, useRef, useEffect } from "react";
2
+ import { useParams } from "react-router-dom";
3
+ import { IoSend } from "react-icons/io5";
4
+ import { FaRobot, FaUser } from "react-icons/fa";
5
+ import { RiChatNewLine } from "react-icons/ri";
6
+ import { useChat } from "../hooks/useChat";
7
+
8
+ export function ChatPage() {
9
+ const { conversationId } = useParams();
10
+ const { messages, loading, streaming, sendMessage } = useChat(conversationId);
11
+ const [input, setInput] = useState("");
12
+ const messagesEndRef = useRef<HTMLDivElement>(null);
13
+
14
+ const scrollToBottom = () => {
15
+ messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
16
+ };
17
+
18
+ useEffect(() => {
19
+ scrollToBottom();
20
+ }, [messages]);
21
+
22
+ const handleSubmit = (e: React.FormEvent) => {
23
+ e.preventDefault();
24
+ if (!input.trim() || streaming) return;
25
+ sendMessage(input);
26
+ setInput("");
27
+ };
28
+
29
+ const handleKeyDown = (e: React.KeyboardEvent) => {
30
+ if (e.key === "Enter" && !e.shiftKey) {
31
+ e.preventDefault();
32
+ handleSubmit(e);
33
+ }
34
+ };
35
+
36
+ if (loading && messages.length === 0) {
37
+ return (
38
+ <div className="chat-container">
39
+ <div className="empty-state">
40
+ <p>Loading...</p>
41
+ </div>
42
+ </div>
43
+ );
44
+ }
45
+
46
+ return (
47
+ <div className="chat-container">
48
+ <div className="chat-messages">
49
+ {messages.length === 0 ? (
50
+ <div className="empty-state">
51
+ <RiChatNewLine className="empty-state-icon" />
52
+ <h2>Start a conversation</h2>
53
+ <p>Send a message to begin chatting with the AI assistant</p>
54
+ </div>
55
+ ) : (
56
+ messages.map((msg) => (
57
+ <div key={msg.id} className={`message ${msg.role}`}>
58
+ <div className="message-avatar">
59
+ {msg.role === "user" ? <FaUser size={16} /> : <FaRobot size={16} />}
60
+ </div>
61
+ <div className="message-content">
62
+ {msg.content || (
63
+ <div className="typing-indicator">
64
+ <span></span>
65
+ <span></span>
66
+ <span></span>
67
+ </div>
68
+ )}
69
+ </div>
70
+ </div>
71
+ ))
72
+ )}
73
+ <div ref={messagesEndRef} />
74
+ </div>
75
+
76
+ <div className="chat-input-container">
77
+ <form onSubmit={handleSubmit} className="chat-input-wrapper">
78
+ <textarea
79
+ className="chat-input"
80
+ value={input}
81
+ onChange={(e) => setInput(e.target.value)}
82
+ onKeyDown={handleKeyDown}
83
+ placeholder="Type your message..."
84
+ rows={1}
85
+ disabled={streaming}
86
+ />
87
+ <button type="submit" className="send-btn" disabled={!input.trim() || streaming}>
88
+ <IoSend size={18} />
89
+ </button>
90
+ </form>
91
+ </div>
92
+ </div>
93
+ );
94
+ }
@@ -0,0 +1 @@
1
+ export * from "./ChatPage";
@@ -0,0 +1 @@
1
+ export * from "./useChat";
@@ -0,0 +1,149 @@
1
+ import { useState, useEffect, useCallback } from "react";
2
+ import { chatService } from "../services/chatService";
3
+ import type { Conversation, Message } from "../types";
4
+
5
+ export function useChat(conversationId?: string) {
6
+ const [messages, setMessages] = useState<Message[]>([]);
7
+ const [conversations, setConversations] = useState<Conversation[]>([]);
8
+ const [currentConversationId, setCurrentConversationId] = useState<string | undefined>(conversationId);
9
+ const [loading, setLoading] = useState(false);
10
+ const [streaming, setStreaming] = useState(false);
11
+
12
+ // Load conversations
13
+ const loadConversations = useCallback(async () => {
14
+ try {
15
+ const data = await chatService.getConversations();
16
+ setConversations(data);
17
+ } catch (error) {
18
+ console.error("Failed to load conversations:", error);
19
+ }
20
+ }, []);
21
+
22
+ // Load messages for a conversation
23
+ const loadMessages = useCallback(async (convId: string) => {
24
+ try {
25
+ setLoading(true);
26
+ const data = await chatService.getMessages(convId);
27
+ setMessages(data);
28
+ setCurrentConversationId(convId);
29
+ } catch (error) {
30
+ console.error("Failed to load messages:", error);
31
+ } finally {
32
+ setLoading(false);
33
+ }
34
+ }, []);
35
+
36
+ // Send message
37
+ const sendMessage = useCallback(async (content: string, useStream = true) => {
38
+ if (!content.trim()) return;
39
+
40
+ // Add user message optimistically
41
+ const userMessage: Message = {
42
+ id: `temp-${Date.now()}`,
43
+ conversation_id: currentConversationId || "",
44
+ role: "user",
45
+ content,
46
+ created_at: new Date().toISOString(),
47
+ };
48
+ setMessages((prev) => [...prev, userMessage]);
49
+
50
+ if (useStream) {
51
+ setStreaming(true);
52
+ let assistantMessage: Message = {
53
+ id: `temp-assistant-${Date.now()}`,
54
+ conversation_id: currentConversationId || "",
55
+ role: "assistant",
56
+ content: "",
57
+ created_at: new Date().toISOString(),
58
+ };
59
+ setMessages((prev) => [...prev, assistantMessage]);
60
+
61
+ try {
62
+ for await (const chunk of chatService.streamMessage({
63
+ message: content,
64
+ conversationId: currentConversationId,
65
+ })) {
66
+ if (chunk.conversationId && !currentConversationId) {
67
+ setCurrentConversationId(chunk.conversationId);
68
+ }
69
+ assistantMessage = { ...assistantMessage, content: assistantMessage.content + chunk.content };
70
+ setMessages((prev) => [...prev.slice(0, -1), assistantMessage]);
71
+ }
72
+ loadConversations();
73
+ } catch (error) {
74
+ console.error("Stream error:", error);
75
+ setMessages((prev) => prev.slice(0, -1));
76
+ } finally {
77
+ setStreaming(false);
78
+ }
79
+ } else {
80
+ setLoading(true);
81
+ try {
82
+ const response = await chatService.sendMessage({
83
+ message: content,
84
+ conversationId: currentConversationId,
85
+ });
86
+
87
+ if (!currentConversationId) {
88
+ setCurrentConversationId(response.conversationId);
89
+ }
90
+
91
+ const assistantMessage: Message = {
92
+ id: `assistant-${Date.now()}`,
93
+ conversation_id: response.conversationId,
94
+ role: "assistant",
95
+ content: response.message,
96
+ created_at: new Date().toISOString(),
97
+ };
98
+ setMessages((prev) => [...prev, assistantMessage]);
99
+ loadConversations();
100
+ } catch (error) {
101
+ console.error("Send error:", error);
102
+ } finally {
103
+ setLoading(false);
104
+ }
105
+ }
106
+ }, [currentConversationId, loadConversations]);
107
+
108
+ // Delete conversation
109
+ const deleteConversation = useCallback(async (id: string) => {
110
+ try {
111
+ await chatService.deleteConversation(id);
112
+ setConversations((prev) => prev.filter((c) => c.id !== id));
113
+ if (id === currentConversationId) {
114
+ setMessages([]);
115
+ setCurrentConversationId(undefined);
116
+ }
117
+ } catch (error) {
118
+ console.error("Failed to delete:", error);
119
+ }
120
+ }, [currentConversationId]);
121
+
122
+ // New chat
123
+ const startNewChat = useCallback(() => {
124
+ setMessages([]);
125
+ setCurrentConversationId(undefined);
126
+ }, []);
127
+
128
+ useEffect(() => {
129
+ loadConversations();
130
+ }, [loadConversations]);
131
+
132
+ useEffect(() => {
133
+ if (conversationId) {
134
+ loadMessages(conversationId);
135
+ }
136
+ }, [conversationId, loadMessages]);
137
+
138
+ return {
139
+ messages,
140
+ conversations,
141
+ currentConversationId,
142
+ loading,
143
+ streaming,
144
+ sendMessage,
145
+ deleteConversation,
146
+ startNewChat,
147
+ loadMessages,
148
+ };
149
+ }
@@ -0,0 +1,4 @@
1
+ export * from "./components";
2
+ export * from "./hooks";
3
+ export * from "./services";
4
+ export * from "./types";
@@ -0,0 +1,81 @@
1
+ import { API_URL } from "../../config/env";
2
+ import type { Conversation, Message, ChatRequest, ChatResponse, ApiResponse } from "./types";
3
+
4
+ export const chatService = {
5
+ // Conversations
6
+ async getConversations(): Promise<Conversation[]> {
7
+ const res = await fetch(`${API_URL}/conversations`);
8
+ const data: ApiResponse<Conversation[]> = await res.json();
9
+ if (!data.success) throw new Error(data.error);
10
+ return data.data || [];
11
+ },
12
+
13
+ async getConversation(id: string): Promise<Conversation> {
14
+ const res = await fetch(`${API_URL}/conversations/${id}`);
15
+ const data: ApiResponse<Conversation> = await res.json();
16
+ if (!data.success) throw new Error(data.error);
17
+ return data.data!;
18
+ },
19
+
20
+ async getMessages(conversationId: string): Promise<Message[]> {
21
+ const res = await fetch(`${API_URL}/conversations/${conversationId}/messages`);
22
+ const data: ApiResponse<Message[]> = await res.json();
23
+ if (!data.success) throw new Error(data.error);
24
+ return data.data || [];
25
+ },
26
+
27
+ async deleteConversation(id: string): Promise<void> {
28
+ const res = await fetch(`${API_URL}/conversations/${id}`, { method: "DELETE" });
29
+ const data: ApiResponse<null> = await res.json();
30
+ if (!data.success) throw new Error(data.error);
31
+ },
32
+
33
+ // Chat
34
+ async sendMessage(request: ChatRequest): Promise<ChatResponse> {
35
+ const res = await fetch(`${API_URL}/chat`, {
36
+ method: "POST",
37
+ headers: { "Content-Type": "application/json" },
38
+ body: JSON.stringify(request),
39
+ });
40
+ const data: ApiResponse<ChatResponse> = await res.json();
41
+ if (!data.success) throw new Error(data.error);
42
+ return data.data!;
43
+ },
44
+
45
+ // Stream chat
46
+ async *streamMessage(request: ChatRequest): AsyncGenerator<{ content: string; conversationId: string }> {
47
+ const res = await fetch(`${API_URL}/chat/stream`, {
48
+ method: "POST",
49
+ headers: { "Content-Type": "application/json" },
50
+ body: JSON.stringify(request),
51
+ });
52
+
53
+ if (!res.ok) throw new Error("Failed to stream");
54
+
55
+ const reader = res.body?.getReader();
56
+ if (!reader) throw new Error("No reader");
57
+
58
+ const decoder = new TextDecoder();
59
+ let buffer = "";
60
+
61
+ while (true) {
62
+ const { done, value } = await reader.read();
63
+ if (done) break;
64
+
65
+ buffer += decoder.decode(value, { stream: true });
66
+ const lines = buffer.split("\n");
67
+ buffer = lines.pop() || "";
68
+
69
+ for (const line of lines) {
70
+ if (line.startsWith("data: ")) {
71
+ try {
72
+ const data = JSON.parse(line.slice(6));
73
+ yield data;
74
+ } catch {
75
+ // Skip invalid JSON
76
+ }
77
+ }
78
+ }
79
+ }
80
+ },
81
+ };
@@ -0,0 +1 @@
1
+ export * from "./chatService";
@@ -0,0 +1,33 @@
1
+ export interface Conversation {
2
+ id: string;
3
+ title: string;
4
+ created_at: string;
5
+ updated_at: string;
6
+ }
7
+
8
+ export interface Message {
9
+ id: string;
10
+ conversation_id: string;
11
+ role: "user" | "assistant" | "system";
12
+ content: string;
13
+ created_at: string;
14
+ }
15
+
16
+ export interface ChatRequest {
17
+ message: string;
18
+ conversationId?: string;
19
+ model?: string;
20
+ }
21
+
22
+ export interface ChatResponse {
23
+ message: string;
24
+ conversationId: string;
25
+ model: string;
26
+ }
27
+
28
+ export interface ApiResponse<T> {
29
+ success: boolean;
30
+ data?: T;
31
+ error?: string;
32
+ message?: string;
33
+ }