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:
veguAI
2026-07-22 09:59:17 +03:00
committed by GitHub
parent b20684eeaa
commit 142f90141c
3 changed files with 70 additions and 22 deletions

View File

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

View File

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

View File

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