support message detail interface

This commit is contained in:
2023-11-08 15:55:52 +08:00
parent 11b835460b
commit 9142665585
3 changed files with 60 additions and 43 deletions

View File

@@ -13,6 +13,7 @@ import ChatGPT, {
calculate_token_length, calculate_token_length,
ChunkMessage, ChunkMessage,
FetchResponse, FetchResponse,
MessageDetail,
} from "./chatgpt"; } from "./chatgpt";
import Message from "./message"; import Message from "./message";
import models from "./models"; import models from "./models";
@@ -258,6 +259,7 @@ export default function ChatBOX(props: {
); );
_setTemplateAPIs(templateAPIs); _setTemplateAPIs(templateAPIs);
}; };
const [images, setImages] = useState<MessageDetail[]>([]);
return ( return (
<div className="grow flex flex-col p-2 dark:text-black"> <div className="grow flex flex-col p-2 dark:text-black">
@@ -633,7 +635,13 @@ export default function ChatBOX(props: {
chatStore.history chatStore.history
.filter(({ hide }) => !hide) .filter(({ hide }) => !hide)
.slice(chatStore.postBeginIndex) .slice(chatStore.postBeginIndex)
.map(({ content }) => content) .map(({ content }) => {
if (typeof content === "string") {
return content;
} else {
return content.map((c) => c?.text).join(" ");
}
})
) )
.concat([inputMsg]) .concat([inputMsg])
.join(" "); .join(" ");

View File

@@ -1,8 +1,22 @@
export interface MessageDetail {
type: "text" | "image_url";
text?: string;
image_url?: string;
}
export interface Message { export interface Message {
role: "system" | "user" | "assistant" | "function"; role: "system" | "user" | "assistant" | "function";
content: string; content: string | MessageDetail[];
name?: "example_user" | "example_assistant"; name?: "example_user" | "example_assistant";
} }
export const getMessageText = (message: Message): string => {
if (typeof message.content === "string") {
return message.content;
}
return message.content
.filter((c) => c.type === "text")
.map((c) => c?.text)
.join("\n");
};
export interface ChunkMessage { export interface ChunkMessage {
model: string; model: string;
@@ -29,9 +43,15 @@ export interface FetchResponse {
}[]; }[];
} }
// https://help.openai.com/en/articles/4936856-what-are-tokens-and-how-to-count-them // https://help.openai.com/en/articles/4936856-what-are-tokens-and-how-to-count-them
export function calculate_token_length(content: string): number { export function calculate_token_length(
const totalCount = content.length; content: string | MessageDetail[]
const chineseCount = content.match(/[\u00ff-\uffff]|\S+/g)?.length ?? 0; ): number {
const text =
typeof content === "string"
? content
: content.map((c) => c?.text).join(" ");
const totalCount = text.length;
const chineseCount = text.match(/[\u00ff-\uffff]|\S+/g)?.length ?? 0;
const englishCount = totalCount - chineseCount; const englishCount = totalCount - chineseCount;
const tokenLength = englishCount / 4 + (chineseCount * 4) / 3; const tokenLength = englishCount / 4 + (chineseCount * 4) / 3;
return ~~tokenLength; return ~~tokenLength;
@@ -151,12 +171,6 @@ class Chat {
return j; return j;
} }
async say(content: string): Promise<string> {
this.messages.push({ role: "user", content });
await this.complete();
return this.messages.slice(-1)[0].content;
}
async *processStreamResponse(resp: Response) { async *processStreamResponse(resp: Response) {
const reader = resp?.body?.pipeThrough(new TextDecoderStream()).getReader(); const reader = resp?.body?.pipeThrough(new TextDecoderStream()).getReader();
if (reader === undefined) { if (reader === undefined) {
@@ -210,7 +224,8 @@ class Chat {
} }
return ( return (
resp?.choices[0]?.message?.content ?? `Error: ${JSON.stringify(resp)}` (resp?.choices[0]?.message?.content as string) ??
`Error: ${JSON.stringify(resp)}`
); );
} }
@@ -221,7 +236,7 @@ class Chat {
completeWithSteam() { completeWithSteam() {
this.total_tokens = this.messages this.total_tokens = this.messages
.map((msg) => this.calculate_token_length(msg.content) + 20) .map((msg) => this.calculate_token_length(msg.content as string) + 20)
.reduce((a, v) => a + v); .reduce((a, v) => a + v);
return this._fetch(true); return this._fetch(true);
} }

View File

@@ -1,7 +1,7 @@
import { Tr, langCodeContext, LANG_OPTIONS } from "./translate"; import { Tr, langCodeContext, LANG_OPTIONS } from "./translate";
import { useState, useEffect, StateUpdater } from "preact/hooks"; import { useState, useEffect, StateUpdater } from "preact/hooks";
import { ChatStore, ChatStoreMessage } from "./app"; import { ChatStore, ChatStoreMessage } from "./app";
import { calculate_token_length } from "./chatgpt"; import { calculate_token_length, getMessageText } from "./chatgpt";
import Markdown from "preact-markdown"; import Markdown from "preact-markdown";
import TTSButton from "./tts"; import TTSButton from "./tts";
@@ -14,20 +14,6 @@ interface EditMessageProps {
function EditMessage(props: EditMessageProps) { function EditMessage(props: EditMessageProps) {
const { setShowEdit, chat, setChatStore, chatStore } = props; const { setShowEdit, chat, setChatStore, chatStore } = props;
useEffect(() => {
const handleKeyPress = (event: any) => {
if (event.keyCode === 27) {
// keyCode for ESC key is 27
setShowEdit(false);
}
};
document.addEventListener("keydown", handleKeyPress);
return () => {
document.removeEventListener("keydown", handleKeyPress);
};
}, []); // The empty dependency array ensures that the effect runs only once
return ( return (
<div <div
@@ -37,18 +23,25 @@ function EditMessage(props: EditMessageProps) {
onClick={() => setShowEdit(false)} onClick={() => setShowEdit(false)}
> >
<div className="w-full h-full z-20"> <div className="w-full h-full z-20">
<textarea {typeof chat.content === "string" && (
className={"w-full h-full"} <textarea
value={chat.content} className={"w-full h-full"}
onClick={(event: any) => { value={chat.content}
event.stopPropagation(); onClick={(event: any) => {
}} event.stopPropagation();
onChange={(event: any) => { }}
chat.content = event.target.value; onChange={(event: any) => {
chat.token = calculate_token_length(chat.content); chat.content = event.target.value;
setChatStore({ ...chatStore }); chat.token = calculate_token_length(chat.content);
}} setChatStore({ ...chatStore });
></textarea> }}
onKeyPress={(event: any) => {
if (event.keyCode == 27) {
setShowEdit(false);
}
}}
></textarea>
)}
<div className={"w-full flex justify-center"}> <div className={"w-full flex justify-center"}>
<button <button
className={"m-2 p-1 rounded bg-green-500"} className={"m-2 p-1 rounded bg-green-500"}
@@ -112,7 +105,7 @@ export default function Message(props: Props) {
<> <>
<button <button
onClick={() => { onClick={() => {
navigator.clipboard.writeText(chat.content); navigator.clipboard.writeText(getMessageText(chat));
setShowCopiedHint(true); setShowCopiedHint(true);
setTimeout(() => setShowCopiedHint(false), 1000); setTimeout(() => setShowCopiedHint(false), 1000);
}} }}
@@ -152,7 +145,8 @@ export default function Message(props: Props) {
> >
<p className={renderMarkdown ? "" : "message-content"}> <p className={renderMarkdown ? "" : "message-content"}>
{chat.hide ? ( {chat.hide ? (
chat.content.split("\n")[0].slice(0, 16) + "... (deleted)" getMessageText(chat).split("\n")[0].slice(0, 16) +
"... (deleted)"
) : renderMarkdown ? ( ) : renderMarkdown ? (
// @ts-ignore // @ts-ignore
<Markdown markdown={chat.content} /> <Markdown markdown={chat.content} />
@@ -167,7 +161,7 @@ export default function Message(props: Props) {
{chatStore.tts_api && chatStore.tts_key && ( {chatStore.tts_api && chatStore.tts_key && (
<TTSButton <TTSButton
chatStore={chatStore} chatStore={chatStore}
text={chat.content} text={getMessageText(chat)}
setChatStore={setChatStore} setChatStore={setChatStore}
/> />
)} )}