-
-
Notifications
You must be signed in to change notification settings - Fork 6
Implement System-Wide Concurrency and Performance Improvements #345
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
eb3c708
fb825a1
f039453
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,76 +1,79 @@ | ||
| import { nanoid } from 'nanoid'; | ||
| import { notFound, redirect } from 'next/navigation'; | ||
| import { Chat } from '@/components/chat'; | ||
| import { getChat, getChatMessages } from '@/lib/actions/chat'; // Added getChatMessages | ||
| import { AI } from '@/app/actions'; | ||
| import { getChat, getChatMessages } from '@/lib/actions/chat'; | ||
| import { AI, AIState } from '@/app/actions'; | ||
| import { MapDataProvider } from '@/components/map/map-data-context'; | ||
| import { getCurrentUserIdOnServer } from '@/lib/auth/get-current-user'; // For server-side auth | ||
| import type { AIMessage } from '@/lib/types'; // For AIMessage type | ||
| import type { Message as DrizzleMessage } from '@/lib/actions/chat-db'; // For DrizzleMessage type | ||
| import { getCurrentUserIdOnServer } from '@/lib/auth/get-current-user'; | ||
| import type { AIMessage } from '@/lib/types'; | ||
| import type { Message as DrizzleMessage } from '@/lib/actions/chat-db'; | ||
|
|
||
| export const maxDuration = 60; | ||
|
|
||
| export interface SearchPageProps { | ||
| params: Promise<{ id: string }>; // Keep as is for now | ||
| params: Promise<{ id: string }>; | ||
| } | ||
|
|
||
| const validRoles: AIMessage['role'][] = ['user', 'assistant', 'system', 'function', 'data', 'tool']; | ||
|
|
||
| function safeGetRole(role: string): AIMessage['role'] { | ||
| if (validRoles.includes(role as AIMessage['role'])) { | ||
| return role as AIMessage['role']; | ||
| } | ||
| console.warn(`Invalid role "${role}" found in database, defaulting to 'user'.`); | ||
| return 'user'; | ||
| } | ||
|
|
||
|
|
||
| export async function generateMetadata({ params }: SearchPageProps) { | ||
| const { id } = await params; // Keep as is for now | ||
| // TODO: Metadata generation might need authenticated user if chats are private | ||
| // For now, assuming getChat can be called or it handles anon access for metadata appropriately | ||
| const userId = await getCurrentUserIdOnServer(); // Attempt to get user for metadata | ||
| const chat = await getChat(id, userId || 'anonymous'); // Pass userId or 'anonymous' if none | ||
| const { id } = await params; | ||
| const userId = await getCurrentUserIdOnServer(); | ||
| const chat = await getChat(id, userId || 'anonymous'); | ||
| return { | ||
| title: chat?.title?.toString().slice(0, 50) || 'Search', | ||
| }; | ||
| } | ||
|
|
||
| export default async function SearchPage({ params }: SearchPageProps) { | ||
| const { id } = await params; // Keep as is for now | ||
| const { id } = await params; | ||
| const userId = await getCurrentUserIdOnServer(); | ||
|
|
||
| if (!userId) { | ||
| // If no user, redirect to login or show appropriate page | ||
| // For now, redirecting to home, but a login page would be better. | ||
| redirect('/'); | ||
| } | ||
|
|
||
| const chat = await getChat(id, userId); | ||
|
|
||
| if (!chat) { | ||
| // If chat doesn't exist or user doesn't have access (handled by getChat) | ||
| notFound(); | ||
| } | ||
|
|
||
| // Fetch messages for the chat | ||
| const dbMessages: DrizzleMessage[] = await getChatMessages(chat.id); | ||
|
|
||
| // Transform DrizzleMessages to AIMessages | ||
| const initialMessages: AIMessage[] = dbMessages.map((dbMsg): AIMessage => { | ||
| return { | ||
| id: dbMsg.id, | ||
| role: dbMsg.role as AIMessage['role'], // Cast role, ensure AIMessage['role'] includes all dbMsg.role possibilities | ||
| role: safeGetRole(dbMsg.role), | ||
| content: dbMsg.content, | ||
| createdAt: dbMsg.createdAt ? new Date(dbMsg.createdAt) : undefined, | ||
| // 'type' and 'name' are not in the basic Drizzle 'messages' schema. | ||
| // These would be undefined unless specific logic is added to derive them. | ||
| // For instance, if a message with role 'tool' should have a 'name', | ||
| // or if some messages have a specific 'type' based on content or other flags. | ||
| // This mapping assumes standard user/assistant messages primarily. | ||
| }; | ||
| }); | ||
|
|
||
| return ( | ||
| <AI | ||
| initialAIState={{ | ||
| const initialAIState = { | ||
| conversations: [ | ||
| { | ||
| id: nanoid(), | ||
| chatId: chat.id, | ||
| messages: initialMessages, // Use the transformed messages from the database | ||
| // isSharePage: true, // This was in PR#533, but share functionality is removed. | ||
| // If needed for styling or other logic, it can be set. | ||
| }} | ||
| > | ||
| messages: initialMessages, | ||
| } | ||
| ] | ||
| } satisfies AIState; | ||
|
|
||
| return ( | ||
| <AI initialAIState={initialAIState}> | ||
| <MapDataProvider> | ||
| <Chat id={id} /> | ||
| </MapDataProvider> | ||
| </AI> | ||
| ); | ||
| } | ||
| } |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -23,7 +23,7 @@ export interface ChatPanelRef { | |||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| export const ChatPanel = forwardRef<ChatPanelRef, ChatPanelProps>(({ messages, input, setInput }, ref) => { | ||||||||||||||||||||||||||||||||||
| const [, setMessages] = useUIState<typeof AI>() | ||||||||||||||||||||||||||||||||||
| const { submit, clearChat } = useActions() | ||||||||||||||||||||||||||||||||||
| const { submit } = useActions() | ||||||||||||||||||||||||||||||||||
| // Removed mcp instance as it's no longer passed to submit | ||||||||||||||||||||||||||||||||||
| const [isMobile, setIsMobile] = useState(false) | ||||||||||||||||||||||||||||||||||
| const [selectedFile, setSelectedFile] = useState<File | null>(null) | ||||||||||||||||||||||||||||||||||
|
|
@@ -69,7 +69,10 @@ export const ChatPanel = forwardRef<ChatPanelRef, ChatPanelProps>(({ messages, i | |||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => { | ||||||||||||||||||||||||||||||||||
| const handleSubmit = async ( | ||||||||||||||||||||||||||||||||||
| e: React.FormEvent<HTMLFormElement>, | ||||||||||||||||||||||||||||||||||
| newChat?: boolean | ||||||||||||||||||||||||||||||||||
| ) => { | ||||||||||||||||||||||||||||||||||
| e.preventDefault() | ||||||||||||||||||||||||||||||||||
| if (!input && !selectedFile) { | ||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||
|
|
@@ -86,18 +89,23 @@ export const ChatPanel = forwardRef<ChatPanelRef, ChatPanelProps>(({ messages, i | |||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| setMessages(currentMessages => [ | ||||||||||||||||||||||||||||||||||
| ...currentMessages, | ||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||
| id: nanoid(), | ||||||||||||||||||||||||||||||||||
| component: <UserMessage content={content} /> | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| ]) | ||||||||||||||||||||||||||||||||||
| if (!newChat) { | ||||||||||||||||||||||||||||||||||
| setMessages(currentMessages => [ | ||||||||||||||||||||||||||||||||||
| ...currentMessages, | ||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||
| id: nanoid(), | ||||||||||||||||||||||||||||||||||
| component: <UserMessage content={content} /> | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| ]) | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| const formData = new FormData(e.currentTarget) | ||||||||||||||||||||||||||||||||||
| if (selectedFile) { | ||||||||||||||||||||||||||||||||||
| formData.append('file', selectedFile) | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| if (newChat) { | ||||||||||||||||||||||||||||||||||
| formData.append('newChat', 'true') | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| setInput('') | ||||||||||||||||||||||||||||||||||
| clearAttachment() | ||||||||||||||||||||||||||||||||||
|
|
@@ -106,10 +114,12 @@ export const ChatPanel = forwardRef<ChatPanelRef, ChatPanelProps>(({ messages, i | |||||||||||||||||||||||||||||||||
| setMessages(currentMessages => [...currentMessages, responseMessage as any]) | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| const handleClear = async () => { | ||||||||||||||||||||||||||||||||||
| setMessages([]) | ||||||||||||||||||||||||||||||||||
| const handleNewConversation = async () => { | ||||||||||||||||||||||||||||||||||
| clearAttachment() | ||||||||||||||||||||||||||||||||||
| await clearChat() | ||||||||||||||||||||||||||||||||||
| const formData = new FormData() | ||||||||||||||||||||||||||||||||||
| formData.append('newChat', 'true') | ||||||||||||||||||||||||||||||||||
| const responseMessage = await submit(formData) | ||||||||||||||||||||||||||||||||||
| setMessages(currentMessages => [...currentMessages, responseMessage as any]) | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+117
to
123
Contributor
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. Avoid clearing local UI state to prevent flicker when starting a new conversation
Apply this diff: - const handleNewConversation = async () => {
- setMessages([])
- clearAttachment()
- const formData = new FormData()
- formData.append('newChat', 'true')
- const responseMessage = await submit(formData)
- setMessages(currentMessages => [...currentMessages, responseMessage as any])
- }
+ const handleNewConversation = async () => {
+ clearAttachment()
+ const formData = new FormData()
+ formData.append('newChat', 'true')
+ const responseMessage = await submit(formData)
+ setMessages(currentMessages => [...currentMessages, responseMessage as any])
+ }If you need a visual separator immediately, the server will add one via 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||||||||||||||
|
|
@@ -129,10 +139,10 @@ export const ChatPanel = forwardRef<ChatPanelRef, ChatPanelProps>(({ messages, i | |||||||||||||||||||||||||||||||||
| type="button" | ||||||||||||||||||||||||||||||||||
| variant={'secondary'} | ||||||||||||||||||||||||||||||||||
| className="rounded-full bg-secondary/80 group transition-all hover:scale-105 pointer-events-auto" | ||||||||||||||||||||||||||||||||||
| onClick={() => handleClear()} | ||||||||||||||||||||||||||||||||||
| onClick={handleNewConversation} | ||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||
| <span className="text-sm mr-2 group-hover:block hidden animate-in fade-in duration-300"> | ||||||||||||||||||||||||||||||||||
| New | ||||||||||||||||||||||||||||||||||
| New Conversation | ||||||||||||||||||||||||||||||||||
| </span> | ||||||||||||||||||||||||||||||||||
| <Plus size={18} className="group-hover:rotate-90 transition-all" /> | ||||||||||||||||||||||||||||||||||
| </Button> | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
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.
Starting a new conversation currently adds a server response to the local UI state even though the server returns no UI content for a pure
newChatrequest. This yields a blank message item in the timeline after clearing, which is confusing and unnecessary.Suggestion
Avoid appending the server response for a
newChat-only submit. You can still notify the server to create the conversation without pushing a UI item:const handleNewConversation = async () => {
setMessages([]);
clearAttachment();
const formData = new FormData();
formData.append('newChat', 'true');
await submit(formData); // no UI append here
};
Reply with "@CharlieHelps yes please" if you'd like me to add a commit with this change.