import { useState } from "preact/hooks"; import type { ChatStore } from "./app"; import ChatGPT, { ChunkMessage } from "./chatgpt"; import Settings from "./settings"; export default function ChatBOX(props: { chatStore: ChatStore; setChatStore: (cs: ChatStore) => void; }) { const { chatStore, setChatStore } = props; // prevent error if (chatStore === undefined) return
; const [inputMsg, setInputMsg] = useState(""); const [showGenerating, setShowGenerating] = useState(false); const [generatingMessage, setGeneratingMessage] = useState(""); const client = new ChatGPT(chatStore.apiKey); const _completeWithStreamMode = async () => { // call api, return reponse text const response = await client.completeWithSteam(); console.log("response", response); const reader = response.body?.getReader(); const allChunkMessage: string[] = []; new ReadableStream({ async start() { while (true) { let responseDone = false; let state = await reader?.read(); let done = state?.done; let value = state?.value; if (done) break; let text = new TextDecoder().decode(value); // console.log("text:", text); const lines = text .trim() .split("\n") .map((line) => line.trim()) .filter((i) => { if (!i) return false; if (i === "data: [DONE]") { responseDone = true; return false; } return true; }); console.log("lines", lines); const jsons: ChunkMessage[] = lines .map((line) => { return JSON.parse(line.trim().slice("data: ".length)); }) .filter((i) => i); // console.log("jsons", jsons); const chunkText = jsons .map((j) => j.choices[0].delta.content ?? "") .join(""); // console.log("chunk text", chunkText); allChunkMessage.push(chunkText); setShowGenerating(true); setGeneratingMessage(allChunkMessage.join("")); if (responseDone) break; } setShowGenerating(false); // console.log("push to history", allChunkMessage); chatStore.history.push({ role: "assistant", content: allChunkMessage.join(""), }); // manually copy status from client to chatStore chatStore.maxTokens = client.max_tokens; chatStore.tokenMargin = client.tokens_margin; chatStore.totalTokens = client.total_tokens + 39 + client.calculate_token_length(allChunkMessage.join("")); setChatStore({ ...chatStore }); setGeneratingMessage(""); setShowGenerating(false); }, }); }; const _completeWithFetchMode = async () => { // call api, return reponse text const response = await client.complete(); chatStore.history.push({ role: "assistant", content: response }); setShowGenerating(false); }; // wrap the actuall complete api const complete = async () => { // manually copy status from chatStore to client client.apiEndpoint = chatStore.apiEndpoint; client.sysMessageContent = chatStore.systemMessageContent; client.messages = chatStore.history.slice(chatStore.postBeginIndex); try { setShowGenerating(true); if (chatStore.streamMode) { await _completeWithStreamMode(); } else { await _completeWithFetchMode(); } // manually copy status from client to chatStore chatStore.maxTokens = client.max_tokens; chatStore.tokenMargin = client.tokens_margin; chatStore.totalTokens = client.total_tokens; // when total token > max token - margin token: // ChatGPT will "forgot" some historical message // so client.message.length will be less than chatStore.history.length chatStore.postBeginIndex = chatStore.history.length - client.messages.length; console.log("postBeginIndex", chatStore.postBeginIndex); setChatStore({ ...chatStore }); } catch (error) { alert(error); } finally { setShowGenerating(false); } }; // when user click the "send" button or ctrl+Enter in the textarea const send = async (msg = "") => { const inputMsg = msg; if (!inputMsg) { console.log("empty message"); return; } chatStore.history.push({ role: "user", content: inputMsg.trim() }); setChatStore({ ...chatStore }); setInputMsg(""); await complete(); setChatStore({ ...chatStore }); }; const [showSettings, setShowSettings] = useState(false); return (

setShowSettings(true)} >

{" "}
Total: {chatStore.totalTokens}{" "} Max: {chatStore.maxTokens}{" "} Margin: {chatStore.tokenMargin}{" "} Message: {chatStore.history.length - chatStore.postBeginIndex} {" "} Cut: {chatStore.postBeginIndex}

{!chatStore.apiKey && (

请先在上方设置 (OPENAI) API KEY

)} {!chatStore.apiEndpoint && (

请先在上方设置 API Endpoint

)} {chatStore.history.length === 0 && (

暂无历史对话记录

)} {chatStore.history.map((chat, i) => { const pClassName = chat.role === "assistant" ? "p-2 rounded relative bg-white my-2 text-left dark:bg-gray-700 dark:text-white" : "p-2 rounded relative bg-green-400 my-2 text-right"; const iconClassName = chat.role === "user" ? "absolute bottom-0 left-0" : "absolute bottom-0 right-0"; const DeleteIcon = () => ( ); return (

{chat.content .split("\n") .filter((line) => line) .map((line) => (

{line}

))}

); })} {showGenerating && (

{generatingMessage ? generatingMessage.split("\n").map((line) =>

{line}

) : "生成中,请保持网络稳定"} ...

)}
); }