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": { "dependencies": {
"@huggingface/jinja": "0.3.1", "@huggingface/jinja": "0.3.1",
"@inquirer/select": "2.3.10", "@inquirer/select": "2.3.10",
"ace-builds": "1.36.3",
"classnames": "2.5.1", "classnames": "2.5.1",
"preact": "10.22.0" "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 { @keyframes swipe-from-left {
0% { 0% {
position: relative; 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 type { JSX } from "preact/jsx-runtime"
import { useIsVisible } from '@common/hooks/useIsVisible';
export const AutoTextarea = (props: JSX.HTMLAttributes<HTMLTextAreaElement>) => { export const AutoTextarea = (props: JSX.HTMLAttributes<HTMLTextAreaElement>) => {
const { value } = props; const { value } = props;
const ref = useRef<HTMLTextAreaElement>(null); const ref = useRef<HTMLTextAreaElement>(null);
const [isVisible, setVisible] = useState(false); const isVisible = useIsVisible(ref, true);
useEffect(() => { useEffect(() => {
if (ref.current) { if (ref.current) {
@ -15,20 +17,7 @@ export const AutoTextarea = (props: JSX.HTMLAttributes<HTMLTextAreaElement>) =>
} }
}, [value, isVisible]); }, [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} /> return <textarea {...props} ref={ref} />
}; };

View File

@ -37,7 +37,3 @@
overflow: hidden; 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 { useBool } from "@common/hooks/useBool";
import { Modal } from "@common/components/modal/modal"; import { Modal } from "@common/components/modal/modal";
import { StateContext } from "../../contexts/state"; import { StateContext } from "../../contexts/state";
import { LLMContext } from "../../contexts/llm"; import { LLMContext } from "../../contexts/llm";
import { MiniChat } from "../minichat/minichat"; import { MiniChat } from "../minichat/minichat";
import { AutoTextarea } from "../autoTextarea";
import styles from './header.module.css'; import styles from './header.module.css';
import { DOMTools } from "../../dom"; import { Ace } from "../ace";
import { AutoTextarea } from "../autoTextarea";
export const Header = () => { export const Header = () => {
const { getContextLength } = useContext(LLMContext); const { getContextLength } = useContext(LLMContext);
@ -95,7 +95,7 @@ export const Header = () => {
<AutoTextarea value={systemPrompt} onInput={setSystemPrompt} /> <AutoTextarea value={systemPrompt} onInput={setSystemPrompt} />
<hr /> <hr />
<h4 class={styles.modalTitle}>User prompt template</h4> <h4 class={styles.modalTitle}>User prompt template</h4>
<AutoTextarea value={userPrompt} onInput={setUserPrompt} class={styles.template} /> <Ace value={userPrompt} onInput={setUserPrompt} />
<hr /> <hr />
<h4 class={styles.modalTitle}>Banned phrases</h4> <h4 class={styles.modalTitle}>Banned phrases</h4>
<AutoTextarea <AutoTextarea

View File

@ -63,7 +63,15 @@ export const LLMContextProvider = ({ children }: { children?: any }) => {
} = useContext(StateContext); } = useContext(StateContext);
const generating = useBool(false); 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(() => ({ const actions: IActions = useMemo(() => ({
applyChatTemplate: (messages: ITemplateMessage[], templateString: string, eosToken = '</s>') => { applyChatTemplate: (messages: ITemplateMessage[], templateString: string, eosToken = '</s>') => {

View File

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