0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

MCP使い方 with ChatGPT

Last updated at Posted at 2025-09-20

MCP入門 〜面倒なことはAIエージェントにやらせよう〜 by からあげ を聞きながら
https://qiita.com/kaizen_nagoya/items/54b648c838fae8d57e38
がQiitaで80,000 viewsを超えた。
自己第5位。2025年に入って、第3位、第4位のCの記事とともに、転職後5年間書いた記事がはじめてviews自己上位10に入った。

松尾研LLMコミュニティ【Paper&Hacks #48】
発表者:からあげさん
テーマ:MCP入門 〜面倒なことはAIエージェントにやらせよう〜
https://matsuolab-community.connpass.com/event/357792/

からあげさんのお話をお聞きしたのは、8ヶ月ぶり。

Day11 【松尾研LLMコミュニティ】面倒なことはLLMにやらせよう "Beginning LLM" 2024年10月17日
https://qiita.com/kaizen_nagoya/items/efdc23fbe67cdae2126e

からあげさんのお話は、いつもわかりやすい。
QA で 一番 印象に残ったのは

さわってみて便利だった。

今年書いたC言語について、Views3位、4位になった記事。

C言語(C++)が必要な人と必要ない人
https://qiita.com/kaizen_nagoya/items/2afe9e846b55b24cb6f1

C言語を習得する3つの方法
https://qiita.com/kaizen_nagoya/items/84cab0888c193bba429b

Views上位64記事 20250617現在
https://qiita.com/kaizen_nagoya/items/26dbbd04cf18b7742972

それぞれchatGPTで補足を記述。

C言語(C++)が必要な人、必要ない人 with ChatGPT
https://qiita.com/kaizen_nagoya/items/a074cb8cd292d8d94bd4

C言語を習得する3つの方法 with ChatGPT
https://qiita.com/kaizen_nagoya/items/4a3518a18fa49f46787f

MCPに関して補足するためにいくつかChatGPTとやりとりした。この記事もその一環。

MCP入門 〜面倒なことはAIエージェントにやらせよう〜 by からあげ を聞きながら、補足と反論 by ChatGPT
https://qiita.com/kaizen_nagoya/items/0939d58d31666562090c

json-rpc 事始め with ChatGPT
https://qiita.com/kaizen_nagoya/items/7a19cf878a04924478fd

MIDI は、なぜ Software defined audioの中心技術じゃないのか。
https://qiita.com/kaizen_nagoya/items/e5d3ad0326cf6433f666

USB, History and Future with ChatGPT
https://qiita.com/kaizen_nagoya/items/4f1961a5e81f93889657

「「からあげ帝国憲法」を作ってみた」を読んで with ChatGPT day1
https://qiita.com/kaizen_nagoya/items/851b771dd5df031d419d

職業訓練(IT)
https://qiita.com/kaizen_nagoya/items/95368b63fa21d64271ec

ChatGPT

MCPの使い方の代表例を5つ、ソースコード付きでお願いします。

ChatGPT:

承知しました。「MCP」という言葉は文脈によって複数意味がありますが、ここでは マイコン(Microcontroller Peripheral)やマルチコアプロセッサ(Multi-Core Processor)関連のPeripheral制御や通信ライブラリの一般的な使い方 という想定で整理します。もし別の「MCP(例:Microsoft Certified Professional)」を想定されていた場合は教えてください。
ここでは、代表例として Arduino/STM32向けのMCPシリーズチップ(例:MCP23017 GPIO Expander, MCP2515 CAN Controller) の使い方を挙げます。

  1. MCP23017 (I2C GPIO Expander) を使ったデジタル出力
#include <Wire.h>
#include "Adafruit_MCP23017.h"

Adafruit_MCP23017 mcp;

void setup() {
  mcp.begin();      // I2Cアドレス0x20で初期化
  mcp.pinMode(0, OUTPUT); // ピン0を出力に設定
}

void loop() {
  mcp.digitalWrite(0, HIGH); // LED ON
  delay(500);
  mcp.digitalWrite(0, LOW);  // LED OFF
  delay(500);
}

'2. MCP23017 を使ったデジタル入力(スイッチ読み取り)

#include <Wire.h>
#include "Adafruit_MCP23017.h"

Adafruit_MCP23017 mcp;

void setup() {
  mcp.begin();
  mcp.pinMode(0, INPUT);
  Serial.begin(9600);
}

void loop() {
  int val = mcp.digitalRead(0);
  Serial.println(val);
  delay(200);
}

'3. MCP2515 (SPI CAN Controller) を使ったCANメッセージ送信

#include <SPI.h>
#include "mcp_can.h"

#define CAN0_CS 10
MCP_CAN CAN0(CAN0_CS);

void setup() {
  Serial.begin(115200);
  if (CAN0.begin(MCP_ANY, 500E3, MCP_8MHZ) == CAN_OK) {
    Serial.println("CAN BUS Initialized");
  } else {
    Serial.println("CAN BUS Failed");
  }
  CAN0.setMode(MCP_NORMAL);
}

void loop() {
  byte data[8] = {0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08};
  CAN0.sendMsgBuf(0x100, 0, 8, data); // ID:0x100
  delay(1000);
}

'4. MCP2515 を使ったCANメッセージ受信

#include <SPI.h>
#include "mcp_can.h"

#define CAN0_CS 10
MCP_CAN CAN0(CAN0_CS);

void setup() {
  Serial.begin(115200);
  CAN0.begin(MCP_ANY, 500E3, MCP_8MHZ);
  CAN0.setMode(MCP_NORMAL);
}

void loop() {
  if (CAN0.checkReceive() == CAN_MSGAVAIL) {
    long unsigned int id;
    byte len;
    byte buf[8];
    CAN0.readMsgBuf(&id, &len, buf);
    Serial.print("ID: ");
    Serial.print(id, HEX);
    Serial.print(" Data: ");
    for (int i = 0; i < len; i++) {
      Serial.print(buf[i], HEX);
      Serial.print(" ");
    }
    Serial.println();
  }
}

'5. MCP4725 (I2C DAC) を使ったアナログ出力

#include <Wire.h>
#include <Adafruit_MCP4725.h>

Adafruit_MCP4725 dac;

void setup() {
  dac.begin(0x60); // I2Cアドレス0x60
}

void loop() {
  for (int i = 0; i <= 4095; i += 100) {
    dac.setVoltage(i, false); // 12-bit DAC
    delay(50);
  }
}

✅ まとめ:
MCP23017 → GPIO拡張 (入力/出力)
MCP2515 → CAN通信
MCP4725 → DAC (アナログ出力)
これらは ArduinoやSTM32などでMCPシリーズチップを扱う代表例 です。
希望であれば、次に Raspberry Pi (Python) でのMCP使用例 もソースコード付きで紹介できます。
作りますか?

ごめんなさい。Model Context Protocolです。

ChatGPT:

なるほど、ありがとうございます。では MCP = Model Context Protocol に関する代表的な使い方を、ソースコード例付きで整理します。
MCPは基本的に AIモデルや生成モデルのコンテキストを管理・共有するための通信プロトコル として使われます。典型例は モデルの呼び出し、コンテキスト保存、履歴管理、複数モデル連携 です。

  1. MCPでモデル呼び出し
from mcp import MCPClient

# MCPサーバに接続
client = MCPClient("http://localhost:8080")

# モデルにテキストを送信
response = client.call_model(
    model_name="gpt-5-mini",
    input_text="MCPの基本的な使い方を教えてください。"
)

print(response.output_text)

ポイント: MCPはHTTPやWebSocket経由でモデル呼び出しを抽象化できます。
2. コンテキストの保存と再利用

# 新しいコンテキストを作成
context_id = client.create_context("user_session_1")

# モデル呼び出し時にコンテキストを指定
response = client.call_model(
    model_name="gpt-5-mini",
    input_text="前回の会話を踏まえて続きを生成して。",
    context_id=context_id
)

# コンテキスト内の履歴を確認
history = client.get_context_history(context_id)
print(history)

ポイント: MCPは複数モデル間でコンテキストを共有可能です。

  1. 複数モデルのチェイン(パイプライン)
# 文書要約 -> 翻訳のパイプライン
summary = client.call_model(
    model_name="gpt-5-mini",
    input_text="長い文章を要約してください。",
    context_id="pipeline_1"
)

translation = client.call_model(
    model_name="translation-model",
    input_text=summary.output_text,
    context_id="pipeline_1"
)

print("翻訳結果:", translation.output_text)

ポイント: MCPは異なるモデルをつなぎ、同じコンテキストで管理できます。
4. コンテキストの部分更新

# コンテキストに新しい情報を追加
client.update_context(
    context_id="user_session_1",
    additional_text="ユーザーはPythonに詳しい"
)

# その情報を反映して質問
response = client.call_model(
    model_name="gpt-5-mini",
    input_text="PythonでMCPを使った例を出して",
    context_id="user_session_1"
)

print(response.output_text)

ポイント: MCPはコンテキストを部分的に更新できるので、履歴をリセットせずに情報を追加できます。
5. WebSocketでリアルタイム応答

import asyncio
from mcp import MCPWebSocketClient

async def main():
    ws_client = MCPWebSocketClient("ws://localhost:8080/ws")
    await ws_client.connect()

    await ws_client.send({
        "model": "gpt-5-mini",
        "input_text": "ストリーミングで応答してください",
        "context_id": "session_ws"
    })

    async for message in ws_client.receive_stream():
        print("部分応答:", message)

asyncio.run(main())

ポイント: MCPはWebSocketを使ったストリーミング応答にも対応できます。
✅ まとめ
MCPの代表的な使い方5つ:
モデル呼び出し(単発)
コンテキスト保存と再利用
複数モデルのチェイン(パイプライン)
コンテキストの部分更新
WebSocketでリアルタイム応答
もし希望であれば、次に MCPのJSON-RPC形式での通信例 もソースコード付きで示すことができます。
作りますか?

はい。お願いします。

ChatGPT:

了解です。では MCPをJSON-RPC形式で使う例 を示します。JSON-RPCは、HTTPやWebSocket経由でモデル呼び出しやコンテキスト操作を標準化する仕組みです。
以下は Pythonでのクライアント実装例 です。

  1. 単発モデル呼び出し(JSON-RPC over HTTP)
import requests
import json

url = "http://localhost:8080/jsonrpc"

payload = {
    "jsonrpc": "2.0",
    "method": "call_model",
    "params": {
        "model_name": "gpt-5-mini",
        "input_text": "MCPの基本的な使い方を教えてください。",
    },
    "id": 1
}

response = requests.post(url, json=payload)
result = response.json()

print("モデル出力:", result.get("result", {}).get("output_text"))
2. コンテキスト作成と利用
# コンテキスト作成
create_context_payload = {
    "jsonrpc": "2.0",
    "method": "create_context",
    "params": {"context_name": "user_session_1"},
    "id": 2
}

resp = requests.post(url, json=create_context_payload).json()
context_id = resp["result"]["context_id"]

# コンテキストを指定してモデル呼び出し
call_payload = {
    "jsonrpc": "2.0",
    "method": "call_model",
    "params": {
        "model_name": "gpt-5-mini",
        "input_text": "前回の会話を踏まえて続きを生成して。",
        "context_id": context_id
    },
    "id": 3
}

resp2 = requests.post(url, json=call_payload).json()
print(resp2["result"]["output_text"])
3. コンテキスト更新
update_payload = {
    "jsonrpc": "2.0",
    "method": "update_context",
    "params": {
        "context_id": context_id,
        "additional_text": "ユーザーはPythonに詳しい"
    },
    "id": 4
}

requests.post(url, json=update_payload)
4. 複数モデルチェインJSON-RPCで順次呼び出す
# 要約モデル呼び出し
summary_payload = {
    "jsonrpc": "2.0",
    "method": "call_model",
    "params": {
        "model_name": "gpt-summary",
        "input_text": "長文を要約してください。",
        "context_id": context_id
    },
    "id": 5
}

summary_resp = requests.post(url, json=summary_payload).json()
summary_text = summary_resp["result"]["output_text"]

# 翻訳モデル呼び出し
translation_payload = {
    "jsonrpc": "2.0",
    "method": "call_model",
    "params": {
        "model_name": "translation-en-ja",
        "input_text": summary_text,
        "context_id": context_id
    },
    "id": 6
}

translation_resp = requests.post(url, json=translation_payload).json()
print("翻訳結果:", translation_resp["result"]["output_text"])
5. JSON-RPC over WebSocketストリーミング
import asyncio
import websockets
import json

async def main():
    uri = "ws://localhost:8080/ws"
    async with websockets.connect(uri) as ws:
        payload = {
            "jsonrpc": "2.0",
            "method": "call_model",
            "params": {
                "model_name": "gpt-5-mini",
                "input_text": "ストリーミングで応答してください",
                "context_id": context_id
            },
            "id": 7
        }
        await ws.send(json.dumps(payload))

        async for message in ws:
            data = json.loads(message)
            if "result" in data:
                print("部分応答:", data["result"].get("output_text"))

asyncio.run(main())

✅ ポイントまとめ
MCPは モデル呼び出し、コンテキスト管理、履歴保持、複数モデル連携 をJSON-RPCで統一的に行える。
HTTPは単発リクエスト向け、WebSocketはストリーミング向け。
context_id を指定することで複数呼び出しでも状態を保持可能。
希望であれば、さらに Python以外の言語(Node.jsやGoなど)でのJSON-RPC MCP利用例 も示せます。
作りますか?

はい。お願いします。

ChatGPT:

わかりました。では Node.js と Go での MCP (Model Context Protocol) JSON-RPC 利用例 を示します。Python以外の言語でもHTTP/WebSocketで同様に呼び出せます。

  1. Node.js でのJSON-RPC呼び出し(HTTP)
// 必要パッケージ: npm install axios
const axios = require('axios');

