danielrosehill commited on
Commit
6e8f6f8
·
1 Parent(s): 82d5e51
Files changed (2) hide show
  1. app.py +342 -0
  2. datasource.txt +1 -0
app.py ADDED
@@ -0,0 +1,342 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import re
3
+ import json
4
+ from pathlib import Path
5
+ from typing import Dict, List, Optional, Tuple, Any
6
+
7
+ import gradio as gr
8
+
9
+
10
+ # ----------------------
11
+ # Data loading utilities
12
+ # ----------------------
13
+
14
+ HERE = Path(__file__).parent
15
+ DATASOURCE_TXT = HERE / "datasource.txt"
16
+ STATIC_DATA_DIR = HERE / "static_data"
17
+
18
+
19
+ def _slugify(text: str) -> str:
20
+ text = text.strip().lower()
21
+ text = re.sub(r"[^a-z0-9\s_-]+", "", text)
22
+ text = re.sub(r"[\s_-]+", "-", text).strip("-")
23
+ return text or "uncategorized"
24
+
25
+
26
+ def _titleize_slug(slug: str) -> str:
27
+ return re.sub(r"[-_]+", " ", slug).title()
28
+
29
+
30
+ def _read_text(path: Path) -> Optional[str]:
31
+ try:
32
+ return path.read_text(encoding="utf-8")
33
+ except Exception:
34
+ return None
35
+
36
+
37
+ def _try_parse_json(text: str) -> Optional[dict]:
38
+ try:
39
+ return json.loads(text)
40
+ except Exception:
41
+ return None
42
+
43
+
44
+ def _try_parse_yaml(text: str) -> Optional[dict]:
45
+ try:
46
+ import yaml # type: ignore
47
+
48
+ return yaml.safe_load(text)
49
+ except Exception:
50
+ return None
51
+
52
+
53
+ def _extract_field(obj: dict, keys: List[str]) -> Optional[str]:
54
+ for k in keys:
55
+ if k in obj and isinstance(obj[k], str) and obj[k].strip():
56
+ return obj[k].strip()
57
+ # common nested forms, like { system: { content: "..." } } or messages: [{role: system, content: "..."}]
58
+ if "system" in obj and isinstance(obj["system"], dict):
59
+ for k in ("content", "prompt", "text"):
60
+ v = obj["system"].get(k)
61
+ if isinstance(v, str) and v.strip():
62
+ return v.strip()
63
+ if "messages" in obj and isinstance(obj["messages"], list):
64
+ for m in obj["messages"]:
65
+ if isinstance(m, dict) and m.get("role") == "system":
66
+ content = m.get("content")
67
+ if isinstance(content, str) and content.strip():
68
+ return content.strip()
69
+ return None
70
+
71
+
72
+ def _extract_agent_from_obj(obj: dict, fallback_category: str, source_path: Path) -> Optional[dict]:
73
+ # Heuristics to recognize agent-like configs.
74
+ name = _extract_field(obj, ["name", "agent_name", "title", "id"])
75
+ description = _extract_field(obj, ["description", "desc", "about", "summary"])
76
+ system_prompt = _extract_field(
77
+ obj,
78
+ [
79
+ "system_prompt",
80
+ "prompt",
81
+ "instructions",
82
+ "system",
83
+ "system_instructions",
84
+ "system_text",
85
+ ],
86
+ )
87
+
88
+ if not (name and system_prompt):
89
+ return None
90
+
91
+ category = (
92
+ _extract_field(obj, ["category", "group", "type"]) or fallback_category or "uncategorized"
93
+ )
94
+ category_slug = _slugify(category)
95
+ agent_id = _slugify(f"{category_slug}-{name}")
96
+
97
+ return {
98
+ "id": agent_id,
99
+ "name": name,
100
+ "description": description or "",
101
+ "system_prompt": system_prompt,
102
+ "category": category_slug,
103
+ "source": str(source_path.relative_to(HERE)),
104
+ }
105
+
106
+
107
+ def _scan_static_data(root: Path) -> List[dict]:
108
+ agents: List[dict] = []
109
+ patterns = ["**/*.json", "**/*.yaml", "**/*.yml"]
110
+ for pattern in patterns:
111
+ for fp in root.glob(pattern):
112
+ if not fp.is_file():
113
+ continue
114
+ text = _read_text(fp)
115
+ if not text:
116
+ continue
117
+ data = _try_parse_json(text)
118
+ if data is None:
119
+ data = _try_parse_yaml(text)
120
+ if not isinstance(data, dict):
121
+ continue
122
+ # derive category from parent folder name as a fallback
123
+ fallback_category = fp.parent.name
124
+ agent = _extract_agent_from_obj(data, fallback_category, fp)
125
+ if agent:
126
+ agents.append(agent)
127
+ return agents
128
+
129
+
130
+ def _maybe_snapshot_download_from_hf(url: str, target_dir: Path) -> Optional[Path]:
131
+ # Attempt to fetch dataset to local static_data using huggingface_hub.
132
+ try:
133
+ from huggingface_hub import snapshot_download
134
+
135
+ # Accept full URL like https://huggingface.co/datasets/owner/name
136
+ m = re.match(r"https?://huggingface.co/datasets/([^/]+/[^/]+)", url.strip())
137
+ if not m:
138
+ return None
139
+ repo_id = m.group(1)
140
+ target_dir.mkdir(parents=True, exist_ok=True)
141
+ local_path = snapshot_download(repo_id=repo_id, repo_type="dataset")
142
+ # Mirror files into target_dir for predictable path
143
+ src = Path(local_path)
144
+ for p in src.rglob("*"):
145
+ if p.is_file():
146
+ rel = p.relative_to(src)
147
+ dest = target_dir / rel
148
+ dest.parent.mkdir(parents=True, exist_ok=True)
149
+ try:
150
+ dest.write_bytes(p.read_bytes())
151
+ except Exception:
152
+ pass
153
+ return target_dir
154
+ except Exception:
155
+ # No network or hub not installed; just skip
156
+ return None
157
+
158
+
159
+ def load_agents() -> Tuple[Dict[str, Any], List[dict], List[str]]:
160
+ """
161
+ Returns (catalog_by_category, agents, warnings)
162
+ catalog_by_category: {category_slug: {label: str, agents: [agent_id, ...]}}
163
+ agents: list of agent dicts
164
+ warnings: list of warning strings for UI
165
+ """
166
+ warnings: List[str] = []
167
+ agents: List[dict] = []
168
+
169
+ # 1) Prefer local static_data if present
170
+ if STATIC_DATA_DIR.exists():
171
+ agents = _scan_static_data(STATIC_DATA_DIR)
172
+ else:
173
+ # 2) Try to download dataset indicated by datasource.txt
174
+ url = _read_text(DATASOURCE_TXT) or ""
175
+ if url.strip():
176
+ maybe_dir = _maybe_snapshot_download_from_hf(url.strip(), STATIC_DATA_DIR)
177
+ if maybe_dir and maybe_dir.exists():
178
+ agents = _scan_static_data(maybe_dir)
179
+ else:
180
+ warnings.append(
181
+ "Dataset fetch unavailable. Add a local 'static_data' folder with agent configs."
182
+ )
183
+ else:
184
+ warnings.append("No datasource URL found; using fallback sample data.")
185
+
186
+ # 3) Fallback sample if nothing found
187
+ if not agents:
188
+ agents = [
189
+ {
190
+ "id": "code-assist-starter",
191
+ "name": "Code Assist Starter",
192
+ "description": "A simple code generation assistant for boilerplate tasks.",
193
+ "system_prompt": (
194
+ "You are a helpful coding agent. Generate concise, correct code and explain key steps."
195
+ ),
196
+ "category": "code-assist",
197
+ "source": "sample",
198
+ },
199
+ {
200
+ "id": "docs-navigator",
201
+ "name": "Docs Navigator",
202
+ "description": "Answers questions using project docs and summarizes APIs.",
203
+ "system_prompt": (
204
+ "Act as a technical writer. Read provided docs and produce accurate, concise answers with citations when possible."
205
+ ),
206
+ "category": "documentation",
207
+ "source": "sample",
208
+ },
209
+ ]
210
+ warnings.append(
211
+ "Showing sample data. Add 'static_data' with JSON/YAML agent configs to replace."
212
+ )
213
+
214
+ # Build catalog
215
+ catalog: Dict[str, Dict[str, Any]] = {}
216
+ for a in agents:
217
+ cat = a.get("category") or "uncategorized"
218
+ cat_slug = _slugify(str(cat))
219
+ if cat_slug not in catalog:
220
+ catalog[cat_slug] = {"label": _titleize_slug(cat_slug), "agents": []}
221
+ catalog[cat_slug]["agents"].append(a["id"])
222
+
223
+ # Sort categories and agents by display name
224
+ catalog = dict(sorted(catalog.items(), key=lambda kv: kv[1]["label"]))
225
+ name_by_id = {a["id"]: a["name"] for a in agents}
226
+ for c in catalog.values():
227
+ c["agents"].sort(key=lambda aid: name_by_id.get(aid, aid).lower())
228
+
229
+ return catalog, agents, warnings
230
+
231
+
232
+ # ----------------------
233
+ # Gradio application
234
+ # ----------------------
235
+
236
+
237
+ def build_ui():
238
+ catalog, agents, warnings = load_agents()
239
+ agent_by_id = {a["id"]: a for a in agents}
240
+
241
+ # Initial selections
242
+ first_cat = next(iter(catalog.keys())) if catalog else None
243
+ first_agent = catalog[first_cat]["agents"][0] if first_cat and catalog[first_cat]["agents"] else None
244
+
245
+ def categories_choices():
246
+ return [(c["label"], slug) for slug, c in catalog.items()]
247
+
248
+ def agents_choices(cat_slug: Optional[str]):
249
+ if not cat_slug or cat_slug not in catalog:
250
+ return []
251
+ return [
252
+ (agent_by_id[aid]["name"], aid)
253
+ for aid in catalog[cat_slug]["agents"]
254
+ if aid in agent_by_id
255
+ ]
256
+
257
+ def on_category_change(cat_slug: str):
258
+ choices = agents_choices(cat_slug)
259
+ value = choices[0][1] if choices else None
260
+ return gr.update(choices=choices, value=value), *on_agent_change(value)
261
+
262
+ def on_agent_change(agent_id: Optional[str]):
263
+ if not agent_id or agent_id not in agent_by_id:
264
+ return (
265
+ "Select an agent",
266
+ gr.update(value="", visible=True),
267
+ gr.update(value="", visible=True),
268
+ gr.update(value="", visible=False),
269
+ )
270
+ a = agent_by_id[agent_id]
271
+ header = f"# {a['name']}"
272
+ desc = a.get("description", "")
273
+ prompt = a.get("system_prompt", "")
274
+ src = a.get("source", "")
275
+ footer = f"Source: {src}" if src else ""
276
+ return (
277
+ header,
278
+ gr.update(value=desc, visible=True),
279
+ gr.update(value=prompt, visible=True),
280
+ gr.update(value=footer, visible=bool(footer)),
281
+ )
282
+
283
+ with gr.Blocks(title="Code Gen Agents Network") as demo:
284
+ with gr.Row():
285
+ with gr.Column(scale=1, min_width=280):
286
+ with gr.Sidebar():
287
+ gr.Markdown("## Categories")
288
+ dd_category = gr.Dropdown(
289
+ label="Category",
290
+ choices=categories_choices(),
291
+ value=first_cat,
292
+ )
293
+ dd_agent = gr.Dropdown(
294
+ label="Agent",
295
+ choices=agents_choices(first_cat),
296
+ value=first_agent,
297
+ )
298
+ if warnings:
299
+ with gr.Accordion("Notes", open=False):
300
+ gr.Markdown("\n".join(f"- {w}" for w in warnings))
301
+
302
+ with gr.Column(scale=3):
303
+ md_header = gr.Markdown(visible=True)
304
+ tb_desc = gr.Textbox(
305
+ label="Description",
306
+ lines=4,
307
+ show_copy_button=True,
308
+ )
309
+ tb_prompt = gr.Textbox(
310
+ label="System Prompt",
311
+ lines=12,
312
+ show_copy_button=True,
313
+ )
314
+ md_footer = gr.Markdown(visible=False)
315
+
316
+ # Wire events
317
+ dd_category.change(
318
+ on_category_change,
319
+ inputs=[dd_category],
320
+ outputs=[dd_agent, md_header, tb_desc, tb_prompt, md_footer],
321
+ )
322
+ dd_agent.change(
323
+ on_agent_change,
324
+ inputs=[dd_agent],
325
+ outputs=[md_header, tb_desc, tb_prompt, md_footer],
326
+ )
327
+
328
+ # Initialize content
329
+ header, desc_upd, prompt_upd, footer_upd = on_agent_change(first_agent)
330
+ md_header.value = header
331
+ tb_desc.value = desc_upd.value if isinstance(desc_upd, gr.Update) else ""
332
+ tb_prompt.value = prompt_upd.value if isinstance(prompt_upd, gr.Update) else ""
333
+ md_footer.value = footer_upd.value if isinstance(footer_upd, gr.Update) else ""
334
+ md_footer.visible = bool(footer_upd.value) if isinstance(footer_upd, gr.Update) else False
335
+
336
+ return demo
337
+
338
+
339
+ if __name__ == "__main__":
340
+ demo = build_ui()
341
+ demo.launch()
342
+
datasource.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ https://huggingface.co/datasets/danielrosehill/Code-Gen-Agents-0925