anki_generator 1.1.0 → 1.4.0
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.
- checksums.yaml +4 -4
- data/.gitignore +59 -0
- data/.rubocop.yml +79 -0
- data/.ruby-version +1 -0
- data/.tool-versions +1 -0
- data/CHANGELOG.md +173 -0
- data/Gemfile +21 -0
- data/Makefile +20 -0
- data/README.md +160 -32
- data/Rakefile +174 -0
- data/anki_generator.gemspec +42 -0
- data/bin/anki_generator +3 -2
- data/docs/CI_SETUP.md +120 -0
- data/docs/architecture/current-v1.3.0.architecture.json +315 -0
- data/docs/architecture/current-v1.3.0.html +14990 -0
- data/docs/architecture/current-v1.3.0.visual-check.json +548 -0
- data/docs/architecture/phase3-proposed.architecture.json +310 -0
- data/docs/architecture/phase3-proposed.html +15001 -0
- data/docs/architecture/phase3-proposed.visual-check.json +548 -0
- data/docs/phase3-draft.md +86 -0
- data/examples/example_class.rb +13 -0
- data/examples/manual_cards.yaml +5 -0
- data/examples/study_prompt.txt +3 -0
- data/input/input.yaml.example +3 -0
- data/lib/anki_generator/anki_connect_client.rb +85 -0
- data/lib/anki_generator/apkg_schema.rb +257 -0
- data/lib/anki_generator/apkg_writer.rb +149 -0
- data/lib/anki_generator/card.rb +83 -0
- data/lib/anki_generator/cli.rb +183 -0
- data/lib/anki_generator/client_factory.rb +20 -0
- data/lib/anki_generator/commands/create_ai_template.rb +39 -0
- data/lib/anki_generator/commands/generate_deck.rb +43 -0
- data/lib/anki_generator/commands/generate_yaml.rb +63 -0
- data/lib/anki_generator/commands/import.rb +75 -0
- data/lib/anki_generator/commands/prompt_based.rb +74 -0
- data/lib/anki_generator/commands/prompt_to_deck.rb +84 -0
- data/lib/anki_generator/commands/push.rb +32 -0
- data/lib/anki_generator/commands/serve.rb +99 -0
- data/lib/anki_generator/commands/test_api.rb +33 -0
- data/lib/anki_generator/deck_builder.rb +184 -0
- data/lib/anki_generator/errors.rb +22 -0
- data/lib/anki_generator/file_processor.rb +154 -0
- data/lib/anki_generator/importers/csv.rb +52 -0
- data/lib/anki_generator/importers/markdown.rb +72 -0
- data/lib/anki_generator/prompt_builder.rb +77 -0
- data/lib/anki_generator/server.rb +98 -0
- data/lib/anki_generator/ui.rb +28 -0
- data/lib/anki_generator/version.rb +5 -0
- data/lib/anki_generator.rb +18 -114
- data/prompt.txt +5 -0
- metadata +100 -43
- data/lib/anki_cli.rb +0 -259
- data/lib/file_processor.rb +0 -156
- data/lib/openrouter_client.rb +0 -158
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": 1,
|
|
3
|
+
"diagram_type": "architecture",
|
|
4
|
+
"meta": {
|
|
5
|
+
"title": "Anki Generator — Phase 3: Hosted Sync Service",
|
|
6
|
+
"output": "phase3-architecture.html",
|
|
7
|
+
"quality_profile": "showcase",
|
|
8
|
+
"locale": "en",
|
|
9
|
+
"views": [
|
|
10
|
+
{
|
|
11
|
+
"id": "sync-path",
|
|
12
|
+
"label": "Offline-first sync",
|
|
13
|
+
"focus": [
|
|
14
|
+
"android",
|
|
15
|
+
"ios",
|
|
16
|
+
"api",
|
|
17
|
+
"postgres"
|
|
18
|
+
],
|
|
19
|
+
"note": "Apps cache decks locally and sync deltas over HTTPS; the API is the only server surface."
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"id": "generation-path",
|
|
23
|
+
"label": "Async AI generation",
|
|
24
|
+
"focus": [
|
|
25
|
+
"api",
|
|
26
|
+
"queue",
|
|
27
|
+
"gensvc",
|
|
28
|
+
"llm",
|
|
29
|
+
"postgres",
|
|
30
|
+
"objectstore"
|
|
31
|
+
],
|
|
32
|
+
"note": "Generation is a background job: the API enqueues, the worker reuses the gem and writes cards back."
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
},
|
|
36
|
+
"components": [
|
|
37
|
+
{
|
|
38
|
+
"id": "android",
|
|
39
|
+
"type": "frontend",
|
|
40
|
+
"label": "Android app",
|
|
41
|
+
"sublabel": "offline cache · Kotlin",
|
|
42
|
+
"pos": [
|
|
43
|
+
40,
|
|
44
|
+
80
|
|
45
|
+
],
|
|
46
|
+
"size": [
|
|
47
|
+
150,
|
|
48
|
+
64
|
|
49
|
+
]
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"id": "ios",
|
|
53
|
+
"type": "frontend",
|
|
54
|
+
"label": "iOS app",
|
|
55
|
+
"sublabel": "offline cache · Swift",
|
|
56
|
+
"pos": [
|
|
57
|
+
40,
|
|
58
|
+
220
|
|
59
|
+
],
|
|
60
|
+
"size": [
|
|
61
|
+
150,
|
|
62
|
+
64
|
|
63
|
+
]
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"id": "web",
|
|
67
|
+
"type": "frontend",
|
|
68
|
+
"label": "Web client",
|
|
69
|
+
"sublabel": "hosted serve UI",
|
|
70
|
+
"pos": [
|
|
71
|
+
40,
|
|
72
|
+
360
|
|
73
|
+
],
|
|
74
|
+
"size": [
|
|
75
|
+
150,
|
|
76
|
+
64
|
|
77
|
+
]
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"id": "api",
|
|
81
|
+
"type": "backend",
|
|
82
|
+
"label": "Sync API",
|
|
83
|
+
"sublabel": "Ruby · JSON / HTTPS",
|
|
84
|
+
"pos": [
|
|
85
|
+
320,
|
|
86
|
+
220
|
|
87
|
+
],
|
|
88
|
+
"size": [
|
|
89
|
+
150,
|
|
90
|
+
70
|
|
91
|
+
],
|
|
92
|
+
"tag": "auth: magic link / JWT"
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"id": "postgres",
|
|
96
|
+
"type": "database",
|
|
97
|
+
"label": "PostgreSQL",
|
|
98
|
+
"sublabel": "users · decks · cards",
|
|
99
|
+
"pos": [
|
|
100
|
+
580,
|
|
101
|
+
220
|
|
102
|
+
],
|
|
103
|
+
"size": [
|
|
104
|
+
160,
|
|
105
|
+
64
|
|
106
|
+
]
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"id": "queue",
|
|
110
|
+
"type": "messagebus",
|
|
111
|
+
"label": "Job queue",
|
|
112
|
+
"sublabel": "Solid Queue",
|
|
113
|
+
"pos": [
|
|
114
|
+
320,
|
|
115
|
+
396
|
|
116
|
+
],
|
|
117
|
+
"size": [
|
|
118
|
+
150,
|
|
119
|
+
64
|
|
120
|
+
]
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"id": "gensvc",
|
|
124
|
+
"type": "backend",
|
|
125
|
+
"label": "Generation worker",
|
|
126
|
+
"sublabel": "reuses anki_generator gem",
|
|
127
|
+
"pos": [
|
|
128
|
+
580,
|
|
129
|
+
396
|
|
130
|
+
],
|
|
131
|
+
"size": [
|
|
132
|
+
170,
|
|
133
|
+
64
|
|
134
|
+
]
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
"id": "objectstore",
|
|
138
|
+
"type": "cloud",
|
|
139
|
+
"label": "Object storage",
|
|
140
|
+
"sublabel": ".apkg exports",
|
|
141
|
+
"pos": [
|
|
142
|
+
860,
|
|
143
|
+
82
|
|
144
|
+
],
|
|
145
|
+
"size": [
|
|
146
|
+
150,
|
|
147
|
+
64
|
|
148
|
+
]
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
"id": "llm",
|
|
152
|
+
"type": "cloud",
|
|
153
|
+
"label": "LLM providers",
|
|
154
|
+
"sublabel": "OpenRouter · Ollama",
|
|
155
|
+
"pos": [
|
|
156
|
+
880,
|
|
157
|
+
396
|
|
158
|
+
],
|
|
159
|
+
"size": [
|
|
160
|
+
150,
|
|
161
|
+
64
|
|
162
|
+
]
|
|
163
|
+
}
|
|
164
|
+
],
|
|
165
|
+
"boundaries": [
|
|
166
|
+
{
|
|
167
|
+
"kind": "region",
|
|
168
|
+
"label": "Single small VPS / container host (cheap to run)",
|
|
169
|
+
"wraps": [
|
|
170
|
+
"api",
|
|
171
|
+
"postgres",
|
|
172
|
+
"queue",
|
|
173
|
+
"gensvc"
|
|
174
|
+
]
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
"kind": "region",
|
|
178
|
+
"label": "User devices",
|
|
179
|
+
"wraps": [
|
|
180
|
+
"android",
|
|
181
|
+
"ios",
|
|
182
|
+
"web"
|
|
183
|
+
]
|
|
184
|
+
}
|
|
185
|
+
],
|
|
186
|
+
"connections": [
|
|
187
|
+
{
|
|
188
|
+
"id": "android-sync",
|
|
189
|
+
"from": "android",
|
|
190
|
+
"to": "api",
|
|
191
|
+
"label": "HTTPS sync",
|
|
192
|
+
"variant": "emphasis",
|
|
193
|
+
"toSide": "top",
|
|
194
|
+
"via": [
|
|
195
|
+
[
|
|
196
|
+
255,
|
|
197
|
+
112
|
|
198
|
+
],
|
|
199
|
+
[
|
|
200
|
+
255,
|
|
201
|
+
150
|
|
202
|
+
],
|
|
203
|
+
[
|
|
204
|
+
395,
|
|
205
|
+
150
|
|
206
|
+
]
|
|
207
|
+
]
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
"id": "ios-sync",
|
|
211
|
+
"from": "ios",
|
|
212
|
+
"to": "api",
|
|
213
|
+
"variant": "emphasis"
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
"id": "web-sync",
|
|
217
|
+
"from": "web",
|
|
218
|
+
"to": "api",
|
|
219
|
+
"fromSide": "top",
|
|
220
|
+
"toSide": "bottom"
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
"id": "api-sql",
|
|
224
|
+
"from": "api",
|
|
225
|
+
"to": "postgres",
|
|
226
|
+
"label": "SQL"
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
"id": "enqueue",
|
|
230
|
+
"from": "api",
|
|
231
|
+
"to": "queue",
|
|
232
|
+
"label": "enqueue",
|
|
233
|
+
"variant": "dashed",
|
|
234
|
+
"fromSide": "bottom",
|
|
235
|
+
"toSide": "top",
|
|
236
|
+
"labelAt": [
|
|
237
|
+
395,
|
|
238
|
+
340
|
|
239
|
+
]
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
"id": "queue-to-worker",
|
|
243
|
+
"from": "queue",
|
|
244
|
+
"to": "gensvc"
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
"id": "worker-to-llm",
|
|
248
|
+
"from": "gensvc",
|
|
249
|
+
"to": "llm",
|
|
250
|
+
"label": "completions",
|
|
251
|
+
"variant": "dashed"
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
"id": "worker-writes",
|
|
255
|
+
"from": "gensvc",
|
|
256
|
+
"to": "postgres",
|
|
257
|
+
"label": "insert cards",
|
|
258
|
+
"fromSide": "top",
|
|
259
|
+
"toSide": "bottom"
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
"id": "export-store",
|
|
263
|
+
"from": "api",
|
|
264
|
+
"to": "objectstore",
|
|
265
|
+
"label": "store .apkg",
|
|
266
|
+
"variant": "dashed",
|
|
267
|
+
"fromSide": "top",
|
|
268
|
+
"toSide": "bottom",
|
|
269
|
+
"via": [
|
|
270
|
+
[
|
|
271
|
+
395,
|
|
272
|
+
175
|
|
273
|
+
],
|
|
274
|
+
[
|
|
275
|
+
935,
|
|
276
|
+
175
|
|
277
|
+
]
|
|
278
|
+
]
|
|
279
|
+
}
|
|
280
|
+
],
|
|
281
|
+
"cards": [
|
|
282
|
+
{
|
|
283
|
+
"dot": "cyan",
|
|
284
|
+
"title": "Clients: offline first",
|
|
285
|
+
"items": [
|
|
286
|
+
"Decks cached in SQLite (SQLDelight / GRDB) so study works with no network",
|
|
287
|
+
"Sync sends only deltas: card edits, review state, new decks",
|
|
288
|
+
"Generation requests return a job id; cards appear when the job finishes"
|
|
289
|
+
]
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
"dot": "emerald",
|
|
293
|
+
"title": "Service: reuse the gem",
|
|
294
|
+
"items": [
|
|
295
|
+
"Sync API is a thin Ruby service over the existing AnkiGenerator::* objects",
|
|
296
|
+
"Solid Queue + one worker process run AI generation out of request path",
|
|
297
|
+
"Web client is today's serve UI with auth and the API as backend"
|
|
298
|
+
]
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
"dot": "rose",
|
|
302
|
+
"title": "Cost: hobbyist scale",
|
|
303
|
+
"items": [
|
|
304
|
+
"One small VPS or free-tier container runs API + worker + queue",
|
|
305
|
+
"PostgreSQL: managed free tier or the same VPS",
|
|
306
|
+
"LLM cost stays pay-per-use via OpenRouter, or $0 with a local Ollama sidecar"
|
|
307
|
+
]
|
|
308
|
+
}
|
|
309
|
+
]
|
|
310
|
+
}
|