mirror of
https://github.com/MervinPraison/PraisonAI.git
synced 2026-11-07 17:33:33 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c5f4feae62 | ||
|
|
8fa9a69f84 | ||
|
|
8293d5b106 | ||
|
|
a2d6e6b889 | ||
|
|
17b7a6aa90 | ||
|
|
1513d729ba |
No files matched your search
@@ -0,0 +1,60 @@
|
||||
modules = ["python-3.12", "nodejs-20", "rust-stable", "bash"]
|
||||
[agent]
|
||||
expertMode = true
|
||||
integrations = ["github:1.0.0"]
|
||||
|
||||
[nix]
|
||||
channel = "stable-25_05"
|
||||
packages = ["libxcrypt"]
|
||||
|
||||
[[ports]]
|
||||
localPort = 5000
|
||||
externalPort = 80
|
||||
|
||||
[[ports]]
|
||||
localPort = 8000
|
||||
externalPort = 8000
|
||||
|
||||
[workflows]
|
||||
runButton = "Project"
|
||||
|
||||
[[workflows.workflow]]
|
||||
name = "Project"
|
||||
mode = "parallel"
|
||||
author = "agent"
|
||||
|
||||
[[workflows.workflow.tasks]]
|
||||
task = "workflow.run"
|
||||
args = "Start application"
|
||||
|
||||
[[workflows.workflow.tasks]]
|
||||
task = "workflow.run"
|
||||
args = "Backend API"
|
||||
|
||||
[[workflows.workflow]]
|
||||
name = "Start application"
|
||||
author = "agent"
|
||||
|
||||
[workflows.workflow.metadata]
|
||||
outputType = "webview"
|
||||
|
||||
[[workflows.workflow.tasks]]
|
||||
task = "shell.exec"
|
||||
args = "python run.py"
|
||||
waitForPort = 5000
|
||||
|
||||
[[workflows.workflow]]
|
||||
name = "Backend API"
|
||||
author = "agent"
|
||||
|
||||
[[workflows.workflow.tasks]]
|
||||
task = "shell.exec"
|
||||
args = "python backend/main.py"
|
||||
waitForPort = 8000
|
||||
|
||||
[workflows.workflow.metadata]
|
||||
outputType = "console"
|
||||
|
||||
[deployment]
|
||||
deploymentTarget = "autoscale"
|
||||
run = ["python", "run.py"]
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
Build a clean, modern web dashboard for managing and interacting with AI agents powered by PraisonAI. Use React + Vite for the frontend, and a simple Python FastAPI backend. Keep it simple but functional — no overengineering.
|
||||
|
||||
---
|
||||
|
||||
## TECH STACK
|
||||
- Frontend: React + Vite + TailwindCSS (CDN, no build step for Tailwind)
|
||||
- Backend: FastAPI (Python)
|
||||
- Storage: JSON files (no database needed yet, keep it simple)
|
||||
- API calls: fetch() to the FastAPI backend
|
||||
|
||||
---
|
||||
|
||||
## PROJECT STRUCTURE
|
||||
/backend
|
||||
main.py # FastAPI app
|
||||
agents_store.json # persists agent definitions
|
||||
history_store.json # persists conversation history
|
||||
|
||||
/frontend
|
||||
index.html
|
||||
src/
|
||||
App.jsx
|
||||
components/
|
||||
Sidebar.jsx
|
||||
AgentList.jsx
|
||||
AgentDetail.jsx
|
||||
ChatPanel.jsx
|
||||
ActivityLog.jsx
|
||||
AgentForm.jsx
|
||||
|
||||
---
|
||||
|
||||
## BACKEND (FastAPI - main.py)
|
||||
|
||||
Create these REST endpoints:
|
||||
|
||||
### Agents CRUD
|
||||
GET /agents → list all agents
|
||||
POST /agents → create agent
|
||||
PUT /agents/{id} → update agent
|
||||
DELETE /agents/{id} → delete agent
|
||||
GET /agents/{id} → get single agent details
|
||||
|
||||
### Chat
|
||||
POST /agents/{id}/chat → send message to agent, returns response + activity log
|
||||
GET /agents/{id}/history → get full conversation history
|
||||
DELETE /agents/{id}/history → clear history
|
||||
|
||||
### Activity
|
||||
GET /agents/{id}/activity → get activity log (tools used, connections, actions)
|
||||
|
||||
Each agent object should look like:
|
||||
{
|
||||
"id": "uuid",
|
||||
"name": "string",
|
||||
"role": "string",
|
||||
"instructions": "string",
|
||||
"tools": ["list of tool names"],
|
||||
"connections": ["other agent ids or external APIs"],
|
||||
"llm": "string (e.g. gpt-4o-mini)",
|
||||
"created_at": "timestamp",
|
||||
"status": "active | inactive"
|
||||
}
|
||||
|
||||
Each chat call should:
|
||||
1. Load agent config
|
||||
2. Run a PraisonAI agent with that config
|
||||
3. Capture stdout/tool calls/actions into an activity log entry
|
||||
4. Save message + response + activity to history
|
||||
5. Return all of it in one response
|
||||
|
||||
---
|
||||
|
||||
## FRONTEND LAYOUT
|
||||
|
||||
Split into 3 columns:
|
||||
|
||||
### LEFT SIDEBAR (narrow)
|
||||
- App logo/name "AgentOS"
|
||||
- List of all agents (name + status dot green/grey)
|
||||
- "+ New Agent" button at bottom
|
||||
- Click agent → loads it in center panel
|
||||
|
||||
### CENTER PANEL (main)
|
||||
Two tabs at top: "Chat" | "Details"
|
||||
|
||||
#### Chat tab:
|
||||
- Conversation history (bubbles: user right, agent left)
|
||||
- Each agent message has a collapsible "Activity" section below it showing:
|
||||
- What tools were called
|
||||
- What external connections were used
|
||||
- Raw output / reasoning steps
|
||||
- Input box at bottom + Send button
|
||||
- "Clear History" button top right
|
||||
|
||||
#### Details tab:
|
||||
- Shows agent config (name, role, instructions, LLM, tools, connections)
|
||||
- Edit button → opens inline form to modify
|
||||
- Shows stats: total messages, last active, tools used count
|
||||
|
||||
### RIGHT PANEL (narrower)
|
||||
- "Activity Feed" — real-time log of what the selected agent has done
|
||||
- Each entry: timestamp, action type (tool_call, api_call, message, error), short description
|
||||
- Color coded: blue=tool, green=success, red=error, grey=message
|
||||
- Scrollable, newest at top
|
||||
|
||||
---
|
||||
|
||||
## AGENT FORM (Add/Edit modal or inline)
|
||||
Fields:
|
||||
- Name (text input)
|
||||
- Role (text input, short description)
|
||||
- Instructions / System Prompt (large textarea)
|
||||
- LLM Model (dropdown: gpt-4o-mini, gpt-4o, claude-3-haiku, claude-sonnet, gemini-flash)
|
||||
- Tools (multi-select checkboxes): web_search, code_interpreter, file_reader, calculator, wikipedia
|
||||
- Connections (text inputs, add/remove rows): label + URL or agent name
|
||||
- Status toggle (active/inactive)
|
||||
Save / Cancel buttons
|
||||
|
||||
---
|
||||
|
||||
## STYLE GUIDE
|
||||
- Dark theme (background #0f1117, panels #1a1d27, accent #6c63ff purple)
|
||||
- Clean sans-serif font (Inter from Google Fonts)
|
||||
- Subtle borders (#2a2d3e)
|
||||
- Agent status dot: green (#22c55e) active, grey (#6b7280) inactive
|
||||
- Activity log colors: blue (#3b82f6) tool, green (#22c55e) success, red (#ef4444) error
|
||||
- Rounded corners (8px), subtle shadows
|
||||
- Mobile responsive (sidebar collapses to hamburger on small screens)
|
||||
|
||||
---
|
||||
|
||||
## SAMPLE DATA
|
||||
Pre-populate agents_store.json with 2 example agents so the UI is not empty:
|
||||
|
||||
Agent 1:
|
||||
- Name: "Research Agent"
|
||||
- Role: "Searches the web and summarizes information"
|
||||
- Tools: web_search, wikipedia
|
||||
- LLM: gpt-4o-mini
|
||||
|
||||
Agent 2:
|
||||
- Name: "Code Agent"
|
||||
- Role: "Writes and explains code"
|
||||
- Tools: code_interpreter, calculator
|
||||
- LLM: gpt-4o-mini
|
||||
|
||||
---
|
||||
|
||||
## IMPORTANT CONSTRAINTS
|
||||
- No TypeScript (plain JSX only)
|
||||
- No Redux (useState/useEffect only)
|
||||
- No external UI libraries (just Tailwind utility classes)
|
||||
- JSON file storage only (no SQLite, no Postgres)
|
||||
- Keep each file under 200 lines if possible, split into components
|
||||
- PraisonAI backend calls should use praisonaiagents library
|
||||
- Add CORS middleware to FastAPI so frontend can call backend freely
|
||||
- Read OPENAI_API_KEY from environment variable (os.environ)
|
||||
|
||||
---
|
||||
|
||||
## DELIVERABLE
|
||||
- Working React frontend that talks to FastAPI backend
|
||||
- I can add/edit/delete agents via the UI
|
||||
- I can chat with any agent and see what it does
|
||||
- History is saved and persists across page refreshes
|
||||
- Activity log updates after each interaction
|
||||
- README with how to run both frontend and backend
|
||||
@@ -0,0 +1,24 @@
|
||||
[
|
||||
{
|
||||
"id": "agent-001",
|
||||
"name": "Research Agent",
|
||||
"role": "Searches the web and summarizes information",
|
||||
"instructions": "You are a research assistant. Search the web and Wikipedia to find accurate, up-to-date information. Summarize findings clearly and concisely. Always cite your sources.",
|
||||
"tools": ["web_search", "wikipedia"],
|
||||
"connections": [],
|
||||
"llm": "gpt-4o-mini",
|
||||
"created_at": "2025-01-01T00:00:00Z",
|
||||
"status": "active"
|
||||
},
|
||||
{
|
||||
"id": "agent-002",
|
||||
"name": "Code Agent",
|
||||
"role": "Writes and explains code",
|
||||
"instructions": "You are a coding assistant. Write clean, well-commented code. Explain how code works step by step. Help debug issues and suggest improvements.",
|
||||
"tools": ["code_interpreter", "calculator"],
|
||||
"connections": [],
|
||||
"llm": "gpt-4o-mini",
|
||||
"created_at": "2025-01-01T00:00:00Z",
|
||||
"status": "active"
|
||||
}
|
||||
]
|
||||
+301
@@ -0,0 +1,301 @@
|
||||
"""SQLite persistence for AgentOS agents and chat history."""
|
||||
import json
|
||||
import os
|
||||
import sqlite3
|
||||
from contextlib import contextmanager
|
||||
from datetime import datetime, timezone
|
||||
from typing import Dict, Iterator, List, Optional
|
||||
|
||||
BASE_DIR = os.path.dirname(__file__)
|
||||
DB_FILE = os.environ.get("AGENTOS_DB", os.path.join(BASE_DIR, "agentos.db"))
|
||||
LEGACY_AGENTS_FILE = os.path.join(BASE_DIR, "agents_store.json")
|
||||
LEGACY_HISTORY_FILE = os.path.join(BASE_DIR, "history_store.json")
|
||||
|
||||
ALLOWED_STATUS = {"active", "auditing", "decommissioned"}
|
||||
|
||||
SCHEMA = """
|
||||
CREATE TABLE IF NOT EXISTS agents (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
instructions TEXT NOT NULL DEFAULT '',
|
||||
model TEXT NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'active'
|
||||
CHECK (status IN ('active', 'auditing', 'decommissioned')),
|
||||
generation INTEGER NOT NULL DEFAULT 1,
|
||||
parent_id TEXT REFERENCES agents(id) ON DELETE SET NULL,
|
||||
performance_score REAL,
|
||||
token_spend INTEGER NOT NULL DEFAULT 0,
|
||||
exit_summary TEXT,
|
||||
role TEXT,
|
||||
tools TEXT DEFAULT '[]',
|
||||
connections TEXT DEFAULT '[]',
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS chat_history (
|
||||
id TEXT PRIMARY KEY,
|
||||
agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
|
||||
user_message TEXT NOT NULL,
|
||||
agent_response TEXT NOT NULL,
|
||||
activity TEXT NOT NULL DEFAULT '[]',
|
||||
timestamp TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_chat_history_agent_ts
|
||||
ON chat_history(agent_id, timestamp);
|
||||
"""
|
||||
|
||||
|
||||
@contextmanager
|
||||
def get_conn() -> Iterator[sqlite3.Connection]:
|
||||
conn = sqlite3.connect(DB_FILE)
|
||||
conn.row_factory = sqlite3.Row
|
||||
conn.execute("PRAGMA foreign_keys = ON")
|
||||
try:
|
||||
yield conn
|
||||
conn.commit()
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def _now() -> str:
|
||||
# Timezone-aware UTC timestamp, ISO-8601 with trailing Z to match prior shape.
|
||||
return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%f") + "Z"
|
||||
|
||||
|
||||
def _row_to_dict(row: sqlite3.Row) -> Dict:
|
||||
d = dict(row)
|
||||
# Legacy alias so existing frontend/clients that read `llm` still work.
|
||||
d["llm"] = d["model"]
|
||||
# Parse JSON fields
|
||||
try:
|
||||
d["tools"] = json.loads(d.get("tools") or "[]")
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
d["tools"] = []
|
||||
try:
|
||||
d["connections"] = json.loads(d.get("connections") or "[]")
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
d["connections"] = []
|
||||
return d
|
||||
|
||||
|
||||
def init_db() -> None:
|
||||
with get_conn() as conn:
|
||||
conn.executescript(SCHEMA)
|
||||
cur = conn.execute("SELECT COUNT(*) FROM agents")
|
||||
if cur.fetchone()[0] == 0:
|
||||
_migrate_from_json(conn)
|
||||
cur = conn.execute("SELECT COUNT(*) FROM chat_history")
|
||||
if cur.fetchone()[0] == 0:
|
||||
_migrate_history_from_json(conn)
|
||||
|
||||
|
||||
def _migrate_from_json(conn: sqlite3.Connection) -> None:
|
||||
if not os.path.exists(LEGACY_AGENTS_FILE):
|
||||
return
|
||||
try:
|
||||
with open(LEGACY_AGENTS_FILE) as f:
|
||||
legacy = json.load(f)
|
||||
except (OSError, json.JSONDecodeError):
|
||||
return
|
||||
for a in legacy:
|
||||
status = a.get("status", "active")
|
||||
if status not in ALLOWED_STATUS:
|
||||
status = "active"
|
||||
created = a.get("created_at") or _now()
|
||||
conn.execute(
|
||||
"""
|
||||
INSERT OR IGNORE INTO agents
|
||||
(id, name, instructions, model, status, generation, parent_id,
|
||||
performance_score, token_spend, exit_summary, role, tools, connections, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, 1, NULL, NULL, 0, NULL, ?, ?, ?, ?, ?)
|
||||
""",
|
||||
(
|
||||
a["id"],
|
||||
a.get("name", "Unnamed"),
|
||||
a.get("instructions", ""),
|
||||
a.get("llm") or a.get("model") or "gpt-4o-mini",
|
||||
status,
|
||||
a.get("role"),
|
||||
json.dumps(a.get("tools", [])),
|
||||
json.dumps(a.get("connections", [])),
|
||||
created,
|
||||
created,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def _migrate_history_from_json(conn: sqlite3.Connection) -> None:
|
||||
if not os.path.exists(LEGACY_HISTORY_FILE):
|
||||
return
|
||||
try:
|
||||
with open(LEGACY_HISTORY_FILE) as f:
|
||||
legacy = json.load(f)
|
||||
except (OSError, json.JSONDecodeError):
|
||||
return
|
||||
if not isinstance(legacy, dict):
|
||||
return
|
||||
for agent_id, entries in legacy.items():
|
||||
if not isinstance(entries, list):
|
||||
continue
|
||||
for entry in entries:
|
||||
conn.execute(
|
||||
"""
|
||||
INSERT OR IGNORE INTO chat_history
|
||||
(id, agent_id, user_message, agent_response, activity, timestamp)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
""",
|
||||
(
|
||||
entry.get("id") or os.urandom(4).hex(),
|
||||
agent_id,
|
||||
entry.get("user_message", ""),
|
||||
entry.get("agent_response", ""),
|
||||
json.dumps(entry.get("activity", [])),
|
||||
entry.get("timestamp") or _now(),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def list_agents() -> List[Dict]:
|
||||
with get_conn() as conn:
|
||||
rows = conn.execute(
|
||||
"SELECT * FROM agents ORDER BY created_at ASC"
|
||||
).fetchall()
|
||||
return [_row_to_dict(r) for r in rows]
|
||||
|
||||
|
||||
def get_agent(agent_id: str) -> Optional[Dict]:
|
||||
with get_conn() as conn:
|
||||
row = conn.execute(
|
||||
"SELECT * FROM agents WHERE id = ?", (agent_id,)
|
||||
).fetchone()
|
||||
return _row_to_dict(row) if row else None
|
||||
|
||||
|
||||
def create_agent(agent: Dict) -> Dict:
|
||||
now = _now()
|
||||
with get_conn() as conn:
|
||||
conn.execute(
|
||||
"""
|
||||
INSERT INTO agents
|
||||
(id, name, instructions, model, status, generation, parent_id,
|
||||
performance_score, token_spend, exit_summary, role, tools, connections, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""",
|
||||
(
|
||||
agent["id"],
|
||||
agent["name"],
|
||||
agent.get("instructions", ""),
|
||||
agent["model"],
|
||||
agent.get("status", "active"),
|
||||
agent.get("generation", 1),
|
||||
agent.get("parent_id"),
|
||||
agent.get("performance_score"),
|
||||
agent.get("token_spend", 0),
|
||||
agent.get("exit_summary"),
|
||||
agent.get("role"),
|
||||
json.dumps(agent.get("tools", [])),
|
||||
json.dumps(agent.get("connections", [])),
|
||||
now,
|
||||
now,
|
||||
),
|
||||
)
|
||||
return get_agent(agent["id"]) # type: ignore[return-value]
|
||||
|
||||
|
||||
UPDATABLE_FIELDS = {
|
||||
"name",
|
||||
"instructions",
|
||||
"model",
|
||||
"status",
|
||||
"generation",
|
||||
"parent_id",
|
||||
"performance_score",
|
||||
"token_spend",
|
||||
"exit_summary",
|
||||
"role",
|
||||
"tools",
|
||||
"connections",
|
||||
}
|
||||
|
||||
|
||||
def update_agent(agent_id: str, fields: Dict) -> Optional[Dict]:
|
||||
sets = {k: v for k, v in fields.items() if k in UPDATABLE_FIELDS}
|
||||
if not sets:
|
||||
return get_agent(agent_id)
|
||||
# Serialize JSON fields
|
||||
for k in ["tools", "connections"]:
|
||||
if k in sets and isinstance(sets[k], list):
|
||||
sets[k] = json.dumps(sets[k])
|
||||
columns = ", ".join(f"{k} = ?" for k in sets) + ", updated_at = ?"
|
||||
values = list(sets.values()) + [_now(), agent_id]
|
||||
with get_conn() as conn:
|
||||
cur = conn.execute(
|
||||
f"UPDATE agents SET {columns} WHERE id = ?", values
|
||||
)
|
||||
if cur.rowcount == 0:
|
||||
return None
|
||||
return get_agent(agent_id)
|
||||
|
||||
|
||||
def delete_agent(agent_id: str) -> bool:
|
||||
with get_conn() as conn:
|
||||
cur = conn.execute("DELETE FROM agents WHERE id = ?", (agent_id,))
|
||||
return cur.rowcount > 0
|
||||
|
||||
|
||||
# ----- chat history --------------------------------------------------------
|
||||
|
||||
|
||||
def _row_to_history(row: sqlite3.Row) -> Dict:
|
||||
d = dict(row)
|
||||
try:
|
||||
d["activity"] = json.loads(d.get("activity") or "[]")
|
||||
except json.JSONDecodeError:
|
||||
d["activity"] = []
|
||||
d.pop("agent_id", None)
|
||||
return d
|
||||
|
||||
|
||||
def add_history_entry(agent_id: str, entry: Dict) -> Dict:
|
||||
with get_conn() as conn:
|
||||
conn.execute(
|
||||
"""
|
||||
INSERT INTO chat_history
|
||||
(id, agent_id, user_message, agent_response, activity, timestamp)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
""",
|
||||
(
|
||||
entry["id"],
|
||||
agent_id,
|
||||
entry["user_message"],
|
||||
entry["agent_response"],
|
||||
json.dumps(entry.get("activity", [])),
|
||||
entry["timestamp"],
|
||||
),
|
||||
)
|
||||
return entry
|
||||
|
||||
|
||||
def list_history(agent_id: str) -> List[Dict]:
|
||||
with get_conn() as conn:
|
||||
rows = conn.execute(
|
||||
"SELECT * FROM chat_history WHERE agent_id = ? ORDER BY timestamp ASC",
|
||||
(agent_id,),
|
||||
).fetchall()
|
||||
return [_row_to_history(r) for r in rows]
|
||||
|
||||
|
||||
def clear_history(agent_id: str) -> None:
|
||||
with get_conn() as conn:
|
||||
conn.execute("DELETE FROM chat_history WHERE agent_id = ?", (agent_id,))
|
||||
|
||||
|
||||
def list_activity(agent_id: str, limit: int = 100) -> List[Dict]:
|
||||
"""Flattened activity across all of an agent's chat entries, newest first, capped."""
|
||||
entries = list_history(agent_id)
|
||||
activity: List[Dict] = []
|
||||
for entry in reversed(entries):
|
||||
activity.extend(entry.get("activity", []))
|
||||
return activity[:limit]
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
+234
@@ -0,0 +1,234 @@
|
||||
"""AgentOS FastAPI backend."""
|
||||
import os
|
||||
import uuid
|
||||
from contextlib import asynccontextmanager
|
||||
from datetime import datetime, timezone
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
|
||||
try:
|
||||
from . import db as agentdb # package import (uvicorn backend.main:app)
|
||||
except ImportError: # pragma: no cover - script execution fallback
|
||||
import sys
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
import db as agentdb # type: ignore
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
agentdb.init_db()
|
||||
yield
|
||||
|
||||
|
||||
app = FastAPI(title="AgentOS API", lifespan=lifespan)
|
||||
|
||||
allowed_origins = os.environ.get("AGENTOS_CORS_ORIGINS", "http://localhost:5173").split(",")
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=allowed_origins,
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
|
||||
def _now_iso() -> str:
|
||||
return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%f") + "Z"
|
||||
|
||||
|
||||
class AgentCreate(BaseModel):
|
||||
# Accept fields for frontend compatibility.
|
||||
# `protected_namespaces=()` silences Pydantic v2 warnings about the
|
||||
# `model` field name colliding with its protected namespace.
|
||||
model_config = ConfigDict(extra="ignore", protected_namespaces=())
|
||||
|
||||
name: str
|
||||
instructions: str = ""
|
||||
model: Optional[str] = None
|
||||
llm: Optional[str] = None # legacy alias for `model`
|
||||
status: str = "active"
|
||||
generation: int = 1
|
||||
parent_id: Optional[str] = None
|
||||
performance_score: Optional[float] = None
|
||||
token_spend: int = 0
|
||||
exit_summary: Optional[str] = None
|
||||
role: Optional[str] = None
|
||||
tools: List[str] = []
|
||||
connections: List[str] = []
|
||||
|
||||
|
||||
class AgentUpdate(BaseModel):
|
||||
model_config = ConfigDict(extra="ignore", protected_namespaces=())
|
||||
|
||||
name: Optional[str] = None
|
||||
instructions: Optional[str] = None
|
||||
model: Optional[str] = None
|
||||
llm: Optional[str] = None # legacy alias for `model`
|
||||
status: Optional[str] = None
|
||||
generation: Optional[int] = None
|
||||
parent_id: Optional[str] = None
|
||||
performance_score: Optional[float] = None
|
||||
token_spend: Optional[int] = None
|
||||
exit_summary: Optional[str] = None
|
||||
role: Optional[str] = None
|
||||
tools: Optional[List[str]] = None
|
||||
connections: Optional[List[str]] = None
|
||||
|
||||
|
||||
class ChatMessage(BaseModel):
|
||||
message: str
|
||||
|
||||
|
||||
def _validate_status(status: str) -> None:
|
||||
if status not in agentdb.ALLOWED_STATUS:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"status must be one of {sorted(agentdb.ALLOWED_STATUS)}",
|
||||
)
|
||||
|
||||
|
||||
@app.get("/agents")
|
||||
def list_agents():
|
||||
return agentdb.list_agents()
|
||||
|
||||
|
||||
@app.get("/agents/{agent_id}")
|
||||
def get_agent(agent_id: str):
|
||||
agent = agentdb.get_agent(agent_id)
|
||||
if not agent:
|
||||
raise HTTPException(status_code=404, detail="Agent not found")
|
||||
return agent
|
||||
|
||||
|
||||
@app.post("/agents", status_code=201)
|
||||
def create_agent(data: AgentCreate):
|
||||
_validate_status(data.status)
|
||||
if data.parent_id and not agentdb.get_agent(data.parent_id):
|
||||
raise HTTPException(status_code=400, detail="parent_id does not exist")
|
||||
record = {
|
||||
"id": f"agent-{uuid.uuid4().hex[:8]}",
|
||||
"name": data.name,
|
||||
"instructions": data.instructions,
|
||||
"model": data.model or data.llm or "gpt-4o-mini",
|
||||
"status": data.status,
|
||||
"generation": data.generation,
|
||||
"parent_id": data.parent_id,
|
||||
"performance_score": data.performance_score,
|
||||
"token_spend": data.token_spend,
|
||||
"exit_summary": data.exit_summary,
|
||||
"role": data.role,
|
||||
"tools": data.tools,
|
||||
"connections": data.connections,
|
||||
}
|
||||
return agentdb.create_agent(record)
|
||||
|
||||
|
||||
@app.put("/agents/{agent_id}")
|
||||
def update_agent(agent_id: str, data: AgentUpdate):
|
||||
fields = data.model_dump(exclude_none=True)
|
||||
# Legacy `llm` alias -> `model`
|
||||
if "llm" in fields and "model" not in fields:
|
||||
fields["model"] = fields["llm"]
|
||||
fields.pop("llm", None)
|
||||
|
||||
if "status" in fields:
|
||||
_validate_status(fields["status"])
|
||||
if fields.get("parent_id") and not agentdb.get_agent(fields["parent_id"]):
|
||||
raise HTTPException(status_code=400, detail="parent_id does not exist")
|
||||
|
||||
updated = agentdb.update_agent(agent_id, fields)
|
||||
if updated is None:
|
||||
raise HTTPException(status_code=404, detail="Agent not found")
|
||||
return updated
|
||||
|
||||
|
||||
@app.delete("/agents/{agent_id}", status_code=204)
|
||||
def delete_agent(agent_id: str):
|
||||
# chat_history rows cascade via FK ON DELETE CASCADE
|
||||
if not agentdb.delete_agent(agent_id):
|
||||
raise HTTPException(status_code=404, detail="Agent not found")
|
||||
|
||||
|
||||
@app.post("/agents/{agent_id}/chat")
|
||||
def chat_with_agent(agent_id: str, body: ChatMessage):
|
||||
agent = agentdb.get_agent(agent_id)
|
||||
if not agent:
|
||||
raise HTTPException(status_code=404, detail="Agent not found")
|
||||
|
||||
activity: List[Dict] = []
|
||||
response_text = ""
|
||||
|
||||
api_key = os.environ.get("OPENAI_API_KEY", "")
|
||||
if api_key:
|
||||
try:
|
||||
from praisonaiagents import Agent
|
||||
activity.append(_log("tool", f"Initializing {agent['model']} agent"))
|
||||
pa_agent = Agent(
|
||||
name=agent["name"],
|
||||
instructions=agent["instructions"],
|
||||
llm=agent["model"],
|
||||
)
|
||||
activity.append(_log("tool", "Running agent inference"))
|
||||
result = pa_agent.start(body.message)
|
||||
response_text = str(result)
|
||||
activity.append(_log("success", "Agent response generated"))
|
||||
except Exception as e:
|
||||
activity.append(_log("error", f"Agent error: {str(e)[:120]}"))
|
||||
response_text = f"Agent encountered an error: {str(e)[:200]}"
|
||||
else:
|
||||
activity.append(_log("message", "No OPENAI_API_KEY set — using demo mode"))
|
||||
response_text = _demo_response(agent, body.message)
|
||||
activity.append(_log("success", "Demo response generated"))
|
||||
|
||||
entry = {
|
||||
"id": uuid.uuid4().hex[:8],
|
||||
"user_message": body.message,
|
||||
"agent_response": response_text,
|
||||
"activity": activity,
|
||||
"timestamp": _now_iso(),
|
||||
}
|
||||
agentdb.add_history_entry(agent_id, entry)
|
||||
return entry
|
||||
|
||||
|
||||
@app.get("/agents/{agent_id}/history")
|
||||
def get_history(agent_id: str):
|
||||
return agentdb.list_history(agent_id)
|
||||
|
||||
|
||||
@app.delete("/agents/{agent_id}/history", status_code=204)
|
||||
def clear_history(agent_id: str):
|
||||
agentdb.clear_history(agent_id)
|
||||
|
||||
|
||||
@app.get("/agents/{agent_id}/activity")
|
||||
def get_activity(agent_id: str):
|
||||
return agentdb.list_activity(agent_id, limit=100)
|
||||
|
||||
|
||||
def _log(action_type: str, description: str) -> Dict:
|
||||
return {
|
||||
"id": uuid.uuid4().hex[:8],
|
||||
"timestamp": _now_iso(),
|
||||
"type": action_type,
|
||||
"description": description,
|
||||
}
|
||||
|
||||
|
||||
def _demo_response(agent: Dict, message: str) -> str:
|
||||
name = agent.get("name", "Agent")
|
||||
return (
|
||||
f"Hi! I'm {name}. "
|
||||
f"You asked: \"{message}\"\n\n"
|
||||
f"I'm running on {agent.get('model', 'unknown model')}. "
|
||||
f"To enable real AI responses, set your OPENAI_API_KEY environment variable."
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
uvicorn.run(app, host="localhost", port=8000)
|
||||
@@ -0,0 +1,47 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>AgentOS</title>
|
||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><rect width='100' height='100' rx='20' fill='%236c63ff'/><text y='.9em' font-size='70' x='15' fill='white'>A</text></svg>" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet" />
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
bg: '#0f1117',
|
||||
panel: '#1a1d27',
|
||||
accent: '#6c63ff',
|
||||
border: '#2a2d3e',
|
||||
'status-active': '#22c55e',
|
||||
'status-inactive': '#6b7280',
|
||||
'activity-tool': '#3b82f6',
|
||||
'activity-success': '#22c55e',
|
||||
'activity-error': '#ef4444',
|
||||
'activity-message': '#6b7280',
|
||||
},
|
||||
fontFamily: {
|
||||
sans: ['Inter', 'sans-serif'],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
body { background-color: #0f1117; color: #e2e8f0; font-family: 'Inter', sans-serif; }
|
||||
::-webkit-scrollbar { width: 4px; }
|
||||
::-webkit-scrollbar-track { background: #1a1d27; }
|
||||
::-webkit-scrollbar-thumb { background: #2a2d3e; border-radius: 2px; }
|
||||
::-webkit-scrollbar-thumb:hover { background: #6c63ff; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.jsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
Generated
+1677
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "agentos-frontend",
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --host 0.0.0.0 --port 5000",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-react": "^4.2.0",
|
||||
"vite": "^5.0.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,260 @@
|
||||
import { useState, useEffect, useCallback } from 'react'
|
||||
import Sidebar from './components/Sidebar'
|
||||
import ChatPanel from './components/ChatPanel'
|
||||
import AgentDetail from './components/AgentDetail'
|
||||
import ActivityLog from './components/ActivityLog'
|
||||
import AgentForm from './components/AgentForm'
|
||||
|
||||
const API = '/api'
|
||||
|
||||
export default function App() {
|
||||
const [agents, setAgents] = useState([])
|
||||
const [selectedId, setSelectedId] = useState(null)
|
||||
const [activeTab, setActiveTab] = useState('chat')
|
||||
const [showForm, setShowForm] = useState(false)
|
||||
const [editingAgent, setEditingAgent] = useState(null)
|
||||
const [sidebarOpen, setSidebarOpen] = useState(true)
|
||||
const [history, setHistory] = useState([])
|
||||
const [activity, setActivity] = useState([])
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const fetchAgents = useCallback(async () => {
|
||||
try {
|
||||
const res = await fetch(`${API}/agents`)
|
||||
const data = await res.json()
|
||||
setAgents(data)
|
||||
if (!selectedId && data.length > 0) setSelectedId(data[0].id)
|
||||
} catch (e) {
|
||||
console.error('Failed to fetch agents', e)
|
||||
}
|
||||
}, [selectedId])
|
||||
|
||||
const fetchHistory = useCallback(async (id, signal) => {
|
||||
if (!id) return
|
||||
try {
|
||||
const res = await fetch(`${API}/agents/${id}/history`, { signal })
|
||||
if (!res.ok) return
|
||||
const data = await res.json()
|
||||
setHistory(data)
|
||||
} catch (e) {
|
||||
if (e.name !== 'AbortError') console.error(e)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const fetchActivity = useCallback(async (id, signal) => {
|
||||
if (!id) return
|
||||
try {
|
||||
const res = await fetch(`${API}/agents/${id}/activity`, { signal })
|
||||
if (!res.ok) return
|
||||
const data = await res.json()
|
||||
setActivity(data)
|
||||
} catch (e) {
|
||||
if (e.name !== 'AbortError') console.error(e)
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => { fetchAgents() }, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedId) {
|
||||
const controller = new AbortController()
|
||||
fetchHistory(selectedId, controller.signal)
|
||||
fetchActivity(selectedId, controller.signal)
|
||||
return () => controller.abort()
|
||||
}
|
||||
}, [selectedId, fetchHistory, fetchActivity])
|
||||
|
||||
const handleSelectAgent = (id) => {
|
||||
setSelectedId(id)
|
||||
setActiveTab('chat')
|
||||
}
|
||||
|
||||
const handleSendMessage = async (message) => {
|
||||
if (!selectedId) return
|
||||
setLoading(true)
|
||||
try {
|
||||
const res = await fetch(`${API}/agents/${selectedId}/chat`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ message }),
|
||||
})
|
||||
if (!res.ok) {
|
||||
throw new Error(`Chat failed: ${res.status}`)
|
||||
}
|
||||
const entry = await res.json()
|
||||
setHistory(prev => [...prev, entry])
|
||||
setActivity(prev => [...(entry.activity || []), ...prev])
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleClearHistory = async () => {
|
||||
if (!selectedId) return
|
||||
try {
|
||||
const res = await fetch(`${API}/agents/${selectedId}/history`, { method: 'DELETE' })
|
||||
if (!res.ok) throw new Error(`Clear history failed: ${res.status}`)
|
||||
setHistory([])
|
||||
setActivity([])
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
const handleSaveAgent = async (data) => {
|
||||
try {
|
||||
if (editingAgent) {
|
||||
const res = await fetch(`${API}/agents/${editingAgent.id}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
if (!res.ok) throw new Error(`Update agent failed: ${res.status}`)
|
||||
const updated = await res.json()
|
||||
setAgents(prev => prev.map(a => a.id === updated.id ? updated : a))
|
||||
} else {
|
||||
const res = await fetch(`${API}/agents`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
if (!res.ok) throw new Error(`Create agent failed: ${res.status}`)
|
||||
const created = await res.json()
|
||||
setAgents(prev => [...prev, created])
|
||||
setSelectedId(created.id)
|
||||
}
|
||||
setShowForm(false)
|
||||
setEditingAgent(null)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
alert('Could not save agent. Please try again.')
|
||||
}
|
||||
}
|
||||
|
||||
const handleDeleteAgent = async (id) => {
|
||||
if (!window.confirm('Delete this agent?')) return
|
||||
try {
|
||||
const res = await fetch(`${API}/agents/${id}`, { method: 'DELETE' })
|
||||
if (!res.ok) throw new Error(`Delete agent failed: ${res.status}`)
|
||||
setAgents(prev => prev.filter(a => a.id !== id))
|
||||
if (selectedId === id) {
|
||||
const remaining = agents.filter(a => a.id !== id)
|
||||
setSelectedId(remaining.length > 0 ? remaining[0].id : null)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
alert('Could not delete agent. Please try again.')
|
||||
}
|
||||
}
|
||||
|
||||
const selectedAgent = agents.find(a => a.id === selectedId)
|
||||
|
||||
return (
|
||||
<div className="flex h-screen overflow-hidden" style={{ background: '#0f1117' }}>
|
||||
{/* Mobile overlay */}
|
||||
{sidebarOpen && (
|
||||
<div className="fixed inset-0 bg-black/50 z-10 md:hidden" onClick={() => setSidebarOpen(false)} />
|
||||
)}
|
||||
|
||||
{/* Sidebar */}
|
||||
<div className={`
|
||||
fixed md:relative z-20 h-full transition-transform duration-300
|
||||
${sidebarOpen ? 'translate-x-0' : '-translate-x-full md:translate-x-0'}
|
||||
${sidebarOpen ? 'w-64' : 'w-0 md:w-64'}
|
||||
`}>
|
||||
<Sidebar
|
||||
agents={agents}
|
||||
selectedId={selectedId}
|
||||
onSelect={handleSelectAgent}
|
||||
onNewAgent={() => { setEditingAgent(null); setShowForm(true) }}
|
||||
onClose={() => setSidebarOpen(false)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Main content */}
|
||||
<div className="flex flex-1 min-w-0 overflow-hidden">
|
||||
{/* Center panel */}
|
||||
<div className="flex flex-col flex-1 min-w-0 overflow-hidden border-x" style={{ borderColor: '#2a2d3e' }}>
|
||||
{/* Header */}
|
||||
<div className="flex items-center px-4 py-3 border-b shrink-0" style={{ background: '#1a1d27', borderColor: '#2a2d3e' }}>
|
||||
<button
|
||||
onClick={() => setSidebarOpen(!sidebarOpen)}
|
||||
className="mr-3 p-1.5 rounded-lg hover:bg-white/10 transition-colors md:hidden"
|
||||
>
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
|
||||
</svg>
|
||||
</button>
|
||||
{selectedAgent ? (
|
||||
<>
|
||||
<div className="flex items-center gap-2 flex-1">
|
||||
<div className={`w-2 h-2 rounded-full shrink-0 ${selectedAgent.status === 'active' ? 'bg-status-active' : 'bg-status-inactive'}`} style={{ background: selectedAgent.status === 'active' ? '#22c55e' : '#6b7280' }} />
|
||||
<span className="font-semibold truncate">{selectedAgent.name}</span>
|
||||
<span className="text-sm text-gray-400 truncate hidden sm:block">— {selectedAgent.role}</span>
|
||||
</div>
|
||||
<div className="flex gap-1 ml-2">
|
||||
<button
|
||||
onClick={() => setActiveTab('chat')}
|
||||
className={`px-3 py-1.5 text-sm rounded-lg font-medium transition-colors ${activeTab === 'chat' ? 'text-white' : 'text-gray-400 hover:text-white'}`}
|
||||
style={activeTab === 'chat' ? { background: '#6c63ff' } : {}}
|
||||
>Chat</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('details')}
|
||||
className={`px-3 py-1.5 text-sm rounded-lg font-medium transition-colors ${activeTab === 'details' ? 'text-white' : 'text-gray-400 hover:text-white'}`}
|
||||
style={activeTab === 'details' ? { background: '#6c63ff' } : {}}
|
||||
>Details</button>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<span className="text-gray-400 text-sm">No agent selected</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Panel content */}
|
||||
<div className="flex-1 overflow-hidden">
|
||||
{!selectedAgent ? (
|
||||
<div className="flex flex-col items-center justify-center h-full text-gray-500">
|
||||
<svg className="w-16 h-16 mb-4 opacity-30" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1} d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17H3a2 2 0 01-2-2V5a2 2 0 012-2h14a2 2 0 012 2v10a2 2 0 01-2 2h-2" />
|
||||
</svg>
|
||||
<p className="text-lg font-medium">Select or create an agent</p>
|
||||
<p className="text-sm mt-1">Choose an agent from the sidebar to get started</p>
|
||||
</div>
|
||||
) : activeTab === 'chat' ? (
|
||||
<ChatPanel
|
||||
agent={selectedAgent}
|
||||
history={history}
|
||||
loading={loading}
|
||||
onSend={handleSendMessage}
|
||||
onClearHistory={handleClearHistory}
|
||||
/>
|
||||
) : (
|
||||
<AgentDetail
|
||||
agent={selectedAgent}
|
||||
history={history}
|
||||
onEdit={() => { setEditingAgent(selectedAgent); setShowForm(true) }}
|
||||
onDelete={() => handleDeleteAgent(selectedAgent.id)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right activity panel */}
|
||||
<div className="hidden lg:flex flex-col w-72 shrink-0 overflow-hidden" style={{ background: '#1a1d27' }}>
|
||||
<ActivityLog activity={activity} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Agent form modal */}
|
||||
{showForm && (
|
||||
<AgentForm
|
||||
agent={editingAgent}
|
||||
onSave={handleSaveAgent}
|
||||
onCancel={() => { setShowForm(false); setEditingAgent(null) }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
const TYPE_CONFIG = {
|
||||
tool: { color: '#3b82f6', bg: 'rgba(59,130,246,0.1)', label: 'TOOL', icon: '⚙️' },
|
||||
success: { color: '#22c55e', bg: 'rgba(34,197,94,0.1)', label: 'OK', icon: '✓' },
|
||||
error: { color: '#ef4444', bg: 'rgba(239,68,68,0.1)', label: 'ERR', icon: '✗' },
|
||||
message: { color: '#6b7280', bg: 'rgba(107,114,128,0.1)', label: 'MSG', icon: '●' },
|
||||
}
|
||||
|
||||
function ActivityItem({ item }) {
|
||||
const cfg = TYPE_CONFIG[item.type] || TYPE_CONFIG.message
|
||||
return (
|
||||
<div className="flex items-start gap-2.5 py-2.5 border-b" style={{ borderColor: '#2a2d3e' }}>
|
||||
<div className="w-5 h-5 rounded flex items-center justify-center shrink-0 mt-0.5 text-xs font-bold" style={{ background: cfg.bg, color: cfg.color }}>
|
||||
{cfg.icon}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-xs leading-snug" style={{ color: '#d1d5db' }}>
|
||||
{item.description}
|
||||
</p>
|
||||
<p className="text-xs mt-0.5" style={{ color: '#4b5563' }}>
|
||||
{new Date(item.timestamp).toLocaleTimeString()}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default function ActivityLog({ activity }) {
|
||||
return (
|
||||
<div className="flex flex-col h-full border-l" style={{ borderColor: '#2a2d3e' }}>
|
||||
<div className="px-4 py-3 border-b shrink-0" style={{ borderColor: '#2a2d3e' }}>
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-sm font-semibold text-white">Activity Feed</h3>
|
||||
<span className="text-xs px-2 py-0.5 rounded-full" style={{ background: 'rgba(108,99,255,0.15)', color: '#818cf8' }}>
|
||||
{activity.length}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Legend */}
|
||||
<div className="flex flex-wrap gap-x-3 gap-y-1 mt-2">
|
||||
{Object.entries(TYPE_CONFIG).map(([key, cfg]) => (
|
||||
<div key={key} className="flex items-center gap-1 text-xs" style={{ color: cfg.color }}>
|
||||
<span>{cfg.icon}</span>
|
||||
<span style={{ color: '#6b7280' }}>{key}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 overflow-y-auto px-4">
|
||||
{activity.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center h-full text-center py-8" style={{ color: '#4b5563' }}>
|
||||
<div className="text-3xl mb-2 opacity-40">📋</div>
|
||||
<p className="text-xs">No activity yet.<br />Send a message to see logs.</p>
|
||||
</div>
|
||||
) : (
|
||||
activity.map(item => <ActivityItem key={item.id} item={item} />)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
export default function AgentDetail({ agent, history, onEdit, onDelete }) {
|
||||
const totalMessages = history.length
|
||||
const lastActive = history.length > 0
|
||||
? new Date(history[history.length - 1].timestamp).toLocaleString()
|
||||
: 'Never'
|
||||
|
||||
const toolsUsed = new Set()
|
||||
history.forEach(entry => {
|
||||
entry.activity?.forEach(a => {
|
||||
if (a.type === 'tool') toolsUsed.add(a.description)
|
||||
})
|
||||
})
|
||||
|
||||
const TOOL_ICONS = {
|
||||
web_search: '🔍',
|
||||
wikipedia: '📖',
|
||||
code_interpreter: '💻',
|
||||
file_reader: '📄',
|
||||
calculator: '🧮',
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-full overflow-y-auto px-6 py-6">
|
||||
{/* Header */}
|
||||
<div className="flex items-start justify-between mb-6">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-12 h-12 rounded-2xl flex items-center justify-center text-xl font-bold" style={{ background: 'rgba(108,99,255,0.2)', border: '1px solid rgba(108,99,255,0.3)', color: '#6c63ff' }}>
|
||||
{agent.name.charAt(0).toUpperCase()}
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-xl font-bold text-white">{agent.name}</h2>
|
||||
<p className="text-sm mt-0.5" style={{ color: '#9ca3af' }}>{agent.role}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className={`px-2.5 py-1 rounded-full text-xs font-medium`}
|
||||
style={{ background: agent.status === 'active' ? 'rgba(34,197,94,0.15)' : 'rgba(107,114,128,0.15)', color: agent.status === 'active' ? '#22c55e' : '#6b7280' }}>
|
||||
{agent.status}
|
||||
</span>
|
||||
<button
|
||||
onClick={onEdit}
|
||||
className="px-3 py-1.5 rounded-lg text-sm font-medium transition-all hover:opacity-90"
|
||||
style={{ background: '#6c63ff', color: 'white' }}
|
||||
>
|
||||
Edit
|
||||
</button>
|
||||
<button
|
||||
onClick={onDelete}
|
||||
className="px-3 py-1.5 rounded-lg text-sm font-medium transition-colors"
|
||||
style={{ border: '1px solid #ef4444', color: '#ef4444' }}
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Stats */}
|
||||
<div className="grid grid-cols-3 gap-3 mb-6">
|
||||
{[
|
||||
{ label: 'Total Messages', value: totalMessages },
|
||||
{ label: 'Tools Used', value: toolsUsed.size },
|
||||
{ label: 'Last Active', value: lastActive, small: true },
|
||||
].map(stat => (
|
||||
<div key={stat.label} className="rounded-xl p-4" style={{ background: '#0f1117', border: '1px solid #2a2d3e' }}>
|
||||
<p className="text-xs mb-1" style={{ color: '#6b7280' }}>{stat.label}</p>
|
||||
<p className={`font-semibold text-white ${stat.small ? 'text-xs' : 'text-xl'}`}>{stat.value}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Instructions */}
|
||||
<Section title="System Instructions">
|
||||
<p className="text-sm leading-relaxed whitespace-pre-wrap" style={{ color: '#d1d5db' }}>
|
||||
{agent.instructions || <span style={{ color: '#6b7280' }}>No instructions set</span>}
|
||||
</p>
|
||||
</Section>
|
||||
|
||||
{/* Config */}
|
||||
<div className="grid grid-cols-2 gap-4 mb-4">
|
||||
<Section title="LLM Model">
|
||||
<span className="inline-block px-3 py-1.5 rounded-lg text-sm font-medium" style={{ background: 'rgba(108,99,255,0.15)', color: '#818cf8', border: '1px solid rgba(108,99,255,0.2)' }}>
|
||||
{agent.llm}
|
||||
</span>
|
||||
</Section>
|
||||
<Section title="Status">
|
||||
<span className="inline-flex items-center gap-2 text-sm" style={{ color: '#d1d5db' }}>
|
||||
<span className="w-2 h-2 rounded-full" style={{ background: agent.status === 'active' ? '#22c55e' : '#6b7280' }} />
|
||||
{agent.status}
|
||||
</span>
|
||||
</Section>
|
||||
</div>
|
||||
|
||||
{/* Tools */}
|
||||
<Section title="Tools">
|
||||
{(agent.tools?.length ?? 0) === 0 ? (
|
||||
<p className="text-sm" style={{ color: '#6b7280' }}>No tools configured</p>
|
||||
) : (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{(agent.tools || []).map(tool => (
|
||||
<span key={tool} className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-sm" style={{ background: '#0f1117', border: '1px solid #2a2d3e', color: '#d1d5db' }}>
|
||||
<span>{TOOL_ICONS[tool] || '🔧'}</span>
|
||||
{tool}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
|
||||
{/* Connections */}
|
||||
{(agent.connections?.length ?? 0) > 0 && (
|
||||
<Section title="Connections">
|
||||
<div className="space-y-2">
|
||||
{(agent.connections || []).map((conn, i) => (
|
||||
<div key={i} className="flex items-center gap-2 text-sm px-3 py-2 rounded-lg" style={{ background: '#0f1117', border: '1px solid #2a2d3e', color: '#d1d5db' }}>
|
||||
<span style={{ color: '#6b7280' }}>→</span>
|
||||
{conn}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function Section({ title, children }) {
|
||||
return (
|
||||
<div className="mb-4 rounded-xl p-4" style={{ background: '#0f1117', border: '1px solid #2a2d3e' }}>
|
||||
<h3 className="text-xs font-semibold uppercase tracking-wider mb-3" style={{ color: '#6b7280' }}>{title}</h3>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
import { useState } from 'react'
|
||||
|
||||
const AVAILABLE_TOOLS = [
|
||||
{ id: 'web_search', label: 'Web Search', icon: '🔍' },
|
||||
{ id: 'code_interpreter', label: 'Code Interpreter', icon: '💻' },
|
||||
{ id: 'file_reader', label: 'File Reader', icon: '📄' },
|
||||
{ id: 'calculator', label: 'Calculator', icon: '🧮' },
|
||||
{ id: 'wikipedia', label: 'Wikipedia', icon: '📖' },
|
||||
]
|
||||
|
||||
const LLM_MODELS = [
|
||||
'gpt-4o-mini',
|
||||
'gpt-4o',
|
||||
'claude-3-haiku',
|
||||
'claude-3-5-sonnet',
|
||||
'gemini-1.5-flash',
|
||||
]
|
||||
|
||||
export default function AgentForm({ agent, onSave, onCancel }) {
|
||||
const [form, setForm] = useState({
|
||||
name: agent?.name || '',
|
||||
role: agent?.role || '',
|
||||
instructions: agent?.instructions || '',
|
||||
llm: agent?.llm || 'gpt-4o-mini',
|
||||
tools: agent?.tools || [],
|
||||
connections: agent?.connections || [],
|
||||
status: agent?.status || 'active',
|
||||
})
|
||||
const [newConn, setNewConn] = useState('')
|
||||
|
||||
const set = (key, val) => setForm(prev => ({ ...prev, [key]: val }))
|
||||
|
||||
const toggleTool = (toolId) => {
|
||||
set('tools', form.tools.includes(toolId)
|
||||
? form.tools.filter(t => t !== toolId)
|
||||
: [...form.tools, toolId])
|
||||
}
|
||||
|
||||
const addConnection = () => {
|
||||
if (!newConn.trim()) return
|
||||
set('connections', [...form.connections, newConn.trim()])
|
||||
setNewConn('')
|
||||
}
|
||||
|
||||
const removeConnection = (i) => {
|
||||
set('connections', form.connections.filter((_, idx) => idx !== i))
|
||||
}
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault()
|
||||
if (!form.name.trim()) return
|
||||
onSave(form)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4" style={{ background: 'rgba(0,0,0,0.7)', backdropFilter: 'blur(4px)' }}>
|
||||
<div className="w-full max-w-2xl max-h-[90vh] overflow-y-auto rounded-2xl" style={{ background: '#1a1d27', border: '1px solid #2a2d3e' }}>
|
||||
<form onSubmit={handleSubmit}>
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between px-6 py-4 border-b" style={{ borderColor: '#2a2d3e' }}>
|
||||
<h2 className="text-lg font-bold text-white">{agent ? 'Edit Agent' : 'New Agent'}</h2>
|
||||
<button type="button" onClick={onCancel} className="p-1.5 rounded-lg hover:bg-white/10 transition-colors text-gray-400">
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="px-6 py-5 space-y-5">
|
||||
{/* Name + Role */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<Field label="Name *">
|
||||
<input
|
||||
type="text"
|
||||
value={form.name}
|
||||
onChange={e => set('name', e.target.value)}
|
||||
placeholder="Research Agent"
|
||||
required
|
||||
className="w-full px-3 py-2.5 rounded-lg text-sm outline-none"
|
||||
style={{ background: '#0f1117', border: '1px solid #2a2d3e', color: '#e2e8f0' }}
|
||||
onFocus={e => e.target.style.borderColor = '#6c63ff'}
|
||||
onBlur={e => e.target.style.borderColor = '#2a2d3e'}
|
||||
/>
|
||||
</Field>
|
||||
<Field label="Role">
|
||||
<input
|
||||
type="text"
|
||||
value={form.role}
|
||||
onChange={e => set('role', e.target.value)}
|
||||
placeholder="Searches the web..."
|
||||
required
|
||||
className="w-full px-3 py-2.5 rounded-lg text-sm outline-none"
|
||||
style={{ background: '#0f1117', border: '1px solid #2a2d3e', color: '#e2e8f0' }}
|
||||
onFocus={e => e.target.style.borderColor = '#6c63ff'}
|
||||
onBlur={e => e.target.style.borderColor = '#2a2d3e'}
|
||||
/>
|
||||
</Field>
|
||||
</div>
|
||||
|
||||
{/* Instructions */}
|
||||
<Field label="Instructions / System Prompt">
|
||||
<textarea
|
||||
value={form.instructions}
|
||||
onChange={e => set('instructions', e.target.value)}
|
||||
placeholder="You are a helpful assistant that..."
|
||||
rows={4}
|
||||
className="w-full px-3 py-2.5 rounded-lg text-sm outline-none resize-none"
|
||||
style={{ background: '#0f1117', border: '1px solid #2a2d3e', color: '#e2e8f0' }}
|
||||
onFocus={e => e.target.style.borderColor = '#6c63ff'}
|
||||
onBlur={e => e.target.style.borderColor = '#2a2d3e'}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
{/* LLM + Status */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<Field label="LLM Model">
|
||||
<select
|
||||
value={form.llm}
|
||||
onChange={e => set('llm', e.target.value)}
|
||||
className="w-full px-3 py-2.5 rounded-lg text-sm outline-none"
|
||||
style={{ background: '#0f1117', border: '1px solid #2a2d3e', color: '#e2e8f0' }}
|
||||
>
|
||||
{LLM_MODELS.map(m => <option key={m} value={m}>{m}</option>)}
|
||||
</select>
|
||||
</Field>
|
||||
<Field label="Status">
|
||||
<div className="flex gap-3 pt-1">
|
||||
{['active', 'auditing', 'decommissioned'].map(s => (
|
||||
<label key={s} className="flex items-center gap-2 cursor-pointer">
|
||||
<div
|
||||
onClick={() => set('status', s)}
|
||||
className="w-4 h-4 rounded-full border-2 flex items-center justify-center transition-colors cursor-pointer"
|
||||
style={{ borderColor: form.status === s ? '#6c63ff' : '#4b5563', background: form.status === s ? '#6c63ff' : 'transparent' }}
|
||||
>
|
||||
{form.status === s && <div className="w-1.5 h-1.5 rounded-full bg-white" />}
|
||||
</div>
|
||||
<span className="text-sm" style={{ color: '#d1d5db' }}>{s}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</Field>
|
||||
</div>
|
||||
|
||||
{/* Tools */}
|
||||
<Field label="Tools">
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 gap-2">
|
||||
{AVAILABLE_TOOLS.map(tool => {
|
||||
const active = form.tools.includes(tool.id)
|
||||
return (
|
||||
<button
|
||||
key={tool.id}
|
||||
type="button"
|
||||
onClick={() => toggleTool(tool.id)}
|
||||
className="flex items-center gap-2 px-3 py-2 rounded-lg text-sm transition-all"
|
||||
style={{
|
||||
background: active ? 'rgba(108,99,255,0.15)' : '#0f1117',
|
||||
border: `1px solid ${active ? '#6c63ff' : '#2a2d3e'}`,
|
||||
color: active ? '#818cf8' : '#9ca3af',
|
||||
}}
|
||||
>
|
||||
<span>{tool.icon}</span>
|
||||
<span>{tool.label}</span>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</Field>
|
||||
|
||||
{/* Connections */}
|
||||
<Field label="Connections">
|
||||
<div className="space-y-2">
|
||||
{form.connections.map((conn, i) => (
|
||||
<div key={i} className="flex items-center gap-2">
|
||||
<span className="flex-1 px-3 py-2 rounded-lg text-sm" style={{ background: '#0f1117', border: '1px solid #2a2d3e', color: '#d1d5db' }}>
|
||||
{conn}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => removeConnection(i)}
|
||||
className="p-1.5 rounded-lg hover:bg-red-500/20 transition-colors"
|
||||
style={{ color: '#ef4444' }}
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
type="text"
|
||||
value={newConn}
|
||||
onChange={e => setNewConn(e.target.value)}
|
||||
onKeyDown={e => e.key === 'Enter' && (e.preventDefault(), addConnection())}
|
||||
placeholder="Agent name or API URL..."
|
||||
className="flex-1 px-3 py-2 rounded-lg text-sm outline-none"
|
||||
style={{ background: '#0f1117', border: '1px solid #2a2d3e', color: '#e2e8f0' }}
|
||||
onFocus={e => e.target.style.borderColor = '#6c63ff'}
|
||||
onBlur={e => e.target.style.borderColor = '#2a2d3e'}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={addConnection}
|
||||
className="px-3 py-2 rounded-lg text-sm transition-colors"
|
||||
style={{ background: '#2a2d3e', color: '#d1d5db' }}
|
||||
>
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Field>
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="flex gap-3 justify-end px-6 py-4 border-t" style={{ borderColor: '#2a2d3e' }}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onCancel}
|
||||
className="px-5 py-2.5 rounded-xl text-sm font-medium transition-colors hover:bg-white/5"
|
||||
style={{ border: '1px solid #2a2d3e', color: '#9ca3af' }}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
className="px-5 py-2.5 rounded-xl text-sm font-medium transition-all hover:opacity-90 active:scale-95"
|
||||
style={{ background: '#6c63ff', color: 'white' }}
|
||||
>
|
||||
{agent ? 'Save Changes' : 'Create Agent'}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function Field({ label, children }) {
|
||||
return (
|
||||
<div>
|
||||
<label className="block text-xs font-semibold uppercase tracking-wider mb-2" style={{ color: '#6b7280' }}>
|
||||
{label}
|
||||
</label>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
import { useState, useRef, useEffect } from 'react'
|
||||
|
||||
function ActivityEntry({ item }) {
|
||||
const colorMap = {
|
||||
tool: '#3b82f6',
|
||||
success: '#22c55e',
|
||||
error: '#ef4444',
|
||||
message: '#6b7280',
|
||||
}
|
||||
const color = colorMap[item.type] || '#6b7280'
|
||||
const labels = { tool: 'TOOL', success: 'OK', error: 'ERR', message: 'MSG' }
|
||||
|
||||
return (
|
||||
<div className="flex items-start gap-2 text-xs py-0.5">
|
||||
<span className="font-mono font-bold shrink-0" style={{ color }}>{labels[item.type] || 'LOG'}</span>
|
||||
<span style={{ color: '#94a3b8' }}>{item.description}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function MessageBubble({ entry }) {
|
||||
const [expanded, setExpanded] = useState(false)
|
||||
const hasActivity = entry.activity && entry.activity.length > 0
|
||||
|
||||
return (
|
||||
<div className="space-y-2 mb-4">
|
||||
{/* User message */}
|
||||
<div className="flex justify-end">
|
||||
<div className="max-w-[75%] px-4 py-2.5 rounded-2xl rounded-tr-sm text-sm" style={{ background: '#6c63ff', color: 'white' }}>
|
||||
{entry.user_message}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Agent message */}
|
||||
<div className="flex justify-start flex-col">
|
||||
<div className="max-w-[80%] px-4 py-2.5 rounded-2xl rounded-tl-sm text-sm" style={{ background: '#1e2235', color: '#e2e8f0', border: '1px solid #2a2d3e' }}>
|
||||
<p className="whitespace-pre-wrap leading-relaxed">{entry.agent_response}</p>
|
||||
</div>
|
||||
|
||||
{/* Activity collapsible */}
|
||||
{hasActivity && (
|
||||
<div className="mt-1.5 max-w-[80%]">
|
||||
<button
|
||||
onClick={() => setExpanded(v => !v)}
|
||||
className="flex items-center gap-1.5 text-xs px-2 py-1 rounded-md transition-colors hover:bg-white/5"
|
||||
style={{ color: '#6b7280' }}
|
||||
>
|
||||
<svg className={`w-3 h-3 transition-transform ${expanded ? 'rotate-90' : ''}`} fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
Activity ({entry.activity.length} steps)
|
||||
</button>
|
||||
{expanded && (
|
||||
<div className="mt-1 px-3 py-2 rounded-lg space-y-0.5" style={{ background: '#0f1117', border: '1px solid #2a2d3e' }}>
|
||||
{entry.activity.map(a => <ActivityEntry key={a.id} item={a} />)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<p className="text-xs mt-1 ml-1" style={{ color: '#4b5563' }}>
|
||||
{new Date(entry.timestamp).toLocaleTimeString()}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default function ChatPanel({ agent, history, loading, onSend, onClearHistory }) {
|
||||
const [input, setInput] = useState('')
|
||||
const bottomRef = useRef(null)
|
||||
|
||||
useEffect(() => {
|
||||
bottomRef.current?.scrollIntoView({ behavior: 'smooth' })
|
||||
}, [history, loading])
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault()
|
||||
const msg = input.trim()
|
||||
if (!msg || loading) return
|
||||
setInput('')
|
||||
onSend(msg)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full">
|
||||
{/* Clear button */}
|
||||
<div className="flex justify-end px-4 py-2 shrink-0 border-b" style={{ borderColor: '#2a2d3e' }}>
|
||||
<button
|
||||
onClick={onClearHistory}
|
||||
className="text-xs px-3 py-1.5 rounded-lg transition-colors hover:bg-white/5"
|
||||
style={{ color: '#6b7280', border: '1px solid #2a2d3e' }}
|
||||
>
|
||||
Clear History
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Messages */}
|
||||
<div className="flex-1 overflow-y-auto px-4 py-4">
|
||||
{history.length === 0 && !loading && (
|
||||
<div className="flex flex-col items-center justify-center h-full text-center text-gray-500">
|
||||
<div className="w-14 h-14 rounded-2xl flex items-center justify-center mb-3 text-2xl" style={{ background: 'rgba(108,99,255,0.15)', border: '1px solid rgba(108,99,255,0.3)' }}>
|
||||
🤖
|
||||
</div>
|
||||
<p className="font-medium text-gray-300">{agent.name}</p>
|
||||
<p className="text-sm mt-1">{agent.role}</p>
|
||||
<p className="text-xs mt-3 max-w-xs">Send a message to start the conversation</p>
|
||||
</div>
|
||||
)}
|
||||
{history.map(entry => <MessageBubble key={entry.id} entry={entry} />)}
|
||||
{loading && (
|
||||
<div className="flex justify-start mb-4">
|
||||
<div className="px-4 py-3 rounded-2xl rounded-tl-sm" style={{ background: '#1e2235', border: '1px solid #2a2d3e' }}>
|
||||
<div className="flex gap-1.5 items-center">
|
||||
<div className="w-2 h-2 rounded-full animate-bounce" style={{ background: '#6c63ff', animationDelay: '0ms' }} />
|
||||
<div className="w-2 h-2 rounded-full animate-bounce" style={{ background: '#6c63ff', animationDelay: '150ms' }} />
|
||||
<div className="w-2 h-2 rounded-full animate-bounce" style={{ background: '#6c63ff', animationDelay: '300ms' }} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div ref={bottomRef} />
|
||||
</div>
|
||||
|
||||
{/* Input */}
|
||||
<form onSubmit={handleSubmit} className="px-4 py-3 shrink-0 border-t" style={{ borderColor: '#2a2d3e', background: '#1a1d27' }}>
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
type="text"
|
||||
value={input}
|
||||
onChange={e => setInput(e.target.value)}
|
||||
placeholder={`Message ${agent.name}...`}
|
||||
className="flex-1 px-4 py-2.5 rounded-xl text-sm outline-none transition-colors"
|
||||
style={{ background: '#0f1117', border: '1px solid #2a2d3e', color: '#e2e8f0' }}
|
||||
onFocus={e => e.target.style.borderColor = '#6c63ff'}
|
||||
onBlur={e => e.target.style.borderColor = '#2a2d3e'}
|
||||
disabled={loading}
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading || !input.trim()}
|
||||
className="px-4 py-2.5 rounded-xl text-sm font-medium transition-all hover:opacity-90 disabled:opacity-40 disabled:cursor-not-allowed active:scale-95"
|
||||
style={{ background: '#6c63ff', color: 'white' }}
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
export default function Sidebar({ agents, selectedId, onSelect, onNewAgent, onClose }) {
|
||||
return (
|
||||
<div className="flex flex-col h-full" style={{ background: '#1a1d27', borderRight: '1px solid #2a2d3e' }}>
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between px-4 py-4 border-b shrink-0" style={{ borderColor: '#2a2d3e' }}>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-7 h-7 rounded-lg flex items-center justify-center text-white font-bold text-sm" style={{ background: '#6c63ff' }}>
|
||||
A
|
||||
</div>
|
||||
<span className="font-bold text-white text-lg tracking-tight">AgentOS</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="p-1.5 rounded-lg hover:bg-white/10 transition-colors md:hidden text-gray-400"
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Agent list */}
|
||||
<div className="flex-1 overflow-y-auto py-2 px-2">
|
||||
<p className="px-2 py-1.5 text-xs font-semibold uppercase tracking-wider" style={{ color: '#6b7280' }}>
|
||||
Agents ({agents.length})
|
||||
</p>
|
||||
{agents.length === 0 ? (
|
||||
<div className="px-2 py-4 text-center text-sm text-gray-500">
|
||||
No agents yet.<br />Create your first one below.
|
||||
</div>
|
||||
) : (
|
||||
agents.map(agent => (
|
||||
<button
|
||||
key={agent.id}
|
||||
onClick={() => onSelect(agent.id)}
|
||||
className="w-full flex items-center gap-3 px-3 py-2.5 rounded-lg text-left transition-all mb-0.5 group"
|
||||
style={{
|
||||
background: selectedId === agent.id ? 'rgba(108,99,255,0.15)' : 'transparent',
|
||||
borderLeft: selectedId === agent.id ? '2px solid #6c63ff' : '2px solid transparent',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="w-2 h-2 rounded-full shrink-0 mt-0.5"
|
||||
style={{ background: agent.status === 'active' ? '#22c55e' : '#6b7280' }}
|
||||
/>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className={`text-sm font-medium truncate ${selectedId === agent.id ? 'text-white' : 'text-gray-300 group-hover:text-white'}`}>
|
||||
{agent.name}
|
||||
</p>
|
||||
<p className="text-xs truncate mt-0.5" style={{ color: '#6b7280' }}>
|
||||
{agent.role}
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* New agent button */}
|
||||
<div className="p-3 border-t shrink-0" style={{ borderColor: '#2a2d3e' }}>
|
||||
<button
|
||||
onClick={onNewAgent}
|
||||
className="w-full flex items-center justify-center gap-2 px-4 py-2.5 rounded-lg text-sm font-medium transition-all hover:opacity-90 active:scale-95"
|
||||
style={{ background: '#6c63ff', color: 'white' }}
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
|
||||
</svg>
|
||||
New Agent
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import App from './App'
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
)
|
||||
@@ -0,0 +1,18 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
server: {
|
||||
host: '0.0.0.0',
|
||||
port: 5000,
|
||||
allowedHosts: true,
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://localhost:8000',
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/api/, '')
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,6 @@
|
||||
def main():
|
||||
print("Hello from repl-nix-workspace!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
BIN
Binary file not shown.
@@ -0,0 +1,15 @@
|
||||
[project]
|
||||
name = "repl-nix-workspace"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = [
|
||||
"aiosqlite>=0.22.1",
|
||||
"fastapi>=0.136.0",
|
||||
"httpx>=0.28.1",
|
||||
"passlib[bcrypt]>=1.7.4",
|
||||
"pydantic[email]>=2.13.2",
|
||||
"pyjwt>=2.12.1",
|
||||
"sqlalchemy>=2.0.49",
|
||||
"uvicorn>=0.44.0",
|
||||
]
|
||||
@@ -0,0 +1,53 @@
|
||||
# AgentOS — PraisonAI Agent Dashboard
|
||||
|
||||
## Overview
|
||||
A full-stack web dashboard for managing and interacting with AI agents powered by PraisonAI.
|
||||
|
||||
## Architecture
|
||||
|
||||
### Frontend (React + Vite) — Port 5000
|
||||
```
|
||||
frontend/
|
||||
index.html # Entry point, Tailwind CDN, Google Fonts
|
||||
vite.config.js # Vite config, proxy /api → localhost:8000
|
||||
src/
|
||||
main.jsx # React root
|
||||
App.jsx # Main app layout, state management
|
||||
components/
|
||||
Sidebar.jsx # Agent list + New Agent button
|
||||
ChatPanel.jsx # Conversation with activity collapsible
|
||||
AgentDetail.jsx # Agent config view + stats
|
||||
ActivityLog.jsx # Real-time activity feed (right panel)
|
||||
AgentForm.jsx # Create/edit agent modal
|
||||
```
|
||||
|
||||
### Backend (FastAPI) — Port 8000
|
||||
```
|
||||
backend/
|
||||
main.py # FastAPI app, JSON storage
|
||||
agents_store.json # Agent definitions (persisted)
|
||||
history_store.json # Conversation history (persisted)
|
||||
```
|
||||
|
||||
## Running
|
||||
- Frontend: `python run.py` (starts Vite dev server on port 5000)
|
||||
- Backend: `python backend/main.py` (starts FastAPI on port 8000)
|
||||
|
||||
## API Endpoints
|
||||
- `GET /agents` — list agents
|
||||
- `POST /agents` — create agent
|
||||
- `PUT /agents/{id}` — update agent
|
||||
- `DELETE /agents/{id}` — delete agent
|
||||
- `POST /agents/{id}/chat` — send message, get response + activity
|
||||
- `GET /agents/{id}/history` — conversation history
|
||||
- `DELETE /agents/{id}/history` — clear history
|
||||
- `GET /agents/{id}/activity` — activity log
|
||||
|
||||
## Key Environment Variables
|
||||
- `OPENAI_API_KEY` — enables real AI responses via praisonaiagents
|
||||
(without it, demo mode is used)
|
||||
|
||||
## Platform API (legacy)
|
||||
The original praisonai-platform FastAPI app is still available in:
|
||||
- `src/praisonai-platform/` — multi-tenant workspace/issue tracking API
|
||||
- `src/praisonai-agents/` — core praisonaiagents SDK
|
||||
@@ -0,0 +1,11 @@
|
||||
"""Start the AgentOS frontend dev server on port 5000."""
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
if __name__ == "__main__":
|
||||
os.chdir(os.path.join(os.path.dirname(__file__), "frontend"))
|
||||
subprocess.run(
|
||||
["npm", "run", "dev"],
|
||||
env={**os.environ},
|
||||
)
|
||||
@@ -0,0 +1,508 @@
|
||||
version = 1
|
||||
revision = 3
|
||||
requires-python = ">=3.12"
|
||||
|
||||
[[package]]
|
||||
name = "aiosqlite"
|
||||
version = "0.22.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/4e/8a/64761f4005f17809769d23e518d915db74e6310474e733e3593cfc854ef1/aiosqlite-0.22.1.tar.gz", hash = "sha256:043e0bd78d32888c0a9ca90fc788b38796843360c855a7262a532813133a0650", size = 14821, upload-time = "2025-12-23T19:25:43.997Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/00/b7/e3bf5133d697a08128598c8d0abc5e16377b51465a33756de24fa7dee953/aiosqlite-0.22.1-py3-none-any.whl", hash = "sha256:21c002eb13823fad740196c5a2e9d8e62f6243bd9e7e4a1f87fb5e44ecb4fceb", size = 17405, upload-time = "2025-12-23T19:25:42.139Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "annotated-doc"
|
||||
version = "0.0.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "annotated-types"
|
||||
version = "0.7.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyio"
|
||||
version = "4.13.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "idna" },
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bcrypt"
|
||||
version = "5.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d4/36/3329e2518d70ad8e2e5817d5a4cac6bba05a47767ec416c7d020a965f408/bcrypt-5.0.0.tar.gz", hash = "sha256:f748f7c2d6fd375cc93d3fba7ef4a9e3a092421b8dbf34d8d4dc06be9492dfdd", size = 25386, upload-time = "2025-09-25T19:50:47.829Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/13/85/3e65e01985fddf25b64ca67275bb5bdb4040bd1a53b66d355c6c37c8a680/bcrypt-5.0.0-cp313-cp313t-macosx_10_12_universal2.whl", hash = "sha256:f3c08197f3039bec79cee59a606d62b96b16669cff3949f21e74796b6e3cd2be", size = 481806, upload-time = "2025-09-25T19:49:05.102Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/dc/01eb79f12b177017a726cbf78330eb0eb442fae0e7b3dfd84ea2849552f3/bcrypt-5.0.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:200af71bc25f22006f4069060c88ed36f8aa4ff7f53e67ff04d2ab3f1e79a5b2", size = 268626, upload-time = "2025-09-25T19:49:06.723Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8c/cf/e82388ad5959c40d6afd94fb4743cc077129d45b952d46bdc3180310e2df/bcrypt-5.0.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:baade0a5657654c2984468efb7d6c110db87ea63ef5a4b54732e7e337253e44f", size = 271853, upload-time = "2025-09-25T19:49:08.028Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/86/7134b9dae7cf0efa85671651341f6afa695857fae172615e960fb6a466fa/bcrypt-5.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:c58b56cdfb03202b3bcc9fd8daee8e8e9b6d7e3163aa97c631dfcfcc24d36c86", size = 269793, upload-time = "2025-09-25T19:49:09.727Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/82/6296688ac1b9e503d034e7d0614d56e80c5d1a08402ff856a4549cb59207/bcrypt-5.0.0-cp313-cp313t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4bfd2a34de661f34d0bda43c3e4e79df586e4716ef401fe31ea39d69d581ef23", size = 289930, upload-time = "2025-09-25T19:49:11.204Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/18/884a44aa47f2a3b88dd09bc05a1e40b57878ecd111d17e5bba6f09f8bb77/bcrypt-5.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:ed2e1365e31fc73f1825fa830f1c8f8917ca1b3ca6185773b349c20fd606cec2", size = 272194, upload-time = "2025-09-25T19:49:12.524Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/8f/371a3ab33c6982070b674f1788e05b656cfbf5685894acbfef0c65483a59/bcrypt-5.0.0-cp313-cp313t-manylinux_2_34_aarch64.whl", hash = "sha256:83e787d7a84dbbfba6f250dd7a5efd689e935f03dd83b0f919d39349e1f23f83", size = 269381, upload-time = "2025-09-25T19:49:14.308Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/34/7e4e6abb7a8778db6422e88b1f06eb07c47682313997ee8a8f9352e5a6f1/bcrypt-5.0.0-cp313-cp313t-manylinux_2_34_x86_64.whl", hash = "sha256:137c5156524328a24b9fac1cb5db0ba618bc97d11970b39184c1d87dc4bf1746", size = 271750, upload-time = "2025-09-25T19:49:15.584Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/1b/54f416be2499bd72123c70d98d36c6cd61a4e33d9b89562c22481c81bb30/bcrypt-5.0.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:38cac74101777a6a7d3b3e3cfefa57089b5ada650dce2baf0cbdd9d65db22a9e", size = 303757, upload-time = "2025-09-25T19:49:17.244Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/62/062c24c7bcf9d2826a1a843d0d605c65a755bc98002923d01fd61270705a/bcrypt-5.0.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:d8d65b564ec849643d9f7ea05c6d9f0cd7ca23bdd4ac0c2dbef1104ab504543d", size = 306740, upload-time = "2025-09-25T19:49:18.693Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/c8/1fdbfc8c0f20875b6b4020f3c7dc447b8de60aa0be5faaf009d24242aec9/bcrypt-5.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:741449132f64b3524e95cd30e5cd3343006ce146088f074f31ab26b94e6c75ba", size = 334197, upload-time = "2025-09-25T19:49:20.523Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/c1/8b84545382d75bef226fbc6588af0f7b7d095f7cd6a670b42a86243183cd/bcrypt-5.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:212139484ab3207b1f0c00633d3be92fef3c5f0af17cad155679d03ff2ee1e41", size = 352974, upload-time = "2025-09-25T19:49:22.254Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/a6/ffb49d4254ed085e62e3e5dd05982b4393e32fe1e49bb1130186617c29cd/bcrypt-5.0.0-cp313-cp313t-win32.whl", hash = "sha256:9d52ed507c2488eddd6a95bccee4e808d3234fa78dd370e24bac65a21212b861", size = 148498, upload-time = "2025-09-25T19:49:24.134Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/48/a9/259559edc85258b6d5fc5471a62a3299a6aa37a6611a169756bf4689323c/bcrypt-5.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f6984a24db30548fd39a44360532898c33528b74aedf81c26cf29c51ee47057e", size = 145853, upload-time = "2025-09-25T19:49:25.702Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/df/9714173403c7e8b245acf8e4be8876aac64a209d1b392af457c79e60492e/bcrypt-5.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:9fffdb387abe6aa775af36ef16f55e318dcda4194ddbf82007a6f21da29de8f5", size = 139626, upload-time = "2025-09-25T19:49:26.928Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/14/c18006f91816606a4abe294ccc5d1e6f0e42304df5a33710e9e8e95416e1/bcrypt-5.0.0-cp314-cp314t-macosx_10_12_universal2.whl", hash = "sha256:4870a52610537037adb382444fefd3706d96d663ac44cbb2f37e3919dca3d7ef", size = 481862, upload-time = "2025-09-25T19:49:28.365Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/67/49/dd074d831f00e589537e07a0725cf0e220d1f0d5d8e85ad5bbff251c45aa/bcrypt-5.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:48f753100931605686f74e27a7b49238122aa761a9aefe9373265b8b7aa43ea4", size = 268544, upload-time = "2025-09-25T19:49:30.39Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f5/91/50ccba088b8c474545b034a1424d05195d9fcbaaf802ab8bfe2be5a4e0d7/bcrypt-5.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f70aadb7a809305226daedf75d90379c397b094755a710d7014b8b117df1ebbf", size = 271787, upload-time = "2025-09-25T19:49:32.144Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/e7/d7dba133e02abcda3b52087a7eea8c0d4f64d3e593b4fffc10c31b7061f3/bcrypt-5.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:744d3c6b164caa658adcb72cb8cc9ad9b4b75c7db507ab4bc2480474a51989da", size = 269753, upload-time = "2025-09-25T19:49:33.885Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/33/fc/5b145673c4b8d01018307b5c2c1fc87a6f5a436f0ad56607aee389de8ee3/bcrypt-5.0.0-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a28bc05039bdf3289d757f49d616ab3efe8cf40d8e8001ccdd621cd4f98f4fc9", size = 289587, upload-time = "2025-09-25T19:49:35.144Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/27/d7/1ff22703ec6d4f90e62f1a5654b8867ef96bafb8e8102c2288333e1a6ca6/bcrypt-5.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:7f277a4b3390ab4bebe597800a90da0edae882c6196d3038a73adf446c4f969f", size = 272178, upload-time = "2025-09-25T19:49:36.793Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/88/815b6d558a1e4d40ece04a2f84865b0fef233513bd85fd0e40c294272d62/bcrypt-5.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:79cfa161eda8d2ddf29acad370356b47f02387153b11d46042e93a0a95127493", size = 269295, upload-time = "2025-09-25T19:49:38.164Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/8c/e0db387c79ab4931fc89827d37608c31cc57b6edc08ccd2386139028dc0d/bcrypt-5.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:a5393eae5722bcef046a990b84dff02b954904c36a194f6cfc817d7dca6c6f0b", size = 271700, upload-time = "2025-09-25T19:49:39.917Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/83/1570edddd150f572dbe9fc00f6203a89fc7d4226821f67328a85c330f239/bcrypt-5.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7f4c94dec1b5ab5d522750cb059bb9409ea8872d4494fd152b53cca99f1ddd8c", size = 334034, upload-time = "2025-09-25T19:49:41.227Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/f2/ea64e51a65e56ae7a8a4ec236c2bfbdd4b23008abd50ac33fbb2d1d15424/bcrypt-5.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0cae4cb350934dfd74c020525eeae0a5f79257e8a201c0c176f4b84fdbf2a4b4", size = 352766, upload-time = "2025-09-25T19:49:43.08Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/d4/1a388d21ee66876f27d1a1f41287897d0c0f1712ef97d395d708ba93004c/bcrypt-5.0.0-cp314-cp314t-win32.whl", hash = "sha256:b17366316c654e1ad0306a6858e189fc835eca39f7eb2cafd6aaca8ce0c40a2e", size = 152449, upload-time = "2025-09-25T19:49:44.971Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3f/61/3291c2243ae0229e5bca5d19f4032cecad5dfb05a2557169d3a69dc0ba91/bcrypt-5.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:92864f54fb48b4c718fc92a32825d0e42265a627f956bc0361fe869f1adc3e7d", size = 149310, upload-time = "2025-09-25T19:49:46.162Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/89/4b01c52ae0c1a681d4021e5dd3e45b111a8fb47254a274fa9a378d8d834b/bcrypt-5.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dd19cf5184a90c873009244586396a6a884d591a5323f0e8a5922560718d4993", size = 143761, upload-time = "2025-09-25T19:49:47.345Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/29/6237f151fbfe295fe3e074ecc6d44228faa1e842a81f6d34a02937ee1736/bcrypt-5.0.0-cp38-abi3-macosx_10_12_universal2.whl", hash = "sha256:fc746432b951e92b58317af8e0ca746efe93e66555f1b40888865ef5bf56446b", size = 494553, upload-time = "2025-09-25T19:49:49.006Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/b6/4c1205dde5e464ea3bd88e8742e19f899c16fa8916fb8510a851fae985b5/bcrypt-5.0.0-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c2388ca94ffee269b6038d48747f4ce8df0ffbea43f31abfa18ac72f0218effb", size = 275009, upload-time = "2025-09-25T19:49:50.581Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/71/427945e6ead72ccffe77894b2655b695ccf14ae1866cd977e185d606dd2f/bcrypt-5.0.0-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:560ddb6ec730386e7b3b26b8b4c88197aaed924430e7b74666a586ac997249ef", size = 278029, upload-time = "2025-09-25T19:49:52.533Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/17/72/c344825e3b83c5389a369c8a8e58ffe1480b8a699f46c127c34580c4666b/bcrypt-5.0.0-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d79e5c65dcc9af213594d6f7f1fa2c98ad3fc10431e7aa53c176b441943efbdd", size = 275907, upload-time = "2025-09-25T19:49:54.709Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/7e/d4e47d2df1641a36d1212e5c0514f5291e1a956a7749f1e595c07a972038/bcrypt-5.0.0-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2b732e7d388fa22d48920baa267ba5d97cca38070b69c0e2d37087b381c681fd", size = 296500, upload-time = "2025-09-25T19:49:56.013Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0f/c3/0ae57a68be2039287ec28bc463b82e4b8dc23f9d12c0be331f4782e19108/bcrypt-5.0.0-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0c8e093ea2532601a6f686edbc2c6b2ec24131ff5c52f7610dd64fa4553b5464", size = 278412, upload-time = "2025-09-25T19:49:57.356Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/2b/77424511adb11e6a99e3a00dcc7745034bee89036ad7d7e255a7e47be7d8/bcrypt-5.0.0-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:5b1589f4839a0899c146e8892efe320c0fa096568abd9b95593efac50a87cb75", size = 275486, upload-time = "2025-09-25T19:49:59.116Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/0a/405c753f6158e0f3f14b00b462d8bca31296f7ecfc8fc8bc7919c0c7d73a/bcrypt-5.0.0-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:89042e61b5e808b67daf24a434d89bab164d4de1746b37a8d173b6b14f3db9ff", size = 277940, upload-time = "2025-09-25T19:50:00.869Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/62/83/b3efc285d4aadc1fa83db385ec64dcfa1707e890eb42f03b127d66ac1b7b/bcrypt-5.0.0-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:e3cf5b2560c7b5a142286f69bde914494b6d8f901aaa71e453078388a50881c4", size = 310776, upload-time = "2025-09-25T19:50:02.393Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/95/7d/47ee337dacecde6d234890fe929936cb03ebc4c3a7460854bbd9c97780b8/bcrypt-5.0.0-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f632fd56fc4e61564f78b46a2269153122db34988e78b6be8b32d28507b7eaeb", size = 312922, upload-time = "2025-09-25T19:50:04.232Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d6/3a/43d494dfb728f55f4e1cf8fd435d50c16a2d75493225b54c8d06122523c6/bcrypt-5.0.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:801cad5ccb6b87d1b430f183269b94c24f248dddbbc5c1f78b6ed231743e001c", size = 341367, upload-time = "2025-09-25T19:50:05.559Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/55/ab/a0727a4547e383e2e22a630e0f908113db37904f58719dc48d4622139b5c/bcrypt-5.0.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3cf67a804fc66fc217e6914a5635000259fbbbb12e78a99488e4d5ba445a71eb", size = 359187, upload-time = "2025-09-25T19:50:06.916Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/bb/461f352fdca663524b4643d8b09e8435b4990f17fbf4fea6bc2a90aa0cc7/bcrypt-5.0.0-cp38-abi3-win32.whl", hash = "sha256:3abeb543874b2c0524ff40c57a4e14e5d3a66ff33fb423529c88f180fd756538", size = 153752, upload-time = "2025-09-25T19:50:08.515Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/aa/4190e60921927b7056820291f56fc57d00d04757c8b316b2d3c0d1d6da2c/bcrypt-5.0.0-cp38-abi3-win_amd64.whl", hash = "sha256:35a77ec55b541e5e583eb3436ffbbf53b0ffa1fa16ca6782279daf95d146dcd9", size = 150881, upload-time = "2025-09-25T19:50:09.742Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/54/12/cd77221719d0b39ac0b55dbd39358db1cd1246e0282e104366ebbfb8266a/bcrypt-5.0.0-cp38-abi3-win_arm64.whl", hash = "sha256:cde08734f12c6a4e28dc6755cd11d3bdfea608d93d958fffbe95a7026ebe4980", size = 144931, upload-time = "2025-09-25T19:50:11.016Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/ba/2af136406e1c3839aea9ecadc2f6be2bcd1eff255bd451dd39bcf302c47a/bcrypt-5.0.0-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:0c418ca99fd47e9c59a301744d63328f17798b5947b0f791e9af3c1c499c2d0a", size = 495313, upload-time = "2025-09-25T19:50:12.309Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ac/ee/2f4985dbad090ace5ad1f7dd8ff94477fe089b5fab2040bd784a3d5f187b/bcrypt-5.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddb4e1500f6efdd402218ffe34d040a1196c072e07929b9820f363a1fd1f4191", size = 275290, upload-time = "2025-09-25T19:50:13.673Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/6e/b77ade812672d15cf50842e167eead80ac3514f3beacac8902915417f8b7/bcrypt-5.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7aeef54b60ceddb6f30ee3db090351ecf0d40ec6e2abf41430997407a46d2254", size = 278253, upload-time = "2025-09-25T19:50:15.089Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/36/c4/ed00ed32f1040f7990dac7115f82273e3c03da1e1a1587a778d8cea496d8/bcrypt-5.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f0ce778135f60799d89c9693b9b398819d15f1921ba15fe719acb3178215a7db", size = 276084, upload-time = "2025-09-25T19:50:16.699Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e7/c4/fa6e16145e145e87f1fa351bbd54b429354fd72145cd3d4e0c5157cf4c70/bcrypt-5.0.0-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a71f70ee269671460b37a449f5ff26982a6f2ba493b3eabdd687b4bf35f875ac", size = 297185, upload-time = "2025-09-25T19:50:18.525Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/b4/11f8a31d8b67cca3371e046db49baa7c0594d71eb40ac8121e2fc0888db0/bcrypt-5.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f8429e1c410b4073944f03bd778a9e066e7fad723564a52ff91841d278dfc822", size = 278656, upload-time = "2025-09-25T19:50:19.809Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ac/31/79f11865f8078e192847d2cb526e3fa27c200933c982c5b2869720fa5fce/bcrypt-5.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:edfcdcedd0d0f05850c52ba3127b1fce70b9f89e0fe5ff16517df7e81fa3cbb8", size = 275662, upload-time = "2025-09-25T19:50:21.567Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/8d/5e43d9584b3b3591a6f9b68f755a4da879a59712981ef5ad2a0ac1379f7a/bcrypt-5.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:611f0a17aa4a25a69362dcc299fda5c8a3d4f160e2abb3831041feb77393a14a", size = 278240, upload-time = "2025-09-25T19:50:23.305Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/48/44590e3fc158620f680a978aafe8f87a4c4320da81ed11552f0323aa9a57/bcrypt-5.0.0-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:db99dca3b1fdc3db87d7c57eac0c82281242d1eabf19dcb8a6b10eb29a2e72d1", size = 311152, upload-time = "2025-09-25T19:50:24.597Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/85/e4fbfc46f14f47b0d20493669a625da5827d07e8a88ee460af6cd9768b44/bcrypt-5.0.0-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:5feebf85a9cefda32966d8171f5db7e3ba964b77fdfe31919622256f80f9cf42", size = 313284, upload-time = "2025-09-25T19:50:26.268Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/25/ae/479f81d3f4594456a01ea2f05b132a519eff9ab5768a70430fa1132384b1/bcrypt-5.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3ca8a166b1140436e058298a34d88032ab62f15aae1c598580333dc21d27ef10", size = 341643, upload-time = "2025-09-25T19:50:28.02Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/d2/36a086dee1473b14276cd6ea7f61aef3b2648710b5d7f1c9e032c29b859f/bcrypt-5.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:61afc381250c3182d9078551e3ac3a41da14154fbff647ddf52a769f588c4172", size = 359698, upload-time = "2025-09-25T19:50:31.347Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/f6/688d2cd64bfd0b14d805ddb8a565e11ca1fb0fd6817175d58b10052b6d88/bcrypt-5.0.0-cp39-abi3-win32.whl", hash = "sha256:64d7ce196203e468c457c37ec22390f1a61c85c6f0b8160fd752940ccfb3a683", size = 153725, upload-time = "2025-09-25T19:50:34.384Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/b9/9d9a641194a730bda138b3dfe53f584d61c58cd5230e37566e83ec2ffa0d/bcrypt-5.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:64ee8434b0da054d830fa8e89e1c8bf30061d539044a39524ff7dec90481e5c2", size = 150912, upload-time = "2025-09-25T19:50:35.69Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/27/44/d2ef5e87509158ad2187f4dd0852df80695bb1ee0cfe0a684727b01a69e0/bcrypt-5.0.0-cp39-abi3-win_arm64.whl", hash = "sha256:f2347d3534e76bf50bca5500989d6c1d05ed64b440408057a37673282c654927", size = 144953, upload-time = "2025-09-25T19:50:37.32Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2026.2.25"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.3.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/57/75/31212c6bf2503fdf920d87fee5d7a86a2e3bcf444984126f13d8e4016804/click-8.3.2.tar.gz", hash = "sha256:14162b8b3b3550a7d479eafa77dfd3c38d9dc8951f6f69c78913a8f9a7540fd5", size = 302856, upload-time = "2026-04-03T19:14:45.118Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/20/71885d8b97d4f3dde17b1fdb92dbd4908b00541c5a3379787137285f602e/click-8.3.2-py3-none-any.whl", hash = "sha256:1924d2c27c5653561cd2cae4548d1406039cb79b858b747cfea24924bbc1616d", size = 108379, upload-time = "2026-04-03T19:14:43.505Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dnspython"
|
||||
version = "2.8.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8c/8b/57666417c0f90f08bcafa776861060426765fdb422eb10212086fb811d26/dnspython-2.8.0.tar.gz", hash = "sha256:181d3c6996452cb1189c4046c61599b84a5a86e099562ffde77d26984ff26d0f", size = 368251, upload-time = "2025-09-07T18:58:00.022Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl", hash = "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af", size = 331094, upload-time = "2025-09-07T18:57:58.071Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "email-validator"
|
||||
version = "2.3.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "dnspython" },
|
||||
{ name = "idna" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f5/22/900cb125c76b7aaa450ce02fd727f452243f2e91a61af068b40adba60ea9/email_validator-2.3.0.tar.gz", hash = "sha256:9fc05c37f2f6cf439ff414f8fc46d917929974a82244c20eb10231ba60c54426", size = 51238, upload-time = "2025-08-26T13:09:06.831Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl", hash = "sha256:80f13f623413e6b197ae73bb10bf4eb0908faf509ad8362c5edeb0be7fd450b4", size = 35604, upload-time = "2025-08-26T13:09:05.858Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastapi"
|
||||
version = "0.136.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "annotated-doc" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "starlette" },
|
||||
{ name = "typing-extensions" },
|
||||
{ name = "typing-inspection" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/4e/d9/e66315807e41e69e7f6a1b42a162dada2f249c5f06ad3f1a95f84ab336ef/fastapi-0.136.0.tar.gz", hash = "sha256:cf08e067cc66e106e102d9ba659463abfac245200752f8a5b7b1e813de4ff73e", size = 396607, upload-time = "2026-04-16T11:47:13.623Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/26/a3/0bd5f0cdb0bbc92650e8dc457e9250358411ee5d1b65e42b6632387daf81/fastapi-0.136.0-py3-none-any.whl", hash = "sha256:8793d44ec7378e2be07f8a013cf7f7aa47d6327d0dfe9804862688ec4541a6b4", size = 117556, upload-time = "2026-04-16T11:47:11.922Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "greenlet"
|
||||
version = "3.4.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/86/94/a5935717b307d7c71fe877b52b884c6af707d2d2090db118a03fbd799369/greenlet-3.4.0.tar.gz", hash = "sha256:f50a96b64dafd6169e595a5c56c9146ef80333e67d4476a65a9c55f400fc22ff", size = 195913, upload-time = "2026-04-08T17:08:00.863Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/65/8b/3669ad3b3f247a791b2b4aceb3aa5a31f5f6817bf547e4e1ff712338145a/greenlet-3.4.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:1a54a921561dd9518d31d2d3db4d7f80e589083063ab4d3e2e950756ef809e1a", size = 286902, upload-time = "2026-04-08T15:52:12.138Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/38/3e/3c0e19b82900873e2d8469b590a6c4b3dfd2b316d0591f1c26b38a4879a5/greenlet-3.4.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:16dec271460a9a2b154e3b1c2fa1050ce6280878430320e85e08c166772e3f97", size = 606099, upload-time = "2026-04-08T16:24:38.408Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/33/99fef65e7754fc76a4ed14794074c38c9ed3394a5bd129d7f61b705f3168/greenlet-3.4.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:90036ce224ed6fe75508c1907a77e4540176dcf0744473627785dd519c6f9996", size = 618837, upload-time = "2026-04-08T16:30:58.298Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/57/eae2cac10421feae6c0987e3dc106c6d86262b1cb379e171b017aba893a6/greenlet-3.4.0-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6f0def07ec9a71d72315cf26c061aceee53b306c36ed38c35caba952ea1b319d", size = 624901, upload-time = "2026-04-08T16:40:38.981Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/36/f7/229f3aed6948faa20e0616a0b8568da22e365ede6a54d7d369058b128afd/greenlet-3.4.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a1c4f6b453006efb8310affb2d132832e9bbb4fc01ce6df6b70d810d38f1f6dc", size = 615062, upload-time = "2026-04-08T15:56:33.766Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/8a/0e73c9b94f31d1cc257fe79a0eff621674141cdae7d6d00f40de378a1e42/greenlet-3.4.0-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:0e1254cf0cbaa17b04320c3a78575f29f3c161ef38f59c977108f19ffddaf077", size = 423927, upload-time = "2026-04-08T16:43:05.293Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/08/97/d988180011aa40135c46cd0d0cf01dd97f7162bae14139b4a3ef54889ba5/greenlet-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9b2d9a138ffa0e306d0e2b72976d2fb10b97e690d40ab36a472acaab0838e2de", size = 1573511, upload-time = "2026-04-08T16:26:20.058Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/0f/a5a26fe152fb3d12e6a474181f6e9848283504d0afd095f353d85726374b/greenlet-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8424683caf46eb0eb6f626cb95e008e8cc30d0cb675bdfa48200925c79b38a08", size = 1640396, upload-time = "2026-04-08T15:57:30.88Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/42/cf/bb2c32d9a100e36ee9f6e38fad6b1e082b8184010cb06259b49e1266ca01/greenlet-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0a53fb071531d003b075c444014ff8f8b1a9898d36bb88abd9ac7b3524648a2", size = 238892, upload-time = "2026-04-08T17:03:10.094Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/47/6c41314bac56e71436ce551c7fbe3cc830ed857e6aa9708dbb9c65142eb6/greenlet-3.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:f38b81880ba28f232f1f675893a39cf7b6db25b31cc0a09bb50787ecf957e85e", size = 235599, upload-time = "2026-04-08T15:52:54.3Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/75/7e9cd1126a1e1f0cd67b0eda02e5221b28488d352684704a78ed505bd719/greenlet-3.4.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:43748988b097f9c6f09364f260741aa73c80747f63389824435c7a50bfdfd5c1", size = 285856, upload-time = "2026-04-08T15:52:45.82Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/c4/3e2df392e5cb199527c4d9dbcaa75c14edcc394b45040f0189f649631e3c/greenlet-3.4.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5566e4e2cd7a880e8c27618e3eab20f3494452d12fd5129edef7b2f7aa9a36d1", size = 610208, upload-time = "2026-04-08T16:24:39.674Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/da/af/750cdfda1d1bd30a6c28080245be8d0346e669a98fdbae7f4102aa95fff3/greenlet-3.4.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1054c5a3c78e2ab599d452f23f7adafef55062a783a8e241d24f3b633ba6ff82", size = 621269, upload-time = "2026-04-08T16:30:59.767Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/93/c8c508d68ba93232784bbc1b5474d92371f2897dfc6bc281b419f2e0d492/greenlet-3.4.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:98eedd1803353daf1cd9ef23eef23eda5a4d22f99b1f998d273a8b78b70dd47f", size = 628455, upload-time = "2026-04-08T16:40:40.698Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/54/78/0cbc693622cd54ebe25207efbb3a0eb07c2639cb8594f6e3aaaa0bb077a8/greenlet-3.4.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f82cb6cddc27dd81c96b1506f4aa7def15070c3b2a67d4e46fd19016aacce6cf", size = 617549, upload-time = "2026-04-08T15:56:34.893Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7f/46/cfaaa0ade435a60550fd83d07dfd5c41f873a01da17ede5c4cade0b9bab8/greenlet-3.4.0-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:b7857e2202aae67bc5725e0c1f6403c20a8ff46094ece015e7d474f5f7020b55", size = 426238, upload-time = "2026-04-08T16:43:06.865Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/c0/8966767de01343c1ff47e8b855dc78e7d1a8ed2b7b9c83576a57e289f81d/greenlet-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:227a46251ecba4ff46ae742bc5ce95c91d5aceb4b02f885487aff269c127a729", size = 1575310, upload-time = "2026-04-08T16:26:21.671Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/38/bcdc71ba05e9a5fda87f63ffc2abcd1f15693b659346df994a48c968003d/greenlet-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5b99e87be7eba788dd5b75ba1cde5639edffdec5f91fe0d734a249535ec3408c", size = 1640435, upload-time = "2026-04-08T15:57:32.572Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/c2/19b664b7173b9e4ef5f77e8cef9f14c20ec7fce7920dc1ccd7afd955d093/greenlet-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:849f8bc17acd6295fcb5de8e46d55cc0e52381c56eaf50a2afd258e97bc65940", size = 238760, upload-time = "2026-04-08T17:04:03.878Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/96/795619651d39c7fbd809a522f881aa6f0ead504cc8201c3a5b789dfaef99/greenlet-3.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:9390ad88b652b1903814eaabd629ca184db15e0eeb6fe8a390bbf8b9106ae15a", size = 235498, upload-time = "2026-04-08T17:05:00.584Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/02/bde66806e8f169cf90b14d02c500c44cdbe02c8e224c9c67bafd1b8cadd1/greenlet-3.4.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:10a07aca6babdd18c16a3f4f8880acfffc2b88dfe431ad6aa5f5740759d7d75e", size = 286291, upload-time = "2026-04-08T17:09:34.307Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/1f/39da1c336a87d47c58352fb8a78541ce63d63ae57c5b9dae1fe02801bbc2/greenlet-3.4.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:076e21040b3a917d3ce4ad68fb5c3c6b32f1405616c4a57aa83120979649bd3d", size = 656749, upload-time = "2026-04-08T16:24:41.721Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/6c/90ee29a4ee27af7aa2e2ec408799eeb69ee3fcc5abcecac6ddd07a5cd0f2/greenlet-3.4.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e82689eea4a237e530bb5cb41b180ef81fa2160e1f89422a67be7d90da67f615", size = 669084, upload-time = "2026-04-08T16:31:01.372Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/4a/74078d3936712cff6d3c91a930016f476ce4198d84e224fe6d81d3e02880/greenlet-3.4.0-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:06c2d3b89e0c62ba50bd7adf491b14f39da9e7e701647cb7b9ff4c99bee04b19", size = 673405, upload-time = "2026-04-08T16:40:42.527Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/07/49/d4cad6e5381a50947bb973d2f6cf6592621451b09368b8c20d9b8af49c5b/greenlet-3.4.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4df3b0b2289ec686d3c821a5fee44259c05cfe824dd5e6e12c8e5f5df23085cf", size = 665621, upload-time = "2026-04-08T15:56:35.995Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/79/3e/df8a83ab894751bc31e1106fdfaa80ca9753222f106b04de93faaa55feb7/greenlet-3.4.0-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:070b8bac2ff3b4d9e0ff36a0d19e42103331d9737e8504747cd1e659f76297bd", size = 471670, upload-time = "2026-04-08T16:43:08.512Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/37/31/d1edd54f424761b5d47718822f506b435b6aab2f3f93b465441143ea5119/greenlet-3.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8bff29d586ea415688f4cec96a591fcc3bf762d046a796cdadc1fdb6e7f2d5bf", size = 1622259, upload-time = "2026-04-08T16:26:23.201Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/c6/6d3f9cdcb21c4e12a79cb332579f1c6aa1af78eb68059c5a957c7812d95e/greenlet-3.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8a569c2fb840c53c13a2b8967c63621fafbd1a0e015b9c82f408c33d626a2fda", size = 1686916, upload-time = "2026-04-08T15:57:34.282Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/45/c1ca4a1ad975de4727e52d3ffe641ae23e1d7a8ffaa8ff7a0477e1827b92/greenlet-3.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:207ba5b97ea8b0b60eb43ffcacf26969dd83726095161d676aac03ff913ee50d", size = 239821, upload-time = "2026-04-08T17:03:48.423Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/c4/6f621023364d7e85a4769c014c8982f98053246d142420e0328980933ceb/greenlet-3.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:f8296d4e2b92af34ebde81085a01690f26a51eb9ac09a0fcadb331eb36dbc802", size = 236932, upload-time = "2026-04-08T17:04:33.551Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/8f/18d72b629783f5e8d045a76f5325c1e938e659a9e4da79c7dcd10169a48d/greenlet-3.4.0-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:d70012e51df2dbbccfaf63a40aaf9b40c8bed37c3e3a38751c926301ce538ece", size = 294681, upload-time = "2026-04-08T15:52:35.778Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/ad/5fa86ec46769c4153820d58a04062285b3b9e10ba3d461ee257b68dcbf53/greenlet-3.4.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a58bec0751f43068cd40cff31bb3ca02ad6000b3a51ca81367af4eb5abc480c8", size = 658899, upload-time = "2026-04-08T16:24:43.32Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/f0/4e8174ca0e87ae748c409f055a1ba161038c43cc0a5a6f1433a26ac2e5bf/greenlet-3.4.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:05fa0803561028f4b2e3b490ee41216a842eaee11aed004cc343a996d9523aa2", size = 665284, upload-time = "2026-04-08T16:31:02.833Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/92/466b0d9afd44b8af623139a3599d651c7564fa4152f25f117e1ee5949ffb/greenlet-3.4.0-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c4cd56a9eb7a6444edbc19062f7b6fbc8f287c663b946e3171d899693b1c19fa", size = 665872, upload-time = "2026-04-08T16:40:43.912Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/19/da/991cf7cd33662e2df92a1274b7eb4d61769294d38a1bba8a45f31364845e/greenlet-3.4.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e60d38719cb80b3ab5e85f9f1aed4960acfde09868af6762ccb27b260d68f4ed", size = 661861, upload-time = "2026-04-08T15:56:37.269Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/14/3395a7ef3e260de0325152ddfe19dffb3e49fe10873b94654352b53ad48e/greenlet-3.4.0-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:1f85f204c4d54134ae850d401fa435c89cd667d5ce9dc567571776b45941af72", size = 489237, upload-time = "2026-04-08T16:43:09.993Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/36/c5/6c2c708e14db3d9caea4b459d8464f58c32047451142fe2cfd90e7458f41/greenlet-3.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7f50c804733b43eded05ae694691c9aa68bca7d0a867d67d4a3f514742a2d53f", size = 1622182, upload-time = "2026-04-08T16:26:24.777Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/4c/50c5fed19378e11a29fabab1f6be39ea95358f4a0a07e115a51ca93385d8/greenlet-3.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2d4f0635dc4aa638cda4b2f5a07ae9a2cff9280327b581a3fcb6f317b4fbc38a", size = 1685050, upload-time = "2026-04-08T15:57:36.453Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/72/85ae954d734703ab48e622c59d4ce35d77ce840c265814af9c078cacc7aa/greenlet-3.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:1a4a48f24681300c640f143ba7c404270e1ebbbcf34331d7104a4ff40f8ea705", size = 245554, upload-time = "2026-04-08T17:03:50.044Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h11"
|
||||
version = "0.16.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httpcore"
|
||||
version = "1.0.9"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
{ name = "h11" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httpx"
|
||||
version = "0.28.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
{ name = "certifi" },
|
||||
{ name = "httpcore" },
|
||||
{ name = "idna" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.11"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "passlib"
|
||||
version = "1.7.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b6/06/9da9ee59a67fae7761aab3ccc84fa4f3f33f125b370f1ccdb915bf967c11/passlib-1.7.4.tar.gz", hash = "sha256:defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04", size = 689844, upload-time = "2020-10-08T19:00:52.121Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/a4/ab6b7589382ca3df236e03faa71deac88cae040af60c071a78d254a62172/passlib-1.7.4-py2.py3-none-any.whl", hash = "sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1", size = 525554, upload-time = "2020-10-08T19:00:49.856Z" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
bcrypt = [
|
||||
{ name = "bcrypt" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "2.13.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "annotated-types" },
|
||||
{ name = "pydantic-core" },
|
||||
{ name = "typing-extensions" },
|
||||
{ name = "typing-inspection" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/09/e5/06d23afac9973109d1e3c8ad38e1547a12e860610e327c05ee686827dc37/pydantic-2.13.2.tar.gz", hash = "sha256:b418196607e61081c3226dcd4f0672f2a194828abb9109e9cfb84026564df2d1", size = 843836, upload-time = "2026-04-17T09:31:59.636Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/77/ca/b45c378e6e8d0b90577288b533e04e95b7afd61bb1d51b6c263176435489/pydantic-2.13.2-py3-none-any.whl", hash = "sha256:a525087f4c03d7e7456a3de89b64cd693d2229933bb1068b9af6befd5563694e", size = 471947, upload-time = "2026-04-17T09:31:57.541Z" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
email = [
|
||||
{ name = "email-validator" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic-core"
|
||||
version = "2.46.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/43/bb/4742f05b739b2478459bb16fa8470549518c802e06ddcf3f106c5081315e/pydantic_core-2.46.2.tar.gz", hash = "sha256:37bb079f9ee3f1a519392b73fda2a96379b31f2013c6b467fe693e7f2987f596", size = 471269, upload-time = "2026-04-17T09:10:07.017Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/97/ec/2fafa4c86f5d2a69372c7cddef30925fd0e370b1efaf556609c1a0196d8a/pydantic_core-2.46.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ea1ad8c89da31512fe2d249cf0638fb666925bda341901541bc5f3311c6fcc9e", size = 2101729, upload-time = "2026-04-17T09:12:30.042Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cf/55/be5386c2c4b49af346e8a26b748194ff25757bbb6cf544130854e997af7a/pydantic_core-2.46.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b308da17b92481e0587244631c5529e5d91d04cb2b08194825627b1eca28e21e", size = 1951546, upload-time = "2026-04-17T09:10:10.585Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/29/92/89e273a055ce440e6636c756379af35ad86da9d336a560049c3ba5e41c80/pydantic_core-2.46.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d333a50bdd814a917d8d6a7ee35ba2395d53ddaa882613bc24e54a9d8b129095", size = 1976178, upload-time = "2026-04-17T09:11:49.619Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/91/b3/e4664469cf70c0cb0f7b2f5719d64e5968bb6f38217042c2afa3d3c4ba17/pydantic_core-2.46.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1d00b99590c5bd1fabbc5d28b170923e32c1b1071b1f1de1851a4d14d89eb192", size = 2051697, upload-time = "2026-04-17T09:12:04.917Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/98/58/dbf68213ee06ce51cdd6d8c95f97980e646858c45bd96bd2dfb40433be73/pydantic_core-2.46.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9f0e686960ffe9e65066395af856ac2d52c159043144433602c50c221d81c1ba", size = 2233160, upload-time = "2026-04-17T09:12:00.956Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f5/d3/68092aa0ee6c60ff4de4740eb82db3d4ce338ec89b3cecb978c532472f12/pydantic_core-2.46.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d1128da41c9cb474e0a4701f9c363ec645c9d1a02229904c76bf4e0a194fde2", size = 2298398, upload-time = "2026-04-17T09:10:29.694Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/51/5d6155eb737db55b0ad354ca5f333ef009f75feb67df2d79a84bace45af6/pydantic_core-2.46.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48649cf2d8c358d79586e9fb2f8235902fcaa2d969ec1c5301f2d1873b2f8321", size = 2094058, upload-time = "2026-04-17T09:12:10.995Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/f3/eb4a986197d71319430464ff181226c95adc8f06d932189b158bae5a82f5/pydantic_core-2.46.2-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:b902f0fc7c2cf503865a05718b68147c6cd5d0a3867af38c527be574a9fa6e9d", size = 2130388, upload-time = "2026-04-17T09:12:41.159Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/56/00/44a9c4fe6d0f64b5786d6a8c649d6f0e34ba6c89b3663add1066e54451a2/pydantic_core-2.46.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e80011f808b03d1d87a8f1e76ae3da19a18eb706c823e17981dcf1fae43744fc", size = 2184245, upload-time = "2026-04-17T09:12:36.532Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/6b/685b98a834d5e3d1c34a1bde1627525559dd223b75075bc7490cdb24eb33/pydantic_core-2.46.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b839d5c802e31348b949b6473f8190cddbf7d47475856d8ac995a373ee16ec59", size = 2186842, upload-time = "2026-04-17T09:13:04.054Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/64/caa2f5a2ac8b6113adaa410ccdf31ba7f54897a6e54cd0d726fc7e780c88/pydantic_core-2.46.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:c6b1064f3f9cf9072e1d59dd2936f9f3b668bec1c37039708c9222db703c0d5b", size = 2336066, upload-time = "2026-04-17T09:12:13.006Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/f9/7d2701bf82945b5b9e7df8347be97ef6a36da2846bfe5b4afec299ffe27b/pydantic_core-2.46.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:37a68e6f2ac95578ce3c0564802404b27b24988649616e556c07e77111ed3f1d", size = 2363691, upload-time = "2026-04-17T09:13:42.972Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/65/0dab11574101522941055109419db3cc09db871643dc3fc74e2413215e5b/pydantic_core-2.46.2-cp312-cp312-win32.whl", hash = "sha256:d9ffa75a7ef4b97d6e5e205fabd4304ef01fec09e6f1bdde04b9ad1b07d20289", size = 1958801, upload-time = "2026-04-17T09:11:31.981Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/2b/df84baa609c676f6450b8ecad44ea59146c805e3371b7b52443c0899f989/pydantic_core-2.46.2-cp312-cp312-win_amd64.whl", hash = "sha256:0551f2d2ddb68af5a00e26497f8025c538f73ef3cb698f8e5a487042cd2792a8", size = 2072634, upload-time = "2026-04-17T09:11:02.407Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/4e/e1ce8029fc438086a946739bf9d596f70ff470aad4a8345555920618cabe/pydantic_core-2.46.2-cp312-cp312-win_arm64.whl", hash = "sha256:83aef30f106edcc21a6a4cc44b82d3169a1dbe255508db788e778f3c804d3583", size = 2026188, upload-time = "2026-04-17T09:13:11.083Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/07/2b/662e48254479a2d3450ba24b1e25061108b64339794232f503990c519144/pydantic_core-2.46.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:d26e9eea3715008a09a74585fe9becd0c67fbb145dc4df9756d597d7230a652c", size = 2101762, upload-time = "2026-04-17T09:10:13.87Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/73/ab/bafd7c7503757ccc8ec4d1911e106fe474c629443648c51a88f08b0fe91a/pydantic_core-2.46.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:48b36e3235140510dc7861f0cd58b714b1cdd3d48f75e10ce52e69866b746f10", size = 1951814, upload-time = "2026-04-17T09:12:25.934Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/cc/7549c2d57ba2e9a42caa5861a2d398dbe31c02c6aca783253ace59ce84f8/pydantic_core-2.46.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36b1f99dc451f1a3981f236151465bcf995bbe712d0727c9f7b236fe228a8133", size = 1977329, upload-time = "2026-04-17T09:13:37.605Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/50/7ed4a8a0d478a4dca8f0134a5efa7193f03cc8520dd4c9509339fb2e5002/pydantic_core-2.46.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8641c8d535c2d95b45c2e19b646ecd23ebba35d461e0ae48a3498277006250ab", size = 2051832, upload-time = "2026-04-17T09:12:49.771Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/16/bb35b193741c0298ddc5f5e4234269efdc0c65e2bcd198aa0de9b68845e4/pydantic_core-2.46.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:20fb194788a0a50993e87013e693494ba183a2af5b44e99cf060bbae10912b11", size = 2233127, upload-time = "2026-04-17T09:11:04.449Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/91/a5/98f4b637149185addea19e1785ea20c373cca31b202f589111d8209d9873/pydantic_core-2.46.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9262d11d0cd11ee3303a95156939402bed6cedfe5ed0e331b95a283a4da6eb8b", size = 2297418, upload-time = "2026-04-17T09:11:25.929Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/36/90/93a5d21990b152da7b7507b7fddb0b935f6a0984d57ac3ec45a6e17777a2/pydantic_core-2.46.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac204542736aa295fa25f713b7fad6fc50b46ab7764d16087575c85f085174f3", size = 2093735, upload-time = "2026-04-17T09:12:06.908Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/14/22/b8b1ffdddf08b4e84380bcb67f41dbbf4c171377c1d36fc6290794bb2094/pydantic_core-2.46.2-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9a7c43a0584742dface3ca0daf6f719d46c1ac2f87cf080050f9ae052c75e1b2", size = 2127570, upload-time = "2026-04-17T09:11:53.906Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c6/26/e60d72b4e2d0ce1fa811044a974412ac1c567fe067d97b3e6b290530786e/pydantic_core-2.46.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fd05e1edb6a90ad446fa268ab09e59202766b837597b714b2492db11ee87fab9", size = 2183524, upload-time = "2026-04-17T09:11:30.092Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/35/32/36bec7584a1eefb17dec4dfa1c946d3fe4440f466c5705b8adfda69c9a9f/pydantic_core-2.46.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:91155b110788b5501abc7ea954f1d08606219e4e28e3c73a94124307c06efb80", size = 2185408, upload-time = "2026-04-17T09:10:57.228Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/d6/1a5689d873620efd67d6b163db0c444c056adb0849b5bc33e2b9f09665a6/pydantic_core-2.46.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:e4e2c72a529fa03ff228be1d2b76944013f428220b764e03cc50ada67e17a42c", size = 2335171, upload-time = "2026-04-17T09:11:43.369Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/8e/675104802abe8ef502b072050ee5f2e915251aa1a3af87e1015ce31ec42d/pydantic_core-2.46.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:56291ec1a11c3499890c99a8fd9053b47e60fe837a77ec72c0671b1b8b3dce24", size = 2362743, upload-time = "2026-04-17T09:10:18.333Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/bc/86c5dde4fa6e24467680eef5047da3c1a19be0a527d0d8e14aa76b39307c/pydantic_core-2.46.2-cp313-cp313-win32.whl", hash = "sha256:b50f9c5f826ddca1246f055148df939f5f3f2d0d96db73de28e2233f22210d4c", size = 1958074, upload-time = "2026-04-17T09:12:38.622Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/97/2537e8c1282b2c4eb062580c0d7a4339e10b072b803d1ee0b7f1f0a5c22c/pydantic_core-2.46.2-cp313-cp313-win_amd64.whl", hash = "sha256:251a57788823230ca8cbc99e6245d1a2ed6e180ec4864f251c94182c580c7f2e", size = 2071741, upload-time = "2026-04-17T09:13:32.405Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/da/aa/2ee75798706f9dbc4e76dbe59e41a396c5c311e3d6223b9cf6a5fa7780be/pydantic_core-2.46.2-cp313-cp313-win_arm64.whl", hash = "sha256:315d32d1a71494d6b4e1e14a9fa7a4329597b4c4340088ad7e1a9dafbeed92a9", size = 2025955, upload-time = "2026-04-17T09:10:15.567Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d0/96/a50ccb6b539ae780f73cea74905468777680e30c6c3bdf714b9d4c116ea0/pydantic_core-2.46.2-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:4f59b45f3ef8650c0c736a57f59031d47ed9df4c0a64e83796849d7d14863a2d", size = 2097111, upload-time = "2026-04-17T09:10:49.617Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/34/5f/fdead7b3afa822ab6e5a18ee0ecffd54937de1877c01ed13a342e0fb3f07/pydantic_core-2.46.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3a075a29ebef752784a91532a1a85be6b234ccffec0a9d7978a92696387c3da6", size = 1951904, upload-time = "2026-04-17T09:12:32.062Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/95/e0/1c5d547e550cdab1bec737492aa08865337af6fe7fc9b96f7f45f17d9519/pydantic_core-2.46.2-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d12d786e30c04a9d307c5d7080bf720d9bac7f1668191d8e37633a9562749e2", size = 1978667, upload-time = "2026-04-17T09:11:35.589Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/cb/665ce629e218c8228302cb94beff4f6531082a2c87d3ecc3d5e63a26f392/pydantic_core-2.46.2-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0d5e6d6343b0b5dcacb3503b5de90022968da8ed0ab9ab39d3eda71c20cbf84e", size = 2046721, upload-time = "2026-04-17T09:11:47.725Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/e9/6cb2cf60f54c1472bbdfce19d957553b43dbba79d1d7b2930a195c594785/pydantic_core-2.46.2-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:233eebac0999b6b9ba76eb56f3ec8fce13164aa16b6d2225a36a79e0f95b5973", size = 2228483, upload-time = "2026-04-17T09:12:08.837Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/2a/93e018dd5571f781ebaeda8c0cf65398489d5bee9b1f484df0b6149b43b9/pydantic_core-2.46.2-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9cc0eee720dd2f14f3b7c349469402b99ad81a174ab49d3533974529e9d93992", size = 2294663, upload-time = "2026-04-17T09:12:52.053Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/4f/49e57ca55c770c93d9bb046666a54949b42e3c9099a0c5fe94557873fe30/pydantic_core-2.46.2-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83ee76bf2c9910513dbc19e7d82367131fa7508dedd6186a462393071cc11059", size = 2098742, upload-time = "2026-04-17T09:13:45.472Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c6/b0/6e46b5cd3332af665f794b8cdeea206618a8630bd9e7bcc36864518fce81/pydantic_core-2.46.2-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:d61db38eb4ee5192f0c261b7f2d38e420b554df8912245e3546aee5c45e2fd78", size = 2125922, upload-time = "2026-04-17T09:12:54.304Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/d1/40850c81585be443a2abfdf7f795f8fae831baf8e2f9b2133c8246ac671c/pydantic_core-2.46.2-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8f09a713d17bcd55da8ab02ebd9110c5246a49c44182af213b5212800af8bc83", size = 2183000, upload-time = "2026-04-17T09:10:59.027Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/af/8493d7dfa03ebb7866909e577c6aa65ea0de7377b86023cc51d0c8e11db3/pydantic_core-2.46.2-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:30cacc5fb696e64b8ef6fd31d9549d394dd7d52760db072eecb98e37e3af1677", size = 2180335, upload-time = "2026-04-17T09:12:57.01Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/72/5b/1f6a344c4ffdf284da41c6067b82d5ebcbd11ce1b515ae4b662d4adb6f61/pydantic_core-2.46.2-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:7ccfb105fcfe91a22bbb5563ad3dc124bc1aa75bfd2e53a780ab05f78cdf6108", size = 2330002, upload-time = "2026-04-17T09:12:02.958Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/25/ff/9a694126c12d6d2f48a0cafa6f8eef88ef0d8825600e18d03ff2e896c3b2/pydantic_core-2.46.2-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:13ffef637dc8370c249e5b26bd18e9a80a4fca3d809618c44e18ec834a7ca7a8", size = 2359920, upload-time = "2026-04-17T09:10:27.764Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/c8/3a35c763d68a9cb2675eb10ef242cf66c5d4701b28ae12e688d67d2c180e/pydantic_core-2.46.2-cp314-cp314-win32.whl", hash = "sha256:1b0ab6d756ca2704a938e6c31b53f290c2f9c10d3914235410302a149de1a83e", size = 1953701, upload-time = "2026-04-17T09:13:30.021Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/6a/f2726a780365f7dfd89d62036f984f7acb99978c60c5e1fa7c0cb898ed11/pydantic_core-2.46.2-cp314-cp314-win_amd64.whl", hash = "sha256:99ebade8c9ada4df975372d8dd25883daa0e379a05f1cd0c99aa0c04368d01a6", size = 2071867, upload-time = "2026-04-17T09:10:39.205Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e1/79/76baacb9feba3d7c399b245ca1a29c74ea0db04ea693811374827eec2290/pydantic_core-2.46.2-cp314-cp314-win_arm64.whl", hash = "sha256:de87422197cf7f83db91d89c86a21660d749b3cd76cd8a45d115b8e675670f02", size = 2017252, upload-time = "2026-04-17T09:10:26.175Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/3b/77c26938f817668d9ad9bab1a905cb23f11d9a3d4bf724d429b3e55a8eaf/pydantic_core-2.46.2-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:236f22b4a206b5b61db955396b7cf9e2e1ff77f372efe9570128ccfcd6a525eb", size = 2094545, upload-time = "2026-04-17T09:12:19.339Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/de/42c13f590e3c260966aa49bcdb1674774f975467c49abd51191e502bea28/pydantic_core-2.46.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c2012f64d2cd7cca50f49f22445aa5a88691ac2b4498ee0a9a977f8ca4f7289f", size = 1933953, upload-time = "2026-04-17T09:09:55.889Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/84/ebe3ebb3e2d8db656937cfa6f97f544cb7132f2307a4a7dfdcd0ea102a12/pydantic_core-2.46.2-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d07d6c63106d3a9c9a333e2636f9c82c703b1a9e3b079299e58747964e4fdb72", size = 1974435, upload-time = "2026-04-17T09:10:12.371Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b9/15/0bf51ca6709477cd4ef86148b6d7844f3308f029eac361dd0383f1e17b1a/pydantic_core-2.46.2-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c326a2b4b85e959d9a1fc3a11f32f84611b6ec07c053e1828a860edf8d068208", size = 2031113, upload-time = "2026-04-17T09:10:00.752Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/02/ae/b7b5af9b79db036d9e61a44c481c17a213dc8fc4b8b71fe6875a72fc778b/pydantic_core-2.46.2-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac8a65e798f2462552c00d2e013d532c94d646729dda98458beaf51f9ec7b120", size = 2236325, upload-time = "2026-04-17T09:10:33.227Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/ae/ecef7477b5a03d4a499708f7e75d2836452ebb70b776c2d64612b334f57a/pydantic_core-2.46.2-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a3c2bc1cc8164bedbc160b7bb1e8cc1e8b9c27f69ae4f9ae2b976cdae02b2dd", size = 2278135, upload-time = "2026-04-17T09:10:23.287Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/e4/2f9d82faa47af6c39fc3f120145fd915971e1e0cb6b55b494fad9fdf8275/pydantic_core-2.46.2-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e69aa5e10b7e8b1bb4a6888650fd12fcbf11d396ca11d4a44de1450875702830", size = 2109071, upload-time = "2026-04-17T09:11:06.149Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/9c/677cf10873fbd0b116575ab7b97c90482b21564f8a8040beb18edef7a577/pydantic_core-2.46.2-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:4e6df5c3301e65fb42bc5338bf9a1027a02b0a31dc7f54c33775229af474daf0", size = 2106028, upload-time = "2026-04-17T09:10:51.525Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d6/53/6a06183544daba51c059123a2064a99039df25f115a06bdb26f2ea177038/pydantic_core-2.46.2-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2c2f6e32548ac8d559b47944effcf8ae4d81c161f6b6c885edc53bc08b8f192d", size = 2164816, upload-time = "2026-04-17T09:11:56.187Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/57/6f/10fcdd9e3eca66fc828eef0f6f5850f2dd3bca2c59e6e041fb8bc3da39be/pydantic_core-2.46.2-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:b089a81c58e6ea0485562bbbbbca4f65c0549521606d5ef27fba217aac9b665a", size = 2166130, upload-time = "2026-04-17T09:10:03.804Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/29/83/92d3fd0e0156cad2e3cb5c26de73794af78ac9fa0c22ab666e566dd67061/pydantic_core-2.46.2-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:7f700a6d6f64112ae9193709b84303bbab84424ad4b47d0253301aabce9dfc70", size = 2316605, upload-time = "2026-04-17T09:12:45.249Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/97/f1/facffdb970981068219582e499b8d0871ed163ffcc6b347de5c412669e4c/pydantic_core-2.46.2-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:67db6814beaa5fefe91101ec7eb9efda613795767be96f7cf58b1ca8c9ca9972", size = 2358385, upload-time = "2026-04-17T09:09:54.657Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/a1/b8160b2f22b2199467bc68581a4ed380643c16b348a27d6165c6c242d694/pydantic_core-2.46.2-cp314-cp314t-win32.whl", hash = "sha256:32fbc7447be8e3be99bf7869f7066308f16be55b61f9882c2cefc7931f5c7664", size = 1942373, upload-time = "2026-04-17T09:12:59.594Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/90/db89acabe5b150e11d1b59fe3d947dda2ef6abbfef5c82f056ff63802f5d/pydantic_core-2.46.2-cp314-cp314t-win_amd64.whl", hash = "sha256:b317a2b97019c0b95ce99f4f901ae383f40132da6706cdf1731066a73394c25c", size = 2052078, upload-time = "2026-04-17T09:10:19.96Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/97/32/e19b83ceb07a3f1bb21798407790bbc9a31740158fd132b94139cb84e16c/pydantic_core-2.46.2-cp314-cp314t-win_arm64.whl", hash = "sha256:7dcb9d40930dfad7ab6b20bcc6ca9d2b030b0f347a0cd9909b54bd53ead521b1", size = 2016941, upload-time = "2026-04-17T09:12:34.447Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/d2/66c146f421178641bda880b0267c0d57dd84f5fec9ecc8e46be17b480742/pydantic_core-2.46.2-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:e9fcabd1857492b5bf16f90258babde50f618f55d046b1309972da2396321ff9", size = 2091621, upload-time = "2026-04-17T09:12:47.501Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/b2/c28419aa9fc8055f4ac8e801d1d11c6357351bfa4321ed9bafab3eb98087/pydantic_core-2.46.2-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:fb3ec2c7f54c07b30d89983ce78dc32c37dd06a972448b8716d609493802d628", size = 1937059, upload-time = "2026-04-17T09:10:53.554Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/ce/cd0824a2db213dc17113291b7a09b9b0ccd9fbf97daa4b81548703341baf/pydantic_core-2.46.2-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:130a6c837d819ef33e8c2bf702ed2c3429237ea69807f1140943d6f4bdaf52fa", size = 1997278, upload-time = "2026-04-17T09:12:23.784Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/69/47283fe3c0c967d3e9e9cd6c42b70907610c8a6f8d6e8381f1bb55f8006c/pydantic_core-2.46.2-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c2e25417cec5cd9bddb151e33cb08c50160f317479ecc02b22a95ec18f8fe004", size = 2147096, upload-time = "2026-04-17T09:12:43.124Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyjwt"
|
||||
version = "2.12.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c2/27/a3b6e5bf6ff856d2509292e95c8f57f0df7017cf5394921fc4e4ef40308a/pyjwt-2.12.1.tar.gz", hash = "sha256:c74a7a2adf861c04d002db713dd85f84beb242228e671280bf709d765b03672b", size = 102564, upload-time = "2026-03-13T19:27:37.25Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/7a/8dd906bd22e79e47397a61742927f6747fe93242ef86645ee9092e610244/pyjwt-2.12.1-py3-none-any.whl", hash = "sha256:28ca37c070cad8ba8cd9790cd940535d40274d22f80ab87f3ac6a713e6e8454c", size = 29726, upload-time = "2026-03-13T19:27:35.677Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "repl-nix-workspace"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "aiosqlite" },
|
||||
{ name = "fastapi" },
|
||||
{ name = "httpx" },
|
||||
{ name = "passlib", extra = ["bcrypt"] },
|
||||
{ name = "pydantic", extra = ["email"] },
|
||||
{ name = "pyjwt" },
|
||||
{ name = "sqlalchemy" },
|
||||
{ name = "uvicorn" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "aiosqlite", specifier = ">=0.22.1" },
|
||||
{ name = "fastapi", specifier = ">=0.136.0" },
|
||||
{ name = "httpx", specifier = ">=0.28.1" },
|
||||
{ name = "passlib", extras = ["bcrypt"], specifier = ">=1.7.4" },
|
||||
{ name = "pydantic", extras = ["email"], specifier = ">=2.13.2" },
|
||||
{ name = "pyjwt", specifier = ">=2.12.1" },
|
||||
{ name = "sqlalchemy", specifier = ">=2.0.49" },
|
||||
{ name = "uvicorn", specifier = ">=0.44.0" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlalchemy"
|
||||
version = "2.0.49"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/09/45/461788f35e0364a8da7bda51a1fe1b09762d0c32f12f63727998d85a873b/sqlalchemy-2.0.49.tar.gz", hash = "sha256:d15950a57a210e36dd4cec1aac22787e2a4d57ba9318233e2ef8b2daf9ff2d5f", size = 9898221, upload-time = "2026-04-03T16:38:11.704Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/49/b3/2de412451330756aaaa72d27131db6dde23995efe62c941184e15242a5fa/sqlalchemy-2.0.49-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4bbccb45260e4ff1b7db0be80a9025bb1e6698bdb808b83fff0000f7a90b2c0b", size = 2157681, upload-time = "2026-04-03T16:53:07.132Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/84/b2a56e2105bd11ebf9f0b93abddd748e1a78d592819099359aa98134a8bf/sqlalchemy-2.0.49-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fb37f15714ec2652d574f021d479e78cd4eb9d04396dca36568fdfffb3487982", size = 3338976, upload-time = "2026-04-03T17:07:40Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/fa/65fcae2ed62f84ab72cf89536c7c3217a156e71a2c111b1305ab6f0690e2/sqlalchemy-2.0.49-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3bb9ec6436a820a4c006aad1ac351f12de2f2dbdaad171692ee457a02429b672", size = 3351937, upload-time = "2026-04-03T17:12:23.374Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/2f/6fd118563572a7fe475925742eb6b3443b2250e346a0cc27d8d408e73773/sqlalchemy-2.0.49-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8d6efc136f44a7e8bc8088507eaabbb8c2b55b3dbb63fe102c690da0ddebe55e", size = 3281646, upload-time = "2026-04-03T17:07:41.949Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c5/d7/410f4a007c65275b9cf82354adb4bb8ba587b176d0a6ee99caa16fe638f8/sqlalchemy-2.0.49-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e06e617e3d4fd9e51d385dfe45b077a41e9d1b033a7702551e3278ac597dc750", size = 3316695, upload-time = "2026-04-03T17:12:25.642Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/95/81f594aa60ded13273a844539041ccf1e66c5a7bed0a8e27810a3b52d522/sqlalchemy-2.0.49-cp312-cp312-win32.whl", hash = "sha256:83101a6930332b87653886c01d1ee7e294b1fe46a07dd9a2d2b4f91bcc88eec0", size = 2117483, upload-time = "2026-04-03T17:05:40.896Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/9e/fd90114059175cac64e4fafa9bf3ac20584384d66de40793ae2e2f26f3bb/sqlalchemy-2.0.49-cp312-cp312-win_amd64.whl", hash = "sha256:618a308215b6cececb6240b9abde545e3acdabac7ae3e1d4e666896bf5ba44b4", size = 2144494, upload-time = "2026-04-03T17:05:42.282Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/81/81755f50eb2478eaf2049728491d4ea4f416c1eb013338682173259efa09/sqlalchemy-2.0.49-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df2d441bacf97022e81ad047e1597552eb3f83ca8a8f1a1fdd43cd7fe3898120", size = 2154547, upload-time = "2026-04-03T16:53:08.64Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/bc/3494270da80811d08bcfa247404292428c4fe16294932bce5593f215cad9/sqlalchemy-2.0.49-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8e20e511dc15265fb433571391ba313e10dd8ea7e509d51686a51313b4ac01a2", size = 3280782, upload-time = "2026-04-03T17:07:43.508Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cd/f5/038741f5e747a5f6ea3e72487211579d8cbea5eb9827a9cbd61d0108c4bd/sqlalchemy-2.0.49-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47604cb2159f8bbd5a1ab48a714557156320f20871ee64d550d8bf2683d980d3", size = 3297156, upload-time = "2026-04-03T17:12:27.697Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/88/50/a6af0ff9dc954b43a65ca9b5367334e45d99684c90a3d3413fc19a02d43c/sqlalchemy-2.0.49-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:22d8798819f86720bc646ab015baff5ea4c971d68121cb36e2ebc2ee43ead2b7", size = 3228832, upload-time = "2026-04-03T17:07:45.38Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/d1/5f6bdad8de0bf546fc74370939621396515e0cdb9067402d6ba1b8afbe9a/sqlalchemy-2.0.49-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9b1c058c171b739e7c330760044803099c7fff11511e3ab3573e5327116a9c33", size = 3267000, upload-time = "2026-04-03T17:12:29.657Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/30/ad62227b4a9819a5e1c6abff77c0f614fa7c9326e5a3bdbee90f7139382b/sqlalchemy-2.0.49-cp313-cp313-win32.whl", hash = "sha256:a143af2ea6672f2af3f44ed8f9cd020e9cc34c56f0e8db12019d5d9ecf41cb3b", size = 2115641, upload-time = "2026-04-03T17:05:43.989Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/17/3a/7215b1b7d6d49dc9a87211be44562077f5f04f9bb5a59552c1c8e2d98173/sqlalchemy-2.0.49-cp313-cp313-win_amd64.whl", hash = "sha256:12b04d1db2663b421fe072d638a138460a51d5a862403295671c4f3987fb9148", size = 2141498, upload-time = "2026-04-03T17:05:45.7Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/28/4b/52a0cb2687a9cd1648252bb257be5a1ba2c2ded20ba695c65756a55a15a4/sqlalchemy-2.0.49-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24bd94bb301ec672d8f0623eba9226cc90d775d25a0c92b5f8e4965d7f3a1518", size = 3560807, upload-time = "2026-04-03T16:58:31.666Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8c/d8/fda95459204877eed0458550d6c7c64c98cc50c2d8d618026737de9ed41a/sqlalchemy-2.0.49-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a51d3db74ba489266ef55c7a4534eb0b8db9a326553df481c11e5d7660c8364d", size = 3527481, upload-time = "2026-04-03T17:06:00.155Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/0a/2aac8b78ac6487240cf7afef8f203ca783e8796002dc0cf65c4ee99ff8bb/sqlalchemy-2.0.49-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:55250fe61d6ebfd6934a272ee16ef1244e0f16b7af6cd18ab5b1fc9f08631db0", size = 3468565, upload-time = "2026-04-03T16:58:33.414Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a5/3d/ce71cfa82c50a373fd2148b3c870be05027155ce791dc9a5dcf439790b8b/sqlalchemy-2.0.49-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:46796877b47034b559a593d7e4b549aba151dae73f9e78212a3478161c12ab08", size = 3477769, upload-time = "2026-04-03T17:06:02.787Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/e8/0a9f5c1f7c6f9ca480319bf57c2d7423f08d31445974167a27d14483c948/sqlalchemy-2.0.49-cp313-cp313t-win32.whl", hash = "sha256:9c4969a86e41454f2858256c39bdfb966a20961e9b58bf8749b65abf447e9a8d", size = 2143319, upload-time = "2026-04-03T17:02:04.328Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/51/fb5240729fbec73006e137c4f7a7918ffd583ab08921e6ff81a999d6517a/sqlalchemy-2.0.49-cp313-cp313t-win_amd64.whl", hash = "sha256:b9870d15ef00e4d0559ae10ee5bc71b654d1f20076dbe8bc7ed19b4c0625ceba", size = 2175104, upload-time = "2026-04-03T17:02:05.989Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/55/33/bf28f618c0a9597d14e0b9ee7d1e0622faff738d44fe986ee287cdf1b8d0/sqlalchemy-2.0.49-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:233088b4b99ebcbc5258c755a097aa52fbf90727a03a5a80781c4b9c54347a2e", size = 2156356, upload-time = "2026-04-03T16:53:09.914Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/a7/5f476227576cb8644650eff68cc35fa837d3802b997465c96b8340ced1e2/sqlalchemy-2.0.49-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:57ca426a48eb2c682dae8204cd89ea8ab7031e2675120a47924fabc7caacbc2a", size = 3276486, upload-time = "2026-04-03T17:07:46.9Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2e/84/efc7c0bf3a1c5eef81d397f6fddac855becdbb11cb38ff957888603014a7/sqlalchemy-2.0.49-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:685e93e9c8f399b0c96a624799820176312f5ceef958c0f88215af4013d29066", size = 3281479, upload-time = "2026-04-03T17:12:32.226Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/91/68/bb406fa4257099c67bd75f3f2261b129c63204b9155de0d450b37f004698/sqlalchemy-2.0.49-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9e0400fa22f79acc334d9a6b185dc00a44a8e6578aa7e12d0ddcd8434152b187", size = 3226269, upload-time = "2026-04-03T17:07:48.678Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/67/84/acb56c00cca9f251f437cb49e718e14f7687505749ea9255d7bd8158a6df/sqlalchemy-2.0.49-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a05977bffe9bffd2229f477fa75eabe3192b1b05f408961d1bebff8d1cd4d401", size = 3248260, upload-time = "2026-04-03T17:12:34.381Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/56/19/6a20ea25606d1efd7bd1862149bb2a22d1451c3f851d23d887969201633f/sqlalchemy-2.0.49-cp314-cp314-win32.whl", hash = "sha256:0f2fa354ba106eafff2c14b0cc51f22801d1e8b2e4149342023bd6f0955de5f5", size = 2118463, upload-time = "2026-04-03T17:05:47.093Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cf/4f/8297e4ed88e80baa1f5aa3c484a0ee29ef3c69c7582f206c916973b75057/sqlalchemy-2.0.49-cp314-cp314-win_amd64.whl", hash = "sha256:77641d299179c37b89cf2343ca9972c88bb6eef0d5fc504a2f86afd15cd5adf5", size = 2144204, upload-time = "2026-04-03T17:05:48.694Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/33/95e7216df810c706e0cd3655a778604bbd319ed4f43333127d465a46862d/sqlalchemy-2.0.49-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c1dc3368794d522f43914e03312202523cc89692f5389c32bea0233924f8d977", size = 3565474, upload-time = "2026-04-03T16:58:35.128Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/a4/ed7b18d8ccf7f954a83af6bb73866f5bc6f5636f44c7731fbb741f72cc4f/sqlalchemy-2.0.49-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7c821c47ecfe05cc32140dcf8dc6fd5d21971c86dbd56eabfe5ba07a64910c01", size = 3530567, upload-time = "2026-04-03T17:06:04.587Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/73/a3/20faa869c7e21a827c4a2a42b41353a54b0f9f5e96df5087629c306df71e/sqlalchemy-2.0.49-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9c04bff9a5335eb95c6ecf1c117576a0aa560def274876fd156cfe5510fccc61", size = 3474282, upload-time = "2026-04-03T16:58:37.131Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/50/276b9a007aa0764304ad467eceb70b04822dc32092492ee5f322d559a4dc/sqlalchemy-2.0.49-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7f605a456948c35260e7b2a39f8952a26f077fd25653c37740ed186b90aaa68a", size = 3480406, upload-time = "2026-04-03T17:06:07.176Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/c3/c80fcdb41905a2df650c2a3e0337198b6848876e63d66fe9188ef9003d24/sqlalchemy-2.0.49-cp314-cp314t-win32.whl", hash = "sha256:6270d717b11c5476b0cbb21eedc8d4dbb7d1a956fd6c15a23e96f197a6193158", size = 2149151, upload-time = "2026-04-03T17:02:07.281Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/52/9f1a62feab6ed368aff068524ff414f26a6daebc7361861035ae00b05530/sqlalchemy-2.0.49-cp314-cp314t-win_amd64.whl", hash = "sha256:275424295f4256fd301744b8f335cff367825d270f155d522b30c7bf49903ee7", size = 2184178, upload-time = "2026-04-03T17:02:08.623Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/30/8519fdde58a7bdf155b714359791ad1dc018b47d60269d5d160d311fdc36/sqlalchemy-2.0.49-py3-none-any.whl", hash = "sha256:ec44cfa7ef1a728e88ad41674de50f6db8cfdb3e2af84af86e0041aaf02d43d0", size = 1942158, upload-time = "2026-04-03T16:53:44.135Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "starlette"
|
||||
version = "1.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/81/69/17425771797c36cded50b7fe44e850315d039f28b15901ab44839e70b593/starlette-1.0.0.tar.gz", hash = "sha256:6a4beaf1f81bb472fd19ea9b918b50dc3a77a6f2e190a12954b25e6ed5eea149", size = 2655289, upload-time = "2026-03-22T18:29:46.779Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/c9/584bc9651441b4ba60cc4d557d8a547b5aff901af35bda3a4ee30c819b82/starlette-1.0.0-py3-none-any.whl", hash = "sha256:d3ec55e0bb321692d275455ddfd3df75fff145d009685eb40dc91fc66b03d38b", size = 72651, upload-time = "2026-03-22T18:29:45.111Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.15.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-inspection"
|
||||
version = "0.4.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uvicorn"
|
||||
version = "0.44.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "click" },
|
||||
{ name = "h11" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5e/da/6eee1ff8b6cbeed47eeb5229749168e81eb4b7b999a1a15a7176e51410c9/uvicorn-0.44.0.tar.gz", hash = "sha256:6c942071b68f07e178264b9152f1f16dfac5da85880c4ce06366a96d70d4f31e", size = 86947, upload-time = "2026-04-06T09:23:22.826Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/23/a5bbd9600dd607411fa644c06ff4951bec3a4d82c4b852374024359c19c0/uvicorn-0.44.0-py3-none-any.whl", hash = "sha256:ce937c99a2cc70279556967274414c087888e8cec9f9c94644dfca11bd3ced89", size = 69425, upload-time = "2026-04-06T09:23:21.524Z" },
|
||||
]
|
||||
Reference in new issue
Block a user