fixed bugs

This commit is contained in:
belviskhoremk
2026-02-22 23:24:44 +00:00
parent 5bd496d355
commit 88ca23adde
6 changed files with 1605 additions and 241 deletions

View File

@@ -66,9 +66,9 @@ PLAN_LIMITS = {
"max_chatbots": 999999,
"max_published": 1,
"models": [
"accounts/fireworks/models/llama-v3p1-70b-instruct",
"accounts/fireworks/models/mixtral-8x7b-instruct",
"accounts/fireworks/models/qwen2p5-72b-instruct",
"accounts/fireworks/models/kimi-k2-instruct-0905",
"accounts/fireworks/models/deepseek-v3p2",
"accounts/fireworks/models/glm-4p7",
],
"conversations_limit": 5000,
"code_export": False,
@@ -109,9 +109,9 @@ PLAN_LIMITS = {
}
MODEL_PROVIDERS = {
"accounts/fireworks/models/llama-v3p1-70b-instruct": "fireworks",
"accounts/fireworks/models/mixtral-8x7b-instruct": "fireworks",
"accounts/fireworks/models/qwen2p5-72b-instruct": "fireworks",
"accounts/fireworks/models/kimi-k2-instruct-0905": "fireworks",
"accounts/fireworks/models/deepseek-v3p2": "fireworks",
"accounts/fireworks/models/glm-4p7": "fireworks",
"gpt-4o": "openai",
"gpt-4-turbo": "openai",
"gpt-3.5-turbo": "openai",
@@ -121,7 +121,7 @@ MODEL_PROVIDERS = {
}
DEFAULT_MODELS = {
"starter": "accounts/fireworks/models/llama-v3p1-70b-instruct",
"starter": "accounts/fireworks/models/kimi-k2-instruct-0905",
"pro": "gpt-4o",
"enterprise": "gpt-4o",
}

View File

@@ -1,3 +1,4 @@
from contextlib import asynccontextmanager
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
@@ -13,13 +14,27 @@ logging.basicConfig(
)
logger = logging.getLogger(__name__)
# ── App ────────────────────────────────────────────────────────────────────────
# BUG-13 FIX: Replace deprecated @app.on_event("startup") with lifespan
@asynccontextmanager
async def lifespan(app: FastAPI):
# Startup
logger.info("Contexta API starting up...")
logger.info(f"Environment: {settings.app_env}")
logger.info(f"Allowed origins: {settings.allowed_origins_list}")
yield
# Shutdown
logger.info("Contexta API shutting down...")
# ── App ──────────────────────────────────────────────────────────────────────────
app = FastAPI(
title="Contexta API",
description="AI Chatbot Platform - Create, deploy and share custom AI chatbots powered by your data",
version="1.0.0",
docs_url="/docs",
redoc_url="/redoc",
lifespan=lifespan,
)
# ── Middleware ─────────────────────────────────────────────────────────────────
@@ -55,19 +70,12 @@ async def health():
return {"status": "healthy", "environment": settings.app_env}
# ── Sentry ────────────────────────────────────────────────────────────────────
# ── Sentry ────────────────────────────────────────────────────────────────────
if settings.sentry_dsn:
import sentry_sdk
sentry_sdk.init(dsn=settings.sentry_dsn, traces_sample_rate=0.1)
logger.info("Sentry initialized")
# ── Startup ───────────────────────────────────────────────────────────────────
@app.on_event("startup")
async def startup_event():
logger.info("Contexta API starting up...")
logger.info(f"Environment: {settings.app_env}")
logger.info(f"Allowed origins: {settings.allowed_origins_list}")
if __name__ == "__main__":
import uvicorn

View File

@@ -188,7 +188,7 @@ def _get_or_create_conversation(
def _get_conversation_history(conversation_id: str, supabase) -> List[dict]:
messages = supabase.table("messages").select("role, content") \
.eq("conversation_id", conversation_id) \
.order("created_at", asc=True) \
.order("created_at", desc=True) \
.limit(20) \
.execute()
return messages.data or []

View File

@@ -1,5 +1,6 @@
import zipfile
import io
import json
from typing import Dict, Any
@@ -56,6 +57,13 @@ langdetect==1.0.9
"""
# BUG-14 FIX: Helper to safely escape strings for use in generated Python code
def _escape_for_python(value: str) -> str:
"""Escape a string so it can be safely embedded in generated Python source code.
Uses json.dumps which properly escapes quotes, backslashes, and special chars."""
return json.dumps(value)
def _env_example(chatbot: Dict, qdrant_url: str, qdrant_key: str):
name = chatbot.get("name", "My Chatbot").upper().replace(" ", "_")
return f"""# {name} - Environment Configuration
@@ -87,6 +95,11 @@ ALLOWED_ORIGINS=http://localhost:3000,https://yourdomain.com
def _main_py(chatbot: Dict):
# BUG-14 FIX: Use json.dumps to safely escape system_prompt
# Previously used f-string with triple quotes, which broke if prompt contained """ or {
safe_name = _escape_for_python(chatbot.get("name", "Chatbot"))
safe_prompt = _escape_for_python(chatbot.get("system_prompt") or "You are a helpful assistant.")
return f'''"""
Auto-generated FastAPI backend for: {chatbot.get("name", "Chatbot")}
Generated by Contexta Platform
@@ -101,8 +114,11 @@ from rag_engine import RAGEngine
load_dotenv()
# BUG-14 FIX: System prompt stored safely via json-escaped string
SYSTEM_PROMPT = {safe_prompt}
app = FastAPI(
title="{chatbot.get("name", "Chatbot")} API",
title={safe_name} + " API",
version="1.0.0"
)
@@ -123,7 +139,7 @@ rag = RAGEngine(
llm_api_key=os.getenv("LLM_API_KEY"),
embedding_api_key=os.getenv("EMBEDDING_API_KEY"),
embedding_model=os.getenv("EMBEDDING_MODEL", "text-embedding-3-small"),
system_prompt="""{chatbot.get("system_prompt") or "You are a helpful assistant."}""",
system_prompt=SYSTEM_PROMPT,
)
@@ -164,7 +180,7 @@ async def chat(request: ChatRequest):
@app.get("/health")
def health():
return {{"status": "healthy", "chatbot": "{chatbot.get("name", "Chatbot")}"}}
return {{"status": "healthy", "chatbot": {safe_name}}}
if __name__ == "__main__":
@@ -187,93 +203,127 @@ logger = logging.getLogger(__name__)
class RAGEngine:
def __init__(self, qdrant_url, qdrant_api_key, collection_name,
llm_provider, llm_model, llm_api_key,
embedding_api_key, embedding_model, system_prompt=""):
embedding_api_key, embedding_model, system_prompt):
self.collection_name = collection_name
self.llm_provider = llm_provider
self.llm_model = llm_model
self.llm_api_key = llm_api_key
self.embedding_model = embedding_model
self.system_prompt = system_prompt
self.embedding_model = embedding_model
# Qdrant
qdrant_kwargs = {"url": qdrant_url}
if qdrant_api_key:
qdrant_kwargs["api_key"] = qdrant_api_key
self.qdrant = QdrantClient(**qdrant_kwargs)
# Initialize Qdrant
self.qdrant = QdrantClient(url=qdrant_url, api_key=qdrant_api_key)
# OpenAI for embeddings
# Initialize embedding client
self.embed_client = AsyncOpenAI(api_key=embedding_api_key)
async def embed(self, text: str) -> List[float]:
resp = await self.embed_client.embeddings.create(
model=self.embedding_model, input=text
async def _get_embedding(self, text: str) -> List[float]:
response = await self.embed_client.embeddings.create(
model=self.embedding_model,
input=text,
)
return resp.data[0].embedding
return response.data[0].embedding
async def retrieve(self, query_vector: List[float], limit: int = 5) -> List[Dict]:
async def _search_vectors(self, query_embedding: List[float], top_k: int = 5) -> List[Dict]:
results = self.qdrant.search(
collection_name=self.collection_name,
query_vector=query_vector,
limit=limit,
score_threshold=0.3,
query_vector=query_embedding,
limit=top_k,
)
return [{"text": r.payload.get("text", ""), "document_name": r.payload.get("file_name", ""), "score": r.score}
for r in results]
return [
{
"document_name": r.payload.get("document_name", "Unknown"),
"text": r.payload.get("text", ""),
"score": r.score,
}
for r in results
]
async def generate(self, messages: List[Dict]) -> str:
async def query(self, query: str, history: List[Dict] = None, language: str = "en") -> Dict:
# Get embedding for query
query_embedding = await self._get_embedding(query)
# Search for relevant chunks
sources = await self._search_vectors(query_embedding)
# Build context from sources
context = "\\n\\n".join([
f"[Source: {s['document_name']}]\\n{s['text']}"
for s in sources
])
# Build messages
messages = [
{"role": "system", "content": f"{self.system_prompt}\\n\\nUse the following context to answer:\\n{context}"},
]
if history:
messages.extend(history[-10:])
messages.append({"role": "user", "content": query})
# Generate response based on provider
response_text = await self._generate(messages)
return {
"response": response_text,
"sources": sources,
}
async def _generate(self, messages: List[Dict]) -> str:
if self.llm_provider == "openai":
from openai import AsyncOpenAI
client = AsyncOpenAI(api_key=self.llm_api_key)
resp = await client.chat.completions.create(
model=self.llm_model, messages=messages, max_tokens=1000
response = await client.chat.completions.create(
model=self.llm_model,
messages=messages,
max_tokens=1000,
)
return resp.choices[0].message.content
return response.choices[0].message.content
elif self.llm_provider == "anthropic":
import anthropic
client = anthropic.AsyncAnthropic(api_key=self.llm_api_key)
system = next((m["content"] for m in messages if m["role"] == "system"), "")
conv = [m for m in messages if m["role"] != "system"]
resp = await client.messages.create(
model=self.llm_model, max_tokens=1000, system=system, messages=conv
system = messages[0]["content"]
msgs = [m for m in messages[1:] if m["role"] in ("user", "assistant")]
response = await client.messages.create(
model=self.llm_model,
max_tokens=1000,
system=system,
messages=msgs,
)
return resp.content[0].text
elif self.llm_provider == "fireworks":
return response.content[0].text
elif self.llm_provider == "google":
import google.generativeai as genai
genai.configure(api_key=self.llm_api_key)
model = genai.GenerativeModel(self.llm_model)
prompt = "\\n".join([f"{m['role']}: {m['content']}" for m in messages])
response = await model.generate_content_async(prompt)
return response.text
else:
import httpx
async with httpx.AsyncClient(timeout=60) as c:
r = await c.post(
headers = {"Authorization": f"Bearer {self.llm_api_key}", "Content-Type": "application/json"}
async with httpx.AsyncClient(timeout=60) as client:
resp = await client.post(
"https://api.fireworks.ai/inference/v1/chat/completions",
headers={"Authorization": f"Bearer {self.llm_api_key}"},
headers=headers,
json={"model": self.llm_model, "messages": messages, "max_tokens": 1000},
)
r.raise_for_status()
return r.json()["choices"][0]["message"]["content"]
return "Error: unknown provider"
async def query(self, query: str, history: List[Dict] = None, language: str = "en") -> Dict:
if history is None:
history = []
query_vec = await self.embed(query)
docs = await self.retrieve(query_vec)
context = "\\n\\n---\\n\\n".join(d["text"] for d in docs) or "No context found."
system = f"{self.system_prompt}\\n\\nContext:\\n{context}"
messages = [{"role": "system", "content": system}]
for h in history[-10:]:
messages.append(h)
messages.append({"role": "user", "content": query})
response = await self.generate(messages)
return {"response": response, "sources": docs}
resp.raise_for_status()
return resp.json()["choices"][0]["message"]["content"]
'''
def _dockerfile():
return """FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
"""
@@ -281,100 +331,76 @@ CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
def _docker_compose(chatbot: Dict):
name = chatbot.get("name", "chatbot").lower().replace(" ", "-")
return f"""version: '3.8'
return f"""version: "3.8"
services:
api:
build: .
ports:
- "8000:8000"
env_file: .env
restart: unless-stopped
container_name: {name}-api
ports:
- "${{PORT:-8000}}:8000"
env_file:
- .env
restart: unless-stopped
"""
def _chat_widget_tsx(chatbot: Dict):
safe_name = json.dumps(chatbot.get("name", "Chatbot"))
safe_welcome = json.dumps(chatbot.get("welcome_message", "Hello! How can I help you?"))
color = chatbot.get("primary_color", "#6366f1")
welcome = chatbot.get("welcome_message", "Hello! How can I help you today?")
name = chatbot.get("name", "Assistant")
return f'''import React, {{ useState, useRef, useEffect }} from "react";
return f'''import React, {{ useState }} from "react";
import {{ useChat }} from "./useChat";
const PRIMARY_COLOR = "{color}";
const BOT_NAME = "{name}";
const WELCOME_MESSAGE = "{welcome}";
export const ChatWidget: React.FC = () => {{
const [isOpen, setIsOpen] = useState(false);
const {{ messages, isLoading, sendMessage }} = useChat(WELCOME_MESSAGE);
const bottomRef = useRef<HTMLDivElement>(null);
const {{ messages, isLoading, sendMessage }} = useChat({safe_welcome});
const [input, setInput] = useState("");
useEffect(() => {{
bottomRef.current?.scrollIntoView({{ behavior: "smooth" }});
}}, [messages]);
const handleSend = () => {{
if (!input.trim() || isLoading) return;
sendMessage(input.trim());
setInput("");
}};
return (
<>
{{isOpen && (
<div style={{{{
position: "fixed", bottom: 90, right: 20, width: 360, height: 520,
borderRadius: 16, boxShadow: "0 20px 60px rgba(0,0,0,0.2)",
display: "flex", flexDirection: "column", background: "#fff",
fontFamily: "system-ui, -apple-system, sans-serif", zIndex: 9999
}}}}>
<div style={{{{ background: PRIMARY_COLOR, padding: "16px 20px",
borderRadius: "16px 16px 0 0", display: "flex", justifyContent: "space-between", alignItems: "center" }}}}>
<span style={{{{ color: "#fff", fontWeight: 600, fontSize: 16 }}}}>{{BOT_NAME}}</span>
<button onClick={{() => setIsOpen(false)}}
style={{{{ background: "none", border: "none", color: "#fff", cursor: "pointer", fontSize: 20 }}}}>×</button>
<div style={{{{ position: "fixed", bottom: 80, right: 20, width: 380, height: 500,
borderRadius: 16, overflow: "hidden", boxShadow: "0 8px 30px rgba(0,0,0,0.15)",
display: "flex", flexDirection: "column", background: "#fff", zIndex: 9999 }}}}>
<div style={{{{ background: "{color}", color: "#fff", padding: "12px 16px",
fontWeight: 600, fontSize: 14 }}}}>
{safe_name}
</div>
<div style={{{{ flex: 1, overflowY: "auto", padding: 16, display: "flex", flexDirection: "column", gap: 12 }}}}>
{{messages.map((msg, i) => (
<div key={{i}} style={{{{ display: "flex", justifyContent: msg.role === "user" ? "flex-end" : "flex-start" }}}}>
<div style={{{{
maxWidth: "80%", padding: "10px 14px", borderRadius: 12, fontSize: 14, lineHeight: 1.5,
background: msg.role === "user" ? PRIMARY_COLOR : "#f3f4f6",
color: msg.role === "user" ? "#fff" : "#111"
}}}}>{{msg.content}}</div>
<div style={{{{ flex: 1, overflowY: "auto", padding: 12 }}}}>
{{messages.map((m, i) => (
<div key={{i}} style={{{{ display: "flex", justifyContent: m.role === "user" ? "flex-end" : "flex-start", marginBottom: 8 }}}}>
<div style={{{{ background: m.role === "user" ? "{color}" : "#f3f4f6",
color: m.role === "user" ? "#fff" : "#1f2937",
borderRadius: 12, padding: "8px 12px", maxWidth: "80%", fontSize: 13 }}}}>
{{m.content}}
</div>
</div>
))}}
{{isLoading && <div style={{{{ color: "#6b7280", fontSize: 13 }}}}>Thinking...</div>}}
<div ref={{bottomRef}} />
{{isLoading && <div style={{{{ color: "#9ca3af", fontSize: 12 }}}}>Typing...</div>}}
</div>
<div style={{{{ padding: "12px 16px", borderTop: "1px solid #e5e7eb", display: "flex", gap: 8 }}}}>
<input
style={{{{ flex: 1, border: "1px solid #e5e7eb", borderRadius: 8, padding: "8px 12px", outline: "none", fontSize: 14 }}}}
placeholder="Type a message..."
onKeyDown={{(e) => {{
if (e.key === "Enter" && !e.shiftKey) {{
e.preventDefault();
const val = (e.target as HTMLInputElement).value.trim();
if (val) {{ sendMessage(val); (e.target as HTMLInputElement).value = ""; }}
}}
}}}}
/>
<button
style={{{{ background: PRIMARY_COLOR, color: "#fff", border: "none", borderRadius: 8,
padding: "8px 14px", cursor: "pointer", fontSize: 14 }}}}
onClick={{(e) => {{
const input = (e.currentTarget.previousSibling as HTMLInputElement);
const val = input.value.trim();
if (val) {{ sendMessage(val); input.value = ""; }}
}}}}
>Send</button>
<div style={{{{ borderTop: "1px solid #e5e7eb", padding: 8, display: "flex", gap: 8 }}}}>
<input value={{input}} onChange={{e => setInput(e.target.value)}}
onKeyDown={{e => e.key === "Enter" && handleSend()}}
placeholder="Type a message..." style={{{{ flex: 1, border: "1px solid #d1d5db",
borderRadius: 8, padding: "6px 10px", fontSize: 13, outline: "none" }}}} />
<button onClick={{handleSend}} disabled={{isLoading}}
style={{{{ background: "{color}", color: "#fff", border: "none", borderRadius: 8,
padding: "6px 14px", cursor: "pointer", fontSize: 13 }}}}>Send</button>
</div>
</div>
)}}
<button
onClick={{() => setIsOpen(!isOpen)}}
style={{{{
position: "fixed", bottom: 20, right: 20, width: 56, height: 56,
borderRadius: "50%", background: PRIMARY_COLOR, border: "none",
cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center",
boxShadow: "0 4px 20px rgba(0,0,0,0.2)", zIndex: 9999, fontSize: 24
}}}}
>
{{isOpen ? "×" : "💬"}}
<button onClick={{() => setIsOpen(!isOpen)}} style={{{{ position: "fixed", bottom: 20,
right: 20, width: 56, height: 56, borderRadius: 28, background: "{color}",
color: "#fff", border: "none", cursor: "pointer", fontSize: 24, zIndex: 9999,
boxShadow: "0 4px 12px rgba(0,0,0,0.15)", display: "flex", alignItems: "center",
justifyContent: "center" }}}}>
{{isOpen ? "\\u00d7" : "\\ud83d\\udcac"}}
</button>
</>
);
@@ -458,8 +484,10 @@ export interface ChatResponse {
def _package_json(chatbot: Dict):
name = chatbot.get("name", "chatbot").lower().replace(" ", "-")
# Sanitize name for package.json
safe_name = "".join(c for c in name if c.isalnum() or c == "-")
return f'''{{
"name": "{name}-widget",
"name": "{safe_name}-widget",
"version": "1.0.0",
"scripts": {{
"dev": "vite",
@@ -554,159 +582,106 @@ def _frontend_readme(chatbot: Dict):
## Quick Start
```bash
cp .env.example .env
# Set VITE_API_URL to your backend URL
npm install
npm run dev
```
Create a `.env` file:
```
VITE_API_URL=http://localhost:8000
```
## Build for Production
```bash
npm run build
```
## Embed in Any Website
The built files will be in `dist/`.
## Embed in Your Website
```html
<script src="path/to/dist/chatbot-widget.umd.cjs"></script>
<script src="path/to/dist/chatbot-widget.js"></script>
```
## Environment Variables
- `VITE_API_URL` - Backend API URL (default: http://localhost:8000)
"""
def _quick_start(chatbot: Dict):
return f"""# Quick Start - {chatbot.get("name", "Chatbot")}
Get your chatbot running in 5 minutes!
return f"""# {chatbot.get("name", "Chatbot")} - Quick Start Guide
## Prerequisites
- Python 3.11+
- Node.js 18+
- API key from OpenAI, Anthropic, or Google
- Node.js 18+ (for the widget)
## 1. Configure Environment (2 min)
## Step 1: Backend Setup (2 minutes)
Run the setup wizard:
```bash
python setup.py
```
Or manually:
```bash
cd backend
cp .env.example .env
# Edit .env with your keys
```
## 2. Start Backend (1 min)
```bash
cd backend
# Edit .env with your API keys
pip install -r requirements.txt
uvicorn main:app --reload
```
Backend runs at: http://localhost:8000
Visit http://localhost:8000/health to verify.
## 3. Start Frontend Widget (1 min)
## Step 2: Frontend Setup (1 minute)
```bash
cd frontend
npm install
echo "VITE_API_URL=http://localhost:8000" > .env
npm run dev
```
Widget available at: http://localhost:3000
## Step 3: Test It!
## 4. Embed in Your Website
Open http://localhost:5173 and start chatting!
After building (`npm run build`):
```html
<script src="dist/chatbot-widget.umd.cjs"></script>
```
## Deployment
## Deploy
### Railway (Recommended)
```bash
railway init
railway up
```
### Docker
```bash
cd backend && docker-compose up -d
```
See `backend/README.md` and `frontend/README.md` for deployment guides.
"""
def _setup_wizard(chatbot: Dict):
return f'''#!/usr/bin/env python3
"""
Interactive setup wizard for {chatbot.get("name", "Chatbot")}
"""
"""Interactive setup wizard for {chatbot.get("name", "Chatbot")}"""
import os
from pathlib import Path
import sys
def main():
print("""
╔══════════════════════════════════════╗
{chatbot.get("name", "Chatbot")} Setup Wizard ║
╚══════════════════════════════════════╝
""")
print("=" * 50)
print(f" Setup Wizard: {chatbot.get("name", "Chatbot")}")
print("=" * 50)
print()
env_vars = {{}}
# LLM Provider
print("Choose your LLM provider:")
print("1. OpenAI (GPT-4o)")
print("2. Anthropic (Claude)")
print("3. Google (Gemini)")
print("4. Fireworks AI (Free, open-source models)")
choice = input("\\nEnter choice (1-4): ").strip()
print(" 1. OpenAI (GPT-4o, GPT-4 Turbo)")
print(" 2. Anthropic (Claude 3.5 Sonnet)")
print(" 3. Google (Gemini 1.5 Pro)")
print(" 4. Fireworks AI (Llama, Mixtral)")
choice = input("Enter choice [1]: ").strip() or "1"
providers = {{"1": "openai", "2": "anthropic", "3": "google", "4": "fireworks"}}
models = {{"1": "gpt-4o", "2": "claude-3-5-sonnet-20241022", "3": "gemini-1.5-pro",
"4": "accounts/fireworks/models/llama-v3p1-70b-instruct"}}
env_vars["LLM_PROVIDER"] = providers.get(choice, "openai")
provider = providers.get(choice, "openai")
model = models.get(choice, "gpt-4o")
env_vars["LLM_API_KEY"] = input(f"Enter {{env_vars['LLM_PROVIDER']}} API key: ").strip()
env_vars["EMBEDDING_API_KEY"] = input("Enter OpenAI API key (for embeddings): ").strip() or env_vars["LLM_API_KEY"]
api_key = input(f"Enter your {{provider}} API key: ").strip()
env_content = f"""LLM_PROVIDER={{provider}}
LLM_MODEL={{model}}
LLM_API_KEY={{api_key}}
EMBEDDING_API_KEY={{api_key if provider == "openai" else input("Enter OpenAI key for embeddings: ").strip()}}
EMBEDDING_MODEL=text-embedding-3-small
QDRANT_URL={os.getenv("QDRANT_URL", "your-qdrant-url")}
QDRANT_API_KEY={os.getenv("QDRANT_API_KEY", "your-qdrant-key")}
QDRANT_COLLECTION={chatbot.get("qdrant_collection_name", "chatbot_collection")}
"""
env_file = Path("backend/.env")
env_file.write_text(env_content)
print("\\n✅ .env file created!")
frontend_url = input("\\nBackend URL for frontend (default: http://localhost:8000): ").strip()
if not frontend_url:
frontend_url = "http://localhost:8000"
Path("frontend/.env").write_text(f"VITE_API_URL={{frontend_url}}\\n")
print("✅ Frontend .env created!")
print("""
\\n╔══════════════════════════════════════╗
║ Setup Complete! 🎉 ║
╠══════════════════════════════════════╣
║ Backend: cd backend && uvicorn ║
║ main:app --reload ║
║ Frontend: cd frontend && npm dev ║
╚══════════════════════════════════════╝
""")
# Write .env
env_path = os.path.join("backend", ".env")
with open(env_path, "w") as f:
for k, v in env_vars.items():
f.write(f"{{k}}={{v}}\\n")
print(f"\\nConfiguration saved to {{env_path}}")
print("\\nTo start: cd backend && uvicorn main:app --reload")
if __name__ == "__main__":
main()

View File

@@ -5,5 +5,10 @@ description = "Add your description here"
requires-python = ">=3.12"
dependencies = [
"fastapi>=0.131.0",
"openai>=2.21.0",
"qdrant-client>=1.17.0",
"sentry-sdk>=2.53.0",
"spglib>=2.7.0",
"supabase>=2.28.0",
"uvicorn>=0.41.0",
]

1376
uv.lock generated

File diff suppressed because it is too large Load Diff