1
0
Fork 0

Global edit tools

This commit is contained in:
Pabloader 2026-11-02 13:58:33 +00:00
parent 8acac07c04
commit d5481e1a13
3 changed files with 19 additions and 43 deletions

View File

@ -3,6 +3,7 @@
}
.root {
position: relative;
white-space: pre-wrap;
outline: none;
word-wrap: break-word;

View File

@ -1,4 +1,4 @@
import { useEffect, useRef, useState } from "preact/hooks";
import { useEffect, useRef } from "preact/hooks";
import type { JSX } from "preact";
import clsx from "clsx";
import styles from "../assets/content-editable.module.css";
@ -9,6 +9,7 @@ type Props = Omit<JSX.HTMLAttributes<HTMLDivElement>, 'value' | 'onInput'> & {
placeholder?: string;
autoLines?: boolean;
highlight?: boolean;
disabled?: boolean;
onInput?: JSX.EventHandler<JSX.TargetedInputEvent<HTMLDivElement>>;
};
@ -83,6 +84,7 @@ export const ContentEditable = ({
placeholder,
autoLines,
highlight: enableHighlight,
disabled,
onInput,
class: externalClass,
...props
@ -118,10 +120,10 @@ export const ContentEditable = ({
<div
ref={ref}
{...props}
contentEditable
contentEditable={!disabled}
data-placeholder={value?.replaceAll('\n', '').length ? undefined : placeholder}
class={clsx(styles.root, autoLines && styles.autoLines, externalClass)}
onInput={handleInput}
class={clsx(styles.root, autoLines && styles.autoLines, externalClass, disabled && styles.disabled)}
onInput={disabled ? undefined : handleInput}
/>
);
};

View File

@ -101,7 +101,7 @@ export namespace Tools {
appState.dispatch({
type: 'ADD_CHARACTER',
worldId: appState.currentWorld.id,
storyId: appState.currentStory.id,
storyId: args.world ? null : appState.currentStory.id,
character: {
id: crypto.randomUUID(),
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)' }
)),
world: Type.Optional(Type.Boolean({ description: "If `true`, entry would be visible to all stories under that world" })),
}),
}),
'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.`;
}
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());
if (existingRelationIndex !== -1) {
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" }),
target_name: Type.String({ description: "The target character's full name" }),
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({
@ -244,7 +246,7 @@ export namespace Tools {
appState.dispatch({
type: 'ADD_LOCATION',
worldId: appState.currentWorld.id,
storyId: appState.currentStory.id,
storyId: args.world ? null : appState.currentStory.id,
location: {
id: crypto.randomUUID(),
name: args.name.trim(),
@ -265,6 +267,7 @@ export namespace Tools {
scale: Type.Optional(Type.Enum(VALID_SCALES, {
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({
@ -289,7 +292,7 @@ export namespace Tools {
appState.dispatch({
type: 'ADD_LORE_ENTRY',
worldId: appState.currentWorld.id,
storyId: appState.currentStory.id,
storyId: args.world ? null : appState.currentStory.id,
entry: {
id: crypto.randomUUID(),
title: args.title.trim(),
@ -304,6 +307,7 @@ export namespace Tools {
parameters: Type.Object({
title: Type.String({ description: "The lore entry's title" }),
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({
@ -324,42 +328,11 @@ export namespace Tools {
// Append mode: when old_text is not provided, append new_text
if (args.old_text == null) {
if (!isScratchpad) {
const isAppendToStory = (tc: LLM.ToolCall) => {
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;
const lines = args.new_text.split(/\n+/ig).filter(l => l.trim());
const numLines = lines.length;
let cropped = false;
if (!isScratchpad && numLines > LINES_LIMIT) {
const lines = args.new_text.split('\n');
let kept = 0;
const croppedLines: string[] = [];
for (const line of lines) {
@ -373,13 +346,13 @@ export namespace Tools {
cropped = true;
}
dispatchEdit(currentText + '\n' + args.new_text);
dispatchEdit(currentText.trim() + '\n' + args.new_text.trim());
appState.dispatch({ type: 'SET_CURRENT_TAB', tab });
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!`
: `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;
}