add new characters to turn order, fix leaving, move image count to config
This commit is contained in:
parent
fea124b3f3
commit
16e7243da1
|
@ -9,6 +9,6 @@ COPY requirements/base.txt /taleweave/requirements/base.txt
|
||||||
RUN pip install --no-cache-dir -r requirements/base.txt
|
RUN pip install --no-cache-dir -r requirements/base.txt
|
||||||
RUN pip install --no-cache-dir --index-url https://test.pypi.org/simple/ packit_llm==0.1.0
|
RUN pip install --no-cache-dir --index-url https://test.pypi.org/simple/ packit_llm==0.1.0
|
||||||
|
|
||||||
COPY adventure/ /taleweave/adventure/
|
COPY taleweave/ /taleweave/taleweave/
|
||||||
|
|
||||||
CMD ["python", "-m", "adventure.main"]
|
CMD ["python", "-m", "taleweave.main"]
|
||||||
|
|
11
README.md
11
README.md
|
@ -107,19 +107,16 @@ server:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Start the TaleWeave AI engine
|
# Start the TaleWeave AI engine
|
||||||
python3 -m adventure.main \
|
python3 -m taleweave.main \
|
||||||
--world worlds/outback-animals-1 \
|
--world worlds/outback-animals-1 \
|
||||||
--world-prompt ./adventure/prompts.yml:outback-animals \
|
--world-prompt ./taleweave/prompts.yml:outback-animals \
|
||||||
--discord=true \
|
--discord=true \
|
||||||
--server=true \
|
--server=true \
|
||||||
--rooms 3 \
|
--rooms 3 \
|
||||||
--turns 30 \
|
--turns 30 \
|
||||||
--optional-actions=true \
|
--optional-actions=true \
|
||||||
--actions adventure.sim_systems:init_actions \
|
--actions taleweave.systems.sim:init_actions \
|
||||||
--systems adventure.sim_systems:init_logic
|
--systems taleweave.systems.sim:init_logic
|
||||||
|
|
||||||
# --actions adventure.custom_systems:init_actions
|
|
||||||
# --systems adventure.custom_systems:init_logic
|
|
||||||
```
|
```
|
||||||
|
|
||||||
This will generate a relatively small world with 3 rooms or areas, run for 30 steps, then shut down.
|
This will generate a relatively small world with 3 rooms or areas, run for 30 steps, then shut down.
|
||||||
|
|
|
@ -54,9 +54,12 @@ export function App(props: AppProps) {
|
||||||
|
|
||||||
function setPlayer(character: Maybe<Character>) {
|
function setPlayer(character: Maybe<Character>) {
|
||||||
// do not call setCharacter until the server confirms the player change
|
// do not call setCharacter until the server confirms the player change
|
||||||
|
// eslint-disable-next-line no-null/no-null
|
||||||
|
let become = null;
|
||||||
if (doesExist(character)) {
|
if (doesExist(character)) {
|
||||||
sendMessage(JSON.stringify({ type: 'player', become: character.name }));
|
become = character.name;
|
||||||
}
|
}
|
||||||
|
sendMessage(JSON.stringify({ type: 'player', become }));
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendInput(input: string) {
|
function sendInput(input: string) {
|
||||||
|
@ -96,10 +99,20 @@ export function App(props: AppProps) {
|
||||||
setActiveTurn(event.client === clientId);
|
setActiveTurn(event.client === clientId);
|
||||||
break;
|
break;
|
||||||
case 'player':
|
case 'player':
|
||||||
if (event.status === 'join' && doesExist(world) && event.client === clientId) {
|
if (doesExist(world) && event.client === clientId) {
|
||||||
|
switch (event.status) {
|
||||||
|
case 'join': {
|
||||||
const { character: characterName } = event;
|
const { character: characterName } = event;
|
||||||
const character = world.rooms.flatMap((room) => room.characters).find((a) => a.name === characterName);
|
const character = world.rooms.flatMap((room) => room.characters).find((a) => a.name === characterName);
|
||||||
setCharacter(character);
|
setCharacter(character);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'leave':
|
||||||
|
setCharacter(undefined);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// ignore other player events
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'players':
|
case 'players':
|
||||||
|
|
|
@ -8,7 +8,7 @@ render:
|
||||||
checkpoints: [
|
checkpoints: [
|
||||||
"diffusion-sdxl-dynavision-0-5-5-7.safetensors",
|
"diffusion-sdxl-dynavision-0-5-5-7.safetensors",
|
||||||
]
|
]
|
||||||
path: /tmp/adventure-images
|
path: /tmp/taleweave-images
|
||||||
sizes:
|
sizes:
|
||||||
landscape:
|
landscape:
|
||||||
width: 1280
|
width: 1280
|
||||||
|
|
|
@ -81,14 +81,18 @@ class AdventureClient(Client):
|
||||||
channel = message.channel
|
channel = message.channel
|
||||||
user_name = author.name # include nick
|
user_name = author.name # include nick
|
||||||
|
|
||||||
if message.content.startswith("!adventure"):
|
if message.content.startswith(
|
||||||
|
bot_config.command_prefix + bot_config.name_command
|
||||||
|
):
|
||||||
world = get_current_world()
|
world = get_current_world()
|
||||||
if world:
|
if world:
|
||||||
active_world = f"Active world: {world.name} (theme: {world.theme})"
|
active_world = f"Active world: {world.name} (theme: {world.theme})"
|
||||||
else:
|
else:
|
||||||
active_world = "No active world"
|
active_world = "No active world"
|
||||||
|
|
||||||
await message.channel.send(f"Hello! Welcome to Adventure! {active_world}")
|
await message.channel.send(
|
||||||
|
f"Hello! Welcome to {bot_config.name_title}! {active_world}"
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
if message.content.startswith("!help"):
|
if message.content.startswith("!help"):
|
||||||
|
|
|
@ -19,7 +19,7 @@ from pyee.base import EventEmitter
|
||||||
|
|
||||||
from taleweave.game_system import GameSystem
|
from taleweave.game_system import GameSystem
|
||||||
from taleweave.models.entity import Character, Room, World
|
from taleweave.models.entity import Character, Room, World
|
||||||
from taleweave.models.event import GameEvent
|
from taleweave.models.event import GameEvent, StatusEvent
|
||||||
from taleweave.utils.string import normalize_name
|
from taleweave.utils.string import normalize_name
|
||||||
|
|
||||||
logger = getLogger(__name__)
|
logger = getLogger(__name__)
|
||||||
|
@ -49,12 +49,17 @@ def get_event_name(event: GameEvent | Type[GameEvent]):
|
||||||
|
|
||||||
def broadcast(message: str | GameEvent):
|
def broadcast(message: str | GameEvent):
|
||||||
if isinstance(message, GameEvent):
|
if isinstance(message, GameEvent):
|
||||||
event_name = get_event_name(message)
|
event = message
|
||||||
logger.debug(f"broadcasting {event_name}")
|
|
||||||
event_emitter.emit(event_name, message)
|
|
||||||
else:
|
else:
|
||||||
logger.warning("broadcasting a string message is deprecated")
|
logger.warning(
|
||||||
event_emitter.emit(STRING_EVENT_TYPE, message)
|
"broadcasting a string message is deprecated, converting to status event: %s",
|
||||||
|
message,
|
||||||
|
)
|
||||||
|
event = StatusEvent(text=message)
|
||||||
|
|
||||||
|
event_name = get_event_name(event)
|
||||||
|
logger.debug(f"broadcasting {event_name}: {event}")
|
||||||
|
event_emitter.emit(event_name, event)
|
||||||
|
|
||||||
|
|
||||||
def is_union(type_: Type | UnionType):
|
def is_union(type_: Type | UnionType):
|
||||||
|
|
|
@ -286,6 +286,7 @@ def generate_character(
|
||||||
dest_room: Room,
|
dest_room: Room,
|
||||||
additional_prompt: str = "",
|
additional_prompt: str = "",
|
||||||
detail_prompt: str = "",
|
detail_prompt: str = "",
|
||||||
|
add_to_world_order: bool = True,
|
||||||
) -> Character:
|
) -> Character:
|
||||||
existing_characters = [character.name for character in list_characters(world)] + [
|
existing_characters = [character.name for character in list_characters(world)] + [
|
||||||
character.name for character in list_characters_in_room(dest_room)
|
character.name for character in list_characters_in_room(dest_room)
|
||||||
|
@ -350,6 +351,10 @@ def generate_character(
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.exception("error generating item")
|
logger.exception("error generating item")
|
||||||
|
|
||||||
|
if add_to_world_order:
|
||||||
|
logger.info(f"adding character {name} to end of world turn order")
|
||||||
|
world.order.append(name)
|
||||||
|
|
||||||
return character
|
return character
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,9 @@ class Size:
|
||||||
@dataclass
|
@dataclass
|
||||||
class DiscordBotConfig:
|
class DiscordBotConfig:
|
||||||
channels: List[str]
|
channels: List[str]
|
||||||
|
command_prefix: str
|
||||||
|
name_command: str
|
||||||
|
name_title: str
|
||||||
content_intent: bool = False
|
content_intent: bool = False
|
||||||
|
|
||||||
|
|
||||||
|
@ -24,6 +27,7 @@ class BotConfig:
|
||||||
class RenderConfig:
|
class RenderConfig:
|
||||||
cfg: int | IntRange
|
cfg: int | IntRange
|
||||||
checkpoints: List[str]
|
checkpoints: List[str]
|
||||||
|
count: int
|
||||||
path: str
|
path: str
|
||||||
sizes: Dict[str, Size]
|
sizes: Dict[str, Size]
|
||||||
steps: int | IntRange
|
steps: int | IntRange
|
||||||
|
@ -80,19 +84,27 @@ class Config:
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_CONFIG = Config(
|
DEFAULT_CONFIG = Config(
|
||||||
bot=BotConfig(discord=DiscordBotConfig(channels=["adventure"])),
|
bot=BotConfig(
|
||||||
|
discord=DiscordBotConfig(
|
||||||
|
channels=["taleweave"],
|
||||||
|
command_prefix="!",
|
||||||
|
name_command="taleweave",
|
||||||
|
name_title="Taleweave AI",
|
||||||
|
),
|
||||||
|
),
|
||||||
render=RenderConfig(
|
render=RenderConfig(
|
||||||
cfg=IntRange(min=5, max=8),
|
cfg=IntRange(min=5, max=8),
|
||||||
checkpoints=[
|
checkpoints=[
|
||||||
"diffusion-sdxl-dynavision-0-5-5-7.safetensors",
|
"diffusion-sdxl-dynavision-0-5-5-7.safetensors",
|
||||||
],
|
],
|
||||||
path="/tmp/adventure-images",
|
count=2,
|
||||||
|
path="/tmp/taleweave-images",
|
||||||
sizes={
|
sizes={
|
||||||
"landscape": Size(width=1024, height=768),
|
"landscape": Size(width=1024, height=768),
|
||||||
"portrait": Size(width=768, height=1024),
|
"portrait": Size(width=768, height=1024),
|
||||||
"square": Size(width=768, height=768),
|
"square": Size(width=768, height=768),
|
||||||
},
|
},
|
||||||
steps=IntRange(min=30, max=30),
|
steps=30,
|
||||||
),
|
),
|
||||||
server=ServerConfig(websocket=WebsocketServerConfig(host="localhost", port=8001)),
|
server=ServerConfig(websocket=WebsocketServerConfig(host="localhost", port=8001)),
|
||||||
world=WorldConfig(
|
world=WorldConfig(
|
||||||
|
|
|
@ -9,7 +9,6 @@ from random import choice, randint
|
||||||
from re import sub
|
from re import sub
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
from typing import List
|
from typing import List
|
||||||
from uuid import uuid4
|
|
||||||
|
|
||||||
import websocket # NOTE: websocket-client (https://github.com/websocket-client/websocket-client)
|
import websocket # NOTE: websocket-client (https://github.com/websocket-client/websocket-client)
|
||||||
from fnvhash import fnv1a_32
|
from fnvhash import fnv1a_32
|
||||||
|
@ -17,6 +16,7 @@ from jinja2 import Environment, FileSystemLoader, select_autoescape
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
from taleweave.context import broadcast
|
from taleweave.context import broadcast
|
||||||
|
from taleweave.models.base import uuid
|
||||||
from taleweave.models.config import DEFAULT_CONFIG, RenderConfig
|
from taleweave.models.config import DEFAULT_CONFIG, RenderConfig
|
||||||
from taleweave.models.entity import WorldEntity
|
from taleweave.models.entity import WorldEntity
|
||||||
from taleweave.models.event import (
|
from taleweave.models.event import (
|
||||||
|
@ -35,7 +35,7 @@ from .prompt import prompt_from_entity, prompt_from_event
|
||||||
logger = getLogger(__name__)
|
logger = getLogger(__name__)
|
||||||
|
|
||||||
server_address = environ["COMFY_API"]
|
server_address = environ["COMFY_API"]
|
||||||
client_id = uuid4().hex
|
client_id = uuid()
|
||||||
render_config: RenderConfig = DEFAULT_CONFIG.render
|
render_config: RenderConfig = DEFAULT_CONFIG.render
|
||||||
|
|
||||||
|
|
||||||
|
@ -265,12 +265,18 @@ def render_loop():
|
||||||
logger.info(
|
logger.info(
|
||||||
"using existing images for event %s: %s", event, existing_images
|
"using existing images for event %s: %s", event, existing_images
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if isinstance(event, WorldEntity):
|
||||||
|
title = event.name # TODO: generate a real title
|
||||||
|
else:
|
||||||
|
title = event.type
|
||||||
|
|
||||||
broadcast(
|
broadcast(
|
||||||
RenderEvent(
|
RenderEvent(
|
||||||
paths=existing_images,
|
paths=existing_images,
|
||||||
prompt="",
|
prompt="reusing existing images",
|
||||||
source=event,
|
source=event,
|
||||||
title="Existing Images",
|
title=title,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
|
@ -288,7 +294,7 @@ def render_loop():
|
||||||
# render or not
|
# render or not
|
||||||
if prompt:
|
if prompt:
|
||||||
logger.debug("rendering prompt for event %s: %s", event, prompt)
|
logger.debug("rendering prompt for event %s: %s", event, prompt)
|
||||||
image_paths = generate_images(prompt, 2, prefix=prefix)
|
image_paths = generate_images(prompt, render_config.count, prefix=prefix)
|
||||||
broadcast(
|
broadcast(
|
||||||
RenderEvent(paths=image_paths, prompt=prompt, source=event, title=title)
|
RenderEvent(paths=image_paths, prompt=prompt, source=event, title=title)
|
||||||
)
|
)
|
||||||
|
|
|
@ -20,7 +20,7 @@ from taleweave.context import (
|
||||||
subscribe,
|
subscribe,
|
||||||
)
|
)
|
||||||
from taleweave.models.config import DEFAULT_CONFIG, WebsocketServerConfig
|
from taleweave.models.config import DEFAULT_CONFIG, WebsocketServerConfig
|
||||||
from taleweave.models.entity import Character, Item, Room, World
|
from taleweave.models.entity import World, WorldEntity
|
||||||
from taleweave.models.event import (
|
from taleweave.models.event import (
|
||||||
GameEvent,
|
GameEvent,
|
||||||
PlayerEvent,
|
PlayerEvent,
|
||||||
|
@ -128,15 +128,40 @@ async def handler(websocket):
|
||||||
|
|
||||||
elif "become" in data:
|
elif "become" in data:
|
||||||
character_name = data["become"]
|
character_name = data["become"]
|
||||||
if has_player(character_name):
|
if character_name is not None and has_player(character_name):
|
||||||
logger.error(
|
logger.error(
|
||||||
f"character {character_name} is already in use"
|
f"character {character_name} is already in use"
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# TODO: should this always remove?
|
player = get_player(id)
|
||||||
|
if player and isinstance(player, RemotePlayer):
|
||||||
remove_player(id)
|
remove_player(id)
|
||||||
|
|
||||||
|
# TODO: deduplicate this leaving block
|
||||||
|
if character_name is None:
|
||||||
|
player_name = get_player_name(id)
|
||||||
|
logger.info(
|
||||||
|
"disconnecting player %s from %s",
|
||||||
|
player_name,
|
||||||
|
player.name,
|
||||||
|
)
|
||||||
|
broadcast_player_event(
|
||||||
|
player.name, player_name, "leave"
|
||||||
|
)
|
||||||
|
broadcast_player_list()
|
||||||
|
|
||||||
|
character, _ = get_character_agent_for_name(player.name)
|
||||||
|
if character and player.fallback_agent:
|
||||||
|
logger.info(
|
||||||
|
"restoring LLM agent for %s", player.name
|
||||||
|
)
|
||||||
|
set_character_agent(
|
||||||
|
player.name, character, player.fallback_agent
|
||||||
|
)
|
||||||
|
|
||||||
|
continue
|
||||||
|
|
||||||
character, llm_agent = get_character_agent_for_name(
|
character, llm_agent = get_character_agent_for_name(
|
||||||
character_name
|
character_name
|
||||||
)
|
)
|
||||||
|
@ -263,7 +288,7 @@ socket_thread = None
|
||||||
|
|
||||||
|
|
||||||
def server_json(obj):
|
def server_json(obj):
|
||||||
if isinstance(obj, (Character, Item, Room)):
|
if isinstance(obj, WorldEntity):
|
||||||
return obj.name
|
return obj.name
|
||||||
|
|
||||||
return world_json(obj)
|
return world_json(obj)
|
||||||
|
|
|
@ -22,14 +22,14 @@ rules:
|
||||||
type: room
|
type: room
|
||||||
temperature: hot
|
temperature: hot
|
||||||
chance: 0.2
|
chance: 0.2
|
||||||
trigger: [adventure.systems.sim.environment_triggers:hot_room]
|
trigger: [taleweave.systems.sim.environment_triggers:hot_room]
|
||||||
|
|
||||||
- group: environment-temperature
|
- group: environment-temperature
|
||||||
match:
|
match:
|
||||||
type: room
|
type: room
|
||||||
temperature: cold
|
temperature: cold
|
||||||
chance: 0.2
|
chance: 0.2
|
||||||
trigger: [adventure.systems.sim.environment_triggers:cold_room]
|
trigger: [taleweave.systems.sim.environment_triggers:cold_room]
|
||||||
|
|
||||||
labels:
|
labels:
|
||||||
- match:
|
- match:
|
||||||
|
|
|
@ -42,6 +42,9 @@ def initialize_weather(world: World):
|
||||||
room.attributes["time"] = time_of_day.name
|
room.attributes["time"] = time_of_day.name
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: generate indoor/outdoor attributes
|
||||||
|
|
||||||
|
|
||||||
def simulate_weather(world: World, turn: int, data: None = None):
|
def simulate_weather(world: World, turn: int, data: None = None):
|
||||||
time_of_day = get_time_of_day(turn)
|
time_of_day = get_time_of_day(turn)
|
||||||
for room in world.rooms:
|
for room in world.rooms:
|
||||||
|
|
|
@ -38,6 +38,9 @@ rules:
|
||||||
|
|
||||||
# weather initial state
|
# weather initial state
|
||||||
- group: weather
|
- group: weather
|
||||||
|
match:
|
||||||
|
type: room
|
||||||
|
outdoor: true
|
||||||
rule: |
|
rule: |
|
||||||
"weather" not in attributes
|
"weather" not in attributes
|
||||||
set:
|
set:
|
||||||
|
|
Loading…
Reference in New Issue