duqing2026 commited on
Commit
1690cb0
·
verified ·
1 Parent(s): b3127f2

Force push code via HfApi (Git fallback)

Browse files
Files changed (2) hide show
  1. app.py +117 -5
  2. scripts/force_push_code.py +45 -0
app.py CHANGED
@@ -359,6 +359,43 @@ class ShowcaseHandler(BaseHTTPRequestHandler):
359
 
360
  def do_GET(self) -> None:
361
  _, path = self._parse()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
362
 
363
  if path.startswith("/static/"):
364
  file_path = Path(".") / path.lstrip("/")
@@ -707,7 +744,32 @@ class ShowcaseHandler(BaseHTTPRequestHandler):
707
  <html lang="zh-CN">
708
  <head>
709
  <meta charset="utf-8" />
710
- <title>项目集/工具站</title>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
711
  <meta name="viewport" content="width=device-width, initial-scale=1" />
712
  <style>
713
  :root { --bg: #f3f4f6; --panel: #ffffff; --card: #ffffff; --muted: #6b7280; --fg: #111827; --accent: #2563eb; --border: rgba(148,163,184,0.35); }
@@ -719,8 +781,10 @@ class ShowcaseHandler(BaseHTTPRequestHandler):
719
  .title { font-weight:700; font-size:20px; color:#0f172a; }
720
  .sub { font-size:12px; color: var(--muted); }
721
  .actions { display:flex; align-items:center; gap:8px; }
722
- .btn { border:1px solid var(--border); border-radius:999px; padding:8px 18px; font-weight:600; cursor:pointer; color:var(--fg); background:#ffffff; box-shadow: 0 4px 12px rgba(0,0,0,0.05); transition: all .2s ease; }
723
  .btn:hover { border-color:var(--accent); color:var(--accent); box-shadow: 0 8px 16px rgba(37,99,235,0.15); transform: translateY(-1px); }
 
 
724
  .layout { flex: 1; display:flex; gap:16px; margin-top:16px; overflow: hidden; }
725
  aside.panel { width: 280px; display: flex; flex-direction: column; overflow-y: auto; flex-shrink: 0; }
726
  main.panel { flex: 1; overflow-y: auto; display: flex; flex-direction: column; }
@@ -794,7 +858,10 @@ class ShowcaseHandler(BaseHTTPRequestHandler):
794
  <div class="shell">
795
  <div class="topbar">
796
  <div class="brand"><div class="title">项目集/工具站</div><div class="sub">项目分类与搜索</div></div>
797
- <div class="actions"><button class="btn" id="admin-toggle">管理</button></div>
 
 
 
798
  </div>
799
  <div class="layout">
800
  <aside class="panel">
@@ -866,7 +933,7 @@ class ShowcaseHandler(BaseHTTPRequestHandler):
866
  </div>
867
  <script>
868
  const TYPES = {types_json};
869
- const state = { items: [], counts: {}, total: 0, q: "", type: "", admin: false, authenticated: false, newProjectTypes: [] };
870
 
871
  function pickGradient(project) {
872
  if (state.type && (project.ptypes || []).includes(state.type)) {
@@ -1009,10 +1076,51 @@ class ShowcaseHandler(BaseHTTPRequestHandler):
1009
  renderFilters();
1010
  const grid = document.getElementById("grid"); grid.innerHTML = "";
1011
  for (const p of state.items) grid.appendChild(card(p));
 
 
 
 
 
 
 
 
 
 
1012
  }
1013
  function openModal(project) {
 
 
 
 
1014
  document.getElementById("modal").classList.add("open");
1015
- document.getElementById("modal-title").textContent = project.name || "项目演示";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1016
  let src = "";
1017
  if (project.embed_url || project.hf_space_url) {
1018
  const base = project.embed_url || project.hf_space_url;
@@ -1041,6 +1149,10 @@ class ShowcaseHandler(BaseHTTPRequestHandler):
1041
  });
1042
  }
1043
  function closeModal() {
 
 
 
 
1044
  document.getElementById("modal").classList.remove("open");
1045
  document.getElementById("iframe").src = "";
1046
  }
 
359
 
360
  def do_GET(self) -> None:
361
  _, path = self._parse()
362
+
363
+ if path == "/robots.txt":
364
+ content = "User-agent: *\nAllow: /\nSitemap: https://huggingface.co/spaces/duqing2026/project-show/sitemap.xml"
365
+ self.send_response(200)
366
+ self.send_header("Content-Type", "text/plain")
367
+ self.send_header("Content-Length", str(len(content)))
368
+ self.end_headers()
369
+ self.wfile.write(content.encode("utf-8"))
370
+ return
371
+
372
+ if path == "/sitemap.xml":
373
+ base_url = "https://huggingface.co/spaces/duqing2026/project-show"
374
+ xml = '<?xml version="1.0" encoding="UTF-8"?>\n'
375
+ xml += '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n'
376
+ xml += f' <url>\n <loc>{base_url}</loc>\n <changefreq>daily</changefreq>\n <priority>1.0</priority>\n </url>\n'
377
+
378
+ # Add projects to sitemap
379
+ try:
380
+ for p in store.projects:
381
+ pid = p.get("id")
382
+ if pid is not None:
383
+ updated = p.get("updated_at", "")
384
+ if updated and len(updated) >= 10: updated = updated[:10]
385
+ else: updated = datetime.datetime.utcnow().strftime("%Y-%m-%d")
386
+
387
+ # XML escape for loc if needed, but IDs are usually ints. safe.
388
+ xml += f' <url>\n <loc>{base_url}?project_id={pid}</loc>\n <changefreq>weekly</changefreq>\n <priority>0.8</priority>\n <lastmod>{updated}</lastmod>\n </url>\n'
389
+ except Exception as e:
390
+ print(f"Sitemap gen error: {e}")
391
+
392
+ xml += '</urlset>'
393
+ self.send_response(200)
394
+ self.send_header("Content-Type", "application/xml")
395
+ self.send_header("Content-Length", str(len(xml)))
396
+ self.end_headers()
397
+ self.wfile.write(xml.encode("utf-8"))
398
+ return
399
 
400
  if path.startswith("/static/"):
401
  file_path = Path(".") / path.lstrip("/")
 
744
  <html lang="zh-CN">
745
  <head>
746
  <meta charset="utf-8" />
747
+ <title>渡青的项目展示 - 全栈开发与AI实战 | Duqing's Showcase</title>
748
+ <meta name="description" content="探索渡青的个人项目集,涵盖RAG系统、电商应用、游戏开发、小程序及AI模型实战。分享编程心得与技术创新。" />
749
+ <meta name="keywords" content="渡青, 个人主页, 项目展示, 全栈开发, Python, RAG, AI, 编程博客, 语雀" />
750
+ <meta name="author" content="渡青" />
751
+ <meta property="og:type" content="website" />
752
+ <meta property="og:title" content="渡青的项目展示 - 全栈开发与AI实战" />
753
+ <meta property="og:description" content="探索渡青的个人项目集,涵盖RAG系统、电商应用、游戏开发、小程序及AI模型实战。" />
754
+ <meta property="twitter:card" content="summary" />
755
+ <meta property="twitter:title" content="渡青的项目展示" />
756
+ <meta property="twitter:description" content="探索渡青的个人项目集,涵盖RAG系统、电商应用、游戏开发、小程序及AI模型实战。" />
757
+ <script type="application/ld+json">
758
+ {
759
+ "@context": "https://schema.org",
760
+ "@type": "ProfilePage",
761
+ "mainEntity": {
762
+ "@type": "Person",
763
+ "name": "渡青",
764
+ "alternateName": "Duqing",
765
+ "description": "全栈开发者,专注于 Python, RAG, AI Agent 及 Web 应用开发。",
766
+ "url": "https://huggingface.co/spaces/duqing2026/project-show",
767
+ "sameAs": [
768
+ "https://www.yuque.com/lianmt"
769
+ ]
770
+ }
771
+ }
772
+ </script>
773
  <meta name="viewport" content="width=device-width, initial-scale=1" />
774
  <style>
775
  :root { --bg: #f3f4f6; --panel: #ffffff; --card: #ffffff; --muted: #6b7280; --fg: #111827; --accent: #2563eb; --border: rgba(148,163,184,0.35); }
 
781
  .title { font-weight:700; font-size:20px; color:#0f172a; }
782
  .sub { font-size:12px; color: var(--muted); }
783
  .actions { display:flex; align-items:center; gap:8px; }
784
+ .btn { border:1px solid var(--border); border-radius:999px; padding:8px 18px; font-size:14px; font-weight:600; cursor:pointer; color:var(--fg); background:#ffffff; box-shadow: 0 4px 12px rgba(0,0,0,0.05); transition: all .2s ease; }
785
  .btn:hover { border-color:var(--accent); color:var(--accent); box-shadow: 0 8px 16px rgba(37,99,235,0.15); transform: translateY(-1px); }
786
+ .btn-outline-primary { border-color: var(--accent); color: var(--accent); background: rgba(37,99,235,0.04); }
787
+ .btn-outline-primary:hover { background: var(--accent); color: #ffffff; box-shadow: 0 8px 16px rgba(37,99,235,0.25); }
788
  .layout { flex: 1; display:flex; gap:16px; margin-top:16px; overflow: hidden; }
789
  aside.panel { width: 280px; display: flex; flex-direction: column; overflow-y: auto; flex-shrink: 0; }
790
  main.panel { flex: 1; overflow-y: auto; display: flex; flex-direction: column; }
 
858
  <div class="shell">
859
  <div class="topbar">
860
  <div class="brand"><div class="title">项目集/工具站</div><div class="sub">项目分类与搜索</div></div>
861
+ <div class="actions">
862
+ <a href="https://www.yuque.com/lianmt" target="_blank" class="btn btn-outline-primary" style="text-decoration:none;">我的博客</a>
863
+ <button class="btn" id="admin-toggle">管理</button>
864
+ </div>
865
  </div>
866
  <div class="layout">
867
  <aside class="panel">
 
933
  </div>
934
  <script>
935
  const TYPES = {types_json};
936
+ const state = { items: [], counts: {}, total: 0, q: "", type: "", admin: false, authenticated: false, newProjectTypes: [], initialLoad: true };
937
 
938
  function pickGradient(project) {
939
  if (state.type && (project.ptypes || []).includes(state.type)) {
 
1076
  renderFilters();
1077
  const grid = document.getElementById("grid"); grid.innerHTML = "";
1078
  for (const p of state.items) grid.appendChild(card(p));
1079
+
1080
+ if (state.initialLoad) {
1081
+ state.initialLoad = false;
1082
+ const params = new URLSearchParams(window.location.search);
1083
+ const pid = params.get("project_id");
1084
+ if (pid) {
1085
+ const p = state.items.find(x => x.id == pid);
1086
+ if (p) openModal(p);
1087
+ }
1088
+ }
1089
  }
1090
  function openModal(project) {
1091
+ const url = new URL(window.location);
1092
+ url.searchParams.set("project_id", project.id);
1093
+ window.history.pushState({}, "", url);
1094
+
1095
  document.getElementById("modal").classList.add("open");
1096
+
1097
+ // Update header with copy link btn
1098
+ const head = document.querySelector(".modal-head");
1099
+ head.innerHTML = "";
1100
+
1101
+ const title = document.createElement("div"); title.className = "modal-title"; title.textContent = project.name || "项目演示";
1102
+ const actions = document.createElement("div"); actions.style.display = "flex"; actions.style.gap = "8px";
1103
+
1104
+ const copyBtn = document.createElement("button"); copyBtn.className = "btn-sm"; copyBtn.textContent = "复制链接";
1105
+ copyBtn.onclick = () => {
1106
+ const link = window.location.href;
1107
+ navigator.clipboard.writeText(link).then(() => {
1108
+ const orig = copyBtn.textContent;
1109
+ copyBtn.textContent = "已复制";
1110
+ setTimeout(() => copyBtn.textContent = orig, 2000);
1111
+ });
1112
+ };
1113
+
1114
+ const closeBtn = document.createElement("button"); closeBtn.className = "btn-sm"; closeBtn.textContent = "关闭";
1115
+ closeBtn.id = "close-modal"; // Restore ID for event listener binding if needed, but we bind click below
1116
+ closeBtn.onclick = closeModal;
1117
+
1118
+ actions.appendChild(copyBtn);
1119
+ actions.appendChild(closeBtn);
1120
+
1121
+ head.appendChild(title);
1122
+ head.appendChild(actions);
1123
+
1124
  let src = "";
1125
  if (project.embed_url || project.hf_space_url) {
1126
  const base = project.embed_url || project.hf_space_url;
 
1149
  });
1150
  }
1151
  function closeModal() {
1152
+ const url = new URL(window.location);
1153
+ url.searchParams.delete("project_id");
1154
+ window.history.pushState({}, "", url);
1155
+
1156
  document.getElementById("modal").classList.remove("open");
1157
  document.getElementById("iframe").src = "";
1158
  }
scripts/force_push_code.py ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ from huggingface_hub import HfApi
4
+ from dotenv import load_dotenv
5
+
6
+ # Load env
7
+ load_dotenv(override=True)
8
+
9
+ HF_TOKEN = os.getenv("HF_TOKEN")
10
+ REPO_ID = "duqing2026/project-show"
11
+
12
+ if not HF_TOKEN:
13
+ print("Error: HF_TOKEN not found.")
14
+ sys.exit(1)
15
+
16
+ api = HfApi(token=HF_TOKEN)
17
+
18
+ print(f"Force pushing code to Space: {REPO_ID}...")
19
+
20
+ # Allow patterns: upload everything except exclusions
21
+ # Better to explicitly exclude
22
+ ignore_patterns = [
23
+ ".git*",
24
+ ".env*",
25
+ "venv/*",
26
+ "hf_project_showcase_data/*",
27
+ "__pycache__/*",
28
+ "*.pyc",
29
+ ".DS_Store",
30
+ "*.log"
31
+ ]
32
+
33
+ try:
34
+ api.upload_folder(
35
+ folder_path=".",
36
+ repo_id=REPO_ID,
37
+ repo_type="space",
38
+ path_in_repo=".",
39
+ commit_message="Force push code via HfApi (Git fallback)",
40
+ ignore_patterns=ignore_patterns
41
+ )
42
+ print("Code pushed successfully via HfApi.")
43
+ except Exception as e:
44
+ print(f"Failed to push code: {e}")
45
+ sys.exit(1)