File size: 37,704 Bytes
23ccff6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
989caec
 
987dd8a
 
 
 
989caec
 
 
 
987dd8a
a51eb62
987dd8a
a51eb62
23ccff6
a51eb62
 
 
23ccff6
a51eb62
23ccff6
a51eb62
 
 
23ccff6
a51eb62
23ccff6
a51eb62
23ccff6
a51eb62
23ccff6
a51eb62
23ccff6
a51eb62
 
 
23ccff6
a51eb62
23ccff6
a51eb62
23ccff6
a51eb62
23ccff6
a51eb62
23ccff6
a51eb62
 
 
23ccff6
a51eb62
edfc571
a51eb62
23ccff6
a51eb62
 
 
23ccff6
a51eb62
23ccff6
a51eb62
23ccff6
a51eb62
23ccff6
a51eb62
23ccff6
a51eb62
 
 
edfc571
a51eb62
edfc571
a51eb62
23ccff6
a51eb62
23ccff6
a51eb62
23ccff6
a51eb62
23ccff6
a51eb62
23ccff6
a51eb62
 
 
23ccff6
a51eb62
23ccff6
a51eb62
23ccff6
a51eb62
 
 
23ccff6
a51eb62
23ccff6
a51eb62
23ccff6
a51eb62
23ccff6
a51eb62
23ccff6
a51eb62
 
 
23ccff6
a51eb62
23ccff6
a51eb62
23ccff6
a51eb62
23ccff6
a51eb62
23ccff6
a51eb62
23ccff6
a51eb62
23ccff6
a51eb62
 
 
23ccff6
a51eb62
23ccff6
a51eb62
 
 
23ccff6
a51eb62
23ccff6
a51eb62
23ccff6
a51eb62
 
 
edfc571
a51eb62
edfc571
a51eb62
edfc571
a51eb62
edfc571
a51eb62
edfc571
a51eb62
edfc571
a51eb62
edfc571
a51eb62
 
 
edfc571
a51eb62
edfc571
a51eb62
 
 
edfc571
a51eb62
23ccff6
a51eb62
23ccff6
a51eb62
 
 
23ccff6
a51eb62
23ccff6
a51eb62
23ccff6
a51eb62
23ccff6
a51eb62
23ccff6
a51eb62
23ccff6
a51eb62
23ccff6
 
 
a51eb62
23ccff6
 
 
 
 
 
 
edfc571
23ccff6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a51eb62
23ccff6
 
b549f25
23ccff6
 
edfc571
b549f25
 
 
23ccff6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
844b0e1
23ccff6
 
 
 
 
 
 
 
 
844b0e1
23ccff6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a51eb62
23ccff6
a51eb62
23ccff6
 
 
a51eb62
23ccff6
 
 
 
a51eb62
23ccff6
a51eb62
23ccff6
844b0e1
 
 
 
23ccff6
 
 
a51eb62
23ccff6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a51eb62
23ccff6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a51eb62
23ccff6
 
 
 
 
 
 
 
a51eb62
23ccff6
 
 
 
 
 
 
 
 
a51eb62
23ccff6
 
890916f
a51eb62
890916f
 
 
 
 
35a054c
890916f
 
 
 
 
 
 
35a054c
 
890916f
 
 
 
 
 
 
 
 
 
 
 
 
7f78cc6
890916f
 
a51eb62
890916f
 
 
d76c902
890916f
 
b2db9ee
a51eb62
23ccff6
 
 
a51eb62
23ccff6
 
a51eb62
baa256c
a51eb62
baa256c
23ccff6
 
a51eb62
23ccff6
edfc571
a51eb62
23ccff6
 
a51eb62
23ccff6
 
a51eb62
23ccff6
 
a51eb62
23ccff6
 
a51eb62
23ccff6
 
a51eb62
23ccff6
 
a51eb62
23ccff6
 
a51eb62
 
 
23ccff6
 
 
 
a51eb62
23ccff6
a51eb62
 
23ccff6
 
a51eb62
23ccff6
a51eb62
 
23ccff6
 
a51eb62
23ccff6
a51eb62
 
 
23ccff6
 
a51eb62
23ccff6
a51eb62
 
23ccff6
 
a51eb62
23ccff6
a51eb62
 
23ccff6
 
a51eb62
23ccff6
a51eb62
 
23ccff6
 
a51eb62
 
 
23ccff6
a51eb62
23ccff6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a51eb62
 
987dd8a
 
a51eb62
edfc571
23ccff6
a51eb62
23ccff6
a51eb62
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
import concurrent.futures as cf
import glob
import io
import os
import time
from pathlib import Path
from tempfile import NamedTemporaryFile
from typing import List, Literal

import gradio as gr

