Spaces:
Running on Zero
Running on Zero
File size: 8,287 Bytes
f44aac9 9eec184 f44aac9 9eec184 96da637 9e8a876 96da637 9e8a876 96da637 9eec184 96da637 9eec184 96da637 9eec184 96da637 9eec184 96da637 f44aac9 9eec184 3b181a1 9eec184 3b181a1 9eec184 3b181a1 9eec184 3b181a1 9eec184 3b181a1 f44aac9 9eec184 f44aac9 7575503 f44aac9 9eec184 f44aac9 7575503 f44aac9 9eec184 f44aac9 52c63cf 9eec184 f44aac9 9eec184 f44aac9 9eec184 f44aac9 9392ef3 f44aac9 9e8a876 9392ef3 9e8a876 f44aac9 9392ef3 9eec184 9392ef3 9e8a876 9392ef3 f44aac9 dd8c015 f44aac9 dd8c015 f44aac9 52c63cf 9392ef3 f44aac9 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 | from __future__ import annotations
from dataclasses import dataclass, field
from typing import Any
import uuid
from hackathon_advisor.data import Project, ProjectIndex, WhitespaceItem
from hackathon_advisor.scoring import ScoreCard, score_idea
GOALS = [
"Off the Grid",
"Well-Tuned",
"Off-Brand",
"Llama Champion",
"Sharing is Caring",
"Field Notes",
]
GOAL_PROFILE_BY_ID = {
"Off the Grid": {
"label": "Local-first",
"description": "Favor ideas that work without proprietary inference APIs.",
},
"Well-Tuned": {
"label": "Trainable",
"description": "Shape good examples into a tiny fine-tune dataset.",
},
"Off-Brand": {
"label": "Distinct voice",
"description": "Leave room for an interface and tone people remember.",
},
"Llama Champion": {
"label": "llama.cpp path",
"description": "Prefer small-model choices that can run locally.",
},
"Sharing is Caring": {
"label": "Shareable artifact",
"description": "Make an output people can save, post, or compare.",
},
"Field Notes": {
"label": "Build notes",
"description": "Keep decisions easy to write up from the saved session.",
},
}
def goal_profiles() -> list[dict[str, str]]:
return [
{
"id": goal,
"label": GOAL_PROFILE_BY_ID[goal]["label"],
"description": GOAL_PROFILE_BY_ID[goal]["description"],
}
for goal in GOALS
]
def goal_label(goal: str) -> str:
return GOAL_PROFILE_BY_ID.get(goal, {}).get("label", goal)
def normalize_goals(raw_goals: Any, default: list[str] | None = None) -> list[str]:
if raw_goals is None:
return list(default or [])
if not isinstance(raw_goals, list):
return list(default or [])
goals: list[str] = []
seen: set[str] = set()
for raw_goal in raw_goals:
goal = str(raw_goal)
if goal in GOALS and goal not in seen:
goals.append(goal)
seen.add(goal)
return goals
def goals_from_state(state: dict[str, Any]) -> list[str]:
if "goals" not in state:
return GOALS[:3]
return normalize_goals(state.get("goals"), default=[])
@dataclass
class Idea:
id: str
title: str
pitch: str
goals: list[str] = field(default_factory=lambda: GOALS[:3])
score: dict | None = None
artifact: dict[str, Any] | None = None
def to_dict(self) -> dict:
return {
"id": self.id,
"title": self.title,
"pitch": self.pitch,
"goals": self.goals,
"score": self.score,
"artifact": self.artifact,
}
@dataclass(frozen=True)
class ToolEvent:
name: str
summary: str
def to_dict(self) -> dict:
return {"name": self.name, "summary": self.summary}
class AdvisorTools:
def __init__(self, index: ProjectIndex) -> None:
self.index = index
def list_projects(self, limit: int = 8) -> tuple[list[Project], ToolEvent]:
projects = self.index.top_projects(limit=limit)
return projects, ToolEvent("list_projects", f"Read {len(projects)} prominent Space cards.")
def search_projects(self, query: str, limit: int = 5) -> tuple[list[Project], ToolEvent]:
hits = self.index.search(query, limit=limit)
projects = [hit.project for hit in hits]
return projects, ToolEvent("search_projects", f"Found {len(projects)} nearby Space echoes.")
def find_whitespace(self, limit: int = 5) -> tuple[list[WhitespaceItem], ToolEvent]:
items = self.index.find_whitespace(limit=limit)
return items, ToolEvent("find_whitespace", f"Ranked {len(items)} under-explored regions.")
def save_idea(self, state: dict[str, Any], title: str, pitch: str) -> tuple[Idea, ToolEvent]:
ideas = [Idea(**item) for item in state.get("ideas", [])]
current_id = state.get("current_idea_id")
goals = goals_from_state(state)
idea = next((item for item in ideas if item.id == current_id), None)
if idea is None or _is_new_idea(idea, title, pitch):
idea = Idea(id=uuid.uuid4().hex[:8], title=title, pitch=pitch, goals=goals)
ideas.append(idea)
else:
idea.title = title
idea.pitch = pitch
idea.goals = goals
state["ideas"] = [item.to_dict() for item in ideas]
state["current_idea_id"] = idea.id
return idea, ToolEvent("save_idea", f"Wrote idea page '{idea.title}'.")
def score_idea(self, idea: Idea) -> tuple[ScoreCard, ToolEvent]:
score = score_idea(self.index, idea.title, idea.pitch, idea.goals)
idea.score = score.to_dict()
return score, ToolEvent("score_idea", f"Pressed a five-quadrant seal: {score.overall}/10.")
def make_plan(self, idea: Idea, profile: dict[str, Any] | None = None) -> tuple[list[str], ToolEvent]:
plan = [
"Lock a one-sentence promise and one test input that proves what is different.",
"Compare against the nearest echoes, then sharpen the part only this idea can own.",
"Build the smallest happy path: input, nearby project citations, score, and one shareable output.",
"Add one selected-goal feature only after the core loop is smooth enough to explain without narration.",
"Write build notes from the exact decisions, screenshots, and outputs.",
]
profile_steps = profile_plan_steps(profile)
if profile_steps:
plan[1:1] = profile_steps
if any("Well" in goal for goal in idea.goals):
plan.insert(
max(0, len(plan) - 1),
"Collect successful advisor examples before training a tiny LoRA.",
)
return plan, ToolEvent("make_plan", f"Drafted {len(plan)} build steps.")
def idea_from_text(text: str) -> tuple[str, str]:
cleaned = " ".join(text.strip().split())
if not cleaned:
return "Blank Page", "A project direction waiting for one concrete user and one concrete tension."
title = cleaned
for prefix in ("i want to build", "build", "make", "my idea is", "idea:"):
if cleaned.lower().startswith(prefix):
title = cleaned[len(prefix) :].strip(" :-")
break
pitch = cleaned
explicit_pitch = False
if " -- " in title:
title, pitch = (part.strip() for part in title.split(" -- ", 1))
explicit_pitch = True
raw_title = title
title = raw_title[:64].strip(" .") or "Unwritten Page"
if len(raw_title) > 64 or (not explicit_pitch and len(title) < len(cleaned)):
title = f"{title[:58].strip()}..."
return _display_title(title), pitch
def _is_new_idea(current: Idea, title: str, pitch: str) -> bool:
return current.title.strip().casefold() != title.strip().casefold() or current.pitch.strip() != pitch.strip()
def profile_plan_steps(profile: dict[str, Any] | None) -> list[str]:
if not isinstance(profile, dict):
return []
steps: list[str] = []
time = _short_profile_value(profile.get("time"))
skills = _short_profile_value(profile.get("skills"))
constraints = _short_profile_value(profile.get("constraints"))
preferences = _short_profile_value(profile.get("preferences"))
if time:
steps.append(f"Scope the first prototype to {time}; cut anything that cannot fit that window.")
if skills:
steps.append(f"Use your {skills} strength for the first working surface before adding new tooling.")
if constraints:
steps.append(f"Test the constraint early: {constraints}. Do this before polishing the artifact.")
if preferences:
steps.append(f"Shape the demo around {preferences} so the result feels intentional, not generic.")
return steps
def _short_profile_value(value: Any, limit: int = 84) -> str:
text = " ".join(str(value or "").split())
if len(text) <= limit:
return text
return text[: limit - 3].rstrip(" ,.;:") + "..."
def _display_title(title: str) -> str:
if not title:
return "Unwritten Page"
if any(char.isupper() or char.isdigit() for char in title):
return title[0].upper() + title[1:]
return title.capitalize()
|