Added markdown builder (codex)
This commit is contained in:
@@ -417,6 +417,8 @@ Telegram ботом, которому отправили видео).
|
|||||||
9. **Собрать финальный Markdown файл**
|
9. **Собрать финальный Markdown файл**
|
||||||
- Для сборки Markdown файла используются элементы, полученные на предыдщем
|
- Для сборки Markdown файла используются элементы, полученные на предыдщем
|
||||||
шаге, и хранимые в файле `structure_refined.json`
|
шаге, и хранимые в файле `structure_refined.json`
|
||||||
|
- Сборка выполняется локально, без использования ИИ. Ссылки на изображения
|
||||||
|
разрешаются по `image.event_id` через `events.json`.
|
||||||
- В итоге создаётся файл `output.md`, который может включать в себя ссылки
|
- В итоге создаётся файл `output.md`, который может включать в себя ссылки
|
||||||
на изображения из директории `images/` (относительно директории
|
на изображения из директории `images/` (относительно директории
|
||||||
промежуточных данных)
|
промежуточных данных)
|
||||||
|
|||||||
23
main.py
23
main.py
@@ -16,6 +16,7 @@ from asr_filter import AsrFilter, AsrFilterResult
|
|||||||
from asr_eventizer import AsrEventizer
|
from asr_eventizer import AsrEventizer
|
||||||
from structure_builder import Structure, StructureBuilder
|
from structure_builder import Structure, StructureBuilder
|
||||||
from structure_refiner import StructureRefiner
|
from structure_refiner import StructureRefiner
|
||||||
|
from markdown_builder import MarkdownBuilder
|
||||||
from windowizer import Windowizer
|
from windowizer import Windowizer
|
||||||
|
|
||||||
from agent import Agent
|
from agent import Agent
|
||||||
@@ -272,10 +273,23 @@ def on_structure_refiner(current_step: Step, input_data: dict | None) -> tuple[S
|
|||||||
result = refiner.refine(Structure(**input_data))
|
result = refiner.refine(Structure(**input_data))
|
||||||
return (Step.MARKDOWN_BUILDER, result.model_dump(mode="json"))
|
return (Step.MARKDOWN_BUILDER, result.model_dump(mode="json"))
|
||||||
|
|
||||||
|
def on_markdown_builder(current_step: Step, input_data: dict | None) -> tuple[Step | None, str | None]:
|
||||||
|
if os.path.isfile(WORKFLOW_DATA[current_step][0]):
|
||||||
|
logging.info("Skipping Markdown builder")
|
||||||
|
return (None, None)
|
||||||
|
if input_data is None:
|
||||||
|
logging.error("Can't build Markdown without input_data")
|
||||||
|
return (None, None)
|
||||||
|
with open("events.json", "rb") as f:
|
||||||
|
timeline = Timeline(**json.load(f))
|
||||||
|
logging.info("Building Markdown...")
|
||||||
|
builder = MarkdownBuilder(timeline)
|
||||||
|
return (None, builder.build(Structure(**input_data)))
|
||||||
|
|
||||||
#
|
#
|
||||||
# Main
|
# Main
|
||||||
#
|
#
|
||||||
WORKFLOW_DATA: dict[Step, tuple[str, Callable[[Step, dict | None], tuple[Step | None, dict | None]] | None]] = {
|
WORKFLOW_DATA: dict[Step, tuple[str, Callable[[Step, dict | None], tuple[Step | None, dict | str | None]] | None]] = {
|
||||||
Step.MEDIA_SEPARATION: ("audio.mp3", on_media_separation),
|
Step.MEDIA_SEPARATION: ("audio.mp3", on_media_separation),
|
||||||
Step.VOICE_RECOGNITION: ("asr_raw.json", on_voice_recognition),
|
Step.VOICE_RECOGNITION: ("asr_raw.json", on_voice_recognition),
|
||||||
Step.ASR_FILTER: ("asr.json", on_asr_filter),
|
Step.ASR_FILTER: ("asr.json", on_asr_filter),
|
||||||
@@ -284,7 +298,7 @@ WORKFLOW_DATA: dict[Step, tuple[str, Callable[[Step, dict | None], tuple[Step |
|
|||||||
Step.REFERENCE_RESOLVER: ("events.json", on_reference_resolver),
|
Step.REFERENCE_RESOLVER: ("events.json", on_reference_resolver),
|
||||||
Step.STRUCTURE_BUILDER: ("structure.json", on_structure_builder),
|
Step.STRUCTURE_BUILDER: ("structure.json", on_structure_builder),
|
||||||
Step.STRUCTURE_REFINER: ("structure_refined.json", on_structure_refiner),
|
Step.STRUCTURE_REFINER: ("structure_refined.json", on_structure_refiner),
|
||||||
Step.MARKDOWN_BUILDER: ("output.md", None)
|
Step.MARKDOWN_BUILDER: ("output.md", on_markdown_builder)
|
||||||
}
|
}
|
||||||
"""Information about workflow.
|
"""Information about workflow.
|
||||||
|
|
||||||
@@ -314,8 +328,11 @@ def main() -> None:
|
|||||||
logging.info(f"Executing step {step_to_do}")
|
logging.info(f"Executing step {step_to_do}")
|
||||||
step_to_do, ret = func(step_to_do, intermediate_result)
|
step_to_do, ret = func(step_to_do, intermediate_result)
|
||||||
if ret is not None:
|
if ret is not None:
|
||||||
|
with open(output_file_path, "w", encoding="utf-8") as f:
|
||||||
|
if isinstance(ret, str):
|
||||||
|
f.write(ret)
|
||||||
|
else:
|
||||||
intermediate_result = ret
|
intermediate_result = ret
|
||||||
with open(output_file_path, "w") as f:
|
|
||||||
json.dump(ret, f, ensure_ascii=False, indent=4)
|
json.dump(ret, f, ensure_ascii=False, indent=4)
|
||||||
logging.info(f"Intermediate results are saved {output_file_path}")
|
logging.info(f"Intermediate results are saved {output_file_path}")
|
||||||
# final report
|
# final report
|
||||||
|
|||||||
59
markdown_builder.py
Normal file
59
markdown_builder.py
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
from structure_builder import (
|
||||||
|
DefinitionElement,
|
||||||
|
HeadingElement,
|
||||||
|
ImageElement,
|
||||||
|
ImportantElement,
|
||||||
|
OrderedListElement,
|
||||||
|
ParagraphElement,
|
||||||
|
Structure,
|
||||||
|
StructureElement,
|
||||||
|
UnorderedListElement,
|
||||||
|
)
|
||||||
|
from utils import Event, Timeline
|
||||||
|
|
||||||
|
|
||||||
|
class MarkdownBuilder:
|
||||||
|
"""Render a document structure to Markdown without using AI."""
|
||||||
|
|
||||||
|
def __init__(self, timeline: Timeline) -> None:
|
||||||
|
self._events: dict[int, Event] = {}
|
||||||
|
for event in timeline.events:
|
||||||
|
if event.id in self._events:
|
||||||
|
raise ValueError(f"Duplicate event ID: {event.id}")
|
||||||
|
self._events[event.id] = event
|
||||||
|
|
||||||
|
def _render_image(self, element: ImageElement) -> str:
|
||||||
|
event = self._events.get(element.event_id)
|
||||||
|
if event is None or event.type != "vis" or not event.payload:
|
||||||
|
raise ValueError(
|
||||||
|
f"Image element references invalid visual event: {element.event_id}"
|
||||||
|
)
|
||||||
|
alt = event.text.replace("[", "").replace("]", "").replace("\n", " ")
|
||||||
|
return f""
|
||||||
|
|
||||||
|
def _render_element(self, element: StructureElement) -> str:
|
||||||
|
if isinstance(element, HeadingElement):
|
||||||
|
return f"{'#' * element.level} {element.text}"
|
||||||
|
if isinstance(element, ParagraphElement):
|
||||||
|
return element.text
|
||||||
|
if isinstance(element, UnorderedListElement):
|
||||||
|
return "\n".join(f"- {item}" for item in element.items)
|
||||||
|
if isinstance(element, OrderedListElement):
|
||||||
|
return "\n".join(
|
||||||
|
f"{index}. {item}"
|
||||||
|
for index, item in enumerate(element.items, start=1)
|
||||||
|
)
|
||||||
|
if isinstance(element, DefinitionElement):
|
||||||
|
return f"**{element.term}.** {element.text}"
|
||||||
|
if isinstance(element, ImportantElement):
|
||||||
|
text = f"**Важно:** {element.text}"
|
||||||
|
return "\n".join(f"> {line}" for line in text.splitlines())
|
||||||
|
if isinstance(element, ImageElement):
|
||||||
|
return self._render_image(element)
|
||||||
|
raise TypeError(f"Unsupported structure element: {type(element).__name__}")
|
||||||
|
|
||||||
|
def build(self, structure: Structure) -> str:
|
||||||
|
blocks = [self._render_element(element) for element in structure.elements]
|
||||||
|
if not blocks:
|
||||||
|
return ""
|
||||||
|
return "\n\n".join(blocks) + "\n"
|
||||||
Reference in New Issue
Block a user