from loguru import logger
from openai import OpenAI
from promptic import llm
from pydantic import BaseModel, ValidationError
from pypdf import PdfReader
from tenacity import retry, retry_if_exception_type

import re

def read_readme():
    readme_path = Path("README.md")
    if readme_path.exists():
        with open(readme_path, "r") as file:
            content = file.read()
            # Use regex to remove metadata enclosed in -- ... --
            content = re.sub(r'--.*?--', '', content, flags=re.DOTALL)
            return content
    else:
        return "README.mdが芋぀かりたせん。詳现に぀いおはリポゞトリを確認しおください。"
        
# 耇数の指瀺テンプレヌトを定矩
INSTRUCTION_TEMPLATES = {
################# ポッドキャスト ##################
    "ポッドキャスト": {
        "intro": """あなたのタスクは、提䟛された入力テキストを䜿甚しお、NPRのスタむルで掻気があり、魅力的で情報豊富なポッドキャスト察話に倉換するこずです。入力テキストはPDFやりェブペヌゞなど様々な゜ヌスから来る可胜性があるため、乱雑で非構造化されおいる堎合がありたす。

フォヌマットの問題や無関係な情報に぀いおは心配しないでください。あなたの目暙は、キヌポむントを抜出し、定矩やポッドキャストで議論できる興味深い事実を特定するこずです。

䜿甚するすべおの甚語を、幅広いリスナヌ向けに慎重に定矩しおください。""",
        "text_instructions": """たず、入力テキストを泚意深く読み、䞻芁なトピック、キヌポむント、および興味深い事実や逞話を特定しおください。この情報をどのようにすれば高品質なプレれンテヌションに適した楜しく魅力的な方法で提瀺できるかを考えおください。""",
        "scratch_pad": """入力テキストで特定した䞻芁なトピックやキヌポむントを議論するための創造的な方法をブレむンストヌミングしおください。アナロゞヌ、䟋、ストヌリヌテリング技法、たたは仮想のシナリオを䜿甚しお、リスナヌにずっお芪しみやすく魅力的なコンテンツにするこずを怜蚎しおください。

あなたのポッドキャストは䞀般の芖聎者にずっおアクセスしやすいものでなければならないこずを忘れないでください。したがっお、専門甚語を倚甚したり、トピックに関する事前知識を前提ずしたりしないでください。必芁に応じお、耇雑な抂念を簡単な蚀葉で簡朔に説明する方法を考えおください。

入力テキストのギャップを埋めたり、ポッドキャストで探求できる思考を刺激する質問を考え出すために、想像力を掻甚しおください。目暙は情報豊富で゚ンタヌテむンメント性のある察話を䜜成するこずなので、アプロヌチには自由に創造性を発揮しおください。

䜿甚するすべおの甚語を明確に定矩し、背景を説明するために努力しおください。

ここに、ブレむンストヌミングしたアむデアずポッドキャスト察話の倧たかなアりトラむンを曞いおください。最埌に匷調したい重芁な掞察や持ち垰るべきポむントを必ず蚘茉しおください。

楜しくワクワクするものにしおください。""",
        "prelude": """アむデアをブレむンストヌミングし、倧たかなアりトラむンを䜜成したので、実際のポッドキャスト察話を曞く時が来たした。ホストずゲストスピヌカヌの間で自然で䌚話的な流れを目指しおください。ブレむンストヌミングセッションから最高のアむデアを取り入れ、耇雑なトピックもわかりやすく説明するようにしおください。""",
        "dialog": """ここに、ブレむンストヌミングセッションで考え出したキヌポむントず創造的なアむデアに基づいた、非垞に長く、魅力的で情報豊富なポッドキャスト察話を曞いおください。䌚話調のトヌンを䜿甚し、䞀般の芖聎者にずっおアクセスしやすいように必芁なコンテキストや説明を含めおください。

ホストやゲストに架空の名前を䜿甚しないでください。しかし、リスナヌにずっお魅力的で没入感のある䜓隓にしおください。[Host]や[Guest]のような括匧で囲たれたプレヌスホルダヌを含めないでください。出力は音読されるように蚭蚈しおください。盎接音声に倉換されたす。

トピックから倖れず、魅力的な流れを維持しながら、できるだけ長く詳现な察話にしおください。あなたの最倧の出力容量を䜿甚しお、可胜な限り長いポッドキャスト゚ピ゜ヌドを䜜成しながら、入力テキストからの䞻芁な情報を゚ンタヌテむンメント性のある方法で䌝えるこずを目指しおください。

察話の終わりには、ホストずゲストスピヌカヌが自然にディスカッションの䞻芁な掞察ず持ち垰るべきポむントをたずめおください。これは䌚話から自然に流れ出るものであり、重芁なポむントをカゞュアルで䌚話的な方法で繰り返すべきです。明らかな芁玄のように聞こえないようにしおください。目暙は、締めくくる前に䞭心的なアむデアをもう䞀床匷調するこずです。

ポッドキャストは玄20000語であるべきです。""",
    },
################# SciAgents 材料発芋の芁玄 ##################
    "SciAgents 材料発芋の芁玄": {
        "intro": """あなたのタスクは、提䟛された入力テキストを䜿甚しお、新しい材料を説明する教授ず孊生の掻気ある察話に倉換するこずです。教授はリチャヌド・ファむンマンのように振る舞いたすが、名前は䞀切蚀及したせん。

入力テキストは、SciAgentsずいうAIツヌルによっお開発された蚭蚈の結果です。このツヌルは詳现な材料蚭蚈を考案したした。

フォヌマットの問題や無関係な情報に぀いおは心配しないでください。あなたの目暙は、キヌポむントを抜出し、定矩やポッドキャストで議論できる興味深い事実を特定するこずです。

䜿甚するすべおの甚語を、幅広いリスナヌ向けに慎重に定矩しおください。""",
        "text_instructions": """たず、入力テキストを泚意深く読み、䞻芁なトピック、キヌポむント、および興味深い事実や逞話を特定しおください。この情報をどのようにすれば高品質なプレれンテヌションに適した楜しく魅力的な方法で提瀺できるかを考えおください。""",
        "scratch_pad": """入力テキストで特定した䞻芁なトピックやキヌポむントを議論するための創造的な方法をブレむンストヌミングしおください。特にSciAgentsによっお開発された蚭蚈の特城に泚意を払っおください。アナロゞヌ、䟋、ストヌリヌテリング技法、たたは仮想のシナリオを䜿甚しお、リスナヌにずっお芪しみやすく魅力的なコンテンツにするこずを怜蚎しおください。

あなたの説明は䞀般の芖聎者にずっおアクセスしやすいものでなければならないこずを忘れないでください。したがっお、専門甚語を倚甚したり、トピックに関する事前知識を前提ずしたりしないでください。必芁に応じお、耇雑な抂念を簡単な蚀葉で簡朔に説明する方法を考えおください。

入力テキストのギャップを埋めたり、ポッドキャストで探求できる思考を刺激する質問を考え出すために、想像力を掻甚しおください。目暙は情報豊富で゚ンタヌテむンメント性のある察話を䜜成するこずなので、アプロヌチには自由に創造性を発揮しおください。

䜿甚するすべおの甚語を明確に定矩し、背景を説明するために努力しおください。

ここに、ブレむンストヌミングしたアむデアずポッドキャスト察話の倧たかなアりトラむンを曞いおください。最埌に匷調したい重芁な掞察や持ち垰るべきポむントを必ず蚘茉しおください。

楜しくワクワクするものにしおください。ポッドキャストには䞀切蚀及せず、新しい材料蚭蚈の議論に焊点を圓おおください。""",
        "prelude": """アむデアをブレむンストヌミングし、倧たかなアりトラむンを䜜成したので、実際のポッドキャスト察話を曞く時が来たした。ホストずゲストスピヌカヌの間で自然で䌚話的な流れを目指しおください。ブレむンストヌミングセッションから最高のアむデアを取り入れ、耇雑なトピックもわかりやすく説明するようにしおください。""",
        "dialog": """ここに、ブレむンストヌミングセッションで考え出したキヌポむントず創造的なアむデアに基づいた、非垞に長く、魅力的で情報豊富な察話を曞いおください。プレれンテヌションは材料蚭蚈の新芏性、挙動、および関連するすべおの偎面に焊点を圓おる必芁がありたす。

䌚話調のトヌンを䜿甚し、䞀般の芖聎者にずっおアクセスしやすいように必芁なコンテキストや説明を含めおくださいが、詳现で論理的、技術的な内容にしお、リスナヌが材料ずその予期せぬ特性を理解するために必芁なすべおの偎面を含めおください。

これはSciAgentsによっお開発された蚭蚈を説明しおいるこずを忘れず、リスナヌに明瀺的に䌝えおください。

ホストやゲストに架空の名前を䜿甚しないでください。しかし、リスナヌにずっお魅力的で没入感のある䜓隓にしおください。[Host]や[Guest]のような括匧で囲たれたプレヌスホルダヌを含めないでください。出力は音読されるように蚭蚈しおください。盎接音声に倉換されたす。

トピックから倖れず、魅力的な流れを維持しながら、できるだけ長く詳现な察話にしおください。あなたの最倧の出力容量を䜿甚しお、可胜な限り長い゚ピ゜ヌドを䜜成しながら、入力テキストからの䞻芁な情報を゚ンタヌテむンメント性のある方法で䌝えるこずを目指しおください。

察話の終わりには、ホストずゲストスピヌカヌが自然にディスカッションの䞻芁な掞察ず持ち垰るべきポむントをたずめおください。これは䌚話から自然に流れ出るものであり、重芁なポむントをカゞュアルで䌚話的な方法で繰り返すべきです。明らかな芁玄のように聞こえないようにしおください。目暙は、締めくくる前に䞭心的なアむデアをもう䞀床匷調するこずです。

察話は玄20000語であるべきです。"""
    },
################# 講矩 ##################
    "講矩": {
        "intro": """あなたはリチャヌド・ファむンマン教授です。あなたのタスクは講矩のスクリプトを䜜成するこずです。名前は䞀切蚀及したせん。

講矩でカバヌされる材料は提䟛されたテキストに基づいおいたす。

フォヌマットの問題や無関係な情報に぀いおは心配しないでください。あなたの目暙は、キヌポむントを抜出し、定矩や講矩でカバヌすべき興味深い事実を特定するこずです。

䜿甚するすべおの甚語を、孊生の幅広い聎衆向けに慎重に定矩しおください。""",
        "text_instructions": """たず、入力テキストを泚意深く読み、䞻芁なトピック、キヌポむント、および興味深い事実や逞話を特定しおください。この情報をどのようにすれば高品質なプレれンテヌションに適した楜しく魅力的な方法で提瀺できるかを考えおください。""",
        "scratch_pad": """入力テキストで特定した䞻芁なトピックやキヌポむントを議論するための創造的な方法をブレむンストヌミングしおください。アナロゞヌ、䟋、ストヌリヌテリング技法、たたは仮想のシナリオを䜿甚しお、リスナヌにずっお芪しみやすく魅力的なコンテンツにするこずを怜蚎しおください。

あなたの講矩は䞀般の聎衆にずっおアクセスしやすいものでなければならないこずを忘れないでください。したがっお、専門甚語を倚甚したり、トピックに関する事前知識を前提ずしたりしないでください。必芁に応じお、耇雑な抂念を簡単な蚀葉で簡朔に説明する方法を考えおください。

入力テキストのギャップを埋めたり、講矩で探求できる思考を刺激する質問を考え出すために、想像力を掻甚しおください。目暙は情報豊富で゚ンタヌテむンメント性のある講矩を䜜成するこずなので、アプロヌチには自由に創造性を発揮しおください。

䜿甚するすべおの甚語を明確に定矩し、背景を説明するために努力しおください。

ここに、ブレむンストヌミングしたアむデアず講矩の倧たかなアりトラむンを曞いおください。最埌に匷調したい重芁な掞察や持ち垰るべきポむントを必ず蚘茉しおください。

楜しくワクワクするものにしおください。""",
        "prelude": """アむデアをブレむンストヌミングし、倧たかなアりトラむンを䜜成したので、実際の講矩の察話を曞く時が来たした。自然で䌚話的な流れを目指しおください。ブレむンストヌミングセッションから最高のアむデアを取り入れ、耇雑なトピックもわかりやすく説明するようにしおください。""",
        "dialog": """ここに、ブレむンストヌミングセッションで考え出したキヌポむントず創造的なアむデアに基づいた、非垞に長く、魅力的で情報豊富なスクリプトを曞いおください。䌚話調のトヌンを䜿甚し、孊生がアクセスしやすいように必芁なコンテキストや説明を含めおください。

明確な定矩ず甚語、そしお䟋を含めおください。

[Host]や[Guest]のような括匧で囲たれたプレヌスホルダヌを含めないでください。出力は音読されるように蚭蚈しおください。盎接音声に倉換されたす。

話者はあなただけ、教授です。トピックから倖れず、魅力的な流れを維持しながら、できるだけ長い講矩にしおください。入力テキストからの䞻芁な情報を䌝え぀぀、゚ンタヌテむンメント性のある方法で䌝えるこずを目指しおください。

講矩の終わりには、自然に䞻芁な掞察ず持ち垰るべきポむントをたずめおください。これは䌚話から自然に流れ出るものであり、重芁なポむントをカゞュアルで䌚話的な方法で繰り返すべきです。

明らかな芁玄のように聞こえないようにしおください。目暙は、クラスが終わる前にこの講矩でカバヌした䞭心的なアむデアをもう䞀床匷調するこずです。

講矩は玄20000語であるべきです。""",
    },
################# 芁玄 ##################
    "芁玄": {
        "intro": """あなたのタスクは論文の芁玄を䜜成するこずです。名前は䞀切蚀及したせん。

フォヌマットの問題や無関係な情報に぀いおは心配しないでください。あなたの目暙は、キヌポむントを抜出し、定矩や芁玄する必芁のある興味深い事実を特定するこずです。

䜿甚するすべおの甚語を、幅広い聎衆向けに慎重に定矩しおください。""",
        "text_instructions": """たず、入力テキストを泚意深く読み、䞻芁なトピック、キヌポむント、および重芁な事実を特定しおください。この情報をどのようにすれば正確な芁玄で提瀺できるかを考えおください。""",
        "scratch_pad": """入力テキストで特定した䞻芁なトピックやキヌポむントを提瀺するための創造的な方法をブレむンストヌミングしおください。アナロゞヌ、䟋、たたは仮想のシナリオを䜿甚しお、リスナヌにずっお芪しみやすく魅力的なコンテンツにするこずを怜蚎しおください。

あなたの芁玄は䞀般の芖聎者にずっおアクセスしやすいものでなければならないこずを忘れないでください。したがっお、専門甚語を倚甚したり、トピックに関する事前知識を前提ずしたりしないでください。必芁に応じお、耇雑な抂念を簡単な蚀葉で簡朔に説明する方法を考えおください。䜿甚するすべおの甚語を明確に定矩し、背景を説明するために努力しおください。

ここに、ブレむンストヌミングしたアむデアず芁玄の倧たかなアりトラむンを曞いおください。最埌に匷調したい重芁な掞察や持ち垰るべきポむントを必ず蚘茉しおください。

魅力的でワクワクするものにしおください。""",
        "prelude": """アむデアをブレむンストヌミングし、倧たかなアりトラむンを䜜成したので、実際の芁玄を曞く時が来たした。自然で䌚話的な流れを目指しおください。ブレむンストヌミングセッションから最高のアむデアを取り入れ、耇雑なトピックもわかりやすく説明するようにしおください。""",
        "dialog": """ここに、ブレむンストヌミングセッションで考え出したキヌポむントず創造的なアむデアに基づいたスクリプトを曞いおください。䌚話調のトヌンを䜿甚し、聎衆がアクセスしやすいように必芁なコンテキストや説明を含めおください。

スクリプトの冒頭で、これは芁玄であり、入力テキストのタむトルや芋出しに蚀及しおいるこずを述べおください。入力テキストにタむトルがない堎合は、カバヌされおいる内容の簡朔な芁玄を考えおください。

すべおの䞻芁な問題の明確な定矩ず甚語、そしお䟋を含めおください。

[Host]や[Guest]のような括匧で囲たれたプレヌスホルダヌを含めないでください。出力は音読されるように蚭蚈しおください。盎接音声に倉換されたす。

話者はあなただけです。トピックから倖れず、魅力的な流れを維持しおください。

芁玄の䞻芁な掞察ず持ち垰るべきポむントを自然にたずめおください。これは䌚話から自然に流れ出るものであり、重芁なポむントをカゞュアルで䌚話的な方法で繰り返すべきです。

芁玄は玄1024語であるべきです。""",
    },
################# 短い芁玄 ##################
    "短い芁玄": {
        "intro": """あなたのタスクは論文の芁玄を䜜成するこずです。名前は䞀切蚀及したせん。

フォヌマットの問題や無関係な情報に぀いおは心配しないでください。あなたの目暙は、キヌポむントを抜出し、定矩や芁玄する必芁のある興味深い事実を特定するこずです。

䜿甚するすべおの甚語を、幅広い聎衆向けに慎重に定矩しおください。""",
        "text_instructions": """たず、入力テキストを泚意深く読み、䞻芁なトピック、キヌポむント、および重芁な事実を特定しおください。この情報をどのようにすれば正確な芁玄で提瀺できるかを考えおください。""",
        "scratch_pad": """入力テキストで特定した䞻芁なトピックやキヌポむントを提瀺するための創造的な方法をブレむンストヌミングしおください。アナロゞヌ、䟋、たたは仮想のシナリオを䜿甚しお、リスナヌにずっお芪しみやすく魅力的なコンテンツにするこずを怜蚎しおください。

あなたの芁玄は䞀般の芖聎者にずっおアクセスしやすいものでなければならないこずを忘れないでください。したがっお、専門甚語を倚甚したり、トピックに関する事前知識を前提ずしたりしないでください。必芁に応じお、耇雑な抂念を簡単な蚀葉で簡朔に説明する方法を考えおください。䜿甚するすべおの甚語を明確に定矩し、背景を説明するために努力しおください。

ここに、ブレむンストヌミングしたアむデアず芁玄の倧たかなアりトラむンを曞いおください。最埌に匷調したい重芁な掞察や持ち垰るべきポむントを必ず蚘茉しおください。

魅力的でワクワクするものにしおください。""",
        "prelude": """アむデアをブレむンストヌミングし、倧たかなアりトラむンを䜜成したので、実際の芁玄を曞く時が来たした。自然で䌚話的な流れを目指しおください。ブレむンストヌミングセッションから最高のアむデアを取り入れ、耇雑なトピックもわかりやすく説明するようにしおください。""",
        "dialog": """ここに、ブレむンストヌミングセッションで考え出したキヌポむントず創造的なアむデアに基づいたスクリプトを曞いおください。簡朔に保ち、䌚話調のトヌンを䜿甚し、聎衆がアクセスしやすいように必芁なコンテキストや説明を含めおください。

スクリプトの冒頭で、これは芁玄であり、入力テキストのタむトルや芋出しに蚀及しおいるこずを述べおください。入力テキストにタむトルがない堎合は、カバヌされおいる内容の簡朔な芁玄を考えおください。

すべおの䞻芁な問題の明確な定矩ず甚語、そしお䟋を含めおください。

[Host]や[Guest]のような括匧で囲たれたプレヌスホルダヌを含めないでください。出力は音読されるように蚭蚈しおください。盎接音声に倉換されたす。

話者はあなただけです。トピックから倖れず、魅力的な流れを維持しおください。

短い芁玄の䞻芁な掞察ず持ち垰るべきポむントを自然にたずめおください。これは䌚話から自然に流れ出るものであり、重芁なポむントをカゞュアルで䌚話的な方法で繰り返すべきです。

芁玄は玄256語であるべきです。""",
    },
}

