1
0
Fork 0
tsgames/src/games/ai-story/components/header/header.tsx

196 lines
7.8 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.modal}>
<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} class={styles.modal}>
<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} />
&nbsp;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>
);
}