diff --git a/docs/img/0.30.0/editor-has-messages.png b/docs/img/0.30.0/editor-has-messages.png new file mode 100644 index 00000000..c2e32ead Binary files /dev/null and b/docs/img/0.30.0/editor-has-messages.png differ diff --git a/docs/img/0.30.0/editor-revision-history.png b/docs/img/0.30.0/editor-revision-history.png new file mode 100644 index 00000000..236c6420 Binary files /dev/null and b/docs/img/0.30.0/editor-revision-history.png differ diff --git a/docs/img/0.30.0/editor-revision-issue-identified.png b/docs/img/0.30.0/editor-revision-issue-identified.png new file mode 100644 index 00000000..16cb251e Binary files /dev/null and b/docs/img/0.30.0/editor-revision-issue-identified.png differ diff --git a/docs/img/0.30.0/editor-revision-rewriting.png b/docs/img/0.30.0/editor-revision-rewriting.png new file mode 100644 index 00000000..1cd19b28 Binary files /dev/null and b/docs/img/0.30.0/editor-revision-rewriting.png differ diff --git a/docs/img/0.30.0/editor-revision-settings-dedupe.png b/docs/img/0.30.0/editor-revision-settings-dedupe.png index 5e7bc316..a9f5641c 100644 Binary files a/docs/img/0.30.0/editor-revision-settings-dedupe.png and b/docs/img/0.30.0/editor-revision-settings-dedupe.png differ diff --git a/docs/img/0.30.0/editor-revision-settings.png b/docs/img/0.30.0/editor-revision-settings.png index 5203725b..3ae7e025 100644 Binary files a/docs/img/0.30.0/editor-revision-settings.png and b/docs/img/0.30.0/editor-revision-settings.png differ diff --git a/docs/user-guide/agents/editor/settings.md b/docs/user-guide/agents/editor/settings.md index 564096f6..0d80242f 100644 --- a/docs/user-guide/agents/editor/settings.md +++ b/docs/user-guide/agents/editor/settings.md @@ -49,6 +49,10 @@ This means it comes at a noticable delay IF it finds issues, but the improvement Check this to enable revision. +##### Automatic Revision + +Check this to enable automatic revision - this will analyze each incoming actor or narrator message and attempt to fix it if there are issues. + ##### Revision Method Which method to use to fix issues. diff --git a/src/talemate/agents/editor/__init__.py b/src/talemate/agents/editor/__init__.py index d9cbae77..7a1b68e1 100644 --- a/src/talemate/agents/editor/__init__.py +++ b/src/talemate/agents/editor/__init__.py @@ -15,6 +15,7 @@ import talemate.agents.editor.nodes from talemate.agents.memory.rag import MemoryRAGMixin from talemate.agents.editor.revision import RevisionMixin +from talemate.agents.editor.websocket_handler import EditorWebsocketHandler if TYPE_CHECKING: from talemate.agents.conversation import ConversationAgentEmission @@ -38,6 +39,7 @@ class EditorAgent( agent_type = "editor" verbose_name = "Editor" + websocket_handler = EditorWebsocketHandler @classmethod def init_actions(cls) -> dict[str, AgentAction]: diff --git a/src/talemate/agents/editor/revision.py b/src/talemate/agents/editor/revision.py index 1d2f42b6..5677a400 100644 --- a/src/talemate/agents/editor/revision.py +++ b/src/talemate/agents/editor/revision.py @@ -1,6 +1,7 @@ from typing import TYPE_CHECKING import structlog import uuid +import pydantic import re from talemate.agents.base import ( set_processing, @@ -52,16 +53,30 @@ detect_bad_prose_condition = AgentActionConditional( value=True, ) +class RevisionContextState(pydantic.BaseModel): + message_id: int | None = None + revision_disabled_context = ContextVar("revision_disabled", default=False) +revision_context = ContextVar("revision_context", default=RevisionContextState()) + class RevisionDisabled: - def __enter__(self): self.token = revision_disabled_context.set(True) def __exit__(self, exc_type, exc_value, traceback): revision_disabled_context.reset(self.token) +class RevisionContext: + def __init__(self, message_id: int | None = None): + self.message_id = message_id + + def __enter__(self): + self.token = revision_context.set(RevisionContextState(message_id=self.message_id)) + + def __exit__(self, exc_type, exc_value, traceback): + revision_context.reset(self.token) + class RevisionMixin: """ @@ -79,6 +94,13 @@ class RevisionMixin: icon="mdi-typewriter", description="Remove / rewrite content based on criteria and instructions.", config={ + "automatic_revision": AgentActionConfig( + type="bool", + label="Automatic revision", + description="Enable / Disable automatic revision.", + value=True, + quick_toggle=True, + ), "revision_method": AgentActionConfig( type="text", label="Revision method", @@ -180,6 +202,10 @@ class RevisionMixin: def revision_enabled(self): return self.actions["revision"].enabled + @property + def revision_automatic_enabled(self): + return self.actions["revision"].config["automatic_revision"].value + @property def revision_method(self): return self.actions["revision"].config["revision_method"].value @@ -234,7 +260,7 @@ class RevisionMixin: Called when a conversation or narrator message is generated """ - if not self.revision_enabled: + if not self.revision_enabled or not self.revision_automatic_enabled: return try: @@ -262,9 +288,12 @@ class RevisionMixin: scene:"Scene" = self.scene + ctx = revision_context.get() + messages = scene.collect_messages( typ=["narrator", "character"], - max_messages=self.revision_repetition_range + max_messages=self.revision_repetition_range, + start_idx=scene.message_index(ctx.message_id) -1 if ctx.message_id else None ) return_messages = [] @@ -600,7 +629,7 @@ class RevisionMixin: if detect_bad_prose: bad_prose_identified = await self.revision_detect_bad_prose(text) for identified in bad_prose_identified: - issues.append(f"Bad prose: `{identified['phrase']}` (reason: {identified['reason']}, instructions: {identified['instructions']})") + issues.append(f"Bad prose: `{identified['phrase']}` (reason: {identified['reason']}, matched: {identified['matched']}, instructions: {identified['instructions']})") log.debug("revision_rewrite: bad_prose_identified", bad_prose_identified=bad_prose_identified) # Step 3 - Check if we have enough issues to warrant a rewrite diff --git a/src/talemate/prompts/templates/editor/revision-analysis.jinja2 b/src/talemate/prompts/templates/editor/revision-analysis.jinja2 index 01eba830..bbf9d588 100644 --- a/src/talemate/prompts/templates/editor/revision-analysis.jinja2 +++ b/src/talemate/prompts/templates/editor/revision-analysis.jinja2 @@ -8,7 +8,7 @@ <|SECTION:ISSUE {{ issues }}: REPEATED TEXT|> These sentences have been classified as repetition and must either be substantially rewritten or removed. When removing a sentence, substitute something else, that's meaningful to the story and context. -{% for repeat in repetition %}- `{{ repeat["text_a"].strip() }}` +{% for repeat in repetition %}- MATCH: `{{ repeat["text_a"].strip() }}` {% endfor %} <|CLOSE_SECTION|> {# end repetition #}{% endif %} @@ -16,8 +16,11 @@ These sentences have been classified as repetition and must either be substantia {% set issues = issues + 1 %} <|SECTION:ISSUE {{ issues }}: UNWANTED PROSE|> These phrases or words have been identified by the director as bad, and MUST be changed accordingly. +Important: These are semantic matches based on meaning, not literal text. Focus on the underlying concept or idea that triggered the match, rather than expecting exact phrase matches. -{% for phrase in bad_prose %}- `{{ phrase.phrase }}` -- {{ phrase.instructions }} +{% for phrase in bad_prose %}- MATCH: `{{ phrase.phrase }}` + - TRIGGER: "{{ phrase.matched }}" + - INSTRUCTIONS: {{ phrase.instructions }} {% endfor %} <|CLOSE_SECTION|> {# end unwanted prose #}{% endif %} diff --git a/src/talemate/tale_mate.py b/src/talemate/tale_mate.py index c9b9ca6d..0409d079 100644 --- a/src/talemate/tale_mate.py +++ b/src/talemate/tale_mate.py @@ -1124,6 +1124,7 @@ class Scene(Emitter): max_iterations: int = 100, max_messages: int | None = None, stop_on_time_passage: bool = False, + start_idx: int | None = None, ): """ Finds all messages in the history that match the given typ and source @@ -1135,7 +1136,11 @@ class Scene(Emitter): messages = [] iterations = 0 collected = 0 - for idx in range(len(self.history) - 1, -1, -1): + + if start_idx is None: + start_idx = len(self.history) - 1 + + for idx in range(start_idx, -1, -1): message = self.history[idx] if (not typ or message.typ in typ) and ( not source or message.source == source diff --git a/talemate_frontend/src/components/CharacterMessage.vue b/talemate_frontend/src/components/CharacterMessage.vue index 75d845b0..0e7809a9 100644 --- a/talemate_frontend/src/components/CharacterMessage.vue +++ b/talemate_frontend/src/components/CharacterMessage.vue @@ -59,6 +59,12 @@ Create Pin + + + mdi-typewriter + Editor Revision + + mdi-source-fork @@ -76,8 +82,48 @@ import { parseText } from '@/utils/textParser'; export default { - props: ['character', 'text', 'color', 'message_id', 'uxLocked', 'isLastMessage'], - inject: ['requestDeleteMessage', 'getWebsocket', 'createPin', 'forkSceneInitiate', 'fixMessageContinuityErrors', 'autocompleteRequest', 'autocompleteInfoMessage', 'getMessageStyle'], + //props: ['character', 'text', 'color', 'message_id', 'uxLocked', 'isLastMessage'], + props: { + character: { + type: String, + required: true, + }, + text: { + type: String, + required: true, + }, + color: { + type: String, + required: true, + }, + message_id: { + type: Number, + required: true, + }, + uxLocked: { + type: Boolean, + required: true, + }, + isLastMessage: { + type: Boolean, + required: true, + }, + editorRevisionsEnabled: { + type: Boolean, + default: false, + }, + }, + inject: [ + 'requestDeleteMessage', + 'getWebsocket', + 'createPin', + 'forkSceneInitiate', + 'fixMessageContinuityErrors', + 'autocompleteRequest', + 'autocompleteInfoMessage', + 'getMessageStyle', + 'reviseMessage', + ], computed: { parts() { return parseText(this.text); diff --git a/talemate_frontend/src/components/NarratorMessage.vue b/talemate_frontend/src/components/NarratorMessage.vue index e6c6ec42..f2b7af81 100644 --- a/talemate_frontend/src/components/NarratorMessage.vue +++ b/talemate_frontend/src/components/NarratorMessage.vue @@ -44,6 +44,12 @@ Create Pin + + + mdi-typewriter + Editor Revision + + @@ -65,8 +71,41 @@ import { parseText } from '@/utils/textParser'; export default { - props: ['text', 'message_id', 'uxLocked', 'isLastMessage'], - inject: ['requestDeleteMessage', 'getWebsocket', 'createPin', 'forkSceneInitiate', 'fixMessageContinuityErrors', 'autocompleteRequest', 'autocompleteInfoMessage', 'getMessageStyle', 'openWorldStateManager'], + // props: ['text', 'message_id', 'uxLocked', 'isLastMessage'], + + props: { + text: { + type: String, + required: true + }, + message_id: { + required: true + }, + uxLocked: { + type: Boolean, + required: true + }, + isLastMessage: { + type: Boolean, + required: true + }, + editorRevisionsEnabled: { + type: Boolean, + default: false, + }, + }, + inject: [ + 'requestDeleteMessage', + 'getWebsocket', + 'createPin', + 'forkSceneInitiate', + 'fixMessageContinuityErrors', + 'autocompleteRequest', + 'autocompleteInfoMessage', + 'getMessageStyle', + 'openWorldStateManager', + 'reviseMessage', + ], computed: { parts() { return parseText(this.text); diff --git a/talemate_frontend/src/components/SceneMessages.vue b/talemate_frontend/src/components/SceneMessages.vue index 945b700f..b17fe43e 100644 --- a/talemate_frontend/src/components/SceneMessages.vue +++ b/talemate_frontend/src/components/SceneMessages.vue @@ -10,7 +10,7 @@
- +
@@ -43,7 +43,7 @@
- +
@@ -99,6 +99,9 @@ export default { type: Boolean, default: false, }, + agentStatus: { + type: Object, + } }, components: { CharacterMessage, @@ -122,6 +125,11 @@ export default { }, } }, + computed: { + editorRevisionsEnabled() { + return this.agentStatus && this.agentStatus.editor && this.agentStatus.editor.actions && this.agentStatus.editor.actions["revision"] && this.agentStatus.editor.actions["revision"].enabled; + } + }, inject: ['getWebsocket', 'registerMessageHandler', 'setWaitingForInput'], provide() { return { @@ -131,6 +139,7 @@ export default { forkSceneInitiate: this.forkSceneInitiate, getMessageColor: this.getMessageColor, getMessageStyle: this.getMessageStyle, + reviseMessage: this.reviseMessage, } }, methods: { @@ -267,6 +276,14 @@ export default { })); }, + reviseMessage(message_id) { + this.getWebsocket().send(JSON.stringify({ + type: 'editor', + action: 'request_revision', + message_id: message_id, + })); + }, + handleMessage(data) { var i; diff --git a/talemate_frontend/src/components/TalemateApp.vue b/talemate_frontend/src/components/TalemateApp.vue index 8833eebb..b71f47e9 100644 --- a/talemate_frontend/src/components/TalemateApp.vue +++ b/talemate_frontend/src/components/TalemateApp.vue @@ -186,7 +186,12 @@
- +