Spaces:
Sleeping
Sleeping
File size: 3,232 Bytes
4ae4ae8 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 | """Card engine — state machine that builds a thought card from conversation."""
from __future__ import annotations
from dataclasses import dataclass, field
from enum import Enum
from distortion_parser import detect_distortions
from phase_detector import detect_phase
from session import ThoughtCard
class CardState(Enum):
IDLE = "idle"
TRIGGERED = "triggered"
DISTORTION_TAGGED = "distortion_tagged"
EVIDENCE_GATHERING = "evidence_gathering"
REFRAMING = "reframing"
COMPLETE = "complete"
@dataclass
class ActiveCard:
"""Represents a card being built during conversation."""
state: CardState = CardState.IDLE
card: ThoughtCard = field(default_factory=ThoughtCard)
turn_count: int = 0 # turns since card was triggered
def update_card(active: ActiveCard, model_response: str, user_message: str) -> ActiveCard:
"""Update the active card based on model response and user message.
This is called after each model response. It advances the card state machine.
"""
active.turn_count += 1
# Detect distortions in model output
distortions = detect_distortions(model_response)
if distortions:
for d in distortions:
if d not in active.card.distortions:
active.card.distortions.append(d)
# Detect phase
phase = detect_phase(model_response)
# State transitions
if active.state == CardState.IDLE:
# Card triggers when model reflects back a thought or names a distortion
if distortions or phase in ("automatic_thought", "situation"):
active.state = CardState.TRIGGERED
if user_message and not active.card.automatic_thought:
active.card.automatic_thought = _extract_thought(user_message)
elif active.state == CardState.TRIGGERED:
if distortions:
active.state = CardState.DISTORTION_TAGGED
elif phase in ("evidence_for", "evidence_against"):
active.state = CardState.EVIDENCE_GATHERING
elif active.state == CardState.DISTORTION_TAGGED:
if phase in ("evidence_for", "evidence_against"):
active.state = CardState.EVIDENCE_GATHERING
elif active.state == CardState.EVIDENCE_GATHERING:
# Accumulate evidence from user messages
if phase == "evidence_for" and user_message:
active.card.evidence_for.append(user_message[:120])
elif phase == "evidence_against" and user_message:
active.card.evidence_against.append(user_message[:120])
elif phase == "reframe":
active.state = CardState.REFRAMING
elif active.state == CardState.REFRAMING:
if user_message and not active.card.balanced_thought:
active.card.balanced_thought = user_message[:200]
active.state = CardState.COMPLETE
return active
def should_show_card(active: ActiveCard) -> bool:
"""Whether the card should be visible in the UI."""
return active.state != CardState.IDLE
def reset_card() -> ActiveCard:
"""Reset to a fresh card."""
return ActiveCard()
def _extract_thought(message: str) -> str:
"""Extract the core thought from a user message (first 150 chars)."""
return message[:150].strip()
|