Merge branch 'tool-call'

This commit is contained in:
2023-11-10 19:51:07 +08:00
6 changed files with 409 additions and 83 deletions

View File

@@ -21,9 +21,11 @@ export interface TemplateAPI {
key: string; key: string;
endpoint: string; endpoint: string;
} }
export interface ChatStore { export interface ChatStore {
chatgpt_api_web_version: string; chatgpt_api_web_version: string;
systemMessageContent: string; systemMessageContent: string;
toolsString: string;
history: ChatStoreMessage[]; history: ChatStoreMessage[];
postBeginIndex: number; postBeginIndex: number;
tokenMargin: number; tokenMargin: number;
@@ -67,11 +69,13 @@ export const newChatStore = (
tts_api = "", tts_api = "",
tts_key = "", tts_key = "",
tts_speed = 1.0, tts_speed = 1.0,
tts_speed_enabled = false tts_speed_enabled = false,
toolsString = ""
): ChatStore => { ): ChatStore => {
return { return {
chatgpt_api_web_version: CHATGPT_API_WEB_VERSION, chatgpt_api_web_version: CHATGPT_API_WEB_VERSION,
systemMessageContent: getDefaultParams("sys", systemMessageContent), systemMessageContent: getDefaultParams("sys", systemMessageContent),
toolsString,
history: [], history: [],
postBeginIndex: 0, postBeginIndex: 0,
tokenMargin: 1024, tokenMargin: 1024,
@@ -173,6 +177,7 @@ export function App() {
if (ret.maxGenTokens_enabled === undefined) ret.maxGenTokens_enabled = true; if (ret.maxGenTokens_enabled === undefined) ret.maxGenTokens_enabled = true;
if (ret.model === undefined) ret.model = "gpt-3.5-turbo"; if (ret.model === undefined) ret.model = "gpt-3.5-turbo";
if (ret.responseModelName === undefined) ret.responseModelName = ""; if (ret.responseModelName === undefined) ret.responseModelName = "";
if (ret.toolsString === undefined) ret.toolsString = "";
if (ret.chatgpt_api_web_version === undefined) if (ret.chatgpt_api_web_version === undefined)
// this is from old version becasue it is undefined, // this is from old version becasue it is undefined,
// so no higher than v1.3.0 // so no higher than v1.3.0
@@ -250,7 +255,8 @@ export function App() {
chatStore.tts_api, chatStore.tts_api,
chatStore.tts_key, chatStore.tts_key,
chatStore.tts_speed, chatStore.tts_speed,
chatStore.tts_speed_enabled chatStore.tts_speed_enabled,
chatStore.toolsString
) )
); );
setSelectedChatIndex(newKey as number); setSelectedChatIndex(newKey as number);

View File

@@ -13,7 +13,9 @@ import ChatGPT, {
calculate_token_length, calculate_token_length,
ChunkMessage, ChunkMessage,
FetchResponse, FetchResponse,
Message as MessageType,
MessageDetail, MessageDetail,
ToolCall,
} from "./chatgpt"; } from "./chatgpt";
import Message from "./message"; import Message from "./message";
import models from "./models"; import models from "./models";
@@ -41,6 +43,9 @@ export default function ChatBOX(props: {
const [generatingMessage, setGeneratingMessage] = useState(""); const [generatingMessage, setGeneratingMessage] = useState("");
const [showRetry, setShowRetry] = useState(false); const [showRetry, setShowRetry] = useState(false);
const [isRecording, setIsRecording] = useState("Mic"); const [isRecording, setIsRecording] = useState("Mic");
const [showAddToolMsg, setShowAddToolMsg] = useState(false);
const [newToolCallID, setNewToolCallID] = useState("");
const [newToolContent, setNewToolContent] = useState("");
const mediaRef = createRef(); const mediaRef = createRef();
const messagesEndRef = createRef(); const messagesEndRef = createRef();
@@ -67,12 +72,48 @@ export default function ChatBOX(props: {
let responseTokenCount = 0; let responseTokenCount = 0;
chatStore.streamMode = true; chatStore.streamMode = true;
const allChunkMessage: string[] = []; const allChunkMessage: string[] = [];
const allChunkTool: ToolCall[] = [];
setShowGenerating(true); setShowGenerating(true);
for await (const i of client.processStreamResponse(response)) { for await (const i of client.processStreamResponse(response)) {
chatStore.responseModelName = i.model; chatStore.responseModelName = i.model;
responseTokenCount += 1; responseTokenCount += 1;
allChunkMessage.push(i.choices[0].delta.content ?? ""); 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); setShowGenerating(false);
const content = allChunkMessage.join(""); const content = allChunkMessage.join("");
@@ -99,6 +140,7 @@ export default function ChatBOX(props: {
chatStore.history.push({ chatStore.history.push({
role: "assistant", role: "assistant",
content, content,
tool_calls: allChunkTool,
hide: false, hide: false,
token: responseTokenCount, token: responseTokenCount,
example: false, example: false,
@@ -127,7 +169,7 @@ export default function ChatBOX(props: {
chatStore.cost += cost; chatStore.cost += cost;
addTotalCost(cost); addTotalCost(cost);
} }
const content = client.processFetchResponse(data); const msg = client.processFetchResponse(data);
// estimate user's input message token // estimate user's input message token
let aboveToken = 0; let aboveToken = 0;
@@ -147,9 +189,11 @@ export default function ChatBOX(props: {
chatStore.history.push({ chatStore.history.push({
role: "assistant", role: "assistant",
content, content: msg.content,
tool_calls: msg.tool_calls,
hide: false, hide: false,
token: data.usage.completion_tokens ?? calculate_token_length(content), token:
data.usage.completion_tokens ?? calculate_token_length(msg.content),
example: false, example: false,
}); });
setShowGenerating(false); setShowGenerating(false);
@@ -160,6 +204,7 @@ export default function ChatBOX(props: {
// manually copy status from chatStore to client // manually copy status from chatStore to client
client.apiEndpoint = chatStore.apiEndpoint; client.apiEndpoint = chatStore.apiEndpoint;
client.sysMessageContent = chatStore.systemMessageContent; client.sysMessageContent = chatStore.systemMessageContent;
client.toolsString = chatStore.toolsString;
client.tokens_margin = chatStore.tokenMargin; client.tokens_margin = chatStore.tokenMargin;
client.temperature = chatStore.temperature; client.temperature = chatStore.temperature;
client.enable_temperature = chatStore.temperature_enabled; client.enable_temperature = chatStore.temperature_enabled;
@@ -172,18 +217,22 @@ export default function ChatBOX(props: {
.filter(({ hide }) => !hide) .filter(({ hide }) => !hide)
.slice(chatStore.postBeginIndex) .slice(chatStore.postBeginIndex)
// only copy content and role attribute to client for posting // only copy content and role attribute to client for posting
.map(({ content, role, example }) => { .map(({ content, role, example, tool_call_id, tool_calls }) => {
if (example) { const ret: MessageType = {
return {
content,
role: "system",
name: role === "assistant" ? "example_assistant" : "example_user",
};
}
return {
content, content,
role, 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.model = chatStore.model;
client.max_tokens = chatStore.maxTokens; client.max_tokens = chatStore.maxTokens;
@@ -406,6 +455,7 @@ export default function ChatBOX(props: {
className="mx-2 underline cursor-pointer" className="mx-2 underline cursor-pointer"
onClick={() => { onClick={() => {
chatStore.systemMessageContent = ""; chatStore.systemMessageContent = "";
chatStore.toolsString = "";
chatStore.history = []; chatStore.history = [];
setChatStore({ ...chatStore }); setChatStore({ ...chatStore });
}} }}
@@ -943,6 +993,93 @@ export default function ChatBOX(props: {
{Tr("User")} {Tr("User")}
</button> </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>
</div> </div>
); );

View File

@@ -8,13 +8,53 @@ export interface MessageDetail {
text?: string; text?: string;
image_url?: ImageURL; image_url?: ImageURL;
} }
export interface ToolCall {
index: number;
id?: string;
type: string;
function: {
name: string;
arguments: string;
};
}
export interface Message { export interface Message {
role: "system" | "user" | "assistant" | "function"; role: "system" | "user" | "assistant" | "tool";
content: string | MessageDetail[]; content: string | MessageDetail[];
name?: "example_user" | "example_assistant"; 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 => { export const getMessageText = (message: Message): string => {
if (typeof message.content === "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;
} }
return message.content return message.content
@@ -78,6 +118,7 @@ class Chat {
OPENAI_API_KEY: string; OPENAI_API_KEY: string;
messages: Message[]; messages: Message[];
sysMessageContent: string; sysMessageContent: string;
toolsString: string;
total_tokens: number; total_tokens: number;
max_tokens: number; max_tokens: number;
max_gen_tokens: number; max_gen_tokens: number;
@@ -96,6 +137,7 @@ class Chat {
OPENAI_API_KEY: string | undefined, OPENAI_API_KEY: string | undefined,
{ {
systemMessage = "", systemMessage = "",
toolsString = "",
max_tokens = 4096, max_tokens = 4096,
max_gen_tokens = 2048, max_gen_tokens = 2048,
enable_max_gen_tokens = true, enable_max_gen_tokens = true,
@@ -121,6 +163,7 @@ class Chat {
this.enable_max_gen_tokens = enable_max_gen_tokens; this.enable_max_gen_tokens = enable_max_gen_tokens;
this.tokens_margin = tokens_margin; this.tokens_margin = tokens_margin;
this.sysMessageContent = systemMessage; this.sysMessageContent = systemMessage;
this.toolsString = toolsString;
this.apiEndpoint = apiEndPoint; this.apiEndpoint = apiEndPoint;
this.model = model; this.model = model;
this.temperature = temperature; this.temperature = temperature;
@@ -178,6 +221,25 @@ class Chat {
body["max_tokens"] = this.max_gen_tokens; 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, { return fetch(this.apiEndpoint, {
method: "POST", method: "POST",
headers: { headers: {
@@ -224,7 +286,7 @@ class Chat {
console.log("line", line); console.log("line", line);
try { try {
const jsonStr = line.slice("data:".length).trim(); const jsonStr = line.slice("data:".length).trim();
const json = JSON.parse(jsonStr); const json = JSON.parse(jsonStr) as StreamingResponseChunk;
yield json; yield json;
} catch (e) { } catch (e) {
console.log(`Chunk parse error at: ${line}`); 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) { if (resp.error !== undefined) {
throw JSON.stringify(resp.error); throw JSON.stringify(resp.error);
} }
@@ -249,15 +311,19 @@ class Chat {
this.forgetSomeMessages(); this.forgetSomeMessages();
} }
return ( let content = resp.choices[0].message?.content ?? "";
(resp?.choices[0]?.message?.content as string) ?? if (
`Error: ${JSON.stringify(resp)}` !resp.choices[0]?.message?.content &&
); !resp.choices[0]?.message?.tool_calls
} ) {
content = `Unparsed response: ${JSON.stringify(resp)}`;
}
async complete(): Promise<string> { return {
const resp = await this.fetch(); role: "assistant",
return this.processFetchResponse(resp); content,
tool_calls: resp?.choices[0]?.message?.tool_calls,
};
} }
completeWithSteam() { completeWithSteam() {

View File

@@ -28,6 +28,6 @@ body::-webkit-scrollbar {
display: none; display: none;
} }
p.message-content { .message-content {
white-space: pre-wrap; white-space: pre-wrap;
} }

View File

@@ -12,35 +12,97 @@ interface EditMessageProps {
setChatStore: (cs: ChatStore) => void; setChatStore: (cs: ChatStore) => void;
} }
export const isVailedJSON = (str: string): boolean => {
try {
JSON.parse(str);
} catch (e) {
return false;
}
return true;
};
function EditMessage(props: EditMessageProps) { function EditMessage(props: EditMessageProps) {
const { setShowEdit, chat, setChatStore, chatStore } = props; const { setShowEdit, chat, setChatStore, chatStore } = props;
return ( return (
<div <div
className={ 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)} 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" ? ( {typeof chat.content === "string" ? (
<textarea <div className="flex flex-col">
className={"w-full h-full"} {chat.tool_call_id && (
value={chat.content} <span className="my-2">
onClick={(event: any) => { <label>tool_call_id: </label>
event.stopPropagation(); <input
}} className="rounded border border-gray-400"
onChange={(event: any) => { value={chat.tool_call_id}
chat.content = event.target.value; onChange={(event: any) => {
chat.token = calculate_token_length(chat.content); chat.tool_call_id = event.target.value;
setChatStore({ ...chatStore }); setChatStore({ ...chatStore });
}} }}
onKeyPress={(event: any) => { />
if (event.keyCode == 27) { </span>
setShowEdit(false); )}
} {chat.tool_calls &&
}} chat.tool_calls.map((tool_call) => (
></textarea> <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 <div
className={"w-full h-full flex flex-col overflow-scroll"} className={"w-full h-full flex flex-col overflow-scroll"}
@@ -245,14 +307,18 @@ export default function Message(props: Props) {
</span> </span>
); );
const CopyIcon = () => { const copyToClipboard = (text: string) => {
navigator.clipboard.writeText(text);
setShowCopiedHint(true);
setTimeout(() => setShowCopiedHint(false), 1000);
};
const CopyIcon = ({ textToCopy }: { textToCopy: string }) => {
return ( return (
<> <>
<button <button
onClick={() => { onClick={() => {
navigator.clipboard.writeText(getMessageText(chat)); copyToClipboard(textToCopy);
setShowCopiedHint(true);
setTimeout(() => setShowCopiedHint(false), 1000);
}} }}
> >
📋 📋
@@ -288,40 +354,81 @@ export default function Message(props: Props) {
: "bg-green-400" : "bg-green-400"
} ${chat.hide ? "opacity-50" : ""}`} } ${chat.hide ? "opacity-50" : ""}`}
> >
<p className={renderMarkdown ? "" : "message-content"}> {chat.hide ? (
{typeof chat.content !== "string" ? ( getMessageText(chat).split("\n")[0].slice(0, 18) + "... (deleted)"
// render for multiple messages ) : typeof chat.content !== "string" ? (
chat.content.map((mdt) => chat.content.map((mdt) =>
mdt.type === "text" ? ( mdt.type === "text" ? (
chat.hide ? ( chat.hide ? (
mdt.text?.split("\n")[0].slice(0, 16) + "... (deleted)" mdt.text?.split("\n")[0].slice(0, 16) + "... (deleted)"
) : renderMarkdown ? ( ) : renderMarkdown ? (
// @ts-ignore // @ts-ignore
<Markdown markdown={mdt.text} /> <Markdown markdown={mdt.text} />
) : (
mdt.text
)
) : ( ) : (
<img mdt.text
className="cursor-pointer max-w-xs max-h-32 p-1"
src={mdt.image_url?.url}
onClick={() => {
window.open(mdt.image_url?.url, "_blank");
}}
/>
) )
) : (
<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 ? ( ) : chat.tool_calls ? (
getMessageText(chat).split("\n")[0].slice(0, 16) + <div className="message-content">
"... (deleted)" <div>
) : renderMarkdown ? ( {chat.tool_calls?.map((tool_call) => (
// @ts-ignore <div className="bg-blue-300 dark:bg-blue-800 p-1 rounded my-1">
<Markdown markdown={getMessageText(chat)} /> <strong>
) : ( Tool Call ID:{" "}
getMessageText(chat) <span
)} className="p-1 m-1 rounded cursor-pointer hover:opacity-50 hover:underline"
</p> 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" /> <hr className="mt-2" />
<div className="w-full flex justify-between"> <div className="w-full flex justify-between">
<DeleteIcon /> <DeleteIcon />
@@ -333,7 +440,7 @@ export default function Message(props: Props) {
setChatStore={setChatStore} setChatStore={setChatStore}
/> />
)} )}
<CopyIcon /> <CopyIcon textToCopy={getMessageText(chat)} />
</div> </div>
</div> </div>
{showEdit && ( {showEdit && (

View File

@@ -5,6 +5,7 @@ import models from "./models";
import { TemplateChatStore } from "./chatbox"; import { TemplateChatStore } from "./chatbox";
import { tr, Tr, langCodeContext, LANG_OPTIONS } from "./translate"; import { tr, Tr, langCodeContext, LANG_OPTIONS } from "./translate";
import p from "preact-markdown"; import p from "preact-markdown";
import { isVailedJSON } from "./message";
const TTS_VOICES: string[] = [ const TTS_VOICES: string[] = [
"alloy", "alloy",
@@ -60,7 +61,7 @@ const SelectModel = (props: {
const LongInput = (props: { const LongInput = (props: {
chatStore: ChatStore; chatStore: ChatStore;
setChatStore: (cs: ChatStore) => void; setChatStore: (cs: ChatStore) => void;
field: "systemMessageContent"; field: "systemMessageContent" | "toolsString";
help: string; help: string;
}) => { }) => {
return ( return (
@@ -373,6 +374,15 @@ export default (props: {
help="系统消息用于指示ChatGPT的角色和一些前置条件例如“你是一个有帮助的人工智能助理”或者“你是一个专业英语翻译把我的话全部翻译成英语”详情参考 OPEAN AI API 文档" help="系统消息用于指示ChatGPT的角色和一些前置条件例如“你是一个有帮助的人工智能助理”或者“你是一个专业英语翻译把我的话全部翻译成英语”详情参考 OPEAN AI API 文档"
{...props} {...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 <Input
field="apiKey" field="apiKey"
help="OPEN AI API 密钥,请勿泄漏此密钥" help="OPEN AI API 密钥,请勿泄漏此密钥"