Compare commits

..

3 Commits

Author SHA1 Message Date
veguAI
72867c930e 0.32.3 (#219)
* set 0.33.0

* fix nodeeditor context menu issues

* notes
2025-08-24 17:17:08 +03:00
veguAI
eddddd5034 0.32.2 (#216)
* fix api url error in koboldcpp

* set 0.32.2

* relock

* add 0.32.2 note
2025-08-23 19:20:39 +03:00
veguAI
25e646c56a 0.32.1 (#213)
* GLM 4.5 templates

* set 0.33 and relock

* fix issues with character creation

* relock

* prompt tweaks

* fix lmstudio

* fix issue with npm on windows failing on paths
set 0.32.1

* linting

* update what's new

* #214 (#215)

* max-height and overflow

* max-height and overflow

* v-tabs to list and offset new scrollbar at the top so it doesnt overlap into the divider

* tweaks

* tweaks

* prompt tweaks

---------

Co-authored-by: Iceman Oakenbear <89090218+IcemanOakenbear@users.noreply.github.com>
2025-08-23 01:16:18 +03:00
23 changed files with 1595 additions and 1293 deletions

View File

@@ -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",
]

View File

@@ -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)

View File

@@ -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(
{

View File

@@ -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

View File

@@ -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)

View File

@@ -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.

View File

@@ -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" %}

View File

@@ -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 -%}

View File

@@ -1,3 +1,3 @@
__all__ = ["VERSION"]
VERSION = "0.32.0"
VERSION = "0.32.3"

View File

@@ -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",

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "talemate_frontend",
"version": "0.32.0",
"version": "0.32.3",
"private": true,
"type": "module",
"scripts": {

View File

@@ -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;

View File

@@ -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>

View File

@@ -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: [

View File

@@ -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">

View File

@@ -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;

View File

@@ -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">

View File

@@ -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">

View File

@@ -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: {

View File

@@ -0,0 +1,7 @@
[gMASK]<sop><|system|>
{{ system_message }}<|user|>
{{ user_message }}
/nothink<|assistant|>
{{ coercion_message}}

View File

@@ -0,0 +1,4 @@
[gMASK]<sop><|system|>
{{ system_message }}<|user|>
{{ user_message }}<|assistant|>
{{ coercion_message}}

2311
uv.lock generated

File diff suppressed because it is too large Load Diff