VPSで「認証つきDify環境」を作る⑤:Difyアプリを「Customモデル」としてUIに出す
💡 この記事はシリーズ⑤です。
ここでは、Difyで作ったフロー(=アプリ)をChatbotUIの「モデル選択」に表示できるようにします。
もともとChatbotUIにはCustomルートの仕組みがあるので、そこへDifyを載せるイメージです。
⑤Difyアプリを「Customモデル」としてUIに出す
ここでは、Difyで作ったフロー(=アプリ)を、ChatbotUIの「モデル選択」に出せるようにします。もともと、ChatbotUIにはCustomルートを設定する用意がありますので、それを使えるようにしていくイメージです。
このあたりからは、ゴリゴリとコードを書き直していきますのでややこしくなります。頑張ってついてきてくださいね。
では、順番にいってみましょう!
1.Supabase のスキーマを拡張(最小識別子)
どのサービスに送るのか(OpenAI/Custom など)と、Difyの会話ID(続きの会話に使う)を保存するため、チャットの履歴テーブル public.chats に、送信先と会話IDを入れる欄を追加します。
Supabaseを開いて、ダッシュボード → 左メニュー SQL editor を開き、以下をコピペして実行(Run/Ctrl+Enter)します。

-- どのプロバイダに送るか(既存は openai 等)を保持
alter table public.chats
add column if not exists provider text not null default 'openai';
-- Dify の会話継続に使う conversation_id を保持(初回は null)
alter table public.chats
add column if not exists provider_thread_id text;
-- provider='dify' のレコードを 'custom' に更新
update public.chats
set provider = 'custom'
where provider = 'dify';
2.「Custom」を使える状態(有効化)にする~サーバからUIへお知らせ~
サーバ側で「Customを使いますよ」とUIに伝えるフラグを立てます。
app/api/keys/route.tsに一行追記します。
cd /opt/chatbot-ui
sudo nano /opt/chatbot-ui/app/api/keys/route.ts
赤字部分を一行追記します。
// app/api/keys/route.ts
import { isUsingEnvironmentKey } from "@/lib/envs"
import { createResponse } from "@/lib/server/server-utils"
import { EnvKey } from "@/types/key-type"
import { VALID_ENV_KEYS } from "@/types/valid-keys"
export async function GET() {
const envKeyMap: Record<string, VALID_ENV_KEYS> = {
azure: VALID_ENV_KEYS.AZURE_OPENAI_API_KEY,
openai: VALID_ENV_KEYS.OPENAI_API_KEY,
google: VALID_ENV_KEYS.GOOGLE_GEMINI_API_KEY,
anthropic: VALID_ENV_KEYS.ANTHROPIC_API_KEY,
mistral: VALID_ENV_KEYS.MISTRAL_API_KEY,
groq: VALID_ENV_KEYS.GROQ_API_KEY,
perplexity: VALID_ENV_KEYS.PERPLEXITY_API_KEY,
openrouter: VALID_ENV_KEYS.OPENROUTER_API_KEY,
openai_organization_id: VALID_ENV_KEYS.OPENAI_ORGANIZATION_ID,
azure_openai_endpoint: VALID_ENV_KEYS.AZURE_OPENAI_ENDPOINT,
azure_gpt_35_turbo_name: VALID_ENV_KEYS.AZURE_GPT_35_TURBO_NAME,
azure_gpt_45_vision_name: VALID_ENV_KEYS.AZURE_GPT_45_VISION_NAME,
azure_gpt_45_turbo_name: VALID_ENV_KEYS.AZURE_GPT_45_TURBO_NAME,
azure_embeddings_name: VALID_ENV_KEYS.AZURE_EMBEDDINGS_NAME
}
const isUsingEnvKeyMap = Object.keys(envKeyMap).reduce<Record<string, boolean>>(
(acc, provider) => {
const key = envKeyMap[provider]
if (key) acc[provider] = isUsingEnvironmentKey(key as EnvKey)
return acc
},
{}
)
+ isUsingEnvKeyMap.custom = true
return createResponse({ isUsingEnvKeyMap }, 200)
}
3.「Customモデル一覧」を返すAPIを作る
新規ファイルを作成します。このファイルが、UIがDifyアプリ(=dify_appsテーブルの内容)を読んで一覧表示するための中核です。
ディレクトリ&ファイルを作成します。
sudo mkdir -p /opt/chatbot-ui/app/api/models/custom
sudo nano /opt/chatbot-ui/app/api/models/custom/route.ts
以下の内容を貼り付けしてください。
// app/api/models/custom/route.ts
import { NextResponse } from 'next/server'
import { supabaseAdmin } from '@/lib/supabaseAdmin'
export const dynamic = 'force-dynamic' // キャッシュ抑止(一覧が変わる可能性があるため)
export const runtime = 'nodejs' // Edge 実行を避ける
export async function GET() {
const { data, error } = await supabaseAdmin
.from('dify_apps')
.select('slug, display_name')
.eq('is_active', true)
.order('display_name', { ascending: true })
if (error) {
return NextResponse.json({ error: error.message }, { status: 500 })
}
const models = (data ?? []).map((r) => ({
id: r.slug, // chats.model に入る値
name: r.display_name, // UI表示名
}))
return NextResponse.json(models, { status: 200 })
}
4. 画面(UI)に「Customモデル」を出す
APIで取ってきた Customモデルの一覧を、既存のモデルリストに混ぜて、「モデル選択」に表示できるようにします。未登録のIDでも壊れないよう予防も入れます。8つのファイルを作成・編集して、最後にテストします。
①空のCustomリストを用意
【新規作成ファイル】lib/models/custom-llm-list.ts
custom 用の空リストを新規作成します。
cd /opt/chatbot-ui
sudo nano /opt/chatbot-ui/lib/models/custom-llm-list.ts
以下の内容をコピペしてください。
// /lib/models/custom-llm-list.ts
import type { LLM } from "@/types";
export const CUSTOM_LLM_LIST: LLM[] = [];
②総合リストにCustomを足す
【編集するファイル】lib/models/llm/llm-list.ts
CUSTOM_LLM_LIST を import → LLM_LIST と LLM_LIST_MAP に custom を追加します。
cd /opt/chatbot-ui
sudo nano /opt/chatbot-ui/lib/models/llm/llm-list.ts
「+」部分を3行追記します。
import { LLM } from "@/types"
import { ANTHROPIC_LLM_LIST } from "./anthropic-llm-list"
import { GOOGLE_LLM_LIST } from "./google-llm-list"
import { MISTRAL_LLM_LIST } from "./mistral-llm-list"
import { GROQ_LLM_LIST } from "./groq-llm-list"
import { OPENAI_LLM_LIST } from "./openai-llm-list"
import { PERPLEXITY_LLM_LIST } from "./perplexity-llm-list"
+ import { CUSTOM_LLM_LIST } from "../custom-llm-list"
export const LLM_LIST: LLM[] = [
...OPENAI_LLM_LIST,
...GOOGLE_LLM_LIST,
...MISTRAL_LLM_LIST,
...GROQ_LLM_LIST,
...PERPLEXITY_LLM_LIST,
+ ...CUSTOM_LLM_LIST,
...ANTHROPIC_LLM_LIST
]
export const LLM_LIST_MAP: Record<string, LLM[]> = {
openai: OPENAI_LLM_LIST,
azure: OPENAI_LLM_LIST,
google: GOOGLE_LLM_LIST,
mistral: MISTRAL_LLM_LIST,
groq: GROQ_LLM_LIST,
perplexity: PERPLEXITY_LLM_LIST,
+ custom: CUSTOM_LLM_LIST,
anthropic: ANTHROPIC_LLM_LIST
}
③モデル取得の流れにCustomを組み込む
【編集するファイル】lib/models/fetch-models.ts
/api/keys の返り値で custom:true の時だけ /api/models/custom を呼ぶようにします。また、取得した {id, name} を LLM 型に直してリストへ追加します。あと、失敗してもアプリが落ちないように try/catch しておきます。
cd /opt/chatbot-ui
sudo nano /opt/chatbot-ui/lib/models/fetch-models.ts
「+」部分を追記します。
import { Tables } from "@/supabase/types"
import { LLM, LLMID, OpenRouterLLM } from "@/types"
import { toast } from "sonner"
import { LLM_LIST_MAP } from "./llm/llm-list"
export const fetchHostedModels = async (profile: Tables<"profiles">) => {
try {
const providers = ["google", "anthropic", "mistral", "groq", "perplexity"]
if (profile.use_azure_openai) {
providers.push("azure")
} else {
providers.push("openai")
}
const response = await fetch("/api/keys")
if (!response.ok) {
throw new Error(`Server is not responding.`)
}
const data = await response.json()
let modelsToAdd: LLM[] = []
for (const provider of providers) {
let providerKey: keyof typeof profile
if (provider === "google") {
providerKey = "google_gemini_api_key"
} else if (provider === "azure") {
providerKey = "azure_openai_api_key"
} else {
providerKey = `${provider}_api_key` as keyof typeof profile
}
if (profile?.[providerKey] || data.isUsingEnvKeyMap[provider]) {
const models = LLM_LIST_MAP[provider]
if (Array.isArray(models)) {
modelsToAdd.push(...models)
}
}
}
+ if (data?.isUsingEnvKeyMap?.custom) {
+ try {
+ const res = await fetch("/api/models/custom", { cache: "no-store" })
+ if (res.ok) {
+ const customList: Array<{ id: string; name: string }> = await res.json()
+ const mapped: LLM[] = customList.map((m) => ({
+ modelId: m.id as LLMID, // chats.model に入る値(= slug)
+ modelName: m.name || m.id, // UI表示名
+ provider: "custom", // ← ここが送信先の判定キー
+ hostedId: m.id, // 任意。IDをそのまま
+ platformLink: "https://www.example.com", // 表示用リンク(任意)
+ imageInput: false // Dify側の画像入力を使わない前提(使うなら true に)
+ }))
+ modelsToAdd.push(...mapped)
+ } else {
+ console.warn("Failed to fetch custom models: " + (await res.text()))
+ }
+ } catch (e) {
+ console.warn("Error fetching custom models: " + e)
+ }
+ }
return {
envKeyMap: data.isUsingEnvKeyMap,
hostedModels: modelsToAdd
}
} catch (error) {
console.warn("Error fetching hosted models: " + error)
}
}
// --- 以下略 ---
④型(LLMID)をゆるくしてslugを受け入れる
【編集するファイル】types/llms.ts
export type CustomLLMID = string; を足し、LLMID に含めます。
cd /opt/chatbot-ui
sudo nano /opt/chatbot-ui/types/llms.ts
「+」部分を2行追記します。
import { ModelProvider } from "."
export type LLMID =
| OpenAILLMID
| GoogleLLMID
| AnthropicLLMID
| MistralLLMID
| GroqLLMID
| PerplexityLLMID
+ | CustomLLMID
// OpenAI Models (UPDATED 5/13/24)
export type OpenAILLMID =
| "gpt-4o" // GPT-4o
| "gpt-4-turbo-preview" // GPT-4 Turbo
| "gpt-4-vision-preview" // GPT-4 Vision
| "gpt-4" // GPT-4
| "gpt-3.5-turbo" // Updated GPT-3.5 Turbo
// Google Models
export type GoogleLLMID =
| "gemini-pro" // Gemini Pro
| "gemini-pro-vision" // Gemini Pro Vision
| "gemini-1.5-pro-latest" // Gemini 1.5 Pro
| "gemini-1.5-flash" // Gemini 1.5 Flash
// Anthropic Models
export type AnthropicLLMID =
| "claude-2.1" // Claude 2
| "claude-instant-1.2" // Claude Instant
| "claude-3-haiku-20240307" // Claude 3 Haiku
| "claude-3-sonnet-20240229" // Claude 3 Sonnet
| "claude-3-opus-20240229" // Claude 3 Opus
| "claude-3-5-sonnet-20240620" // Claude 3.5 Sonnet
// Mistral Models
export type MistralLLMID =
| "mistral-tiny" // Mistral Tiny
| "mistral-small-latest" // Mistral Small
| "mistral-medium-latest" // Mistral Medium
| "mistral-large-latest" // Mistral Large
export type GroqLLMID =
| "llama3-8b-8192" // LLaMA3-8b
| "llama3-70b-8192" // LLaMA3-70b
| "mixtral-8x7b-32768" // Mixtral-8x7b
| "gemma-7b-it" // Gemma-7b IT
// Perplexity Models (UPDATED 1/31/24)
export type PerplexityLLMID =
| "pplx-7b-online" // Perplexity Online 7B
| "pplx-70b-online" // Perplexity Online 70B
| "pplx-7b-chat" // Perplexity Chat 7B
| "pplx-70b-chat" // Perplexity Chat 70B
| "mixtral-8x7b-instruct" // Mixtral 8x7B Instruct
| "mistral-7b-instruct" // Mistral 7B Instruct
| "llama-2-70b-chat" // Llama2 70B Chat
| "codellama-34b-instruct" // CodeLlama 34B Instruct
| "codellama-70b-instruct" // CodeLlama 70B Instruct
| "sonar-small-chat" // Sonar Small Chat
| "sonar-small-online" // Sonar Small Online
| "sonar-medium-chat" // Sonar Medium Chat
| "sonar-medium-online" // Sonar Medium Online
+ // Dify(Custom)のモデルID(= slug)
+ export type CustomLLMID = string;
export interface LLM {
modelId: LLMID
modelName: string
provider: ModelProvider
hostedId: string
platformLink: string
imageInput: boolean
pricing?: {
currency: string
unit: string
inputCost: number
outputCost?: number
}
}
export interface OpenRouterLLM extends LLM {
maxContext: number
}
⑤未登録IDの安全弁(上限値の既定)
【編集するファイル】lib/chat-setting-limits.ts
DEFAULT_LIMITS を用意して、未知のIDでもUIが壊れないようにします。代表例として support-bot を1件登録しておくのもOK。getChatSettingLimits(id) を作って、無ければ既定値を返します。
cd /opt/chatbot-ui
sudo nano /opt/chatbot-ui/lib/chat-setting-limits.ts
「+」部分を2ブロック追記します。
import { LLMID } from "@/types"
type ChatSettingLimits = {
MIN_TEMPERATURE: number
MAX_TEMPERATURE: number
MAX_TOKEN_OUTPUT_LENGTH: number
MAX_CONTEXT_LENGTH: number
}
+ // ★追加: 未登録モデル用の既定値(UIのスライダー等が未定義で壊れないように)
+ export const DEFAULT_LIMITS: ChatSettingLimits = {
+ MIN_TEMPERATURE: 0.0,
+ MAX_TEMPERATURE: 2.0,
+ MAX_TOKEN_OUTPUT_LENGTH: 4096,
+ MAX_CONTEXT_LENGTH: 128000
+ }
export const CHAT_SETTING_LIMITS: Record<LLMID, ChatSettingLimits> = {
// ANTHROPIC MODELS
// --- 中略 ---
"sonar-medium-online": {
MIN_TEMPERATURE: 0.0,
MAX_TEMPERATURE: 1.0,
MAX_TOKEN_OUTPUT_LENGTH: 12000,
MAX_CONTEXT_LENGTH: 12000
},
+ // ★任意: Dify(Custom)用に代表の slug を1件だけ登録しておくと安心
+ // (新しい slug は登録しなくても、下のフォールバックで動作します)
+ "support-bot": {
+ MIN_TEMPERATURE: 0.0,
+ MAX_TEMPERATURE: 2.0,
+ MAX_TOKEN_OUTPUT_LENGTH: 4096,
+ MAX_CONTEXT_LENGTH: 128000
+ }
+ }
+ // ★追加: 未登録モデルIDのときに既定値を返すヘルパ
+ export const getChatSettingLimits = (id: string): ChatSettingLimits => {
+ return CHAT_SETTING_LIMITS[id as keyof typeof CHAT_SETTING_LIMITS] ?? DEFAULT_LIMITS
+ }
⑥モデル選択で重複を消す
【編集するファイル】components/models/model-select.tsx
useMemo で同じIDが複数来た時に1つにまとめます。
components/models/model-select.tsxを修正
cd /opt/chatbot-ui
sudo nano components/models/model-select.tsx
「-」部分は削除し、「+」部分を追記します。
import { ChatbotUIContext } from "@/context/context"
import { LLM, LLMID, ModelProvider } from "@/types"
import { IconCheck, IconChevronDown } from "@tabler/icons-react"
- import { FC, useContext, useEffect, useRef, useState } from "react"
+ import { FC, useContext, useEffect, useMemo, useRef, useState } from "react"
import { Button } from "../ui/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuTrigger
} from "../ui/dropdown-menu"
import { Input } from "../ui/input"
import { Tabs, TabsList, TabsTrigger } from "../ui/tabs"
import { ModelIcon } from "./model-icon"
import { ModelOption } from "./model-option"
interface ModelSelectProps {
selectedModelId: string
onSelectModel: (modelId: LLMID) => void
}
export const ModelSelect: FC<ModelSelectProps> = ({
selectedModelId,
onSelectModel
}) => {
const {
profile,
models,
availableHostedModels,
availableLocalModels,
availableOpenRouterModels
} = useContext(ChatbotUIContext)
const inputRef = useRef<HTMLInputElement>(null)
const triggerRef = useRef<HTMLButtonElement>(null)
const [isOpen, setIsOpen] = useState(false)
const [search, setSearch] = useState("")
const [tab, setTab] = useState<"hosted" | "local">("hosted")
useEffect(() => {
if (isOpen) {
setTimeout(() => {
inputRef.current?.focus()
}, 100) // FIX: hacky
}
}, [isOpen])
const handleSelectModel = (modelId: LLMID) => {
onSelectModel(modelId)
setIsOpen(false)
}
- const allModels = [
- ...models.map(model => ({
- modelId: model.model_id as LLMID,
- modelName: model.name,
- provider: "custom" as ModelProvider,
- hostedId: model.id,
- platformLink: "",
- imageInput: false
- })),
- ...availableHostedModels,
- ...availableLocalModels,
- ...availableOpenRouterModels
- ]
+ const allModels = useMemo(() => {
+ const merged: LLM[] = [
+ ...availableHostedModels,
+ ...availableLocalModels,
+ ...availableOpenRouterModels
+ ]
+ const seen = new Set<string>()
+ return merged.filter(m => {
+ if (seen.has(m.modelId)) return false
+ seen.add(m.modelId)
+ return true
+ })
+ }, [availableHostedModels, availableLocalModels, availableOpenRouterModels])
const groupedModels = allModels.reduce<Record<string, LLM[]>>(
(groups, model) => {
// --- 以下略 ---
⑦プロフィール保存後、Customモデルも読み直す
【編集するファイル】components/utility/profile-settings.tsx
既存プロバイダと同じ流れで custom も扱い、/api/models/custom の結果を重複なく availableHostedModels に足すようにします。
cd /opt/chatbot-ui
sudo nano components/utility/profile-settings.tsx
「-」部分は削除し、「+」部分を追記します。
import { ChatbotUIContext } from "@/context/context"
import {
PROFILE_CONTEXT_MAX,
PROFILE_DISPLAY_NAME_MAX,
PROFILE_USERNAME_MAX,
PROFILE_USERNAME_MIN
} from "@/db/limits"
import { updateProfile } from "@/db/profile"
import { uploadProfileImage } from "@/db/storage/profile-images"
import { exportLocalStorageAsJSON } from "@/lib/export-old-data"
import { fetchOpenRouterModels } from "@/lib/models/fetch-models"
import { LLM_LIST_MAP } from "@/lib/models/llm/llm-list"
import { supabase } from "@/lib/supabase/browser-client"
import { cn } from "@/lib/utils"
- import { OpenRouterLLM, } from "@/types"
+ import { OpenRouterLLM, LLM } from "@/types"// ★追加: LLM を使うので import
import {
IconCircleCheckFilled,
IconCircleXFilled,
IconFileDownload,
IconLoader2,
IconLogout,
IconUser
} from "@tabler/icons-react"
// --- 中略 ---
setProfile(updatedProfile)
toast.success("Profile updated!")
const providers = [
"openai",
"google",
"azure",
"anthropic",
"mistral",
"groq",
"perplexity",
"openrouter",
+ "custom" // ★追加
]
providers.forEach(async provider => {
+ // ★追加: Custom(Dify) は envKeyMap.custom が true なら /api/models/custom を取得して反映
+ if (provider === "custom") {
+ if (envKeyMap["custom"]) {
+ try {
+ const res = await fetch("/api/models/custom", { cache: "no-store" })
+ if (res.ok) {
+ const customList: Array<{ id: string; name: string }> =
+ await res.json()
+ const mapped: LLM[] = customList.map(m => ({
+ modelId: m.id as any,
+ modelName: m.name || m.id,
+ provider: "custom",
+ hostedId: m.id,
+ platformLink: "https://www.example.com",
+ imageInput: false
+ }))
+ setAvailableHostedModels(prev => {
+ const seen = new Set(prev.map(p => p.modelId))
+ const merged = [...prev]
+ for (const m of mapped) {
+ if (!seen.has(m.modelId)) {
+ merged.push(m)
+ seen.add(m.modelId)
+ }
+ }
+ return merged
+ })
+ } else {
+ console.warn("Failed to fetch custom models", await res.text())
+ }
+ } catch (e) {
+ console.warn("Error fetching custom models:", e)
+ }
+ } else {
+ // env で custom が無効なら、custom を一覧から取り除く
+ setAvailableHostedModels(prev =>
+ prev.filter(m => m.provider !== "custom")
+ )
+ }
+ return // ★Custom の処理はここで終了。他プロバイダの既存ロジックには触れない
+ }
let providerKey: keyof typeof profile
if (provider === "google") {
providerKey = "google_gemini_api_key"
} else if (provider === "azure") {
providerKey = "azure_openai_api_key"
} else {
providerKey = `${provider}_api_key` as keyof typeof profile
}
// --- 以下略 ---
⑧Customでも壊れないように調整
【編集するファイル】components/ui/chat-settings-form.tsx
Custom(= Dify の slug)を選んだときも、温度や最大トークンなどの上限値スライダーが正常表示・保存できるようにします。
cd /opt/chatbot-ui
sudo nano components/ui/chat-settings-form.tsx
「-」部分は削除し、「+」部分を追記します。
"use client"
import { ChatbotUIContext } from "@/context/context"
- import { CHAT_SETTING_LIMITS } from "@/lib/chat-setting-limits"
+ import { getChatSettingLimits } from "@/lib/chat-setting-limits"
import { ChatSettings } from "@/types"
import { IconInfoCircle } from "@tabler/icons-react"
import { FC, useContext } from "react"
import { ModelSelect } from "../models/model-select"
import { AdvancedSettings } from "./advanced-settings"
import { Checkbox } from "./checkbox"
import { Label } from "./label"
// --- 中略 ---
const AdvancedContent: FC<AdvancedContentProps> = ({
chatSettings,
onChangeChatSettings,
showTooltip
}) => {
const { profile, selectedWorkspace, availableOpenRouterModels, models } =
useContext(ChatbotUIContext)
const isCustomModel = models.some(
model => model.model_id === chatSettings.model
)
function findOpenRouterModel(modelId: string) {
return availableOpenRouterModels.find(model => model.modelId === modelId)
}
- const MODEL_LIMITS = CHAT_SETTING_LIMITS[chatSettings.model] || {
- MIN_TEMPERATURE: 0,
- MAX_TEMPERATURE: 1,
- MAX_CONTEXT_LENGTH:
- findOpenRouterModel(chatSettings.model)?.maxContext || 4096
- }
+ const _limits = getChatSettingLimits(chatSettings.model)
+ const MODEL_LIMITS = {
+ ..._limits,
+ MAX_CONTEXT_LENGTH:
+ findOpenRouterModel(chatSettings.model)?.maxContext ||
+ _limits.MAX_CONTEXT_LENGTH
+ }
return (
<div className="mt-5">
<div className="space-y-3">
// --- 以下略 ---
⑨ビルド&テスト
ここまででいったん、ビルド&テストしてみましょう。
cd /opt/chatbot-ui
rm -rf .next
npm run build
npm run start -- -H 0.0.0.0 -p 3000
★チェックリスト
curl -s https://chat.example.com/api/keys | python3 -m json.tool
→ "custom": true を確認します。
curl -s https://chat.example.com/api/models/custom | python3 -m json.tool
→ [{ "id": "support-bot", "name": "..." }] のように出るはずです。
chat.example.com にブラウザでアクセスしてChatbotUIにログインします。
右上のモデル選択のところをクリックすると「テスト_チャットフロー」と表示されていてモデル選択できるようになっています。

ただ、この時点でモデルを選択しても会話はできません。
画面の「モデル選択」に Difyのdisplay_name が出て、選べる状態になっているところまでを確認して、次章で“送る先”をつなぎ、会話ができるようにします。
🚀 まとめ
これで、ChatbotUIのモデル選択画面にDifyのdisplay_nameが表示され、選択できる状態になりました。
次章では、実際に選択したモデルへメッセージを送信し、Dify経由で会話を成立させる部分を実装していきます。
🪄 次の記事:
[VPSで「認証つきDify環境」を作る⑥:SSEでDifyに中継して“会話できるUI”にする(予定)]
Discussion