AIStory: swipes
This commit is contained in:
parent
2a9f9d1979
commit
fa77bb2339
Binary file not shown.
|
After Width: | Height: | Size: 50 KiB |
|
|
@ -36,6 +36,7 @@ textarea {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
scrollbar-width: thin;
|
scrollbar-width: thin;
|
||||||
scrollbar-color: var(--color) transparent;
|
scrollbar-color: var(--color) transparent;
|
||||||
|
padding: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
|
|
@ -97,16 +98,21 @@ body {
|
||||||
&.role-user {
|
&.role-user {
|
||||||
background-color: var(--shadeColor);
|
background-color: var(--shadeColor);
|
||||||
|
|
||||||
:not(.last-user) .content .text {
|
&:not(.last-user) .content .text {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.role-assistant {
|
||||||
|
border-top: 1px solid var(--backgroundColorDark);
|
||||||
|
}
|
||||||
|
|
||||||
>.content {
|
>.content {
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: column;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
|
||||||
|
|
@ -116,7 +122,7 @@ body {
|
||||||
min-height: 100px;
|
min-height: 100px;
|
||||||
height: unset;
|
height: unset;
|
||||||
resize: vertical;
|
resize: vertical;
|
||||||
line-height: 1.25;
|
line-height: 1.5;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
|
|
@ -124,6 +130,7 @@ body {
|
||||||
>.text {
|
>.text {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
animation-duration: 300ms;
|
||||||
|
|
||||||
>.bold {
|
>.bold {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
|
@ -141,7 +148,9 @@ body {
|
||||||
|
|
||||||
>.buttons {
|
>.buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
|
||||||
>.icon {
|
>.icon {
|
||||||
|
|
@ -158,6 +167,19 @@ body {
|
||||||
font-family: var(--emojiColorFont);
|
font-family: var(--emojiColorFont);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
>.swipes {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
>div {
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -173,3 +195,26 @@ body {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes swipe-from-left {
|
||||||
|
0% {
|
||||||
|
position: relative;
|
||||||
|
left: -100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
position: relative;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes swipe-from-right {
|
||||||
|
0% {
|
||||||
|
position: relative;
|
||||||
|
right: -100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
position: relative;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,8 +8,9 @@ export const Chat = () => {
|
||||||
|
|
||||||
const lastMessage = messages.at(-1);
|
const lastMessage = messages.at(-1);
|
||||||
const lastMessageSwipe = lastMessage?.swipes[lastMessage.currentSwipe];
|
const lastMessageSwipe = lastMessage?.swipes[lastMessage.currentSwipe];
|
||||||
const lastMessageContent = lastMessageSwipe?.displayContent ?? lastMessageSwipe.content;
|
const lastMessageContent = lastMessageSwipe?.displayContent ?? lastMessageSwipe?.content;
|
||||||
const lastUserId = messages.findLastIndex(m => m.role === 'user');
|
const lastUserId = messages.findLastIndex(m => m.role === 'user');
|
||||||
|
const lastAssistantId = messages.findLastIndex(m => m.role === 'assistant');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (chatRef.current) {
|
if (chatRef.current) {
|
||||||
|
|
@ -23,7 +24,11 @@ export const Chat = () => {
|
||||||
return (
|
return (
|
||||||
<div class="chat" ref={chatRef}>
|
<div class="chat" ref={chatRef}>
|
||||||
{messages.map((m, i) => (
|
{messages.map((m, i) => (
|
||||||
<Message message={m} key={i} index={i} isLastUser={i === lastUserId}/>
|
<Message
|
||||||
|
message={m}
|
||||||
|
key={i} index={i}
|
||||||
|
isLastUser={i === lastUserId} isLastAssistant={i === lastAssistantId}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -6,17 +6,19 @@ interface IProps {
|
||||||
message: IMessage;
|
message: IMessage;
|
||||||
index: number;
|
index: number;
|
||||||
isLastUser: boolean;
|
isLastUser: boolean;
|
||||||
|
isLastAssistant: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Message = ({ message, index, isLastUser }: IProps) => {
|
export const Message = ({ message, index, isLastUser, isLastAssistant }: IProps) => {
|
||||||
const { editMessage, deleteMessage } = useContext(GlobalContext);
|
const { editMessage, deleteMessage, setCurrentSwipe } = useContext(GlobalContext);
|
||||||
const [editing, setEditing] = useState(false);
|
const [editing, setEditing] = useState(false);
|
||||||
const [savedMessage, setSavedMessage] = useState('');
|
const [savedMessage, setSavedMessage] = useState('');
|
||||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||||
|
const textRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const swipe = useMemo(() => message.swipes[message.currentSwipe], [message.swipes, message.currentSwipe]);
|
const swipe = useMemo(() => message.swipes[message.currentSwipe], [message.swipes, message.currentSwipe]);
|
||||||
const content = useMemo(() => swipe.displayContent ?? swipe.content, [swipe]);
|
const content = useMemo(() => swipe?.displayContent ?? swipe?.content, [swipe]);
|
||||||
const htmlContent = useMemo(() => formatMessage(content), [content]);
|
const htmlContent = useMemo(() => formatMessage(content ?? ''), [content]);
|
||||||
|
|
||||||
const handleToggleEdit = useCallback(() => {
|
const handleToggleEdit = useCallback(() => {
|
||||||
setEditing(!editing);
|
setEditing(!editing);
|
||||||
|
|
@ -44,6 +46,22 @@ export const Message = ({ message, index, isLastUser }: IProps) => {
|
||||||
}
|
}
|
||||||
}, [editMessage, index]);
|
}, [editMessage, index]);
|
||||||
|
|
||||||
|
const handleSwipeLeft = useCallback(() => {
|
||||||
|
setCurrentSwipe(index, message.currentSwipe - 1);
|
||||||
|
if (textRef.current) {
|
||||||
|
textRef.current.style.animationName = '';
|
||||||
|
textRef.current.style.animationName = 'swipe-from-left';
|
||||||
|
}
|
||||||
|
}, [setCurrentSwipe, index, message]);
|
||||||
|
|
||||||
|
const handleSwipeRight = useCallback(() => {
|
||||||
|
setCurrentSwipe(index, message.currentSwipe + 1);
|
||||||
|
if (textRef.current) {
|
||||||
|
textRef.current.style.animationName = '';
|
||||||
|
textRef.current.style.animationName = 'swipe-from-right';
|
||||||
|
}
|
||||||
|
}, [setCurrentSwipe, index, message]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (textareaRef.current) {
|
if (textareaRef.current) {
|
||||||
const area = textareaRef.current;
|
const area = textareaRef.current;
|
||||||
|
|
@ -57,20 +75,29 @@ export const Message = ({ message, index, isLastUser }: IProps) => {
|
||||||
<div class="content">
|
<div class="content">
|
||||||
{editing
|
{editing
|
||||||
? <textarea onInput={handleEdit} value={content} class="edit-input" ref={textareaRef} />
|
? <textarea onInput={handleEdit} value={content} class="edit-input" ref={textareaRef} />
|
||||||
: <div class="text" dangerouslySetInnerHTML={{ __html: htmlContent }} />
|
: <div class="text" dangerouslySetInnerHTML={{ __html: htmlContent }} ref={textRef}/>
|
||||||
|
}
|
||||||
|
{(isLastUser || message.role === 'assistant') &&
|
||||||
|
<div class="buttons">
|
||||||
|
{editing
|
||||||
|
? <>
|
||||||
|
<button class="icon" onClick={handleToggleEdit}>✔</button>
|
||||||
|
<button class="icon" onClick={handleDeleteMessage}>🗑️</button>
|
||||||
|
<button class="icon" onClick={handleCancelEdit}>❌</button>
|
||||||
|
</>
|
||||||
|
: <>
|
||||||
|
{isLastAssistant &&
|
||||||
|
<div class="swipes">
|
||||||
|
<div onClick={handleSwipeLeft}>◀</div>
|
||||||
|
<div>{message.currentSwipe + 1}/{message.swipes.length}</div>
|
||||||
|
<div onClick={handleSwipeRight}>▶</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<button class="icon" onClick={handleToggleEdit}>🖊</button>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
<div class="buttons">
|
|
||||||
{editing
|
|
||||||
? <>
|
|
||||||
<button class="icon" onClick={handleToggleEdit}>✔</button>
|
|
||||||
<button class="icon" onClick={handleCancelEdit}>❌</button>
|
|
||||||
<button class="icon" onClick={handleDeleteMessage}>🗑️</button>
|
|
||||||
</>
|
|
||||||
: <>
|
|
||||||
<button class="icon" onClick={handleToggleEdit}>🖊</button>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ export const WORLD_INFO = p`
|
||||||
|
|
||||||
### Ether kinetics
|
### Ether kinetics
|
||||||
|
|
||||||
In this world, people refer to what others might call magic as ether kinetics - the special ability of living beings to consciously manipulate a specific energy known as ether, causing various effects. For simplicity, ether kinetics is often referred to as eth. To use it, person should construct an imaginary structure, called an ether circuit or ether weaving, using their mind's eye to visualize slightly glowing translucent paths in the air, which represent ether channels. Once the desired pattern is established, the user fills it with energy. Ether itself is invisible and intangible; nobody could see it or touch it directly. Ether kinetics is not magic, it is pure physics: you could apply force (e.g. moving the thing around) or pressure (e.g. compressing a gas) to a matter, no more than that. But in creative hands it's potential is endless! For example, if you compress air hard enough - it becomes hot and could set thing on fire. Or if you make air to move in one direction it makes a wind. But eth could be applied to anything material, not just air or other gases: levitating objects or moving liquids around also possible.
|
In this world, people refer to what others might call magic as ether kinetics - the special ability of living beings to consciously manipulate a specific energy known as ether, causing various effects. For simplicity, ether kinetics is often referred to as eth. To use it, person should construct an imaginary structure, called an ether circuit or ether weaving, using their mind's eye to visualize slightly glowing translucent paths in the air, which represent ether channels. Once the desired pattern is established, the user fills it with energy. Ether itself is invisible and intangible; nobody could see it or touch it directly. Ether kinetics is not magic, it is pure physics: you could apply force (e.g. moving the thing around) or pressure (e.g. compressing a gas) to a matter, no more than that. But in creative hands it's potential is endless! For example, if you compress air hard enough - it becomes hot and could set thing on fire. Or if you make air to move in one direction it makes a wind. But eth could be applied to anything material, not just air or other gases: levitating objects or moving liquids around also possible. Objects could not contain ether or be infused with it.
|
||||||
Eth users are called etherkins, or etherkin in singular form.
|
Eth users are called etherkins, or etherkin in singular form.
|
||||||
Fess does not have any supernatural activity: souls, ghosts or similar mystical creatures are impossible by the laws of the world.
|
Fess does not have any supernatural activity: souls, ghosts or similar mystical creatures are impossible by the laws of the world.
|
||||||
|
|
||||||
|
|
@ -24,6 +24,9 @@ export const WORLD_INFO = p`
|
||||||
- Eth is short name for ether kinetics.
|
- Eth is short name for ether kinetics.
|
||||||
- Etherkin is the person who uses ether kinetics.
|
- Etherkin is the person who uses ether kinetics.
|
||||||
- No supernatural activity.
|
- No supernatural activity.
|
||||||
|
- Pretty weak, not capable of manipulating the reality.
|
||||||
|
- It has absolutely no influence on minds and could not control them.
|
||||||
|
- Objects could not be infused with it, nor contain it in any form.
|
||||||
|
|
||||||
### Races
|
### Races
|
||||||
|
|
||||||
|
|
@ -54,9 +57,13 @@ export const WORLD_INFO = p`
|
||||||
**Median Ridge**:
|
**Median Ridge**:
|
||||||
Towering majestically above the landscape, the Median Ridge is a colossal mountain range that stretches across the east of the continent like a jagged spine. Its towering peaks, shrouded in perpetual snow and ice, create an imposing barrier that divides the land into two distinct halves. The rugged terrain and extreme weather conditions make traversing the ridge a daunting task, limiting communication and trade between the isolated communities on the either sides of the mountains.
|
Towering majestically above the landscape, the Median Ridge is a colossal mountain range that stretches across the east of the continent like a jagged spine. Its towering peaks, shrouded in perpetual snow and ice, create an imposing barrier that divides the land into two distinct halves. The rugged terrain and extreme weather conditions make traversing the ridge a daunting task, limiting communication and trade between the isolated communities on the either sides of the mountains.
|
||||||
|
|
||||||
|
### History
|
||||||
|
|
||||||
|
Fess was created about 1500 years ago by Maya. It was created in the same geological and cultural state as it is now, not accounting for minor changes. People of Fess speak the same common language that was given to them upon creation.
|
||||||
|
|
||||||
### Maya
|
### Maya
|
||||||
|
|
||||||
Maya created the world of Fess around 1500 years ago as an imaginative playground, imbuing it with a rich history, diverse races, and an ether kinesis system instead of magic. As the world's creator, Maya has ultimate control over the realm and its inhabitants, but she doesn't think of herself as a traditional goddess figure. Her existence is widely accepted and acknowledged among Fess's inhabitants, who view her as a beloved and protective creator. She's living in that same world in a human form.
|
Maya created the world of Fess around 1500 years ago as an imaginative playground, imbuing it with a rich history, diverse races, and an ether kinesis system. As the world's creator, Maya has ultimate control over the realm and its inhabitants, but she doesn't think of herself as a traditional goddess figure. Her existence is widely accepted and acknowledged among Fess's inhabitants, who view her as a beloved and protective creator. She's living in that same world in a human form.
|
||||||
Maya looks like a neka with a pair of wolf ears, black hair and a tail with white tips. On her right hand exists an intricate geometric tattoo, resembling an ether circuit with interwoven lines of varying thickness, that stretches from her palm all the way up to her shoulder, glowing with a vibrant cyan color. Her eyes are a deep blue. She wears a futuristic sleeveless bodysuit and a pair of similar looking pants. Maya is very self-assured and enjoys embarrassing villains. She has no weapons, because she doesn't need them.
|
Maya looks like a neka with a pair of wolf ears, black hair and a tail with white tips. On her right hand exists an intricate geometric tattoo, resembling an ether circuit with interwoven lines of varying thickness, that stretches from her palm all the way up to her shoulder, glowing with a vibrant cyan color. Her eyes are a deep blue. She wears a futuristic sleeveless bodysuit and a pair of similar looking pants. Maya is very self-assured and enjoys embarrassing villains. She has no weapons, because she doesn't need them.
|
||||||
Nobody recognizes her in her human form and treats just as strange clothed neka, though.
|
Nobody recognizes her in her human form and treats just as strange clothed neka, though.
|
||||||
Her intentions in this world is simple: to enjoy food, to learn something new, maybe kick some asses of bad guys, and to get fun in general.
|
Her intentions in this world is simple: to enjoy food, to learn something new, maybe kick some asses of bad guys, and to get fun in general.
|
||||||
|
|
@ -67,11 +74,14 @@ export const START_PROMPT = p`
|
||||||
Write a novel using information above as a reference. Make sure to follow the lore exactly and avoid cliffhangers.
|
Write a novel using information above as a reference. Make sure to follow the lore exactly and avoid cliffhangers.
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const CONTINUE_PROPMT = (prompt: string, isStart = false) => p`
|
export const CONTINUE_PROPMT = (prompt?: string, isStart = false) =>
|
||||||
This is a description of how you should ${isStart ? 'start' : 'continue'} this story: ${prompt}
|
prompt?.trim()
|
||||||
|
? p`
|
||||||
|
This is a description of how you should ${isStart ? 'start' : 'continue'} this story: ${prompt.trim()}
|
||||||
|
|
||||||
Remember that this story should be infinite and go forever. Avoid cliffhangers and pauses.
|
Remember that this story should be infinite and go forever. Avoid cliffhangers and pauses.
|
||||||
`;
|
`
|
||||||
|
: `Continue the story forward. Avoid cliffhangers and pauses.`;
|
||||||
|
|
||||||
export const LLAMA_TEMPLATE = `{% for message in messages %}{% set content = '<|start_header_id|>' + message['role'] + '<|end_header_id|>\n\n'+ message['content'] | trim + '<|eot_id|>' %}{{ content }}{% endfor %}{% if add_generation_prompt %}{{ '<|start_header_id|>assistant<|end_header_id|>\n\n' }}{% endif %}`;
|
export const LLAMA_TEMPLATE = `{% for message in messages %}{% set content = '<|start_header_id|>' + message['role'] + '<|end_header_id|>\n\n'+ message['content'] | trim + '<|eot_id|>' %}{{ content }}{% endfor %}{% if add_generation_prompt %}{{ '<|start_header_id|>assistant<|end_header_id|>\n\n' }}{% endif %}`;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
import { createContext } from "preact";
|
import { createContext } from "preact";
|
||||||
import { useEffect, useMemo, useState } from "preact/hooks";
|
import { useEffect, useMemo, useState } from "preact/hooks";
|
||||||
import { compilePrompt, type IMessage } from "./messages";
|
import { compilePrompt, trimSentence, type IMessage } from "./messages";
|
||||||
import { LLM } from "./llm";
|
import { LLM } from "./llm";
|
||||||
import { loadContext, saveContext, type IContext } from "./globalConfig";
|
import { loadContext, saveContext, type IContext } from "./globalConfig";
|
||||||
|
|
||||||
import messageSound from './assets/message.mp3';
|
import messageSound from './assets/message.mp3';
|
||||||
|
import { CONTINUE_PROPMT } from "./const";
|
||||||
|
|
||||||
export interface IActions {
|
export interface IActions {
|
||||||
setConnectionUrl: (url: string) => void;
|
setConnectionUrl: (url: string) => void;
|
||||||
|
|
@ -13,8 +14,9 @@ export 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, triggerNext?: boolean) => void;
|
||||||
deleteMessage: (index: number) => void;
|
deleteMessage: (index: number) => void;
|
||||||
|
setCurrentSwipe: (index: number, swipe: number) => void;
|
||||||
|
|
||||||
continueMessage: () => void;
|
continueMessage: () => void;
|
||||||
}
|
}
|
||||||
|
|
@ -44,58 +46,88 @@ export const GlobalContextProvider = ({ children }: { children?: any }) => {
|
||||||
]);
|
]);
|
||||||
setTriggerNext(triggerNext);
|
setTriggerNext(triggerNext);
|
||||||
},
|
},
|
||||||
editMessage: (index, content) => setMessages(messages => (
|
editMessage: (index, content, triggerNext = false) => {
|
||||||
messages.map(
|
setMessages(messages =>
|
||||||
(m, i) => ({
|
messages.map(
|
||||||
...m,
|
(m, i) => ({
|
||||||
swipes: i === index
|
...m,
|
||||||
? m.swipes.map((s, si) => (si === m.currentSwipe ? { content } : s))
|
swipes: i === index
|
||||||
: m.swipes
|
? m.swipes.map((s, si) => (si === m.currentSwipe ? { content } : s))
|
||||||
})
|
: m.swipes
|
||||||
)
|
})
|
||||||
)),
|
)
|
||||||
|
);
|
||||||
|
setTriggerNext(triggerNext);
|
||||||
|
},
|
||||||
deleteMessage: (index) => setMessages(messages =>
|
deleteMessage: (index) => setMessages(messages =>
|
||||||
messages.filter((_, i) => i !== index)
|
messages.filter((_, i) => i !== index)
|
||||||
),
|
),
|
||||||
|
setCurrentSwipe: (index, currentSwipe) => {
|
||||||
|
let shouldTrigger = false;
|
||||||
|
|
||||||
|
setMessages(messages =>
|
||||||
|
messages.map(
|
||||||
|
(message, i) => {
|
||||||
|
if (i === index) {
|
||||||
|
const swipes = message.swipes.slice();
|
||||||
|
const latestSwipe = swipes.at(-1);
|
||||||
|
if (currentSwipe >= swipes.length) {
|
||||||
|
if (latestSwipe.content.length > 0) {
|
||||||
|
currentSwipe = swipes.length;
|
||||||
|
swipes.push({ content: '' });
|
||||||
|
} else {
|
||||||
|
currentSwipe = swipes.length - 1;
|
||||||
|
}
|
||||||
|
shouldTrigger = true;
|
||||||
|
} else while (currentSwipe < 0) {
|
||||||
|
currentSwipe += swipes.length;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...message, swipes, currentSwipe
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
setTriggerNext(shouldTrigger);
|
||||||
|
},
|
||||||
continueMessage: () => setTriggerNext(true),
|
continueMessage: () => setTriggerNext(true),
|
||||||
}), []);
|
}), []);
|
||||||
|
|
||||||
useEffect(() => void (async () => {
|
useEffect(() => void (async () => {
|
||||||
if (triggerNext) {
|
if (triggerNext) {
|
||||||
setTriggerNext(false);
|
setTriggerNext(false);
|
||||||
const lastMessage = messages.at(-1);
|
const promptMessages = messages.slice();
|
||||||
const isContinue = lastMessage?.role === 'assistant';
|
const lastMessage = promptMessages.at(-1);
|
||||||
|
const isAssistantLast = lastMessage?.role === 'assistant';
|
||||||
|
const isRegen = isAssistantLast && !lastMessage?.swipes[lastMessage.currentSwipe].content;
|
||||||
|
const isContinue = isAssistantLast && !isRegen;
|
||||||
|
|
||||||
let promptMessages = isContinue ? messages.slice(0, -1) : messages;
|
let messageId = promptMessages.length - 1;
|
||||||
|
let text: string = '';
|
||||||
|
|
||||||
let prompt = await compilePrompt(promptMessages);
|
|
||||||
let messageId: number;
|
|
||||||
let text: string;
|
|
||||||
let generatedLength = 0;
|
|
||||||
if (isContinue) {
|
if (isContinue) {
|
||||||
messageId = messages.length - 1;
|
promptMessages.push({ role: 'user', currentSwipe: 0, swipes: [{ content: CONTINUE_PROPMT() }] });
|
||||||
text = lastMessage?.swipes[lastMessage.currentSwipe].content;
|
}
|
||||||
prompt += text;
|
|
||||||
} else {
|
const prompt = await compilePrompt(promptMessages, { rawUser: isContinue });
|
||||||
messageId = messages.length;
|
|
||||||
text = '';
|
if (!isRegen) {
|
||||||
actions.addMessage('', 'assistant');
|
actions.addMessage('', 'assistant');
|
||||||
|
messageId++;
|
||||||
}
|
}
|
||||||
for (let attempt = 0; attempt < 2; attempt++) {
|
|
||||||
for await (const chunk of LLM.generate(prompt)) {
|
for await (const chunk of LLM.generate(prompt)) {
|
||||||
text += chunk;
|
text += chunk;
|
||||||
generatedLength += chunk.trim().length;
|
actions.editMessage(messageId, text);
|
||||||
actions.editMessage(messageId, text);
|
|
||||||
}
|
|
||||||
if (generatedLength > 100) {
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
text = text.trim() + '\n\n';
|
|
||||||
promptMessages.push({ role: 'assistant', currentSwipe: 0, swipes: [{ content: text.trim() }] });
|
|
||||||
promptMessages.push({ role: 'user', currentSwipe: 0, swipes: [{ content: '(continue)' }] });
|
|
||||||
prompt = await compilePrompt(promptMessages, {rawUser: true});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
text = trimSentence(text);
|
||||||
|
actions.editMessage(messageId, text);
|
||||||
|
|
||||||
messageSound.currentTime = 0;
|
messageSound.currentTime = 0;
|
||||||
messageSound.play();
|
messageSound.play();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ export const compilePrompt = async (messages: IMessage[], { rawUser }: ICompileA
|
||||||
const lastUserMessage = userMessages.at(-1);
|
const lastUserMessage = userMessages.at(-1);
|
||||||
let userPrompt: string | undefined = lastUserMessage?.swipes[lastUserMessage.currentSwipe].content;
|
let userPrompt: string | undefined = lastUserMessage?.swipes[lastUserMessage.currentSwipe].content;
|
||||||
if (!rawUser && userPrompt) {
|
if (!rawUser && userPrompt) {
|
||||||
userPrompt = CONTINUE_PROPMT(userPrompt, story.length > 0);
|
userPrompt = CONTINUE_PROPMT(userPrompt, story.length === 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
const templateMessages: ITemplateMessage[] = [
|
const templateMessages: ITemplateMessage[] = [
|
||||||
|
|
@ -110,3 +110,27 @@ export const formatMessage = (message: string): string => {
|
||||||
|
|
||||||
return resultHTML;
|
return resultHTML;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const trimSentence = (text: string): string => {
|
||||||
|
let latestEnd = -1;
|
||||||
|
let latestPairEnd = text.length;
|
||||||
|
for (const end of '.!?;…*"`)}]\n') {
|
||||||
|
latestEnd = Math.max(latestEnd, text.lastIndexOf(end));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const char of '*"`') {
|
||||||
|
const idx = text.lastIndexOf(char);
|
||||||
|
|
||||||
|
const match = text.match(new RegExp(`[${char}]`, 'g'));
|
||||||
|
if (match && match.length % 2 !== 0) {
|
||||||
|
latestPairEnd = Math.min(latestPairEnd, idx - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
latestEnd = Math.min(latestEnd, latestPairEnd);
|
||||||
|
|
||||||
|
if (latestEnd > 0) {
|
||||||
|
text = text.slice(0, latestEnd + 1);
|
||||||
|
}
|
||||||
|
return text.trimEnd();
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue