mirror of
https://github.com/vegu-ai/talemate.git
synced 2025-12-24 15:39:34 +01:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
72867c930e | ||
|
|
eddddd5034 | ||
|
|
25e646c56a |
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "talemate"
|
||||
version = "0.32.0"
|
||||
version = "0.32.3"
|
||||
description = "AI-backed roleplay and narrative tools"
|
||||
authors = [{name = "VeguAITools"}]
|
||||
license = {text = "GNU Affero General Public License v3.0"}
|
||||
@@ -121,7 +121,8 @@ override-dependencies = [
|
||||
# chatterbox wants torch 2.6.0, but is confirmed working with 2.7.1
|
||||
"torchaudio>=2.7.1",
|
||||
"torch>=2.7.1",
|
||||
"numpy>=2",
|
||||
# numba needs numpy < 2.3
|
||||
"numpy>=2,<2.3",
|
||||
"pydantic>=2.11",
|
||||
]
|
||||
|
||||
|
||||
@@ -131,6 +131,9 @@ class CharacterManagementMixin:
|
||||
name = await creator.determine_character_name(name, instructions=content)
|
||||
log.debug("persist_character", adjusted_name=name)
|
||||
|
||||
if name in self.scene.all_character_names:
|
||||
raise ValueError(f'Name "{name}" already exists.')
|
||||
|
||||
# Create the blank character
|
||||
character: "Character" = self.scene.Character(name=name, is_player=is_player)
|
||||
|
||||
|
||||
@@ -119,7 +119,7 @@ class DirectorWebsocketHandler(Plugin):
|
||||
if isinstance(exc, GenerationCancelled):
|
||||
handle_generation_cancelled(exc)
|
||||
|
||||
await self.signal_operation_failed("Error persisting character")
|
||||
await self.signal_operation_failed(f"Error persisting character: {exc}")
|
||||
else:
|
||||
self.websocket_handler.queue_put(
|
||||
{
|
||||
|
||||
@@ -193,6 +193,14 @@ class KoboldCppClient(ClientBase):
|
||||
def default_prompt_template(self) -> str:
|
||||
return "KoboldAI.jinja2"
|
||||
|
||||
@property
|
||||
def api_url(self) -> str:
|
||||
return self.client_config.api_url
|
||||
|
||||
@api_url.setter
|
||||
def api_url(self, value: str):
|
||||
self.client_config.api_url = value
|
||||
|
||||
def api_endpoint_specified(self, url: str) -> bool:
|
||||
return "/v1" in self.api_url
|
||||
|
||||
|
||||
@@ -33,6 +33,12 @@ class LMStudioClient(ClientBase):
|
||||
),
|
||||
]
|
||||
|
||||
@property
|
||||
def api_key(self):
|
||||
# LMStudio doesn't currently support API keys so we'll just use a dummy key
|
||||
# since the openai client requires it.
|
||||
return "sk-1234"
|
||||
|
||||
def make_client(self):
|
||||
return AsyncOpenAI(base_url=self.api_url + "/v1", api_key=self.api_key)
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ You are writing a novel-style narrative continuation featuring {{ talking_charac
|
||||
|
||||
{% include "writing-style-instructions.jinja2" %}
|
||||
|
||||
Your task is to write the next part of the story from {{ talking_character.name }}'s perspective, continuing the narrative in flowing, novel-like prose.
|
||||
Your task is to write the next part of the story featuring {{ talking_character.name }}, continuing the narrative in flowing, novel-like prose.
|
||||
|
||||
{% set dialogue_examples -%}
|
||||
{% for example in talking_character.random_dialogue_examples(scene, num=2, strip_name=True) -%}
|
||||
@@ -25,7 +25,7 @@ Your task is to write the next part of the story from {{ talking_character.name
|
||||
- NEVER write dialogue for other characters
|
||||
- NEVER describe other characters' actions, thoughts, or reactions
|
||||
- NEVER make other characters speak or act
|
||||
- Focus EXCLUSIVELY on {{ talking_character.name }}'s perspective, actions, thoughts, and words
|
||||
- Focus EXCLUSIVELY on {{ talking_character.name }}'s actions, thoughts, and words
|
||||
- Other characters can exist in the scene but you cannot control them
|
||||
- **ENVIRONMENTAL REACTIONS ARE ALLOWED**: You CAN describe how the environment or objects respond (e.g., "the door opened," "rain started," "the fire crackled")
|
||||
|
||||
@@ -41,7 +41,6 @@ Really think about the above!!!
|
||||
- Use concise, focused descriptions
|
||||
- **AVOID PURPLE PROSE**: Keep descriptions practical and avoid overly flowery or elaborate language. Prefer simple, direct descriptions over ornate ones
|
||||
- **BE CONCISE**: Don't over-describe scenes, emotions, or actions. A few well-chosen details are better than lengthy descriptions
|
||||
- **CRITICAL - Tense and Perspective Consistency**: Examine the existing conversation history carefully and maintain the EXACT same tense (past/present) and perspective (first/second/third person) used in previous messages. If previous messages use third person past tense ("He walked"), continue with third person past tense. If they use first person present ("I walk"), continue with first person present. Do NOT switch styles mid-conversation.
|
||||
|
||||
**Scene Progression - PRIORITIZE MOVING FORWARD**: Always advance the story. Don't just react - make things happen. Consider:
|
||||
- What {{ talking_character.name }} wants to achieve in this moment
|
||||
@@ -58,6 +57,10 @@ Really think about the above!!!
|
||||
- **Vary your opening patterns**: Avoid starting consecutive responses with similar sentence structures (e.g., "{{ talking_character.name }}'s [object]..." or "{{ talking_character.name }} [verbed]...")
|
||||
- **Focus on different aspects**: If you've recently described equipment/tools, shift to emotions, environment, or internal thoughts instead
|
||||
|
||||
**CRITICAL - NARRATIVE CONSISTENCY WARNINGS**:
|
||||
- **TENSE**: Examine the existing conversation history and maintain the EXACT same tense (past/present) used in previous messages. If previous messages use past tense ("walked"), continue with past tense. If they use present tense ("walks"), continue with present tense. **NEVER switch tenses mid-conversation**.
|
||||
- **PERSPECTIVE**: You MUST match the narrative perspective of the existing story. If the story is written in first person from another character's perspective, DO NOT switch to {{ talking_character.name }}'s first person perspective. If the story uses third person ("he/she walked"), continue with third person for {{ talking_character.name }}. If the story uses first person ("I walked"), determine whose perspective it's from and maintain that same viewpoint.
|
||||
- **The character whose turn it is to act does NOT automatically become the narrator**
|
||||
|
||||
Based on {{ talking_character.name}}'s established dialogue patterns, maintain consistency with their voice and speaking style.
|
||||
{% if dialogue_examples.strip() %}
|
||||
@@ -68,7 +71,7 @@ Based on {{ talking_character.name}}'s established dialogue patterns, maintain c
|
||||
|
||||
{{ task_instructions }}
|
||||
|
||||
Remember: Write clear, engaging prose that captures {{ talking_character.name }}'s experience in this moment. Focus on their perspective, thoughts, and actions while maintaining the natural flow of the story. Keep it concise and avoid unnecessary embellishment.
|
||||
Remember: Write clear, engaging prose that captures {{ talking_character.name }}'s experience in this moment. Focus on their thoughts and actions while maintaining the natural flow of the story. Keep it concise and avoid unnecessary embellishment.
|
||||
|
||||
**FINAL REMINDER**: You are {{ talking_character.name }}. Write ONLY what {{ talking_character.name }} thinks, says, and does. Do not write for any other character.
|
||||
|
||||
|
||||
@@ -74,7 +74,8 @@ Explain {{ character.name }}'s way of speaking and mannerisms to guide the write
|
||||
|
||||
Focus solely on WHAT needs to be conveyed. Trust the writer to capture {{ character.name }}'s personality and style based on your character description. How do we make {{ character.name }} a believable, natural sounding character in this next moment?
|
||||
|
||||
Finally ALWAYS briefly state the formatting guidelines: Speech MUST go inside "".
|
||||
IMPORTANT: Remind the writer to maintain the story's existing narrative perspective and tense (who's point of view is the narrative written for and in what tense, as identified in the scene analysis).
|
||||
Finally ALWAYS briefly state the formatting guidelines: Speech MUST go inside "".
|
||||
|
||||
{% include "response-length.jinja2" %}
|
||||
|
||||
|
||||
@@ -38,6 +38,8 @@ The information you write will be given to the other story editors to write {{ c
|
||||
{{ li() }}. Is {{ character.name }} aware of the current moment? This is IMPORTANT - It cannot affect their next action if they are not aware. You must be very explicit about this and either say Yes or No.
|
||||
{% endif %}
|
||||
|
||||
{{ li() }}. What narrative perspective and tense is the story written in? (e.g., "third person past tense", "first person present tense from Character X's perspective", etc.)
|
||||
|
||||
{{ li() }}. What is the cadence and nature of the current dialogue? Is it ongoing or is a new dialogue starting? Who is talking to who?
|
||||
|
||||
{% if context_investigation -%}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
__all__ = ["VERSION"]
|
||||
|
||||
VERSION = "0.32.0"
|
||||
VERSION = "0.32.3"
|
||||
|
||||
@@ -836,6 +836,8 @@ class WorldStateManager:
|
||||
"""
|
||||
Creates a new character in the scene.
|
||||
|
||||
DEPRECATED: Use the director agent's persist_character method instead.
|
||||
|
||||
Arguments:
|
||||
generate: Whether to generate name and description if they are not specified; defaults to True.
|
||||
instructions: Optional instructions for the character creation.
|
||||
@@ -871,6 +873,9 @@ class WorldStateManager:
|
||||
if not name:
|
||||
raise ValueError("Failed to generate a name for the character.")
|
||||
|
||||
if name in self.scene.all_character_names:
|
||||
raise ValueError(f'Name "{name}" already exists.')
|
||||
|
||||
if not description and generate:
|
||||
description = await creator.contextual_generate_from_args(
|
||||
context="character detail:description",
|
||||
|
||||
386
talemate_frontend/package-lock.json
generated
386
talemate_frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "talemate_frontend",
|
||||
"version": "0.32.0",
|
||||
"version": "0.32.3",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -820,15 +820,12 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@import "litegraph.js/css/litegraph.css";
|
||||
</style>
|
||||
|
||||
<style>
|
||||
/* Litegraph styles */
|
||||
@import "litegraph.js/css/litegraph.css";
|
||||
|
||||
.litegraph.litecontextmenu {
|
||||
z-index: 100000;
|
||||
}
|
||||
|
||||
.litegraph.lite-search-item {
|
||||
padding: 2px;
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<v-list-subheader>Creative Tools</v-list-subheader>
|
||||
|
||||
<!-- deactivate active characters -->
|
||||
<v-list-item v-for="(character, index) in deactivatableCharacters" :key="index"
|
||||
<v-list-item v-for="(character, index) in deactivatableCharacters" :key="character"
|
||||
@click="deactivateCharacter($event, character)">
|
||||
<template v-slot:prepend>
|
||||
<v-icon color="secondary">mdi-exit-run</v-icon>
|
||||
@@ -20,7 +20,7 @@
|
||||
</v-list-item>
|
||||
|
||||
<!-- reactivate inactive characters -->
|
||||
<v-list-item v-for="(character, index) in inactiveCharacters" :key="index"
|
||||
<v-list-item v-for="(character, index) in inactiveCharacters" :key="character"
|
||||
@click="activateCharacter($event, character)">
|
||||
<template v-slot:prepend>
|
||||
<v-icon color="secondary">mdi-human-greeting</v-icon>
|
||||
@@ -39,7 +39,7 @@
|
||||
</v-list-item>
|
||||
|
||||
<!-- persist passive characters -->
|
||||
<v-list-item v-for="(character, index) in potentialNewCharacters" :key="index"
|
||||
<v-list-item v-for="(character, index) in potentialNewCharacters" :key="character"
|
||||
@click="introduceCharacter($event, character)">
|
||||
<template v-slot:prepend>
|
||||
<v-icon color="warning">mdi-human-greeting</v-icon>
|
||||
|
||||
@@ -75,8 +75,48 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
expand: false,
|
||||
selected: "0.32.0",
|
||||
selected: "0.32.3",
|
||||
whatsNew: [
|
||||
{
|
||||
version: '0.32.3',
|
||||
items: [
|
||||
{
|
||||
title: "Bugfix release",
|
||||
description: "Node editor context menu positioning fix.",
|
||||
items: [
|
||||
"Fix LiteGraph context menu positioning issue"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
version: '0.32.2',
|
||||
items: [
|
||||
{
|
||||
title: "Bugfix release",
|
||||
description: "Bug fixes and connection improvements.",
|
||||
items: [
|
||||
"Fix KoboldCpp connection issues"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
version: '0.32.1',
|
||||
items: [
|
||||
{
|
||||
title: "Bugfix release",
|
||||
description: "Bug fixes and minor improvements.",
|
||||
items: [
|
||||
"Fix LMStudio connection (#212)",
|
||||
"Fix Windows setup failure when any parent folder path contains spaces (#211)",
|
||||
"Fix character creation issues",
|
||||
"Tweak scene analysis and director guidance prompts for conversation",
|
||||
"Add GLM 4.5 templates"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
version: '0.32.0',
|
||||
items: [
|
||||
|
||||
@@ -16,8 +16,7 @@
|
||||
<v-divider></v-divider>
|
||||
|
||||
<v-row>
|
||||
<v-col cols="4">
|
||||
|
||||
<v-col cols="4" style="max-height: 60vh; overflow: auto" class="mt-4">
|
||||
<v-list density="compact" slim v-model:opened="groupsOpen">
|
||||
<v-list-group value="templates" fluid>
|
||||
<template v-slot:activator="{ props }">
|
||||
@@ -41,14 +40,16 @@
|
||||
clear-icon="mdi-close"
|
||||
class="ml-1 mb-1 mt-1"
|
||||
@update:modelValue="autoSelect"></v-text-field>
|
||||
<v-tabs :disabled="busy" v-model="selected" density="compact" direction="vertical" color="indigo-lighten-3">
|
||||
<v-tab density="compact" v-for="(value, attribute) in filteredList"
|
||||
class="text-caption"
|
||||
:key="attribute"
|
||||
:value="attribute">
|
||||
{{ attribute }}
|
||||
</v-tab>
|
||||
</v-tabs>
|
||||
<v-list :disabled="busy" density="compact" color="highlight1">
|
||||
<v-list-item
|
||||
v-for="(value, attribute) in filteredList"
|
||||
:key="attribute"
|
||||
:active="selected === attribute"
|
||||
@click="selected = attribute"
|
||||
>
|
||||
<v-list-item-title>{{ attribute }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
|
||||
</v-col>
|
||||
<v-col cols="8">
|
||||
|
||||
@@ -144,12 +144,9 @@ export default {
|
||||
},
|
||||
|
||||
cancel() {
|
||||
if(!this.character) {
|
||||
return;
|
||||
if (this.character && typeof this.character.cancel === 'function') {
|
||||
this.character.cancel();
|
||||
}
|
||||
|
||||
this.character.cancel();
|
||||
this.character = {};
|
||||
this.$emit('cancelled');
|
||||
},
|
||||
|
||||
@@ -235,7 +232,7 @@ export default {
|
||||
if(this.character.created) {
|
||||
this.character.created(message.data);
|
||||
}
|
||||
this.reset();
|
||||
this.$emit('cancelled');
|
||||
}
|
||||
// Handle director responses (for AI generation)
|
||||
else if (message.type === 'director' && message.action === 'character_persisted') {
|
||||
@@ -244,7 +241,7 @@ export default {
|
||||
if(this.character.created) {
|
||||
this.character.created(message.character);
|
||||
}
|
||||
this.reset();
|
||||
this.$emit('cancelled');
|
||||
}
|
||||
else if ((message.type === 'director' || message.type === 'world_state_manager') && message.action === 'operation_done') {
|
||||
this.busy = false;
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<v-divider></v-divider>
|
||||
|
||||
<v-row>
|
||||
<v-col cols="4">
|
||||
<v-col cols="4" style="max-height: 60vh; overflow: auto" class="mt-4">
|
||||
<v-list density="compact" slim v-model:opened="groupsOpen">
|
||||
<v-list-group value="templates" fluid>
|
||||
<template v-slot:activator="{ props }">
|
||||
@@ -40,14 +40,16 @@
|
||||
clearable density="compact" variant="underlined"
|
||||
class="ml-1 mb-1 mt-1"
|
||||
@update:model-value="autoSelect"></v-text-field>
|
||||
<v-tabs :disabled="busy" direction="vertical" density="compact" v-model="selected" color="indigo-lighten-3">
|
||||
<v-tab v-for="(value, detail) in filteredList"
|
||||
<v-list :disabled="busy" density="compact" color="highlight1">
|
||||
<v-list-item
|
||||
v-for="(value, detail) in filteredList"
|
||||
:key="detail"
|
||||
class="text-caption"
|
||||
:value="detail">
|
||||
:active="selected === detail"
|
||||
@click="selected = detail"
|
||||
>
|
||||
<v-list-item-title class="text-caption">{{ detail }}</v-list-item-title>
|
||||
</v-tab>
|
||||
</v-tabs>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-col>
|
||||
<v-col cols="8">
|
||||
<div v-if="selected && character.details[selected] !== undefined">
|
||||
|
||||
@@ -14,8 +14,7 @@
|
||||
</v-row>
|
||||
<v-divider></v-divider>
|
||||
<v-row>
|
||||
<v-col cols="5">
|
||||
|
||||
<v-col cols="5" style="max-height: 60vh; overflow: auto" class="mt-4">
|
||||
<v-list density="compact" slim v-model:opened="groupsOpen">
|
||||
<v-list-group value="templates" fluid>
|
||||
<template v-slot:activator="{ props }">
|
||||
@@ -39,18 +38,23 @@
|
||||
clearable density="compact" variant="underlined"
|
||||
class="ml-1 mb-1 mt-1"
|
||||
@update:modelValue="autoSelect"></v-text-field>
|
||||
<v-tabs :disabled="busy" v-model="selected" direction="vertical" color="indigo-lighten-3" density="compact">
|
||||
<v-tab v-for="(value, detail) in filteredList"
|
||||
<v-list :disabled="busy" density="compact" color="highlight1">
|
||||
<v-list-item
|
||||
v-for="(value, detail) in filteredList"
|
||||
:key="detail"
|
||||
class="text-caption"
|
||||
:value="detail">
|
||||
<div class="text-left">{{ detail }}<div><v-chip size="x-small" label variant="outlined"
|
||||
color="info">update in {{ value.due }}
|
||||
turns</v-chip>
|
||||
:active="selected === detail"
|
||||
@click="selected = detail"
|
||||
>
|
||||
<v-list-item-title>
|
||||
<div class="text-left">{{ detail }}
|
||||
<div>
|
||||
<v-chip size="x-small" label variant="outlined" color="info">update in {{ value.due }} turns</v-chip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</v-tab>
|
||||
</v-tabs>
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-col>
|
||||
<v-col cols="7">
|
||||
<div v-if="selected && character.reinforcements[selected] !== undefined">
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { defineConfig, loadEnv } from "vite";
|
||||
import { fileURLToPath, URL } from "node:url";
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
import vuetify from "vite-plugin-vuetify";
|
||||
|
||||
@@ -24,7 +25,8 @@ export default defineConfig(({ mode }) => {
|
||||
publicDir: "public",
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": new URL("./src", import.meta.url).pathname,
|
||||
// Use OS-native, decoded path for Windows compatibility (handles spaces)
|
||||
"@": fileURLToPath(new URL("./src", import.meta.url)),
|
||||
},
|
||||
},
|
||||
server: {
|
||||
|
||||
7
templates/llm-prompt/std/GLM-no-reasoning.jinja2
Normal file
7
templates/llm-prompt/std/GLM-no-reasoning.jinja2
Normal file
@@ -0,0 +1,7 @@
|
||||
[gMASK]<sop><|system|>
|
||||
{{ system_message }}<|user|>
|
||||
{{ user_message }}
|
||||
|
||||
|
||||
/nothink<|assistant|>
|
||||
{{ coercion_message}}
|
||||
4
templates/llm-prompt/std/GLM.jinja2
Normal file
4
templates/llm-prompt/std/GLM.jinja2
Normal file
@@ -0,0 +1,4 @@
|
||||
[gMASK]<sop><|system|>
|
||||
{{ system_message }}<|user|>
|
||||
{{ user_message }}<|assistant|>
|
||||
{{ coercion_message}}
|
||||
Reference in New Issue
Block a user