mirror of
https://github.com/vegu-ai/talemate.git
synced 2026-09-02 03:59:12 +02:00
fix: system_time template function crashed on Windows (glibc-only strftime codes) (#134)
* fix: system_time template function crashed on Windows (glibc-only strftime codes) #131 * review: type system_time format as Literal, pin C locale in exact-string tests
This commit is contained in:
@@ -26,6 +26,7 @@
|
||||
- "Pi Bridge Client: The Docker image now ships with pi preinstalled, and pi's configuration directory (models.json, auth.json) is mounted from ./pi next to the compose file, so the client works in Docker out of the box."
|
||||
- "Scene Browser Landing Page: The Quick Load recent-scene cards are smaller and scale with the viewport width, so a full row of recents no longer pushes the Scene Library far down the page."
|
||||
fixes:
|
||||
- "Prompt Templates: The `system_time` template function crashed with 'Invalid format string' on Windows — it used strftime codes only available on Linux/macOS. The time is now formatted platform-independently with identical output."
|
||||
- "Director Chat: Images created through the director chat's image generation action never appeared in the chat — the generation completed and the image was saved to the scene's assets, but the 'Image Generated' message was inserted into a newly created orphan chat instead of the conversation that requested it. The message is now inserted into the initiating chat."
|
||||
- "Node Editor: The Build Prompt node's `memory_prompt` input socket and the Generate Response node's `action_type` input socket were silently ignored — only the node property ever took effect, so a graph computing either value dynamically and wiring it in had no effect. Non-empty wired values now win over the property (the property still applies when the socket is unconnected or resolves empty). Note: graphs that already had something connected to these sockets will start honoring the connection."
|
||||
- "Scene Library: Deleting the save file of the currently loaded scene is now refused with an error - previously the file (and its version history) was deleted but silently recreated by the next save or autosave, making the delete appear to not work. Load a different scene first."
|
||||
|
||||
@@ -16,7 +16,7 @@ import re
|
||||
import uuid
|
||||
from contextvars import ContextVar
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
from typing import Any, Literal
|
||||
from enum import Enum
|
||||
|
||||
import jinja2
|
||||
@@ -1067,7 +1067,9 @@ class Prompt(pydantic.BaseModel):
|
||||
return ""
|
||||
return iso8601_diff_to_human(iso8601_time, scene.ts)
|
||||
|
||||
def system_time(self, format: str = "full") -> str:
|
||||
def system_time(
|
||||
self, format: Literal["full", "date", "time", "iso", "datetime"] = "full"
|
||||
) -> str:
|
||||
"""
|
||||
Returns the current system time in a clear, LLM-friendly format.
|
||||
|
||||
@@ -1084,18 +1086,21 @@ class Prompt(pydantic.BaseModel):
|
||||
"""
|
||||
now = datetime.now()
|
||||
|
||||
if format == "full":
|
||||
return now.strftime("%A, %B %-d, %Y at %-I:%M %p")
|
||||
elif format == "date":
|
||||
return now.strftime("%B %-d, %Y")
|
||||
# %-d / %-I are glibc-only and raise ValueError on Windows, so the
|
||||
# unpadded day and 12-hour clock are built from datetime attributes.
|
||||
hour12 = now.hour % 12 or 12
|
||||
time_str = f"{hour12}:{now:%M} {now:%p}"
|
||||
|
||||
if format == "date":
|
||||
return f"{now:%B} {now.day}, {now.year}"
|
||||
elif format == "time":
|
||||
return now.strftime("%-I:%M %p")
|
||||
return time_str
|
||||
elif format == "iso":
|
||||
return now.strftime("%Y-%m-%dT%H:%M:%S")
|
||||
elif format == "datetime":
|
||||
return now.strftime("%Y-%m-%d %H:%M:%S")
|
||||
else:
|
||||
return now.strftime("%A, %B %-d, %Y at %-I:%M %p")
|
||||
return f"{now:%A}, {now:%B} {now.day}, {now.year} at {time_str}"
|
||||
|
||||
def text_to_chunks(self, text: str, chunk_size: int = 512) -> list[str]:
|
||||
"""
|
||||
|
||||
@@ -25,12 +25,15 @@ boundary, NOT Prompt.request).
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import locale
|
||||
from collections import deque
|
||||
from datetime import datetime
|
||||
|
||||
import pytest
|
||||
|
||||
from conftest import client_responses
|
||||
|
||||
import talemate.prompts.base as prompts_base
|
||||
from talemate.prompts.base import (
|
||||
JoinableList,
|
||||
Prompt,
|
||||
@@ -244,23 +247,62 @@ class TestRandomAndBullet:
|
||||
|
||||
|
||||
class TestSystemTime:
|
||||
def test_full_format(self):
|
||||
p = Prompt.from_text("X")
|
||||
s = p.system_time("full")
|
||||
# Should contain weekday name and 'at'
|
||||
assert " at " in s
|
||||
@pytest.fixture
|
||||
def fixed_now(self, monkeypatch):
|
||||
# The exact-string assertions below assume the C locale for %A/%B/%p.
|
||||
prev_locale = locale.setlocale(locale.LC_TIME)
|
||||
locale.setlocale(locale.LC_TIME, "C")
|
||||
|
||||
def test_iso_format(self):
|
||||
p = Prompt.from_text("X")
|
||||
s = p.system_time("iso")
|
||||
# ISO-like: "YYYY-MM-DDTHH:MM:SS"
|
||||
assert s[4] == "-"
|
||||
assert "T" in s
|
||||
def _freeze(dt):
|
||||
class _FrozenDatetime(datetime):
|
||||
@classmethod
|
||||
def now(cls, tz=None):
|
||||
return dt
|
||||
|
||||
def test_unknown_format_falls_back_to_full(self):
|
||||
monkeypatch.setattr(prompts_base, "datetime", _FrozenDatetime)
|
||||
|
||||
yield _freeze
|
||||
locale.setlocale(locale.LC_TIME, prev_locale)
|
||||
|
||||
def test_full_format(self, fixed_now):
|
||||
fixed_now(datetime(2026, 2, 5, 14, 30, 45))
|
||||
p = Prompt.from_text("X")
|
||||
s = p.system_time("totally-bogus")
|
||||
assert " at " in s
|
||||
assert p.system_time("full") == "Thursday, February 5, 2026 at 2:30 PM"
|
||||
|
||||
def test_date_format(self, fixed_now):
|
||||
fixed_now(datetime(2026, 2, 5, 14, 30, 45))
|
||||
p = Prompt.from_text("X")
|
||||
assert p.system_time("date") == "February 5, 2026"
|
||||
|
||||
def test_time_format(self, fixed_now):
|
||||
fixed_now(datetime(2026, 2, 5, 14, 30, 45))
|
||||
p = Prompt.from_text("X")
|
||||
assert p.system_time("time") == "2:30 PM"
|
||||
|
||||
def test_time_format_midnight(self, fixed_now):
|
||||
fixed_now(datetime(2026, 2, 5, 0, 5, 0))
|
||||
p = Prompt.from_text("X")
|
||||
assert p.system_time("time") == "12:05 AM"
|
||||
|
||||
def test_time_format_noon(self, fixed_now):
|
||||
fixed_now(datetime(2026, 2, 5, 12, 5, 0))
|
||||
p = Prompt.from_text("X")
|
||||
assert p.system_time("time") == "12:05 PM"
|
||||
|
||||
def test_iso_format(self, fixed_now):
|
||||
fixed_now(datetime(2026, 2, 5, 14, 30, 45))
|
||||
p = Prompt.from_text("X")
|
||||
assert p.system_time("iso") == "2026-02-05T14:30:45"
|
||||
|
||||
def test_datetime_format(self, fixed_now):
|
||||
fixed_now(datetime(2026, 2, 5, 14, 30, 45))
|
||||
p = Prompt.from_text("X")
|
||||
assert p.system_time("datetime") == "2026-02-05 14:30:45"
|
||||
|
||||
def test_unknown_format_falls_back_to_full(self, fixed_now):
|
||||
fixed_now(datetime(2026, 2, 5, 14, 30, 45))
|
||||
p = Prompt.from_text("X")
|
||||
assert p.system_time("totally-bogus") == "Thursday, February 5, 2026 at 2:30 PM"
|
||||
|
||||
|
||||
class TestTimeDiff:
|
||||
|
||||
Reference in New Issue
Block a user