1
0
Fork 0

Compare commits

...

2 Commits

Author SHA1 Message Date
Pabloader df46bdafe4 Cost counting 2026-02-17 15:14:59 +00:00
Pabloader adc55d3001 Optimize templates 2026-02-17 14:23:00 +00:00
6 changed files with 51 additions and 39 deletions

View File

@ -2,9 +2,11 @@ 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'> <div class='root' style={{ backgroundImage: `url('${bgImage.src}')` }}>
<div class='app'> <div class='app'>
<Header /> <Header />
<Chat /> <Chat />

View File

@ -24,6 +24,7 @@ export const Message = ({ message, index, isLastUser, isLastAssistant }: IProps)
const content = swipe?.content; const content = swipe?.content;
const summary = swipe?.summary; const summary = swipe?.summary;
const cost = swipe?.cost ?? 0;
const htmlContent = useMemo(() => MessageTools.format(content ?? ''), [content]); const htmlContent = useMemo(() => MessageTools.format(content ?? ''), [content]);
const handleEnableEdit = useCallback(() => { const handleEnableEdit = useCallback(() => {
@ -32,10 +33,10 @@ export const Message = ({ message, index, isLastUser, isLastAssistant }: IProps)
}, [content]); }, [content]);
const handleSaveEdit = useCallback(() => { const handleSaveEdit = useCallback(() => {
editMessage(index, editedMessage.trim()); editMessage(index, editedMessage.trim(), cost);
editSummary(index, ''); editSummary(index, '', 0);
setEditing(false); setEditing(false);
}, [editMessage, editSummary, index, editedMessage]); }, [editMessage, editSummary, index, editedMessage, cost]);
const handleCancelEdit = useCallback(() => { const handleCancelEdit = useCallback(() => {
setEditing(false); setEditing(false);
@ -77,6 +78,7 @@ export const Message = ({ message, index, isLastUser, isLastAssistant }: IProps)
: <> : <>
<div class={styles.text} dangerouslySetInnerHTML={{ __html: htmlContent }} ref={textRef} /> <div class={styles.text} dangerouslySetInnerHTML={{ __html: htmlContent }} ref={textRef} />
{summary && <small class={styles.summary}>{summary}</small>} {summary && <small class={styles.summary}>{summary}</small>}
{cost > 0 && <small class={styles.summary}>💲&nbsp;{cost}</small>}
</> </>
} }
<div class={styles.buttons}> <div class={styles.buttons}>

View File

@ -33,9 +33,9 @@ const MESSAGES_TO_KEEP = 10;
interface IActions { interface IActions {
compilePrompt: (messages: IMessage[], args?: ICompileArgs) => Promise<ICompiledPrompt>; compilePrompt: (messages: IMessage[], args?: ICompileArgs) => Promise<ICompiledPrompt>;
generate: (prompt: string, extraSettings?: IGenerationSettings) => AsyncGenerator<string>; generate: (prompt: string, extraSettings?: IGenerationSettings) => AsyncGenerator<Connection.TextChunk>;
stopGeneration: () => void; stopGeneration: () => void;
summarize: (content: string) => Promise<string>; summarize: (content: string) => Promise<Connection.TextChunk>;
countTokens: (prompt: string) => Promise<number>; countTokens: (prompt: string) => Promise<number>;
} }
export type ILLMContext = IContext & IActions; export type ILLMContext = IContext & IActions;
@ -168,18 +168,17 @@ export const LLMContextProvider = ({ children }: { children?: any }) => {
isRegen, isRegen,
}; };
}, },
generate: async function* (prompt, extraSettings = {}) { generate: async function* (prompt, extraSettings = {}): AsyncGenerator<Connection.TextChunk> {
try { try {
console.log('[LLM.generate]', prompt); console.log('[LLM.generate]', prompt);
setSpentKudos(0);
for await (const { text, cost } of Connection.generate(connection, prompt, { for await (const { text, cost } of Connection.generate(connection, prompt, {
...extraSettings, ...extraSettings,
banned_tokens: bannedWords.filter(w => w.trim()), banned_tokens: bannedWords.filter(w => w.trim()),
})) { })) {
setSpentKudos(sk => sk + cost); setSpentKudos(sk => sk + cost);
setTotalSpentKudos(sk => sk + cost); setTotalSpentKudos(sk => sk + cost);
yield text; yield { text, cost };
} }
} catch (e) { } catch (e) {
if (e instanceof Error && e.name !== 'AbortError') { if (e instanceof Error && e.name !== 'AbortError') {
@ -204,10 +203,13 @@ export const LLMContextProvider = ({ children }: { children?: any }) => {
setSpentKudos(sk => sk + summary.cost); setSpentKudos(sk => sk + summary.cost);
setTotalSpentKudos(sk => sk + summary.cost); setTotalSpentKudos(sk => sk + summary.cost);
return MessageTools.trimSentence(summary.text); return {
text: MessageTools.trimSentence(summary.text),
cost: summary.cost,
};
} catch (e) { } catch (e) {
console.error('Error summarizing:', e); console.error('Error summarizing:', e);
return ''; return { text: '', cost: 0 };
} }
}, },
countTokens: async (prompt) => { countTokens: async (prompt) => {
@ -225,6 +227,7 @@ export const LLMContextProvider = ({ children }: { children?: any }) => {
let messageId = messages.length - 1; let messageId = messages.length - 1;
let text = ''; let text = '';
let cost = 0;
const { prompt, isRegen } = await actions.compilePrompt(messages, { continueLast }); const { prompt, isRegen } = await actions.compilePrompt(messages, { continueLast });
@ -236,17 +239,18 @@ export const LLMContextProvider = ({ children }: { children?: any }) => {
} }
generating.setTrue(); generating.setTrue();
editSummary(messageId, 'Generating...'); editSummary(messageId, 'Generating...', 0);
for await (const chunk of actions.generate(prompt)) { for await (const chunk of actions.generate(prompt)) {
text += chunk; text += chunk.text;
cost += chunk.cost;
setPromptTokens(promptTokens + approximateTokens(text)); setPromptTokens(promptTokens + approximateTokens(text));
editMessage(messageId, text.trim()); editMessage(messageId, text.trim(), cost);
} }
generating.setFalse(); generating.setFalse();
text = MessageTools.trimSentence(text); text = MessageTools.trimSentence(text);
editMessage(messageId, text); editMessage(messageId, text, cost);
editSummary(messageId, ''); editSummary(messageId, '', 0);
MessageTools.playReady(); MessageTools.playReady();
} }
@ -260,8 +264,8 @@ export const LLMContextProvider = ({ children }: { children?: any }) => {
const message = messages[id]; const message = messages[id];
const swipe = MessageTools.getSwipe(message); const swipe = MessageTools.getSwipe(message);
if (message.role === 'assistant' && swipe?.content?.includes('\n') && !swipe.summary) { if (message.role === 'assistant' && swipe?.content?.includes('\n') && !swipe.summary) {
const summary = await actions.summarize(swipe.content); const { text, cost } = await actions.summarize(swipe.content);
editSummary(id, summary); editSummary(id, text, cost);
} }
} }
} catch (e) { } catch (e) {

View File

@ -80,8 +80,8 @@ interface IActions {
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;
editMessage: (index: number, content: string) => void; editMessage: (index: number, content: string, cost: number) => void;
editSummary: (index: number, summary: string) => void; editSummary: (index: number, summary: string, cost: number) => void;
deleteMessage: (index: number) => void; deleteMessage: (index: number) => void;
setCurrentSwipe: (index: number, swipe: number) => void; setCurrentSwipe: (index: number, swipe: number) => void;
addSwipe: (index: number, content: string) => void; addSwipe: (index: number, content: string) => void;
@ -270,11 +270,11 @@ export const StateContextProvider = ({ children }: { children?: any }) => {
]); ]);
setTriggerNext(triggerNext); setTriggerNext(triggerNext);
}, },
editMessage: (index, content) => { editMessage: (index, content, cost) => {
setMessages(messages => MessageTools.updateSwipe(messages, index, { content })); setMessages(messages => MessageTools.updateSwipe(messages, index, { content }, cost));
}, },
editSummary: (index, summary) => { editSummary: (index, summary, cost) => {
setMessages(messages => MessageTools.updateSwipe(messages, index, { summary })); setMessages(messages => MessageTools.updateSwipe(messages, index, { summary }, cost));
}, },
deleteMessage: (index) => setMessages(messages => deleteMessage: (index) => setMessages(messages =>
messages.filter((_, i) => i !== index) messages.filter((_, i) => i !== index)
@ -291,7 +291,7 @@ export const StateContextProvider = ({ children }: { children?: any }) => {
if (currentSwipe >= swipes.length) { if (currentSwipe >= swipes.length) {
if (latestSwipe.content.length > 0) { if (latestSwipe.content.length > 0) {
currentSwipe = swipes.length; currentSwipe = swipes.length;
swipes.push({ content: '' }); swipes.push({ content: '', cost: 0 });
} else { } else {
currentSwipe = swipes.length - 1; currentSwipe = swipes.length - 1;
} }
@ -315,7 +315,7 @@ export const StateContextProvider = ({ children }: { children?: any }) => {
messages.map( messages.map(
(message, i) => { (message, i) => {
if (i === index) { if (i === index) {
const swipes = [...message.swipes, { content }]; const swipes = [...message.swipes, { content, cost: 0 }];
return { return {
...message, ...message,

View File

@ -236,13 +236,17 @@ export namespace Huggingface {
if (config?.chat_template?.trim()) { if (config?.chat_template?.trim()) {
template = config.chat_template.trim() template = config.chat_template.trim()
.replace(/raise_exception\(('[^')]+'|"[^")]+")\)/g, `''`)
.replaceAll('eos_token', `'${config.eos_token ?? ''}'`) .replaceAll('eos_token', `'${config.eos_token ?? ''}'`)
.replaceAll('bos_token', `''`); .replaceAll('bos_token', `''`)
.replace(/\{\{ ?(''|"") ?\}\}/g, '')
if (config.bos_token) { .replace(/\n'/g, `\\n'`)
template = template .replace(/\n"/g, `\\n"`)
.replace(/\{\{ ?(''|"") ?\}\}/g, ''); .replace(/'\s*\+\s*'/g, '')
} .replace(/"\s*\+\s*"/g, '')
.replace(/\{%\s*else\s*%\}\{%\s*endif\s*%\}/gi, '{% endif %}')
.replace(/\{%\s*elif[^}]+%\}\{%\s*endif\s*%\}/gi, '{% endif %}')
.replace(/\{%\s*if[^}]+%\}\{%\s*endif\s*%\}/gi, '');
} }
} }
@ -308,7 +312,6 @@ export namespace Huggingface {
try { try {
let template = compiledTemplates.get(templateString); let template = compiledTemplates.get(templateString);
if (!template) { if (!template) {
templateString = templateString.replace(/raise_exception\(('[^')]+'|"[^")]+")\)/g, `''`)
template = new Template(templateString); template = new Template(templateString);
compiledTemplates.set(templateString, template); compiledTemplates.set(templateString, template);
} }

View File

@ -3,6 +3,7 @@ import messageSound from '../assets/message.mp3';
export interface ISwipe { export interface ISwipe {
content: string; content: string;
summary?: string; summary?: string;
cost: number;
} }
export interface IMessage { export interface IMessage {
@ -14,8 +15,8 @@ export interface IMessage {
export namespace MessageTools { export namespace MessageTools {
export const getSwipe = (message?: IMessage | null) => message?.swipes[message?.currentSwipe]; export const getSwipe = (message?: IMessage | null) => message?.swipes[message?.currentSwipe];
export const create = (content: string, role: IMessage['role'] = 'user', technical = false): IMessage => ( export const create = (content: string, role: IMessage['role'] = 'user', technical = false, cost = 0): IMessage => (
{ role, currentSwipe: 0, swipes: [{ content }], technical } { role, currentSwipe: 0, swipes: [{ content, cost }], technical }
); );
export const playReady = () => { export const playReady = () => {
@ -97,12 +98,12 @@ export namespace MessageTools {
return text.trim(); return text.trim();
} }
export const updateSwipe = (messages: IMessage[], index: number, update: Partial<ISwipe>) => ( export const updateSwipe = (messages: IMessage[], index: number, update: Partial<ISwipe>, cost = 0) => (
messages.map( messages.map(
(m, i) => ({ (m, i) => ({
...m, ...m,
swipes: i === index swipes: i === index
? m.swipes.map((s, si) => (si === m.currentSwipe ? { ...s, ...update } : s)) ? m.swipes.map((s, si) => (si === m.currentSwipe ? { ...s, ...update, cost: s.cost + cost } : s))
: m.swipes : m.swipes
}) })
) )