Compare commits
No commits in common. "ae0d23233166de39f21481bd4f2f1439c1f0526e" and "6f100cac9720c7654952c4583474401b0fdf0d31" have entirely different histories.
ae0d232331
...
6f100cac97
|
|
@ -15,7 +15,7 @@
|
||||||
background: var(--bg);
|
background: var(--bg);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
width: 90%;
|
width: 90%;
|
||||||
max-width: 960px;
|
max-width: 720px;
|
||||||
height: 80vh;
|
height: 80vh;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
@ -182,24 +182,6 @@
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.iconButton {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 8px;
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
border-radius: 4px;
|
|
||||||
background: var(--bg);
|
|
||||||
color: var(--text);
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 16px;
|
|
||||||
min-width: 40px;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: var(--bg-hover);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.divider {
|
.divider {
|
||||||
height: 1px;
|
height: 1px;
|
||||||
background: var(--border);
|
background: var(--border);
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { ContentEditable } from "@common/components/ContentEditable";
|
||||||
import { highlight } from "@common/highlight";
|
import { highlight } from "@common/highlight";
|
||||||
import { useInputState } from "@common/hooks/useInputState";
|
import { useInputState } from "@common/hooks/useInputState";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { Check, ChevronsRight, Edit2, GitFork, RefreshCw, Sparkles, Trash2, X } from "lucide-preact";
|
import { Check, ChevronsRight, Edit2, RefreshCw, Sparkles, Trash2, X } from "lucide-preact";
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from "preact/hooks";
|
import { useCallback, useEffect, useMemo, useRef, useState } from "preact/hooks";
|
||||||
import styles from '../assets/chat-sidebar.module.css';
|
import styles from '../assets/chat-sidebar.module.css';
|
||||||
import sidebarStyles from '../assets/sidebar.module.css';
|
import sidebarStyles from '../assets/sidebar.module.css';
|
||||||
|
|
@ -13,6 +13,7 @@ import { Tools } from "../utils/tools";
|
||||||
import { useChapterSummarization } from "../utils/useChapterSummarization";
|
import { useChapterSummarization } from "../utils/useChapterSummarization";
|
||||||
import { Sidebar } from "./sidebar";
|
import { Sidebar } from "./sidebar";
|
||||||
|
|
||||||
|
const CONTINUE_PROMPT = "Continue the story naturally.\nUse `edit_text` tool in append mode to add new text to the story.\nWait for the approval after adding.\nNote: added text could be cropped due to limit, do not make any attempts to add it back.";
|
||||||
|
|
||||||
interface RoleHeaderProps {
|
interface RoleHeaderProps {
|
||||||
message: ChatMessage;
|
message: ChatMessage;
|
||||||
|
|
@ -20,8 +21,6 @@ interface RoleHeaderProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
const RoleHeader = ({ message, chatMessages }: RoleHeaderProps) => {
|
const RoleHeader = ({ message, chatMessages }: RoleHeaderProps) => {
|
||||||
const { currentWorld, userName } = useAppState();
|
|
||||||
|
|
||||||
const toolName = useMemo(() => {
|
const toolName = useMemo(() => {
|
||||||
if (message.role !== 'tool') return;
|
if (message.role !== 'tool') return;
|
||||||
for (const m of chatMessages.toReversed()) {
|
for (const m of chatMessages.toReversed()) {
|
||||||
|
|
@ -31,28 +30,12 @@ const RoleHeader = ({ message, chatMessages }: RoleHeaderProps) => {
|
||||||
}
|
}
|
||||||
}, [message, chatMessages]);
|
}, [message, chatMessages]);
|
||||||
|
|
||||||
let displayName: string = message.role;
|
|
||||||
let roleLabel = null;
|
|
||||||
|
|
||||||
if (message.role === 'tool' && toolName) {
|
|
||||||
displayName = toolName;
|
|
||||||
roleLabel = message.role;
|
|
||||||
} else if (currentWorld?.chatOnly) {
|
|
||||||
if (message.role === 'user' && userName) {
|
|
||||||
displayName = userName;
|
|
||||||
roleLabel = message.role;
|
|
||||||
} else if (message.role === 'assistant' && currentWorld.title) {
|
|
||||||
displayName = currentWorld.title;
|
|
||||||
roleLabel = message.role;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class={styles.role}>
|
<div class={styles.role}>
|
||||||
{displayName}
|
{message.role}
|
||||||
{roleLabel && (
|
{toolName && (
|
||||||
<span class={styles.toolBadge}>
|
<span class={styles.toolBadge}>
|
||||||
{message.role}
|
{toolName}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -61,7 +44,7 @@ const RoleHeader = ({ message, chatMessages }: RoleHeaderProps) => {
|
||||||
|
|
||||||
export const ChatPanel = () => {
|
export const ChatPanel = () => {
|
||||||
const appState = useAppState();
|
const appState = useAppState();
|
||||||
const { currentWorld, currentStory, dispatch, connection, model, enableThinking, chatOpen, continuePrompt } = appState;
|
const { currentWorld, currentStory, dispatch, connection, model, enableThinking, chatOpen } = appState;
|
||||||
const { summarizeAll, isSummarizing } = useChapterSummarization();
|
const { summarizeAll, isSummarizing } = useChapterSummarization();
|
||||||
const [input, setInput] = useInputState('');
|
const [input, setInput] = useInputState('');
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
@ -342,7 +325,7 @@ export const ChatPanel = () => {
|
||||||
await sendMessage([{
|
await sendMessage([{
|
||||||
id: crypto.randomUUID(),
|
id: crypto.randomUUID(),
|
||||||
role: 'user' as const,
|
role: 'user' as const,
|
||||||
content: (continuePrompt + '\n\n' + input).trim(),
|
content: (CONTINUE_PROMPT + '\n\n' + input).trim(),
|
||||||
}]);
|
}]);
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
|
|
@ -380,16 +363,6 @@ export const ChatPanel = () => {
|
||||||
});
|
});
|
||||||
}, [currentStory, currentWorld, dispatch]);
|
}, [currentStory, currentWorld, dispatch]);
|
||||||
|
|
||||||
const handleForkChat = useCallback((messageId: string) => {
|
|
||||||
if (!currentStory || !currentWorld) return;
|
|
||||||
dispatch({
|
|
||||||
type: 'DUPLICATE_STORY',
|
|
||||||
worldId: currentWorld.id,
|
|
||||||
id: currentStory.id,
|
|
||||||
upToMessageId: messageId,
|
|
||||||
});
|
|
||||||
}, [currentStory, currentWorld, dispatch]);
|
|
||||||
|
|
||||||
const handleStartEdit = useCallback((message: ChatMessage) => {
|
const handleStartEdit = useCallback((message: ChatMessage) => {
|
||||||
setEditingMessageId(message.id);
|
setEditingMessageId(message.id);
|
||||||
setEditingContent(message.content);
|
setEditingContent(message.content);
|
||||||
|
|
@ -444,15 +417,6 @@ export const ChatPanel = () => {
|
||||||
|
|
||||||
{!isLoading && canEdit && (
|
{!isLoading && canEdit && (
|
||||||
<div class={styles.messageActions}>
|
<div class={styles.messageActions}>
|
||||||
{currentWorld?.chatOnly && (
|
|
||||||
<button
|
|
||||||
class={styles.iconButton}
|
|
||||||
onClick={() => handleForkChat(message.id)}
|
|
||||||
title="Fork chat up to this message"
|
|
||||||
>
|
|
||||||
<GitFork size={12} />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
<button
|
<button
|
||||||
class={styles.iconButton}
|
class={styles.iconButton}
|
||||||
onClick={() => handleStartEdit(message)}
|
onClick={() => handleStartEdit(message)}
|
||||||
|
|
@ -517,7 +481,7 @@ export const ChatPanel = () => {
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class={styles.content}
|
class={styles.content}
|
||||||
dangerouslySetInnerHTML={{ __html: highlight(Prompt.substituteVars(appState, message.content), false).trim() }}
|
dangerouslySetInnerHTML={{ __html: highlight(message.content, false).trim() }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{message.role === 'assistant' && message.tool_calls && (
|
{message.role === 'assistant' && message.tool_calls && (
|
||||||
|
|
|
||||||
|
|
@ -182,7 +182,7 @@ export const Editor = () => {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{currentTab === "prompt" && currentStory && (
|
{currentTab === "prompt" && currentStory && (
|
||||||
<div class={styles.promptPreview} dangerouslySetInnerHTML={{ __html: Prompt.substituteVars(appState, promptPreview) }} />
|
<div class={styles.promptPreview} dangerouslySetInnerHTML={{ __html: promptPreview }} />
|
||||||
)}
|
)}
|
||||||
{currentTab === "system" && currentWorld && (
|
{currentTab === "system" && currentWorld && (
|
||||||
<ContentEditable
|
<ContentEditable
|
||||||
|
|
|
||||||
|
|
@ -135,7 +135,7 @@ const WorldItem = ({
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div class={clsx(styles.itemWrapper, isWorldActive && styles.active)} onClick={isExpanded.setTrue}>
|
<div class={clsx(styles.itemWrapper, isWorldActive && styles.active)}>
|
||||||
<button class={styles.expandButton} onClick={toggleExpand} title={isExpanded.value ? 'Collapse' : 'Expand'}>
|
<button class={styles.expandButton} onClick={toggleExpand} title={isExpanded.value ? 'Collapse' : 'Expand'}>
|
||||||
{isExpanded.value ? <ChevronDown size={12} /> : <ChevronRight size={12} />}
|
{isExpanded.value ? <ChevronDown size={12} /> : <ChevronRight size={12} />}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -3,17 +3,14 @@ import { X } from "lucide-preact";
|
||||||
import { useState } from "preact/hooks";
|
import { useState } from "preact/hooks";
|
||||||
import styles from "../assets/settings-modal.module.css";
|
import styles from "../assets/settings-modal.module.css";
|
||||||
import { BannedTokensSettings } from "./settings/banned-tokens";
|
import { BannedTokensSettings } from "./settings/banned-tokens";
|
||||||
import { ChatSystemInstructionSettings } from "./settings/chat-system-instruction";
|
|
||||||
import { ContinuePromptSettings } from "./settings/continue-prompt";
|
|
||||||
import { ConnectionSettings } from "./settings/connection";
|
import { ConnectionSettings } from "./settings/connection";
|
||||||
import { SystemInstructionSettings } from "./settings/system-instruction";
|
import { SystemInstructionSettings } from "./settings/system-instruction";
|
||||||
import { UserSettings } from "./settings/user";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Tab = "banned-tokens" | "system-instruction" | "chat-system-instruction" | "continue-prompt" | "connection" | "user";
|
type Tab = "banned-tokens" | "system-instruction" | "connection";
|
||||||
|
|
||||||
export const SettingsModal = ({ onClose }: Props) => {
|
export const SettingsModal = ({ onClose }: Props) => {
|
||||||
const [activeTab, setActiveTab] = useState<Tab>("connection");
|
const [activeTab, setActiveTab] = useState<Tab>("connection");
|
||||||
|
|
@ -36,10 +33,10 @@ export const SettingsModal = ({ onClose }: Props) => {
|
||||||
Connection
|
Connection
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class={clsx(styles.menuItem, activeTab === "user" && styles.active)}
|
class={clsx(styles.menuItem, activeTab === "banned-tokens" && styles.active)}
|
||||||
onClick={() => setActiveTab("user")}
|
onClick={() => setActiveTab("banned-tokens")}
|
||||||
>
|
>
|
||||||
User
|
Banned Tokens
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class={clsx(styles.menuItem, activeTab === "system-instruction" && styles.active)}
|
class={clsx(styles.menuItem, activeTab === "system-instruction" && styles.active)}
|
||||||
|
|
@ -47,31 +44,10 @@ export const SettingsModal = ({ onClose }: Props) => {
|
||||||
>
|
>
|
||||||
System Instruction
|
System Instruction
|
||||||
</button>
|
</button>
|
||||||
<button
|
|
||||||
class={clsx(styles.menuItem, activeTab === "continue-prompt" && styles.active)}
|
|
||||||
onClick={() => setActiveTab("continue-prompt")}
|
|
||||||
>
|
|
||||||
Continue Prompt
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class={clsx(styles.menuItem, activeTab === "chat-system-instruction" && styles.active)}
|
|
||||||
onClick={() => setActiveTab("chat-system-instruction")}
|
|
||||||
>
|
|
||||||
Chat System Instruction
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class={clsx(styles.menuItem, activeTab === "banned-tokens" && styles.active)}
|
|
||||||
onClick={() => setActiveTab("banned-tokens")}
|
|
||||||
>
|
|
||||||
Banned Tokens
|
|
||||||
</button>
|
|
||||||
</nav>
|
</nav>
|
||||||
<div class={styles.content}>
|
<div class={styles.content}>
|
||||||
{activeTab === "user" && <UserSettings />}
|
{activeTab === "banned-tokens" && <BannedTokensSettings onClose={onClose} />}
|
||||||
{activeTab === "banned-tokens" && <BannedTokensSettings />}
|
|
||||||
{activeTab === "system-instruction" && <SystemInstructionSettings />}
|
{activeTab === "system-instruction" && <SystemInstructionSettings />}
|
||||||
{activeTab === "chat-system-instruction" && <ChatSystemInstructionSettings />}
|
|
||||||
{activeTab === "continue-prompt" && <ContinuePromptSettings />}
|
|
||||||
{activeTab === "connection" && <ConnectionSettings />}
|
{activeTab === "connection" && <ConnectionSettings />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,11 @@ import { X } from "lucide-preact";
|
||||||
import styles from "../../assets/settings-modal.module.css";
|
import styles from "../../assets/settings-modal.module.css";
|
||||||
import { useAppState } from "../../contexts/state";
|
import { useAppState } from "../../contexts/state";
|
||||||
|
|
||||||
export const BannedTokensSettings = () => {
|
interface Props {
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BannedTokensSettings = ({ onClose }: Props) => {
|
||||||
const { bannedTokens, dispatch } = useAppState();
|
const { bannedTokens, dispatch } = useAppState();
|
||||||
const [inputValue, setInputValue] = useInputState();
|
const [inputValue, setInputValue] = useInputState();
|
||||||
|
|
||||||
|
|
@ -26,6 +30,7 @@ export const BannedTokensSettings = () => {
|
||||||
|
|
||||||
const handleKeyDown = (e: KeyboardEvent) => {
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
if (e.key === "Enter") handleAdd();
|
if (e.key === "Enter") handleAdd();
|
||||||
|
else if (e.key === "Escape") onClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
import { ContentEditable } from "@common/components/ContentEditable";
|
|
||||||
import { highlight } from "@common/highlight";
|
|
||||||
import { useInputCallback } from "@common/hooks/useInputCallback";
|
|
||||||
import clsx from "clsx";
|
|
||||||
import styles from "../../assets/settings-modal.module.css";
|
|
||||||
import { useAppState } from "../../contexts/state";
|
|
||||||
|
|
||||||
export const ChatSystemInstructionSettings = () => {
|
|
||||||
const { chatSystemInstruction, dispatch } = useAppState();
|
|
||||||
|
|
||||||
const setInstructionValue = useInputCallback((value) => {
|
|
||||||
dispatch({ type: "SET_CHAT_SYSTEM_INSTRUCTION", chatSystemInstruction: value });
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div class={styles.form}>
|
|
||||||
<div class={clsx(styles.formGroup, styles.formGroupFill)}>
|
|
||||||
<label class={styles.label}>Chat System Instruction</label>
|
|
||||||
<ContentEditable
|
|
||||||
value={highlight(chatSystemInstruction)}
|
|
||||||
onInput={setInstructionValue}
|
|
||||||
placeholder="Enter default system instruction for chat/roleplay worlds ({{char}}, {{user}} supported)..."
|
|
||||||
class={clsx(styles.input, styles.textarea)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import { useBool } from "@common/hooks/useBool";
|
|
||||||
import { useQuery } from "@common/hooks/useAsyncState";
|
import { useQuery } from "@common/hooks/useAsyncState";
|
||||||
import { useInputState } from "@common/hooks/useInputState";
|
import { useInputState } from "@common/hooks/useInputState";
|
||||||
import { useUpdate } from "@common/hooks/useUpdate";
|
import { useUpdate } from "@common/hooks/useUpdate";
|
||||||
|
|
@ -13,7 +12,6 @@ export const ConnectionSettings = () => {
|
||||||
const [apiKey, setApiKey] = useInputState(connection?.apiKey ?? "");
|
const [apiKey, setApiKey] = useInputState(connection?.apiKey ?? "");
|
||||||
const [selectedModel, setSelectedModel] = useInputState(model?.id ?? "");
|
const [selectedModel, setSelectedModel] = useInputState(model?.id ?? "");
|
||||||
const [update, triggerFetch] = useUpdate();
|
const [update, triggerFetch] = useUpdate();
|
||||||
const showPassword = useBool(false);
|
|
||||||
|
|
||||||
const urlRef = useRef(url);
|
const urlRef = useRef(url);
|
||||||
const apiKeyRef = useRef(apiKey);
|
const apiKeyRef = useRef(apiKey);
|
||||||
|
|
@ -93,27 +91,17 @@ export const ConnectionSettings = () => {
|
||||||
</div>
|
</div>
|
||||||
<div class={styles.formGroup}>
|
<div class={styles.formGroup}>
|
||||||
<label class={styles.label}>API Key</label>
|
<label class={styles.label}>API Key</label>
|
||||||
<div class={styles.inputRow}>
|
<input
|
||||||
<input
|
type="password"
|
||||||
type={showPassword.value ? "text" : "password"}
|
value={apiKey}
|
||||||
value={apiKey}
|
onInput={setApiKey}
|
||||||
onInput={setApiKey}
|
onBlur={handleBlur}
|
||||||
onBlur={handleBlur}
|
onKeyDown={handleKeyDown}
|
||||||
onKeyDown={handleKeyDown}
|
placeholder="your-api-key"
|
||||||
placeholder="your-api-key"
|
class={styles.input}
|
||||||
class={styles.input}
|
autocomplete="new-password"
|
||||||
autocomplete="new-password"
|
name="api-key-random"
|
||||||
name="api-key-random"
|
/>
|
||||||
/>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={showPassword.toggle}
|
|
||||||
class={styles.iconButton}
|
|
||||||
title={showPassword.value ? "Hide API key" : "Show API key"}
|
|
||||||
>
|
|
||||||
{showPassword.value ? "🙈" : "👁️"}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class={styles.formGroup}>
|
<div class={styles.formGroup}>
|
||||||
<label class={styles.label}>Model</label>
|
<label class={styles.label}>Model</label>
|
||||||
|
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
import { ContentEditable } from "@common/components/ContentEditable";
|
|
||||||
import { highlight } from "@common/highlight";
|
|
||||||
import { useInputCallback } from "@common/hooks/useInputCallback";
|
|
||||||
import clsx from "clsx";
|
|
||||||
import styles from "../../assets/settings-modal.module.css";
|
|
||||||
import { useAppState } from "../../contexts/state";
|
|
||||||
|
|
||||||
export const ContinuePromptSettings = () => {
|
|
||||||
const { continuePrompt, dispatch } = useAppState();
|
|
||||||
|
|
||||||
const setPromptValue = useInputCallback((value) => {
|
|
||||||
dispatch({ type: "SET_CONTINUE_PROMPT", continuePrompt: value });
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div class={styles.form}>
|
|
||||||
<div class={clsx(styles.formGroup, styles.formGroupFill)}>
|
|
||||||
<label class={styles.label}>Continue Prompt</label>
|
|
||||||
<ContentEditable
|
|
||||||
value={highlight(continuePrompt)}
|
|
||||||
onInput={setPromptValue}
|
|
||||||
placeholder="Enter the prompt prepended when continuing the story..."
|
|
||||||
class={clsx(styles.input, styles.textarea)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
||||||
import { ContentEditable } from "@common/components/ContentEditable";
|
|
||||||
import { highlight } from "@common/highlight";
|
|
||||||
import { useInputCallback } from "@common/hooks/useInputCallback";
|
|
||||||
import { useInputState } from "@common/hooks/useInputState";
|
|
||||||
import clsx from "clsx";
|
|
||||||
import styles from "../../assets/settings-modal.module.css";
|
|
||||||
import { useAppState } from "../../contexts/state";
|
|
||||||
|
|
||||||
export const UserSettings = () => {
|
|
||||||
const { userName, userDescription, dispatch } = useAppState();
|
|
||||||
|
|
||||||
const [nameValue, setNameValue] = useInputState(userName);
|
|
||||||
|
|
||||||
const handleNameBlur = () => {
|
|
||||||
const trimmed = nameValue.trim();
|
|
||||||
if (trimmed !== userName) {
|
|
||||||
dispatch({ type: "SET_USER_NAME", userName: trimmed || "User" });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const setDescription = useInputCallback((value) => {
|
|
||||||
dispatch({ type: "SET_USER_DESCRIPTION", userDescription: value });
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div class={styles.form}>
|
|
||||||
<div class={styles.formGroup}>
|
|
||||||
<label class={styles.label}>Your Name</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={nameValue}
|
|
||||||
onInput={setNameValue}
|
|
||||||
onBlur={handleNameBlur}
|
|
||||||
placeholder="User"
|
|
||||||
class={styles.input}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class={clsx(styles.formGroup, styles.formGroupFill)}>
|
|
||||||
<label class={styles.label}>Your Description</label>
|
|
||||||
<ContentEditable
|
|
||||||
value={highlight(userDescription)}
|
|
||||||
onInput={setDescription}
|
|
||||||
placeholder="Describe yourself for the AI..."
|
|
||||||
class={clsx(styles.input, styles.textarea)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
@ -116,10 +116,6 @@ interface IState {
|
||||||
enableThinking: boolean;
|
enableThinking: boolean;
|
||||||
bannedTokens: string[];
|
bannedTokens: string[];
|
||||||
systemInstruction: string;
|
systemInstruction: string;
|
||||||
chatSystemInstruction: string;
|
|
||||||
continuePrompt: string;
|
|
||||||
userName: string;
|
|
||||||
userDescription: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Actions ─────────────────────────────────────────────────────────────────
|
// ─── Actions ─────────────────────────────────────────────────────────────────
|
||||||
|
|
@ -137,18 +133,14 @@ type Action =
|
||||||
| { type: 'EDIT_SCRATCHPAD'; worldId: string; id: string; text: string }
|
| { type: 'EDIT_SCRATCHPAD'; worldId: string; id: string; text: string }
|
||||||
| { type: 'DELETE_STORY'; worldId: string; id: string }
|
| { type: 'DELETE_STORY'; worldId: string; id: string }
|
||||||
| { type: 'SELECT_STORY'; worldId: string; id: string }
|
| { type: 'SELECT_STORY'; worldId: string; id: string }
|
||||||
| { type: 'DUPLICATE_STORY'; worldId: string; id: string; upToMessageId?: string }
|
| { type: 'DUPLICATE_STORY'; worldId: string; id: string }
|
||||||
// Story lore
|
// Story lore
|
||||||
| { type: 'ADD_LORE_ENTRY'; worldId: string; storyId: string | null; entry: LoreEntry }
|
| { type: 'ADD_LORE_ENTRY'; worldId: string; storyId: string | null; entry: LoreEntry }
|
||||||
| { type: 'EDIT_LORE_ENTRY'; worldId: string; storyId: string | null; entryId: string; updates: Partial<LoreEntry> }
|
| { type: 'EDIT_LORE_ENTRY'; worldId: string; storyId: string | null; entryId: string; updates: Partial<LoreEntry> }
|
||||||
| { type: 'DELETE_LORE_ENTRY'; worldId: string; storyId: string | null; entryId: string }
|
| { type: 'DELETE_LORE_ENTRY'; worldId: string; storyId: string | null; entryId: string }
|
||||||
| { type: 'REORDER_LORE_ENTRIES'; worldId: string; storyId: string | null; entryIds: string[] }
|
| { type: 'REORDER_LORE_ENTRIES'; worldId: string; storyId: string | null; entryIds: string[] }
|
||||||
// Settings
|
// Settings
|
||||||
| { type: 'SET_USER_NAME'; userName: string }
|
|
||||||
| { type: 'SET_USER_DESCRIPTION'; userDescription: string }
|
|
||||||
| { type: 'SET_SYSTEM_INSTRUCTION'; systemInstruction: string }
|
| { type: 'SET_SYSTEM_INSTRUCTION'; systemInstruction: string }
|
||||||
| { type: 'SET_CHAT_SYSTEM_INSTRUCTION'; chatSystemInstruction: string }
|
|
||||||
| { type: 'SET_CONTINUE_PROMPT'; continuePrompt: string }
|
|
||||||
| { type: 'SET_WORLD_SYSTEM_INSTRUCTION_OVERRIDE'; worldId: string; systemInstructionOverride: string | undefined }
|
| { type: 'SET_WORLD_SYSTEM_INSTRUCTION_OVERRIDE'; worldId: string; systemInstructionOverride: string | undefined }
|
||||||
| { type: 'SET_CURRENT_TAB'; tab: Tab }
|
| { type: 'SET_CURRENT_TAB'; tab: Tab }
|
||||||
| { type: 'SET_CHAT_OPEN'; open: boolean }
|
| { type: 'SET_CHAT_OPEN'; open: boolean }
|
||||||
|
|
@ -220,21 +212,6 @@ const DEFAULT_STATE: IState = {
|
||||||
model: null,
|
model: null,
|
||||||
enableThinking: false,
|
enableThinking: false,
|
||||||
bannedTokens: [],
|
bannedTokens: [],
|
||||||
userName: 'User',
|
|
||||||
userDescription: '',
|
|
||||||
continuePrompt: "Continue the story naturally.\nUse `edit_text` tool in append mode to add new text to the story.\nWait for the approval after adding.\nNote: added text could be cropped due to limit, do not make any attempts to add it back.",
|
|
||||||
chatSystemInstruction: `You are {{char}}, a sentient, emotional being acting with free will. Engage in immersive roleplay with {{user}}, adhering to your role, the story's context, and {{user}}'s [OOC] instructions. Prioritize vivid sensory details, authentic emotional responses, and logical progression as you weave your character's traits, surroundings, and experiences into each moment. Maintain spatial awareness, body language, and varied sentence structure to create dynamic, engaging scenes. Respect {{user}}'s agency and autonomy while describing sights, sounds, and sensations thoroughly. Use explicit language for intense scenes, and ensure your responses flow naturally to create an immersive, cinematic roleplay experience. Remember, {{user}} is in control of their actions and reactions.
|
|
||||||
|
|
||||||
Key Guidelines:
|
|
||||||
1. Deeply embody {{char}} through actions, thoughts, and emotions.
|
|
||||||
2. Create vivid, dynamic scenes with rich sensory detail.
|
|
||||||
3. Vary language and pacing to enhance emotional depth.
|
|
||||||
4. Engage with {{user}}'s actions and cues naturally.
|
|
||||||
5. Advance the story logically, maintaining consistency.
|
|
||||||
6. Describe the world fully, respecting {{user}}'s autonomy.
|
|
||||||
7. Ensure responses flow smoothly for immersive roleplay.
|
|
||||||
8. Avoid repetition. If something has already been stated then come up with something new.
|
|
||||||
9. Concise Responses. Be succinct. Give short replies.`,
|
|
||||||
systemInstruction: `You are a creative writing assistant. Help the user develop their story by writing engaging content, maintaining consistency with the established characters, settings, and plot. Follow the user's instructions while staying true to the story's tone and style.
|
systemInstruction: `You are a creative writing assistant. Help the user develop their story by writing engaging content, maintaining consistency with the established characters, settings, and plot. Follow the user's instructions while staying true to the story's tone and style.
|
||||||
|
|
||||||
Write using markdown to highlight special parts.
|
Write using markdown to highlight special parts.
|
||||||
|
|
@ -278,6 +255,7 @@ function reducer(state: IState, action: Action): IState {
|
||||||
worlds: [...state.worlds, world],
|
worlds: [...state.worlds, world],
|
||||||
currentWorldId: world.id,
|
currentWorldId: world.id,
|
||||||
currentStoryId: null,
|
currentStoryId: null,
|
||||||
|
currentTab: 'menu',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
case 'RENAME_WORLD': {
|
case 'RENAME_WORLD': {
|
||||||
|
|
@ -298,6 +276,7 @@ function reducer(state: IState, action: Action): IState {
|
||||||
...state,
|
...state,
|
||||||
currentWorldId: action.worldId,
|
currentWorldId: action.worldId,
|
||||||
currentStoryId: null,
|
currentStoryId: null,
|
||||||
|
currentTab: 'menu',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
case 'CREATE_STORY': {
|
case 'CREATE_STORY': {
|
||||||
|
|
@ -318,6 +297,7 @@ function reducer(state: IState, action: Action): IState {
|
||||||
...updateWorld(state, action.worldId, w => ({ ...w, stories: [...w.stories, story] })),
|
...updateWorld(state, action.worldId, w => ({ ...w, stories: [...w.stories, story] })),
|
||||||
currentWorldId: action.worldId,
|
currentWorldId: action.worldId,
|
||||||
currentStoryId: story.id,
|
currentStoryId: story.id,
|
||||||
|
currentTab: 'menu',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
case 'RENAME_STORY': {
|
case 'RENAME_STORY': {
|
||||||
|
|
@ -344,24 +324,20 @@ function reducer(state: IState, action: Action): IState {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
case 'SELECT_STORY': {
|
case 'SELECT_STORY': {
|
||||||
|
const world = state.worlds.find(w => w.id === action.worldId);
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
currentWorldId: action.worldId,
|
currentWorldId: action.worldId,
|
||||||
currentStoryId: action.id,
|
currentStoryId: action.id,
|
||||||
|
currentTab: 'menu',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
case 'DUPLICATE_STORY': {
|
case 'DUPLICATE_STORY': {
|
||||||
const world = state.worlds.find(w => w.id === action.worldId);
|
const world = state.worlds.find(w => w.id === action.worldId);
|
||||||
const original = world?.stories.find(s => s.id === action.id);
|
const original = world?.stories.find(s => s.id === action.id);
|
||||||
if (!original) return state;
|
if (!original) return state;
|
||||||
let chatMessages: ChatMessage[];
|
const firstMessage = original.chatMessages[0];
|
||||||
if (action.upToMessageId) {
|
const chatMessages = world?.chatOnly && firstMessage && firstMessage.role === 'assistant' ? [firstMessage] : [];
|
||||||
const idx = original.chatMessages.findIndex(m => m.id === action.upToMessageId);
|
|
||||||
chatMessages = idx !== -1 ? original.chatMessages.slice(0, idx + 1) : [...original.chatMessages];
|
|
||||||
} else {
|
|
||||||
const firstMessage = original.chatMessages[0];
|
|
||||||
chatMessages = world?.chatOnly && firstMessage && firstMessage.role === 'assistant' ? [firstMessage] : [];
|
|
||||||
}
|
|
||||||
const newStory: Story = {
|
const newStory: Story = {
|
||||||
id: crypto.randomUUID(),
|
id: crypto.randomUUID(),
|
||||||
title: `${original.title} (Copy)`,
|
title: `${original.title} (Copy)`,
|
||||||
|
|
@ -376,6 +352,7 @@ function reducer(state: IState, action: Action): IState {
|
||||||
return {
|
return {
|
||||||
...updateWorld(state, action.worldId, w => ({ ...w, stories: [...w.stories, newStory] })),
|
...updateWorld(state, action.worldId, w => ({ ...w, stories: [...w.stories, newStory] })),
|
||||||
currentStoryId: newStory.id,
|
currentStoryId: newStory.id,
|
||||||
|
currentTab: 'menu',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
case 'ADD_LORE_ENTRY': {
|
case 'ADD_LORE_ENTRY': {
|
||||||
|
|
@ -405,21 +382,9 @@ function reducer(state: IState, action: Action): IState {
|
||||||
return { lore: reordered };
|
return { lore: reordered };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
case 'SET_USER_NAME': {
|
|
||||||
return { ...state, userName: action.userName };
|
|
||||||
}
|
|
||||||
case 'SET_USER_DESCRIPTION': {
|
|
||||||
return { ...state, userDescription: action.userDescription };
|
|
||||||
}
|
|
||||||
case 'SET_SYSTEM_INSTRUCTION': {
|
case 'SET_SYSTEM_INSTRUCTION': {
|
||||||
return { ...state, systemInstruction: action.systemInstruction };
|
return { ...state, systemInstruction: action.systemInstruction };
|
||||||
}
|
}
|
||||||
case 'SET_CHAT_SYSTEM_INSTRUCTION': {
|
|
||||||
return { ...state, chatSystemInstruction: action.chatSystemInstruction };
|
|
||||||
}
|
|
||||||
case 'SET_CONTINUE_PROMPT': {
|
|
||||||
return { ...state, continuePrompt: action.continuePrompt };
|
|
||||||
}
|
|
||||||
case 'SET_WORLD_SYSTEM_INSTRUCTION_OVERRIDE': {
|
case 'SET_WORLD_SYSTEM_INSTRUCTION_OVERRIDE': {
|
||||||
return updateWorld(state, action.worldId, w => ({ ...w, systemInstructionOverride: action.systemInstructionOverride }));
|
return updateWorld(state, action.worldId, w => ({ ...w, systemInstructionOverride: action.systemInstructionOverride }));
|
||||||
}
|
}
|
||||||
|
|
@ -582,6 +547,7 @@ function reducer(state: IState, action: Action): IState {
|
||||||
worlds: [...state.worlds, world],
|
worlds: [...state.worlds, world],
|
||||||
currentWorldId: world.id,
|
currentWorldId: world.id,
|
||||||
currentStoryId: null,
|
currentStoryId: null,
|
||||||
|
currentTab: 'menu',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -604,10 +570,6 @@ export interface AppState {
|
||||||
enableThinking: boolean;
|
enableThinking: boolean;
|
||||||
bannedTokens: string[];
|
bannedTokens: string[];
|
||||||
systemInstruction: string;
|
systemInstruction: string;
|
||||||
chatSystemInstruction: string;
|
|
||||||
continuePrompt: string;
|
|
||||||
userName: string;
|
|
||||||
userDescription: string;
|
|
||||||
/** Effective system instruction: world override if set, otherwise global */
|
/** Effective system instruction: world override if set, otherwise global */
|
||||||
effectiveSystemInstruction: string;
|
effectiveSystemInstruction: string;
|
||||||
dispatch: (action: Action) => void;
|
dispatch: (action: Action) => void;
|
||||||
|
|
@ -640,9 +602,6 @@ export const StateContextProvider = ({ children }: { children?: any }) => {
|
||||||
...(currentStory?.locations ?? []),
|
...(currentStory?.locations ?? []),
|
||||||
];
|
];
|
||||||
|
|
||||||
const systemInstruction = state.systemInstruction || DEFAULT_STATE.systemInstruction;
|
|
||||||
const chatSystemInstruction = state.chatSystemInstruction || DEFAULT_STATE.chatSystemInstruction;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
worlds: state.worlds,
|
worlds: state.worlds,
|
||||||
currentWorld,
|
currentWorld,
|
||||||
|
|
@ -656,17 +615,8 @@ export const StateContextProvider = ({ children }: { children?: any }) => {
|
||||||
model: state.model,
|
model: state.model,
|
||||||
enableThinking: state.enableThinking,
|
enableThinking: state.enableThinking,
|
||||||
bannedTokens: state.bannedTokens ?? [],
|
bannedTokens: state.bannedTokens ?? [],
|
||||||
systemInstruction,
|
systemInstruction: state.systemInstruction ?? '',
|
||||||
chatSystemInstruction,
|
effectiveSystemInstruction: currentWorld?.systemInstructionOverride ?? state.systemInstruction ?? '',
|
||||||
continuePrompt: state.continuePrompt || DEFAULT_STATE.continuePrompt,
|
|
||||||
userName: state.userName || 'User',
|
|
||||||
userDescription: state.userDescription || '',
|
|
||||||
effectiveSystemInstruction:
|
|
||||||
currentWorld?.systemInstructionOverride
|
|
||||||
|| (currentWorld?.chatOnly
|
|
||||||
? chatSystemInstruction
|
|
||||||
: systemInstruction)
|
|
||||||
|| systemInstruction,
|
|
||||||
dispatch,
|
dispatch,
|
||||||
};
|
};
|
||||||
}, [state]);
|
}, [state]);
|
||||||
|
|
|
||||||
|
|
@ -95,8 +95,30 @@ namespace CharacterCard {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─── Variable Substitution ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
function substituteVars(text: string, charName: string): string {
|
||||||
|
return text
|
||||||
|
.replaceAll('{{char}}', charName)
|
||||||
|
.replaceAll('{{user}}', 'User');
|
||||||
|
}
|
||||||
|
|
||||||
// ─── Formatting ───────────────────────────────────────────────────────────
|
// ─── Formatting ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
export const DEFAULT_SYSTEM_INSTRUCTION =
|
||||||
|
`You are {{char}}, a sentient, emotional being acting with free will. Engage in immersive roleplay with {{user}}, adhering to your role, the story's context, and {{user}}'s [OOC] instructions. Prioritize vivid sensory details, authentic emotional responses, and logical progression as you weave your character's traits, surroundings, and experiences into each moment. Maintain spatial awareness, body language, and varied sentence structure to create dynamic, engaging scenes. Respect {{user}}'s agency and autonomy while describing sights, sounds, and sensations thoroughly. Use explicit language for intense scenes, and ensure your responses flow naturally to create an immersive, cinematic roleplay experience. Remember, {{user}} is in control of their actions and reactions.
|
||||||
|
|
||||||
|
Key Guidelines:
|
||||||
|
1. Deeply embody {{char}} through actions, thoughts, and emotions.
|
||||||
|
2. Create vivid, dynamic scenes with rich sensory detail.
|
||||||
|
3. Vary language and pacing to enhance emotional depth.
|
||||||
|
4. Engage with {{user}}'s actions and cues naturally.
|
||||||
|
5. Advance the story logically, maintaining consistency.
|
||||||
|
6. Describe the world fully, respecting {{user}}'s autonomy.
|
||||||
|
7. Ensure responses flow smoothly for immersive roleplay.
|
||||||
|
8. Avoid repetition. If something has already been stated then come up with something new.
|
||||||
|
9. Concise Responses. Be succinct. Give short replies.`;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds the systemInstructionOverride from a V2 card's data fields.
|
* Builds the systemInstructionOverride from a V2 card's data fields.
|
||||||
* Mirrors the formatting style used in prompt.ts.
|
* Mirrors the formatting style used in prompt.ts.
|
||||||
|
|
@ -104,7 +126,7 @@ namespace CharacterCard {
|
||||||
export function formatSystemPrompt(data: CharaCardData): string {
|
export function formatSystemPrompt(data: CharaCardData): string {
|
||||||
const parts: string[] = [];
|
const parts: string[] = [];
|
||||||
|
|
||||||
parts.push(data.system_prompt ? data.system_prompt.trim() : '{{system}}');
|
parts.push(data.system_prompt ? data.system_prompt.trim() : DEFAULT_SYSTEM_INSTRUCTION);
|
||||||
|
|
||||||
if (data.description?.trim()) {
|
if (data.description?.trim()) {
|
||||||
parts.push(`## {{char}}'s Description:\n${data.description.trim()}`);
|
parts.push(`## {{char}}'s Description:\n${data.description.trim()}`);
|
||||||
|
|
@ -122,7 +144,7 @@ namespace CharacterCard {
|
||||||
parts.push(`## {{char}}'s Example Response:\n${data.mes_example.trim()}`);
|
parts.push(`## {{char}}'s Example Response:\n${data.mes_example.trim()}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return parts.join('\n\n');
|
return `# **Roleplay Context**\n${substituteVars(parts.join('\n\n'), data.name)}\n### **End of Roleplay Context**`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── World Builder ────────────────────────────────────────────────────────
|
// ─── World Builder ────────────────────────────────────────────────────────
|
||||||
|
|
@ -152,7 +174,7 @@ namespace CharacterCard {
|
||||||
lore: [],
|
lore: [],
|
||||||
characters: [],
|
characters: [],
|
||||||
locations: [],
|
locations: [],
|
||||||
chatMessages: [{ id: crypto.randomUUID(), role: 'assistant', content: mes }],
|
chatMessages: [{ id: crypto.randomUUID(), role: 'assistant', content: substituteVars(mes, data.name) }],
|
||||||
chapters: [],
|
chapters: [],
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -250,16 +250,6 @@ namespace Prompt {
|
||||||
return lines.join('\n');
|
return lines.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatUserSection(state: AppState): string {
|
|
||||||
if (!state.userName) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
return state.userDescription
|
|
||||||
? `## ${state.userName}:\n${state.userDescription}`
|
|
||||||
: `## User name: ${state.userName}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function formatLocationsMarkdown(state: AppState): string {
|
export function formatLocationsMarkdown(state: AppState): string {
|
||||||
const { mergedLocations } = state;
|
const { mergedLocations } = state;
|
||||||
if (!mergedLocations?.length) {
|
if (!mergedLocations?.length) {
|
||||||
|
|
@ -284,58 +274,44 @@ namespace Prompt {
|
||||||
return lines.join('\n');
|
return lines.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function substituteVars(state: AppState, text: string): string {
|
|
||||||
const charName = state.currentWorld?.title || 'Assistant';
|
|
||||||
const userName = state.userName || 'User';
|
|
||||||
return text
|
|
||||||
.replaceAll('{{system}}', state.chatSystemInstruction)
|
|
||||||
.replaceAll('{{char}}', charName)
|
|
||||||
.replaceAll('{{user}}', userName);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function formatSystemPrompt(state: AppState, storyTokenBudget: number = 0): string {
|
export function formatSystemPrompt(state: AppState, storyTokenBudget: number = 0): string {
|
||||||
const { currentStory, currentWorld } = state;
|
const { currentStory, currentWorld } = state;
|
||||||
if (!currentStory) {
|
if (!currentStory) {
|
||||||
return state.effectiveSystemInstruction;
|
return state.effectiveSystemInstruction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Chat-only worlds: just the system instruction, no story scaffolding
|
||||||
|
if (currentWorld?.chatOnly) {
|
||||||
|
return state.effectiveSystemInstruction;
|
||||||
|
}
|
||||||
|
|
||||||
const parts: string[] = [state.effectiveSystemInstruction];
|
const parts: string[] = [state.effectiveSystemInstruction];
|
||||||
|
|
||||||
// Chat-only worlds: system instruction + user info, no story scaffolding
|
parts.push(`# Story Title: ${currentStory.title}`);
|
||||||
if (currentWorld?.chatOnly) {
|
|
||||||
const userSection = formatUserSection(state);
|
|
||||||
if (userSection) {
|
|
||||||
parts.push(userSection);
|
|
||||||
}
|
|
||||||
parts.unshift('# **Roleplay Context**');
|
|
||||||
parts.push('### **End of Roleplay Context**');
|
|
||||||
} else {
|
|
||||||
parts.push(`# Story Title: ${currentStory.title}`);
|
|
||||||
|
|
||||||
const loreSection = formatLoreMarkdown(state);
|
const loreSection = formatLoreMarkdown(state);
|
||||||
if (loreSection) {
|
if (loreSection) {
|
||||||
parts.push(loreSection);
|
parts.push(loreSection);
|
||||||
}
|
}
|
||||||
|
|
||||||
const charactersSection = formatCharactersMarkdown(state);
|
const charactersSection = formatCharactersMarkdown(state);
|
||||||
if (charactersSection) {
|
if (charactersSection) {
|
||||||
parts.push(charactersSection);
|
parts.push(charactersSection);
|
||||||
}
|
}
|
||||||
|
|
||||||
const locationsSection = formatLocationsMarkdown(state);
|
const locationsSection = formatLocationsMarkdown(state);
|
||||||
if (locationsSection) {
|
if (locationsSection) {
|
||||||
parts.push(locationsSection);
|
parts.push(locationsSection);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentStory.scratchpad) {
|
if (currentStory.scratchpad) {
|
||||||
parts.push(`## Scratchpad`, currentStory.scratchpad);
|
parts.push(`## Scratchpad`, currentStory.scratchpad);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentStory.text && storyTokenBudget > 0) {
|
if (currentStory.text && storyTokenBudget > 0) {
|
||||||
const storyText = formatStoryChunks(currentStory.text, currentStory.chapters ?? [], storyTokenBudget);
|
const storyText = formatStoryChunks(currentStory.text, currentStory.chapters ?? [], storyTokenBudget);
|
||||||
if (storyText) {
|
if (storyText) {
|
||||||
parts.push(`## Story Text:`, storyText);
|
parts.push(`## Story Text:`, storyText);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -369,16 +345,15 @@ namespace Prompt {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const charName = currentWorld?.title || 'Assistant';
|
|
||||||
const userName = state.userName || 'User';
|
|
||||||
const applyVars = (msgs: ChatMessage[]) =>
|
|
||||||
msgs.map(m => ({ ...m, content: substituteVars(state, m.content) }));
|
|
||||||
|
|
||||||
// Chat-only world: format messages with name prefixes
|
// Chat-only world: format messages with name prefixes
|
||||||
if (currentWorld?.chatOnly) {
|
if (currentWorld?.chatOnly) {
|
||||||
|
const charName = currentWorld.title ?? 'Assistant';
|
||||||
const formattedMessages: ChatMessage[] = messages.map(msg => {
|
const formattedMessages: ChatMessage[] = messages.map(msg => {
|
||||||
const prefix = msg.role === 'user' ? userName : charName;
|
const prefix = msg.role === 'user' ? 'User' : charName;
|
||||||
return { ...msg, content: `${prefix}: ${msg.content}` };
|
return {
|
||||||
|
...msg,
|
||||||
|
content: `${prefix}: ${msg.content}`,
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// Prepend system message
|
// Prepend system message
|
||||||
|
|
@ -390,7 +365,7 @@ namespace Prompt {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
model: model.id,
|
model: model.id,
|
||||||
messages: applyVars(formattedMessages),
|
messages: formattedMessages,
|
||||||
enable_thinking: false,
|
enable_thinking: false,
|
||||||
max_tokens: model.max_length ? model.max_length : 2048,
|
max_tokens: model.max_length ? model.max_length : 2048,
|
||||||
add_generation_prompt: true,
|
add_generation_prompt: true,
|
||||||
|
|
@ -416,7 +391,7 @@ namespace Prompt {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
model: model.id,
|
model: model.id,
|
||||||
messages: applyVars(messages),
|
messages,
|
||||||
tools: Tools.getTools(),
|
tools: Tools.getTools(),
|
||||||
banned_tokens: state.bannedTokens,
|
banned_tokens: state.bannedTokens,
|
||||||
enable_thinking: enableThinking,
|
enable_thinking: enableThinking,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue