import { createEffect, createSignal, Show } from 'solid-js' import { createFileRoute } from '@tanstack/solid-router' import { PlusCircle, MessageCircle, Trash2, Send, Settings, Edit2, } from 'lucide-solid' import MarkdownIt from 'markdown-it' import { SettingsDialog } from '../components/demo.SettingsDialog' import { useAppActions, useAppSelectors, useAppState, } from '../store/demo.hooks' import { store } from '../store/demo.store' import '../demo.index.css' type Message = { id: string role: 'user' | 'assistant' content: string } const md = new MarkdownIt() function Home() { const state = useAppState() const actions = useAppActions() const selectors = useAppSelectors() const currentConversation = () => state().conversations.find((c) => c.id === state().currentConversationId) const messages = () => currentConversation()?.messages || [] // Local state const [input, setInput] = createSignal('') const [editingChatId, setEditingChatId] = createSignal(null) const [isSettingsOpen, setIsSettingsOpen] = createSignal(false) let messagesContainerRef: HTMLDivElement | null const [pendingMessage, setPendingMessage] = createSignal(null) const scrollToBottom = () => { if (messagesContainerRef) { messagesContainerRef.scrollTop = messagesContainerRef.scrollHeight } } // Scroll to bottom when messages change or loading state changes createEffect(() => { state().isLoading messages() scrollToBottom() }) const handleSubmit = async (e: any) => { e.preventDefault() if (!input().trim() || state().isLoading) return const currentInput = input() setInput('') // Clear input early for better UX actions.setLoading(true) try { let conversationId = state().currentConversationId // If no current conversation, create one if (!conversationId) { conversationId = Date.now().toString() const newConversation = { id: conversationId, title: currentInput.trim().slice(0, 30), messages: [], } actions.addConversation(newConversation) } const userMessage: Message = { id: Date.now().toString(), role: 'user' as const, content: currentInput.trim(), } // Add user message actions.addMessage(conversationId, userMessage) // Get active prompt const activePrompt = selectors.getActivePrompt(store.state) let systemPrompt if (activePrompt) { systemPrompt = { value: activePrompt.content, enabled: true, } } const response = await fetch('http://localhost:8080/api/chat', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ messages: [...messages(), userMessage], systemPrompt, }), }) const reader = response.body?.getReader() if (!reader) { throw new Error('No reader found in response') } const decoder = new TextDecoder() let done = false let newMessage = { id: (Date.now() + 1).toString(), role: 'assistant' as const, content: '', } while (!done) { const out = await reader.read() done = out.done if (!done) { try { const jsonTxt = decoder.decode(out.value).replace(/^data:\s+/, '') console.log(jsonTxt) const json = JSON.parse(jsonTxt) if (json.type === 'content_block_delta') { newMessage = { ...newMessage, content: newMessage.content + json.delta.text, } setPendingMessage(newMessage) } } catch (e) { console.error(e) } } } setPendingMessage(null) if (newMessage.content.trim()) { actions.addMessage(conversationId, newMessage) } } catch (error) { console.error('Error:', error) const errorMessage: Message = { id: (Date.now() + 1).toString(), role: 'assistant' as const, content: 'Sorry, I encountered an error processing your request.', } if (state().currentConversationId) { actions.addMessage(state().currentConversationId!, errorMessage) } } finally { actions.setLoading(false) } } const handleNewChat = () => { const newConversation = { id: Date.now().toString(), title: 'New Chat', messages: [], } actions.addConversation(newConversation) } const handleDeleteChat = (id: string) => { actions.deleteConversation(id) } const handleUpdateChatTitle = (id: string, title: string) => { actions.updateConversationTitle(id, title) setEditingChatId(null) } // Handle input change const handleInputChange = (e: any) => { setInput(e.target.value) } return (
{/* Settings Button */}
{/* Sidebar */}
{/* Chat List */}
{state().conversations.map((chat) => (
actions.setCurrentConversationId(chat.id)} > {editingChatId() === chat.id ? ( handleUpdateChatTitle(chat.id, e.target.value) } onBlur={() => setEditingChatId(null)} onKeyDown={(e) => { if (e.key === 'Enter') { handleUpdateChatTitle(chat.id, chat.title) } }} class="flex-1 bg-transparent text-sm text-white focus:outline-none" autofocus /> ) : ( {chat.title} )}
))}
{/* Main Content */}
{state().currentConversationId ? ( <> {/* Messages */}
{[...messages(), pendingMessage()] .filter((v) => v) .map((message) => (
{message!.role === 'assistant' ? (
AI
) : (
Y
)}
))} {state().isLoading && (
AI
Thinking
)}
{/* Input */}