const url = "http://localhost:8080/jsonrpc";

async function callModel() {
    const payload = {
        jsonrpc: "2.0",
        method: "call_model",
        params: {
            model_name: "gpt-5-mini",
            input_text: "MCPの基本的な使い方をNode.jsで教えてください"
        },
        id: 1
    };

    try {
        const response = await axios.post(url, payload);
        console.log("モデル出力:", response.data.result.output_text);
    } catch (err) {
        console.error(err);
    }
}

callModel();
2. Node.js でのWebSocketストリーミング
// 必要パッケージ: npm install ws
const WebSocket = require('ws');

const ws = new WebSocket('ws://localhost:8080/ws');

ws.on('open', function open() {
    const payload = {
        jsonrpc: "2.0",
        method: "call_model",
        params: {
            model_name: "gpt-5-mini",
            input_text: "Node.jsでストリーミング応答してください",
            context_id: "session_ws"
        },
        id: 2
    };
    ws.send(JSON.stringify(payload));
});

ws.on('message', function message(data) {
    const msg = JSON.parse(data);
    if (msg.result && msg.result.output_text) {
        console.log("部分応答:", msg.result.output_text);
    }
});
3. Go でのJSON-RPC呼び出しHTTP
package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io/ioutil"
    "net/http"
)

type RPCRequest struct {
    JSONRPC string                 `json:"jsonrpc"`
    Method  string                 `json:"method"`
    Params  map[string]interface{} `json:"params"`
    ID      int                    `json:"id"`
}

type RPCResponse struct {
    JSONRPC string                 `json:"jsonrpc"`
    Result  map[string]interface{} `json:"result"`
    ID      int                    `json:"id"`
}

func main() {
    url := "http://localhost:8080/jsonrpc"

    payload := RPCRequest{
        JSONRPC: "2.0",
        Method:  "call_model",
        Params: map[string]interface{}{
            "model_name": "gpt-5-mini",
            "input_text": "GoでMCPを使った例を教えてください",
        },
        ID: 1,
    }

    data, _ := json.Marshal(payload)
    resp, err := http.Post(url, "application/json", bytes.NewBuffer(data))
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()

    body, _ := ioutil.ReadAll(resp.Body)
    var rpcResp RPCResponse
    json.Unmarshal(body, &rpcResp)

    fmt.Println("モデル出力:", rpcResp.Result["output_text"])
}

'4. Go でのWebSocketストリーミング

// 必要パッケージ: go get github.com/gorilla/websocket
package main

import (
    "encoding/json"
    "fmt"
    "log"

    "github.com/gorilla/websocket"
)

func main() {
    wsURL := "ws://localhost:8080/ws"
    conn, _, err := websocket.DefaultDialer.Dial(wsURL, nil)
    if err != nil {
        log.Fatal("接続エラー:", err)
    }
    defer conn.Close()

    payload := map[string]interface{}{
        "jsonrpc": "2.0",
        "method":  "call_model",
        "params": map[string]interface{}{
            "model_name": "gpt-5-mini",
            "input_text": "Goでストリーミング応答を受け取る例",
            "context_id": "session_ws",
        },
        "id": 2,
    }

    conn.WriteJSON(payload)

    for {
        _, message, err := conn.ReadMessage()
        if err != nil {
            log.Println("読み取りエラー:", err)
            break
        }
        var msg map[string]interface{}
        json.Unmarshal(message, &msg)
        if result, ok := msg["result"].(map[string]interface{}); ok {
            if text, exists := result["output_text"].(string); exists {
                fmt.Println("部分応答:", text)
            }
        }
    }
}

✅ ポイント
Node.js と Go でも HTTP + JSON-RPC または WebSocket + JSON-RPC でMCPを使える
Pythonと同じく、context_id を使うことでセッションや履歴を管理可能
異なる言語でもプロトコルが統一されているので、マルチモデル・マルチクライアント環境に適応可能
必要であれば、さらに 複数言語でのコンテキスト同期の例(Python ↔ Node.js ↔ Go) も作成可能です。
作りますか?

はい。お願いします。

ChatGPT:

了解です。では Python, Node.js, Go 間で MCP(Model Context Protocol)のコンテキストを同期して利用する例 を示します。これにより、同じ context_id を使ってマルチクライアントで会話やデータを共有できます。
前提
MCPサーバは http://localhost:8080/jsonrpc(HTTP)と ws://localhost:8080/ws(WebSocket)で起動済み
コンテキストID: "shared_session_1" を全クライアントで共通利用
モデル: "gpt-5-mini"

  1. Python でコンテキスト作成・更新
import requests

url = "http://localhost:8080/jsonrpc"
context_id = "shared_session_1"

# コンテキスト作成(既存ならスキップ可)
requests.post(url, json={
    "jsonrpc": "2.0",
    "method": "create_context",
    "params": {"context_name": context_id},
    "id": 1
})

# コンテキスト更新
requests.post(url, json={
    "jsonrpc": "2.0",
    "method": "update_context",
    "params": {
        "context_id": context_id,
        "additional_text": "Pythonクライアントからの初期情報です"
    },
    "id": 2
})

'2. Node.js で同じコンテキストを利用してモデル呼び出し

const axios = require('axios');
const url = "http://localhost:8080/jsonrpc";
const context_id = "shared_session_1";

async function callFromNode() {
    const payload = {
        jsonrpc: "2.0",
        method: "call_model",
        params: {
            model_name: "gpt-5-mini",
            input_text: "Node.jsクライアントから質問します。",
            context_id: context_id
        },
        id: 3
    };

    const response = await axios.post(url, payload);
    console.log("Node.js 出力:", response.data.result.output_text);
}

callFromNode();

'3. Go で同じコンテキストを利用してモデル呼び出し

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io/ioutil"
    "net/http"
)

func main() {
    url := "http://localhost:8080/jsonrpc"
    contextID := "shared_session_1"

    payload := map[string]interface{}{
        "jsonrpc": "2.0",
        "method":  "call_model",
        "params": map[string]interface{}{
            "model_name": "gpt-5-mini",
            "input_text": "Goクライアントから質問します。",
            "context_id": contextID,
        },
        "id": 4,
    }

    data, _ := json.Marshal(payload)
    resp, _ := http.Post(url, "application/json", bytes.NewBuffer(data))
    defer resp.Body.Close()

    body, _ := ioutil.ReadAll(resp.Body)
    var result map[string]interface{}
    json.Unmarshal(body, &result)

    fmt.Println("Go 出力:", result["result"].(map[string]interface{})["output_text"])
}

'4. ポイントまとめ
同じ context_id を使えば複数言語クライアントで履歴共有可能
Pythonで初期情報をセット → Node.jsやGoで続きの質問・回答が可能
HTTPでもWebSocketでも同じコンテキストを利用できる
マルチモデルチェインや部分更新も、全クライアントが同じ context_id を参照すれば共有される
もし希望であれば、次に Python ↔ Node.js ↔ Go 間でWebSocketを使ったリアルタイムストリーミング同期の例 も作れます。
作りますか?

はい。お願いします。

ChatGPT:

了解です。では Python、Node.js、Go 間で MCP(Model Context Protocol)の WebSocket ストリーミングを使ってリアルタイムでコンテキストを同期 する例を示します。
ポイントは以下です:
共通の context_id を使い、複数クライアントで会話やデータをリアルタイム共有
WebSocketストリーミング で部分応答を受信
Python がサーバに質問を送ると、Node.js と Go が同じコンテキストを使って続きの応答を取得可能

  1. Python で WebSocket 接続と送信
import asyncio
import websockets
import json

context_id = "shared_session_ws"

async def main():
    uri = "ws://localhost:8080/ws"
    async with websockets.connect(uri) as ws:
        # MCPモデル呼び出し
        payload = {
            "jsonrpc": "2.0",
            "method": "call_model",
            "params": {
                "model_name": "gpt-5-mini",
                "input_text": "Pythonクライアントからのストリーミング質問です。",
                "context_id": context_id
            },
            "id": 1
        }
        await ws.send(json.dumps(payload))

        async for message in ws:
            data = json.loads(message)
            if "result" in data:
                print("Python 部分応答:", data["result"].get("output_text"))

asyncio.run(main())
2. Node.js で同じコンテキストを利用
const WebSocket = require('ws');
const context_id = "shared_session_ws";

const ws = new WebSocket('ws://localhost:8080/ws');

ws.on('open', function open() {
    const payload = {
        jsonrpc: "2.0",
        method: "call_model",
        params: {
            model_name: "gpt-5-mini",
            input_text: "Node.jsクライアントから続きの質問です。",
            context_id: context_id
        },
        id: 2
    };
    ws.send(JSON.stringify(payload));
});

ws.on('message', function message(data) {
    const msg = JSON.parse(data);
    if (msg.result && msg.result.output_text) {
        console.log("Node.js 部分応答:", msg.result.output_text);
    }
});
3. Go で同じコンテキストを利用
package main

import (
    "encoding/json"
    "fmt"
    "log"

    "github.com/gorilla/websocket"
)

func main() {
    contextID := "shared_session_ws"
    wsURL := "ws://localhost:8080/ws"

    conn, _, err := websocket.DefaultDialer.Dial(wsURL, nil)
    if err != nil {
        log.Fatal("接続エラー:", err)
    }
    defer conn.Close()

    payload := map[string]interface{}{
        "jsonrpc": "2.0",
        "method":  "call_model",
        "params": map[string]interface{}{
            "model_name": "gpt-5-mini",
            "input_text": "Goクライアントから続きの質問です。",
            "context_id": contextID,
        },
        "id": 3,
    }

    conn.WriteJSON(payload)

    for {
        _, message, err := conn.ReadMessage()
        if err != nil {
            log.Println("読み取りエラー:", err)
            break
        }
        var msg map[string]interface{}
        json.Unmarshal(message, &msg)
        if result, ok := msg["result"].(map[string]interface{}); ok {
            if text, exists := result["output_text"].(string); exists {
                fmt.Println("Go 部分応答:", text)
            }
        }
    }
}

✅ ポイントまとめ
同じ context_id を全クライアントで使用 → Python, Node.js, Go が同じ会話履歴や状態を共有
WebSocketを使うことで部分応答をリアルタイムに取得
追加情報や更新も update_context を使えば全クライアントに反映可能
マルチクライアント環境でも、Python ↔ Node.js ↔ Go が同じ MCPコンテキストで連携可能
必要であれば、さらに 複数モデルのチェイン(要約 → 翻訳 → QA)をマルチクライアントで同期する例 も作れます。
作りますか?

はい。お願いします。

ChatGPT:

了解です。では マルチクライアント(Python・Node.js・Go)で MCP のコンテキストを共有しつつ、複数モデルチェイン(要約 → 翻訳 → QA)を同期する例 を示します。
ポイント:
共通コンテキストID を使い、すべてのクライアントが同じ履歴にアクセス
複数モデルチェイン:
長文を要約(Summaryモデル)
要約を翻訳(Translationモデル)
翻訳結果をQAモデルで回答
WebSocketまたはHTTP でストリーミング/同期可能

  1. Python: 要約 → 翻訳 → QA のチェイン
import requests

url = "http://localhost:8080/jsonrpc"
context_id = "shared_chain_session"

# コンテキスト作成
requests.post(url, json={
    "jsonrpc": "2.0",
    "method": "create_context",
    "params": {"context_name": context_id},
    "id": 1
})

# 1. 要約
summary_payload = {
    "jsonrpc": "2.0",
    "method": "call_model",
    "params": {
        "model_name": "summary-model",
        "input_text": "これは長文の例です。MCPで複数モデルチェインを試します。",
        "context_id": context_id
    },
    "id": 2
}
summary_resp = requests.post(url, json=summary_payload).json()
summary_text = summary_resp["result"]["output_text"]
print("要約:", summary_text)

# 2. 翻訳
translation_payload = {
    "jsonrpc": "2.0",
    "method": "call_model",
    "params": {
        "model_name": "translation-en-ja",
        "input_text": summary_text,
        "context_id": context_id
    },
    "id": 3
}
translation_resp = requests.post(url, json=translation_payload).json()
translation_text = translation_resp["result"]["output_text"]
print("翻訳:", translation_text)

# 3. QA
qa_payload = {
    "jsonrpc": "2.0",
    "method": "call_model",
    "params": {
        "model_name": "qa-model",
        "input_text": f"以下の文章について質問してください:\n{translation_text}",
        "context_id": context_id
    },
    "id": 4
}
qa_resp = requests.post(url, json=qa_payload).json()
print("QA 出力:", qa_resp["result"]["output_text"])

'2. Node.js: 同じコンテキストでチェイン続行

const axios = require('axios');
const url = "http://localhost:8080/jsonrpc";
const context_id = "shared_chain_session";

async function chainFromNode() {
    // 要約
    const summaryResp = await axios.post(url, {
        jsonrpc: "2.0",
        method: "call_model",
        params: {
            model_name: "summary-model",
            input_text: "Node.jsクライアントでチェインを続行します。",
            context_id: context_id
        },
        id: 5
    });
    const summaryText = summaryResp.data.result.output_text;
    console.log("Node.js 要約:", summaryText);

    // 翻訳
    const translationResp = await axios.post(url, {
        jsonrpc: "2.0",
        method: "call_model",
        params: {
            model_name: "translation-en-ja",
            input_text: summaryText,
            context_id: context_id
        },
        id: 6
    });
    const translationText = translationResp.data.result.output_text;
    console.log("Node.js 翻訳:", translationText);

    // QA
    const qaResp = await axios.post(url, {
        jsonrpc: "2.0",
        method: "call_model",
        params: {
            model_name: "qa-model",
            input_text: translationText,
            context_id: context_id
        },
        id: 7
    });
    console.log("Node.js QA:", qaResp.data.result.output_text);
}

chainFromNode();

