System prompt
This commit is contained in:
parent
c2c55eb820
commit
d1325b33b7
|
|
@ -111,7 +111,8 @@
|
|||
}
|
||||
|
||||
.input,
|
||||
.select {
|
||||
.select,
|
||||
.textarea {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
|
|
@ -120,6 +121,13 @@
|
|||
color: var(--text);
|
||||
}
|
||||
|
||||
.textarea {
|
||||
resize: vertical;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.footer {
|
||||
padding: 16px 20px;
|
||||
border-top: 1px solid var(--border);
|
||||
|
|
|
|||
|
|
@ -1,98 +0,0 @@
|
|||
import clsx from "clsx";
|
||||
|
||||
import { useInputState } from "@common/hooks/useInputState";
|
||||
|
||||
import { useAppState } from "../contexts/state";
|
||||
import styles from "../assets/settings-modal.module.css";
|
||||
import { X } from "lucide-preact";
|
||||
|
||||
interface Props {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export const BannedTokensModal = ({ onClose }: Props) => {
|
||||
const { bannedTokens, dispatch } = useAppState();
|
||||
const [inputValue, setInputValue] = useInputState();
|
||||
|
||||
const handleAdd = () => {
|
||||
const trimmed = inputValue.trim();
|
||||
if (trimmed && !bannedTokens.includes(trimmed)) {
|
||||
dispatch({
|
||||
type: "SET_BANNED_TOKENS",
|
||||
tokens: [...bannedTokens, trimmed],
|
||||
});
|
||||
setInputValue("");
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemove = (token: string) => {
|
||||
dispatch({
|
||||
type: "SET_BANNED_TOKENS",
|
||||
tokens: bannedTokens.filter((t) => t !== token),
|
||||
});
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === "Enter") {
|
||||
handleAdd();
|
||||
} else if (e.key === "Escape") {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
const sortedTokens = [...bannedTokens].sort((a, b) =>
|
||||
a.trim().toLowerCase().localeCompare(b.trim().toLowerCase())
|
||||
);
|
||||
|
||||
return (
|
||||
<div class={styles.overlay} onClick={onClose}>
|
||||
<div class={styles.modal} onClick={(e) => e.stopPropagation()}>
|
||||
<div class={styles.header}>
|
||||
<h2 class={styles.title}>Banned Tokens</h2>
|
||||
<button class={styles.closeButton} onClick={onClose}>
|
||||
<X size={20} />
|
||||
</button>
|
||||
</div>
|
||||
<div class={styles.content}>
|
||||
<div class={styles.inputRow}>
|
||||
<input
|
||||
type="text"
|
||||
value={inputValue}
|
||||
onInput={setInputValue}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder="Token to ban"
|
||||
class={styles.input}
|
||||
autoFocus
|
||||
/>
|
||||
<button onClick={handleAdd} class={clsx(styles.button, styles.buttonPrimary)}>
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
<div class={styles.divider} />
|
||||
<div class={styles.tokenList}>
|
||||
{sortedTokens.length === 0 ? (
|
||||
<p class={styles.emptyText}>No banned tokens</p>
|
||||
) : (
|
||||
sortedTokens.map((token) => (
|
||||
<div key={token} class={styles.tokenItem}>
|
||||
<span>{token}</span>
|
||||
<button
|
||||
onClick={() => handleRemove(token)}
|
||||
class={styles.tokenRemoveButton}
|
||||
>
|
||||
<X size={12} />
|
||||
</button>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div class={styles.footer}>
|
||||
<button onClick={onClose} class={clsx(styles.button, styles.buttonSecondary)}>
|
||||
Done
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,185 @@
|
|||
import { useMemo, useRef } from "preact/hooks";
|
||||
|
||||
import { useQuery } from "@common/hooks/useAsyncState";
|
||||
import { useInputState } from "@common/hooks/useInputState";
|
||||
import { useUpdate } from "@common/hooks/useUpdate";
|
||||
|
||||
import { useAppState } from "../contexts/state";
|
||||
import LLM from "../utils/llm";
|
||||
import styles from "../assets/settings-modal.module.css";
|
||||
import { X } from "lucide-preact";
|
||||
|
||||
interface Props {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export const ConnectionSettingsModal = ({ onClose }: Props) => {
|
||||
const { connection, model, dispatch } = useAppState();
|
||||
const [url, setUrl] = useInputState(connection?.url ?? "");
|
||||
const [apiKey, setApiKey] = useInputState(connection?.apiKey ?? "");
|
||||
const [selectedModel, setSelectedModel] = useInputState(model?.id ?? "");
|
||||
const [update, triggerFetch] = useUpdate();
|
||||
|
||||
const urlRef = useRef(url);
|
||||
const apiKeyRef = useRef(apiKey);
|
||||
|
||||
urlRef.current = url;
|
||||
apiKeyRef.current = apiKey;
|
||||
|
||||
const connectionToFetch = useMemo<LLM.Connection | null>(() => {
|
||||
const currentUrl = urlRef.current;
|
||||
const currentApiKey = apiKeyRef.current;
|
||||
if (!currentUrl || !currentApiKey) return null;
|
||||
return { url: currentUrl, apiKey: currentApiKey };
|
||||
}, [update]);
|
||||
|
||||
const fetchModels = useMemo(() => async (conn: LLM.Connection | null) => {
|
||||
if (!conn) return [];
|
||||
const r = await LLM.getModels(conn);
|
||||
return r.data;
|
||||
}, []);
|
||||
|
||||
const modelsData = useQuery(fetchModels, connectionToFetch);
|
||||
|
||||
const isLoadingModels = connectionToFetch != null && modelsData == undefined;
|
||||
const groupedModels = useMemo(() => {
|
||||
const sorted = (modelsData ?? []).sort((a, b) => {
|
||||
const aWeight = Number(a.support_tools) * 2 + Number(a.support_thinking);
|
||||
const bWeight = Number(b.support_tools) * 2 + Number(b.support_thinking);
|
||||
if (aWeight !== bWeight) {
|
||||
return bWeight - aWeight;
|
||||
}
|
||||
|
||||
const aContext = a.max_context ?? 0;
|
||||
const bContext = b.max_context ?? 0;
|
||||
if (aContext !== bContext) {
|
||||
return bContext - aContext;
|
||||
}
|
||||
|
||||
return a.id.localeCompare(b.id);
|
||||
});
|
||||
|
||||
// Group by context size
|
||||
const groups = Map.groupBy(sorted, m => m.max_context ?? 0);
|
||||
|
||||
// Convert to array sorted by context size (bigger first)
|
||||
return Array.from(groups.entries())
|
||||
.sort((a, b) => b[0] - a[0])
|
||||
.map(([context, models]) => ({ context, models }));
|
||||
}, [modelsData]);
|
||||
|
||||
const handleBlur = () => {
|
||||
if (url && apiKey) {
|
||||
triggerFetch();
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Enter' && url && apiKey) {
|
||||
triggerFetch();
|
||||
}
|
||||
};
|
||||
|
||||
const handleConfirm = () => {
|
||||
dispatch({
|
||||
type: 'SET_CONNECTION',
|
||||
connection: connectionToFetch,
|
||||
});
|
||||
const selectedModelInfo = modelsData?.find(m => m.id === selectedModel) ?? null;
|
||||
dispatch({
|
||||
type: 'SET_MODEL',
|
||||
model: selectedModelInfo,
|
||||
});
|
||||
onClose();
|
||||
};
|
||||
|
||||
const connectionToTest = url && apiKey ? { url, apiKey } : null;
|
||||
|
||||
return (
|
||||
<div class={styles.overlay} onClick={onClose}>
|
||||
<div class={styles.modal} onClick={(e) => e.stopPropagation()}>
|
||||
<div class={styles.header}>
|
||||
<h2 class={styles.title}>Connection Settings</h2>
|
||||
<button class={styles.closeButton} onClick={onClose}>
|
||||
<X size={20} />
|
||||
</button>
|
||||
</div>
|
||||
<div class={styles.content}>
|
||||
<div class={styles.form} autocomplete="off">
|
||||
<div class={styles.formGroup}>
|
||||
<label class={styles.label}>
|
||||
API URL
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={url}
|
||||
onInput={setUrl}
|
||||
onBlur={handleBlur}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder="http://localhost:1234"
|
||||
class={styles.input}
|
||||
autocomplete="off"
|
||||
name="api-url-random"
|
||||
/>
|
||||
</div>
|
||||
<div class={styles.formGroup}>
|
||||
<label class={styles.label}>
|
||||
API Key
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
value={apiKey}
|
||||
onInput={setApiKey}
|
||||
onBlur={handleBlur}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder="your-api-key"
|
||||
class={styles.input}
|
||||
autocomplete="new-password"
|
||||
name="api-key-random"
|
||||
/>
|
||||
</div>
|
||||
<div class={styles.formGroup}>
|
||||
<label class={styles.label}>
|
||||
Model
|
||||
</label>
|
||||
{connectionToTest ? (
|
||||
isLoadingModels ? (
|
||||
<p>Loading models...</p>
|
||||
) : groupedModels.length > 0 ? (
|
||||
<select
|
||||
value={selectedModel}
|
||||
onChange={setSelectedModel}
|
||||
class={styles.select}
|
||||
>
|
||||
<option value="">Select a model</option>
|
||||
{groupedModels.map(({ context, models }) => (
|
||||
<optgroup key={context} label={`${context} context`}>
|
||||
{models.map(m => (
|
||||
<option key={m.id} value={m.id}>
|
||||
{m.support_tools ? '🔨' : ''}{m.support_thinking ? '🧠' : ''}{m.id} {m.max_length ? `(len: ${m.max_length})` : ''}
|
||||
</option>
|
||||
))}
|
||||
</optgroup>
|
||||
))}
|
||||
</select>
|
||||
) : (
|
||||
<p>No models available</p>
|
||||
)
|
||||
) : (
|
||||
<p>Enter connection details to load models</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class={styles.footer}>
|
||||
<button onClick={onClose} class={`${styles.button} ${styles.buttonSecondary}`}>
|
||||
Cancel
|
||||
</button>
|
||||
<button onClick={handleConfirm} class={`${styles.button} ${styles.buttonPrimary}`}>
|
||||
Confirm
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
import clsx from "clsx";
|
||||
import { Sidebar } from "./sidebar";
|
||||
import { ConnectionSettingsModal } from "./connection-settings-modal";
|
||||
import { SettingsModal } from "./settings-modal";
|
||||
import { BannedTokensModal } from "./banned-tokens-modal";
|
||||
import { useAppState } from "../contexts/state";
|
||||
import { useBool } from "@common/hooks/useBool";
|
||||
import type { Story } from "../contexts/state";
|
||||
import styles from '../assets/menu-sidebar.module.css';
|
||||
import { useState } from "preact/hooks";
|
||||
import { Pencil, X, Plus, Settings, Ban } from "lucide-preact";
|
||||
import { Pencil, X, Plus, Plug, Settings } from "lucide-preact";
|
||||
|
||||
// ─── Story Item ───────────────────────────────────────────────────────────────
|
||||
|
||||
|
|
@ -82,8 +82,8 @@ const StoryItem = ({ story, active, onSelect, onRename, onDelete }: StoryItemPro
|
|||
|
||||
export const MenuSidebar = () => {
|
||||
const { stories, currentStory, dispatch } = useAppState();
|
||||
const isConnectionSettingsOpen = useBool(false);
|
||||
const isSettingsOpen = useBool(false);
|
||||
const isBannedTokensOpen = useBool(false);
|
||||
|
||||
const handleCreate = () => {
|
||||
dispatch({ type: 'CREATE_STORY', title: 'New Story' });
|
||||
|
|
@ -124,20 +124,20 @@ export const MenuSidebar = () => {
|
|||
))}
|
||||
</div>
|
||||
<div class={styles.bottomButtons}>
|
||||
<button class={styles.settingsButton} onClick={isBannedTokensOpen.toggle}>
|
||||
<Ban size={16} /> Banned Tokens
|
||||
</button>
|
||||
<button class={styles.settingsButton} onClick={isSettingsOpen.toggle}>
|
||||
<Settings size={16} /> Settings
|
||||
</button>
|
||||
<button class={styles.settingsButton} onClick={isConnectionSettingsOpen.toggle}>
|
||||
<Plug size={16} /> Connection Settings
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{isBannedTokensOpen.value && (
|
||||
<BannedTokensModal onClose={isBannedTokensOpen.toggle} />
|
||||
)}
|
||||
{isSettingsOpen.value && (
|
||||
<SettingsModal onClose={isSettingsOpen.toggle} />
|
||||
)}
|
||||
{isConnectionSettingsOpen.value && (
|
||||
<ConnectionSettingsModal onClose={isConnectionSettingsOpen.toggle} />
|
||||
)}
|
||||
</Sidebar>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,99 +1,61 @@
|
|||
import { useMemo, useRef } from "preact/hooks";
|
||||
import clsx from "clsx";
|
||||
import { useMemo, useState } from "preact/hooks";
|
||||
|
||||
import { useQuery } from "@common/hooks/useAsyncState";
|
||||
import { useInputState } from "@common/hooks/useInputState";
|
||||
import { useUpdate } from "@common/hooks/useUpdate";
|
||||
|
||||
import { useAppState } from "../contexts/state";
|
||||
import LLM from "../utils/llm";
|
||||
import styles from "../assets/settings-modal.module.css";
|
||||
import { X } from "lucide-preact";
|
||||
import { useInputCallback } from "@common/hooks/useInputCallback";
|
||||
|
||||
interface Props {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
type Tab = "banned-tokens" | "system-instruction";
|
||||
|
||||
export const SettingsModal = ({ onClose }: Props) => {
|
||||
const { connection, model, dispatch } = useAppState();
|
||||
const [url, setUrl] = useInputState(connection?.url ?? "");
|
||||
const [apiKey, setApiKey] = useInputState(connection?.apiKey ?? "");
|
||||
const [selectedModel, setSelectedModel] = useInputState(model?.id ?? "");
|
||||
const [update, triggerFetch] = useUpdate();
|
||||
const { bannedTokens, systemInstruction, dispatch } = useAppState();
|
||||
const [inputValue, setInputValue] = useInputState();
|
||||
const [activeTab, setActiveTab] = useState<Tab>("banned-tokens");
|
||||
|
||||
const urlRef = useRef(url);
|
||||
const apiKeyRef = useRef(apiKey);
|
||||
|
||||
urlRef.current = url;
|
||||
apiKeyRef.current = apiKey;
|
||||
|
||||
const connectionToFetch = useMemo<LLM.Connection | null>(() => {
|
||||
const currentUrl = urlRef.current;
|
||||
const currentApiKey = apiKeyRef.current;
|
||||
if (!currentUrl || !currentApiKey) return null;
|
||||
return { url: currentUrl, apiKey: currentApiKey };
|
||||
}, [update]);
|
||||
|
||||
const fetchModels = useMemo(() => async (conn: LLM.Connection | null) => {
|
||||
if (!conn) return [];
|
||||
const r = await LLM.getModels(conn);
|
||||
return r.data;
|
||||
// Save system instruction on every change
|
||||
const setInstructionValue = useInputCallback((instructionValue) => {
|
||||
dispatch({
|
||||
type: "SET_SYSTEM_INSTRUCTION",
|
||||
systemInstruction: instructionValue,
|
||||
});
|
||||
}, []);
|
||||
|
||||
const modelsData = useQuery(fetchModels, connectionToFetch);
|
||||
|
||||
const isLoadingModels = connectionToFetch != null && modelsData == undefined;
|
||||
const groupedModels = useMemo(() => {
|
||||
const sorted = (modelsData ?? []).sort((a, b) => {
|
||||
const aWeight = Number(a.support_tools) * 2 + Number(a.support_thinking);
|
||||
const bWeight = Number(b.support_tools) * 2 + Number(b.support_thinking);
|
||||
if (aWeight !== bWeight) {
|
||||
return bWeight - aWeight;
|
||||
}
|
||||
|
||||
const aContext = a.max_context ?? 0;
|
||||
const bContext = b.max_context ?? 0;
|
||||
if (aContext !== bContext) {
|
||||
return bContext - aContext;
|
||||
}
|
||||
|
||||
return a.id.localeCompare(b.id);
|
||||
const handleAdd = () => {
|
||||
const trimmed = inputValue.trim();
|
||||
if (trimmed && !bannedTokens.includes(trimmed)) {
|
||||
dispatch({
|
||||
type: "SET_BANNED_TOKENS",
|
||||
tokens: [...bannedTokens, trimmed],
|
||||
});
|
||||
|
||||
// Group by context size
|
||||
const groups = Map.groupBy(sorted, m => m.max_context ?? 0);
|
||||
|
||||
// Convert to array sorted by context size (bigger first)
|
||||
return Array.from(groups.entries())
|
||||
.sort((a, b) => b[0] - a[0])
|
||||
.map(([context, models]) => ({ context, models }));
|
||||
}, [modelsData]);
|
||||
|
||||
const handleBlur = () => {
|
||||
if (url && apiKey) {
|
||||
triggerFetch();
|
||||
setInputValue("");
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemove = (token: string) => {
|
||||
dispatch({
|
||||
type: "SET_BANNED_TOKENS",
|
||||
tokens: bannedTokens.filter((t) => t !== token),
|
||||
});
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Enter' && url && apiKey) {
|
||||
triggerFetch();
|
||||
if (e.key === "Enter") {
|
||||
handleAdd();
|
||||
} else if (e.key === "Escape") {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
const handleConfirm = () => {
|
||||
dispatch({
|
||||
type: 'SET_CONNECTION',
|
||||
connection: connectionToFetch,
|
||||
});
|
||||
const selectedModelInfo = modelsData?.find(m => m.id === selectedModel) ?? null;
|
||||
dispatch({
|
||||
type: 'SET_MODEL',
|
||||
model: selectedModelInfo,
|
||||
});
|
||||
onClose();
|
||||
};
|
||||
|
||||
const connectionToTest = url && apiKey ? { url, apiKey } : null;
|
||||
const sortedTokens = [...bannedTokens].sort((a, b) =>
|
||||
a.trim().toLowerCase().localeCompare(b.trim().toLowerCase())
|
||||
);
|
||||
|
||||
return (
|
||||
<div class={styles.overlay} onClick={onClose}>
|
||||
|
|
@ -104,79 +66,76 @@ export const SettingsModal = ({ onClose }: Props) => {
|
|||
<X size={20} />
|
||||
</button>
|
||||
</div>
|
||||
<div class={styles.tabs}>
|
||||
<button
|
||||
class={clsx(styles.tab, activeTab === "banned-tokens" && styles.active)}
|
||||
onClick={() => setActiveTab("banned-tokens")}
|
||||
>
|
||||
Banned Tokens
|
||||
</button>
|
||||
<button
|
||||
class={clsx(styles.tab, activeTab === "system-instruction" && styles.active)}
|
||||
onClick={() => setActiveTab("system-instruction")}
|
||||
>
|
||||
System Instruction
|
||||
</button>
|
||||
</div>
|
||||
<div class={styles.content}>
|
||||
<div class={styles.form} autocomplete="off">
|
||||
<div class={styles.formGroup}>
|
||||
<label class={styles.label}>
|
||||
API URL
|
||||
</label>
|
||||
{activeTab === "banned-tokens" ? (
|
||||
<>
|
||||
<div class={styles.inputRow}>
|
||||
<input
|
||||
type="text"
|
||||
value={url}
|
||||
onInput={setUrl}
|
||||
onBlur={handleBlur}
|
||||
value={inputValue}
|
||||
onInput={setInputValue}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder="http://localhost:1234"
|
||||
placeholder="Token to ban"
|
||||
class={styles.input}
|
||||
autocomplete="off"
|
||||
name="api-url-random"
|
||||
autoFocus
|
||||
/>
|
||||
<button onClick={handleAdd} class={clsx(styles.button, styles.buttonPrimary)}>
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
<div class={styles.formGroup}>
|
||||
<label class={styles.label}>
|
||||
API Key
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
value={apiKey}
|
||||
onInput={setApiKey}
|
||||
onBlur={handleBlur}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder="your-api-key"
|
||||
class={styles.input}
|
||||
autocomplete="new-password"
|
||||
name="api-key-random"
|
||||
/>
|
||||
</div>
|
||||
<div class={styles.formGroup}>
|
||||
<label class={styles.label}>
|
||||
Model
|
||||
</label>
|
||||
{connectionToTest ? (
|
||||
isLoadingModels ? (
|
||||
<p>Loading models...</p>
|
||||
) : groupedModels.length > 0 ? (
|
||||
<select
|
||||
value={selectedModel}
|
||||
onChange={setSelectedModel}
|
||||
class={styles.select}
|
||||
<div class={styles.divider} />
|
||||
<div class={styles.tokenList}>
|
||||
{sortedTokens.length === 0 ? (
|
||||
<p class={styles.emptyText}>No banned tokens</p>
|
||||
) : (
|
||||
sortedTokens.map((token) => (
|
||||
<div key={token} class={styles.tokenItem}>
|
||||
<span>{token}</span>
|
||||
<button
|
||||
onClick={() => handleRemove(token)}
|
||||
class={styles.tokenRemoveButton}
|
||||
>
|
||||
<option value="">Select a model</option>
|
||||
{groupedModels.map(({ context, models }) => (
|
||||
<optgroup key={context} label={`${context} context`}>
|
||||
{models.map(m => (
|
||||
<option key={m.id} value={m.id}>
|
||||
{m.support_tools ? '🔨' : ''}{m.support_thinking ? '🧠' : ''}{m.id} {m.max_length ? `(len: ${m.max_length})` : ''}
|
||||
</option>
|
||||
))}
|
||||
</optgroup>
|
||||
))}
|
||||
</select>
|
||||
) : (
|
||||
<p>No models available</p>
|
||||
)
|
||||
) : (
|
||||
<p>Enter connection details to load models</p>
|
||||
<X size={12} />
|
||||
</button>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div class={styles.form}>
|
||||
<div class={styles.formGroup}>
|
||||
<label class={styles.label}>
|
||||
System Instruction
|
||||
</label>
|
||||
<textarea
|
||||
value={systemInstruction}
|
||||
onInput={setInstructionValue}
|
||||
placeholder="Enter system instruction for the AI assistant..."
|
||||
class={clsx(styles.input, styles.textarea)}
|
||||
rows={10}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div class={styles.footer}>
|
||||
<button onClick={onClose} class={`${styles.button} ${styles.buttonSecondary}`}>
|
||||
Cancel
|
||||
</button>
|
||||
<button onClick={handleConfirm} class={`${styles.button} ${styles.buttonPrimary}`}>
|
||||
Confirm
|
||||
<button onClick={onClose} class={clsx(styles.button, styles.buttonSecondary)}>
|
||||
Done
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -67,6 +67,10 @@ namespace Prompt {
|
|||
|
||||
parts.push(`# ${currentStory.title}`);
|
||||
|
||||
if (currentStory.lore) {
|
||||
parts.push('## Lore\n' + currentStory.lore);
|
||||
}
|
||||
|
||||
const charactersSection = formatCharactersMarkdown(state);
|
||||
if (charactersSection) {
|
||||
parts.push(charactersSection);
|
||||
|
|
|
|||
Loading…
Reference in New Issue