196 lines
7.8 KiB
TypeScript
196 lines
7.8 KiB
TypeScript
import { useCallback, useContext, useMemo } from "preact/hooks";
|
||
import { useBool } from "@common/hooks/useBool";
|
||
import { Modal } from "@common/components/modal/Modal";
|
||
import { useInputCallback } from "@common/hooks/useInputCallback";
|
||
|
||
import { DEFAULT_STORY, StateContext } from "../../contexts/state";
|
||
import { LLMContext } from "../../contexts/llm";
|
||
import { MiniChat } from "../minichat/minichat";
|
||
import { AutoTextarea } from "../autoTextarea";
|
||
import { Ace } from "../ace";
|
||
import { ConnectionEditor } from "./connectionEditor";
|
||
|
||
import styles from './header.module.css';
|
||
|
||
export const Header = () => {
|
||
const { contextLength, promptTokens, modelName, spentKudos } = useContext(LLMContext);
|
||
const {
|
||
messages,
|
||
connection,
|
||
systemPrompt,
|
||
lore,
|
||
userPrompt,
|
||
bannedWords,
|
||
summarizePrompt,
|
||
summaryEnabled,
|
||
totalSpentKudos,
|
||
stories,
|
||
currentStory,
|
||
setSystemPrompt,
|
||
setLore,
|
||
setUserPrompt,
|
||
addSwipe,
|
||
setBannedWords,
|
||
setInstruct,
|
||
setSummarizePrompt,
|
||
setSummaryEnabled,
|
||
setConnection,
|
||
setCurrentStory,
|
||
createStory,
|
||
deleteStory,
|
||
} = useContext(StateContext);
|
||
|
||
const connectionsOpen = useBool();
|
||
const loreOpen = useBool();
|
||
const promptsOpen = useBool();
|
||
const genparamsOpen = useBool();
|
||
const assistantOpen = useBool();
|
||
const isOnline = useMemo(() => contextLength > 0, [contextLength]);
|
||
|
||
const bannedWordsInput = useMemo(() => bannedWords.join('\n'), [bannedWords]);
|
||
|
||
const handleAssistantAddSwipe = useCallback((answer: string) => {
|
||
const index = messages.findLastIndex(m => m.role === 'assistant');
|
||
addSwipe(index, answer);
|
||
assistantOpen.setFalse();
|
||
}, [addSwipe, messages]);
|
||
|
||
const handleSetBannedWords = useCallback((e: Event) => {
|
||
if (e.target instanceof HTMLTextAreaElement) {
|
||
const words = e.target.value.split('\n');
|
||
setBannedWords(words);
|
||
}
|
||
}, [setBannedWords]);
|
||
|
||
const handleBlurBannedWords = useCallback((e: Event) => {
|
||
if (e.target instanceof HTMLTextAreaElement) {
|
||
const words = e.target.value.toLowerCase().split('\n').sort();
|
||
setBannedWords(words);
|
||
}
|
||
}, [setBannedWords]);
|
||
|
||
const handleSetSummaryEnabled = useCallback((e: Event) => {
|
||
if (e.target instanceof HTMLInputElement) {
|
||
setSummaryEnabled(e.target.checked);
|
||
}
|
||
}, [setSummaryEnabled]);
|
||
|
||
const handleChangeStory = useInputCallback((story) => {
|
||
if (story === '@new') {
|
||
const id = prompt('Story id');
|
||
if (id) {
|
||
createStory(id);
|
||
setCurrentStory(id);
|
||
}
|
||
} else {
|
||
setCurrentStory(story);
|
||
}
|
||
}, []);
|
||
|
||
const handleDeleteStory = useCallback(() => {
|
||
if (confirm(`Delete story "${currentStory}"?`)) {
|
||
deleteStory(currentStory);
|
||
}
|
||
}, [currentStory]);
|
||
|
||
return (
|
||
<div class={styles.header}>
|
||
<div class={styles.inputs}>
|
||
<div class={styles.buttons}>
|
||
<button class={`icon ${isOnline ? styles.online : styles.offline}`} onClick={connectionsOpen.setTrue} title='Connection settings'>
|
||
🔌
|
||
</button>
|
||
</div>
|
||
<div class={styles.info}>
|
||
<span>{modelName}</span>
|
||
<span>📃{promptTokens}/{contextLength}</span>
|
||
{connection.type === 'horde' ? <>
|
||
<span>💲{spentKudos}</span>
|
||
<span>💰{totalSpentKudos}</span>
|
||
</> : null}
|
||
</div>
|
||
</div>
|
||
<div class={styles.buttons}>
|
||
<button class='icon color' title='Edit lore' onClick={loreOpen.setTrue}>
|
||
🌍
|
||
</button>
|
||
<button class='icon color' title='Generation parameters' onClick={genparamsOpen.setTrue}>
|
||
⚙
|
||
</button>
|
||
<button class='icon color' title='Edit prompts' onClick={promptsOpen.setTrue}>
|
||
📃
|
||
</button>
|
||
</div>
|
||
<div class={styles.buttons}>
|
||
<button class='icon' onClick={assistantOpen.setTrue} title='Ask assistant'>
|
||
❓
|
||
</button>
|
||
</div>
|
||
<Modal open={connectionsOpen.value} onClose={connectionsOpen.setFalse}>
|
||
<h3 class={styles.modalTitle}>Connection settings</h3>
|
||
<ConnectionEditor connection={connection} setConnection={setConnection} />
|
||
</Modal>
|
||
<Modal open={loreOpen.value} onClose={loreOpen.setFalse} class={styles.lore}>
|
||
<h3 class={styles.modalTitle}>Lore Editor</h3>
|
||
<div class={styles.currentStory}>
|
||
<select value={currentStory} onChange={handleChangeStory} class={styles.storiesSelector}>
|
||
{Object.keys(stories).map((story) => (
|
||
<option key={story} value={story}>{story}</option>
|
||
))}
|
||
<option value='@new'>New Story...</option>
|
||
</select>
|
||
{currentStory !== DEFAULT_STORY
|
||
? <button class='icon' onClick={handleDeleteStory}>
|
||
🗑️
|
||
</button>
|
||
: null}
|
||
</div>
|
||
<AutoTextarea
|
||
value={lore}
|
||
onInput={setLore}
|
||
placeholder="Describe your world, for example: World of Awoo has big mountains and wide rivers."
|
||
class={styles.loreText}
|
||
/>
|
||
</Modal>
|
||
<Modal open={genparamsOpen.value} onClose={genparamsOpen.setFalse}>
|
||
<h3 class={styles.modalTitle}>Generation Parameters</h3>
|
||
<div className={styles.scrollPane}>
|
||
<h4 class={styles.modalTitle}>Banned phrases</h4>
|
||
<AutoTextarea
|
||
placeholder="Each phrase on separate line"
|
||
value={bannedWordsInput}
|
||
onInput={handleSetBannedWords}
|
||
onBlur={handleBlurBannedWords}
|
||
class={styles.template}
|
||
/>
|
||
</div>
|
||
</Modal>
|
||
<Modal open={promptsOpen.value} onClose={promptsOpen.setFalse}>
|
||
<h3 class={styles.modalTitle}>Prompts Editor</h3>
|
||
<div className={styles.scrollPane}>
|
||
<h4 class={styles.modalTitle}>System prompt</h4>
|
||
<AutoTextarea value={systemPrompt} onInput={setSystemPrompt} />
|
||
<hr />
|
||
<h4 class={styles.modalTitle}>User prompt template</h4>
|
||
<Ace value={userPrompt} onInput={setUserPrompt} />
|
||
<hr />
|
||
<h4 class={styles.modalTitle}>Summary template</h4>
|
||
<Ace value={summarizePrompt} onInput={setSummarizePrompt} />
|
||
<label>
|
||
<input type='checkbox' checked={summaryEnabled} onChange={handleSetSummaryEnabled} />
|
||
Enable summarization
|
||
</label>
|
||
<hr />
|
||
<h4 class={styles.modalTitle}>Instruct template</h4>
|
||
<Ace value={connection.instruct} onInput={setInstruct} />
|
||
</div>
|
||
</Modal>
|
||
<MiniChat
|
||
history={messages}
|
||
open={assistantOpen.value}
|
||
onClose={assistantOpen.setFalse}
|
||
buttons={{ 'Add swipe': handleAssistantAddSwipe }}
|
||
/>
|
||
</div>
|
||
);
|
||
} |