update
Browse files- app/Http/controller/G1 +1 -0
- app/Http/controller/G2 +1 -0
- app/Http/controller/G3 +1 -0
- app/Http/controller/test_folders/.gitignore +3 -5
- app/Http/controller/test_folders/.gpteng/memory/logs/all_output.txt +343 -0
- app/Http/controller/test_folders/.gpteng/memory/logs/gen_entrypoint_chat.txt +196 -0
- app/Http/controller/test_folders/Structure +2 -5
- app/Http/controller/test_folders/api/app/main.py +3 -3
- app/Http/controller/test_folders/api/app/models/__init__.py +8 -3
- app/Http/controller/test_folders/api/app/models/knowledge.py +5 -6
- app/Http/controller/test_folders/api/app/models/team.py +7 -5
- app/Http/controller/test_folders/api/app/models/user.py +7 -7
- app/Http/controller/test_folders/api/app/routers/knowledge.py +10 -13
- app/Http/controller/test_folders/api/app/routers/team.py +11 -13
- app/Http/controller/test_folders/api/app/routers/user.py +12 -16
- app/Http/controller/test_folders/api/app/schemas/team.py +1 -5
- app/Http/controller/test_folders/api/app/schemas/user.py +1 -5
- app/Http/controller/test_folders/prompt +59 -242
- app/Http/controller/test_folders/requirements.txt +3 -2
- app/Http/controller/test_folders/run.sh +2 -4
- staticfiles/gradio.html +1328 -0
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 |
-
|
| 2 |
-
/
|
| 3 |
-
.
|
| 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 |
-
|
| 20 |
-
|
| 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 |
-
|
| 7 |
-
|
| 8 |
-
|
|
|
|
| 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
|
| 2 |
-
from .
|
| 3 |
-
from .
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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.
|
| 3 |
-
|
| 4 |
-
Base = declarative_base()
|
| 5 |
|
| 6 |
class Knowledge(Base):
|
| 7 |
__tablename__ = "knowledge"
|
|
|
|
| 8 |
id = Column(Integer, primary_key=True)
|
| 9 |
-
term = Column(String
|
| 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.
|
| 3 |
-
|
| 4 |
-
Base = declarative_base()
|
| 5 |
|
| 6 |
class Team(Base):
|
| 7 |
__tablename__ = "teams"
|
|
|
|
| 8 |
id = Column(Integer, primary_key=True)
|
| 9 |
-
name = Column(String,
|
| 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.
|
| 3 |
-
|
| 4 |
-
Base = declarative_base()
|
| 5 |
|
| 6 |
class User(Base):
|
| 7 |
__tablename__ = "users"
|
|
|
|
| 8 |
id = Column(Integer, primary_key=True)
|
| 9 |
-
username = Column(String,
|
| 10 |
-
password = Column(String
|
| 11 |
profile = Column(String)
|
| 12 |
tags = Column(String)
|
| 13 |
-
|
| 14 |
-
|
|
|
|
| 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
|
| 4 |
-
from .schemas import KnowledgeCreate, Knowledge
|
| 5 |
|
| 6 |
router = APIRouter()
|
| 7 |
|
| 8 |
@router.post("/knowledge/")
|
| 9 |
-
async def create_knowledge(knowledge:
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 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
|
| 4 |
-
from
|
| 5 |
|
| 6 |
router = APIRouter()
|
| 7 |
|
| 8 |
@router.post("/teams/")
|
| 9 |
-
async def create_team(team:
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 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
|
| 4 |
-
from
|
| 5 |
|
| 6 |
router = APIRouter()
|
| 7 |
|
| 8 |
@router.post("/users/")
|
| 9 |
-
async def create_user(user:
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
|
|
|
|
|
|
|
|
|
| 13 |
return {"message": "User created successfully"}
|
| 14 |
|
| 15 |
@router.get("/users/")
|
| 16 |
-
async def
|
| 17 |
-
users =
|
| 18 |
-
return [
|
| 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
|
| 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 |
-
|
| 69 |
-
※2:"即分割”は100gのみ
|
| 70 |
-
※3:プラチナは100gのみ
|
| 71 |
-
確認:
|
| 72 |
-
税率 :10%
|
| 73 |
-
消費税額等 : 円
|
| 74 |
-
ご請求金額(税込) : 円
|
| 75 |
-
4.ご依頼品明細
|
| 76 |
-
ご依頼品ブランド/形状
|
| 77 |
-
シリアルNo
|
| 78 |
-
重量(g)
|
| 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 |
-
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 |
-
|
| 2 |
-
|
|
|
|
|
|
| 1 |
+
fastapi
|
| 2 |
+
sqlalchemy
|
| 3 |
+
pydantic
|
app/Http/controller/test_folders/run.sh
CHANGED
|
@@ -1,4 +1,2 @@
|
|
| 1 |
-
python -m
|
| 2 |
-
|
| 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 |
+
"<|endoftext|>": 100257,
|
| 198 |
+
"<|fim_prefix|>": 100258,
|
| 199 |
+
"<|fim_middle|>": 100259,
|
| 200 |
+
"<|fim_suffix|>": 100260,
|
| 201 |
+
"<|endofprompt|>": 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'<chatpdf:page number="{page.number}">\n{page.content}\n</chatpdf:page>\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'<chatpdf:page number="(\d+)">\n?(.*?)\n?<\/chatpdf:page>\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 = """() => {
|
| 761 |
+
const KEY_PREFIX = "serverless_chat_with_your_pdf:";
|
| 762 |
+
|
| 763 |
+
const loadSettings = () => {
|
| 764 |
+
const getItem = (key, defaultValue) => {
|
| 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 = () => {
|
| 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) => {
|
| 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) => {
|
| 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) => 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) => 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) => 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) => 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) => 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) => 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) => 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) => 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) => 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) => { 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) => 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) => 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="() => 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>
|