From 850613c911718f81247632a94f466c199b8b9d26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=83=AD=E5=9E=92?= Date: Sat, 7 Dec 2024 11:33:57 +0800 Subject: [PATCH] feat: customizable system prompt --- excalidraw-app/App.tsx | 29 ++++++++- .../excalidraw/components/MagicSettings.scss | 10 +++ .../excalidraw/components/MagicSettings.tsx | 23 ++++++- packages/excalidraw/components/TextField.tsx | 65 +++++++++++++------ packages/excalidraw/components/TextInput.scss | 20 ++++++ .../components/main-menu/DefaultItems.tsx | 11 +++- packages/excalidraw/constants.ts | 4 +- packages/excalidraw/data/magic.ts | 53 +++++++++++---- packages/excalidraw/locales/en.json | 8 ++- packages/excalidraw/locales/zh-CN.json | 26 ++++---- 10 files changed, 193 insertions(+), 56 deletions(-) diff --git a/excalidraw-app/App.tsx b/excalidraw-app/App.tsx index bd4ed193282e..cf41d33a035c 100644 --- a/excalidraw-app/App.tsx +++ b/excalidraw-app/App.tsx @@ -123,7 +123,7 @@ import { appThemeAtom, useHandleAppTheme } from "./useHandleAppTheme"; import { getPreferredLanguage } from "./app-language/language-detector"; import { useAppLangCode } from "./app-language/language-state"; import { EditorLocalStorage } from "../packages/excalidraw/data/EditorLocalStorage"; -import { getBaseUrl, getLLMModel } from "../packages/excalidraw/data/magic"; +import { getBaseUrl, getLLMModel, getTextToDiagramPrompt } from "../packages/excalidraw/data/magic"; const SYSTEM_PROMPT = `Create a Mermaid diagram using the provided text description of a scenario. Your task is to translate the text into a Mermaid Live Editor format, focusing solely on the conversion without including any extraneous content. The output should be a clear and organized visual representation of the relationships or processes described in the text. @@ -866,6 +866,7 @@ const ExcalidrawWrapper = () => { try { const apiKey = EditorLocalStorage.get(EDITOR_LS_KEYS.OAI_API_KEY); const apiBaseUrl = getBaseUrl(); + const textToDiagramPrompt = getTextToDiagramPrompt(); const response = await fetch(`${apiBaseUrl}/chat/completions`, { method: "POST", headers: { @@ -874,7 +875,7 @@ const ExcalidrawWrapper = () => { }, body: JSON.stringify({ messages: [ - { role: "system", content: SYSTEM_PROMPT }, + { role: "system", content: textToDiagramPrompt }, { role: "user", content: input }, ], model: getLLMModel(), @@ -1011,7 +1012,7 @@ const ExcalidrawWrapper = () => { }, }, { - label: "GitHub", + label: "GitHub(Original)", icon: GithubIcon, category: DEFAULT_CATEGORIES.links, predicate: true, @@ -1032,6 +1033,28 @@ const ExcalidrawWrapper = () => { ); }, }, + { + label: "GitHub(Current fork)", + icon: GithubIcon, + category: DEFAULT_CATEGORIES.links, + predicate: true, + keywords: [ + "issues", + "bugs", + "requests", + "report", + "features", + "social", + "community", + ], + perform: () => { + window.open( + "https://github.com/KwokKwok/excalidraw", + "_blank", + "noopener noreferrer", + ); + }, + }, { label: t("labels.followUs"), icon: XBrandIcon, diff --git a/packages/excalidraw/components/MagicSettings.scss b/packages/excalidraw/components/MagicSettings.scss index bd07d84003b9..a7ab331c496a 100644 --- a/packages/excalidraw/components/MagicSettings.scss +++ b/packages/excalidraw/components/MagicSettings.scss @@ -15,4 +15,14 @@ margin-top: 2rem; margin-right: auto; } + + .magic-settings-advanced { + textarea.TextField__input { + min-height: 100px; + resize: vertical; + font-family: var(--ui-font-family); + padding: 0.6em; + line-height: 1.4; + } + } } diff --git a/packages/excalidraw/components/MagicSettings.tsx b/packages/excalidraw/components/MagicSettings.tsx index 5afcc331d493..80161b8b34a3 100644 --- a/packages/excalidraw/components/MagicSettings.tsx +++ b/packages/excalidraw/components/MagicSettings.tsx @@ -13,7 +13,7 @@ import "./MagicSettings.scss"; import TTDDialogTabs from "./TTDDialog/TTDDialogTabs"; import { TTDDialogTab } from "./TTDDialog/TTDDialogTab"; import { useI18n } from "../i18n"; -import { getBaseUrl, getLLMModel, getVLMModel } from "../data/magic"; +import { getBaseUrl, getLLMModel, getVLMModel, getTextToDiagramPrompt, getDiagramToCodePrompt } from "../data/magic"; import { EditorLocalStorage } from "../data/EditorLocalStorage"; import { EDITOR_LS_KEYS } from "../constants"; @@ -38,10 +38,15 @@ export const MagicSettings = (props: { const [vlmModel, setVlmModel] = useState(getVLMModel()); const [llmModel, setLlmModel] = useState(getLLMModel()); + const [textToDiagramPrompt, setTextToDiagramPrompt] = useState(getTextToDiagramPrompt()); + const [diagramToCodePrompt, setDiagramToCodePrompt] = useState(getDiagramToCodePrompt()); + const onConfirm = () => { EditorLocalStorage.set(EDITOR_LS_KEYS.MAGIC_BASE_URL, baseUrl); EditorLocalStorage.set(EDITOR_LS_KEYS.MAGIC_VLM_MODEL, vlmModel); EditorLocalStorage.set(EDITOR_LS_KEYS.MAGIC_LLM_MODEL, llmModel); + EditorLocalStorage.set(EDITOR_LS_KEYS.MAGIC_TEXT_TO_DIAGRAM_PROMPT, textToDiagramPrompt); + EditorLocalStorage.set(EDITOR_LS_KEYS.MAGIC_DIAGRAM_TO_CODE_PROMPT, diagramToCodePrompt); props.onConfirm(keyInputValue.trim(), shouldPersist); }; @@ -159,6 +164,22 @@ export const MagicSettings = (props: { onChange={(value) => setLlmModel(value)} placeholder="Qwen/Qwen2-72B-Instruct" /> + + setTextToDiagramPrompt(value)} + placeholder={t("magicSettings.textToDiagramPromptPlaceholder")} + type="textarea" + /> + + setDiagramToCodePrompt(value)} + placeholder={t("magicSettings.diagramToCodePromptPlaceholder")} + type="textarea" + /> diff --git a/packages/excalidraw/components/TextField.tsx b/packages/excalidraw/components/TextField.tsx index 463ea2c2d413..0ae5933f531d 100644 --- a/packages/excalidraw/components/TextField.tsx +++ b/packages/excalidraw/components/TextField.tsx @@ -15,18 +15,19 @@ import { eyeIcon, eyeClosedIcon } from "./icons"; type TextFieldProps = { onChange?: (value: string) => void; onClick?: () => void; - onKeyDown?: (event: KeyboardEvent) => void; + onKeyDown?: (event: KeyboardEvent) => void; readonly?: boolean; fullWidth?: boolean; selectOnRender?: boolean; + type?: "text" | "password" | "textarea"; label?: string; placeholder?: string; isRedacted?: boolean; } & ({ value: string } | { defaultValue: string }); -export const TextField = forwardRef( +export const TextField = forwardRef( ( { onChange, @@ -37,11 +38,12 @@ export const TextField = forwardRef( selectOnRender, onKeyDown, isRedacted = false, + type = "text", ...rest }, ref, ) => { - const innerRef = useRef(null); + const innerRef = useRef(null); useImperativeHandle(ref, () => innerRef.current!); @@ -54,6 +56,44 @@ export const TextField = forwardRef( const [isTemporarilyUnredacted, setIsTemporarilyUnredacted] = useState(false); + const commonProps = { + className: clsx({ + "is-redacted": + "value" in rest && + rest.value && + isRedacted && + !isTemporarilyUnredacted, + }), + readOnly: readonly, + value: "value" in rest ? rest.value : undefined, + defaultValue: "defaultValue" in rest ? rest.defaultValue : undefined, + placeholder: placeholder, + ref: innerRef as any, + onChange: (event: React.ChangeEvent) => + onChange?.(event.target.value), + onKeyDown, + spellCheck: false, + }; + + if (type === "textarea") { + return ( +
+
{label}
+
+