-
Notifications
You must be signed in to change notification settings - Fork 3
feat(kitchen-sink): persist conversation ids and feedback #229
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
274771e
15d0bdc
a71ac93
47a2af8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,54 +1,97 @@ | ||
| 'use client'; | ||
|
|
||
| import { useState } from 'react'; | ||
| import { useState, useEffect, useCallback } from 'react'; | ||
| import { apiClient } from '@/lib/api/api-client'; | ||
| import type { SupportAgentResult } from '@/lib/capabilities/support-agent/support-agent'; | ||
| import type { StoredMessage } from '@/lib/conversations'; | ||
|
|
||
| export type ModelMessage = { role: 'user' | 'assistant' | 'system'; content: string }; | ||
| export type ModelMessage = StoredMessage; | ||
|
|
||
| export function useSupportChat() { | ||
| type UseSupportChatOptions = { | ||
| conversationId: string | null; | ||
| getMessages: (id: string) => ModelMessage[]; | ||
| setMessages: (id: string, messages: ModelMessage[]) => void; | ||
| updateTitle?: (id: string, title: string) => void; | ||
| }; | ||
|
|
||
| export function useSupportChat({ | ||
| conversationId, | ||
| getMessages, | ||
| setMessages, | ||
| updateTitle, | ||
| }: UseSupportChatOptions) { | ||
| const [input, setInput] = useState(''); | ||
| const [messages, setMessages] = useState<ModelMessage[]>([]); | ||
| const [messages, setLocalMessages] = useState<ModelMessage[]>([]); | ||
| const [result, setResult] = useState<SupportAgentResult | null>(null); | ||
| const [error, setError] = useState<string | null>(null); | ||
| const [isLoading, setIsLoading] = useState(false); | ||
|
|
||
| const handleSubmit = async (e: React.FormEvent) => { | ||
| e.preventDefault(); | ||
|
|
||
| if (!input.trim() || isLoading) return; | ||
| useEffect(() => { | ||
| if (conversationId) { | ||
| const storedMessages = getMessages(conversationId); | ||
| setLocalMessages(storedMessages); | ||
| setResult(null); | ||
| setError(null); | ||
| } else { | ||
| setLocalMessages([]); | ||
| setResult(null); | ||
| setError(null); | ||
| } | ||
| }, [conversationId, getMessages]); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Conversation switch doesn't reset loading state, corrupts UIMedium Severity When switching conversations, the Additional Locations (1) |
||
|
|
||
| setIsLoading(true); | ||
| setError(null); | ||
| const handleSubmit = useCallback( | ||
| async (e: React.FormEvent) => { | ||
| e.preventDefault(); | ||
|
|
||
| const newMessages = [...messages, { role: 'user', content: input } as ModelMessage]; | ||
| setMessages(newMessages); | ||
| setInput(''); | ||
| if (!input.trim() || isLoading || !conversationId) return; | ||
|
|
||
| try { | ||
| const clientResponse = await apiClient.api['support-response'].$post({ | ||
| json: { messages: newMessages }, | ||
| }); | ||
| setIsLoading(true); | ||
| setError(null); | ||
|
|
||
| const json = await clientResponse.json(); | ||
| const userMessage: ModelMessage = { role: 'user', content: input }; | ||
| const newMessages = [...messages, userMessage]; | ||
| setLocalMessages(newMessages); | ||
| setMessages(conversationId, newMessages); | ||
| setInput(''); | ||
|
|
||
| if ('error' in json) { | ||
| throw new Error(json.error); | ||
| if (messages.length === 0 && updateTitle) { | ||
| const title = input.slice(0, 50) + (input.length > 50 ? '...' : ''); | ||
| updateTitle(conversationId, title); | ||
| } | ||
|
|
||
| const agentResult = json.data as unknown as SupportAgentResult; | ||
| setResult(agentResult); | ||
| try { | ||
| const clientResponse = await apiClient.api['support-response'].$post({ | ||
| json: { messages: newMessages, conversationId }, | ||
| }); | ||
|
|
||
| const json = await clientResponse.json(); | ||
|
|
||
| if (agentResult.answer) { | ||
| setMessages((prev) => [...prev, agentResult.answer as ModelMessage]); | ||
| if ('error' in json) { | ||
| throw new Error(json.error); | ||
| } | ||
|
|
||
| const agentResult = json.data as unknown as SupportAgentResult; | ||
| setResult(agentResult); | ||
|
|
||
| if (agentResult.answer) { | ||
| const assistantMessage: ModelMessage = { | ||
| role: 'assistant', | ||
| content: agentResult.answer.content as string, | ||
| links: agentResult.links, | ||
| }; | ||
| const updatedMessages = [...newMessages, assistantMessage]; | ||
| setLocalMessages(updatedMessages); | ||
| setMessages(conversationId, updatedMessages); | ||
| } | ||
| } catch (err) { | ||
| console.error(err); | ||
| setError('❌ Error generating response. Please try again.'); | ||
| } finally { | ||
| setIsLoading(false); | ||
| } | ||
| } catch (err) { | ||
| console.error(err); | ||
| setError('❌ Error generating response. Please try again.'); | ||
| } finally { | ||
| setIsLoading(false); | ||
| } | ||
| }; | ||
| }, | ||
| [input, isLoading, conversationId, messages, setMessages, updateTitle] | ||
| ); | ||
|
|
||
| return { | ||
| input, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| 'use client'; | ||
|
|
||
| import { Conversation } from '@/lib/conversations'; | ||
|
|
||
| type ConversationListProps = { | ||
| conversations: Conversation[]; | ||
| currentId: string | null; | ||
| onSelect: (id: string) => void; | ||
| onCreate: () => void; | ||
| onDelete: (id: string) => void; | ||
| }; | ||
|
|
||
| export function ConversationList({ | ||
| conversations, | ||
| currentId, | ||
| onSelect, | ||
| onCreate, | ||
| onDelete, | ||
| }: ConversationListProps) { | ||
| return ( | ||
| <div className="flex flex-col gap-1"> | ||
| <button | ||
| onClick={onCreate} | ||
| className="w-full py-2 px-3 text-sm font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-100 rounded-md border border-dashed border-gray-300 mb-2" | ||
| > | ||
| + New conversation | ||
| </button> | ||
| {conversations.length === 0 && ( | ||
| <div className="text-xs text-gray-400 px-3 py-2">No conversations yet</div> | ||
| )} | ||
| {conversations.map((conv) => ( | ||
| <div | ||
| key={conv.id} | ||
| className={`group flex items-center justify-between rounded-md cursor-pointer ${ | ||
| currentId === conv.id | ||
| ? 'bg-gray-50 outline outline-1 outline-gray-200 outline-offset-[-1px]' | ||
| : 'hover:bg-gray-50' | ||
| }`} | ||
| > | ||
| <button | ||
| onClick={() => onSelect(conv.id)} | ||
| className="flex-1 py-[0.3125rem] px-3 text-left text-sm truncate" | ||
| title={conv.title} | ||
| > | ||
| {conv.title} | ||
| </button> | ||
| <button | ||
| onClick={(e) => { | ||
| e.stopPropagation(); | ||
| onDelete(conv.id); | ||
| }} | ||
| className="opacity-0 group-hover:opacity-100 p-1 mr-1 text-gray-400 hover:text-red-500 transition-opacity" | ||
| title="Delete conversation" | ||
| > | ||
| <svg | ||
| xmlns="http://www.w3.org/2000/svg" | ||
| viewBox="0 0 20 20" | ||
| fill="currentColor" | ||
| className="w-4 h-4" | ||
| > | ||
| <path | ||
| fillRule="evenodd" | ||
| d="M8.75 1A2.75 2.75 0 006 3.75v.443c-.795.077-1.584.176-2.365.298a.75.75 0 10.23 1.482l.149-.022.841 10.518A2.75 2.75 0 007.596 19h4.807a2.75 2.75 0 002.742-2.53l.841-10.519.149.023a.75.75 0 00.23-1.482A41.03 41.03 0 0014 4.193V3.75A2.75 2.75 0 0011.25 1h-2.5zM10 4c.84 0 1.673.025 2.5.075V3.75c0-.69-.56-1.25-1.25-1.25h-2.5c-.69 0-1.25.56-1.25 1.25v.325C8.327 4.025 9.16 4 10 4zM8.58 7.72a.75.75 0 00-1.5.06l.3 7.5a.75.75 0 101.5-.06l-.3-7.5zm4.34.06a.75.75 0 10-1.5-.06l-.3 7.5a.75.75 0 101.5.06l.3-7.5z" | ||
| clipRule="evenodd" | ||
| /> | ||
| </svg> | ||
| </button> | ||
| </div> | ||
| ))} | ||
| </div> | ||
| ); | ||
| } |


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Feedback state not reset on conversation switch
Medium Severity
The
feedbackGivenstate (keyed by message index) is never cleared when switching conversations. After giving feedback on, say, message index 2 in one conversation, switching to another conversation will incorrectly show that same feedback indicator on its message index 2. The state needs to be reset whencurrentIdchanges.