'3. Go: 同じコンテキストでチェイン続行

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io/ioutil"
    "net/http"
)

func callModel(url string, model string, input string, contextID string, id int) string {
    payload := map[string]interface{}{
        "jsonrpc": "2.0",
        "method":  "call_model",
        "params": map[string]interface{}{
            "model_name": model,
            "input_text": input,
            "context_id": contextID,
        },
        "id": id,
    }
    data, _ := json.Marshal(payload)
    resp, _ := http.Post(url, "application/json", bytes.NewBuffer(data))
    defer resp.Body.Close()
    body, _ := ioutil.ReadAll(resp.Body)
    var result map[string]interface{}
    json.Unmarshal(body, &result)
    return result["result"].(map[string]interface{})["output_text"].(string)
}

func main() {
    url := "http://localhost:8080/jsonrpc"
    contextID := "shared_chain_session"

    summary := callModel(url, "summary-model", "Goクライアントでチェインを実行", contextID, 8)
    fmt.Println("Go 要約:", summary)

    translation := callModel(url, "translation-en-ja", summary, contextID, 9)
    fmt.Println("Go 翻訳:", translation)

    qa := callModel(url, "qa-model", translation, contextID, 10)
    fmt.Println("Go QA:", qa)
}

✅ ポイントまとめ
共通の context_id を使えば全クライアントで履歴・状態を共有
複数モデルチェイン(Summary → Translation → QA)でも同期が可能
Python、Node.js、Go どのクライアントからも同じチェインを継続できる
必要に応じて WebSocket を使ったリアルタイムストリーミング に置き換え可能
希望であれば、このチェインを WebSocket でリアルタイムストリーミングしながら Python ↔ Node.js ↔ Go が同時に反応するデモコード にアップグレードすることもできます。
作りますか?

はい。お願いします。

ChatGPT:

了解です。では Python、Node.js、Go のマルチクライアントが MCP を使って WebSocket 上でリアルタイムに複数モデルチェイン(要約 → 翻訳 → QA)を共有・同期する例 を示します。
ポイント:
全クライアントで 共通 context_id を使用
WebSocket ストリーミング によって部分応答をリアルタイムで取得
モデルチェイン:Summary → Translation → QA
各クライアントが同時にストリーミングを受信可能

  1. Python クライアント
import asyncio
import websockets
import json

context_id = "shared_ws_chain"

async def call_model(ws, model, input_text, req_id):
    payload = {
        "jsonrpc": "2.0",
        "method": "call_model",
        "params": {
            "model_name": model,
            "input_text": input_text,
            "context_id": context_id
        },
        "id": req_id
    }
    await ws.send(json.dumps(payload))
    async for message in ws:
        data = json.loads(message)
        if "result" in data:
            text = data["result"].get("output_text")
            if text:
                print(f"Python {model} 部分応答:", text)
            break  # 次のモデル呼び出しに進む

async def main():
    uri = "ws://localhost:8080/ws"
    async with websockets.connect(uri) as ws:
        # 1. 要約
        await call_model(ws, "summary-model", "これは長文の例です。Pythonクライアントでチェインを実行します。", 1)
        # 2. 翻訳
        await call_model(ws, "translation-en-ja", "これは要約結果です", 2)
        # 3. QA
        await call_model(ws, "qa-model", "これは翻訳結果です", 3)

asyncio.run(main())

'2. Node.js クライアント

const WebSocket = require('ws');
const context_id = "shared_ws_chain";

const ws = new WebSocket('ws://localhost:8080/ws');

function callModel(model, inputText, id) {
    return new Promise(resolve => {
        ws.send(JSON.stringify({
            jsonrpc: "2.0",
            method: "call_model",
            params: { model_name: model, input_text: inputText, context_id: context_id },
            id: id
        }));

        ws.once('message', function message(data) {
            const msg = JSON.parse(data);
            if (msg.result && msg.result.output_text) {
                console.log(`Node.js ${model} 部分応答:`, msg.result.output_text);
                resolve(msg.result.output_text);
            }
        });
    });
}

ws.on('open', async function open() {
    const summary = await callModel("summary-model", "Node.jsクライアントでチェインを実行", 4);
    const translation = await callModel("translation-en-ja", summary, 5);
    await callModel("qa-model", translation, 6);
});

'3. Go クライアント

package main

import (
    "encoding/json"
    "fmt"
    "log"

    "github.com/gorilla/websocket"
)

func callModel(conn *websocket.Conn, model, input string, id int) string {
    payload := map[string]interface{}{
        "jsonrpc": "2.0",
        "method":  "call_model",
        "params": map[string]interface{}{
            "model_name": model,
            "input_text": input,
            "context_id": "shared_ws_chain",
        },
        "id": id,
    }

    conn.WriteJSON(payload)

    _, message, err := conn.ReadMessage()
    if err != nil {
        log.Fatal("読み取りエラー:", err)
    }

    var msg map[string]interface{}
    json.Unmarshal(message, &msg)
    if result, ok := msg["result"].(map[string]interface{}); ok {
        if text, exists := result["output_text"].(string); exists {
            fmt.Println("Go", model, "部分応答:", text)
            return text
        }
    }
    return ""
}

func main() {
    wsURL := "ws://localhost:8080/ws"
    conn, _, err := websocket.DefaultDialer.Dial(wsURL, nil)
    if err != nil {
        log.Fatal("接続エラー:", err)
    }
    defer conn.Close()

    summary := callModel(conn, "summary-model", "Goクライアントでチェインを実行", 7)
    translation := callModel(conn, "translation-en-ja", summary, 8)
    callModel(conn, "qa-model", translation, 9)
}

✅ ポイントまとめ
共通 context_id を使用 → 全クライアントで会話履歴・モデルチェインを共有
WebSocket ストリーミング → 部分応答をリアルタイム受信
Python、Node.js、Go どのクライアントからも同じチェインを実行可能
モデルチェイン(Summary → Translation → QA)が 全クライアントで同期される
必要に応じて update_context を使い、追加情報や質問をリアルタイムに反映可能
もし希望であれば、このセットアップを マルチユーザー同時対話用のリアルタイムデモ環境 として拡張するサンプルも作れます。
作りますか?

はい。お願いします。

ChatGPT:

了解です。では マルチユーザー同時対話用の MCP リアルタイムデモ環境のサンプル を示します。
ポイント:
複数ユーザー(Python、Node.js、Go など)同時接続
共通コンテキストIDを使用し、全ユーザーの発話・質問をリアルタイムで同期
WebSocket ストリーミングで部分応答を即時表示
複数モデルチェイン(Summary → Translation → QA)を全ユーザーで共有

  1. MCP WebSocket デモサーバ(Python FastAPI + WebSocket)
# 必要パッケージ: pip install fastapi uvicorn websockets
from fastapi import FastAPI, WebSocket
from fastapi.middleware.cors import CORSMiddleware
import asyncio
import json

app = FastAPI()

# 複数ユーザーの接続を管理
clients = set()
contexts = {}  # context_id -> list of messages

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_methods=["*"],
    allow_headers=["*"],
)

@app.websocket("/ws")
async def websocket_endpoint(ws: WebSocket):
    await ws.accept()
    clients.add(ws)
    try:
        while True:
            data = await ws.receive_text()
            msg = json.loads(data)
            context_id = msg["params"].get("context_id", "default")
            input_text = msg["params"].get("input_text", "")

            # コンテキストに履歴追加
            contexts.setdefault(context_id, []).append(input_text)

            # シンプルなモデルチェインの模擬応答
            output_text = f"[モデル出力({context_id})] {input_text[::-1]}"  # 逆順文字列を模擬出力

            response = {
                "jsonrpc": "2.0",
                "id": msg["id"],
                "result": {"output_text": output_text}
            }

            # 全クライアントにブロードキャスト
            for client in clients:
                await client.send_text(json.dumps(response))
    except Exception as e:
        print("切断:", e)
    finally:
        clients.remove(ws)

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8080)

'2. Python クライアント(複数ユーザー想定)

import asyncio
import websockets
import json

context_id = "demo_shared_context"

async def user_client(user_name):
    uri = "ws://localhost:8080/ws"
    async with websockets.connect(uri) as ws:
        for i in range(3):
            text = f"{user_name} の発話 {i+1}"
            payload = {
                "jsonrpc": "2.0",
                "method": "call_model",
                "params": {"model_name": "qa-model", "input_text": text, "context_id": context_id},
                "id": i+1
            }
            await ws.send(json.dumps(payload))
            response = await ws.recv()
            data = json.loads(response)
            print(f"{user_name} 部分応答:", data["result"]["output_text"])

async def main():
    await asyncio.gather(
        user_client("Alice"),
        user_client("Bob"),
    )

asyncio.run(main())

