Files
Yi.Admin/Yi.Ai.Vue3/src/pages/chat/api/index.vue
2026-02-09 23:24:41 +08:00

336 lines
9.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { computed, ref } from 'vue';
import { CopyDocument, Connection, Monitor, ChatLineRound, VideoPlay } from '@element-plus/icons-vue';
import { ElMessage } from 'element-plus';
// API Configuration
const apiList = [
{
id: 'openai',
name: 'OpenAI Completions',
url: '/v1/chat/completions',
method: 'POST',
description: 'OpenAI 经典的对话补全接口。虽然官方正逐步转向新标准,但它仍是目前生态中最通用的标准,绝大多数第三方 AI 工具和库都默认支持此协议。',
icon: ChatLineRound,
requestBody: {
"messages": [
{
"role": "user",
"content": "hi"
}
],
"stream": true,
"model": "gpt-5.2-chat"
}
},
{
id: 'claude',
name: 'Claude Messages',
url: '/v1/messages',
method: 'POST',
description: 'Anthropic 官方统一的消息接口。专为 Claude 系列模型设计,支持复杂的对话交互,完美适配 Claude Code 等新一代开发工具。',
icon: Connection,
requestBody: {
"messages": [
{
"role": "user",
"content": "hi"
}
],
"max_tokens": 32000,
"stream": true,
"model": "claude-opus-4-6"
}
},
{
id: 'openai-resp',
name: 'OpenAI Responses',
url: '/v1/responses',
method: 'POST',
description: 'OpenAI 推出的最新一代统一响应接口。旨在提供更灵活、强大的交互能力,是未来对接 Codex 等高级模型和新特性的首选方式。',
icon: Monitor,
requestBody: {
"model": "gpt-5.3-codex",
"stream": true,
"input": [
{"content":"hi","role":"user"}
]
}
},
{
id: 'gemini',
name: 'Gemini GenerateContent',
url: '/v1beta/models/{model}/streamGenerateContent',
method: 'POST',
description: 'Google Gemini 原生生成接口。专为 Gemini 系列多模态模型打造,支持流式生成,是使用 Gemini CLI 及谷歌生态工具的最佳入口。',
icon: VideoPlay,
requestBody: {
"contents": [
{
"role": "user",
"parts": [
{
"text": "hi"
}
]
}
]
}
},
];
const baseUrl = 'https://yxai.chat';
const activeIndex = ref('0');
const currentApi = computed(() => apiList[Number(activeIndex.value)]);
const fullUrl = computed(() => `${baseUrl}${currentApi.value.url}`);
function handleSelect(key: string) {
activeIndex.value = key;
}
async function copyText(text: string) {
try {
await navigator.clipboard.writeText(text);
ElMessage.success('复制成功');
} catch {
ElMessage.error('复制失败');
}
}
</script>
<template>
<div class="api-page">
<el-container class="h-full">
<!-- Desktop Sidebar -->
<el-aside width="280px" class="api-sidebar hidden-sm-and-down">
<div class="sidebar-header">
<h2 class="text-lg font-bold m-0">API 接口文档</h2>
<p class="text-xs text-gray-500 mt-1">开发者接入指南</p>
</div>
<el-menu
:default-active="activeIndex"
class="api-menu"
@select="handleSelect"
>
<el-menu-item
v-for="(api, index) in apiList"
:key="index"
:index="index.toString()"
>
<el-icon><component :is="api.icon" /></el-icon>
<span>{{ api.name }}</span>
</el-menu-item>
</el-menu>
</el-aside>
<el-main class="api-main">
<!-- Mobile Select -->
<div class="hidden-md-and-up mb-6">
<h2 class="text-lg font-bold mb-4">API 接口文档</h2>
<el-select v-model="activeIndex" placeholder="Select API" class="w-full" @change="handleSelect">
<el-option
v-for="(api, index) in apiList"
:key="index"
:label="api.name"
:value="index.toString()"
/>
</el-select>
</div>
<div class="content-container">
<el-alert
title="接口兼容性重要提示"
type="warning"
show-icon
:closable="false"
class="api-warning-alert"
>
<template #default>
<div class="leading-normal text-sm">
2025 年末起AI 领域接口标准逐渐分化原有的统一接口 <code class="bg-yellow-100 px-1 rounded">/v1/chat/completions</code> 已不再兼容所有模型各厂商推出的新接口差异较大接入第三方工具时请务必根据具体模型选择正确的 API 类型您可前往
<router-link to="/model-library" class="text-primary font-bold hover:underline">模型库</router-link>
查看各模型对应的 API 信息
</div>
</template>
</el-alert>
<div class="mb-6">
<div class="flex items-center gap-3 mb-2">
<el-icon :size="24" class="text-primary"><component :is="currentApi.icon" /></el-icon>
<h1 class="text-xl font-bold m-0">{{ currentApi.name }}</h1>
</div>
<p class="text-gray-500 dark:text-gray-400 leading-relaxed text-sm">{{ currentApi.description }}</p>
</div>
<el-card class="box-card mb-4" shadow="hover">
<template #header>
<div class="card-header flex justify-between items-center py-1">
<span class="font-bold text-sm">接口详情</span>
<el-tag :type="currentApi.method === 'POST' ? 'success' : 'warning'" effect="dark" round size="small">
{{ currentApi.method }}
</el-tag>
</div>
</template>
<div class="api-detail-item">
<div class="label mb-2 text-xs font-medium text-gray-500">请求地址 (Endpoint)</div>
<div class="url-box">
<div class="url-content">
<span class="base-url" title="Base URL">{{ baseUrl }}</span>
<span class="api-path" title="Path">{{ currentApi.url }}</span>
</div>
<div class="url-actions">
<el-tooltip content="复制 Base URL" placement="top">
<el-button link @click="copyText(baseUrl)" size="small">
<span class="text-xs font-mono">Base</span>
</el-button>
</el-tooltip>
<el-divider direction="vertical" />
<el-tooltip content="复制完整地址" placement="top">
<el-button link type="primary" @click="copyText(fullUrl)" size="small">
<el-icon><CopyDocument /></el-icon>
</el-button>
</el-tooltip>
</div>
</div>
</div>
</el-card>
<el-card class="box-card" shadow="hover">
<template #header>
<div class="card-header py-1">
<span class="font-bold text-sm">调用示例 (cURL)</span>
</div>
</template>
<div class="code-block bg-gray-50 p-3 rounded-md border border-gray-200 ">
<pre class="text-xs overflow-x-auto font-mono m-0"><code class="language-bash">curl {{ fullUrl }} \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{{ JSON.stringify(currentApi.requestBody, null, 2) }}'</code></pre>
</div>
</el-card>
</div>
</el-main>
</el-container>
</div>
</template>
<style scoped lang="scss">
.api-page {
height: 100%;
background-color: var(--bg-color-tertiary);
display: flex;
flex-direction: column;
overflow: hidden;
}
.api-sidebar {
background-color: var(--bg-color-primary);
border-right: 1px solid var(--border-color-light);
display: flex;
flex-direction: column;
.sidebar-header {
padding: 16px 20px;
border-bottom: 1px solid var(--border-color-light);
}
.api-menu {
border-right: none;
background-color: transparent;
flex: 1;
overflow-y: auto;
padding: 8px 0;
:deep(.el-menu-item) {
border-radius: 8px;
margin: 2px 10px;
height: 40px;
&.is-active {
background-color: var(--el-color-primary-light-9);
color: var(--el-color-primary);
font-weight: 600;
}
&:hover:not(.is-active) {
background-color: var(--el-fill-color-light);
}
}
}
}
.api-main {
padding: 24px;
overflow-y: auto;
}
.content-container {
max-width: 900px;
margin: 0 auto;
}
.text-primary {
color: var(--el-color-primary);
}
.url-box {
display: flex;
align-items: center;
justify-content: space-between;
background-color: var(--el-fill-color-light);
border: 1px solid var(--el-border-color);
border-radius: 8px;
padding: 12px 16px;
gap: 12px;
.url-content {
font-family: 'Monaco', 'Menlo', 'Consolas', monospace;
font-size: 14px;
word-break: break-all;
line-height: 1.5;
.base-url {
color: var(--el-text-color-secondary);
}
.api-path {
color: var(--el-color-primary);
font-weight: 600;
}
}
.url-actions {
display: flex;
align-items: center;
flex-shrink: 0;
}
}
/* Utility classes for responsiveness if unocss/tailwind not fully available in this scope */
@media (max-width: 768px) {
.hidden-sm-and-down {
display: none !important;
}
.hidden-md-and-up {
display: block !important;
}
.api-main {
padding: 20px;
}
}
@media (min-width: 769px) {
.hidden-md-and-up {
display: none !important;
}
}
.api-warning-alert {
margin-bottom: 20px;
}
</style>