1
0
Fork 0

AIStory: ace template editor

This commit is contained in:
Pabloader 2024-11-02 14:53:35 +00:00
parent a4b8883473
commit eed4f492cc
10 changed files with 106 additions and 38 deletions

BIN
bun.lockb

Binary file not shown.

View File

@ -10,6 +10,7 @@
"dependencies": {
"@huggingface/jinja": "0.3.1",
"@inquirer/select": "2.3.10",
"ace-builds": "1.36.3",
"classnames": "2.5.1",
"preact": "10.22.0"
},

View File

@ -0,0 +1,22 @@
import { useEffect, useState, type Ref } from "preact/hooks";
export const useIsVisible = (ref: Ref<HTMLElement>, onlyFirst = false) => {
const [isVisible, setVisible] = useState(false);
useEffect(() => {
if (ref.current) {
const observer = new IntersectionObserver(([entry]) => {
setVisible(entry.isIntersecting);
if (entry.isIntersecting && onlyFirst) {
observer.disconnect();
}
});
observer.observe(ref.current);
return () => observer.disconnect();
}
}, [ref.current, onlyFirst]);
return isVisible;
}

View File

@ -115,6 +115,11 @@ body {
}
}
.ace_editor {
background-color: var(--backgroundColorDark) !important;
border: var(--border) !important;
}
@keyframes swipe-from-left {
0% {
position: relative;

View File

@ -0,0 +1,59 @@
import { useEffect, useMemo, useRef } from "preact/hooks";
import ace from "ace-builds";
import { useIsVisible } from "@common/hooks/useIsVisible";
import "ace-builds/src-noconflict/mode-django";
import "ace-builds/src-noconflict/theme-terminal";
interface IAceProps {
value: string;
onInput: (e: InputEvent | string) => void;
}
export const Ace = ({ value, onInput }: IAceProps) => {
const ref = useRef<HTMLDivElement>(null);
const isVisible = useIsVisible(ref);
const editor = useMemo(() => {
if (ref.current) {
const e = ace.edit(ref.current, {
theme: 'ace/theme/terminal',
mode: 'ace/mode/django',
showGutter: false,
showPrintMargin: false,
highlightActiveLine: false,
displayIndentGuides: false,
fontSize: 16,
maxLines: Infinity,
wrap: "free",
});
return e;
}
}, [isVisible]);
useEffect(() => {
if (editor) {
if (editor.getValue() !== value) {
const pos = editor.getCursorPosition();
editor.setValue(value);
editor.selection.clearSelection();
editor.moveCursorToPosition(pos);
}
}
}, [editor, value]);
useEffect(() => {
if (onInput && editor) {
const e = editor;
const handler = () => onInput(e.getValue());
e.on('input', handler);
return () => e.off('input', handler);
}
}, [editor, onInput]);
return (
<div ref={ref} />
);
}

View File

@ -1,10 +1,12 @@
import { useEffect, useRef, useState } from "preact/hooks";
import { useEffect, useRef } from "preact/hooks";
import type { JSX } from "preact/jsx-runtime"
import { useIsVisible } from '@common/hooks/useIsVisible';
export const AutoTextarea = (props: JSX.HTMLAttributes<HTMLTextAreaElement>) => {
const { value } = props;
const ref = useRef<HTMLTextAreaElement>(null);
const [isVisible, setVisible] = useState(false);
const isVisible = useIsVisible(ref, true);
useEffect(() => {
if (ref.current) {
@ -15,20 +17,7 @@ export const AutoTextarea = (props: JSX.HTMLAttributes<HTMLTextAreaElement>) =>
}
}, [value, isVisible]);
useEffect(() => {
if (ref.current) {
const observer = new IntersectionObserver(([entry]) => {
setVisible(entry.isIntersecting);
if (entry.isIntersecting) {
observer.disconnect();
}
});
observer.observe(ref.current);
return () => observer.disconnect();
}
}, [ref.current]);
return <textarea {...props} ref={ref} />
};

View File

@ -37,7 +37,3 @@
overflow: hidden;
}
}
.template {
font-family: 'Ubuntu Mono', 'Courier New', Courier, monospace;
}

View File

@ -1,14 +1,14 @@
import { useCallback, useContext, useEffect, useMemo, useRef, useState } from "preact/hooks";
import { useCallback, useContext, useEffect, useMemo, useState } from "preact/hooks";
import { useBool } from "@common/hooks/useBool";
import { Modal } from "@common/components/modal/modal";
import { StateContext } from "../../contexts/state";
import { LLMContext } from "../../contexts/llm";
import { MiniChat } from "../minichat/minichat";
import { AutoTextarea } from "../autoTextarea";
import styles from './header.module.css';
import { DOMTools } from "../../dom";
import { AutoTextarea } from "../autoTextarea";
import { Ace } from "../ace";
export const Header = () => {
const { getContextLength } = useContext(LLMContext);
@ -95,7 +95,7 @@ export const Header = () => {
<AutoTextarea value={systemPrompt} onInput={setSystemPrompt} />
<hr />
<h4 class={styles.modalTitle}>User prompt template</h4>
<AutoTextarea value={userPrompt} onInput={setUserPrompt} class={styles.template} />
<Ace value={userPrompt} onInput={setUserPrompt} />
<hr />
<h4 class={styles.modalTitle}>Banned phrases</h4>
<AutoTextarea

View File

@ -63,7 +63,15 @@ export const LLMContextProvider = ({ children }: { children?: any }) => {
} = useContext(StateContext);
const generating = useBool(false);
const userPromptTemplate = useMemo(() => new Template(userPrompt), [userPrompt]);
const userPromptTemplate = useMemo(() => {
try {
return new Template(userPrompt)
} catch {
return {
render: () => userPrompt,
}
}
}, [userPrompt]);
const actions: IActions = useMemo(() => ({
applyChatTemplate: (messages: ITemplateMessage[], templateString: string, eosToken = '</s>') => {

View File

@ -48,20 +48,8 @@ export const loadContext = (): IContext => {
input: '',
systemPrompt: 'You are creative writer. Write a story based on the world description below.',
lore: '',
userPrompt: `
{%- if prompt -%}
{%- if isStart -%}
Start
{%- else -%}
Continue
{%- endif %} this story, taking information into account: {{ prompt | trim }}
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 -%}
`.trim(),
userPrompt: `{% if prompt %}{% if isStart %}Start{% else %}Continue{% endif %} this story, taking information into account: {{ prompt | trim }}
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 %}`,
bannedWords: [],
messages: [],
triggerNext: false,