mirror of
https://github.com/vegu-ai/talemate.git
synced 2026-09-01 19:48:52 +02:00
add continue action to narrator messages
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
features:
|
||||
- "Message Revision History: Regenerated AI messages now show a paginator above the message body. Click the arrows to browse previous regenerations; the version you're viewing becomes the canonical one the AI continues from. Lives in the browser session only."
|
||||
improvements:
|
||||
- "Message Revision History: Continuing a character message now creates a navigable revision entry tagged 'Continued', alongside the existing regenerate entries. The pre-continuation text is reachable via the paginator arrows."
|
||||
- "Message Revision History: Continuing a character or narrator message now creates a navigable revision entry tagged 'Continued', alongside the existing regenerate entries. The pre-continuation text is reachable via the paginator arrows. Narrator messages also gain the Continue action on the hover toolbar to match the character-message flow."
|
||||
- "Pydantic Migration: Internal data models across the codebase converted to pydantic for stricter validation. No user-visible behavior changes."
|
||||
- "Character Sheet: Removed the read-only Character Sheet dialog and its button from the World State panel. The Manage character button (World State Manager) already covers viewing and editing character details."
|
||||
- "Message Toolbar: Consolidated the hover toolbar shared by character, narrator, and context-investigation messages into a single component. Action chips now use a filled (tonal) style for better visibility, and the revision chip is labeled with the editor agent's configured revision method — 'Dedupe', 'Unslop', or 'Targeted Rewrite' — instead of the generic 'Editor Revision' label."
|
||||
|
||||
@@ -70,7 +70,6 @@
|
||||
:scene-rev="sceneRev"
|
||||
>
|
||||
<template #extra-actions>
|
||||
<!-- generate continuation -->
|
||||
<v-chip size="x-small" class="ml-2" label color="primary" v-if="!continuing && isLastMessage" variant="tonal" @click="continueConversation" :disabled="uxLocked || appBusy">
|
||||
<v-icon class="mr-1">mdi-fast-forward</v-icon>
|
||||
Continue
|
||||
@@ -92,6 +91,7 @@
|
||||
import { SceneTextParser } from '@/utils/sceneMessageRenderer';
|
||||
import { insertNewlineAtCursor } from '@/utils/textAreaUtils';
|
||||
import { isPrimaryModifier } from '@/utils/keyboardModifiers';
|
||||
import { spliceContinuation } from '@/utils/messageContinuation';
|
||||
import MessageAssetImage from './MessageAssetImage.vue';
|
||||
import MessageAssetMixin from './MessageAssetMixin.js';
|
||||
import RevisionNav from './RevisionNav.vue';
|
||||
@@ -305,24 +305,8 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
// if text ends with a quote and completion starts with a quote, remove the quotes
|
||||
// and insert a period at the end of the current text
|
||||
if (this.text.endsWith('"') && completion.startsWith('"')) {
|
||||
completion = completion.slice(1);
|
||||
let text = this.text.slice(0, -1);
|
||||
this.editing_text = spliceContinuation(this.text, completion);
|
||||
|
||||
// if text does not end with a period, add one
|
||||
if (!text.endsWith('.')) {
|
||||
text += '.';
|
||||
}
|
||||
|
||||
this.editing_text = text + " " + completion;
|
||||
} else {
|
||||
this.editing_text = this.text + completion;
|
||||
}
|
||||
|
||||
// Tag the commit so the echo lands as a new entry on the
|
||||
// slot's revision stack instead of replacing the active one.
|
||||
this.submitEdit({
|
||||
reason: 'continue',
|
||||
mutation_source: 'continue',
|
||||
|
||||
@@ -61,7 +61,18 @@
|
||||
:tts-busy="ttsBusy"
|
||||
:rev="rev"
|
||||
:scene-rev="sceneRev"
|
||||
/>
|
||||
>
|
||||
<template #extra-actions>
|
||||
<v-chip size="x-small" class="ml-2" label color="narrator" v-if="!continuing && isLastMessage" variant="tonal" @click="continueNarration" :disabled="uxLocked || appBusy">
|
||||
<v-icon class="mr-1">mdi-fast-forward</v-icon>
|
||||
Continue
|
||||
</v-chip>
|
||||
<v-chip size="x-small" class="ml-2" label color="narrator" v-if="continuing && isLastMessage" variant="tonal" disabled>
|
||||
<v-progress-circular class="mr-1" size="14" indeterminate="disable-shrink" color="narrator"></v-progress-circular>
|
||||
Continuing...
|
||||
</v-chip>
|
||||
</template>
|
||||
</MessageToolbar>
|
||||
</div>
|
||||
<div v-else>
|
||||
<span class="text-muted text-caption">To edit the intro message open the <v-btn size="x-small" variant="text" color="primary" @click="openWorldStateManager('scene')"><v-icon>mdi-script</v-icon>Scene Editor</v-btn></span>
|
||||
@@ -84,6 +95,7 @@
|
||||
import { SceneTextParser } from '@/utils/sceneMessageRenderer';
|
||||
import { insertNewlineAtCursor } from '@/utils/textAreaUtils';
|
||||
import { isPrimaryModifier } from '@/utils/keyboardModifiers';
|
||||
import { spliceContinuation } from '@/utils/messageContinuation';
|
||||
import MessageAssetImage from './MessageAssetImage.vue';
|
||||
import MessageAssetMixin from './MessageAssetMixin.js';
|
||||
import RevisionNav from './RevisionNav.vue';
|
||||
@@ -211,6 +223,7 @@ export default {
|
||||
return {
|
||||
editing: false,
|
||||
autocompleting: false,
|
||||
continuing: false,
|
||||
editing_text: "",
|
||||
hovered: false,
|
||||
}
|
||||
@@ -245,6 +258,31 @@ export default {
|
||||
)
|
||||
},
|
||||
|
||||
continueNarration() {
|
||||
this.continuing = true;
|
||||
this.autocompleteRequest(
|
||||
{
|
||||
partial: this.text,
|
||||
context: "narrative:continue",
|
||||
},
|
||||
(completion) => {
|
||||
this.continuing = false;
|
||||
|
||||
if (completion.trim() === "") {
|
||||
return;
|
||||
}
|
||||
|
||||
this.editing_text = spliceContinuation(this.text, completion);
|
||||
|
||||
this.submitEdit({
|
||||
reason: 'continue',
|
||||
mutation_source: 'continue',
|
||||
});
|
||||
},
|
||||
this.$refs.textarea
|
||||
)
|
||||
},
|
||||
|
||||
cancelEdit() {
|
||||
this.editing = false;
|
||||
},
|
||||
@@ -261,8 +299,15 @@ export default {
|
||||
this.$refs.textarea.focus();
|
||||
});
|
||||
},
|
||||
submitEdit() {
|
||||
this.getWebsocket().send(JSON.stringify({ type: 'scene_message', action: 'edit', id: this.message_id, text: this.editing_text }));
|
||||
submitEdit(meta = null) {
|
||||
const payload = {
|
||||
...(meta || {}),
|
||||
type: 'scene_message',
|
||||
action: 'edit',
|
||||
id: this.message_id,
|
||||
text: this.editing_text,
|
||||
};
|
||||
this.getWebsocket().send(JSON.stringify(payload));
|
||||
this.editing = false;
|
||||
},
|
||||
deleteMessage() {
|
||||
|
||||
24
talemate_frontend/src/utils/messageContinuation.js
Normal file
24
talemate_frontend/src/utils/messageContinuation.js
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Splice an autocompleted continuation onto a partial message body.
|
||||
*
|
||||
* When the partial closes a dialogue quote (ends in `"`) and the
|
||||
* completion opens with a fresh quote, the two adjacent quote
|
||||
* characters would collide on naive concatenation. Drop the dangling
|
||||
* closing quote on the partial and the leading quote on the
|
||||
* completion, ensure the partial ends with a period, and join with a
|
||||
* space — yielding a clean break between the closed dialogue and the
|
||||
* next sentence. Otherwise concatenate as-is.
|
||||
*
|
||||
* Shared between CharacterMessage and NarratorMessage continue flows.
|
||||
*/
|
||||
export function spliceContinuation(text, completion) {
|
||||
if (text.endsWith('"') && completion.startsWith('"')) {
|
||||
const tailCompletion = completion.slice(1);
|
||||
let body = text.slice(0, -1);
|
||||
if (!body.endsWith('.')) {
|
||||
body += '.';
|
||||
}
|
||||
return body + ' ' + tailCompletion;
|
||||
}
|
||||
return text + completion;
|
||||
}
|
||||
Reference in New Issue
Block a user