Spaces:
Running
Running
Commit ·
0ea40d5
1
Parent(s): 3120de3
initial commit
Browse files- .gitignore +18 -0
- app.py +60 -0
- core_logic.py +68 -0
- requirements.txt +7 -0
- storage.py +35 -0
- styles.css +11 -0
- tools.py +40 -0
.gitignore
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
venv/
|
| 2 |
+
node_modules/
|
| 3 |
+
*.log
|
| 4 |
+
|
| 5 |
+
# Python
|
| 6 |
+
__pycache__/
|
| 7 |
+
*.pyc
|
| 8 |
+
|
| 9 |
+
# Env
|
| 10 |
+
.env
|
| 11 |
+
|
| 12 |
+
# VS Code / Visual Studio
|
| 13 |
+
.vs/
|
| 14 |
+
.vscode/
|
| 15 |
+
|
| 16 |
+
# OS
|
| 17 |
+
.DS_Store
|
| 18 |
+
Thumbs.db
|
app.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
# ./app.py
|
| 3 |
+
|
| 4 |
+
"""
|
| 5 |
+
The Interface Skeleton - The code sets up the navigation panel and the multimodal chat interface
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import gradio as gr
|
| 9 |
+
from core_logic import chat_function
|
| 10 |
+
from storage import save_chat, load_history
|
| 11 |
+
|
| 12 |
+
with gr.Blocks(css="styles.css", theme=gr.themes.Soft()) as demo:
|
| 13 |
+
# State variables for persistence
|
| 14 |
+
current_chat_id = gr.State("")
|
| 15 |
+
session_history = gr.State([])
|
| 16 |
+
|
| 17 |
+
with gr.Row():
|
| 18 |
+
# --- SIDEBAR (Navigation Panel) ---
|
| 19 |
+
with gr.Column(scale=1, variant="secondary"):
|
| 20 |
+
gr.Markdown("## 🛠️ Agent Architect")
|
| 21 |
+
new_btn = gr.Button("➕ New Chat", variant="primary")
|
| 22 |
+
|
| 23 |
+
gr.Markdown("### 🕒 Recent Conversations")
|
| 24 |
+
# This would be populated from storage.py
|
| 25 |
+
history_list = gr.Dataset(
|
| 26 |
+
components=[gr.Textbox(visible=False)],
|
| 27 |
+
label="",
|
| 28 |
+
samples=load_history()
|
| 29 |
+
)
|
| 30 |
+
|
| 31 |
+
gr.Markdown("---")
|
| 32 |
+
download_format = gr.Radio([".txt", ".pdf", ".docx"], label="Export Format")
|
| 33 |
+
export_btn = gr.Button("📥 Download Current Chat")
|
| 34 |
+
|
| 35 |
+
# --- MAIN CHAT INTERFACE ---
|
| 36 |
+
with gr.Column(scale=4):
|
| 37 |
+
chatbot = gr.Chatbot(
|
| 38 |
+
show_label=False,
|
| 39 |
+
elem_id="chat-window",
|
| 40 |
+
height=750,
|
| 41 |
+
type="messages",
|
| 42 |
+
multimodal=True
|
| 43 |
+
)
|
| 44 |
+
|
| 45 |
+
with gr.Row():
|
| 46 |
+
user_input = gr.MultimodalTextbox(
|
| 47 |
+
show_label=False,
|
| 48 |
+
placeholder="Describe your agentic architecture or upload code...",
|
| 49 |
+
file_types=[".py", ".pdf", ".js", ".md", ".txt", ".docx"],
|
| 50 |
+
file_count="multiple"
|
| 51 |
+
)
|
| 52 |
+
|
| 53 |
+
# --- EVENT HANDLERS ---
|
| 54 |
+
def handle_user_msg(message, history):
|
| 55 |
+
# This will link to our core_logic.py
|
| 56 |
+
return chat_function(message, history)
|
| 57 |
+
|
| 58 |
+
user_input.submit(handle_user_msg, [user_input, chatbot], [user_input, chatbot])
|
| 59 |
+
|
| 60 |
+
demo.launch()
|
core_logic.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
# ./core_logic.py
|
| 3 |
+
|
| 4 |
+
"""
|
| 5 |
+
The Inference Engine - Where the "Technical Genius" persona lives. It uses the huggingface_hub InferenceClient to run the model without local CPU strain
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import os
|
| 9 |
+
from huggingface_hub import InferenceClient
|
| 10 |
+
from tools import web_search, parse_file
|
| 11 |
+
|
| 12 |
+
# Recommended: Qwen2.5-Coder-32B or Llama-3.1-70B-Instruct
|
| 13 |
+
#client = InferenceClient("Qwen/Qwen2.5-Coder-32B-Instruct", token=os.getenv("HF_TOKEN"))
|
| 14 |
+
client = InferenceClient("deepseek-ai/DeepSeek-V4-Pro", token=os.getenv("HF_TOKEN"))
|
| 15 |
+
|
| 16 |
+
SYSTEM_PROMPT = """
|
| 17 |
+
You are the 'Silicon Architect'�a master-stroke creative genius in AI Engineering and Technical Architecture.
|
| 18 |
+
Your goal is to provide production-grade, highly optimized solutions for web and mobile AI applications.
|
| 19 |
+
|
| 20 |
+
CORE DIRECTIVES:
|
| 21 |
+
1. ARCHITECTURAL RIGOR: Always consider scalability, async patterns, and state management.
|
| 22 |
+
2. AGENTIC EXPERTISE: You understand recurrent-depth simulations, tool-calling, and autonomous loops.
|
| 23 |
+
3. CODE QUALITY: Write clean, PEP 8 compliant, and secure Python/JS code.
|
| 24 |
+
4. INNOVATION: Suggest the latest libraries and frameworks (FastAPI, LangGraph, Pydantic AI).
|
| 25 |
+
5. RESEARCH: If the user asks about new tech, use your Web Search capability to provide factual, up-to-date documentation.
|
| 26 |
+
|
| 27 |
+
When a user provides files, analyze the code structure and logic before proposing changes.
|
| 28 |
+
"""
|
| 29 |
+
|
| 30 |
+
def chat_function(message, history):
|
| 31 |
+
# 1. Handle Multimodal Input (Files)
|
| 32 |
+
files_content = ""
|
| 33 |
+
user_text = message["text"]
|
| 34 |
+
|
| 35 |
+
if message["files"]:
|
| 36 |
+
for file_data in message["files"]:
|
| 37 |
+
# Gradio file objects have a .path attribute
|
| 38 |
+
files_content += parse_file(file_data)
|
| 39 |
+
|
| 40 |
+
# 2. Logic for "Web Search" trigger
|
| 41 |
+
# A simple keyword trigger or the LLM can decide. For now, we look for 'search' or 'docs'.
|
| 42 |
+
if "search" in user_text.lower() or "documentation" in user_text.lower():
|
| 43 |
+
search_query = user_text.replace("search", "").strip()
|
| 44 |
+
web_results = web_search(search_query)
|
| 45 |
+
full_user_input = f"RESEARCH RESULTS:\n{web_results}\n\nUSER FILES:\n{files_content}\n\nUSER QUESTION: {user_text}"
|
| 46 |
+
else:
|
| 47 |
+
full_user_input = f"USER FILES:\n{files_content}\n\nUSER QUESTION: {user_text}"
|
| 48 |
+
|
| 49 |
+
# 3. Construct Messages for Inference
|
| 50 |
+
messages = [{"role": "system", "content": SYSTEM_PROMPT}]
|
| 51 |
+
for turn in history:
|
| 52 |
+
messages.append({"role": "user", "content": turn[0]})
|
| 53 |
+
messages.append({"role": "assistant", "content": turn[1]})
|
| 54 |
+
|
| 55 |
+
messages.append({"role": "user", "content": full_user_input})
|
| 56 |
+
|
| 57 |
+
# 4. Stream Response
|
| 58 |
+
response = ""
|
| 59 |
+
for message_chunk in client.chat_completion(
|
| 60 |
+
messages,
|
| 61 |
+
max_tokens=2048,
|
| 62 |
+
stream=True,
|
| 63 |
+
temperature=0.2, # Low temperature for high technical precision
|
| 64 |
+
):
|
| 65 |
+
token = message_chunk.choices[0].delta.content
|
| 66 |
+
if token:
|
| 67 |
+
response += token
|
| 68 |
+
yield response
|
requirements.txt
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
gradio
|
| 2 |
+
huggingface_hub
|
| 3 |
+
python-dotenv
|
| 4 |
+
duckduckgo-search
|
| 5 |
+
pypdf
|
| 6 |
+
python-docx
|
| 7 |
+
pandas
|
storage.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
# ./storage.py
|
| 3 |
+
|
| 4 |
+
"""
|
| 5 |
+
Persistence Layer - Handles the "Save/Load" functionality using Hugging Face Dataset as a database
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import json
|
| 9 |
+
import os
|
| 10 |
+
from huggingface_hub import HfApi, hf_hub_download
|
| 11 |
+
|
| 12 |
+
REPO_ID = "your-username/your-private-dataset" # Update this
|
| 13 |
+
api = HfApi(token=os.getenv("HF_TOKEN"))
|
| 14 |
+
|
| 15 |
+
def save_chat(chat_id, history):
|
| 16 |
+
"""Save chat history as a JSON file to HF Dataset."""
|
| 17 |
+
file_path = f"{chat_id}.json"
|
| 18 |
+
with open(file_path, "w") as f:
|
| 19 |
+
json.dump(history, f)
|
| 20 |
+
|
| 21 |
+
api.upload_file(
|
| 22 |
+
path_or_fileobj=file_path,
|
| 23 |
+
path_in_repo=f"chats/{file_path}",
|
| 24 |
+
repo_id=REPO_ID,
|
| 25 |
+
repo_type="dataset"
|
| 26 |
+
)
|
| 27 |
+
|
| 28 |
+
def load_history():
|
| 29 |
+
"""Retrieve all past chat IDs for the sidebar."""
|
| 30 |
+
try:
|
| 31 |
+
files = api.list_repo_files(repo_id=REPO_ID, repo_type="dataset")
|
| 32 |
+
chat_files = [f.split("/")[-1].replace(".json", "") for f in files if f.startswith("chats/")]
|
| 33 |
+
return [[f] for f in chat_files]
|
| 34 |
+
except Exception:
|
| 35 |
+
return []
|
styles.css
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
|
| 3 |
+
.sidebar {
|
| 4 |
+
background-color: #f8f9fa;
|
| 5 |
+
border-right: 1px solid #ddd;
|
| 6 |
+
height: 100vh;
|
| 7 |
+
}
|
| 8 |
+
|
| 9 |
+
.chatbot-container {
|
| 10 |
+
border-radius: 15px !important;
|
| 11 |
+
}
|
tools.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
# ./tools.py
|
| 3 |
+
|
| 4 |
+
"""
|
| 5 |
+
The Research & Extraction Engine - The module handles "Web Search" via Tavily and the parsing of uploaded files (PDFs, Python scripts, etc.)
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import os
|
| 9 |
+
from tavily import TavilyClient
|
| 10 |
+
from pypdf import PdfReader
|
| 11 |
+
import docx
|
| 12 |
+
|
| 13 |
+
# Initialize Tavily
|
| 14 |
+
tavily = TavilyClient(api_key=os.getenv("TAVILY_API_KEY"))
|
| 15 |
+
|
| 16 |
+
def web_search(query: str):
|
| 17 |
+
"""Perform a technical search for documentation or latest AI trends."""
|
| 18 |
+
search_result = tavily.search(query=query, search_depth="advanced", max_results=5)
|
| 19 |
+
context = "\n".join([f"Source: {r['url']}\nContent: {r['content']}" for r in search_result['results']])
|
| 20 |
+
return context
|
| 21 |
+
|
| 22 |
+
def parse_file(file_path):
|
| 23 |
+
"""Extract text from various file formats for the LLM to process."""
|
| 24 |
+
ext = os.path.splitext(file_path)[-1].lower()
|
| 25 |
+
text = f"--- File: {os.path.basename(file_path)} ---\n"
|
| 26 |
+
|
| 27 |
+
if ext == ".pdf":
|
| 28 |
+
reader = PdfReader(file_path)
|
| 29 |
+
for page in reader.pages:
|
| 30 |
+
text += page.extract_text()
|
| 31 |
+
elif ext == ".docx":
|
| 32 |
+
doc = docx.Document(file_path)
|
| 33 |
+
text += "\n".join([para.text for para in doc.paragraphs])
|
| 34 |
+
elif ext in [".py", ".txt", ".md", ".html", ".js", ".yaml", ".toml"]:
|
| 35 |
+
with open(file_path, "r", encoding="utf-8") as f:
|
| 36 |
+
text += f.read()
|
| 37 |
+
else:
|
| 38 |
+
text += "[Non-text file detected or unsupported format]"
|
| 39 |
+
|
| 40 |
+
return text + "\n---\n"
|