Skip to content

JavaScript 示例

以下代码适用于支持原生 fetch 的 Node.js 20 或更高版本。API Key 从 TOPAPIS_API_KEY 读取。

基础请求函数

js
const apiKey = process.env.TOPAPIS_API_KEY

if (!apiKey) {
  throw new Error('TOPAPIS_API_KEY is not configured')
}

const baseUrl = 'https://api.topapis.cn/v1'

async function requestJson(path, options = {}) {
  const response = await fetch(`${baseUrl}${path}`, {
    ...options,
    headers: {
      Authorization: `Bearer ${apiKey}`,
      'Content-Type': 'application/json',
      ...options.headers,
    },
    signal: AbortSignal.timeout(120_000),
  })

  if (!response.ok) {
    const body = await response.text()
    throw new Error(`TopAPIs returned HTTP ${response.status}: ${body}`)
  }

  return response.json()
}

错误对象可能包含服务端响应体。写日志前应移除用户内容和其他敏感字段。

查询模型并对话

js
const models = await requestJson('/models')
const model = models.data[0]?.id

if (!model) {
  throw new Error('No model is available to this API key')
}

const completion = await requestJson('/chat/completions', {
  method: 'POST',
  body: JSON.stringify({
    model,
    messages: [
      { role: 'user', content: '简要说明如何保护 API Key。' },
    ],
  }),
})

console.log(completion.choices[0].message.content)

流式对话

SSE 数据可能跨网络分块,不能把每个 Uint8Array 直接当作完整事件。以下示例按空行分隔事件,并保留未完成的尾部数据。

js
const response = await fetch(`${baseUrl}/chat/completions`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    model,
    messages: [{ role: 'user', content: '流式返回三条安全建议。' }],
    stream: true,
  }),
  signal: AbortSignal.timeout(120_000),
})

if (!response.ok) {
  throw new Error(`Streaming request failed with HTTP ${response.status}`)
}

if (!response.body) {
  throw new Error('Streaming response has no body')
}

const decoder = new TextDecoder()
let buffer = ''
let streamComplete = false

function writeSseEvent(event) {
  const dataLine = event
    .split(/\r?\n/)
    .find((line) => line.startsWith('data:'))
  if (!dataLine) return false

  const data = dataLine.slice(5).trim()
  if (data === '[DONE]') return true

  const payload = JSON.parse(data)
  process.stdout.write(payload.choices[0]?.delta?.content ?? '')
  return false
}

streamLoop:
for await (const chunk of response.body) {
  buffer += decoder.decode(chunk, { stream: true })
  const events = buffer.split(/\r?\n\r?\n/)
  buffer = events.pop() ?? ''

  for (const event of events) {
    if (writeSseEvent(event)) {
      streamComplete = true
      break streamLoop
    }
  }
}

buffer += decoder.decode()
if (!streamComplete && buffer.trim()) {
  writeSseEvent(buffer)
}

图片生成

js
const image = await requestJson('/images/generations', {
  method: 'POST',
  body: JSON.stringify({
    model: 'gpt-image-2',
    prompt: '清晨的现代城市公园,写实摄影,自然光',
    size: '1024x1024',
    quality: 'high',
    n: 1,
  }),
})

const item = image.data[0]

if (item.url) {
  console.log('Download temporary URL immediately:', item.url)
} else if (item.b64_json) {
  console.log('Received Base64 image data')
} else {
  throw new Error('Unexpected image response')
}

模型标识必须来自当前 API Key 的 /v1/models 响应。

TopAPIs API 文档