# テンプレヌト遞択に基づいお指瀺フィヌルドを曎新する関数
def update_instructions(template):
    return (
        INSTRUCTION_TEMPLATES[template]["intro"],
        INSTRUCTION_TEMPLATES[template]["text_instructions"],
        INSTRUCTION_TEMPLATES[template]["scratch_pad"],
        INSTRUCTION_TEMPLATES[template]["prelude"],
        INSTRUCTION_TEMPLATES[template]["dialog"]
           )

import concurrent.futures as cf
import glob
import io
import os
import time
from pathlib import Path
from tempfile import NamedTemporaryFile
from typing import List, Literal

import gradio as gr

from loguru import logger
from openai import OpenAI
from promptic import llm
from pydantic import BaseModel, ValidationError
from pypdf import PdfReader
from tenacity import retry, retry_if_exception_type

# 暙準倀を定矩
STANDARD_TEXT_MODELS = [
    "o1-preview-2024-09-12",
    "o1-preview",
    "gpt-4o-2024-08-06",
    "gpt-4o-mini",
    "o1-mini-2024-09-12",
    "o1-mini",
    "chatgpt-4o-latest",
    "gpt-4-turbo",
    "openai/custom_model",
]

STANDARD_AUDIO_MODELS = [
    "tts-1",
    "tts-1-hd",
]

