fabricatio 0.3.14.dev8__tar.gz → 0.3.15.dev4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fabricatio-0.3.15.dev4/.github/workflows/build-package.yaml +130 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/PKG-INFO +2 -1
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/README.md +1 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/write_article/write_article.py +44 -8
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/pyproject.toml +1 -1
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/actions/article.py +115 -19
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/models/extra/article_base.py +96 -32
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/models/extra/article_main.py +8 -3
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/models/generic.py +1 -1
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/rust.pyi +13 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/src/config.rs +1 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/src/typst_tools.rs +25 -1
- fabricatio-0.3.15.dev4/templates/built-in/research_content_summary.hbs +17 -0
- fabricatio-0.3.15.dev4/uv.lock +1917 -0
- fabricatio-0.3.14.dev8/.github/workflows/build-package.yaml +0 -97
- fabricatio-0.3.14.dev8/templates.tar.gz +0 -0
- fabricatio-0.3.14.dev8/uv.lock +0 -1917
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/.github/workflows/ruff.yaml +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/.github/workflows/tests.yaml +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/.gitignore +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/Cargo.lock +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/Cargo.toml +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/LICENSE +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/Makefile +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/build.rs +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/correct/correct.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/correct/correct_loop.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/extract_and_inject/.gitignore +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/extract_and_inject/article_rag.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/extract_and_inject/ask.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/extract_and_inject/chunk_article.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/extract_and_inject/extract_and_inject.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/extract_article/extract.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/llm_usages/llm_usage.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/make_a_rating/rating.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/make_diary/commits.json +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/make_diary/diary.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/minor/hello_fabricatio.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/minor/write_a_poem.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/propose_task/.gitignore +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/propose_task/propose.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/reviewer/censor.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/reviewer/review.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/rules/.gitignore +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/rules/draft_ruleset.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/search_bibtex/.gitignore +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/search_bibtex/search.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/simple_chat/chat.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/simple_rag/simple_rag.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/task_handle/handle_task.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/write_article/.gitignore +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/write_article/article_rag.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/write_article/post_process.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/write_outline/.gitignore +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/write_outline/write_outline.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/write_outline/write_outline_corrected.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/proto/tei.proto +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/__init__.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/actions/__init__.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/actions/article_rag.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/actions/fs.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/actions/output.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/actions/rag.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/actions/rules.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/capabilities/__init__.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/capabilities/advanced_judge.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/capabilities/advanced_rag.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/capabilities/censor.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/capabilities/check.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/capabilities/correct.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/capabilities/extract.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/capabilities/persist.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/capabilities/propose.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/capabilities/rag.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/capabilities/rating.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/capabilities/review.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/capabilities/task.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/decorators.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/emitter.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/fs/__init__.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/fs/curd.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/fs/readers.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/journal.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/models/action.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/models/adv_kwargs_types.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/models/extra/__init__.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/models/extra/advanced_judge.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/models/extra/aricle_rag.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/models/extra/article_essence.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/models/extra/article_outline.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/models/extra/article_proposal.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/models/extra/patches.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/models/extra/problem.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/models/extra/rag.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/models/extra/rule.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/models/kwargs_types.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/models/role.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/models/task.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/models/tool.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/models/usages.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/parser.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/py.typed +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/toolboxes/__init__.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/toolboxes/arithmetic.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/toolboxes/fs.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/utils.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/workflows/__init__.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/workflows/articles.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/workflows/rag.py +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/src/bib_tools.rs +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/src/event.rs +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/src/hash.rs +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/src/hbs_helpers.rs +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/src/language.rs +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/src/lib.rs +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/src/macro_utils/Cargo.toml +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/src/macro_utils/src/lib.rs +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/src/tei_client.rs +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/src/templates.rs +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/src/typst_conversion/Cargo.toml +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/src/typst_conversion/src/lib.rs +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/src/word_split.rs +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/as_prompt.hbs +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/binary-exploitation-ctf-solver.hbs +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/chap_summary.hbs +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/check_string.hbs +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/claude-xml.hbs +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/clean-up-code.hbs +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/co_validation.hbs +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/create_json_obj.hbs +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/cryptography-ctf-solver.hbs +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/dependencies.hbs +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/document-the-code.hbs +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/draft_rating_criteria.hbs +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/draft_rating_manual.hbs +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/draft_rating_weights_klee.hbs +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/draft_tool_usage_code.hbs +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/extract.hbs +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/extract_criteria_from_reasons.hbs +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/extract_reasons_from_examples.hbs +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/find-security-vulnerabilities.hbs +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/fix-bugs.hbs +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/fix_troubled_obj.hbs +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/fix_troubled_string.hbs +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/generic_string.hbs +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/improve-performance.hbs +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/liststr.hbs +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/make_choice.hbs +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/make_judgment.hbs +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/pathstr.hbs +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/rate_fine_grind.hbs +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/refactor.hbs +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/refined_query.hbs +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/retrieved_display.hbs +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/reverse-engineering-ctf-solver.hbs +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/review_string.hbs +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/rule_requirement.hbs +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/ruleset_requirement_breakdown.hbs +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/task_briefing.hbs +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/web-ctf-solver.hbs +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/write-git-commit.hbs +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/write-github-pull-request.hbs +0 -0
- {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/write-github-readme.hbs +0 -0
@@ -0,0 +1,130 @@
|
|
1
|
+
name: Build and Release
|
2
|
+
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
branches:
|
6
|
+
- master
|
7
|
+
pull_request:
|
8
|
+
branches:
|
9
|
+
- master
|
10
|
+
|
11
|
+
jobs:
|
12
|
+
determine_changes:
|
13
|
+
name: Determine Release Necessity & Version Details
|
14
|
+
runs-on: ubuntu-latest
|
15
|
+
outputs:
|
16
|
+
version_changed: ${{ steps.check_version_change.outputs.VERSION_CHANGED }}
|
17
|
+
current_version: ${{ steps.get_version.outputs.CURRENT_VERSION }}
|
18
|
+
is_prerelease: ${{ steps.check_prerelease.outputs.IS_PRERELEASE }}
|
19
|
+
steps:
|
20
|
+
- name: Checkout repository
|
21
|
+
uses: actions/checkout@v4
|
22
|
+
- name: Get current version from pyproject.toml
|
23
|
+
id: get_version
|
24
|
+
run: |
|
25
|
+
CURRENT_VERSION_RAW=$(grep '^version' pyproject.toml | cut -d '"' -f 2)
|
26
|
+
echo "CURRENT_VERSION=v$CURRENT_VERSION_RAW" >> $GITHUB_OUTPUT
|
27
|
+
shell: bash
|
28
|
+
- name: Get latest git tag
|
29
|
+
uses: JinoArch/get-latest-tag@latest
|
30
|
+
id: tag
|
31
|
+
- name: Check if version has changed against latest tag
|
32
|
+
id: check_version_change
|
33
|
+
run: |
|
34
|
+
LATEST_TAG=${{ steps.tag.outputs.latestTag }}
|
35
|
+
VERSION_FROM_PYPROJECT=${{ steps.get_version.outputs.CURRENT_VERSION }}
|
36
|
+
echo "Latest git tag is $LATEST_TAG"
|
37
|
+
echo "Current version (from pyproject.toml, prefixed with 'v') is $VERSION_FROM_PYPROJECT"
|
38
|
+
if [ "$LATEST_TAG" != "$VERSION_FROM_PYPROJECT" ]; then
|
39
|
+
echo "VERSION_CHANGED=true" >> $GITHUB_OUTPUT
|
40
|
+
else
|
41
|
+
echo "VERSION_CHANGED=false" >> $GITHUB_OUTPUT
|
42
|
+
fi
|
43
|
+
shell: bash
|
44
|
+
- name: Check if current version is a prerelease
|
45
|
+
id: check_prerelease
|
46
|
+
run: |
|
47
|
+
VERSION_FROM_PYPROJECT=${{ steps.get_version.outputs.CURRENT_VERSION }}
|
48
|
+
if [[ "$VERSION_FROM_PYPROJECT" == *"-"* ]]; then # e.g., v1.0.0-alpha
|
49
|
+
echo "IS_PRERELEASE=true" >> $GITHUB_OUTPUT
|
50
|
+
else
|
51
|
+
echo "IS_PRERELEASE=false" >> $GITHUB_OUTPUT
|
52
|
+
fi
|
53
|
+
shell: bash
|
54
|
+
|
55
|
+
|
56
|
+
create-release:
|
57
|
+
if: needs.determine_changes.outputs.version_changed == 'true'
|
58
|
+
name: Create Release
|
59
|
+
needs: determine_changes
|
60
|
+
runs-on: ubuntu-latest
|
61
|
+
steps:
|
62
|
+
- name: Checkout repository
|
63
|
+
uses: actions/checkout@v4
|
64
|
+
- name: Packing templates
|
65
|
+
run: |
|
66
|
+
tar -czf templates.tar.gz templates
|
67
|
+
- name: Create Release and upload assets
|
68
|
+
id: create_release
|
69
|
+
uses: softprops/action-gh-release@v2
|
70
|
+
with:
|
71
|
+
tag_name: ${{ needs.determine_changes.outputs.current_version }}
|
72
|
+
name: ${{ needs.determine_changes.outputs.current_version }}
|
73
|
+
files: |
|
74
|
+
templates.tar.gz
|
75
|
+
prerelease: ${{ needs.determine_changes.outputs.is_prerelease }}
|
76
|
+
generate_release_notes: true
|
77
|
+
env:
|
78
|
+
GITHUB_TOKEN: ${{ secrets.PAT }}
|
79
|
+
|
80
|
+
|
81
|
+
|
82
|
+
check-and-release:
|
83
|
+
name: Build, Package, and Release
|
84
|
+
needs:
|
85
|
+
- create-release
|
86
|
+
- determine_changes
|
87
|
+
runs-on: ${{ matrix.os }}
|
88
|
+
strategy:
|
89
|
+
matrix:
|
90
|
+
os: [ windows-latest, ubuntu-latest ]
|
91
|
+
python-version: [ "3.12", "3.13" ]
|
92
|
+
steps:
|
93
|
+
- name: Checkout repository
|
94
|
+
uses: actions/checkout@v4
|
95
|
+
|
96
|
+
- name: Install the latest version of uv
|
97
|
+
uses: astral-sh/setup-uv@v6.0.1
|
98
|
+
with:
|
99
|
+
version: "latest"
|
100
|
+
- name: Install nightly rust
|
101
|
+
run: |
|
102
|
+
rustup default nightly
|
103
|
+
- name: Install Protoc
|
104
|
+
uses: arduino/setup-protoc@v3
|
105
|
+
|
106
|
+
- name: Install deps
|
107
|
+
run: |
|
108
|
+
uv sync --no-install-project --all-extras --index https://pypi.org/simple -p ${{ matrix.python-version }}
|
109
|
+
|
110
|
+
- name: Build
|
111
|
+
run: |
|
112
|
+
make PY=${{ matrix.python-version }}
|
113
|
+
|
114
|
+
- name: Upload assets
|
115
|
+
uses: softprops/action-gh-release@v2
|
116
|
+
with:
|
117
|
+
tag_name: ${{ needs.determine_changes.outputs.current_version }}
|
118
|
+
files: |
|
119
|
+
dist/*.whl
|
120
|
+
env:
|
121
|
+
GITHUB_TOKEN: ${{ secrets.PAT }}
|
122
|
+
|
123
|
+
- name: Upload to PyPI
|
124
|
+
if: needs.determine_changes.outputs.version_changed == 'true'
|
125
|
+
run: |
|
126
|
+
make publish PY=${{ matrix.python-version }}
|
127
|
+
env:
|
128
|
+
MATURIN_USERNAME: __token__
|
129
|
+
MATURIN_PASSWORD: ${{ secrets.PYPI_TOKEN }}
|
130
|
+
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: fabricatio
|
3
|
-
Version: 0.3.
|
3
|
+
Version: 0.3.15.dev4
|
4
4
|
Classifier: License :: OSI Approved :: MIT License
|
5
5
|
Classifier: Programming Language :: Rust
|
6
6
|
Classifier: Programming Language :: Python :: 3.12
|
@@ -185,4 +185,5 @@ Special thanks to the contributors and maintainers of:
|
|
185
185
|
- [PyO3](https://github.com/PyO3/pyo3)
|
186
186
|
- [Maturin](https://github.com/PyO3/maturin)
|
187
187
|
- [Handlebars.rs](https://github.com/sunng87/handlebars-rust)
|
188
|
+
- [LiteLLM](https://github.com/BerriAI/litellm)
|
188
189
|
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
import asyncio
|
4
4
|
from pathlib import Path
|
5
|
-
from typing import Optional
|
5
|
+
from typing import List, Optional
|
6
6
|
|
7
7
|
import typer
|
8
8
|
from fabricatio import Event, WorkFlow, logger
|
@@ -12,11 +12,10 @@ from fabricatio.actions.article import (
|
|
12
12
|
GenerateArticleProposal,
|
13
13
|
GenerateInitialOutline,
|
14
14
|
WriteChapterSummary,
|
15
|
+
WriteResearchContentSummary,
|
15
16
|
)
|
16
17
|
from fabricatio.actions.article_rag import ArticleConsultRAG, WriteArticleContentRAG
|
17
18
|
from fabricatio.actions.output import DumpFinalizedOutput, PersistentAll, RenderedDump
|
18
|
-
from fabricatio.fs import safe_text_read
|
19
|
-
from fabricatio.models.extra.article_main import Article
|
20
19
|
from fabricatio.models.extra.article_outline import ArticleOutline
|
21
20
|
from fabricatio.models.task import Task
|
22
21
|
from fabricatio.models.usages import LLMUsage
|
@@ -95,6 +94,11 @@ Role(
|
|
95
94
|
description="Generate chapter summary based on given article outline. dump the outline to the given path. in typst format.",
|
96
95
|
steps=(WriteChapterSummary().to_task_output(),),
|
97
96
|
),
|
97
|
+
Event.quick_instantiate(ns6 := "resc-suma"): WorkFlow(
|
98
|
+
name="Research Content Summary",
|
99
|
+
description="Generate research content summary based on given article outline. dump the outline to the given path. in typst format.",
|
100
|
+
steps=(WriteResearchContentSummary().to_task_output(),),
|
101
|
+
),
|
98
102
|
},
|
99
103
|
)
|
100
104
|
|
@@ -216,21 +220,53 @@ def suma(
|
|
216
220
|
article_path: Path = typer.Option( # noqa: B008
|
217
221
|
Path("article.typ"), "-a", "--article-path", help="Path to the article file."
|
218
222
|
),
|
219
|
-
|
223
|
+
skip_chapters: List[str] = typer.Option( # noqa: B008
|
224
|
+
[], "-s", "--skip-chapters", help="Chapters to skip."
|
225
|
+
),
|
226
|
+
suma_title: str = typer.Option("Chapter Summary", "-t", "--suma-title", help="Title of the chapter summary."),
|
227
|
+
summary_word_count: int = typer.Option(220, "-w", "--word-count", help="Word count for the summary."),
|
220
228
|
) -> None:
|
221
229
|
"""Write chap summary based on given article."""
|
222
|
-
|
230
|
+
_ = ok(
|
223
231
|
asyncio.run(
|
224
232
|
Task(name="write an article")
|
225
233
|
.update_init_context(
|
226
|
-
|
227
|
-
|
234
|
+
article_path=article_path,
|
235
|
+
summary_title=suma_title,
|
236
|
+
skip_chapters=skip_chapters,
|
237
|
+
summary_word_count=summary_word_count,
|
228
238
|
)
|
229
239
|
.delegate(ns5)
|
230
240
|
),
|
231
241
|
"Failed to generate an article ",
|
232
242
|
)
|
233
|
-
logger.success(f"The outline is saved in:\n{
|
243
|
+
logger.success(f"The outline is saved in:\n{article_path.as_posix()}")
|
244
|
+
|
245
|
+
|
246
|
+
@app.command()
|
247
|
+
def rcsuma(
|
248
|
+
article_path: Path = typer.Option( # noqa: B008
|
249
|
+
Path("article.typ"), "-a", "--article-path", help="Path to the article file."
|
250
|
+
),
|
251
|
+
suma_title: str = typer.Option("Research Content", "-t", "--suma-title", help="Title of the summary."),
|
252
|
+
summary_word_count: int = typer.Option(220, "-w", "--word-count", help="Word count for the summary."),
|
253
|
+
paragraph_count: int = typer.Option(1, "-p", "--paragraph-count", help="Number of paragraphs for the summary."),
|
254
|
+
) -> None:
|
255
|
+
"""Write research summary based on given article."""
|
256
|
+
_ = ok(
|
257
|
+
asyncio.run(
|
258
|
+
Task(name="write an article")
|
259
|
+
.update_init_context(
|
260
|
+
article_path=article_path,
|
261
|
+
summary_title=suma_title,
|
262
|
+
summary_word_count=summary_word_count,
|
263
|
+
paragraph_count=paragraph_count,
|
264
|
+
)
|
265
|
+
.delegate(ns6)
|
266
|
+
),
|
267
|
+
"Failed to generate an article ",
|
268
|
+
)
|
269
|
+
logger.success(f"The outline is saved in:\n{article_path.as_posix()}")
|
234
270
|
|
235
271
|
|
236
272
|
if __name__ == "__main__":
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
from asyncio import gather
|
4
4
|
from pathlib import Path
|
5
|
-
from typing import Callable, List, Optional
|
5
|
+
from typing import Callable, ClassVar, List, Optional
|
6
6
|
|
7
7
|
from more_itertools import filter_map
|
8
8
|
from pydantic import Field
|
@@ -15,14 +15,14 @@ from fabricatio.fs import dump_text, safe_text_read
|
|
15
15
|
from fabricatio.journal import logger
|
16
16
|
from fabricatio.models.action import Action
|
17
17
|
from fabricatio.models.extra.article_essence import ArticleEssence
|
18
|
-
from fabricatio.models.extra.article_main import Article
|
18
|
+
from fabricatio.models.extra.article_main import Article, ArticleChapter, ArticleSubsection
|
19
19
|
from fabricatio.models.extra.article_outline import ArticleOutline
|
20
20
|
from fabricatio.models.extra.article_proposal import ArticleProposal
|
21
21
|
from fabricatio.models.extra.rule import RuleSet
|
22
22
|
from fabricatio.models.kwargs_types import ValidateKwargs
|
23
23
|
from fabricatio.models.task import Task
|
24
24
|
from fabricatio.models.usages import LLMUsage
|
25
|
-
from fabricatio.rust import CONFIG, TEMPLATE_MANAGER, BibManager, detect_language
|
25
|
+
from fabricatio.rust import CONFIG, TEMPLATE_MANAGER, BibManager, detect_language, word_count
|
26
26
|
from fabricatio.utils import ok, wrapp_in_block
|
27
27
|
|
28
28
|
|
@@ -277,43 +277,139 @@ class LoadArticle(Action):
|
|
277
277
|
class WriteChapterSummary(Action, LLMUsage):
|
278
278
|
"""Write the chapter summary."""
|
279
279
|
|
280
|
-
|
280
|
+
ctx_override: ClassVar[bool] = True
|
281
281
|
|
282
282
|
paragraph_count: int = 1
|
283
|
+
"""The number of paragraphs to generate in the chapter summary."""
|
283
284
|
|
284
|
-
summary_word_count: int =
|
285
|
-
|
285
|
+
summary_word_count: int = 120
|
286
|
+
"""The number of words to use in each chapter summary."""
|
287
|
+
output_key: str = "summarized_article"
|
288
|
+
"""The key under which the summarized article will be stored in the output."""
|
286
289
|
summary_title: str = "Chapter Summary"
|
287
|
-
|
290
|
+
"""The title to be used for the generated chapter summary section."""
|
291
|
+
|
292
|
+
skip_chapters: List[str] = Field(default_factory=list)
|
293
|
+
"""A list of chapter titles to skip during summary generation."""
|
294
|
+
|
295
|
+
async def _execute(self, article_path: Path, **cxt) -> Article:
|
296
|
+
article = Article.from_article_file(article_path, article_path.stem)
|
297
|
+
|
298
|
+
chaps = [c for c in article.chapters if c.title not in self.skip_chapters]
|
299
|
+
|
300
|
+
retained_chapters = []
|
301
|
+
# Count chapters before filtering based on section presence,
|
302
|
+
# chaps at this point has already been filtered by self.skip_chapters
|
303
|
+
initial_chaps_for_summary_step_count = len(chaps)
|
304
|
+
|
305
|
+
for chapter_candidate in chaps:
|
306
|
+
if chapter_candidate.sections: # Check if the sections list is non-empty
|
307
|
+
retained_chapters.append(chapter_candidate)
|
308
|
+
else:
|
309
|
+
# Log c warning for each chapter skipped due to lack of sections
|
310
|
+
logger.warning(
|
311
|
+
f"Chapter '{chapter_candidate.title}' has no sections and will be skipped for summary generation."
|
312
|
+
)
|
313
|
+
|
314
|
+
chaps = retained_chapters # Update chaps to only include chapters with sections
|
288
315
|
|
289
|
-
|
290
|
-
|
316
|
+
# If chaps is now empty, but there were chapters to consider at the start of this step,
|
317
|
+
# log c specific warning.
|
318
|
+
if not chaps and initial_chaps_for_summary_step_count > 0:
|
319
|
+
raise ValueError("No chapters with sections were found. Please check your input data.")
|
291
320
|
|
321
|
+
# This line was part of the original selection.
|
322
|
+
# It will now log the titles of the chapters that are actually being processed (those with sections).
|
323
|
+
# If 'chaps' is empty, this will result in logger.info(""), which is acceptable.
|
324
|
+
logger.info(";".join(a.title for a in chaps))
|
292
325
|
ret = [
|
293
|
-
|
326
|
+
ArticleSubsection.from_typst_code(self.summary_title, raw)
|
294
327
|
for raw in (
|
295
328
|
await self.aask(
|
296
329
|
TEMPLATE_MANAGER.render_template(
|
297
330
|
CONFIG.templates.chap_summary_template,
|
298
331
|
[
|
299
332
|
{
|
300
|
-
"chapter":
|
301
|
-
"title":
|
302
|
-
"language":
|
333
|
+
"chapter": c.to_typst_code(),
|
334
|
+
"title": c.title,
|
335
|
+
"language": c.language,
|
303
336
|
"summary_word_count": self.summary_word_count,
|
304
337
|
"paragraph_count": self.paragraph_count,
|
305
338
|
}
|
306
|
-
for
|
339
|
+
for c in chaps
|
307
340
|
],
|
308
341
|
)
|
309
342
|
)
|
310
343
|
)
|
311
344
|
]
|
312
345
|
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
346
|
+
for c, n in zip(chaps, ret, strict=True):
|
347
|
+
c: ArticleChapter
|
348
|
+
n: ArticleSubsection
|
349
|
+
if c.sections[-1].title == self.summary_title:
|
350
|
+
logger.debug(f"Removing old summary `{self.summary_title}` at {c.title}")
|
351
|
+
c.sections.pop()
|
352
|
+
|
353
|
+
c.sections[-1].subsections.append(n)
|
354
|
+
|
355
|
+
article.update_article_file(article_path)
|
356
|
+
|
357
|
+
dump_text(
|
358
|
+
article_path, safe_text_read(article_path).replace(f"=== {self.summary_title}", f"== {self.summary_title}")
|
359
|
+
)
|
360
|
+
return article
|
361
|
+
|
362
|
+
|
363
|
+
class WriteResearchContentSummary(Action, LLMUsage):
|
364
|
+
"""Write the research content summary."""
|
365
|
+
|
366
|
+
ctx_override: ClassVar[bool] = True
|
367
|
+
summary_word_count: int = 160
|
368
|
+
"""The number of words to use in the research content summary."""
|
369
|
+
|
370
|
+
output_key: str = "summarized_article"
|
371
|
+
"""The key under which the summarized article will be stored in the output."""
|
372
|
+
|
373
|
+
summary_title: str = "Research Content"
|
374
|
+
"""The title to be used for the generated research content summary section."""
|
375
|
+
|
376
|
+
paragraph_count: int = 1
|
377
|
+
"""The number of paragraphs to generate in the research content summary."""
|
378
|
+
|
379
|
+
async def _execute(self, article_path: Path, **cxt) -> Article:
|
380
|
+
article = Article.from_article_file(article_path, article_path.stem)
|
381
|
+
if not article.chapters:
|
382
|
+
raise ValueError("No chapters found in the article.")
|
383
|
+
chap_1 = article.chapters[0]
|
384
|
+
if not chap_1.sections:
|
385
|
+
raise ValueError("No sections found in the first chapter of the article.")
|
386
|
+
|
387
|
+
outline = article.extrac_outline()
|
388
|
+
suma: str = await self.aask(
|
389
|
+
TEMPLATE_MANAGER.render_template(
|
390
|
+
CONFIG.templates.research_content_summary_template,
|
391
|
+
{
|
392
|
+
"title": outline.title,
|
393
|
+
"outline": outline.to_typst_code(),
|
394
|
+
"language": detect_language(self.summary_title),
|
395
|
+
"summary_word_count": self.summary_word_count,
|
396
|
+
"paragraph_count": self.paragraph_count,
|
397
|
+
},
|
317
398
|
)
|
399
|
+
)
|
400
|
+
logger.success(
|
401
|
+
f"{self.summary_title}|Wordcount: {word_count(suma)}|Expected: {self.summary_word_count}\n{suma}"
|
402
|
+
)
|
403
|
+
|
404
|
+
if chap_1.sections[-1].title == self.summary_title:
|
405
|
+
# remove old
|
406
|
+
logger.debug(f"Removing old summary `{self.summary_title}`")
|
407
|
+
chap_1.sections.pop()
|
318
408
|
|
319
|
-
|
409
|
+
chap_1.sections[-1].subsections.append(ArticleSubsection.from_typst_code(self.summary_title, suma))
|
410
|
+
|
411
|
+
article.update_article_file(article_path)
|
412
|
+
dump_text(
|
413
|
+
article_path, safe_text_read(article_path).replace(f"=== {self.summary_title}", f"== {self.summary_title}")
|
414
|
+
)
|
415
|
+
return article
|
{fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/models/extra/article_base.py
RENAMED
@@ -21,7 +21,15 @@ from fabricatio.models.generic import (
|
|
21
21
|
Titled,
|
22
22
|
WordCount,
|
23
23
|
)
|
24
|
-
from fabricatio.rust import
|
24
|
+
from fabricatio.rust import (
|
25
|
+
detect_language,
|
26
|
+
extract_body,
|
27
|
+
replace_thesis_body,
|
28
|
+
split_out_metadata,
|
29
|
+
strip_comment,
|
30
|
+
to_metadata,
|
31
|
+
word_count,
|
32
|
+
)
|
25
33
|
from fabricatio.utils import fallback_kwargs, ok
|
26
34
|
from pydantic import Field
|
27
35
|
|
@@ -52,10 +60,31 @@ class ArticleMetaData(SketchedAble, Described, WordCount, Titled, Language):
|
|
52
60
|
aims: List[str]
|
53
61
|
"""List of writing aims of the research component in academic style."""
|
54
62
|
|
63
|
+
_unstructured_body: str = ""
|
64
|
+
"""Store the source of the unknown information."""
|
65
|
+
|
55
66
|
@property
|
56
67
|
def typst_metadata_comment(self) -> str:
|
57
68
|
"""Generates a comment for the metadata of the article component."""
|
58
|
-
|
69
|
+
data = self.model_dump(
|
70
|
+
include={"description", "aims", "expected_word_count"},
|
71
|
+
by_alias=True,
|
72
|
+
)
|
73
|
+
return to_metadata({k: v for k, v in data.items() if v})
|
74
|
+
|
75
|
+
@property
|
76
|
+
def unstructured_body(self) -> str:
|
77
|
+
"""Returns the unstructured body of the article component."""
|
78
|
+
return self._unstructured_body
|
79
|
+
|
80
|
+
def update_unstructured_body[S: "ArticleMetaData"](self: S, body: str) -> S:
|
81
|
+
"""Update the unstructured body of the article component."""
|
82
|
+
self._unstructured_body = body
|
83
|
+
return self
|
84
|
+
|
85
|
+
@property
|
86
|
+
def language(self) -> str:
|
87
|
+
return detect_language(self.title)
|
59
88
|
|
60
89
|
|
61
90
|
class FromTypstCode(ArticleMetaData):
|
@@ -67,13 +96,8 @@ class FromTypstCode(ArticleMetaData):
|
|
67
96
|
data, body = split_out_metadata(body)
|
68
97
|
|
69
98
|
return cls(
|
70
|
-
heading=title,
|
71
|
-
**fallback_kwargs(
|
72
|
-
data or {},
|
73
|
-
elaboration="",
|
74
|
-
expected_word_count=word_count(body),
|
75
|
-
aims=[],
|
76
|
-
),
|
99
|
+
heading=title.strip(),
|
100
|
+
**fallback_kwargs(data or {}, elaboration="", expected_word_count=word_count(body), aims=[]),
|
77
101
|
**kwargs,
|
78
102
|
)
|
79
103
|
|
@@ -83,7 +107,7 @@ class ToTypstCode(ArticleMetaData):
|
|
83
107
|
|
84
108
|
def to_typst_code(self) -> str:
|
85
109
|
"""Converts the component into a Typst code snippet for rendering."""
|
86
|
-
return f"{self.title}\n{self.typst_metadata_comment}\n"
|
110
|
+
return f"{self.title}\n{self.typst_metadata_comment}\n\n{self._unstructured_body}"
|
87
111
|
|
88
112
|
|
89
113
|
class ArticleOutlineBase(
|
@@ -151,12 +175,16 @@ class SectionBase[T: SubSectionBase](ArticleOutlineBase):
|
|
151
175
|
@classmethod
|
152
176
|
def from_typst_code(cls, title: str, body: str, **kwargs) -> Self:
|
153
177
|
"""Creates an Article object from the given Typst code."""
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
178
|
+
raw = extract_sections(body, level=3, section_char="=")
|
179
|
+
|
180
|
+
return (
|
181
|
+
super()
|
182
|
+
.from_typst_code(
|
183
|
+
title,
|
184
|
+
body,
|
185
|
+
subsections=[cls.child_type.from_typst_code(*pack) for pack in raw],
|
186
|
+
)
|
187
|
+
.update_unstructured_body("" if raw else strip_comment(body))
|
160
188
|
)
|
161
189
|
|
162
190
|
def resolve_update_conflict(self, other: Self) -> str:
|
@@ -191,6 +219,11 @@ class SectionBase[T: SubSectionBase](ArticleOutlineBase):
|
|
191
219
|
return f"Section `{self.title}` contains no subsections, expected at least one, but got 0, you can add one or more as needed."
|
192
220
|
return ""
|
193
221
|
|
222
|
+
@property
|
223
|
+
def exact_word_count(self) -> int:
|
224
|
+
"""Returns the exact word count of the article section outline."""
|
225
|
+
return sum(a.exact_word_count for a in self.subsections)
|
226
|
+
|
194
227
|
|
195
228
|
class ChapterBase[T: SectionBase](ArticleOutlineBase):
|
196
229
|
"""Base class for article chapters."""
|
@@ -206,12 +239,16 @@ class ChapterBase[T: SectionBase](ArticleOutlineBase):
|
|
206
239
|
@classmethod
|
207
240
|
def from_typst_code(cls, title: str, body: str, **kwargs) -> Self:
|
208
241
|
"""Creates an Article object from the given Typst code."""
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
242
|
+
raw_sec = extract_sections(body, level=2, section_char="=")
|
243
|
+
|
244
|
+
return (
|
245
|
+
super()
|
246
|
+
.from_typst_code(
|
247
|
+
title,
|
248
|
+
body,
|
249
|
+
sections=[cls.child_type.from_typst_code(*pack) for pack in raw_sec],
|
250
|
+
)
|
251
|
+
.update_unstructured_body("" if raw_sec else strip_comment(body))
|
215
252
|
)
|
216
253
|
|
217
254
|
def resolve_update_conflict(self, other: Self) -> str:
|
@@ -243,6 +280,15 @@ class ChapterBase[T: SectionBase](ArticleOutlineBase):
|
|
243
280
|
return f"Chapter `{self.title}` contains no sections, expected at least one, but got 0, you can add one or more as needed."
|
244
281
|
return ""
|
245
282
|
|
283
|
+
@property
|
284
|
+
def exact_word_count(self) -> int:
|
285
|
+
"""Calculates the total word count across all sections in the chapter.
|
286
|
+
|
287
|
+
Returns:
|
288
|
+
int: The cumulative word count of all sections.
|
289
|
+
"""
|
290
|
+
return sum(a.exact_word_count for a in self.sections)
|
291
|
+
|
246
292
|
|
247
293
|
class ArticleBase[T: ChapterBase](FinalizedDumpAble, AsPrompt, FromTypstCode, ToTypstCode, ABC):
|
248
294
|
"""Base class for article outlines."""
|
@@ -263,15 +309,33 @@ class ArticleBase[T: ChapterBase](FinalizedDumpAble, AsPrompt, FromTypstCode, To
|
|
263
309
|
|
264
310
|
child_type: ClassVar[Type[ChapterBase]]
|
265
311
|
|
312
|
+
@property
|
313
|
+
def language(self) -> str:
|
314
|
+
if self.title:
|
315
|
+
return super().language
|
316
|
+
return self.chapters[0].language
|
317
|
+
|
318
|
+
@property
|
319
|
+
def exact_word_count(self) -> int:
|
320
|
+
"""Calculates the total word count across all chapters in the article.
|
321
|
+
|
322
|
+
Returns:
|
323
|
+
int: The cumulative word count of all chapters.
|
324
|
+
"""
|
325
|
+
return sum(ch.exact_word_count for ch in self.chapters)
|
326
|
+
|
266
327
|
@classmethod
|
267
328
|
def from_typst_code(cls, title: str, body: str, **kwargs) -> Self:
|
268
329
|
"""Generates an article from the given Typst code."""
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
330
|
+
raw = extract_sections(body, level=1, section_char="=")
|
331
|
+
return (
|
332
|
+
super()
|
333
|
+
.from_typst_code(
|
334
|
+
title,
|
335
|
+
body,
|
336
|
+
chapters=[cls.child_type.from_typst_code(*pack) for pack in raw],
|
337
|
+
)
|
338
|
+
.update_unstructured_body("" if raw else strip_comment(body))
|
275
339
|
)
|
276
340
|
|
277
341
|
def iter_dfs_rev(
|
@@ -350,7 +414,7 @@ class ArticleBase[T: ChapterBase](FinalizedDumpAble, AsPrompt, FromTypstCode, To
|
|
350
414
|
|
351
415
|
def to_typst_code(self) -> str:
|
352
416
|
"""Generates the Typst code representation of the article."""
|
353
|
-
return f"// #{super().to_typst_code()}\n
|
417
|
+
return f"// #Title: {super().to_typst_code()}\n" + "\n\n".join(a.to_typst_code() for a in self.chapters)
|
354
418
|
|
355
419
|
def finalized_dump(self) -> str:
|
356
420
|
"""Generates standardized hierarchical markup for academic publishing systems.
|
@@ -401,11 +465,11 @@ class ArticleBase[T: ChapterBase](FinalizedDumpAble, AsPrompt, FromTypstCode, To
|
|
401
465
|
"""Set all chap, sec, subsec have same word count sum up to be `self.expected_word_count`."""
|
402
466
|
return self.avg_chap_wordcount().avg_sec_wordcount().avg_subsec_wordcount()
|
403
467
|
|
404
|
-
def update_article_file(self, file: str | Path) ->
|
468
|
+
def update_article_file[S: "ArticleBase"](self: S, file: str | Path) -> S:
|
405
469
|
"""Update the article file."""
|
406
470
|
file = Path(file)
|
407
471
|
string = safe_text_read(file)
|
408
|
-
if updated := replace_thesis_body(string, ARTICLE_WRAPPER, self.to_typst_code()):
|
472
|
+
if updated := replace_thesis_body(string, ARTICLE_WRAPPER, f"\n\n{self.to_typst_code()}\n\n"):
|
409
473
|
dump_text(file, updated)
|
410
474
|
logger.success(f"Successfully updated {file.as_posix()}.")
|
411
475
|
else:
|
@@ -413,7 +477,7 @@ class ArticleBase[T: ChapterBase](FinalizedDumpAble, AsPrompt, FromTypstCode, To
|
|
413
477
|
return self
|
414
478
|
|
415
479
|
@classmethod
|
416
|
-
def from_article_file[S: "ArticleBase"](cls: Type[S], file: str | Path, title: str) -> S:
|
480
|
+
def from_article_file[S: "ArticleBase"](cls: Type[S], file: str | Path, title: str = "") -> S:
|
417
481
|
"""Load article from file."""
|
418
482
|
file = Path(file)
|
419
483
|
string = safe_text_read(file)
|