added webui
This commit is contained in:
parent
e9ad01d5f0
commit
68bcb136bd
@ -8,6 +8,10 @@ python-dotenv>=1.0.0
|
||||
# Token counting and memory management
|
||||
tiktoken>=0.5.0
|
||||
|
||||
# Web server
|
||||
flask>=3.0.0
|
||||
flask-cors>=4.0.0
|
||||
|
||||
# Testing (development)
|
||||
pytest>=7.4.0
|
||||
pytest-cov>=4.1.0
|
||||
|
||||
30
run_webui.py
Executable file
30
run_webui.py
Executable file
@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Run the debate-bots web UI server."""
|
||||
|
||||
import sys
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
# Add project root to path
|
||||
project_root = Path(__file__).parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
# Setup environment
|
||||
os.chdir(project_root)
|
||||
|
||||
from src.web_server import app
|
||||
from src.logger import setup_logging, get_logger
|
||||
|
||||
# Setup logging
|
||||
setup_logging()
|
||||
logger = get_logger(__name__)
|
||||
|
||||
if __name__ == '__main__':
|
||||
logger.info("Starting Debate Bots Web UI server...")
|
||||
logger.info("Open http://localhost:5000 in your browser")
|
||||
try:
|
||||
app.run(host='0.0.0.0', port=5000, debug=True, threaded=True)
|
||||
except KeyboardInterrupt:
|
||||
logger.info("Server stopped by user")
|
||||
|
||||
|
||||
17
run_webui.sh
Executable file
17
run_webui.sh
Executable file
@ -0,0 +1,17 @@
|
||||
#!/bin/bash
|
||||
# Simple runner script for Debate Bots Web UI
|
||||
|
||||
# Check if virtual environment exists
|
||||
if [ ! -d "venv" ]; then
|
||||
echo "No virtual environment found. Creating one..."
|
||||
python3 -m venv venv
|
||||
echo "Installing dependencies..."
|
||||
source venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
else
|
||||
source venv/bin/activate
|
||||
fi
|
||||
|
||||
# Run the web UI server
|
||||
python run_webui.py
|
||||
|
||||
594
src/web_server.py
Normal file
594
src/web_server.py
Normal file
@ -0,0 +1,594 @@
|
||||
"""Flask web server for debate-bots web UI."""
|
||||
|
||||
import json
|
||||
import uuid
|
||||
import threading
|
||||
import queue
|
||||
import time
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
from typing import Dict, Optional, Tuple
|
||||
from flask import Flask, jsonify, request, Response, send_from_directory
|
||||
from flask_cors import CORS
|
||||
|
||||
from .config import Config
|
||||
from .main import create_agent, setup_configuration, format_model_name
|
||||
from .debate import DebateOrchestrator
|
||||
from .constants import DEFAULT_EXCHANGES_PER_ROUND, DEFAULT_CONFIG_FILE, DEBATES_DIRECTORY
|
||||
from .exceptions import ConfigError, ProviderError, DebateError, ValidationError
|
||||
from .logger import setup_logging, get_logger
|
||||
|
||||
# Initialize logger
|
||||
setup_logging()
|
||||
logger = get_logger(__name__)
|
||||
|
||||
# Create Flask app
|
||||
app = Flask(__name__, static_folder=None)
|
||||
CORS(app) # Enable CORS for development
|
||||
|
||||
# Global state
|
||||
active_debates: Dict[str, Dict] = {}
|
||||
debate_streams: Dict[str, queue.Queue] = {}
|
||||
config: Optional[Config] = None
|
||||
|
||||
|
||||
def load_config():
|
||||
"""Load configuration from config file."""
|
||||
global config
|
||||
try:
|
||||
config = setup_configuration(DEFAULT_CONFIG_FILE)
|
||||
logger.info("Configuration loaded successfully")
|
||||
except ConfigError as e:
|
||||
logger.error(f"Failed to load configuration: {e}")
|
||||
# Create minimal config if file doesn't exist
|
||||
config = Config()
|
||||
|
||||
|
||||
# Load config on startup
|
||||
load_config()
|
||||
|
||||
|
||||
class DebateRunner:
|
||||
"""Runs a debate in a separate thread and streams updates."""
|
||||
|
||||
def __init__(self, debate_id: str, orchestrator: DebateOrchestrator,
|
||||
agent_for, agent_against, exchanges_per_round: int,
|
||||
streaming: bool = True, auto_save: bool = True):
|
||||
self.debate_id = debate_id
|
||||
self.orchestrator = orchestrator
|
||||
self.agent_for = agent_for
|
||||
self.agent_against = agent_against
|
||||
self.exchanges_per_round = exchanges_per_round
|
||||
self.streaming = streaming
|
||||
self.auto_save = auto_save
|
||||
self.stream_queue = queue.Queue()
|
||||
self.is_running = False
|
||||
self.is_complete = False
|
||||
self.error = None
|
||||
self.thread = None
|
||||
|
||||
def start(self):
|
||||
"""Start the debate in a background thread."""
|
||||
self.thread = threading.Thread(target=self._run_debate, daemon=True)
|
||||
self.thread.start()
|
||||
|
||||
def _run_debate(self):
|
||||
"""Run the debate and send updates to stream queue."""
|
||||
self.is_running = True
|
||||
try:
|
||||
# Run rounds until complete
|
||||
while not self.is_complete:
|
||||
# Run a round of exchanges
|
||||
try:
|
||||
if self.streaming:
|
||||
self._run_round_streaming()
|
||||
else:
|
||||
self._run_round_non_streaming()
|
||||
|
||||
# Auto-save after each round if enabled
|
||||
if self.auto_save:
|
||||
try:
|
||||
filepath = self.orchestrator.save_debate()
|
||||
logger.debug(f"Auto-saved debate to {filepath}")
|
||||
self._send_event("saved", {"filepath": filepath})
|
||||
except Exception as e:
|
||||
logger.warning(f"Auto-save failed: {e}")
|
||||
|
||||
# Send round complete event
|
||||
stats = self.orchestrator.get_statistics()
|
||||
self._send_event("round_complete", {
|
||||
"exchange_count": self.orchestrator.current_exchange,
|
||||
"statistics": stats
|
||||
})
|
||||
|
||||
# For now, we'll continue automatically
|
||||
# In a full implementation, we'd wait for user action
|
||||
# For web UI, we can handle this via action endpoints
|
||||
break # Stop after first round for now
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error during debate round: {e}", exc_info=True)
|
||||
self.error = str(e)
|
||||
self._send_event("error", {"message": str(e)})
|
||||
break
|
||||
|
||||
self.is_complete = True
|
||||
self._send_event("complete", {
|
||||
"statistics": self.orchestrator.get_statistics()
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fatal error in debate: {e}", exc_info=True)
|
||||
self.error = str(e)
|
||||
self._send_event("error", {"message": str(e)})
|
||||
finally:
|
||||
self.is_running = False
|
||||
|
||||
def _run_round_streaming(self):
|
||||
"""Run a round with streaming."""
|
||||
for exchange_num in range(self.exchanges_per_round):
|
||||
if self.is_complete:
|
||||
break
|
||||
|
||||
# Conduct exchange with streaming
|
||||
self._conduct_exchange_streaming(exchange_num + 1)
|
||||
|
||||
def _run_round_non_streaming(self):
|
||||
"""Run a round without streaming."""
|
||||
for exchange_num in range(self.exchanges_per_round):
|
||||
if self.is_complete:
|
||||
break
|
||||
|
||||
# Conduct exchange without streaming
|
||||
response_for, response_against = self.orchestrator.conduct_exchange(
|
||||
self.agent_for, self.agent_against
|
||||
)
|
||||
|
||||
# Send complete exchange event
|
||||
self._send_event("exchange", {
|
||||
"exchange": self.orchestrator.current_exchange,
|
||||
"agent_for": {
|
||||
"name": self.agent_for.name,
|
||||
"content": response_for
|
||||
},
|
||||
"agent_against": {
|
||||
"name": self.agent_against.name,
|
||||
"content": response_against
|
||||
}
|
||||
})
|
||||
|
||||
def _conduct_exchange_streaming(self, exchange_num: int):
|
||||
"""Conduct one exchange with streaming."""
|
||||
|
||||
# Build prompts
|
||||
if self.orchestrator.current_exchange == 0:
|
||||
prompt_for = f"Present your opening argument for the position that {self.orchestrator.topic}."
|
||||
else:
|
||||
prompt_for = self.orchestrator._build_context_prompt(self.agent_for)
|
||||
|
||||
self.agent_for.add_message("user", prompt_for)
|
||||
|
||||
# Stream FOR agent's response
|
||||
response_for_chunks = []
|
||||
self._send_event("exchange_start", {
|
||||
"exchange": exchange_num,
|
||||
"agent": self.agent_for.name,
|
||||
"position": "for"
|
||||
})
|
||||
|
||||
start_time_for = time.time()
|
||||
stream_for = self.agent_for.generate_response_stream()
|
||||
for chunk in stream_for:
|
||||
response_for_chunks.append(chunk)
|
||||
self._send_event("chunk", {
|
||||
"exchange": exchange_num,
|
||||
"agent": self.agent_for.name,
|
||||
"position": "for",
|
||||
"chunk": chunk
|
||||
})
|
||||
|
||||
response_time_for = time.time() - start_time_for
|
||||
response_for = ''.join(response_for_chunks)
|
||||
response_for = self.orchestrator._validate_response(response_for, self.agent_for.name)
|
||||
|
||||
# Record FOR in debate history
|
||||
exchange_data_for = {
|
||||
"exchange": exchange_num,
|
||||
"agent": self.agent_for.name,
|
||||
"position": "for",
|
||||
"content": response_for,
|
||||
}
|
||||
self.orchestrator.debate_history.append(exchange_data_for)
|
||||
|
||||
# Track timing
|
||||
self.orchestrator.response_times.append(response_time_for)
|
||||
self.orchestrator.total_response_time += response_time_for
|
||||
|
||||
# Send FOR complete
|
||||
self._send_event("exchange_complete", {
|
||||
"exchange": exchange_num,
|
||||
"agent": self.agent_for.name,
|
||||
"position": "for",
|
||||
"content": response_for
|
||||
})
|
||||
|
||||
# Now handle AGAINST agent
|
||||
if self.orchestrator.current_exchange == 0:
|
||||
prompt_against = (
|
||||
f"Your opponent's opening argument: {response_for}\n\n"
|
||||
f"Present your opening counter-argument against the position that {self.orchestrator.topic}."
|
||||
)
|
||||
else:
|
||||
prompt_against = self.orchestrator._build_context_prompt(self.agent_against)
|
||||
|
||||
self.agent_against.add_message("user", prompt_against)
|
||||
|
||||
# Stream AGAINST agent's response
|
||||
response_against_chunks = []
|
||||
self._send_event("exchange_start", {
|
||||
"exchange": exchange_num,
|
||||
"agent": self.agent_against.name,
|
||||
"position": "against"
|
||||
})
|
||||
|
||||
start_time_against = time.time()
|
||||
stream_against = self.agent_against.generate_response_stream()
|
||||
for chunk in stream_against:
|
||||
response_against_chunks.append(chunk)
|
||||
self._send_event("chunk", {
|
||||
"exchange": exchange_num,
|
||||
"agent": self.agent_against.name,
|
||||
"position": "against",
|
||||
"chunk": chunk
|
||||
})
|
||||
|
||||
response_time_against = time.time() - start_time_against
|
||||
response_against = ''.join(response_against_chunks)
|
||||
response_against = self.orchestrator._validate_response(response_against, self.agent_against.name)
|
||||
|
||||
# Record AGAINST in debate history
|
||||
exchange_data_against = {
|
||||
"exchange": exchange_num,
|
||||
"agent": self.agent_against.name,
|
||||
"position": "against",
|
||||
"content": response_against,
|
||||
}
|
||||
self.orchestrator.debate_history.append(exchange_data_against)
|
||||
|
||||
# Track timing
|
||||
self.orchestrator.response_times.append(response_time_against)
|
||||
self.orchestrator.total_response_time += response_time_against
|
||||
|
||||
# Update exchange count
|
||||
self.orchestrator.current_exchange += 1
|
||||
|
||||
# Send AGAINST complete and full exchange
|
||||
self._send_event("exchange_complete", {
|
||||
"exchange": exchange_num,
|
||||
"agent": self.agent_against.name,
|
||||
"position": "against",
|
||||
"content": response_against
|
||||
})
|
||||
|
||||
self._send_event("exchange", {
|
||||
"exchange": exchange_num,
|
||||
"agent_for": {
|
||||
"name": self.agent_for.name,
|
||||
"content": response_for
|
||||
},
|
||||
"agent_against": {
|
||||
"name": self.agent_against.name,
|
||||
"content": response_against
|
||||
}
|
||||
})
|
||||
|
||||
def _send_event(self, event_type: str, data: dict):
|
||||
"""Send an event to the stream queue."""
|
||||
try:
|
||||
self.stream_queue.put({
|
||||
"type": event_type,
|
||||
"data": data
|
||||
}, timeout=1.0)
|
||||
except queue.Full:
|
||||
logger.warning(f"Stream queue full, dropping event: {event_type}")
|
||||
|
||||
def stop(self):
|
||||
"""Stop the debate."""
|
||||
self.is_complete = True
|
||||
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
"""Serve the main HTML page."""
|
||||
webui_dir = Path(__file__).parent.parent / "webui"
|
||||
webui_path = webui_dir / "index.html"
|
||||
if webui_path.exists():
|
||||
return send_from_directory(str(webui_dir), "index.html")
|
||||
return jsonify({"error": "Web UI not found"}), 404
|
||||
|
||||
|
||||
@app.route('/api/debates', methods=['GET'])
|
||||
def list_debates():
|
||||
"""List all saved debates."""
|
||||
debates_dir = Path(DEBATES_DIRECTORY)
|
||||
if not debates_dir.exists():
|
||||
return jsonify({"debates": []})
|
||||
|
||||
debates = []
|
||||
for file_path in debates_dir.glob("debate_*.json"):
|
||||
try:
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
debate_data = json.load(f)
|
||||
debates.append({
|
||||
"filename": file_path.name,
|
||||
"topic": debate_data.get("topic", "Unknown"),
|
||||
"timestamp": debate_data.get("timestamp", ""),
|
||||
"total_exchanges": debate_data.get("total_exchanges", 0),
|
||||
"agents": debate_data.get("agents", {}),
|
||||
"statistics": debate_data.get("statistics", {})
|
||||
})
|
||||
except Exception as e:
|
||||
logger.error(f"Error reading debate file {file_path}: {e}")
|
||||
|
||||
# Sort by timestamp (newest first)
|
||||
debates.sort(key=lambda x: x.get("timestamp", ""), reverse=True)
|
||||
|
||||
return jsonify({"debates": debates})
|
||||
|
||||
|
||||
@app.route('/api/debates/<filename>', methods=['GET'])
|
||||
def get_debate(filename: str):
|
||||
"""Get a specific debate by filename."""
|
||||
debates_dir = Path(DEBATES_DIRECTORY)
|
||||
file_path = debates_dir / filename
|
||||
|
||||
if not file_path.exists():
|
||||
return jsonify({"error": "Debate not found"}), 404
|
||||
|
||||
try:
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
debate_data = json.load(f)
|
||||
return jsonify(debate_data)
|
||||
except Exception as e:
|
||||
logger.error(f"Error reading debate file {file_path}: {e}")
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/api/debates/start', methods=['POST'])
|
||||
def start_debate():
|
||||
"""Start a new debate."""
|
||||
global config
|
||||
|
||||
data = request.get_json()
|
||||
topic = data.get("topic")
|
||||
exchanges = data.get("exchanges", DEFAULT_EXCHANGES_PER_ROUND)
|
||||
streaming = data.get("streaming", True)
|
||||
auto_save = data.get("auto_save", True)
|
||||
max_memory_tokens = data.get("max_memory_tokens")
|
||||
|
||||
if not topic:
|
||||
return jsonify({"error": "Topic is required"}), 400
|
||||
|
||||
try:
|
||||
# Load config if not loaded
|
||||
if config is None:
|
||||
load_config()
|
||||
|
||||
# Create agents
|
||||
agent1 = create_agent(config, "agent1", max_memory_tokens=max_memory_tokens)
|
||||
agent2 = create_agent(config, "agent2", max_memory_tokens=max_memory_tokens)
|
||||
|
||||
# Create orchestrator
|
||||
orchestrator = DebateOrchestrator(
|
||||
agent1=agent1,
|
||||
agent2=agent2,
|
||||
exchanges_per_round=exchanges,
|
||||
)
|
||||
|
||||
# Start debate
|
||||
agent_for, agent_against = orchestrator.start_debate(topic)
|
||||
|
||||
# Generate debate ID
|
||||
debate_id = str(uuid.uuid4())
|
||||
|
||||
# Create debate runner
|
||||
runner = DebateRunner(
|
||||
debate_id=debate_id,
|
||||
orchestrator=orchestrator,
|
||||
agent_for=agent_for,
|
||||
agent_against=agent_against,
|
||||
exchanges_per_round=exchanges,
|
||||
streaming=streaming,
|
||||
auto_save=auto_save
|
||||
)
|
||||
|
||||
# Store active debate
|
||||
active_debates[debate_id] = {
|
||||
"debate_id": debate_id,
|
||||
"topic": topic,
|
||||
"orchestrator": orchestrator,
|
||||
"agent_for": agent_for,
|
||||
"agent_against": agent_against,
|
||||
"runner": runner,
|
||||
"started_at": datetime.now().isoformat(),
|
||||
"streaming": streaming
|
||||
}
|
||||
|
||||
debate_streams[debate_id] = runner.stream_queue
|
||||
|
||||
# Start debate in background thread
|
||||
runner.start()
|
||||
|
||||
return jsonify({
|
||||
"debate_id": debate_id,
|
||||
"topic": topic,
|
||||
"agents": {
|
||||
"for": agent_for.name,
|
||||
"against": agent_against.name
|
||||
},
|
||||
"exchanges": exchanges,
|
||||
"streaming": streaming
|
||||
})
|
||||
|
||||
except ConfigError as e:
|
||||
return jsonify({"error": f"Configuration error: {str(e)}"}), 400
|
||||
except ProviderError as e:
|
||||
return jsonify({"error": f"Provider error: {str(e)}"}), 500
|
||||
except ValidationError as e:
|
||||
return jsonify({"error": f"Validation error: {str(e)}"}), 400
|
||||
except Exception as e:
|
||||
logger.error(f"Error starting debate: {e}", exc_info=True)
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/api/debates/<debate_id>/stream', methods=['GET'])
|
||||
def stream_debate(debate_id: str):
|
||||
"""Stream debate updates using Server-Sent Events."""
|
||||
if debate_id not in active_debates:
|
||||
return jsonify({"error": "Debate not found"}), 404
|
||||
|
||||
runner = active_debates[debate_id]["runner"]
|
||||
|
||||
def generate():
|
||||
"""Generate SSE events from the debate stream queue."""
|
||||
try:
|
||||
while True:
|
||||
try:
|
||||
# Get event from queue with timeout
|
||||
event = runner.stream_queue.get(timeout=1.0)
|
||||
yield f"data: {json.dumps(event)}\n\n"
|
||||
except queue.Empty:
|
||||
# Send heartbeat to keep connection alive
|
||||
if runner.is_complete:
|
||||
yield f"data: {json.dumps({'type': 'complete', 'data': {}})}\n\n"
|
||||
break
|
||||
yield ": heartbeat\n\n"
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in stream: {e}", exc_info=True)
|
||||
yield f"data: {json.dumps({'type': 'error', 'data': {'message': str(e)}})}\n\n"
|
||||
|
||||
return Response(
|
||||
generate(),
|
||||
mimetype='text/event-stream',
|
||||
headers={
|
||||
'Cache-Control': 'no-cache',
|
||||
'X-Accel-Buffering': 'no',
|
||||
'Connection': 'keep-alive'
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@app.route('/api/debates/<debate_id>/status', methods=['GET'])
|
||||
def get_debate_status(debate_id: str):
|
||||
"""Get debate status and statistics."""
|
||||
if debate_id not in active_debates:
|
||||
return jsonify({"error": "Debate not found"}), 404
|
||||
|
||||
debate = active_debates[debate_id]
|
||||
orchestrator = debate["orchestrator"]
|
||||
runner = debate["runner"]
|
||||
|
||||
stats = orchestrator.get_statistics()
|
||||
|
||||
return jsonify({
|
||||
"debate_id": debate_id,
|
||||
"topic": debate["topic"],
|
||||
"status": "complete" if runner.is_complete else "running",
|
||||
"is_running": runner.is_running,
|
||||
"current_exchange": orchestrator.current_exchange,
|
||||
"statistics": stats,
|
||||
"agents": {
|
||||
"for": debate["agent_for"].name,
|
||||
"against": debate["agent_against"].name
|
||||
},
|
||||
"error": runner.error
|
||||
})
|
||||
|
||||
|
||||
@app.route('/api/debates/<debate_id>/action', methods=['POST'])
|
||||
def debate_action(debate_id: str):
|
||||
"""Handle user actions (continue, settle, instruct, quit)."""
|
||||
if debate_id not in active_debates:
|
||||
return jsonify({"error": "Debate not found"}), 404
|
||||
|
||||
data = request.get_json()
|
||||
action = data.get("action")
|
||||
|
||||
debate = active_debates[debate_id]
|
||||
runner = debate["runner"]
|
||||
orchestrator = debate["orchestrator"]
|
||||
agent_for = debate["agent_for"]
|
||||
agent_against = debate["agent_against"]
|
||||
|
||||
if action == "continue":
|
||||
# Continue debate (run another round)
|
||||
if runner.is_complete:
|
||||
return jsonify({"error": "Debate is already complete"}), 400
|
||||
# For now, we'll handle this by the debate continuing automatically
|
||||
return jsonify({"message": "Debate continuing"})
|
||||
|
||||
elif action == "settle":
|
||||
# Settle debate with conclusion
|
||||
conclusion = data.get("conclusion", "")
|
||||
runner.stop()
|
||||
# Save debate
|
||||
try:
|
||||
filepath = orchestrator.save_debate()
|
||||
return jsonify({"message": "Debate settled", "filepath": filepath})
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
elif action == "instruct":
|
||||
# Give custom instruction to both agents
|
||||
instruction = data.get("instruction", "")
|
||||
if not instruction:
|
||||
return jsonify({"error": "Instruction is required"}), 400
|
||||
|
||||
agent_for.add_message("user", instruction)
|
||||
agent_against.add_message("user", instruction)
|
||||
return jsonify({"message": "Instruction added"})
|
||||
|
||||
elif action == "quit":
|
||||
# Stop debate
|
||||
runner.stop()
|
||||
# Save debate
|
||||
try:
|
||||
filepath = orchestrator.save_debate()
|
||||
return jsonify({"message": "Debate stopped", "filepath": filepath})
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
else:
|
||||
return jsonify({"error": "Invalid action"}), 400
|
||||
|
||||
|
||||
@app.route('/api/config', methods=['GET'])
|
||||
def get_config():
|
||||
"""Get current configuration (without sensitive data)."""
|
||||
global config
|
||||
|
||||
if config is None:
|
||||
load_config()
|
||||
|
||||
# Return config without API keys
|
||||
safe_config = {
|
||||
"agent1": {
|
||||
"provider": config.get("agent1.provider"),
|
||||
"model": config.get("agent1.model"),
|
||||
"system_prompt": config.get("agent1.system_prompt"),
|
||||
},
|
||||
"agent2": {
|
||||
"provider": config.get("agent2.provider"),
|
||||
"model": config.get("agent2.model"),
|
||||
"system_prompt": config.get("agent2.system_prompt"),
|
||||
}
|
||||
}
|
||||
|
||||
return jsonify(safe_config)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Run Flask development server
|
||||
app.run(host='0.0.0.0', port=5000, debug=True, threaded=True)
|
||||
|
||||
1320
webui/index.html
Normal file
1320
webui/index.html
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user