Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3,962 changes: 0 additions & 3,962 deletions gui.py

This file was deleted.

1,922 changes: 1,895 additions & 27 deletions poetry.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ authors = ["Your Name <you@example.com>"]

[tool.poetry.dependencies]
python = ">=3.10.0,<3.12"
requests = "^2.32.3"
requests = "^2.32.4"
replicate = "^1.0.2"
python-dotenv = "^1.0.0"
Pillow = "^11.1.0"
Expand Down
11 changes: 11 additions & 0 deletions run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env python3
import sys
import os

# Add the project root directory to the python path
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))

from src.main import main

if __name__ == "__main__":
main()
1,229 changes: 0 additions & 1,229 deletions shared_utils.py

This file was deleted.

Empty file added src/__init__.py
Empty file.
Empty file added src/core/__init__.py
Empty file.
File renamed without changes.
File renamed without changes.
1,222 changes: 23 additions & 1,199 deletions main.py → src/core/conversation_manager.py

Large diffs are not rendered by default.

530 changes: 530 additions & 0 deletions src/core/engine.py

Large diffs are not rendered by default.

59 changes: 59 additions & 0 deletions src/core/session_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import json
import os
from datetime import datetime

class SessionManager:
"""Manages saving and loading of conversation sessions"""

def __init__(self, sessions_dir="sessions"):
self.sessions_dir = sessions_dir
if not os.path.exists(self.sessions_dir):
os.makedirs(self.sessions_dir)

def save_session(self, filename, conversation, branch_conversations=None, active_branch=None, metadata=None):
"""Save the current session to a JSON file"""
if not filename.endswith('.json'):
filename += '.json'

filepath = os.path.join(self.sessions_dir, filename)

data = {
"conversation": conversation,
"branch_conversations": branch_conversations or {},
"active_branch": active_branch,
"metadata": metadata or {},
"saved_at": datetime.now().isoformat(),
"version": "1.0"
}

try:
with open(filepath, 'w', encoding='utf-8') as f:
json.dump(data, f, indent=2, ensure_ascii=False)
return True, filepath
except Exception as e:
return False, str(e)

def load_session(self, filename):
"""Load a session from a JSON file"""
filepath = os.path.join(self.sessions_dir, filename)

if not os.path.exists(filepath):
return False, f"File not found: {filename}"

try:
with open(filepath, 'r', encoding='utf-8') as f:
data = json.load(f)
return True, data
except Exception as e:
return False, str(e)

def list_sessions(self):
"""List available saved sessions"""
try:
files = [f for f in os.listdir(self.sessions_dir) if f.endswith('.json')]
# Sort by modification time, newest first
files.sort(key=lambda x: os.path.getmtime(os.path.join(self.sessions_dir, x)), reverse=True)
return files
except Exception as e:
print(f"Error listing sessions: {e}")
return []
95 changes: 95 additions & 0 deletions src/core/worker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import os
import json
import logging
import threading
from PyQt6.QtCore import QObject, QRunnable, pyqtSignal, pyqtSlot

from src.core.config import AI_MODELS
from src.services.llm_service import (
call_claude_api,
call_openrouter_api,
call_openai_api,
call_replicate_api,
call_deepseek_api,
)
from src.services.media_service import generate_video_with_sora
from src.core.config import SORA_SECONDS, SORA_SIZE

class WorkerSignals(QObject):
"""Defines the signals available from a running worker thread"""
finished = pyqtSignal()
error = pyqtSignal(str)
response = pyqtSignal(str, str)
result = pyqtSignal(str, object) # Signal for complete result object
progress = pyqtSignal(str)
streaming_chunk = pyqtSignal(str, str) # Signal for streaming tokens: (ai_name, chunk)


class Worker(QRunnable):
"""Worker thread for processing AI turns using QThreadPool"""

def __init__(self, ai_name, conversation, model, system_prompt, is_branch=False, branch_id=None, gui=None):
super().__init__()
self.ai_name = ai_name
self.conversation = conversation.copy() # Make a copy to prevent race conditions
self.model = model
self.system_prompt = system_prompt
self.is_branch = is_branch
self.branch_id = branch_id
self.gui = gui

# Create signals object
self.signals = WorkerSignals()

@pyqtSlot()
def run(self):
"""Process the AI turn when the thread is started"""
print(f"[Worker] >>> Starting run() for {self.ai_name} ({self.model})")
try:
# Emit progress update
self.signals.progress.emit(f"Processing {self.ai_name} turn with {self.model}...")

# Define streaming callback
def stream_chunk(chunk: str):
self.signals.streaming_chunk.emit(self.ai_name, chunk)

# Process the turn with streaming
from src.core.engine import ai_turn

print(f"[Worker] Calling ai_turn for {self.ai_name}...")
result = ai_turn(
self.ai_name,
self.conversation,
self.model,
self.system_prompt,
gui=self.gui,
streaming_callback=stream_chunk
)
print(f"[Worker] ai_turn completed for {self.ai_name}, result type: {type(result)}")

# Emit both the text response and the full result object
if isinstance(result, dict):
response_content = result.get('content', '')
print(f"[Worker] Emitting response for {self.ai_name}, content length: {len(response_content) if response_content else 0}")
# Emit the simple text response for backward compatibility
self.signals.response.emit(self.ai_name, response_content)
# Also emit the full result object for HTML contribution processing
self.signals.result.emit(self.ai_name, result)
else:
# Handle simple string responses
print(f"[Worker] Emitting string response for {self.ai_name}")
self.signals.response.emit(self.ai_name, result if result else "")
self.signals.result.emit(self.ai_name, {"content": result, "model": self.model})

# Emit finished signal
print(f"[Worker] <<< Finished run() for {self.ai_name}, emitting finished signal")
self.signals.finished.emit()

except Exception as e:
# Emit error signal
print(f"[Worker] !!! ERROR in run() for {self.ai_name}: {e}")
import traceback
traceback.print_exc()
self.signals.error.emit(str(e))
# Still emit finished signal even if there's an error
self.signals.finished.emit()
27 changes: 27 additions & 0 deletions src/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import sys
import os
from PyQt6.QtWidgets import QApplication
from src.ui.main_window import LiminalBackroomsApp
from src.ui.widgets.custom_widgets import COLORS # Ensure fonts or styles are loaded if needed
from src.utils.font_loader import load_fonts

def main():
"""Main entry point for the application"""
# Create the application
app = QApplication(sys.argv)
app.setApplicationName("Liminal Backrooms")

# Load custom fonts
load_fonts()

# Create the main window
main_window = LiminalBackroomsApp()

# Show the window
main_window.show()

# Run the event loop
sys.exit(app.exec())

if __name__ == "__main__":
main()
Empty file added src/services/__init__.py
Empty file.
Loading