Global edit tools
This commit is contained in:
parent
8acac07c04
commit
d5481e1a13
|
|
@ -3,6 +3,7 @@
|
|||
}
|
||||
|
||||
.root {
|
||||
position: relative;
|
||||
white-space: pre-wrap;
|
||||
outline: none;
|
||||
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 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}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue