iT邦幫忙

2025 iThome 鐵人賽

DAY 26
0
生成式 AI

30天用React打造AI工具箱系列 第 26

30天用React打造AI工具箱Day26

  • 分享至 

  • xImage
  •  

30天用React打造AI工具箱Day26

今天我們要教大家如何讓之前做的ChatBox工具能夠連上OpenAI API。
不論你手上的key是舊的sk-xxxxx或新的sk-proj-xxxxx,都能順利運作。
我們會同時建立:

  • 前端(React Chatbox)
  • 後端(Node.js Proxy Server)
  • .env環境設定

.env設定

# .env
OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxx
OPENAI_PROJECT_ID=proj_xxxxxxxxxxxxxxxxxxxxx   # 若你是 project key 才需要
PORT=3001

然後建立一個server.js

// server.js
import express from "express"
import fetch from "node-fetch"
import cors from "cors"
import dotenv from "dotenv"

dotenv.config()
const app = express()
app.use(cors())
app.use(express.json())

app.get("/", (req, res) => {
  res.send("✅ ChatGPT Proxy Server 正常運作中!")
})

app.post("/api/chat", async (req, res) => {
  try {
    const apiKey = process.env.OPENAI_API_KEY
    const projectId = process.env.OPENAI_PROJECT_ID

    if (!apiKey) {
      return res.status(400).json({ error: "❌ 缺少 OPENAI_API_KEY,請確認 .env 是否正確" })
    }

    // 根據 key 類型選擇正確 header
    const headers = {
      "Content-Type": "application/json",
      Authorization: `Bearer ${apiKey}`,
    }

    // 若為 project key(sk-proj 開頭),必須額外加上 project id
    if (apiKey.startsWith("sk-proj") && projectId) {
      headers["OpenAI-Project"] = projectId
    }

    const response = await fetch("https://api.openai.com/v1/chat/completions", {
      method: "POST",
      headers,
      body: JSON.stringify({
        model: "gpt-4o-mini",
        messages: req.body.messages,
      }),
    })

    const data = await response.json()
    res.status(response.status).json(data)
  } catch (error) {
    console.error("❌ Proxy error:", error)
    res.status(500).json({ error: "Proxy server error" })
  }
})

const PORT = process.env.PORT || 3001
app.listen(PORT, () => console.log(`🚀 Server running on http://localhost:${PORT}`))

讓server.js判斷你是用哪一種的api key

最後改動一下原本的chatbox

// src/pages/Chatbox.jsx
import { useState } from "react"

export default function Chatbox() {
  const [input, setInput] = useState("")
  const [messages, setMessages] = useState([
    { role: "assistant", content: "你好!我是 ChatGPT 助手 😊" },
  ])
  const [loading, setLoading] = useState(false)
  const [error, setError] = useState("")

  const handleSend = async () => {
    if (!input.trim()) return
    const newMsg = { role: "user", content: input }
    setMessages((prev) => [...prev, newMsg])
    setInput("")
    setError("")
    setLoading(true)

    try {
      const res = await fetch("http://localhost:3001/api/chat", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ messages: [...messages, newMsg] }),
      })

      if (!res.ok) {
        const err = await res.json()
        throw new Error(err.error?.message || `API 錯誤:${res.status}`)
      }

      const data = await res.json()
      const reply = data.choices?.[0]?.message?.content || "⚠️ 沒有回應內容"
      setMessages((prev) => [...prev, { role: "assistant", content: reply }])
    } catch (err) {
      setError(`⚠️ 聊天失敗:${err.message}`)
      console.error(err)
    } finally {
      setLoading(false)
    }
  }

  return (
    <div className="space-y-4">
      <h2 className="text-xl font-bold">💬 ChatGPT 聊天工具</h2>
      <div className="bg-white p-4 rounded shadow space-y-2">
        {messages.map((m, i) => (
          <div
            key={i}
            className={`p-2 rounded ${
              m.role === "user"
                ? "bg-blue-100 text-right"
                : "bg-gray-100 text-left"
            }`}
          >
            {m.content}
          </div>
        ))}

        {error && <p className="text-red-500">{error}</p>}

        <div className="flex space-x-2 mt-3">
          <input
            value={input}
            onChange={(e) => setInput(e.target.value)}
            placeholder="輸入訊息..."
            className="flex-1 border px-3 py-2 rounded"
          />
          <button
            onClick={handleSend}
            disabled={loading}
            className="bg-green-600 text-white px-4 py-2 rounded hover:bg-green-700 disabled:opacity-50"
          >
            {loading ? "發送中..." : "發送"}
          </button>
        </div>
      </div>
    </div>
  )
}

今天我們讓ChatBox成功接上了OpenAI API。
為了支援兩種不同的金鑰(user key與project key),
我們在server.js中做了自動判斷:
若金鑰開頭是sk-proj→ 自動加上OpenAI-Project header。
若是一般的sk-→正常通過。
這樣無論是舊版或新版金鑰,都能讓你的chatbox變成真正能聊天的chatbox!


上一篇
30天用React打造AI工具箱 Day25
下一篇
30天用React打造AI工具箱 Day27
系列文
30天用React打造AI工具箱27
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言