Merge branch 'tool-call'
This commit is contained in:
10
src/app.tsx
10
src/app.tsx
@@ -21,9 +21,11 @@ export interface TemplateAPI {
|
||||
key: string;
|
||||
endpoint: string;
|
||||
}
|
||||
|
||||
export interface ChatStore {
|
||||
chatgpt_api_web_version: string;
|
||||
systemMessageContent: string;
|
||||
toolsString: string;
|
||||
history: ChatStoreMessage[];
|
||||
postBeginIndex: number;
|
||||
tokenMargin: number;
|
||||
@@ -67,11 +69,13 @@ export const newChatStore = (
|
||||
tts_api = "",
|
||||
tts_key = "",
|
||||
tts_speed = 1.0,
|
||||
tts_speed_enabled = false
|
||||
tts_speed_enabled = false,
|
||||
toolsString = ""
|
||||
): ChatStore => {
|
||||
return {
|
||||
chatgpt_api_web_version: CHATGPT_API_WEB_VERSION,
|
||||
systemMessageContent: getDefaultParams("sys", systemMessageContent),
|
||||
toolsString,
|
||||
history: [],
|
||||
postBeginIndex: 0,
|
||||
tokenMargin: 1024,
|
||||
@@ -173,6 +177,7 @@ export function App() {
|
||||
if (ret.maxGenTokens_enabled === undefined) ret.maxGenTokens_enabled = true;
|
||||
if (ret.model === undefined) ret.model = "gpt-3.5-turbo";
|
||||
if (ret.responseModelName === undefined) ret.responseModelName = "";
|
||||
if (ret.toolsString === undefined) ret.toolsString = "";
|
||||
if (ret.chatgpt_api_web_version === undefined)
|
||||
// this is from old version becasue it is undefined,
|
||||
// so no higher than v1.3.0
|
||||
@@ -250,7 +255,8 @@ export function App() {
|
||||
chatStore.tts_api,
|
||||
chatStore.tts_key,
|
||||
chatStore.tts_speed,
|
||||
chatStore.tts_speed_enabled
|
||||
chatStore.tts_speed_enabled,
|
||||
chatStore.toolsString
|
||||
)
|
||||
);
|
||||
setSelectedChatIndex(newKey as number);
|
||||
|
||||
163
src/chatbox.tsx
163
src/chatbox.tsx
@@ -13,7 +13,9 @@ import ChatGPT, {
|
||||
calculate_token_length,
|
||||
ChunkMessage,
|
||||
FetchResponse,
|
||||
Message as MessageType,
|
||||
MessageDetail,
|
||||
ToolCall,
|
||||
} from "./chatgpt";
|
||||
import Message from "./message";
|
||||
import models from "./models";
|
||||
@@ -41,6 +43,9 @@ export default function ChatBOX(props: {
|
||||
const [generatingMessage, setGeneratingMessage] = useState("");
|
||||
const [showRetry, setShowRetry] = useState(false);
|
||||
const [isRecording, setIsRecording] = useState("Mic");
|
||||
const [showAddToolMsg, setShowAddToolMsg] = useState(false);
|
||||
const [newToolCallID, setNewToolCallID] = useState("");
|
||||
const [newToolContent, setNewToolContent] = useState("");
|
||||
const mediaRef = createRef();
|
||||
|
||||
const messagesEndRef = createRef();
|
||||
@@ -67,12 +72,48 @@ export default function ChatBOX(props: {
|
||||
let responseTokenCount = 0;
|
||||
chatStore.streamMode = true;
|
||||
const allChunkMessage: string[] = [];
|
||||
const allChunkTool: ToolCall[] = [];
|
||||
setShowGenerating(true);
|
||||
for await (const i of client.processStreamResponse(response)) {
|
||||
chatStore.responseModelName = i.model;
|
||||
responseTokenCount += 1;
|
||||
allChunkMessage.push(i.choices[0].delta.content ?? "");
|
||||
setGeneratingMessage(allChunkMessage.join(""));
|
||||
const tool_calls = i.choices[0].delta.tool_calls;
|
||||
if (tool_calls) {
|
||||
for (const tool_call of tool_calls) {
|
||||
// init
|
||||
if (tool_call.id) {
|
||||
allChunkTool.push({
|
||||
id: tool_call.id,
|
||||
type: tool_call.type,
|
||||
index: tool_call.index,
|
||||
function: {
|
||||
name: tool_call.function.name,
|
||||
arguments: "",
|
||||
},
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// update tool call arguments
|
||||
const tool = allChunkTool.find(
|
||||
(tool) => tool.index === tool_call.index
|
||||
);
|
||||
|
||||
if (!tool) {
|
||||
console.log("tool (by index) not found", tool_call.index);
|
||||
continue;
|
||||
}
|
||||
|
||||
tool.function.arguments += tool_call.function.arguments;
|
||||
}
|
||||
}
|
||||
setGeneratingMessage(
|
||||
allChunkMessage.join("") +
|
||||
allChunkTool.map((tool) => {
|
||||
return `Tool Call ID: ${tool.id}\nType: ${tool.type}\nFunction: ${tool.function.name}\nArguments: ${tool.function.arguments}`;
|
||||
})
|
||||
);
|
||||
}
|
||||
setShowGenerating(false);
|
||||
const content = allChunkMessage.join("");
|
||||
@@ -99,6 +140,7 @@ export default function ChatBOX(props: {
|
||||
chatStore.history.push({
|
||||
role: "assistant",
|
||||
content,
|
||||
tool_calls: allChunkTool,
|
||||
hide: false,
|
||||
token: responseTokenCount,
|
||||
example: false,
|
||||
@@ -127,7 +169,7 @@ export default function ChatBOX(props: {
|
||||
chatStore.cost += cost;
|
||||
addTotalCost(cost);
|
||||
}
|
||||
const content = client.processFetchResponse(data);
|
||||
const msg = client.processFetchResponse(data);
|
||||
|
||||
// estimate user's input message token
|
||||
let aboveToken = 0;
|
||||
@@ -147,9 +189,11 @@ export default function ChatBOX(props: {
|
||||
|
||||
chatStore.history.push({
|
||||
role: "assistant",
|
||||
content,
|
||||
content: msg.content,
|
||||
tool_calls: msg.tool_calls,
|
||||
hide: false,
|
||||
token: data.usage.completion_tokens ?? calculate_token_length(content),
|
||||
token:
|
||||
data.usage.completion_tokens ?? calculate_token_length(msg.content),
|
||||
example: false,
|
||||
});
|
||||
setShowGenerating(false);
|
||||
@@ -160,6 +204,7 @@ export default function ChatBOX(props: {
|
||||
// manually copy status from chatStore to client
|
||||
client.apiEndpoint = chatStore.apiEndpoint;
|
||||
client.sysMessageContent = chatStore.systemMessageContent;
|
||||
client.toolsString = chatStore.toolsString;
|
||||
client.tokens_margin = chatStore.tokenMargin;
|
||||
client.temperature = chatStore.temperature;
|
||||
client.enable_temperature = chatStore.temperature_enabled;
|
||||
@@ -172,18 +217,22 @@ export default function ChatBOX(props: {
|
||||
.filter(({ hide }) => !hide)
|
||||
.slice(chatStore.postBeginIndex)
|
||||
// only copy content and role attribute to client for posting
|
||||
.map(({ content, role, example }) => {
|
||||
if (example) {
|
||||
return {
|
||||
content,
|
||||
role: "system",
|
||||
name: role === "assistant" ? "example_assistant" : "example_user",
|
||||
};
|
||||
}
|
||||
return {
|
||||
.map(({ content, role, example, tool_call_id, tool_calls }) => {
|
||||
const ret: MessageType = {
|
||||
content,
|
||||
role,
|
||||
tool_calls,
|
||||
};
|
||||
|
||||
if (example) {
|
||||
ret.name =
|
||||
ret.role === "assistant" ? "example_assistant" : "example_user";
|
||||
ret.role = "system";
|
||||
}
|
||||
|
||||
if (tool_call_id) ret.tool_call_id = tool_call_id;
|
||||
|
||||
return ret;
|
||||
});
|
||||
client.model = chatStore.model;
|
||||
client.max_tokens = chatStore.maxTokens;
|
||||
@@ -406,6 +455,7 @@ export default function ChatBOX(props: {
|
||||
className="mx-2 underline cursor-pointer"
|
||||
onClick={() => {
|
||||
chatStore.systemMessageContent = "";
|
||||
chatStore.toolsString = "";
|
||||
chatStore.history = [];
|
||||
setChatStore({ ...chatStore });
|
||||
}}
|
||||
@@ -943,6 +993,93 @@ export default function ChatBOX(props: {
|
||||
{Tr("User")}
|
||||
</button>
|
||||
)}
|
||||
{chatStore.develop_mode && (
|
||||
<button
|
||||
className="disabled:line-through disabled:bg-slate-500 rounded m-1 p-1 border-2 bg-cyan-400 hover:bg-cyan-600"
|
||||
disabled={showGenerating || !chatStore.apiKey}
|
||||
onClick={() => {
|
||||
setShowAddToolMsg(true);
|
||||
}}
|
||||
>
|
||||
{Tr("Tool")}
|
||||
</button>
|
||||
)}
|
||||
{showAddToolMsg && (
|
||||
<div
|
||||
className="absolute z-10 bg-black bg-opacity-50 w-full h-full flex justify-center items-center left-0 top-0 overflow-scroll"
|
||||
onClick={() => {
|
||||
setShowAddToolMsg(false);
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="bg-white rounded p-2 z-20 flex flex-col"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<h2>Add Tool Message</h2>
|
||||
<hr className="my-2" />
|
||||
<span>
|
||||
<label>tool_call_id</label>
|
||||
<input
|
||||
className="rounded m-1 p-1 border-2 border-gray-400"
|
||||
type="text"
|
||||
value={newToolCallID}
|
||||
onChange={(event: any) =>
|
||||
setNewToolCallID(event.target.value)
|
||||
}
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
<label>Content</label>
|
||||
<textarea
|
||||
className="rounded m-1 p-1 border-2 border-gray-400"
|
||||
rows={5}
|
||||
value={newToolContent}
|
||||
onChange={(event: any) =>
|
||||
setNewToolContent(event.target.value)
|
||||
}
|
||||
></textarea>
|
||||
</span>
|
||||
<span className={`flex justify-between p-2`}>
|
||||
<button
|
||||
className="rounded m-1 p-1 border-2 bg-red-400 hover:bg-red-600"
|
||||
onClick={() => setShowAddToolMsg(false)}
|
||||
>
|
||||
{Tr("Cancle")}
|
||||
</button>
|
||||
<button
|
||||
className="rounded m-1 p-1 border-2 bg-cyan-400 hover:bg-cyan-600"
|
||||
onClick={() => {
|
||||
if (!newToolCallID.trim()) {
|
||||
alert("tool_call_id is empty");
|
||||
return;
|
||||
}
|
||||
if (!newToolContent.trim()) {
|
||||
alert("content is empty");
|
||||
return;
|
||||
}
|
||||
chatStore.history.push({
|
||||
role: "tool",
|
||||
tool_call_id: newToolCallID.trim(),
|
||||
content: newToolContent.trim(),
|
||||
token: calculate_token_length(newToolContent),
|
||||
hide: false,
|
||||
example: false,
|
||||
});
|
||||
update_total_tokens();
|
||||
setChatStore({ ...chatStore });
|
||||
setNewToolCallID("");
|
||||
setNewToolContent("");
|
||||
setShowAddToolMsg(false);
|
||||
}}
|
||||
>
|
||||
{Tr("Add")}
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -8,13 +8,53 @@ export interface MessageDetail {
|
||||
text?: string;
|
||||
image_url?: ImageURL;
|
||||
}
|
||||
export interface ToolCall {
|
||||
index: number;
|
||||
id?: string;
|
||||
type: string;
|
||||
function: {
|
||||
name: string;
|
||||
arguments: string;
|
||||
};
|
||||
}
|
||||
export interface Message {
|
||||
role: "system" | "user" | "assistant" | "function";
|
||||
role: "system" | "user" | "assistant" | "tool";
|
||||
content: string | MessageDetail[];
|
||||
name?: "example_user" | "example_assistant";
|
||||
tool_calls?: ToolCall[];
|
||||
tool_call_id?: string;
|
||||
}
|
||||
|
||||
interface Delta {
|
||||
role?: string;
|
||||
content?: string;
|
||||
tool_calls?: ToolCall[];
|
||||
}
|
||||
|
||||
interface Choices {
|
||||
index: number;
|
||||
delta: Delta;
|
||||
finish_reason: string | null;
|
||||
}
|
||||
|
||||
export interface StreamingResponseChunk {
|
||||
id: string;
|
||||
object: string;
|
||||
created: number;
|
||||
model: string;
|
||||
system_fingerprint: string;
|
||||
choices: Choices[];
|
||||
}
|
||||
export const getMessageText = (message: Message): string => {
|
||||
if (typeof message.content === "string") {
|
||||
// function call message
|
||||
if (message.tool_calls) {
|
||||
return message.tool_calls
|
||||
.map((tc) => {
|
||||
return `Tool Call ID: ${tc.id}\nType: ${tc.type}\nFunction: ${tc.function.name}\nArguments: ${tc.function.arguments}}`;
|
||||
})
|
||||
.join("\n");
|
||||
}
|
||||
return message.content;
|
||||
}
|
||||
return message.content
|
||||
@@ -78,6 +118,7 @@ class Chat {
|
||||
OPENAI_API_KEY: string;
|
||||
messages: Message[];
|
||||
sysMessageContent: string;
|
||||
toolsString: string;
|
||||
total_tokens: number;
|
||||
max_tokens: number;
|
||||
max_gen_tokens: number;
|
||||
@@ -96,6 +137,7 @@ class Chat {
|
||||
OPENAI_API_KEY: string | undefined,
|
||||
{
|
||||
systemMessage = "",
|
||||
toolsString = "",
|
||||
max_tokens = 4096,
|
||||
max_gen_tokens = 2048,
|
||||
enable_max_gen_tokens = true,
|
||||
@@ -121,6 +163,7 @@ class Chat {
|
||||
this.enable_max_gen_tokens = enable_max_gen_tokens;
|
||||
this.tokens_margin = tokens_margin;
|
||||
this.sysMessageContent = systemMessage;
|
||||
this.toolsString = toolsString;
|
||||
this.apiEndpoint = apiEndPoint;
|
||||
this.model = model;
|
||||
this.temperature = temperature;
|
||||
@@ -178,6 +221,25 @@ class Chat {
|
||||
body["max_tokens"] = this.max_gen_tokens;
|
||||
}
|
||||
|
||||
// parse toolsString to function call format
|
||||
const ts = this.toolsString.trim();
|
||||
if (ts) {
|
||||
try {
|
||||
const fcList: any[] = JSON.parse(ts);
|
||||
body["tools"] = fcList.map((fc) => {
|
||||
return {
|
||||
type: "function",
|
||||
function: fc,
|
||||
};
|
||||
});
|
||||
} catch (e) {
|
||||
console.log("toolsString parse error");
|
||||
throw (
|
||||
"Function call toolsString parse error, not a valied json list: " + e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return fetch(this.apiEndpoint, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
@@ -224,7 +286,7 @@ class Chat {
|
||||
console.log("line", line);
|
||||
try {
|
||||
const jsonStr = line.slice("data:".length).trim();
|
||||
const json = JSON.parse(jsonStr);
|
||||
const json = JSON.parse(jsonStr) as StreamingResponseChunk;
|
||||
yield json;
|
||||
} catch (e) {
|
||||
console.log(`Chunk parse error at: ${line}`);
|
||||
@@ -234,7 +296,7 @@ class Chat {
|
||||
}
|
||||
}
|
||||
|
||||
processFetchResponse(resp: FetchResponse): string {
|
||||
processFetchResponse(resp: FetchResponse): Message {
|
||||
if (resp.error !== undefined) {
|
||||
throw JSON.stringify(resp.error);
|
||||
}
|
||||
@@ -249,15 +311,19 @@ class Chat {
|
||||
this.forgetSomeMessages();
|
||||
}
|
||||
|
||||
return (
|
||||
(resp?.choices[0]?.message?.content as string) ??
|
||||
`Error: ${JSON.stringify(resp)}`
|
||||
);
|
||||
}
|
||||
let content = resp.choices[0].message?.content ?? "";
|
||||
if (
|
||||
!resp.choices[0]?.message?.content &&
|
||||
!resp.choices[0]?.message?.tool_calls
|
||||
) {
|
||||
content = `Unparsed response: ${JSON.stringify(resp)}`;
|
||||
}
|
||||
|
||||
async complete(): Promise<string> {
|
||||
const resp = await this.fetch();
|
||||
return this.processFetchResponse(resp);
|
||||
return {
|
||||
role: "assistant",
|
||||
content,
|
||||
tool_calls: resp?.choices[0]?.message?.tool_calls,
|
||||
};
|
||||
}
|
||||
|
||||
completeWithSteam() {
|
||||
|
||||
@@ -28,6 +28,6 @@ body::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
p.message-content {
|
||||
.message-content {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
217
src/message.tsx
217
src/message.tsx
@@ -12,35 +12,97 @@ interface EditMessageProps {
|
||||
setChatStore: (cs: ChatStore) => void;
|
||||
}
|
||||
|
||||
export const isVailedJSON = (str: string): boolean => {
|
||||
try {
|
||||
JSON.parse(str);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
function EditMessage(props: EditMessageProps) {
|
||||
const { setShowEdit, chat, setChatStore, chatStore } = props;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
"absolute bg-black bg-opacity-50 w-full h-full top-0 left-0 pt-5 px-5 pb-20 rounded z-10"
|
||||
"absolute bg-black bg-opacity-50 w-full h-full top-0 left-0 rounded z-10 overflow-scroll"
|
||||
}
|
||||
onClick={() => setShowEdit(false)}
|
||||
>
|
||||
<div className="w-full h-full z-20">
|
||||
<div
|
||||
className="m-10 p-2 bg-white rounded"
|
||||
onClick={(event: any) => {
|
||||
event.stopPropagation();
|
||||
}}
|
||||
>
|
||||
{typeof chat.content === "string" ? (
|
||||
<textarea
|
||||
className={"w-full h-full"}
|
||||
value={chat.content}
|
||||
onClick={(event: any) => {
|
||||
event.stopPropagation();
|
||||
}}
|
||||
onChange={(event: any) => {
|
||||
chat.content = event.target.value;
|
||||
chat.token = calculate_token_length(chat.content);
|
||||
setChatStore({ ...chatStore });
|
||||
}}
|
||||
onKeyPress={(event: any) => {
|
||||
if (event.keyCode == 27) {
|
||||
setShowEdit(false);
|
||||
}
|
||||
}}
|
||||
></textarea>
|
||||
<div className="flex flex-col">
|
||||
{chat.tool_call_id && (
|
||||
<span className="my-2">
|
||||
<label>tool_call_id: </label>
|
||||
<input
|
||||
className="rounded border border-gray-400"
|
||||
value={chat.tool_call_id}
|
||||
onChange={(event: any) => {
|
||||
chat.tool_call_id = event.target.value;
|
||||
setChatStore({ ...chatStore });
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
)}
|
||||
{chat.tool_calls &&
|
||||
chat.tool_calls.map((tool_call) => (
|
||||
<div className="flex flex-col w-full">
|
||||
<span className="my-2 w-full">
|
||||
<label>Tool Call ID: </label>
|
||||
<input
|
||||
value={tool_call.id}
|
||||
className="rounded border border-gray-400"
|
||||
/>
|
||||
</span>
|
||||
<span className="my-2 w-full">
|
||||
<label>Function: </label>
|
||||
<input
|
||||
value={tool_call.function.name}
|
||||
className="rounded border border-gray-400"
|
||||
/>
|
||||
</span>
|
||||
<span className="my-2">
|
||||
<label>Arguments: </label>
|
||||
<span className="underline">
|
||||
Vailed JSON:{" "}
|
||||
{isVailedJSON(tool_call.function.arguments) ? "🆗" : "❌"}
|
||||
</span>
|
||||
<textarea
|
||||
className="rounded border border-gray-400 w-full h-32 my-2"
|
||||
value={tool_call.function.arguments}
|
||||
onChange={(event: any) => {
|
||||
tool_call.function.arguments =
|
||||
event.target.value.trim();
|
||||
setChatStore({ ...chatStore });
|
||||
}}
|
||||
></textarea>
|
||||
</span>
|
||||
<hr className="my-2" />
|
||||
</div>
|
||||
))}
|
||||
<textarea
|
||||
className="rounded border border-gray-400 w-full h-32 my-2"
|
||||
value={chat.content}
|
||||
onChange={(event: any) => {
|
||||
chat.content = event.target.value;
|
||||
chat.token = calculate_token_length(chat.content);
|
||||
setChatStore({ ...chatStore });
|
||||
}}
|
||||
onKeyPress={(event: any) => {
|
||||
if (event.keyCode == 27) {
|
||||
setShowEdit(false);
|
||||
}
|
||||
}}
|
||||
></textarea>
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
className={"w-full h-full flex flex-col overflow-scroll"}
|
||||
@@ -245,14 +307,18 @@ export default function Message(props: Props) {
|
||||
</span>
|
||||
);
|
||||
|
||||
const CopyIcon = () => {
|
||||
const copyToClipboard = (text: string) => {
|
||||
navigator.clipboard.writeText(text);
|
||||
setShowCopiedHint(true);
|
||||
setTimeout(() => setShowCopiedHint(false), 1000);
|
||||
};
|
||||
|
||||
const CopyIcon = ({ textToCopy }: { textToCopy: string }) => {
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(getMessageText(chat));
|
||||
setShowCopiedHint(true);
|
||||
setTimeout(() => setShowCopiedHint(false), 1000);
|
||||
copyToClipboard(textToCopy);
|
||||
}}
|
||||
>
|
||||
📋
|
||||
@@ -288,40 +354,81 @@ export default function Message(props: Props) {
|
||||
: "bg-green-400"
|
||||
} ${chat.hide ? "opacity-50" : ""}`}
|
||||
>
|
||||
<p className={renderMarkdown ? "" : "message-content"}>
|
||||
{typeof chat.content !== "string" ? (
|
||||
// render for multiple messages
|
||||
chat.content.map((mdt) =>
|
||||
mdt.type === "text" ? (
|
||||
chat.hide ? (
|
||||
mdt.text?.split("\n")[0].slice(0, 16) + "... (deleted)"
|
||||
) : renderMarkdown ? (
|
||||
// @ts-ignore
|
||||
<Markdown markdown={mdt.text} />
|
||||
) : (
|
||||
mdt.text
|
||||
)
|
||||
{chat.hide ? (
|
||||
getMessageText(chat).split("\n")[0].slice(0, 18) + "... (deleted)"
|
||||
) : typeof chat.content !== "string" ? (
|
||||
chat.content.map((mdt) =>
|
||||
mdt.type === "text" ? (
|
||||
chat.hide ? (
|
||||
mdt.text?.split("\n")[0].slice(0, 16) + "... (deleted)"
|
||||
) : renderMarkdown ? (
|
||||
// @ts-ignore
|
||||
<Markdown markdown={mdt.text} />
|
||||
) : (
|
||||
<img
|
||||
className="cursor-pointer max-w-xs max-h-32 p-1"
|
||||
src={mdt.image_url?.url}
|
||||
onClick={() => {
|
||||
window.open(mdt.image_url?.url, "_blank");
|
||||
}}
|
||||
/>
|
||||
mdt.text
|
||||
)
|
||||
) : (
|
||||
<img
|
||||
className="cursor-pointer max-w-xs max-h-32 p-1"
|
||||
src={mdt.image_url?.url}
|
||||
onClick={() => {
|
||||
window.open(mdt.image_url?.url, "_blank");
|
||||
}}
|
||||
/>
|
||||
)
|
||||
) : // render for single message
|
||||
chat.hide ? (
|
||||
getMessageText(chat).split("\n")[0].slice(0, 16) +
|
||||
"... (deleted)"
|
||||
) : renderMarkdown ? (
|
||||
// @ts-ignore
|
||||
<Markdown markdown={getMessageText(chat)} />
|
||||
) : (
|
||||
getMessageText(chat)
|
||||
)}
|
||||
</p>
|
||||
)
|
||||
) : chat.tool_calls ? (
|
||||
<div className="message-content">
|
||||
<div>
|
||||
{chat.tool_calls?.map((tool_call) => (
|
||||
<div className="bg-blue-300 dark:bg-blue-800 p-1 rounded my-1">
|
||||
<strong>
|
||||
Tool Call ID:{" "}
|
||||
<span
|
||||
className="p-1 m-1 rounded cursor-pointer hover:opacity-50 hover:underline"
|
||||
onClick={() => copyToClipboard(String(tool_call.id))}
|
||||
>
|
||||
{tool_call?.id}
|
||||
</span>
|
||||
</strong>
|
||||
<p>Type: {tool_call?.type}</p>
|
||||
<p>
|
||||
Function:
|
||||
<span
|
||||
className="p-1 m-1 rounded cursor-pointer hover:opacity-50 hover:underline"
|
||||
onClick={() =>
|
||||
copyToClipboard(tool_call.function.name)
|
||||
}
|
||||
>
|
||||
{tool_call.function.name}
|
||||
</span>
|
||||
</p>
|
||||
<p>
|
||||
Arguments:
|
||||
<span
|
||||
className="p-1 m-1 rounded cursor-pointer hover:opacity-50 hover:underline"
|
||||
onClick={() =>
|
||||
copyToClipboard(tool_call.function.arguments)
|
||||
}
|
||||
>
|
||||
{tool_call.function.arguments}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
) : renderMarkdown ? (
|
||||
// @ts-ignore
|
||||
<Markdown markdown={getMessageText(chat)} />
|
||||
) : (
|
||||
<div className="message-content">
|
||||
{
|
||||
// only show when content is string or list of message
|
||||
chat.content && getMessageText(chat)
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
<hr className="mt-2" />
|
||||
<div className="w-full flex justify-between">
|
||||
<DeleteIcon />
|
||||
@@ -333,7 +440,7 @@ export default function Message(props: Props) {
|
||||
setChatStore={setChatStore}
|
||||
/>
|
||||
)}
|
||||
<CopyIcon />
|
||||
<CopyIcon textToCopy={getMessageText(chat)} />
|
||||
</div>
|
||||
</div>
|
||||
{showEdit && (
|
||||
|
||||
@@ -5,6 +5,7 @@ import models from "./models";
|
||||
import { TemplateChatStore } from "./chatbox";
|
||||
import { tr, Tr, langCodeContext, LANG_OPTIONS } from "./translate";
|
||||
import p from "preact-markdown";
|
||||
import { isVailedJSON } from "./message";
|
||||
|
||||
const TTS_VOICES: string[] = [
|
||||
"alloy",
|
||||
@@ -60,7 +61,7 @@ const SelectModel = (props: {
|
||||
const LongInput = (props: {
|
||||
chatStore: ChatStore;
|
||||
setChatStore: (cs: ChatStore) => void;
|
||||
field: "systemMessageContent";
|
||||
field: "systemMessageContent" | "toolsString";
|
||||
help: string;
|
||||
}) => {
|
||||
return (
|
||||
@@ -373,6 +374,15 @@ export default (props: {
|
||||
help="系统消息,用于指示ChatGPT的角色和一些前置条件,例如“你是一个有帮助的人工智能助理”,或者“你是一个专业英语翻译,把我的话全部翻译成英语”,详情参考 OPEAN AI API 文档"
|
||||
{...props}
|
||||
/>
|
||||
<span>
|
||||
Valied JSON:{" "}
|
||||
{isVailedJSON(props.chatStore.toolsString) ? "🆗" : "❌"}
|
||||
</span>
|
||||
<LongInput
|
||||
field="toolsString"
|
||||
help="function call tools, should be valied json format in list"
|
||||
{...props}
|
||||
/>
|
||||
<Input
|
||||
field="apiKey"
|
||||
help="OPEN AI API 密钥,请勿泄漏此密钥"
|
||||
|
||||
Reference in New Issue
Block a user