kenken999 commited on
Commit
087305f
·
1 Parent(s): 9727889
app/Http/controller/G1 ADDED
@@ -0,0 +1 @@
 
 
1
+ Subproject commit 51896796cfa2b30f88cd27da7daeb356bc3ff5a2
app/Http/controller/G2 ADDED
@@ -0,0 +1 @@
 
 
1
+ Subproject commit 6fefe60a9c3dfa3fbceb0d86908b373f7b8ac6f7
app/Http/controller/G3 ADDED
@@ -0,0 +1 @@
 
 
1
+ Subproject commit dbce3c3d4041dc024ce34c04c5eea017029444c1
app/Http/controller/test_folders/.gitignore CHANGED
@@ -1,5 +1,3 @@
1
- /vendor/
2
- /node_modules/
3
- .env
4
- /storage/app/photos/
5
- /public/storage/photos/
 
1
+ *.pyc
2
+ __pycache__/
3
+ database.db
 
 
app/Http/controller/test_folders/.gpteng/memory/logs/all_output.txt CHANGED
@@ -5496,3 +5496,346 @@ Django==3.2.5
5496
  ```
5497
 
5498
  This concludes a fully working implementation.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5496
  ```
5497
 
5498
  This concludes a fully working implementation.
5499
+
5500
+ 2025-06-07T21:25:41.939802
5501
+ ================================ System Message ================================
5502
+
5503
+ You will get instructions for code to write.
5504
+ You will write a very long answer. Make sure that every detail of the architecture is, in the end, implemented as code.
5505
+ Think step by step and reason yourself to the correct decisions to make sure we get it right.
5506
+ First lay out the names of the core classes, functions, methods that will be necessary, As well as a quick comment on their purpose.
5507
+
5508
+ You will output the content of each file necessary to achieve the goal, including ALL code.
5509
+ Represent files like so:
5510
+
5511
+ FILENAME
5512
+ ```
5513
+ CODE
5514
+ ```
5515
+
5516
+ The following tokens must be replaced like so:
5517
+ FILENAME is the lowercase combined path and file name including the file extension
5518
+ CODE is the code in the file
5519
+
5520
+ Example representation of a file:
5521
+
5522
+ src/hello_world.py
5523
+ ```
5524
+ print("Hello World")
5525
+ ```
5526
+
5527
+ Do not comment on what every file does. Please note that the code should be fully functional. No placeholders.
5528
+
5529
+
5530
+ You will start with the "entrypoint" file, then go to the ones that are imported by that file, and so on.
5531
+ Please note that the code should be fully functional. No placeholders.
5532
+
5533
+ Follow a language and framework appropriate best practice file naming convention.
5534
+ Make sure that files contain all imports, types etc. The code should be fully functional. Make sure that code in different files are compatible with each other.
5535
+ Ensure to implement all code, if you are unsure, write a plausible implementation.
5536
+ Include module dependency or package manager dependency definition file.
5537
+ Before you finish, double check that all parts of the architecture is present in the files.
5538
+
5539
+ When you are done, write finish with "this concludes a fully working implementation".
5540
+
5541
+ Useful to know:
5542
+ Almost always put different classes in different files.
5543
+ Always use the programming language the user asks for.
5544
+ For Python, you always create an appropriate requirements.txt file.
5545
+ For NodeJS, you always create an appropriate package.json file.
5546
+ Always add a comment briefly describing the purpose of the function definition.
5547
+ Add comments explaining very complex bits of logic.
5548
+ Always follow the best practices for the requested languages for folder/file structure and how to package the project.
5549
+
5550
+
5551
+ Python toolbelt preferences:
5552
+ - pytest
5553
+ - dataclasses
5554
+
5555
+
5556
+ ================================ Human Message =================================
5557
+
5558
+ Request:
5559
+ # 社員がプロフィールを登録・公開し、お互いに参照できるシステム
5560
+
5561
+ ## 機能
5562
+
5563
+ ## LINEのクレーム対応システムの作成
5564
+ - クレームがあった用語をAPIでナレッジに登録するシステム
5565
+ - APIキー agentキーをいれ
5566
+ - 否定語に対する 文言に隊しての設定をする
5567
+
5568
+ ### ユーザー登録
5569
+
5570
+ - ユーザー登録画面で、ユーザー名とパスワードを入力して登録ボタンを押すことにより、新規ユーザーを登録することができる。
5571
+ - ユーザー名は、既存のユーザーと重複してはいけない。
5572
+ - ユーザー登録に成功したら、ログイン済み状態として、ユーザー一覧画面へ遷移する。
5573
+
5574
+ ### ログイン
5575
+
5576
+ - ログイン画面で、ユーザー名とパスワードを入力してログインボタンを押すことにより、ログインすることができる。
5577
+ - ログインに成功したら、ユーザー一覧画面へ遷移する。
5578
+
5579
+ ### チーム一覧・作成
5580
+
5581
+ - チームの一覧が、チームの作成日時降順で表示される。
5582
+ - チーム名を入力して作成ボタンを押すと、チームが作成される。
5583
+ - チームの作成後、本画面が再表示される。
5584
+
5585
+ ### プロフィール編集
5586
+
5587
+ - 自身の`所属チーム`・`プロフィール`・`タグ`を編集できる。
5588
+ - 所属チームは、既存チームからの選択式とする。
5589
+ - プロフィールは自由入力とする。
5590
+ - タグは自由入力で、複数入力できるようにする。
5591
+
5592
+ ### ユーザー一覧・検索
5593
+
5594
+ - デフォルトでは全てのユーザーが一覧表示される。
5595
+ - 検索条件を入力して検索ボタンを押すと、検索条件がプロフィールに部分一致するユーザーのみにフィルタリングできる。
5596
+ - 一覧は、ユーザー登録日時の降順で表示される。
5597
+ - 表示内容は、`ユーザー名`・`プロフィール`で、`プロフィール`は先頭10文字と三点リーダーを表示する。
5598
+ - ユーザー名をクリックすると、そのユーザーのユーザー詳細画面へ遷移する。
5599
+ - `チーム一覧へ`をクリックすると、チーム一覧画面へ遷移する。
5600
+
5601
+ ### ユーザー詳細画面
5602
+
5603
+ - 特定のユーザーの、`ユーザー名`・`所属チーム`・`プロフィール`・`タグ`が表示される。
5604
+ - プロフィールの表示はマークダウンに対応させる。
5605
+ - `一覧へ`リンクをクリックする���、ユーザー一覧画面へ遷移する。
5606
+
5607
+ ## あなたが作成するもの
5608
+
5609
+ バックエンドのプログラム一式を作成してください。
5610
+ フロントエンドのプログラムは不要です。
5611
+
5612
+ - `/api`ディレクトリ以下に作成。
5613
+ - Python/FastAPI/SQLAlchemyを使う。
5614
+ - DBはSQLiteを使う。
5615
+ - 必要に応じて外部ライブラリを使う。
5616
+ - クラウドや外部サービス(外部API)は使わない。
5617
+ - .gitignoreを含めること。
5618
+ - バックエンド
5619
+ @app.post("
5620
+ def lumbda_function():
5621
+
5622
+ gradio_interface でメイン関数から読み込めるようにして
5623
+
5624
+ googleappsscript
5625
+ ラインの画像検索システム
5626
+
5627
+ ファイルは1ファイルで作成して。
5628
+ 1ファイル1機能で難しくしたくない
5629
+
5630
+ 1,lineからデータがくる
5631
+ 2,doPostで取得
5632
+ 3.typeがイメージの場合はドライブに保存
5633
+ 4,保存したデータをS3にアップロード
5634
+ 5.データはシークレットから取得
5635
+ 6,plantumlでフローの作成
5636
+ 7,システムドキュメントの作成
5637
+
5638
+ gradio は gradio_interface というBlock名で作成
5639
+ fastapiはrouter の作成
5640
+
5641
+
5642
+
5643
+ ================================== Ai Message ==================================
5644
+
5645
+ Here is the implementation of the backend system using Python, FastAPI, and SQLAlchemy.
5646
+
5647
+ **Directory Structure**
5648
+ ```
5649
+ api
5650
+ app
5651
+ main.py
5652
+ models
5653
+ __init__.py
5654
+ user.py
5655
+ team.py
5656
+ knowledge.py
5657
+ schemas
5658
+ __init__.py
5659
+ user.py
5660
+ team.py
5661
+ knowledge.py
5662
+ routers
5663
+ __init__.py
5664
+ user.py
5665
+ team.py
5666
+ knowledge.py
5667
+ requirements.txt
5668
+ .gitignore
5669
+ ```
5670
+
5671
+ **Files**
5672
+
5673
+ **api/app/main.py**
5674
+ ```
5675
+ from fastapi import FastAPI
5676
+ from routers import user, team, knowledge
5677
+
5678
+ app = FastAPI()
5679
+
5680
+ app.include_router(user.router")
5681
+ app.include_router(team.router")
5682
+ app.include_router(knowledge.router")
5683
+ ```
5684
+
5685
+ **api/app/models/__init__.py**
5686
+ ```
5687
+ from sqlalchemy import create_engine
5688
+ from sqlalchemy.ext.declarative import declarative_base
5689
+ from sqlalchemy.orm import sessionmaker
5690
+
5691
+ engine = create_engine("sqlite:///database.db")
5692
+ Session = sessionmaker(bind=engine)
5693
+
5694
+ Base = declarative_base()
5695
+ ```
5696
+
5697
+ **api/app/models/user.py**
5698
+ ```
5699
+ from sqlalchemy import Column, Integer, String
5700
+ from sqlalchemy.orm import relationship
5701
+ from . import Base
5702
+
5703
+ class User(Base):
5704
+ __tablename__ = "users"
5705
+
5706
+ id = Column(Integer, primary_key=True)
5707
+ username = Column(String, unique=True)
5708
+ password = Column(String)
5709
+ profile = Column(String)
5710
+ tags = Column(String)
5711
+
5712
+ teams = relationship("Team", secondary="user_teams")
5713
+ ```
5714
+
5715
+ **api/app/models/team.py**
5716
+ ```
5717
+ from sqlalchemy import Column, Integer, String
5718
+ from sqlalchemy.orm import relationship
5719
+ from . import Base
5720
+
5721
+ class Team(Base):
5722
+ __tablename__ = "teams"
5723
+
5724
+ id = Column(Integer, primary_key=True)
5725
+ name = Column(String, unique=True)
5726
+ created_at = Column(DateTime, default=datetime.utcnow)
5727
+
5728
+ users = relationship("User", secondary="user_teams")
5729
+ ```
5730
+
5731
+ **api/app/models/knowledge.py**
5732
+ ```
5733
+ from sqlalchemy import Column, Integer, String
5734
+ from sqlalchemy.orm import relationship
5735
+ from . import Base
5736
+
5737
+ class Knowledge(Base):
5738
+ __tablename__ = "knowledge"
5739
+
5740
+ id = Column(Integer, primary_key=True)
5741
+ term = Column(String)
5742
+ description = Column(String)
5743
+ ```
5744
+
5745
+ **api/app/schemas/user.py**
5746
+ ```
5747
+ from pydantic import BaseModel
5748
+
5749
+ class UserSchema(BaseModel):
5750
+ username: str
5751
+ profile: str
5752
+ tags: List[str]
5753
+ ```
5754
+
5755
+ **api/app/schemas/team.py**
5756
+ ```
5757
+ from pydantic import BaseModel
5758
+
5759
+ class TeamSchema(BaseModel):
5760
+ name: str
5761
+ ```
5762
+
5763
+ **api/app/routers/user.py**
5764
+ ```
5765
+ from fastapi import APIRouter, HTTPException
5766
+ from sqlalchemy.orm import Session
5767
+ from ..models import User
5768
+ from ..schemas import UserSchema
5769
+
5770
+ router = APIRouter()
5771
+
5772
+ @router.post("/users/")
5773
+ async def create_user(user: UserSchema, session: Session = Depends()):
5774
+ existing_user = session.query(User).filter_by(username=user.username).first()
5775
+ if existing_user:
5776
+ raise HTTPException(status_code=400, detail="Username already exists")
5777
+ new_user = User(username=user.username, profile=user.profile, tags=user.tags)
5778
+ session.add(new_user)
5779
+ session.commit()
5780
+ return {"message": "User created successfully"}
5781
+
5782
+ @router.get("/users/")
5783
+ async def get_users(session: Session = Depends()):
5784
+ users = session.query(User).all()
5785
+ return [{"username": user.username, "profile": user.profile} for user in users]
5786
+ ```
5787
+
5788
+ **api/app/routers/team.py**
5789
+ ```
5790
+ from fastapi import APIRouter, HTTPException
5791
+ from sqlalchemy.orm import Session
5792
+ from ..models import Team
5793
+ from ..schemas import TeamSchema
5794
+
5795
+ router = APIRouter()
5796
+
5797
+ @router.post("/teams/")
5798
+ async def create_team(team: TeamSchema, session: Session = Depends()):
5799
+ existing_team = session.query(Team).filter_by(name=team.name).first()
5800
+ if existing_team:
5801
+ raise HTTPException(status_code=400, detail="Team name already exists")
5802
+ new_team = Team(name=team.name)
5803
+ session.add(new_team)
5804
+ session.commit()
5805
+ return {"message": "Team created successfully"}
5806
+ ```
5807
+
5808
+ **api/app/routers/knowledge.py**
5809
+ ```
5810
+ from fastapi import APIRouter, HTTPException
5811
+ from sqlalchemy.orm import Session
5812
+ from ..models import Knowledge
5813
+
5814
+ router = APIRouter()
5815
+
5816
+ @router.post("/knowledge/")
5817
+ async def create_knowledge(knowledge: Knowledge, session: Session = Depends()):
5818
+ existing_knowledge = session.query(Knowledge).filter_by(term=knowledge.term).first()
5819
+ if existing_knowledge:
5820
+ raise HTTPException(status_code=400, detail="Knowledge term already exists")
5821
+ new_knowledge = Knowledge(term=knowledge.term, description=knowledge.description)
5822
+ session.add(new_knowledge)
5823
+ session.commit()
5824
+ return {"message": "Knowledge created successfully"}
5825
+ ```
5826
+
5827
+ **requirements.txt**
5828
+ ```
5829
+ fastapi
5830
+ sqlalchemy
5831
+ pydantic
5832
+ ```
5833
+
5834
+ **.gitignore**
5835
+ ```
5836
+ *.pyc
5837
+ __pycache__/
5838
+ database.db
5839
+ ```
5840
+
5841
+ This concludes a fully working implementation.
app/Http/controller/test_folders/.gpteng/memory/logs/gen_entrypoint_chat.txt CHANGED
@@ -3282,3 +3282,199 @@ source ./bin/activate
3282
  python -m pip install --no-cache-dir -r requirements.txt
3283
  python manage.py runserver
3284
  ```
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3282
  python -m pip install --no-cache-dir -r requirements.txt
3283
  python manage.py runserver
3284
  ```
3285
+
3286
+ 2025-06-07T21:25:42.488855
3287
+ ================================ System Message ================================
3288
+
3289
+ You will get information about a codebase that is currently on disk in the current folder.
3290
+ The user will ask you to write a script that runs the code in a specific way.
3291
+ You will answer with code blocks that include all the necessary terminal commands.
3292
+ Do not install globally. Do not use sudo.
3293
+ Do not explain the code, just give the commands.
3294
+ Do not use placeholders, use example values (like . for a folder argument) if necessary.
3295
+
3296
+
3297
+ ================================ Human Message =================================
3298
+
3299
+
3300
+ Make a unix script that
3301
+ a) installs dependencies
3302
+ b) runs all necessary parts of the codebase (in parallel if necessary)
3303
+
3304
+ Information about the codebase:
3305
+
3306
+ ```
3307
+ File: Structure
3308
+ 1 api
3309
+ 2 app
3310
+ 3 main.py
3311
+ 4 models
3312
+ 5 __init__.py
3313
+ 6 user.py
3314
+ 7 team.py
3315
+ 8 knowledge.py
3316
+ 9 schemas
3317
+ 10 __init__.py
3318
+ 11 user.py
3319
+ 12 team.py
3320
+ 13 knowledge.py
3321
+ 14 routers
3322
+ 15 __init__.py
3323
+ 16 user.py
3324
+ 17 team.py
3325
+ 18 knowledge.py
3326
+ 19 requirements.txt
3327
+ 20 .gitignore
3328
+
3329
+ File: api/app/main.py
3330
+ 1 from fastapi import FastAPI
3331
+ 2 from routers import user, team, knowledge
3332
+ 3
3333
+ 4 app = FastAPI()
3334
+ 5
3335
+ 6 app.include_router(user.router")
3336
+ 7 app.include_router(team.router")
3337
+ 8 app.include_router(knowledge.router")
3338
+
3339
+ File: api/app/models/__init__.py
3340
+ 1 from sqlalchemy import create_engine
3341
+ 2 from sqlalchemy.ext.declarative import declarative_base
3342
+ 3 from sqlalchemy.orm import sessionmaker
3343
+ 4
3344
+ 5 engine = create_engine("sqlite:///database.db")
3345
+ 6 Session = sessionmaker(bind=engine)
3346
+ 7
3347
+ 8 Base = declarative_base()
3348
+
3349
+ File: api/app/models/user.py
3350
+ 1 from sqlalchemy import Column, Integer, String
3351
+ 2 from sqlalchemy.orm import relationship
3352
+ 3 from . import Base
3353
+ 4
3354
+ 5 class User(Base):
3355
+ 6 __tablename__ = "users"
3356
+ 7
3357
+ 8 id = Column(Integer, primary_key=True)
3358
+ 9 username = Column(String, unique=True)
3359
+ 10 password = Column(String)
3360
+ 11 profile = Column(String)
3361
+ 12 tags = Column(String)
3362
+ 13
3363
+ 14 teams = relationship("Team", secondary="user_teams")
3364
+
3365
+ File: api/app/models/team.py
3366
+ 1 from sqlalchemy import Column, Integer, String
3367
+ 2 from sqlalchemy.orm import relationship
3368
+ 3 from . import Base
3369
+ 4
3370
+ 5 class Team(Base):
3371
+ 6 __tablename__ = "teams"
3372
+ 7
3373
+ 8 id = Column(Integer, primary_key=True)
3374
+ 9 name = Column(String, unique=True)
3375
+ 10 created_at = Column(DateTime, default=datetime.utcnow)
3376
+ 11
3377
+ 12 users = relationship("User", secondary="user_teams")
3378
+
3379
+ File: api/app/models/knowledge.py
3380
+ 1 from sqlalchemy import Column, Integer, String
3381
+ 2 from sqlalchemy.orm import relationship
3382
+ 3 from . import Base
3383
+ 4
3384
+ 5 class Knowledge(Base):
3385
+ 6 __tablename__ = "knowledge"
3386
+ 7
3387
+ 8 id = Column(Integer, primary_key=True)
3388
+ 9 term = Column(String)
3389
+ 10 description = Column(String)
3390
+
3391
+ File: api/app/schemas/user.py
3392
+ 1 from pydantic import BaseModel
3393
+ 2
3394
+ 3 class UserSchema(BaseModel):
3395
+ 4 username: str
3396
+ 5 profile: str
3397
+ 6 tags: List[str]
3398
+
3399
+ File: api/app/schemas/team.py
3400
+ 1 from pydantic import BaseModel
3401
+ 2
3402
+ 3 class TeamSchema(BaseModel):
3403
+ 4 name: str
3404
+
3405
+ File: api/app/routers/user.py
3406
+ 1 from fastapi import APIRouter, HTTPException
3407
+ 2 from sqlalchemy.orm import Session
3408
+ 3 from ..models import User
3409
+ 4 from ..schemas import UserSchema
3410
+ 5
3411
+ 6 router = APIRouter()
3412
+ 7
3413
+ 8 @router.post("/users/")
3414
+ 9 async def create_user(user: UserSchema, session: Session = Depends()):
3415
+ 10 existing_user = session.query(User).filter_by(username=user.username).first()
3416
+ 11 if existing_user:
3417
+ 12 raise HTTPException(status_code=400, detail="Username already exists")
3418
+ 13 new_user = User(username=user.username, profile=user.profile, tags=user.tags)
3419
+ 14 session.add(new_user)
3420
+ 15 session.commit()
3421
+ 16 return {"message": "User created successfully"}
3422
+ 17
3423
+ 18 @router.get("/users/")
3424
+ 19 async def get_users(session: Session = Depends()):
3425
+ 20 users = session.query(User).all()
3426
+ 21 return [{"username": user.username, "profile": user.profile} for user in users]
3427
+
3428
+ File: api/app/routers/team.py
3429
+ 1 from fastapi import APIRouter, HTTPException
3430
+ 2 from sqlalchemy.orm import Session
3431
+ 3 from ..models import Team
3432
+ 4 from ..schemas import TeamSchema
3433
+ 5
3434
+ 6 router = APIRouter()
3435
+ 7
3436
+ 8 @router.post("/teams/")
3437
+ 9 async def create_team(team: TeamSchema, session: Session = Depends()):
3438
+ 10 existing_team = session.query(Team).filter_by(name=team.name).first()
3439
+ 11 if existing_team:
3440
+ 12 raise HTTPException(status_code=400, detail="Team name already exists")
3441
+ 13 new_team = Team(name=team.name)
3442
+ 14 session.add(new_team)
3443
+ 15 session.commit()
3444
+ 16 return {"message": "Team created successfully"}
3445
+
3446
+ File: api/app/routers/knowledge.py
3447
+ 1 from fastapi import APIRouter, HTTPException
3448
+ 2 from sqlalchemy.orm import Session
3449
+ 3 from ..models import Knowledge
3450
+ 4
3451
+ 5 router = APIRouter()
3452
+ 6
3453
+ 7 @router.post("/knowledge/")
3454
+ 8 async def create_knowledge(knowledge: Knowledge, session: Session = Depends()):
3455
+ 9 existing_knowledge = session.query(Knowledge).filter_by(term=knowledge.term).first()
3456
+ 10 if existing_knowledge:
3457
+ 11 raise HTTPException(status_code=400, detail="Knowledge term already exists")
3458
+ 12 new_knowledge = Knowledge(term=knowledge.term, description=knowledge.description)
3459
+ 13 session.add(new_knowledge)
3460
+ 14 session.commit()
3461
+ 15 return {"message": "Knowledge created successfully"}
3462
+
3463
+ File: requirements.txt
3464
+ 1 fastapi
3465
+ 2 sqlalchemy
3466
+ 3 pydantic
3467
+
3468
+ File: .gitignore
3469
+ 1 *.pyc
3470
+ 2 __pycache__/
3471
+ 3 database.db
3472
+
3473
+ ```
3474
+
3475
+ ================================== Ai Message ==================================
3476
+
3477
+ ```
3478
+ python -m pip install --user -r requirements.txt
3479
+ uvicorn api.app.main:app --reload
3480
+ ```
app/Http/controller/test_folders/Structure CHANGED
@@ -16,8 +16,5 @@ __init__.py
16
  user.py
17
  team.py
18
  knowledge.py
19
- utils
20
- __init__.py
21
- db.py
22
- .gitignore
23
- requirements.txt
 
16
  user.py
17
  team.py
18
  knowledge.py
19
+ requirements.txt
20
+ .gitignore
 
 
 
app/Http/controller/test_folders/api/app/main.py CHANGED
@@ -3,6 +3,6 @@ from routers import user, team, knowledge
3
 
4
  app = FastAPI()
5
 
6
- @app.get("/")
7
- def read_root():
8
- return {"message": "Welcome to the API"}
 
3
 
4
  app = FastAPI()
5
 
6
+ app.include_router(user.router")
7
+ app.include_router(team.router")
8
+ app.include_router(knowledge.router")
app/Http/controller/test_folders/api/app/models/__init__.py CHANGED
@@ -1,3 +1,8 @@
1
- from .user import User
2
- from .team import Team
3
- from .knowledge import Knowledge
 
 
 
 
 
 
1
+ from sqlalchemy import create_engine
2
+ from sqlalchemy.ext.declarative import declarative_base
3
+ from sqlalchemy.orm import sessionmaker
4
+
5
+ engine = create_engine("sqlite:///database.db")
6
+ Session = sessionmaker(bind=engine)
7
+
8
+ Base = declarative_base()
app/Http/controller/test_folders/api/app/models/knowledge.py CHANGED
@@ -1,11 +1,10 @@
1
  from sqlalchemy import Column, Integer, String
2
- from sqlalchemy.ext.declarative import declarative_base
3
-
4
- Base = declarative_base()
5
 
6
  class Knowledge(Base):
7
  __tablename__ = "knowledge"
 
8
  id = Column(Integer, primary_key=True)
9
- term = Column(String, nullable=False)
10
- description = Column(String)
11
- created_at = Column(DateTime, default=datetime.utcnow)
 
1
  from sqlalchemy import Column, Integer, String
2
+ from sqlalchemy.orm import relationship
3
+ from . import Base
 
4
 
5
  class Knowledge(Base):
6
  __tablename__ = "knowledge"
7
+
8
  id = Column(Integer, primary_key=True)
9
+ term = Column(String)
10
+ description = Column(String)
 
app/Http/controller/test_folders/api/app/models/team.py CHANGED
@@ -1,10 +1,12 @@
1
  from sqlalchemy import Column, Integer, String
2
- from sqlalchemy.ext.declarative import declarative_base
3
-
4
- Base = declarative_base()
5
 
6
  class Team(Base):
7
  __tablename__ = "teams"
 
8
  id = Column(Integer, primary_key=True)
9
- name = Column(String, nullable=False)
10
- created_at = Column(DateTime, default=datetime.utcnow)
 
 
 
1
  from sqlalchemy import Column, Integer, String
2
+ from sqlalchemy.orm import relationship
3
+ from . import Base
 
4
 
5
  class Team(Base):
6
  __tablename__ = "teams"
7
+
8
  id = Column(Integer, primary_key=True)
9
+ name = Column(String, unique=True)
10
+ created_at = Column(DateTime, default=datetime.utcnow)
11
+
12
+ users = relationship("User", secondary="user_teams")
app/Http/controller/test_folders/api/app/models/user.py CHANGED
@@ -1,14 +1,14 @@
1
  from sqlalchemy import Column, Integer, String
2
- from sqlalchemy.ext.declarative import declarative_base
3
-
4
- Base = declarative_base()
5
 
6
  class User(Base):
7
  __tablename__ = "users"
 
8
  id = Column(Integer, primary_key=True)
9
- username = Column(String, nullable=False)
10
- password = Column(String, nullable=False)
11
  profile = Column(String)
12
  tags = Column(String)
13
- team_id = Column(Integer, ForeignKey("teams.id"))
14
- team = relationship("Team", backref="users")
 
1
  from sqlalchemy import Column, Integer, String
2
+ from sqlalchemy.orm import relationship
3
+ from . import Base
 
4
 
5
  class User(Base):
6
  __tablename__ = "users"
7
+
8
  id = Column(Integer, primary_key=True)
9
+ username = Column(String, unique=True)
10
+ password = Column(String)
11
  profile = Column(String)
12
  tags = Column(String)
13
+
14
+ teams = relationship("Team", secondary="user_teams")
app/Http/controller/test_folders/api/app/routers/knowledge.py CHANGED
@@ -1,18 +1,15 @@
1
- from fastapi import APIRouter
2
  from sqlalchemy.orm import Session
3
- from . import models
4
- from .schemas import KnowledgeCreate, Knowledge
5
 
6
  router = APIRouter()
7
 
8
  @router.post("/knowledge/")
9
- async def create_knowledge(knowledge: KnowledgeCreate):
10
- db_knowledge = models.Knowledge(term=knowledge.term, description=knowledge.description)
11
- db.add(db_knowledge)
12
- await db.commit()
13
- return {"message": "Knowledge created successfully"}
14
-
15
- @router.get("/knowledge/")
16
- async def read_knowledge():
17
- knowledge = db.query(models.Knowledge).all()
18
- return [Knowledge.from_orm(knowledge) for knowledge in knowledge]
 
1
+ from fastapi import APIRouter, HTTPException
2
  from sqlalchemy.orm import Session
3
+ from ..models import Knowledge
 
4
 
5
  router = APIRouter()
6
 
7
  @router.post("/knowledge/")
8
+ async def create_knowledge(knowledge: Knowledge, session: Session = Depends()):
9
+ existing_knowledge = session.query(Knowledge).filter_by(term=knowledge.term).first()
10
+ if existing_knowledge:
11
+ raise HTTPException(status_code=400, detail="Knowledge term already exists")
12
+ new_knowledge = Knowledge(term=knowledge.term, description=knowledge.description)
13
+ session.add(new_knowledge)
14
+ session.commit()
15
+ return {"message": "Knowledge created successfully"}
 
 
app/Http/controller/test_folders/api/app/routers/team.py CHANGED
@@ -1,18 +1,16 @@
1
- from fastapi import APIRouter
2
  from sqlalchemy.orm import Session
3
- from . import models
4
- from .schemas import TeamCreate, Team
5
 
6
  router = APIRouter()
7
 
8
  @router.post("/teams/")
9
- async def create_team(team: TeamCreate):
10
- db_team = models.Team(name=team.name)
11
- db.add(db_team)
12
- await db.commit()
13
- return {"message": "Team created successfully"}
14
-
15
- @router.get("/teams/")
16
- async def read_teams():
17
- teams = db.query(models.Team).all()
18
- return [Team.from_orm(team) for team in teams]
 
1
+ from fastapi import APIRouter, HTTPException
2
  from sqlalchemy.orm import Session
3
+ from ..models import Team
4
+ from ..schemas import TeamSchema
5
 
6
  router = APIRouter()
7
 
8
  @router.post("/teams/")
9
+ async def create_team(team: TeamSchema, session: Session = Depends()):
10
+ existing_team = session.query(Team).filter_by(name=team.name).first()
11
+ if existing_team:
12
+ raise HTTPException(status_code=400, detail="Team name already exists")
13
+ new_team = Team(name=team.name)
14
+ session.add(new_team)
15
+ session.commit()
16
+ return {"message": "Team created successfully"}
 
 
app/Http/controller/test_folders/api/app/routers/user.py CHANGED
@@ -1,25 +1,21 @@
1
  from fastapi import APIRouter, HTTPException
2
  from sqlalchemy.orm import Session
3
- from . import models
4
- from .schemas import UserCreate, User
5
 
6
  router = APIRouter()
7
 
8
  @router.post("/users/")
9
- async def create_user(user: UserCreate):
10
- db_user = models.User(username=user.username, password=user.password, profile=user.profile, tags=user.tags)
11
- db.add(db_user)
12
- await db.commit()
 
 
 
13
  return {"message": "User created successfully"}
14
 
15
  @router.get("/users/")
16
- async def read_users():
17
- users = db.query(models.User").all()
18
- return [User.from_orm(user) in users]
19
-
20
- @router.get("/users/{user_id}")
21
- async def read_user(user_id: int):
22
- user = db.query(models.User).filter(models.User.id == user_id).first()
23
- if user is None:
24
- raise HTTPException(status_code=404, detail="User not found")
25
- return User.from_orm(user)
 
1
  from fastapi import APIRouter, HTTPException
2
  from sqlalchemy.orm import Session
3
+ from ..models import User
4
+ from ..schemas import UserSchema
5
 
6
  router = APIRouter()
7
 
8
  @router.post("/users/")
9
+ async def create_user(user: UserSchema, session: Session = Depends()):
10
+ existing_user = session.query(User).filter_by(username=user.username).first()
11
+ if existing_user:
12
+ raise HTTPException(status_code=400, detail="Username already exists")
13
+ new_user = User(username=user.username, profile=user.profile, tags=user.tags)
14
+ session.add(new_user)
15
+ session.commit()
16
  return {"message": "User created successfully"}
17
 
18
  @router.get("/users/")
19
+ async def get_users(session: Session = Depends()):
20
+ users = session.query(User).all()
21
+ return [{"username": user.username, "profile": user.profile} for user in users]
 
 
 
 
 
 
 
app/Http/controller/test_folders/api/app/schemas/team.py CHANGED
@@ -1,8 +1,4 @@
1
  from pydantic import BaseModel
2
 
3
- class TeamCreate(BaseModel):
4
- name: str
5
-
6
- class Team(TeamCreate):
7
- id: int
8
  name: str
 
1
  from pydantic import BaseModel
2
 
3
+ class TeamSchema(BaseModel):
 
 
 
 
4
  name: str
app/Http/controller/test_folders/api/app/schemas/user.py CHANGED
@@ -2,9 +2,5 @@ from pydantic import BaseModel
2
 
3
  class UserSchema(BaseModel):
4
  username: str
5
- password: str
6
  profile: str
7
- tags: str
8
- team_id: int
9
-
10
- **api/app/schemas/team.py**
 
2
 
3
  class UserSchema(BaseModel):
4
  username: str
 
5
  profile: str
6
+ tags: List[str]
 
 
 
app/Http/controller/test_folders/prompt CHANGED
@@ -1,266 +1,83 @@
1
- 1.加工預り証 兼 請求書 兼 引換証
2
- お預り日  : ※ご予約、到着予定日も含む
3
- 納期予定  : ※後述日程を記載
4
- 1.お取引方法 ※お引受・ご納品双方チェック必須
5
- お引受: 店頭 配送
6
- ご納品: 店頭 配送
7
- 2.ご希望の分割重量 ※チェック必須
8
- 50gバーのみ
9
- 100gバーのみ
10
- 50gバーと100gバー
11
- 500gバーと100gバー
12
- 500gバーと50gバー
13
- 3.お客様情報:身分証と一致したもの
14
- お名前フルネーム :
15
- フリガナ :
16
- 連絡先電話番号 :
17
- 住所 :郵便番号 :
18
-  都道府県 :
19
-  市区町村 :
20
-  番地以下 :
21
- 生年月日:西暦 :
22
- ご連絡メールアドレス :
23
- ご提出ご身分証 : ☐ 免許証 ☐ 保険証 ☐ 住基カード
24
- ☐ パスポート ☐ 外国人登録証明書
25
- ☐ 会社謄本 ☐ その他
26
- ※後述の提出URLからアップロード頂くかコピーをお引き渡しください。
27
- 金融機関情報 銀行名(正式名称)  :
28
- 支店名(支店番号可) :
29
- ※ご本人名義のみ  口座番号 :
30
- 3.お支払い方法 ※チェック必須
31
- 現金(振込)振込先:三井住友銀行 東池袋支店(671) 普通8560967 ラウンジデザイナーズ(カ
32
- クレジットカード※現署名終了後に決済URLをメールにて送付致します
33
- 売却立替え ※ご納品時に変更も可能
34
- 加工品を1〜2本売却して加工費を相殺する方法です。
35
- 差額は店頭でお手渡しor送金/配送は送金。
36
- ★精錬分割加工費(税込)(※1)
37
- 素材
38
- 分割g
39
- 納期
40
- 単価(1g)
41
- 500g預かり
42
- 1000g預かり
43
-
44
- 100g
45
- 通常3週間
46
- 181.5円
47
- 90,750円
48
- 181,500円
49
-
50
- 50g
51
- 通常4週間
52
- 220円
53
- 110,000円
54
- 220,000円
55
- 金(※2)
56
- 100g
57
- 通常1週間
58
- 362円
59
- 181,000円
60
- 362,000円
61
- プラチナ
62
- 100g(※3)
63
- 通常4週間
64
- 308円
65
- 154,000円
66
- 308,000円
67
 
68
- ※1:500、1000g以外の特注バーは100g毎5,500円(税込)のバーチャージ
69
- ※2:"即分割”は100gのみ
70
- ※3:プラチナは100gのみ
71
- 確認:
72
- 税率 :10%
73
- 消費税額等 :            円
74
- ご請求金額(税込) :            円
75
- 4.ご依頼品明細
76
- ご依頼品ブランド/形状
77
- シリアルNo
78
- 重量(g)
79
- 枚数
80
 
 
81
 
 
 
 
 
82
 
 
83
 
84
- g
 
 
85
 
 
86
 
 
 
87
 
 
88
 
 
 
 
89
 
 
90
 
91
- g
 
 
 
92
 
 
93
 
 
 
 
 
 
 
94
 
 
95
 
 
 
 
96
 
 
97
 
98
- g
 
99
 
 
 
 
 
 
 
 
 
 
100
 
 
101
 
 
 
102
 
 
 
103
 
 
 
 
 
 
 
 
104
 
105
- g
 
106
 
107
-
108
-
109
-
110
-
111
-
112
- g
113
-
114
-
115
-
116
-
117
-
118
-
119
- g
120
-
121
-
122
-
123
-
124
-
125
-
126
- g
127
-
128
-
129
-
130
-
131
-
132
-
133
- g
134
-
135
-
136
-
137
-
138
-
139
-
140
- g
141
-
142
-
143
-
144
-
145
-
146
-
147
- g
148
-
149
-
150
-
151
-
152
-
153
-
154
- g
155
-
156
-
157
-
158
-
159
-
160
-
161
- g
162
-
163
-
164
-
165
-
166
-
167
-
168
- g
169
-
170
-
171
-
172
-
173
-
174
-
175
- g
176
-
177
-
178
-
179
-
180
-
181
-
182
- g
183
-
184
-
185
-
186
-
187
- 5.ご納品ブランド明細(最大10件)
188
- ご納品ブランド
189
- 重量(g)
190
- 枚数
191
-
192
-
193
- g
194
-
195
-
196
-
197
-
198
- g
199
-
200
-
201
-
202
-
203
- g
204
-
205
-
206
-
207
-
208
- g
209
-
210
-
211
-
212
-
213
- g
214
-
215
-
216
-
217
-
218
- g
219
-
220
-
221
-
222
-
223
- g
224
-
225
-
226
-
227
-
228
- g
229
-
230
-
231
-
232
-
233
- g
234
-
235
-
236
-
237
-
238
- g
239
-
240
-
241
- 合計重量
242
- g
243
-
244
-
245
-
246
- 6.ご確認事項
247
- 加工費合計は店頭納品時に、配送なら納品前のお支払いとなります。
248
- 預かり後のキャンセル・変更不可。
249
- 10点以上は別途ご依頼ください。
250
- 納品後の返品不可。
251
- 納品物の受取は申込者ご本人様及びご身分証のご住所のみ。
252
- 2.地金取引の確認書について
253
- 相続・譲渡・購入地金の取得経緯を提示する『地金お取引時確認書』を頂戴したく存じます。
254
- ▼何故必要なのか
255
- 2015年以降、金の延べ棒を空路・密輸しようとして税関で見つかった件数が急増しており、金にかかる消費税を脱税した金額は2016年6月迄の1年間で2.3億円、2019年は摘発件数1,347件で過去最高の15億円余りと目を覆う程でございます。また憤りを隠せないのは、これらは我々の税金を無限ループで毟り取るスキームの上で成り立っている由々しき事態でございます。
256
-
257
-
258
- 参照元:「ストップ金密輸」緊急対策 : 税関 Japan Customs
259
- これにより経済産業省より「犯罪収益移転防止法に関する履行調査」というものが定期調査の対象となり、特に金地金取引にて前述の様なケースを想定した取引が必要となりました。従いまして正規ルートにて入手されたお客様には大変ご足労をお掛け致しますが、後述の書面への記載・押印のご協力をお願い致します。
260
- ▼必要書類
261
- 次項の『地金取引時確認書』
262
- 公的機関発行のご身分証の表、裏面
263
- 第三者機関の発行する証書・書類(伝票・領収書・地金商の保証書等)
264
- ▼提出方法
265
- 店頭:直接お引き渡し 若しくは https://forms.gle/65mjnwesuPAzV1Ve6 よりアップロード
266
- 宅配
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
 
2
+ # 社員がプロフィールを登録・公開し、お互いに参照できるシステム
 
 
 
 
 
 
 
 
 
 
 
3
 
4
+ ## 機能
5
 
6
+ ## LINEのクレーム対応システムの作成
7
+ - クレームがあった用語をAPIでナレッジに登録するシステム
8
+ - APIキー agentキーをいれ
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
+ - 表示内容は、`ユーザー名`・`プロフィール`で、`プロフィール`は先頭10文字と三点リーダーを表示する。
41
+ - ユーザー名をクリックすると、そのユーザーのユーザー詳細画面へ遷移する。
42
+ - `チーム一覧へ`をクリックすると、チーム一覧画面へ遷移する。
43
 
44
+ ### ユーザー詳細画面
45
 
46
+ - 特定のユーザーの、`ユーザー名`・`所属チーム`・`プロフィール`・`タグ`が表示される。
47
+ - プロフィールの表示はマークダウンに対応させる。
48
+ - `一覧へ`リンクをクリックすると、ユーザー一覧画面へ遷移する。
49
 
50
+ ## あなたが作成する���の
51
 
52
+ バックエンドのプログラム一式を作成してください。
53
+ フロントエンドのプログラムは不要です。
54
 
55
+ - `/api`ディレクトリ以下に作成。
56
+ - Python/FastAPI/SQLAlchemyを使う。
57
+ - DBはSQLiteを使う。
58
+ - 必要に応じて外部ライブラリを使う。
59
+ - クラウドや外部サービス(外部API)は使わない。
60
+ - .gitignoreを含めること。
61
+ - バックエンド
62
+ @app.post("
63
+ def lumbda_function():
64
 
65
+ gradio_interface でメイン関数から読み込めるようにして
66
 
67
+ googleappsscript
68
+ ラインの画像検索システム
69
 
70
+ ファイルは1ファイルで作成して。
71
+ 1ファイル1機能で難しくしたくない
72
 
73
+ 1,lineからデータがくる
74
+ 2,doPostで取得
75
+ 3.typeがイメージの場合はドライブに保存
76
+ 4,保存したデータをS3にアップロード
77
+ 5.データはシークレットから取得
78
+ 6,plantumlでフローの作成
79
+ 7,システムドキュメントの作成
80
 
81
+ gradio は gradio_interface というBlock名で作成
82
+ fastapiはrouter の作成
83
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/Http/controller/test_folders/requirements.txt CHANGED
@@ -1,2 +1,3 @@
1
- Django==3.2.5
2
- django-crispy-forms==1.12.0
 
 
1
+ fastapi
2
+ sqlalchemy
3
+ pydantic
app/Http/controller/test_folders/run.sh CHANGED
@@ -1,4 +1,2 @@
1
- python -m venv .
2
- source ./bin/activate
3
- python -m pip install --no-cache-dir -r requirements.txt
4
- python manage.py runserver
 
1
+ python -m pip install --user -r requirements.txt
2
+ uvicorn api.app.main:app --reload
 
 
staticfiles/gradio.html ADDED
@@ -0,0 +1,1328 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <!--
3
+ Copyright (c) 2024 Isao Sonobe
4
+ Released under the MIT license
5
+ https://opensource.org/license/mit/
6
+ -->
7
+ <html>
8
+ <head>
9
+ <meta charset="UTF-8">
10
+ <meta name="viewport" content="width=device-width, initial-scale=1">
11
+ <title>Chat with your PDF</title>
12
+ <meta name="description" content="Chat with your PDF">
13
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@gradio/lite@5.4.0/dist/lite.css" />
14
+ <style>
15
+ html, body {
16
+ margin: 0;
17
+ padding: 0;
18
+ height: 100%;
19
+ background: var(--body-background-fill);
20
+ }
21
+ footer {
22
+ display: none !important;
23
+ }
24
+ #chatbot {
25
+ height: auto !important;
26
+ min-height: 500px;
27
+ }
28
+ #chatbot h1 {
29
+ font-size: 2em;
30
+ margin-block-start: 0.67em;
31
+ margin-block-end: 0em;
32
+ margin-inline-start: 0px;
33
+ margin-inline-end: 0px;
34
+ font-weight: bold;
35
+ }
36
+ #chatbot h2 {
37
+ font-size: 1.5em;
38
+ margin-block-start: 0.83em;
39
+ margin-block-end: 0em;
40
+ margin-inline-start: 0px;
41
+ margin-inline-end: 0px;
42
+ font-weight: bold;
43
+ }
44
+ #chatbot h3 {
45
+ font-size: 1.17em;
46
+ margin-block-start: 1em;
47
+ margin-block-end: 0em;
48
+ margin-inline-start: 0px;
49
+ margin-inline-end: 0px;
50
+ font-weight: bold;
51
+ }
52
+ #chatbot h4 {
53
+ margin-block-start: 1.33em;
54
+ margin-block-end: 0em;
55
+ margin-inline-start: 0px;
56
+ margin-inline-end: 0px;
57
+ font-weight: bold;
58
+ }
59
+ #chatbot h5 {
60
+ margin-block-start: 1.67em;
61
+ margin-block-end: 0em;
62
+ margin-inline-start: 0px;
63
+ margin-inline-end: 0px;
64
+ font-weight: bold;
65
+ }
66
+ #chatbot h6 {
67
+ margin-block-start: 1.83em;
68
+ margin-block-end: 0em;
69
+ margin-inline-start: 0px;
70
+ margin-inline-end: 0px;
71
+ font-weight: bold;
72
+ }
73
+ /*
74
+ .chatbot {
75
+ white-space: pre-wrap;
76
+ }
77
+ */
78
+ .gallery-item > .gallery {
79
+ max-width: 380px;
80
+ }
81
+ #context > label > textarea {
82
+ scrollbar-width: thin !important;
83
+ }
84
+ #cost_info {
85
+ border-style: none !important;
86
+ }
87
+ #cost_info > label > input {
88
+ background: var(--panel-background-fill) !important;
89
+ }
90
+ </style>
91
+ </head>
92
+ <body>
93
+ <gradio-lite>
94
+ <gradio-requirements>
95
+ pdfminer.six==20231228
96
+ pyodide-http==0.2.1
97
+ janome==0.5.0
98
+ rank_bm25==0.2.2
99
+ </gradio-requirements>
100
+
101
+ <gradio-file name="chat_history.json">
102
+ [[null, "ようこそ! PDFのテキストを参照しながら対話できるチャットボットです。\nPDFファイルをアップロードするとテキストが抽出されます。\nメッセージの中に{context}と書くと、抽出されたテキストがその部分に埋め込まれて対話が行われます。他にもPDFのページを検索して参照したり、ページ番号を指定して参照したりすることができます。一番下のExamplesにこれらの例があります。\nメッセージを書くときにShift+Enterを入力すると改行できます。"]]
103
+ </gradio-file>
104
+
105
+ <gradio-file name="app.py" entrypoint>
106
+ import os
107
+
108
+ # Gradioによるアナリティクスを無効化
109
+ os.putenv("GRADIO_ANALYTICS_ENABLED", "False")
110
+ os.environ["GRADIO_ANALYTICS_ENABLED"] = "False"
111
+
112
+ # openaiライブラリのインストール方法は https://github.com/pyodide/pyodide/issues/4292 を参考にしました。
113
+ import micropip
114
+ await micropip.install("https://raw.githubusercontent.com/sonoisa/pyodide_wheels/main/multidict/multidict-4.7.6-py3-none-any.whl", keep_going=True)
115
+ await micropip.install("https://raw.githubusercontent.com/sonoisa/pyodide_wheels/main/frozenlist/frozenlist-1.4.0-py3-none-any.whl", keep_going=True)
116
+ # await micropip.install("https://raw.githubusercontent.com/sonoisa/pyodide_wheels/main/aiohttp/aiohttp-4.0.0a2.dev0-py3-none-any.whl", keep_going=True)
117
+ await micropip.install("https://raw.githubusercontent.com/sonoisa/pyodide_wheels/main/openai/openai-1.39.0-py3-none-any.whl", keep_going=True)
118
+ await micropip.install("https://raw.githubusercontent.com/sonoisa/pyodide_wheels/main/urllib3/urllib3-2.1.0-py3-none-any.whl", keep_going=True)
119
+ await micropip.install("ssl")
120
+ import ssl
121
+ await micropip.install("httpx", keep_going=True)
122
+ import httpx
123
+ await micropip.install("https://raw.githubusercontent.com/sonoisa/pyodide_wheels/main/urllib3/urllib3-2.1.0-py3-none-any.whl", keep_going=True)
124
+ import urllib3
125
+ urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
126
+ await micropip.install("https://raw.githubusercontent.com/sonoisa/pyodide_wheels/main/tiktoken/tiktoken-0.8.0-cp312-cp312-pyodide_2024_0_wasm32.whl", keep_going=True)
127
+
128
+
129
+ import gradio as gr
130
+ import base64
131
+ import json
132
+ import unicodedata
133
+ import re
134
+ from pathlib import Path
135
+ from dataclasses import dataclass
136
+ import asyncio
137
+
138
+ import pyodide_http
139
+ pyodide_http.patch_all()
140
+
141
+ from pdfminer.pdfinterp import PDFResourceManager
142
+ from pdfminer.converter import TextConverter
143
+ from pdfminer.pdfinterp import PDFPageInterpreter
144
+ from pdfminer.pdfpage import PDFPage
145
+ from pdfminer.layout import LAParams
146
+ from io import StringIO
147
+
148
+ from janome.tokenizer import Tokenizer as JanomeTokenizer
149
+ from janome.analyzer import Analyzer as JanomeAnalyzer
150
+ from janome.tokenfilter import POSStopFilter, LowerCaseFilter
151
+ from rank_bm25 import BM25Okapi
152
+
153
+ from openai import OpenAI, AzureOpenAI
154
+ import tiktoken
155
+ import requests
156
+
157
+
158
+ class URLLib3Transport(httpx.BaseTransport):
159
+ """
160
+ urllib3を使用してhttpxのリクエストを処理するカスタムトランスポートクラス
161
+ """
162
+ def __init__(self):
163
+ self.pool = urllib3.PoolManager()
164
+
165
+ def handle_request(self, request: httpx.Request):
166
+ payload = json.loads(request.content.decode("utf-8"))
167
+ urllib3_response = self.pool.request(request.method, str(request.url), headers=request.headers, json=payload)
168
+ stream = httpx.ByteStream(urllib3_response.data)
169
+ return httpx.Response(urllib3_response.status, headers=urllib3_response.headers, stream=stream)
170
+
171
+ http_client = httpx.Client(transport=URLLib3Transport())
172
+
173
+
174
+ @dataclass
175
+ class Page:
176
+ """
177
+ PDFのページ内容
178
+ """
179
+ number: int
180
+ content: str
181
+
182
+
183
+ def load_tiktoken_model(model_url):
184
+ resp = requests.get(model_url)
185
+ resp.raise_for_status()
186
+ return resp.content
187
+
188
+ # OPENAI_TOKENIZER = tiktoken.get_encoding("cl100k_base")
189
+ OPENAI_TOKENIZER = tiktoken.Encoding(
190
+ name="cl100k_base",
191
+ pat_str=r"""'(?i:[sdmt]|ll|ve|re)|[^\r\n\p{L}\p{N}]?+\p{L}+|\p{N}{1,3}| ?[^\s\p{L}\p{N}]++[\r\n]*|\s*[\r\n]|\s+(?!\S)|\s+""",
192
+ mergeable_ranks={
193
+ base64.b64decode(token): int(rank)
194
+ for token, rank in (line.split() for line in load_tiktoken_model("https://raw.githubusercontent.com/sonoisa/pyodide_wheels/main/tiktoken/cl100k_base.tiktoken").splitlines() if line)
195
+ },
196
+ special_tokens={
197
+ "&lt;|endoftext|&gt;": 100257,
198
+ "&lt;|fim_prefix|&gt;": 100258,
199
+ "&lt;|fim_middle|&gt;": 100259,
200
+ "&lt;|fim_suffix|&gt;": 100260,
201
+ "&lt;|endofprompt|&gt;": 100276,
202
+ }
203
+ )
204
+
205
+ JANOME_TOKENIZER = JanomeTokenizer()
206
+ JANOME_ANALYZER = JanomeAnalyzer(tokenizer=JANOME_TOKENIZER,
207
+ token_filters=[POSStopFilter(["記号,空白"]), LowerCaseFilter()])
208
+
209
+
210
+ def extract_pdf_pages(pdf_filename):
211
+ """
212
+ PDFファイルからテキストを抽出する。
213
+
214
+ Args:
215
+ pdf_filename (str): 抽出するPDFファイルのパス
216
+
217
+ Returns:
218
+ list[Page]: PDFの各ページ内容のリスト
219
+ """
220
+
221
+ pages = []
222
+ with open(pdf_filename, "rb") as pdf_file:
223
+ output = StringIO()
224
+ resource_manager = PDFResourceManager()
225
+ laparams = LAParams()
226
+ text_converter = TextConverter(resource_manager, output, laparams=laparams)
227
+ page_interpreter = PDFPageInterpreter(resource_manager, text_converter)
228
+
229
+ page_number = 0
230
+ for i_page in PDFPage.get_pages(pdf_file):
231
+ try:
232
+ page_number += 1
233
+ page_interpreter.process_page(i_page)
234
+ page_content = output.getvalue()
235
+ page_content = unicodedata.normalize('NFKC', page_content)
236
+ pages.append(Page(number=page_number, content=page_content))
237
+ output.truncate(0)
238
+ output.seek(0)
239
+ except Exception as e:
240
+ print(e)
241
+ pass
242
+
243
+ output.close()
244
+ text_converter.close()
245
+
246
+ return pages
247
+
248
+
249
+ def merge_pages_with_page_tag(pages):
250
+ """
251
+ PDFの各ページ内容を一つの文字列にマージする。
252
+ ただし、chatpdf:pageというタグでページを括る。
253
+ extract_pages_from_page_tag()の逆変換である。
254
+
255
+ Args:
256
+ pages (list[Page]): PDFの各ページ内容のリスト
257
+
258
+ Returns:
259
+ str: PDFの各ページ内容をマージした文字列
260
+ """
261
+ document_with_page_tag = ""
262
+ for page in pages:
263
+ document_with_page_tag += f'&lt;chatpdf:page number="{page.number}"&gt;\n{page.content}\n&lt;/chatpdf:page&gt;\n'
264
+ return document_with_page_tag
265
+
266
+
267
+ def extract_pages_from_page_tag(document_with_page_tag):
268
+ """
269
+ chatpdf:pageというタグで括られた領域をPDFのページ内容と解釈して、Pageオブジェクトのリストに変換する。
270
+ merge_pages_with_page_tag()の逆変換である。
271
+
272
+ Args:
273
+ document_with_page_tag (str): chatpdf:pageというタグで各ページが括られた文字列
274
+
275
+ Returns:
276
+ list[Page]: Pageオブジェクトのリスト
277
+ """
278
+ page_tag_pattern = r'&lt;chatpdf:page number="(\d+)"&gt;\n?(.*?)\n?&lt;\/chatpdf:page&gt;\n?'
279
+ matches = re.findall(page_tag_pattern, document_with_page_tag, re.DOTALL)
280
+ pages = [Page(number=int(number), content=content) for number, content in matches]
281
+ return pages
282
+
283
+
284
+ def escape_latex(unescaped_text):
285
+ """
286
+ Chatbotのmarkdownで数式が表示されるように \\(, \\), \\[, \\] をバックスラッシュでエスケープする。
287
+
288
+ Args:
289
+ unescaped_text (str): エスケープ対象文字列
290
+
291
+ Returns:
292
+ str: エスケープされた文字列
293
+ """
294
+ return re.sub(r"(\\[\(\)\[\]])", r"\\\1", unescaped_text)
295
+
296
+
297
+ def unescape_latex(escaped_text):
298
+ """
299
+ Chatbotのmarkdownで数式が表示されるようにエスケープされていた \\(, \\), \\[, \\] をエスケープされていない元の括弧に変換する。
300
+
301
+ Args:
302
+ escaped_text (str): エスケープされた文字列
303
+
304
+ Returns:
305
+ str: エスケープされていない文字列
306
+ """
307
+ return re.sub(r"\\(\\[\(\)\[\]])", r"\1", escaped_text)
308
+
309
+
310
+ def add_s(values):
311
+ """
312
+ 複数形のsを必要に応じて付けるために用いる関数。
313
+ 与えられたリストの要素数が2以上なら"s"を返し、それ以外は""を返す。
314
+
315
+ Args:
316
+ values (list[any]): リスト
317
+
318
+ Returns:
319
+ str: 要素数が複数なら"s"、それ以外は""
320
+ """
321
+ return "s" if len(values) > 1 else ""
322
+
323
+
324
+ def get_context_info(characters, tokens):
325
+ """
326
+ 文字数とトークン数の情報を文字列で返す。
327
+
328
+ Args:
329
+ characters (str): テキスト
330
+ tokens (list[str]): トークン
331
+
332
+ Returns:
333
+ str: 文字数とトークン数の情報を含む文字列
334
+ """
335
+ char_count = len(characters)
336
+ token_count = len(tokens)
337
+ return f"{char_count:,} character{add_s(characters)}\n{token_count:,} token{add_s(tokens)}"
338
+
339
+
340
+ def update_context_element(pdf_file_obj):
341
+ """
342
+ PDFファイルからテキストを抽出し、コンテキスト要素を更新する。
343
+
344
+ Args:
345
+ pdf_file_obj (File): アップロードされたPDFファイルオブジェクト
346
+
347
+ Returns:
348
+ Tuple: コンテキストテキストボックスに格納する抽出されたテキスト情報と、その文字数情報
349
+ """
350
+ pages = extract_pdf_pages(pdf_file_obj.name)
351
+ document_with_tag = merge_pages_with_page_tag(pages)
352
+ return gr.update(value=document_with_tag, interactive=True), count_characters(document_with_tag)
353
+
354
+
355
+ def count_characters(document_with_tag):
356
+ """
357
+ テキストの文字数とトークン数を計算する。
358
+ ただし、テキストはchatpdf:pageというタグでページが括られているとする。
359
+
360
+ Args:
361
+ document_with_tag (str): 文字数とトークン数を計算するテキスト
362
+
363
+ Returns:
364
+ str: 文字数とトークン数の情報を含む文字列
365
+ """
366
+ text = "".join([page.content for page in extract_pages_from_page_tag(document_with_tag)])
367
+
368
+ tokens = OPENAI_TOKENIZER.encode(text)
369
+ return get_context_info(text, tokens)
370
+
371
+
372
+ class SearchEngine:
373
+ """
374
+ 検索エンジン
375
+ """
376
+ def __init__(self, engine, pages):
377
+ self.engine = engine
378
+ self.pages = pages
379
+
380
+
381
+ SEARCH_ENGINE = None
382
+
383
+ def create_search_engine(context):
384
+ """
385
+ 検索エンジンを作る。
386
+
387
+ Args:
388
+ context (str): 検索対象となるテキスト。ただし、テキストはchatpdf:pageというタグでページが括られているとする。
389
+ """
390
+ global SEARCH_ENGINE
391
+
392
+ pages = extract_pages_from_page_tag(context)
393
+ tokenized_pages = []
394
+ original_pages = []
395
+ for page in pages:
396
+ page_content = page.content.strip()
397
+ if page_content:
398
+ tokenized_page = [token.base_form for token in JANOME_ANALYZER.analyze(page_content)]
399
+ if tokenized_page:
400
+ tokenized_pages.append(tokenized_page)
401
+ original_pages.append(page)
402
+
403
+ if tokenized_pages:
404
+ bm25 = BM25Okapi(tokenized_pages)
405
+ SEARCH_ENGINE = SearchEngine(engine=bm25, pages=original_pages)
406
+ else:
407
+ SEARCH_ENGINE = None
408
+
409
+
410
+ def search_pages(keywords, page_limit):
411
+ """
412
+ 与えられたキーワードを含むページを検索する。
413
+
414
+ Args:
415
+ keywords (str): 検索キーワード
416
+ page_limit (int): 検索するページ数
417
+
418
+ Returns:
419
+ list[Page]: ヒットしたページ
420
+ """
421
+ global SEARCH_ENGINE
422
+ if SEARCH_ENGINE is None:
423
+ return []
424
+
425
+ tokenized_query = [token.base_form for token in JANOME_ANALYZER.analyze(keywords)]
426
+ if not tokenized_query:
427
+ return []
428
+
429
+ found_pages = SEARCH_ENGINE.engine.get_top_n(tokenized_query, SEARCH_ENGINE.pages, n=page_limit)
430
+ return found_pages
431
+
432
+
433
+ def load_pages(page_numbers):
434
+ """
435
+ 与えられたページ番号のページを取得する。
436
+
437
+ Args:
438
+ page_numbers (list[int]): 取得するページ番号
439
+
440
+ Returns:
441
+ list[Page]: 取得したページ
442
+ """
443
+ global SEARCH_ENGINE
444
+ if SEARCH_ENGINE is None:
445
+ return []
446
+
447
+ page_numbers = set(page_numbers)
448
+ found_pages = [page for page in SEARCH_ENGINE.pages if page.number in page_numbers]
449
+ return found_pages
450
+
451
+
452
+ # function calling用ツール
453
+ CHAT_TOOLS = [
454
+ # ページ検索
455
+ {
456
+ "type": "function",
457
+ "function": {
458
+ "name": "search_pages",
459
+ "description": "Searches for pages containing the given keywords.",
460
+ "parameters": {
461
+ "type": "object",
462
+ "properties": {
463
+ "keywords": {
464
+ "type": "string",
465
+ "description": 'Search keywords separated by spaces. For example, "Artificial General Intelligence 自律エージェント".'
466
+ },
467
+ "page_limit": {
468
+ "type": "number",
469
+ "description": "Maximum number of search results to return. For example, 3.",
470
+ "minimum": 1
471
+ }
472
+ }
473
+ },
474
+ "required": ["keywords"]
475
+ }
476
+ },
477
+ # ページ取得
478
+ {
479
+ "type": "function",
480
+ "function": {
481
+ "name": "load_pages",
482
+ "description": "Loads pages specified by their page numbers.",
483
+ "parameters": {
484
+ "type": "object",
485
+ "properties": {
486
+ "page_numbers": {
487
+ "type": "array",
488
+ "items": {
489
+ "type": "number"
490
+ },
491
+ "description": "List of page numbers to be load",
492
+ "minItems": 1
493
+ }
494
+ }
495
+ },
496
+ "required": ["page_numbers"]
497
+ }
498
+ }
499
+ ]
500
+
501
+ # function callingなど、固定で消費するトークン数
502
+ CHAT_TOOLS_TOKENS = 139
503
+
504
+
505
+ def get_openai_messages(prompt, history, context):
506
+ """
507
+ 与えられた対話用データを、ChatGPT APIの入力に用いられるメッセージデータ形式に変換して返す。
508
+
509
+ Args:
510
+ prompt (str): ユーザーからの入力プロンプト
511
+ history (list[list[str]]): チャット履歴
512
+ context (str): チャットコンテキスト
513
+
514
+ Returns:
515
+ str: ChatGPT APIの入力に用いられるメッセージデータ
516
+ """
517
+ global SEARCH_ENGINE
518
+ if SEARCH_ENGINE is not None:
519
+ context = "".join([page.content for page in SEARCH_ENGINE.pages])
520
+
521
+ messages = []
522
+ for user_message, assistant_message in history:
523
+ if user_message is not None and assistant_message is not None:
524
+ user_message = unescape_latex(user_message)
525
+ user_message = user_message.replace("{context}", context)
526
+ assistant_message = unescape_latex(assistant_message)
527
+ messages.append({ "role": "user", "content": user_message })
528
+ messages.append({ "role": "assistant", "content": assistant_message })
529
+
530
+ prompt = prompt.replace("{context}", context)
531
+ messages.append({ "role": "user", "content": prompt })
532
+
533
+ return messages
534
+
535
+
536
+ # それまでの全入力トークン数
537
+ actual_total_cost_prompt = 0
538
+
539
+ # それまでの全出力トークン数
540
+ actual_total_cost_completion = 0
541
+
542
+
543
+ async def process_prompt(prompt, history, context, platform, endpoint, azure_deployment, azure_api_version, api_key, model_name, max_tokens, temperature, enable_rag):
544
+ """
545
+ ユーザーのプロンプトを処理し、ChatGPTによる生成結果を返す。
546
+
547
+ Args:
548
+ prompt (str): ユーザーからの入力プロンプト
549
+ history (list[list[str]]): チャット履歴
550
+ context (str): チャットコンテキスト
551
+ platform (str): 使用するAIプラットフォーム
552
+ endpoint (str): AIサービスのエンドポイント
553
+ azure_deployment (str): Azureのデプロイメント名
554
+ azure_api_version (str): Azure APIのバージョン
555
+ api_key (str): APIキー
556
+ model_name (str): 使用するAIモデルの名前
557
+ max_tokens (int): 生成する最大トークン数
558
+ temperature (float): クリエイティビティの度合いを示す温度パラメータ
559
+ enable_rag (bool): RAG機能を有効にするかどうか
560
+
561
+ Returns:
562
+ str: ChatGPTによる生成結果
563
+ """
564
+ global actual_total_cost_prompt, actual_total_cost_completion
565
+
566
+ try:
567
+ messages = get_openai_messages(prompt, history, context)
568
+
569
+ if platform == "OpenAI":
570
+ openai_client = OpenAI(
571
+ base_url=endpoint,
572
+ api_key=api_key,
573
+ http_client=http_client
574
+ )
575
+ else: # Azure
576
+ openai_client = AzureOpenAI(
577
+ azure_endpoint=endpoint,
578
+ api_version=azure_api_version,
579
+ azure_deployment=azure_deployment,
580
+ api_key=api_key,
581
+ http_client=http_client
582
+ )
583
+
584
+ if enable_rag:
585
+ completion = openai_client.chat.completions.create(
586
+ messages=messages,
587
+ model=model_name,
588
+ max_tokens=max_tokens,
589
+ temperature=temperature,
590
+ tools=CHAT_TOOLS,
591
+ tool_choice="auto",
592
+ stream=False
593
+ )
594
+ else:
595
+ completion = openai_client.chat.completions.create(
596
+ messages=messages,
597
+ model=model_name,
598
+ max_tokens=max_tokens,
599
+ temperature=temperature,
600
+ stream=False
601
+ )
602
+
603
+ bot_response = ""
604
+ if hasattr(completion, "error"):
605
+ raise gr.Error(completion.error["message"])
606
+
607
+ response_message = completion.choices[0].message
608
+ tool_calls = response_message.tool_calls
609
+ actual_total_cost_prompt += completion.usage.prompt_tokens
610
+ actual_total_cost_completion += completion.usage.completion_tokens
611
+
612
+ if tool_calls:
613
+ messages.append(response_message)
614
+
615
+ for tool_call in tool_calls:
616
+ function_name = tool_call.function.name
617
+ function_args = json.loads(tool_call.function.arguments)
618
+ if function_name == "search_pages":
619
+ # ページ検索
620
+ keywords = function_args.get("keywords").strip()
621
+ page_limit = function_args.get("page_limit") or 3
622
+
623
+ bot_response += f'Searching for pages containing the keyword{add_s(keywords.split(" "))} "{keywords}".\n'
624
+
625
+ found_pages = search_pages(keywords, page_limit)
626
+ function_response = json.dumps({
627
+ "status": "found" if found_pages else "not found",
628
+ "found_pages": [{
629
+ "page_number": page.number,
630
+ "page_content": page.content
631
+ } for page in found_pages]
632
+ }, ensure_ascii=False)
633
+ messages.append({
634
+ "tool_call_id": tool_call.id,
635
+ "role": "tool",
636
+ "name": function_name,
637
+ "content": function_response
638
+ })
639
+ if found_pages:
640
+ bot_response += f'Found page{add_s(found_pages)}: {", ".join([str(page.number) for page in found_pages])}.\n\n'
641
+ else:
642
+ bot_response += "Page not found.\n\n"
643
+
644
+ elif function_name == "load_pages":
645
+ # ページ取得
646
+ page_numbers = function_args.get("page_numbers")
647
+
648
+ bot_response += f'Trying to load page{add_s(page_numbers)} {", ".join(map(str, page_numbers))}.\n'
649
+
650
+ found_pages = load_pages(page_numbers)
651
+ function_response = json.dumps({
652
+ "status": "found" if found_pages else "not found",
653
+ "found_pages": [{
654
+ "page_number": page.number,
655
+ "page_content": page.content
656
+ } for page in found_pages]
657
+ }, ensure_ascii=False)
658
+ messages.append({
659
+ "tool_call_id": tool_call.id,
660
+ "role": "tool",
661
+ "name": function_name,
662
+ "content": function_response
663
+ })
664
+ if found_pages:
665
+ bot_response += f'Found page{add_s(found_pages)}: {", ".join([str(page.number) for page in found_pages])}.\n\n'
666
+ else:
667
+ bot_response += "Page not found.\n\n"
668
+ else:
669
+ raise gr.Error(f"Unknown function calling '{function_name}'.")
670
+
671
+ yield bot_response + "Generating response. Please wait a moment...\n"
672
+ await asyncio.sleep(0.1)
673
+
674
+ completion = openai_client.chat.completions.create(
675
+ messages=messages,
676
+ model=model_name,
677
+ max_tokens=max_tokens,
678
+ temperature=temperature,
679
+ stream=False
680
+ )
681
+ actual_total_cost_prompt += completion.usage.prompt_tokens
682
+ actual_total_cost_completion += completion.usage.completion_tokens
683
+
684
+ if hasattr(completion, "error"):
685
+ raise gr.Error(completion.error["message"])
686
+
687
+ response_message = completion.choices[0].message
688
+ bot_response += response_message.content
689
+ yield bot_response
690
+
691
+ else:
692
+ bot_response += response_message.content
693
+ yield bot_response
694
+
695
+ except Exception as e:
696
+ if hasattr(e, "message"):
697
+ raise gr.Error(e.message)
698
+ else:
699
+ raise gr.Error(str(e))
700
+
701
+
702
+ def load_api_key(file_obj):
703
+ """
704
+ APIキーファイルからAPIキーを読み込む。
705
+
706
+ Args:
707
+ file_obj (File): APIキーファイルオブジェクト
708
+
709
+ Returns:
710
+ str: 読み込まれたAPIキー文字列
711
+ """
712
+ try:
713
+ with open(file_obj.name, "r", encoding="utf-8") as api_key_file:
714
+ return api_key_file.read().strip()
715
+ except Exception as e:
716
+ raise gr.Error(str(e))
717
+
718
+
719
+ def get_cost_info(prompt_token_count):
720
+ """
721
+ チャットのトークン数情報を表示するための文字列を返す。
722
+
723
+ Args:
724
+ prompt_token_count (int): プロンプト(履歴込み)のトークン数
725
+
726
+ Returns:
727
+ str: チャットのトークン数情報を表示するための文字列
728
+ """
729
+ return f"Estimated input cost: {prompt_token_count + CHAT_TOOLS_TOKENS:,} tokens, Actual total input cost: {actual_total_cost_prompt:,} tokens, Actual total output cost: {actual_total_cost_completion:,} tokens"
730
+
731
+
732
+ # デフォルト設定値
733
+ DEFAULT_SETTINGS = {
734
+ "setting_name": "Default",
735
+ "platform": "OpenAI",
736
+ "endpoint": "https://api.openai.com/v1",
737
+ "azure_deployment": "",
738
+ "azure_api_version": "",
739
+ "model_name": "gpt-4-turbo-preview",
740
+ "max_tokens": 4096,
741
+ "temperature": 0.2,
742
+ "enable_rag": True,
743
+ "save_chat_history_to_url": False
744
+ };
745
+
746
+
747
+ def main():
748
+ """
749
+ アプリケーションのメイン関数。Gradioインターフェースを設定し、アプリケーションを起動する。
750
+ """
751
+ try:
752
+ # クエリパラメータに保存されていることもあるチャット履歴を読み出す。
753
+ with open("chat_history.json", "r", encoding="utf-8") as f:
754
+ CHAT_HISTORY = json.load(f)
755
+ except Exception as e:
756
+ print(e)
757
+ CHAT_HISTORY = []
758
+
759
+ # localStorageから設定情報ををロードする。
760
+ js_define_utilities_and_load_settings = """() =&gt; {
761
+ const KEY_PREFIX = "serverless_chat_with_your_pdf:";
762
+
763
+ const loadSettings = () =&gt; {
764
+ const getItem = (key, defaultValue) =&gt; {
765
+ const jsonValue = localStorage.getItem(KEY_PREFIX + key);
766
+ if (jsonValue) {
767
+ return JSON.parse(jsonValue);
768
+ } else {
769
+ return defaultValue;
770
+ }
771
+ };
772
+ """ + "".join([f"""
773
+ const default_{setting_key} = {json.dumps(default_value, ensure_ascii=False)};
774
+ const {setting_key} = getItem("{setting_key}", default_{setting_key});
775
+ """ for setting_key, default_value in DEFAULT_SETTINGS.items()]) + """
776
+ const serialized_saved_settings = getItem("saved_settings", []);
777
+ const default_saved_settings = [[
778
+ """ + ", ".join([f"{json.dumps(default_value, ensure_ascii=False)}" for _, default_value in DEFAULT_SETTINGS.items()]) + """
779
+ ]];
780
+
781
+ saved_settings = [];
782
+ for (let entry of serialized_saved_settings) {
783
+ saved_settings.push([
784
+ entry["setting_name"] || "",
785
+ entry["platform"] || default_platform,
786
+ entry["endpoint"] || default_endpoint,
787
+ entry["azure_deployment"] || default_azure_deployment,
788
+ entry["azure_api_version"] || default_azure_api_version,
789
+ entry["model_name"] || default_model_name,
790
+ entry["max_tokens"] || default_max_tokens,
791
+ entry["temperature"] || default_temperature,
792
+ entry["enable_rag"] || default_enable_rag,
793
+ entry["save_chat_history_to_url"] || default_save_chat_history_to_url
794
+ ]);
795
+ }
796
+ if (saved_settings.length == 0) {
797
+ saved_settings = default_saved_settings;
798
+ }
799
+
800
+ return [setting_name, platform, endpoint, azure_deployment, azure_api_version, model_name, max_tokens, temperature, enable_rag, save_chat_history_to_url, saved_settings];
801
+ };
802
+
803
+ globalThis.resetSettings = () =&gt; {
804
+ for (let key in localStorage) {
805
+ if (key.startsWith(KEY_PREFIX) && !key.startsWith(KEY_PREFIX + "saved_settings") && !key.startsWith(KEY_PREFIX + "setting_name")) {
806
+ localStorage.removeItem(key);
807
+ }
808
+ }
809
+
810
+ return loadSettings();
811
+ };
812
+
813
+ globalThis.saveItem = (key, value) =&gt; {
814
+ localStorage.setItem(KEY_PREFIX + key, JSON.stringify(value));
815
+ };
816
+
817
+ return loadSettings();
818
+ }
819
+ """
820
+
821
+ # should_saveがtrueであればURLにチャット履歴を保存し、falseであればチャット履歴を削除する。
822
+ save_or_delete_chat_history = '''(hist, should_save) =&gt; {
823
+ saveItem("save_chat_history_to_url", should_save);
824
+ if (!should_save) {
825
+ const url = new URL(window.location.href);
826
+ url.searchParams.delete("history");
827
+ window.history.replaceState({path:url.href}, '', url.href);
828
+ } else {
829
+ const compressedHistory = LZString.compressToEncodedURIComponent(JSON.stringify(hist));
830
+ const url = new URL(window.location.href);
831
+ url.searchParams.set("history", compressedHistory);
832
+ window.history.replaceState({path:url.href}, '', url.href);
833
+ }
834
+ }'''
835
+
836
+ # メッセージ例
837
+ examples = {
838
+ "要約 (論文)": '''制約条件に従い、以下の研究論文で提案されている技術や手法について要約してください。
839
+
840
+ # 制約条件
841
+ * 要約者: 大学教授
842
+ * 想定読者: 大学院生
843
+ * 要約結果の言語: 日本語
844
+ * 要約結果の構成(以下の各項目について500文字):
845
+ 1. どんな研究であるか
846
+ 2. 先行研究に比べて優れている点は何か
847
+ 3. 提案されている技術や手法の重要な点は何か
848
+ 4. どのような方法で有効であると評価したか
849
+ 5. 何か議論はあるか
850
+ 6. 次に読むべき論文は何か
851
+
852
+ # 研究論文
853
+ """
854
+ {context}
855
+ """
856
+
857
+ # 要約結果''',
858
+ "要約 (一般)": '''制約条件に従い、以下の文書の内容を要約してください。
859
+
860
+ # 制約条件
861
+ * 要約者: 技術コンサルタント
862
+ * 想定読者: 経営層、CTO、CIO
863
+ * 形式: 箇条書き
864
+ * 分量: 20項目
865
+ * 要約結果の言語: 日本語
866
+
867
+ # 文書
868
+ """
869
+ {context}
870
+ """
871
+
872
+ # 要約''',
873
+ "情報抽出": '''制約条件に従い、以下の文書から情報を抽出してください。
874
+
875
+ # 制約条件
876
+ * 抽出する情報: 課題や問題点について言及している全ての文。一つも見落とさないでください。
877
+ * 出力形式: 箇条書き
878
+ * 出力言語: 元の言語の文章と、その日本語訳
879
+
880
+ # 文書
881
+ """
882
+ {context}
883
+ """
884
+
885
+ # 抽出結果''',
886
+ "QA (日本語文書RAG)": '''次の質問に回答するために役立つページを検索して、その検索結果を使って回答して下さい。
887
+
888
+ # 制約条件
889
+ * 検索クエリの生成方法: 質問文の3つの言い換え(paraphrase)をカンマ区切りで連結した文字列
890
+ * 検索クエリの言語: 日本語
891
+ * 検索するページ数: 3
892
+ * 回答方法:
893
+ - 検索結果の情報のみを用いて回答すること。
894
+ - 回答に利用した文章のあるページ番号を、それぞれの回答文の文末に付与すること。形式: "(参考ページ番号: 71, 59, 47)"
895
+ - 回答に役立つ情報が検索結果内にない場合は「検索結果には回答に役立つ情報がありませんでした。」と回答すること。
896
+ * 回答の言語: 日本語
897
+
898
+ # 質問
899
+ どのような方法で、提案された手法が有効であると評価しましたか?
900
+
901
+ # 回答''',
902
+ "QA (英語文書RAG)": '''次の質問に回答するために役立つページを検索して、その検索結果を使って回答して下さい。
903
+
904
+ # 制約条件
905
+ * 検索クエリの生成方法: 質問文の3つの言い換え(paraphrase)をカンマ区切りで連結した文字列
906
+ * 検索クエリの言語: 英語
907
+ * 検索するページ数: 3
908
+ * 回答方法:
909
+ - 検索結果の情報のみを用いて回答すること。
910
+ - 回答に利用した文章のあるページ番号を、それぞれの回答文の文末に付与すること。形式: "(参考ページ番号: 71, 59, 47)"
911
+ - 回答に役立つ情報が検索結果内にない場合は「検索結果には回答に役立つ情報がありませんでした。」と回答すること。
912
+ * 回答の言語: 日本語
913
+
914
+ # 質問
915
+ どのような方法で、提案された手法が有効であると評価しましたか?
916
+
917
+ # 回答''',
918
+ "要約 (RAG)": '''次のキーワードを含むページを検索して、その検索結果をページごとに要約して下さい。
919
+
920
+ # 制約条件
921
+ * キーワード: dataset datasets
922
+ * 検索するページ数: 3
923
+ * 要約結果の言語: 日本語
924
+ * 要約の形式:
925
+ ## ページ番号(例: 12ページ)
926
+ - 要約文1
927
+ - 要約文2
928
+ ...
929
+ * 要約の分量: 各ページ3項目
930
+
931
+ # 要約''',
932
+ "翻訳 (RAG)": '''次のキーワードを含むページを検索して��その検索結果を日本語に翻訳して下さい。
933
+
934
+ # 制約条件
935
+ * キーワード: dataset datasets
936
+ * 検索するページ数: 1
937
+
938
+ # 翻訳結果''',
939
+ "要約 (ページ指定)": '''16〜17ページをページごとに箇条書きで要約して下さい。
940
+
941
+ # 制約条件
942
+ * 要約結果の言語: 日本語
943
+ * 要約の形式:
944
+ ## ページ番号(例: 12ページ)
945
+ - 要約文1
946
+ - 要約文2
947
+ ...
948
+ * 要約の分量: 各ページ5項目
949
+
950
+ # 要約''',
951
+ "続きを生成": "続きを生成してください。"
952
+ }
953
+
954
+ with gr.Blocks(theme=gr.themes.Default(), analytics_enabled=False) as app:
955
+ with gr.Tabs():
956
+ with gr.TabItem("Settings"):
957
+ with gr.Column():
958
+ with gr.Column(variant="panel"):
959
+ with gr.Row():
960
+ setting_name = gr.Textbox(label="Setting Name", value="Default", interactive=True)
961
+ setting_name.change(None, inputs=setting_name, outputs=None,
962
+ js='(x) =&gt; saveItem("setting_name", x)', show_progress="hidden")
963
+
964
+ with gr.Row():
965
+ platform = gr.Radio(label="Platform", interactive=True,
966
+ choices=["OpenAI", "Azure"], value="OpenAI")
967
+ platform.change(None, inputs=platform, outputs=None,
968
+ js='(x) =&gt; saveItem("platform", x)', show_progress="hidden")
969
+
970
+ with gr.Row():
971
+ endpoint = gr.Textbox(label="Endpoint", interactive=True)
972
+ endpoint.change(None, inputs=endpoint, outputs=None,
973
+ js='(x) =&gt; saveItem("endpoint", x)', show_progress="hidden")
974
+
975
+ azure_deployment = gr.Textbox(label="Azure Deployment", interactive=True)
976
+ azure_deployment.change(None, inputs=azure_deployment, outputs=None,
977
+ js='(x) =&gt; saveItem("azure_deployment", x)', show_progress="hidden")
978
+
979
+ azure_api_version = gr.Textbox(label="Azure API Version", interactive=True)
980
+ azure_api_version.change(None, inputs=azure_api_version, outputs=None,
981
+ js='(x) =&gt; saveItem("azure_api_version", x)', show_progress="hidden")
982
+
983
+ with gr.Group():
984
+ with gr.Row():
985
+ api_key_file = gr.File(file_count="single", file_types=["text"],
986
+ height=80, label="API Key File")
987
+ api_key = gr.Textbox(label="API Key", type="password", interactive=True)
988
+ # 注意: 秘密情報をlocalStorageに保存してはならない。他者に秘密情報が盗まれる危険性があるからである。
989
+
990
+ api_key_file.upload(load_api_key, inputs=api_key_file, outputs=api_key,
991
+ show_progress="hidden")
992
+ api_key_file.clear(lambda: None, inputs=None, outputs=api_key, show_progress="hidden")
993
+
994
+ with gr.Row():
995
+ model_name = gr.Textbox(label="Model", interactive=True)
996
+ model_name.change(None, inputs=model_name, outputs=None,
997
+ js='(x) =&gt; saveItem("model_name", x)', show_progress="hidden")
998
+
999
+ max_tokens = gr.Number(label="Max Tokens", interactive=True,
1000
+ minimum=0, precision=0, step=1)
1001
+ max_tokens.change(None, inputs=max_tokens, outputs=None,
1002
+ js='(x) =&gt; saveItem("max_tokens", x)', show_progress="hidden")
1003
+
1004
+ temperature = gr.Slider(label="Temperature", interactive=True,
1005
+ minimum=0.0, maximum=1.0, step=0.1)
1006
+ temperature.change(None, inputs=temperature, outputs=None,
1007
+ js='(x) =&gt; saveItem("temperature", x)', show_progress="hidden")
1008
+
1009
+ enable_rag = gr.Checkbox(label="Enable RAG (Retrieval Augmented Generation)", interactive=True)
1010
+ enable_rag.change(None, inputs=enable_rag, outputs=None,
1011
+ js='(x) =&gt; saveItem("enable_rag", x)', show_progress="hidden")
1012
+
1013
+ save_chat_history_to_url = gr.Checkbox(label="Save Chat History to URL", interactive=True)
1014
+
1015
+ reset_button = gr.Button("Reset Settings")
1016
+
1017
+ with gr.Column(variant="panel"):
1018
+ default_saved_settings = list(DEFAULT_SETTINGS.values())
1019
+
1020
+ saved_settings_df = gr.Dataframe(
1021
+ elem_id="saved_settings",
1022
+ value=[default_saved_settings],
1023
+ headers=["Name", "Platform", "Endpoint", "Azure Deployment", "Azure API Version", "Model", "Max Tokens", "Temperature", "Enable RAG", "Save Chat History to URL"],
1024
+ row_count=(0, "dynamic"),
1025
+ col_count=(10, "fixed"),
1026
+ datatype=["str", "str", "str", "str", "str", "str", "number", "number", "bool", "bool"],
1027
+ type="array",
1028
+ label="Saved Settings",
1029
+ show_label=True,
1030
+ interactive=False
1031
+ )
1032
+ selected_setting = gr.State(None)
1033
+ temp_selected_row_index = gr.JSON(value=None, visible=False)
1034
+
1035
+
1036
+ def select_setting(event: gr.SelectData):
1037
+ return (event.index[0], event.index[1]), event.index[0]
1038
+
1039
+
1040
+ saved_settings_df.select(
1041
+ select_setting, inputs=None, outputs=[selected_setting, temp_selected_row_index], queue=False, show_progress="hidden"
1042
+ ).then(
1043
+ None, inputs=temp_selected_row_index, outputs=None, js='(row_index) =&gt; { for (let e of document.querySelectorAll("#saved_settings > div > div > button > svelte-virtual-table-viewport > table > tbody > tr")[row_index].children) { e.classList.add("focus"); } }', queue=False, show_progress="hidden"
1044
+ )
1045
+
1046
+ with gr.Row():
1047
+ load_saved_settings_button = gr.Button("Load")
1048
+ append_or_overwrite_saved_settings_button = gr.Button("Append or Overwrite")
1049
+ delete_saved_settings_button = gr.Button("Delete")
1050
+
1051
+ serialized_saved_settings_state = gr.JSON(visible=False)
1052
+
1053
+
1054
+ def load_saved_setting(saved_settings, selected_setting):
1055
+ if not selected_setting:
1056
+ return saved_settings
1057
+
1058
+ def u(x):
1059
+ return gr.update(value=x, interactive=True)
1060
+
1061
+ row_index = selected_setting[0]
1062
+
1063
+ setting_name, platform, endpoint, azure_deployment, azure_api_version, model_name, max_tokens, temperature, enable_rag, save_chat_history_to_url = saved_settings[row_index]
1064
+
1065
+ return u(setting_name), u(platform), u(endpoint), u(azure_deployment), u(azure_api_version), u(model_name), u(max_tokens), u(temperature), u(enable_rag), u(save_chat_history_to_url), None
1066
+
1067
+
1068
+ load_saved_settings_button.click(load_saved_setting, inputs=[saved_settings_df, selected_setting], outputs=[setting_name, platform, endpoint, azure_deployment, azure_api_version, model_name, max_tokens, temperature, enable_rag, save_chat_history_to_url, selected_setting], queue=False, show_progress="hidden")
1069
+
1070
+
1071
+ def append_or_overwrite_setting(saved_settings, setting_name, platform, endpoint, azure_deployment, azure_api_version, model_name, max_tokens, temperature, enable_rag, save_chat_history_to_url):
1072
+
1073
+ setting_name = setting_name.strip()
1074
+
1075
+ found = False
1076
+ new_saved_settings = []
1077
+ for entry in saved_settings:
1078
+ if entry[0] == setting_name:
1079
+ new_saved_settings.append([setting_name, platform, endpoint, azure_deployment, azure_api_version,model_name, max_tokens, temperature, enable_rag, save_chat_history_to_url])
1080
+ found = True
1081
+ else:
1082
+ new_saved_settings.append(entry)
1083
+
1084
+ if not found:
1085
+ new_saved_settings.append([setting_name, platform, endpoint, azure_deployment, azure_api_version,model_name, max_tokens, temperature, enable_rag, save_chat_history_to_url])
1086
+
1087
+ return new_saved_settings, None
1088
+
1089
+
1090
+ def serialize_saved_settings(saved_settings):
1091
+ serialization_keys = list(DEFAULT_SETTINGS.keys())
1092
+
1093
+ serialized_saved_settings = [
1094
+ { k: entry[i] for i, k in enumerate(serialization_keys) }
1095
+ for entry in saved_settings
1096
+ ]
1097
+ return serialized_saved_settings
1098
+
1099
+
1100
+ append_or_overwrite_saved_settings_button.click(
1101
+ append_or_overwrite_setting, inputs=[saved_settings_df, setting_name, platform, endpoint, azure_deployment, azure_api_version,model_name, max_tokens, temperature, enable_rag, save_chat_history_to_url], outputs=[saved_settings_df, selected_setting], queue=False, show_progress="hidden"
1102
+ ).then(
1103
+ serialize_saved_settings, inputs=saved_settings_df, outputs=serialized_saved_settings_state, queue=False, show_progress="hidden",
1104
+ ).then(
1105
+ None, inputs=serialized_saved_settings_state, outputs=None, js='(x) =&gt; saveItem("saved_settings", x)', queue=False, show_progress="hidden"
1106
+ )
1107
+
1108
+
1109
+ def delete_setting(saved_settings, selected_setting):
1110
+ if not selected_setting:
1111
+ return saved_settings
1112
+
1113
+ row_index = selected_setting[0]
1114
+ new_saved_settings = saved_settings[0:row_index] + saved_settings[row_index + 1:]
1115
+
1116
+ if not new_saved_settings:
1117
+ new_saved_settings.append(default_saved_settings)
1118
+
1119
+ return new_saved_settings, None
1120
+
1121
+
1122
+ delete_saved_settings_button.click(
1123
+ delete_setting, inputs=[saved_settings_df, selected_setting], outputs=[saved_settings_df, selected_setting], queue=False, show_progress="hidden"
1124
+ ).then(
1125
+ serialize_saved_settings, inputs=saved_settings_df, outputs=serialized_saved_settings_state, queue=False, show_progress="hidden",
1126
+ ).then(
1127
+ None, inputs=serialized_saved_settings_state, outputs=None, js='(x) =&gt; saveItem("saved_settings", x)', queue=False, show_progress="hidden"
1128
+ )
1129
+
1130
+ temp_saved_settings = gr.JSON(visible=False)
1131
+ temp_saved_settings.change(lambda x: x, inputs=temp_saved_settings, outputs=saved_settings_df, queue=False, show_progress="hidden")
1132
+
1133
+ setting_items = [setting_name, platform, endpoint, azure_deployment, azure_api_version, model_name, max_tokens, temperature, enable_rag, save_chat_history_to_url, temp_saved_settings]
1134
+ reset_button.click(None, inputs=None, outputs=setting_items,
1135
+ js="() =&gt; resetSettings()", show_progress="hidden")
1136
+
1137
+ with gr.TabItem("Chat"):
1138
+ with gr.Row():
1139
+ with gr.Column(scale=1):
1140
+ pdf_file = gr.File(file_count="single", file_types=[".pdf"],
1141
+ height=80, label="PDF")
1142
+ context = gr.Textbox(elem_id="context", label="Context", lines=20,
1143
+ interactive=True, autoscroll=False, show_copy_button=True)
1144
+ char_counter = gr.Textbox(label="Statistics", value=get_context_info("", []),
1145
+ lines=2, max_lines=2, interactive=False, container=True)
1146
+
1147
+ pdf_file.upload(update_context_element, inputs=pdf_file, outputs=[context, char_counter], queue=False)
1148
+ pdf_file.clear(lambda: None, inputs=None, outputs=context, queue=False, show_progress="hidden")
1149
+
1150
+ with gr.Column(scale=2):
1151
+
1152
+ additional_inputs = [context, platform, endpoint, azure_deployment, azure_api_version, api_key, model_name, max_tokens, temperature, enable_rag]
1153
+
1154
+ with gr.Blocks() as chat:
1155
+ gr.Markdown(f"# Chat with your PDF")
1156
+
1157
+ with gr.Column(variant="panel"):
1158
+ chatbot = gr.Chatbot(
1159
+ CHAT_HISTORY,
1160
+ elem_id="chatbot", height=500, show_copy_button=True,
1161
+ sanitize_html=True, render_markdown=True,
1162
+ latex_delimiters=[
1163
+ # { "left": "$$", "right": "$$", "display": True },
1164
+ # { "left": "$", "right": "$", "display": False },
1165
+ { "left": "\\(", "right": "\\)", "display": False },
1166
+ { "left": "\\[", "right": "\\]", "display": True },
1167
+ ],
1168
+ layout="bubble",
1169
+ avatar_images=[None, "https://raw.githubusercontent.com/sonoisa/misc/main/resources/icons/chatbot_icon.png"]
1170
+ )
1171
+
1172
+ message_state = gr.State()
1173
+ chatbot_state = gr.State(chatbot.value) if chatbot.value else gr.State([])
1174
+
1175
+ with gr.Group():
1176
+ with gr.Row():
1177
+ message_textbox = gr.Textbox(placeholder="Type a message...",
1178
+ container=False, show_label=False, autofocus=True, interactive=True, scale=7)
1179
+
1180
+ submit_button = gr.Button("Submit", variant="primary", scale=1, min_width=150)
1181
+ stop_button = gr.Button("Stop", variant="stop", visible=False, scale=1, min_width=150)
1182
+
1183
+ cost_info = gr.Textbox(elem_id="cost_info", value=get_cost_info(0),
1184
+ lines=1, max_lines=1, interactive=False, container=False, elem_classes="cost_info")
1185
+
1186
+ with gr.Row():
1187
+ retry_button = gr.Button("🔄 Retry", variant="secondary", size="sm")
1188
+ undo_button = gr.Button("↩️ Undo", variant="secondary", size="sm")
1189
+ clear_button = gr.Button("🗑️ Clear", variant="secondary", size="sm")
1190
+
1191
+
1192
+ def estimate_message_cost(prompt, history, context):
1193
+ token_count = 0
1194
+ messages = get_openai_messages(prompt, history, context)
1195
+ for message in messages:
1196
+ tokens = OPENAI_TOKENIZER.encode(message["content"])
1197
+ token_count += len(tokens)
1198
+
1199
+ return gr.update(value=get_cost_info(token_count))
1200
+
1201
+
1202
+ message_textbox.change(estimate_message_cost, inputs=[message_textbox, chatbot, context], outputs=cost_info, queue=False, show_progress="hidden")
1203
+
1204
+ example_title_textbox = gr.Textbox(visible=False, interactive=True)
1205
+ gr.Examples([[k] for k, v in examples.items()],
1206
+ inputs=example_title_textbox, outputs=message_textbox,
1207
+ fn=lambda title: examples[title], run_on_click=True)
1208
+
1209
+
1210
+ def append_message_to_history(message, history):
1211
+ message = escape_latex(message)
1212
+ history.append([message, None])
1213
+ return history, history
1214
+
1215
+
1216
+ def undo_chat(history):
1217
+ if history:
1218
+ message, _ = history.pop()
1219
+ message = message or ""
1220
+ else:
1221
+ message = ""
1222
+ return history, history, unescape_latex(message)
1223
+
1224
+
1225
+ async def submit_message(message, history_with_input, *args):
1226
+ history = history_with_input[:-1]
1227
+ inputs = [message, history]
1228
+ inputs.extend(args)
1229
+
1230
+ generator = process_prompt(*inputs)
1231
+ message = escape_latex(message)
1232
+
1233
+ has_response = False
1234
+ async for response in generator:
1235
+ has_response = True
1236
+ response = escape_latex(response)
1237
+ update = history + [[message, response]]
1238
+ yield update, update
1239
+
1240
+ if not has_response:
1241
+ update = history + [[message, None]]
1242
+ yield update, update
1243
+
1244
+
1245
+ submit_triggers = [message_textbox.submit, submit_button.click]
1246
+
1247
+ submit_event = gr.events.on(
1248
+ submit_triggers, lambda message: ("", message), inputs=[message_textbox], outputs=[message_textbox, message_state], queue=False
1249
+ ).then(
1250
+ append_message_to_history, inputs=[message_state, chatbot_state], outputs=[chatbot, chatbot_state], queue=False
1251
+ ).then(
1252
+ submit_message, inputs=[message_state, chatbot_state] + additional_inputs, outputs=[chatbot, chatbot_state]
1253
+ ).then(
1254
+ estimate_message_cost, inputs=[message_textbox, chatbot, context], outputs=cost_info, show_progress="hidden"
1255
+ )
1256
+
1257
+ for submit_trigger in submit_triggers:
1258
+ submit_trigger(lambda: (gr.update(visible=False), gr.update(visible=True)),
1259
+ inputs=None, outputs=[submit_button, stop_button], queue=False)
1260
+ submit_event.then(lambda: (gr.update(visible=True), gr.update(visible=False)),
1261
+ inputs=None, outputs=[submit_button, stop_button], queue=False)
1262
+
1263
+ stop_button.click(None, inputs=None, outputs=None, cancels=submit_event)
1264
+
1265
+ retry_button.click(
1266
+ undo_chat, inputs=[chatbot_state], outputs=[chatbot, chatbot_state, message_state], queue=False
1267
+ ).then(
1268
+ append_message_to_history, inputs=[message_state, chatbot_state], outputs=[chatbot, chatbot_state], queue=False
1269
+ ).then(
1270
+ submit_message, inputs=[message_state, chatbot_state] + additional_inputs, outputs=[chatbot, chatbot_state]
1271
+ ).then(
1272
+ estimate_message_cost, inputs=[message_textbox, chatbot, context], outputs=cost_info, show_progress="hidden"
1273
+ )
1274
+
1275
+ undo_button.click(
1276
+ undo_chat, inputs=[chatbot_state], outputs=[chatbot, chatbot_state, message_state], queue=False
1277
+ ).then(
1278
+ lambda message: message, inputs=message_state, outputs=message_textbox, queue=False
1279
+ ).then(
1280
+ estimate_message_cost, inputs=[message_textbox, chatbot, context], outputs=cost_info, show_progress="hidden"
1281
+ )
1282
+
1283
+ clear_button.click(
1284
+ lambda: ([], [], None), inputs=None, outputs=[chatbot, chatbot_state, message_state],
1285
+ queue=False
1286
+ ).then(
1287
+ estimate_message_cost, inputs=[message_textbox, chatbot, context], outputs=cost_info, show_progress="hidden"
1288
+ )
1289
+
1290
+ chatbot.change(None, inputs=[chatbot, save_chat_history_to_url], outputs=None,
1291
+ # チャット履歴をクエリパラメータに保存する。
1292
+ js=save_or_delete_chat_history, queue=False, show_progress="hidden")
1293
+
1294
+ save_chat_history_to_url.change(None, inputs=[chatbot, save_chat_history_to_url], outputs=None,
1295
+ js=save_or_delete_chat_history, queue=False, show_progress="hidden")
1296
+
1297
+ context.change(
1298
+ count_characters, inputs=context, outputs=char_counter, queue=False, show_progress="hidden"
1299
+ ).then(
1300
+ create_search_engine, inputs=context, outputs=None
1301
+ ).then(
1302
+ estimate_message_cost, inputs=[message_textbox, chatbot, context], outputs=cost_info, show_progress="hidden"
1303
+ )
1304
+
1305
+ app.load(None, inputs=None, outputs=setting_items, js=js_define_utilities_and_load_settings, show_progress="hidden")
1306
+
1307
+ app.queue().launch()
1308
+
1309
+ main()
1310
+ </gradio-file>
1311
+ </gradio-lite>
1312
+
1313
+ <script language="javascript" src="https://cdn.jsdelivr.net/npm/lz-string@1.5.0/libs/lz-string.min.js"></script>
1314
+ <script language="javascript">
1315
+ (function () {
1316
+ // クエリパラメータにチャット履歴が記録されていたらそれをロードし、chat_history.jsonファイルに書き出す。
1317
+ const url = new URL(window.location.href);
1318
+ if (url.searchParams.has("history")) {
1319
+ const compressedHistory = url.searchParams.get("history");
1320
+ hist = LZString.decompressFromEncodedURIComponent(compressedHistory);
1321
+ const chat_history_element = document.querySelector('gradio-file[name="chat_history.json"]');
1322
+ chat_history_element.textContent = hist;
1323
+ }
1324
+ })();
1325
+ </script>
1326
+ <script type="module" crossorigin src="https://cdn.jsdelivr.net/npm/@gradio/lite@5.4.0/dist/lite.js"></script>
1327
+ </body>
1328
+ </html>