Skip to content

Commit 9585f64

Browse files
committed
feat: add segment_names config + robust asset resolution
- Add segment_names map to Config for ID → full name mapping (e.g. "01" → "01-architecture") - Add resolve_segment_name() helper - Add _find_asset() for consistent file lookup across narration, audio, and recordings (exact name, ID prefix, glob fallback) - Refactor segments API and narration GET/PUT to use unified resolution — fixes narration detection for projects with named segments like tekton-dag Made-with: Cursor
1 parent f51bc39 commit 9585f64

File tree

2 files changed

+46
-39
lines changed

2 files changed

+46
-39
lines changed

src/docgen/config.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,15 @@ def segments_default(self) -> list[str]:
4444
def segments_all(self) -> list[str]:
4545
return self.raw.get("segments", {}).get("all", self.segments_default)
4646

47+
@property
48+
def segment_names(self) -> dict[str, str]:
49+
"""Map segment ID → full name stem, e.g. {"01": "01-architecture"}."""
50+
return self.raw.get("segment_names", {})
51+
52+
def resolve_segment_name(self, seg_id: str) -> str:
53+
"""Return the full name for a segment, falling back to the ID itself."""
54+
return self.segment_names.get(seg_id, seg_id)
55+
4756
@property
4857
def visual_map(self) -> dict[str, Any]:
4958
return self.raw.get("visual_map", {})

src/docgen/wizard.py

Lines changed: 37 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,23 @@ def generate_narration_via_llm(
169169
# Flask app factory
170170
# ---------------------------------------------------------------------------
171171

172+
def _find_asset(directory: Path, seg_name: str, seg_id: str, ext: str) -> Path | None:
173+
"""Find an asset file by segment name, then segment ID prefix, then glob."""
174+
if not directory.exists():
175+
return None
176+
exact = directory / f"{seg_name}{ext}"
177+
if exact.exists():
178+
return exact
179+
exact_id = directory / f"{seg_id}{ext}"
180+
if exact_id.exists():
181+
return exact_id
182+
for f in directory.glob(f"{seg_id}-*{ext}"):
183+
return f
184+
for f in directory.glob(f"{seg_id}*{ext}"):
185+
return f
186+
return None
187+
188+
172189
def create_app(config: Any | None = None) -> Flask:
173190
app = Flask(
174191
__name__,
@@ -277,34 +294,23 @@ def api_segments():
277294
state = load_state(base)
278295
result = []
279296
for seg_id in cfg.segments_all:
280-
narration_path = cfg.narration_dir / f"{seg_id}.md"
281-
audio_path = cfg.audio_dir / f"{seg_id}.mp3"
282-
283-
# Try to find the recording with any name prefix
284-
rec_found = None
285-
if cfg.recordings_dir.exists():
286-
for mp4 in cfg.recordings_dir.glob(f"{seg_id}*.mp4"):
287-
rec_found = mp4
288-
break
289-
if not rec_found:
290-
for mp4 in cfg.recordings_dir.glob(f"*{seg_id}*.mp4"):
291-
rec_found = mp4
292-
break
297+
seg_name = cfg.resolve_segment_name(seg_id)
298+
299+
narr_found = _find_asset(cfg.narration_dir, seg_name, seg_id, ".md")
300+
audio_found = _find_asset(cfg.audio_dir, seg_name, seg_id, ".mp3")
301+
rec_found = _find_asset(cfg.recordings_dir, seg_name, seg_id, ".mp4")
293302

294303
seg_state = state.get("segments", {}).get(seg_id, {})
295304
result.append({
296305
"id": seg_id,
306+
"name": seg_name,
297307
"status": seg_state.get("status", "draft"),
298308
"revision_notes": seg_state.get("revision_notes", ""),
299-
"has_narration": narration_path.exists(),
300-
"has_audio": audio_path.exists() or bool(
301-
list(cfg.audio_dir.glob(f"*{seg_id}*.mp3")) if cfg.audio_dir.exists() else []
302-
),
309+
"has_narration": narr_found is not None,
310+
"has_audio": audio_found is not None,
303311
"has_recording": rec_found is not None,
304-
"narration_path": str(narration_path.relative_to(base)) if narration_path.exists() else None,
305-
"audio_path": str(next(cfg.audio_dir.glob(f"*{seg_id}*.mp3")).relative_to(base))
306-
if cfg.audio_dir.exists() and list(cfg.audio_dir.glob(f"*{seg_id}*.mp3"))
307-
else None,
312+
"narration_path": str(narr_found.relative_to(base)) if narr_found else None,
313+
"audio_path": str(audio_found.relative_to(base)) if audio_found else None,
308314
"recording_path": str(rec_found.relative_to(base)) if rec_found else None,
309315
"visual_map": cfg.visual_map.get(seg_id, {}),
310316
})
@@ -317,18 +323,13 @@ def api_get_narration(segment_id: str):
317323
cfg = _cfg()
318324
if not cfg:
319325
return jsonify({"error": "no config"}), 400
320-
# Try exact match first, then glob
321-
candidates = [
322-
cfg.narration_dir / f"{segment_id}.md",
323-
]
324-
if cfg.narration_dir.exists():
325-
candidates.extend(cfg.narration_dir.glob(f"*{segment_id}*.md"))
326-
for p in candidates:
327-
if p.exists():
328-
return jsonify({
329-
"text": p.read_text(encoding="utf-8"),
330-
"path": str(p.relative_to(cfg.base_dir)),
331-
})
326+
seg_name = cfg.resolve_segment_name(segment_id)
327+
found = _find_asset(cfg.narration_dir, seg_name, segment_id, ".md")
328+
if found:
329+
return jsonify({
330+
"text": found.read_text(encoding="utf-8"),
331+
"path": str(found.relative_to(cfg.base_dir)),
332+
})
332333
return jsonify({"text": "", "path": None})
333334

334335
@app.route("/api/narration/<segment_id>", methods=["PUT"])
@@ -338,12 +339,9 @@ def api_put_narration(segment_id: str):
338339
return jsonify({"error": "no config"}), 400
339340
data = request.json or {}
340341
text = data.get("text", "")
341-
# Find or create narration file
342-
target = cfg.narration_dir / f"{segment_id}.md"
343-
if cfg.narration_dir.exists():
344-
for existing in cfg.narration_dir.glob(f"*{segment_id}*.md"):
345-
target = existing
346-
break
342+
seg_name = cfg.resolve_segment_name(segment_id)
343+
found = _find_asset(cfg.narration_dir, seg_name, segment_id, ".md")
344+
target = found or (cfg.narration_dir / f"{seg_name}.md")
347345
cfg.narration_dir.mkdir(parents=True, exist_ok=True)
348346
target.write_text(text, encoding="utf-8")
349347
return jsonify({"ok": True, "path": str(target.relative_to(cfg.base_dir))})

0 commit comments

Comments
 (0)