Compare commits

...

14 Commits

Author SHA1 Message Date
vegu-ai-tools
028ca360a4 prompt tweaks 2025-08-22 23:54:03 +03:00
vegu-ai-tools
bbb66d63fd tweaks 2025-08-22 19:41:17 +03:00
vegu-ai-tools
0dc696301a tweaks 2025-08-22 19:22:43 +03:00
vegu-ai-tools
23998628ad v-tabs to list and offset new scrollbar at the top so it doesnt overlap into the divider 2025-08-22 19:11:58 +03:00
Iceman Oakenbear
35f872f94d #214 (#215)
* max-height and overflow

* max-height and overflow
2025-08-21 10:12:31 +03:00
vegu-ai-tools
6c082cd4e3 update what's new 2025-08-20 16:10:52 +03:00
vegu-ai-tools
30ec3038c3 linting 2025-08-20 16:06:02 +03:00
vegu-ai-tools
62e1c4f653 fix issue with npm on windows failing on paths
set 0.32.1
2025-08-20 15:40:28 +03:00
vegu-ai-tools
daadf6f1d0 fix lmstudio 2025-08-20 14:43:13 +03:00
vegu-ai-tools
ec512512e6 prompt tweaks 2025-08-18 00:49:08 +03:00
vegu-ai-tools
990ad4c285 relock 2025-08-13 02:23:39 +03:00
vegu-ai-tools
0f72a7ab86 fix issues with character creation 2025-08-13 00:32:43 +03:00
vegu-ai-tools
e19ad23f1d set 0.33 and relock 2025-08-12 12:44:16 +03:00
vegu-ai-tools
8733d54735 GLM 4.5 templates 2025-08-09 01:19:55 +03:00
21 changed files with 1562 additions and 1289 deletions

View File

@@ -1,6 +1,6 @@
[project]
name = "talemate"
version = "0.32.0"
version = "0.32.1"
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

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

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.1",
"private": true,
"type": "module",
"scripts": {

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,24 @@ export default {
data() {
return {
expand: false,
selected: "0.32.0",
selected: "0.32.1",
whatsNew: [
{
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