`3. Node.js クライアント(同じコンテキストで同期)

const WebSocket = require('ws');
const context_id = "demo_shared_context";

function userClient(userName) {
    const ws = new WebSocket('ws://localhost:8080/ws');
    ws.on('open', () => {
        for (let i = 0; i < 3; i++) {
            const payload = {
                jsonrpc: "2.0",
                method: "call_model",
                params: {
                    model_name: "qa-model",
                    input_text: `${userName} の発話 ${i+1}`,
                    context_id: context_id
                },
                id: i+1
            };
            ws.send(JSON.stringify(payload));
        }
    });

    ws.on('message', (data) => {
        const msg = JSON.parse(data);
        if (msg.result && msg.result.output_text) {
            console.log(userName, "部分応答:", msg.result.output_text);
        }
    });
}

userClient("Charlie");
userClient("Dave");

'4. Go クライアント(同じコンテキストで同期)

package main

import (
    "encoding/json"
    "fmt"
    "log"

    "github.com/gorilla/websocket"
)

func userClient(userName string) {
    conn, _, err := websocket.DefaultDialer.Dial("ws://localhost:8080/ws", nil)
    if err != nil {
        log.Fatal(err)
    }
    defer conn.Close()

    for i := 0; i < 3; i++ {
        payload := map[string]interface{}{
            "jsonrpc": "2.0",
            "method":  "call_model",
            "params": map[string]interface{}{
                "model_name": "qa-model",
                "input_text": fmt.Sprintf("%s の発話 %d", userName, i+1),
                "context_id": "demo_shared_context",
            },
            "id": i + 1,
        }
        conn.WriteJSON(payload)

        _, message, _ := conn.ReadMessage()
        var msg map[string]interface{}
        json.Unmarshal(message, &msg)
        if result, ok := msg["result"].(map[string]interface{}); ok {
            if text, exists := result["output_text"].(string); exists {
                fmt.Println(userName, "部分応答:", text)
            }
        }
    }
}

func main() {
    go userClient("Eve")
    go userClient("Frank")
    select {} // 永久待機
}

✅ ポイントまとめ
複数ユーザーが同時に接続 → 全員が同じ context_id を参照
全クライアントにブロードキャスト → リアルタイムで全ユーザーに部分応答を共有
モデルチェインや履歴も共有可能 → update_context を組み込めば、さらに複雑な同期も対応
Python, Node.js, Go の混在環境でも同じコンテキストを使って会話可能
この構成を拡張すれば、チャットルーム形式のリアルタイム協調AIシステム を簡単に構築できます。
希望であれば、次に 要約→翻訳→QA のマルチモデルチェインをこのマルチユーザー環境でリアルタイムに流すサンプル も作成できます。
作りますか?

はい。お願いします。

ChatGPT:

了解です。では マルチユーザー同時接続環境で MCP を使い、要約 → 翻訳 → QA の複数モデルチェインをリアルタイムにストリーミングするサンプル を作ります。
ポイント:
共通コンテキストID を使い、全ユーザーが同じ履歴・状態を共有
WebSocket ストリーミング で部分応答を即時配信
モデルチェイン:Summary → Translation → QA
Python、Node.js、Go のクライアントが同時にチェインの結果を受信可能

  1. Python MCP WebSocket デモサーバ(モデルチェイン対応)
from fastapi import FastAPI, WebSocket
from fastapi.middleware.cors import CORSMiddleware
import asyncio
import json

app = FastAPI()
clients = set()
contexts = {}  # context_id -> list of messages

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_methods=["*"],
    allow_headers=["*"],
)

# 模擬モデルチェイン関数
async def model_chain(context_id, input_text):
    # 1. 要約
    summary = f"[Summary] {input_text[:10]}..."  
    contexts[context_id].append(summary)
    await broadcast(context_id, summary)

    # 2. 翻訳
    translation = f"[Translation] {summary[::-1]}"  # 逆順文字列で模擬
    contexts[context_id].append(translation)
    await broadcast(context_id, translation)

    # 3. QA
    qa = f"[QA] {translation.upper()}"  
    contexts[context_id].append(qa)
    await broadcast(context_id, qa)

async def broadcast(context_id, message):
    payload = {
        "jsonrpc": "2.0",
        "id": 0,
        "result": {"output_text": message}
    }
    for client in clients:
        await client.send_text(json.dumps(payload))

@app.websocket("/ws")
async def websocket_endpoint(ws: WebSocket):
    await ws.accept()
    clients.add(ws)
    try:
        while True:
            data = await ws.receive_text()
            msg = json.loads(data)
            context_id = msg["params"].get("context_id", "default")
            input_text = msg["params"].get("input_text", "")
            contexts.setdefault(context_id, []).append(input_text)
            # 非同期でモデルチェイン処理
            asyncio.create_task(model_chain(context_id, input_text))
    except Exception as e:
        print("切断:", e)
    finally:
        clients.remove(ws)

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8080)

'2. Python クライアント(マルチユーザー想定)

import asyncio
import websockets
import json

context_id = "multi_chain_demo"

async def user_client(user_name):
    uri = "ws://localhost:8080/ws"
    async with websockets.connect(uri) as ws:
        for i in range(2):
            text = f"{user_name} の発話 {i+1}"
            payload = {
                "jsonrpc": "2.0",
                "method": "call_model",
                "params": {
                    "model_name": "qa-model",
                    "input_text": text,
                    "context_id": context_id
                },
                "id": i+1
            }
            await ws.send(json.dumps(payload))
            # ストリーミングで部分応答を受信
            async for message in ws:
                data = json.loads(message)
                if "result" in data:
                    print(f"{user_name} 部分応答:", data["result"]["output_text"])
                    break

async def main():
    await asyncio.gather(
        user_client("Alice"),
        user_client("Bob")
    )

asyncio.run(main())

'3. Node.js クライアント

const WebSocket = require('ws');
const context_id = "multi_chain_demo";

function userClient(userName) {
    const ws = new WebSocket('ws://localhost:8080/ws');

    ws.on('open', () => {
        for (let i = 0; i < 2; i++) {
            const payload = {
                jsonrpc: "2.0",
                method: "call_model",
                params: {
                    model_name: "qa-model",
                    input_text: `${userName} の発話 ${i+1}`,
                    context_id: context_id
                },
                id: i+1
            };
            ws.send(JSON.stringify(payload));
        }
    });

    ws.on('message', (data) => {
        const msg = JSON.parse(data);
        if (msg.result && msg.result.output_text) {
            console.log(userName, "部分応答:", msg.result.output_text);
        }
    });
}

userClient("Charlie");
userClient("Dave");

'4. Go クライアント

package main

import (
    "encoding/json"
    "fmt"
    "log"

    "github.com/gorilla/websocket"
)

func userClient(userName string) {
    conn, _, err := websocket.DefaultDialer.Dial("ws://localhost:8080/ws", nil)
    if err != nil {
        log.Fatal(err)
    }
    defer conn.Close()

    for i := 0; i < 2; i++ {
        payload := map[string]interface{}{
            "jsonrpc": "2.0",
            "method":  "call_model",
            "params": map[string]interface{}{
                "model_name": "qa-model",
                "input_text": fmt.Sprintf("%s の発話 %d", userName, i+1),
                "context_id": "multi_chain_demo",
            },
            "id": i + 1,
        }
        conn.WriteJSON(payload)

        _, message, _ := conn.ReadMessage()
        var msg map[string]interface{}
        json.Unmarshal(message, &msg)
        if result, ok := msg["result"].(map[string]interface{}); ok {
            if text, exists := result["output_text"].(string); exists {
                fmt.Println(userName, "部分応答:", text)
            }
        }
    }
}

func main() {
    go userClient("Eve")
    go userClient("Frank")
    select {}
}
 ポイントまとめ
マルチユーザー同時接続  全員が同じ context_id でモデルチェインを共有
WebSocket ストリーミング  部分応答をリアルタイムに全クライアントに配信
モデルチェインSummary  Translation  QA を全ユーザーが同時に体験
非同期タスク (asyncio.create_task) を使って各発話のモデルチェイン処理を並列実行
PythonNode.jsGo どのクライアントからも同じ環境でリアルタイム対話可能
このデモ構成をさらに拡張すればマルチユーザー協調AIチャットシステム  リアルタイム翻訳/QA支援ツール としても利用できます
必要であれば次に モデルチェインの処理を実際の GPT 系モデル呼び出しに置き換えた完全版デモ のサンプルも作成できます
作りますか

はいお願いします

ChatGPT:
了解ですでは先ほどのマルチユーザー WebSocket デモを 実際の GPT 系モデル呼び出しに置き換えた完全版デモ を作成します
ポイント

OpenAI GPT 系モデルgpt-4, gpt-5-mini など  MCP 経由で呼び出す
マルチユーザー同時接続
複数モデルチェインSummary  Translation  QA
WebSocket ストリーミングでリアルタイム配信
1. Python デモサーバGPT 呼び出し版
from fastapi import FastAPI, WebSocket
from fastapi.middleware.cors import CORSMiddleware
import asyncio
import json
import openai

# OpenAI API キー設定
openai.api_key = "YOUR_OPENAI_API_KEY"

app = FastAPI()
clients = set()
contexts = {}  # context_id -> list of履歴

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_methods=["*"],
    allow_headers=["*"],
)

async def broadcast(message):
    payload = {"jsonrpc": "2.0", "id": 0, "result": {"output_text": message}}
    for client in clients:
        await client.send_text(json.dumps(payload))

async def gpt_call(prompt, model="gpt-5-mini"):
    response = openai.chat.completions.create(
        model=model,
        messages=[{"role":"user", "content": prompt}],
        stream=True
    )
    # ストリーミング部分応答
    output_text = ""
    async for chunk in response:
        if 'delta' in chunk.choices[0]:
            delta = chunk.choices[0].delta.get('content', '')
            output_text += delta
            await broadcast(output_text)
    return output_text

async def model_chain(context_id, input_text):
    # 1. 要約
    summary_prompt = f"以下を要約してください:\n{input_text}"
    summary = await gpt_call(summary_prompt, model="gpt-5-mini")
    contexts[context_id].append(summary)

    # 2. 翻訳
    translation_prompt = f"以下を日本語に翻訳してください:\n{summary}"
    translation = await gpt_call(translation_prompt, model="gpt-5-mini")
    contexts[context_id].append(translation)

    # 3. QA
    qa_prompt = f"次の内容に基づき質問に答えてください:\n{translation}"
    qa = await gpt_call(qa_prompt, model="gpt-5-mini")
    contexts[context_id].append(qa)

@app.websocket("/ws")
async def websocket_endpoint(ws: WebSocket):
    await ws.accept()
    clients.add(ws)
    try:
        while True:
            data = await ws.receive_text()
            msg = json.loads(data)
            context_id = msg["params"].get("context_id", "default")
            input_text = msg["params"].get("input_text", "")
            contexts.setdefault(context_id, []).append(input_text)
            asyncio.create_task(model_chain(context_id, input_text))
    except Exception as e:
        print("切断:", e)
    finally:
        clients.remove(ws)

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8080)
2. Python クライアント
import asyncio
import websockets
import json

context_id = "multi_user_gpt_chain"

async def user_client(user_name):
    uri = "ws://localhost:8080/ws"
    async with websockets.connect(uri) as ws:
        for i in range(2):
            text = f"{user_name} の質問 {i+1}"
            payload = {
                "jsonrpc": "2.0",
                "method": "call_model",
                "params": {"model_name": "gpt-5-mini", "input_text": text, "context_id": context_id},
                "id": i+1
            }
            await ws.send(json.dumps(payload))
            async for message in ws:
                data = json.loads(message)
                if "result" in data:
                    print(f"{user_name} 部分応答:", data["result"]["output_text"])
                    break

async def main():
    await asyncio.gather(
        user_client("Alice"),
        user_client("Bob")
    )

asyncio.run(main())
3. Node.js クライアント
const WebSocket = require('ws');
const context_id = "multi_user_gpt_chain";

function userClient(userName) {
    const ws = new WebSocket('ws://localhost:8080/ws');
    ws.on('open', () => {
        for (let i = 0; i < 2; i++) {
            const payload = {
                jsonrpc: "2.0",
                method: "call_model",
                params: { model_name: "gpt-5-mini", input_text: `${userName} の質問 ${i+1}`, context_id },
                id: i+1
            };
            ws.send(JSON.stringify(payload));
        }
    });
    ws.on('message', (data) => {
        const msg = JSON.parse(data);
        if (msg.result && msg.result.output_text) {
            console.log(userName, "部分応答:", msg.result.output_text);
        }
    });
}

userClient("Charlie");
userClient("Dave");
4. Go クライアント
package main

import (
    "encoding/json"
    "fmt"
    "log"

    "github.com/gorilla/websocket"
)

func userClient(userName string) {
    conn, _, err := websocket.DefaultDialer.Dial("ws://localhost:8080/ws", nil)
    if err != nil {
        log.Fatal(err)
    }
    defer conn.Close()

    for i := 0; i < 2; i++ {
        payload := map[string]interface{}{
            "jsonrpc": "2.0",
            "method":  "call_model",
            "params": map[string]interface{}{
                "model_name": "gpt-5-mini",
                "input_text": fmt.Sprintf("%s の質問 %d", userName, i+1),
                "context_id": "multi_user_gpt_chain",
            },
            "id": i + 1,
        }
        conn.WriteJSON(payload)

        _, message, _ := conn.ReadMessage()
        var msg map[string]interface{}
        json.Unmarshal(message, &msg)
        if result, ok := msg["result"].(map[string]interface{}); ok {
            if text, exists := result["output_text"].(string); exists {
                fmt.Println(userName, "部分応答:", text)
            }
        }
    }
}

func main() {
    go userClient("Eve")
    go userClient("Frank")
    select {} // 永久待機
}

✅ ポイントまとめ
実際の GPT モデルを使用 → Summary → Translation → QA のモデルチェインをリアルタイムで実行
マルチユーザー同時接続 → Python、Node.js、Go すべてのユーザーで同じコンテキストを共有
WebSocket ストリーミング → 部分応答を即時配信
非同期タスク によって各ユーザー発話のチェイン処理を並列化
追加で update_context や履歴管理を組み込めば、より複雑な協調対話システムを構築可能
希望であれば、このデモを フロントエンド(React / Vue / HTML)で可視化して全ユーザーが同時にチャット・モデルチェイン結果を見るブラウザデモ に拡張した例も作れます。
作りますか?

はい。お願いします。

ChatGPT:

了解です。では、先ほどの マルチユーザー GPT モデルチェインデモ を ブラウザで可視化 するサンプルを作成します。
ポイント:
WebSocket 接続で MCP サーバと通信
マルチユーザー同時接続 → Python/Node.js/Go クライアントと同じコンテキストを共有
リアルタイムにチャット履歴とモデルチェイン結果を表示
簡易 React フロントエンド を使用

  1. React フロントエンド (TypeScript)
// App.tsx
import React, { useState, useEffect, useRef } from "react";

interface Message {
  user: string;
  text: string;
}

const WS_URL = "ws://localhost:8080/ws";
const CONTEXT_ID = "multi_user_gpt_chain";

export default function App() {
  const [messages, setMessages] = useState<Message[]>([]);
  const [input, setInput] = useState("");
  const wsRef = useRef<WebSocket | null>(null);
  const userName = prompt("ユーザー名を入力") || "Anonymous";

  useEffect(() => {
    wsRef.current = new WebSocket(WS_URL);
    wsRef.current.onmessage = (event) => {
      const data = JSON.parse(event.data);
      if (data.result && data.result.output_text) {
        setMessages((prev) => [
          ...prev,
          { user: "Model", text: data.result.output_text },
        ]);
      }
    };
    return () => {
      wsRef.current?.close();
    };
  }, []);

  const sendMessage = () => {
    if (!input || !wsRef.current) return;
    const payload = {
      jsonrpc: "2.0",
      method: "call_model",
      params: {
        model_name: "gpt-5-mini",
        input_text: input,
        context_id: CONTEXT_ID,
      },
      id: Date.now(),
    };
    wsRef.current.send(JSON.stringify(payload));
    setMessages((prev) => [...prev, { user: userName, text: input }]);
    setInput("");
  };

  return (
    <div className="p-4 max-w-2xl mx-auto">
      <h1 className="text-2xl font-bold mb-4">リアルタイム GPT チャット</h1>
      <div className="border p-4 h-96 overflow-y-scroll mb-4 bg-gray-50">
        {messages.map((msg, idx) => (
          <div key={idx} className={msg.user === "Model" ? "text-blue-600" : "text-black"}>
            <strong>{msg.user}:</strong> {msg.text}
          </div>
        ))}
      </div>
      <div className="flex">
        <input
          type="text"
          className="border p-2 flex-1"
          value={input}
          onChange={(e) => setInput(e.target.value)}
          onKeyDown={(e) => e.key === "Enter" && sendMessage()}
        />
        <button className="ml-2 p-2 bg-blue-500 text-white" onClick={sendMessage}>
          送信
        </button>
      </div>
    </div>
  );
}

'2. 補足
TailwindCSS を使っています(簡単にスタイル適用可能)
WebSocket で MCP サーバに接続 → 送信したメッセージは即座に全ユーザーにブロードキャスト
マルチモデルチェインの結果(Summary → Translation → QA)は Model として表示
複数ブラウザで同時に接続すると、全ユーザーの発話とモデル結果がリアルタイムで同期されます
'3. 実行手順
MCP WebSocket サーバを起動(先ほどの Python サーバ)
React プロジェクト作成
npx create-react-app mcp-demo --template typescript
cd mcp-demo
npm install
npm install tailwindcss
上記 App.tsx を置き換え
開発サーバ起動
npm start
複数ブラウザでアクセス → リアルタイムチャットを確認
この構成で:
Python / Node.js / Go のバックエンドクライアントも接続可能
ブラウザ上で全ユーザーの発話・モデルチェイン結果を同時に可視化
希望であれば、このデモに Summary → Translation → QA の個別パネル表示 や 部分応答ごとのアニメーション表示 まで追加して、より直感的なリアルタイム可視化 UI を作る例も提示できます。
作りますか?

はい。お願いします。

ChatGPT:

了解です。では リアルタイムでモデルチェインの各段階(Summary → Translation → QA)を個別パネル表示し、部分応答ごとにアニメーションで可視化するブラウザ UI の例を作ります。
ポイント:
Summary / Translation / QA を別パネルで表示
部分応答が届くごとにアニメーションで文字を追加
マルチユーザー同時接続対応
TailwindCSS + React + WebSocket

  1. React コンポーネント例
// App.tsx
import React, { useState, useEffect, useRef } from "react";

interface Message {
  user: string;
  text: string;
}

const WS_URL = "ws://localhost:8080/ws";
const CONTEXT_ID = "multi_user_gpt_chain";

export default function App() {
  const [userMessages, setUserMessages] = useState<Message[]>([]);
  const [summaryText, setSummaryText] = useState("");
  const [translationText, setTranslationText] = useState("");
  const [qaText, setQaText] = useState("");
  const [input, setInput] = useState("");
  const wsRef = useRef<WebSocket | null>(null);
  const userName = prompt("ユーザー名を入力") || "Anonymous";

  useEffect(() => {
    wsRef.current = new WebSocket(WS_URL);
    wsRef.current.onmessage = (event) => {
      const data = JSON.parse(event.data);
      if (data.result && data.result.output_text) {
        const msg = data.result.output_text;

        // モデルチェインの段階ごとに識別(模擬例: [Summary], [Translation], [QA])
        if (msg.startsWith("[Summary]")) setSummaryText((prev) => prev + msg.replace("[Summary]", ""));
        else if (msg.startsWith("[Translation]")) setTranslationText((prev) => prev + msg.replace("[Translation]", ""));
        else if (msg.startsWith("[QA]")) setQaText((prev) => prev + msg.replace("[QA]", ""));
      }
    };
    return () => wsRef.current?.close();
  }, []);

  const sendMessage = () => {
    if (!input || !wsRef.current) return;

    // ユーザー発話を送信
    const payload = {
      jsonrpc: "2.0",
      method: "call_model",
      params: {
        model_name: "gpt-5-mini",
        input_text: input,
        context_id: CONTEXT_ID,
      },
      id: Date.now(),
    };
    wsRef.current.send(JSON.stringify(payload));
    setUserMessages((prev) => [...prev, { user: userName, text: input }]);
    setInput("");

    // 各パネルを初期化
    setSummaryText("");
    setTranslationText("");
    setQaText("");
  };

  return (
    <div className="p-4 max-w-4xl mx-auto">
      <h1 className="text-2xl font-bold mb-4">リアルタイム GPT モデルチェイン可視化</h1>

      {/* 入力欄 */}
      <div className="flex mb-4">
        <input
          type="text"
          className="border p-2 flex-1"
          value={input}
          onChange={(e) => setInput(e.target.value)}
          onKeyDown={(e) => e.key === "Enter" && sendMessage()}
        />
        <button className="ml-2 p-2 bg-blue-500 text-white" onClick={sendMessage}>
          送信
        </button>
      </div>

      {/* ユーザーメッセージ */}
      <div className="border p-2 mb-4 h-32 overflow-y-scroll bg-gray-50">
        <h2 className="font-semibold mb-1">ユーザー発話</h2>
        {userMessages.map((msg, idx) => (
          <div key={idx}><strong>{msg.user}:</strong> {msg.text}</div>
        ))}
      </div>

      {/* モデルチェインパネル */}
      <div className="grid grid-cols-3 gap-4">
        <div className="border p-2 h-64 overflow-y-scroll bg-yellow-50">
          <h2 className="font-semibold mb-1">Summary</h2>
          <p>{summaryText}</p>
        </div>
        <div className="border p-2 h-64 overflow-y-scroll bg-green-50">
          <h2 className="font-semibold mb-1">Translation</h2>
          <p>{translationText}</p>
        </div>
        <div className="border p-2 h-64 overflow-y-scroll bg-blue-50 text-white">
          <h2 className="font-semibold mb-1">QA</h2>
          <p>{qaText}</p>
        </div>
      </div>
    </div>
  );
}

'2. 補足・ポイント
モデルチェイン段階ごとにパネル分け
Summary → Translation → QA
MCP サーバ側で [Summary], [Translation], [QA] のプレフィックスで送信するとパネルに振り分け可能
部分応答を即時追加
WebSocket で受信するたびに setXXXText((prev) => prev + newText) で文字列を追加
リアルタイムに「アニメーション風」に表示
マルチユーザー対応
複数ブラウザで同じ CONTEXT_ID に接続すると、全員の入力とモデルチェイン結果を同時に可視化
TailwindCSS で簡易レイアウト
グリッドで 3 パネル表示
パネルごとに背景色を変えて視覚的にわかりやすく
この構成で、ユーザーが発話するたびに:
左パネルに Summary が文字単位でストリーミング表示
中パネルに Translation が同様に表示
右パネルに QA が表示
マルチユーザーが同時に接続しても全員がリアルタイムでモデルチェインの状態を確認可能です。
希望であれば、さらに 部分応答が1文字ずつフェードインするアニメーション や ユーザーごとの色分け表示 まで追加して、より直感的でライブ感のある可視化 UI に拡張するサンプルも作れます。
作りますか?

はい。お願いします。

ChatGPT:

了解です。では 文字単位でフェードインするアニメーション+ユーザーごとに色分けしたリアルタイムモデルチェイン可視化 UI のサンプルを作ります。
ポイント:
Summary / Translation / QA の各パネルで文字単位アニメーション
マルチユーザー対応 → ユーザーごとに文字色を変える
WebSocket ストリーミングで部分応答を追加
React + TailwindCSS を使用

  1. React コンポーネント例(文字単位フェードイン&色分け)
// App.tsx
import React, { useState, useEffect, useRef } from "react";

interface Message {
  user: string;
  text: string;
}

const WS_URL = "ws://localhost:8080/ws";
const CONTEXT_ID = "multi_user_gpt_chain";

// ユーザーごとの色マップ
const userColors: Record<string, string> = {
  Alice: "text-red-500",
  Bob: "text-green-500",
  Charlie: "text-blue-500",
  Dave: "text-yellow-500",
  Eve: "text-purple-500",
  Frank: "text-pink-500",
  Model: "text-gray-800",
};

export default function App() {
  const [userMessages, setUserMessages] = useState<Message[]>([]);
  const [summaryChars, setSummaryChars] = useState<{char: string, user: string}[]>([]);
  const [translationChars, setTranslationChars] = useState<{char: string, user: string}[]>([]);
  const [qaChars, setQaChars] = useState<{char: string, user: string}[]>([]);
  const [input, setInput] = useState("");
  const wsRef = useRef<WebSocket | null>(null);
  const userName = prompt("ユーザー名を入力") || "Anonymous";

  // 文字を1つずつフェードイン表示
  const appendChars = (text: string, setter: React.Dispatch<React.SetStateAction<{char:string,user:string}[]>>, user: string) => {
    let index = 0;
    const interval = setInterval(() => {
      if (index >= text.length) {
        clearInterval(interval);
        return;
      }
      setter(prev => [...prev, {char: text[index], user}]);
      index++;
    }, 20); // 20msごとに1文字追加
  };

  useEffect(() => {
    wsRef.current = new WebSocket(WS_URL);
    wsRef.current.onmessage = (event) => {
      const data = JSON.parse(event.data);
      if (data.result && data.result.output_text) {
        const msg = data.result.output_text;

        // 段階判定
        if (msg.startsWith("[Summary]")) appendChars(msg.replace("[Summary]",""), setSummaryChars, "Model");
        else if (msg.startsWith("[Translation]")) appendChars(msg.replace("[Translation]",""), setTranslationChars, "Model");
        else if (msg.startsWith("[QA]")) appendChars(msg.replace("[QA]",""), setQaChars, "Model");
      }
    };
    return () => wsRef.current?.close();
  }, []);

  const sendMessage = () => {
    if (!input || !wsRef.current) return;

    const payload = {
      jsonrpc: "2.0",
      method: "call_model",
      params: {
        model_name: "gpt-5-mini",
        input_text: input,
        context_id: CONTEXT_ID,
      },
      id: Date.now(),
    };
    wsRef.current.send(JSON.stringify(payload));

    setUserMessages(prev => [...prev, {user: userName, text: input}]);
    setInput("");

    // パネルを初期化
    setSummaryChars([]);
    setTranslationChars([]);
    setQaChars([]);
  };

  const renderChars = (chars: {char:string,user:string}[]) => {
    return chars.map((c, idx) => (
      <span key={idx} className={`${userColors[c.user] || "text-gray-800"} transition-opacity duration-200`}>
        {c.char}
      </span>
    ));
  };

  return (
    <div className="p-4 max-w-5xl mx-auto">
      <h1 className="text-2xl font-bold mb-4">リアルタイム GPT モデルチェイン可視化</h1>

      <div className="flex mb-4">
        <input
          type="text"
          className="border p-2 flex-1"
          value={input}
          onChange={(e) => setInput(e.target.value)}
          onKeyDown={(e) => e.key === "Enter" && sendMessage()}
        />
        <button className="ml-2 p-2 bg-blue-500 text-white" onClick={sendMessage}>送信</button>
      </div>

      {/* ユーザーメッセージ */}
      <div className="border p-2 mb-4 h-32 overflow-y-scroll bg-gray-50">
        <h2 className="font-semibold mb-1">ユーザー発話</h2>
        {userMessages.map((msg, idx) => (
          <div key={idx} className={userColors[msg.user] || "text-gray-800"}><strong>{msg.user}:</strong> {msg.text}</div>
        ))}
      </div>

      {/* モデルチェインパネル */}
      <div className="grid grid-cols-3 gap-4">
        <div className="border p-2 h-64 overflow-y-scroll bg-yellow-50">
          <h2 className="font-semibold mb-1">Summary</h2>
          <div>{renderChars(summaryChars)}</div>
        </div>
        <div className="border p-2 h-64 overflow-y-scroll bg-green-50">
          <h2 className="font-semibold mb-1">Translation</h2>
          <div>{renderChars(translationChars)}</div>
        </div>
        <div className="border p-2 h-64 overflow-y-scroll bg-blue-50 text-white">
          <h2 className="font-semibold mb-1">QA</h2>
          <div>{renderChars(qaChars)}</div>
        </div>
      </div>
    </div>
  );
}

'2. 特徴・ポイント
文字単位のフェードインアニメーション
setInterval で1文字ずつ追加
transition-opacity + TailwindCSS で滑らかに表示
ユーザーごとの色分け
userColors マップでユーザーごとに文字色を指定
モデル出力は Model として固定色
リアルタイムマルチユーザー対応
複数ブラウザで同時接続すると、全ユーザーの発話とモデルチェイン結果が反映される
Summary / Translation / QA の3パネル表示
各パネルで段階別に部分応答が追加されるため、チェインの流れが直感的にわかる
この UI を使うと、例えば:
Alice が入力 → Summary が左パネルに文字単位で表示
同時に Translation が中央パネルに文字単位で追加
最後に QA が右パネルに文字単位でフェードイン
Bob が同時に入力 → 全ユーザーに文字が色分け表示
…というリアルタイム体験が可能です。
希望であれば、この UI に 部分応答が来るたびに自動スクロール や ユーザーごとにアバター表示 を追加して、さらにライブ感のある協調チャット UI に拡張する例も作れます。
作りますか?

はい。お願いします。

ChatGPT:

了解です。では、自動スクロール+ユーザーアバター表示付きのリアルタイム協調モデルチェイン UI の例を作ります。
ポイント:
部分応答が追加されるたびに自動スクロール
ユーザーごとのアバター表示(モデルは固定アイコン)
Summary / Translation / QA パネル別表示
文字単位フェードイン + 色分け
マルチユーザー対応

  1. React コンポーネント例
// App.tsx
import React, { useState, useEffect, useRef } from "react";

interface CharItem {
  char: string;
  user: string;
}

interface Message {
  user: string;
  text: string;
}

const WS_URL = "ws://localhost:8080/ws";
const CONTEXT_ID = "multi_user_gpt_chain";

// ユーザーごとの色とアバター
const userMeta: Record<string, { color: string; avatar: string }> = {
  Alice: { color: "text-red-500", avatar: "🧑‍🦰" },
  Bob: { color: "text-green-500", avatar: "🧑‍🦱" },
  Charlie: { color: "text-blue-500", avatar: "🧑‍🦳" },
  Dave: { color: "text-yellow-500", avatar: "🧑‍🦲" },
  Eve: { color: "text-purple-500", avatar: "👩‍🦰" },
  Frank: { color: "text-pink-500", avatar: "👩‍🦱" },
  Model: { color: "text-gray-800", avatar: "🤖" },
};

export default function App() {
  const [userMessages, setUserMessages] = useState<Message[]>([]);
  const [summaryChars, setSummaryChars] = useState<CharItem[]>([]);
  const [translationChars, setTranslationChars] = useState<CharItem[]>([]);
  const [qaChars, setQaChars] = useState<CharItem[]>([]);
  const [input, setInput] = useState("");
  const wsRef = useRef<WebSocket | null>(null);
  const userName = prompt("ユーザー名を入力") || "Anonymous";

  const summaryRef = useRef<HTMLDivElement>(null);
  const translationRef = useRef<HTMLDivElement>(null);
  const qaRef = useRef<HTMLDivElement>(null);

  // 文字追加関数
  const appendChars = (text: string, setter: React.Dispatch<React.SetStateAction<CharItem[]>>, user: string) => {
    let index = 0;
    const interval = setInterval(() => {
      if (index >= text.length) {
        clearInterval(interval);
        return;
      }
      setter(prev => [...prev, { char: text[index], user }]);
      index++;
    }, 20);
  };

  // 自動スクロール
  useEffect(() => {
    if (summaryRef.current) summaryRef.current.scrollTop = summaryRef.current.scrollHeight;
  }, [summaryChars]);

  useEffect(() => {
    if (translationRef.current) translationRef.current.scrollTop = translationRef.current.scrollHeight;
  }, [translationChars]);

  useEffect(() => {
    if (qaRef.current) qaRef.current.scrollTop = qaRef.current.scrollHeight;
  }, [qaChars]);

  // WebSocket 接続
  useEffect(() => {
    wsRef.current = new WebSocket(WS_URL);
    wsRef.current.onmessage = (event) => {
      const data = JSON.parse(event.data);
      if (data.result && data.result.output_text) {
        const msg = data.result.output_text;
        if (msg.startsWith("[Summary]")) appendChars(msg.replace("[Summary]",""), setSummaryChars, "Model");
        else if (msg.startsWith("[Translation]")) appendChars(msg.replace("[Translation]",""), setTranslationChars, "Model");
        else if (msg.startsWith("[QA]")) appendChars(msg.replace("[QA]",""), setQaChars, "Model");
      }
    };
    return () => wsRef.current?.close();
  }, []);

  const sendMessage = () => {
    if (!input || !wsRef.current) return;

    const payload = {
      jsonrpc: "2.0",
      method: "call_model",
      params: {
        model_name: "gpt-5-mini",
        input_text: input,
        context_id: CONTEXT_ID,
      },
      id: Date.now(),
    };
    wsRef.current.send(JSON.stringify(payload));

    setUserMessages(prev => [...prev, { user: userName, text: input }]);
    setInput("");

    // パネル初期化
    setSummaryChars([]);
    setTranslationChars([]);
    setQaChars([]);
  };

  const renderChars = (chars: CharItem[]) =>
    chars.map((c, idx) => (
      <span key={idx} className={`${userMeta[c.user]?.color || "text-gray-800"} transition-opacity duration-200`}>
        {c.char}
      </span>
    ));

  return (
    <div className="p-4 max-w-5xl mx-auto">
      <h1 className="text-2xl font-bold mb-4">リアルタイム GPT モデルチェイン可視化</h1>

      {/* 入力欄 */}
      <div className="flex mb-4">
        <input
          type="text"
          className="border p-2 flex-1"
          value={input}
          onChange={(e) => setInput(e.target.value)}
          onKeyDown={(e) => e.key === "Enter" && sendMessage()}
        />
        <button className="ml-2 p-2 bg-blue-500 text-white" onClick={sendMessage}>送信</button>
      </div>

      {/* ユーザーメッセージ */}
      <div className="border p-2 mb-4 h-32 overflow-y-scroll bg-gray-50">
        <h2 className="font-semibold mb-1">ユーザー発話</h2>
        {userMessages.map((msg, idx) => (
          <div key={idx} className="flex items-center mb-1">
            <span className="mr-2">{userMeta[msg.user]?.avatar || "👤"}</span>
            <span className={userMeta[msg.user]?.color || "text-gray-800"}><strong>{msg.user}:</strong> {msg.text}</span>
          </div>
        ))}
      </div>

      {/* モデルチェインパネル */}
      <div className="grid grid-cols-3 gap-4">
        <div className="border p-2 h-64 overflow-y-scroll bg-yellow-50" ref={summaryRef}>
          <h2 className="font-semibold mb-1">Summary</h2>
          <div className="flex flex-wrap items-start">{renderChars(summaryChars)}</div>
        </div>
        <div className="border p-2 h-64 overflow-y-scroll bg-green-50" ref={translationRef}>
          <h2 className="font-semibold mb-1">Translation</h2>
          <div className="flex flex-wrap items-start">{renderChars(translationChars)}</div>
        </div>
        <div className="border p-2 h-64 overflow-y-scroll bg-blue-50 text-white" ref={qaRef}>
          <h2 className="font-semibold mb-1">QA</h2>
          <div className="flex flex-wrap items-start">{renderChars(qaChars)}</div>
        </div>
      </div>
    </div>
  );
}

'2. 特徴・ポイント
自動スクロール
useRef + scrollTop = scrollHeight で最新文字まで自動スクロール
ユーザーアバター表示
userMeta[user].avatar をユーザー名横に表示
モデルは 🤖 で固定
文字単位フェードイン
setInterval で1文字ずつ追加
Tailwind transition-opacity duration-200 で滑らか
ユーザーごとの色分け
複数ユーザーが同時に発話しても視認性が高い
マルチパネル表示
Summary / Translation / QA の3パネルでチェイン全体を直感的に可視化
マルチユーザー対応
同じ CONTEXT_ID に接続すると、全ユーザーの発話・モデル出力がリアルタイムで反映
この UI で、複数ブラウザユーザーが同時に発話すると:
各ユーザーの発話に対応するアバターと文字色で表示
モデルチェイン(Summary → Translation → QA)の部分応答が文字単位でアニメーション表示
各パネルは自動スクロールして常に最新を表示
…というライブ感のある協調チャット体験が可能です。
希望であれば、さらに モデルチェインごとにタイムスタンプや部分応答速度表示 を追加して、リアルタイム処理の進行状況を可視化する UI に拡張する例も作れます。
作りますか?

はい。お願いします。

ChatGPT:

了解です。では、タイムスタンプ&部分応答速度(文字単位での処理速度)を可視化するリアルタイムモデルチェイン UI を作ります。
ポイント:
Summary / Translation / QA パネルごとにタイムスタンプ表示
部分応答速度を文字色の濃淡で可視化(速い部分は濃く、遅い部分は薄く)
既存のユーザーアバター表示+自動スクロール+文字単位フェードイン
マルチユーザー対応

  1. React コンポーネント例
// App.tsx
import React, { useState, useEffect, useRef } from "react";

interface CharItem {
  char: string;
  user: string;
  timestamp: number;
  speed: number; // ms/char
}

interface Message {
  user: string;
  text: string;
}

const WS_URL = "ws://localhost:8080/ws";
const CONTEXT_ID = "multi_user_gpt_chain";

const userMeta: Record<string, { color: string; avatar: string }> = {
  Alice: { color: "text-red-500", avatar: "🧑‍🦰" },
  Bob: { color: "text-green-500", avatar: "🧑‍🦱" },
  Charlie: { color: "text-blue-500", avatar: "🧑‍🦳" },
  Dave: { color: "text-yellow-500", avatar: "🧑‍🦲" },
  Eve: { color: "text-purple-500", avatar: "👩‍🦰" },
  Frank: { color: "text-pink-500", avatar: "👩‍🦱" },
  Model: { color: "text-gray-800", avatar: "🤖" },
};

export default function App() {
  const [userMessages, setUserMessages] = useState<Message[]>([]);
  const [summaryChars, setSummaryChars] = useState<CharItem[]>([]);
  const [translationChars, setTranslationChars] = useState<CharItem[]>([]);
  const [qaChars, setQaChars] = useState<CharItem[]>([]);
  const [input, setInput] = useState("");
  const wsRef = useRef<WebSocket | null>(null);
  const userName = prompt("ユーザー名を入力") || "Anonymous";

  const summaryRef = useRef<HTMLDivElement>(null);
  const translationRef = useRef<HTMLDivElement>(null);
  const qaRef = useRef<HTMLDivElement>(null);

  const appendChars = (
    text: string,
    setter: React.Dispatch<React.SetStateAction<CharItem[]>>,
    user: string
  ) => {
    let index = 0;
    let lastTimestamp = performance.now();

    const interval = setInterval(() => {
      if (index >= text.length) {
        clearInterval(interval);
        return;
      }
      const now = performance.now();
      const speed = now - lastTimestamp;
      lastTimestamp = now;
      setter(prev => [...prev, { char: text[index], user, timestamp: now, speed }]);
      index++;
    }, 20);
  };

  // 自動スクロール
  useEffect(() => { if(summaryRef.current) summaryRef.current.scrollTop = summaryRef.current.scrollHeight }, [summaryChars]);
  useEffect(() => { if(translationRef.current) translationRef.current.scrollTop = translationRef.current.scrollHeight }, [translationChars]);
  useEffect(() => { if(qaRef.current) qaRef.current.scrollTop = qaRef.current.scrollHeight }, [qaChars]);

  // WebSocket接続
  useEffect(() => {
    wsRef.current = new WebSocket(WS_URL);
    wsRef.current.onmessage = (event) => {
      const data = JSON.parse(event.data);
      if (data.result && data.result.output_text) {
        const msg = data.result.output_text;
        if (msg.startsWith("[Summary]")) appendChars(msg.replace("[Summary]",""), setSummaryChars, "Model");
        else if (msg.startsWith("[Translation]")) appendChars(msg.replace("[Translation]",""), setTranslationChars, "Model");
        else if (msg.startsWith("[QA]")) appendChars(msg.replace("[QA]",""), setQaChars, "Model");
      }
    };
    return () => wsRef.current?.close();
  }, []);

  const sendMessage = () => {
    if (!input || !wsRef.current) return;
    const payload = {
      jsonrpc: "2.0",
      method: "call_model",
      params: { model_name: "gpt-5-mini", input_text: input, context_id: CONTEXT_ID },
      id: Date.now(),
    };
    wsRef.current.send(JSON.stringify(payload));
    setUserMessages(prev => [...prev, { user: userName, text: input }]);
    setInput("");
    setSummaryChars([]); setTranslationChars([]); setQaChars([]);
  };

  // 部分応答速度を色の濃淡で可視化
  const renderChars = (chars: CharItem[]) =>
    chars.map((c, idx) => {
      const baseColor = userMeta[c.user]?.color || "text-gray-800";
      // speed: 20ms=濃い, 200ms=薄い
      const opacity = Math.max(0.3, Math.min(1, 200/c.speed));
      return (
        <span key={idx} className={`${baseColor} transition-opacity duration-100`} style={{ opacity }}>
          {c.char}
        </span>
      );
    });

  return (
    <div className="p-4 max-w-5xl mx-auto">
      <h1 className="text-2xl font-bold mb-4">リアルタイム GPT モデルチェイン可視化</h1>

      {/* 入力欄 */}
      <div className="flex mb-4">
        <input type="text" className="border p-2 flex-1" value={input} onChange={e=>setInput(e.target.value)}
          onKeyDown={e=>e.key==="Enter" && sendMessage()} />
        <button className="ml-2 p-2 bg-blue-500 text-white" onClick={sendMessage}>送信</button>
      </div>

      {/* ユーザーメッセージ */}
      <div className="border p-2 mb-4 h-32 overflow-y-scroll bg-gray-50">
        <h2 className="font-semibold mb-1">ユーザー発話</h2>
        {userMessages.map((msg, idx)=>(
          <div key={idx} className="flex items-center mb-1">
            <span className="mr-2">{userMeta[msg.user]?.avatar || "👤"}</span>
            <span className={userMeta[msg.user]?.color || "text-gray-800"}><strong>{msg.user}:</strong> {msg.text}</span>
          </div>
        ))}
      </div>

      {/* モデルチェインパネル */}
      <div className="grid grid-cols-3 gap-4">
        <div className="border p-2 h-64 overflow-y-scroll bg-yellow-50" ref={summaryRef}>
          <h2 className="font-semibold mb-1">Summary</h2>
          <div className="flex flex-wrap items-start">
            {renderChars(summaryChars)}
          </div>
        </div>
        <div className="border p-2 h-64 overflow-y-scroll bg-green-50" ref={translationRef}>
          <h2 className="font-semibold mb-1">Translation</h2>
          <div className="flex flex-wrap items-start">
            {renderChars(translationChars)}
          </div>
        </div>
        <div className="border p-2 h-64 overflow-y-scroll bg-blue-50 text-white" ref={qaRef}>
          <h2 className="font-semibold mb-1">QA</h2>
          <div className="flex flex-wrap items-start">
            {renderChars(qaChars)}
          </div>
        </div>
      </div>
    </div>
  );
}

'2. 特徴・ポイント
タイムスタンプ管理
文字単位で performance.now() を保存
文字ごとの処理速度を算出
部分応答速度可視化
速い文字ほど濃く、遅い文字は薄く表示 (opacity で可視化)
ユーザーアバター表示
発話パネルでユーザー別アイコン表示
文字単位フェードイン
setInterval で1文字ずつ追加
transition-opacity で滑らかに表示
自動スクロール
最新文字が常に表示される
マルチパネル表示
Summary / Translation / QA の3パネルでチェイン全体を可視化
マルチユーザー対応
複数ブラウザユーザーの発話・モデル出力をリアルタイムに同期
この UI を使えば、モデルチェイン処理の速度・進行状況をリアルタイムで確認しながら、マルチユーザーで協調して操作 することが可能になります。
希望であれば、さらに 文字ごとの出現時間グラフ化(可視化チャート) を追加して、文字単位の処理スピードを数値的に解析する拡張も作れます。
作りますか?

はい。お願いします。

ChatGPT:

了解です。では、文字単位の出現時間(処理速度)を可視化するグラフ付きリアルタイムモデルチェイン UI を作ります。
ポイント:
Summary / Translation / QA の各パネルに文字単位処理速度をグラフ表示
部分応答が届くごとにグラフが更新
既存の文字フェードイン・ユーザー色分け・アバター表示・自動スクロール対応
Recharts を使った簡易グラフ表示

  1. React コンポーネント例(Recharts 使用)
// App.tsx
import React, { useState, useEffect, useRef } from "react";
import { LineChart, Line, XAxis, YAxis, Tooltip, ResponsiveContainer } from "recharts";

interface CharItem {
  char: string;
  user: string;
  timestamp: number;
  speed: number;
}

interface Message {
  user: string;
  text: string;
}

const WS_URL = "ws://localhost:8080/ws";
const CONTEXT_ID = "multi_user_gpt_chain";

const userMeta: Record<string, { color: string; avatar: string }> = {
  Alice: { color: "text-red-500", avatar: "🧑‍🦰" },
  Bob: { color: "text-green-500", avatar: "🧑‍🦱" },
  Charlie: { color: "text-blue-500", avatar: "🧑‍🦳" },
  Dave: { color: "text-yellow-500", avatar: "🧑‍🦲" },
  Eve: { color: "text-purple-500", avatar: "👩‍🦰" },
  Frank: { color: "text-pink-500", avatar: "👩‍🦱" },
  Model: { color: "text-gray-800", avatar: "🤖" },
};

export default function App() {
  const [userMessages, setUserMessages] = useState<Message[]>([]);
  const [summaryChars, setSummaryChars] = useState<CharItem[]>([]);
  const [translationChars, setTranslationChars] = useState<CharItem[]>([]);
  const [qaChars, setQaChars] = useState<CharItem[]>([]);
  const [input, setInput] = useState("");
  const wsRef = useRef<WebSocket | null>(null);
  const userName = prompt("ユーザー名を入力") || "Anonymous";

  const summaryRef = useRef<HTMLDivElement>(null);
  const translationRef = useRef<HTMLDivElement>(null);
  const qaRef = useRef<HTMLDivElement>(null);

  const appendChars = (text: string, setter: React.Dispatch<React.SetStateAction<CharItem[]>>, user: string) => {
    let index = 0;
    let lastTimestamp = performance.now();
    const interval = setInterval(() => {
      if (index >= text.length) {
        clearInterval(interval);
        return;
      }
      const now = performance.now();
      const speed = now - lastTimestamp;
      lastTimestamp = now;
      setter(prev => [...prev, { char: text[index], user, timestamp: now, speed }]);
      index++;
    }, 20);
  };

  useEffect(() => { if(summaryRef.current) summaryRef.current.scrollTop = summaryRef.current.scrollHeight }, [summaryChars]);
  useEffect(() => { if(translationRef.current) translationRef.current.scrollTop = translationRef.current.scrollHeight }, [translationChars]);
  useEffect(() => { if(qaRef.current) qaRef.current.scrollTop = qaRef.current.scrollHeight }, [qaChars]);

  useEffect(() => {
    wsRef.current = new WebSocket(WS_URL);
    wsRef.current.onmessage = (event) => {
      const data = JSON.parse(event.data);
      if (data.result && data.result.output_text) {
        const msg = data.result.output_text;
        if (msg.startsWith("[Summary]")) appendChars(msg.replace("[Summary]",""), setSummaryChars, "Model");
        else if (msg.startsWith("[Translation]")) appendChars(msg.replace("[Translation]",""), setTranslationChars, "Model");
        else if (msg.startsWith("[QA]")) appendChars(msg.replace("[QA]",""), setQaChars, "Model");
      }
    };
    return () => wsRef.current?.close();
  }, []);

  const sendMessage = () => {
    if (!input || !wsRef.current) return;
    const payload = {
      jsonrpc: "2.0",
      method: "call_model",
      params: { model_name: "gpt-5-mini", input_text: input, context_id: CONTEXT_ID },
      id: Date.now(),
    };
    wsRef.current.send(JSON.stringify(payload));
    setUserMessages(prev => [...prev, { user: userName, text: input }]);
    setInput("");
    setSummaryChars([]); setTranslationChars([]); setQaChars([]);
  };

  const renderChars = (chars: CharItem[]) => chars.map((c, idx) => (
    <span key={idx} className={`${userMeta[c.user]?.color || "text-gray-800"} transition-opacity duration-100`}>
      {c.char}
    </span>
  ));

  const renderChart = (chars: CharItem[]) => {
    const data = chars.map((c, idx) => ({ index: idx, speed: c.speed }));
    return (
      <ResponsiveContainer width="100%" height={80}>
        <LineChart data={data}>
          <XAxis dataKey="index" hide />
          <YAxis domain={[0, 'dataMax']} hide />
          <Tooltip />
          <Line type="monotone" dataKey="speed" stroke="#8884d8" dot={false} isAnimationActive={false} />
        </LineChart>
      </ResponsiveContainer>
    );
  };

  return (
    <div className="p-4 max-w-5xl mx-auto">
      <h1 className="text-2xl font-bold mb-4">リアルタイム GPT モデルチェイン可視化(速度グラフ付き)</h1>

      {/* 入力欄 */}
      <div className="flex mb-4">
        <input type="text" className="border p-2 flex-1" value={input} onChange={e=>setInput(e.target.value)}
          onKeyDown={e=>e.key==="Enter" && sendMessage()} />
        <button className="ml-2 p-2 bg-blue-500 text-white" onClick={sendMessage}>送信</button>
      </div>

      {/* ユーザーメッセージ */}
      <div className="border p-2 mb-4 h-32 overflow-y-scroll bg-gray-50">
        <h2 className="font-semibold mb-1">ユーザー発話</h2>
        {userMessages.map((msg, idx)=>(
          <div key={idx} className="flex items-center mb-1">
            <span className="mr-2">{userMeta[msg.user]?.avatar || "👤"}</span>
            <span className={userMeta[msg.user]?.color || "text-gray-800"}><strong>{msg.user}:</strong> {msg.text}</span>
          </div>
        ))}
      </div>

      {/* モデルチェインパネル */}
      <div className="grid grid-cols-3 gap-4">
        <div className="border p-2 h-64 overflow-y-scroll bg-yellow-50" ref={summaryRef}>
          <h2 className="font-semibold mb-1">Summary</h2>
          <div className="flex flex-wrap items-start">{renderChars(summaryChars)}</div>
          {renderChart(summaryChars)}
        </div>
        <div className="border p-2 h-64 overflow-y-scroll bg-green-50" ref={translationRef}>
          <h2 className="font-semibold mb-1">Translation</h2>
          <div className="flex flex-wrap items-start">{renderChars(translationChars)}</div>
          {renderChart(translationChars)}
        </div>
        <div className="border p-2 h-64 overflow-y-scroll bg-blue-50 text-white" ref={qaRef}>
          <h2 className="font-semibold mb-1">QA</h2>
          <div className="flex flex-wrap items-start">{renderChars(qaChars)}</div>
          {renderChart(qaChars)}
        </div>
      </div>
    </div>
  );
}

'2. 特徴・ポイント
文字単位の処理速度を可視化
speed(ms/char)を LineChart に反映
部分応答が増えるごとにグラフが更新
既存の機能維持
文字単位フェードイン
ユーザー色分け
アバター表示
自動スクロール
Recharts を使用
ResponsiveContainer で自動リサイズ
LineChart で速度の変化を直感的に確認
マルチパネル表示
Summary / Translation / QA それぞれにグラフ付き
この UI によって、文字単位の生成スピードや遅延の状態をリアルタイムで視覚的に把握しながら、マルチユーザーで協調してモデルチェインの動作を観察できます。
さらに拡張するなら、ユーザー別の速度比較グラフ や 平均速度・最大速度表示 も追加可能です。
作りますか?

はい。お願いします。

ChatGPT:

了解です。では、さらに進化させて ユーザー別速度比較グラフ+平均・最大速度表示付きリアルタイムモデルチェイン UI を作ります。
ポイント:
ユーザーごとの文字処理速度を比較する折れ線グラフ
Summary / Translation / QA 各パネルに平均速度と最大速度を表示
既存の文字単位フェードイン・色分け・アバター表示・自動スクロール対応
Recharts を使った複数ライン表示

  1. React コンポーネント例
// App.tsx
import React, { useState, useEffect, useRef } from "react";
import { LineChart, Line, XAxis, YAxis, Tooltip, Legend, ResponsiveContainer } from "recharts";

interface CharItem {
  char: string;
  user: string;
  timestamp: number;
  speed: number;
}

interface Message {
  user: string;
  text: string;
}

const WS_URL = "ws://localhost:8080/ws";
const CONTEXT_ID = "multi_user_gpt_chain";

const userMeta: Record<string, { color: string; avatar: string }> = {
  Alice: { color: "text-red-500", avatar: "🧑‍🦰" },
  Bob: { color: "text-green-500", avatar: "🧑‍🦱" },
  Charlie: { color: "text-blue-500", avatar: "🧑‍🦳" },
  Dave: { color: "text-yellow-500", avatar: "🧑‍🦲" },
  Eve: { color: "text-purple-500", avatar: "👩‍🦰" },
  Frank: { color: "text-pink-500", avatar: "👩‍🦱" },
  Model: { color: "text-gray-800", avatar: "🤖" },
};

export default function App() {
  const [userMessages, setUserMessages] = useState<Message[]>([]);
  const [summaryChars, setSummaryChars] = useState<CharItem[]>([]);
  const [translationChars, setTranslationChars] = useState<CharItem[]>([]);
  const [qaChars, setQaChars] = useState<CharItem[]>([]);
  const [input, setInput] = useState("");
  const wsRef = useRef<WebSocket | null>(null);
  const userName = prompt("ユーザー名を入力") || "Anonymous";

  const summaryRef = useRef<HTMLDivElement>(null);
  const translationRef = useRef<HTMLDivElement>(null);
  const qaRef = useRef<HTMLDivElement>(null);

  const appendChars = (text: string, setter: React.Dispatch<React.SetStateAction<CharItem[]>>, user: string) => {
    let index = 0;
    let lastTimestamp = performance.now();
    const interval = setInterval(() => {
      if (index >= text.length) { clearInterval(interval); return; }
      const now = performance.now();
      const speed = now - lastTimestamp;
      lastTimestamp = now;
      setter(prev => [...prev, { char: text[index], user, timestamp: now, speed }]);
      index++;
    }, 20);
  };

  useEffect(() => { if(summaryRef.current) summaryRef.current.scrollTop = summaryRef.current.scrollHeight }, [summaryChars]);
  useEffect(() => { if(translationRef.current) translationRef.current.scrollTop = translationRef.current.scrollHeight }, [translationChars]);
  useEffect(() => { if(qaRef.current) qaRef.current.scrollTop = qaRef.current.scrollHeight }, [qaChars]);

  useEffect(() => {
    wsRef.current = new WebSocket(WS_URL);
    wsRef.current.onmessage = (event) => {
      const data = JSON.parse(event.data);
      if (data.result && data.result.output_text) {
        const msg = data.result.output_text;
        if (msg.startsWith("[Summary]")) appendChars(msg.replace("[Summary]",""), setSummaryChars, "Model");
        else if (msg.startsWith("[Translation]")) appendChars(msg.replace("[Translation]",""), setTranslationChars, "Model");
        else if (msg.startsWith("[QA]")) appendChars(msg.replace("[QA]",""), setQaChars, "Model");
      }
    };
    return () => wsRef.current?.close();
  }, []);

  const sendMessage = () => {
    if (!input || !wsRef.current) return;
    const payload = {
      jsonrpc: "2.0",
      method: "call_model",
      params: { model_name: "gpt-5-mini", input_text: input, context_id: CONTEXT_ID },
      id: Date.now(),
    };
    wsRef.current.send(JSON.stringify(payload));
    setUserMessages(prev => [...prev, { user: userName, text: input }]);
    setInput("");
    setSummaryChars([]); setTranslationChars([]); setQaChars([]);
  };

  const renderChars = (chars: CharItem[]) => chars.map((c, idx) => (
    <span key={idx} className={`${userMeta[c.user]?.color || "text-gray-800"} transition-opacity duration-100`}>
      {c.char}
    </span>
  ));

  // ユーザー別グラフデータ生成
  const generateChartData = (chars: CharItem[]) => {
    const data: any[] = [];
    chars.forEach((c, idx) => {
      data[idx] = data[idx] || { index: idx };
      data[idx][c.user] = c.speed;
    });
    return data;
  };

  // 平均・最大速度計算
  const calcStats = (chars: CharItem[]) => {
    if (!chars.length) return { avg: 0, max: 0 };
    const speeds = chars.map(c=>c.speed);
    const avg = speeds.reduce((a,b)=>a+b,0)/speeds.length;
    const max = Math.max(...speeds);
    return { avg, max };
  };

  const renderChart = (chars: CharItem[]) => {
    const data = generateChartData(chars);
    return (
      <ResponsiveContainer width="100%" height={120}>
        <LineChart data={data}>
          <XAxis dataKey="index" hide />
          <YAxis domain={[0,'dataMax']} hide />
          <Tooltip />
          <Legend />
          {Object.keys(userMeta).map(user=>(
            <Line key={user} type="monotone" dataKey={user} stroke={userMeta[user].color.replace("text-","")} dot={false} isAnimationActive={false} />
          ))}
        </LineChart>
      </ResponsiveContainer>
    );
  };

  return (
    <div className="p-4 max-w-6xl mx-auto">
      <h1 className="text-2xl font-bold mb-4">リアルタイム GPT モデルチェイン可視化(ユーザー別速度比較)</h1>

      <div className="flex mb-4">
        <input type="text" className="border p-2 flex-1" value={input} onChange={e=>setInput(e.target.value)}
          onKeyDown={e=>e.key==="Enter" && sendMessage()} />
        <button className="ml-2 p-2 bg-blue-500 text-white" onClick={sendMessage}>送信</button>
      </div>

      <div className="border p-2 mb-4 h-32 overflow-y-scroll bg-gray-50">
        <h2 className="font-semibold mb-1">ユーザー発話</h2>
        {userMessages.map((msg, idx)=>(
          <div key={idx} className="flex items-center mb-1">
            <span className="mr-2">{userMeta[msg.user]?.avatar || "👤"}</span>
            <span className={userMeta[msg.user]?.color || "text-gray-800"}><strong>{msg.user}:</strong> {msg.text}</span>
          </div>
        ))}
      </div>

      <div className="grid grid-cols-3 gap-4">
        {[
          { title:"Summary", chars:summaryChars, ref:summaryRef },
          { title:"Translation", chars:translationChars, ref:translationRef },
          { title:"QA", chars:qaChars, ref:qaRef },
        ].map(({title, chars, ref}, idx)=> {
          const stats = calcStats(chars);
          return (
            <div key={idx} className="border p-2 h-72 overflow-y-scroll bg-yellow-50" ref={ref}>
              <h2 className="font-semibold mb-1">{title}</h2>
              <div className="flex flex-wrap items-start">{renderChars(chars)}</div>
              <div className="mt-2 text-sm text-gray-700">
                平均速度: {stats.avg.toFixed(1)} ms/char, 最大速度: {stats.max.toFixed(1)} ms/char
              </div>
              {renderChart(chars)}
            </div>
          );
        })}
      </div>
    </div>
  );
}

'2. 特徴・ポイント
ユーザー別速度比較グラフ
LineChart に複数ユーザーの速度を表示
文字ごとの処理速度を可視化
平均速度・最大速度表示
各パネル下部にリアルタイムで更新
既存機能維持
文字単位フェードイン
ユーザー色分け
アバター表示
自動スクロール
マルチパネル表示
Summary / Translation / QA 各パネルにグラフ付き
マルチユーザー対応
複数ブラウザで同時入力可
モデルとユーザーの速度比較が一目でわかる
この UI により、文字生成の速度や遅延をユーザー別に比較しながら、リアルタイムモデルチェインの進行状況を把握できます。
さらに拡張すれば、文字生成時間のヒートマップ や ユーザー間の応答遅延差をグラフ化 してパフォーマンス分析も可能です。
希望であれば、次のステップとして 文字単位ヒートマップ表示 を追加した UI を作れます。
作りますか?

はい。お願いします。

ChatGPT:

了解です。では、文字単位のヒートマップ表示付きリアルタイムモデルチェイン UI を作ります。
ポイント:
文字生成速度に応じて背景色を変化(速い文字は緑、遅い文字は赤)
Summary / Translation / QA 各パネルに対応
既存の文字フェードイン・ユーザー色分け・アバター表示・自動スクロール対応
リアルタイムでユーザー別・モデル出力の遅延を視覚化

  1. React コンポーネント例(文字単位ヒートマップ)
// App.tsx
import React, { useState, useEffect, useRef } from "react";

interface CharItem {
  char: string;
  user: string;
  timestamp: number;
  speed: number; // ms/char
}

interface Message {
  user: string;
  text: string;
}

const WS_URL = "ws://localhost:8080/ws";
const CONTEXT_ID = "multi_user_gpt_chain";

const userMeta: Record<string, { color: string; avatar: string }> = {
  Alice: { color: "text-red-500", avatar: "🧑‍🦰" },
  Bob: { color: "text-green-500", avatar: "🧑‍🦱" },
  Charlie: { color: "text-blue-500", avatar: "🧑‍🦳" },
  Dave: { color: "text-yellow-500", avatar: "🧑‍🦲" },
  Eve: { color: "text-purple-500", avatar: "👩‍🦰" },
  Frank: { color: "text-pink-500", avatar: "👩‍🦱" },
  Model: { color: "text-gray-800", avatar: "🤖" },
};

// ヒートマップ用の色変換関数
const speedToColor = (speed: number) => {
  // 20ms=緑, 200ms=赤
  const capped = Math.min(Math.max(speed, 20), 200);
  const green = Math.floor(255 - ((capped-20)/180)*255);
  const red = Math.floor(((capped-20)/180)*255);
  return `rgb(${red},${green},0)`;
};

export default function App() {
  const [userMessages, setUserMessages] = useState<Message[]>([]);
  const [summaryChars, setSummaryChars] = useState<CharItem[]>([]);
  const [translationChars, setTranslationChars] = useState<CharItem[]>([]);
  const [qaChars, setQaChars] = useState<CharItem[]>([]);
  const [input, setInput] = useState("");
  const wsRef = useRef<WebSocket | null>(null);
  const userName = prompt("ユーザー名を入力") || "Anonymous";

  const summaryRef = useRef<HTMLDivElement>(null);
  const translationRef = useRef<HTMLDivElement>(null);
  const qaRef = useRef<HTMLDivElement>(null);

  const appendChars = (text: string, setter: React.Dispatch<React.SetStateAction<CharItem[]>>, user: string) => {
    let index = 0;
    let lastTimestamp = performance.now();
    const interval = setInterval(() => {
      if (index >= text.length) { clearInterval(interval); return; }
      const now = performance.now();
      const speed = now - lastTimestamp;
      lastTimestamp = now;
      setter(prev => [...prev, { char: text[index], user, timestamp: now, speed }]);
      index++;
    }, 20);
  };

  useEffect(() => { if(summaryRef.current) summaryRef.current.scrollTop = summaryRef.current.scrollHeight }, [summaryChars]);
  useEffect(() => { if(translationRef.current) translationRef.current.scrollTop = translationRef.current.scrollHeight }, [translationChars]);
  useEffect(() => { if(qaRef.current) qaRef.current.scrollTop = qaRef.current.scrollHeight }, [qaChars]);

  useEffect(() => {
    wsRef.current = new WebSocket(WS_URL);
    wsRef.current.onmessage = (event) => {
      const data = JSON.parse(event.data);
      if (data.result && data.result.output_text) {
        const msg = data.result.output_text;
        if (msg.startsWith("[Summary]")) appendChars(msg.replace("[Summary]",""), setSummaryChars, "Model");
        else if (msg.startsWith("[Translation]")) appendChars(msg.replace("[Translation]",""), setTranslationChars, "Model");
        else if (msg.startsWith("[QA]")) appendChars(msg.replace("[QA]",""), setQaChars, "Model");
      }
    };
    return () => wsRef.current?.close();
  }, []);

  const sendMessage = () => {
    if (!input || !wsRef.current) return;
    const payload = {
      jsonrpc: "2.0",
      method: "call_model",
      params: { model_name: "gpt-5-mini", input_text: input, context_id: CONTEXT_ID },
      id: Date.now(),
    };
    wsRef.current.send(JSON.stringify(payload));
    setUserMessages(prev => [...prev, { user: userName, text: input }]);
    setInput("");
    setSummaryChars([]); setTranslationChars([]); setQaChars([]);
  };

  const renderCharsHeatmap = (chars: CharItem[]) =>
    chars.map((c, idx) => (
      <span key={idx} style={{ backgroundColor: speedToColor(c.speed) }} className={`${userMeta[c.user]?.color || "text-gray-800"} transition-opacity duration-100`}>
        {c.char}
      </span>
    ));

  return (
    <div className="p-4 max-w-6xl mx-auto">
      <h1 className="text-2xl font-bold mb-4">リアルタイム GPT モデルチェイン可視化(文字単位ヒートマップ)</h1>

      <div className="flex mb-4">
        <input type="text" className="border p-2 flex-1" value={input} onChange={e=>setInput(e.target.value)}
          onKeyDown={e=>e.key==="Enter" && sendMessage()} />
        <button className="ml-2 p-2 bg-blue-500 text-white" onClick={sendMessage}>送信</button>
      </div>

      <div className="border p-2 mb-4 h-32 overflow-y-scroll bg-gray-50">
        <h2 className="font-semibold mb-1">ユーザー発話</h2>
        {userMessages.map((msg, idx)=>(
          <div key={idx} className="flex items-center mb-1">
            <span className="mr-2">{userMeta[msg.user]?.avatar || "👤"}</span>
            <span className={userMeta[msg.user]?.color || "text-gray-800"}><strong>{msg.user}:</strong> {msg.text}</span>
          </div>
        ))}
      </div>

      <div className="grid grid-cols-3 gap-4">
        {[
          { title:"Summary", chars:summaryChars, ref:summaryRef },
          { title:"Translation", chars:translationChars, ref:translationRef },
          { title:"QA", chars:qaChars, ref:qaRef },
        ].map(({title, chars, ref}, idx)=>(
          <div key={idx} className="border p-2 h-72 overflow-y-scroll bg-yellow-50" ref={ref}>
            <h2 className="font-semibold mb-1">{title}</h2>
            <div className="flex flex-wrap items-start">{renderCharsHeatmap(chars)}</div>
          </div>
        ))}
      </div>
    </div>
  );
}
  1. 特徴・ポイント
    文字単位ヒートマップ
    文字生成速度に応じて背景色を変化(速い=緑、遅い=赤)
    文字ごとの速度のばらつきを直感的に把握可能
    ユーザー色分けとアバター表示
    モデルと複数ユーザーの発話を同時に識別
    文字単位フェードイン&自動スクロール
    最新文字が常に見える
    マルチパネル表示
    Summary / Translation / QA に対応
    リアルタイム表示
    部分応答ごとにヒートマップが更新され、速度変化を即座に確認可能
    この UI により、文字単位の生成速度の違いを色で直感的に把握しながら、複数ユーザーやモデルの出力をリアルタイムで観察できます。
    希望であれば、さらに ヒートマップ+折れ線グラフの複合表示 を作り、速度の推移をより詳細に可視化する UI にも拡張できます。
    作りますか?
    ChatGPT の回答は必ずしも正しいとは限りません。重要な情報は確認するようにしてください。

参照

ChatGPT利用の技4つ with ChatGPT
https://qiita.com/kaizen_nagoya/items/4a178bd6a09a4a471a7f

ChatGPTによるQiita記事の改良と補足
https://qiita.com/kaizen_nagoya/items/20604d6dd0702ab95c2f

ChatGPTによるQiita記事の改良と補足(2)
https://qiita.com/kaizen_nagoya/items/996275307ffc8c1243f8

本から始める 面倒なことはChatGPTにやらせよう by からあげ
https://qiita.com/kaizen_nagoya/items/f5ce2a18db54b5610e4b

MCP入門 〜面倒なことはAIエージェントにやらせよう〜 by からあげ を聞きながら
https://qiita.com/kaizen_nagoya/items/54b648c838fae8d57e38

【松尾研LLMコミュニティ】面倒なことはLLMにやらせよう "Beginning LLM"2024年10月17日 AI(9)
https://qiita.com/kaizen_nagoya/items/efdc23fbe67cdae2126e

MCP使い方 with ChatGPT
https://qiita.com/kaizen_nagoya/items/7b2faa278749e4642a36

設計:ChatGPTで特異解か一般解を求める AI(1)
https://qiita.com/kaizen_nagoya/items/4dec580e16a7c84b0ec4

みんなの使い方から学ぶ
https://qiita.com/kaizen_nagoya/items/8e7de492d896df94a36e

AI・機械学習 昨日、今日、明日
https://qiita.com/kaizen_nagoya/items/adb184c8fc7a65ac9756

DNA LLM and genome for survey 2200 papers by name.
https://qiita.com/kaizen_nagoya/items/ce8a28d6072f340a9d59

ChatGPTから学ぶ 3倍褒めて3分の1貶す
https://qiita.com/kaizen_nagoya/items/91e70fd8caa25076718b

AI時代の相棒は、キミに決めた! 推しのAIエージェントをシェアしよう!企画参加まとめへの序章
https://qiita.com/kaizen_nagoya/items/e3fd7557bac97d1b88f2

記事投稿数、いいね数の目標設定して ChatGPTに聞いてみた。
https://qiita.com/kaizen_nagoya/items/019e2ad9c3e45e45854e

AI時代の相棒は、キミに決めた! ChatGPT推し ver.0
https://qiita.com/kaizen_nagoya/items/8068a884fb2e64cd3c4f

無料ChatGPTの処理限界
https://qiita.com/kaizen_nagoya/items/84683aee26d9f4a28af0

by ChatGPT, with ChatGPT and people opinions.
https://qiita.com/kaizen_nagoya/items/57f00f9b48b25bc231e6

ChatGPTの使い方の利点と課題 by ChatGPT
https://qiita.com/kaizen_nagoya/items/0f469e473a6ed0407daa

DataRobot 風 AI agent Robotをあなたに
https://qiita.com/kaizen_nagoya/items/1ca8ca1dedefaf739d16

ChatGPTの賢い使い方
https://qiita.com/kaizen_nagoya/items/3a7e4583553543eeeeb3

ChatGPTの弱みをあなたに
https://qiita.com/kaizen_nagoya/items/3c97c63a22939e203905

ChatGPTで裸の王様
https://qiita.com/kaizen_nagoya/items/8377be38d3a763e93811

設計 仕様 制約 AI利用開発の鍵 by ChatGPT
https://qiita.com/kaizen_nagoya/items/1b9cb2f9d26308d84791

PowerPoint 筋書き(outline), 見直し(review), 見栄え加工 by ChatGPT
https://qiita.com/kaizen_nagoya/items/37bb97fc250fca37295b

逃げ回るChatGPTを追い詰めるには
https://qiita.com/kaizen_nagoya/items/cd60426e626ba05e0377

三人寄れば文珠の知恵は直交する能力前提 by ChatGPT
https://qiita.com/kaizen_nagoya/items/512e5611615e2426bb42

プログラマ3年で社長事業(未成年編) 博士論文支援追加
https://qiita.com/kaizen_nagoya/items/326452b5bf29c144c307

自殺防止手引き原稿 補足 by ChatGPT
https://qiita.com/kaizen_nagoya/items/7e459fa7edf7205c2667

しつけ(discipline) の測定視点 by ChatGPT
https://qiita.com/kaizen_nagoya/items/6b5ce03c545f7bbd8723

技術者は英語を学ぶ必要はない。英単語の羅列でいい=プログラム。
https://qiita.com/kaizen_nagoya/items/4fc3963a21e0339fd1c9

猫中心設計から鳥中心設計へ part2
https://qiita.com/kaizen_nagoya/items/4d7a5a33c85e0b01dc5d

プログラマ必須科目 ChatGPTにも確認
https://qiita.com/kaizen_nagoya/items/7b30d9d119792130facd

なんで音楽とプログラムの関係がうまく書けないか。
https://qiita.com/kaizen_nagoya/items/489a68d360df4b26f87d

AI利用業務手引き
https://qiita.com/kaizen_nagoya/items/f201b292f37dba16e9ef

C言語(C++)が必要な人と必要ない人
https://qiita.com/kaizen_nagoya/items/2afe9e846b55b24cb6f1

C言語(C++)が必要な人、必要ない人 ChatGPT
https://qiita.com/kaizen_nagoya/items/a074cb8cd292d8d94bd4

C言語を習得する3つの方法
https://qiita.com/kaizen_nagoya/items/84cab0888c193bba429b

C言語を習得する3つの方法 ChatGPT
https://qiita.com/kaizen_nagoya/items/4a3518a18fa49f46787f

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?