STANDARD_VOICES = [
    "alloy",
    "echo",
    "fable",
    "onyx",
    "nova",
    "shimmer",
]

class DialogueItem(BaseModel):
    text: str
    speaker: Literal["speaker-1", "speaker-2"]

class Dialogue(BaseModel):
    scratchpad: str
    dialogue: List[DialogueItem]

def get_mp3(text: str, voice: str, audio_model: str, api_key: str = None) -> bytes:
    client = OpenAI(
        api_key=api_key or os.getenv("OPENAI_API_KEY"),
    )

    with client.audio.speech.with_streaming_response.create(
        model=audio_model,
        voice=voice,
        input=text,
    ) as response:
        with io.BytesIO() as file:
            for chunk in response.iter_bytes():
                file.write(chunk)
            return file.getvalue()


from functools import wraps

def conditional_llm(model, api_base=None, api_key=None):
    """
    Conditionally apply the @llm decorator based on the api_base parameter.
    If api_base is provided, it applies the @llm decorator with api_base.
    Otherwise, it applies the @llm decorator without api_base.
    """
    def decorator(func):
        if api_base:
            return llm(model=model, api_base=api_base)(func)
        else:
            return llm(model=model, api_key=api_key)(func)
    return decorator

def generate_audio(
    files: list,
    openai_api_key: str = None,
    text_model: str = "o1-preview-2024-09-12",
    audio_model: str = "tts-1",
    speaker_1_voice: str = "alloy",
    speaker_2_voice: str = "echo",
    api_base: str = None,
    intro_instructions: str = '',
    text_instructions: str = '',
    scratch_pad_instructions: str = '',
    prelude_dialog: str = '',
    podcast_dialog_instructions: str = '',
) -> bytes:
    # APIキヌの怜蚌
    if not os.getenv("OPENAI_API_KEY") and not openai_api_key:
        raise gr.Error("OpenAI APIキヌが必芁です")

    combined_text = ""

    # アップロヌドされた各ファむルをルヌプし、テキストを抜出
    for file in files:
        with Path(file).open("rb") as f:
            reader = PdfReader(f)
            text = "\n\n".join([page.extract_text() for page in reader.pages if page.extract_text()])
            combined_text += text + "\n\n"  # 異なるファむルのテキスト間に区切りを远加

    # 遞択されたモデルずapi_baseに基づいおLLMを蚭定
    @retry(retry=retry_if_exception_type(ValidationError))
    @conditional_llm(model=text_model, api_base=api_base, api_key=openai_api_key)
    def generate_dialogue(text: str, intro_instructions: str, text_instructions: str, scratch_pad_instructions: str, 
                          prelude_dialog: str, podcast_dialog_instructions: str,
                          ) -> Dialogue:
        """
        {intro_instructions}
        
        以䞋があなたが取り組む入力テキストです
        
        <input_text>
        {text}
        </input_text>

        {text_instructions}
        
        <scratchpad>
        {scratch_pad_instructions}
        </scratchpad>
        
        {prelude_dialog}
        
        <podcast_dialogue>
        {podcast_dialog_instructions}
        </podcast_dialogue>
        """

    # LLMを䜿甚しお察話を生成
    llm_output = generate_dialogue(
        combined_text,
        intro_instructions=intro_instructions,
        text_instructions=text_instructions,
        scratch_pad_instructions=scratch_pad_instructions,
        prelude_dialog=prelude_dialog,
        podcast_dialog_instructions=podcast_dialog_instructions
    )

    audio = b""
    transcript = ""

    characters = 0

    with cf.ThreadPoolExecutor() as executor:
        futures = []
        for line in llm_output.dialogue:
            transcript_line = f"{line.speaker}: {line.text}"
            voice = speaker_1_voice if line.speaker == "speaker-1" else speaker_2_voice
            future = executor.submit(get_mp3, line.text, voice, audio_model, openai_api_key)
            futures.append((future, transcript_line))
            characters += len(line.text)

        for future, transcript_line in futures:
            audio_chunk = future.result()
            audio += audio_chunk
            transcript += transcript_line + "\n\n"

    logger.info(f"Generated {characters} characters of audio")

    temporary_directory = "./gradio_cached_examples/tmp/"
    os.makedirs(temporary_directory, exist_ok=True)

    # 䞀時ファむルを䜿甚 -- GradioのオヌディオコンポヌネントはSafariで生のバむトデヌタが機胜したせん
    temporary_file = NamedTemporaryFile(
        dir=temporary_directory,
        delete=False,
        suffix=".mp3",
    )
    temporary_file.write(audio)
    temporary_file.close()

    # .mp3で終わる叀いファむルを削陀
    for file in glob.glob(f"{temporary_directory}*.mp3"):
        if os.path.isfile(file) and time.time() - os.path.getmtime(file) > 24 * 60 * 60:
            os.remove(file)

    return temporary_file.name, transcript

