Commit b40dc588 by renyizhao

使用指南

parent cf1b2f23
/**
* 新 NewAPI 模型使用方式 - Profile 配置
*
* 移植自 newapi/web/src/features/pricing/lib/api-example-profiles.ts
* 适配 Vue3 + JavaScript
*
* 负责:
* 1) profile 名称 → 中文标签映射
* 2) profile → 支持的场景列表(basic / image-reference / video-reference)
* 3) profile + endpoint + scenario → 专属请求体
* 4) profile + endpoint + scenario → 专属参数补充
*/
/** 场景类型 */
export const SCENARIO_LABELS = {
basic: '基础示例',
'image-reference': '图片参考',
'video-reference': '视频参考',
}
/** Profile 标签映射(与新 NewAPI 保持一致) */
export const PROFILE_LABELS = {
standard: '基础示例',
'ctyun-h3': 'MiniMax H3',
seedance: 'Seedance',
seedance2: 'Seedance 2.0',
'ctyun-wan': 'Wan / HappyHorse',
'ctyun-wan-image': 'Wan / HappyHorse I2V',
'ctyun-wan3': 'Wan 3.0',
'ctyun-wan-reference': 'Wan 2.7 R2V',
'ctyun-happyhorse-reference': 'HappyHorse R2V',
'ctyun-seedream': 'Seedream',
'ctyun-seedream-pro': 'Seedream 5.0 pro',
'ctyun-message-image': 'Qwen / Wan Image',
'ctyun-message-image-edit': 'Qwen / Wan Image Edit',
'ctyun-qwen-rerank': 'Qwen Rerank',
'ctyun-gte-rerank': 'GTE Rerank',
'ctyun-vl-embedding': 'Qwen VL Embedding',
}
/**
* 合并同 endpoint 的多个 api_examples 条目(按 profile 去重,providers/groups 取并集)
*/
export function mergeAPIExamples(examples, endpoint) {
const merged = new Map()
for (const example of examples || []) {
if (example.endpoint !== endpoint) continue
const previous = merged.get(example.profile)
merged.set(example.profile, {
...example,
providers: [
...new Set([...(previous?.providers ?? []), ...(example.providers ?? [])]),
].sort(),
groups: [
...new Set([...(previous?.groups ?? []), ...(example.groups ?? [])]),
].sort(),
})
}
return [...merged.values()]
}
/**
* 返回某个 profile 支持的场景列表
*/
export function exampleScenarios(profile) {
if (['ctyun-wan-reference'].includes(profile)) {
return ['image-reference', 'video-reference']
}
if (
[
'ctyun-happyhorse-reference',
'ctyun-wan-image',
'ctyun-message-image-edit',
].includes(profile)
) {
return ['image-reference']
}
if (['ctyun-h3', 'seedance2', 'ctyun-wan3'].includes(profile)) {
return ['basic', 'image-reference', 'video-reference']
}
if (
[
'seedance',
'ctyun-seedream',
'ctyun-seedream-pro',
'ctyun-message-image',
].includes(profile)
) {
return ['basic', 'image-reference']
}
return ['basic']
}
/**
* 根据 (model, endpoint, profile, scenario) 生成专属请求体
* 如果不需要专属请求体(例如 standard),返回 undefined,由默认 builder 处理
*
* @returns {object|undefined}
*/
export function profileRequest(model, endpoint, profile, scenario) {
if (!PROFILE_LABELS[profile] || profile === 'standard') return undefined
if (endpoint === 'openai-video') {
const wan = profile.includes('wan') || profile.includes('happyhorse')
const body = {
model,
prompt: 'A calm lake at sunset.',
seconds: '5',
resolution: profile === 'ctyun-h3' ? '768P' : '1080P',
}
const seedance = profile.startsWith('seedance')
if (seedance) {
delete body.resolution
body.metadata = { resolution: '1080p' }
}
if (profile === 'ctyun-h3') body.ratio = '16:9'
if (scenario !== 'basic') {
const video = scenario === 'video-reference'
const url = video
? 'https://example.com/reference.mp4'
: 'https://example.com/reference.jpg'
if (wan) {
if (profile === 'ctyun-wan-image') {
body.image = url
} else {
body.metadata = {
input: {
media: [
{ type: video ? 'reference_video' : 'reference_image', url },
],
},
}
}
} else {
const type = video ? 'video_url' : 'image_url'
body.metadata = {
...(seedance ? { resolution: '1080p' } : {}),
content: [
{
type,
[type]: { url },
role: video ? 'reference_video' : 'first_frame',
},
],
}
}
}
return body
}
if (endpoint === 'image-generation') {
return {
model,
prompt: 'A calm lake at sunset.',
size: profile.includes('seedream') ? '2K' : '1024x1024',
n: 1,
stream: false,
...(scenario === 'image-reference'
? { image: 'https://example.com/reference.jpg' }
: {}),
}
}
if (endpoint === 'jina-rerank') {
return {
model,
query: 'What is the capital of China?',
documents: [
'Beijing is the capital of China.',
'Shanghai is a major city.',
],
top_n: 1,
...(profile === 'ctyun-qwen-rerank'
? { instruct: 'Rank documents by relevance to the query.' }
: { return_documents: true }),
}
}
if (endpoint === 'embeddings' && profile === 'ctyun-vl-embedding') {
return {
model,
input: {
contents: [
{ text: 'A calm lake.' },
{ image: 'https://example.com/reference.jpg' },
],
},
encoding_format: 'float',
dimensions: 1024,
}
}
return undefined
}
/**
* 在基础参数表上叠加 profile 专属参数
*
* @param {Array} base - 基础参数列表(来自 buildSupportedParameters)
* @param {string} endpoint
* @param {string} profile
* @param {string} scenario
* @returns {Array}
*/
export function profileParameters(base, endpoint, profile, scenario) {
if (!PROFILE_LABELS[profile] || profile === 'standard') return base
let params = base.map((p) => ({ ...p }))
if (endpoint === 'openai-video') {
params = params.filter((p) => p.name !== 'size')
params.push({
name: profile.startsWith('seedance')
? 'metadata.resolution'
: 'resolution',
type: 'enum',
enumValues:
profile === 'ctyun-h3'
? ['480P', '768P', '2K']
: ['480P', '720P', '1080P'],
descriptionKey: '输出分辨率',
})
if (profile === 'ctyun-h3') {
params.push({
name: 'ratio',
type: 'string',
descriptionKey: '输出宽高比。H3 帧输入自适应;纯文本输入需显式指定 ratio。',
})
}
if (scenario !== 'basic') {
const wan = profile.includes('wan') || profile.includes('happyhorse')
if (profile === 'ctyun-wan-image') {
params.push({
name: 'image',
type: 'string',
required: true,
descriptionKey: '输入图片 URL 或图片 URL 数组,用于图片编辑。',
})
} else {
params.push({
name: wan ? 'metadata.input.media' : 'metadata.content',
type: 'array',
required: true,
descriptionKey: wan
? '高级视频输入:input.media 包含类型化媒体 URL;不要混用帧输入与参考输入。'
: '高级视频输入:content 包含 text、image_url、video_url 或 audio_url 项,可指定 role。',
})
}
}
}
if (endpoint === 'image-generation') {
if (scenario === 'image-reference') {
params.push({
name: 'image',
type: 'string',
required: true,
descriptionKey: '输入图片 URL 或图片 URL 数组,用于图片编辑。',
})
}
params.push({
name: 'stream',
type: 'boolean',
defaultValue: false,
descriptionKey: '此适配器中 Qwen/万相图像和 Seedream 5.0 pro 必须使用 stream=false。',
})
const size = params.find((p) => p.name === 'size')
if (size) {
size.descriptionKey = profile.includes('seedream')
? '使用模型支持的图像尺寸,例如 Seedream 推荐 2K。'
: '使用 WIDTHxHEIGHT(每边 512~2048);Wan 同时支持 1K、2K、4K。原生参数 size 使用 WIDTH*HEIGHT。'
}
if (profile.includes('message-image')) {
params.push({
name: 'parameters',
type: 'object',
descriptionKey: '原生图像参数覆盖顶层的 size 和 watermark;需要时可在此填 size、n、seed。',
})
}
}
if (endpoint === 'jina-rerank') {
params.push(
profile === 'ctyun-qwen-rerank'
? {
name: 'instruct',
type: 'string',
descriptionKey: '可选的重排序指令',
}
: {
name: 'return_documents',
type: 'boolean',
descriptionKey: '是否在重排结果中包含文档原文',
}
)
}
if (profile === 'ctyun-vl-embedding') {
params = params.map((p) =>
p.name === 'input'
? {
...p,
type: 'object',
descriptionKey:
'使用 input.contents;每项恰好包含一个非空的 text、image 或 video 字符串。',
}
: p
)
const encoding = params.find((p) => p.name === 'encoding_format')
if (encoding) encoding.enumValues = ['float']
}
return params
}
\ No newline at end of file
/**
* 新 NewAPI 模型使用方式 - 统一导出
*
* 在 puhui 客户端复用 NewAPI 前端代码模板与参数定义
* 来源:newapi/web/src/features/pricing/lib/
*/
// 代码模板
export { buildSample } from './code-samples.js'
// Profile 配置
export {
SCENARIO_LABELS,
PROFILE_LABELS,
mergeAPIExamples,
exampleScenarios,
profileRequest,
profileParameters,
} from './example-profiles.js'
// 参数定义
export {
COMMON_CHAT_PARAMS,
REASONING_PARAMS,
EMBEDDING_PARAMS,
IMAGE_PARAMS,
VIDEO_PARAMS,
buildSchemaParams,
buildEndpointParameters,
} from './api-parameters.js'
\ No newline at end of file
...@@ -391,10 +391,10 @@ ...@@ -391,10 +391,10 @@
</transition> </transition>
<!-- 模型详情侧边栏 --> <!-- 模型详情侧边栏 -->
<el-drawer v-model="showModelDrawer" direction="rtl" :size="500" title="模型详情"> <el-drawer v-model="showModelDrawer" direction="rtl" :size="700" title="模型详情">
<template #default> <template #default>
<div v-if="selectedModel" class="model-detail"> <div v-if="selectedModel" class="model-detail">
<!-- 图标和模型名 --> <!-- 顶部:图标 + 模型名(始终显示,不属于 tab) -->
<div class="model-detail-header"> <div class="model-detail-header">
<div class="model-detail-icon"> <div class="model-detail-icon">
<LobeIcon <LobeIcon
...@@ -406,6 +406,9 @@ ...@@ -406,6 +406,9 @@
<div class="model-detail-name">{{ selectedModel.modelName }}</div> <div class="model-detail-name">{{ selectedModel.modelName }}</div>
</div> </div>
<el-tabs v-model="drawerActiveTab" class="model-detail-tabs">
<!-- Tab 1:概览(基本信息/描述/标签/价格) -->
<el-tab-pane label="概览" name="overview">
<!-- 基本信息 --> <!-- 基本信息 -->
<div class="model-detail-section"> <div class="model-detail-section">
<div class="section-title">基本信息</div> <div class="section-title">基本信息</div>
...@@ -521,6 +524,13 @@ ...@@ -521,6 +524,13 @@
<span class="price-value">¥{{ formatModelPrice(selectedModel.audioCompletionPrice) }} / 1M tokens</span> <span class="price-value">¥{{ formatModelPrice(selectedModel.audioCompletionPrice) }} / 1M tokens</span>
</div> </div>
</template> </template>
</el-tab-pane>
<!-- Tab 2:使用方式 -->
<el-tab-pane label="使用方式" name="usage">
<ModelUsageMethod :model="selectedModel" />
</el-tab-pane>
</el-tabs>
</div> </div>
</template> </template>
</el-drawer> </el-drawer>
...@@ -550,6 +560,7 @@ import PDFObject from 'pdfobject'; ...@@ -550,6 +560,7 @@ import PDFObject from 'pdfobject';
import axios from "axios"; import axios from "axios";
import ModelCard from '@/views/console/components/model-card.vue' import ModelCard from '@/views/console/components/model-card.vue'
import LobeIcon from '@/components/LobeIcon/index.vue' import LobeIcon from '@/components/LobeIcon/index.vue'
import ModelUsageMethod from '@/components/ModelUsageMethod.vue'
import { Search } from '@element-plus/icons-vue' import { Search } from '@element-plus/icons-vue'
const route = useRoute() const route = useRoute()
...@@ -579,9 +590,11 @@ const qrCode = ref({ ...@@ -579,9 +590,11 @@ const qrCode = ref({
// 模型详情侧边栏 // 模型详情侧边栏
const showModelDrawer = ref(false) const showModelDrawer = ref(false)
const selectedModel = ref(null) const selectedModel = ref(null)
const drawerActiveTab = ref('overview')
function openModelDetail(model) { function openModelDetail(model) {
selectedModel.value = model selectedModel.value = model
drawerActiveTab.value = 'overview' // 每次打开重置到「概览」Tab
showModelDrawer.value = true showModelDrawer.value = true
} }
...@@ -1717,7 +1730,7 @@ onMounted(() => { ...@@ -1717,7 +1730,7 @@ onMounted(() => {
border: 1px solid #ebeef5; border: 1px solid #ebeef5;
border-radius: 8px; border-radius: 8px;
/* 基础列(档位,输入,输出)+扩展价格列(最多7列),每列最小100px */ /* 基础列(档位,输入,输出)+扩展价格列(最多7列),每列最小100px */
min-width: 1000px; // min-width: 1000px;
} }
.tier-header { .tier-header {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment