AIStory: ace template editor
This commit is contained in:
parent
a4b8883473
commit
eed4f492cc
|
|
@ -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"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -115,6 +115,11 @@ body {
|
|||
}
|
||||
}
|
||||
|
||||
.ace_editor {
|
||||
background-color: var(--backgroundColorDark) !important;
|
||||
border: var(--border) !important;
|
||||
}
|
||||
|
||||
@keyframes swipe-from-left {
|
||||
0% {
|
||||
position: relative;
|
||||
|
|
|
|||
|
|
@ -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} />
|
||||
);
|
||||
}
|
||||
|
|
@ -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} />
|
||||
};
|
||||
|
|
|
|||
|
|
@ -36,8 +36,4 @@
|
|||
textarea {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.template {
|
||||
font-family: 'Ubuntu Mono', 'Courier New', Courier, monospace;
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>') => {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in New Issue