mirror of
https://github.com/jnsahaj/tweakcn.git
synced 2026-08-30 18:10:28 +08:00
d21c8e3d40
Uploaded images were inlined as full-resolution base64 in the message metadata, and the entire chat history is posted on every turn. Two 4MB uploads exceeded the 4.5MB request body cap, so the platform rejected the request before the route ran (FUNCTION_PAYLOAD_TOO_LARGE). - Downscale and re-encode raster uploads client-side (1536px longest edge, WebP with a JPEG fallback, ~300KB target). UI screenshots land around 12KB with imperceptible color drift, so token extraction is unaffected. - Trim images from the oldest messages when the serialized request body would still exceed the budget, rather than failing the request outright. - Raise MAX_IMAGE_FILES from 2 to 5 now that the payload allows it, and wrap the multi-image message preview row so the thumbnails stay legible. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
110 lines
3.4 KiB
TypeScript
110 lines
3.4 KiB
TypeScript
"use client";
|
|
|
|
import { SUBSCRIPTION_STATUS_QUERY_KEY } from "@/hooks/use-subscription";
|
|
import { toast } from "@/hooks/use-toast";
|
|
import { useAIChatStore } from "@/store/ai-chat-store";
|
|
import { ChatMessage } from "@/types/ai";
|
|
import { applyGeneratedTheme } from "@/utils/ai/apply-theme";
|
|
import { trimMessagesToRequestBudget } from "@/utils/ai/messages";
|
|
|
|
import { parseAiSdkTransportError } from "@/lib/ai/parse-ai-sdk-transport-error";
|
|
import { useChat } from "@ai-sdk/react";
|
|
import { useQueryClient } from "@tanstack/react-query";
|
|
import { DefaultChatTransport } from "ai";
|
|
import { createContext, useContext, useEffect, useRef } from "react";
|
|
|
|
interface ChatContext extends ReturnType<typeof useChat<ChatMessage>> {
|
|
startNewChat: () => void;
|
|
resetMessagesUpToIndex: (index: number) => void;
|
|
}
|
|
|
|
const ChatContext = createContext<ChatContext | null>(null);
|
|
|
|
export function ChatProvider({ children }: { children: React.ReactNode }) {
|
|
const queryClient = useQueryClient();
|
|
const storedMessages = useAIChatStore((s) => s.messages);
|
|
const setStoredMessages = useAIChatStore((s) => s.setMessages);
|
|
|
|
const hasStoreHydrated = useAIChatStore((s) => s.hasHydrated);
|
|
const hasInitializedRef = useRef(false);
|
|
|
|
const chat = useChat<ChatMessage>({
|
|
transport: new DefaultChatTransport({
|
|
api: "/api/generate-theme",
|
|
prepareSendMessagesRequest: ({ id, messages, trigger, messageId, body }) => ({
|
|
body: {
|
|
id,
|
|
trigger,
|
|
messageId,
|
|
...body,
|
|
messages: trimMessagesToRequestBudget(messages),
|
|
},
|
|
}),
|
|
}),
|
|
onError: (error) => {
|
|
const defaultMessage = "Failed to generate theme. Please try again.";
|
|
const normalizedError = parseAiSdkTransportError(error, defaultMessage);
|
|
|
|
toast({
|
|
title: "An error occurred",
|
|
description: normalizedError.message,
|
|
variant: "destructive",
|
|
});
|
|
},
|
|
onData: (dataPart) => {
|
|
const { type, data } = dataPart;
|
|
if (type === "data-generated-theme-styles") {
|
|
if (data.status === "ready") applyGeneratedTheme(data.themeStyles);
|
|
}
|
|
},
|
|
onFinish: () => {
|
|
queryClient.invalidateQueries({ queryKey: [SUBSCRIPTION_STATUS_QUERY_KEY] });
|
|
},
|
|
});
|
|
|
|
const startNewChat = () => {
|
|
chat.stop();
|
|
chat.setMessages([]);
|
|
};
|
|
|
|
const resetMessagesUpToIndex = (index: number) => {
|
|
const newMessages = chat.messages.slice(0, index);
|
|
chat.setMessages(newMessages);
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (!hasInitializedRef.current) return;
|
|
|
|
// Only update the stored messages when the chat is not currently processing a request
|
|
if (chat.status === "ready" || chat.status === "error") {
|
|
console.log("----- ✅ Updating Stored Messages -----");
|
|
setStoredMessages(chat.messages);
|
|
}
|
|
}, [chat.status, chat.messages]);
|
|
|
|
useEffect(() => {
|
|
if (!hasStoreHydrated || hasInitializedRef.current) return;
|
|
|
|
if (storedMessages.length > 0) {
|
|
console.log("----- ☑️ Populating Chat with Stored Messages -----");
|
|
chat.setMessages(storedMessages);
|
|
}
|
|
|
|
hasInitializedRef.current = true;
|
|
}, [hasStoreHydrated, storedMessages]);
|
|
|
|
return (
|
|
<ChatContext.Provider value={{ ...chat, startNewChat, resetMessagesUpToIndex }}>
|
|
{children}
|
|
</ChatContext.Provider>
|
|
);
|
|
}
|
|
|
|
export function useChatContext() {
|
|
const ctx = useContext(ChatContext);
|
|
if (!ctx) {
|
|
throw new Error("useChatContext must be used within an ChatProvider");
|
|
}
|
|
return ctx;
|
|
}
|