UNPKG

@gongfu/prompt-editor

Version:

A powerful prompt engineering editor SDK for AI applications

1,378 lines (1,367 loc) 134 kB
"use client" // src/components/PromptEditor.tsx import { useCallback, useEffect, useMemo as useMemo2 } from "react"; import MonacoEditor from "@monaco-editor/react"; import { motion as motion3, AnimatePresence as AnimatePresence2 } from "framer-motion"; // src/hooks/usePromptEditorStore.ts import { create } from "zustand"; import { immer } from "zustand/middleware/immer"; var initialTemplate = { id: "", name: "Untitled Prompt", content: "", variables: [], tags: [] }; var usePromptEditorStore = create()( immer((set2, get) => ({ // State template: initialTemplate, isDirty: false, isValid: true, errors: {}, history: [initialTemplate], historyIndex: 0, selectedVariableId: void 0, previewValues: {}, // Actions updateTemplate: (updates) => set2((state) => { Object.assign(state.template, updates); state.isDirty = true; addToHistory(state); }), updateContent: (content) => set2((state) => { state.template.content = content; state.isDirty = true; addToHistory(state); }), addVariable: (variable) => set2((state) => { if (state.template.variables.some((v) => v.name === variable.name)) { state.errors[variable.name] = "Variable name already exists"; return; } state.template.variables.push(variable); state.isDirty = true; state.errors = {}; addToHistory(state); }), updateVariable: (name, updates) => set2((state) => { const index = state.template.variables.findIndex((v) => v.name === name); if (index !== -1) { if (updates.name && updates.name !== name) { const nameExists = state.template.variables.some( (v, i) => i !== index && v.name === updates.name ); if (nameExists) { state.errors[name] = "Variable name already exists"; return; } } Object.assign(state.template.variables[index], updates); state.isDirty = true; state.errors = {}; addToHistory(state); } }), removeVariable: (name) => set2((state) => { state.template.variables = state.template.variables.filter((v) => v.name !== name); delete state.previewValues[name]; state.isDirty = true; addToHistory(state); }), setPreviewValue: (name, value) => set2((state) => { state.previewValues[name] = value; }), undo: () => set2((state) => { if (state.historyIndex > 0) { state.historyIndex--; state.template = JSON.parse(JSON.stringify(state.history[state.historyIndex])); state.isDirty = state.historyIndex !== 0; } }), redo: () => set2((state) => { if (state.historyIndex < state.history.length - 1) { state.historyIndex++; state.template = JSON.parse(JSON.stringify(state.history[state.historyIndex])); state.isDirty = true; } }), reset: () => set2((state) => { state.template = initialTemplate; state.isDirty = false; state.isValid = true; state.errors = {}; state.history = [initialTemplate]; state.historyIndex = 0; state.previewValues = {}; }), loadTemplate: (template) => set2((state) => { state.template = template; state.isDirty = false; state.isValid = true; state.errors = {}; state.history = [template]; state.historyIndex = 0; state.previewValues = {}; template.variables.forEach((variable) => { if (variable.defaultValue !== void 0) { state.previewValues[variable.name] = variable.defaultValue; } }); }), save: async () => { const state = get(); if (state.isValid) { set2((state2) => { state2.isDirty = false; state2.template.updatedAt = /* @__PURE__ */ new Date(); }); } }, validate: () => { const state = get(); const errors = {}; let isValid = true; if (!state.template.name || state.template.name.trim() === "") { errors.name = "Template name is required"; isValid = false; } const variableNames = /* @__PURE__ */ new Set(); state.template.variables.forEach((variable, index) => { if (!variable.name || variable.name.trim() === "") { errors[`variable_${index}`] = "Variable name is required"; isValid = false; } else if (variableNames.has(variable.name)) { errors[`variable_${index}`] = "Duplicate variable name"; isValid = false; } else { variableNames.add(variable.name); } if (variable.validation?.pattern) { try { new RegExp(variable.validation.pattern); } catch { errors[`variable_${index}_pattern`] = "Invalid regular expression"; isValid = false; } } }); set2((state2) => { state2.errors = errors; state2.isValid = isValid; }); return isValid; } })) ); function addToHistory(state) { if (state.historyIndex < state.history.length - 1) { state.history = state.history.slice(0, state.historyIndex + 1); } const newHistory = JSON.parse(JSON.stringify(state.template)); state.history.push(newHistory); state.historyIndex = state.history.length - 1; const maxHistory = 50; if (state.history.length > maxHistory) { state.history = state.history.slice(-maxHistory); state.historyIndex = state.history.length - 1; } } // src/components/VariablePanel.tsx import { useState as useState2 } from "react"; import { motion as motion2 } from "framer-motion"; // src/components/VariableForm.tsx import { useState } from "react"; import { Fragment, jsx, jsxs } from "react/jsx-runtime"; var VariableForm = ({ variable, onSubmit, onCancel, customTypes, locale = "en" }) => { const [formData, setFormData] = useState({ name: variable?.name || "", type: variable?.type || "text", description: variable?.description || "", defaultValue: variable?.defaultValue || "", required: variable?.required || false, options: variable?.options || [], validation: variable?.validation || {} }); const [errors, setErrors] = useState({}); const labels = { en: { name: "Variable Name", type: "Type", description: "Description", defaultValue: "Default Value", required: "Required", save: "Save", cancel: "Cancel", addOption: "Add Option", optionLabel: "Label", optionValue: "Value", validation: "Validation", minValue: "Min Value", maxValue: "Max Value", pattern: "Pattern (RegExp)", errorMessage: "Error Message" }, "zh-CN": { name: "\u53D8\u91CF\u540D\u79F0", type: "\u7C7B\u578B", description: "\u63CF\u8FF0", defaultValue: "\u9ED8\u8BA4\u503C", required: "\u5FC5\u586B", save: "\u4FDD\u5B58", cancel: "\u53D6\u6D88", addOption: "\u6DFB\u52A0\u9009\u9879", optionLabel: "\u6807\u7B7E", optionValue: "\u503C", validation: "\u9A8C\u8BC1\u89C4\u5219", minValue: "\u6700\u5C0F\u503C", maxValue: "\u6700\u5927\u503C", pattern: "\u6B63\u5219\u8868\u8FBE\u5F0F", errorMessage: "\u9519\u8BEF\u63D0\u793A" } }[locale]; const typeOptions = [ { value: "text", label: locale === "zh-CN" ? "\u6587\u672C" : "Text" }, { value: "number", label: locale === "zh-CN" ? "\u6570\u5B57" : "Number" }, { value: "boolean", label: locale === "zh-CN" ? "\u5E03\u5C14\u503C" : "Boolean" }, { value: "select", label: locale === "zh-CN" ? "\u9009\u62E9" : "Select" }, { value: "multiline", label: locale === "zh-CN" ? "\u591A\u884C\u6587\u672C" : "Multiline" }, ...customTypes?.map((t) => ({ value: t.type, label: t.label })) || [] ]; const validate = () => { const newErrors = {}; if (!formData.name?.trim()) { newErrors.name = locale === "zh-CN" ? "\u53D8\u91CF\u540D\u4E0D\u80FD\u4E3A\u7A7A" : "Variable name is required"; } else if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(formData.name)) { newErrors.name = locale === "zh-CN" ? "\u53D8\u91CF\u540D\u53EA\u80FD\u5305\u542B\u5B57\u6BCD\u3001\u6570\u5B57\u548C\u4E0B\u5212\u7EBF\uFF0C\u4E14\u4E0D\u80FD\u4EE5\u6570\u5B57\u5F00\u5934" : "Variable name must contain only letters, numbers, and underscores, and cannot start with a number"; } if (formData.type === "select" && (!formData.options || formData.options.length === 0)) { newErrors.options = locale === "zh-CN" ? "\u9009\u62E9\u7C7B\u578B\u5FC5\u987B\u81F3\u5C11\u6709\u4E00\u4E2A\u9009\u9879" : "Select type must have at least one option"; } if (formData.validation?.pattern) { try { new RegExp(formData.validation.pattern); } catch { newErrors.pattern = locale === "zh-CN" ? "\u65E0\u6548\u7684\u6B63\u5219\u8868\u8FBE\u5F0F" : "Invalid regular expression"; } } setErrors(newErrors); return Object.keys(newErrors).length === 0; }; const handleSubmit = (e) => { e.preventDefault(); if (validate()) { onSubmit(formData); } }; const handleAddOption = () => { setFormData({ ...formData, options: [...formData.options || [], { label: "", value: "" }] }); }; const handleUpdateOption = (index, field, value) => { const newOptions = [...formData.options || []]; newOptions[index] = { ...newOptions[index], [field]: value }; setFormData({ ...formData, options: newOptions }); }; const handleRemoveOption = (index) => { const newOptions = [...formData.options || []]; newOptions.splice(index, 1); setFormData({ ...formData, options: newOptions }); }; return /* @__PURE__ */ jsxs("form", { className: "variable-form", onSubmit: handleSubmit, children: [ /* @__PURE__ */ jsxs("div", { className: "form-group", children: [ /* @__PURE__ */ jsxs("label", { children: [ labels.name, " *" ] }), /* @__PURE__ */ jsx( "input", { type: "text", value: formData.name, onChange: (e) => setFormData({ ...formData, name: e.target.value }), className: errors.name ? "error" : "" } ), errors.name && /* @__PURE__ */ jsx("span", { className: "error-message", children: errors.name }) ] }), /* @__PURE__ */ jsxs("div", { className: "form-group", children: [ /* @__PURE__ */ jsxs("label", { children: [ labels.type, " *" ] }), /* @__PURE__ */ jsx( "select", { value: formData.type, onChange: (e) => setFormData({ ...formData, type: e.target.value }), children: typeOptions.map((option) => /* @__PURE__ */ jsx("option", { value: option.value, children: option.label }, option.value)) } ) ] }), /* @__PURE__ */ jsxs("div", { className: "form-group", children: [ /* @__PURE__ */ jsx("label", { children: labels.description }), /* @__PURE__ */ jsx( "textarea", { value: formData.description, onChange: (e) => setFormData({ ...formData, description: e.target.value }), rows: 2 } ) ] }), /* @__PURE__ */ jsxs("div", { className: "form-group", children: [ /* @__PURE__ */ jsx("label", { children: labels.defaultValue }), formData.type === "boolean" ? /* @__PURE__ */ jsx( "input", { type: "checkbox", checked: formData.defaultValue || false, onChange: (e) => setFormData({ ...formData, defaultValue: e.target.checked }) } ) : formData.type === "multiline" ? /* @__PURE__ */ jsx( "textarea", { value: formData.defaultValue, onChange: (e) => setFormData({ ...formData, defaultValue: e.target.value }), rows: 2 } ) : /* @__PURE__ */ jsx( "input", { type: formData.type === "number" ? "number" : "text", value: formData.defaultValue, onChange: (e) => setFormData({ ...formData, defaultValue: e.target.value }) } ) ] }), /* @__PURE__ */ jsx("div", { className: "form-group", children: /* @__PURE__ */ jsxs("label", { children: [ /* @__PURE__ */ jsx( "input", { type: "checkbox", checked: formData.required, onChange: (e) => setFormData({ ...formData, required: e.target.checked }) } ), labels.required ] }) }), formData.type === "select" && /* @__PURE__ */ jsxs("div", { className: "form-group", children: [ /* @__PURE__ */ jsx("label", { children: "Options" }), formData.options?.map((option, index) => /* @__PURE__ */ jsxs("div", { className: "option-row", children: [ /* @__PURE__ */ jsx( "input", { type: "text", placeholder: labels.optionLabel, value: option.label, onChange: (e) => handleUpdateOption(index, "label", e.target.value) } ), /* @__PURE__ */ jsx( "input", { type: "text", placeholder: labels.optionValue, value: option.value, onChange: (e) => handleUpdateOption(index, "value", e.target.value) } ), /* @__PURE__ */ jsx("button", { type: "button", onClick: () => handleRemoveOption(index), children: "\xD7" }) ] }, index)), /* @__PURE__ */ jsxs("button", { type: "button", onClick: handleAddOption, className: "add-option-btn", children: [ "+ ", labels.addOption ] }), errors.options && /* @__PURE__ */ jsx("span", { className: "error-message", children: errors.options }) ] }), (formData.type === "text" || formData.type === "number") && /* @__PURE__ */ jsxs("details", { className: "form-group", children: [ /* @__PURE__ */ jsx("summary", { children: labels.validation }), formData.type === "number" && /* @__PURE__ */ jsxs(Fragment, { children: [ /* @__PURE__ */ jsxs("div", { className: "form-row", children: [ /* @__PURE__ */ jsx("label", { children: labels.minValue }), /* @__PURE__ */ jsx( "input", { type: "number", value: formData.validation?.min || "", onChange: (e) => setFormData({ ...formData, validation: { ...formData.validation, min: e.target.value ? Number(e.target.value) : void 0 } }) } ) ] }), /* @__PURE__ */ jsxs("div", { className: "form-row", children: [ /* @__PURE__ */ jsx("label", { children: labels.maxValue }), /* @__PURE__ */ jsx( "input", { type: "number", value: formData.validation?.max || "", onChange: (e) => setFormData({ ...formData, validation: { ...formData.validation, max: e.target.value ? Number(e.target.value) : void 0 } }) } ) ] }) ] }), formData.type === "text" && /* @__PURE__ */ jsxs("div", { className: "form-row", children: [ /* @__PURE__ */ jsx("label", { children: labels.pattern }), /* @__PURE__ */ jsx( "input", { type: "text", value: formData.validation?.pattern || "", onChange: (e) => setFormData({ ...formData, validation: { ...formData.validation, pattern: e.target.value } }), className: errors.pattern ? "error" : "" } ), errors.pattern && /* @__PURE__ */ jsx("span", { className: "error-message", children: errors.pattern }) ] }), /* @__PURE__ */ jsxs("div", { className: "form-row", children: [ /* @__PURE__ */ jsx("label", { children: labels.errorMessage }), /* @__PURE__ */ jsx( "input", { type: "text", value: formData.validation?.message || "", onChange: (e) => setFormData({ ...formData, validation: { ...formData.validation, message: e.target.value } }) } ) ] }) ] }), /* @__PURE__ */ jsxs("div", { className: "form-actions", children: [ /* @__PURE__ */ jsx("button", { type: "button", onClick: onCancel, className: "btn-secondary", children: labels.cancel }), /* @__PURE__ */ jsx("button", { type: "submit", className: "btn-primary", children: labels.save }) ] }) ] }); }; // src/components/VariableList.tsx import { motion, AnimatePresence } from "framer-motion"; import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime"; var VariableList = ({ variables, editingVariable, onEdit, onUpdate, onRemove, customTypes, readOnly = false, locale = "en" }) => { const typeLabels = { text: locale === "zh-CN" ? "\u6587\u672C" : "Text", number: locale === "zh-CN" ? "\u6570\u5B57" : "Number", boolean: locale === "zh-CN" ? "\u5E03\u5C14\u503C" : "Boolean", select: locale === "zh-CN" ? "\u9009\u62E9" : "Select", multiline: locale === "zh-CN" ? "\u591A\u884C\u6587\u672C" : "Multiline" }; return /* @__PURE__ */ jsx2("div", { className: "variable-list", children: /* @__PURE__ */ jsx2(AnimatePresence, { children: variables.map((variable) => /* @__PURE__ */ jsx2( motion.div, { className: "variable-item", initial: { opacity: 0, y: -10 }, animate: { opacity: 1, y: 0 }, exit: { opacity: 0, y: -10 }, layout: true, children: editingVariable === variable.name ? /* @__PURE__ */ jsx2( VariableForm, { variable, onSubmit: (updatedVariable) => { onUpdate(variable.name, updatedVariable); onEdit(null); }, onCancel: () => onEdit(null), customTypes, locale } ) : /* @__PURE__ */ jsxs2("div", { className: "variable-item__content", children: [ /* @__PURE__ */ jsxs2("div", { className: "variable-item__header", children: [ /* @__PURE__ */ jsxs2("div", { className: "variable-item__info", children: [ /* @__PURE__ */ jsx2("span", { className: "variable-item__name", children: /* @__PURE__ */ jsx2("code", { children: `{{${variable.name}}}` }) }), /* @__PURE__ */ jsx2("span", { className: "variable-item__type", children: typeLabels[variable.type] || variable.type }), variable.required && /* @__PURE__ */ jsx2("span", { className: "variable-item__required", children: "*" }) ] }), !readOnly && /* @__PURE__ */ jsxs2("div", { className: "variable-item__actions", children: [ /* @__PURE__ */ jsx2( "button", { className: "variable-item__action", onClick: () => onEdit(variable.name), title: locale === "zh-CN" ? "\u7F16\u8F91" : "Edit", children: /* @__PURE__ */ jsx2(EditIcon, {}) } ), /* @__PURE__ */ jsx2( "button", { className: "variable-item__action variable-item__action--danger", onClick: () => onRemove(variable.name), title: locale === "zh-CN" ? "\u5220\u9664" : "Delete", children: /* @__PURE__ */ jsx2(DeleteIcon, {}) } ) ] }) ] }), variable.description && /* @__PURE__ */ jsx2("p", { className: "variable-item__description", children: variable.description }), variable.defaultValue !== void 0 && /* @__PURE__ */ jsxs2("div", { className: "variable-item__default", children: [ /* @__PURE__ */ jsx2("span", { children: locale === "zh-CN" ? "\u9ED8\u8BA4\u503C\uFF1A" : "Default: " }), /* @__PURE__ */ jsx2("code", { children: JSON.stringify(variable.defaultValue) }) ] }), variable.type === "select" && variable.options && /* @__PURE__ */ jsxs2("div", { className: "variable-item__options", children: [ /* @__PURE__ */ jsx2("span", { children: locale === "zh-CN" ? "\u9009\u9879\uFF1A" : "Options: " }), variable.options.map((opt, idx) => /* @__PURE__ */ jsxs2("span", { className: "variable-item__option", children: [ opt.label, " (", opt.value, ")" ] }, idx)) ] }), variable.validation && Object.keys(variable.validation).length > 0 && /* @__PURE__ */ jsxs2("div", { className: "variable-item__validation", children: [ /* @__PURE__ */ jsx2("span", { children: locale === "zh-CN" ? "\u9A8C\u8BC1\uFF1A" : "Validation: " }), variable.validation.min !== void 0 && /* @__PURE__ */ jsxs2("span", { children: [ "Min: ", variable.validation.min ] }), variable.validation.max !== void 0 && /* @__PURE__ */ jsxs2("span", { children: [ "Max: ", variable.validation.max ] }), variable.validation.pattern && /* @__PURE__ */ jsxs2("span", { children: [ "Pattern: ", /* @__PURE__ */ jsx2("code", { children: variable.validation.pattern }) ] }) ] }) ] }) }, variable.name )) }) }); }; var EditIcon = () => /* @__PURE__ */ jsx2("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "currentColor", children: /* @__PURE__ */ jsx2("path", { d: "M12.146.854a.5.5 0 01.708 0l2.292 2.292a.5.5 0 010 .708l-9 9a.5.5 0 01-.168.11l-5 2a.5.5 0 01-.65-.65l2-5a.5.5 0 01.11-.168l9-9zM12.5 2L14 3.5 12.5 5 11 3.5 12.5 2zm-10 10L1 14l2-1.5L12.5 3 11 1.5 1.5 11z" }) }); var DeleteIcon = () => /* @__PURE__ */ jsxs2("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "currentColor", children: [ /* @__PURE__ */ jsx2("path", { d: "M5.5 5.5A.5.5 0 016 6v6a.5.5 0 01-1 0V6a.5.5 0 01.5-.5zm2.5 0a.5.5 0 01.5.5v6a.5.5 0 01-1 0V6a.5.5 0 01.5-.5zm3 .5a.5.5 0 00-1 0v6a.5.5 0 001 0V6z" }), /* @__PURE__ */ jsx2("path", { d: "M14.5 3a1 1 0 01-1 1H13v9a2 2 0 01-2 2H5a2 2 0 01-2-2V4h-.5a1 1 0 01-1-1V2a1 1 0 011-1H6a1 1 0 011-1h2a1 1 0 011 1h3.5a1 1 0 011 1v1zM4 4h8v9a1 1 0 01-1 1H5a1 1 0 01-1-1V4z" }) ] }); // src/components/VariablePanel.tsx import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime"; var VariablePanel = ({ variables, customTypes, readOnly = false, locale = "en" }) => { const [isAddingVariable, setIsAddingVariable] = useState2(false); const [editingVariable, setEditingVariable] = useState2(null); const { addVariable, updateVariable, removeVariable } = usePromptEditorStore(); const handleAddVariable = (variable) => { addVariable(variable); setIsAddingVariable(false); }; const handleUpdateVariable = (name, updates) => { updateVariable(name, updates); setEditingVariable(null); }; const handleRemoveVariable = (name) => { if (window.confirm(locale === "zh-CN" ? "\u786E\u5B9A\u5220\u9664\u8BE5\u53D8\u91CF\u5417\uFF1F" : "Are you sure to delete this variable?")) { removeVariable(name); } }; const labels = { en: { title: "Variables", addVariable: "Add Variable", noVariables: "No variables defined", description: "Define variables to make your prompt dynamic" }, "zh-CN": { title: "\u53D8\u91CF", addVariable: "\u6DFB\u52A0\u53D8\u91CF", noVariables: "\u6682\u65E0\u53D8\u91CF", description: "\u5B9A\u4E49\u53D8\u91CF\u4F7F\u63D0\u793A\u8BCD\u66F4\u52A0\u7075\u6D3B" } }[locale]; return /* @__PURE__ */ jsxs3("div", { className: "variable-panel", children: [ /* @__PURE__ */ jsxs3("div", { className: "variable-panel__header", children: [ /* @__PURE__ */ jsx3("h3", { children: labels.title }), !readOnly && /* @__PURE__ */ jsxs3( "button", { className: "variable-panel__add-btn", onClick: () => setIsAddingVariable(true), disabled: isAddingVariable, children: [ "+ ", labels.addVariable ] } ) ] }), /* @__PURE__ */ jsx3("div", { className: "variable-panel__content", children: variables.length === 0 && !isAddingVariable ? /* @__PURE__ */ jsxs3("div", { className: "variable-panel__empty", children: [ /* @__PURE__ */ jsx3("p", { children: labels.noVariables }), /* @__PURE__ */ jsx3("small", { children: labels.description }) ] }) : /* @__PURE__ */ jsxs3(Fragment2, { children: [ isAddingVariable && /* @__PURE__ */ jsx3( motion2.div, { initial: { opacity: 0, y: -10 }, animate: { opacity: 1, y: 0 }, exit: { opacity: 0, y: -10 }, children: /* @__PURE__ */ jsx3( VariableForm, { onSubmit: handleAddVariable, onCancel: () => setIsAddingVariable(false), customTypes, locale } ) } ), /* @__PURE__ */ jsx3( VariableList, { variables, editingVariable, onEdit: setEditingVariable, onUpdate: handleUpdateVariable, onRemove: handleRemoveVariable, customTypes, readOnly, locale } ) ] }) }) ] }); }; // src/components/Toolbar.tsx import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime"; var Toolbar = ({ config, isDirty, onSave, onExport, onImport, locale = "en" }) => { const { undo, redo, history, historyIndex } = usePromptEditorStore(); const canUndo = historyIndex > 0; const canRedo = historyIndex < history.length - 1; const handleImport = () => { const input = document.createElement("input"); input.type = "file"; input.accept = ".json,.yaml,.yml,.txt"; input.onchange = async (e) => { const file = e.target.files?.[0]; if (file && onImport) { await onImport(file); } }; input.click(); }; const handleExport = (format) => { if (onExport) { onExport(format); } }; const labels = { en: { save: "Save", export: "Export", import: "Import", undo: "Undo", redo: "Redo", format: "Format", preview: "Preview", settings: "Settings" }, "zh-CN": { save: "\u4FDD\u5B58", export: "\u5BFC\u51FA", import: "\u5BFC\u5165", undo: "\u64A4\u9500", redo: "\u91CD\u505A", format: "\u683C\u5F0F\u5316", preview: "\u9884\u89C8", settings: "\u8BBE\u7F6E" } }[locale]; const defaultConfig = { showSave: true, showExport: true, showImport: true, showUndo: true, showRedo: true, showFormat: false, showPreview: false, showSettings: false, ...config }; return /* @__PURE__ */ jsxs4("div", { className: "prompt-editor__toolbar", children: [ /* @__PURE__ */ jsxs4("div", { className: "toolbar__group", children: [ defaultConfig.showSave && onSave && /* @__PURE__ */ jsxs4( "button", { className: "toolbar__btn toolbar__btn--primary", onClick: onSave, disabled: !isDirty, title: labels.save, children: [ /* @__PURE__ */ jsx4(SaveIcon, {}), /* @__PURE__ */ jsx4("span", { children: labels.save }) ] } ), defaultConfig.showUndo && /* @__PURE__ */ jsx4( "button", { className: "toolbar__btn", onClick: undo, disabled: !canUndo, title: labels.undo, children: /* @__PURE__ */ jsx4(UndoIcon, {}) } ), defaultConfig.showRedo && /* @__PURE__ */ jsx4( "button", { className: "toolbar__btn", onClick: redo, disabled: !canRedo, title: labels.redo, children: /* @__PURE__ */ jsx4(RedoIcon, {}) } ) ] }), /* @__PURE__ */ jsxs4("div", { className: "toolbar__group", children: [ defaultConfig.showImport && onImport && /* @__PURE__ */ jsxs4( "button", { className: "toolbar__btn", onClick: handleImport, title: labels.import, children: [ /* @__PURE__ */ jsx4(ImportIcon, {}), /* @__PURE__ */ jsx4("span", { children: labels.import }) ] } ), defaultConfig.showExport && onExport && /* @__PURE__ */ jsxs4("div", { className: "toolbar__dropdown", children: [ /* @__PURE__ */ jsxs4("button", { className: "toolbar__btn", title: labels.export, children: [ /* @__PURE__ */ jsx4(ExportIcon, {}), /* @__PURE__ */ jsx4("span", { children: labels.export }), /* @__PURE__ */ jsx4(ChevronDownIcon, {}) ] }), /* @__PURE__ */ jsxs4("div", { className: "toolbar__dropdown-menu", children: [ /* @__PURE__ */ jsx4("button", { onClick: () => handleExport("json"), children: "JSON" }), /* @__PURE__ */ jsx4("button", { onClick: () => handleExport("yaml"), children: "YAML" }), /* @__PURE__ */ jsx4("button", { onClick: () => handleExport("markdown"), children: "Markdown" }), /* @__PURE__ */ jsx4("button", { onClick: () => handleExport("txt"), children: "Text" }) ] }) ] }) ] }), defaultConfig.customActions && defaultConfig.customActions.length > 0 && /* @__PURE__ */ jsx4("div", { className: "toolbar__group", children: defaultConfig.customActions.map((action) => /* @__PURE__ */ jsxs4( "button", { className: "toolbar__btn", onClick: action.onClick, disabled: action.disabled, title: action.tooltip, children: [ action.icon, /* @__PURE__ */ jsx4("span", { children: action.label }) ] }, action.id )) }) ] }); }; var SaveIcon = () => /* @__PURE__ */ jsx4("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "currentColor", children: /* @__PURE__ */ jsx4("path", { d: "M13.5 2h-11A1.5 1.5 0 001 3.5v9A1.5 1.5 0 002.5 14h11a1.5 1.5 0 001.5-1.5v-9A1.5 1.5 0 0013.5 2zM12 12H4V6h8v6z" }) }); var UndoIcon = () => /* @__PURE__ */ jsx4("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "currentColor", children: /* @__PURE__ */ jsx4("path", { d: "M8 3a5 5 0 00-5 5h2a3 3 0 016 0 3 3 0 01-6 0H3l3-3-3-3v2a5 5 0 105 5V8z" }) }); var RedoIcon = () => /* @__PURE__ */ jsx4("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "currentColor", children: /* @__PURE__ */ jsx4("path", { d: "M8 3a5 5 0 015 5h-2a3 3 0 00-6 0 3 3 0 006 0h2l-3-3 3-3v2a5 5 0 11-5 5V8z" }) }); var ImportIcon = () => /* @__PURE__ */ jsx4("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "currentColor", children: /* @__PURE__ */ jsx4("path", { d: "M8 10l-4-4h3V2h2v4h3l-4 4zm-6 3v1h12v-1H2z" }) }); var ExportIcon = () => /* @__PURE__ */ jsx4("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "currentColor", children: /* @__PURE__ */ jsx4("path", { d: "M8 6l4 4H9v4H7v-4H4l4-4zM2 3v1h12V3H2z" }) }); var ChevronDownIcon = () => /* @__PURE__ */ jsx4("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "currentColor", children: /* @__PURE__ */ jsx4("path", { d: "M2 4l4 4 4-4H2z" }) }); // src/components/PreviewPanel.tsx import { useMemo } from "react"; import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime"; var PreviewPanel = ({ template, locale = "en" }) => { const { previewValues, setPreviewValue } = usePromptEditorStore(); const previewContent = useMemo(() => { let content = template.content; template.variables.forEach((variable) => { const value = previewValues[variable.name] ?? variable.defaultValue ?? ""; const regex = new RegExp(`{{\\s*${variable.name}\\s*}}`, "g"); content = content.replace(regex, String(value)); }); return content; }, [template.content, template.variables, previewValues]); const labels = { en: { title: "Preview", variables: "Variables", output: "Output", noContent: "No content to preview" }, "zh-CN": { title: "\u9884\u89C8", variables: "\u53D8\u91CF\u503C", output: "\u8F93\u51FA\u7ED3\u679C", noContent: "\u6682\u65E0\u5185\u5BB9\u53EF\u9884\u89C8" } }[locale]; return /* @__PURE__ */ jsxs5("div", { className: "preview-panel", children: [ /* @__PURE__ */ jsx5("div", { className: "preview-panel__header", children: /* @__PURE__ */ jsx5("h3", { children: labels.title }) }), /* @__PURE__ */ jsxs5("div", { className: "preview-panel__content", children: [ template.variables.length > 0 && /* @__PURE__ */ jsxs5("div", { className: "preview-panel__variables", children: [ /* @__PURE__ */ jsx5("h4", { children: labels.variables }), template.variables.map((variable) => /* @__PURE__ */ jsxs5("div", { className: "preview-panel__variable", children: [ /* @__PURE__ */ jsxs5("label", { children: [ variable.name, variable.required && /* @__PURE__ */ jsx5("span", { className: "required", children: "*" }) ] }), renderVariableInput( variable, previewValues[variable.name], (value) => setPreviewValue(variable.name, value) ), variable.description && /* @__PURE__ */ jsx5("small", { children: variable.description }) ] }, variable.name)) ] }), /* @__PURE__ */ jsxs5("div", { className: "preview-panel__output", children: [ /* @__PURE__ */ jsx5("h4", { children: labels.output }), /* @__PURE__ */ jsx5("div", { className: "preview-panel__output-content", children: previewContent ? /* @__PURE__ */ jsx5("pre", { children: previewContent }) : /* @__PURE__ */ jsx5("p", { className: "preview-panel__empty", children: labels.noContent }) }) ] }) ] }) ] }); }; function renderVariableInput(variable, value, onChange) { switch (variable.type) { case "text": return /* @__PURE__ */ jsx5( "input", { type: "text", value: value || "", onChange: (e) => onChange(e.target.value), placeholder: variable.defaultValue } ); case "number": return /* @__PURE__ */ jsx5( "input", { type: "number", value: value || "", onChange: (e) => onChange(e.target.value ? Number(e.target.value) : ""), min: variable.validation?.min, max: variable.validation?.max, placeholder: variable.defaultValue } ); case "boolean": return /* @__PURE__ */ jsx5( "input", { type: "checkbox", checked: value ?? variable.defaultValue ?? false, onChange: (e) => onChange(e.target.checked) } ); case "select": return /* @__PURE__ */ jsxs5( "select", { value: value || "", onChange: (e) => onChange(e.target.value), children: [ /* @__PURE__ */ jsx5("option", { value: "", children: "-- Select --" }), variable.options?.map((option) => /* @__PURE__ */ jsx5("option", { value: option.value, children: option.label }, option.value)) ] } ); case "multiline": return /* @__PURE__ */ jsx5( "textarea", { value: value || "", onChange: (e) => onChange(e.target.value), placeholder: variable.defaultValue, rows: 3 } ); default: return /* @__PURE__ */ jsx5( "input", { type: "text", value: value || "", onChange: (e) => onChange(e.target.value), placeholder: variable.defaultValue } ); } } // src/components/PromptEditor.tsx import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime"; var PromptEditor = ({ initialTemplate: initialTemplate2, mode = "light", readOnly = false, showLineNumbers = true, showVariablePanel = true, showPreview = true, height = "600px", width = "100%", theme, locale = "en", onSave, onExport, onImport, customVariableTypes, toolbar, className, style }) => { const { template, isDirty, updateContent, loadTemplate, save } = usePromptEditorStore(); useEffect(() => { if (initialTemplate2) { loadTemplate(initialTemplate2); } }, [initialTemplate2, loadTemplate]); const handleEditorChange = useCallback((value) => { if (value !== void 0 && !readOnly) { updateContent(value); } }, [updateContent, readOnly]); const handleSave = useCallback(async () => { if (onSave) { await onSave(template); await save(); } else { await save(); } }, [template, onSave, save]); const editorOptions = useMemo2(() => ({ readOnly, lineNumbers: showLineNumbers ? "on" : "off", minimap: { enabled: false }, scrollBeyondLastLine: false, wordWrap: "on", fontSize: 14, fontFamily: 'Consolas, "Courier New", monospace', padding: { top: 16, bottom: 16 }, automaticLayout: true }), [readOnly, showLineNumbers]); const monacoTheme = theme?.monacoTheme || (mode === "dark" ? "vs-dark" : "vs"); const containerStyle = { height, width, ...style }; return /* @__PURE__ */ jsxs6( "div", { className: `prompt-editor prompt-editor--${mode} ${className || ""}`, style: containerStyle, "data-theme": mode, children: [ /* @__PURE__ */ jsx6( Toolbar, { config: toolbar, isDirty, onSave: handleSave, onExport: onExport ? (format) => onExport(template, format) : void 0, onImport, locale } ), /* @__PURE__ */ jsxs6("div", { className: "prompt-editor__main", children: [ /* @__PURE__ */ jsx6(AnimatePresence2, { children: showVariablePanel && /* @__PURE__ */ jsx6( motion3.div, { className: "prompt-editor__variables", initial: { width: 0, opacity: 0 }, animate: { width: 300, opacity: 1 }, exit: { width: 0, opacity: 0 }, transition: { duration: 0.2 }, children: /* @__PURE__ */ jsx6( VariablePanel, { variables: template.variables, customTypes: customVariableTypes, readOnly, locale } ) } ) }), /* @__PURE__ */ jsx6("div", { className: "prompt-editor__content", children: /* @__PURE__ */ jsx6( MonacoEditor, { value: template.content, language: "markdown", theme: monacoTheme, options: editorOptions, onChange: handleEditorChange, beforeMount: (monaco) => { registerVariableCompletion(monaco, template.variables); } } ) }), /* @__PURE__ */ jsx6(AnimatePresence2, { children: showPreview && /* @__PURE__ */ jsx6( motion3.div, { className: "prompt-editor__preview", initial: { width: 0, opacity: 0 }, animate: { width: 400, opacity: 1 }, exit: { width: 0, opacity: 0 }, transition: { duration: 0.2 }, children: /* @__PURE__ */ jsx6( PreviewPanel, { template, locale } ) } ) }) ] }) ] } ); }; function registerVariableCompletion(monaco, variables) { monaco.languages.registerCompletionItemProvider("markdown", { provideCompletionItems: (model, position) => { const word = model.getWordUntilPosition(position); const range = { startLineNumber: position.lineNumber, endLineNumber: position.lineNumber, startColumn: word.startColumn, endColumn: word.endColumn }; const suggestions = variables.map((variable) => ({ label: `{{${variable.name}}}`, kind: monaco.languages.CompletionItemKind.Variable, documentation: variable.description, insertText: `{{${variable.name}}}`, range })); return { suggestions }; }, triggerCharacters: ["{"] }); } // ../../node_modules/.pnpm/js-yaml@4.1.0/node_modules/js-yaml/dist/js-yaml.mjs function isNothing(subject) { return typeof subject === "undefined" || subject === null; } function isObject(subject) { return typeof subject === "object" && subject !== null; } function toArray(sequence) { if (Array.isArray(sequence)) return sequence; else if (isNothing(sequence)) return []; return [sequence]; } function extend(target, source) { var index, length, key, sourceKeys; if (source) { sourceKeys = Object.keys(source); for (index = 0, length = sourceKeys.length; index < length; index += 1) { key = sourceKeys[index]; target[key] = source[key]; } } return target; } function repeat(string, count) { var result = "", cycle; for (cycle = 0; cycle < count; cycle += 1) { result += string; } return result; } function isNegativeZero(number) { return number === 0 && Number.NEGATIVE_INFINITY === 1 / number; } var isNothing_1 = isNothing; var isObject_1 = isObject; var toArray_1 = toArray; var repeat_1 = repeat; var isNegativeZero_1 = isNegativeZero; var extend_1 = extend; var common = { isNothing: isNothing_1, isObject: isObject_1, toArray: toArray_1, repeat: repeat_1, isNegativeZero: isNegativeZero_1, extend: extend_1 }; function formatError(exception2, compact) { var where = "", message = exception2.reason || "(unknown reason)"; if (!exception2.mark) return message; if (exception2.mark.name) { where += 'in "' + exception2.mark.name + '" '; } where += "(" + (exception2.mark.line + 1) + ":" + (exception2.mark.column + 1) + ")"; if (!compact && exception2.mark.snippet) { where += "\n\n" + exception2.mark.snippet; } return message + " " + where; } function YAMLException$1(reason, mark) { Error.call(this); this.name = "YAMLException"; this.reason = reason; this.mark = mark; this.message = formatError(this, false); if (Error.captureStackTrace) { Error.captureStackTrace(this, this.constructor); } else { this.stack = new Error().stack || ""; } } YAMLException$1.prototype = Object.create(Error.prototype); YAMLException$1.prototype.constructor = YAMLException$1; YAMLException$1.prototype.toString = function toString(compact) { return this.name + ": " + formatError(this, compact); }; var exception = YAMLException$1; function getLine(buffer, lineStart, lineEnd, position, maxLineLength) { var head = ""; var tail = ""; var maxHalfLength = Math.floor(maxLineLength / 2) - 1; if (position - lineStart > maxHalfLength) { head = " ... "; lineStart = position - maxHalfLength + head.length; } if (lineEnd - position > maxHalfLength) { tail = " ..."; lineEnd = position + maxHalfLength - tail.length; } return { str: head + buffer.slice(lineStart, lineEnd).replace(/\t/g, "\u2192") + tail, pos: position - lineStart + head.length // relative position }; } function padStart(string, max) { return common.repeat(" ", max - string.length) + string; } function makeSnippet(mark, options) { options = Object.create(options || null); if (!mark.buffer) return null; if (!options.maxLength) options.maxLength = 79; if (typeof options.indent !== "number") options.indent = 1; if (typeof options.linesBefore !== "number") options.linesBefore = 3; if (typeof options.linesAfter !== "number") options.linesAfter = 2; var re = /\r?\n|\r|\0/g; var lineStarts = [0]; var lineEnds = []; var match; var foundLineNo = -1; while (match = re.exec(mark.buffer)) { lineEnds.push(match.index); lineStarts.push(match.index + match[0].length); if (mark.position <= match.index && foundLineNo < 0) { foundLineNo = lineStarts.length - 2; } } if (foundLineNo < 0) foundLineNo = lineStarts.length - 1; var result = "", i, line; var lineNoLength = Math.min(mark.line + options.linesAfter, lineEnds.length).toString().length; var maxLineLength = options.maxLength - (options.indent + lineNoLength + 3); for (i = 1; i <= options.linesBefore; i++) { if (foundLineNo - i < 0) break; line = getLine( mark.buffer, lineStarts[foundLineNo - i], lineEnds[foundLineNo - i], mark.position - (lineStarts[foundLineNo] - lineStarts[foundLineNo - i]), maxLineLength ); result = common.repeat(" ", options.indent) + padStart((mark.line - i + 1).toString(), lineNoLength) + " | " + line.str + "\n" + result; } line = getLine(mark.buffer, lineStarts[foundLineNo], lineEnds[foundLineNo], mark.position, maxLineLength); result += common.repeat(" ", options.indent) + padStart((mark.line + 1).toString(), lineNoLength) + " | " + line.str + "\n"; result += common.repeat("-", options.indent + lineNoLength + 3 + line.pos) + "^\n"; for (i = 1; i <= options.linesAfter; i++) { if (foundLineNo + i >= lineEnds.length) break; line = getLine( mark.buffer, lineStarts[foundLineNo + i], lineEnds[foundLineNo + i], mark.position - (lineStarts[foundLineNo] - lineStarts[foundLineNo + i]), maxLineLength ); result += common.repeat(" ", options.indent) + padStart((mark.line + i + 1).toString(), lineNoLength) + " | " + line.str + "\n"; } return result.replace(/\n$/, ""); } var snippet = makeSnippet; var TYPE_CONSTRUCTOR_OPTIONS = [ "kind", "multi", "resolve", "construct", "instanceOf", "predicate", "represent", "representName", "defaultStyle", "styleAliases" ]; var YAML_NODE_KINDS = [ "scalar", "sequence", "mapping" ]; function compileStyleAliases(map2) { var result = {}; if (map2 !== null) { Object.keys(map2).forEach(function(style) { map2[style].forEach(function(alias) { result[String(alias)] = style; }); }); } return result; } function Type$1(tag, options) { options = options || {}; Object.keys(options).forEach(function(name) { if (TYPE_CONSTRUCTOR_OPTIONS.indexOf(name) === -1) { throw new exception('Unknown option "' + name + '" is met in definition of "' + tag + '" YAML type.'); } }); this.options = options; this.tag = tag; this.kind = options["kind"] || null; this.resolve = options["resolve"] || function() { return true; }; this.construct = options["construct"] || function(data) { return data; }; this.instanceOf = options["instanceOf"] || null; this.predicate = options["predicate"] || null; this.represent = options["represent"] || null; this.representName = options["representName"] || null; this.defaultStyle = options["defaultStyle"] || null; this.multi = options["multi"] || false; this.styleAliases = compileStyleAliases(options["styleAliases"] || null); if (YAML_NODE_KINDS.indexOf(this.kind) === -1) { throw new exception('Unknown kind "' + this.kind + '" is specified for "' + tag + '" YAML type.'); } } var type = Type$1; function compileList(schema2, name) { var result = []; schema2[name].forEach(function(currentType) { var newIndex = result.length; result.forEach(function(previousType, previousIndex) { if (previousType.tag === currentType.tag && previousType.kind === currentType.kind && previousType.multi === currentType.multi) { newIndex = previousIndex; } }); result[newIndex] = currentType; }); return result; } function compileMap() { var result = { scalar: {}, sequence: {}, mapping: {}, fallback: {}, multi: { scalar: [], sequence: [], mapping: [], fallback: [] } }, index, length; function collectType(type2) { if (type2.multi) { result.multi[type2.kind].push(type2); result.multi["fallback"].push(type2); } else { result[type2.kind][type2.tag] = result["fallback"][type2.tag] = type2; } } for (index = 0, length = arguments.length; index < length; index += 1) { arguments[index].forEach(collectType); } return result; } function Schema$1(definition) { return this.extend(definition); } Schema$1.prototype.extend = function extend2(definition) { var implicit = []; var explicit = []; if (definition instanceof type) { explicit.push(definition); } else if (Array.isArray(definition)) { explicit = explicit.concat(definition); } else if (definition && (Array.isArray(definition.implicit) || Array.isArray(definition.explicit))) { if (definition.implicit) implicit = implicit.concat(definition.implicit); if (definition.explicit) explicit = explicit.concat(definition.explicit); } else { throw new exception("Schema.extend argument should be a Type, [ Type ], or a schema definition ({ implicit: [...], explicit: [...] })"); } implicit.forEach(function(type$1) { if (!(type$1 instanceof type)) { throw new exception("Specified list of YAML types (or a single Type object) contains a non-Type object."); } if (type$1.loadKind && type$1.loadKind !== "scalar") { throw new exception("There is a non-scalar type in the implicit list of a schema. Implicit resolving of such types is not supported."); } if (type$1.multi) { throw new exception("There is a multi type in the implicit list of a schema. Multi tags can only be listed as explicit."); } }); explicit.forEach(function(type$1) { if (!(type$1 instanceof type)) {