AIStory: ace template editor
This commit is contained in:
parent
a4b8883473
commit
eed4f492cc
|
|
@ -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"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
@keyframes swipe-from-left {
|
||||||
0% {
|
0% {
|
||||||
position: relative;
|
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 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} />
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -36,8 +36,4 @@
|
||||||
textarea {
|
textarea {
|
||||||
overflow: hidden;
|
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 { 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
|
||||||
|
|
|
||||||
|
|
@ -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>') => {
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue