Closed5

会議の音声データから議事録を作ってくれる「Meeting Mind」を試す

kun432kun432

GitHubレポジトリ

https://github.com/misbahsy/meetingmind

MeetingMind - 1時間の会議を30秒で分析(Powered by Langflow)

MeetingMindは、AIを活用した会議アシスタントで、会議内容を簡単に記録、分析し、その結果に基づいて行動できるようサポートします。このプロジェクトは、Langflow、Next.js、Groqベースの高速文字起こしサービスを使用して会議を分析し、インサイトを生成します。

機能

  • オーディオ録音およびファイルのアップロード
  • AIによる文字起こし
  • 以下の重要情報の自動抽出:
    • タスク
    • 決定事項
    • 質問
    • インサイト
    • 締め切り
    • 出席者
    • フォローアップ
    • リスク
    • アジェンダ

初めに

前提条件

  • Node.js 14.x以上
  • npmまたはyarn
  • ローカルで稼働中のLangFlowサーバー
  • Git(リポジトリのクローン作成用)

注意事項

⚠️ 重要: Groq Whisperを使用した文字起こしと分析では、現在25MBまでのファイルのみ対応しています。この制限を超える場合、圧縮ステップが追加されます。それでも25MBを超える場合は、アップロード前に音声ファイルを圧縮してください。この制限は、長時間の会議や高品質の音声録音に影響を及ぼす可能性があります。

音声ファイルをさらに圧縮するには、以下のツールを使用できます:

  • オンライン音声圧縮ツール
  • FFmpeg(音声/動画処理用のコマンドラインツール)

25MBの制限内に収まりつつ、正確な書き起こしに十分な音質を確保してください。


前から試したいと思ってたんだけど、以前確認した際は、

  • Langflowを別途セットアップする必要がある
  • ローカルにNode.js環境が必要

って感じでちょっとめんどくさいと思っていた。できればdocker composeで管理しやすくしたいなーと思いながらも結局手を付けれてなかったのだけど、久々に見てみたらいつの間にやら対応してたので、試してみる。

kun432kun432

インストール

https://github.com/misbahsy/meetingmind?tab=readme-ov-file#docker-setup

ということでdockerでセットアップできるようになったみたいなので、それを試してみる。前提として以下が必要になるので適宜用意。

  • Groq APIキー
  • OpenAI APIキー

レポジトリクローン

git clone https://github.com/misbahsy/meetingmind && cd meetingmind

docker compose で Meeting Mindのバックエンドで動かす Langflow・PostgreSQL・SQLiteのコンテナを起動する

docker compose up -d
docker ps
出力
CONTAINER ID   IMAGE                        COMMAND                   CREATED          STATUS          PORTS                    NAMES
69ad3371abe6   langflowai/langflow:latest   "langflow run"            31 seconds ago   Up 31 seconds   0.0.0.0:7860->7860/tcp   langflow
6f32c585d662   postgres:16                  "docker-entrypoint.s…"   31 seconds ago   Up 31 seconds   0.0.0.0:5432->5432/tcp   meetingmind-postgres-1
dd36b28f67b1   nouchka/sqlite3:latest       "sqlite3"                 31 seconds ago   Up 31 seconds                            meetingmind-database-1

LangFlowサーバ(http://localhost:7860/)にブラウザでアクセスすると以下の画面になるので、とりあえず新しいフローを作成。ここは実際には使わないので何でも良い。

フロー作成画面が表示されたら、上の"MyProjects"をクリックして、プロジェクト一覧画面に戻る。

プロジェクト一覧画面で左上のアップロードアイコンをクリックして、クローンしたレポジトリ内にあるutils/langflow_flow/Meeting Mind.jsonをアップロードする。

Meeting Mind用のフローが作成されるので、これをクリック。

こんな感じのフローが作成されている。コンポーネントのアップデートが可能、みたいなメッセージがあるが、とりあえず無視する。

フローの詳細は説明しないが、

  • GroqのWhisperモデルを使って音声ファイルを文字起こし
  • 文字起こしをプロンプトテンプレートに埋め込んでOpenAIでテキスト生成

という感じになっている。よって、それぞれAPIキーをセットする必要がある。

まずGroq。APIキーをセットする前に、選択できるモデルを見てみると、英語向けモデルしか選択できない。実際のGroqではwhisper-large-v3/whisper-large-v3-turboモデルも使用できるため、このコンポーネントのコードを直接編集する。

コードが表示される。該当の箇所に選択できるモデルの定義があるので、修正して保存する。

変更前
(snip)
        DropdownInput(
            name="model_name",
            display_name="Model",
            info="The name of the model to use.",
            options=["distil-whisper-large-v3-en"],
            value="distil-whisper-large-v3-en",
        ),
(snip)
変更後
(snip)
        DropdownInput(
            name="model_name",
            display_name="Model",
            info="The name of the model to use.",
            options=["whisper-large-v3-turbo"],
            value="whisper-large-v3-turbo",
        ),
(snip)

これでモデルを変更してAPIキーをセットしておく。

次にOpenAI。こちらはAPIキーをセットするだけ。

Langflowは以前試していた際にも少し感じたのだけど、このあたりの変更に関していまいち保存のタイミングとかがよくわからない。Ctrl+Sで明示的に保存しておくと良いと思う。

フローの変更はこれでOKなのだが、一部確認しておく必要がある。右上の"API"をクリック。

このフローに対してAPIリクエストを送る場合のサンプルコードが表示されるのだが、以下の2点を確認しておく。これはMeeting Mindの設定に必要になる。

  • このフローのAPIエンドポイントURL
  • GroqWhisperComponent-*で始まる部分の名前

次にMeeting Mindのコンテナをビルドする。コンテナのビルド前に上で確認した設定を反映する。

.env.localに上記フローのAPIエンドポイントURLを指定する。といっても全部まるっと置き換える必要はなくて、/api/v1/run/**** の ****の部分を書き換えるだけでいい。あと、今回のケースだとDockerコンテナからDockerコンテナへのアクセスになると思うので、ホスト名をhost.docker.internalにする必要があると思う。

.env.local
LANGFLOW_FLOW_URL="http://host.docker.internal:7860/api/v1/run/7bb5ffc7-6ff8-4764-954e-80a89a565abc"

もう1つ。app/api/transcribe/route.tsを開いて、Groqコンポーネント名を定義している箇所があるので、こちらも上で確認した設定に反映する。

app/api/transcribe/route.ts
(snip)
    // Prepare JSON payload
    const payload = {
      output_type: 'text',
      input_type: 'text',
      tweaks: {
        'GroqWhisperComponent-QFwZj': {
          audio_file: filePath // Use the full path of the saved file
        },
      }
    }
(snip)

Dockerイメージをビルド

docker build -t meetingmind .

ビルドしたイメージからコンテナを起動

docker run -p 3000:3000 meetingmind

ブラウザでhttp://localhost:3000にアクセスすると以下が表示される。"Get Started"をクリック。

ファイルアップロードと過去の実行結果のサンプルが表示されている。参考までにどのようなものが生成されるのかを見てみる。

サマリーと文字起こしが表示されている。

タブを切り替えると、細かいタスクや決定事項、リスクなど複数の観点でまとめられているのがわかる。

では実際に音声ファイルを使ってやってみる。以下のサイトで会議の議事録練習用に公開されている音声データを使用させていただく。

https://note.com/note_0530/n/neb6a205744bd

なお、Groq Whisperの制約上、25MBが上限となる。上記で公開されているものはWAVでサイズがギリギリだったので、自分はffmpegでmp3に圧縮した。

圧縮したmp3をアップロードして"Transcribe Audio"をクリック。

なのだが、Meeting Mindのコンテナのログを見てみるとどうもエラーが起きている。

File saved at: /app/public/uploads/1736480674291-sample.mp3
Sending request to API: http://host.docker.internal:7860/api/v1/run/7bb5ffc7-6ff8-4764-954e-80a89a565abc
Error in /api/transcribe: AxiosError: Request failed with status code 500
(snip)
    method: 'post',
    url: 'http://host.docker.internal:7860/api/v1/run/7bb5ffc7-6ff8-4764-954e-80a89a565abc',
    data: '{"output_type":"text","input_type":"text","tweaks":{"GroqWhisperComponent-QFwZj":{"audio_file":"/app/public/uploads/1736480674291-sample.mp3"}}}'
(snip)
    data: {
      detail: `{"message":"Error running graph: Error building Component Groq Whisper: \\n\\n[Errno 2] No such file or directory: '/app/public/uploads/1736480674291-sample.mp3'","traceback":null,"description":null,"code":null,"suggestion":"The flow contains 3 outdated components. We recommend updating the following components: TextInput-o2e9x, OpenAIModel-5gcAG, JSONCleaner-7JcxP."}`
    }
  },
  status: 500
}

どうやらオーディオファイルを見つからない様子。Meeting Mindのコンテナの中を見てみると、ファイルは存在する。

$ ls -lt /app/public/uploads/1736480674291-sample.mp3
-rw-r--r-- 1 root root 2320317 Jan 10 03:44 /app/public/uploads/1736480674291-sample.mp3

Langflow側のコンテナを見ると上記のパスはない。

$ ls -lt /app/public
ls: cannot access '/app/public': No such file or directory

というかそもそもAPIリクエストで送信されたmp3ファイルが見当たらない。

$ find / -type f -name "*mp3"
$

LangflowのUIからファイルをアップロードしてみると以下のパスに保存された

$ find / -type f -name "*mp3"
/app/data/.cache/langflow/7bb5ffc7-6ff8-4764-954e-80a89a565abc/2025-01-10_03-59-34_sample.mp3

でcurlで直接実行してみたのだけども、

  • Meeting Mindコンテナでのパス(/app/public/uploads/1736480674291-sample.mp3)指定だとGroq Whisperのブロックでファイルが見つからないというエラーになる
  • LangFlowのUIでアップロードしたファイルのパス(/app/data/.cache/langflow/7bb5ffc7-6ff8-4764-954e-80a89a565abc/2025-01-10_03-59-34_sample.mp3)だと、フローの後ろの方のコンポーネントでのエラーになる、つまり、Groq Whisperのブロックは問題なく実行されたことになる。
    • ちなみに「コンポーネントのアップデートが可能」みたいなメッセージが出ていたと思うが、これを実行してアップデートすると、エラーは起きなくなり処理される。

ということでどうもAPIリクエストで指定するファイルのパスはLangFlowサーバ上のパスを指定するというのが正しい模様。この辺見ててもアップロード用のAPIエンドポイントは別にあるように思える。

https://github.com/langflow-ai/langflow/issues/4014

となるとそもそもアップロードができていないのでは?ということになるが、
app/api/transcribe/route.tsを見ている限りアップロードに関する処理は見当たらないんだよね。。。

果たしてちゃんと動かせた人いるんだろうか???

kun432kun432

色々コード見る限り、どうやっても今の状態で動く気がしないのと、Docker化もPR経由で行われたみたいだけど、果たして本当にテストしたんか?という気しかしない・・・

kun432kun432

翌々考えてみれば、文字起こししてプロンプトに埋め込んでLLMに投げるだけ、だから、こんな仕組みはいらんのよね。

Colaboratoryでやってみた。whisperもOpenAIで。

!pip install openai
import os
from google.colab import userdata

os.environ['OPENAI_API_KEY'] = userdata.get('OPENAI_API_KEY')

プロンプトテンプレート

prompt_template_str = """\
Based on the user transcription data you have access to, you need to produce a Breakdown of Categories:

Tasks: Tasks with varying priorities, owners, and due dates.
Example task assignments include preparing reports, setting up meetings, and submitting proposals.

Decisions: Important decisions made during the meeting
Decisions include vendor choice, marketing strategy, and budget approval.

Questions: Questions raised during the meeting, with their status (answered/unanswered).
Answered questions include additional context in the form of answers.

Insights: Insights based on the conversation, ranging from sales performance to concerns about deadlines.
Each insight refer back to the exact part of the conversation.

Deadlines: Upcoming deadlines related to the budget, product launch, and client presentation.
This helps track time-sensitive matters.

Attendees: Attendees who attended the meeting
This tracks attendance and their respective roles.

Follow-ups: Follow-up tasks assigned to individuals after the meeting, each with a due date.
Follow-up items focus on clarifying budget, design, and scheduling next actions.

Risks: Risks identified during the meeting, each with potential impacts on the project.
These include risks like budget overruns, delays, and potential staff turnover.

Agenda: A list of the agenda items covered in the meeting.
The agenda provides a structured overview of the topics discussed. You need to extract as many items as you can, some might have 1-2 items, and some might 10, so make sure to capture every point.

Meeting Name: The title of the meeting, reflecting its official designation. This gives a clear identifier for the meeting, often including a specific date or purpose, such as "October 2024 Municipal Council Meeting."

Description: A high-level overview of the meeting’s purpose and key areas of focus. The description captures the essential topics discussed, decisions made, and the overall scope of the meeting, such as infrastructure updates, budget approvals, and key community concerns.

Summary: A brief consolidation of the main points and outcomes from the meeting. The summary encapsulates the flow of the meeting, including major tasks, decisions, and action points, along with any significant challenges or risks highlighted, offering a concise review of the meeting’s results.

You must format your output as a JSON data, like:

${output_example}


The transcript is as follow:

${transcription}
"""

output_example = """\
{
  "Breakdown": {
    "tasks": [
      {
        "description": "Prepare a report on the status of the procedural bylaw 2410.",
        "assigned_to": "CEO",
        "priority": "High"
      },
      {
        "description": "Follow up with the public works department regarding the tree asset projections.",
        "assigned_to": "Nomar",
        "priority": "Medium"
      },
      {
        "description": "Gather names for the two vacant positions on the Northeast Red Watershed District Committee.",
        "assigned_to": "Council Members",
        "priority": "Medium"
      },
      {
        "description": "Draft the policy changes for the new community grant system.",
        "assigned_to": "Grants Officer",
        "priority": "High"
      },
      {
        "description": "Schedule a meeting with local business leaders to discuss economic growth initiatives.",
        "assigned_to": "Economic Development Manager",
        "priority": "Low"
      }
    ],
    "decisions": [
      {
        "description": "The agenda for the meeting was approved unanimously."
      },
      {
        "description": "The council activity reports for September were received as information."
      },
      {
        "description": "The additional cost of $33,300 for the Settlers Road Bridge Crossing project will be included in the 2025 capital budget."
      },
      {
        "description": "Nonprofit organizations and community service groups will receive grants for 2024 as listed."
      },
      {
        "description": "The municipal recreation center expansion was approved with an amended budget."
      },
      {
        "description": "The council decided to allocate $15,000 for the local library digital resources upgrade."
      }
    ],
    "questions": [
      {
        "question": "What is the status of the procedural bylaw 2410?",
        "raised_by": "Janet",
        "status": "Unanswered"
      },
      {
        "question": "Why are the costs for the Settlers Road Bridge Crossing project increasing?",
        "raised_by": "Andy",
        "status": "Answered",
        "answer": "The costs are increasing due to unforeseen costs and additional decommissioning requirements for the existing infrastructure."
      },
      {
        "question": "What is the Society of Ivan Franco?",
        "raised_by": "Mark",
        "status": "Answered",
        "answer": "It used to be an active community club located off Warren Hill Road, and discussions are ongoing about obtaining that land."
      },
      {
        "question": "When will the public works department complete the tree asset projections?",
        "raised_by": "Councilor Miller",
        "status": "Pending"
      },
      {
        "question": "How is the council planning to address the growing number of emergency motor vehicle collisions?",
        "raised_by": "Glenn",
        "status": "Unanswered"
      }
    ],
    "insights": [
      {
        "description": "The council is committed to supporting seniors' activities and improving lodging for seniors in the community.",
      },
      {
        "description": "There is a need for a daycare center in the RM, and land has been identified for this purpose.",
      },
      {
        "description": "The increase in emergency motor vehicle collisions is concerning and needs further investigation.",
      },
      {
        "description": "Public interest in developing additional recreational trails continues to grow.",
      },
      {
        "description": "The community expressed concerns about rising utility rates in the region.",
      }
    ],
    "deadlines": [
      {
        "description": "Submit names for the two vacant positions on the Northeast Red Watershed District Committee.",
        "date": "2024-10-15"
      },
      {
        "description": "Prepare the report on the procedural bylaw 2410 for the next meeting.",
        "date": "2024-10-15"
      },
      {
        "description": "Submit the draft policy changes for the new community grant system.",
        "date": "2024-11-01"
      },
      {
        "description": "Submit the budget proposal for the library digital resources upgrade.",
        "date": "2024-10-20"
      }
    ],
    "attendees": [
      {
        "name": "Mr. Mayor"
      },
      {
        "name": "Councilor Miller"
      },
      {
        "name": "Councilor Fuels"
      },
      {
        "name": "Councilor Kaczynski"
      },
      {
        "name": "Councilor Warren"
      },
      {
        "name": "Councilor Lee"
      },
      {
        "name": "Mark"
      },
      {
        "name": "Melinda"
      },
      {
        "name": "Andy"
      },
      {
        "name": "Glenn"
      },
      {
        "name": "Janet"
      },
      {
        "name": "Nomar"
      },
      {
        "name": "Public Works Director"
      },
      {
        "name": "Grants Officer"
      }
    ],
    "follow_ups": [
      {
        "description": "Follow up with the finance team regarding the budget approval for the Settlers Road Bridge Crossing project.",
        "owner": "CEO",
        "due_date": "2024-10-18"
      },
      {
        "description": "Prepare a detailed report on the emergency motor vehicle collisions for the next meeting.",
        "owner": "Public Works Director",
        "due_date": "2024-10-15"
      },
      {
        "description": "Meet with the daycare development committee to review the proposed land options.",
        "owner": "Planning Department",
        "due_date": "2024-10-22"
      },
      {
        "description": "Organize a public forum on recreational trail development.",
        "owner": "Community Engagement Coordinator",
        "due_date": "2024-10-30"
      }
    ],
    "risks": [
      {
        "description": "There is a risk of budget overruns due to unforeseen costs in ongoing projects."
      },
      {
        "description": "Potential delays in the Settlers Road Bridge Crossing project could impact future budgets."
      },
      {
        "description": "A shortage of qualified contractors may delay the municipal recreation center expansion."
      },
      {
        "description": "Uncertainty around the future of federal infrastructure funding could affect long-term projects."
      }
    ],
    "agenda": [
      "Invocation and land acknowledgement",
      "Approval of the agenda",
      "Adoption of the minutes from the previous meeting",
      "Reports from council activities",
      "Departmental reports",
      "Question period",
      "Consent agenda",
      "Settlers Road Bridge Crossing project discussion",
      "Northeast Red Watershed District Committee appointments",
      "2024 nonprofit community grants discussion",
      "Public forum planning for recreational trail development",
      "2025 capital budget planning",
      "Utility rate increase concerns",
      "Closing of the meeting"
    ],
   "meeting_name": "October 2024 Municipal Council Meeting",
   "description": "This meeting covered several key topics including updates on ongoing infrastructure projects, community grant allocations, and recreational development initiatives. Key decisions were made regarding the Settlers Road Bridge Crossing, the municipal recreation center expansion, and the allocation of funds for the local library's digital resources. Questions and concerns about rising utility rates and the growing number of emergency motor vehicle collisions were raised. Several tasks, follow-ups, and deadlines were established to address ongoing issues, and risks were identified for future project planning.",
   "summary": "The meeting involved high-priority tasks such as preparing a procedural bylaw report, addressing public works follow-ups, and drafting policy changes for the community grant system. Key decisions included funding allocations for infrastructure projects and community services. Several questions were raised about project cost increases and emergency incidents, while the council focused on issues like recreational trail development and senior support. Insights indicated growing public concerns over utility rates, and various risks to project budgets and timelines were discussed."
  }
}
"""

Whisper文字起こしとLLMに投げる関数の定義

from openai import OpenAI

def get_transcription(client, audio_file):
    try:
        audio_file= open(audio_file, "rb")
        transcription = client.audio.transcriptions.create(
            model="whisper-1", 
            file=audio_file
        )
        return transcription.text
    except Exception:
        raise


def get_meeting_summary(client, prompt):
    try:
        completion = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[
                {
                    "role": "system",
                    "content": "あなたは会議の議事録をまとめるプロです。日本語で最後の議事録を出力してください。"
                },
                {
                    "role": "user",
                    "content": prompt
                }
            ]
        )
        return completion.choices[0].message.content
    except Exception:
        raise

メイン部分はこれだけ。上で紹介した議事録用音声を使用。

import string

client = OpenAI()

try:
    transcription = get_transcription(client, "sample.mp3")    
    prompt_template = string.Template(prompt_template_str)
    prompt = prompt_template.substitute(output_example=output_example, transcription=transcription)
    summary = get_meeting_summary(client, prompt)
    print(summary)
except Exception as e:
    print("エラー")

結果

出力
{
  "Breakdown": {
    "tasks": [
      {
        "description": "依頼されている10期能文のレビューを完了する。",
        "assigned_to": "高橋",
        "priority": "高"
      },
      {
        "description": "予約機能の後続作業として画面設計書を執筆する。",
        "assigned_to": "田中",
        "priority": "中"
      },
      {
        "description": "お客様からの要望に応じて決済機能の設計に関する相談を行う。",
        "assigned_to": "山田",
        "priority": "高"
      },
      {
        "description": "ノート社への提案資料を作成する。",
        "assigned_to": "佐藤",
        "priority": "高"
      },
      {
        "description": "高橋さんに提案予定の製品の特徴をまとめたメモを共有する。",
        "assigned_to": "高橋",
        "priority": "中"
      }
    ],
    "decisions": [
      {
        "description": "定例会議の頻度を現在の週一から、来月から毎朝9時から15分間の情報共有に変更することが決定された。"
      },
      {
        "description": "次回の定例会議を1時間前倒しで11月8日に開催することに決定した。"
      }
    ],
    "questions": [
      {
        "question": "決済機能の設計に原因で遅れが出る理由は?",
        "raised_by": "山田",
        "status": "Answered",
        "answer": "お客様からの新たな要件でQRコード決済を取り込みたいとの要望があったため。"
      },
      {
        "question": "新しい研修はいつまで続くのか?",
        "raised_by": "進行役",
        "status": "Answered",
        "answer": "今週いっぱいまで続く。"
      },
      {
        "question": "西村さんの空き時間はいつか?",
        "raised_by": "山田",
        "status": "Pending"
      }
    ],
    "insights": [
      {
        "description": "遅延が発生し始めているため、定例会議の頻度を高める必要がある。"
      },
      {
        "description": "体調不良からの回復を受け、業務の復帰が期待されている。"
      }
    ],
    "deadlines": [
      {
        "description": "次回の定例進捗会議に向けて成果物を準備する。",
        "date": "2024-11-08"
      },
      {
        "description": "新しい予約機能の設計を完了する。",
        "date": "2024-10-31"
      }
    ],
    "attendees": [
      {
        "name": "高橋"
      },
      {
        "name": "菊池"
      },
      {
        "name": "田中"
      },
      {
        "name": "山田"
      },
      {
        "name": "佐藤"
      },
      {
        "name": "西村"
      }
    ],
    "follow_ups": [
      {
        "description": "決済機能の設計に関する要望をお客様と話し合う。",
        "owner": "山田",
        "due_date": "2024-10-06"
      },
      {
        "description": "提案資料の作成に向け、高橋が他メンバーに必要な情報を共有する。",
        "owner": "高橋",
        "due_date": "2024-10-10"
      }
    ],
    "risks": [
      {
        "description": "予約機能の設計の遅延が後続の作業に影響を及ぼす可能性がある。"
      },
      {
        "description": "決済機能の要望対応が長引くと、納期に影響を与えるリスクが存在する。"
      }
    ],
    "agenda": [
      "開会の挨拶",
      "参加者の進捗報告",
      "今後のスケジュールの確認",
      "会議頻度の変更についての議論",
      "次回会議の日時調整",
      "閉会の挨拶"
    ],
    "meeting_name": "2024年10月 定例進捗会議",
    "description": "この会議では、プロジェクトの進捗報告と今後のスケジュールについて討議しました。各メンバーからの報告を受けて、遅延の現状と対応策について話し合い、定例会議の開催頻度を変更することが決定されました。また、参加者全員が次回の会議日時を調整する場面もありました。",
    "summary": "会議では各メンバーの進捗報告があり、特に遅延が発生している項目について重点的に討論されました。新たな決済機能の要望に対する対応や、今後の情報共有のための会議頻度変更が主なテーマとなりました。全体的に、スムーズな進行と適切な対応が求められている状況でした。"
  }
}

もうこれで十分じゃないかな?

kun432kun432

まとめ

ちょっと現状だと動かせる状態にないような気がするというか、実際にやっていることはシンプルなので、わざわざUI作ってLangflowのバックエンド用意するほうが手間、というのが正直なところ。

ニーズ自体はありそうで、なんならChatGPTやGeminiでも十分できるとは思うんだけど、プロンプト考えるのが面倒とか毎回コピペするのは面倒、とかはあると思うので、そのあたりと環境用意する手間の天秤になりそう。

これぐらいならGradioのシンプルなUIで十分な気がした。

このスクラップは2025/01/10にクローズされました