stash code

This commit is contained in:
lloydzhou 2024-08-30 17:31:20 +08:00
parent cac99e3908
commit 271f58d9cf
7 changed files with 246 additions and 194 deletions

View File

@ -241,49 +241,16 @@ export class ChatGPTApi implements LLMApi {
);
}
if (shouldStream) {
const [tools1, funcs2] = usePluginStore
const [tools, funcs] = usePluginStore
.getState()
.getAsTools(useChatStore.getState().currentSession().mask?.plugin);
console.log("getAsTools", tools1, funcs2);
// return
// TODO mock tools and funcs
const tools = [
{
type: "function",
function: {
name: "get_current_weather",
description: "Get the current weather",
parameters: {
type: "object",
properties: {
location: {
type: "string",
description: "The city and country, eg. San Francisco, USA",
},
format: {
type: "string",
enum: ["celsius", "fahrenheit"],
},
},
required: ["location", "format"],
},
},
},
];
const funcs = {
get_current_weather: (args: any) => {
console.log("call get_current_weather", args);
return new Promise((resolve) => {
setTimeout(() => resolve("30"), 3000);
});
},
};
console.log("getAsTools", tools, funcs);
stream(
chatPath,
requestPayload,
getHeaders(),
tools1,
funcs2,
tools,
funcs,
controller,
// parseSSE
(text: string, runTools: ChatMessageTool[]) => {

View File

@ -442,70 +442,6 @@ export function ChatActions(props: {
const navigate = useNavigate();
const chatStore = useChatStore();
const pluginStore = usePluginStore();
console.log("pluginStore", pluginStore.getAll());
// test
if (pluginStore.getAll().length == 0) {
pluginStore.create({
title: "Pet API",
version: "1.0.0",
content: `{
"openapi": "3.0.2",
"info": {
"title": "Pet API",
"version": "1.0.0"
},
"paths": {
"/api/pets": {
"get": {
"operationId": "getPets",
"description": "Returns all pets from the system that the user has access to",
"responses": {
"200": {
"description": "List of Pets",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Pet"
}
}
}
}
}
}
}
}
},
"components": {
"schemas": {
"Pet": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"type": {
"type": "string",
"enum": [
"cat",
"dog"
]
},
"name": {
"type": "string"
}
},
"required": [
"id",
"type"
]
}
}
}
}`,
});
}
// switch themes
const theme = config.theme;
@ -805,9 +741,7 @@ export function ChatActions(props: {
value: Plugin.Artifacts,
},
].concat(
pluginStore
.getAll()
.map((item) => ({
pluginStore.getAll().map((item) => ({
title: `${item.title}@${item.version}`,
value: item.id,
})),

View File

@ -0,0 +1,15 @@
.plugin-title {
font-weight: bolder;
font-size: 16px;
margin: 10px 0;
}
.plugin-content {
font-size: 14px;
font-family: inherit;
pre code {
max-height: 240px;
overflow-y: auto;
white-space: pre-wrap;
}
}

View File

@ -1,7 +1,11 @@
import { useDebouncedCallback } from "use-debounce";
import OpenAPIClientAxios from "openapi-client-axios";
import yaml from "js-yaml";
import { IconButton } from "./button";
import { ErrorBoundary } from "./error";
import styles from "./mask.module.scss";
import pluginStyles from "./plugin.module.scss";
import DownloadIcon from "../icons/download.svg";
import EditIcon from "../icons/edit.svg";
@ -11,7 +15,7 @@ import DeleteIcon from "../icons/delete.svg";
import EyeIcon from "../icons/eye.svg";
import CopyIcon from "../icons/copy.svg";
import { Plugin, usePluginStore } from "../store/plugin";
import { Plugin, usePluginStore, FunctionToolService } from "../store/plugin";
import {
Input,
List,
@ -20,7 +24,9 @@ import {
Popover,
Select,
showConfirm,
showToast,
} from "./ui-lib";
import { downloadAs } from "../utils";
import Locale from "../locales";
import { useNavigate } from "react-router-dom";
import { useEffect, useState } from "react";
@ -30,12 +36,56 @@ import { nanoid } from "nanoid";
export function PluginPage() {
const navigate = useNavigate();
const pluginStore = usePluginStore();
const plugins = pluginStore.getAll();
const allPlugins = pluginStore.getAll();
const [searchPlugins, setSearchPlugins] = useState<Plugin[]>([]);
const [searchText, setSearchText] = useState("");
const plugins = searchText.length > 0 ? searchPlugins : allPlugins;
// refactored already, now it accurate
const onSearch = (text: string) => {
setSearchText(text);
if (text.length > 0) {
const result = allPlugins.filter((m) =>
m.title.toLowerCase().includes(text.toLowerCase()),
);
setSearchPlugins(result);
} else {
setSearchPlugins(allPlugins);
}
};
const [editingPluginId, setEditingPluginId] = useState<string | undefined>();
const editingPlugin = pluginStore.get(editingPluginId);
const editingPluginTool = FunctionToolService.get(editingPlugin?.id);
const closePluginModal = () => setEditingPluginId(undefined);
const onChangePlugin = useDebouncedCallback((editingPlugin, e) => {
const content = e.target.innerText;
try {
const api = new OpenAPIClientAxios({ definition: yaml.load(content) });
api
.init()
.then(() => {
if (content != editingPlugin.content) {
pluginStore.updatePlugin(editingPlugin.id, (plugin) => {
plugin.content = content;
const tool = FunctionToolService.add(plugin, true);
plugin.title = tool.api.definition.info.title;
plugin.version = tool.api.definition.info.version;
});
}
})
.catch((e) => {
console.error(e);
showToast(Locale.Plugin.EditModal.Error);
});
} catch (e) {
console.error(e);
showToast(Locale.Plugin.EditModal.Error);
}
}, 100).bind(null, editingPlugin);
return (
<ErrorBoundary>
<div className={styles["mask-page"]}>
@ -61,6 +111,27 @@ export function PluginPage() {
</div>
<div className={styles["mask-page-body"]}>
<div className={styles["mask-filter"]}>
<input
type="text"
className={styles["search-bar"]}
placeholder={Locale.Plugin.Page.Search}
autoFocus
onInput={(e) => onSearch(e.currentTarget.value)}
/>
<IconButton
className={styles["mask-create"]}
icon={<AddIcon />}
text={Locale.Plugin.Page.Create}
bordered
onClick={() => {
const createdPlugin = pluginStore.create();
setEditingPluginId(createdPlugin.id);
}}
/>
</div>
<div>
{plugins.map((m) => (
<div className={styles["mask-item"]} key={m.id}>
@ -71,7 +142,9 @@ export function PluginPage() {
{m.title}@<small>{m.version}</small>
</div>
<div className={styles["mask-info"] + " one-line"}>
{`${Locale.Plugin.Item.Info(m.content.length)} / / `}
{Locale.Plugin.Item.Info(
FunctionToolService.add(m).length,
)}
</div>
</div>
</div>
@ -123,24 +196,48 @@ export function PluginPage() {
onClick={() =>
downloadAs(
JSON.stringify(editingPlugin),
`${editingPlugin.name}.json`,
`${editingPlugin.title}@${editingPlugin.version}.json`,
)
}
/>,
<IconButton
key="copy"
icon={<CopyIcon />}
bordered
text={Locale.Plugin.EditModal.Clone}
onClick={() => {
navigate(Path.Plugins);
pluginStore.create(editingPlugin);
setEditingPluginId(undefined);
}}
/>,
]}
>
PluginConfig
<div className={styles["mask-page"]}>
<div className={pluginStyles["plugin-title"]}>
{Locale.Plugin.EditModal.Content}
</div>
<div
className={`markdown-body ${pluginStyles["plugin-content"]}`}
dir="auto"
>
<pre>
<code
contentEditable={true}
dangerouslySetInnerHTML={{ __html: editingPlugin.content }}
onBlur={onChangePlugin}
></code>
</pre>
</div>
<div className={pluginStyles["plugin-title"]}>
{Locale.Plugin.EditModal.Method}
</div>
<div className={styles["mask-page-body"]} style={{ padding: 0 }}>
{editingPluginTool?.tools.map((tool, index) => (
<div className={styles["mask-item"]} key={index}>
<div className={styles["mask-header"]}>
<div className={styles["mask-title"]}>
<div className={styles["mask-name"]}>
{tool?.function?.name}
</div>
<div className={styles["mask-info"] + " one-line"}>
{tool?.function?.description}
</div>
</div>
</div>
</div>
))}
</div>
</div>
</Modal>
</div>
)}

View File

@ -509,10 +509,6 @@ const cn = {
Clear: "上下文已清除",
Revert: "恢复上下文",
},
Plugin: {
Name: "插件",
Artifacts: "Artifacts",
},
Discovery: {
Name: "发现",
},
@ -534,6 +530,30 @@ const cn = {
View: "查看",
},
},
Plugin: {
Name: "插件",
Artifacts: "Artifacts",
Page: {
Title: "插件",
SubTitle: (count: number) => `${count} 个插件`,
Search: "搜索插件",
Create: "新建",
},
Item: {
Info: (count: number) => `${count} 方法`,
View: "查看",
Edit: "编辑",
Delete: "删除",
DeleteConfirm: "确认删除?",
},
EditModal: {
Title: (readonly: boolean) => `编辑插件 ${readonly ? "(只读)" : ""}`,
Download: "下载",
Content: "OpenAPI Schema",
Method: "方法",
Error: "格式错误",
},
},
Mask: {
Name: "面具",
Page: {

View File

@ -517,10 +517,6 @@ const en: LocaleType = {
Clear: "Context Cleared",
Revert: "Revert",
},
Plugin: {
Name: "Plugin",
Artifacts: "Artifacts",
},
Discovery: {
Name: "Discovery",
},
@ -544,6 +540,7 @@ const en: LocaleType = {
},
Plugin: {
Name: "Plugin",
Artifacts: "Artifacts",
Page: {
Title: "Plugins",
SubTitle: (count: number) => `${count} plugins`,
@ -551,8 +548,7 @@ const en: LocaleType = {
Create: "Create",
},
Item: {
Info: (count: number) => `${count} plugins`,
Chat: "Chat",
Info: (count: number) => `${count} method`,
View: "View",
Edit: "Edit",
Delete: "Delete",
@ -562,25 +558,9 @@ const en: LocaleType = {
Title: (readonly: boolean) =>
`Edit Plugin ${readonly ? "(readonly)" : ""}`,
Download: "Download",
Clone: "Clone",
},
Config: {
Avatar: "Bot Avatar",
Name: "Bot Name",
Sync: {
Title: "Use Global Config",
SubTitle: "Use global config in this chat",
Confirm: "Confirm to override custom config with global config?",
},
HideContext: {
Title: "Hide Context Prompts",
SubTitle: "Do not show in-context prompts in chat",
},
Share: {
Title: "Share This Plugin",
SubTitle: "Generate a link to this mask",
Action: "Copy Link",
},
Content: "OpenAPI Schema",
Method: "Method",
Error: "OpenAPI Schema Error",
},
},
Mask: {

View File

@ -10,16 +10,91 @@ export type Plugin = {
createdAt: number;
title: string;
version: string;
context: string;
content: string;
builtin: boolean;
};
export type FunctionToolItem = {
type: string;
function: {
name: string;
description?: string;
parameters: Object;
};
};
type FunctionToolServiceItem = {
api: OpenAPIClientAxios;
tools: FunctionToolItem[];
funcs: Function[];
};
export const FunctionToolService = {
tools: {} as Record<string, FunctionToolServiceItem>,
add(plugin: Plugin, replace = false) {
if (!replace && this.tools[plugin.id]) return this.tools[plugin.id];
const api = new OpenAPIClientAxios({
definition: yaml.load(plugin.content),
});
console.log("add", plugin, api);
try {
api.initSync();
} catch (e) {}
const operations = api.getOperations();
return (this.tools[plugin.id] = {
api,
length: operations.length,
tools: operations.map((o) => {
const parameters = o?.requestBody?.content["application/json"]
?.schema || {
type: "object",
properties: {},
};
if (!parameters["required"]) {
parameters["required"] = [];
}
if (o.parameters instanceof Array) {
o.parameters.forEach((p) => {
if (p.in == "query" || p.in == "path") {
// const name = `${p.in}__${p.name}`
const name = p.name;
console.log("p", p, p.schema);
parameters["properties"][name] = {
type: p.schema.type,
description: p.description,
};
if (p.required) {
parameters["required"].push(name);
}
}
});
}
return {
type: "function",
function: {
name: o.operationId,
description: o.description,
parameters: parameters,
},
};
}),
funcs: operations.reduce((s, o) => {
s[o.operationId] = api.client[o.operationId];
return s;
}, {}),
});
},
get(id) {
return this.tools[id];
},
};
export const createEmptyPlugin = () =>
({
id: nanoid(),
title: "",
version: "",
context: "",
version: "1.0.0",
content: "",
builtin: false,
createdAt: Date.now(),
}) as Plugin;
@ -69,46 +144,10 @@ export const usePluginStore = createPersistStore(
const selected = ids
.map((id) => plugins[id])
.filter((i) => i)
.map((i) => [
i,
new OpenAPIClientAxios({ definition: yaml.load(i.content) }),
])
.map(([item, api]) => {
api.initSync();
const operations = api.getOperations().map((o) => {
const parameters = o.parameters;
.map((p) => FunctionToolService.add(p));
return [
{
type: "function",
function: {
name: o.operationId,
description: o.description,
parameters: o.parameters,
},
},
api.client[o.operationId],
];
// return [{
// }, function(arg) {
// const args = []
// for (const p in parameters) {
// if (p.type === "object") {
// const a = {}
// for (const n of p.)
// }
// }
// }]
});
return [item, api, operations];
});
console.log("selected", selected);
const result = selected.reduce((s, i) => s.concat(i[2]), []);
return [
result.map(([t, _]) => t),
result.reduce((s, i) => {
s[i[0].function.name] = i[1];
return s;
}, {}),
selected.reduce((s, i) => s.concat(i.tools), []),
selected.reduce((s, i) => Object.assign(s, i.funcs), {}),
];
},
get(id?: string) {