AI story: Automatic summarization
This commit is contained in:
parent
cc43e035fe
commit
b8090dac86
|
|
@ -47,7 +47,7 @@ export const Header = () => {
|
|||
|
||||
const handleBlurBannedWords = useCallback((e: Event) => {
|
||||
if (e.target instanceof HTMLTextAreaElement) {
|
||||
const words = e.target.value.split('\n').sort();
|
||||
const words = e.target.value.toLowerCase().split('\n').sort();
|
||||
setBannedWords(words);
|
||||
}
|
||||
}, [setBannedWords]);
|
||||
|
|
|
|||
|
|
@ -27,10 +27,14 @@
|
|||
border: var(--border);
|
||||
min-height: 100px;
|
||||
height: unset;
|
||||
resize: vertical;
|
||||
line-height: 1.5;
|
||||
padding: 5px;
|
||||
border-radius: var(--border-radius);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.summary {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
|
|
|
|||
|
|
@ -14,25 +14,31 @@ interface IProps {
|
|||
}
|
||||
|
||||
export const Message = ({ message, index, isLastUser, isLastAssistant }: IProps) => {
|
||||
const { messages, editMessage, deleteMessage, setCurrentSwipe, setMessages } = useContext(StateContext);
|
||||
const { messages, editMessage, editSummary, deleteMessage, setCurrentSwipe, setMessages } = useContext(StateContext);
|
||||
const [editing, setEditing] = useState(false);
|
||||
const [savedMessage, setSavedMessage] = useState('');
|
||||
const [editedMessage, setEditedMessage] = useState('');
|
||||
const textRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const content = useMemo(() => MessageTools.getSwipe(message)?.content, [message]);
|
||||
const swipe = useMemo(() => MessageTools.getSwipe(message), [message]);
|
||||
|
||||
const content = swipe?.content;
|
||||
const summary = swipe?.summary;
|
||||
const htmlContent = useMemo(() => MessageTools.format(content ?? ''), [content]);
|
||||
|
||||
const handleToggleEdit = useCallback(() => {
|
||||
setEditing(!editing);
|
||||
if (!editing) {
|
||||
setSavedMessage(content ?? '');
|
||||
}
|
||||
}, [editing, content]);
|
||||
const handleEnableEdit = useCallback(() => {
|
||||
setEditing(true);
|
||||
setEditedMessage(content ?? '');
|
||||
}, [content]);
|
||||
|
||||
const handleSaveEdit = useCallback(() => {
|
||||
editMessage(index, editedMessage.trim());
|
||||
editSummary(index, '');
|
||||
setEditing(false);
|
||||
}, [editMessage, editSummary, index, editedMessage]);
|
||||
|
||||
const handleCancelEdit = useCallback(() => {
|
||||
setEditing(false);
|
||||
editMessage(index, savedMessage);
|
||||
}, [editMessage, index, savedMessage]);
|
||||
}, [editMessage, index]);
|
||||
|
||||
const handleDeleteMessage = useCallback(() => {
|
||||
if (confirm('Delete message?')) {
|
||||
|
|
@ -51,9 +57,9 @@ export const Message = ({ message, index, isLastUser, isLastAssistant }: IProps)
|
|||
const handleEdit = useCallback((e: InputEvent) => {
|
||||
if (e.target instanceof HTMLTextAreaElement) {
|
||||
const newContent = e.target.value;
|
||||
editMessage(index, newContent);
|
||||
setEditedMessage(newContent);
|
||||
}
|
||||
}, [editMessage, index]);
|
||||
}, []);
|
||||
|
||||
const handleSwipeLeft = useCallback(() => {
|
||||
setCurrentSwipe(index, message.currentSwipe - 1);
|
||||
|
|
@ -69,17 +75,20 @@ export const Message = ({ message, index, isLastUser, isLastAssistant }: IProps)
|
|||
<div class={`${styles.message} ${styles[message.role]} ${isLastUser ? styles.lastUser : ''}`}>
|
||||
<div class={styles.content}>
|
||||
{editing
|
||||
? <AutoTextarea onInput={handleEdit} value={content} />
|
||||
: <div class={styles.text} dangerouslySetInnerHTML={{ __html: htmlContent }} ref={textRef} />
|
||||
? <AutoTextarea onInput={handleEdit} value={editedMessage} />
|
||||
: <>
|
||||
<div class={styles.text} dangerouslySetInnerHTML={{ __html: htmlContent }} ref={textRef} />
|
||||
{summary && <small class={styles.summary}>{summary}</small>}
|
||||
</>
|
||||
}
|
||||
{(isLastUser || message.role === 'assistant') &&
|
||||
<div class={styles.buttons}>
|
||||
{editing
|
||||
? <>
|
||||
<button class='icon' onClick={handleToggleEdit}>✔</button>
|
||||
<button class='icon' onClick={handleDeleteMessage}>🗑️</button>
|
||||
<button class='icon' onClick={handleSaveEdit} title='Save'>✔</button>
|
||||
<button class='icon' onClick={handleDeleteMessage} title='Delete'>🗑️</button>
|
||||
<button class='icon' onClick={handleStopHere} title='Stop here'>⛔</button>
|
||||
<button class='icon' onClick={handleCancelEdit}>❌</button>
|
||||
<button class='icon' onClick={handleCancelEdit} title='Cancel'>❌</button>
|
||||
</>
|
||||
: <>
|
||||
{isLastAssistant &&
|
||||
|
|
@ -89,7 +98,7 @@ export const Message = ({ message, index, isLastUser, isLastAssistant }: IProps)
|
|||
<div onClick={handleSwipeRight}>▶</div>
|
||||
</div>
|
||||
}
|
||||
<button class='icon' onClick={handleToggleEdit} title="Edit">🖊</button>
|
||||
<button class='icon' onClick={handleEnableEdit} title="Edit">🖊</button>
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -58,11 +58,11 @@ export const MiniChat = ({ history = [], buttons = {}, open, onClose }: IProps)
|
|||
|
||||
for await (const chunk of generate(prompt)) {
|
||||
text += chunk;
|
||||
setMessages(MessageTools.updateContent(newMessages, messageId, text));
|
||||
setMessages(MessageTools.updateSwipe(newMessages, messageId, text));
|
||||
}
|
||||
|
||||
setMessages([
|
||||
...MessageTools.updateContent(newMessages, messageId, MessageTools.trimSentence(text)),
|
||||
...MessageTools.updateSwipe(newMessages, messageId, MessageTools.trimSentence(text)),
|
||||
MessageTools.create('', 'user', true),
|
||||
]);
|
||||
MessageTools.playReady();
|
||||
|
|
@ -78,7 +78,7 @@ export const MiniChat = ({ history = [], buttons = {}, open, onClose }: IProps)
|
|||
|
||||
const handleChange = useCallback((i: number, e: InputEvent) => {
|
||||
if (e.target instanceof HTMLTextAreaElement) {
|
||||
setMessages(MessageTools.updateContent(messages, i, e.target.value));
|
||||
setMessages(MessageTools.updateSwipe(messages, i, e.target.value));
|
||||
}
|
||||
}, [messages]);
|
||||
|
||||
|
|
|
|||
|
|
@ -84,10 +84,11 @@ export const LLMContext = createContext<ILLMContext>({} as ILLMContext);
|
|||
export const LLMContextProvider = ({ children }: { children?: any }) => {
|
||||
const {
|
||||
connectionUrl, messages, triggerNext, lore, userPrompt, systemPrompt, bannedWords, instruct, summarizePrompt,
|
||||
setTriggerNext, addMessage, editMessage, setInstruct,
|
||||
setTriggerNext, addMessage, editMessage, editSummary, setInstruct,
|
||||
} = useContext(StateContext);
|
||||
|
||||
const generating = useBool(false);
|
||||
const summarizing = useBool(false);
|
||||
const blockConnection = useBool(false);
|
||||
const [promptTokens, setPromptTokens] = useState(0);
|
||||
const [contextLength, setContextLength] = useState(0);
|
||||
|
|
@ -293,7 +294,7 @@ export const LLMContextProvider = ({ children }: { children?: any }) => {
|
|||
|
||||
const tokens = await Array.fromAsync(actions.generate(prompt));
|
||||
|
||||
return tokens.join('');
|
||||
return MessageTools.trimSentence(tokens.join(''));
|
||||
},
|
||||
countTokens: async (prompt) => {
|
||||
if (!connectionUrl) {
|
||||
|
|
@ -333,6 +334,7 @@ export const LLMContextProvider = ({ children }: { children?: any }) => {
|
|||
messageId++;
|
||||
}
|
||||
|
||||
editSummary(messageId, 'Generating...');
|
||||
for await (const chunk of actions.generate(prompt)) {
|
||||
text += chunk;
|
||||
setPromptTokens(tokens + Math.round(text.length * 0.25));
|
||||
|
|
@ -340,7 +342,8 @@ export const LLMContextProvider = ({ children }: { children?: any }) => {
|
|||
}
|
||||
|
||||
text = MessageTools.trimSentence(text);
|
||||
editMessage(messageId, text.trim());
|
||||
editMessage(messageId, text);
|
||||
editSummary(messageId, '');
|
||||
|
||||
setPromptTokens(0); // trigger calculation
|
||||
|
||||
|
|
@ -348,6 +351,21 @@ export const LLMContextProvider = ({ children }: { children?: any }) => {
|
|||
}
|
||||
})(), [actions, triggerNext, messages, generating.value]);
|
||||
|
||||
useEffect(() => void (async () => {
|
||||
if (!generating.value && !summarizing.value) {
|
||||
summarizing.setTrue();
|
||||
for (let id = 0; id < messages.length; id++) {
|
||||
const message = messages[id];
|
||||
const swipe = MessageTools.getSwipe(message);
|
||||
if (message.role === 'assistant' && swipe?.content?.includes('\n') && !swipe.summary) {
|
||||
const summary = await actions.summarize(swipe.content);
|
||||
editSummary(id, summary);
|
||||
}
|
||||
}
|
||||
summarizing.setFalse();
|
||||
}
|
||||
})(), [messages, generating.value, summarizing.value]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!blockConnection.value) {
|
||||
setPromptTokens(0);
|
||||
|
|
|
|||
|
|
@ -29,7 +29,8 @@ interface IActions {
|
|||
|
||||
setMessages: (messages: IMessage[]) => void;
|
||||
addMessage: (content: string, role: IMessage['role'], triggerNext?: boolean) => void;
|
||||
editMessage: (index: number, content: string, triggerNext?: boolean) => void;
|
||||
editMessage: (index: number, content: string) => void;
|
||||
editSummary: (index: number, summary: string) => void;
|
||||
deleteMessage: (index: number) => void;
|
||||
setCurrentSwipe: (index: number, swipe: number) => void;
|
||||
addSwipe: (index: number, content: string) => void;
|
||||
|
|
@ -120,9 +121,11 @@ export const StateContextProvider = ({ children }: { children?: any }) => {
|
|||
]);
|
||||
setTriggerNext(triggerNext);
|
||||
},
|
||||
editMessage: (index, content, triggerNext = false) => {
|
||||
setMessages(messages => MessageTools.updateContent(messages, index, content));
|
||||
setTriggerNext(triggerNext);
|
||||
editMessage: (index, content) => {
|
||||
setMessages(messages => MessageTools.updateSwipe(messages, index, { content }));
|
||||
},
|
||||
editSummary: (index, summary) => {
|
||||
setMessages(messages => MessageTools.updateSwipe(messages, index, { summary }));
|
||||
},
|
||||
deleteMessage: (index) => setMessages(messages =>
|
||||
messages.filter((_, i) => i !== index)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import messageSound from './assets/message.mp3';
|
|||
|
||||
export interface ISwipe {
|
||||
content: string;
|
||||
summary?: string;
|
||||
}
|
||||
|
||||
export interface IMessage {
|
||||
|
|
@ -94,15 +95,15 @@ export namespace MessageTools {
|
|||
if (latestEnd > 0) {
|
||||
text = text.slice(0, latestEnd + 1);
|
||||
}
|
||||
return text.trimEnd();
|
||||
return text.trim();
|
||||
}
|
||||
|
||||
export const updateContent = (messages: IMessage[], index: number, content: string) => (
|
||||
export const updateSwipe = (messages: IMessage[], index: number, update: Partial<ISwipe>) => (
|
||||
messages.map(
|
||||
(m, i) => ({
|
||||
...m,
|
||||
swipes: i === index
|
||||
? m.swipes.map((s, si) => (si === m.currentSwipe ? { content } : s))
|
||||
? m.swipes.map((s, si) => (si === m.currentSwipe ? { ...s, ...update } : s))
|
||||
: m.swipes
|
||||
})
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in New Issue