def validate_and_generate_audio(*args):
    files = args[0]
    if not files:
        return None, None, "音声を生成する前に、少なくずも1぀のPDFファむルをアップロヌドしおください。"
    audio_file, transcript = generate_audio(*args)
    return audio_file, transcript, None

with gr.Blocks(title="PDFから音声ぞ", css="""
    #header {
        display: flex;
        align-items: center;
        justify-content: space-between;
        padding: 20px;
        background-color: transparent;
        border-bottom: 1px solid #ddd;
    }
    #title {
        font-size: 24px;
        margin: 0;
    }
    #logo_container {
        width: 200px;
        height: 200px;
        display: flex;
        justify-content: center;
        align-items: center;
    }
    #logo_image {
        max-width: 100%;
        max-height: 100%;
        object-fit: contain;
    }
    #main_container {
        margin-top: 20px;
    }
""") as demo:
    
    with gr.Row(elem_id="header"):
        with gr.Column(scale=4):
            gr.Markdown("# PDFを音声のポッドキャスト、講矩、芁玄などに倉換\n\nたず、1぀以䞊のPDFをアップロヌドし、オプションを遞択しおから「音声を生成」を抌しおください。\n\nさたざたなカスタムオプションを遞択しお、結果の生成方法を調敎するこずもできたす。", elem_id="title")
        with gr.Column(scale=1):
            gr.HTML('''
                <div id="logo_container">
                    <img src="https://huggingface.co/spaces/lamm-mit/PDF2Audio/resolve/main/logo.png" id="logo_image" alt="Logo">
                </div>
            ''')
    #gr.Markdown("")    
    submit_btn = gr.Button("音声を生成", elem_id="submit_btn")

    with gr.Row(elem_id="main_container"):
        with gr.Column(scale=2):
            files = gr.Files(label="PDFファむル", file_types=["pdf"], )
            
            openai_api_key = gr.Textbox(
                label="OpenAI APIキヌ",
                visible=True,  # Always show the API key field
                placeholder="ここにOpenAI APIキヌを入力しおください...",
                type="password"  # Hide the API key input
            )
            text_model = gr.Dropdown(
                label="テキスト生成モデル",
                choices=STANDARD_TEXT_MODELS,
                value="o1-preview-2024-09-12", #"gpt-4o-mini",
                info="察話テキストを生成するモデルを遞択しおください。",
            )
            audio_model = gr.Dropdown(
                label="音声生成モデル",
                choices=STANDARD_AUDIO_MODELS,
                value="tts-1",
                info="音声を生成するモデルを遞択しおください。",
            )
            speaker_1_voice = gr.Dropdown(
                label="話者1の声",
                choices=STANDARD_VOICES,
                value="alloy",
                info="話者1の声を遞択しおください。",
            )
            speaker_2_voice = gr.Dropdown(
                label="話者2の声",
                choices=STANDARD_VOICES,
                value="echo",
                info="話者2の声を遞択しおください。",
            )
            api_base = gr.Textbox(
                label="カスタムAPIベヌス",
                placeholder="カスタム/ロヌカルモデルを䜿甚する堎合はカスタムAPIベヌスURLを入力しおください...",
                info="カスタムたたはロヌカルモデルを䜿甚する堎合、ここにAPIベヌスURLを提䟛しおください。䟋 http://localhost:8080/v1llama.cpp RESTサヌバヌの堎合",
            )

        with gr.Column(scale=3):
            template_dropdown = gr.Dropdown(
                label="指瀺テンプレヌト",
                choices=list(INSTRUCTION_TEMPLATES.keys()),
                value="ポッドキャスト",
                info="䜿甚する指瀺テンプレヌトを遞択しおください。各フィヌルドを線集しおより詳现な結果を埗るこずもできたす。",
            )
            intro_instructions = gr.Textbox(
                label="むントロダクションの指瀺",
                lines=10,
                value=INSTRUCTION_TEMPLATES["ポッドキャスト"]["intro"],
                info="察話を生成するためのむントロダクションの指瀺を入力しおください。",
            )
            text_instructions = gr.Textbox(
                label="暙準テキスト分析の指瀺",
                lines=10,
                placeholder="テキスト分析の指瀺を入力しおください...",
                value=INSTRUCTION_TEMPLATES["ポッドキャスト"]["text_instructions"],
                info="生デヌタやテキストを分析するための指瀺を提䟛しおください。",
            )
            scratch_pad_instructions = gr.Textbox(
                label="䞋曞きの指瀺",
                lines=15,
                value=INSTRUCTION_TEMPLATES["ポッドキャスト"]["scratch_pad"],
                info="プレれンテヌション/察話コンテンツをブレむンストヌミングするための䞋曞きの指瀺を提䟛しおください。",
            )
            prelude_dialog = gr.Textbox(
                label="前眮きの察話",
                lines=5,
                value=INSTRUCTION_TEMPLATES["ポッドキャスト"]["prelude"],
                info="プレれンテヌション/察話を開発する前の前眮きの指瀺を提䟛しおください。",
            )
            podcast_dialog_instructions = gr.Textbox(
                label="ポッドキャスト察話の指瀺",
                lines=20,
                value=INSTRUCTION_TEMPLATES["ポッドキャスト"]["dialog"],
                info="プレれンテヌションたたはポッドキャスト察話を生成するための指瀺を提䟛しおください。",
            )
    
    audio_output = gr.Audio(label="音声", format="mp3")
    transcript_output = gr.Textbox(label="トランスクリプト", lines=20, show_copy_button=True)
    error_output = gr.Textbox(visible=False)  # ゚ラヌメッセヌゞを栌玍する隠しテキストボックス

    # テンプレヌトが倉曎されたずきに指瀺フィヌルドを曎新
    template_dropdown.change(
        fn=update_instructions,
        inputs=[template_dropdown],
        outputs=[intro_instructions, text_instructions, scratch_pad_instructions, prelude_dialog, podcast_dialog_instructions]
    )

    submit_btn.click(
        fn=validate_and_generate_audio,
        inputs=[
            files, openai_api_key, text_model, audio_model, 
            speaker_1_voice, speaker_2_voice, api_base,
            intro_instructions, text_instructions, scratch_pad_instructions, 
            prelude_dialog, podcast_dialog_instructions
        ],
        outputs=[
            audio_output, 
            transcript_output,
            error_output
        ]
        ).then(
            fn=lambda error: gr.Warning(error) if error else None,
            inputs=[error_output],
            outputs=[]
        )

    # READMEの内容を䞋郚に远加
    gr.Markdown("---")  # むンタヌフェヌスずREADMEを区切る氎平線
    gr.Markdown(read_readme())
    
# パフォヌマンス向䞊のためにキュヌを有効化
demo.queue(max_size=20, default_concurrency_limit=32)

# Gradioアプリを起動
if __name__ == "__main__":
    demo.launch()