move scene message nodes to separate module

This commit is contained in:
vegu-ai-tools
2026-05-17 14:25:11 +03:00
parent 51a182eb3e
commit 3141945249
4 changed files with 348 additions and 332 deletions

View File

@@ -3,6 +3,7 @@ import talemate.game.engine.nodes.command # noqa: F401
import talemate.game.engine.nodes.logic # noqa: F401
import talemate.game.engine.nodes.state # noqa: F401
import talemate.game.engine.nodes.scene # noqa: F401
import talemate.game.engine.nodes.scene_messages # noqa: F401
import talemate.game.engine.nodes.scene_intent # noqa: F401
import talemate.game.engine.nodes.world_state # noqa: F401
import talemate.game.engine.nodes.run # noqa: F401

View File

@@ -777,333 +777,6 @@ class UnpackInteractionState(Node):
)
@register("scene/message/CharacterMessage")
class CharacterMessage(Node):
"""
Creates a character message from a character and a message
Inputs:
- character: The character object
- message: The message to send
- source: The source of the message - player or ai, so whether the message is result of user input or AI generated
- from_choice: For player messages this indicates that the message was generated from a choice selection, for ai sourced messages this indicates the instruction that was followed
Properties:
- source: The source of the message
Outputs:
- message: The message object (this is a scene_message.CharacterMessage instance)
"""
class Fields:
source = PropertyField(
name="source",
description="The source of the message",
type="str",
default="player",
choices=[
"player",
"ai",
],
)
def __init__(self, title="Character Message", **kwargs):
super().__init__(title=title, **kwargs)
def setup(self):
self.add_input("character", socket_type="character")
self.add_input("message", socket_type="str")
self.add_input("source", socket_type="str", optional=True)
self.add_input("from_choice", socket_type="str", optional=True)
self.set_property("source", "player")
self.add_output("message", socket_type="message_object")
async def run(self, state: GraphState):
character: "Character" = self.get_input_value("character")
message = self.get_input_value("message")
source = self.get_input_value("source")
from_choice = self.get_input_value("from_choice")
extra = {}
if isinstance(from_choice, str):
extra["from_choice"] = from_choice
# Capture the character's current avatar at message creation time
# (not default avatar - messages only use current_avatar if set)
if character.current_avatar:
extra["asset_id"] = character.current_avatar
extra["asset_type"] = "avatar"
# prefix name: if not already prefixed
if not message.startswith(f"{character.name}: "):
message = f"{character.name}: {message}"
message = scene_message.CharacterMessage(message, source=source, **extra)
self.set_output_values({"message": message})
@register("scene/message/NarratorMessage")
class NarratorMessage(Node):
"""
Creates a narrator message
Inputs:
- message: The message to send
- source: The source of the message - player or ai, so whether the message is result of user input or AI generated
- meta: A dictionary of meta information to attach to the message. This will generally be arguments and function name that was called on the narrator agent to generate the message and will be used when regenerating the message.
Properties:
- source: The source of the message
Outputs:
- message: The message object (this is a scene_message.NarratorMessage instance)
"""
class Fields:
source = PropertyField(
name="source",
description="The source of the message",
type="str",
default="ai",
choices=[
"player",
"ai",
],
)
def __init__(self, title="Narrator Message", **kwargs):
super().__init__(title=title, **kwargs)
def setup(self):
self.add_input("message", socket_type="str")
self.add_input("source", socket_type="str", optional=True)
self.add_input("meta", socket_type="dict", optional=True)
self.set_property("source", "ai")
self.add_output("message", socket_type="message_object")
async def run(self, state: GraphState):
message = self.get_input_value("message")
source = self.get_input_value("source")
meta = self.get_input_value("meta")
extra = {}
if meta and isinstance(meta, dict):
extra["meta"] = meta
message = scene_message.NarratorMessage(message, source=source, **extra)
self.set_output_values({"message": message})
@register("scene/message/DirectorMessage")
class DirectorMessage(Node):
"""
Creates a director message
Inputs:
- message: The message to send
- source: The source of the message - player or ai, so whether the message is result of user input or AI generated
- meta: A dictionary of meta information to attach to the message. Can hold the character name that the message is related to.
- character: The character object that the message is related to
Properties:
- source: The source of the message
- action: Describes the director action
Outputs:
- message: The message object (this is a scene_message.DirectorMessage instance)
"""
class Fields:
source = PropertyField(
name="source",
description="The source of the message",
type="str",
default="ai",
choices=[
"player",
"ai",
],
)
action = PropertyField(
name="action",
description="Describes the director action",
type="str",
default="actor_instruction",
choices=[
"actor_instruction",
"user_direction",
],
)
subtype = PropertyField(
name="subtype",
description="The subtype of the director message, used for further categorization of the message",
type="str",
default=UNRESOLVED,
choices=[
"function_call",
"user_direction",
],
)
def __init__(self, title="Director Message", **kwargs):
super().__init__(title=title, **kwargs)
def setup(self):
self.add_input("message", socket_type="str")
self.add_input("source", socket_type="str", optional=True)
self.add_input("meta", socket_type="dict", optional=True)
self.add_input("character", socket_type="character", optional=True)
self.add_input("action", socket_type="str", optional=True)
self.add_input("subtype", socket_type="str", optional=True)
self.set_property("source", "ai")
self.set_property("action", "actor_instruction")
self.set_property("subtype", UNRESOLVED)
self.add_output("message", socket_type="message_object")
self.add_output("source", socket_type="str")
self.add_output("meta", socket_type="dict")
self.add_output("character", socket_type="character")
self.add_output("action", socket_type="str")
self.add_output("subtype", socket_type="str")
async def run(self, state: GraphState):
message = self.normalized_input_value("message")
source = self.normalized_input_value("source")
action = self.normalized_input_value("action")
meta = self.normalized_input_value("meta")
subtype = self.normalized_input_value("subtype")
character: "Character" = self.normalized_input_value("character")
extra = {}
if meta and isinstance(meta, dict):
extra["meta"] = meta
message = scene_message.DirectorMessage(
message, source=source, action=action, subtype=subtype, **extra
)
if character is not None:
message.set_meta(character=character.name)
self.set_output_values(
{
"message": message,
"source": source,
"meta": meta,
"character": character,
"action": action,
"subtype": subtype,
}
)
@register("scene/message/UnpackMeta")
class UnpackMessageMeta(Node):
"""
Unpacks a message meta dictionary
into arguments
Inputs:
- meta: The meta dictionary
Outputs:
- agent_name: The agent name
- function_name: The function name
- arguments: The arguments dictionary
"""
def __init__(self, title="Unpack Message Meta", **kwargs):
super().__init__(title=title, **kwargs)
def setup(self):
self.add_input("meta", socket_type="dict")
self.add_output("agent_name", socket_type="str")
self.add_output("function_name", socket_type="str")
self.add_output("arguments", socket_type="dict")
async def run(self, state: GraphState):
meta = self.get_input_value("meta")
self.set_output_values(
{
"agent_name": meta["agent"],
"function_name": meta["function"],
"arguments": meta.get("arguments", {}).copy(),
}
)
@register("scene/message/ToggleMessageContextVisibility")
class ToggleMessageContextVisibility(Node):
"""
Hide or show a message. Hidden messages are not displayed to the AI.
Inputs:
- message: The message object
Properties:
- hidden: Whether the message is hidden
Outputs:
- message: The message object
"""
class Fields:
hidden = PropertyField(
name="hidden",
description="Whether the message is hidden",
type="bool",
default=False,
)
def __init__(self, title="Toggle Message Context Visibility", **kwargs):
super().__init__(title=title, **kwargs)
def setup(self):
self.add_input("message", socket_type="message_object")
self.set_property("hidden", False)
self.add_output("message", socket_type="message_object")
async def run(self, state: GraphState):
message = self.require_input("message")
hidden = self.get_property("hidden")
if hidden:
message.hide()
else:
message.unhide()
self.set_output_values({"message": message})
@register("input/WaitForInput")
class WaitForInput(Node):
"""

View File

@@ -0,0 +1,340 @@
from typing import TYPE_CHECKING
from .core import (
Node,
GraphState,
UNRESOLVED,
PropertyField,
)
from .registry import register
import talemate.scene_message as scene_message
if TYPE_CHECKING:
from talemate.character import Character
@register("scene/message/CharacterMessage")
class CharacterMessage(Node):
"""
Creates a character message from a character and a message
Inputs:
- character: The character object
- message: The message to send
- source: The source of the message - player or ai, so whether the message is result of user input or AI generated
- from_choice: For player messages this indicates that the message was generated from a choice selection, for ai sourced messages this indicates the instruction that was followed
Properties:
- source: The source of the message
Outputs:
- message: The message object (this is a scene_message.CharacterMessage instance)
"""
class Fields:
source = PropertyField(
name="source",
description="The source of the message",
type="str",
default="player",
choices=[
"player",
"ai",
],
)
def __init__(self, title="Character Message", **kwargs):
super().__init__(title=title, **kwargs)
def setup(self):
self.add_input("character", socket_type="character")
self.add_input("message", socket_type="str")
self.add_input("source", socket_type="str", optional=True)
self.add_input("from_choice", socket_type="str", optional=True)
self.set_property("source", "player")
self.add_output("message", socket_type="message_object")
async def run(self, state: GraphState):
character: "Character" = self.get_input_value("character")
message = self.get_input_value("message")
source = self.get_input_value("source")
from_choice = self.get_input_value("from_choice")
extra = {}
if isinstance(from_choice, str):
extra["from_choice"] = from_choice
# Capture the character's current avatar at message creation time
# (not default avatar - messages only use current_avatar if set)
if character.current_avatar:
extra["asset_id"] = character.current_avatar
extra["asset_type"] = "avatar"
# prefix name: if not already prefixed
if not message.startswith(f"{character.name}: "):
message = f"{character.name}: {message}"
message = scene_message.CharacterMessage(message, source=source, **extra)
self.set_output_values({"message": message})
@register("scene/message/NarratorMessage")
class NarratorMessage(Node):
"""
Creates a narrator message
Inputs:
- message: The message to send
- source: The source of the message - player or ai, so whether the message is result of user input or AI generated
- meta: A dictionary of meta information to attach to the message. This will generally be arguments and function name that was called on the narrator agent to generate the message and will be used when regenerating the message.
Properties:
- source: The source of the message
Outputs:
- message: The message object (this is a scene_message.NarratorMessage instance)
"""
class Fields:
source = PropertyField(
name="source",
description="The source of the message",
type="str",
default="ai",
choices=[
"player",
"ai",
],
)
def __init__(self, title="Narrator Message", **kwargs):
super().__init__(title=title, **kwargs)
def setup(self):
self.add_input("message", socket_type="str")
self.add_input("source", socket_type="str", optional=True)
self.add_input("meta", socket_type="dict", optional=True)
self.set_property("source", "ai")
self.add_output("message", socket_type="message_object")
async def run(self, state: GraphState):
message = self.get_input_value("message")
source = self.get_input_value("source")
meta = self.get_input_value("meta")
extra = {}
if meta and isinstance(meta, dict):
extra["meta"] = meta
message = scene_message.NarratorMessage(message, source=source, **extra)
self.set_output_values({"message": message})
@register("scene/message/DirectorMessage")
class DirectorMessage(Node):
"""
Creates a director message
Inputs:
- message: The message to send
- source: The source of the message - player or ai, so whether the message is result of user input or AI generated
- meta: A dictionary of meta information to attach to the message. Can hold the character name that the message is related to.
- character: The character object that the message is related to
Properties:
- source: The source of the message
- action: Describes the director action
Outputs:
- message: The message object (this is a scene_message.DirectorMessage instance)
"""
class Fields:
source = PropertyField(
name="source",
description="The source of the message",
type="str",
default="ai",
choices=[
"player",
"ai",
],
)
action = PropertyField(
name="action",
description="Describes the director action",
type="str",
default="actor_instruction",
choices=[
"actor_instruction",
"user_direction",
],
)
subtype = PropertyField(
name="subtype",
description="The subtype of the director message, used for further categorization of the message",
type="str",
default=UNRESOLVED,
choices=[
"function_call",
"user_direction",
],
)
def __init__(self, title="Director Message", **kwargs):
super().__init__(title=title, **kwargs)
def setup(self):
self.add_input("message", socket_type="str")
self.add_input("source", socket_type="str", optional=True)
self.add_input("meta", socket_type="dict", optional=True)
self.add_input("character", socket_type="character", optional=True)
self.add_input("action", socket_type="str", optional=True)
self.add_input("subtype", socket_type="str", optional=True)
self.set_property("source", "ai")
self.set_property("action", "actor_instruction")
self.set_property("subtype", UNRESOLVED)
self.add_output("message", socket_type="message_object")
self.add_output("source", socket_type="str")
self.add_output("meta", socket_type="dict")
self.add_output("character", socket_type="character")
self.add_output("action", socket_type="str")
self.add_output("subtype", socket_type="str")
async def run(self, state: GraphState):
message = self.normalized_input_value("message")
source = self.normalized_input_value("source")
action = self.normalized_input_value("action")
meta = self.normalized_input_value("meta")
subtype = self.normalized_input_value("subtype")
character: "Character" = self.normalized_input_value("character")
extra = {}
if meta and isinstance(meta, dict):
extra["meta"] = meta
message = scene_message.DirectorMessage(
message, source=source, action=action, subtype=subtype, **extra
)
if character is not None:
message.set_meta(character=character.name)
self.set_output_values(
{
"message": message,
"source": source,
"meta": meta,
"character": character,
"action": action,
"subtype": subtype,
}
)
@register("scene/message/UnpackMeta")
class UnpackMessageMeta(Node):
"""
Unpacks a message meta dictionary
into arguments
Inputs:
- meta: The meta dictionary
Outputs:
- agent_name: The agent name
- function_name: The function name
- arguments: The arguments dictionary
"""
def __init__(self, title="Unpack Message Meta", **kwargs):
super().__init__(title=title, **kwargs)
def setup(self):
self.add_input("meta", socket_type="dict")
self.add_output("agent_name", socket_type="str")
self.add_output("function_name", socket_type="str")
self.add_output("arguments", socket_type="dict")
async def run(self, state: GraphState):
meta = self.get_input_value("meta")
self.set_output_values(
{
"agent_name": meta["agent"],
"function_name": meta["function"],
"arguments": meta.get("arguments", {}).copy(),
}
)
@register("scene/message/ToggleMessageContextVisibility")
class ToggleMessageContextVisibility(Node):
"""
Hide or show a message. Hidden messages are not displayed to the AI.
Inputs:
- message: The message object
Properties:
- hidden: Whether the message is hidden
Outputs:
- message: The message object
"""
class Fields:
hidden = PropertyField(
name="hidden",
description="Whether the message is hidden",
type="bool",
default=False,
)
def __init__(self, title="Toggle Message Context Visibility", **kwargs):
super().__init__(title=title, **kwargs)
def setup(self):
self.add_input("message", socket_type="message_object")
self.set_property("hidden", False)
self.add_output("message", socket_type="message_object")
async def run(self, state: GraphState):
message = self.require_input("message")
hidden = self.get_property("hidden")
if hidden:
message.hide()
else:
message.unhide()
self.set_output_values({"message": message})

View File

@@ -23,9 +23,7 @@ from talemate.game.engine.nodes.core import (
)
from talemate.game.engine.nodes.scene import (
ActivateCharacter,
CharacterMessage as CharacterMessageNode,
DeactivateCharacter,
DirectorMessage as DirectorMessageNode,
GetCharacter,
GetCharacterAttribute,
GetCharacterDescription,
@@ -41,7 +39,6 @@ from talemate.game.engine.nodes.scene import (
IsPlayerCharacter,
ListCharacters,
MakeCharacter,
NarratorMessage as NarratorMessageNode,
RemoveAllCharacters,
RemoveCharacter,
RestoreScene,
@@ -53,14 +50,19 @@ from talemate.game.engine.nodes.scene import (
SetDescription,
SetIntroduction,
SetTitle,
ToggleMessageContextVisibility,
TriggerGameLoopActorIter,
UnpackCharacter,
UnpackInteractionState,
UnpackMessageMeta,
UpdateCharacterData,
WaitForInput,
)
from talemate.game.engine.nodes.scene_messages import (
CharacterMessage as CharacterMessageNode,
DirectorMessage as DirectorMessageNode,
NarratorMessage as NarratorMessageNode,
ToggleMessageContextVisibility,
UnpackMessageMeta,
)
import talemate.events as events
import talemate.scene_message as scene_message