Spaces:
Configuration error
Configuration error
Upload 9 files
Browse files- .gitignore +3 -0
- README.md +9 -14
- app.py +103 -56
- code_intel.py +43 -0
- context.py +18 -0
- directives.py +28 -0
- project.py +132 -0
- requirements.txt +3 -0
- style.css +7 -0
.gitignore
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
context.json
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.zip
|
README.md
CHANGED
|
@@ -1,15 +1,10 @@
|
|
| 1 |
-
---
|
| 2 |
-
title: TheFinalBuddy
|
| 3 |
-
emoji: 💬
|
| 4 |
-
colorFrom: yellow
|
| 5 |
-
colorTo: purple
|
| 6 |
-
sdk: gradio
|
| 7 |
-
sdk_version: 5.42.0
|
| 8 |
-
app_file: app.py
|
| 9 |
-
pinned: false
|
| 10 |
-
hf_oauth: true
|
| 11 |
-
hf_oauth_scopes:
|
| 12 |
-
- inference-api
|
| 13 |
-
---
|
| 14 |
|
| 15 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
DeepSite Buddy Pro - Ready-to-upload Hugging Face Space
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
+
Features included:
|
| 4 |
+
- Customizable buddy name & simple evolving personality
|
| 5 |
+
- Code analysis for multiple languages (basic)
|
| 6 |
+
- File upload & scaffold generator (login system)
|
| 7 |
+
- Project explorer, file editor, live preview, and export ZIP
|
| 8 |
+
- Dreamy pink/blue UI (static/style.css)
|
| 9 |
+
|
| 10 |
+
To deploy: upload this repository as a new Hugging Face Space (Gradio).
|
app.py
CHANGED
|
@@ -1,70 +1,117 @@
|
|
| 1 |
import gradio as gr
|
| 2 |
-
from
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
max_tokens,
|
| 10 |
-
temperature,
|
| 11 |
-
top_p,
|
| 12 |
-
hf_token: gr.OAuthToken,
|
| 13 |
-
):
|
| 14 |
-
"""
|
| 15 |
-
For more information on `huggingface_hub` Inference API support, please check the docs: https://huggingface.co/docs/huggingface_hub/v0.22.2/en/guides/inference
|
| 16 |
-
"""
|
| 17 |
-
client = InferenceClient(token=hf_token.token, model="openai/gpt-oss-20b")
|
| 18 |
|
| 19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
|
| 21 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
|
| 23 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
|
| 25 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
temperature=temperature,
|
| 32 |
-
top_p=top_p,
|
| 33 |
-
):
|
| 34 |
-
choices = message.choices
|
| 35 |
-
token = ""
|
| 36 |
-
if len(choices) and choices[0].delta.content:
|
| 37 |
-
token = choices[0].delta.content
|
| 38 |
|
| 39 |
-
|
| 40 |
-
|
|
|
|
|
|
|
|
|
|
| 41 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
additional_inputs=[
|
| 50 |
-
gr.Textbox(value="You are a friendly Chatbot.", label="System message"),
|
| 51 |
-
gr.Slider(minimum=1, maximum=2048, value=512, step=1, label="Max new tokens"),
|
| 52 |
-
gr.Slider(minimum=0.1, maximum=4.0, value=0.7, step=0.1, label="Temperature"),
|
| 53 |
-
gr.Slider(
|
| 54 |
-
minimum=0.1,
|
| 55 |
-
maximum=1.0,
|
| 56 |
-
value=0.95,
|
| 57 |
-
step=0.05,
|
| 58 |
-
label="Top-p (nucleus sampling)",
|
| 59 |
-
),
|
| 60 |
-
],
|
| 61 |
-
)
|
| 62 |
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
|
|
|
|
| 68 |
|
| 69 |
-
|
| 70 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import gradio as gr
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
from core.directives import BuddyPersonality
|
| 4 |
+
from core.code_intel import analyze_code, detect_language
|
| 5 |
+
from core.project import list_project, read_file, write_file, export_project, scaffold_login_system
|
| 6 |
+
from utils.context import load_context, save_context
|
| 7 |
+
import json, shutil, os
|
| 8 |
|
| 9 |
+
CTX = load_context()
|
| 10 |
+
buddy = BuddyPersonality()
|
| 11 |
+
BASE = Path('scaffold_projects')
|
| 12 |
+
BASE.mkdir(exist_ok=True)
|
| 13 |
|
| 14 |
+
sample = BASE / 'welcome_project'
|
| 15 |
+
if not sample.exists():
|
| 16 |
+
(sample / 'hello.txt').parent.mkdir(parents=True, exist_ok=True)
|
| 17 |
+
(sample / 'hello.txt').write_text('Welcome to DeepSite Buddy Pro!\nUse the UI to generate projects, upload files, and edit them.')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
|
| 19 |
+
def run_analysis(file):
|
| 20 |
+
try:
|
| 21 |
+
content = file.read().decode('utf-8', errors='ignore')
|
| 22 |
+
except Exception:
|
| 23 |
+
try:
|
| 24 |
+
with open(file.name, 'r', encoding='utf-8', errors='ignore') as f:
|
| 25 |
+
content = f.read()
|
| 26 |
+
except Exception as e:
|
| 27 |
+
return f'Could not read file: {e}'
|
| 28 |
+
lang = detect_language(file.name if hasattr(file,'name') else 'file')
|
| 29 |
+
res = analyze_code(file.name if hasattr(file,'name') else 'file', content)
|
| 30 |
+
return json.dumps({"language": lang, "analysis": res}, indent=2, ensure_ascii=False)
|
| 31 |
|
| 32 |
+
def cmd_make_login():
|
| 33 |
+
p = BASE / 'login_system'
|
| 34 |
+
if p.exists():
|
| 35 |
+
shutil.rmtree(p)
|
| 36 |
+
scaffold_login_system(p)
|
| 37 |
+
return f'Login system scaffold created at {p}. Use "Show Project Tree" to view.'
|
| 38 |
|
| 39 |
+
def show_tree(proj_name='welcome_project'):
|
| 40 |
+
p = BASE / proj_name
|
| 41 |
+
if not p.exists():
|
| 42 |
+
return f'Project {proj_name} does not exist.'
|
| 43 |
+
return list_project(p)
|
| 44 |
|
| 45 |
+
def read_proj_file(proj_name, rel_path):
|
| 46 |
+
p = BASE / proj_name / rel_path
|
| 47 |
+
content = read_file(p)
|
| 48 |
+
if content is None:
|
| 49 |
+
return f'File not found: {rel_path}'
|
| 50 |
+
return content
|
| 51 |
|
| 52 |
+
def save_proj_file(proj_name, rel_path, content):
|
| 53 |
+
p = BASE / proj_name / rel_path
|
| 54 |
+
write_file(p, content)
|
| 55 |
+
return f'Wrote {rel_path}'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
|
| 57 |
+
def export_proj_zip(proj_name):
|
| 58 |
+
p = BASE / proj_name
|
| 59 |
+
if not p.exists():
|
| 60 |
+
return None
|
| 61 |
+
return export_project(p, f'{proj_name}_export')
|
| 62 |
|
| 63 |
+
with gr.Blocks(css='static/style.css', title='DeepSite Buddy Pro') as demo:
|
| 64 |
+
gr.Markdown('# DeepSite Buddy Pro 🌸☁️\nA dreamy AI developer buddy.')
|
| 65 |
+
with gr.Row():
|
| 66 |
+
with gr.Column(scale=3):
|
| 67 |
+
name_in = gr.Textbox(label='Set Buddy Name', placeholder=buddy.get_name())
|
| 68 |
+
set_name_btn = gr.Button('Set Name')
|
| 69 |
+
chat_in = gr.Textbox(label='Say something to Buddy', placeholder='e.g. make login system or analyze my file')
|
| 70 |
+
chat_btn = gr.Button('Run')
|
| 71 |
+
file_in = gr.File(label='Upload code file for analysis', file_count='single')
|
| 72 |
+
analysis_out = gr.Code(label='Analysis Output', language='json')
|
| 73 |
+
with gr.Row():
|
| 74 |
+
make_login = gr.Button('Make Login System (scaffold)')
|
| 75 |
+
show_tree_btn = gr.Button('Show Project Tree')
|
| 76 |
+
project_select = gr.Dropdown(choices=[p.name for p in BASE.iterdir() if p.is_dir()], value='welcome_project', label='Project')
|
| 77 |
+
export_btn = gr.Button('Export Project ZIP')
|
| 78 |
+
with gr.Column(scale=2):
|
| 79 |
+
gr.Markdown('### Project Explorer')
|
| 80 |
+
tree_out = gr.Textbox(label='Project Tree', lines=20)
|
| 81 |
+
gr.Markdown('### File Editor')
|
| 82 |
+
file_path = gr.Textbox(label='Relative path (e.g., frontend/index.html)', placeholder='hello.txt')
|
| 83 |
+
file_editor = gr.Code('', label='Editor', language='auto', lines=20)
|
| 84 |
+
save_file_btn = gr.Button('Save File')
|
| 85 |
+
preview_html = gr.HTML('<div style="padding:12px;background:rgba(255,255,255,0.6);border-radius:8px;">Live preview will show HTML content when you open an HTML file.</div>')
|
| 86 |
|
| 87 |
+
def _set_name(n):
|
| 88 |
+
if not n:
|
| 89 |
+
return buddy.get_name()
|
| 90 |
+
buddy.set_name(n)
|
| 91 |
+
return f'Buddy name set to {n}'
|
| 92 |
+
set_name_btn.click(_set_name, inputs=[name_in], outputs=[name_in])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 93 |
|
| 94 |
+
def _run_chat(text, uploaded_file):
|
| 95 |
+
if uploaded_file:
|
| 96 |
+
return run_analysis(uploaded_file)
|
| 97 |
+
lower = (text or '').lower()
|
| 98 |
+
if any(k in lower for k in ('make login','login system','make a login')):
|
| 99 |
+
return cmd_make_login()
|
| 100 |
+
if any(k in lower for k in ('show project','project tree','show tree')):
|
| 101 |
+
return show_tree(project_select.value)
|
| 102 |
+
out = f"Hello, I'm {buddy.get_name()}! I can analyze files, scaffold projects, and export zips. You said: {text}"
|
| 103 |
+
buddy.record_conversation(text, out)
|
| 104 |
+
return out
|
| 105 |
|
| 106 |
+
chat_btn.click(_run_chat, inputs=[chat_in, file_in], outputs=[analysis_out])
|
| 107 |
|
| 108 |
+
make_login.click(lambda: cmd_make_login(), inputs=None, outputs=[analysis_out, tree_out])
|
| 109 |
+
show_tree_btn.click(lambda: show_tree(project_select.value), inputs=None, outputs=tree_out)
|
| 110 |
+
|
| 111 |
+
def _open_file(proj, rel):
|
| 112 |
+
return read_proj_file(proj, rel)
|
| 113 |
+
file_path.change(_open_file, inputs=[project_select, file_path], outputs=file_editor)
|
| 114 |
+
save_file_btn.click(lambda proj, rel, content: save_proj_file(proj, rel, content), inputs=[project_select, file_path, file_editor], outputs=analysis_out)
|
| 115 |
+
export_btn.click(lambda proj: export_proj_zip(proj), inputs=[project_select], outputs=gr.File(label='Download ZIP'))
|
| 116 |
+
|
| 117 |
+
demo.launch()
|
code_intel.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import ast, re
|
| 2 |
+
|
| 3 |
+
def detect_language(filename):
|
| 4 |
+
if not filename or "." not in filename:
|
| 5 |
+
return "text"
|
| 6 |
+
ext = filename.split(".")[-1].lower()
|
| 7 |
+
mapping = {"py":"python","js":"javascript","ts":"typescript","java":"java","cs":"csharp","html":"html","css":"css","json":"json","md":"markdown"}
|
| 8 |
+
return mapping.get(ext, ext)
|
| 9 |
+
|
| 10 |
+
def analyze_python(code: str):
|
| 11 |
+
try:
|
| 12 |
+
tree = ast.parse(code)
|
| 13 |
+
except Exception as e:
|
| 14 |
+
return {"ok": False, "error": f"Syntax Error: {e}", "suggestion": "Check indentation or syntax near the reported location."}
|
| 15 |
+
issues = []
|
| 16 |
+
class FuncVisitor(ast.NodeVisitor):
|
| 17 |
+
def visit_FunctionDef(self, node):
|
| 18 |
+
start = node.lineno
|
| 19 |
+
end = node.lineno
|
| 20 |
+
for n in ast.walk(node):
|
| 21 |
+
if hasattr(n, "lineno"):
|
| 22 |
+
end = max(end, n.lineno)
|
| 23 |
+
length = end - start + 1
|
| 24 |
+
if length > 200:
|
| 25 |
+
issues.append({"type":"large_function","message":f"Function '{node.name}' is {length} lines; consider refactoring."})
|
| 26 |
+
self.generic_visit(node)
|
| 27 |
+
FuncVisitor().visit(tree)
|
| 28 |
+
return {"ok": True, "issues": issues, "summary": "Parsed AST successfully."}
|
| 29 |
+
|
| 30 |
+
def analyze_javascript(code: str):
|
| 31 |
+
if "console.log" in code and "debugger" in code:
|
| 32 |
+
return {"ok": True, "notes":"Found console.log and debugger. Remove before prod."}
|
| 33 |
+
if "function(" not in code and "=>" not in code:
|
| 34 |
+
return {"ok": True, "notes":"No obvious function constructs found."}
|
| 35 |
+
return {"ok": True, "notes":"Basic JS analysis complete."}
|
| 36 |
+
|
| 37 |
+
def analyze_code(filename, code):
|
| 38 |
+
lang = detect_language(filename)
|
| 39 |
+
if lang == "python":
|
| 40 |
+
return analyze_python(code)
|
| 41 |
+
if lang in ("javascript","typescript"):
|
| 42 |
+
return analyze_javascript(code)
|
| 43 |
+
return {"ok": True, "notes": f"Language {lang} not deeply analyzed. Size {len(code)} bytes.", "head": code[:800]}
|
context.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
CTX_PATH = Path('context.json')
|
| 4 |
+
|
| 5 |
+
def load_context():
|
| 6 |
+
if CTX_PATH.exists():
|
| 7 |
+
try:
|
| 8 |
+
return json.loads(CTX_PATH.read_text(encoding='utf-8'))
|
| 9 |
+
except Exception:
|
| 10 |
+
return {}
|
| 11 |
+
return {}
|
| 12 |
+
|
| 13 |
+
def save_context(ctx: dict):
|
| 14 |
+
try:
|
| 15 |
+
CTX_PATH.write_text(json.dumps(ctx, indent=2, ensure_ascii=False), encoding='utf-8')
|
| 16 |
+
return True
|
| 17 |
+
except Exception:
|
| 18 |
+
return False
|
directives.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from utils.context import load_context, save_context
|
| 2 |
+
|
| 3 |
+
class BuddyPersonality:
|
| 4 |
+
def __init__(self):
|
| 5 |
+
self.ctx = load_context()
|
| 6 |
+
self.defaults = {"name":"Buddy","tone":"friendly","verbosity":"medium"}
|
| 7 |
+
self.ctx.setdefault("buddy", {})
|
| 8 |
+
for k,v in self.defaults.items():
|
| 9 |
+
self.ctx["buddy"].setdefault(k, v)
|
| 10 |
+
save_context(self.ctx)
|
| 11 |
+
|
| 12 |
+
def get_name(self):
|
| 13 |
+
return self.ctx["buddy"].get("name", self.defaults["name"])
|
| 14 |
+
|
| 15 |
+
def set_name(self, name):
|
| 16 |
+
self.ctx["buddy"]["name"] = name
|
| 17 |
+
save_context(self.ctx)
|
| 18 |
+
|
| 19 |
+
def record_conversation(self, user_text, bot_text):
|
| 20 |
+
history = self.ctx.setdefault("history", [])
|
| 21 |
+
history.append({"user": user_text, "bot": bot_text})
|
| 22 |
+
short_count = sum(1 for h in history[-20:] if len(h["user"]) < 40)
|
| 23 |
+
if short_count > 10:
|
| 24 |
+
self.ctx["buddy"]["verbosity"] = "short"
|
| 25 |
+
save_context(self.ctx)
|
| 26 |
+
|
| 27 |
+
def summary(self):
|
| 28 |
+
return {"name": self.get_name(), "tone": self.ctx["buddy"].get("tone"), "verbosity": self.ctx["buddy"].get("verbosity")}
|
project.py
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pathlib import Path
|
| 2 |
+
import shutil, json
|
| 3 |
+
|
| 4 |
+
def list_project(path: Path, max_depth=6):
|
| 5 |
+
out = []
|
| 6 |
+
if not path.exists():
|
| 7 |
+
return "No project found at " + str(path)
|
| 8 |
+
def _walk(p, depth):
|
| 9 |
+
if depth > max_depth:
|
| 10 |
+
return
|
| 11 |
+
for child in sorted(p.iterdir(), key=lambda x: (not x.is_dir(), x.name.lower())):
|
| 12 |
+
out.append((" "*depth) + ("📁 " if child.is_dir() else "📄 ") + child.name)
|
| 13 |
+
if child.is_dir():
|
| 14 |
+
_walk(child, depth+1)
|
| 15 |
+
_walk(path, 0)
|
| 16 |
+
return "\n".join(out)
|
| 17 |
+
|
| 18 |
+
def read_file(path: Path):
|
| 19 |
+
if not path.exists():
|
| 20 |
+
return None
|
| 21 |
+
try:
|
| 22 |
+
return path.read_text(encoding='utf-8', errors='ignore')
|
| 23 |
+
except Exception as e:
|
| 24 |
+
return None
|
| 25 |
+
|
| 26 |
+
def write_file(path: Path, content: str):
|
| 27 |
+
path.parent.mkdir(parents=True, exist_ok=True)
|
| 28 |
+
path.write_text(content, encoding='utf-8')
|
| 29 |
+
return True
|
| 30 |
+
|
| 31 |
+
def export_project(path: Path, zip_name: str):
|
| 32 |
+
if not path.exists():
|
| 33 |
+
return None
|
| 34 |
+
base = Path(zip_name).stem
|
| 35 |
+
shutil.make_archive(base, 'zip', root_dir=path)
|
| 36 |
+
return base + '.zip'
|
| 37 |
+
|
| 38 |
+
def scaffold_login_system(root: Path):
|
| 39 |
+
from textwrap import dedent
|
| 40 |
+
root.mkdir(parents=True, exist_ok=True)
|
| 41 |
+
frontend = root / "frontend"
|
| 42 |
+
backend = root / "backend"
|
| 43 |
+
frontend.mkdir(exist_ok=True)
|
| 44 |
+
backend.mkdir(exist_ok=True)
|
| 45 |
+
(frontend / "index.html").write_text(dedent("""
|
| 46 |
+
<!doctype html>
|
| 47 |
+
<html>
|
| 48 |
+
<head>
|
| 49 |
+
<meta charset='utf-8'/>
|
| 50 |
+
<title>Login</title>
|
| 51 |
+
<link rel='stylesheet' href='style.css'/>
|
| 52 |
+
</head>
|
| 53 |
+
<body>
|
| 54 |
+
<div class='card'>
|
| 55 |
+
<h2>Login</h2>
|
| 56 |
+
<form id='form'>
|
| 57 |
+
<input id='email' placeholder='email' required/>
|
| 58 |
+
<input id='password' type='password' placeholder='password' required/>
|
| 59 |
+
<button type='button' onclick='signup()'>Sign Up</button>
|
| 60 |
+
<button type='button' onclick='login()'>Log In</button>
|
| 61 |
+
</form>
|
| 62 |
+
<pre id='out'></pre>
|
| 63 |
+
</div>
|
| 64 |
+
<script>
|
| 65 |
+
async function signup(){ const e=document.getElementById('email').value; const p=document.getElementById('password').value; const res=await fetch('/signup',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({email:e,password:p})}); document.getElementById('out').innerText=await res.text(); }
|
| 66 |
+
async function login(){ const e=document.getElementById('email').value; const p=document.getElementById('password').value; const res=await fetch('/login',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({email:e,password:p})}); document.getElementById('out').innerText=await res.text(); }
|
| 67 |
+
</script>
|
| 68 |
+
</body>
|
| 69 |
+
</html>
|
| 70 |
+
"""))
|
| 71 |
+
(frontend / "style.css").write_text(dedent("""
|
| 72 |
+
body { background: linear-gradient(135deg,#ffd1e8,#cfe9ff); font-family: Arial, sans-serif; }
|
| 73 |
+
.card{max-width:360px;margin:60px auto;padding:20px;border-radius:12px;background:rgba(255,255,255,0.8);box-shadow:0 8px 30px rgba(0,0,0,0.08);}
|
| 74 |
+
input{display:block;width:100%;margin:8px 0;padding:10px;border-radius:8px;border:1px solid #ddd}
|
| 75 |
+
button{margin-right:8px;padding:8px 12px;border-radius:8px;border:none;background:#ff8fc1;color:white}
|
| 76 |
+
pre{background:#fff;padding:8px;border-radius:6px;border:1px solid #eee}
|
| 77 |
+
"""))
|
| 78 |
+
(backend / "app.py").write_text(dedent("""
|
| 79 |
+
from wsgiref.simple_server import make_server
|
| 80 |
+
import json, os
|
| 81 |
+
DB='users.json'
|
| 82 |
+
def app(environ, start_response):
|
| 83 |
+
path = environ.get('PATH_INFO','/')
|
| 84 |
+
try:
|
| 85 |
+
size = int(environ.get('CONTENT_LENGTH') or 0)
|
| 86 |
+
except Exception:
|
| 87 |
+
size = 0
|
| 88 |
+
body = environ['wsgi.input'].read(size) if size else b''
|
| 89 |
+
if path == '/signup' or path == '/login':
|
| 90 |
+
try:
|
| 91 |
+
data = json.loads(body.decode('utf-8'))
|
| 92 |
+
except Exception:
|
| 93 |
+
data = {}
|
| 94 |
+
email = data.get('email'); password = data.get('password')
|
| 95 |
+
if not email or not password:
|
| 96 |
+
start_response('400 Bad Request', [('Content-Type','text/plain')])
|
| 97 |
+
return [b'Missing email or password']
|
| 98 |
+
users = {}
|
| 99 |
+
if os.path.exists(DB):
|
| 100 |
+
try:
|
| 101 |
+
users = json.loads(open(DB,'r').read())
|
| 102 |
+
except Exception:
|
| 103 |
+
users={}
|
| 104 |
+
if path == '/signup':
|
| 105 |
+
if email in users:
|
| 106 |
+
start_response('400 Bad Request', [('Content-Type','text/plain')])
|
| 107 |
+
return [b'User exists']
|
| 108 |
+
users[email] = {'password': password}
|
| 109 |
+
open(DB,'w').write(json.dumps(users))
|
| 110 |
+
start_response('200 OK', [('Content-Type','text/plain')])
|
| 111 |
+
return [b'Signed up']
|
| 112 |
+
else:
|
| 113 |
+
u = users.get(email)
|
| 114 |
+
if not u or u.get('password') != password:
|
| 115 |
+
start_response('401 Unauthorized', [('Content-Type','text/plain')])
|
| 116 |
+
return [b'Invalid credentials']
|
| 117 |
+
start_response('200 OK', [('Content-Type','text/plain')])
|
| 118 |
+
return [b'Logged in']
|
| 119 |
+
if path == '/' or path.endswith('.html') or path.endswith('.css') or path.endswith('.js'):
|
| 120 |
+
local = os.path.join(os.path.dirname(__file__), '..', 'frontend', path.lstrip('/'))
|
| 121 |
+
if os.path.exists(local):
|
| 122 |
+
start_response('200 OK', [('Content-Type','text/html')])
|
| 123 |
+
return [open(local,'rb').read()]
|
| 124 |
+
start_response('404 Not Found', [('Content-Type','text/plain')])
|
| 125 |
+
return [b'Not Found']
|
| 126 |
+
if __name__=='__main__':
|
| 127 |
+
print('Starting tiny server on http://localhost:8000 (for local testing)')
|
| 128 |
+
make_server('',8000,app).serve_forever()
|
| 129 |
+
"""))
|
| 130 |
+
(root := root := root if isinstance(root, Path) else root).absolute()
|
| 131 |
+
(root / 'README.md').write_text('Login system scaffold: frontend + backend (tiny WSGI server).')
|
| 132 |
+
return True
|
requirements.txt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
gradio==3.40.0
|
| 2 |
+
requests==2.31.0
|
| 3 |
+
beautifulsoup4==4.12.2
|
style.css
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
body { background: linear-gradient(135deg,#ffd1e8,#cfe9ff); font-family: "Segoe UI", Roboto, Arial, sans-serif; }
|
| 2 |
+
.container{ max-width:1100px; margin:20px auto; padding:18px; border-radius:14px; background: linear-gradient(180deg, rgba(255,255,255,0.8), rgba(255,255,255,0.6)); box-shadow: 0 10px 40px rgba(0,0,0,0.08); }
|
| 3 |
+
.header{ display:flex; align-items:center; justify-content:space-between; }
|
| 4 |
+
.badge{ background: rgba(255,255,255,0.6); padding:8px 12px; border-radius:999px; box-shadow: inset 0 -2px 6px rgba(255,255,255,0.6); }
|
| 5 |
+
.card{ border-radius:12px; padding:12px; background: linear-gradient(180deg, rgba(255,255,255,0.9), rgba(255,255,255,0.85)); margin-top:12px; }
|
| 6 |
+
.codebox{ font-family: monospace; font-size:13px; }
|
| 7 |
+
.tree{ font-family: monospace; white-space: pre; background: rgba(255,255,255,0.6); padding:12px; border-radius:8px; }
|