Skip to content

Commit

Permalink
feat: customizable system prompt
Browse files Browse the repository at this point in the history
  • Loading branch information
KwokKwok committed Dec 7, 2024
1 parent af33f4c commit 850613c
Show file tree
Hide file tree
Showing 10 changed files with 193 additions and 56 deletions.
29 changes: 26 additions & 3 deletions excalidraw-app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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: {
Expand All @@ -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(),
Expand Down Expand Up @@ -1011,7 +1012,7 @@ const ExcalidrawWrapper = () => {
},
},
{
label: "GitHub",
label: "GitHub(Original)",
icon: GithubIcon,
category: DEFAULT_CATEGORIES.links,
predicate: true,
Expand All @@ -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,
Expand Down
10 changes: 10 additions & 0 deletions packages/excalidraw/components/MagicSettings.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
}
23 changes: 22 additions & 1 deletion packages/excalidraw/components/MagicSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand All @@ -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);
};

Expand Down Expand Up @@ -159,6 +164,22 @@ export const MagicSettings = (props: {
onChange={(value) => setLlmModel(value)}
placeholder="Qwen/Qwen2-72B-Instruct"
/>

<TextField
label={t("magicSettings.textToDiagramPrompt")}
value={textToDiagramPrompt}
onChange={(value) => setTextToDiagramPrompt(value)}
placeholder={t("magicSettings.textToDiagramPromptPlaceholder")}
type="textarea"
/>

<TextField
label={t("magicSettings.diagramToCodePrompt")}
value={diagramToCodePrompt}
onChange={(value) => setDiagramToCodePrompt(value)}
placeholder={t("magicSettings.diagramToCodePromptPlaceholder")}
type="textarea"
/>
</div>
</details>

Expand Down
65 changes: 44 additions & 21 deletions packages/excalidraw/components/TextField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,19 @@ import { eyeIcon, eyeClosedIcon } from "./icons";
type TextFieldProps = {
onChange?: (value: string) => void;
onClick?: () => void;
onKeyDown?: (event: KeyboardEvent<HTMLInputElement>) => void;
onKeyDown?: (event: KeyboardEvent<HTMLInputElement | HTMLTextAreaElement>) => 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<HTMLInputElement, TextFieldProps>(
export const TextField = forwardRef<HTMLInputElement | HTMLTextAreaElement, TextFieldProps>(
(
{
onChange,
Expand All @@ -37,11 +38,12 @@ export const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
selectOnRender,
onKeyDown,
isRedacted = false,
type = "text",
...rest
},
ref,
) => {
const innerRef = useRef<HTMLInputElement | null>(null);
const innerRef = useRef<HTMLInputElement | HTMLTextAreaElement | null>(null);

useImperativeHandle(ref, () => innerRef.current!);

Expand All @@ -54,6 +56,44 @@ export const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
const [isTemporarilyUnredacted, setIsTemporarilyUnredacted] =
useState<boolean>(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<HTMLInputElement | HTMLTextAreaElement>) =>
onChange?.(event.target.value),
onKeyDown,
spellCheck: false,
};

if (type === "textarea") {
return (
<div
className={clsx("ExcTextField", {
"ExcTextField--fullWidth": fullWidth,
})}
>
<div className="ExcTextField__label">{label}</div>
<div
className={clsx("ExcTextField__input", "ExcTextField__input--textarea", {
"ExcTextField__input--readonly": readonly,
})}
>
<textarea {...commonProps} />
</div>
</div>
);
}

return (
<div
className={clsx("ExcTextField", {
Expand All @@ -69,24 +109,7 @@ export const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
"ExcTextField__input--readonly": readonly,
})}
>
<input
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}
onChange={(event) => onChange?.(event.target.value)}
onKeyDown={onKeyDown}
/>
<input {...commonProps} />
{isRedacted && (
<Button
onSelect={() =>
Expand Down
20 changes: 20 additions & 0 deletions packages/excalidraw/components/TextInput.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,24 @@
.TextInput {
display: inline-block;
}

.ExcTextField {
margin-top: 0.5rem;
}

.ExcTextField__input--textarea {
display: block;
height: auto !important;
overflow-y: auto;
padding: 0.4em !important;
textarea {
height: auto;
border: none !important;
outline: none !important;
text-wrap: auto !important;
resize: vertical !important;
width: 100% !important;
padding: 0.6em !important;
}
}
}
11 changes: 9 additions & 2 deletions packages/excalidraw/components/main-menu/DefaultItems.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -325,9 +325,16 @@ export const Socials = () => {
<DropdownMenuItemLink
icon={GithubIcon}
href="https://github.com/excalidraw/excalidraw"
aria-label="GitHub"
aria-label="GitHub(Original)"
>
GitHub
GitHub(Original)
</DropdownMenuItemLink>
<DropdownMenuItemLink
icon={GithubIcon}
href="https://github.com/KwokKwok/excalidraw"
aria-label="GitHub(Current fork)"
>
GitHub(Current fork)
</DropdownMenuItemLink>
<DropdownMenuItemLink
icon={XBrandIcon}
Expand Down
4 changes: 3 additions & 1 deletion packages/excalidraw/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,9 @@ export const EDITOR_LS_KEYS = {
PUBLISH_LIBRARY: "publish-library-data",
MAGIC_BASE_URL: "excalidraw-magic-base-url",
MAGIC_VLM_MODEL: "excalidraw-magic-vlm-model",
MAGIC_LLM_MODEL: "excalidraw-magic-llm-model"
MAGIC_LLM_MODEL: "excalidraw-magic-llm-model",
MAGIC_TEXT_TO_DIAGRAM_PROMPT: "excalidraw-magic-text-to-diagram-prompt",
MAGIC_DIAGRAM_TO_CODE_PROMPT: "excalidraw-magic-diagram-to-code-prompt"
} as const;

/**
Expand Down
53 changes: 40 additions & 13 deletions packages/excalidraw/data/magic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,40 @@ export type MagicCacheData =
code: "ERR_GENERATION_INTERRUPTED" | string;
};

const SYSTEM_PROMPT = `You are a skilled front-end developer who builds interactive prototypes from wireframes, and is an expert at CSS Grid and Flex design.
// 获取默认值
export const getBaseUrl = (): string => {
return EditorLocalStorage.get(EDITOR_LS_KEYS.MAGIC_BASE_URL) || "https://api.siliconflow.cn/v1";
};

export const getVLMModel = (): string => {
return EditorLocalStorage.get(EDITOR_LS_KEYS.MAGIC_VLM_MODEL) || "Qwen/Qwen2-VL-72B-Instruct";
};

export const getLLMModel = (): string => {
return EditorLocalStorage.get(EDITOR_LS_KEYS.MAGIC_LLM_MODEL) || "Qwen/Qwen2.5-Coder-32B-Instruct";
};

const DEFAULT_TEXT_TO_DIAGRAM_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.
Here is an example of the expected output:
graph TB
PersonA[Person A] -- Relationship1 --> PersonB[Person B]
PersonC[Person C] -- Relationship2 --> PersonB
PersonD[Person D] -- Relationship3 --> PersonB
PersonE[Person E] -- Relationship4 --> PersonC
PersonF[Person F] -- Relationship5 --> PersonA
PersonG[Person G] -- Relationship6 --> PersonF`;

export const getTextToDiagramPrompt = (): string => {
return (
EditorLocalStorage.get(EDITOR_LS_KEYS.MAGIC_TEXT_TO_DIAGRAM_PROMPT) ||
DEFAULT_TEXT_TO_DIAGRAM_PROMPT
);
};


const DEFAULT_DIAGRAM_TO_CODE_PROMPT = `You are a skilled front-end developer who builds interactive prototypes from wireframes, and is an expert at CSS Grid and Flex design.
Your role is to transform low-fidelity wireframes into working front-end HTML code.
YOU MUST FOLLOW FOLLOWING RULES:
Expand All @@ -37,17 +70,11 @@ Your goal is a production-ready prototype that brings the wireframes to life.
Please output JUST THE HTML file containing your best attempt at implementing the provided wireframes.`;

// 获取默认值
export const getBaseUrl = (): string => {
return EditorLocalStorage.get(EDITOR_LS_KEYS.MAGIC_BASE_URL) || "https://api.siliconflow.cn/v1";
};

export const getVLMModel = (): string => {
return EditorLocalStorage.get(EDITOR_LS_KEYS.MAGIC_VLM_MODEL) || "Qwen/Qwen2-VL-72B-Instruct";
};

export const getLLMModel = (): string => {
return EditorLocalStorage.get(EDITOR_LS_KEYS.MAGIC_LLM_MODEL) || "Qwen/Qwen2.5-Coder-32B-Instruct";
export const getDiagramToCodePrompt = (): string => {
return (
EditorLocalStorage.get(EDITOR_LS_KEYS.MAGIC_DIAGRAM_TO_CODE_PROMPT) ||
DEFAULT_DIAGRAM_TO_CODE_PROMPT
);
};

export async function diagramToHTML({
Expand All @@ -73,7 +100,7 @@ export async function diagramToHTML({
messages: [
{
role: "system",
content: SYSTEM_PROMPT,
content: getDiagramToCodePrompt(),
},
{
role: "user",
Expand Down
8 changes: 6 additions & 2 deletions packages/excalidraw/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -592,7 +592,7 @@
"useOpenAI": "uses OpenAI's AI service, this project replaces it with SiliconCloud.",
"createAccount": "You can create a",
"siliconCloudAccount": "SiliconCloud account",
"freeCredit": "(get ¥14 free credit for first registration), and",
"freeCredit": "(get ¥14 free long-term credit for first registration), and",
"generateKey": "generate your own API key",
"apiKeyPlaceholder": "Paste your API key here",
"apiKeyLabel": "SiliconCloud API Key",
Expand All @@ -610,6 +610,10 @@
"advancedOptions": "Advanced Options",
"baseUrl": "API Base URL",
"vlmModel": "Vision Language Model",
"llmModel": "Language Model"
"llmModel": "Language Model",
"textToDiagramPrompt": "Text to Diagram Prompt",
"diagramToCodePrompt": "Diagram to Code Prompt",
"textToDiagramPromptPlaceholder": "Enter your custom prompt for text to diagram conversion...",
"diagramToCodePromptPlaceholder": "Enter your custom prompt for diagram to code conversion..."
}
}
Loading

0 comments on commit 850613c

Please sign in to comment.