Global edit tools
This commit is contained in:
parent
8acac07c04
commit
d5481e1a13
|
|
@ -3,6 +3,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.root {
|
.root {
|
||||||
|
position: relative;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
outline: none;
|
outline: none;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { useEffect, useRef, useState } from "preact/hooks";
|
import { useEffect, useRef } from "preact/hooks";
|
||||||
import type { JSX } from "preact";
|
import type { JSX } from "preact";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import styles from "../assets/content-editable.module.css";
|
import styles from "../assets/content-editable.module.css";
|
||||||
|
|
@ -9,6 +9,7 @@ type Props = Omit<JSX.HTMLAttributes<HTMLDivElement>, 'value' | 'onInput'> & {
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
autoLines?: boolean;
|
autoLines?: boolean;
|
||||||
highlight?: boolean;
|
highlight?: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
onInput?: JSX.EventHandler<JSX.TargetedInputEvent<HTMLDivElement>>;
|
onInput?: JSX.EventHandler<JSX.TargetedInputEvent<HTMLDivElement>>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -83,6 +84,7 @@ export const ContentEditable = ({
|
||||||
placeholder,
|
placeholder,
|
||||||
autoLines,
|
autoLines,
|
||||||
highlight: enableHighlight,
|
highlight: enableHighlight,
|
||||||
|
disabled,
|
||||||
onInput,
|
onInput,
|
||||||
class: externalClass,
|
class: externalClass,
|
||||||
...props
|
...props
|
||||||
|
|
@ -118,10 +120,10 @@ export const ContentEditable = ({
|
||||||
<div
|
<div
|
||||||
ref={ref}
|
ref={ref}
|
||||||
{...props}
|
{...props}
|
||||||
contentEditable
|
contentEditable={!disabled}
|
||||||
data-placeholder={value?.replaceAll('\n', '').length ? undefined : placeholder}
|
data-placeholder={value?.replaceAll('\n', '').length ? undefined : placeholder}
|
||||||
class={clsx(styles.root, autoLines && styles.autoLines, externalClass)}
|
class={clsx(styles.root, autoLines && styles.autoLines, externalClass, disabled && styles.disabled)}
|
||||||
onInput={handleInput}
|
onInput={disabled ? undefined : handleInput}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,7 @@ export namespace Tools {
|
||||||
appState.dispatch({
|
appState.dispatch({
|
||||||
type: 'ADD_CHARACTER',
|
type: 'ADD_CHARACTER',
|
||||||
worldId: appState.currentWorld.id,
|
worldId: appState.currentWorld.id,
|
||||||
storyId: appState.currentStory.id,
|
storyId: args.world ? null : appState.currentStory.id,
|
||||||
character: {
|
character: {
|
||||||
id: crypto.randomUUID(),
|
id: crypto.randomUUID(),
|
||||||
name: args.name.trim(),
|
name: args.name.trim(),
|
||||||
|
|
@ -134,6 +134,7 @@ export namespace Tools {
|
||||||
}),
|
}),
|
||||||
{ description: 'Optional list of relationships with other characters (allowed only when adding a new character)' }
|
{ description: 'Optional list of relationships with other characters (allowed only when adding a new character)' }
|
||||||
)),
|
)),
|
||||||
|
world: Type.Optional(Type.Boolean({ description: "If `true`, entry would be visible to all stories under that world" })),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
'set_character_relation': tool({
|
'set_character_relation': tool({
|
||||||
|
|
@ -150,7 +151,7 @@ export namespace Tools {
|
||||||
return `Error: Target character "${args.target_name.trim()}" not found, please add it first.`;
|
return `Error: Target character "${args.target_name.trim()}" not found, please add it first.`;
|
||||||
}
|
}
|
||||||
const inStory = appState.currentStory.characters.some(c => c.id === character.id);
|
const inStory = appState.currentStory.characters.some(c => c.id === character.id);
|
||||||
const storyId = inStory ? appState.currentStory.id : null;
|
const storyId = inStory && !args.world ? appState.currentStory.id : null;
|
||||||
const existingRelationIndex = character.relations.findIndex(r => r.name === args.target_name.trim());
|
const existingRelationIndex = character.relations.findIndex(r => r.name === args.target_name.trim());
|
||||||
if (existingRelationIndex !== -1) {
|
if (existingRelationIndex !== -1) {
|
||||||
appState.dispatch({
|
appState.dispatch({
|
||||||
|
|
@ -183,6 +184,7 @@ export namespace Tools {
|
||||||
character_name: Type.String({ description: "The character's full name to add/edit the relation for" }),
|
character_name: Type.String({ description: "The character's full name to add/edit the relation for" }),
|
||||||
target_name: Type.String({ description: "The target character's full name" }),
|
target_name: Type.String({ description: "The target character's full name" }),
|
||||||
relation: Type.String({ description: 'The relationship type (e.g., friend, enemy, sibling)' }),
|
relation: Type.String({ description: 'The relationship type (e.g., friend, enemy, sibling)' }),
|
||||||
|
world: Type.Optional(Type.Boolean({ description: "If `true`, entry would be visible to all stories under that world" })),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
'get_location': tool({
|
'get_location': tool({
|
||||||
|
|
@ -244,7 +246,7 @@ export namespace Tools {
|
||||||
appState.dispatch({
|
appState.dispatch({
|
||||||
type: 'ADD_LOCATION',
|
type: 'ADD_LOCATION',
|
||||||
worldId: appState.currentWorld.id,
|
worldId: appState.currentWorld.id,
|
||||||
storyId: appState.currentStory.id,
|
storyId: args.world ? null : appState.currentStory.id,
|
||||||
location: {
|
location: {
|
||||||
id: crypto.randomUUID(),
|
id: crypto.randomUUID(),
|
||||||
name: args.name.trim(),
|
name: args.name.trim(),
|
||||||
|
|
@ -265,6 +267,7 @@ export namespace Tools {
|
||||||
scale: Type.Optional(Type.Enum(VALID_SCALES, {
|
scale: Type.Optional(Type.Enum(VALID_SCALES, {
|
||||||
description: `Location scale (enum): ${VALID_SCALES.join(', ')}`,
|
description: `Location scale (enum): ${VALID_SCALES.join(', ')}`,
|
||||||
})),
|
})),
|
||||||
|
world: Type.Optional(Type.Boolean({ description: "If `true`, entry would be visible to all stories under that world" })),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
'set_lore_entry': tool({
|
'set_lore_entry': tool({
|
||||||
|
|
@ -289,7 +292,7 @@ export namespace Tools {
|
||||||
appState.dispatch({
|
appState.dispatch({
|
||||||
type: 'ADD_LORE_ENTRY',
|
type: 'ADD_LORE_ENTRY',
|
||||||
worldId: appState.currentWorld.id,
|
worldId: appState.currentWorld.id,
|
||||||
storyId: appState.currentStory.id,
|
storyId: args.world ? null : appState.currentStory.id,
|
||||||
entry: {
|
entry: {
|
||||||
id: crypto.randomUUID(),
|
id: crypto.randomUUID(),
|
||||||
title: args.title.trim(),
|
title: args.title.trim(),
|
||||||
|
|
@ -304,6 +307,7 @@ export namespace Tools {
|
||||||
parameters: Type.Object({
|
parameters: Type.Object({
|
||||||
title: Type.String({ description: "The lore entry's title" }),
|
title: Type.String({ description: "The lore entry's title" }),
|
||||||
text: Type.String({ description: 'The lore entry content.' }),
|
text: Type.String({ description: 'The lore entry content.' }),
|
||||||
|
world: Type.Optional(Type.Boolean({ description: "If `true`, entry would be visible to all stories under that world" })),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
'edit_text': tool({
|
'edit_text': tool({
|
||||||
|
|
@ -324,42 +328,11 @@ export namespace Tools {
|
||||||
|
|
||||||
// Append mode: when old_text is not provided, append new_text
|
// Append mode: when old_text is not provided, append new_text
|
||||||
if (args.old_text == null) {
|
if (args.old_text == null) {
|
||||||
if (!isScratchpad) {
|
const lines = args.new_text.split(/\n+/ig).filter(l => l.trim());
|
||||||
const isAppendToStory = (tc: LLM.ToolCall) => {
|
const numLines = lines.length;
|
||||||
const jsonArgs = JSON.stringify(tc.function.arguments);
|
|
||||||
return tc.function.name === 'edit_text'
|
|
||||||
&& (
|
|
||||||
!jsonArgs.match(/\\?"old_text\\?"/)
|
|
||||||
|| jsonArgs.match(/\\?"old_text\\?":\\?"\\?"/)
|
|
||||||
)
|
|
||||||
&& (
|
|
||||||
!jsonArgs.match(/\\?"target\\?"/)
|
|
||||||
|| jsonArgs.match(/\\?"target\\?":\\?"story\\?"/)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Check that there was a user message since the last edit_text append
|
|
||||||
const messages = appState.currentStory.chatMessages;
|
|
||||||
let hasUserMessageSinceLastAppend = true;
|
|
||||||
for (let i = messages.length - 1; i >= 0; i--) {
|
|
||||||
const m = messages[i];
|
|
||||||
if (m.role === 'user') break;
|
|
||||||
if (m.role === 'assistant' && m.tool_calls?.some(isAppendToStory)) {
|
|
||||||
hasUserMessageSinceLastAppend = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!hasUserMessageSinceLastAppend) {
|
|
||||||
return `Error: You cannot add new text to the story until user approves your last edit. Stop right there.`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const numLines = args.new_text.split('\n').filter(l => l.trim()).length;
|
|
||||||
|
|
||||||
let cropped = false;
|
let cropped = false;
|
||||||
if (!isScratchpad && numLines > LINES_LIMIT) {
|
if (!isScratchpad && numLines > LINES_LIMIT) {
|
||||||
const lines = args.new_text.split('\n');
|
|
||||||
let kept = 0;
|
let kept = 0;
|
||||||
const croppedLines: string[] = [];
|
const croppedLines: string[] = [];
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
|
|
@ -373,13 +346,13 @@ export namespace Tools {
|
||||||
cropped = true;
|
cropped = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatchEdit(currentText + '\n' + args.new_text);
|
dispatchEdit(currentText.trim() + '\n' + args.new_text.trim());
|
||||||
appState.dispatch({ type: 'SET_CURRENT_TAB', tab });
|
appState.dispatch({ type: 'SET_CURRENT_TAB', tab });
|
||||||
let message = cropped
|
let message = cropped
|
||||||
? `Added text:\n${args.new_text.split('\n').filter(l => l.trim()).map(l => '> ' + l).join('\n')}\n\nNote: The rest was cropped due to ${LINES_LIMIT} lines limit!`
|
? `Added text:\n${args.new_text.split('\n').filter(l => l.trim()).map(l => '> ' + l).join('\n')}\n\nNote: The rest was cropped due to ${LINES_LIMIT} lines limit!`
|
||||||
: `Text appended to ${target} successfully.`;
|
: `Text appended to ${target} successfully.`;
|
||||||
|
|
||||||
message += `\nNote: you can't continue nor add more text until user's approval. Stop any attempts and wait.`
|
message += `\nNote: you can't continue nor add more text until user's approval. Stop any attempts and wait, even if you think the work is not finished.`
|
||||||
|
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue