fabricatio 0.2.11.dev3__cp312-cp312-win_amd64.whl → 0.2.12__cp312-cp312-win_amd64.whl
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/actions/article_rag.py +172 -71
- fabricatio/actions/output.py +43 -2
- fabricatio/capabilities/advanced_rag.py +56 -0
- fabricatio/capabilities/rag.py +4 -4
- fabricatio/config.py +3 -3
- fabricatio/fs/curd.py +1 -1
- fabricatio/models/action.py +6 -8
- fabricatio/models/extra/aricle_rag.py +42 -19
- fabricatio/models/extra/article_base.py +79 -37
- fabricatio/models/extra/article_main.py +89 -45
- fabricatio/models/extra/article_outline.py +41 -3
- fabricatio/models/generic.py +10 -6
- fabricatio/models/kwargs_types.py +1 -1
- fabricatio/models/role.py +5 -4
- fabricatio/rust.cp312-win_amd64.pyd +0 -0
- fabricatio/rust.pyi +34 -1
- fabricatio/utils.py +1 -1
- fabricatio-0.2.12.data/scripts/tdown.exe +0 -0
- {fabricatio-0.2.11.dev3.dist-info → fabricatio-0.2.12.dist-info}/METADATA +1 -1
- {fabricatio-0.2.11.dev3.dist-info → fabricatio-0.2.12.dist-info}/RECORD +22 -21
- fabricatio-0.2.11.dev3.data/scripts/tdown.exe +0 -0
- {fabricatio-0.2.11.dev3.dist-info → fabricatio-0.2.12.dist-info}/WHEEL +0 -0
- {fabricatio-0.2.11.dev3.dist-info → fabricatio-0.2.12.dist-info}/licenses/LICENSE +0 -0
@@ -9,7 +9,7 @@ from fabricatio.journal import logger
|
|
9
9
|
from fabricatio.models.extra.rag import MilvusDataBase
|
10
10
|
from fabricatio.models.generic import AsPrompt
|
11
11
|
from fabricatio.models.kwargs_types import ChunkKwargs
|
12
|
-
from fabricatio.rust import BibManager,
|
12
|
+
from fabricatio.rust import BibManager, blake3_hash, split_into_chunks
|
13
13
|
from fabricatio.utils import ok
|
14
14
|
from more_itertools.recipes import flatten, unique
|
15
15
|
from pydantic import Field
|
@@ -53,7 +53,7 @@ class ArticleChunk(MilvusDataBase, AsPrompt):
|
|
53
53
|
|
54
54
|
def _as_prompt_inner(self) -> Dict[str, str]:
|
55
55
|
return {
|
56
|
-
f"[[{ok(self._cite_number, 'You need to update cite number first.')}]] reference `{self.article_title}`": self.chunk
|
56
|
+
f"[[{ok(self._cite_number, 'You need to update cite number first.')}]] reference `{self.article_title}` from {self.as_auther_seq()}": self.chunk
|
57
57
|
}
|
58
58
|
|
59
59
|
@property
|
@@ -139,15 +139,9 @@ class ArticleChunk(MilvusDataBase, AsPrompt):
|
|
139
139
|
return re.sub(r"\[[\d\s,\\~–-]+]", "", string)
|
140
140
|
|
141
141
|
@property
|
142
|
-
def
|
143
|
-
"""Get the
|
144
|
-
|
145
|
-
for n in self.authors:
|
146
|
-
if is_chinese(n):
|
147
|
-
ret.append(n[0])
|
148
|
-
else:
|
149
|
-
ret.append(n.split()[-1])
|
150
|
-
return ret
|
142
|
+
def auther_lastnames(self) -> List[str]:
|
143
|
+
"""Get the last name of the authors."""
|
144
|
+
return [n.split()[-1] for n in self.authors]
|
151
145
|
|
152
146
|
def as_auther_seq(self) -> str:
|
153
147
|
"""Get the auther sequence."""
|
@@ -155,13 +149,13 @@ class ArticleChunk(MilvusDataBase, AsPrompt):
|
|
155
149
|
case 0:
|
156
150
|
raise ValueError("No authors found")
|
157
151
|
case 1:
|
158
|
-
return f"({self.
|
152
|
+
return f"({self.auther_lastnames[0]},{self.year}){self.as_typst_cite()}"
|
159
153
|
case 2:
|
160
|
-
return f"({self.
|
154
|
+
return f"({self.auther_lastnames[0]}{self.and_word}{self.auther_lastnames[1]},{self.year}){self.as_typst_cite()}"
|
161
155
|
case 3:
|
162
|
-
return f"({self.
|
156
|
+
return f"({self.auther_lastnames[0]},{self.auther_lastnames[1]}{self.and_word}{self.auther_lastnames[2]},{self.year}){self.as_typst_cite()}"
|
163
157
|
case _:
|
164
|
-
return f"({self.
|
158
|
+
return f"({self.auther_lastnames[0]},{self.auther_lastnames[1]}{self.and_word}{self.auther_lastnames[2]}{self.etc_word},{self.year}){self.as_typst_cite()}"
|
165
159
|
|
166
160
|
def update_cite_number(self, cite_number: int) -> Self:
|
167
161
|
"""Update the cite number."""
|
@@ -182,20 +176,32 @@ class CitationManager(AsPrompt):
|
|
182
176
|
abbr_sep: str = "-"
|
183
177
|
"""Separator for abbreviated citation numbers."""
|
184
178
|
|
185
|
-
def update_chunks(
|
179
|
+
def update_chunks(
|
180
|
+
self, article_chunks: List[ArticleChunk], set_cite_number: bool = True, dedup: bool = True
|
181
|
+
) -> Self:
|
186
182
|
"""Update article chunks."""
|
187
183
|
self.article_chunks.clear()
|
188
184
|
self.article_chunks.extend(article_chunks)
|
185
|
+
if dedup:
|
186
|
+
self.article_chunks = list(unique(self.article_chunks, lambda c: blake3_hash(c.chunk.encode())))
|
189
187
|
if set_cite_number:
|
190
188
|
self.set_cite_number_all()
|
191
189
|
return self
|
192
190
|
|
193
|
-
def
|
191
|
+
def empty(self) -> Self:
|
192
|
+
"""Empty the article chunks."""
|
193
|
+
self.article_chunks.clear()
|
194
|
+
return self
|
195
|
+
|
196
|
+
def add_chunks(self, article_chunks: List[ArticleChunk], set_cite_number: bool = True, dedup: bool = True) -> Self:
|
194
197
|
"""Add article chunks."""
|
195
198
|
self.article_chunks.extend(article_chunks)
|
199
|
+
if dedup:
|
200
|
+
self.article_chunks = list(unique(self.article_chunks, lambda c: blake3_hash(c.chunk.encode())))
|
196
201
|
if set_cite_number:
|
197
202
|
self.set_cite_number_all()
|
198
203
|
return self
|
204
|
+
|
199
205
|
def set_cite_number_all(self) -> Self:
|
200
206
|
"""Set citation numbers for all article chunks."""
|
201
207
|
for i, a in enumerate(self.article_chunks, 1):
|
@@ -208,7 +214,7 @@ class CitationManager(AsPrompt):
|
|
208
214
|
|
209
215
|
def apply(self, string: str) -> str:
|
210
216
|
"""Apply citation replacements to the input string."""
|
211
|
-
for origin,m in re.findall(self.pat, string):
|
217
|
+
for origin, m in re.findall(self.pat, string):
|
212
218
|
logger.info(f"Matching citation: {m}")
|
213
219
|
notations = self.convert_to_numeric_notations(m)
|
214
220
|
logger.info(f"Citing Notations: {notations}")
|
@@ -216,9 +222,26 @@ class CitationManager(AsPrompt):
|
|
216
222
|
logger.info(f"Citation Number Sequence: {citation_number_seq}")
|
217
223
|
dedup = self.deduplicate_citation(citation_number_seq)
|
218
224
|
logger.info(f"Deduplicated Citation Number Sequence: {dedup}")
|
219
|
-
string=string.replace(origin, self.unpack_cite_seq(dedup))
|
225
|
+
string = string.replace(origin, self.unpack_cite_seq(dedup))
|
220
226
|
return string
|
221
227
|
|
228
|
+
def citation_count(self, string: str) -> int:
|
229
|
+
"""Get the citation count in the string."""
|
230
|
+
count = 0
|
231
|
+
for _, m in re.findall(self.pat, string):
|
232
|
+
logger.info(f"Matching citation: {m}")
|
233
|
+
notations = self.convert_to_numeric_notations(m)
|
234
|
+
logger.info(f"Citing Notations: {notations}")
|
235
|
+
citation_number_seq = list(flatten(self.decode_expr(n) for n in notations))
|
236
|
+
logger.info(f"Citation Number Sequence: {citation_number_seq}")
|
237
|
+
count += len(dedup := self.deduplicate_citation(citation_number_seq))
|
238
|
+
logger.info(f"Deduplicated Citation Number Sequence: {dedup}")
|
239
|
+
return count
|
240
|
+
|
241
|
+
def citation_coverage(self, string: str) -> float:
|
242
|
+
"""Get the citation coverage in the string."""
|
243
|
+
return self.citation_count(string) / len(self.article_chunks)
|
244
|
+
|
222
245
|
def decode_expr(self, string: str) -> List[int]:
|
223
246
|
"""Decode citation expression into a list of integers."""
|
224
247
|
if self.abbr_sep in string:
|
@@ -1,6 +1,6 @@
|
|
1
1
|
"""A foundation for hierarchical document components with dependency tracking."""
|
2
2
|
|
3
|
-
from abc import ABC
|
3
|
+
from abc import ABC
|
4
4
|
from enum import StrEnum
|
5
5
|
from typing import Generator, List, Optional, Self, Tuple
|
6
6
|
|
@@ -18,7 +18,8 @@ from fabricatio.models.generic import (
|
|
18
18
|
Titled,
|
19
19
|
WordCount,
|
20
20
|
)
|
21
|
-
from fabricatio.rust import
|
21
|
+
from fabricatio.rust import split_out_metadata, to_metadata, word_count
|
22
|
+
from fabricatio.utils import fallback_kwargs
|
22
23
|
from pydantic import Field
|
23
24
|
|
24
25
|
|
@@ -46,14 +47,49 @@ class ArticleMetaData(SketchedAble, Described, WordCount, Titled, Language):
|
|
46
47
|
aims: List[str]
|
47
48
|
"""List of writing aims of the research component in academic style."""
|
48
49
|
|
50
|
+
@property
|
51
|
+
def typst_metadata_comment(self) -> str:
|
52
|
+
"""Generates a comment for the metadata of the article component."""
|
53
|
+
return to_metadata(self.model_dump(include={"description", "aims", "expected_word_count"}, by_alias=True))
|
54
|
+
|
55
|
+
|
56
|
+
class FromTypstCode(ArticleMetaData):
|
57
|
+
"""Base class for article components that can be created from a Typst code snippet."""
|
58
|
+
|
59
|
+
@classmethod
|
60
|
+
def from_typst_code(cls, title: str, body: str, **kwargs) -> Self:
|
61
|
+
"""Converts a Typst code snippet into an article component."""
|
62
|
+
data, body = split_out_metadata(body)
|
63
|
+
|
64
|
+
return cls(
|
65
|
+
heading=title,
|
66
|
+
**fallback_kwargs(
|
67
|
+
data or {},
|
68
|
+
elaboration="",
|
69
|
+
expected_word_count=word_count(body),
|
70
|
+
aims=[],
|
71
|
+
),
|
72
|
+
**kwargs,
|
73
|
+
)
|
74
|
+
|
75
|
+
|
76
|
+
class ToTypstCode(ArticleMetaData):
|
77
|
+
"""Base class for article components that can be converted to a Typst code snippet."""
|
78
|
+
|
79
|
+
def to_typst_code(self) -> str:
|
80
|
+
"""Converts the component into a Typst code snippet for rendering."""
|
81
|
+
return f"{self.title}\n{self.typst_metadata_comment}\n"
|
82
|
+
|
49
83
|
|
50
84
|
class ArticleOutlineBase(
|
51
|
-
ArticleMetaData,
|
52
85
|
ResolveUpdateConflict,
|
53
86
|
ProposedUpdateAble,
|
54
87
|
PersistentAble,
|
55
88
|
ModelHash,
|
56
89
|
Introspect,
|
90
|
+
FromTypstCode,
|
91
|
+
ToTypstCode,
|
92
|
+
ABC,
|
57
93
|
):
|
58
94
|
"""Base class for article outlines."""
|
59
95
|
|
@@ -79,23 +115,13 @@ class ArticleOutlineBase(
|
|
79
115
|
"""Updates the current instance with the attributes of another instance."""
|
80
116
|
return self.update_metadata(other)
|
81
117
|
|
82
|
-
@abstractmethod
|
83
|
-
def to_typst_code(self) -> str:
|
84
|
-
"""Converts the component into a Typst code snippet for rendering."""
|
85
|
-
|
86
118
|
|
87
119
|
class SubSectionBase(ArticleOutlineBase):
|
88
120
|
"""Base class for article sections and subsections."""
|
89
121
|
|
90
122
|
def to_typst_code(self) -> str:
|
91
123
|
"""Converts the component into a Typst code snippet for rendering."""
|
92
|
-
return (
|
93
|
-
f"=== {self.title}\n"
|
94
|
-
f"{comment(f'Desc:\n{self.description}\nAims:\n{"\n".join(self.aims)}')}\n"
|
95
|
-
+ f"Expected Word Count:{self.expected_word_count}"
|
96
|
-
if self.expected_word_count
|
97
|
-
else ""
|
98
|
-
)
|
124
|
+
return f"=== {super().to_typst_code()}"
|
99
125
|
|
100
126
|
def introspect(self) -> str:
|
101
127
|
"""Introspects the article subsection outline."""
|
@@ -120,13 +146,7 @@ class SectionBase[T: SubSectionBase](ArticleOutlineBase):
|
|
120
146
|
Returns:
|
121
147
|
str: The formatted Typst code snippet.
|
122
148
|
"""
|
123
|
-
return (
|
124
|
-
f"== {self.title}\n"
|
125
|
-
f"{comment(f'Desc:\n{self.description}\nAims:\n{"\n".join(self.aims)}')}\n"
|
126
|
-
+ f"Expected Word Count:{self.expected_word_count}"
|
127
|
-
if self.expected_word_count
|
128
|
-
else ""
|
129
|
-
) + "\n\n".join(subsec.to_typst_code() for subsec in self.subsections)
|
149
|
+
return f"== {super().to_typst_code()}" + "\n\n".join(subsec.to_typst_code() for subsec in self.subsections)
|
130
150
|
|
131
151
|
def resolve_update_conflict(self, other: Self) -> str:
|
132
152
|
"""Resolve update errors in the article outline."""
|
@@ -169,13 +189,7 @@ class ChapterBase[T: SectionBase](ArticleOutlineBase):
|
|
169
189
|
|
170
190
|
def to_typst_code(self) -> str:
|
171
191
|
"""Converts the chapter into a Typst formatted code snippet for rendering."""
|
172
|
-
return (
|
173
|
-
f"= {self.title}\n"
|
174
|
-
f"{comment(f'Desc:\n{self.description}\nAims:\n{"\n".join(self.aims)}')}\n"
|
175
|
-
+ f"Expected Word Count:{self.expected_word_count}"
|
176
|
-
if self.expected_word_count
|
177
|
-
else ""
|
178
|
-
) + "\n\n".join(sec.to_typst_code() for sec in self.sections)
|
192
|
+
return f"= {super().to_typst_code()}" + "\n\n".join(sec.to_typst_code() for sec in self.sections)
|
179
193
|
|
180
194
|
def resolve_update_conflict(self, other: Self) -> str:
|
181
195
|
"""Resolve update errors in the article outline."""
|
@@ -207,12 +221,13 @@ class ChapterBase[T: SectionBase](ArticleOutlineBase):
|
|
207
221
|
return ""
|
208
222
|
|
209
223
|
|
210
|
-
class ArticleBase[T: ChapterBase](FinalizedDumpAble, AsPrompt,
|
224
|
+
class ArticleBase[T: ChapterBase](FinalizedDumpAble, AsPrompt, FromTypstCode, ToTypstCode, ABC):
|
211
225
|
"""Base class for article outlines."""
|
212
226
|
|
213
|
-
|
214
|
-
|
215
|
-
|
227
|
+
description: str = Field(
|
228
|
+
alias="elaboration",
|
229
|
+
)
|
230
|
+
"""The abstract of this article, which serves as a concise summary of an academic article, encapsulating its core purpose, methodologies, key results,
|
216
231
|
and conclusions while enabling readers to rapidly assess the relevance and significance of the study.
|
217
232
|
Functioning as the article's distilled essence, it succinctly articulates the research problem, objectives,
|
218
233
|
and scope, providing a roadmap for the full text while also facilitating database indexing, literature reviews,
|
@@ -297,6 +312,10 @@ class ArticleBase[T: ChapterBase](FinalizedDumpAble, AsPrompt, WordCount, Descri
|
|
297
312
|
for _, _, subsec in self.iter_subsections():
|
298
313
|
yield subsec.title
|
299
314
|
|
315
|
+
def to_typst_code(self) -> str:
|
316
|
+
"""Generates the Typst code representation of the article."""
|
317
|
+
return f"// #{super().to_typst_code()}\n\n" + "\n\n".join(a.to_typst_code() for a in self.chapters)
|
318
|
+
|
300
319
|
def finalized_dump(self) -> str:
|
301
320
|
"""Generates standardized hierarchical markup for academic publishing systems.
|
302
321
|
|
@@ -317,8 +336,31 @@ class ArticleBase[T: ChapterBase](FinalizedDumpAble, AsPrompt, WordCount, Descri
|
|
317
336
|
=== Implementation Details
|
318
337
|
== Evaluation Protocol
|
319
338
|
"""
|
320
|
-
return
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
339
|
+
return self.to_typst_code()
|
340
|
+
|
341
|
+
def avg_chap_wordcount[S: "ArticleBase"](self: S) -> S:
|
342
|
+
"""Set all chap have same word count sum up to be `self.expected_word_count`."""
|
343
|
+
avg = int(self.expected_word_count / len(self.chapters))
|
344
|
+
for c in self.chapters:
|
345
|
+
c.expected_word_count = avg
|
346
|
+
return self
|
347
|
+
|
348
|
+
def avg_sec_wordcount[S: "ArticleBase"](self: S) -> S:
|
349
|
+
"""Set all sec have same word count sum up to be `self.expected_word_count`."""
|
350
|
+
for c in self.chapters:
|
351
|
+
avg = int(c.expected_word_count / len(c.sections))
|
352
|
+
for s in c.sections:
|
353
|
+
s.expected_word_count = avg
|
354
|
+
return self
|
355
|
+
|
356
|
+
def avg_subsec_wordcount[S: "ArticleBase"](self: S) -> S:
|
357
|
+
"""Set all subsec have same word count sum up to be `self.expected_word_count`."""
|
358
|
+
for _, s in self.iter_sections():
|
359
|
+
avg = int(s.expected_word_count / len(s.subsections))
|
360
|
+
for ss in s.subsections:
|
361
|
+
ss.expected_word_count = avg
|
362
|
+
return self
|
363
|
+
|
364
|
+
def avg_wordcount_recursive[S: "ArticleBase"](self: S) -> S:
|
365
|
+
"""Set all chap, sec, subsec have same word count sum up to be `self.expected_word_count`."""
|
366
|
+
return self.avg_chap_wordcount().avg_sec_wordcount().avg_subsec_wordcount()
|
@@ -12,13 +12,22 @@ from fabricatio.models.extra.article_base import (
|
|
12
12
|
SubSectionBase,
|
13
13
|
)
|
14
14
|
from fabricatio.models.extra.article_outline import (
|
15
|
+
ArticleChapterOutline,
|
15
16
|
ArticleOutline,
|
17
|
+
ArticleSectionOutline,
|
18
|
+
ArticleSubsectionOutline,
|
16
19
|
)
|
17
20
|
from fabricatio.models.generic import Described, PersistentAble, SequencePatch, SketchedAble, WithRef, WordCount
|
18
|
-
from fabricatio.rust import
|
21
|
+
from fabricatio.rust import (
|
22
|
+
convert_all_block_tex,
|
23
|
+
convert_all_inline_tex,
|
24
|
+
fix_misplaced_labels,
|
25
|
+
split_out_metadata,
|
26
|
+
word_count,
|
27
|
+
)
|
19
28
|
from pydantic import Field, NonNegativeInt
|
20
29
|
|
21
|
-
PARAGRAPH_SEP = "// - -
|
30
|
+
PARAGRAPH_SEP = "\n\n// - - -\n\n"
|
22
31
|
|
23
32
|
|
24
33
|
class Paragraph(SketchedAble, WordCount, Described):
|
@@ -89,17 +98,17 @@ class ArticleSubsection(SubSectionBase):
|
|
89
98
|
Returns:
|
90
99
|
str: Typst code snippet for rendering.
|
91
100
|
"""
|
92
|
-
return
|
101
|
+
return super().to_typst_code() + PARAGRAPH_SEP.join(p.content for p in self.paragraphs)
|
93
102
|
|
94
103
|
@classmethod
|
95
|
-
def from_typst_code(cls, title: str, body: str) -> Self:
|
104
|
+
def from_typst_code(cls, title: str, body: str, **kwargs) -> Self:
|
96
105
|
"""Creates an Article object from the given Typst code."""
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
106
|
+
_, para_body = split_out_metadata(body)
|
107
|
+
|
108
|
+
return super().from_typst_code(
|
109
|
+
title,
|
110
|
+
body,
|
111
|
+
paragraphs=[Paragraph.from_content(p) for p in para_body.split(PARAGRAPH_SEP)],
|
103
112
|
)
|
104
113
|
|
105
114
|
|
@@ -107,16 +116,14 @@ class ArticleSection(SectionBase[ArticleSubsection]):
|
|
107
116
|
"""Atomic argumentative unit with high-level specificity."""
|
108
117
|
|
109
118
|
@classmethod
|
110
|
-
def from_typst_code(cls, title: str, body: str) -> Self:
|
119
|
+
def from_typst_code(cls, title: str, body: str, **kwargs) -> Self:
|
111
120
|
"""Creates an Article object from the given Typst code."""
|
112
|
-
return
|
121
|
+
return super().from_typst_code(
|
122
|
+
title,
|
123
|
+
body,
|
113
124
|
subsections=[
|
114
125
|
ArticleSubsection.from_typst_code(*pack) for pack in extract_sections(body, level=3, section_char="=")
|
115
126
|
],
|
116
|
-
heading=title,
|
117
|
-
elaboration="",
|
118
|
-
expected_word_count=word_count(body),
|
119
|
-
aims=[],
|
120
127
|
)
|
121
128
|
|
122
129
|
|
@@ -124,21 +131,18 @@ class ArticleChapter(ChapterBase[ArticleSection]):
|
|
124
131
|
"""Thematic progression implementing research function."""
|
125
132
|
|
126
133
|
@classmethod
|
127
|
-
def from_typst_code(cls, title: str, body: str) -> Self:
|
134
|
+
def from_typst_code(cls, title: str, body: str, **kwargs) -> Self:
|
128
135
|
"""Creates an Article object from the given Typst code."""
|
129
|
-
return
|
136
|
+
return super().from_typst_code(
|
137
|
+
title,
|
138
|
+
body,
|
130
139
|
sections=[
|
131
140
|
ArticleSection.from_typst_code(*pack) for pack in extract_sections(body, level=2, section_char="=")
|
132
141
|
],
|
133
|
-
heading=title,
|
134
|
-
elaboration="",
|
135
|
-
expected_word_count=word_count(body),
|
136
|
-
aims=[],
|
137
142
|
)
|
138
143
|
|
139
144
|
|
140
145
|
class Article(
|
141
|
-
SketchedAble,
|
142
146
|
WithRef[ArticleOutline],
|
143
147
|
PersistentAble,
|
144
148
|
ArticleBase[ArticleChapter],
|
@@ -157,30 +161,70 @@ class Article(
|
|
157
161
|
"Original Article": self.display(),
|
158
162
|
}
|
159
163
|
|
160
|
-
def convert_tex(self) -> Self:
|
164
|
+
def convert_tex(self, paragraphs: bool = True, descriptions: bool = True) -> Self:
|
161
165
|
"""Convert tex to typst code."""
|
162
|
-
|
163
|
-
for
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
p.content
|
174
|
-
.replace(r" \) ", "$")
|
175
|
-
.replace("\\[\n", "$$\n")
|
176
|
-
.replace("\n\\]", "\n$$")
|
177
|
-
)
|
166
|
+
if descriptions:
|
167
|
+
for a in self.iter_dfs():
|
168
|
+
a.description = fix_misplaced_labels(a.description)
|
169
|
+
a.description = convert_all_inline_tex(a.description)
|
170
|
+
a.description = convert_all_block_tex(a.description)
|
171
|
+
|
172
|
+
if paragraphs:
|
173
|
+
for _, _, subsec in self.iter_subsections():
|
174
|
+
for p in subsec.paragraphs:
|
175
|
+
p.content = fix_misplaced_labels(p.content)
|
176
|
+
p.content = convert_all_inline_tex(p.content)
|
177
|
+
p.content = convert_all_block_tex(p.content)
|
178
178
|
return self
|
179
179
|
|
180
180
|
@override
|
181
181
|
def iter_subsections(self) -> Generator[Tuple[ArticleChapter, ArticleSection, ArticleSubsection], None, None]:
|
182
182
|
return super().iter_subsections() # pyright: ignore [reportReturnType]
|
183
183
|
|
184
|
+
def extrac_outline(self) -> ArticleOutline:
|
185
|
+
"""Extract outline from article."""
|
186
|
+
# Create an empty list to hold chapter outlines
|
187
|
+
chapters = []
|
188
|
+
|
189
|
+
# Iterate through each chapter in the article
|
190
|
+
for chapter in self.chapters:
|
191
|
+
# Create an empty list to hold section outlines
|
192
|
+
sections = []
|
193
|
+
|
194
|
+
# Iterate through each section in the chapter
|
195
|
+
for section in chapter.sections:
|
196
|
+
# Create an empty list to hold subsection outlines
|
197
|
+
subsections = []
|
198
|
+
|
199
|
+
# Iterate through each subsection in the section
|
200
|
+
for subsection in section.subsections:
|
201
|
+
# Create a subsection outline and add it to the list
|
202
|
+
subsections.append(
|
203
|
+
ArticleSubsectionOutline(**subsection.model_dump(exclude={"paragraphs"}, by_alias=True))
|
204
|
+
)
|
205
|
+
|
206
|
+
# Create a section outline and add it to the list
|
207
|
+
sections.append(
|
208
|
+
ArticleSectionOutline(
|
209
|
+
**section.model_dump(exclude={"subsections"}, by_alias=True),
|
210
|
+
subsections=subsections,
|
211
|
+
)
|
212
|
+
)
|
213
|
+
|
214
|
+
# Create a chapter outline and add it to the list
|
215
|
+
chapters.append(
|
216
|
+
ArticleChapterOutline(
|
217
|
+
**chapter.model_dump(exclude={"sections"}, by_alias=True),
|
218
|
+
sections=sections,
|
219
|
+
)
|
220
|
+
)
|
221
|
+
|
222
|
+
# Create and return the article outline
|
223
|
+
return ArticleOutline(
|
224
|
+
**self.model_dump(exclude={"chapters"}, by_alias=True),
|
225
|
+
chapters=chapters,
|
226
|
+
)
|
227
|
+
|
184
228
|
@classmethod
|
185
229
|
def from_outline(cls, outline: ArticleOutline) -> "Article":
|
186
230
|
"""Generates an article from the given outline.
|
@@ -218,15 +262,14 @@ class Article(
|
|
218
262
|
return article
|
219
263
|
|
220
264
|
@classmethod
|
221
|
-
def from_typst_code(cls, title: str, body: str) -> Self:
|
265
|
+
def from_typst_code(cls, title: str, body: str, **kwargs) -> Self:
|
222
266
|
"""Generates an article from the given Typst code."""
|
223
|
-
return
|
267
|
+
return super().from_typst_code(
|
268
|
+
title,
|
269
|
+
body,
|
224
270
|
chapters=[
|
225
271
|
ArticleChapter.from_typst_code(*pack) for pack in extract_sections(body, level=1, section_char="=")
|
226
272
|
],
|
227
|
-
heading=title,
|
228
|
-
expected_word_count=word_count(body),
|
229
|
-
abstract="",
|
230
273
|
)
|
231
274
|
|
232
275
|
@classmethod
|
@@ -248,3 +291,4 @@ class Article(
|
|
248
291
|
|
249
292
|
for a in self.iter_dfs():
|
250
293
|
a.title = await text(f"Edit `{a.title}`.", default=a.title).ask_async() or a.title
|
294
|
+
return self
|
@@ -1,7 +1,8 @@
|
|
1
1
|
"""A module containing the ArticleOutline class, which represents the outline of an academic paper."""
|
2
2
|
|
3
|
-
from typing import Dict
|
3
|
+
from typing import Dict, Self
|
4
4
|
|
5
|
+
from fabricatio.fs.readers import extract_sections
|
5
6
|
from fabricatio.models.extra.article_base import (
|
6
7
|
ArticleBase,
|
7
8
|
ChapterBase,
|
@@ -9,7 +10,7 @@ from fabricatio.models.extra.article_base import (
|
|
9
10
|
SubSectionBase,
|
10
11
|
)
|
11
12
|
from fabricatio.models.extra.article_proposal import ArticleProposal
|
12
|
-
from fabricatio.models.generic import PersistentAble,
|
13
|
+
from fabricatio.models.generic import PersistentAble, WithRef
|
13
14
|
|
14
15
|
|
15
16
|
class ArticleSubsectionOutline(SubSectionBase):
|
@@ -18,14 +19,39 @@ class ArticleSubsectionOutline(SubSectionBase):
|
|
18
19
|
|
19
20
|
class ArticleSectionOutline(SectionBase[ArticleSubsectionOutline]):
|
20
21
|
"""A slightly more detailed research component specification for academic paper generation, Must contain subsections."""
|
22
|
+
@classmethod
|
23
|
+
def from_typst_code(cls, title: str, body: str, **kwargs) -> Self:
|
24
|
+
"""Parse the given Typst code into an ArticleSectionOutline instance."""
|
25
|
+
return super().from_typst_code(
|
26
|
+
title,
|
27
|
+
body,
|
28
|
+
subsections=[
|
29
|
+
ArticleSubsectionOutline.from_typst_code(*pack)
|
30
|
+
for pack in extract_sections(body, level=3, section_char="=")
|
31
|
+
],
|
32
|
+
)
|
33
|
+
|
21
34
|
|
22
35
|
|
23
36
|
class ArticleChapterOutline(ChapterBase[ArticleSectionOutline]):
|
24
37
|
"""Macro-structural unit implementing standard academic paper organization. Must contain sections."""
|
25
38
|
|
39
|
+
@classmethod
|
40
|
+
def from_typst_code(cls, title: str, body: str, **kwargs) -> Self:
|
41
|
+
"""Parse the given Typst code into an ArticleChapterOutline instance."""
|
42
|
+
return super().from_typst_code(
|
43
|
+
title,
|
44
|
+
body,
|
45
|
+
sections=[
|
46
|
+
ArticleSectionOutline.from_typst_code(*pack)
|
47
|
+
for pack in extract_sections(body, level=2, section_char="=")
|
48
|
+
],
|
49
|
+
|
50
|
+
)
|
51
|
+
|
52
|
+
|
26
53
|
|
27
54
|
class ArticleOutline(
|
28
|
-
SketchedAble,
|
29
55
|
WithRef[ArticleProposal],
|
30
56
|
PersistentAble,
|
31
57
|
ArticleBase[ArticleChapterOutline],
|
@@ -38,3 +64,15 @@ class ArticleOutline(
|
|
38
64
|
"Original Article Proposal": self.referenced.display(),
|
39
65
|
"Original Article Outline": self.display(),
|
40
66
|
}
|
67
|
+
|
68
|
+
@classmethod
|
69
|
+
def from_typst_code(cls, title: str, body: str, **kwargs) -> Self:
|
70
|
+
"""Parse the given Typst code into an ArticleOutline instance."""
|
71
|
+
return super().from_typst_code(
|
72
|
+
title,
|
73
|
+
body,
|
74
|
+
chapters=[
|
75
|
+
ArticleChapterOutline.from_typst_code(*pack)
|
76
|
+
for pack in extract_sections(body, level=1, section_char="=")
|
77
|
+
],
|
78
|
+
)
|
fabricatio/models/generic.py
CHANGED
@@ -122,7 +122,7 @@ class FromMapping(Base):
|
|
122
122
|
|
123
123
|
@classmethod
|
124
124
|
@abstractmethod
|
125
|
-
def from_mapping(cls, mapping: Mapping[str, Any], **kwargs: Any) -> List[
|
125
|
+
def from_mapping[S](cls: S, mapping: Mapping[str, Any], **kwargs: Any) -> List[S]:
|
126
126
|
"""Generate a list of objects from a mapping."""
|
127
127
|
|
128
128
|
|
@@ -186,7 +186,7 @@ class WithRef[T](Base):
|
|
186
186
|
@overload
|
187
187
|
def update_ref[S: WithRef](self: S, reference: None = None) -> S: ...
|
188
188
|
|
189
|
-
def update_ref[S: WithRef](self: S, reference: Union[T, "WithRef[T]", None] = None) -> S:
|
189
|
+
def update_ref[S: WithRef](self: S, reference: Union[T, "WithRef[T]", None] = None) -> S:
|
190
190
|
"""Update the reference of the object.
|
191
191
|
|
192
192
|
Args:
|
@@ -201,7 +201,7 @@ class WithRef[T](Base):
|
|
201
201
|
self._reference = reference # pyright: ignore [reportAttributeAccessIssue]
|
202
202
|
return self
|
203
203
|
|
204
|
-
def derive[S: WithRef](self: S, reference: Any) -> S:
|
204
|
+
def derive[S: WithRef](self: S, reference: Any) -> S:
|
205
205
|
"""Derive a new object from the current object.
|
206
206
|
|
207
207
|
Args:
|
@@ -789,7 +789,7 @@ class ScopedConfig(Base):
|
|
789
789
|
"""The dimensions of the Milvus server."""
|
790
790
|
|
791
791
|
@final
|
792
|
-
def fallback_to(self, other: "ScopedConfig") -> Self:
|
792
|
+
def fallback_to(self, other: Union["ScopedConfig", Any]) -> Self:
|
793
793
|
"""Merge configuration values with fallback priority.
|
794
794
|
|
795
795
|
Copies non-null values from 'other' to self where current values are None.
|
@@ -800,6 +800,9 @@ class ScopedConfig(Base):
|
|
800
800
|
Returns:
|
801
801
|
Self: Current instance with merged values
|
802
802
|
"""
|
803
|
+
if not isinstance(other, ScopedConfig):
|
804
|
+
return self
|
805
|
+
|
803
806
|
# Iterate over the attribute names and copy values from 'other' to 'self' where applicable
|
804
807
|
# noinspection PydanticTypeChecker,PyTypeChecker
|
805
808
|
for attr_name in ScopedConfig.model_fields:
|
@@ -811,7 +814,7 @@ class ScopedConfig(Base):
|
|
811
814
|
return self
|
812
815
|
|
813
816
|
@final
|
814
|
-
def hold_to(self, others: Union["ScopedConfig", Iterable["ScopedConfig"]]) -> Self:
|
817
|
+
def hold_to(self, others: Union[Union["ScopedConfig", Any], Iterable[Union["ScopedConfig", Any]]]) -> Self:
|
815
818
|
"""Propagate non-null values to other configurations.
|
816
819
|
|
817
820
|
Copies current non-null values to target configurations where they are None.
|
@@ -824,7 +827,8 @@ class ScopedConfig(Base):
|
|
824
827
|
"""
|
825
828
|
if not isinstance(others, Iterable):
|
826
829
|
others = [others]
|
827
|
-
|
830
|
+
|
831
|
+
for other in (o for o in others if isinstance(o, ScopedConfig)):
|
828
832
|
# noinspection PyTypeChecker,PydanticTypeChecker
|
829
833
|
for attr_name in ScopedConfig.model_fields:
|
830
834
|
if (attr := getattr(self, attr_name)) is not None and getattr(other, attr_name) is None:
|