AI Story: add bg, add summarize toggle
This commit is contained in:
parent
763085726b
commit
ece1621e73
|
|
@ -18,7 +18,7 @@
|
||||||
border-radius: var(--border-radius, 0);
|
border-radius: var(--border-radius, 0);
|
||||||
|
|
||||||
&::backdrop {
|
&::backdrop {
|
||||||
background-color: var(--shadeColor, rgba(0, 0, 0, 0.2));
|
backdrop-filter: blur(5px);
|
||||||
}
|
}
|
||||||
|
|
||||||
>.content {
|
>.content {
|
||||||
|
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 96 KiB |
|
|
@ -1,6 +1,6 @@
|
||||||
:root {
|
:root {
|
||||||
--backgroundColorDark: #111111;
|
--backgroundColorDark: rgba(0, 0, 0, 0.3);
|
||||||
--backgroundColor: #333333;
|
--backgroundColor: rgba(51, 51, 51, 0.9);
|
||||||
--color: #DCDCD2;
|
--color: #DCDCD2;
|
||||||
--italicColor: #AFAFAF;
|
--italicColor: #AFAFAF;
|
||||||
--quoteColor: #D4E5FF;
|
--quoteColor: #D4E5FF;
|
||||||
|
|
@ -67,19 +67,27 @@ button {
|
||||||
|
|
||||||
body {
|
body {
|
||||||
color: var(--color);
|
color: var(--color);
|
||||||
background-color: var(--backgroundColor);
|
|
||||||
width: 100dvw;
|
width: 100dvw;
|
||||||
height: 100dvh;
|
height: 100dvh;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1.5;
|
||||||
|
|
||||||
|
.root {
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
font-size: 16px;
|
}
|
||||||
line-height: 1.5;
|
|
||||||
|
|
||||||
.app {
|
.app {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
background-color: var(--backgroundColor);
|
||||||
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 1200px;
|
max-width: 1200px;
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,16 @@ import { Header } from "./header/header";
|
||||||
import { Chat } from "./chat";
|
import { Chat } from "./chat";
|
||||||
import { Input } from "./input";
|
import { Input } from "./input";
|
||||||
|
|
||||||
|
import bgImage from '../assets/bg.jpg';
|
||||||
|
|
||||||
export const App = () => {
|
export const App = () => {
|
||||||
return (
|
return (
|
||||||
|
<div class='root' style={{ backgroundImage: `url('${bgImage.src}')` }}>
|
||||||
<div class='app'>
|
<div class='app'>
|
||||||
<Header />
|
<Header />
|
||||||
<Chat />
|
<Chat />
|
||||||
<Input />
|
<Input />
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,8 @@ import { Ace } from "../ace";
|
||||||
export const Header = () => {
|
export const Header = () => {
|
||||||
const { modelName, modelTemplate, contextLength, promptTokens, blockConnection } = useContext(LLMContext);
|
const { modelName, modelTemplate, contextLength, promptTokens, blockConnection } = useContext(LLMContext);
|
||||||
const {
|
const {
|
||||||
messages, connectionUrl, systemPrompt, lore, userPrompt, bannedWords, instruct, summarizePrompt,
|
messages, connectionUrl, systemPrompt, lore, userPrompt, bannedWords, instruct, summarizePrompt, summaryEnabled,
|
||||||
setConnectionUrl, setSystemPrompt, setLore, setUserPrompt, addSwipe, setBannedWords, setInstruct, setSummarizePrompt,
|
setConnectionUrl, setSystemPrompt, setLore, setUserPrompt, addSwipe, setBannedWords, setInstruct, setSummarizePrompt, setSummaryEnabled,
|
||||||
} = useContext(StateContext);
|
} = useContext(StateContext);
|
||||||
|
|
||||||
const loreOpen = useBool();
|
const loreOpen = useBool();
|
||||||
|
|
@ -52,6 +52,12 @@ export const Header = () => {
|
||||||
}
|
}
|
||||||
}, [setBannedWords]);
|
}, [setBannedWords]);
|
||||||
|
|
||||||
|
const handleSetSummaryEnabled = useCallback((e: Event) => {
|
||||||
|
if (e.target instanceof HTMLInputElement) {
|
||||||
|
setSummaryEnabled(e.target.checked);
|
||||||
|
}
|
||||||
|
}, [setSummaryEnabled]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class={styles.header}>
|
<div class={styles.header}>
|
||||||
<div class={styles.inputs}>
|
<div class={styles.inputs}>
|
||||||
|
|
@ -128,6 +134,10 @@ export const Header = () => {
|
||||||
<hr />
|
<hr />
|
||||||
<h4 class={styles.modalTitle}>Summary template</h4>
|
<h4 class={styles.modalTitle}>Summary template</h4>
|
||||||
<Ace value={summarizePrompt} onInput={setSummarizePrompt} />
|
<Ace value={summarizePrompt} onInput={setSummarizePrompt} />
|
||||||
|
<label>
|
||||||
|
<input type='checkbox' checked={summaryEnabled} onChange={handleSetSummaryEnabled}/>
|
||||||
|
Enable summarization
|
||||||
|
</label>
|
||||||
<hr />
|
<hr />
|
||||||
<h4 class={styles.modalTitle}>Instruct template</h4>
|
<h4 class={styles.modalTitle}>Instruct template</h4>
|
||||||
<Ace value={instruct} onInput={setInstruct} />
|
<Ace value={instruct} onInput={setInstruct} />
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ export const MiniChat = ({ history = [], buttons = {}, open, onClose }: IProps)
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
DOMTools.scrollDown(ref.current, false);
|
setTimeout(() => DOMTools.scrollDown(ref.current, false), 100);
|
||||||
}, [generating, open]);
|
}, [generating, open]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
||||||
|
|
@ -38,11 +38,18 @@ const DEFAULT_GENERATION_SETTINGS = {
|
||||||
top_k: 100,
|
top_k: 100,
|
||||||
top_p: 0.92,
|
top_p: 0.92,
|
||||||
banned_tokens: [],
|
banned_tokens: [],
|
||||||
max_length: 512,
|
max_length: 300,
|
||||||
trim_stop: true,
|
trim_stop: true,
|
||||||
stop_sequence: ['[INST]', '[/INST]', '</s>', '<|']
|
stop_sequence: ['[INST]', '[/INST]', '</s>', '<|'],
|
||||||
|
dry_allowed_length: 5,
|
||||||
|
dry_multiplier: 0.8,
|
||||||
|
dry_base: 1,
|
||||||
|
dry_sequence_breakers: ["\n", ":", "\"", "*"],
|
||||||
|
dry_penalty_last_n: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MESSAGES_TO_KEEP = 10;
|
||||||
|
|
||||||
type IGenerationSettings = Partial<typeof DEFAULT_GENERATION_SETTINGS>;
|
type IGenerationSettings = Partial<typeof DEFAULT_GENERATION_SETTINGS>;
|
||||||
|
|
||||||
interface IActions {
|
interface IActions {
|
||||||
|
|
@ -81,14 +88,18 @@ export const normalizeModel = (model: string) => {
|
||||||
|
|
||||||
export const LLMContext = createContext<ILLMContext>({} as ILLMContext);
|
export const LLMContext = createContext<ILLMContext>({} as ILLMContext);
|
||||||
|
|
||||||
|
const processing = {
|
||||||
|
tokenizing: false,
|
||||||
|
summarizing: false,
|
||||||
|
}
|
||||||
|
|
||||||
export const LLMContextProvider = ({ children }: { children?: any }) => {
|
export const LLMContextProvider = ({ children }: { children?: any }) => {
|
||||||
const {
|
const {
|
||||||
connectionUrl, messages, triggerNext, lore, userPrompt, systemPrompt, bannedWords, instruct, summarizePrompt,
|
connectionUrl, messages, triggerNext, lore, userPrompt, systemPrompt, bannedWords, instruct, summarizePrompt, summaryEnabled,
|
||||||
setTriggerNext, addMessage, editMessage, editSummary, setInstruct,
|
setTriggerNext, addMessage, editMessage, editSummary, setInstruct,
|
||||||
} = useContext(StateContext);
|
} = useContext(StateContext);
|
||||||
|
|
||||||
const generating = useBool(false);
|
const generating = useBool(false);
|
||||||
const summarizing = useBool(false);
|
|
||||||
const blockConnection = useBool(false);
|
const blockConnection = useBool(false);
|
||||||
const [promptTokens, setPromptTokens] = useState(0);
|
const [promptTokens, setPromptTokens] = useState(0);
|
||||||
const [contextLength, setContextLength] = useState(0);
|
const [contextLength, setContextLength] = useState(0);
|
||||||
|
|
@ -156,10 +167,8 @@ export const LLMContextProvider = ({ children }: { children?: any }) => {
|
||||||
const lastUserMessage = userMessages.at(-1);
|
const lastUserMessage = userMessages.at(-1);
|
||||||
const firstUserMessage = userMessages.at(0);
|
const firstUserMessage = userMessages.at(0);
|
||||||
|
|
||||||
const system = `${systemPrompt}\n\n${lore}`.trim();
|
|
||||||
|
|
||||||
const templateMessages: Huggingface.ITemplateMessage[] = [
|
const templateMessages: Huggingface.ITemplateMessage[] = [
|
||||||
{ role: 'system', content: system },
|
{ role: 'system', content: systemPrompt.trim() },
|
||||||
];
|
];
|
||||||
|
|
||||||
if (keepUsers) {
|
if (keepUsers) {
|
||||||
|
|
@ -168,7 +177,8 @@ export const LLMContextProvider = ({ children }: { children?: any }) => {
|
||||||
|
|
||||||
for (const message of messages) {
|
for (const message of messages) {
|
||||||
const { role } = message;
|
const { role } = message;
|
||||||
const content = MessageTools.getSwipe(message)?.content ?? '';
|
const swipe = MessageTools.getSwipe(message);
|
||||||
|
let content = swipe?.content ?? '';
|
||||||
if (role === 'user' && usersRemaining > keepUsers) {
|
if (role === 'user' && usersRemaining > keepUsers) {
|
||||||
usersRemaining--;
|
usersRemaining--;
|
||||||
} else if (role === 'assistant' && templateMessages.at(-1).role === 'assistant') {
|
} else if (role === 'assistant' && templateMessages.at(-1).role === 'assistant') {
|
||||||
|
|
@ -188,7 +198,16 @@ export const LLMContextProvider = ({ children }: { children?: any }) => {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const story = promptMessages.filter(m => m.role === 'assistant')
|
const story = promptMessages.filter(m => m.role === 'assistant')
|
||||||
.map(m => MessageTools.getSwipe(m)?.content.trim()).join('\n\n');
|
.map((m, i, msgs) => {
|
||||||
|
const swipe = MessageTools.getSwipe(m);
|
||||||
|
if (!swipe) return '';
|
||||||
|
|
||||||
|
let { content, summary } = swipe;
|
||||||
|
if (summary && i < msgs.length - MESSAGES_TO_KEEP) {
|
||||||
|
content = summary;
|
||||||
|
}
|
||||||
|
return content;
|
||||||
|
}).join('\n\n');
|
||||||
|
|
||||||
if (story.length > 0) {
|
if (story.length > 0) {
|
||||||
const prompt = MessageTools.getSwipe(firstUserMessage)?.content;
|
const prompt = MessageTools.getSwipe(firstUserMessage)?.content;
|
||||||
|
|
@ -215,6 +234,8 @@ export const LLMContextProvider = ({ children }: { children?: any }) => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
templateMessages[1].content = `${lore}\n\n${templateMessages[1].content}`;
|
||||||
|
|
||||||
const prompt = Huggingface.applyChatTemplate(instruct, templateMessages);
|
const prompt = Huggingface.applyChatTemplate(instruct, templateMessages);
|
||||||
return {
|
return {
|
||||||
prompt,
|
prompt,
|
||||||
|
|
@ -326,8 +347,6 @@ export const LLMContextProvider = ({ children }: { children?: any }) => {
|
||||||
let text: string = '';
|
let text: string = '';
|
||||||
|
|
||||||
const { prompt, isRegen } = await actions.compilePrompt(messages);
|
const { prompt, isRegen } = await actions.compilePrompt(messages);
|
||||||
const tokens = await actions.countTokens(prompt);
|
|
||||||
setPromptTokens(tokens);
|
|
||||||
|
|
||||||
if (!isRegen) {
|
if (!isRegen) {
|
||||||
addMessage('', 'assistant');
|
addMessage('', 'assistant');
|
||||||
|
|
@ -337,7 +356,7 @@ export const LLMContextProvider = ({ children }: { children?: any }) => {
|
||||||
editSummary(messageId, 'Generating...');
|
editSummary(messageId, 'Generating...');
|
||||||
for await (const chunk of actions.generate(prompt)) {
|
for await (const chunk of actions.generate(prompt)) {
|
||||||
text += chunk;
|
text += chunk;
|
||||||
setPromptTokens(tokens + Math.round(text.length * 0.25));
|
setPromptTokens(promptTokens + Math.round(text.length * 0.25));
|
||||||
editMessage(messageId, text.trim());
|
editMessage(messageId, text.trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -345,15 +364,14 @@ export const LLMContextProvider = ({ children }: { children?: any }) => {
|
||||||
editMessage(messageId, text);
|
editMessage(messageId, text);
|
||||||
editSummary(messageId, '');
|
editSummary(messageId, '');
|
||||||
|
|
||||||
setPromptTokens(0); // trigger calculation
|
|
||||||
|
|
||||||
MessageTools.playReady();
|
MessageTools.playReady();
|
||||||
}
|
}
|
||||||
})(), [actions, triggerNext, messages, generating.value]);
|
})(), [triggerNext]);
|
||||||
|
|
||||||
useEffect(() => void (async () => {
|
useEffect(() => void (async () => {
|
||||||
if (!generating.value && !summarizing.value) {
|
if (summaryEnabled && !generating.value && !processing.summarizing) {
|
||||||
summarizing.setTrue();
|
try {
|
||||||
|
processing.summarizing = true;
|
||||||
for (let id = 0; id < messages.length; id++) {
|
for (let id = 0; id < messages.length; id++) {
|
||||||
const message = messages[id];
|
const message = messages[id];
|
||||||
const swipe = MessageTools.getSwipe(message);
|
const swipe = MessageTools.getSwipe(message);
|
||||||
|
|
@ -362,22 +380,21 @@ export const LLMContextProvider = ({ children }: { children?: any }) => {
|
||||||
editSummary(id, summary);
|
editSummary(id, summary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
summarizing.setFalse();
|
} catch (e) {
|
||||||
|
console.error(`Could not summarize`, e)
|
||||||
|
} finally {
|
||||||
|
processing.summarizing = false;
|
||||||
}
|
}
|
||||||
})(), [messages, generating.value, summarizing.value]);
|
}
|
||||||
|
})(), [messages]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!blockConnection.value) {
|
if (!blockConnection.value) {
|
||||||
setPromptTokens(0);
|
setPromptTokens(0);
|
||||||
setContextLength(0);
|
setContextLength(0);
|
||||||
|
setModelName('');
|
||||||
|
|
||||||
getContextLength().then(setContextLength);
|
getContextLength().then(setContextLength);
|
||||||
}
|
|
||||||
}, [connectionUrl, instruct, blockConnection.value]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!blockConnection.value) {
|
|
||||||
setModelName('');
|
|
||||||
getModelName().then(normalizeModel).then(setModelName);
|
getModelName().then(normalizeModel).then(setModelName);
|
||||||
}
|
}
|
||||||
}, [connectionUrl, blockConnection.value]);
|
}, [connectionUrl, blockConnection.value]);
|
||||||
|
|
@ -397,14 +414,24 @@ export const LLMContextProvider = ({ children }: { children?: any }) => {
|
||||||
}
|
}
|
||||||
}, [modelName]);
|
}, [modelName]);
|
||||||
|
|
||||||
useEffect(() => {
|
const calculateTokens = useCallback(async () => {
|
||||||
if (promptTokens === 0 && !blockConnection.value) {
|
if (!processing.tokenizing && !blockConnection.value && !generating.value) {
|
||||||
actions.compilePrompt(messages)
|
try {
|
||||||
.then(({ prompt }) => actions.countTokens(prompt))
|
processing.tokenizing = true;
|
||||||
.then(setPromptTokens)
|
const { prompt } = await actions.compilePrompt(messages);
|
||||||
.catch(e => console.error(`Could not count tokens`, e));
|
const tokens = await actions.countTokens(prompt);
|
||||||
|
setPromptTokens(tokens);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Could not count tokens`, e)
|
||||||
|
} finally {
|
||||||
|
processing.tokenizing = false;
|
||||||
}
|
}
|
||||||
}, [actions, promptTokens, messages, blockConnection.value]);
|
}
|
||||||
|
}, [actions, messages, blockConnection.value]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
calculateTokens();
|
||||||
|
}, [messages, connectionUrl, blockConnection.value, instruct, /* systemPrompt, lore, userPrompt TODO debounce*/]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ interface IContext {
|
||||||
lore: string;
|
lore: string;
|
||||||
userPrompt: string;
|
userPrompt: string;
|
||||||
summarizePrompt: string;
|
summarizePrompt: string;
|
||||||
|
summaryEnabled: boolean;
|
||||||
bannedWords: string[];
|
bannedWords: string[];
|
||||||
messages: IMessage[];
|
messages: IMessage[];
|
||||||
triggerNext: boolean;
|
triggerNext: boolean;
|
||||||
|
|
@ -26,6 +27,7 @@ interface IActions {
|
||||||
setSummarizePrompt: (prompt: string | Event) => void;
|
setSummarizePrompt: (prompt: string | Event) => void;
|
||||||
setBannedWords: (words: string[]) => void;
|
setBannedWords: (words: string[]) => void;
|
||||||
setTriggerNext: (triggerNext: boolean) => void;
|
setTriggerNext: (triggerNext: boolean) => void;
|
||||||
|
setSummaryEnabled: (summaryEnabled: boolean) => void;
|
||||||
|
|
||||||
setMessages: (messages: IMessage[]) => void;
|
setMessages: (messages: IMessage[]) => void;
|
||||||
addMessage: (content: string, role: IMessage['role'], triggerNext?: boolean) => void;
|
addMessage: (content: string, role: IMessage['role'], triggerNext?: boolean) => void;
|
||||||
|
|
@ -64,11 +66,21 @@ export const loadContext = (): IContext => {
|
||||||
connectionUrl: 'http://localhost:5001',
|
connectionUrl: 'http://localhost:5001',
|
||||||
input: '',
|
input: '',
|
||||||
instruct: Instruct.CHATML,
|
instruct: Instruct.CHATML,
|
||||||
systemPrompt: 'You are creative writer. Write a story based on the world description below.',
|
systemPrompt: 'You are a creative writer. Write a story based on the world description below. Story should be adult and mature; and could include swearing, violence and unfairness. Portray characters realistically and stay in the lore.',
|
||||||
lore: '',
|
lore: '',
|
||||||
userPrompt: `{% if prompt %}{% if isStart %}Start{% else %}Continue{% endif %} this story, taking information into account: {{ prompt | trim }}
|
userPrompt: `{% if isStart -%}
|
||||||
Remember that this story should be infinite and go forever. Avoid cliffhangers and pauses, be creative.{% elif isStart %}Write a novel using information above as a reference. Make sure to follow the lore exactly and avoid cliffhangers.{% else %}Continue the story forward. Avoid cliffhangers and pauses.{% endif %}`,
|
Write a novel using information above as a reference.
|
||||||
summarizePrompt: 'Shrink following text down to one paragraph, keeping all important details, your answer should contain only the shortened text:\n\n{{ message }}',
|
{%- else -%}
|
||||||
|
Continue the story forward.
|
||||||
|
{%- endif %}
|
||||||
|
|
||||||
|
{% if prompt -%}
|
||||||
|
This is the description of what I want to happen next: {{ prompt | trim }}
|
||||||
|
{% endif %}
|
||||||
|
Remember that this story should be infinite and go forever.
|
||||||
|
Make sure to follow the world description and rules exactly. Avoid cliffhangers and pauses, be creative.`,
|
||||||
|
summarizePrompt: 'Shrink following text down, keeping all important details:\n\n{{ message }}\n\nAnswer with shortened text only.',
|
||||||
|
summaryEnabled: false,
|
||||||
bannedWords: [],
|
bannedWords: [],
|
||||||
messages: [],
|
messages: [],
|
||||||
triggerNext: false,
|
triggerNext: false,
|
||||||
|
|
@ -101,6 +113,7 @@ export const StateContextProvider = ({ children }: { children?: any }) => {
|
||||||
const [summarizePrompt, setSummarizePrompt] = useInputState(loadedContext.summarizePrompt);
|
const [summarizePrompt, setSummarizePrompt] = useInputState(loadedContext.summarizePrompt);
|
||||||
const [bannedWords, setBannedWords] = useState<string[]>(loadedContext.bannedWords);
|
const [bannedWords, setBannedWords] = useState<string[]>(loadedContext.bannedWords);
|
||||||
const [messages, setMessages] = useState(loadedContext.messages);
|
const [messages, setMessages] = useState(loadedContext.messages);
|
||||||
|
const [summaryEnabled, setSummaryEnabled] = useState(loadedContext.summaryEnabled);
|
||||||
|
|
||||||
const [triggerNext, setTriggerNext] = useState(false);
|
const [triggerNext, setTriggerNext] = useState(false);
|
||||||
|
|
||||||
|
|
@ -113,6 +126,7 @@ export const StateContextProvider = ({ children }: { children?: any }) => {
|
||||||
setSummarizePrompt,
|
setSummarizePrompt,
|
||||||
setLore,
|
setLore,
|
||||||
setTriggerNext,
|
setTriggerNext,
|
||||||
|
setSummaryEnabled,
|
||||||
setBannedWords: (words) => setBannedWords([...words]),
|
setBannedWords: (words) => setBannedWords([...words]),
|
||||||
|
|
||||||
setMessages: (newMessages) => setMessages(newMessages.slice()),
|
setMessages: (newMessages) => setMessages(newMessages.slice()),
|
||||||
|
|
@ -192,6 +206,7 @@ export const StateContextProvider = ({ children }: { children?: any }) => {
|
||||||
lore,
|
lore,
|
||||||
userPrompt,
|
userPrompt,
|
||||||
summarizePrompt,
|
summarizePrompt,
|
||||||
|
summaryEnabled,
|
||||||
bannedWords,
|
bannedWords,
|
||||||
messages,
|
messages,
|
||||||
triggerNext,
|
triggerNext,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue