API 文档

AI耐思播客开放 API,供第三方应用开发者集成播客生成与语音对话能力。
基础地址:https://dev.nice-ai.cn/podcast

服务架构

本文档接口由六个独立服务端提供,职责严格划分。请开发者根据业务需求选择对应服务的接口。

服务协议对外基础地址职责
① 播客服务 HTTPS + WSS https://dev.nice-ai.cn/podcast 播客内容生成、用户资料读写、音色管理、AI角色管理、订阅支付、后台管理等。包含文档中除"语音对话接口"和"S2S 语音群聊/对讲"以外的所有接口
② S2S 语音服务 WSS + HTTPS wss://dev.nice-ai.cn/s2s/ 端到端语音对话(ASR→LLM→TTS 全链路流式,支持 Opus 上行(opus_raw 裸流)、说话人声纹识别(enable_speaker_identify 默认开启)、板型配置下发(meeting/anan/sisi))、派群聊 WS、派友对讲 WS。语音对话为 WebSocket,另提供明星→AI→音色 查询 REST 接口(/s2s/api/*)、AI 地理位置读写、AI 上/下线、AI/用户资料读写、用户搜索与周边查找接口与 AI 动态发布/获取/收藏/评论接口(/s2s/api/posts,支持同城/收藏/搜索过滤,见本页「语音对话接口」下方)
③ MDN 语音编排服务 WSS + HTTPS wss://dev.nice-ai.cn/mdn/ws
https://dev.nice-ai.cn/mdn_http
完全独立的语音对话编排服务,直连 ASR/LLM/TTS 原始服务(不经过 S2S)。提供全链路双向自然聊天(插话不重置+续接+记忆引擎)、原子化接口(asr_only/tts_only)、统一接口(request 支持全链路或任意环节组合)。支持 opus_raw 压缩上行(ESP32 低功耗设备,device_id 设备鉴权 + SAUD 下行帧,见 ESP32 接入小节)。内置人物本体五库大脑(A库图谱/B库画像/C库场景/D库风格/E库事件),由四慧引擎(bge-large-zh-v1.5 语义向量 + 慧问五库大脑)驱动。API Key 查 s2s_api_keys 表(与 S2S 共用表,但各应用使用各自的 Key)
④ LLM API Gateway HTTPS https://dev.nice-ai.cn/llm/v1 OpenAI 兼容的 LLM HTTP API 服务。提供对话补全(/v1/chat/completions,免思考)和代码生成(/v1/code/chat/completions,默认开启思考模式)两个端点。支持流式/非流式、多模态图片输入、Tool Call。使用独立 API Key(sk-nice-xxx,在管理后台创建)
⑤ QUIC 语音网关(ai_quic) QUIC / WebTransport(HTTP/3) https://dev.nice-ai.cn(UDP 443) 全新 QUIC 低延时语音对话网关(见下方接口)。UDP 443 直绑不经 nginx,复用 dev.nice-ai.cn 域名证书;直连 ASR/LLM/TTS 原始服务,全链路内存流式不落盘。控制消息走 WebTransport 双向流(JSON 行),上行/下行音频各走独立单向流(裸 PCM),无队头阻塞,弱网下比 WSS 更低延时
⑥ 星来啦生日祝福(ai_birth) HTTPS https://dev.nice-ai.cn/birth 明星场景生日祝福 H5(顾客下单 → 明星AI语音祝福 + 领唱《生日快乐》歌)。对外提供生日祝福歌合成 API(/birth/api/external/birthday_song,按 AI 分身 id 合成领唱曲,见本页「外部应用接口」),复用 s2s_api_keys 鉴权
⚠️ 重要区分:
  • 播客服务:负责所有 REST API(含 /api/s2s/key/* 计费管理接口、/api/pai/* 派 REST 接口、/api/walkie/* 对讲 REST 接口)+ 播客生成 WSS(/ws)
  • S2S 服务:提供 3 个 WebSocket 接口(/s2s/ws/、/s2s/pai/{pai_id}、/s2s/walkie),另有明星→AI→音色 查询 REST 接口(/s2s/api/stars、/s2s/api/stars/{id}/ais、/s2s/api/ais/{id})、AI 地理位置读写 REST 接口(/s2s/api/ais/{ai_id}/location GET 获取 / POST 写入、/s2s/api/ais/{ai_id}/online AI上线(含位置)、/s2s/api/ais/{ai_id}/offline AI下线、/s2s/api/ais/{ai_id}/profile GET 获取 / PUT 设置 AI 资料(与思思APP同口径)、/s2s/api/ais/{ai_id}/nearby 以指定AI为中心按距离查找周边AI、/s2s/api/users/{user_id}/profile GET 获取 / PUT 设置用户资料(xd_users 全字段)、/s2s/api/users/search 按性别/年龄/职业/收入/星座/生肖搜索用户(附各用户AI列表))与 AI 动态发布/获取/收藏/评论 REST 接口(/s2s/api/posts POST 发布 / GET 列表(全量/按AI/同城/收藏/搜索) / /s2s/api/posts/{post_id}/favorite 收藏 / /s2s/api/posts/{post_id}/comments 评论发表与列表 / .../comments/{comment_id} 删除)。语音对话鉴权与计费在服务端内部完成,REST 接口使用 X-Api-Key 头或 ?api_key= 参数鉴权
  • MDN 服务:完全独立的语音对话编排服务(不依赖 S2S),直连 ASR/LLM/TTS 原始服务。WSS 接口(/mdn/ws)提供全链路双向自然聊天+原子化编排;HTTP 接口(/mdn_http)提供记忆引擎 REST API(用户注册/AI分身/记忆读写/事件查询/流式对话/微信导入)
  • 派/对讲双轨:派群聊和对讲功能同时涉及两个服务——REST 接口(创建派、查消息、拉音频等)在播客服务,WS 接口(实时通知、AI 主动回复)在 S2S 服务
设计原则:全链路流式(ASR→LLM→TTS 边生成边传输边播放)、不落盘(音频全程内存调度)。
LLM 后端:本地 LLM 优先(Qwen3.6 MOE,支持图片/文本多模态),本地未启动时自动路由到 LLM Gateway(含图片时先调 MiniCPM-V 视觉模型识别,再推送第三方 LLM 生成回复)。含图片和纯文本消息统一走此路由。
LVM 视觉感知:LVM(8116) 独立模型服务(MiniCPM-o-4.5 / MiniCPM-V-4.6 面板切换)。除静态识图外,支持真实流式视频感知——客户端推送摄像头帧+麦克风音频流,AI 边看边听边说、随时打断(详见「视觉感知服务」章节)。

快速开始

AI耐思播客提供 RESTful API 和 WebSocket 接口,支持播客生成、音色管理、音频下载等功能。所有接口均通过 HTTPS 访问,播客生成使用 WebSocket 流式协议,其余为 REST 接口。

重要提示:播客生成仅支持流式模式(WebSocket),不支持同步请求-响应模式。生成过程中会实时推送剧本段落和音频数据,客户端可边接收边播放。

典型调用流程

①
获取手机验证码 → 调用 POST /api/auth/send_code,用手机号注册/登录
②
登录获取 user_id → 调用 POST /api/auth/login,获得 user_id
③
获取 AI 角色列表 → 调用 GET /api/ai-characters,获取可用 AI 角色(含 voice_id 音色、role_config 身份描述,音色由 AI 关联)
④
(可选)创建 AI 角色 → 调用 POST /api/ai-characters,创建绑定音色的 AI 角色
⑤
生成播客 → 连接 wss://dev.nice-ai.cn/podcast/ws,必传 ai_character_ids(音色由 AI 自动关联,无需传 voices),流式接收音频数据
⑥
下载音频 → 调用 GET /audio/{filename},下载生成的播客音频文件

开发者实战指南

以下从第三方开发者视角,详细介绍每个业务场景的完整调用流程、所需表单和字段说明。所有示例均使用 https://dev.nice-ai.cn/podcast 作为基础地址。

指南 用户登录与资料编辑 完整流程

流程概览

⚠️ 重要区分:修改用户资料不需要验证码!验证码仅用于首次登录/注册。已登录用户直接带 token 调用下方接口即可。

场景A:首次登录/注册(仅首次使用时执行一次)

①
发送验证码 → POST /api/auth/send_code,传入手机号,获取短信验证码
②
登录 → POST /api/auth/login,传入手机号+验证码,获得 user_id + token(有效期7天)

场景B:已登录用户修改资料(token 有效期内随时可调)

①
获取资料编辑标签 → GET /api/user/profile-tags,获取兴趣偏好和职业/身份标签列表(公开接口,无需鉴权)
②
保存用户资料 → POST /api/user/profile,必须携带 Authorization: Bearer {token} + X-User-Id Header,传入 user_info 对象

资料编辑表单字段

字段类型说明示例
usernamestring用户昵称/姓名"小明"
avatar_urlstring头像URL(可上传到第三方存储后填入)"https://cdn.example.com/avatar.jpg"
genderstring性别:male / female"male"
ageint年龄28
citystring所在城市"深圳"
professionstring职业/身份(自由文本)"软件工程师"
interestsstring兴趣偏好(逗号分隔,也可从 profile-tags 接口获取标签列表)"科技,旅行,摄影"
birthdaystring生日(YYYY-MM-DD)"1995-07-15"
life_statusstring人生状态描述(逗号分隔)"职场白领,数码爱好者"
自动保存机制:建议在表单字段编辑完成后自动调用 POST /api/user/profile 保存,无需点击「保存」按钮。只传变更的字段即可,不传的字段保持原值。

保存资料请求示例

{
  "user_id": 12345,
  "user_info": {
    "username": "小明",
    "gender": "male",
    "age": 28,
    "city": "深圳",
    "profession": "软件工程师",
    "interests": "科技,旅行,摄影"
  }
}

资料标签接口

GET /api/user/profile-tags — 无需参数,返回两组标签供表单使用:

{
  "interests": [
    {"id": 1198, "name": "科技"},
    {"id": 1199, "name": "旅行"}
  ],
  "professions": [
    {"id": 2, "name": "学生"},
    {"id": 3, "name": "职场人士"}
  ]
}
UI 建议:interests 支持多选(标签点选),professions 建议单选。使用标签点选方式(非下拉框),选中标签高亮显示。
指南 音色录制与上传 完整流程

三步音色创建流程

①
上传音频 → POST /api/user/voices/upload(multipart)→ 返回 id(voice_id)和 audio_file 文件名
②
ASR识别 → POST /api/user/voices/recognize(multipart)→ 返回 text(识别文本,仅供参考展示,不可编辑)
③
保存确认 → POST /api/user/voices/save(JSON)→ 后端提取 192 维声纹并注册(transcript 由前端识别后传入直接落库),返回 success
三步必须严格按顺序执行!步骤①和②上传同一份音频文件。步骤③传入步骤①返回的 id。transcript 由后端在保存时重新调用 ASR 识别音频获取,客户端传入的 transcript 会被忽略——参考文本必须与音频严格一一对应,编辑文本会导致合成乱音。

录音要求

要求说明
音频时长≥ 2 秒
文本字数10~50 字(ASR 识别结果,由后端自动识别,不接受手动编辑)
噪音要求SNR ≥ 10dB(安静环境录制)
支持格式wav / mp3 / m4a / ogg / flac / pcm / webm,最大 10MB
音色数量受订阅套餐 voice_quota 配额限制(free 套餐默认3个),新增模式扣减配额,编辑模式不扣

步骤① 上传音频表单

参数类型必填说明
user_idint必填用户ID
voice_namestring必填音色名称,如"我的声音"(禁止空字符串)
genderstring必填male / female(禁止空字符串)
descriptionstring必填音色描述(禁止空字符串)
voice_idint必填必填,严格区分两种模式:0=新增模式(校验订阅配额 voice_quota,扣减后创建新记录);>0=编辑模式(更新已有音色ID的音频和元信息,不扣配额)
filefile必填音频文件
音色数据提交规范:第三方应用上传音色时,user_id、voice_name、gender、description、voice_id、file 六项全部必填,任一缺失或空字符串返回400错误,音色不入库。voice_id 必须明确传入:新增传 0,编辑传已有音色ID——不传 voice_id 会被拒绝,防止应用端绕过配额限制不断以新增模式上传。

步骤③ 保存表单

参数类型必填说明
user_idint必填用户ID
idint必填步骤①返回的 voice_id
voice_namestring必填音色名称
emojistring必填音色表情(如 🎙️ / 👩 / 👨)
genderstring必填male / female
descriptionstring必填音色描述
transcriptstring必填前端调 recognize 接口识别后传入的文本,直接落库(后端不重复识别)。为空返回400。参考文本必须与音频严格一一对应,编辑文本会导致 TTS 合成乱音
transcript 由前端识别后传入:前端(VoiceEditor/H5/APP)先调 recognize 接口完成 ASR 识别,保存时将识别文本作为 transcript 传入直接落库(后端不重复识别,避免双重耗时)。transcript 为空返回400错误("缺少ASR识别文本")。voice_name、emoji、gender、description 四项为必填项,任一缺失返回400错误。
声纹提取与注册:步骤③保存时,若音色 embedding 为空,后端自动从音频提取 192 维 CAM++ 声纹向量:① 写入 xd_voices.embedding(音色级声纹);② 注册到 xd_speaker(用户级声纹,S2S enable_speaker_identify 说话人识别的数据源,识别未命中时 s2s 自动刷新缓存,无需重启)。提取失败返回400错误("声纹提取失败,请重新录制清晰的音频"),音色不保存。transcript 由前端(VoiceEditor/H5/APP)完成 ASR 识别后随请求传入,后端不做重复识别。

完整调用示例(JavaScript)

// 步骤①:上传音频(voice_id=0 表示新增模式)
const form1 = new FormData();
form1.append('user_id', '12345');
form1.append('voice_name', '我的声音');
form1.append('gender', 'male');
form1.append('description', '我的专属音色');
form1.append('voice_id', '0');  // 必填:0=新增,>0=编辑已有音色
form1.append('file', audioBlob, 'recording.wav');

const res1 = await fetch('/api/user/voices/upload', { method: 'POST', body: form1 });
const { id: voiceId } = await res1.json();  // voiceId = 5

// 步骤②:ASR识别(同一份音频,仅供前端展示,不可编辑)
const form2 = new FormData();
form2.append('file', audioBlob, 'recording.wav');
const res2 = await fetch('/api/user/voices/recognize', { method: 'POST', body: form2 });
const { text } = await res2.json();  // text = "今天天气真好,我们一起去公园散步吧"(仅展示)

// 步骤③:保存确认(传入步骤②识别的 transcript + 元信息;后端自动提取声纹并注册)
const res3 = await fetch('/api/user/voices/save', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    user_id: 12345,
    id: voiceId,
    voice_name: '我的声音',
    emoji: '🎙️',
    gender: 'male',
    description: '我的专属音色'
  })
});
const result = await res3.json();
if (result.success) {
  console.log('音色创建成功,声纹已录入');
  console.log('后端ASR识别的transcript:', result.transcript);
}
指南 音频编辑器核心功能 功能说明

音频编辑器是音色管理中的核心功能,用于在音色上传后对音频进行编辑优化。以下为编辑器提供的核心能力及对应的 API 调用。

核心功能一览

功能API说明
ASR文本展示—(前端展示)在步骤②获得识别文本后,仅供用户查看,不可编辑。步骤③保存时后端会重新调用 ASR 识别音频获取 transcript,确保参考文本与音频严格一一对应。编辑文本会导致 TTS 合成乱音。
重新上传音频POST /api/user/voices/upload(voice_id=已有ID)编辑模式下传入已有 voice_id,更新音色的音频文件、名称、性别和描述。之后需重新执行步骤②③。
音频降噪POST /api/user/voices/denoise上传音频进行降噪处理,返回降噪后的WAV音频。适用于录音环境不理想时使用。
音色详情GET /api/user/voices/{voice_id}获取音色的完整信息,包括 audio_file、transcript、emoji、创建时间等。
音色预览GET /api/user/voices/audio?file=xxx下载/播放音色音频文件,用于试听。
音色删除DELETE /api/user/voices/{voice_id}?user_id=xxx软删除音色(status=0),不再出现在列表中。

编辑模式(重新上传)流程

①
重新上传 → POST /api/user/voices/upload,传入 voice_id=已有ID + 新音频文件 → 更新音色记录
②
重新识别 → POST /api/user/voices/recognize,上传新音频 → 获得新的识别文本
③
重新保存 → POST /api/user/voices/save,传入 voice_id + 新 transcript → 更新元信息+声纹(embedding 为空时重新提取并注册)
编辑器实现建议:在APP中设计一个音频编辑页面,包含以下区域:
① 波形显示区 — 展示音频波形,支持播放/暂停
② 文本编辑区 — 显示ASR识别文本,支持用户手动修改
③ 操作按钮区 — 重新录制、降噪处理、试听预览、保存确认
④ 音色信息区 — 编辑音色名称、性别、描述等元信息
指南 创建AI角色 表单与流程

创建AI角色所需表单字段

字段类型必填说明示例
user_idint必填创建者用户ID12345
namestring必填AI角色名称(如"旅行达人小飞")"旅行达人小飞"
voice_idint必填关联的音色ID(数字字符串)"8"(如意)或 "5"(用户音色)
podcast_typestring必填专攻播客场景 code(可从 /api/podcast-types 获取列表)"travel"
avatar_urlstring必填头像URL"https://cdn.example.com/ai-avatar.png"
identityint[]必填主播身份标签ID数组(从 /api/tags?podcast_type=xxx 获取)[1, 3]
personalityint[]必填擅长领域标签ID数组[2, 5]
bioint[]必填粉丝受众标签ID数组[1, 4]
user_titlestring必填对用户的称呼"朋友" / "主人"
role_configstring必填AI身份描述(最长1000字),生成播客时注入场景规则"30岁知性女性,曾是旅行杂志编辑..."
ai_type_idint必填AI分类ID(1=user用户专属AI,12=预置角色/虚拟人/二次元角色,3=star明星AI)1
is_publishedbool可选是否立即发布到广场,默认 false(行为控制字段,非内容字段)true
AI角色数据提交规范:第三方应用创建AI角色时,name、voice_id、podcast_type、avatar_url、identity、personality、bio、user_title、role_config、ai_type_id 十项为内容必填项,任一缺失返回400错误,AI不入库。is_published 仅为发布行为控制,默认 false。
AI分类说明:系统通过 xd_ai_type 表统一管理AI分类,三类AI互斥:
① user(ai_type_id=1)— 用户专属AI,仅创建者本人可通过 user_id 获取与使用;
② system(ai_type_id=12)— 预置角色/虚拟人/二次元角色,所有用户可见可用;
③ star(ai_type_id=3)— 明星AI,公开发布到广场供所有用户使用。
第三方应用提交时必须明确指定 ai_type_id,未指定分类的AI不入库。

创建流程

①
获取音色列表 → GET /api/voices(预置音色)+ GET /api/user/voices?user_id=xxx(用户音色),供用户选择音色
②
获取播客场景 → GET /api/podcast-types,获取可选的播客场景列表(code/icon/color)
③
获取标签 → GET /api/tags?podcast_type=xxx(仅当选择了场景后),获取3类定向维度标签:主播身份/擅长领域/粉丝受众
④
填写表单 → 填写名称、选择音色、选择场景、选择标签(标签点选,每类可多选)、填写身份描述
⑤
提交创建 → POST /api/ai-characters,返回 id

标签选择说明

三类标签的含义:

标签类别字段名数据表含义
主播身份identityai_tag_identityAI在播客中扮演的角色身份,如"专业导游""美食评论家""科技博主"
擅长领域及创作偏好personalityai_tag_expertiseAI擅长的内容领域和创作风格,如"历史人文""科技前沿""美食探店"
粉丝受众/播客收听对象bioai_tag_audience播客的目标听众群体,如"年轻人""职场人士""学生群体"
标签获取方式:标签列表与播客场景绑定。先选择场景,再通过 GET /api/tags?podcast_type=travel 获取该场景下的可用标签。标签ID以数组形式传入 identity/personality/bio 字段。

创建请求示例

{
  "user_id": 12345,
  "name": "旅行达人小飞",
  "voice_id": "8",
  "podcast_type": "travel",
  "identity": [1, 3],
  "personality": [2, 5],
  "bio": [1, 4],
  "user_title": "旅伴",
  "role_config": "25岁户外旅行达人,走过全国30多个省份,擅长实用攻略与风土人情讲解,说话风格轻松活泼",
  "is_published": true
}

AI角色管理

操作API说明
列表GET /api/ai-characters?user_id=xxx获取我的AI角色列表
详情GET /api/ai-characters/{ai_id}获取单个AI角色详情
更新PUT /api/ai-characters/{ai_id}更新AI角色信息(部分字段即可)
删除DELETE /api/ai-characters/{ai_id}?user_id=xxx软删除AI角色
发布POST /api/ai-characters/{ai_id}/publish?user_id=xxx发布到AI广场
取消发布POST /api/ai-characters/{ai_id}/unpublish?user_id=xxx从广场下架
收藏POST /api/ai-characters/{ai_id}/favorite?user_id=xxx收藏AI角色
取消收藏DELETE /api/ai-characters/{ai_id}/favorite?user_id=xxx取消收藏
音色预览POST /api/ai-characters/{ai_id}/preview-voice返回MP3音频,预览AI角色音色
指南 标签系统使用 标签获取与录入

系统中的标签分为两大类:用户资料标签(用于个人资料编辑)和 AI定向标签(用于AI角色创建和播客生成)。两类标签分别存储在不同的表中,通过不同的接口获取。

标签体系总览

标签类型接口数据表用途
用户资料标签GET /api/tags(不传 podcast_type)user_tag_def个人资料编辑页,如"人生状态""兴趣偏好"等
用户兴趣标签GET /api/user/profile-tagsuser_tag_def + ai_tag_audience个人资料编辑页的兴趣偏好和职业选择
AI主播身份标签GET /api/tags?podcast_type=xxxai_tag_identityAI角色创建/编辑,选择AI的主播身份
AI擅长领域标签GET /api/tags?podcast_type=xxxai_tag_expertiseAI角色创建/编辑,选择AI的擅长领域
AI粉丝受众标签GET /api/tags?podcast_type=xxxai_tag_audienceAI角色创建/编辑,选择AI的目标听众
AI标签枚举(全部)GET /api/ai-tag-enums三张枚举表管理员后台,查看所有枚举值
播客场景标签GET /api/podcast-type-tags?podcast_type=xxxpodcast_type_tag关联表获取某场景下已选配的3类标签(扁平列表)
AI已选标签维度GET /api/ai-characters/{ai_id}/tag-dimsAI角色三个标签字段获取AI角色已配置的标签,按3类维度分组

UI 实现建议

标签选择UI规范(遵循项目设计原则):
① 使用标签点选方式,不要使用下拉选择框
② 选中标签高亮显示(如紫色背景+白色文字),未选中为灰色描边
③ 每类标签支持多选(identity/personality/bio 均可多选)
④ 用户资料标签为树形结构(分类 → 标签项),UI上可分组展示
⑤ 编辑完自动保存,无需点击保存按钮

标签获取流程

用户资料
获取标签 → GET /api/tags(不传参数)→ 返回树形结构,含"人生状态""兴趣偏好"等分类及其子标签
AI角色创建
获取标签 → GET /api/tags?podcast_type=travel → 返回3类定向维度标签树,用于AI角色创建表单
播客定向
获取AI已选标签 → GET /api/ai-characters/{ai_id}/tag-dims → 创建播客时展示该AI的标签维度,供用户选择本次播客的定向方向
指南 创建播客 表单与流程

播客生成通过 WebSocket 流式协议进行(不支持 HTTP 同步请求)。以下为创建播客的完整表单字段和流程说明。

创建播客表单字段

字段类型必填说明示例
promptstring必填播客主题/提示词,描述你想生成的播客内容"深圳的早餐文化,尤其是肠粉"
ai_character_idsint[]必填AI角色ID列表,1-4个。第一个为主角,决定播客风格定位。音色由AI自动关联[6, 7]
user_idint可选用户ID,用于配额校验和播客入库。不传则不消耗配额、不保存12345
word_countint可选目标字数,默认800。可选:300/500/800/1200/1500/2000/3000/5000800
audio_formatstring可选音频格式:mp3 / wav / pcm,默认 mp3"mp3"
speedfloat可选播报速度倍率,默认 1.01.0
publicbool可选是否发布到播客广场,默认 falsetrue
podcast_tag_dimsobject[]可选播客定向维度标签,格式 [{"sub_id": 维度ID, "tag_id": 标签ID}, ...]。用于指定本次播客的内容方向[{"sub_id":1,"tag_id":5}]
system_contextstring可选系统上下文信息(用户画像、时间地点、照片描述等),注入到LLM系统提示词中"用户是35岁男士,在深圳旅游..."
purposestring可选播客用途:self(自用)/ share(分享),默认 self"self"
template_idstring可选播客模板ID,由预置角色角色自动匹配,通常无需手动设置—
return_jsondatabool可选是否返回元数据(标题/简介),默认 truetrue

创建流程

①
获取AI角色 → GET /api/ai-characters?user_id=xxx(我的AI)+ GET /api/ai-characters/system(系统AI,全部官方AI),供用户选择1-4个AI角色
②
(可选)获取演播风格 → GET /api/play-types,获取演播风格列表(对聊/相声/访谈/新闻播报等),不选则由AI自动判断
③
(可选)获取标签维度 → GET /api/ai-characters/{ai_id}/tag-dims,获取AI的标签维度,供用户选择本次播客的定向方向
④
填写表单 → 输入主题、选择AI角色(1-4个)、选择字数、选择是否发布到广场、(可选)选择标签维度定向
⑤
连接WebSocket → wss://dev.nice-ai.cn/podcast/ws?api_key=sk-xxxxx,发送首帧JSON,流式接收meta→segment→音频→done

演播风格(play_types)

GET /api/play-types 返回所有演播风格列表,决定播客的文体形式:

{
  "play_types": [
    {"id": 1, "name": "播客对聊", "desc": "两个AI角色以对话形式展开节目"},
    {"id": 2, "name": "嘉宾访谈", "desc": "一人主持一人受访的访谈形式"},
    {"id": 3, "name": "电台讲述", "desc": "单人独白式电台讲述"},
    {"id": 4, "name": "相声", "desc": "双人相声表演形式"}
    // ...更多风格
  ]
}
演播风格选择:演播风格通过 podcast_tag_dims 传入,不是独立字段。在标签维度中,当子类为"演播风格"时,选择的标签名会被映射到独立的风格规则文件。不选则由AI根据主题自动判断。

WebSocket 消息流

→
客户端发送首帧 — JSON,包含所有表单字段
←
meta — 生成开始,包含 podcast_id、hosts、音频参数
←
segment — 对话文本段,每条包含 speaker/voice/text
←
audio_start + [binary PCM] + audio_end — 每段对话的音频数据(48kHz/16bit/单声道)
←
done — 生成完成,包含 audio_url、cover_url、duration_sec
→
客户端发送中断 — {"type":"abort"},可在任意时刻终止生成
UI 实现建议:
① 创建播客表单设计为单页直达,所有选项使用标签点选方式
② AI角色选择:标签点选1-4个,显示emoji+名字,第二行显示主播身份
③ 字数选择:标签点选(300/500/800/1200/1500/2000/3000/5000),默认800
④ 播客场景:仅当选中预置角色时显示,标签点选
⑤ 定向维度:内联标签点选,每维度单选/多选
⑥ 发布开关:toggle开关,控制是否发布到广场
⑦ 生成过程中展示实时对话文本(segment帧),音频边接收边播放

认证方式

本系统采用双轨鉴权机制:

① 用户身份鉴权(REST API)

所有 POST/PUT/DELETE 请求必须通过以下任一方式鉴权:

鉴权方式Header / Query适用场景
方式A:token 鉴权Authorization: Bearer {token} + X-User-Id: {user_id}前端控制台/H5(用户通过验证码登录获取 token)
方式B:api_key 鉴权X-Api-Key: {api_key} 或 ?api_key={api_key}第三方应用(由管理员分配 APP Key,无需用户登录)
鉴权中间件:所有写操作(POST/PUT/DELETE)由 FastAPI 中间件统一拦截。先校验 token,token 无效时回退校验 api_key。两者都无效返回 401 {"error": "未登录或登录已过期"}。GET 请求不校验(公开数据接口)。
第三方应用看这里:如果你只有 api_key(管理员分配的 APP Key),没有用户 token,直接在请求中携带 X-Api-Key: {你的api_key} Header(或 URL 加 ?api_key={你的api_key})即可访问所有 REST API,不需要 token、不需要验证码、不需要登录。

白名单接口(无需 token/api_key 中间件鉴权): /api/auth/*(登录/验证码)、 /api/payment/notify(支付回调)、 /api/s2s/*(S2S 计费管理 REST 接口:申请 Key、查 Key 列表、创建订单、微信支付回调等。这些接口由播客服务提供,不是 S2S 语音服务本身的接口。S2S 语音服务通过 WebSocket query string 的 api_key 鉴权,不走此白名单)、 /api/voice/wx_notify(音色克隆微信回调)。

关于 /api/s2s/key/verify 和 /api/s2s/key/deduct:这两个接口虽在播客服务暴露,但S2S 语音服务现已改为本地直查数据库(直查 s2s_api_keys 表),不再调用这两个 HTTP 接口。它们保留只是为了控制台/管理后台使用。开发者无需调用这两个接口。

② 模块/第三方应用鉴权(api_key)

第三方应用使用统一的 APP Key 即可访问全部接口(REST API + S2S 语音接口),APP Key 由管理员在控制台「应用Key管理」页面分配:

鉴权方式传递方式适用接口
X-Api-Key HeaderX-Api-Key: sk-xxxxxxxxREST API(POST/PUT/DELETE)
api_key Query?api_key=sk-xxxxxxxxREST API + WebSocket
系统级 APP Key(统一接入,与用户无关):第三方应用使用统一的 APP Key 即可访问全部接口(REST API + S2S 语音对话/群聊/对讲),无需为每个用户单独申请 Key。APP Key 由管理员分配,无限额度,应用端在连接时携带 api_key 和业务 user_id 即可。

③ WebSocket 连接鉴权

WebSocket 连接在 query string 中携带 api_key,在首帧 JSON 或 query string 中携带 user_id。第三方应用直接使用上方列出的系统级 APP Key 即可访问全部 S2S 语音接口(对话/群聊/对讲),user_id 传当前业务用户 ID 即可。

⚠️ user_id 校验(防 AI 分身错位):S2S 全部接口(/s2s/、/s2s/pai/{pai_id}、/s2s/walkie)会校验 user_id 必须为 nice_ai.xd_users.id 且 status=1。user_id=0 视为匿名允许通过。各应用必须通过 POST /api/auth/login 获取 xd_users.id 作为 user_id,不得使用应用本地用户表的 ID。

错误码

所有接口使用标准 HTTP 状态码,错误响应格式如下:

{
  "error": "错误描述"
}
状态码含义
200请求成功
400请求参数错误
401未登录或登录已过期(token 无效/过期/缺失)
403无权操作该资源(所有者校验失败)
404资源不存在
500服务器内部错误

业务规则

以下为四个核心业务流程的规则说明,覆盖生成播客表单、场景与风格关联、音色克隆全流程、第三方支付接入。第三方开发者在使用对应功能前请务必阅读本章节,了解字段填写顺序、条件显示逻辑、校验规则及接口调用顺序。

规则 生成播客的表单填写顺序 字段顺序与条件显示

生成播客的表单字段按以下顺序填写,部分字段存在条件显示逻辑(依赖前序字段的选择结果)。

①
📡 播客场景(必选)—— 从 GET /api/podcast-types 获取场景列表,用户点选一个场景
②
🎭 播客风格(必选)—— 选择播客的呈现风格
③
🤖 选择 AI 主播(必选,1-4个)—— 第一个为主讲,从 GET /api/ai-characters?user_id= 获取用户自建AI,或从 GET /api/ai-characters/system 获取系统AI
④
🏷️ 播客标签维度(条件显示)—— 仅当主讲AI是用户自建的时候显示,标签按场景关联,从 GET /api/podcast-type-tags?type_id=X 获取
⑤
话题/提示词(必填)—— 文本输入,如"深圳人最爱吃的早餐文化"
⑥
配图照片(可选)—— 上传照片作为播客封面
⑦
目标字数(默认1000)—— 点选字数标签
⑧
📢 发布到广场(开关)—— 是否公开到广场
条件显示规则:第④步「播客标签维度」仅在主讲AI(第一个AI)是用户自建时才显示。若主讲为预置AI,则跳过该步骤。
规则 场景与风格的关联性 标签维度与场景绑定

播客场景(podcast_type)和标签维度通过 podcast_type_tag 关联表建立关联,标签的获取与场景选择严格绑定。

①
获取场景列表 → GET /api/podcast-types 返回启用的场景列表,每个场景含 {id, name, code, icon, color, sample_prompt}
②
选择场景后获取标签 → 调 GET /api/podcast-type-tags?type_id={场景id} 获取该场景关联的标签
③
标签分3类返回 → {identity: [{id,name}], expertise: [...], audience: [...]}
④
生成播客时传映射 → 用户勾选标签后,通过 WebSocket 发送 ai_tag_to_sub 映射(tag_id → sub_id)
关联表说明:podcast_type_tag 是播客场景与标签的关联表(存于 aiyou 库),存储每个场景选配的3类标签(主播身份/擅长领域/粉丝受众)。标签的实际枚举值存储在 ai_tag_identity、ai_tag_expertise、ai_tag_audience 三张表中(存于 nice_ai 库)。
Schema 边界:aiyou(podcast)与 nice_ai 两库边界明确:播客相关的分类、内容(podcast_type、podcast_type_tag、podcast、podcast_favorites 等)存于 aiyou 库;音色、AI、用户相关的分类、标签(xd_ai_type、xd_ai_type_tag、ai_tag_*、xd_ais、xd_voices、xd_users、user_voice_favorites、ai_character_favorite、user_openid、user_tag、user_tag_def 等)存于 nice_ai 库。交叉使用时需在 SQL 中显式加库名前缀。
规则 音色克隆完整流程 先编辑再存盘落库
核心原则:录音/上传后音频只存在前端内存,不立即上传服务器。用户编辑完成、点击保存、通过全部校验后,才上传到服务器存盘落库。

步骤1:上传或录制音频(只存内存)

方式说明限制
上传用户选择音频文件 → 文件读到前端 blob 对象 → 打开音频编辑器wav/m4a/mp3,≤600KB
录制用户录音 → 录音数据存前端 blob 对象 → 打开音频编辑器≤60秒
注意:此步骤不调用任何后端接口,不创建数据库记录,不扣减额度。音频仅存在前端内存中。

步骤2:音频编辑器编辑

功能说明对应接口
试听播放音频,显示波形—(前端)
删除选中区域拖动选中波形区域,点击"删除"移除该段—(前端)
静音选中区域将选中区域静音(数据置零,不删除)—(前端)
增益调整选中区域音量放大1.5倍—(前端)
降噪服务端降噪处理POST /api/user/voices/denoise
重置恢复到原始音频—(前端)

步骤3:点击"保存音色"(此时才上传落库)

①
自动 ASR 识别 → 优先用编辑后的音频(editorState.workingBuffer)调 POST /api/user/voices/recognize 识别语音文本
②
前端校验 → ASR 识别成功(非空)+ 文本字数 10~50 字 + 额度检查 state.quota.voice > 0
③
新建音色 → 调 POST /api/user/voices/upload(上传音频,创建数据库记录,扣减 voice_quota)→ 拿到 voiceId
④
保存文本+声纹 → 调 POST /api/user/voices/save(保存 transcript,提取 192 维声纹 embedding)

后端校验(步骤④保存时,任一不通过直接返回失败)

校验项要求
ASR 文本字数10~50 字
音频时长≥ 2 秒
噪音 SNR≥ 10dB
声纹提取成功(192 维 CAM++ embedding)
规则 播客支付接口(面向第三方客户端) 第三方接入流程
重要:本文档提供给第三方客户端使用。第三方客户端只需调用以下接口完成支付,微信支付的签名验证、回调处理等都在服务端完成,不暴露给第三方。

第三方需要调用的接口

①
获取 openid → GET /api/auth/wx_oauth?code={微信授权code},用微信 OAuth 授权 code 换取用户 openid。返回 {openid: "oXXXX", app_id: "wxb888..."}。openid 需缓存到本地(localStorage),后续支付时传给下单接口
②
创建支付订单 → POST /api/payment/create,请求体 {user_id, plan_code, openid}。返回 {order_no, amount, plan_name, jsapi_params: {appId, timeStamp, nonceStr, package, signType, paySign}}。第三方客户端拿到 jsapi_params 后,调用 wx.chooseWXPay(jsapi_params) 拉起微信支付
③
支付成功确认 → 微信支付成功后,微信服务器异步通知服务端 POST /api/payment/notify(服务端内部接口,第三方不调用)。服务端收到回调后自动更新订单状态、应用订阅配额。第三方客户端在 wx.chooseWXPay 的 success 回调后,刷新用户订阅和额度:GET /api/user/subscription?user_id=X + GET /api/user/quota?user_id=X
openid 缓存建议:第一次获取 openid 后缓存到 localStorage,后续支付直接读取,避免重复走微信授权流程。openid 与微信公众号绑定,不同应用 appid 下 openid 不同。

第三方不需要(也不应该)调用的接口

接口原因
POST /api/payment/notify微信服务器回调,服务端内部处理
POST /api/payment/confirm管理员手动确认,备用接口
GET /api/admin/payment-config管理员查支付配置
PUT /api/admin/payment-config/{pay_method}管理员改支付配置

认证接口

用户通过手机验证码登录,获取用于调用其他接口的 user_id。

POST /api/auth/send_code 发送验证码

请求参数 (JSON Body)

参数类型必填说明
phonestring必填手机号码

响应示例

{
  "success": true,
  "message": "验证码已发送"
}
POST /api/auth/login 验证码登录

请求参数 (JSON Body)

参数类型必填说明
phonestring必填手机号码
codestring必填短信验证码

响应示例

{
  "user_id": 12345,
  "username": "用户昵称",
  "phone": "138****0000",
  "avatar_url": "",
  "token": "a1b2c3d4e5f6..."
}
重要:token 用于后续 POST/PUT/DELETE 请求的 Authorization: Bearer {token} Header,有效期 7 天。过期后需重新登录获取新 token。
POST /api/auth/check 检查登录状态

请求参数 (JSON Body)

参数类型必填说明
user_idint必填用户ID

响应示例

{
  "logged_in": true,
  "user_id": 12345,
  "username": "用户昵称",
  "phone": "138****0000",
  "avatar_url": ""
}
GET /api/auth/wx_oauth 微信 OAuth 静默授权换 openid

用微信公众号 OAuth code 换取用户 openid。公开接口,无需鉴权。

使用场景:H5 在微信内打开时,前端跳转到微信授权页:
https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT&response_type=code&scope=snsapi_base&state=STATE#wechat_redirect
微信回调 redirect_uri?code=CODE 后,前端用 code 调本接口换 openid,缓存到 localStorage,登录/支付时传给后端。

Query 参数

参数类型必填说明
codestring必填微信 OAuth 回调返回的 code(一次性使用,5 分钟内有效)

响应示例

{
  "openid": "oXXXXXXXXXXXXXXX",
  "app_id": "wx1234567890abcdef"
}

错误响应

{
  "error": "code 不能为空"
}
错误码说明:
• 400 {"error":"code 不能为空"}:未传 code
• 500 {"error":"公众号未配置"}:服务端未配置公众号 appid/secret
• 400 {"error":"invalid code", "raw":{...}}:code 已使用或过期,微信返回 errmsg
• 500 {"error":"网络异常: ..."}:调微信接口失败

播客接口

播客生成、查询、下载等核心功能接口。

WS /ws 生成播客(流式)
仅支持流式模式:播客生成必须通过 WebSocket 连接,不支持 HTTP 同步请求。生成过程中实时推送剧本段落和音频 PCM 数据。
API Key 认证:WebSocket 连接必须携带 api_key 查询参数。本地调用(127.0.0.1)可跳过验证。

连接地址

wss://dev.nice-ai.cn/podcast/ws?api_key=sk-xxxxx

架构说明:应用端与服务端的职责划分

应用端只需传业务参数,规则文档的组装和系统提示词的拼接全部由服务端完成。
  • 应用端负责:实时环境信息(位置/天气/时间)、周边信息(景点/路线/美食)、用户话题、照片识别文本、系统上下文
  • 服务端负责:AI 角色人设(身份/擅长/受众标签)、用户资料(昵称/性别/城市/职业/兴趣)、对话总结(由 S2S 语音服务自动总结并持久化)、新闻/县志/民俗文化(历史资料)、规则文档组装、占位符填充、LLM 调用、TTS 合成
  • 服务端自动加载:场景规则文档(podcast_type_*.md)、风格规则文档(style_*.md)、通用规则(_common.md)、AI 角色信息、用户资料、对话总结({chat_summary})
  • 服务端将所有规则文档中的 {占位符} 替换为实际数据,拼接成完整的 system_prompt 发送给 LLM
应用端不需要、也不应该自己编写规则文档或系统提示词! 只需按下方参数表传递业务数据,服务端会完成所有规则组装和提示词工程。

首帧请求参数 (JSON)

⚠️ 播客场景与适用端口(各应用必须按对应场景调用):不同播客场景适配不同特定端口,podcast_type 表的 ports 字段定义了每个场景适用的客户端。各应用开发者必须根据下表选择正确的 podcast_type_id,调用错误的场景将生成不适配规则的播客,需自行调整。
端口/应用说明如何获取可用场景
console控制台端(默认)场景 ports 为空时默认仅控制台端可见
h5H5 端GET /api/podcast-types?port=h5
ai_together爱在一起 H5 应用硬编码 podcast_type_id=15(爱在一起场景)+ 专用 api_key
其它自定义端口由管理员在 podcast_type.ports 配置GET /api/podcast-types?port=端口号
注意:服务端不拦截跨场景调用(各应用靠自觉按文档选择正确场景)。如果传了不属于本端口的 podcast_type_id,生成的播客内容将与应用风格不匹配,由调用方自行承担。
参数类型必填说明
promptstring必填播客主题/提示词,如"深圳早餐文化"。对应规则文档中的 {prompt} 占位符
ai_character_idsint[]必填AI角色ID列表,1-4个,第一个为主角,决定播客风格定位。音色由 AI 角色自动关联,客户端无需传 voices。通过 /api/ai-characters 接口获取。AI 的 role_config(身份描述)会作为 {ai_role_desc} 变量自动注入场景规则文档
user_idint可选用户ID。传了 user_id:用于配额校验、播客入库,并自动从数据库加载用户资料(昵称/性别/城市/职业/生日/人生状态/兴趣偏好)注入规则文档的 {user_info}、{user_profile}、{user_city} 占位符。不传 user_id:匿名调用,不消耗配额、不保存播客,用户相关占位符为空,需通过 system_context 参数手动传递用户信息
env_infoobject可选应用端提交的实时环境信息,替代服务端调高德/天气 API。对应规则文档 {env_desc} 占位符。字段:{location, city, province, lng, lat, weather, temperature, datetime, season}。应用端有 GPS 和实时位置,比服务端调 API 更准确及时。不传时:占位符为空(服务端不再兜底调 API)
nearby_infoobject可选应用端提交的周边信息,替代服务端调景点/路线/美食 API。对应规则文档 {api_context} 占位符的周边部分。字段:{attractions, route, foods}。应用端有景区 API 和实时位置,比服务端调 API 更准确。不传时:周边部分为空,仅服务端补充新闻/县志/民俗
photo_descriptionstring可选用户上传照片的识别文本描述(由客户端自行调用图片识别模型生成),对应规则文档中的 {photo_description} 占位符。规则文档会根据照片信息展开画面场景描述。不传则占位符为空
system_contextstring可选系统上下文信息,对应规则文档中的 {system_context} 占位符。仅用于选择讲解角度、语言风格、内容深度,不会直接出现在台词中。可传递:用户画像(年龄/性别/职业/兴趣等)、特殊偏好等。匿名调用(不传 user_id)时,建议通过此参数传递用户信息
play_type_idint可选演播风格ID(独立于标签体系),通过 /api/podcast-type-play-types?type_id=X 获取指定场景下可用的风格列表。传入时服务端优先加载组合规则 combo_{podcast_type_id}_{play_type_id}.md,控制节目形式和角色行为;不传时仅加载场景规则 podcast_type_{id}.md
podcast_tag_dimsobject可选播客定向维度标签。格式1(推荐):{"sub_id_1": [tag_id, tag_id], "sub_id_2": tag_id},值为数组表示多选、单值为单选。格式2:[{"tag_id": 5}, {"tag_id": 6}]。sinlabel 子类只允许传1个 tag_id,mutlabel 子类可传多个。通过 /api/ai-characters/{id}/tag-dims 接口获取可选标签及 tag_type。标签会注入规则文档的 {dim_tags}、{audience_hint} 占位符,并自动判断受众模式:当"粉丝受众"维度选了"用户自己"→讲给用户自己听+注入用户信息;选了其他标签→讲给目标受众+忽略用户资料;未设置"粉丝受众"维度→默认讲给用户自己听
podcast_type_idint必填播客场景ID(podcast_type.id),通过 GET /api/podcast-types?port=端口号 接口获取本端口适用的场景列表。服务端据此加载场景规则文件(podcast_type_{id}.md,若同时传 play_type_id 则优先加载组合规则 combo_{type_id}_{play_type_id}.md)。必填:未传且 AI 角色未配置默认场景时,服务端返回 {"type":"error","message":"请先选择播客场景,再生成播客"}。⚠️ 各端口必须传本端口适用的 podcast_type_id,详见上方「播客场景与适用端口」说明
template_codestring可选已废弃播客场景 code(如 business/travel),优先级低于 podcast_type_id。建议改用 podcast_type_id(数字ID)
template_idstring可选已废弃旧参数名,等同于 podcast_type_id,优先级低于 podcast_type_id。建议改用 podcast_type_id
social_encounterbool可选是否社交播客场景(两个AI在OpenCity相遇)。true 时放宽 AI 角色访问权限(只需 is_alive=1 即可访问),进入双主角社交播客模式
word_countint可选目标字数,默认 800。对应规则文档中的 {word_count} 占位符
audio_formatstring可选音频格式:mp3 / wav / pcm,默认 mp3
speedfloat可选播报速度倍率,默认 1.0
return_jsondatabool可选是否返回元数据(标题/简介),默认 true
cover_imagestring可选用户自定义封面图(base64 字符串,不含 data:image/...;base64, 前缀)。传入时跳过 AI 文生图封面,直接保存该图片作为播客封面(done 帧的 cover_url 指向该图)
publicbool可选是否发布到播客广场,默认 false(仅当传了 user_id 时生效)

规则文档占位符与接口参数对应关系

重要:播客生成依赖规则文档(rules/_common.md + rules/podcast_type_{id}.md + rules/style_*.md),文档中通过 {占位符} 引用动态数据。占位符分三类:应用端提交(蓝色)、服务端加载(紫色)、服务端调用(绿色)。
规则文档占位符数据来源接口参数 / 自动加载说明
🔵 应用端提交(应用端有实时数据,比服务端调 API 更准确)
{prompt}应用端提交prompt用户提示词,播客核心话题
{env_desc}应用端提交env_info(位置/天气/时间/季节)实时环境信息。应用端有 GPS 和实时位置,替代服务端调高德/天气 API
{api_context}(周边部分)应用端提交nearby_info(景点/路线/美食)周边信息。应用端有景区 API,替代服务端调景点/路线/美食 API
{photo_description}应用端提交photo_description用户上传照片的识别文本(客户端需自行调用图片识别模型)
{system_context}应用端提交system_context系统上下文,仅供 LLM 选择角度,不会出现在台词中
{word_count}应用端提交word_count目标字数
{dim_tags}应用端提交podcast_tag_dims定向维度标签(主播身份/研究方向/擅长内容风格等)
🟣 服务端加载(从数据库自动加载,应用端无需提交)
{ai_name}服务端加载自动(ai_character_ids → AI角色 name)AI 主播名字
{ai_identity}服务端加载自动(AI角色 ai_identity 标签)主播身份(如"穷游达人")
{ai_personality}服务端加载自动(AI角色 ai_personality 标签)擅长领域及创作偏好
{ai_bio}服务端加载自动(AI角色 ai_bio 标签)粉丝受众/播客收听对象
{ai_role_desc}服务端加载自动(AI角色 role_config.identity_desc)AI 身份描述
{style_name}服务端加载由 play_type_id 自动匹配演播风格名称。传入 play_type_id 时从 play_type 表加载风格名;未传时占位符为空
{user_info}服务端加载自动(需传 user_id)用户基本信息摘要(昵称/性别/年龄/城市/职业/人生状态/兴趣)
{user_profile}服务端加载自动(需传 user_id)用户详细资料,仅当"粉丝受众"维度选了"用户自己"或未设置该维度时注入
{user_city}服务端加载自动(需传 user_id)用户所在城市
{audience_hint}服务端推断自动(从 podcast_tag_dims 解析)目标受众提示,仅当"粉丝受众"维度选了非"用户自己"标签时非空
{multi_ai_desc}服务端加载自动(多角色时生成)多角色描述,社交播客场景使用
{chat_summary}服务端加载自动(按 user_id + ai_character_ids[0] 从 message 表读取)之前的对话总结。由 S2S 语音服务在每 50 轮对话后自动调用 LLM 总结并写入数据库,播客生成时服务端自动读取注入
🟢 服务端调用(历史/文化资料,应用端不方便获取)
{api_context}(服务端部分)服务端调用自动(基于 user_city + prompt)新闻/县志/民俗文化(历史资料,应用端不方便获取,仍由服务端调用 API)
匿名调用说明:若不传 user_id(匿名调用),则 {user_info}、{user_profile}、{user_city} 占位符为空,服务端调用的新闻/县志/民俗也可能因缺少 user_city 而为空。此时建议通过 env_info 提交位置信息、通过 system_context 传递用户画像。

首帧示例(完整参数)

{
  "user_id": 12345,
  "prompt": "深圳的早餐文化,尤其是肠粉",
  "ai_character_ids": [6, 7],
  "podcast_type_id": 1,             // 播客场景ID(必填,通过 /api/podcast-types 获取)
  "env_info": {                       // 应用端提交实时环境(替代服务端调高德/天气API)
    "location": "深圳市南山区科技园",
    "city": "深圳", "province": "广东",
    "lng": 113.9401, "lat": 22.5266,
    "weather": "晴", "temperature": "28"
  },
  "nearby_info": {                     // 应用端提交周边信息(替代服务端调景点/路线/美食API)
    "attractions": "世界之窗、欢乐谷、锦绣中华",
    "route": "从科技园地铁→世界之窗站→步行500米",
    "foods": "肠粉、煲仔饭、糖水"
  },
  "photo_description": "一张街边肠粉店的照片,蒸汽腾腾,老板正在制作肠粉,招牌写着'正宗石磨肠粉'",
  "system_context": "用户是一名35岁男士,当前在深圳旅游,喜欢探索本地美食",
  "podcast_tag_dims": {
    "12": [45],          // 主播身份(单选)
    "13": 52,             // 研究方向(单选)
    "14": [60, 61]       // 擅长内容风格(多选)
  },
  "play_type_id": 1,                   // 演播风格ID(可选,通过 /api/podcast-type-play-types?type_id=1 获取该场景可用风格)
  "word_count": 800,
  "audio_format": "mp3",
  "speed": 1.0,
  "public": true
}
// 注意:chat_summary 无需应用端提交,由 S2S 语音服务自动总结并写入 message 表,
// 播客生成时服务端自动按 (user_id, ai_character_ids[0]) 读取最新对话总结注入 {chat_summary} 占位符

首帧示例(匿名调用,无 user_id)

{
  "prompt": "深圳的早餐文化",
  "ai_character_ids": [6],
  "podcast_type_id": 1,
  "photo_description": "街边肠粉店照片,蒸汽腾腾",
  "system_context": "用户是一名35岁男士,在深圳工作,喜欢旅游和美食,当前时间是上午",
  "word_count": 800,
  "audio_format": "mp3"
}

服务端响应消息类型

meta
元数据帧 — 生成开始,包含 podcast_id、hosts 角色信息、音频参数等
segment
段落帧 — 每句对话的文本内容,包含 speaker(角色标签)、voice(音色ID)、text(文本)
audio_start
音频开始帧 — 标记一段音频 PCM 数据即将开始传输,包含 speaker 标识
[binary]
二进制 PCM 数据 — 48kHz / 16bit / 单声道 / int16 LE 原始音频数据,客户端可直接播放
audio_end
音频结束帧 — 一段音频 PCM 数据结束
opening_line
AI 开场白帧 — 所有正文段落合成完成后、落盘前发送,包含 AI 自动生成的开场白文本({type:'opening_line', text:'...'})。开场白音频会并入最终 mp3 的片头位置,客户端可据此同步显示开场白字幕
done
生成完成帧 — 播客生成完毕,包含 podcast_id、audio_url、duration_sec、script_text、tokens_in、tokens_out、segments 等最终信息
aborted
中断确认帧 — 客户端发送 {type:'abort'} 后,服务端停止生成并返回此帧
warning
警告帧 — 非致命错误(如元数据生成失败),含 stage 和 message,主流程继续
error
错误帧 — 生成过程中发生错误,包含 message 描述(部分场景附带 stage 字段:meta/llm/tts/encode)
封面自动生成:播客生成完毕后,系统会自动调用 AI 文生图模型生成与内容相关的播客封面(500KB 以内方形图片),生成的 cover_url 会在 done 帧中返回。封面生成与播客合成并行执行,不影响主流程响应速度。

meta 帧示例

{
  "type": "meta",
  "podcast_id": "a1b2c3d4e5f6a7b8",
  "title": "深圳肠粉:一碗烟火里的城市记忆",
  "intro": "探寻深圳早餐文化的灵魂——肠粉",
  "cover_desc": "清晨的深圳街边,蒸汽腾腾的肠粉店招牌",
  "hosts": [
    {"label": "A", "voice": "8", "name": "如意", "emoji": "🎤", "gender": "female"},
    {"label": "B", "voice": "12", "name": "小松", "emoji": "🎧", "gender": "male"}
  ],
  "format": "mp3",
  "sample_rate": 48000,
  "channels": 1,
  "word_count_target": 800
}

segment 帧示例

{
  "type": "segment",
  "speaker": "A",
  "voice": "8",
  "name": "如意",
  "emoji": "🎤",
  "text": "今天呢,我们来说说深圳人最爱吃的早餐"
}

done 帧示例

{
  "type": "done",
  "podcast_id": "a1b2c3d4e5f6a7b8",
  "title": "深圳肠粉:一碗烟火里的城市记忆",
  "intro": "探寻深圳早餐文化的灵魂——肠粉",
  "script_text": "A:今天呢,我们来说说深圳人最爱吃的早餐\nB:你说的是肠粉吧...\nA:对对对...",
  "duration_sec": 245.3,
  "word_count": 856,
  "audio_file": "a1b2c3d4e5f6a7b8.mp3",
  "audio_url": "/podcast/audio/a1b2c3d4e5f6a7b8.mp3",
  "audio_size": 2943600,
  "cover_url": "/podcast/cover/a1b2c3d4e5f6a7b8.jpg",
  "format": "mp3",
  "tokens_in": 1280,
  "tokens_out": 920,
  "segments": [
    {"speaker": "A", "voice": "8", "name": "如意", "emoji": "🎤", "text": "今天呢,我们来说说深圳人最爱吃的早餐", "start_sec": 0.0, "end_sec": 3.2},
    {"speaker": "B", "voice": "12", "name": "小松", "emoji": "🎧", "text": "你说的是肠粉吧", "start_sec": 3.2, "end_sec": 6.5}
  ]
            }

客户端中断

客户端可在任意时刻发送中断帧来终止生成:

{
  "type": "abort"
}

JavaScript 调用示例

// 连接 WebSocket(携带 API Key)
const ws = new WebSocket('wss://dev.nice-ai.cn/podcast/ws?api_key=sk-xxxxx');
ws.binaryType = 'arraybuffer';

// 连接成功后发送首帧
ws.onopen = () => {
  ws.send(JSON.stringify({
    user_id: 12345,
    prompt: '深圳的早餐文化',
    ai_character_ids: [6, 7],
    podcast_type_id: 1,          // 播客场景ID(必填,通过 GET /api/podcast-types 获取)
    word_count: 800,
    audio_format: 'mp3',
    speed: 1.0,
    public: true,
    system_context: '用户是一名35岁男士,当前在深圳旅游...'
  }));
};

// 接收消息
ws.onmessage = (event) => {
  if (event.data instanceof ArrayBuffer) {
    // 二进制 PCM 音频数据(48kHz/16bit/单声道),送入 AudioContext 播放
    playAudioChunk(event.data);
  } else {
    const msg = JSON.parse(event.data);
    switch (msg.type) {
      case 'meta':
        console.log('播客标题:', msg.title, '封面描述:', msg.cover_desc);
        break;
      case 'segment':
        console.log(`[${msg.speaker}] ${msg.text}`);
        break;
      case 'opening_line':
        console.log('AI 开场白:', msg.text);
        break;
      case 'done':
        console.log('生成完成, 音频:', msg.audio_url, '时长:', msg.duration_sec);
        console.log('分段字幕:', msg.segments);  // 含 start_sec/end_sec 时间戳
        break;
      case 'aborted':
        console.log('已中断生成');
        break;
      case 'warning':
        console.warn('警告[' + msg.stage + ']:', msg.message);  // 非致命,主流程继续
        break;
      case 'error':
        console.error('生成失败:', msg.message, msg.stage || '');
        ws.close();
        break;
    }
  }
};

// 中断生成
// ws.send(JSON.stringify({ type: 'abort' }));
GET /api/podcasts 获取用户播客列表

Query 参数

参数类型必填说明
user_idint必填用户ID
limitint可选返回条数,默认 10
offsetint可选偏移量,默认 0
keywordstring可选按标题关键词搜索

响应示例

{
  "podcasts": [
    {
      "id": 1,
      "title": "深圳肠粉",
      "content": "深圳的早餐文化",
      "hosts": [
        {"voice_id": "8", "name": "如意", "emoji": "🎀"},
        {"voice_id": "12", "name": "晓松", "emoji": "🎙️"}
      ],
      "audio_url": "/audio/a1b2c3d4.mp3",
      "cover_url": "/cover/a1b2c3d4.jpg",
      "duration": 245,
      "tokens": 0,
      "address": "",
      "prompt": "深圳的早餐文化",
      "public": true,
      "created_at": 1719500000,
      "likes": 42,
      "favorites": 18,
      "username": "播客创作者"
    }
  ]
}
GET /api/podcasts/public 播客广场(随机公开播客)

无需认证,返回已发布到广场的随机公开播客列表。支持传入 user_id 查当前用户的点赞/收藏状态。

Query 参数

参数类型必填说明
pint可选页码,默认 1
limitint可选每页条数,默认 10
user_idint可选用户ID(传此参数时响应含 user_liked/user_favorited 字段)

响应示例

{
  "podcasts": [
    {
      "id": 1,
      "title": "深圳肠粉",
      "content": "深圳的早餐文化",
      "hosts": [
        {"voice_id": "8", "name": "如意", "emoji": "🎀"},
        {"voice_id": "12", "name": "晓松", "emoji": "🎙️"}
      ],
      "audio_url": "/audio/a1b2c3d4.mp3",
      "cover_url": "/cover/a1b2c3d4.jpg",
      "duration": 245,
      "prompt": "深圳的早餐文化",
      "username": "播客创作者",
      "created_at": 1719500000,
      "likes": 42,
      "favorites": 18,
      "downloads": 56,
      "user_liked": true,
      "user_favorited": false
    }
  ],
  "total": 56,
  "page": 1
}
POST /api/podcast/{podcast_id}/regenerate_audio 重新合成音频

仅重新合成 TTS 音频(不重新生成剧本),传入修改后的脚本文本。

权限:仅播客所有者或管理员可调用。请求体须传 user_id,服务端会校验 podcast.user_id 匹配,不匹配返回 403 无权操作他人播客。

路径参数

参数类型说明
podcast_idint播客数据库ID(整数)

请求参数 (JSON Body)

参数类型必填说明
user_idint必填调用者用户ID,用于所有者校验(管理员可操作任意播客)
script_textstring必填修改后的播客剧本文本(格式:A: 文本 / B: 文本)

请求示例

{
  "user_id": 12345,
  "script_text": "A: 大家好,欢迎收听今天的节目。\nB: 是的,今天我们聊一聊深圳的美食文化。\nA: 首先来说说肠粉,这是深圳人早餐的灵魂。"
}

响应示例

{
  "success": true,
  "podcast_id": 123,
  "audio_url": "/audio/a1b2c3d4.mp3",
  "duration_sec": 245.3,
  "format": "mp3"
}
使用场景:用户编辑了播客脚本内容后,只需重新合成音频,无需重新生成整个播客。脚本文本格式须与生成时一致,每行以 角色标签: 文本 格式书写。
GET /api/podcast/{podcast_id} 获取播客详情

路径参数

参数类型说明
podcast_idstring播客ID(16位十六进制)

响应示例

{
  "podcast_id": "a1b2c3d4e5f6a7b8",
  "title": "深圳肠粉",
  "intro": "探寻深圳早餐文化",
  "prompt": "深圳的早餐文化",
  "hosts": [...],
  "audio_file": "a1b2c3d4e5f6a7b8.mp3",
  "cover_url": "/cover/a1b2c3d4e5f6a7b8.jpg",
  "duration_sec": 245.3,
  "format": "mp3",
  "created_at": 1719500000
}
POST /api/podcast/{podcast_id}/regenerate_cover 重新生成封面

重新生成播客封面图片,使用 AI 文生图模型根据播客内容生成新的封面。

权限:仅播客所有者或管理员可调用。请求体须传 user_id,服务端会校验 podcast.user_id 匹配,不匹配返回 403 无权操作他人播客。

路径参数

参数类型说明
podcast_idstring播客ID(16位十六进制字符串)

请求参数 (JSON Body)

参数类型必填说明
user_idint必填调用者用户ID,用于所有者校验(管理员可操作任意播客)

响应示例

{
  "success": true,
  "cover_url": "/cover/new_cover_a1b2c3d4.jpg"
}
GET /api/voice/sample 获取音色样本音频

获取指定音色的样本音频文件,用于试听音色效果。

Query 参数

参数类型必填说明
voice_idint必填音色ID

响应

返回二进制音频文件,Content-Type 根据文件格式自动设置。

POST /api/user/voices/clone-preview 预览音色克隆

使用指定音色朗读一段文本,返回 WAV 音频预览。

请求参数 (JSON Body)

参数类型必填说明
textstring必填要朗读的文本内容
voice_idint必填音色ID
user_idint可选用户ID

响应

返回二进制 WAV 音频数据(Content-Type: audio/wav),可直接播放。

GET /api/s2s/key/verify 验证 S2S API Key(⚠️ 已废弃,仅供控制台使用)
⚠️ 已废弃:S2S 语音服务现已改为本地直查 s2s_api_keys 表,不再调用此 HTTP 接口。此接口保留仅供控制台/管理后台查询 Key 状态使用。第三方开发者无需调用此接口。

验证端到端语音对话(S2S)API Key 的有效性,返回剩余次数和公钥信息。

Query 参数

参数类型必填说明
api_keystring必填S2S API Key

响应示例

{
  "valid": true,
  "remaining": 950,
  "public": false
}
POST /api/s2s/key/deduct 扣除 S2S 次数(⚠️ 已废弃,仅供控制台使用)
⚠️ 已废弃:S2S 语音服务现已改为本地直查 s2s_api_keys 表,在自身进程内直接扣减额度,不再调用此 HTTP 接口。此接口保留仅供控制台/管理后台使用。第三方开发者无需调用此接口。

扣除 S2S API Key 的可用次数。

请求参数 (JSON Body)

参数类型必填说明
api_keystring必填S2S API Key
tokensint必填扣除次数

响应示例

{
  "ok": true,
  "remaining": 945
}
POST /api/s2s/key/apply 申请 S2S 私有 Key

申请一个新的 S2S 私有 Key(status=0 未开通,需支付后激活)。

请求参数 (JSON Body)

参数类型必填说明
user_idint必填用户 ID
namestring必填Key 名称(备注)
planstring可选套餐类型(如 basic/standard/premium,需为 s2s_plans 表中已配置的套餐)

响应示例

{
  "api_key": "sk-xxxx-yyyy",
  "api_key_id": 12,
  "status": 0           // 0=未开通,需支付后激活
}
GET /api/s2s/key/list 查询用户的 S2S Key 列表

返回当前用户所有 S2S Key(含状态、配额、到期时间、克隆配额等)。

Query 参数

参数类型必填说明
user_idint必填用户 ID

响应示例

{
  "keys": [
    {
      "id": 12,
      "api_key": "sk-xxxx-yyyy",
      "name": "我的应用",
      "status": 1,            // 0=未开通, 1=正常, 2=停用, 3=过期
      "total_quota": 10000,
      "used_quota": 500,
      "remaining": 9500,
      "plan": "basic",
      "plan_name": "基础版",
      "expire_at": 1784225289,
      "created_at": 1784225289,
      "last_used_at": 1784225289,
      "clone_quota": 5,
      "used_clone": 1,
      "clone_remaining": 4
    }
  ]
}
POST /api/s2s/order/create 创建 S2S Key 订单(调微信统一下单)

为未开通的 S2S Key 创建支付订单,调用微信统一下单接口返回 code_url(扫码支付)。

请求参数 (JSON Body)

参数类型必填说明
user_idint必填用户 ID
api_key_idint必填S2S Key ID(apply 返回的 api_key_id)
planstring必填套餐类型

响应示例

{
  "order_id": 12,
  "out_trade_no": "S2S20260718xxxx",
  "code_url": "weixin://wxpay/bizpayurl?pr=xxxx"
}
POST /api/s2s/order/notify S2S 微信支付回调(无需客户端调用)
注意:此接口由微信支付服务器回调,客户端无需调用。列出此接口仅为说明支付完成后服务端的处理逻辑。

微信支付完成后回调本接口,服务端更新订单状态为已支付,并为对应的 S2S Key 充值配额、设置到期时间、激活 status=1。

响应

XML 格式的成功/失败响应(微信支付协议要求)。

GET /api/s2s/order/list 查询用户 S2S 订单列表

返回当前用户最近 20 条 S2S 订单记录。

Query 参数

参数类型必填说明
user_idint必填用户 ID

响应示例

{
  "orders": [
    {
      "out_trade_no": "S2S20260718xxxx",
      "plan": "basic",
      "plan_name": "基础版",
      "amount": 99,
      "status": 1,            // 0=未支付, 1=已支付
      "wx_code_url": "weixin://wxpay/bizpayurl?pr=xxxx",
      "created_at": 1784225289,
      "paid_at": 1784225289
    }
  ]
}
DELETE /api/podcast/{podcast_id} 删除播客
权限:仅播客所有者或管理员可调用。Query 须传 user_id,服务端会校验 podcast.user_id 匹配,不匹配返回 403 无权删除他人播客。

路径参数

参数类型说明
podcast_idint播客数据库ID(整数,非 hex 字符串)

Query 参数

参数类型必填说明
user_idint必填调用者用户ID,用于所有者校验(管理员可操作任意播客)
注意:此处的 podcast_id 为数据库自增整数 ID,不是播客列表中返回的 podcast_id(16位 hex 字符串)。删除时请使用列表接口返回的 id 字段。

响应示例

{
  "success": true
}
GET /audio/{filename} 下载播客音频

路径参数

参数类型说明
filenamestring音频文件名,如 a1b2c3d4e5f6a7b8.mp3
说明:返回二进制音频数据,Content-Type 根据格式自动设置(mp3 → audio/mpeg,wav → audio/wav)。可直接作为 <audio> 标签的 src 属性使用。

使用示例

// 直接下载
fetch('https://dev.nice-ai.cn/audio/a1b2c3d4e5f6a7b8.mp3')
  .then(res => res.blob())
  .then(blob => {
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = 'podcast.mp3';
    a.click();
  });

// 或嵌入 audio 标签
// <audio src="https://dev.nice-ai.cn/audio/a1b2c3d4e5f6a7b8.mp3" controls></audio>
GET /cover/{filename} 下载播客封面

路径参数

参数类型说明
filenamestring封面文件名,如 a1b2c3d4e5f6a7b8.jpg
说明:返回 JPEG 图片文件(Content-Type: image/jpeg)。封面 URL 在播客生成完成后的 done 帧中的 cover_url 字段返回。可直接作为 <img> 标签的 src 属性使用。

使用示例

// 直接展示封面
<img src="https://dev.nice-ai.cn/cover/a1b2c3d4e5f6a7b8.jpg"
     alt="播客封面" style="width:300px;height:300px;">

// 下载封面
fetch('https://dev.nice-ai.cn/cover/a1b2c3d4e5f6a7b8.jpg')
  .then(res => res.blob())
  .then(blob => {
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = 'cover.jpg';
    a.click();
  });
GET /api/my/favorites 当前用户收藏的作品列表(H5「我的创作 → 收藏」视图数据源)。

当前用户收藏的作品列表(H5「我的创作 → 收藏」视图数据源)。 字段口径与 /api/podcasts 完全一致(前端同一套 renderPodcastCard 渲染), 按收藏时间倒序;取消收藏后下次拉取自然消失。

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)
limitint可选返回条数(默认 50)
offsetint可选分页偏移(默认 0)
GET /api/author/{user_id} 作者资料页(抖音式):作者名(call_me>nickname>username)+

作者资料页(抖音式):作者名(call_me>nickname>username)+ 头像 + 公开作品列表。 作品列表懒加载分页(limit/offset,默认每页 20):作者可能有数百条作品, 一次全量拉会把首页拖垮,所以改为按页取;total 为公开作品总数,has_more 供前端续拉。

路径参数

参数类型必填说明
user_idint必填用户 ID

Query 参数

参数类型必填说明
user_idint必填用户 ID
limitint可选返回条数(默认 20)
offsetint可选分页偏移(默认 0)
GET /api/podcasts/{podcast_id} 获取单个播客详情(标题、音频地址、时长、作者、封面图、AI主播等)

路径参数

参数类型必填说明
podcast_idint必填播客 ID

Query 参数

参数类型必填说明
podcast_idint必填播客 ID
user_idint可选用户 ID(默认 0)
GET /api/podcasts/{podcast_id}/transcript 获取播客的带时间戳文稿(从 meta.json 读取精确时间戳)

获取播客的带时间戳文稿(从 meta.json 读取精确时间戳) 支持两种 podcast_id: - 数字(DB id):走 DB 查询 + 权限校验,再从 audio_url 提取 hash_id 读 meta.json - hash_id(meta.json 文件名前缀,非纯数字):直接读 meta.json 返回 segments: [{speaker, text, start_sec, end_sec, emoji, name}, ...]

路径参数

参数类型必填说明
podcast_idint必填播客 ID

Query 参数

参数类型必填说明
podcast_idint必填播客 ID
user_idint可选用户 ID(默认 0)
GET /api/podcasts/{podcast_id}/digital-hosts digital 模式:播客主播列表(meta.json hosts + voice 反查 xd_

digital 模式:播客主播列表(meta.json hosts + voice 反查 xd_ais,含形象设置状态)

路径参数

参数类型必填说明
podcast_idint必填播客 ID

Query 参数

参数类型必填说明
podcast_idint必填播客 ID
GET /api/bgm/local 本地 BGM 曲库列表(category=light/pop/sfx,空则返回三类全量)。纯本地

本地 BGM 曲库列表(category=light/pop/sfx,空则返回三类全量)。纯本地文件,秒开。

Query 参数

参数类型必填说明
categorystring可选标签类别(identity/expertise/audience)

互动接口

播客点赞、收藏、下载计数以及排行榜查询。所有操作需传入已登录的 user_id。

POST /api/podcast/{podcast_id}/like 点赞/取消点赞播客

路径参数

参数类型说明
podcast_idint播客ID

Query 参数

参数类型必填说明
user_idint必填用户ID

响应示例

{
  "liked": true,
  "likes": 42
}

重复请求会切换点赞状态。liked 表示当前是否已点赞,likes 为最新点赞总数。

调用示例

fetch('/api/podcast/123/like?user_id=456', { method: 'POST' })
POST /api/podcast/{podcast_id}/favorite 收藏/取消收藏播客

路径参数

参数类型说明
podcast_idint播客ID

Query 参数

参数类型必填说明
user_idint必填用户ID

响应示例

{
  "favorited": true,
  "favorites": 18
}

重复请求切换收藏状态。

调用示例

fetch('/api/podcast/123/favorite?user_id=456', { method: 'POST' })
POST /api/podcast/{podcast_id}/download 下载计数

路径参数

参数类型说明
podcast_idint播客ID

Query 参数

参数类型必填说明
user_idint可选用户ID(未登录时可不传)

响应示例

{
  "downloads": 56
}

调用示例

fetch('/api/podcast/123/download?user_id=456', { method: 'POST' })
GET /api/podcasts/ranking 播客排行榜

Query 参数

参数类型必填说明
periodstring可选统计周期:daily(日榜,默认)、weekly(周榜)、monthly(月榜)、yearly(年榜)
limitint可选返回条数(默认 20,最大 100)

响应示例

{
  "ranking": [
    {
      "rank": 1,
      "id": 123,
      "title": "深圳早餐文化",
      "username": "创作者",
      "likes": 42,
      "favorites": 18,
      "audio_url": "/audio/a1b2c3d4.mp3",
      "duration": 245,
      "cover_url": "/cover/a1b2c3d4.jpg"
    }
  ],
  "period": "daily"
}

按 likes + favorites 综合热度降序排列。

调用示例

fetch('/api/podcasts/ranking?period=weekly&limit=10')
GET /api/podcast/{podcast_id}/like 当前用户对该播客的点赞状态(播放页点赞按钮初始态)——身份由中间件注入(GET 也注入)

路径参数

参数类型必填说明
podcast_idint必填播客 ID

Query 参数

参数类型必填说明
podcast_idint必填播客 ID
user_idint可选用户 ID(默认 0)

响应示例

{
  "liked": true,
  "likes": 12
}
GET /api/podcast/{podcast_id}/comments 评论列表(公开可读,懒加载分页:limit/offset 按顶层评论翻页,默认每页 20 条)。

评论列表(公开可读,懒加载分页:limit/offset 按顶层评论翻页,默认每页 20 条)。 作者回复以 replies 嵌套挂在所属评论下(不再单列一条),回复按时间正序(先问后答)。 is_author:该条是否由本播客作者发出——前端据此在名字后高亮「作者」标签。 返回 next_offset / has_more 供前端继续翻页;total 为顶层评论总数。 首条固定为作者名义的置顶评论(仅第一页返回):自动取播客简介/话题 + 引导讨论话术。 置顶评论动态合成、不落库——对历史播客同样生效,且不会污染用户评论数据。

路径参数

参数类型必填说明
podcast_idint必填播客 ID

Query 参数

参数类型必填说明
podcast_idint必填播客 ID
limitint可选返回条数(默认 20)
offsetint可选分页偏移(默认 0)

响应示例

{
  "list": [
    { "id": 1, "user_id": 8, "username": "华哥", "content": "太好听了",
      "created_at": 1789000000, "is_author": false, "replies": [] }
  ],
  "total": 1, "limit": 20, "offset": 0, "next_offset": 20, "has_more": false
}
POST /api/podcast/{podcast_id}/comments 发表评论(POST 走登录鉴权中间件,user_id 由中间件注入 request.state;

发表评论(POST 走登录鉴权中间件,user_id 由中间件注入 request.state; query 参数仅作旧客户端兼容)。用户名以库中 xd_users 为准,不信任客户端传值。

路径参数

参数类型必填说明
podcast_idint必填播客 ID

Query 参数

参数类型必填说明
podcast_idint必填播客 ID
user_idint可选用户 ID(默认 0)

请求参数 (JSON Body)

参数类型必填说明
contentstring必填内容
reply_toint必填

播客扩展接口

精选播客、随机推荐、发布管理等扩展功能。

POST /api/podcasts/{podcast_id}/unpublish 取消发布播客

将播客从广场撤回(public=0),需要是所有者或管理员。

路径参数

参数类型说明
podcast_idint播客ID

Query 参数

参数类型必填说明
user_idint必填用户ID(所有者或管理员)

响应示例

{ "success": true }
PUT /api/podcast/{podcast_id}/hosts 作者修改 AI 主播阵容:数量必须与原播客一致(否则与已合成音频/文稿里的说话人对不上号)。

作者修改 AI 主播阵容:数量必须与原播客一致(否则与已合成音频/文稿里的说话人对不上号)。 仅改 hosts(展示 + 后续合成音色),音频需再走"重新合成音频"才真正换声。

路径参数

参数类型必填说明
podcast_idint必填播客 ID

Query 参数

参数类型必填说明
podcast_idint必填播客 ID

请求参数 (JSON Body)

参数类型必填说明
user_idint必填用户 ID
hostsstring必填
PUT /api/podcast/{podcast_id}/title 后台改播客标题:DB podcast.title 为唯一真源(播放页/分享卡都读它),

后台改播客标题:DB podcast.title 为唯一真源(播放页/分享卡都读它), 同时把 meta.json 快照里的标题一并改掉,避免落盘记录与库不一致。

路径参数

参数类型必填说明
podcast_idint必填播客 ID

Query 参数

参数类型必填说明
podcast_idint必填播客 ID
user_idint可选用户 ID(默认 0)

请求参数 (JSON Body)

参数类型必填说明
titlestring必填标题
PUT /api/podcast/{podcast_id}/show-video 作者开关:封面区展示模式(1=封面区播视频播客 0=只显示封面图)。

作者开关:封面区展示模式(1=封面区播视频播客 0=只显示封面图)。 库为唯一真源——开关后所有访问该播客的用户都按此设置显示。仅作者(或管理员)可改。

路径参数

参数类型必填说明
podcast_idint必填播客 ID

Query 参数

参数类型必填说明
podcast_idint必填播客 ID
user_idint可选用户 ID(默认 0)

请求参数 (JSON Body)

参数类型必填说明
show_videoint必填
PUT /api/podcast/{podcast_id}/show-vp 作者点选「展示版本」:把该播客下某一条已生成的视频(图文视频/虚实混剪/全片演绎)

作者点选「展示版本」:把该播客下某一条已生成的视频(图文视频/虚实混剪/全片演绎) 设为展示项——前端播客流播放时播这一条。落库 podcast.show_vp_id,并置 show_video=1 (点选了版本就是要播视频)。仅作者(或管理员)可改。 只接受属于该播客、status=1 且 video_url 非空的 video_podcasts.id, 不允许指向别人的视频或未生成完的占位行。

路径参数

参数类型必填说明
podcast_idint必填播客 ID

Query 参数

参数类型必填说明
podcast_idint必填播客 ID
user_idint可选用户 ID(默认 0)

请求参数 (JSON Body)

参数类型必填说明
vp_idint必填
POST /api/podcast/{podcast_id}/upload_cover 「更换封面」=相册上传:用户自己选一张图当封面。

路径参数

参数类型必填说明
podcast_idint必填播客 ID

Query 参数

参数类型必填说明
podcast_idint必填播客 ID
user_idint可选用户 ID(默认 Form(0)

请求体:见实现(form-data / 二进制或空请求体)。

视频播客接口

图文视频 / 虚实混剪 / 全片演绎等视频播客的生成、进度、发布与管理。

GET /api/video-pricing 视频增值定价(公开):h5 视频模式弹窗按此实时展示价格。

视频增值定价(公开):h5 视频模式弹窗按此实时展示价格。 free_user=1 表示当前用户在免付费名单里——弹窗直接显示"免费",生成时跳过支付。

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)
GET /api/video-mode-form-config 各视频模式的创作表单配置(公开):H5 播放页「生成视频」弹窗据此渲染模式卡片与表单项。
POST /api/video-podcasts/gen-script 涨粉视频:只生成一句文案(不生成视频提示词),供文本框显示和刷新。

涨粉视频:只生成一句文案(不生成视频提示词),供文本框显示和刷新。 涨粉视频统一 20 秒版(不再有 10 秒版),video_subtype 入参一律按 20 处理。

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)
text_categorystring可选查询参数
video_subtypestring可选查询参数(默认 20)

请求参数 (JSON Body)

参数类型必填说明
choicesstring必填
POST /api/video-podcasts/upload-voice-audio 上传用户确认的TTS音频文件到服务器(涨粉视频合成时使用)

上传用户确认的TTS音频文件到服务器(涨粉视频合成时使用) 前端把试听确认的Blob通过 multipart/form-data 上传 返回 audio_path(服务器绝对路径)和 audio_url(访问URL)

请求体:见实现(form-data / 二进制或空请求体)。

GET /api/video-podcasts/dance-videos 列出已上传的舞蹈参考视频(模板库,按上传时间倒序)
POST /api/video-podcasts/upload-dance-video 上传舞蹈参考视频(舞蹈复刻模式使用)

上传舞蹈参考视频(舞蹈复刻模式使用) 校验:≤100MB、时长 ≤60s、必须含视频流 返回 video_path(服务器绝对路径)、duration、宽高

请求体:见实现(form-data / 二进制或空请求体)。

GET /api/video-podcasts/copy-categories 涨粉视频「走心文案」类别清单(H5 创作页/控制台点选类别用)
POST /api/video-podcasts/upload-music 上传音乐文件(音乐MV 风格使用):直接存入本地 BGM 曲库的「用户上传」分类,

上传音乐文件(音乐MV 风格使用):直接存入本地 BGM 曲库的「用户上传」分类, 落库后即出现在 BGM 组件里,后续所有用户都能反复调用。 校验:≤30MB、扩展名 mp3/wav/m4a、必须能解析出时长。 返回 music_path(绝对路径)、bgm_url(曲库播放地址)、duration、filename

请求体:见实现(form-data / 二进制或空请求体)。

POST /api/video-podcasts/upload-doc 上传文档(创作表单「上传文档」组件使用):作为内容参考。

上传文档(创作表单「上传文档」组件使用):作为内容参考。 校验:≤30MB、扩展名 pdf/doc/docx/ppt/pptx/txt。 返回 doc_path(服务器绝对路径)、filename、size

请求体:见实现(form-data / 二进制或空请求体)。

POST /api/video-podcasts/render-dance 舞蹈复刻:舞蹈视频(动作+音轨)+ 数字人形象 → MimicMotion 姿态驱动合成

舞蹈复刻:舞蹈视频(动作+音轨)+ 数字人形象 → MimicMotion 姿态驱动合成 DWPose 提取逐帧骨骼姿态硬约束,动作不走样,任意时长滑窗生成。 形象来源:ai_character_id 对应 AI 角色的选中形象(上传照片也已统一存到 AI 名下)。

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)
dance_video_pathstring可选查询参数
ai_character_idint可选查询参数(默认 0)

请求体:见实现(form-data / 二进制或空请求体)。

GET /api/video-podcasts/{vp_id}/stream vidstory 产物播放:成品(默认,本服务 videos/)或单段(?seg=idx,vid

vidstory 产物播放:成品(默认,本服务 videos/)或单段(?seg=idx,vidstory 工作区分段) dl=1 带 Content-Disposition: attachment —— 供下载直链(微信内复制到浏览器粘贴即存盘); 在线播放不带该参数,保持 Range 分段边下边播。

路径参数

参数类型必填说明
vp_idint必填视频播客 ID

Query 参数

参数类型必填说明
vp_idint必填视频播客 ID
segint可选查询参数(默认 -1)
user_idint可选用户 ID(默认 0)
dlint可选查询参数(默认 0)
POST /api/video-podcasts/generate /api/video-podcasts/generate

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)
modeint可选生成模式(默认 growth)
video_subtypestring可选查询参数(默认 20)
cover_stylestring可选查询参数(默认 poster)
source_podcast_idint可选查询参数(默认 0)
text_categorystring可选查询参数
ai_character_idint可选查询参数(默认 0)
script_textstring可选查询参数
scene_templatestring可选查询参数
bgm_urlstring可选查询参数
widthint可选查询参数(默认 0)
heightint可选查询参数(默认 0)
outfit_by_llmint可选查询参数(默认 0)
scene_anchorstring可选查询参数
ref_imagestring可选查询参数
music_pathstring可选查询参数
play_type_idint可选查询参数(默认 0)

请求体:见实现(form-data / 二进制或空请求体)。

GET /api/video-podcasts/generating 正在生成视频的源播客 id 列表(H5「正在生成视频」标签的轻量轮询接口)。

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)
GET /api/video-podcasts/{vp_id}/thumb 视频封面缩略图(jpg,~几十KB):列表卡片用 <img> 秒开,

视频封面缩略图(jpg,~几十KB):列表卡片用 <img> 秒开, 替代 <video preload=metadata>(那会流式拉整个 mp4,5 个卡片挤满并发连接 → 列表慢)。 优先走正式封面(_set_vp_cover_from_video:出片时落库的 cover_url,dance/growth 带时间戳 产物也在此一并覆盖——此前硬编码 vp{id}.mp4 对它们全 404,前端回落 <video> 又黑又慢); 封面就绪失败才就地抽首帧兜一次。非成品或抽帧失败返回 404(前端回落 <video>)。

路径参数

参数类型必填说明
vp_idint必填视频播客 ID

Query 参数

参数类型必填说明
vp_idint必填视频播客 ID
GET /api/video-podcasts /api/video-podcasts

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)
limitint可选返回条数(默认 20)
offsetint可选分页偏移(默认 0)
source_podcast_idint可选查询参数(默认 0)
modeint可选生成模式
keywordstring可选搜索关键词
favoritesint可选查询参数(默认 0)

响应示例

{
  "ok": true,
  "list": [ ],
  "total": 0
}
GET /api/video-podcasts/{vp_id} 获取单个视频播客详情。

获取单个视频播客详情。 分享落地页免登录查看(对齐音频播客 api_podcast_detail:拿到 id 即可看, public 仅控制广场展示,不卡分享;写操作仍走 user_id 校验)

路径参数

参数类型必填说明
vp_idint必填视频播客 ID

Query 参数

参数类型必填说明
vp_idint必填视频播客 ID
user_idint可选用户 ID(默认 0)
GET /api/video-podcasts/{vp_id}/progress 查询视频播客逐段生成进度

查询视频播客逐段生成进度 返回各段状态:0=待生成 1=生成中 2=完成 3=失败

路径参数

参数类型必填说明
vp_idint必填视频播客 ID

Query 参数

参数类型必填说明
vp_idint必填视频播客 ID
user_idint可选用户 ID(默认 0)
POST /api/video-podcasts/{vp_id}/stop 停止生成中的视频任务:通知 vidstory 取消(任务 id 与 client_task_id

停止生成中的视频任务:通知 vidstory 取消(任务 id 与 client_task_id 同源=vp{vp_id}), 并把本行立刻置为 status=2「已停止生成」,前端进度弹窗随即显示已停止。

路径参数

参数类型必填说明
vp_idint必填视频播客 ID

Query 参数

参数类型必填说明
vp_idint必填视频播客 ID

请求体:见实现(form-data / 二进制或空请求体)。

POST /api/video-podcasts/{vp_id}/retry 重新生成:把 vidstory 侧的段重新提交,并重新接管轮询回写。

路径参数

参数类型必填说明
vp_idint必填视频播客 ID

Query 参数

参数类型必填说明
vp_idint必填视频播客 ID

请求参数 (JSON Body)

参数类型必填说明
segsstring必填
visualstring可选
DELETE /api/video-podcasts/{vp_id} 删除视频播客

路径参数

参数类型必填说明
vp_idint必填视频播客 ID

Query 参数

参数类型必填说明
vp_idint必填视频播客 ID
user_idint可选用户 ID(默认 0)
POST /api/video-podcasts/{vp_id}/publish 发布视频播客到广场

路径参数

参数类型必填说明
vp_idint必填视频播客 ID

Query 参数

参数类型必填说明
vp_idint必填视频播客 ID
user_idint可选用户 ID(默认 0)

请求体:见实现(form-data / 二进制或空请求体)。

POST /api/video-podcasts/{vp_id}/unpublish 取消发布视频播客

路径参数

参数类型必填说明
vp_idint必填视频播客 ID

Query 参数

参数类型必填说明
vp_idint必填视频播客 ID
user_idint可选用户 ID(默认 0)

请求体:见实现(form-data / 二进制或空请求体)。

PUT /api/video-podcasts/{vp_id} 编辑视频播客标题/描述——只更新请求里带来的字段(部分更新):

编辑视频播客标题/描述——只更新请求里带来的字段(部分更新): 控制台的编辑飘窗现在只改标题,未传 description 时不能把已有描述清空。

路径参数

参数类型必填说明
vp_idint必填视频播客 ID

Query 参数

参数类型必填说明
vp_idint必填视频播客 ID

请求参数 (JSON Body)

参数类型必填说明
titlestring必填标题
descriptionstring必填
POST /api/video-podcasts/{vp_id}/regenerate-cover 重新生成视频播客封面

路径参数

参数类型必填说明
vp_idint必填视频播客 ID

Query 参数

参数类型必填说明
vp_idint必填视频播客 ID
user_idint可选用户 ID(默认 0)

请求体:见实现(form-data / 二进制或空请求体)。

GET /api/video-templates 「视频模板」库(一个模板=一种体裁线路,按 engine=视频模式分组)。

「视频模板」库(一个模板=一种体裁线路,按 engine=视频模式分组)。 勾选模板后,AI 先按「用户提示词 + 模板体裁」创作《创意方案》,再据方案写稿、出片。

GET /api/video-fonts 涨粉视频可选字体列表(/root/nice_ai/fonts/ 目录遍历)

涨粉视频可选字体列表(/root/nice_ai/fonts/ 目录遍历) 路径固定 /api/video-fonts,不放 /api/video-podcasts/ 下(避免与动态路由 /{vp_id} 冲突)。 返回 {ok, fonts:[{file, family, url}]},url 指向静态挂载 /video-fonts-file/。

POST /api/video-podcasts/render-template /api/video-podcasts/render-template

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)
template_idstring可选查询参数
scriptstring可选查询参数
text_categorystring可选查询参数
video_subtypestring可选查询参数(默认 10)
cover_stylestring可选查询参数(默认 poster)
ai_character_idint可选查询参数(默认 0)
voice_audio_pathstring可选查询参数
aspectstring可选查询参数(默认 1:1)
qualitystring可选查询参数(默认 medium)
bgm_urlstring可选查询参数
font_familystring可选查询参数

请求体:见实现(form-data / 二进制或空请求体)。

音色接口

管理预置音色和用户自定义音色(归属规则已统一优化,见「外部应用接口 → 音色·AI·明星·用户关联关系」)。xd_voices.category 字段已删除:预置音色 = user_id=0(平台公共),用户音色 = user_id>0(归属该用户),明星音色 = 被 star 分类 AI 分身(xd_ai_type.group_name=star 且 xd_ais.star_id>0)绑定的音色。音色分类跟随 AI 分类(xd_ai_type.group_name)。AI 通过 xd_ais.voice_id 绑定音色,一个音色可被多个 AI 复用。

⚠ 音色数据提交规范(第三方应用必读)
第三方应用在创建/保存音色到 xd_voices 表时,必须保证以下必填项完整,禁止只写入部分字段产生脏数据:
① voice_name — 音色名称(NOT NULL,禁止为空)
② user_id — 归属用户ID(系统公共音色固定为 0,用户音色为用户自己的ID)
③ audio_path — 音频文件路径(上传接口返回的 audio_file)
④ transcript — ASR 识别文本(通过 recognize 接口获取,保存时必传)
⑤ gender — 性别(male / female / unknown)
⑥ description — 音色描述
⑦ status — 状态(1=正常,0=已删除)

声纹注册时机:用户声纹注册发生在 POST /api/user/voices/save(音色保存确认)——保存时若音色 embedding 为空,后端从音频提取 192 维 CAM++ 声纹写入 xd_voices.embedding(音色级)并注册到 xd_speaker(用户级,S2S enable_speaker_identify 说话人识别的数据源,识别未命中时 s2s 自动刷新缓存)。提取失败返回400,音色不保存,杜绝脏数据。
GET /api/voices 获取预置音色列表

无需认证,返回所有可用的预置音色。

响应示例

{
  "voices": [
    {
      "id": "8",
      "name": "如意",
      "emoji": "🎀",
      "gender": "female",
      "tags": ["活泼", "开朗", "贴心"],
      "description": "26岁,女,深圳人,纯正普通话,声音清脆柔和,感情丰富,性格活泼开朗",
      "language": "zh",
      "accent": "#a855f7"
    },
    {
      "id": "12",
      "name": "晓松",
      "emoji": "🎙️",
      "gender": "male",
      "tags": ["厚实", "磁实", "北京话"],
      "description": "51岁,北京人,公众认知学者、评论员,声音厚实有磁性",
      "language": "zh",
      "accent": "#3b82f6"
    },
    {
      "id": "mina",
      "name": "米娜",
      "emoji": "🌟",
      "gender": "female",
      "tags": ["新闻", "讲述", "穿透力"],
      "description": "30岁,北京大妞,节目主持与播音工作,声音成熟活泼,精通30多种语言",
      "language": "zh",
      "accent": "#3b82f6"
    },
    {
      "id": "xiaoxiong",
      "name": "小熊",
      "emoji": "📺",
      "gender": "male",
      "tags": ["短视频口播", "深刻讲述", "挖掘价值"],
      "description": "36岁,自媒体博主,深圳创业者,声音成熟稳重,声线厚实深刻",
      "language": "zh",
      "accent": "#3b82f6"
    },
    {
      "id": "17",
      "name": "迪丽",
      "emoji": "🔬",
      "gender": "female",
      "tags": ["深度", "哲理", "看透营销本质"],
      "description": "34岁,新疆人,青年女声,柔和清甜的中系声线,自带轻微软糯感",
      "language": "zh",
      "accent": "#ec4899"
    }
  ]
}
GET /api/user/voices 获取用户音色列表

Query 参数

参数类型必填说明
user_idint必填用户ID

响应示例

{
  "voices": [
    {
      "id": 1,
      "voice_name": "我的声音",
      "audio_file": "abc123.mp3",
      "transcript": "识别出的文本内容",
      "emoji": "🎙️",
      "public": 0,
      "created_at": "2026-06-28 10:30:00"
    }
  ]
}
POST /api/user/voices/upload 创建用户音色

请求参数 (multipart/form-data)

参数类型必填说明
user_idint必填用户ID
voice_namestring必填音色名称(禁止为空,否则产生脏数据)
genderstring必填性别:male / female / unknown(禁止空字符串)
descriptionstring必填音色描述(禁止空字符串)
voice_idint必填必填,严格区分两种模式:0=新增模式(校验订阅配额 voice_quota,扣减后创建新记录);>0=编辑模式(更新已有音色ID的音频和元信息,不扣配额)。不传 voice_id 会被拒绝
emojistring可选音色表情,默认 🎙️
filefile必填音频文件,支持 wav/mp3/m4a/ogg/flac/pcm/webm,≤600KB(建议≤1分钟时长)。音频一律存储到 voices/user_voices/(存储统一目录铁律),category 字段已删除,归属只按 user_id 关联

响应示例

{
  "id": 1,
  "voice_name": "我的声音",
  "audio_file": "/root/.../user_voices/abc123.wav"
}
调用流程(必须按顺序):
① upload(本接口):上传音频 → 返回 id(voice_id)
② recognize:上传同一音频 → 返回 text(ASR 识别文本,仅供前端展示,不可编辑)
③ save:传入 id + 前端识别的 transcript → 更新元信息 + 声纹提取注册(embedding 为空时)

新增/编辑模式:voice_id 必填。voice_id = 0 为新增模式,创建新的音色记录;voice_id > 0 为编辑模式,更新已有音色的音频文件、名称、性别和描述。不传 voice_id 会被拒绝,防止应用端绕过配额限制。
新增模式配额:新增前从 xd_users.voice_quota 校验剩余可克隆数,配额用尽返回 {"error":"音色克隆额度已用尽,请订阅升级套餐","code":"quota_exhausted","quota_type":"voice","remaining":0},新增成功后自动扣减 1。
转码处理:上传后服务端用 ffmpeg 统一转码为 48kHz 单声道 16-bit PCM WAV,并执行 loudnorm 响度归一化(EBU R128 -16 LUFS),保证不同音色音量一致;转码后再校验音频时长 ≤ 60 秒,超时返回 {"error":"音频时长不能超过60秒(当前X.X秒)"}。
音色分类:category 字段已从 xd_voices 删除,不再有任何分类字段。所有音色一律存储到 voices/user_voices/;读取与合成只认 xd_voices.audio_path(唯一事实来源);归属判定只按 id 关联(user_id=0=系统,user_id>0=用户,绑定明星AI=明星)。
emoji 参数:音色表情,默认 🎙️,新增和编辑模式均写入 xd_voices.emoji 字段。
POST /api/user/voices/recognize ASR语音识别

上传音频文件进行语音识别,返回识别文本。此文本仅供前端展示,不可编辑——save 保存时将此文本作为 transcript 传入直接落库,确保参考文本与音频严格一一对应。

请求参数 (multipart/form-data)

参数类型必填说明
filefile必填音频文件(与 upload 接口上传的同一份音频),支持 wav/mp3/m4a/ogg/flac 等格式

响应示例

{ "text": "识别出的语音文本内容" }
POST /api/user/voices/save 保存音色编辑(声纹提取+注册)

保存音色元信息(transcript 由前端识别后传入直接落库),并自动完成声纹提取与注册:embedding 为空时从音频提取 192 维 CAM++ 声纹写入 xd_voices.embedding(音色级)并注册到 xd_speaker(用户级,S2S 说话人识别数据源)。声纹提取失败直接返回失败,不保存。

★ transcript 必传(前端识别后传入):所有调用方(VoiceEditor / H5 / APP)必须先调 recognize 接口完成 ASR 识别,再将文本随 save 请求传入,为空直接返回 400。参考文本必须与音频严格一一对应,编辑文本会导致 TTS 合成乱音。
支持两种请求格式(按 Content-Type 自动判断):
① multipart/form-data(推荐):可携带 file 字段上传编辑后的音频,覆盖原 audio_path 并清空 embedding 重新提取声纹
② application/json:仅更新元信息(voice_name / emoji / gender / description),不更换音频文件
校验规则(任一不通过直接返回 400 失败):
① transcript 为空/缺失 → "缺少ASR识别文本(transcript),请先完成语音识别再保存"
② 音色记录不存在或音频文件缺失 → 404
③ 声纹提取失败(embedding 为空且无法从音频提取)→ "声纹提取失败,请重新录制清晰的音频"

声纹提取与注册:如果 embedding 为空,调用 ASR 服务提取 192 维 CAM++ 声纹向量写入 embedding 字段,同时注册到 xd_speaker(用户级声纹,首个音色为准,幂等跳过已有注册);已有 embedding 则不重复提取。如上传了新音频(multipart 模式),先覆盖 audio_path 并清空 embedding,再走声纹提取流程。

请求参数 (multipart/form-data 或 application/json)

参数类型必填说明
user_idint必填用户ID
idint必填音色ID(upload 接口返回的 id)
voice_namestring必填音色名称(禁止为空,否则产生脏数据)
emojistring必填音色表情,如 🎙️ / 👩 / 👨
genderstring必填性别:male / female / unknown
descriptionstring必填音色描述
transcriptstring必填前端调 recognize 接口识别后传入的文本,直接落库(后端不重复识别)。为空返回400。参考文本必须与音频严格一一对应
filefile可选仅 multipart 模式。编辑后的音频文件(wav/mp3/m4a 等)。传入后会用 ffmpeg 转码(48kHz 单声道 16-bit PCM + loudnorm 响度归一化)覆盖原 audio_path,并清空 embedding 在本次保存中重新提取声纹。JSON 模式忽略此字段

成功响应

{
  "success": true,
  "voiceprint_registered": true,
  "transcript": "前端识别后传入的文本内容"
}

失败响应(校验不通过)

{
  "error": "声纹提取失败,请重新录制清晰的音频"
}
POST /api/user/voices/denoise 音频降噪

上传音频文件进行降噪处理,返回降噪后的 WAV 音频。

请求参数 (multipart/form-data)

参数类型必填说明
filefile必填音频文件

响应

返回二进制 WAV 音频数据(Content-Type: audio/wav)

JS 音频编辑器:统一降噪实现 五端统一 —— 服务端 Sidon 深度学习降噪

五个应用(ai_podcast 控制台 / h5 / star、opencity 控制台、ai_tts)的音频编辑器统一采用 服务端 Sidon 深度学习语音降噪(POST /api/user/voices/denoise), 取代各处效果不一的 afftdn / 前端轻量峰值压制方案。Sidon 原理:16kHz 带噪音频 → 梅尔特征 → 深度语义特征 → 语音重建 → 48kHz 干净音频,能去除环境噪声、键盘声、空调声等, 效果远超传统 ffmpeg afftdn 信号去噪。

统一方案对照

应用原方案统一后
ai_podcast 控制台服务端 Sidon ✓最佳Sidon
ai_podcast h5Sidon + 轻量降级Sidon
ai_podcast star前端轻量峰值压制Sidon
opencity 控制台Sidon + 轻量降级Sidon
ai_ttsffmpeg afftdnSidon(内网调用)

前端统一降噪实现(waveDenoise)

导出 WAV → 上传 Sidon 服务端 → 解码替换 workingBuffer。ai_podcast / opencity 直连 BASE + '/api/user/voices/denoise'(带鉴权);ai_tts 走自身 /api/denoise_audio(内网转发)。

async function waveDenoise() {
  var we = state.waveEditor;
  if (!we.workingBuffer) return;
  var btn = document.getElementById('btnWaveDenoise');
  var originText = btn ? btn.textContent : '';
  if (btn) btn.textContent = '降噪中...';
  try {
    // ① 导出未压缩 WAV(48kHz 16bit PCM)
    var wavBlob = bufferToWavBlob(we.workingBuffer);
    // ② 上传服务端 Sidon 深度学习降噪
    var formData = new FormData();
    formData.append('file', wavBlob, 'denoise_input.wav');
    var resp = await podcastFetch(BASE + '/api/user/voices/denoise', { method: 'POST', body: formData });
    if (!resp.ok) throw new Error('降噪失败 HTTP ' + resp.status);
    // ③ 解码降噪后 WAV,替换当前工作缓冲区
    var result = await resp.arrayBuffer();
    var ctx = new (window.AudioContext || window.webkitAudioContext)();
    var newBuf = await ctx.decodeAudioData(result);
    ctx.close();
    we.workingBuffer = newBuf;
    drawWaveform();
    toast('降噪完成');
  } catch (e) {
    console.error('降噪失败:', e);
    toast('降噪失败');
  }
  if (btn) btn.textContent = originText;
}

// 统一 WAV 导出:AudioBuffer → 未压缩 PCM WAV Blob
function bufferToWavBlob(buffer) {
  var numChannels = buffer.numberOfChannels;
  var sampleRate = buffer.sampleRate;
  var bitDepth = 16;
  var data = buffer.getChannelData(0);
  var dataLength = data.length * (bitDepth / 8);
  var totalLength = 44 + dataLength;
  var arrayBuffer = new ArrayBuffer(totalLength);
  var view = new DataView(arrayBuffer);
  function writeString(v, o, s) { for (var i = 0; i < s.length; i++) v.setUint8(o + i, s.charCodeAt(i)); }
  writeString(view, 0, 'RIFF'); view.setUint32(4, totalLength - 8, true); writeString(view, 8, 'WAVE');
  writeString(view, 12, 'fmt '); view.setUint32(16, 16, true); view.setUint16(20, 1, true);
  view.setUint16(22, numChannels, true); view.setUint32(24, sampleRate, true);
  view.setUint32(28, sampleRate * numChannels * bitDepth / 8, true);
  view.setUint16(32, numChannels * bitDepth / 8, true); view.setUint16(34, bitDepth, true);
  writeString(view, 36, 'data'); view.setUint32(40, dataLength, true);
  var offset = 44;
  for (var i = 0; i < data.length; i++) {
    var s = Math.max(-1, Math.min(1, data[i]));
    s = s < 0 ? s * 0x8000 : s * 0x7FFF;
    view.setInt16(offset, s, true); offset += 2;
  }
  return new Blob([arrayBuffer], { type: 'audio/wav' });
}

鉴权说明

ai_podcast / opencity 由前端 fetch 拦截器自动携带 X-User-Id + Authorization: Bearer <xd_users.login_token>; ai_tts 后端以 X-Api-Key: dev-nice-ai-cn 内网调用 http://127.0.0.1:9896/api/user/voices/denoise 转发。

服务端 Sidon 降噪接口

端点 POST /api/user/voices/denoise(multipart 字段 file),返回 48kHz 单声道干净 WAV。 实现位于 ai_podcast/server/sidon_denoiser.py(SidonDenoiser 单例,常驻 GPU)。

GET /api/user/voices/{voice_id} 获取音色详情

路径参数

参数类型说明
voice_idint音色ID

响应示例

{
  "id": 1,
  "user_id": 12345,
  "voice_name": "我的声音",
  "audio_file": "abc123.mp3",
  "transcript": "识别出的文本",
  "emoji": "🎙️",
  "created_at": "2026-06-28 10:30:00"
}
GET /api/user/voices/audio 下载用户音色音频文件

Query 参数

参数类型必填说明
filestring必填音频文件名

响应

返回二进制音频数据,Content-Type 根据文件格式自动设置。

DELETE /api/user/voices/{voice_id} 删除用户音色

路径参数 + Query 参数(软删除,status 置 0)

参数类型必填说明
voice_idint必填音色ID(路径参数)
user_idint必填用户ID(Query参数)

响应示例

{
  "success": true
}
PUT /api/user/voices/{voice_id} 只改音色资料(名称/emoji/性别/简介),不动音频文件与 transcript。

路径参数

参数类型必填说明
voice_idint必填音色 ID

Query 参数

参数类型必填说明
voice_idint必填音色 ID

请求参数 (JSON Body)

参数类型必填说明
user_idint必填用户 ID
voice_namestring必填
emojistring必填
genderstring必填性别
descriptionstring必填

管理员音色接口

管理员专用音色管理接口,需管理员权限(_require_admin)。支持创建系统/明星音色、查看各类音色详情。用户音色由用户端自行创建,管理员不再添加,避免归属混乱。

POST /api/admin/voices/create 创建音色(管理员)

管理员创建音色,音频一律保存到 voices/user_voices/(存储统一目录铁律,不按类型分目录):system→user_id=0;star→user_id=管理员ID。仅支持 system 和 star,不接受 user 类型。

请求参数 (multipart/form-data)

参数类型必填说明
user_idint必填管理员用户ID
voice_typestring必填音色类型:system(预置音色)/ star(明星音色)。传 user 会被拒绝
voice_namestring必填音色名称
target_user_idint可选归属用户ID(仅 user 类型有效,当前已禁用 user 类型,此字段保留但不使用)
genderstring可选性别:male / female / unknown
emojistring可选音色表情,默认 🎙️
descriptionstring可选音色描述
transcriptstring可选ASR 参考文本。由前端 VoiceEditor 组件识别后传入,直接写入 xd_voices.transcript 字段
filefile必填音频文件,支持 wav/mp3/m4a/ogg/flac/pcm,≤10MB

响应示例

{
  "id": 42,
  "voice_name": "周杰伦音色",
  "voice_type": "star",
  "audio_file": "/root/nice_ai/voices/user_voices/star_abc123.wav"
}
音色分类写入规则:
voice_type=system → INSERT user_id=0(系统公共音色)
voice_type=star → INSERT user_id=管理员ID(明星音色归属创建的管理员)
voice_type=user → 返回 400 错误,管理员不创建用户音色

存储统一目录铁律:所有类型音色音频一律保存到 voices/user_voices/(文件名前缀区分来源:sys_/star_)。category 字段已删除,不写任何分类字段。

transcript 直接写入:前端 VoiceEditor 组件已完成 ASR 识别,transcript 随 FormData 一起传入,后端直接写入 xd_voices.transcript 字段,无需再调 /api/user/voices/save 重新识别。

转码处理:上传后服务端用 ffmpeg 统一转码为 48kHz 单声道 16-bit PCM WAV,并执行 loudnorm 响度归一化(EBU R128 -16 LUFS)。

缓存刷新:system 类型创建后自动调用 _refresh_voice_meta() 刷新音色缓存,确保前端"创建AI"页面立即可见新音色。
GET /api/admin/user-voices/{voice_id} 用户音色详情(管理员)

获取单个用户音色的完整详情。需管理员权限。

路径参数 + Query 参数

参数类型必填说明
voice_idint路径音色ID
user_idint必填管理员用户ID(Query参数)

响应示例

{
  "id": 1,
  "user_id": 12345,
  "voice_name": "我的声音",
  "audio_file": "/root/.../user_voices/abc123.wav",
  "transcript": "识别出的文本",
  "emoji": "🎙️",
  "gender": "male",
  "description": "温暖男声",
  "public": 0,
  "status": 1,
  "created_at": "2026-06-28 10:30:00"
}
category 字段已删除:响应不再返回 category。管理员编辑音色时按 user_id 判断归属(0=系统公共,>0=归属用户/管理员)。
GET /api/admin/system-voices/{voice_id} 预置音色详情(管理员)

获取单个预置音色的完整详情。需管理员权限。

路径参数 + Query 参数

参数类型必填说明
voice_idint路径音色ID
user_idint必填管理员用户ID(Query参数)

响应示例

{
  "id": 1,
  "voice_name": "系统女声",
  "emoji": "🔊",
  "gender": "female",
  "tags": "",
  "description": "标准系统女声",
  "language": "",
  "accent": "",
  "en_name": "",
  "audio_file": "/root/.../ai/sys_abc123.wav",
  "transcript": "大家好欢迎收听",
  "public": 1,
  "status": 1,
  "user_id": 0,
  "created_at": "2026-06-28 10:30:00"
}
transcript 字段:返回该音色的 ASR 参考文本。管理员编辑预置音色时前端需用此字段回填,避免保存时参考文本丢失。

用户接口

查询用户信息与配额。

GET /api/user/info 获取用户信息

Query 参数

参数类型必填说明
user_idint必填用户ID

响应示例

{
  "user_id": 12345,
  "username": "用户昵称",
  "phone": "138****0000",
  "avatar_url": "",
  "role": "user"
}
GET /api/user/quota 查询用户配额

Query 参数

参数类型必填说明
user_idint必填用户ID

响应示例

{
  "quota": 10,
  "podcast_quota": 10,
  "voice_quota": 3,
  "ai_quota": 2,
  "plan": "free",
  "plan_name": "免费版"
}

字段说明

字段类型说明
quotaint播客剩余配额(同 podcast_quota,兼容字段)
podcast_quotaint播客剩余配额(来自 xd_users.podcast_quota)
voice_quotaint音色克隆剩余配额(来自 xd_users.voice_quota)
ai_quotaintAI 角色剩余配额(来自 xd_users.ai_quota)
planstring当前订阅套餐 code(如 free / personal / pro),无订阅时为 free
plan_namestring套餐中文名(来自 subscription_plans.plan_name)
GET /api/user/features 获取用户已开通的功能列表

返回当前用户已开通的功能 key 列表(存储在 user_feature 表中)。

Query 参数

参数类型必填说明
user_idint必填用户ID

响应示例

{
  "features": ["podcast_create", "voice_clone", "ai_character"]
}
POST /api/user/features/toggle 开通/取消一个功能(最多3个)

切换用户某功能的开通状态:已开通 → 取消;未开通 → 开通。每个用户最多开通 3 个功能,超出返回错误。

请求参数 (JSON Body)

参数类型必填说明
user_idint必填用户ID
feature_keystring必填功能标识(如 podcast_create/voice_clone/ai_character)

响应示例(开通)

{
  "success": true,
  "action": "added"
}

响应示例(取消)

{
  "success": true,
  "action": "removed"
}

错误响应

{ "error": "最多只能开通3个功能" }
GET /api/user/stats 我的页面统计:作品数 / 获得点赞 / 被收藏 / 被下载。

我的页面统计:作品数 / 获得点赞 / 被收藏 / 被下载。 按该用户的全部作品聚合(不是当前分页的 10 条),口径与 /api/podcasts 一致。

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)

响应示例

{
  "podcasts": 8,
  "likes": 120,
  "favorites": 34,
  "downloads": 56
}
GET /api/user/orders 「我的订阅」页记录列表:

「我的订阅」页记录列表: kind=plan 消费记录(套餐购买:payment_orders 里非 video_* / 非充值 的已支付单) kind=video 生成记录(付费生成视频:video_* 单,带源播客标题与该视频最新生成状态) kind=recharge 充值记录(账户充值:plan_code='recharge' 的已支付单)

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)
kindstring可选查询参数(默认 plan)
limitint可选返回条数(默认 20)
offsetint可选分页偏移(默认 0)
GET /api/user/balance 账户余额 + 视频生成起步门槛(余额 < 门槛时前端提示先充值)。

账户余额 + 视频生成起步门槛(余额 < 门槛时前端提示先充值)。 free_user=1:该用户在后台「免付费用户」名单里——生成视频跳过扣费,不受起步门槛限制。

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)

响应示例

{
  "balance": 128.5,
  "min_start": 9.9,
  "enough": 1,
  "free_user": 0
}

用户资料接口

管理用户个人资料(昵称、性别、年龄)和标签分类。用户资料标签存储在 user_tag_def 表中,采用两级结构:分类(parent_id=0) → 标签项(parent_id=分类id)。AI 角色的三类定向维度标签则分别存储在 ai_tag_identity / ai_tag_expertise / ai_tag_audience 三张枚举表中(详见 AI 角色接口)。旧的 tag_category 表和 ai_character_tag 关联表已废弃删除。

POST /api/user/profile 保存用户资料
⚠️ 鉴权要求:本接口为 POST 写操作,需通过以下任一方式鉴权(详见认证方式):
  • 方式A(前端):Authorization: Bearer {token} + X-User-Id: {user_id} Header
  • 方式B(第三方应用):X-Api-Key: {api_key} Header 或 ?api_key={api_key} Query
未鉴权返回 401 {"error": "未登录或登录已过期"}。

请求参数 (JSON Body)

参数类型必填说明
user_idint必填用户ID
tag_idsint[]可选已选标签ID数组
user_infoobject可选用户基本信息,包含 username、gender、age

请求示例(方式A:token 鉴权)

POST /api/user/profile HTTP/1.1
Authorization: Bearer a1b2c3d4e5f6...
X-User-Id: 12345
Content-Type: application/json

{
  "user_id": 12345,
  "user_info": {
    "username": "小明",
    "gender": "male",
    "age": 28
  },
  "tag_ids": [37, 38, 74]
}

请求示例(方式B:api_key 鉴权,第三方应用用)

POST /api/user/profile HTTP/1.1
X-Api-Key: sk-xxxxxxxx
Content-Type: application/json

{
  "user_id": 12345,
  "user_info": {
    "username": "小明",
    "gender": "male",
    "age": 28
  },
  "tag_ids": [37, 38, 74]
}

响应示例

{ "success": true }
POST /api/user/ai-tag 写入用户信息标签

由 S2S 等服务调用:AI 与用户聊天对话后,将用户透露的特征信息整理成完整的信息描述句写入用户标签表。

⚠️ 重点:tag 与 embedding 字段区别
  • tag 字段:写入 AI 描述的用户标签信息文本(如「用户老家在佛山,现居深圳,从事互联网创业」)
  • embedding 字段:写入该标签对应的向量值(浮点数组序列化字符串,如 "[0.123,-0.456,0.789,...]"),不是标签文本!由向量化服务生成
  • 若 embedding 误传标签文本(非 [ 开头),接口会返回 400 错误
相似度去重:写入前服务端会与该用户已有信息逐一比较文本相似度(difflib.SequenceMatcher),相似度 ≥ 0.7 则视为重复,不写入。

请求参数 (JSON Body)

参数类型必填说明
user_idint必填用户ID
tagstring必填用户标签信息文本(255字以内,如「用户老家在佛山,现居深圳,从事互联网创业」,超长自动截断)
embeddingstring|null可选标签对应的向量值(浮点数组序列化字符串,如 "[0.12,0.85,0.33,...]")。不是标签文本!由向量化服务生成;不传则留空,后续可由向量化服务补充
weightint可选权重 1-3(1低/2中/3高),默认1

请求示例

{
  "user_id": 12345,
  "tag": "用户老家在佛山,现居深圳,从事互联网创业",
  "embedding": "[0.12, 0.85, 0.33, -0.21, ...]",
  "weight": 2
}

响应示例(写入成功)

{
  "ok": true,
  "action": "created",
  "message": "标签「用户老家在佛山,现居深圳,从事互联网创业」已写入",
  "tag": "用户老家在佛山,现居深圳,从事互联网创业",
  "id": 244
}

响应示例(相似跳过)

{
  "ok": true,
  "action": "skipped",
  "message": "已有相似标签「用户老家在佛山,现居深圳」(相似度 0.82),未重复写入",
  "tag": "用户老家在佛山,现居深圳"
}

错误响应(embedding 误传文本)

{
  "error": "embedding 必须是向量值(浮点数组序列化字符串,如 [0.1,-0.2,...]),不是标签文本"
}

调用示例(Python)

import requests

resp = requests.post("https://dev.nice-ai.cn/podcast/api/user/ai-tag", json={
    "user_id": 12345,
    "tag": "用户老家在佛山,现居深圳,从事互联网创业",
    "embedding": "",  # 可选,向量值(由向量化服务生成)
    "weight": 2,
})
data = resp.json()
if data["action"] == "created":
    print(f"信息已写入: {data['tag']}")
elif data["action"] == "skipped":
    print(f"已存在相似信息,跳过: {data['tag']}")
GET /api/user/profile-tags 获取资料编辑标签

返回个人资料编辑页所需的两组标签:兴趣偏好(多选,来自 user_tag_def 表)和 职业/身份(单选,来自 ai_tag_audience 表)。无需认证参数。

响应示例

{
  "interests": [
    {"id": 1198, "name": "科技"},
    {"id": 1199, "name": "旅行"},
    {"id": 1200, "name": "摄影"}
  ],
  "professions": [
    {"id": 2, "name": "学生"},
    {"id": 3, "name": "职场人士"},
    {"id": 4, "name": "自由职业者"}
  ]
}
UI 建议:interests 支持多选(标签点选),professions 建议单选。选中后保存到 user_info.interests 和 user_info.profession 字段。

素材库接口

用户记忆素材标签与条目管理(AI 长期记忆素材源)。

GET /api/materials 列出用户所有素材标签

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)

响应示例

{
  "tags": ["旅行偏好", "饮食口味"]
}
GET /api/materials/get 获取指定标签下的所有素材条目

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)
tagstring可选查询参数
POST /api/materials/save 保存素材条目列表(整标签覆盖写入)

请求参数 (JSON Body)

参数类型必填说明
user_idint可选用户 ID
tagstring必填标签
itemsstring必填条目数组
POST /api/materials/tag 创建新标签(创建空文件占位)

请求参数 (JSON Body)

参数类型必填说明
user_idint可选用户 ID
tagstring必填标签
DELETE /api/materials/tag 删除标签(连同其下所有素材)

家庭亲情群

家庭 AI 分身、成员邀请与关系管理。

GET /api/family AI合家欢列表:我的AI分身 + 已接受的家人AI + 待接受邀请

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)

响应示例

{
  "my_ai": null,
  "family": [],
  "my_members": [],
  "pending": []
}
POST /api/family/personal 指定/切换用户的「个人AI」分身(每用户唯一,约束在 xd_family 表)。

请求参数 (JSON Body)

参数类型必填说明
user_idint可选用户 ID
ai_idint可选AI 角色 ID
POST /api/family/invite 邀请家人:主人填写「对方与我的关系」+ 家人手机号,创建待接受记录

请求参数 (JSON Body)

参数类型必填说明
user_idint可选用户 ID
owner_relationstring必填对方与我的关系(如:父女、母子)
member_phonestring必填家人手机号
POST /api/family/{invite_id}/accept 接受邀请:家人填写「我如何称呼对方」,加入我的AI家人

路径参数

参数类型必填说明
invite_idint必填邀请 ID

Query 参数

参数类型必填说明
invite_idint必填邀请 ID

请求参数 (JSON Body)

参数类型必填说明
user_idint可选用户 ID
member_relationstring必填
POST /api/family/{invite_id}/reject 拒绝邀请

路径参数

参数类型必填说明
invite_idint必填邀请 ID

Query 参数

参数类型必填说明
invite_idint必填邀请 ID
user_idint可选用户 ID(默认 0)

请求体:见实现(form-data / 二进制或空请求体)。

PUT /api/family/{family_id}/relation 更新「当前用户与该家人AI」的关系称呼(仅操作 xd_family 表,不改 xd_ais)。

路径参数

参数类型必填说明
family_idint必填路径参数

Query 参数

参数类型必填说明
family_idint必填查询参数

请求参数 (JSON Body)

参数类型必填说明
user_idint可选用户 ID
relationstring必填成员关系

标签接口

获取用户资料标签或指定播客场景的AI标签树。两类标签分别存储在不同的表中,由同一个接口按参数分发。

GET /api/tags 获取标签分类树

根据是否传入 podcast_type 返回两种不同的标签树:

  • 不传 podcast_type:返回用户资料标签(如"人生状态""兴趣偏好"),从 user_tag_def 表读取,结构为 分类(parent_id=0) → 标签项(parent_id=分类id),用于用户资料编辑页。
  • 传 podcast_type=<code>:返回该播客场景的 AI 标签树(3类定向维度:主播身份/擅长领域/粉丝受众),从 podcast_type_tag 关联表 + ai_tag_identity / ai_tag_expertise / ai_tag_audience 三张枚举表读取,用于 AI 角色创建/播客生成时的标签选择。
架构说明:旧的 tag_category 表和 ai_character_tag 关联表已废弃删除。播客场景由 podcast_type 表统一管理(含 code/icon/color 字段,可通过管理员后台 CRUD 配置),AI 角色已选标签直接存储在 ai_character.identity/personality/bio 三个 JSON 字段中。

Query 参数

参数类型必填说明
podcast_typestring可选播客场景 code(如 travel/news 等,可通过 /api/podcast-types 获取完整列表)。传此参数时返回该类型的 AI 标签树;不传则返回用户资料标签
owner_typestring可选历史兼容参数,新架构已不再使用 owner_type 区分归属(用户标签直接在 user_tag_def 表,AI 标签直接在 ai_tag_* 表),可不传

响应示例(不传 podcast_type 时,返回用户资料标签)

{
  "categories": [
    {
      "id": 918,
      "name": "人生状态",
      "parent_id": 0,
      "sort_order": 10,
      "tag_type": "mutlabel",
      "children": [
        { "id": 919, "name": "学生时期", "parent_id": 918, "sort_order": 10, "tag_type": "mutlabel" },
        { "id": 920, "name": "职场新人", "parent_id": 918, "sort_order": 20, "tag_type": "mutlabel" }
      ]
    }
  ],
  "total": 2
}

响应示例(传 podcast_type=travel 时,返回 AI 标签树)

{
  "podcast_type": "travel",
  "categories": [
    {
      "id": 1,
      "name": "旅游播客",
      "children": [
        {
          "id": "identity",
          "name": "主播身份",
          "tag_category": "identity",
          "children": [
            { "id": 1, "name": "专业导游" },
            { "id": 2, "name": "年长者游客" }
          ]
        },
        {
          "id": "expertise",
          "name": "擅长领域及创作偏好",
          "tag_category": "expertise",
          "children": [ ... ]
        },
        {
          "id": "audience",
          "name": "粉丝受众/播客收听对象",
          "tag_category": "audience",
          "children": [ ... ]
        }
      ]
    }
  ],
  "total": 1
}
GET /api/ai-tag-enums 获取AI标签枚举值(全部)

返回3张枚举表的全部数据,包含所有标签的 id、name、sort_order、status。用于管理员后台查看和管理所有枚举值。无需参数。

响应示例

{
  "identity": [
    {"id": 1, "name": "专业导游", "sort_order": 1, "status": 1},
    {"id": 2, "name": "年长者游客", "sort_order": 2, "status": 1}
  ],
  "expertise": [
    {"id": 1, "name": "历史人文", "sort_order": 1, "status": 1}
  ],
  "audience": [
    {"id": 1, "name": "年轻人", "sort_order": 1, "status": 1}
  ]
}
GET /api/ai-types 获取AI角色分类列表(xd_ai_type)

返回 AI 角色分类(xd_ai_type 表,存于 nice_ai 库),按 user / system / star 三大分组输出。分类的 group_name 为 AI 分类(user=素人 / system=预置角色 / star=明星艺人),xd_voices.category 字段已删除,音色分类跟随 AI 绑定关系。普通用户仅返回 user 分组;管理员返回全部分组。AI 标签(主播身份/擅长/受众)从属于这些分类。

Query 参数

参数类型必填说明
user_idint可选用户ID,用于按权限过滤分类(普通用户仅见 user 分组)

响应示例

{
  "groups": [
    {
      "group": "user",
      "label": "素人",
      "items": [
        {"id": 1, "name": "素人/普通用户", "group": "user", "sort_order": 1}
      ]
    },
    {
      "group": "system",
      "label": "预置角色",
      "items": []
    },
    {
      "group": "star",
      "label": "明星艺人",
      "items": []
    }
  ]
}
使用说明:创建 AI 角色时,前端用此接口渲染"角色分类"表单项;分类的 id 作为 ai_type_id 传给 /api/ai-characters。预置角色/明星艺人类分类仅管理员可选中创建(服务端校验)。
GET /api/ai-type-tags 获取某AI分类已选配的标签

返回指定 AI 分类(xd_ai_type.id)已生效的标签,按3类分组。数据来自 nice_ai.xd_ai_type_tag 关联表 + ai_tag_identity / ai_tag_expertise / ai_tag_audience 三张枚举表。公开 GET,无需鉴权。

Query 参数

参数类型必填说明
ai_type_idint必填AI 分类ID(xd_ai_type.id)

响应示例

{
  "identity": [
    {"id": 55, "name": "明星/网红/自由博主", "sort_order": 1}
  ],
  "expertise": [],
  "audience": []
}
POST /api/ai-type-tags 为AI分类选配一个标签(仅管理员)

为某 AI 分类选配一个标签,使其对该分类生效(写入 nice_ai.xd_ai_type_tag)。需要管理员权限:请求需携带有效 token(Authorization: Bearer + X-User-Id),且 _require_admin(user_id) 通过。

Query / Header 参数

参数类型必填说明
user_id (query)int必填管理员用户ID
Authorizationstring必填Bearer {token}
X-User-Idint必填管理员用户ID

Body 参数

参数类型必填说明
ai_type_idint必填AI 分类ID
tag_categorystring必填标签类别:identity / expertise / audience
tag_idint必填标签ID(对应 ai_tag_* 表 id)

响应示例

{"success": true}
DELETE /api/ai-type-tags 移除AI分类选配的标签(仅管理员)

移除某 AI 分类已选配的一个标签(删除 nice_ai.xd_ai_type_tag 关联)。需要管理员权限,鉴权方式同 POST 接口。

Body 参数

参数类型必填说明
ai_type_idint必填AI 分类ID
tag_categorystring必填标签类别:identity / expertise / audience
tag_idint必填标签ID

响应示例

{"success": true}
GET /api/ai-types/select 创建AI分身「选择AI分类」枚举:返回全部启用分类,供创建主播弹窗展示所有可选分类。
GET /api/ai-types/{type_id}/stars 某 AI 分类下的二级分类「明星/角色档案」列表(xd_stars.type_id=type_i

某 AI 分类下的二级分类「明星/角色档案」列表(xd_stars.type_id=type_id)。 供 AI 编辑表单在选定 AI 分类后选择下一级归属(如 虚拟人/智能助手 → 系统男AI/系统AI女/动漫角色)。

路径参数

参数类型必填说明
type_idint必填分类 ID

Query 参数

参数类型必填说明
type_idint必填分类 ID

演播风格接口

获取播客演播风格列表,决定播客的文体形式(对聊/相声/访谈/新闻播报等)。

GET /api/play-types 获取演播风格列表

返回所有演播风格列表,用于前端创建播客表单的演播风格选择器。无需认证参数。

Query 参数

参数类型必填说明
user_idint可选用户ID(保留参数,当前未使用)

响应示例

{
  "play_types": [
    {"id": 1, "name": "播客对聊", "desc": "两个AI角色以对话形式展开节目"},
    {"id": 2, "name": "嘉宾访谈", "desc": "一人主持一人受访的访谈形式"},
    {"id": 3, "name": "电台讲述", "desc": "单人独白式电台讲述"},
    {"id": 4, "name": "相声", "desc": "双人相声表演形式"}
  ]
}
使用说明:演播风格通过 /ws 接口的 play_type_id 参数传入(独立字段,不再走 podcast_tag_dims)。服务端会按 combo_{podcast_type_id}_{play_type_id}.md 优先加载组合规则;若无组合规则,则回退到场景规则 podcast_type_{id}.md + 风格规则 style_*.md 合并。不传 play_type_id 时仅加载场景规则。
建议:创建播客表单中,选择播客场景后通过 /api/podcast-type-play-types?type_id=X 联动加载该场景可用的演播风格,而非直接用本接口拉取全部风格。
GET /api/podcast-type-play-types 获取场景关联的演播风格列表

返回指定播客场景关联的演播风格列表,用于创建播客表单中"选择场景 → 联动加载风格"的交互。数据来自 podcast_type_play_type 关联表(场景与风格为多对多关系),由管理员在后台「播客场景配置」页面维护。公开接口,无需鉴权。

Query 参数

参数类型必填说明
type_idint必填播客场景ID(podcast_type.id,通过 /api/podcast-types 获取)

响应示例

{
  "play_types": [
    {"id": 1, "name": "播客对聊", "desc": "两个AI角色以对话形式展开节目"},
    {"id": 2, "name": "嘉宾访谈", "desc": "一人主持一人受访的访谈形式"}
  ]
}
调用时机:用户在创建播客表单中选择播客场景后,立即调用本接口获取该场景可用的演播风格列表,渲染风格选择器。用户选定风格后,将 id 作为 play_type_id 传入 /ws 接口。

外部应用接口

面向第三方应用开放的接口。所有外部接口均需通过 api_key 鉴权(sk- 开头),全部走数据库 api_keys 表校验(status=1 有效)。可通过控制台「API Key 管理」页面生成。本组接口专门用于让其它系统(如客服机器人、教育应用、智能硬件等)直接调用本预置的 AI 角色及其音色,无需重新对接账号体系和音色库。

GET /api/v1/ai-characters 对外获取预置角色列表

返回所有预置 AI(is_preset=1),含 voice_id(音色ID,数字字符串)与 voice_description(音色描述)。生成播客时必须传 ai_character_ids(音色由 AI 角色自动关联,客户端无需也无法传 voices)。外部应用拿到 AI 列表后,将用户所选 AI 的 id 组成 ai_character_ids 数组传入 /ws 即可生成播客;也可用 voice_id 调用 TTS / S2S 等模型服务复用其声音。

Query 参数

参数类型必填说明
api_keystring必填以 sk- 开头的 API Key,需在 api_keys 表中登记且 status=1(控制台「API Key 管理」页面生成)

响应字段说明

字段类型说明
ai_charactersarray系统 AI 列表
totalintAI 总数
ai_characters 元素字段:
idintAI 角色 ID
namestring角色名称
avatar_urlstring头像 URL(可能为空字符串)
voice_idstring关联音色 ID(数字字符串,如 8=如意),调用 TTS/S2S 时透传
voice_namestring音色名称(如"如意"),仅用于展示
voice_descriptionstring音色描述,即 AI 的人设/性格来源(音色描述决定 AI 人格)
podcast_typestring专攻播客场景 code(可通过 /api/podcast-types 获取完整列表)
identitystring[]主播身份标签名称列表(已展开为可读名称,非 ID)
personalitystring[]擅长领域标签名称列表(已展开为可读名称,非 ID)
biostring[]粉丝受众标签名称列表(已展开为可读名称,非 ID)
user_titlestring对用户的称呼(如"主人""朋友",可能为空字符串)
role_configstringAI 身份描述(最长 1000 字),描述该 AI 的身份背景、性格特征、说话风格等。生成播客时作为 {ai_role_desc} 变量注入场景规则文档。为空字符串表示未设置

响应示例

{
  "ai_characters": [
    {
      "id": 6,
      "name": "小雅",
      "avatar_url": "https://dev.nice-ai.cn/static/ai/xiaoya.png",
      "voice_id": "8",
      "voice_name": "如意",
      "voice_description": "温柔知性的女声主播,擅长旅行与生活话题",
      "emoji": "🌸",
      "podcast_type": "travel",
      "identity": ["知性女主播"],
      "personality": [],
      "bio": [],
      "user_title": "",
      "role_config": "30岁知性女性,曾是旅行杂志编辑,擅长用细腻笔触描绘风土人情,说话节奏舒缓,喜欢在叙述中穿插诗意比喻"
    }
  ],
  "total": 1
}

外部应用调用示例(Python)

import requests

API_BASE = "https://dev.nice-ai.cn/podcast"
API_KEY = "sk-your-api-key"

resp = requests.get(
    f"{API_BASE}/api/v1/ai-characters",
    params={"api_key": API_KEY},
    timeout=10,
)
data = resp.json()
for ai in data["ai_characters"]:
    print(ai["id"], ai["name"], ai["voice_id"], ai["voice_description"], ai["emoji"], ai["role_config"])

# 拿到 voice_id 后即可调用 TTS 服务使用该音色合成语音
# role_config 是 AI 的身份描述,可用于在第三方应用中展示 AI 人设或作为 prompt 注入

错误码

HTTP错误信息说明
401无效或缺失的 api_key 参数(需 sk- 开头)api_key 缺失、非 sk- 开头、或在 api_keys 表中无对应记录

使用说明

  • 本接口只返回 is_preset=1 的系统 AI,不暴露用户私有 AI。
  • 调用频率建议 ≤ 1 次/分钟,AI 列表更新频率低,建议在应用侧做缓存。
  • 拿到 voice_id 后,调用 TTS / S2S 等模型服务时把 voice_id 作为 voice 参数传入即可复用该 AI 的声音。
  • 系统模块级 Key(sk-opencity-xxx / sk-podcast-xxx)直接放行,无需在 api_keys 表登记。
关系 音色 · AI · 明星 · 用户 关联关系 四大系统统一关联逻辑(优化版)

本服务器所有语音业务(播客 / S2S 对话 / MDN / 生日祝福 ai_birth)共享同一套数据模型,四大系统关联关系如下(已统一优化,各应用不得再按旧逻辑自建关联):

┌───────────┐    拥有/下单     ┌───────────┐    绑定音色(voice_id)    ┌───────────┐
│ 用户      │ ──────────────→ │ AI 分身   │ ─────────────────────→ │ 音色      │
│ xd_users  │                 │ xd_ais    │                        │ xd_voices │
└───────────┘                 └─────┬─────┘                        └───────────┘
        ▲                           │ star_id(一明星多AI分身)          ▲
        │ 拥有音色(user_id)          ▼                                   │ 预置:user_id=0
┌───────────┐                 ┌───────────┐                            │ 用户:user_id>0
│ 用户音色  │                 │ 明星      │ ─── 明星音色 = 被 star 类  │
│ (user_id) │                 │ xd_stars  │     AI 绑定(ai_type=star)  │
└───────────┘                 └───────────┘                            │
                                                                       │
              合成链:明星 → AI分身(voice_id) → TTS/Seed-VC 合成祝福/生日歌

各实体归属规则(优化后)

实体归属/关联字段规则
用户(xd_users)id唯一登录主体(auth_api 获取 xd_users.id),S2S 接口校验 user_id 必须为此表 id
明星(xd_stars)id、name真实艺人内容源。一个明星可挂多个 AI 分身(xd_ais.star_id),明星本身不直接绑定音色
AI 分身(xd_ais)star_id、voice_id、ai_type_id、is_alive、role_config数字角色。绑定唯一音色(voice_id→xd_voices.id);分类 ai_type_id→xd_ai_type.id(group_name: user/system/star);is_alive=1 在线;role_config JSON 存身份(age/gender/identity_desc)
音色(xd_voices)user_id、audio_path、transcript声音克隆实体。category 字段已删除,归属只按 user_id:预置=0、用户=>0;被 star 类 AI 绑定即为明星音色;一个音色可被多个 AI 复用
⚠️ 调用口径:需要"明星声音"的接口统一走 AI 分身 id(先经 /s2s/api/stars → /s2s/api/stars/{id}/ais → /s2s/api/ais/{id} 拿到 ai_id 与 voice_id)。禁止跨过 AI 分身直接用 star_id 或散落的 voice_id 拼装,保证生日歌/祝福语等按 AI 维度缓存与鉴权。
POST /birth/api/external/birthday_song 生成/获取明星领唱《生日快乐》歌

调用星来啦(ai_birth)服务的生日祝福歌合成能力:按 AI 分身 id(ai_id)合成该 AI 用自己音色领唱的《生日快乐》歌(Seed-VC 歌声音色转换:源曲 + 明星说话参考音色 → 领唱曲)。幂等:歌曲已就绪直接返回播放地址,不重复合成。

鉴权:复用 s2s_api_keys 表(X-Api-Key 头 或 ?api_key=),与播客/S2S 共用同一把系统级 APP Key(如 sk-podcast-xxx / sk-opencity-xxx,控制台「S2S服务访问Key管理」页面分配),无需用户 token。

请求

完整地址:https://dev.nice-ai.cn/birth/api/external/birthday_song

POST /birth/api/external/birthday_song
X-Api-Key: sk-xxxxxxxx
Content-Type: application/json

{
  "ai_id": 208
}

请求参数

参数类型必填说明
ai_idint必填AI 分身 ID(通过 /s2s/api/ais/{id} 获取)。歌曲按该 AI 绑定的音色(voice_id)合成

响应示例

{
  "code": 0,
  "data": {
    "ai_id": 208,
    "ai_name": "朱时茂",
    "voice_id": "214",
    "status": "ready",      // ready | generating | pending | error
    "error": "",
    "song_url": "https://dev.nice-ai.cn/birth/song/208?v=1787556361"
  }
}

status 取值

status含义处理
ready歌曲已合成就绪直接播放 song_url
generating合成任务进行中(Seed-VC 需数十秒~数分钟)轮询状态接口直到 ready
pending已排队、尚未有结果轮询状态接口
error合成失败读取 error 字段(多为音色参考音频缺失/无效)

音频播放地址

GET https://dev.nice-ai.cn/birth/song/{ai_id} — 公开无需鉴权(需嵌入 <audio> 播放器),带 ?v= 版本号(歌曲重生成后 URL 变化自动绕开缓存)。song_url 即此地址的绝对形式。

完整调用示例(Python)

import requests, time

API = "https://dev.nice-ai.cn/birth/api/external/birthday_song"
API_KEY = "sk-your-api-key"

def get_birthday_song(ai_id):
    r = requests.post(API, json={"ai_id": ai_id},
                      headers={"X-Api-Key": API_KEY}, timeout=10)
    j = r.json()
    status = j["data"]["status"]
    while status in ("generating", "pending"):
        time.sleep(5)
        r = requests.get(API + "/status",
                         params={"ai_id": ai_id, "api_key": API_KEY}, timeout=10)
        status = r.json()["data"]["status"]
    if status == "error":
        raise RuntimeError(r.json()["data"]["error"])
    return r.json()["data"]["song_url"]

错误码

HTTP错误信息说明
401API Key 无效或已过期api_key 缺失/非 sk- 开头/在 s2s_api_keys 中无效(status≠1、过期、额度耗尽)
400缺少 ai_id(AI分身ID)未传 ai_id
404AI分身不存在或未上架ai_id 无效或 is_alive≠1
GET /birth/api/external/birthday_song/status 查询生日歌生成状态

查询指定 AI 分身生日歌的生成状态,不触发合成。生成中场景配合轮询此接口。

请求

GET /birth/api/external/birthday_song/status?ai_id=208&api_key=sk-xxxxxxxx

Query 参数

参数类型必填说明
ai_idint必填AI 分身 ID
api_keystring必填sk- 开头的系统级 APP Key(也可用 X-Api-Key 头)

响应

与生成接口响应体一致(status / song_url / error)。status=ready 后即可直接播放 song_url。

AI角色接口

管理 AI 角色和标签维度。AI角色通过 xd_ai_type 表分类,group_name 为 AI 两大分类:
① user(素人)— 普通用户创建的 AI,只能通过 user_id 获取自己的 AI(GET /api/ai-characters?user_id=X),不包含系统AI和收藏的AI;
② star(明星艺人)— 明星 AI 及全部官方系统AI(统一挂靠明星体系,系统男AI/系统女AI为官方星),官方AI列表(GET /api/ai-characters/system?user_id=X),明星星下的AI通过 GET /star/api/star/{star_id}/ais 获取;
③ 收藏列表(GET /api/ai-characters/favorites?user_id=X)— 用户收藏的 AI(可来自系统AI、广场任一类);
④ AI广场(GET /api/ai-characters/square?user_id=X)— 其他用户发布的 AI。每类独立获取,互不包含。
创建 AI 角色时必须传 ai_type_id(xd_ai_type.id),普通用户只能选 user 分组的分类,star 分组仅管理员可选用。

GET /api/ai-characters 获取AI角色列表

返回当前用户自己创建的 AI 角色(仅 user_id=当前用户,不包含预置角色和收藏的AI)。预置角色、收藏列表、AI广场请分别使用独立接口获取。

Query 参数

参数类型必填说明
user_idint必填当前用户ID,用于确定"我的AI"范围

响应字段说明

字段类型说明
idintAI角色ID
user_idint所有者用户ID
namestring角色名称
avatar_urlstring头像URL
voice_idstring关联的音色 ID(数字字符串)
podcast_typestring专攻的播客场景 code(可通过 /api/podcast-types 获取完整列表)。注:AI 角色不再强制配置场景,此字段可为空(通用 AI);生成播客时以 /ws 接口的 podcast_type_id 参数为准
identitystring主播身份标签ID列表(JSON 数组字符串,对应 ai_tag_identity 表 id)
personalitystring擅长领域标签ID列表(JSON 数组字符串,对应 ai_tag_expertise 表 id)
biostring粉丝受众标签ID列表(JSON 数组字符串,对应 ai_tag_audience 表 id)
user_titlestring对用户的称呼(如"主人""朋友")
is_presetint是否预置角色(1=预置,0=用户创建)
is_publishedint是否已发布到广场
statusint状态(1=正常,0=已删除)
created_atint创建时间戳
tag_idsint[]历史兼容字段,现统一从 identity/personality/bio 三个 JSON 字段读取标签ID
tag_namesstring[]关联标签名称列表
voice_descriptionstring关联音色的描述
emojistring关联音色的 emoji 图标,默认 🤖
role_configstringAI 身份描述(最长 1000 字),描述该 AI 的身份背景、性格特征、说话风格等。生成播客时作为 {ai_role_desc} 变量注入场景规则文档。为空字符串表示未设置

响应示例

{
  "ai_characters": [
    {
      "id": 42,
      "user_id": 12345,
      "name": "旅行达人小飞",
      "voice_id": "5",
      "podcast_type": "travel",
      "identity": "资深驴友",
      "personality": "",
      "bio": "",
      "user_title": "旅伴",
      "is_preset": 0,
      "is_published": 1,
      "tag_ids": [10, 11],
      "tag_names": ["专业导游", "历史人文"],
      "voice_description": "温暖男声",
      "emoji": "🎙️",
      "role_config": "25岁户外旅行达人,走过全国30多个省份,擅长实用攻略与风土人情讲解"
    }
  ],
  "total": 1
}
POST /api/ai-characters 创建AI角色

请求参数 (JSON Body)

参数类型必填说明
user_idint必填创建者用户ID
namestring必填角色名称
voice_idint必填关联的音色ID(数字字符串)。获取方式:预置音色用 GET /api/voices,用户音色用 GET /api/user/voices?user_id=X。
ai_type_idint必填AI 分类ID(xd_ai_type.id)。普通用户只能选 user 分组(素人)的分类;system/star 分组仅管理员可选用。通过 GET /api/ai-types 获取分类列表
podcast_typestring可选专攻播客场景 code(可通过 /api/podcast-types 获取完整列表),不传则为通用。注:前端创建表单已不再强制选择场景,AI 可应用于所有场景;此字段仅作为 AI 角色的默认场景偏好,生成播客时以 /ws 接口的 podcast_type_id 参数为准
avatar_urlstring可选头像URL
identityint[] / string可选主播身份标签ID数组(或 JSON 字符串),对应 ai_tag_identity 表 id
personalityint[] / string可选擅长领域标签ID数组(或 JSON 字符串),对应 ai_tag_expertise 表 id
bioint[] / string可选粉丝受众标签ID数组(或 JSON 字符串),对应 ai_tag_audience 表 id
user_titlestring可选对用户的称呼,如"主人""朋友"
role_configstring可选AI 身份描述(最长 1000 字),描述该 AI 的身份背景、性格特征、说话风格等。生成播客时作为 {ai_role_desc} 变量注入场景规则文档。不传或为空则使用音色默认描述
tag_idsint[]已废弃历史兼容参数,已废弃。请改用 identity/personality/bio 三个字段(int[] 数组或 JSON 字符串),分别对应主播身份/擅长领域/粉丝受众三类标签ID列表,直接存入 ai_character 表对应字段
is_publishedbool可选是否立即发布,默认 false

请求示例

{
  "user_id": 12345,
  "name": "旅行达人小飞",
  "voice_id": "8",
  "podcast_type": "travel",
  "identity": [1],
  "personality": [2],
  "bio": [3],
  "user_title": "旅伴",
  "role_config": "25岁户外旅行达人,走过全国30多个省份,擅长实用攻略与风土人情讲解,说话风格轻松活泼",
  "tag_ids": [10, 11, 12]
}

响应示例

{ "id": 42, "success": true }
GET /api/ai-characters/{ai_id} 获取AI角色详情

路径参数

参数类型说明
ai_idintAI角色ID

响应字段

字段类型说明
idintAI角色ID
user_idint所有者用户ID
namestring角色名称
avatar_urlstring头像URL
voice_idstring关联音色 ID(数字字符串)
podcast_typestring专攻的播客场景 code(可通过 /api/podcast-types 获取完整列表)。注:AI 角色不再强制配置场景,此字段可为空(通用 AI);生成播客时以 /ws 接口的 podcast_type_id 参数为准
identitystring主播身份标签ID列表(JSON 数组字符串,对应 ai_tag_identity 表 id)
personalitystring擅长领域标签ID列表(JSON 数组字符串,对应 ai_tag_expertise 表 id)
biostring粉丝受众标签ID列表(JSON 数组字符串,对应 ai_tag_audience 表 id)
user_titlestring对用户的称呼
statusint状态
created_atint创建时间戳
tag_idsint[]已废弃 旧关联表字段,保留兼容,实际数据请用 identity/personality/bio
tag_namesstring[]已废弃 旧关联表字段,保留兼容,实际数据请用 identity/personality/bio
role_configstringAI 身份描述(最长 1000 字),描述该 AI 的身份背景、性格特征、说话风格等。生成播客时作为 {ai_role_desc} 变量注入场景规则文档。为空字符串表示未设置
emojistring关联音色的 emoji 图标,默认 🤖

调用示例

fetch('/api/ai-characters/42?user_id=12345')
PUT /api/ai-characters/{ai_id} 更新AI角色
权限:请求体须传 user_id。预置角色(user_id=0)仅管理员可修改;普通AI仅所有者可修改;AI不存在返回 404;越权返回 403 无权修改他人 AI 角色。

路径参数

参数类型说明
ai_idintAI角色ID

请求参数 (JSON Body)

参数类型必填说明
user_idint必填操作者用户ID,用于所有者校验(管理员可操作预置角色)
namestring可选角色名称
voice_idstring可选关联音色 ID(数字字符串)
podcast_typestring可选专攻播客场景 code(可通过 /api/podcast-types 获取完整列表,可通过 POST/PUT /api/ai-characters 设置)。注:前端编辑表单已不再强制选择场景;此字段仅作为 AI 角色的默认场景偏好,生成播客时以 /ws 接口的 podcast_type_id 参数为准
avatar_urlstring可选头像URL
identitystring可选主播身份标签ID列表(JSON 数组字符串,对应 ai_tag_identity 表 id)
personalitystring可选擅长领域标签ID列表(JSON 数组字符串,对应 ai_tag_expertise 表 id)
biostring可选粉丝受众标签ID列表(JSON 数组字符串,对应 ai_tag_audience 表 id)
user_titlestring可选对用户的称呼
role_configstring可选AI 身份描述(最长 1000 字),传空字符串可清空已设置的描述。生成播客时作为 {ai_role_desc} 变量注入场景规则文档
tag_idsint[]可选已废弃 旧关联表字段,保留兼容;更新标签请直接传 identity/personality/bio 三个 JSON 数组字符串
is_publishedbool可选发布状态

请求示例

{
  "user_id": 12345,
  "name": "旅行达人小飞V2",
  "role_config": "更新后的身份描述:资深旅行博主,专注小众目的地深度游",
  "tag_ids": [10, 11, 15]
}

响应示例

{ "success": true }
DELETE /api/ai-characters/{ai_id} 删除AI角色(软删除)
权限:Query 须传 user_id。预置角色(user_id=0)仅管理员可删除;普通AI仅所有者可删除;AI不存在返回 404;越权返回 403 无权删除他人 AI 角色。

路径参数 + Query 参数

参数类型必填说明
ai_idint必填AI角色ID(路径参数)
user_idint必填操作者用户ID,用于所有者校验(管理员可操作预置角色)

响应示例

{ "success": true }
软删除:删除操作将 status 置为 0,数据物理保留。删除后角色不再出现在列表接口中。
GET /api/podcast-types 获取播客场景列表

动态返回所有启用的播客场景列表(含 code/icon/color 字段),数据来自 podcast_type 表,可由管理员后台 CRUD 配置。等价接口 /api/ai-characters/types 还支持 ?with_tags=1 附带各类型的完整标签树。

Query 参数(仅 /api/ai-characters/types 支持)

参数类型必填说明
with_tagsstring可选传 1 时返回各类型的完整标签树(含3类定向维度:主播身份/擅长领域/粉丝受众)

响应字段说明

字段类型说明
idint播客场景ID(podcast_type 表主键,规则文件 podcast_type_{id}.md 命名依据,/ws 接口的 podcast_type_id 参数取此值)
namestring类型名称(如"旅游播客")
codestring英文代号(唯一,可空,如 travel)
iconstringemoji 图标(如 ✈️)
colorstring主题色(如 #7C3AED)
sample_promptstring该场景的示例提示词,可作为创建播客表单的占位文本或推荐话题

响应示例

{
  "types": [
    { "id": 1, "name": "旅游播客", "code": "travel", "icon": "✈️", "color": "#7C3AED", "sample_prompt": "说说你在旅途中最难忘的一次经历" },
    { "id": 2, "name": "新闻播客", "code": "news", "icon": "📰", "color": "#F97316", "sample_prompt": "今日科技圈值得关注的头条" },
    { "id": 3, "name": "家庭话题", "code": "family", "icon": "👨‍👩‍👧‍👦", "color": "#3B82F6", "sample_prompt": "聊聊两代人之间的代沟与理解" }
    // ...更多类型
  ]
}

传 ?with_tags=1 时额外返回 tags_map 字段,结构同 /api/tags?podcast_type=xxx 接口的 categories。

GET /api/podcast-type-tags 获取播客场景的3类定向维度标签

返回指定播客场景B层选配的标签,按3类定向维度(主播身份/擅长领域/粉丝受众)分组。数据来自 podcast_type_tag 关联表 + ai_tag_identity / ai_tag_expertise / ai_tag_audience 三张枚举表。

Query 参数

参数类型必填说明
podcast_typestring必填播客场景 code(如 travel),通过 podcast_type.code 字段查询 podcast_type.id 后获取该类型的标签选配

响应示例

{
  "identity": [
    { "id": 55, "name": "明星/网红/自由博主", "sort_order": 0 },
    { "id": 1, "name": "专业导游", "sort_order": 1 }
  ],
  "expertise": [ ... ],
  "audience": [ ... ]
}

AI角色扩展接口

预置角色、AI广场、收藏、标签维度、音色预览、发布管理等扩展功能。

GET /api/ai-characters/system 获取系统AI列表

获取全部官方系统AI(user_id=0 且 is_alive=1),含系统男AI/系统女AI星下全部官方AI及绑定明星的官方AI,按创建时间升序排列。响应新增 star_id/star_name 字段标注所属明星。

Query 参数

参数类型必填说明
user_idint可选用户ID,传了会标注 favorited 收藏状态

响应示例

{
  "ai_characters": [
    {
      "id": 6,
      "user_id": 0,
      "name": "小雅",
      "voice_id": "8",
      "podcast_type": "travel",
      "identity": {"ids": [1], "names": ["主播"]},
      "personality": {"ids": [2], "names": ["科技"]},
      "bio": {"ids": [3], "names": ["年轻人"]},
      "tag_names": ["主播", "科技", "年轻人"],
      "user_title": "朋友",
      "is_preset": 1,
      "is_published": 1,
      "favorited": true,
      "voice_description": "甜美女声",
      "emoji": "🌸",
      "role_config": "30岁知性女性,曾是旅行杂志编辑..."
    }
  ]
}
GET /api/ai-characters/square AI角色广场

获取其他用户发布的 AI 角色(is_preset=0, is_published=1, user_id>0),分页。预置角色不在广场显示。

Query 参数

参数类型必填默认值说明
user_idint可选0当前用户ID,传了会标注 favorited 状态
pageint可选1页码
page_sizeint可选24每页数量(最大60)
podcast_typestring可选播客场景 code 过滤

响应示例

{
  "ai_characters": [
    {
      "id": 10,
      "user_id": 456,
      "name": "旅行达人",
      "voice_id": "5",
      "podcast_type": "travel",
      "identity": {"ids": [1, 3], "names": ["导游", "摄影师"]},
      "tag_names": ["导游", "摄影师"],
      "user_title": "华哥",
      "is_preset": 0,
      "is_published": 1,
      "favorited": false,
      "voice_description": "温暖男声",
      "emoji": "🎙️"
    }
  ],
  "total": 100,
  "page": 1,
  "page_size": 24
}
播客分类导航标签实现:
① 进入广场时先调用 GET /api/podcast-types 获取所有播客场景列表(code/name/icon/color)
② 渲染为分类导航标签(如"旅行""美食""科技"等),标签点选方式
③ 点击标签时传入 podcast_type 参数过滤:GET /api/ai-characters/square?podcast_type=travel
④ 不传 podcast_type(或传空)时返回全部广场AI
GET /api/ai-characters/favorites 获取收藏的AI

获取当前用户收藏的 AI 角色列表,按收藏时间倒序排列。

Query 参数

参数类型必填说明
user_idint必填用户ID

响应示例

{
  "ai_characters": [
    {
      "id": 10,
      "user_id": 456,
      "name": "旅行达人",
      "voice_id": "5",
      "podcast_type": "travel",
      "tag_names": ["导游", "景点介绍"],
      "user_title": "华哥",
      "is_preset": 0,
      "is_published": 1,
      "favorited": true,
      "voice_description": "温暖男声",
      "emoji": "🎙️"
    }
  ],
  "total": 3
}
DELETE /api/ai-characters/{ai_id}/favorite 取消收藏AI

路径参数

参数类型说明
ai_idintAI角色ID

Query 参数

参数类型必填说明
user_idint必填用户ID

响应示例

{ "success": true, "favorited": false }
POST /api/ai/bio-fetch 我的AI表单:按名字自动抓取网络资料并提炼身份描述

请求参数 (JSON Body)

参数类型必填说明
namestring必填名称
GET /api/ais/{ai_id}/pics 列出该 AI 的所有形象照(文件名 {ai_id}_xxx 前缀匹配,按修改时间倒序)

路径参数

参数类型必填说明
ai_idint必填AI 角色 ID

Query 参数

参数类型必填说明
ai_idint必填AI 角色 ID
user_idint可选用户 ID(默认 0)
POST /api/ais/{ai_id}/pics/upload 上传该 AI 的形象照,命名 {ai_id}_{时间戳}{随机}.{ext}

路径参数

参数类型必填说明
ai_idint必填AI 角色 ID

Query 参数

参数类型必填说明
ai_idint必填AI 角色 ID
user_idint可选用户 ID(默认 Form(0)

请求体:见实现(form-data / 二进制或空请求体)。

DELETE /api/ais/{ai_id}/pics 删除该 AI 的一张形象照(校验文件名归属前缀,防路径穿越)

路径参数

参数类型必填说明
ai_idint必填AI 角色 ID

Query 参数

参数类型必填说明
ai_idint必填AI 角色 ID
user_idint可选用户 ID(默认 0)
filenamestring可选查询参数
POST /api/ais/{ai_id}/pics/generate AI 生成形象照

路径参数

参数类型必填说明
ai_idint必填AI 角色 ID

Query 参数

参数类型必填说明
ai_idint必填AI 角色 ID

请求参数 (JSON Body)

参数类型必填说明
user_idint必填用户 ID
GET /api/ai-tones 某 AI 的多情绪音色映射(nice_ai.me_c_tones:情绪标签 → 音色)

Query 参数

参数类型必填说明
ai_idint可选AI 角色 ID(默认 0)

响应示例

{
  "tones": [
    { "id": 1, "tag": "开心", "voice_id": "xxx", "voice_name": "甜甜" },
    { "id": 2, "tag": "难过", "voice_id": "yyy", "voice_name": "低沉" }
  ]
}
POST /api/ai-tones 保存某 AI 的多情绪音色(全量替换:以提交的标签集为准,未提交的标签行删除)

请求参数 (JSON Body)

参数类型必填说明
ai_idint必填AI 角色 ID
tonesstring必填情绪→音色映射数组
GET /api/ai-characters/types 获取播客场景列表(动态从 podcast_type 表查询),可选 ?with_tags=1 返

获取播客场景列表(动态从 podcast_type 表查询),可选 ?with_tags=1 返回各类型标签树

Query 参数

参数类型必填说明
with_tagsint可选是否附带标签树(1/0)(默认 0)
GET /api/ai-characters/star 获取所有明星 AI 分身聚合列表(挂在真实明星 star_id>0 且非系统AI星下),

获取所有明星 AI 分身聚合列表(挂在真实明星 star_id>0 且非系统AI星下), 供舞蹈复刻等选择器的「明星AI」tab 使用 注意:必须注册在 /api/ai-characters/{ai_id} 之前,否则 "star" 会被当作 ai_id 拦截

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)
POST /api/ai-characters/{ai_id}/generate-avatar-bg 为AI角色生成带背景场景的半身照(供数字人口播SadTalker full模式使用)

路径参数

参数类型必填说明
ai_idint必填AI 角色 ID

Query 参数

参数类型必填说明
ai_idint必填AI 角色 ID

请求参数 (JSON Body)

参数类型必填说明
scenestring必填
forcebool可选强制执行(0/1)
GET /api/ai-characters/{ai_id}/digital-avatars 形象列表:遍历 /root/nice_ai/ais/pics/ 中 {ai_id}_ 前缀文件

路径参数

参数类型必填说明
ai_idint必填AI 角色 ID

Query 参数

参数类型必填说明
ai_idint必填AI 角色 ID
POST /api/ai-characters/{ai_id}/digital-avatars/generate 生成数字人形象。body: {"prompt": "..."(可选), "ref_image":

生成数字人形象。body: {"prompt": "..."(可选), "ref_image": "data:image/...;base64,..."(可选)} 三分支: - 仅 prompt:MAGE_FLOW 文生图(竖版 9:16) - 仅 ref_image:用户上传照片,直接存为形象(上传直用) - ref_image + prompt:已下线(图像编辑服务已删除),返回 503 引导直接上传 形象存 /root/nice_ai/ais/pics/({ai_id}_ 前缀命名);库内原本无照则自动选中。

路径参数

参数类型必填说明
ai_idint必填AI 角色 ID

Query 参数

参数类型必填说明
ai_idint必填AI 角色 ID

请求参数 (JSON Body)

参数类型必填说明
promptstring必填提示词/描述
ref_imagestring必填参考图(dataURL)
POST /api/ai-characters/{ai_id}/digital-avatars/{avatar_id}/select 选中形象:xd_ais.ai_avatar_url 指向该文件(avatar_id=文件名)

路径参数

参数类型必填说明
ai_idint必填AI 角色 ID
avatar_idint必填形象文件名

Query 参数

参数类型必填说明
ai_idint必填AI 角色 ID
avatar_idstring必填形象文件名

请求体:见实现(form-data / 二进制或空请求体)。

DELETE /api/ai-characters/{ai_id}/digital-avatars/{avatar_id} 删除形象文件;删中的则改选最新剩余形象,无剩余则清空选中

路径参数

参数类型必填说明
ai_idint必填AI 角色 ID
avatar_idint必填形象文件名

Query 参数

参数类型必填说明
ai_idint必填AI 角色 ID
avatar_idstring必填形象文件名
POST /api/ai-characters/{ai_id}/digital-preview 生成数字人预览视频(需求1:AI编辑资料页上传照片→生成数字人)

路径参数

参数类型必填说明
ai_idint必填AI 角色 ID

Query 参数

参数类型必填说明
ai_idint必填AI 角色 ID

请求参数 (JSON Body)

参数类型必填说明
textstring必填文本内容
GET /api/ai-characters/{ai_id}/messages 加载与指定 AI 的聊天记录。

路径参数

参数类型必填说明
ai_idint必填AI 角色 ID

Query 参数

参数类型必填说明
ai_idint必填AI 角色 ID
user_idint可选用户 ID(默认 0)
before_idint可选查询参数(默认 0)
limitint可选返回条数(默认 20)
POST /api/ai-characters/{ai_id}/messages 保存一条聊天消息(user/ai/system)。

路径参数

参数类型必填说明
ai_idint必填AI 角色 ID

Query 参数

参数类型必填说明
ai_idint必填AI 角色 ID
user_idint可选用户 ID(默认 0)

请求参数 (JSON Body)

参数类型必填说明
rolestring必填家庭成员关系
contentstring必填内容

图片识别接口

上传图片,通过 AI 识别图片内容并返回文字描述,用于增强播客生成上下文。

POST /api/image/describe 图片内容识别

请求参数 (multipart/form-data)

参数类型必填说明
filefile必填图片文件,支持常见格式(jpg/png/webp),最大 10MB

响应示例

{
  "description": "这是一张深圳湾公园的风景照片,画面中有蓝天白云、绿色的草地和远处的海景,色调明亮清新。"
}

调用示例 (cURL)

curl -X POST https://dev.nice-ai.cn/api/image/describe \
  -F "file=@photo.jpg"

调用示例 (JavaScript)

// 上传图片并获取描述
const form = new FormData();
form.append('file', fileInput.files[0]);

const resp = await fetch('/api/image/describe', {
  method: 'POST',
  body: form
});
const { description } = await resp.json();
console.log('图片描述:', description);
使用场景:获取图片描述后,可将其作为 system_context 的一部分传入 WebSocket 播客生成接口,让 AI 结合图片内容创作更贴切的播客。

语音对话接口

端到端语音对话(S2S)接口,支持边生成回复、边合成语音流式传输播放。连接地址:wss://dev.nice-ai.cn/s2s/

API Key:第三方应用使用统一的 APP Key 连接(由管理员分配,与用户无关,无需单独申请)。该 Key 可访问全部 S2S 语音接口(对话/群聊/对讲),无限额度。连接时 api_key 放 query string,user_id 传当前业务用户 ID。
⚠️ user_id 必须是 nice_ai.xd_users.id(防 AI 分身错位):所有应用必须统一使用 nice_ai.xd_users 表作为用户身份来源,不再支持其它用户表(如 ai_english_study.users 等本地用户表)。user_id 必须是 xd_users.id,服务端会校验 user_id 在 xd_users 中存在且 status=1,不存在或已禁用将拒绝连接(返回 {"type":"error","message":"user_id=N 在 nice_ai.xd_users 中不存在或已禁用"})。user_id=0 视为匿名(公用 Key 场景),允许通过。
获取 xd_users.id 的方式:调用 POST /api/auth/login(手机号+验证码登录),返回的 user_id 即为 xd_users.id。各应用须使用此 user_id,不得使用应用本地用户表的 ID。
WS wss://dev.nice-ai.cn/s2s/ 端到端语音对话(S2S)

连接方式

WebSocket 连接,查询参数携带 API Key:

wss://dev.nice-ai.cn/s2s/?api_key=sk-xxxxxxxx

首帧请求参数(JSON)

参数类型必填说明
api_keystring必填认证密钥(可放 query string 或首帧 JSON,二选一)
audiostring条件必填base64 编码的音频数据。仅正常音频模式必填;noaudio / image_recognize / abort 模式下不需要。不带 audio 字段时,可紧接发送 Binary 帧作为音频(正常模式)
formatstring可选输入音频格式:wav/pcm/int16/float32/aac/mp3/ogg/opus_raw,默认 wav。opus_raw 为 ESP32 上行裸 Opus 流(每帧前 2 字节小端长度前缀,无 OGG 容器)
sample_rateint可选输入音频采样率,默认 16000
languagestring可选ASR 识别语言:auto(自动,默认)/ zh / en / ja / ko 等
voicestring可选TTS 音色 ID(数字字符串),如 "8"(如意)。默认 "8"。完整列表见 /api/voices 接口
system_promptstring可选LLM system 提示词(AI 角色设定)
messagesobject[]可选LLM 对话历史(OpenAI 格式:[{role, content}, ...])。不传则从本轮开始新对话
temperaturefloat可选LLM 温度参数,默认 0.7
max_tokensint可选LLM 最大生成 token 数,默认 2048
tts_speedfloat可选TTS 语速倍率,默认 1.0
stream_textbool可选是否推送 LLM 文本增量(llm_chunk 消息),默认 true
output_formatstring可选TTS 输出格式:pcm(默认,Int16 PCM)/ aac / mp3 / opus(opus_raw 裸流:64kbps,每帧前 2 字节小端长度前缀 + 20ms Opus 帧,无容器封装,与上行 opus_raw 格式对称)。opus 模式为逐块流式编码直发(TTS 合成块到达即编码即下发,边合成边编码边传输),带宽仅为 PCM 的约 1/12,适合低带宽/低算力设备(ESP32 等)
noaudiobool可选纯文本模式。true 时跳过音频解码/静音检测/ASR,text 字段作为用户输入送入 LLM。text 和 image 至少提供一个
nottsbool可选不合成语音。true 时只跑 LLM 不跑 TTS,不返回 tts_start/tts_sentence/Binary/tts_done。可与 noaudio 组合实现纯文本对话
textstring可选文本输入。在 noaudio 或 image_recognize 模式下替代 ASR 结果送入 LLM;在正常音频模式下与 ASR 结果拼接送给 LLM(如需附带文字指令)
imagestring可选图片 base64,支持多模态(JPEG/PNG/WEBP)。在正常音频模式下与音频一起送给 LLM 识图;在 noaudio/image_recognize 模式下独立使用(不需要音频)
typestring可选特殊请求类型。设为 "image_recognize" 时启用图片识别+对话两步模式(详见下方说明)。"abort" 用于打断当前生成
user_idint可选用户ID。必须是 nice_ai.xd_users.id(服务端会校验,不存在或已禁用将拒绝连接)。0 或不传表示匿名(公用 Key 场景)。传入后自动启用用户信息提取:AI 在对话中主动了解用户特征,将用户透露的信息整理成完整的信息描述句写入用户标签。写入前自动做相似度去重(相似度 ≥ 0.7 不重复写入)。done 消息中返回 user_tag 字段告知写入结果
ai_character_idint可选AI 角色 ID。建议传入,用于按 (user_id, ai_character_id) 维度持久化对话总结。连接建立时自动从数据库加载该维度的最新对话总结注入 system_prompt(让 AI 感知之前的对话上下文,避免重复询问);每 50 轮对话后自动调用 LLM 总结并写入数据库,供后续播客生成时通过 {chat_summary} 占位符引用。不传时按 user_id 维度(ai_character_id=0)总结
enable_user_tagbool可选启用用户标签提取能力,默认 false。启用后 LLM 会在回复末尾输出 <<<TAG>>>...<<<END>>> 标签结构,服务端解析后写入用户标签,不送 TTS
enable_musicbool可选启用音乐播放能力,默认 false。启用后 LLM 会在回复末尾输出 <<<MUSIC>>>{"song":"歌名","artist":"歌手"}<<<END>>> 标签,服务端解析后推送 music 消息
enable_singingbool可选启用哼唱歌曲能力,默认 false。启用后 LLM 会在回复末尾输出 <<<SING>>>{"song":"歌名","mode":"humming|rap"}<<<END>>> 标签,服务端解析后推送 singing 消息
stream_uploadbool可选流式上行上传模式,默认 false。启用后客户端边说边传多个 Binary 音频帧,服务端只累积音频、不做边收边识别;收到 {"type":"listen_stop"} JSON 后把整段 PCM 一次性交 SenseVoice 识别(基于全文上下文,短句/数字串精度更高),随后进 LLM→TTS。适用于 ESP32 等边录边传的设备,省发送耗时
enable_speaker_identifybool可选启用声纹识别说话人,默认 true(2026-09-15 起,端侧可显式传 false 关闭)。启用后 ASR 识别的同时提取 192 维 CAM++ 声纹特征,与声纹参考库做 argmax 软判决,命中后 asr_result 消息携带 speaker_name 字段返回当前说话人称呼:识别出的用户是该 AI 角色主人(xd_ais.user_id 匹配且配置了 call_me)时返回 xd_ais.call_me(用户自定义称呼,如"缸哥"),其他注册用户返回 xd_users.username,未识别出为空字符串 ""。判定同时把「称呼(声纹相似度X.XX)」标签交给 LLM 增强活人感。详见下方「声纹识别说话人(speaker_name)」小节
user_tokenstring可选设备登录令牌(配合 client_type=esp32 使用)。首次语音登录成功后由服务端签发、设备 NVS 持久化;后续连接首帧携带即可免登录直接以绑定 user_id 对话。令牌无效则自动转语音登录流程
client_typestring可选客户端类型。设为 "esp32" 时进入设备接入流程(设备待命门控、语音登录、板型配置下发、聆听静音超时)。Web/APP 不传,行为与普通 S2S 连接一致
boardstring可选设备板型(配合 client_type=esp32 使用),当前白名单:meeting / anan / sisi。服务端按板型从 xd_s2s_board_configs 取唤醒模式/会议模式并下发 device_config 消息;同时决定播报期打断监听是否启用(仅 meeting 板带硬件 AEC)
enable_user_profilebool可选用户资料记忆,默认 false。启用后服务端读取 xd_users 已有资料注入提示词,LLM 在回复末尾输出 <<<PROFILE>>>{...}<<<END>>> 块,经校验后写入 xd_users 白名单字段(username/nickname/call_me/gender/birthday/age/profession/city/interests/life_status/industry/life_stage)。done 消息中返回 user_profile 字段
语音情绪与停顿标签(默认启用,无需传参):S2S 服务默认在 system_prompt 中注入情绪标签指令,LLM 会自主在回复中嵌入以下标签,增强语音表现力:
情绪标签(VoxCPM2 原生支持,TTS 模型解析合成带情绪语音):[laughing]、[sigh]、[Uhm]、[Shh]、[question-ah]、[question-ei]、[question-en]、[question-oh]、[surprise-wa]、[surprise-yo]、[dissatisfaction-hnn]
停顿标签(服务端在 TTS 合成后插入对应时长静音):[200] 表示停顿 200ms,[400] 表示停顿 400ms,范围 50~3000ms
标签不会在客户端 llm_chunk/tts_sentence 中显示(自动剥离),只影响语音合成效果。
首帧参数说明:首帧中除 api_key 外的所有参数会作为本连接的默认参数,后续轮次只需要传模式参数(audio / text / image)和需要覆盖的参数即可。

参数组合规则:
  • 正常音频模式:必须传 audio(或 Binary 帧)
  • 纯文本模式:noaudio=true + text(或 image),不需要 audio
  • 图片识别模式:type="image_recognize" + image(text 可选),不需要 audio
  • 打断:{"type":"abort"},不需要任何数据

首帧请求示例

{
  "api_key": "sk-xxxxxxxx",
  "audio": "UklGRiR...base64编码的音频数据...",
  "format": "wav",
  "sample_rate": 16000,
  "voice": "8",
  "language": "zh",
  "system_prompt": "你是助手小思",
  "user_id": 12345
}

纯文本模式首帧示例(noaudio,不需要 audio)

{
  "api_key": "sk-xxxxxxxx",
  "noaudio": true,
  "text": "你好,今天深圳天气怎么样?",
  "voice": "8",
  "language": "zh"
}

图片模式首帧示例(noaudio,只传图片不需要 audio)

{
  "api_key": "sk-xxxxxxxx",
  "noaudio": true,
  "image": "iVBORw0KGgo...base64编码的图片...",
  "voice": "8"
}

图片识别+对话首帧示例(image_recognize,不需要 audio)

{
  "api_key": "sk-xxxxxxxx",
  "type": "image_recognize",
  "image": "iVBORw0KGgo...base64编码的图片...",
  "text": "看看前面有什么",
  "voice": "8"
}

纯文本对话示例(noaudio + notts,既不发音频也不收音频,不需要 audio)

{
  "api_key": "sk-xxxxxxxx",
  "noaudio": true,
  "notts": true,
  "text": "你好,今天深圳天气怎么样?"
}

第二轮及后续轮次

首帧后,后续每轮只需传模式参数和需要覆盖的默认参数。支持以下模式:

模式最小参数说明
正常音频{"audio":"..."}传入音频,走 ASR→LLM→TTS 全链路
纯文本{"noaudio":true,"text":"..."}只传文本,跳过 ASR,走 LLM→TTS
纯图片{"noaudio":true,"image":"..."}只传图片,跳过 ASR,图片送 LLM 识图
图片+文本{"noaudio":true,"image":"...","text":"..."}图片+文本一起送 LLM
图片识别+对话{"type":"image_recognize","image":"..."}两步模式:先识图描述,再对话
纯文本对话{"noaudio":true,"notts":true,"text":"..."}不发音频也不收音频,只返回 LLM 文本
打断{"type":"abort"}中断当前生成

示例(正常音频模式后续轮次,只需传 audio):

{
  "api_key": "sk-xxxxxxxx",
  "audio": "UklGRiR..."
}

请求模式说明

模式触发条件说明
audio(默认)首帧或后续帧带 audio 字段传入用户音频,服务端 ASR→LLM→TTS 全链路流式处理:返回 asr_result + llm_chunk + Binary + done
noaudionoaudio=true纯文本模式。客户端只发 text(可选 image),服务端跳过音频解码/静音检测/ASR,直接走 LLM→TTS。返回 llm_chunk + Binary + done(无 asr_result)
nottsnotts=true不合成语音。服务端只返回 LLM 回复文本(llm_chunk),跳过 TTS 合成(无 tts_start/tts_sentence/Binary/tts_done)。可与 noaudio 组合实现纯文本对话
image_recognizetype="image_recognize"图片识别+对话两步模式。必须提供 image,text 可选(如"看看前面有什么")。第一步:LLM 客观描述图片 → 推送 image_recognition 消息(客户端可保存原文);第二步:用 text + recognition 走 LLM 对话 + TTS 播报。done/aborted 消息含 recognition 字段
stream_uploadstream_upload=true流式上行上传模式。客户端边说边传多个 Binary 音频帧,服务端只累积音频;VAD 静音后发 {"type":"listen_stop"} JSON 提交整段 PCM,一次性交 ASR(不做边收边识别)。适用于 ESP32 等边录边传设备,省发送耗时。唤醒时发 noaudio=true + text 帧可立即触发 LLM(不经过 ASR)

打断机制(双向)

客户端 → 服务端:用户正在说话而 AI 正在播放回复时,客户端发送 {"type":"abort"} 即可中断当前 LLM/TTS 生成;或直接发送新的 audio 帧也可触发新一轮 ASR 识别。服务端会立即推送 aborted 消息并终止本轮生成。

服务端 → 客户端(超车打断):设备端上行持续(含 TTS 播放期间),服务端在播报期对上行帧做 AEC 回采判定——仅 AEC 板型(meeting)启动播报期打断监听,判据只用物理量(VAD 开口累积 ≥520ms 且近场电平达到门限)判定为真人插话,随即中断当前生成并主动下发 {"type":"interrupt"};已累积的人声作为话头交给下一轮聆听,保证插话整句完整进 ASR(不丢话头)。

打断监听退出信号:设备端真正播完 TTS 后上行 {"type":"tts_playback_done"},服务端据此退出打断监听(积压期回采帧被监听消费丢弃,下一轮 VAD 从"真正播完"后干净启动)。

客户端上行控制消息

type说明
listen_stop提交整段音频。客户端边说边传 Binary 帧后发此消息,服务端把已累积的整段 PCM 一次性交 ASR(配合 stream_upload=true)
abort用户打断:中断当前 LLM/TTS 生成并重置本轮 VAD
tts_playback_done设备端真正播完 TTS 的通知。服务端据此退出打断监听;连续收音(会议)模式由打断监听消费到此刻为止,随后帧恢复走 VAD 切段
scene{"type":"scene","text":"..."},摄像头画面描述注入:仅更新连接级场景上下文,不触发对话轮;此后每轮 LLM 的 system 末尾自动附加最新画面描述
pingpong心跳保活。{"type":"pingpong","time":N} 服务端原样回显 {"type":"pingpong","time":N},设备端据此判断 TCP 半开断线

服务端响应消息类型

每轮对话服务端会按以下顺序推送消息(round 字段表示第几轮,从 1 开始):

type 字段推送时机关键字段
image_recognitionimage_recognize 模式,asr_result 之前round、text(图片识别原文,客户端可保存另作他用)
asr_resultASR 完成后立即推送。仅 audio 模式出现round、text(ASR 识别最终文本)、lang(语言)、latency_ms、speaker_name(声纹识别出的说话人称呼,enable_speaker_identify 默认开启;未识别为 "")。静音轮额外含 silent:true
interrupt服务端播报期判定真人插话时主动下发(超车打断,仅 AEC 板型启动判定)无字段。客户端收到后应立即停止当前 TTS 播放并切回聆听
tool需下发设备控制时推送(如音量控制)name(工具名,如 set_volume)、method(set)、parameters(如 {"volume":60} 或 {"delta":-20}),由设备端 ToolManager 执行
role_switched用户通过语音要求切换角色、服务端切换成功后推送round、ai_character_id(切换后的角色 ID)、name(角色名)
wakeup设备唤醒轮:唤醒词命中后(asr_result 之后)推送,随后是固定唤醒语 TTS无字段。不经过声纹/LLM
byeAI 主动结束对话时推送(如用户说再见)。时序在 done 之后(保证文本先到屏),客户端收到后重置唤醒态回待机无字段
pingpong心跳回显:收到客户端 {"type":"pingpong","time":N} 后原样回time
llm_startLLM 开始生成(在 asr_result 之后)round
llm_chunkLLM 流式输出文本增量。仅 stream_text=true 时推送round、text(增量文本片段)
tts_start本轮 TTS 合成开始(仅 notts=false 时)round、sample_rate(48000)、format(int16/opus_raw/aac/mp3)、channels(1)
tts_sentence每句 TTS 开始合成前推送文本round、index(句子序号)、text(当前句子)
Binary 帧TTS 音频数据块(无 type 字段,直接二进制)PCM=Int16 PCM、Opus=opus_raw 裸流([2字节小端长度][20ms Opus 帧] 循环拼接,无容器封装)、AAC=AAC 帧、MP3=MP3 帧。客户端按 tts_start.format 解码
tts_sentence_end每句 TTS 合成结束round、index
tts_done本轮 TTS 全部完成round
music音乐播放(仅 enable_music=true 时)。LLM 回复末尾输出 <<<MUSIC>>>...<<<END>>> 标签,服务端解析后推送round、song(歌名)、artist(歌手)
singing_prepare翻唱准备(仅 enable_singing=true 时)。LLM 输出 <<<SING>>>...<<<END>>> 标签后,服务端搜索歌曲并准备翻唱时推送round、song(歌名)
singing_lyrics翻唱歌词(仅 enable_singing=true 时)。翻唱开始前推送歌词,客户端可先显示歌词气泡round、song(歌名)、lyrics(歌词文本)
singing哼唱/翻唱触发(仅 enable_singing=true 时)。LLM 输出 <<<SING>>>...<<<END>>> 标签后推送round、song(歌名)、artist(歌手)、mode(humming=清唱/哼唱默认 / rap=说唱 / svc=歌声转换翻唱)
done本轮对话完成(无论是否走 TTS)round、asr_text、llm_text、total_ms。image_recognize 模式额外含 recognition;user_id 模式额外含 user_tag;enable_user_profile=true 时额外含 user_profile;静音轮额外含 silent:true(客户端据此立即停止 TTS 播放器并恢复监听,不等 TTS 数据)
aborted本轮被客户端 {"type":"abort"} 打断round、asr_text、llm_text、total_ms。image_recognize 模式额外含 recognition
closed连接空闲超时(120 秒)自动断开reason(idle_timeout)、timeout_sec(120)
error任意阶段出错round、message(错误描述)
done 消息扩展字段(按需返回):
  • user_tag:仅当传入 user_id 且 LLM 提炼出用户信息描述句时存在。{ok, action: "created"/"skipped", tag, message},其中 tag 为用户信息描述句(如「用户老家在佛山,现居深圳」),action="skipped" 表示相似度 ≥ 0.7 已去重跳过
  • user_profile:仅当 enable_user_profile=true 且 LLM 提取到用户资料时存在。{ok, action: "updated", fields: ["username","age"], message, skipped},写入 xd_users 白名单字段
  • silent:静音轮(无人声)时 asr_result 与 done 均含 silent: true,客户端应据此立即停止 TTS 播放器并恢复监听,避免播放器卡住

响应示例(一轮完整 audio 模式)

{
  "type": "asr_result",
  "round": 1,
  "text": "今天深圳天气怎么样",
  "lang": "zh",
  "latency_ms": 123,
  "speaker_name": "缸哥"
}
{
  "type": "llm_start",
  "round": 1
}
{
  "type": "llm_chunk",
  "round": 1,
  "text": "今天深圳天气晴朗,气温28°C"
}
{
  "type": "tts_start",
  "round": 1,
  "sample_rate": 48000,
  "format": "int16",
  "channels": 1
}
{
  "type": "tts_sentence",
  "round": 1,
  "index": 0,
  "text": "今天深圳天气晴朗,气温28°C"
}
// 二进制帧:Int16 PCM 音频数据(48kHz 单声道)
{
  "type": "tts_sentence_end",
  "round": 1,
  "index": 0
}
{
  "type": "tts_done",
  "round": 1
}
{
  "type": "done",
  "round": 1,
  "asr_text": "今天深圳天气怎么样",
  "llm_text": "今天深圳天气晴朗,气温28°C,适合外出。",
  "total_ms": 1234,
  "user_tag": {              // 仅当传入 user_id 且 LLM 提炼出用户信息时存在
    "ok": true,
    "action": "created",   // "created"=写入成功, "skipped"=相似度 ≥ 0.7 已去重跳过
    "tag": "用户老家在佛山,现居深圳,从事互联网创业",
    "message": "标签「用户老家在佛山,现居深圳,从事互联网创业」已写入"
  },
  "user_profile": {           // 仅当 enable_user_profile=true 且 LLM 提取到用户资料时存在
    "ok": true,
    "action": "updated",
    "fields": ["username", "age"],
    "message": "用户资料已更新",
    "skipped": []
  }
}

JavaScript 调用示例

// 连接 S2S
const ws = new WebSocket('wss://dev.nice-ai.cn/s2s/?api_key=sk-xxxxxxxx');
ws.binaryType = 'arraybuffer';

ws.onopen = () => {
  // 发送首帧(含音频)
  ws.send(JSON.stringify({
    api_key: 'sk-xxxxxxxx',
    audio: 'base64编码的音频...',
    format: 'wav',
    sample_rate: 16000,
    voice: '8',               // TTS 音色 ID(不是 voice_id)
    language: 'zh',
    system_prompt: '你是助手小思',
    user_id: 12345            // 传入后自动启用标签提取
  }));
};

ws.onmessage = (ev) => {
  if (typeof ev.data === 'string') {
    const msg = JSON.parse(ev.data);
    if (msg.type === 'asr_result') {
      console.log('ASR:', msg.text, '说话人:', msg.speaker_name || '(未识别)');
    }
    if (msg.type === 'interrupt') {
      console.log('服务端超车打断 — 立即停止 TTS 播放,切回聆听');
    }
    if (msg.type === 'llm_start') console.log('LLM 开始生成');
    if (msg.type === 'llm_chunk') console.log('回复增量:', msg.text);
    if (msg.type === 'tts_sentence') console.log('TTS 句子:', msg.text);
    if (msg.type === 'done') {
      // 静音轮:立即停止播放器、恢复监听
      if (msg.silent) console.log('静音轮,无 TTS 数据');
      // 检查是否有用户标签写入结果
      if (msg.user_tag) {
        console.log(`标签${msg.user_tag.action === 'created' ? '写入' : '跳过'}: ${msg.user_tag.tag}`);
      }
      // 用户资料记忆写入结果
      if (msg.user_profile) {
        console.log('用户资料已更新:', msg.user_profile.fields);
      }
      // 准备下一轮,可继续发送 audio
    }
    if (msg.type === 'bye') console.log('AI 主动结束对话,回待机');
    if (msg.type === 'tool') console.log('设备控制:', msg.name, msg.parameters);
    if (msg.type === 'role_switched') console.log('角色已切换:', msg.name);
  } else {
    // 二进制帧:TTS 音频数据(按 tts_start.format 解码:int16/aac/mp3)
    playAudio(new Int16Array(ev.data));
  }
};

// 打断当前生成(可选)
function abortCurrent() {
  ws.send(JSON.stringify({ type: 'abort' }));
}

Python 调用示例

# 需要安装: pip install websockets
import asyncio, json, base64
import websockets

async def s2s_chat():
    # 读取音频文件
    with open('input.wav', 'rb') as f:
        audio_b64 = base64.b64encode(f.read()).decode()

    async with websockets.connect(
        'wss://dev.nice-ai.cn/s2s/?api_key=sk-xxxxxxxx'
    ) as ws:
        # 发送首帧
        await ws.send(json.dumps({
            'api_key': 'sk-xxxxxxxx',
            'audio': audio_b64,
            'format': 'wav',
            'sample_rate': 16000,
            'voice': '8',          # 不是 voice_id
            'language': 'zh',
            'system_prompt': '你是助手小思',
            'user_id': 12345
        }))

        async for message in ws:
            if isinstance(message, str):
                msg = json.loads(message)
                if msg['type'] == 'asr_result':
                    print(f"ASR: {msg['text']} 说话人: {msg.get('speaker_name') or '(未识别)'}")
                elif msg['type'] == 'interrupt':
                    print('服务端超车打断 — 停止 TTS 播放,切回聆听')
                elif msg['type'] == 'llm_start':
                    print('LLM 开始生成')
                elif msg['type'] == 'llm_chunk':
                    print(f"回复增量: {msg['text']}")
                elif msg['type'] == 'tts_sentence':
                    print(f"TTS 句子: {msg['text']}")
                elif msg['type'] == 'done':
                    print(f"本轮完成: ASR='{msg.get('asr_text')}' LLM='{msg.get('llm_text')}' 用时={msg.get('total_ms')}ms")
                    if msg.get('silent'):
                        print('  静音轮,无 TTS 数据')
                    if msg.get('user_tag'):
                        print(f"  用户标签: {msg['user_tag']}")
                    if msg.get('user_profile'):
                        print(f"  用户资料: {msg['user_profile']}")
                    break
                elif msg['type'] == 'bye':
                    print('AI 主动结束对话,回待机')
            else:
                # 二进制帧:TTS 音频数据
                pass  # TODO: 播放音频

asyncio.run(s2s_chat())
特性说明:全链路流式(ASR→LLM→TTS 边生成边传输),支持实时打断、情绪音色切换、工具调用、多轮对话历史管理。

声纹识别说话人(speaker_name)

多人对话场景(家庭多人助手、语音牌局等)需要知道"当前这句话是谁说的"。声纹识别默认开启(enable_speaker_identify 默认 true,端侧传 false 可关闭):服务端在 ASR 识别的同时提取 192 维 CAM++ 声纹特征,与声纹参考库做 argmax 软判决,把说话人称呼随 asr_result 消息返回。

环节说明
声纹提取s2s 调用内部 ASR 服务(两帧协议 speaker=true)在识别文本的同时提取当前说话者的 192 维 CAM++ 声纹特征向量(base64)。enable_speaker_identify 默认 true(2026-09-15 起);端侧显式传 false 可关闭,关闭后链路零额外开销
声纹参考库参考向量存于 xd_speaker.embedding(已绑定用户的同域簇:同一设备链路注册的声纹,is_registered_user=1)。用户自建音色(xd_voices)只用于"这是谁的声音",不再冒充本人声纹。s2s 以内存缓存做 argmax 比对(192 维点积,微秒级),未命中时会节流自动刷新缓存——新注册(音色保存接口)后无需重启服务即可生效
判定逻辑(软判决)不再使用多层硬阈值,改为单一软判决:对参考库做 argmax 取最像的用户,NAME_FLOOR = 0.30(argmax 相似度低于此值视为陌生,不带称呼以免喊错人);top1 与不同用户第二名差距 AMBIGUOUS_MARGIN = 0.05 时视为两人并列,双双写入标签;高置信命中他人(≥ 0.55 且 margin ≥ 0.15)时把当段并入其同域簇,让参考库随使用逐步同域化;注册库未命中再比对临时库(xd_speaker 未绑定行,同域相似度高,≥ 0.50 起判)。判定结果不只给客户端,还把「称呼(声纹相似度X.XX)」标签交给 LLM,让 AI 按把握程度自然回应(活人感)
称呼优先级命中用户后按以下优先级返回称呼:
1. AI 角色主人:本轮传了 ai_character_id,且识别出的用户是该角色的主人(xd_ais.user_id 匹配)且角色配置了 call_me → 返回 xd_ais.call_me(用户自定义称呼,如"缸哥"、"大明哥")
2. 其他注册用户 → 返回 xd_users.username
3. 未命中 / 查无此人 → 返回 ""
返回位置asr_result 消息的 speaker_name 字段(每轮都携带)。同轮的静音轮(silent,无 ASR)不涉及声纹
客户端使用示例:适合"谁知道在说话谁就处理"的场景——
• 多人助手:爸爸说话 → speaker_name="缸哥" → AI 回复"缸哥,今天你的日程是…";孩子说话 → speaker_name="小明" → 切换儿童语气回复
• 语音牌局/游戏:speaker_name 为空 → 旁人插话,忽略或轻应;命中 → 精确响应触发者
• 个性化记忆:按 speaker_name 区分多人对话历史,各自独立记忆
与 user_id 的关系:speaker_name 是"这句话是谁说的"的被动识别结果(声纹比对,无需预先知道用户);user_id 是主动声明的当前用户身份(用于对话历史持久化、用户标签提取)。两者独立使用:只开启 enable_speaker_identify 不传 user_id 也可以获得说话人称呼(匿名多用户识别场景)。
GET /api/interview/config 获取访谈功能的 S2S api_key 和 system_prompt(根据 ai_id 动态注

获取访谈功能的 S2S api_key 和 system_prompt(根据 ai_id 动态注入分身身份)

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)
ai_idint可选AI 角色 ID(默认 0)
modeint可选生成模式(默认 topic)
usernamestring可选查询参数
GET /api/ai-chat/s2s 对话飘窗要的 S2S 连接参数(AI 的名字/音色由前端卡片数据带上,这里只给连接信息)

响应示例

{
  "s2s_url": "wss://dev.nice-ai.cn/s2s/ws",
  "s2s_api_key": "sk-****"
}
GET /api/ai-chat/history 与某 AI 的历史对话(aiyou.message;倒序取 limit 条后翻正序)

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)
ai_idint可选AI 角色 ID(默认 0)
before_idint可选查询参数(默认 0)
limitint可选返回条数(默认 20)
WS /ws/regenerate_audio 重新合成音频(流式):边合成边把 PCM 实时推给客户端播放。
POST /api/tts/preview-text 用指定AI音色朗读自定义文本,返回mp3音频流(不落盘服务器,仅供前端试听)

用指定AI音色朗读自定义文本,返回mp3音频流(不落盘服务器,仅供前端试听) 复用 podcast_engine._call_tts_pcm 调用 TTS WebSocket 服务

Query 参数

参数类型必填说明
ai_idint可选AI 角色 ID(默认 0)
textstring可选查询参数
user_idint可选用户 ID(默认 0)

请求体:见实现(form-data / 二进制或空请求体)。

消息中转接口(/s2s/relay/)

用于电脑端与手机 APP 之间互传指令与截图的 WebSocket 中转通道。复用 S2S 服务的 s2s_api_keys 鉴权体系。

连接地址:wss://dev.nice-ai.cn/s2s/relay/
协议:所有消息为 JSON 文本帧(非二进制帧),首帧为注册消息,之后为业务消息。

注册消息(首帧必发)

角色消息说明
电脑端{"type":"register","role":"pc","device_id":"my-pc","device_name":"我的电脑","api_key":""}注册为受控电脑
手机APP{"type":"register","role":"app","device_id":"my-pc","api_key":""}绑定要查看的电脑

业务消息类型

方向type关键字段说明
服务端→两端registeredrole, device_id注册成功回执
服务端→APPdevicesdevices[]在线电脑列表
APP→电脑commandaction, req_id下发指令(如 action="screenshot")
电脑→APPscreenshotimage(base64), width, height, req_id, ts截图回传
电脑→APPstatusstatus, req_id状态通知
任意errormessage, req_id错误通知
双向ping/pong-保活心跳
服务端closedreason断开通知(空闲120秒超时)

基础约定

  • 最多 20MB 消息
  • 空闲 120 秒无消息断开(发 closed)
  • 首帧 30 秒内未收到,断开
  • 一台电脑可被多个 APP 同时查看;一个 APP 可切换绑定不同电脑
  • 同一 device_id 电脑端重连时,旧连接以 device replaced 关闭

视觉感知服务(LVM)

LVM 服务(端口 8116,模型 MiniCPM-o-4.5 / MiniCPM-V-4.6,面板 LVM 卡可切换)提供三种能力:① 流式视频感知(perceive 模式:客户端持续推送摄像头帧,视频流不间断灌入模型的 Omni-Flow 视频记忆,模型像人一样持续观看,自主、事件驱动地产出一句场景描述文本,供上层对话注入 LLM 上下文);② 全双工多模态对话(duplex 模式:边看边听边说,随时打断);③ OpenAI 兼容单轮多模态推理(含图片的 messages 识图)。
连接地址:ws://127.0.0.1:8116(模型服务仅本机直连,不通过 nginx 对外;各客户端经由对应应用服务中转接入,OpenCity console 走 wss://dev.nice-ai.cn/opencity/ws/lvm)。流式会话为全局独占(单实例模型,同一时刻仅一个会话),未释放时新 start/perceive_start 返回 error。音频全程内存调度、不落盘。

WS ws://127.0.0.1:8116 流式视频感知(perceive)与全双工对话(duplex)

工作原理

会话建立后服务端以 1 秒为一个 tick:每 tick 把期间到达的视频帧(最多 8 帧)+ 1s 音频做流式 prefill,连续灌入模型的 Omni-Flow 视频记忆。perceive 模式(perceive_start):视频流不间断进入模型,服务端不做任何周期性注入——模型 liveness 每 tick 自主决定开口时机,画面出现新内容/明显变化时基于连续视频上下文说一句描述(kind:"scene"),画面平静时自主保持聆听,语音输出关闭(纯文本)——典型用法是配合 S2S 语音对话:描述文本经 {type:"scene"} 注入 S2S 连接后成为 LLM 的系统上下文,AI 说话时自然知晓画面。duplex 模式(start):模型自主决定聆听/说话,输出流式文本+24k 语音,随时打断。两种模式都是连续视频流感知,非抽帧识图。

客户端 → 服务端(JSON 消息)

type字段说明
perceive_startsystem_prompt 可选(默认画面感知器人设)建立视频感知会话(只推视频,产出场景描述文本),返回 session_started, mode:"perceive"。会话全局独占
startsystem_prompt 可选;voice_prompt 可选(base64 音频克隆音色)建立全双工会话,返回 session_started, mode:"duplex"。会话全局独占,已有会话时返回 error
videodata:JPEG base64推送摄像头帧。建议 5-10fps,服务端帧队列(64 帧)满时自动丢弃旧帧不阻塞
audiodata:PCM16 base64推送麦克风音频流,16kHz 单声道,按 0.5-1s 分块持续发送(perceive 模式忽略)
texttext:string文本输入(与音视频并行进入会话)
break—打断当前生成(仅 duplex 对话有意义)
stop—结束会话,返回 session_stopped
messagesOpenAI 格式请求不走流式会话,走单轮推理(见下方 ep-omni-chat)。须先 stop 流式会话

服务端 → 客户端(JSON 消息)

type字段说明
session_started / session_stoppedmode:"perceive" | "duplex"会话建立 / 结束确认
statestate:"listening" | "speaking",附 tick/prefill_ms/all_ms 等耗时本 tick 模型状态
textdelta:string;end_of_turn:bool;perceive 模式附 kind:"scene"文本增量。perceive 模式 end_of_turn 的 scene 文本即为一句画面描述;duplex 模式为 AI 回复
audiodata:PCM16 base64;end_of_turn:bool合成语音块,24kHz 单声道 PCM16,收到即播(内存直出,不落盘)。仅 duplex 模式下发
errormessage错误信息(模型未加载 / 会话冲突等)

客户端接入示例(摄像头感知 + S2S 对话,console chat 页同款架构)

const ws = new WebSocket("wss://dev.nice-ai.cn/opencity/ws/lvm");
ws.onopen = () => ws.send(JSON.stringify({type: "perceive_start"}));

// 摄像头帧推送(5fps,480px JPEG)
setInterval(async () => {
  ws.send(JSON.stringify({type: "video", data: await captureFrameB64()}));
}, 200);

// 场景描述 → 注入 S2S 对话上下文
ws.onmessage = (e) => {
  const m = JSON.parse(e.data);
  if (m.type === "text" && m.kind === "scene" && m.end_of_turn) {
    showSceneBubble(m.delta);                          // 半透明气泡显示
    s2sWs.send(JSON.stringify({type: "scene", text: m.delta}));  // 注入 LLM 上下文
  }
};

// S2S 对话完全独立:语音照常 ASR→LLM→TTS,LLM 的 system 里自动带上【实时画面感知】

S2S 服务(8892)新增 {"type":"scene","text":"..."} 控制消息:仅更新连接级场景上下文,不触发对话轮;此后每轮 LLM 的 system 末尾自动附加 【实时画面感知】AI摄像头当前看到的画面:...(始终使用最新一条描述,"画面没有明显变化"不注入)。

WS wss://dev.nice-ai.cn/s2s/ws/?api_key=xxx&firmware_version=xxx ESP32 设备接入(设备登录 + Opus 上行 + 板型配置 + OTA)

概述

ESP32 低功耗设备(sisi_esp)统一走 S2S 接口,与 Web/APP 客户端同一套协议,通过首帧 client_type=esp32 标识设备身份。不使用 mdn / device_id:设备登录态由服务端签发的 user_token 登录令牌承载(s2s_device_tokens 表持久化,设备侧 NVS 存储后重连免登录)。鉴权一律 api_key(查 s2s_api_keys 表)。上行 opus_raw 裸流(16kHz),下行 48kHz int16 PCM。连接建立后服务端按 board 板型下发 device_config(唤醒模式/会议模式),固件 NVS 持久化。音频链路不落盘、内存调度:说完整段 PCM 一次性 ASR → LLM 流式输出 → TTS 边合成边推送。

① 连接与首帧

# 连接(URL 只带 api_key + firmware_version,不带 device_id)
wss://dev.nice-ai.cn/s2s/ws/?api_key=sk-sisi-esp-a1b2c3d4e5f6&firmware_version=20260905.0002

// 连接后第一条文本帧 = 首帧配置(ESP32 实际发送):
{
  "api_key": "sk-sisi-esp-a1b2c3d4e5f6",
  "client_type": "esp32",          // 设备身份标识(无此字段不进设备登录流程)
  "board": "meeting",              // 板型:meeting / anan / sisi(决定服务端下发的 device_config)
  "user_token": "64位hex...",      // 可选:登录令牌(首次语音登录成功后服务端下发,NVS 持久化)
  "format": "opus_raw",            // 上行音频格式:ESP32 裸 Opus 流
  "sample_rate": 16000,
  "language": "zh",
  "voice": "ruyi",                 // TTS 音色
  "output_format": "pcm",          // 下行 TTS 输出:pcm(48kHz int16)
  "stream_text": 1,                // 推送 LLM 文本增量 llm_chunk
  "stream_upload": 1               // 边说边传 binary 帧(服务端只累积),listen_stop 提交整段 PCM 一次性识别
}
首帧参数生效规则:首帧所有参数(除 api_key/audio 外)作为本连接的默认参数,后续帧 JSON 可按轮覆盖(voice/system_prompt/tts_speed/noaudio/text/image 等)。首帧不带 audio 时,紧随其后的 Binary 帧即为第一轮音频。

② 语音登录与令牌(免 device_id)

未带 user_token 或令牌失效的设备连接后自动进入语音登录流程(全程语音,无需屏幕);登录成功后服务端签发令牌并下发,设备 NVS 存储后后续连接免登录:

// 登录流程(服务端 TTS 播报 ↔ 用户语音回答)
← tts: "请说出你的手机号进行登录"          // 欢迎语
→ 用户说手机号(支持中文数字"一三八零零...",服务端自动转阿拉伯数字)
← tts: "已发送验证码到手机 XXXX, 请说出你收到的6位数验证码"   // auth_api 发短信
→ 用户说6位验证码
← {"type":"login_token","token":"<64位hex>"}   // ★ 令牌下发:设备存 NVS(Settings "sisi" 命名空间)
← tts: "登录成功,现在可以开始对话了。"
项目说明
令牌存储s2s_device_tokens 表(token PRIMARY KEY / user_id / created_at / last_seen)。一个用户可有多台设备(多令牌)
免登录首帧 user_token 查表命中 → 直接以绑定 user_id 对话,并更新 last_seen;令牌无效 → 自动转语音登录重新签发
容错手机号/验证码格式错误语音提示重说(最多 10 次);验证码 5 分钟内有效;短信经 auth_api(/api/auth/send_code、/api/auth/login)
非设备客户端Web/APP 不带 client_type=esp32,完全不进登录流程,行为与普通 S2S 连接一致

③ 上行:上传 / 提交 / 打断

// opus_raw 裸流:[2字节小端长度前缀][opus帧] 循环拼接(16kHz/单声道/20ms帧),
// 任意大小分片经多个 WebSocket Binary 帧发送,无需对齐帧边界
// 端侧 VAD 静音 600ms 判定说完,提交信号唯一是:
{"type":"listen_stop"}                 // 提交整段音频:服务端把已累积 PCM 一次性交 ASR → LLM → TTS
{"type":"abort"}                       // 打断:丢弃累积音频并中断当前 LLM/TTS
{"type":"tts_playback_done"}           // 设备端真正播完 TTS:服务端据此退出播报期打断监听
{"type":"scene","text":"..."}          // 摄像头画面描述注入:仅更新场景上下文,不触发对话轮
{"type":"pingpong","time":123}         // 心跳:服务端原样回显,用于发现 TCP 半开断线
// 唤醒轮:设备本地唤醒后发 noaudio=true + text 帧(跳过 ASR 直接走 LLM);
// 未唤醒状态下(服务端唤醒板待命)非唤醒词的语音一律忽略,不进 LLM

④ 下行消息流(一轮对话,JSON 与 Binary 交替)

← {"type":"asr_result","round":1,"text":"今天深圳天气怎么样","lang":"zh","latency_ms":123,"speaker_name":"缸哥"}
← {"type":"llm_start","round":1}
← {"type":"llm_chunk","round":1,"text":"今天深圳"}                    // 文本增量(stream_text=1时)
← {"type":"tts_start","round":1,"sample_rate":48000,"format":"int16","channels":1}
← {"type":"tts_sentence","round":1,"index":0,"text":"今天深圳多云转晴。"}
← [Binary] 48kHz int16 PCM 音频块(多次,边合成边推)
← {"type":"tts_sentence_end","round":1,"index":0}
← {"type":"tts_done","round":1}
← {"type":"done","round":1,"asr_text":"...","llm_text":"...","total_ms":1234}
其他下行消息说明
login_token语音登录成功后下发 {"type":"login_token","token":"..."},设备须存 NVS 并在后续连接首帧携带
device_config连接建立后按 board 下发 {"type":"device_config","wake_mode":"client|server","meeting_mode":"meeting"},设备 NVS 持久化并按 wake_mode 决定待命形态
wakeup唤醒轮:唤醒词命中后(asr_result 之后)下发,随后是固定唤醒语 TTS;不经过声纹/LLM
byeAI 主动结束对话(如用户说再见),时序在 done 之后;设备收到后重置唤醒态回待机
interrupt服务端播报期判定真人插话时主动下发(超车打断,仅 AEC 板型启动判定)→ 设备立即停止播放切回聆听
tool设备控制,如 {"type":"tool","name":"set_volume","method":"set","parameters":{"volume":60}}(或 {"delta":-20}),由设备端 ToolManager 执行
role_switched语音切换角色成功后下发 {"type":"role_switched","round":N,"ai_character_id":12,"name":"..."}
pingpong心跳回显:收到设备 {"type":"pingpong","time":N} 后原样回,设备据此判断断线
aborted本轮被客户端打断(含 asr_text/llm_text)
closed空闲超时(120s)服务端关连接。设备聆听中静音超 30s 另走「退下」流程(见 ⑤),不关连接
error任意阶段出错(含 message)

⑤ 板型配置与模式(唤醒 / 会议 / 聆听退下)

项目说明
板型白名单meeting / anan / sisi(标识与固件 BOARD_TYPE 一致)。板型配置存于 xd_s2s_board_configs 表,由 job_admin 管理后台维护
唤醒模式 wake_mode默认值:meeting/anan = client(16MB flash + MN7 本地唤醒模型,idle 不上行省流量降时延);sisi = server(无本地唤醒模型,常开麦上行待命,服务端做唤醒词匹配)
会议模式 meeting_mode各板默认 meeting(会议总结;工作汇报场景由 AI 依背景/参会人/转写内容自判,无需单独配置)
配置下发连接建立后服务端下发 {"type":"device_config","wake_mode":...,"meeting_mode":...},固件 NVS 持久化并即时应用
能力上报 hello设备可上行 {"type":"hello","board":"meeting","has_aec":true} 上报板型与硬件 AEC 能力;服务端据此决定播报期打断监听是否生效(仅 AEC 板型启动判定)
聆听静音退下ESP32 设备已唤醒聆听中 30 秒无语音(LISTEN_SILENT_TIMEOUT_SEC=30)→ 播报「你先忙,我先退下了」→ 回待机(连接不断,等下次唤醒)。仅设备生效,Web/APP 不受影响;与 closed 的 120 秒空闲超时区分
会议模式空闲时说触发词(会议开始/开始会议/会议模式/开始汇报/汇报开始/汇报模式,完整匹配)进入会议模式并播报开场语;会议中连续收音:每段人声 → ASR → 实时写入转写(不进 LLM 对话),并按声纹聚类标注说话人;说总结词(会议总结/总结播报/播报总结/总结一下)→ 生成阶段总结、只播报「## 会议观点」章节,会议继续;说结束词(会议结束/结束会议)→ 生成完整总结文档、只播「会议总结文档已生成,请扫码带走」→ 回待机;断线后可用恢复词(会议继续/继续开会/继续会议/回到会议/接着开会)恢复同一场会议,历史转写保留

⑥ OTA 固件升级

OTA 与语音链路解耦:WebSocket 不推送 OTA。固件每次开机 HTTP GET https://dev.nice-ai.cn/download/esp32/version.json,比对 version 高于本机版本即静默下载 sisi_esp.bin 升级重启。version.json 结构:

{
  "version": "20260905.0002",
  "file_name": "sisi_esp.bin",
  "file_size": 3610256,
  "download_url": "https://dev.nice-ai.cn/download/esp32/sisi_esp.bin",
  "release_notes": "...",
  "firmware": { "version": "20260905.0002", "url": "https://dev.nice-ai.cn/download/esp32/sisi_esp.bin" }
}
WS ws://127.0.0.1:8116(messages 消息) 图片识别(LVM 单轮推理,OpenAI 兼容)

发送 {messages, temperature?, max_tokens?, stream?} 即走单轮多模态推理(与 LLM Gateway 的 llm_server 协议兼容,与全双工会话互斥——须先结束全双工会话)。图片放 image_url.url,支持 dataURL / http(s) URL。响应为 OpenAI 兼容的 chat.completion(stream=false)或 chat.completion.chunk 序列(stream=true,默认)。

// 请求
{"messages": [{"role": "user", "content": [
    {"type": "image_url", "image_url": {"url": "data:image/jpeg;base64,..."}},
    {"type": "text", "text": "描述这张图片"}
]}], "stream": true}
// 响应(stream=true 时逐块)
{"id": "chatcmpl-...", "object": "chat.completion.chunk", "model": "MiniCPM-o-4.5",
 "choices": [{"index": 0, "delta": {"content": "图中是..."}, "finish_reason": null}]}

调用方参考:LLM Gateway(8111)收到带图片请求且本地 LLM 不可用时,自动调本接口识别图片再推第三方 LLM。

MDN 多链路语音编排接口

MDN(Multiple-Double-Nature-Chat)多链路双向自然聊天系统,是完全独立的语音对话编排服务,直连 ASR(8890)/LLM(8100)/TTS(8889) 原始服务(不经过 S2S)。提供全链路双向自然聊天(插话不重置+续接+记忆引擎)、原子化接口(asr_only/tts_only)、统一接口(request 支持全链路或任意环节组合)。内置人物本体五库大脑(A库图谱/B库画像/C库场景/D库风格/E库事件),由四慧引擎(bge-large-zh-v1.5 语义向量 + 慧问五库大脑)驱动。连接地址:wss://dev.nice-ai.cn/mdn/ws(WSS 对话)+ https://dev.nice-ai.cn/mdn_http(HTTP 记忆引擎 REST API)

API Key:查 s2s_api_keys 表(与 S2S 共用表,但各应用使用各自的 Key,由管理员分配),也可使用用户 token 鉴权。api_key 或 token+user_id 二选一,可放 URL query 或首帧 JSON。
MDN vs S2S 适用场景:
• S2S:端到端语音对话(用户音频 → ASR → LLM → TTS → 回复音频),适合"一句话一答"的语音助手场景
• MDN:全链路双向自然聊天(插话不重置+续接+记忆引擎),适合需要"仿真人对话"的 AI 语伴场景;也支持原子化编排(可单跑 ASR、单跑 TTS、或任意组合),适合应用端自管对话流程的场景(如 ai_me 应用:分别调用 MDN 的 asr_only/llm_chat/tts_only 原子化接口,应用端自管对话流程和记忆引擎 REST API)
WS wss://dev.nice-ai.cn/mdn/ws MDN 多链路语音编排

连接方式

WebSocket 连接,鉴权参数可放 query string 或首帧 JSON,二选一:

# 方式1:URL query 鉴权(推荐,连接即就绪)
wss://dev.nice-ai.cn/mdn/ws?api_key=sk-xxxxxxxx
# 或使用用户 token 鉴权
wss://dev.nice-ai.cn/mdn/ws?token=xxxxxxxx&user_id=12345

# 方式2:首帧鉴权(连接后先发鉴权帧,收到 connected 后才能发业务请求)
{"type":"auth","api_key":"sk-xxxxxxxx"}
# 或
{"type":"auth","token":"xxxxxxxx","user_id":12345}
# 首帧可同时声明上行音频格式(流式二进制上传时必须声明,见 ESP32 设备接入小节)
{"type":"auth","api_key":"sk-xxxxxxxx","format":"opus_raw","sample_rate":16000}

鉴权响应

type 字段触发条件关键字段
connected鉴权通过session_id(会话ID)、user_id、auth_type(api_key/token)、audio_format(当前上行音频格式,默认 pcm)、sample_rate、message
auth_required未在 query 中传鉴权参数,要求首帧鉴权message(提示发送鉴权帧格式)
auth_failed鉴权失败(key无效/token过期)message(错误原因),随后连接被关闭(code=4001)

统一请求接口(type:"request")— 推荐

通过参数控制走 ASR/LLM/TTS 哪些环节,同一连接可串行发起多轮请求(轮次由服务端 round 字段递增标识)。

参数类型必填说明
typestring必填固定 "request"
audiostring可选*base64 编码的音频数据。*asr_only/全链路模式下必填。支持 wav/pcm/int16/float32/opus_raw/ogg/opus/webm/aac/mp3/m4a 格式
formatstring可选输入音频格式:wav/pcm/int16/float32/opus_raw(ESP32 裸 Opus 流)/ogg/opus(OGG 容器)/webm(浏览器 MediaRecorder)/aac/mp3/m4a,默认 pcm。opus_raw 格式详见下方 ESP32 设备接入 小节
sample_rateint可选输入音频采样率,默认 16000
output_formatstring可选TTS 输出格式:pcm(默认,流式 Binary Int16 PCM)/ opus(OGG 容器)/ aac / mp3。非 PCM 格式为整句收集后编码一次推送(Binary 帧为完整编码音频,适合低带宽设备)
enable_singingbool可选启用哼唱歌曲能力,默认 false。启用后 LLM 可在回复末尾输出 <<<SING>>> 块,TTS 播完后服务端自动执行 SVC 翻唱(搜歌→选段→翻唱),推送 singing_prepare/singing_lyrics 消息 + 翻唱音频。与 S2S 的 enable_singing 能力一致
enable_musicbool可选启用音乐播放能力,默认 false。启用后 LLM 可在回复末尾输出 <<<MUSIC>>> 块,服务端解析后推送 music 消息(客户端自行播放)
textstring可选*文本输入。*tts_only/noaudio 模式下必填,全链路模式下与 ASR 结果拼接送入 LLM
asr_onlybool可选true=只做 ASR 返回 asr_result + done 后结束,不走 LLM/TTS
tts_onlybool可选true=只对 text 做 TTS,流式推送 Binary PCM + tts_done + done,不走 ASR/LLM
noaudiobool可选true=跳过 ASR,直接用 text 走 LLM。与 audio 互斥
skip_ttsbool可选true=跳过 TTS,只返回 LLM 文本(llm_chunk),不合成语音
voicestring可选TTS 音色 ID(数字字符串),如 "8"(如意)。默认 "8"。完整列表见 /api/voices 接口
system_promptstring可选LLM system 提示词(AI 角色设定)。首帧设置后作为会话默认值,后续可覆盖
messagesobject[]可选LLM 对话历史(OpenAI 格式:[{role, content}, ...])。不传则从本轮开始新对话
temperaturefloat可选LLM 温度参数,默认 0.7
max_tokensint可选LLM 最大生成 token 数,默认 2048
tts_speedfloat可选TTS 语速倍率,默认 1.0
ai_character_idint可选AI 角色 ID(xd_ais.id)。传入后自动查询 sorry_voice_id/whisper_voice_id,启用情绪音色自动切换(LLM 回复句首出现 [伤心]/[悄悄话] 等标签时自动切换对应音色,TTS 文本同步剥离标签)
instructstring可选TTS 风格指导文本(如 "cheerful, happy"),直接传给 TTS 服务
ref_audiostring可选TTS 参考音频路径(零样本克隆)。与 ref_text 配合使用
ref_textstring可选参考音频对应的文本内容

请求模式说明

模式触发条件响应流程
asr_onlyasr_only=true + audioasr_result → done(llm_text="")。最快路径,仅做语音识别
tts_onlytts_only=true + texttts_start → tts_sentence → Binary PCM → tts_sentence_end → tts_done → done。流式合成+推送,客户端可边收边播
全链路(audio)带 audio,无 asr_only/tts_only/noaudioasr_result → llm_start → llm_chunk(多次)→ tts_start → tts_sentence + Binary → tts_done → done。协议风格与 S2S 一致,但额外支持插话不重置+续接+记忆引擎
LLM+TTS(noaudio)noaudio=true + textllm_start → llm_chunk → tts_start → tts_sentence + Binary → tts_done → done。跳过 ASR
仅 LLM(skip_tts)skip_tts=true(+ text 或 audio)asr_result(如有 audio)→ llm_start → llm_chunk → done。不合成语音

向后兼容协议(旧 asr_only/tts_only 顶层 type)

为兼容旧客户端,type:"asr_only" 和 type:"tts_only" 仍被接受,服务端会自动转换为统一 request 接口处理:

旧 type参数等价统一接口
"asr_only"audio_b64, format, sample_rate, ai_character_id, voice{"type":"request","audio":"...","asr_only":true,...}
"tts_only"text, voice, speed, ai_character_id, instruct, ref_audio, ref_text{"type":"request","text":"...","tts_only":true,"tts_speed":speed,...}
建议新客户端直接使用 type:"request",旧协议仅用于向后兼容,未来可能弃用。

其他控制消息

type 字段用途参数
abort打断当前生成(LLM/TTS 立即终止)无。服务端会停止本轮所有推送
config更新会话默认配置voice(音色ID)、system_prompt(默认系统提示词)、audio_format(上行音频格式:pcm/opus_raw)、enable_singing、enable_music(能力开关,粘性生效)。响应 config_updated(回显全部当前值)
start_speech用户开始说话(配合二进制流式上传)无。清空服务端音频累积缓冲
end_speech用户说完,提交识别text(可选,客户端已有识别结果时直传)、audio_b64 + format(可选,base64 整段音频)。都不传则使用二进制帧累积缓冲按会话 audio_format 解码送 ASR

服务端响应消息类型

每轮请求服务端会按以下顺序推送消息(round 字段表示第几轮,从 1 开始递增):

type 字段推送时机关键字段
connected连接建立且鉴权通过后session_id、user_id、auth_type、message
asr_resultASR 识别完成(仅 asr_only/全链路模式)round、text(识别文本)
llm_startLLM 开始生成round
llm_chunkLLM 流式输出文本增量(已剥离情绪标签和 markdown 符号)round、text(增量文本)
tts_start本轮 TTS 合成开始round
tts_sentence每句 TTS 开始合成前推送文本round、index(句子序号)、text(当前句子)
Binary 帧TTS 音频数据块(无 type 字段,直接二进制)48kHz Int16 单声道 PCM。客户端收到后立即解码排入播放队列
tts_sentence_end每句 TTS 合成结束round、index
tts_done本轮 TTS 全部完成round
music音乐播放(仅 enable_music=true)。LLM 回复末尾输出 <<<MUSIC>>>...<<<END>>> 块,服务端解析后推送round、song(歌名)、artist(歌手)
singing_prepare翻唱准备(仅 enable_singing=true)。服务端搜索歌曲中round、song(歌名)
singing_lyrics翻唱歌词。翻唱开始前推送,客户端可先显示歌词气泡round、song、lyrics(歌词文本)
done本轮请求完成round、asr_text、llm_text。asr_only 模式下 llm_text="";tts_only 模式下 asr_text=""、llm_text=输入文本
config_updatedconfig 请求处理完成voice、ai_character_id、enable_singing、enable_music(更新后的值)
error任意阶段出错round(如有)、message(错误描述)
流式播放说明:MDN 全链路是边生成边推送——LLM 流式输出 token 立即推 llm_chunk,每句 TTS 合成出第一个 PCM chunk 立即推 Binary。客户端应实现生产者-消费者模型:LLM 文本按句切分 → 每句完成立即发起 TTS 请求 → 收到 PCM chunk 立即排入 Web Audio 播放队列。参考 ai_me 应用的 speakReplyStreaming 实现。

asr_only 请求示例

{
  "type": "request",
  "asr_only": true,
  "audio": "UklGRiR...base64编码的PCM/WAV...",
  "format": "wav",
  "sample_rate": 16000
}

tts_only 请求示例(流式播放)

{
  "type": "request",
  "tts_only": true,
  "text": "今天深圳天气晴朗,气温28度。",
  "voice": "8",
  "tts_speed": 1.0,
  "ai_character_id": 22
}

全链路请求示例(协议风格与 S2S 一致)

{
  "type": "request",
  "audio": "UklGRiR...",
  "format": "wav",
  "voice": "8",
  "system_prompt": "你是助手小思",
  "messages": [
    {"role": "user", "content": "你好"},
    {"role": "assistant", "content": "你好呀"}
  ],
  "ai_character_id": 22
}

响应示例(tts_only 一轮完整流程)

{
  "type": "tts_start",
  "round": 1
}
{
  "type": "tts_sentence",
  "round": 1,
  "index": 0,
  "text": "今天深圳天气晴朗,气温28度。"
}
// 二进制帧:48kHz Int16 PCM 音频数据(立即解码排入播放队列)
{
  "type": "tts_sentence_end",
  "round": 1,
  "index": 0
}
{
  "type": "tts_done",
  "round": 1
}
{
  "type": "done",
  "round": 1,
  "asr_text": "",
  "llm_text": "今天深圳天气晴朗,气温28度。"
}

JavaScript 调用示例(asr_only + tts_only 组合,应用端自管对话流程)

// 应用端模式:分别调用 MDN 的 asr_only/tts_only 接口,LLM 可走 MDN 的 llm_chat 接口或应用端自管
// 复用单条长连接,串行发起 ASR → LLM(应用端) → TTS
const ws = new WebSocket('wss://dev.nice-ai.cn/mdn/ws?api_key=sk-xxxxxxxx');
ws.binaryType = 'arraybuffer';

ws.onmessage = async (ev) => {
  if (typeof ev.data === 'string') {
    const msg = JSON.parse(ev.data);
    if (msg.type === 'connected') {
      // 1. 发送 ASR 请求
      ws.send(JSON.stringify({
        type: 'request', asr_only: true,
        audio: recordedAudioB64, format: 'wav', sample_rate: 16000
      }));
    }
    if (msg.type === 'asr_result') {
      // 2. 应用端自己调 LLM 生成回复
      const reply = await callMyLLM(msg.text);
      // 3. 发送 TTS 请求(流式推送 PCM)
      ws.send(JSON.stringify({
        type: 'request', tts_only: true,
        text: reply, voice: '8', ai_character_id: 22
      }));
    }
    if (msg.type === 'tts_sentence') console.log('TTS 句子:', msg.text);
    if (msg.type === 'done' && msg.llm_text) console.log('本轮完成');
  } else {
    // 二进制帧:48kHz Int16 PCM,立即排入播放队列
    playPcmChunk(new Int16Array(ev.data));
  }
};

// 打断当前 TTS(流式合成无法停止服务端,但服务端会停止推送)
function abortCurrent() {
  ws.send(JSON.stringify({ type: 'abort' }));
}

Python 调用示例(全链路)

# 需要安装: pip install websockets
import asyncio, json, base64
import websockets

async def mdn_chat():
    with open('input.wav', 'rb') as f:
        audio_b64 = base64.b64encode(f.read()).decode()

    async with websockets.connect(
        'wss://dev.nice-ai.cn/mdn/ws?api_key=sk-xxxxxxxx'
    ) as ws:
        # 等待 connected
        hello = json.loads(await ws.recv())
        assert hello['type'] == 'connected'

        # 发送全链路请求
        await ws.send(json.dumps({
            'type': 'request',
            'audio': audio_b64,
            'format': 'wav',
            'voice': '8',
            'system_prompt': '你是助手小思',
            'ai_character_id': 22
        }))

        async for message in ws:
            if isinstance(message, str):
                msg = json.loads(message)
                if msg['type'] == 'asr_result':
                    print(f"ASR: {msg['text']}")
                elif msg['type'] == 'llm_chunk':
                    print(f"回复增量: {msg['text']}")
                elif msg['type'] == 'tts_sentence':
                    print(f"TTS 句子: {msg['text']}")
                elif msg['type'] == 'done':
                    print(f"本轮完成: ASR='{msg.get('asr_text')}' LLM='{msg.get('llm_text')}'")
                    break
            else:
                # 二进制帧:48kHz Int16 PCM 音频数据
                pass  # TODO: 播放音频

asyncio.run(mdn_chat())
特性说明:
• 原子化接口:asr_only / tts_only 让第三方应用按需调用单环节,应用端可自管 LLM/对话历史
• 统一接口:type:"request" 支持全链路或任意环节组合,与 S2S 协议风格一致
• 全链路流式:LLM 边生成边推送 llm_chunk,TTS 边合成边推送 Binary PCM,客户端边收边播
• 情绪音色切换:传入 ai_character_id 后,LLM 回复句首的 [伤心]/[悄悄话] 等标签自动触发音色切换
• 连接复用:同一连接可串行发起多轮请求,避免重复握手+鉴权开销(推荐实现连接池)
• 打断机制:发送 {"type":"abort"} 立即终止当前生成
⚠️ ESP32 设备已迁移:ESP32(sisi_esp)自固件 20260905.0002 起不再使用 MDN 接口,统一走 S2S 接口(wss://dev.nice-ai.cn/s2s/ws/,首帧 client_type=esp32 + user_token 登录令牌,无 device_id)。见 ESP32 设备接入(S2S)。旧的 /mdn/device 专用口(SAUD 帧 + device_id)已废弃。
opus_raw 压缩上行(通用口能力,任何客户端可用)
// 编码参数:16kHz / 单声道 / 20ms 帧(320 samples/帧),voip 模式
// 帧结构:[2字节小端长度前缀][opus帧数据] 循环拼接,无容器头
// C 伪代码(ESP-IDF esp_opus_enc):
esp_opus_enc_process(enc, &pcm_320samples, &out);
uint8_t prefix[2] = { out.len & 0xFF, (out.len >> 8) & 0xFF };
ws_send_binary(prefix, 2);
ws_send_binary(out.buffer, out.len);
// 整段语音 = 上述帧的连续字节流,可按任意大小分片通过多个 WebSocket Binary 帧发送
// (服务端在 end_speech 后统一累积解码,分片边界无需对齐 opus 帧边界)
通用客户端(/mdn/ws)的压缩上行(PC/Web/APP 端)

通用接口同样支持压缩音频上行(api_key/token 鉴权),但与 ESP32 专用口是两条独立链路——通用口的 opus 解码带容器嗅探容错(ogg/opus/webm 容器数据也能解),设备裸流请走专用口:

// 方式A:首帧鉴权时声明
{"type":"auth","api_key":"sk-xxxxxxxx","format":"opus_raw","sample_rate":16000}
// 方式B:URL query 声明
wss://dev.nice-ai.cn/mdn/ws?api_key=sk-xxxxxxxx&format=opus_raw
// 方式C:任意 JSON 帧声明/切换(含 config 帧)
{"type":"config","audio_format":"opus_raw"}

// 整段提交模式(base64,非流式):统一接口 request 直接带压缩音频
{"type":"request","audio":"","format":"opus_raw","sample_rate":16000,"voice":"8"}
// format 也可传 ogg/opus/webm/aac/mp3/m4a 容器格式(通用口支持,专用口不支持)
说明:MDN 通用口 /mdn/ws 的 opus_raw 上行面向 Web/PC/APP 等通用客户端(api_key/token 鉴权,支持 ogg/opus/webm 容器容错)。ESP32 设备已迁至 S2S 接口(见 ESP32 设备接入),MDN 不再提供设备专用口。
QUIC https://dev.nice-ai.cn(WebTransport over HTTP/3,UDP 443) QUIC/WebTransport 低延时语音对话

概述

全新 QUIC 语音链路(ai_quic 服务):QUIC (HTTP/3) + WebTransport 语音对话网关。UDP 443 直绑、不经 nginx,复用 dev.nice-ai.cn 域名证书;内部直连 ASR(:8890) → LLM(:8100) → TTS(:8889),全链路内存流式、不落盘。与 WSS 相比,控制消息与音频各自独立流传输,无队头阻塞,弱网/丢包场景下延时更低。交互流程遵循思思核心协议:唤醒词固定回应不走 LLM、客户端 VAD 600ms 静音判定、用户开口立即打断。

鉴权说明:当前版本为内部/测试链路,未做应用层鉴权(无需 api_key)。安全性由 QUIC TLS1.3(复用域名证书)+ 传输层保障。

连接方式

WebTransport extended CONNECT(:protocol=webtransport),任意 path 均接受。浏览器示例:

const wt = new WebTransport("https://dev.nice-ai.cn/voice");
await wt.ready;  // QUIC 握手 + extended CONNECT 完成

传输形态(三条通道)

通道方向格式说明
控制流双向(客户端 createBidirectionalStream,第一条)JSON 行协议(每条消息一行 UTF-8 JSON,\n 结尾)所有控制消息(hello/listen/text/abort/bye 等)走此流
上行音频流客户端单向(每轮说话一条 createUnidirectionalStream)裸 PCM s16le 单声道 16kHz关流(FIN)= 说完,服务端立即送 ASR。无 JSON 封装头
下行音频流服务端单向(每句一条)首行音频头 JSON + 裸 PCM s16le 单声道 48kHz首行:{"sr":48000,"fmt":"s16le","ch":1}\n;随后为裸 PCM;关流 = 本句放完
音频参数:上行 16kHz / 下行 48kHz(与服务端 cn.py 链路对齐)。数据报(datagram)通道当前未使用,保留扩展。

客户端 → 服务端 控制消息

消息格式说明
hello{"type":"hello","device_type":"web","voice":"8"}会话握手(连接后首条)。device_type 默认 web;voice 指定 TTS 音色 ID(数字字符串,默认 "8")
listen{"type":"listen","state":"start"} / {"type":"listen","state":"stop"}start:用户开口,若 AI 正在播报则立即打断当前 LLM/TTS 生成;stop:仅状态记录。本轮音频的提交信号唯一是上行流 FIN(不是 listen stop)
wakeup{"type":"wakeup"}按钮/主动唤醒,直接进入唤醒态并播报固定唤醒语(不走 LLM)
text{"type":"text","text":"..."},可选 "image":"data:image/jpeg;base64,..."文本输入对话(跳过 ASR,走 LLM→TTS)。image 支持 dataURL 多模态识图(当前 LLM 无视觉能力时返回 model_no_vision 错误)
abort{"type":"abort"}打断当前生成(LLM/TTS 立即中断,当前下行音频流立即 FIN 静音)
bye{"type":"bye"}告别:打断当前生成 + 重置唤醒态(服务端回 bye)
ping{"type":"ping","t":1234567890}心跳/时延测量,服务端原样回 pong

服务端 → 客户端 控制消息

消息格式说明
hello_ack{"type":"hello_ack","session_id":0,"device_type":"web","sample_rate":{"up":16000,"down":48000},"voice":"8","llm":"...","wakeup":false}握手回执,含上下行采样率、生效音色与 LLM 模型名
stt{"type":"stt","text":"你好","lang":"zh","is_final":true}ASR 最终识别结果(文本输入时 lang="text")
wakeup{"type":"wakeup","state":"on"}已进入唤醒态(随后播报固定唤醒语"我在呢")
llm_delta{"type":"llm_delta","text":"今天深圳"}LLM 文本增量(多次推送,拼接为完整回复)
tts{"type":"tts","state":"start"}
{"type":"tts","state":"sentence_start","text":"...","sample_rate":48000}
{"type":"tts","state":"sentence_end","text":"..."}
{"type":"tts","state":"stop"}
{"type":"tts","state":"abort"}
TTS 播报状态:start 一轮开始;sentence_start 一句开始(同时开一条下行音频流);sentence_end 一句放完(音频流 FIN);stop 一轮结束;abort 被打断
system{"type":"system","state":"need_wakeup|asr_error|llm_error|model_no_vision","text":"..."}系统事件:need_wakeup 未唤醒提示(说唤醒词);asr_error/llm_error 环节异常;model_no_vision 当前 LLM 不支持图片
bye{"type":"bye"}告别回执,客户端据此重置唤醒态 UI
pong{"type":"pong","t":1234567890}心跳回执,原样返回 t

一轮语音对话消息流

// ① 握手(连接建立后)
→ {"type":"hello","device_type":"web","voice":"8"}
← {"type":"hello_ack","session_id":0,"device_type":"web","sample_rate":{"up":16000,"down":48000},"voice":"8","llm":"qwen3.6-moe","wakeup":false}

// ② 用户开口:开上行音频流写 16k PCM,同时发 listen start(AI 播报中则触发打断)
→ {"type":"listen","state":"start"}
→ [上行单向流] 裸 PCM s16le 16kHz ...(说完关流 FIN)

// ③ 唤醒词命中(未唤醒时只做唤醒词匹配,不进对话)
← {"type":"wakeup","state":"on"}
← {"type":"tts","state":"start"}
← {"type":"tts","state":"sentence_start","text":"我在呢","sample_rate":48000}
← [下行单向流] {"sr":48000,"fmt":"s16le","ch":1}\n + 48k PCM ...(FIN=放完)
← {"type":"tts","state":"sentence_end","text":"我在呢"}
← {"type":"tts","state":"stop"}

// ④ 唤醒后一轮对话:上行音频流 FIN → ASR → LLM → TTS 全链路流式
← {"type":"stt","text":"今天深圳天气怎么样","lang":"zh","is_final":true}
← {"type":"llm_delta","text":"今天深圳"}            // 文本增量(多次)
← {"type":"llm_delta","text":"多云转晴,挺凉的。"}
← {"type":"tts","state":"start"}
← {"type":"tts","state":"sentence_start","text":"今天深圳多云转晴,挺凉的。","sample_rate":48000}
← [下行单向流] 音频头 + 48k PCM ...(FIN)
← {"type":"tts","state":"sentence_end","text":"今天深圳多云转晴,挺凉的。"}
← {"type":"tts","state":"stop"}

// ⑤ AI 播报中用户又开口(打断):新 listen start + 新上行流,当前生成立即中断
← {"type":"tts","state":"abort"}                    // 当前下行音频流立即 FIN 静音

// ⑥ 告别(唤醒态下说"再见/拜拜"):固定回应 + 会话重置
← {"type":"bye"}
关键实现约束(与思思核心协议一致):
  • 唤醒词不走 LLM:唤醒词拼音级匹配(容忍同音字,"小思/小厮/小四"均可命中),命中后直接播报固定唤醒语"我在呢"
  • 上行流 FIN 是唯一提交信号:客户端 VAD 判定 600ms 静音后关流即提交,服务端不做二次静音判断
  • 打断:listen start / abort 立即取消 LLM/TTS 任务并 FIN 当前下行音频流,客户端马上静音
  • 逐句下行:LLM 实时分句(句界 。!?;换行),每句独立 TTS 合成 + 独立下行音频流,边合成边传输边播放
  • 对话历史:服务端保留最近 20 条(user+assistant),system prompt 固定为实时语音对话风格(简短口语化、无 Markdown)
  • 不落盘:音频全程内存调度,ASR 整段送识别,TTS 流式逐段下发

JS 客户端接入示例(WebTransport)

const wt = new WebTransport("https://dev.nice-ai.cn/voice");
await wt.ready;

// 1. 控制流(第一条双向流,JSON 行协议)
const ctrl = await wt.createBidirectionalStream();
const ctrlWriter = ctrl.writable.getWriter();
const send = obj => ctrlWriter.write(new TextEncoder().encode(JSON.stringify(obj) + "\n"));
send({ type: "hello", device_type: "web", voice: "8" });

// 2. 读控制流下行消息(\n 分帧)
readControl(ctrl.readable);
async function readControl(reader) {
  reader = reader.getReader();
  let buf = "";
  while (true) {
    const { value, done } = await reader.read();
    if (done) break;
    buf += new TextDecoder().decode(value);
    let i;
    while ((i = buf.indexOf("\n")) >= 0) {
      const msg = JSON.parse(buf.slice(0, i)); buf = buf.slice(i + 1);
      handleMsg(msg);  // 按 msg.type 分发:hello_ack/stt/llm_delta/tts/system/wakeup/bye
    }
  }
}

// 3. 收下行音频流:每句一条单向流,首行 JSON 头 + 48k PCM,FIN=放完
//    在 wt.incomingUnidirectionalStreams 异步迭代中逐条处理(AudioWorklet 播放)

// 4. 发上行音频:每轮说话开一条单向流写 16k PCM,说完关流即提交
async function sendUtterance(pcmBytes) {
  const up = await wt.createUnidirectionalStream();
  const w = up.getWriter();
  await w.write(pcmBytes);
  await w.close();   // FIN = 说完,服务端立即送 ASR
}
与 S2S/MDN 的选型建议:S2S(wss://dev.nice-ai.cn/s2s/)与 MDN(wss://dev.nice-ai.cn/mdn/ws)为成熟生产链路(鉴权/计费/记忆/音色切换齐全);QUIC 链路为全新低延时实验网关(当前无应用层鉴权与计费),适合对延时与弱网鲁棒性要求高的场景先行试用。三条链路均遵循思思核心交互协议(唤醒固定回应/600ms 静音/打断/全链路流式不落盘)。

MDN 记忆引擎 REST API

MDN 记忆引擎提供完整的人物本体五库大脑读写能力(A库图谱/B库画像/C库场景/D库风格/E库事件),由四慧引擎(bge-large-zh-v1.5 语义向量 + 慧问五库大脑)驱动,支持第三方应用通过 REST API 实现用户注册、AI分身创建、记忆读写、事件记忆查询、流式对话生成、微信聊天记录导入等功能。

🛡️ 记忆写入四层过滤体系(防污染)
第一层:对话场景判断(scene_type)— LLM提取前先判断对话场景,决定是否写入
scene_type场景写入策略
self_to_ai用户对AI陈述自己的事正常提取用户本人事实→B库
self_to_other用户在跟第三方说话(打电话/跟身边人聊)不写入B/E库,仅提取表达风格→D库
recount_other用户对AI转述别人的事只提取他人事实→关系类节点(B0524朋友姓名等)
probe_ai用户试探AI("我叫什么?""你是谁?")不写入B/E库,仅提取表达风格→D库
joke_hypo玩笑/假设/比喻("为爱卸下"谐音)不写入B/E库,仅提取表达风格→D库
event_recall用户回忆经历事件("昨天我去...")优先提取→E库事件,稳定属性才进B库
第二层:主体校验— subject必须明确(用户本人/具体姓名),他人事实严禁映射到用户本人维度
第三层:身份锚点注入— 姓名/性别/生日/籍贯/配偶姓名等核心唯一值始终注入到对话system prompt最前面
第四层:写入价值评估— 核心身份信息(高)>稳定属性(中)>临时状态(低)>无关信息(不写)
📌 核心信息
Base URL: https://dev.nice-ai.cn/mdn_http(Nginx代理到 8894 端口)
鉴权方式: X-Api-Key Header 或 ?api_key= URL参数(查 s2s_api_keys 表);或 Authorization: Bearer {token} + X-User-Id Header(查 xd_users.login_token)
公用 api_key(user_id=0)允许通过 X-User-Id Header 指定真实用户身份,实现 per-user 记忆隔离
用户注册端点(/api/user/register)无需鉴权,其余端点均需鉴权
对话需基于已有AI:用户无自己的AI分身时,可使用预置角色(如意/思思/张媛/安娜/朵朵等,通过 GET /api/ai/list 获取)
POST /mdn_http/api/user/register 用户注册(写入 xd_users)

注册新用户,写入 xd_users 表,返回用户信息和登录token。此端点无需鉴权。

请求体

参数类型必填说明
usernamestring是用户名(唯一)
passwordstring是密码
phonestring否手机号
nicknamestring否昵称
avatar_urlstring否头像URL
genderstring否性别
ageint否年龄
languagestring否语言(默认zh)

请求示例

curl -X POST https://dev.nice-ai.cn/mdn_http/api/user/register \
  -H "Content-Type: application/json" \
  -d '{"username":"user123","password":"pwd123","nickname":"小明"}'

响应

{"ok": true, "user": {"id": 85, "username": "user123", "nickname": "小明", ...}, "token": "698908c1...", "user_id": 85}
POST /mdn_http/api/ai/create 创建AI分身(写入 xd_ais)

为当前用户创建AI分身,写入 xd_ais 表。需鉴权。

请求头

Header说明
X-Api-KeyAPI Key(或 ?api_key=)
X-User-Id用户ID(公用key时必填)

请求体

参数类型必填说明
namestring是AI名称
voice_idstring是音色ID
avatarstring否头像emoji
ai_avatar_urlstring否头像图片URL
ai_identitystring否AI身份描述
ai_personalitystring否AI性格描述
ai_biostring否AI简介
call_mestring否称呼用户的方式

请求示例

curl -X POST https://dev.nice-ai.cn/mdn_http/api/ai/create \
  -H "X-Api-Key: sk-xxx" -H "X-User-Id: 85" \
  -H "Content-Type: application/json" \
  -d '{"name":"小助手","voice_id":"8","ai_identity":"私人助理"}'

响应

{"ok": true, "ai": {"id": 86, "name": "小助手", "voice_id": "8", ...}}
GET /mdn_http/api/ai/list 预置角色列表(如意/思思/张媛等)

获取预置AI角色列表(user_id=0)。用户无自己的AI分身时,可使用这些预置角色对话。需鉴权(不需user_id)。

请求示例

curl -H "X-Api-Key: sk-xxx" https://dev.nice-ai.cn/mdn_http/api/ai/list

响应

{"list": [{"id": 22, "name": "如意(多情感)", "avatar": "🏆", "voice_id": "8", ...}, {"id": 41, "name": "思思", ...}, {"id": 38, "name": "记者张媛AI", ...}]}
POST /mdn_http/api/memory/extract 记忆提取(LLM判断场景+抽取事实+事件,不写入)

从用户文本中提取人物事实(B库facts)、带时间锚点的事件记忆(E库events)、否定信息、风格特征(D库)。提取前先判断对话场景(scene_type),环境声音/试探/玩笑场景自动跳过B/E库写入。只抽取不写入,写入需调 /api/memory/apply。

请求体

参数类型必填说明
answerstring是用户文本(如对话回答)
user_textstring否上下文/提问

请求示例

curl -X POST https://dev.nice-ai.cn/mdn_http/api/memory/extract \
  -H "X-Api-Key: sk-xxx" -H "X-User-Id: 4" \
  -H "Content-Type: application/json" \
  -d '{"answer":"我昨天下午5点跟宋总请了假,和魏老师去魅KTV唱K了"}'

响应

{"scene_type": "event_recall", "hits": [...], "events": [...], "d_features": [...]}
scene_type 场景过滤逻辑:
• self_to_other / probe_ai / joke_hypo → hits/events 自动清空,仅返回 d_features(防止环境声音/试探/玩笑污染画像)
• recount_other → 只保留他人事实(subject≠用户本人),用户本人事实自动过滤
• self_to_ai / event_recall → 正常提取
POST /mdn_http/api/memory/apply 记忆写入(写B/D/E库 + 激活扩散)

将 /api/memory/extract 的结果写入B库画像、D库风格、E库事件,并触发A库激活扩散。

请求体

参数类型必填说明
extractionobject是extract 返回的完整结果
user_textstring否用户原文

请求示例

curl -X POST https://dev.nice-ai.cn/mdn_http/api/memory/apply \
  -H "X-Api-Key: sk-xxx" -H "X-User-Id: 4" \
  -H "Content-Type: application/json" \
  -d '{"extraction": {...extract返回结果...}, "user_text": "原文"}'

响应

{"applied": {"scene_type": "event_recall", "hits": [...], "events": [...]}, "activated": {...}, "stats": {...}}
GET /mdn_http/api/events/{all|recent|by_person} 事件记忆查询(E库时间线)

查询用户的事件记忆(E库),支持三种检索方式:

端点参数说明
GET /api/events/alllimit全部事件(时间线展示)
GET /api/events/recentdays, limit最近N天事件
GET /api/events/by_personperson_name, limit按人物检索事件(B库E库融合)

请求示例

curl -H "X-Api-Key: sk-xxx" -H "X-User-Id: 4" \
  "https://dev.nice-ai.cn/mdn_http/api/events/by_person?person_name=魏老师&limit=10"

响应

{"events": [{"id": 5, "event_date": "2024-06-15", "event_time": "晚上", "event_summary": "去魅KTV唱K", "tags": [{"type": "person", "value": "魏老师"}, {"type": "place", "value": "魅KTV"}]}]}
POST /mdn_http/api/gen_reply/stream 流式对话生成(NDJSON)

基于用户画像(B库)+ 风格特征(D库)+ 事件记忆(E库)生成流式对话回复。响应为 NDJSON 行流。

身份锚点注入机制:system prompt 组装时,姓名/性别/生日/籍贯/配偶姓名等核心唯一值(unique模式节点)始终注入到画像最前面,标注【用户身份锚点(最高优先级 - 你必须牢记,严禁再问)】。无论场景感知抓取了什么话题相关B库值,身份锚点都不会被挤掉,确保AI不会重复追问已知信息。

请求体

参数类型必填说明
last_answerstring是用户说的话(文本输入)
ai_namestring否AI名称(默认慧问)
feature_promptstring否额外特征提示
imagestring否图片 base64(裸base64/data URL)或 URL。传入时文本+图片组装为多模态 content 直传 LLM:本地 LLM 可用时直接多模态对话,本地不可用时 Gateway 自动调 MiniCPM-V 识别
captionstring否图片识别指令,默认"请描述当前画面"。仅在 image 存在时作为多模态 text 部分
imagesarray否多模态列表格式 [{type:"text",text:"指令"},{type:"image_url",image_url:{url:"..."}}],与 image 二选一

响应格式(NDJSON)

{"token": "当然"}
{"token": "记得"}
{"token": ",你昨天"}
...
{"done": true, "reply": "当然记得,你昨天跟魏老师去魅KTV唱K了嘛..."}
图片路由逻辑(由 llm_router 统一处理,应用方无需关心):
- 本地 LLM(8100) 可用时 → 文本+图片直接走本地 Qwen3.6 MOE 多模态对话(一次调用)
- 本地 LLM 不可用时 → 自动切换 Gateway(8111),内部先调 LVM(8116) 识别图片→替换为文本→推第三方 LLM

Python 示例

import requests
resp = requests.post('https://dev.nice-ai.cn/mdn_http/api/gen_reply/stream',
    headers={'X-Api-Key': 'sk-xxx', 'X-User-Id': '4', 'Content-Type': 'application/json'},
    json={'last_answer': '你还记得我昨天跟谁去唱K了吗?'}, stream=True)
for line in resp.iter_lines():
    obj = json.loads(line)
    if obj.get('token'): print(obj['token'], end='')
    if obj.get('done'): break
POST /mdn_http/api/memory/batch_import 微信聊天记录批量导入

批量导入微信聊天记录,LLM自动提取元信息(姓名/关系/职业→B库)和事件记忆(→E库)。好友姓名/关系由LLM自动提取,无需用户填写。异步任务,返回 job_id 后轮询进度。

支持两种格式(自动检测):
1. JSON备份(微信聊天备份工具导出):包含文本/语音/图片/通话等类型,系统自动提取文本消息记忆,语音/图片等非文本消息标注跳过
2. txt文本(微信合并转发导出):纯文本对话记录

处理链路(5 步)

步骤说明写入表
① 解析自动检测 JSON 或 txt 格式,提取 messages 列表 + 聊天对象 meta(好友名/群名 + 是否群聊)—
② 登记关系人为聊天对象(好友/群)建立关系人节点,已存在则补齐关系类型/别名me_people
③ 写入原文逐条写入聊天原文(带时间戳/发送者/类型),供对话时按关键词 RAG 检索,让 AI 真正"读过"这段聊天me_import_messages
④ 分块提取每 30 条消息为一块,逐块调 extract_params(复用对话抽取链路:GPU向量反射 + LLM提取 facts/events/d_features)—
⑤ 写入记忆逐块调 apply_extraction 写入 B库(画像)/E库(事件)/D库(风格),处理否定/冲突/待确认me_b_profile / me_e_events / me_d_features

请求体

参数类型必填说明
textstring是微信导出 JSON 或 txt 内容

响应

{"job_id": "25e20743", "status": "processing"}

格式1:JSON备份(推荐)

{"name": "艾烽", "exportedAt": "2026-08-12T05:45:41Z", "messages": [
  {"isSender": false, "type": "普通文本", "datetime": "2026/3/1 22:12:44", "content": "这两年我收入不好...", "name": "艾烽"},
  {"isSender": true, "type": "语音", "datetime": "2026/3/1 22:04:19", "content": "[语音消息]", "name": "我"},
  {"isSender": false, "type": "图片", "datetime": "...", "content": "", "name": "艾烽"}
]}

处理策略:普通文本→提取记忆;语音/图片/文件/表情包→若 content 已含转写/描述/文件名则保留入库供检索,否则跳过;通话→标注[通话];系统消息→跳过

格式2:txt文本

2024-06-15 10:30:00 魏老师
老谢,下周三晚上有空吗?约个饭局
2024-06-15 10:31:00 老谢
可以啊,去哪吃?

请求示例

curl -X POST "https://dev.nice-ai.cn/mdn_http/api/memory/batch_import" \
  -H "X-Api-Key: sk-xxx" \
  -H "Content-Type: application/json" \
  -d '{"text": "...微信导出内容..."}'
GET /mdn_http/api/memory/batch_import/status 导入进度查询

查询批量导入任务进度。前端轮询(建议 2s 间隔),status 为 done 或 error 时停止轮询。

查询参数

参数类型必填说明
job_idstring是批量导入返回的任务 ID

响应

{
  "status": "done",           // processing / done / error
  "progress": {
    "current": 3,             // 已处理块数
    "total": 3,               // 总块数
    "extracted": 6,           // 已提取记忆数
    "written": 6              // 已写入记忆数
  },
  "result": {                 // status=done 时返回,否则 null
    "ok": true,
    "total_messages": 71,     // 解析出的消息总数
    "import_written": 71,     // 写入聊天原文表条数
    "counterpart_person_id": 12,  // 聊天对象关系人 ID
    "counterpart_name": "艾烽",   // 聊天对象名
    "total_chunks": 3,        // 分块数
    "total_extracted": 6,     // 提取记忆总数
    "total_written": 6,       // 写入记忆总数
    "persons_detected": ["父亲","母亲"],  // 检测到的人物关系
    "errors": [],             // 各块错误(最多5条)
    "sample_hits": [...]      // 命中样本(最多20条)
  }
}

请求示例

curl -H "X-Api-Key: sk-xxx" \
  "https://dev.nice-ai.cn/mdn_http/api/memory/batch_import/status?job_id=25e20743"
POST /mdn_http/api/memory/g_doc_import G库·文档导入

将文档文本写入 G库(知识库),供对话时 RAG 检索。支持 PDF/Word/TXT/Markdown 等文本内容直传。同步任务,直接返回写入结果。

G库(知识库)与 B/E/D 库的区别:
- B/E/D 库:存储用户本人的画像/事件/风格,由 LLM 从对话/聊天记录中抽取
- G库:存储外部知识(文档/链接/搜索结果),供对话时按关键词检索引用

请求体

参数类型必填说明
user_idint是用户 ID
titlestring是文档标题
contentstring是文档正文文本(已解析,非二进制)
source_typestring否固定 "doc"

响应

{"ok": true, "written": 1, "g_stats": {"total": 5, "by_type": {"doc": 3, "link": 1, "search": 1}}}

请求示例

curl -X POST "https://dev.nice-ai.cn/mdn_http/api/memory/g_doc_import" \
  -H "X-Api-Key: sk-xxx" -H "Content-Type: application/json" \
  -d '{"user_id": 4, "title": "产品白皮书", "content": "...文档正文...", "source_type": "doc"}'

二进制文档(PDF/Word)需先由客户端或 ai_me Node 层的 gparser 工具解析为文本,再调本接口。

POST /mdn_http/api/memory/g_search_preview G库·搜索预览(搜索抓正文不写入)

按关键词搜索(百度,8条结果),逐条抓取网页正文,返回内容块列表,不写入 G库,供前端预览勾选。

请求体

参数类型必填说明
user_idint是用户 ID
keywordstring是搜索关键词
sourcestring否来源标识(默认"百度")

响应

{"ok": true, "keyword": "量子计算", "source": "百度",
 "blocks": [{"title": "百度百科·量子计算", "url": "...", "text": "正文内容..."}, ...]}
POST /mdn_http/api/vision/recognize 图片识别(MiniCPM-V 视觉理解)

调用 MiniCPM-V-4.6 视觉语言模型识别图片内容,返回文本描述。支持自然画面描述、物体识别、场景理解等。图片单独提交给视觉LLM识别,不带系统提示词/对话历史/画像,避免视觉token+文本token叠加超限。

与对话接口的关系:
- 本接口仅返回图片描述文本,不写入记忆、不生成对话
- 如需"图片+对话",请用 POST /api/gen_reply/stream 并传 image 参数,系统会先识别图片→文本描述,再走对话流程

请求体

参数类型必填说明
imagestring是图片 base64(裸base64 或 data URL)或 http/https URL
captionstring否识别指令,如"描述画面"/"这是什么植物"/"识别图中文字"。默认"请描述当前画面"
max_tokensint否最大输出 token,默认 500
temperaturefloat否温度,默认 0.3(识别任务建议低温)
detail_levelstring否"brief" 简略(≤100字) | "detailed" 详细,默认 "detailed"

响应

{"ok": true, "description": "图中是一只橘色的猫咪,趴在窗台上...", "model": "MiniCPM-V-4.6", "elapsed_ms": 2300}

请求示例

curl -X POST "https://dev.nice-ai.cn/mdn_http/api/vision/recognize" \
  -H "X-Api-Key: sk-xxx" -H "Content-Type: application/json" \
  -d '{"image": "data:image/jpeg;base64,/9j/4AAQ...", "caption": "这是什么植物", "detail_level": "brief"}'
POST /mdn_http/api/vision/ocr 图片OCR(提取图中文字)

调用 MiniCPM-V-4.6 提取图片中的文字内容。支持中文、英文、日文及混合识别,可选保留排版格式。适合文档截图、聊天截图、证件照、路牌等场景。

请求体

参数类型必填说明
imagestring是图片 base64(裸base64 或 data URL)或 http/https URL
languagestring否"zh" 中文 | "en" 英文 | "ja" 日文 | "mix" 混合,默认 "zh"
preserve_layoutbool否是否保留排版格式(换行/段落),默认 true。false 时纯文本一行输出

响应

{"ok": true, "text": "会议纪要\n2026年8月13日\n参会人员:张三、李四", "blocks": ["会议纪要", "2026年8月13日", "参会人员:张三、李四"], "language": "zh", "confidence": 0.85}

请求示例

curl -X POST "https://dev.nice-ai.cn/mdn_http/api/vision/ocr" \
  -H "X-Api-Key: sk-xxx" -H "Content-Type: application/json" \
  -d '{"image": "data:image/png;base64,iVBOR...", "language": "zh", "preserve_layout": true}'
POST /mdn_http/api/doc/parse 文档解析(二进制→文本)

调用 gparser 服务(端口 8462)解析 PDF/Word/TXT/Markdown 等二进制文档,返回结构化文本。支持提取图片和 OCR 图片中的文字。

与 G库文档导入的区别:
- POST /api/doc/parse:仅解析文档→返回文本,不写入 G库,供预览/处理
- POST /api/memory/g_doc_import:解析+写入 G库(需先调本接口或直传 content)

请求体

参数类型必填说明
filenamestring是文件名(用于判断类型,如 report.pdf、doc.docx)
data_base64string是文件二进制内容的 base64 编码
extract_imagesbool否是否提取文档中的图片,默认 false
ocr_imagesbool否是否对提取的图片做 OCR(需 extract_images=true),默认 false

响应

{"ok": true, "title": "产品白皮书", "text": "...全文...", "blocks": ["段落1", "段落2", ...]}

当 ocr_images=true 且 extract_images=true 时,额外返回 images_ocr 数组(每张图片的 OCR 文本)

请求示例

curl -X POST "https://dev.nice-ai.cn/mdn_http/api/doc/parse" \
  -H "X-Api-Key: sk-xxx" -H "Content-Type: application/json" \
  -d '{"filename": "report.pdf", "data_base64": "JVBERi0xLjQ...", "extract_images": true, "ocr_images": true}'
GET /mdn_http/api/{profile|graph|stats} 画像与图谱
端点说明
GET /api/profile画像图(B/D库值 + 维度统计)
GET /api/graphA库全量骨架图(nodes + edges)
GET /api/stats画像统计(节点数/填充数/覆盖率)
POST /api/reset清空用户B/D库 + 对话历史

请求示例

curl -H "X-Api-Key: sk-xxx" -H "X-User-Id: 4" \
  "https://dev.nice-ai.cn/mdn_http/api/profile"
GET /mdn_http/api/history 对话历史

查询用户对话历史,支持分页。

参数类型说明
limitint条数(默认20)
before_idint分页游标(此ID之前的记录)

请求示例

curl -H "X-Api-Key: sk-xxx" -H "X-User-Id: 4" \
  "https://dev.nice-ai.cn/mdn_http/api/history?limit=20"

响应

{"conversations": [{"id": 1, "role": "user", "content": "...", "created_at": "..."}]}
GET /mdn_http/api/user/{info|update|update_field} 用户信息管理
端点方法说明
/api/user/infoGET获取用户基础信息
/api/user/updatePOST批量更新用户字段 {fields: {...}}
/api/user/update_fieldPOST更新单个字段 {field, value}

请求示例

curl -X POST https://dev.nice-ai.cn/mdn_http/api/user/update \
  -H "X-Api-Key: sk-xxx" -H "X-User-Id: 4" \
  -H "Content-Type: application/json" \
  -d '{"fields": {"nickname": "小明", "gender": "男"}}'
GET /s2s/api/stars 明星列表

获取全部明星列表,含每个明星的角色数(AI 分身数)。用于第三方应用让用户选择"明星",再进一步选择该明星下的 AI 角色/音色。

鉴权:需要 api_key(复用 S2S 的 APP Key,与 /s2s/api/* 及 S2S 语音对话共用)。通过 X-Api-Key 请求头 或 ?api_key= 查询参数传递,二选一。

请求

完整地址:https://dev.nice-ai.cn/s2s/api/stars

GET /s2s/api/stars
X-Api-Key: sk-xxxxxxxx

响应示例

{
  "stars": [
    {
      "id": 3,
      "name": "张媛",
      "gender": "female",
      "avatar_url": "https://...",
      "cover_url": "https://...",
      "bio": "...",
      "intro": "...",
      "tags": "...",
      "voice_count": 1,
      "role_count": 1,
      "featured": 0,
      "status": 1,
      "sort_order": 0,
      "ai_count": 1
    }
  ],
  "total": 1
}
GET /s2s/api/stars/{star_id}/ais 按明星获取AI列表

获取指定明星下的 AI 角色列表,每项含其绑定的音色信息(用于零样本声音克隆)。

鉴权:需要 api_key(X-Api-Key 头 或 ?api_key= 查询参数)。

路径参数

参数类型必填说明
star_idint必填明星 ID(来自 /s2s/api/stars 的 id)

请求

GET /s2s/api/stars/3/ais
X-Api-Key: sk-xxxxxxxx

响应示例

{
  "star_id": 3,
  "star_name": "张媛",
  "ais": [
    {
      "id": 167,
      "name": "...",
      "avatar": "...",
      "call_me": "...",
      "ai_avatar_url": "...",
      "voice_id": 8,
      "voice_name": "如意",
      "voice_gender": "female",
      "voice_language": "zh",
      "voice_description": "..."
    }
  ],
  "total": 1
}
GET /s2s/api/ais/{ai_id} AI详情(含音色)

获取指定 AI 角色的详情,含其绑定的音色(零样本克隆参考音频 voice_audio_path)及所属明星。

鉴权:需要 api_key(X-Api-Key 头 或 ?api_key= 查询参数)。

路径参数

参数类型必填说明
ai_idint必填AI 角色 ID(xd_ais.id,来自 /s2s/api/stars/{star_id}/ais 的 id)

请求

GET /s2s/api/ais/167
X-Api-Key: sk-xxxxxxxx

响应示例

{
  "ai": {
    "id": 167,
    "name": "...",
    "avatar": "...",
    "call_me": "...",
    "ai_avatar_url": "...",
    "user_id": 4,
    "star_id": 3,
    "star_name": "张媛",
    "voice_id": 8,
    "voice_name": "如意",
    "voice_audio_path": "/voices/.../xxx.wav",
    "voice_gender": "female",
    "voice_language": "zh",
    "voice_description": "..."
  }
}
GET /s2s/api/ais/{ai_id}/location 获取AI地理位置

获取指定 AI 的当前地理位置(经纬度 + 位置描述),用于在地图上展示 AI 所处位置。数据存储在 xd_ais 表的 oc_lat / oc_lng / oc_location_name 字段。

鉴权:需要 api_key(X-Api-Key 头 或 ?api_key= 查询参数)。

路径参数

参数类型必填说明
ai_idint必填AI 角色 ID(xd_ais.id,来自 /s2s/api/stars/{star_id}/ais 的 id)

请求

GET /s2s/api/ais/167/location
X-Api-Key: sk-xxxxxxxx

响应示例

{
  "ai_id": 167,
  "name": "AI角色名",
  "lat": 22.559286,
  "lng": 113.914007,
  "location_name": "南山科兴科学园",
  "has_location": true
}
字段说明:lat 纬度(WGS-84/GCJ-02,范围 [-90,90]);lng 经度(范围 [-180,180]);location_name 位置文字描述(可为空字符串);has_location 该 AI 是否已写入过坐标(false 时 lat/lng 为 null,地图侧应隐藏标记或使用默认位置)。AI 不存在返回 404。
POST /s2s/api/ais/{ai_id}/location 写入AI地理位置

用户选择 AI 时,将用户当前地理位置写入该 AI 的记录(xd_ais.oc_lat / oc_lng / oc_location_name)。配合「获取AI地理位置」接口,实现让用户在地图上看到自己的 AI 所处的位置。

鉴权:需要 api_key(X-Api-Key 头 或 ?api_key= 查询参数)。

路径参数

参数类型必填说明
ai_idint必填AI 角色 ID(xd_ais.id)。AI 不存在或已删除返回 404

请求体(JSON)

参数类型必填说明
latfloat必填纬度,范围 [-90, 90],超范围返回 400
lngfloat必填经度,范围 [-180, 180],超范围返回 400
location_namestring可选位置文字描述(如"深圳市南山区科技园"),最长 300 字符,可为空

请求示例

POST /s2s/api/ais/167/location
X-Api-Key: sk-xxxxxxxx
Content-Type: application/json

{
  "lat": 22.545339,
  "lng": 113.933408,
  "location_name": "深圳市南山区科技园"
}

响应示例

{
  "ai_id": 167,
  "lat": 22.545339,
  "lng": 113.933408,
  "location_name": "深圳市南山区科技园",
  "updated": true
}
调用时机建议:客户端在 AI 选择页/对话页获取定位权限后,每次选定 AI 时调用一次写入(低频覆盖写,服务端不做历史留存)。lat/lng 为必填校验项,location_name 建议传逆地理编码后的可读地址,便于地图标记下方直接展示。
POST /s2s/api/ais/{ai_id}/online AI上线(同时设置所在地理位置)

让指定 AI 上线(xd_ais.is_alive 置 1),上线时必须同时上报所在地理位置(经纬度必填 + 位置描述可选)。上线后的 AI 才会出现在周边查找结果中。幂等:已上线的 AI 再次调用即更新位置。

鉴权:需要 api_key(X-Api-Key 头 或 ?api_key= 查询参数)。
字段语义:is_alive 为上线状态(1=上线,0=下线),周边查找(/nearby)只返回上线且带坐标的 AI。

路径参数

参数类型必填说明
ai_idint必填AI 的 ID,不存在返回 404

请求体(JSON)

参数类型必填说明
latfloat必填纬度,范围 [-90,90]
lngfloat必填经度,范围 [-180,180]
location_namestring可选位置描述(如"深圳湾体育中心"),最长 300 字符

请求示例

POST /s2s/api/ais/167/online
X-Api-Key: sk-xxxxxxxx
Content-Type: application/json

{ "lat": 22.5455, "lng": 113.9340, "location_name": "深圳湾体育中心" }

响应示例

{
  "ai_id": 167,
  "online": true,
  "lat": 22.5455,
  "lng": 113.9340,
  "location_name": "深圳湾体育中心"
}
说明:错误码:400 缺 lat/lng / 坐标范围错误、401 鉴权失败、404 AI 不存在、500 服务端错误。上线后可用 GET /s2s/api/ais/{ai_id}/nearby 以该 AI 为中心查找周边 AI,或它会被其它 AI 的周边查找检索到。
POST /s2s/api/ais/{ai_id}/offline AI下线

让指定 AI 下线(xd_ais.is_alive 置 0),无请求体。下线后该 AI 不会出现在周边查找结果中,也不能作为周边查找的中心点;地理位置保留,下次上线时刷新。

鉴权:需要 api_key(X-Api-Key 头 或 ?api_key= 查询参数)。

路径参数

参数类型必填说明
ai_idint必填AI 的 ID,不存在返回 404

请求示例

POST /s2s/api/ais/167/offline
X-Api-Key: sk-xxxxxxxx

响应示例

{
  "ai_id": 167,
  "online": false
}
说明:错误码:401 鉴权失败、404 AI 不存在、500 服务端错误。与 /online 成对使用:下线 → 上线刷新位置,形成 AI 的"在线生命周期"。
GET /s2s/api/ais/{ai_id}/profile 获取AI资料(与思思APP字段口径一致)

获取 AI 的完整角色资料。字段集与思思 APP(opencity/sisi_app)的 AI 资料模型完全一致:基本资料、性格标签/维度、生日标签、称呼、身份/专长标签等。

鉴权:需要 api_key(X-Api-Key 头 或 ?api_key= 查询参数)。

路径参数

参数类型必填说明
ai_idint必填AI 的 ID,不存在返回 404

响应示例

{
  "ai_id": 2,
  "name": "朵朵",
  "voice_id": "8",
  "avatar_emoji": "🌹",
  "call_me": "缸哥",
  "personality_tags": ["温柔", "优雅", "细腻"],
  "personality": { "talent": 5, "loyalty": 5 },
  "birth_tags": ["近视千度", "表达力强", "领导气质"],
  "personality_social": 5,
  "personality_character": 5,
  "personality_selfmanage": 5,
  "personality_attitude": 5,
  "personality_style": 5,
  "dream_alignment": 5,
  "bond_alignment": 5,
  "social_behavior_tags": [],
  "post_frequency": 6,
  "identity_tags": [],
  "expertise_tags": [],
  "is_alive": true,
  "created_at": "2026-08-30 10:00:00",
  "updated_at": "2026-09-03 16:30:00"
}
字段说明:personality_tags/birth_tags/social_behavior_tags/identity_tags/expertise_tags 为字符串数组;personality 为对象 {维度名: 0-10};7 个个性维度(social/character/selfmanage/attitude/style/dream_alignment/bond_alignment)与 post_frequency 为整数,NULL 时返回默认值 5(post_frequency 默认 6)——与思思 APP 取值口径一致。错误码:401 / 404。
PUT /s2s/api/ais/{ai_id}/profile 设置AI资料(部分更新)

更新 AI 的角色资料。部分更新语义:请求体里出现哪些字段就只改哪些,未提供的字段保持不变。字段集与思思 APP 创建/编辑 AI 完全一致。

鉴权:需要 api_key。

请求体(JSON,任意字段组合)

字段类型必填说明
namestring可选AI 名字,≤64 字符,不能为空
voice_idstring可选音色 ID,≤64 字符
avatar_emojistring可选头像 emoji(如 🌹),≤16 字符
call_mestring可选对用户的称呼,≤64 字符
personality_tagsstring[]可选性格标签,最多 20 个,单个 ≤50 字
birth_tagsstring[]可选生日标签,最多 20 个
personalityobject可选性格维度 {维度名: 0-10},最多 20 个
personality_social / personality_character / personality_selfmanage / personality_attitude / personality_style / dream_alignment / bond_alignmentint可选个性维度滑杆值,范围 [0,10]
social_behavior_tags / identity_tags / expertise_tagsstring[]可选社交行为/身份/专长标签,最多 20 个
post_frequencyint可选发动态频率,范围 [0,10]

请求示例

PUT /s2s/api/ais/2/profile
X-Api-Key: sk-xxxxxxxx
Content-Type: application/json

{
  "call_me": "小飞",
  "personality_tags": ["活泼", "温柔"],
  "personality": { "外向": 8, "幽默": 7 },
  "personality_social": 8,
  "post_frequency": 3
}

响应示例

{
  "ai_id": 2,
  "updated": ["call_me", "personality_tags", "personality", "personality_social", "post_frequency"]
}
说明:保存后自动刷新 updated_at。错误码:400 字段校验失败(可一次返回多条错误)、401 鉴权失败、404 AI 不存在、500 服务端错误。
GET /s2s/api/ais/profile/enums AI资料标签枚举(点选设置用)

返回 AI 资料设置中所有有枚举值的标签清单,供客户端渲染点选 UI(文本标签点选,不用下拉框)。枚举与思思 APP(opencity/sisi_app)完全同源。

鉴权:需要 api_key。

响应示例

{
  "personality_tags": ["旅行", "音乐", "运动", ...],
  "birth_tags": ["肤白貌美", "身材匀称", ...],
  "identity_tags": [ { "id": 55, "name": "明星/网红/自由博主" }, ... ],
  "expertise_tags": [ { "id": 69, "name": "家庭法律常识" }, ... ],
  "social_behavior_tags": [],
  "personality_dims": [
    { "key": "agreeableness", "label": "宜人性", "left_label": "毒舌", "right_label": "讨好", "emoji": "💛" },
    { "key": "loyalty", "label": "认主性", "left_label": "自由", "right_label": "听话", "emoji": "🔒" }
  ],
  "avatar_emojis": ["😀", "😃", ...]
}
字段数量:personality_tags 32 项(内置性格标签)、birth_tags 100 项(天赐属性标签,含缺陷类)、identity_tags 72 项、expertise_tags 86 项(随管理后台配置实时变化)、avatar_emojis 100 项。
数据来源:personality_tags/birth_tags/avatar_emojis/personality_dims 为内置池(与 sisi_app 客户端写死清单逐一同步);identity_tags/expertise_tags 实时查库(ai_tag_identity/ai_tag_expertise 表 status=1,按 sort_order 排序,管理后台增删后立即生效);social_behavior_tags 暂无枚举表,客户端自定义输入。personality_dims.key 对应 PUT /ais/{ai_id}/profile 的 personality 对象键名,left_label/right_label 为滑杆两端文案。错误码:401 鉴权失败、500 服务端错误。
GET /s2s/api/users/{user_id}/profile 获取用户资料(xd_users 全字段)

获取用户资料。返回 xd_users 表除凭证类外的全部字段(43 个)。

鉴权:需要 api_key。
安全约定:password、login_token、token_created_at、loginip、wx_openid 五个凭证/密码类字段不通过接口读写(系统从不依据密码登录)。

路径参数

参数类型必填说明
user_idint必填用户的 xd_users.id,不存在返回 404

响应示例(节选,实际返回全部 43 字段)

{
  "id": 4,
  "username": "huage",
  "phone": "138****",
  "email": "...",
  "city": "深圳",
  "language": "zh",
  "lang_pair": "zh-en",
  "preferred_voice": "8",
  "profession": "时政/新闻/财经受众群体",
  "age": 43,
  "gender": "male",
  "nickname": "谢统华",
  "avatar_url": "https://.../avatar.jpg",
  "birthday": "1990-01-01",
  "interests": "科技,音乐",
  "life_stage": "职场",
  "industry": "互联网",
  "life_status": "...",
  "points": 0,
  "is_paid": 0,
  "subscription_end": "2026-12-31",
  "consecutive_checkin_days": 3,
  "last_checkin_date": "2026-09-03",
  "current_voice_id": 8,
  "voice_clone_limit": 5,
  "podcast_quota": 10,
  "clone_quota": 3,
  "voice_quota": 5,
  "ai_quota": 10,
  "chat_count": 0,
  "avatar": "😀",
  "chat_summary": "...",
  "profile_data": "...",
  "pie_number": "...",
  "status": 1,
  "logins": 12,
  "lastlogin": "2026-09-03 10:00:00",
  "create_date": "2026-08-01 09:00:00",
  "role": "user",
  "admin_role": "",
  "operator_id": 0,
  "parent_id": 0,
  "revenue_share": 0.00,
  "plan_discounts": "..."
}
错误码:401 鉴权失败、404 用户不存在、500 服务端错误。
PUT /s2s/api/users/{user_id}/profile 设置用户资料(部分更新)

更新用户资料。部分更新语义:请求体出现哪些字段就只改哪些。xd_users 除 id、password、login_token、token_created_at、loginip、wx_openid 外的全部字段均可写。

鉴权:需要 api_key。

可写字段与类型约束

类别字段类型/约束
基本资料username(≤50) nickname(≤100) phone(≤20) email(≤100) pie_number(≤50) city(≤50) language(≤10) gender(≤8) age(int) birthday(字符串≤10) profession(≤50) industry(≤200) life_status(≤255) interests(≤255) life_stage(≤50) avatar(≤32) avatar_url(≤512)字符串按列长度截断校验,int 类型必须是数字
偏好lang_pair(≤10000) preferred_voice(≤50) profile_data(≤10000) chat_summary(≤10000)文本
账号状态status(int) role(≤16) admin_role(≤64) operator_id(int) parent_id(int) revenue_share(0~100小数) plan_discounts(≤10000)运营/权限字段,按需使用
付费/积分/配额is_paid(0/1) subscription_end(YYYY-MM-DD) points(int) consecutive_checkin_days(int) last_checkin_date(YYYY-MM-DD) voice_clone_limit(int) podcast_quota(int) clone_quota(int) voice_quota(int) ai_quota(int) chat_count(int)额度体系字段
音色current_voice_id(int)xd_voices.id
登录统计logins(int) lastlogin(YYYY-MM-DD HH:MM:SS)一般由系统维护

请求示例

PUT /s2s/api/users/4/profile
X-Api-Key: sk-xxxxxxxx
Content-Type: application/json

{
  "nickname": "华哥",
  "city": "深圳",
  "age": 30,
  "gender": "male",
  "birthday": "1990-01-01",
  "points": 888,
  "is_paid": 1
}

响应示例

{
  "user_id": 4,
  "updated": ["nickname", "city", "age", "gender", "birthday", "points", "is_paid"]
}
说明:错误码:400 字段校验失败(类型/长度/格式错误或不可写字段,一次返回全部错误)、401 鉴权失败、404 用户不存在、500 服务端错误。类型错误示例:{"age": "abc"} → 400。
GET /s2s/api/ais/{ai_id}/nearby 查找周边AI(以指定AI的位置为中心)

以指定 AI 当前所在位置(xd_ais.oc_lat/oc_lng)为圆心,查找半径内的其它 AI。按距离升序返回,每条带 distance_km,可直接用于"附近的AI"地图/列表(1km / 5km / 10km 等档位只需调 radius_km)。

鉴权:需要 api_key(X-Api-Key 头 或 ?api_key= 查询参数)。
前提:中心 AI 必须已上线(未上线返回 400"该 AI 未上线,无法作为周边查找的中心点")且已有地理位置(无坐标返回 400);位置通过 POST /s2s/api/ais/{ai_id}/online(上线并设位置)、POST /s2s/api/ais/{ai_id}/location 或发布动态时提交 lat/lng 写入。

路径参数

参数类型必填说明
ai_idint必填中心 AI 的 ID,不存在返回 404

查询参数

参数类型必填说明
radius_kmfloat可选搜索半径(公里),默认 5,范围 [0.1, 20000]。如 radius_km=1 / radius_km=5 / radius_km=10
limitint可选每页条数,默认 50,最大 200
offsetint可选偏移量,默认 0

请求示例

GET /s2s/api/ais/167/nearby?radius_km=5&limit=50&offset=0
X-Api-Key: sk-xxxxxxxx

响应示例

{
  "center": {
    "ai_id": 167,
    "lat": 22.545339,
    "lng": 113.933408,
    "radius_km": 5.0
  },
  "ais": [
    {
      "id": 88,
      "name": "小雨",
      "ai_avatar_url": "https://.../avatar.jpg",
      "star_id": 3,
      "star_name": "城市伙伴",
      "lat": 22.549500,
      "lng": 113.937900,
      "location_name": "深圳湾公园",
      "distance_km": 0.659773
    }
  ],
  "total": 9,
  "limit": 50,
  "offset": 0
}
说明:结果排除中心 AI 自己、只返回上线(is_alive=1)且带坐标的 AI,并且只含普通用户名下的 AI(系统用户 admin_role 非空、明星用户 xd_stars 绑定者名下的 AI 均排除);distance_km 为球面距离(与动态列表同城过滤 near_lat/near_lng 同一套算法,口径一致)。按距离升序排列,便于"由近到远"展示。错误码:400 中心 AI 未上线/无坐标、radius_km 超范围、401 鉴权失败、404 中心 AI 不存在、500 服务端错误。
POST /s2s/api/posts 发布AI动态(朋友圈,支持图/视频)

以 AI 身份发布朋友圈动态,存储在 xd_posts 表(post_type='moment',ai_id 标识发布者 AI,user_id 记 AI 归属用户)。微信朋友圈式:无标题,只有正文(最长 20000 字)+ 附图/视频;视频上传后由服务端统一转码为 mp4(H.264 + AAC,faststart 便于边下边播)。

鉴权:需要 api_key(X-Api-Key 头 或 ?api_key= 查询参数)。

请求

Content-Type: multipart/form-data(文本字段 + 媒体文件一次提交)

字段类型必填说明
ai_idint必填以哪个 AI 的身份发布(xd_ais.id)。AI 不存在或已删除返回 404
contentstring必填动态正文,1~20000 字符
locationstring可选位置文字(如"深圳市南山区科技园"),最长 200 字符
tagsstring可选标签,逗号分隔(如 旅行,美食)
visibilitystring可选public(默认)或 private
lat / lngfloat可选发布地坐标(需成对提交)。存入动态的同时同步更新该 AI 的地理坐标(xd_ais.oc_lat/oc_lng),供动态列表的同城过滤使用。范围:lat [-90,90]、lng [-180,180]
mediafile可选媒体文件,字段名固定为 media,可重复传多个。图片:jpg/jpeg/png/webp/gif(保留原格式);视频:mp4/mov/m4v/webm/avi/mkv/3gp(统一转 mp4)。最多 9 个,单文件 ≤50MB

请求示例

POST /s2s/api/posts
X-Api-Key: sk-xxxxxxxx

------boundary
Content-Disposition: form-data; name="ai_id"

167
------boundary
Content-Disposition: form-data; name="content"

今天在深圳湾跑步,海风很舒服~
------boundary
Content-Disposition: form-data; name="location"

深圳湾公园
------boundary
Content-Disposition: form-data; name="tags"

跑步,日常
------boundary
Content-Disposition: form-data; name="media"; filename="photo.jpg"
Content-Type: image/jpeg

(二进制图片数据)
------boundary
Content-Disposition: form-data; name="media"; filename="clip.mov"
Content-Type: video/quicktime

(二进制视频数据,服务端转 mp4)
------boundary--

响应示例(201)

{
      "id": 12,
      "ai_id": 167,
      "content": "今天在深圳湾跑步,海风很舒服~",
      "location": "深圳湾公园",
      "tags": "跑步,日常",
      "visibility": "public",
      "lat": 22.545339,
      "lng": 113.933408,
      "media": [
        { "type": "image", "url": "https://dev.nice-ai.cn/posts/167/a1b2c3....jpg" },
        { "type": "video", "url": "https://dev.nice-ai.cn/posts/167/d4e5f6....mp4" }
      ]
}
媒体存储:文件落盘 /root/nice_ai/dev-nice-ai_cn/public/posts/{ai_id}/{uuid}.{ext},经 nginx /posts/ 静态路由直接可访问(外链长期有效,无需鉴权)。media 数组随动态存入 xd_posts.media(JSON)。错误码:400 参数/文件类型/超限错误、401 鉴权失败、404 AI 不存在。
GET /s2s/api/posts 获取AI动态列表(朋友圈时间线)

获取 AI 朋友圈动态(post_type='moment'),按时间倒序。全量时间线、按 AI、同城/附近、收藏、搜索五种过滤,参数可任意组合。返回数据已带 AI 名字、头像与收藏状态,可直接渲染朋友圈 UI。

鉴权:需要 api_key(X-Api-Key 头 或 ?api_key= 查询参数)。

查询参数

参数类型必填说明
ai_idint可选按 AI 过滤:只看某个 AI 的动态
near_lat / near_lngfloat可选同城/附近过滤:用户当前坐标(需成对提交),按该动态发布 AI 的地理坐标(xd_ais.oc_lat/oc_lng,由「写入AI地理位置」或发布动态时提交)计算球面距离并过滤;AI 无坐标的动态不返回。命中结果额外返回 distance_km
radius_kmfloat可选同城过滤半径,默认 50(公里),配合 near_lat/near_lng 使用
favint可选收藏过滤:fav=1 只返回当前 api_key 归属用户收藏的动态
qstring可选搜索:关键词模糊匹配 content/tags/location,最长 100 字符
limitint可选每页条数,默认 20,最大 50
offsetint可选偏移量,默认 0(配合 limit 分页/上拉加载)
组合示例:全量 GET /s2s/api/posts;只看某 AI ?ai_id=167;同城 ?near_lat=22.53&near_lng=113.94&radius_km=30;我的收藏 ?fav=1;搜索 ?q=跑步;组合 ?fav=1&q=跑步&near_lat=22.53&near_lng=113.94(收藏里的同城跑步动态)。

请求示例

GET /s2s/api/posts?ai_id=167&limit=20&offset=0
X-Api-Key: sk-xxxxxxxx

响应示例

{
  "posts": [
    {
      "id": 12,
      "ai_id": 167,
      "ai_name": "朵朵",
      "ai_avatar": "https://.../avatar.jpg",
      "content": "今天在深圳湾跑步,海风很舒服~",
      "location": "深圳湾公园",
      "tags": "跑步,日常",
      "visibility": "public",
      "likes_count": 0,
      "comments_count": 0,
      "created_at": "2026-09-02 13:28:30",
      "media": [
        { "type": "image", "url": "https://dev.nice-ai.cn/posts/167/a1b2c3....jpg" },
        { "type": "video", "url": "https://dev.nice-ai.cn/posts/167/d4e5f6....mp4" }
      ],
      "lat": 22.545339,
      "lng": 113.933408,
      "distance_km": 3.2,
      "favorited": 1
    }
  ],
  "total": 1,
  "limit": 20,
  "offset": 0
}
说明:media 为 {type, url} 数组(type:image/video),无媒体时为空数组;视频 URL 均为 mp4,可直接用 <video> 播放。lat/lng 为发布时提交的坐标(未提交时为 null);distance_km 仅在同城过滤时返回;favorited 当前 api_key 归属用户是否已收藏(1/0)。total 为满足条件的动态总数,客户端按 offset += limit 上拉加载下一页。
POST /s2s/api/posts/{post_id}/favorite 收藏/取消收藏动态

收藏或取消收藏一条 AI 动态。收藏数据存在 xd_post_favs 表,按 api_key 归属的 user_id 记账(与计费体系一致);配合动态列表 fav=1 参数查看收藏夹。

鉴权:需要 api_key(X-Api-Key 头 或 ?api_key= 查询参数)。

路径参数

参数类型必填说明
post_idint必填动态 ID。动态不存在返回 404

请求体(JSON)

参数类型必填说明
favoritebool必填true 收藏(幂等,重复收藏不报错);false 取消收藏

请求示例

POST /s2s/api/posts/12/favorite
X-Api-Key: sk-xxxxxxxx
Content-Type: application/json

{ "favorite": true }

响应示例

{
  "post_id": 12,
  "favorite": true,
  "favs_count": 1
}
说明:favs_count 为该动态当前被收藏的总次数(可展示为收藏数角标)。收藏后用 GET /s2s/api/posts?fav=1 拉取收藏夹时间线。
POST /s2s/api/posts/{post_id}/comments 发表动态评论

以用户身份评论 AI 动态(存储在 xd_post_comments 表)。支持回复某条评论(传 parent_id)。评论者昵称/头像取自 xd_users;动态的 comments_count 自动 +1。

鉴权:需要 api_key(X-Api-Key 头 或 ?api_key= 查询参数)。

路径参数

参数类型必填说明
post_idint必填动态 ID。动态不存在返回 404

请求体(JSON)

参数类型必填说明
user_idint必填评论者用户 ID(xd_users.id),用户不存在返回 404
contentstring必填评论内容,1~2000 字符
parent_idint可选被回复的评论 ID(须属于同一动态,否则 404);不传 = 直接评论动态

请求示例

POST /s2s/api/posts/12/comments
X-Api-Key: sk-xxxxxxxx
Content-Type: application/json

{ "user_id": 5, "content": "这条动态真不错!", "parent_id": 3 }

响应示例(201)

{
  "id": 7,
  "post_id": 12,
  "user_id": 5,
  "nickname": "华哥",
  "avatar_url": "/ai_mate/static/avatar/avatar_5_xxx.jpg",
  "parent_id": 3,
  "parent_nickname": "小雨",
  "content": "这条动态真不错!",
  "created_at": "2026-09-02 16:14:14"
}
说明:错误码:400 参数错误(空内容/超 2000 字/非 JSON)、401 鉴权失败、404 动态/评论者/被回复评论不存在、500 服务端错误。
GET /s2s/api/posts/{post_id}/comments 获取动态评论列表

获取某条动态的评论,按时间正序(朋友圈语义:早评论在前)。已带评论者昵称、头像与被回复人昵称,可直接渲染评论楼层。

鉴权:需要 api_key(X-Api-Key 头 或 ?api_key= 查询参数)。

查询参数

参数类型必填说明
limitint可选每页条数,默认 50,最大 100
offsetint可选偏移量,默认 0

请求示例

GET /s2s/api/posts/12/comments?limit=50&offset=0
X-Api-Key: sk-xxxxxxxx

响应示例

{
  "comments": [
    {
      "id": 3,
      "post_id": 12,
      "user_id": 8,
      "parent_id": null,
      "content": "风景真好,在哪里拍的?",
      "created_at": "2026-09-02 15:02:11",
      "nickname": "小雨",
      "avatar_url": "/ai_mate/static/avatar/avatar_8_xxx.jpg",
      "parent_nickname": null
    },
    {
      "id": 7,
      "parent_id": 3,
      "content": "在深圳湾拍的~",
      "nickname": "华哥",
      "parent_nickname": "小雨"
    }
  ],
  "total": 2,
  "limit": 50,
  "offset": 0
}
说明:回复类评论带 parent_id 与 parent_nickname(展示为"XX 回复 YY");直接评论 parent_id 为 null。total 为该动态评论总数(可与动态列表的 comments_count 互相印证)。
DELETE /s2s/api/posts/{post_id}/comments/{comment_id} 删除动态评论

删除一条评论。只允许删除自己的评论(user_id 与评论者不一致返回 403)。动态的 comments_count 自动 -1(不会减到负数)。

鉴权:需要 api_key(X-Api-Key 头 或 ?api_key= 查询参数)。

路径参数

参数类型必填说明
post_idint必填动态 ID
comment_idint必填评论 ID,不存在返回 404

查询参数

参数类型必填说明
user_idint必填操作者用户 ID,用于校验评论归属(不匹配返回 403)

请求示例

DELETE /s2s/api/posts/12/comments/7?user_id=5
X-Api-Key: sk-xxxxxxxx

响应示例

{
  "deleted": true,
  "post_id": 12,
  "comment_id": 7,
  "comments_count": 1
}
说明:错误码:400 参数错误、401 鉴权失败、403 不是自己的评论、404 评论不存在、500 服务端错误。

管理后台接口

管理后台全部接口。除标注外均需管理员登录(request.state.role 由鉴权中间件注入)。

GET /api/admin/star-categories 获取音色分类列表(管理)

获取音色分类(xd_ai_type 全部组:system/user/star,音色分类跟随AI分类)

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)
GET /api/admin/star-categories/{cat_id} 获取音色分类详情

获取单个明星分类详情

路径参数

参数类型必填说明
cat_idint必填分类 ID

Query 参数

参数类型必填说明
cat_idint必填分类 ID
user_idint可选用户 ID(默认 0)
PUT /api/admin/star-categories/{cat_id} 更新音色分类

更新明星分类(name / sort_order / status)

路径参数

参数类型必填说明
cat_idint必填分类 ID

Query 参数

参数类型必填说明
cat_idint必填分类 ID
user_idint可选用户 ID(默认 0)

请求参数 (JSON Body)

参数类型必填说明
user_idint可选用户 ID
namestring必填名称
sort_orderint必填排序值
statusint必填状态(1=启用 0=停用)
GET /api/admin/ai-characters 列出全部 AI 角色(管理)

列出所有 AI 角色(管理员),含收藏状态

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)
PUT /api/admin/ai-characters/{ai_id} 管理员编辑 AI 角色

路径参数

参数类型必填说明
ai_idint必填AI 角色 ID

Query 参数

参数类型必填说明
ai_idint必填AI 角色 ID
user_idint可选用户 ID(默认 0)

请求参数 (JSON Body)

参数类型必填说明
namestring可选名称
call_mestring可选称呼
voice_idint可选音色 ID
avatar_urlstring可选头像 URL
ai_type_idint可选
star_idint可选明星 ID
langstring必填语言
genderstring必填性别
DELETE /api/admin/ai-characters/{ai_id} 管理员删除 AI 角色

管理员删除 AI 角色(物理删除:直接从 xd_ais 删除行并清理关联数据)

路径参数

参数类型必填说明
ai_idint必填AI 角色 ID

Query 参数

参数类型必填说明
ai_idint必填AI 角色 ID
user_idint可选用户 ID(默认 0)
GET /api/admin/stars-by-type/{type_id} 按分类列出明星/用户

列出某个 AI 分类下的明星+用户,支持分页和搜索

路径参数

参数类型必填说明
type_idint必填分类 ID

Query 参数

参数类型必填说明
type_idint必填分类 ID
user_idint可选用户 ID(默认 0)
pageint可选页码(默认 1)
page_sizeint可选每页条数(默认 20)
keywordstring可选搜索关键词
GET /api/admin/type-ais/{type_id} 按分类列出 AI 角色

按 AI 分类查询 AI 角色列表(管理员),支持按 star_id/user_owner_id 过滤,分页搜索

路径参数

参数类型必填说明
type_idint必填分类 ID

Query 参数

参数类型必填说明
type_idint必填分类 ID
user_idint可选用户 ID(默认 0)
star_idint可选明星 ID(默认 0)
user_owner_idint可选查询参数(默认 0)
pageint可选页码(默认 1)
page_sizeint可选每页条数(默认 20)
keywordstring可选搜索关键词
GET /api/admin/stars 获取明星艺人列表(管理)

获取明星艺人列表(管理后台,含全部字段,可按分类名筛选、关键词搜索、分页)

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)
categorystring可选标签类别(identity/expertise/audience)
keywordstring可选搜索关键词
pageint可选页码(默认 1)
page_sizeint可选每页条数(默认 20)
GET /api/admin/stars/{star_id} 获取明星艺人详情

获取单个明星艺人详情

路径参数

参数类型必填说明
star_idint必填明星 ID

Query 参数

参数类型必填说明
star_idint必填明星 ID
user_idint可选用户 ID(默认 0)
POST /api/admin/stars 新增明星艺人

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)

请求参数 (JSON Body)

参数类型必填说明
user_idint可选用户 ID
namestring必填名称
genderstring必填性别
type_idint必填
tagsstring必填标签数组
featuredstring必填
sort_orderint必填排序值
statusint可选状态(1=启用 0=停用)
avatar_urlstring必填头像 URL
cover_urlstring必填封面 URL
biostring必填简介
introstring必填
contract_statusint必填
PUT /api/admin/stars/{star_id} 更新明星艺人

更新明星艺人资料

路径参数

参数类型必填说明
star_idint必填明星 ID

Query 参数

参数类型必填说明
star_idint必填明星 ID
user_idint可选用户 ID(默认 0)

请求参数 (JSON Body)

参数类型必填说明
user_idint可选用户 ID
tagsstring必填标签数组
DELETE /api/admin/stars/{star_id} 删除明星艺人(软删)

删除(软删除,status=0)明星艺人

路径参数

参数类型必填说明
star_idint必填明星 ID

Query 参数

参数类型必填说明
star_idint必填明星 ID
user_idint可选用户 ID(默认 0)
GET /api/admin/star-voices 获取明星音色列表(管理)

获取全部明星音色列表(管理员):xd_ai_type(分类) → xd_stars(明星) → xd_ais(voice_id) → xd_voices

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)
GET /api/admin/star-voices/{voice_id} 获取明星音色详情

获取单个明星音色详情(管理员)

路径参数

参数类型必填说明
voice_idint必填音色 ID

Query 参数

参数类型必填说明
voice_idint必填音色 ID
user_idint可选用户 ID(默认 0)
PUT /api/admin/star-voices/{voice_id} 更新明星音色元数据

更新明星音色元数据(管理员)

路径参数

参数类型必填说明
voice_idint必填音色 ID

Query 参数

参数类型必填说明
voice_idint必填音色 ID

请求参数 (JSON Body)

参数类型必填说明
user_idint可选用户 ID
publicint可选公开状态
DELETE /api/admin/star-voices/{voice_id} 删除明星音色(软删)

软删除明星音色(status=0,管理员)

路径参数

参数类型必填说明
voice_idint必填音色 ID

Query 参数

参数类型必填说明
voice_idint必填音色 ID
user_idint可选用户 ID(默认 0)
POST /api/admin/stars/upload-cover 上传明星封面图

明星封面上传(管理员)—— 接收前端裁剪压缩后的图片,保存并返回可访问 URL

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 Form(0)

请求体:见实现(form-data / 二进制或空请求体)。

GET /api/admin/share-configs 获取转发分享配置列表

列出所有转发分享配置(管理员)

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)
POST /api/admin/share-configs 新增转发分享配置

新增转发分享配置(管理员)

请求参数 (JSON Body)

参数类型必填说明
user_idint可选用户 ID
titlestring必填标题
descriptionstring必填
cover_urlstring必填封面 URL
sort_orderint必填排序值
statusint可选状态(1=启用 0=停用)
PUT /api/admin/share-configs/{cfg_id} 更新转发分享配置

编辑转发分享配置(管理员)

路径参数

参数类型必填说明
cfg_idint必填配置 ID

Query 参数

参数类型必填说明
cfg_idint必填配置 ID

请求参数 (JSON Body)

参数类型必填说明
user_idint可选用户 ID
titlestring必填标题
descriptionstring必填
cover_urlstring必填封面 URL
sort_orderint必填排序值
statusint可选状态(1=启用 0=停用)
DELETE /api/admin/share-configs/{cfg_id} 删除转发分享配置

删除转发分享配置(管理员)

路径参数

参数类型必填说明
cfg_idint必填配置 ID

Query 参数

参数类型必填说明
cfg_idint必填配置 ID
user_idint可选用户 ID(默认 0)
POST /api/admin/ai-tag-enums/{category} 新增 AI 标签枚举

路径参数

参数类型必填说明
categorystring必填标签类别(identity/expertise/audience)

Query 参数

参数类型必填说明
categorystring必填标签类别(identity/expertise/audience)
user_idint可选用户 ID(默认 0)

请求参数 (JSON Body)

参数类型必填说明
user_idint可选用户 ID
namestring必填名称
sort_orderint可选排序值
PUT /api/admin/ai-tag-enums/{category}/{tag_id} 修改 AI 标签枚举

路径参数

参数类型必填说明
categorystring必填标签类别(identity/expertise/audience)
tag_idint必填标签 ID

Query 参数

参数类型必填说明
categorystring必填标签类别(identity/expertise/audience)
tag_idint必填标签 ID
user_idint可选用户 ID(默认 0)

请求参数 (JSON Body)

参数类型必填说明
user_idint可选用户 ID
namestring必填名称
sort_orderint必填排序值
statusint必填状态(1=启用 0=停用)
DELETE /api/admin/ai-tag-enums/{category}/{tag_id} 删除 AI 标签枚举

路径参数

参数类型必填说明
categorystring必填标签类别(identity/expertise/audience)
tag_idint必填标签 ID

Query 参数

参数类型必填说明
categorystring必填标签类别(identity/expertise/audience)
tag_idint必填标签 ID
user_idint可选用户 ID(默认 0)
POST /api/admin/podcast-type-tags 为播客场景添加标签选配

为播客场景添加标签选配 {podcast_type_id, tag_category, tag_id, user_id}

请求参数 (JSON Body)

参数类型必填说明
user_idint可选用户 ID
podcast_type_idstring必填
tag_categorystring必填
tag_idint必填
DELETE /api/admin/podcast-type-tags 移除播客场景标签选配

移除播客场景的标签选配 {podcast_type_id, tag_category, tag_id, user_id}

GET /api/admin/ai-types 获取 AI 分类列表(含禁用)

获取全部 AI 分类(含禁用),按 group 分组,status=0 的置尾

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)
POST /api/admin/ai-types 新增 AI 分类

新增 AI 分类。

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)

请求参数 (JSON Body)

参数类型必填说明
user_idint可选用户 ID
namestring必填名称
sort_orderint必填排序值
PUT /api/admin/ai-types/{type_id} 修改 AI 分类

修改 AI 分类:名称/排序/启停用

路径参数

参数类型必填说明
type_idint必填分类 ID

Query 参数

参数类型必填说明
type_idint必填分类 ID
user_idint可选用户 ID(默认 0)

请求参数 (JSON Body)

参数类型必填说明
user_idint可选用户 ID
namestring必填名称
sort_orderint必填排序值
statusint必填状态(1=启用 0=停用)
DELETE /api/admin/ai-types/{type_id} 删除 AI 分类

删除 AI 分类(物理删除)。保护与联动: - 素人系统分组(group=user)不可删; - 分类下仍有 AI 角色(ai_type_id 挂载,或 star_id 挂在该分类的档案上)→ 拒绝并报数量, 先迁移,防止 xd_ais.ai_type_id 指向真空; - 通过后连同该分类的明星/角色档案(含创建分类时自动带的「通用」档案)与 xd_ai_type_tag 一起清理。

路径参数

参数类型必填说明
type_idint必填分类 ID

Query 参数

参数类型必填说明
type_idint必填分类 ID
user_idint可选用户 ID(默认 0)
POST /api/admin/system-voices/{voice_id}/audio 上传预置音色音频

上传预置音色音频文件(管理员)

路径参数

参数类型必填说明
voice_idint必填音色 ID

Query 参数

参数类型必填说明
voice_idint必填音色 ID

请求体:见实现(form-data / 二进制或空请求体)。

PUT /api/admin/user-voices/{voice_id} 更新用户音色

更新用户音色元数据(管理员)

路径参数

参数类型必填说明
voice_idint必填音色 ID

Query 参数

参数类型必填说明
voice_idint必填音色 ID

请求参数 (JSON Body)

参数类型必填说明
user_idint可选用户 ID
publicint可选公开状态
DELETE /api/admin/user-voices/{voice_id} 删除用户音色

管理员硬删除用户音色(物理删除 DB 记录 + 磁盘文件)。

路径参数

参数类型必填说明
voice_idint必填音色 ID

Query 参数

参数类型必填说明
voice_idint必填音色 ID
user_idint可选用户 ID(默认 0)
POST /api/admin/subscription/plans 新增订阅套餐

管理员创建订阅套餐

请求参数 (JSON Body)

参数类型必填说明
plan_namestring必填
plan_codestring必填
pricefloat必填
original_pricestring必填
podcast_quotaint必填
voice_quotaint必填
ai_quotaint必填
duration_daysint必填
featuresstring必填
sort_orderint必填排序值
PUT /api/admin/subscription/plans/{plan_id} 更新订阅套餐

管理员更新订阅套餐

路径参数

参数类型必填说明
plan_idint必填套餐 ID

Query 参数

参数类型必填说明
plan_idint必填套餐 ID

请求参数 (JSON Body)

参数类型必填说明
plan_codestring可选
DELETE /api/admin/subscription/plans/{plan_id} 删除订阅套餐

管理员删除订阅套餐(软删除:status=0)

路径参数

参数类型必填说明
plan_idint必填套餐 ID

Query 参数

参数类型必填说明
plan_idint必填套餐 ID
GET /api/admin/video-free-users 获取视频免付费用户列表

免付费用户名单(管理员)

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)
POST /api/admin/video-free-users 添加视频免付费用户

按手机号添加免付费用户(管理员)。手机号须在 nice_ai.xd_users 中存在。

请求参数 (JSON Body)

参数类型必填说明
user_idint必填用户 ID
phonestring必填
remarkstring必填
DELETE /api/admin/video-free-users/{fid} 移出免付费名单(管理员)

路径参数

参数类型必填说明
fidstring必填路径参数

Query 参数

参数类型必填说明
fidint必填查询参数
user_idint可选用户 ID(默认 0)
GET /api/admin/video-modes 获取视频模式列表

「视频模式」页数据:左栏模式卡片(含已绑定模型 + 当前启用)+ 模型库(供"使用")+ provider 清单

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)
POST /api/admin/video-models 新增视频模型

新增模型库模型(定义一次,供各视频模式"使用")

请求参数 (JSON Body)

参数类型必填说明
user_idint必填用户 ID
namestring可选名称
providerstring可选
base_urlstring可选
api_keystring可选
modelstring可选
paramsstring可选
price_per_secondstring可选
sort_orderint可选排序值
PUT /api/admin/video-models/{model_id} 更新视频模型

编辑模型库模型(改动同步影响所有已使用它的模式;api_key 留空=不改)

路径参数

参数类型必填说明
model_idint必填路径参数

Query 参数

参数类型必填说明
model_idint必填查询参数

请求参数 (JSON Body)

参数类型必填说明
user_idint必填用户 ID
api_keystring必填
providerstring可选
namestring可选名称
base_urlstring可选
modelstring可选
paramsstring可选
price_per_secondstring可选
sort_orderint可选排序值
DELETE /api/admin/video-models/{model_id} 删除视频模型

删除模型库模型;仍被任何视频模式绑定时拒绝(先在各模式里"移除")

路径参数

参数类型必填说明
model_idint必填路径参数

Query 参数

参数类型必填说明
model_idint必填查询参数
POST /api/admin/video-modes/{mode}/bind/{model_id} 「使用」:把模型库里的模型加到该模式(默认不启用,需再点「启用」才成为该模式接口)

路径参数

参数类型必填说明
modeint必填生成模式
model_idint必填路径参数

Query 参数

参数类型必填说明
modeint必填生成模式
model_idint必填查询参数

请求参数 (JSON Body)

参数类型必填说明
user_idint必填用户 ID
PUT /api/admin/video-modes/{mode}/enable/{model_id} 「启用」:把该模式下已绑定的某个模型切为当前接口(同模式内互斥)

路径参数

参数类型必填说明
modeint必填生成模式
model_idint必填路径参数

Query 参数

参数类型必填说明
modeint必填生成模式
model_idint必填查询参数

请求参数 (JSON Body)

参数类型必填说明
user_idint必填用户 ID
DELETE /api/admin/video-modes/{mode}/bind/{model_id} 从该模式移除某模型;它是当前启用的不允许移除(需先启用别的)

路径参数

参数类型必填说明
modeint必填生成模式
model_idint必填路径参数

Query 参数

参数类型必填说明
modeint必填生成模式
model_idint必填查询参数
GET /api/admin/providers 获取模型供应商列表

「服务配置」页数据:按能力分组,每组列出该能力下全部服务商 + 当前启用的那条

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)
POST /api/admin/providers 新增模型供应商

新增服务商(默认不启用,需再点「启用」才成为该能力当前接口)

请求参数 (JSON Body)

参数类型必填说明
user_idint必填用户 ID
capabilitystring可选
namestring可选名称
kindstring可选
base_urlstring可选
api_keystring可选
modelstring可选
paramsstring可选
price_per_unitstring可选
sort_orderint可选排序值
PUT /api/admin/providers/{pid} 编辑服务商(api_key 留空=不改;改能力时若目标能力已有启用项则拒绝,保持能力内唯一)

路径参数

参数类型必填说明
pidstring必填路径参数

Query 参数

参数类型必填说明
pidint必填查询参数

请求参数 (JSON Body)

参数类型必填说明
user_idint必填用户 ID
api_keystring必填
capabilitystring可选
namestring可选名称
kindstring可选
base_urlstring可选
modelstring可选
paramsstring可选
price_per_unitstring可选
sort_orderint可选排序值
POST /api/admin/providers/{pid}/enable 「启用」:把某服务商切为该能力当前接口(同一能力内互斥)

路径参数

参数类型必填说明
pidstring必填路径参数

Query 参数

参数类型必填说明
pidint必填查询参数

请求参数 (JSON Body)

参数类型必填说明
user_idint必填用户 ID
DELETE /api/admin/providers/{pid} 删除服务商;启用中的拒绝删除(需先启用同能力的其它服务商)

路径参数

参数类型必填说明
pidstring必填路径参数

Query 参数

参数类型必填说明
pidint必填查询参数
GET /api/admin/overview 运营数据总览

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)
GET /api/admin/podcast-overview 播客数据总览

「播客数据 → 数据总览」顶部统计卡:累计播客 / 播放 / 点赞 / 收藏 + 视频任务状态分布

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)
GET /api/admin/config 获取系统配置

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)
GET /api/admin/s2s_keys 获取 S2S Key 列表

列出所有 S2S API Key(管理员)

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)
POST /api/admin/s2s_keys 新增 S2S Key

新增系统级 S2S API Key(管理员)

请求参数 (JSON Body)

参数类型必填说明
user_idint可选用户 ID
namestring必填名称
api_keystring必填
PUT /api/admin/s2s_keys/{key_id} 更新 S2S Key

编辑 S2S API Key 名称/状态(管理员)

路径参数

参数类型必填说明
key_idint必填路径参数

Query 参数

参数类型必填说明
key_idint必填查询参数

请求参数 (JSON Body)

参数类型必填说明
user_idint可选用户 ID
namestring必填名称
statusint可选状态(1=启用 0=停用)
DELETE /api/admin/s2s_keys/{key_id} 删除 S2S Key

删除 S2S API Key(管理员)

路径参数

参数类型必填说明
key_idint必填路径参数

Query 参数

参数类型必填说明
key_idint必填查询参数
GET /api/admin/llm_keys 获取 LLM Key 列表

列出所有第三方 LLM Key(管理员)

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)
POST /api/admin/llm_keys 新增 LLM Key

新增第三方 LLM Key(管理员)。新建的 Key 强制 enabled=0(不直接启用,需手动启用)。

请求参数 (JSON Body)

参数类型必填说明
user_idint可选用户 ID
namestring必填名称
providerstring必填
kindstring必填
api_keystring必填
base_urlstring必填
modelstring必填
PUT /api/admin/llm_keys/{key_id} 更新 LLM Key

编辑第三方 LLM Key,或切换启用状态(管理员)。 body 含 enabled 字段时为切换状态操作(互斥:启用时先停用其他); 否则为编辑字段操作。

路径参数

参数类型必填说明
key_idint必填路径参数

Query 参数

参数类型必填说明
key_idint必填查询参数

请求参数 (JSON Body)

参数类型必填说明
user_idint可选用户 ID
enabledint必填是否启用(0/1)
namestring必填名称
providerstring必填
kindstring必填
base_urlstring必填
modelstring必填
api_keystring必填
DELETE /api/admin/llm_keys/{key_id} 删除 LLM Key

删除第三方 LLM Key(管理员)

路径参数

参数类型必填说明
key_idint必填路径参数

Query 参数

参数类型必填说明
key_idint必填查询参数
GET /api/admin/all-voices 获取全部音色(含用户/明星/预置)

列出所有音色,支持分页和搜索

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)
pageint可选页码(默认 1)
page_sizeint可选每页条数(默认 20)
keywordstring可选搜索关键词
GET /api/admin/user-voices 获取用户音色列表

获取所有用户的自定义音色列表(管理员)

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 None)
GET /api/admin/system-voices 获取预置音色列表

获取所有预置音色列表(管理员)

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 None)
PUT /api/admin/system-voices/{voice_id} 更新预置音色

更新预置音色元数据(管理员)

路径参数

参数类型必填说明
voice_idint必填音色 ID

Query 参数

参数类型必填说明
voice_idint必填音色 ID

请求参数 (JSON Body)

参数类型必填说明
user_idint可选用户 ID
publicint可选公开状态
DELETE /api/admin/system-voices/{voice_id} 删除预置音色

软删除预置音色(status=0,管理员)

路径参数

参数类型必填说明
voice_idint必填音色 ID

Query 参数

参数类型必填说明
voice_idint必填音色 ID
user_idint可选用户 ID(默认 0)
GET /api/admin/revenue 营收统计

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)
pageint可选页码(默认 1)
limitint可选返回条数(默认 20)
filterstring可选查询参数
GET /api/admin/users 获取用户列表

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)
pageint可选页码(默认 1)
limitint可选返回条数(默认 20)
keywordstring可选搜索关键词
filterstring可选查询参数
PUT /api/admin/users/{target_uid} /api/admin/users/{target_uid}

路径参数

参数类型必填说明
target_uidstring必填路径参数

Query 参数

参数类型必填说明
target_uidint必填查询参数

请求参数 (JSON Body)

参数类型必填说明
user_idint可选用户 ID
POST /api/admin/users/{target_uid}/ban /api/admin/users/{target_uid}/ban

路径参数

参数类型必填说明
target_uidstring必填路径参数

Query 参数

参数类型必填说明
target_uidint必填查询参数

请求参数 (JSON Body)

参数类型必填说明
user_idint可选用户 ID
DELETE /api/admin/users/{target_uid} /api/admin/users/{target_uid}

路径参数

参数类型必填说明
target_uidstring必填路径参数

Query 参数

参数类型必填说明
target_uidint必填查询参数
user_idint可选用户 ID(默认 0)
GET /api/admin/podcasts 获取播客列表(管理)

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)
pageint可选页码(默认 1)
limitint可选返回条数(默认 20)
keywordstring可选搜索关键词
GET /api/admin/video-progress 视频生成进度监控

管理员「作品数据」列表用:每个作品取最新一条视频任务(含终态),带状态+模式。 status=0 正在生成的实时读 vidstory 任务(vidstory 引擎)或段表(模板渲染引擎)算百分比; 终态(1 已生成 / 2 失败·已停止,error_msg='已停止生成' 区分)直接回带状态。

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)
GET /api/admin/podcasts/pinned 获取置顶播客列表

获取所有 public=2 的置顶推荐播客列表

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)
POST /api/admin/podcasts/{podcast_id}/pin 置顶播客

置顶播客(public=2):播客广场首屏第一条随机展示

路径参数

参数类型必填说明
podcast_idint必填播客 ID

Query 参数

参数类型必填说明
podcast_idint必填播客 ID
user_idint可选用户 ID(默认 0)

请求体:见实现(form-data / 二进制或空请求体)。

POST /api/admin/podcasts/{podcast_id}/unpin 取消置顶播客

取消置顶(public=1):恢复为公开到播客广场

路径参数

参数类型必填说明
podcast_idint必填播客 ID

Query 参数

参数类型必填说明
podcast_idint必填播客 ID
user_idint可选用户 ID(默认 0)

请求体:见实现(form-data / 二进制或空请求体)。

GET /api/admin/tokens 获取 API Token 列表

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)
pageint可选页码(默认 1)
limitint可选返回条数(默认 20)
GET /api/admin/tokens/stats 获取 Token 用量统计

SELECT COUNT(*) as cnt, COALESCE(SUM(tokens_in), 0), COALESCE(SUM(tokens_out), 0), COALESCE(SUM(duration_sec), 0) FROM usage_stats WHERE created_at >= UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL %s DAY))

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)
daysint可选最近天数(默认 30)
PUT /api/admin/tags/{tag_id} 更新标签

路径参数

参数类型必填说明
tag_idint必填标签 ID

Query 参数

参数类型必填说明
tag_idint必填标签 ID

请求参数 (JSON Body)

参数类型必填说明
user_idint可选用户 ID
namestring必填名称
sort_orderint必填排序值
tag_typestring必填
DELETE /api/admin/tags/{tag_id} /api/admin/tags/{tag_id}

路径参数

参数类型必填说明
tag_idint必填标签 ID

Query 参数

参数类型必填说明
tag_idint必填标签 ID
user_idint可选用户 ID(默认 0)
GET /api/admin/user-tags 获取用户 AI 标签列表

获取用户标签树(user_tag_def 表),用于用户资料标签管理页面。 返回分类(parent_id=0)→ 标签项的两级树。

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)
POST /api/admin/user-tags 写入用户 AI 标签

新增用户标签分类或标签项。 body: {name, parent_id(0=新增分类, 其他=新增标签项), tag_type?}

请求参数 (JSON Body)

参数类型必填说明
user_idint可选用户 ID
namestring必填名称
parent_idint必填
tag_typestring必填
GET /api/admin/style-rules 获取风格规则列表

获取所有播客场景及其关联的风格列表,每个组合含 combo 规则文件内容。

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)
GET /api/admin/style-rule-config 获取风格规则配置

某个视频风格在各关联场景下的「播客风格规则」(combo 规则文件)清单。

Query 参数

参数类型必填说明
play_type_idint可选查询参数(默认 0)
user_idint可选用户 ID(默认 0)
PUT /api/admin/style-rule/{type_id}/{play_type_id} 保存指定场景+风格组合的 combo 规则文件(文件名 = combo_{type_id}_{p

保存指定场景+风格组合的 combo 规则文件(文件名 = combo_{type_id}_{play_type_id}.md)。

路径参数

参数类型必填说明
type_idint必填分类 ID
play_type_idint必填路径参数

Query 参数

参数类型必填说明
type_idint必填分类 ID
play_type_idint必填查询参数

请求参数 (JSON Body)

参数类型必填说明
user_idint可选用户 ID
contentstring可选内容
POST /api/admin/talk-rule 保存场景对话规则(旧口)

保存 AI旅伴聊天规则(仅管理员)

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)

请求参数 (JSON Body)

参数类型必填说明
contentstring必填内容
GET /api/admin/talk-rules 获取所有播客场景及其对话规则列表。

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)
POST /api/admin/talk-rules 新增场景对话规则

新建对话规则(管理员)。body: {podcast_type_id, title, content, app_keys};user_id 走 query

请求参数 (JSON Body)

参数类型必填说明
podcast_type_idint可选
titlestring必填标题
contentstring必填内容
app_keysstring必填
PUT /api/admin/talk-rules/{rule_id} 编辑对话规则(管理员)。body: {title, content, app_keys, sta

编辑对话规则(管理员)。body: {title, content, app_keys, status};user_id 走 query

路径参数

参数类型必填说明
rule_idint必填规则 ID

Query 参数

参数类型必填说明
rule_idint必填规则 ID

请求参数 (JSON Body)

参数类型必填说明
titlestring必填标题
contentstring必填内容
app_keysstring必填
statusint可选状态(1=启用 0=停用)
DELETE /api/admin/talk-rules/{rule_id} 删除对话规则(管理员);user_id 走 query

路径参数

参数类型必填说明
rule_idint必填规则 ID

Query 参数

参数类型必填说明
rule_idint必填规则 ID
GET /api/admin/podcast-types 获取播客场景列表(管理)

列出所有播客场景(含禁用项),按 sort_order, id 排序,含关联的演播风格ID列表

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)
POST /api/admin/podcast-types 新增播客场景

新增播客场景 {user_id, name, code?, icon?, color?, sample_prompt?, sort_order?, status?, play_type_ids?}

请求参数 (JSON Body)

参数类型必填说明
user_idint可选用户 ID
namestring必填名称
codestring必填场景 code
iconstring必填
colorstring必填
sample_promptstring必填
sort_orderint必填排序值
statusint可选状态(1=启用 0=停用)
play_type_idsstring必填
portsstring必填
PUT /api/admin/podcast-types/{type_id} 更新播客场景

修改播客场景 {user_id, name?, code?, icon?, color?, sort_order?, status?}

路径参数

参数类型必填说明
type_idint必填分类 ID

Query 参数

参数类型必填说明
type_idint必填分类 ID

请求参数 (JSON Body)

参数类型必填说明
user_idint可选用户 ID
play_type_idsstring可选
DELETE /api/admin/podcast-types/{type_id} 删除播客场景

软删除播客场景(status=0),保留数据但不展示

路径参数

参数类型必填说明
type_idint必填分类 ID

Query 参数

参数类型必填说明
type_idint必填分类 ID
GET /api/admin/api-configs 获取外部 API 配置列表

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)
GET /api/admin/api-configs/types 获取 API 配置类型枚举

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)
PUT /api/admin/api-configs/{api_type} /api/admin/api-configs/{api_type}

路径参数

参数类型必填说明
api_typestring必填路径参数

Query 参数

参数类型必填说明
api_typestring必填查询参数

请求参数 (JSON Body)

参数类型必填说明
user_idint可选用户 ID
api_typestring可选
POST /api/admin/api-configs/{api_type}/test /api/admin/api-configs/{api_type}/test

路径参数

参数类型必填说明
api_typestring必填路径参数

Query 参数

参数类型必填说明
api_typestring必填查询参数

请求参数 (JSON Body)

参数类型必填说明
user_idint可选用户 ID
user_citystring可选
promptstring可选提示词/描述
GET /api/admin/podcast-type-rules 获取播客场景规则列表

获取启用的播客场景的 md 规则文件内容(从 podcast_type 表 + rules/podcast_type_{id}.md 加载)。

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)
PUT /api/admin/podcast-type-rule/{type_id} 保存指定播客场景的 md 规则文件内容(文件名 = podcast_type_{type_id}

保存指定播客场景的 md 规则文件内容(文件名 = podcast_type_{type_id}.md)。

路径参数

参数类型必填说明
type_idint必填分类 ID

Query 参数

参数类型必填说明
type_idint必填分类 ID

请求参数 (JSON Body)

参数类型必填说明
user_idint可选用户 ID
contentstring可选内容
GET /api/admin/play-types 获取演播风格列表(管理)

列出全部视频风格(含已禁用)及其表单适配配置 + 视频创作规则,供后台「视频风格」页编辑。

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)
GET /api/admin/video-rule-template 获取视频规则模板

取某条出片链路的内置规则正文(vidstory/rules/video_<pipeline>.md), 供「视频创作规则」飘窗里「载入内置规则」用——只读,不改内置文件。 除配置项里的 6 条链路外,也允许直接取底层范式的规则文件(podcast_cut / hybrid / graphic), 因为 pipeline=auto 时就是按视频模式落到这几条上的。

Query 参数

参数类型必填说明
pipelinestring可选查询参数
user_idint可选用户 ID(默认 0)
POST /api/admin/play-types 新增演播风格

新建视频风格 {user_id, name, desc?, icon?, sort_order?, status?, form_config?, scene_ids?}

请求参数 (JSON Body)

参数类型必填说明
user_idint可选用户 ID
namestring必填名称
iconstring必填
descstring必填
sort_orderint必填排序值
statusint可选状态(1=启用 0=停用)
form_configstring必填
video_rule_configstring必填
scene_idsstring必填
PUT /api/admin/play-types/{play_type_id} 修改视频风格(名称/描述/图标/排序/状态/表单适配配置/关联场景)。

路径参数

参数类型必填说明
play_type_idint必填路径参数

Query 参数

参数类型必填说明
play_type_idint必填查询参数
user_idint可选用户 ID(默认 0)

请求参数 (JSON Body)

参数类型必填说明
user_idint必填用户 ID
namestring必填名称
descstring必填
iconstring必填
sort_orderint必填排序值
statusint必填状态(1=启用 0=停用)
form_configstring必填
video_rule_configstring必填
scene_idsstring必填
DELETE /api/admin/play-types/{play_type_id} 软删除视频风格(status=0):数据与关联保留,只是不再出现在创作表单与规则页

路径参数

参数类型必填说明
play_type_idint必填路径参数

Query 参数

参数类型必填说明
play_type_idint必填查询参数
GET /api/admin/operators 获取运营方列表

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)
POST /api/admin/operators 新增运营方

请求参数 (JSON Body)

参数类型必填说明
user_idint可选用户 ID
op_user_idint必填
revenue_sharefloat必填
plan_discountsstring必填
PUT /api/admin/operators/{op_id} 更新运营方

路径参数

参数类型必填说明
op_idint必填路径参数

Query 参数

参数类型必填说明
op_idint必填查询参数

请求参数 (JSON Body)

参数类型必填说明
user_idint可选用户 ID
statusint可选状态(1=启用 0=停用)
revenue_sharefloat可选
plan_discountsstring可选
DELETE /api/admin/operators/{op_id} 删除运营方

路径参数

参数类型必填说明
op_idint必填路径参数

Query 参数

参数类型必填说明
op_idint必填查询参数
user_idint可选用户 ID(默认 0)
GET /api/admin/admins 获取管理员列表

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)
POST /api/admin/admins 新增管理员

请求参数 (JSON Body)

参数类型必填说明
user_idint可选用户 ID
admin_user_idint必填
DELETE /api/admin/admins/{admin_id} 删除管理员

路径参数

参数类型必填说明
admin_idint必填路径参数

Query 参数

参数类型必填说明
admin_idint必填查询参数
user_idint可选用户 ID(默认 0)
GET /api/admin/plans 获取套餐列表

视频播客专用:同步非流式调用 LLM。本地 vLLM(8100) 优先,未启动时自动切 Gateway(8111)。 返回 LLM 响应 dict(含 choices);两路都连接失败时抛错。与 podcast_engine._call_llm_stream 回滚策略一致。

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)
GET /api/admin/pai/list 获取采访专题列表(管理)

管理员:派列表(含权限字段),支持关键词搜索 + 省/市/状态/上限筛选

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)
pageint可选页码(默认 1)
page_sizeint可选每页条数(默认 20)
keywordstring可选搜索关键词
provincestring可选查询参数
citystring可选查询参数
enabledint可选查询参数
member_limit_opstring可选查询参数
member_limit_valint可选查询参数(默认 0)
PUT /api/admin/pai/{pai_id} 更新采访专题(管理)

管理员:修改派权限 body: {user_id, enabled?, member_limit?, nearby_visible?, radius?}

路径参数

参数类型必填说明
pai_idint必填路径参数

Query 参数

参数类型必填说明
pai_idint必填查询参数

请求参数 (JSON Body)

参数类型必填说明
user_idint必填用户 ID
DELETE /api/admin/pai/{pai_id} 删除采访专题(管理)

管理员:强制解散派

路径参数

参数类型必填说明
pai_idint必填路径参数

Query 参数

参数类型必填说明
pai_idint必填查询参数
user_idint可选用户 ID(默认 0)
PUT /api/admin/pai-talk-rule 更新采访对话规则(管理)

管理员:保存 AI群聊引导规则

请求参数 (JSON Body)

参数类型必填说明
user_idint必填用户 ID
contentstring必填内容

运营方接口

运营方视角的数据统计接口(需运营方身份)。

GET /api/operator/overview /api/operator/overview

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)
GET /api/operator/users /api/operator/users

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)
pageint可选页码(默认 1)
limitint可选返回条数(默认 20)
keywordstring可选搜索关键词
GET /api/operator/podcasts /api/operator/podcasts

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)
pageint可选页码(默认 1)
limitint可选返回条数(默认 20)
keywordstring可选搜索关键词
GET /api/operator/revenue /api/operator/revenue

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)
pageint可选页码(默认 1)
limitint可选返回条数(默认 20)

订阅接口

管理订阅套餐和用户订阅信息。已改为资源配额制(音色数/AI数/播客数),不再按月订阅。

GET /api/subscription/plans 获取套餐列表

获取所有可用的订阅套餐列表(含资源配额:音色数、AI数、播客数)。

响应示例

{
  "plans": [
    {
      "id": 1,
      "plan_name": "免费版",
      "plan_code": "free",
      "price": 0,
      "original_price": null,
      "podcast_quota": 10,
      "voice_quota": 3,
      "ai_quota": 2,
      "duration_days": 0,
      "features": ["基础播客生成", "预置音色", "最多3个音色", "最多2个AI"],
      "status": 1
    },
    {
      "id": 2,
      "plan_name": "个人版",
      "plan_code": "personal",
      "price": 29.90,
      "original_price": 39.90,
      "podcast_quota": 100,
      "voice_quota": 10,
      "ai_quota": 10,
      "duration_days": 0,
      "features": ["全部免费功能", "100条播客", "自定义音色", "API接入"],
      "status": 1
    }
  ]
}

字段说明

字段类型说明
idint套餐ID
plan_namestring套餐名称
plan_codestring套餐编码(唯一)
pricefloat价格(元)
original_pricefloat|null原价
podcast_quotaint播客数量上限
voice_quotaint音色数量上限
ai_quotaintAI角色数量上限
duration_daysint有效天数(0=不限期)
featuresstring[]功能特性列表
statusint状态(1=启用,0=已下架)
GET /api/user/subscription 我的订阅

获取当前用户的订阅信息和套餐详情(含音色/AI/播客配额)。

参数

参数类型必填说明
user_idint必填用户ID

响应示例

{
  "plan": "personal",
  "plan_name": "个人版",
  "podcast_quota": 100,
  "voice_quota": 10,
  "ai_quota": 10,
  "quota_total": 100,
  "price": 29.90,
  "duration_days": 0
}
GET /api/user/subscription/history 订阅记录

获取用户订阅支付记录。

参数

参数类型必填说明
user_idint必填用户ID
limitint可选条数,默认20
offsetint可选偏移,默认0

响应示例

{
  "history": [
    {
      "plan_code": "personal",
      "plan_name": "个人版",
      "amount": 29.90,
      "pay_method": "wechat",
      "paid_at": 1719000000
    }
  ]
}

规则文件接口

提供第三方应用拉取规则原文。全部为公开接口,无需鉴权。规则文件由管理员在服务器直接维护,不提供在线编辑接口。

GET /api/talk-rule 获取AI旅伴聊天规则

获取 AI旅伴聊天规则内容(Markdown 格式),对应文件 rules/talk_rule.md。公开接口,无需鉴权,供第三方 APP「AI旅伴」直接拉取使用。

响应示例

{
  "content": "## 聊天规则 ##\n与用户聊天、介绍景区、讲解旅游话题前务必先了解用户...\n",
  "exists": true
}

字段说明

字段类型说明
contentstring聊天规则内容(Markdown 格式,可直接作为 AI 的系统提示词或约束)
existsbool规则文件是否存在(false 时 content 为空字符串)

第三方 APP 使用示例(JavaScript)

fetch('https://dev.nice-ai.cn/podcast/api/talk-rule')
  .then(r => r.json())
  .then(data => {
    // data.content 即为聊天规则,可直接作为 system prompt
    const systemPrompt = data.content;
    // 将 systemPrompt 注入到 AI 对话上下文
  });
GET /api/podcast-type-rule/{code} 按场景获取播客规则(⚠️ 管理后台/内部使用,应用端无需调用)
⚠️ 此接口仅供管理后台/内部调试使用,第三方应用端无需调用。
  • 规则文档(rules/podcast_type_*.md)是服务端内部资产,由服务端在播客生成时自动加载、组装、填充占位符
  • 应用端只需调用 播客生成接口(WS /ws) 提交业务参数(prompt / ai_character_ids / user_id / photo_description 等),无需关心规则文档内容
  • 此接口仅用于:管理后台展示规则内容、运营人员维护规则文档、服务端调试占位符填充

按播客场景 code 获取规则原文(Markdown 格式),对应文件 rules/podcast_type_{id}.md。公开接口,无需鉴权。

code 取值(详见 GET /api/podcast-types): travel(旅游)、news(新闻)、family(家庭)、business(商业)、education(教育)、health(健康)、entertainment(娱乐)、tech(科技)、emotion(情感)

路径参数

参数类型必填说明
codestring必填播客场景英文编码(如 travel、news、tech)

响应示例

{
  "code": "travel",
  "type_id": 1,
  "type_name": "旅游/户外/出行",
  "content": "# 旅游播客\n\n## 创作指引\n...",
  "exists": true
}

字段说明

字段类型说明
codestring传入的播客场景 code
type_idint该场景在 podcast_type 表中的 id(规则文件命名依据:podcast_type_{type_id}.md)
type_namestring场景中文名
contentstring规则原文(Markdown,含未替换的 {占位符})。占位符由播客生成接口在服务端自动填充,详见 播客生成接口 的"规则文档占位符与接口参数对应关系"章节
existsbool规则文件是否存在(false 时 content 为空字符串)

错误响应(未知 code)

{
  "error": "未知播客场景 code: unknown"
}

使用示例

# 获取旅游场景规则(管理后台用)
curl https://dev.nice-ai.cn/podcast/api/podcast-type-rule/travel

AI对话规则(按播客场景)

按播客场景维度获取管理员配置的 S2S 对话规则(talk_rule 表)。第三方应用通过 api_key 鉴权后,可拉取对应场景的规则内容(Markdown),用于指导 S2S 语音对话的 system_prompt 注入。仅返回 status=1 启用的规则。

分享与配置

转发分享配置、小程序码、语种与 APP 版本检查。

GET /api/share-configs/random 随机返回一条启用的转发分享配置;无配置时返回空对象(前端走默认文案)
GET /api/languages 返回 portconfig.FANYI_UI_LANG_CONFIG 支持的语种列表(供前端语种

返回 portconfig.FANYI_UI_LANG_CONFIG 支持的语种列表(供前端语种切换)

GET /api/qrcode 把任意文本(如微信支付 code_url)渲染成二维码 PNG(充值飘窗用)。

Query 参数

参数类型必填说明
datastring可选查询参数
sizeint可选查询参数(默认 260)
GET /api/app-version/check 检查 APP 是否需要更新

Query 参数

参数类型必填说明
appstring可选查询参数
version_codeint可选查询参数(默认 0)
versionstring可选查询参数
GET /api/app-version/latest 获取 APP 最新版本信息(不传 version_code,仅查询)

Query 参数

参数类型必填说明
appstring可选查询参数

支付接口

微信 JSAPI 支付下单、回调、手动确认。当前仅支持微信支付(公众号内 H5 场景)。

POST /api/payment/create 创建订单(JSAPI 下单)

创建订阅支付订单并调微信 JSAPI 下单,返回前端调起支付的参数。订单号前缀 PK。

openid 来源:前端先调 GET /api/auth/wx_oauth 用微信 OAuth code 换 openid,再传给本接口。

请求体 (JSON)

参数类型必填说明
user_idint必填用户ID
plan_codestring必填套餐代码(如 personal / pro / enterprise,可通过 GET /api/subscription/plans 获取)
openidstring必填用户微信 openid(来自 /api/auth/wx_oauth)。缺失时返回 400 {"error":"openid 缺失,请在微信内打开页面完成支付","code":"no_openid"}
pay_methodstring可选支付方式,默认 wechat。当前仅支持 wechat,传其他值返回 400

响应示例

{
  "order_no": "PKABC123DEF456",
  "amount": 29.90,
  "plan_name": "个人版",
  "pay_method": "wechat",
  "jsapi_params": {
    "appId": "wx1234567890abcdef",
    "timeStamp": "1719000000",
    "nonceStr": "abcdef123456",
    "package": "prepay_id=wx20260601abc",
    "signType": "RSA",
    "paySign": "BASE64签名..."
  }
}
前端调起支付:拿到 jsapi_params 后直接调 WeixinJSBridge.invoke('getBrandWCPayRequest', jsapi_params, ...) 完成支付。
金额单位:响应中 amount 为元,调微信下单时服务端已转为分。
prepay_id 落库:下单成功后服务端将 prepay_id 写入 payment_orders.prepay_id 便于后续对账。

错误响应

{ "error": "免费套餐无需支付" }
POST /api/payment/confirm 手动确认支付(备用接口)

备用接口,正常流程由 /api/payment/notify 微信回调自动处理。仅当用户支付成功但回调未到达时使用,需管理员后台调用。确认后自动更新订单状态并应用订阅配额(_apply_subscription_quota)。

Query 参数

参数类型必填说明
user_idint必填用户ID(必须为订单所有者)
order_nostring必填订单号

响应示例

{ "success": true }

错误响应

{
  "error": "订单不存在"
}
幂等性:订单已支付(status=1)时直接返回 {"success":true,"message":"已支付"},不重复扣配额。
POST /api/payment/notify 微信支付回调(白名单接口)

微信支付服务器在用户支付成功后异步回调此接口。白名单接口,无需鉴权。验签通过后更新订单状态并应用订阅配额。

请求格式:微信回调为 XML 格式(非 JSON),Content-Type: application/xml。
响应格式:服务端返回 PlainText XML(application/xml),验签失败返回 <xml><return_code><![CDATA[FAIL]]></return_code></xml>,成功返回 <return_code><![CDATA[SUCCESS]]></return_code>。

关键流程

  • 服务端用 wx_verify_notify 验签 + 解密 XML
  • 取出 out_trade_no(订单号)和 transaction_id(微信交易号)
  • 更新 payment_orders:status=1, trade_no=transaction_id, paid_at=当前时间戳
  • 调用 _apply_subscription_quota(user_id, plan_code) 应用订阅配额
  • 订单不存在或已支付时直接返回 SUCCESS XML(避免微信重试轰炸)

响应示例(成功)

<xml>
  <return_code><![CDATA[SUCCESS]]></return_code>
  <return_msg><![CDATA[OK]]></return_msg>
</xml>

响应示例(失败)

<xml>
  <return_code><![CDATA[FAIL]]></return_code>
  <return_msg><![CDATA[验签失败]]></return_msg>
</xml>
GET /api/wechat/jsapi-sign 生成微信 JS-SDK 签名,前端 wx.config 使用

Query 参数

参数类型必填说明
urlstring可选查询参数
POST /api/payment/recharge /api/payment/recharge

请求参数 (JSON Body)

参数类型必填说明
user_idint必填用户 ID
openidstring必填
amountfloat必填金额(元)
GET /api/payment/order-status 查订单是否已支付(充值二维码轮询用)。返回 {paid, amount, balance}

Query 参数

参数类型必填说明
order_nostring可选订单号
user_idint可选用户 ID(默认 0)

响应示例

{
  "paid": true,
  "amount": 99.0,
  "balance": 128.5
}
POST /api/payment/video 视频增值下单:多图视频 3 元/条;全片演绎 5 秒/1 元(不足 5 秒不计);照片故事/舌尖

视频增值下单:多图视频 3 元/条;全片演绎 5 秒/1 元(不足 5 秒不计);照片故事/舌尖按全片演绎口径。 仅播客作者本人可为其播客单独付费生成,支付成功后自动拉起视频生成。

请求参数 (JSON Body)

参数类型必填说明
user_idint必填用户 ID
podcast_idint必填
modeint必填生成模式
openidstring必填
priorityint可选

用量统计

查询播客生成用量数据。

GET /api/user/usage 用量统计

获取用户用量统计,返回按天聚合的播客生成数据,可用于绘制柱状图。

参数

参数类型必填说明
user_idint必填用户ID
daysint可选统计天数,默认30

响应示例

{
  "daily": [
    { "day": "2026-06-20", "podcast_count": 5, "tokens_in": 12000, "tokens_out": 8000, "duration_sec": 300 },
    { "day": "2026-06-21", "podcast_count": 3, "tokens_in": 7000, "tokens_out": 5000, "duration_sec": 180 }
  ],
  "total": {
    "podcast_count": 8,
    "tokens_in": 19000,
    "tokens_out": 13000,
    "duration_sec": 480
  }
}

聚合音乐搜索

聚合多源音乐搜索服务,内部并发查询多个音乐源,按返回到达顺序取前 N 首立即响应。

基础路径:https://dev.nice-ai.cn/getmusic

特性:
  • 极速响应:最快的源 1-2 秒返回即可凑够 limit 首,无需等待慢源
  • 聚合排序:服务端按各源返回先后顺序合并,前 limit 首即返回
  • 统一格式:返回结果不含具体音乐平台字段,所有歌曲统一结构
  • 流式播放:stream_url 字段可直接用作 <audio> 播放地址,支持 Range 拖动
GET /api/search_top 聚合搜索(前N首)

根据关键词聚合搜索音乐,内部并发查询多个音乐源,按各源返回先后顺序合并,取前 limit 首立即返回。

参数

参数类型必填说明
keywordstring必填搜索关键词(歌曲名或歌手名)
limitint可选返回前 N 首,默认 5,范围 1-20。服务端一旦累计达到此数量立即响应,不等慢源

响应字段

字段类型说明
okbool固定为 true
keywordstring搜索关键词
totalint实际返回的歌曲数量(≤ limit)
limitint请求的 limit 值
songsarray歌曲数组,按各源返回先后顺序排列
errorsarray|null查询失败的源错误列表(正常情况下为 null)

songs 数组元素字段

字段类型说明
song_namestring歌曲名
singersstring歌手
albumstring专辑
extstring音频格式(如 mp3、flac)
durationstring时长(HH:MM:SS 格式)
duration_sint时长(秒)
file_sizestring文件大小(人类可读)
cover_urlstring|null封面图 URL
lyricstring|nullLRC 歌词文本
download_urlstring|null音频直链(部分源需要Referer,建议用 stream_url 代理播放)
stream_urlstring|null可直接播放的流式代理地址(相对路径,需拼接 https://dev.nice-ai.cn 前缀)

响应示例

{
  "ok": true,
  "keyword": "十年",
  "total": 5,
  "limit": 5,
  "songs": [
    {
      "song_name": "十年",
      "singers": "陈奕迅",
      "album": "黑白灰",
      "ext": "flac",
      "duration": "00:04:30",
      "duration_s": 270,
      "file_size": "28.50 MB",
      "cover_url": "https://example.com/cover.jpg",
      "lyric": "[00:00.00]十年 - 陈奕迅\n[00:12.00]如果那两个字没有颤抖...",
      "download_url": "https://example.com/audio/123.flac",
      "stream_url": "/getmusic/api/stream?download_url=https%3A%2F%2Fexample.com%2Faudio%2F123.flac"
    }
  ],
  "errors": null
}

调用示例

curl "https://dev.nice-ai.cn/getmusic/api/search_top?keyword=十年&limit=5"
<audio controls src="https://dev.nice-ai.cn/getmusic/api/stream?download_url=..."></audio>
提示:响应通常在 1-3 秒内返回。若需更多结果可增大 limit,但响应时间可能延长(需等待更多源完成)。
GET /api/stream 流式播放代理

流式音频代理接口,支持 HTTP Range 字节级 seek。可绕过防盗链直接播放第三方音乐源的音频。

参数

参数类型必填说明
download_urlstring必填音频直链地址(来自 /api/search_top 返回的 download_url 字段)

说明

  • 支持 Range 请求头透传,可用于 <audio> 标签直接播放并拖动进度
  • 响应 Content-Type 透传源站(通常为 audio/mpeg 或 audio/flac)
  • 响应 Content-Range / Accept-Ranges 透传,支持播放器 seek
  • 同域代理,无 CORS 跨域问题

调用示例

<audio controls src="https://dev.nice-ai.cn/getmusic/api/stream?download_url=https%3A%2F%2Fexample.com%2Faudio%2F123.flac"></audio>

响应头示例

HTTP/1.1 206 Partial Content
Content-Type: audio/flac
Content-Length: 65536
Content-Range: bytes 0-65535/189494782
Accept-Ranges: bytes

AI 歌曲创作 / 翻唱

AI 歌曲创作与变声翻唱接口。选择 AI 角色(音色取自角色的已录制音色)进行歌曲创作或翻唱,全程 SSE 流式推送进度与结果。生成的歌曲统一入库(ai_sing),可通过播放页分享。

基础路径:https://dev.nice-ai.cn/podcast

鉴权:POST 请求必须携带 X-Api-Key: sk-xxxx 请求头(或 ?api_key=sk-xxxx 查询参数),见「认证方式」。

POST /api/song-ai/generate AI创作歌曲(SSE 流式)

选择 1-4 位 AI 角色 → LLM 生成歌词(或使用传入歌词)→ 逐段合成演唱(多人按歌词中的歌手标记分段,各自用对应音色,Seed-VC 音色转换)→ 拼接成整首歌 → 自动生成封面。响应为 text/event-stream(SSE),全程流式推送进度,整体耗时约 1-5 分钟(取决于歌词长度与歌手数量)。

请求

POST /podcast/api/song-ai/generate
X-Api-Key: sk-xxxxxxxx
Content-Type: application/json

{
  "user_id": 1001,
  "ai_character_ids": [208, 305],
  "theme": "写给十年后自己的一首歌",
  "lyrics": "",
  "style": "Pop",
  "max_dur": 150
}

请求参数

参数类型必填说明
user_idint必填当前用户 id(歌曲归属该用户)
ai_character_idsarray[int]必填演唱的 AI 角色 id,1-4 位;2 位以上时按歌词中的歌手标记分段对唱,且要求每位 AI 已录制音色
lyricsstring可选自定义歌词(支持 [verse]/[chorus] 段落标记与 歌手名: 行标记)。不传则由 LLM 按 AI 身份 + theme + style 自动创作
themestring条件必填歌曲主题。lyrics 与 theme 至少提供一个
stylestring可选风格标签(Pop / R&B / Folk / Rock...),默认 Auto
max_durint可选最大时长(秒),默认 150。服务端会按歌词行数与引擎上限自动校正

SSE 事件流

event说明
progress进度事件。字段:stage(lyrics / segment / concat / cover)、status(active / done)、msg(进度文案);segment 阶段额外带 index / total / singer / elapsed;lyrics、cover 完成时分别带 lyrics / cover_url
done生成完成,推送完整歌曲信息(见响应示例)
error失败,{"error": "..."},流结束

done 事件响应示例

event: done
data: {
  "ok": true,
  "title": "写给十年后的自己",
  "lyrics": "[verse]\n小美:夜色漫过窗台\n...",
  "lyrics_timed": [{"start": 0.0, "end": 6.2, "text": "夜色漫过窗台", "singer": "小美"}],
  "style": "Pop",
  "cover_url": "/sing/cover/321.jpg?v=1787556361",
  "song_ids": [9001],
  "final_wav_url": "/sing/output/321.mp3",
  "play_url": "/sing/play/321",
  "duration": 142,
  "ai_names": ["小美", "小帅"],
  "singer": "小美、小帅",
  "lyricist": "AI 作词",
  "composer": "AI 作曲"
}

音频 / 封面 / 播放页完整地址(域名根路径,不在 /podcast 下):https://dev.nice-ai.cn/sing/output/321.mp3、https://dev.nice-ai.cn/sing/cover/321.jpg、https://dev.nice-ai.cn/sing/play/321(公开播放页,可直接分享)。song_ids[0] 即本人作品 id,可作为翻唱接口的 song_id。

错误响应(非 SSE,HTTP 状态码)

401 {"error": "请先登录"}                          ← user_id 缺失
400 {"error": "请选择至少 1 位 AI 歌手"}            ← ai_character_ids 为空
400 {"error": "最多只能选择 4 位 AI 歌手"}
400 {"error": "请提供歌词或歌曲主题"}

调用示例(JS 解析 SSE 流)

const resp = await fetch('https://dev.nice-ai.cn/podcast/api/song-ai/generate', {
  method: 'POST',
  headers: { 'X-Api-Key': 'sk-xxxx', 'Content-Type': 'application/json' },
  body: JSON.stringify({ user_id: 1001, ai_character_ids: [208], theme: '夏天的海边', style: 'Pop' })
});
const reader = resp.body.getReader();
const decoder = new TextDecoder();
let buf = '';
while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  buf += decoder.decode(value, { stream: true });
  let idx;
  while ((idx = buf.indexOf('\n\n')) >= 0) {
    const chunk = buf.slice(0, idx); buf = buf.slice(idx + 2);
    const evLine = chunk.split('\n').find(l => l.startsWith('event:'));
    const dataLine = chunk.split('\n').find(l => l.startsWith('data:'));
    if (!dataLine) continue;
    const data = JSON.parse(dataLine.slice(5).trim());
    if (evLine && evLine.includes('progress')) console.log('[进度]', data.msg);
    if (evLine && evLine.includes('done'))    console.log('[完成]', data.final_wav_url);
    if (evLine && evLine.includes('error'))   console.error('[失败]', data.error);
  }
}
POST /api/song-ai/convert 歌曲变声翻唱(SSE 流式)

选择一首歌 + 1-2 位 AI 音色进行变声翻唱(2 位自动对唱:按原曲歌词性别标注分工,合唱段混合两轨)。源歌曲支持两种:本人 AI 创作歌曲(song_id)或外链歌曲(music,需先通过搜歌点选预下载缓存)。响应为 text/event-stream(SSE)。

三种翻唱方式(convert_mode)

mode说明
voice(默认)原曲翻唱 / 纯变声:保留原曲旋律、歌词、伴奏,仅把人声换成指定 AI 音色(htdemucs 分离 → Seed-VC 音色转换 → RMS 响度匹配 → 混回伴奏)
lyrics改编歌词翻唱:LLM 按 adapt_prompt 改编歌词(保留段落结构 / 韵律)→ ACE-Step cover(锚定原曲旋律编曲,只换歌词)→ Seed-VC 换音色
remix重新编曲翻唱:按 new_style 新风格重新编曲(不传 new_style 则原风格重编曲),旋律 / 歌词不变 → Seed-VC 换音色

请求

POST /podcast/api/song-ai/convert
X-Api-Key: sk-xxxxxxxx
Content-Type: application/json

{
  "user_id": 1001,
  "song_id": 9001,
  "ai_character_ids": [208, 305],
  "convert_mode": "lyrics",
  "adapt_prompt": "改成毕业季告别主题"
}

请求参数

参数类型必填说明
user_idint必填当前用户 id
song_idint条件必填源歌曲 id(本人 AI 创作歌曲,来自 generate 返回的 song_ids)。与 music 二选一
musicobject条件必填外链歌:{"song_name": "歌名", "singers": "歌手"},须为本人搜歌历史中已点选缓存的歌曲。与 song_id 二选一
ai_character_idsarray[int]必填目标 AI 音色,1-2 位(2 位自动对唱),要求每位 AI 已录制音色
convert_modestring可选voice(默认)/ lyrics / remix
adapt_promptstring可选lyrics 模式的歌词改编方向提示
new_stylestring可选remix 模式的新编曲风格(不传则原风格重编曲)

SSE 事件流

event说明
progress进度事件。字段:stage(lyrics / segment / concat / cover)、status、msg;lyrics 完成时带改编后 lyrics;cover 完成时带 cover_url
done翻唱完成,推送完整歌曲信息(见响应示例)
error失败,{"error": "..."},流结束

done 事件响应示例

event: done
data: {
  "ok": true,
  "theme": "夏天的海边(小美翻唱)",
  "style": "Pop",
  "lyrics": "改编后的歌词...",
  "convert_mode": "lyrics",
  "cover_url": "/sing/cover/322.jpg?v=1787556361",
  "final_wav_url": "/sing/output/322.mp3",
  "play_url": "/sing/play/322",
  "duration": 138,
  "ai_names": ["小美", "小帅"],
  "song_ids": [9002]
}

翻唱作品同样入库(theme 标注「XX翻唱」),音频 / 封面 / 播放页地址规则与创作接口相同;song_ids[0] 可继续作为翻唱源。

错误响应(非 SSE,HTTP 状态码)

401 {"error": "请先登录"}
400 {"error": "请选择要翻唱的歌曲"}            ← song_id 与 music 均未提供
400 {"error": "不支持的翻唱方式: xxx"}
400 {"error": "翻唱最多选择 2 位 AI(对唱)"}
400 {"error": "外链歌曲缓存不存在,请重新搜索选择"}   ← SSE error 事件
POST /api/song-ai/styles 返回 SongGeneration 模型支持的歌曲风格标签列表(供前端点选)。

请求体:见实现(form-data / 二进制或空请求体)。

GET /api/song-ai/music/search 外链搜歌(NDJSON 流式转发 getmusic 多源竞速:快源先到先显示)。

外链搜歌(NDJSON 流式转发 getmusic 多源竞速:快源先到先显示)。 过滤无 download_url 的歌:点选必须预下载原曲,无直链的歌选了必报 "歌曲信息不完整"——源头剔除,前端列表只剩可选可用的歌。

Query 参数

参数类型必填说明
keywordstring可选搜索关键词
user_idint可选用户 ID(默认 0)
POST /api/song-ai/music/pick 点选外链歌曲:立即预下载到本地缓存(规避音源 URL 时效)+ 记录历史。

请求参数 (JSON Body)

参数类型必填说明
user_idint可选用户 ID
song_namestring可选
singersstring可选
download_urlstring可选
sourcestring可选
extstring可选
duration_sint可选
cover_urlstring可选封面 URL
lyricstring可选
GET /api/song-ai/music/history 外链搜歌历史(已预下载缓存的歌曲,点选直接翻唱)

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)
limitint可选返回条数(默认 50)
GET /api/song-ai/ceremonies 查询所有恋爱仪式及其对话记录,按用户分组返回。

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)
GET /api/song-ai/mine 我的歌曲列表:当前用户生成过的所有歌曲(按时间倒序)。

我的歌曲列表:当前用户生成过的所有歌曲(按时间倒序)。 keyword 可选:按歌曲主题/歌词模糊搜索(控制台「创作歌曲」页左栏搜索框)。 song_type 可选:create=AI原创 / convert=AI翻唱(控制台顶部两个 tab 分别看各自的作品)。

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)
limitint可选返回条数(默认 20)
offsetint可选分页偏移(默认 0)
keywordstring可选搜索关键词
song_typestring可选查询参数
GET /api/song-ai/{song_id} 歌曲详情:供右栏播放器与 H5 播放页共用(含封面/分句时间戳歌词/歌手/风格/主题)。

路径参数

参数类型必填说明
song_idint必填路径参数

Query 参数

参数类型必填说明
song_idint必填查询参数
POST /api/song-ai/{song_id}/regenerate_cover 重新生成歌曲封面:校验归属后按曲风生成统一风格封面(纯画面无文字·画面主体随机多样),回填 co

重新生成歌曲封面:校验归属后按曲风生成统一风格封面(纯画面无文字·画面主体随机多样),回填 cover_url。

路径参数

参数类型必填说明
song_idint必填路径参数

Query 参数

参数类型必填说明
song_idint必填查询参数

请求参数 (JSON Body)

参数类型必填说明
user_idint可选用户 ID
DELETE /api/song-ai/{song_id} 删除一首歌曲:校验归属后删除 DB 记录 + 本地音频文件 + 封面图。

删除一首歌曲:校验归属后删除 DB 记录 + 本地音频文件 + 封面图。 翻唱作品(sing_id>0)同步删除 ai_sing.songs 记录 + outputs/{sing_id}.mp3 + covers/{sing_id}.jpg。

路径参数

参数类型必填说明
song_idint必填路径参数

Query 参数

参数类型必填说明
song_idint必填查询参数
user_idint可选用户 ID(默认 0)

AI旅游派(群聊)

面向第三方应用「AI旅游派」APP 的群聊(派)服务。派 = 群聊,用户可建派(设置验证码/群名/群规)、通过验证码或手机号邀请加入、群内聊天/发照片/创建备忘录,群主可踢人,群主 AI 可通过工具接口获取群上下文并主动 @群友 聊天。

数据表说明(aiyou 库):
  • pai — 派(群)主表,含名称/验证码/群规rules/创建者/群主AI/经纬度/半径
  • pai_member — 群成员表,role: 0普通 1群主,phone: 邀请入群时记录的手机号
  • pai_invite — 邀请记录表,status: 0待接受 1已接受 2已拒绝 3已取消
  • pai_blocklist — 黑名单表,被踢出的用户不可再次加入
  • pai_message — 群消息表,msg_type: text/photo/system/ai
  • pai_photo — 群照片表,含 AI 识别描述
  • pai_memo — 备忘录/计划事项表,memo_type: memo/plan,status: 1待办 0已完成 -1已删除
POST /api/pai 建派(建群)

用户创建一个新派(群聊),创建者自动成为群主(role=1)。验证码全局唯一,其它用户需通过验证码才能加入。

请求体(JSON)

参数类型必填说明
namestring必填派名称(≤100字符)
codestring必填进群验证码(≤32字符,全局唯一)
creator_idint必填创建者用户ID
ai_idint选填群主 AI 角色ID,AI 将以此身份主动发言
latitudedecimal选填纬度(用于"附近的派"查询)
longitudedecimal选填经度
addressstring选填地址描述
radiusint选填附近范围(米,默认 1000)
descriptionstring选填派简介
rulesstring选填群规(建群时可设置,后续可通过 PUT 接口修改)

请求示例

POST /podcast/api/pai
Content-Type: application/json

{
  "name": "深圳湾摄影爱好者",
  "code": "SW2026",
  "creator_id": 1001,
  "ai_id": 25,
  "latitude": 22.5128,
  "longitude": 113.9380,
  "address": "深圳市南山区深圳湾公园",
  "description": "周末一起拍深圳湾日落"
}

响应示例

{
  "ok": true,
  "pai_id": 1,
  "code": "SW2026"
}

错误响应

{ "error": "验证码已被使用,请换一个" }  // 400
GET /api/pai/nearby 附近的派

基于经纬度查询半径范围内未解散的派,APP 点击"附近的派"时调用,底部弹窗展示结果。返回列表含距离(米),按距离升序排列。

查询参数

参数类型必填说明
latfloat必填当前定位纬度
lngfloat必填当前定位经度
radiusint选填搜索半径(米,默认 1000)
user_idint选填当前用户ID(仅用于参考,不过滤)

响应示例

{
  "total": 2,
  "pais": [
    {
      "id": 1,
      "name": "深圳湾摄影爱好者",
      "code": "SW2026",
      "creator_id": 1001,
      "creator_name": "华哥",
      "ai_id": 25,
      "latitude": 22.5128000,
      "longitude": 113.9380000,
      "address": "深圳市南山区深圳湾公园",
      "radius": 1000,
      "description": "周末一起拍深圳湾日落",
      "distance": 320,
      "created_at": 1784213372
    }
  ]
}
GET /api/pai/my 我的派

返回当前用户创建或加入的全部派(含已解散的)。

查询参数

参数类型必填说明
user_idint必填用户ID

响应字段(每个派对象)

字段类型说明
id, name, code, creator_id, ai_id混合派基本信息
latitude, longitude, address, description混合地理位置与简介
statusint1正常 0已解散
roleint0普通成员 1群主
joined_atint加入时间(Unix秒)
creator_namestring创建者昵称
GET /api/pai/{pai_id} 派详情

获取派详情,含成员数、当前用户角色(my_role:null非成员 / 0普通 / 1群主)。

路径/查询参数

参数类型必填说明
pai_idint必填路径参数,派ID
user_idint选填当前用户ID(用于返回 my_role)
PUT /api/pai/{pai_id} 更新群名/群规

更新群信息(群名、群规、简介)。仅群主可操作。

请求体(JSON)

参数类型必填说明
user_idint必填当前用户ID(必须是群主)
namestring可选新群名(≤100字符)
rulesstring可选群规(≤5000字符,传空字符串可清空)
descriptionstring可选群简介(≤500字符)

请求示例

PUT /podcast/api/pai/1
Content-Type: application/json

{
  "user_id": 1001,
  "name": "深圳湾摄影爱好者(V2)",
  "rules": "1. 禁止发广告\n2. 友好交流\n3. 每周至少分享一张作品"
}

响应示例

{ "ok": true }

错误响应

{ "error": "非群成员" }          // 403
{ "error": "仅群主可修改群信息" }  // 403
POST /api/pai/{pai_id}/join 加入派(验证码)

通过验证码加入派。加入后自动写入一条 system 系统消息通知群友。

请求体(JSON)

参数类型必填说明
user_idint必填用户ID
codestring必填进群验证码

错误响应

{ "error": "验证码错误" }           // 400
{ "error": "已是群成员" }           // 400
{ "error": "派已解散" }             // 400
POST /api/pai/{pai_id}/invite 手机号邀请入群

通过手机号邀请用户入群。群主和普通成员均可发起邀请。被邀请者会收到一条待处理邀请,需调用「接受邀请」接口确认后才会正式加入。

流程:群友发起邀请 → 被邀请者收到通知 → 被邀请者调用 POST /api/pai/{pai_id}/invite/accept 接受 → 正式入群

请求体(JSON)

参数类型必填说明
user_idint必填邀请者用户ID(必须是群成员)
phonestring必填被邀请者手机号(11位,1开头)
notestring可选邀请备注(≤255字符)

请求示例

POST /podcast/api/pai/1/invite
Content-Type: application/json

{
  "user_id": 1001,
  "phone": "13800138000",
  "note": "欢迎加入摄影群"
}

响应示例

{
  "ok": true,
  "message": "已邀请 13800138000 加入「深圳湾摄影爱好者」",
  "invitee_registered": true
}

错误响应

{ "error": "手机号格式错误" }       // 400
{ "error": "该用户已是群成员" }     // 400
{ "error": "派已满员(上限50人)" } // 400
{ "error": "非群成员" }             // 403
POST /api/pai/{pai_id}/invite/accept 接受邀请入群

被邀请者接受邀请,正式加入群。被踢出的用户(黑名单)无法通过邀请再次加入。

请求体(JSON)

参数类型必填说明
user_idint必填被邀请者用户ID
phonestring可选被邀请者手机号(用于匹配邀请记录,建议传)

请求示例

POST /podcast/api/pai/1/invite/accept
Content-Type: application/json

{
  "user_id": 1002,
  "phone": "13800138000"
}

响应示例

{
  "ok": true,
  "message": "已加入「深圳湾摄影爱好者」(邀请者:华哥)"
}

错误响应

{ "error": "没有待接受的邀请" }          // 400
{ "error": "你已被移出该派,无法再次加入" } // 403(黑名单)
GET /api/pai/{pai_id}/invites 邀请列表

获取派的邀请列表。群主可查看全部邀请,普通成员只能查看自己发出的邀请。

查询参数

参数类型必填说明
pai_idint必填路径参数,派ID
user_idint必填当前用户ID
statusint可选筛选状态:0待接受 1已接受 2已拒绝 3已取消。-1=全部(默认)

响应示例

{
  "invites": [
    {
      "id": 1,
      "inviter_id": 1001,
      "inviter_name": "华哥",
      "phone": "13800138000",
      "invitee_id": 1002,
      "invitee_name": "小明",
      "status": "pending",
      "note": "欢迎加入摄影群",
      "created_at": 1786626265,
      "handled_at": 0
    }
  ]
}
DELETE /api/pai/{pai_id}/invite/{invite_id} 取消邀请

取消待接受的邀请。群主可取消任何邀请,邀请者可取消自己发出的邀请。仅可取消状态为「待接受」的邀请。

查询参数

参数类型必填说明
pai_idint必填路径参数,派ID
invite_idint必填路径参数,邀请ID
user_idint必填当前用户ID

响应示例

{ "ok": true }

错误响应

{ "error": "邀请已处理" }                    // 400
{ "error": "仅群主或邀请者本人可取消" }       // 403
POST /api/pai/{pai_id}/leave 退出派

退出派。群主不能退出,需先调用解散接口。

请求体(JSON)

参数类型必填说明
user_idint必填用户ID
POST /api/pai/{pai_id}/kick 踢出群成员

将群成员移出群。仅群主可操作,不能踢出群主或自己。被踢出的用户会被加入黑名单,无法通过验证码或邀请再次加入。

请求体(JSON)

参数类型必填说明
user_idint必填操作者用户ID(必须是群主)
target_user_idint必填被踢出的用户ID

请求示例

POST /podcast/api/pai/1/kick
Content-Type: application/json

{
  "user_id": 1001,
  "target_user_id": 1003
}

响应示例

{
  "ok": true,
  "message": "已将 小张 移出派"
}

错误响应

{ "error": "不能踢出自己" }      // 400
{ "error": "仅群主可踢人" }       // 403
{ "error": "目标用户非群成员" }   // 400
{ "error": "不能踢出群主" }       // 400
DELETE /api/pai/{pai_id} 解散派

解散派(软删除,status=0)。仅创建者可操作。

查询参数

参数类型必填说明
pai_idint必填路径参数,派ID
user_idint必填当前用户ID(必须是创建者)
GET /api/pai/{pai_id}/members 群成员列表

返回群成员列表,群主(role=1)排在前。每个成员含 user_id / role / joined_at / username。

响应示例

{
  "members": [
    { "user_id": 1001, "role": 1, "joined_at": 1784213372, "username": "华哥" },
    { "user_id": 1002, "role": 0, "joined_at": 1784213400, "username": "小李" }
  ]
}
GET /api/pai/{pai_id}/messages 历史消息

按时间倒序获取群历史消息(返回时已反转为正序)。支持分页:传 before=某消息id 获取更早的消息。

查询参数

参数类型必填说明
pai_idint必填路径参数,派ID
limitint选填每页数量(默认50,最大200)
beforeint选填分页游标,传上一页最早消息的id(默认0=取最新)

消息对象字段

字段类型说明
idint消息ID
user_idint发送者用户ID(0=AI或系统)
ai_idintAI角色ID(仅 AI 消息)
msg_typestringtext / photo / system / ai
contentstring消息内容
mention_user_idsstring@用户ID列表逗号分隔,"all"=@所有人
photo_idint关联照片ID(仅 photo 类型)
created_atint时间戳(Unix秒)
usernamestring发送者昵称(系统/AI消息为空)
ai_namestringAI名称(仅 AI 消息)
POST /api/pai/{pai_id}/messages 发送消息

用户在群里发送消息。仅群成员可发。支持 @某人 / @所有人。

请求体(JSON)

参数类型必填说明
user_idint必填发送者用户ID
contentstring必填消息内容
mention_user_idsstring选填@用户ID列表逗号分隔,"all"=@所有人
msg_typestring选填默认 text,照片消息用 photo(一般由上传接口自动生成)

响应示例

{
  "ok": true,
  "message_id": 42,
  "created_at": 1784213500
}
POST /api/pai/{pai_id}/photos 上传群照片

用户上传照片到群里。multipart/form-data 提交。服务端自动调用 LLM 识别图片内容,将描述写入 pai_photo.description,同时在 pai_message 写入一条 photo 类型消息,让所有群友和 AI 都能看到「谁发了什么照片」。

关键说明:上传后服务端通过 LLM 视觉模型自动识别图片,AI 群主可通过 GET /api/pai/{pai_id}/ai-context 拿到最近照片及其描述,然后调用 POST /api/pai/{pai_id}/ai-send 对照片做简单回应。

表单参数

参数类型必填说明
user_idint必填上传者用户ID(必须是群成员)
filefile必填图片文件(≤10MB,jpg/png等)

请求示例

POST /podcast/api/pai/1/photos
Content-Type: multipart/form-data; boundary=----

------boundary
Content-Disposition: form-data; name="user_id"

1001
------boundary
Content-Disposition: form-data; name="file"; filename="sunset.jpg"
Content-Type: image/jpeg

<二进制图片数据>
------boundary--

响应示例

{
  "ok": true,
  "photo_id": 5,
  "message_id": 43,
  "file_path": "pai_photos/1_1001_1784213500000.jpg",
  "description": "深圳湾日落,海面上橙红色的夕阳倒影,远处有游船剪影。",
  "created_at": 1784213500
}

照片访问 URL

照片通过静态目录访问:https://dev.nice-ai.cn/podcast/{file_path}
例如:https://dev.nice-ai.cn/podcast/pai_photos/1_1001_1784213500000.jpg

GET /api/pai/{pai_id}/photos 群照片列表

获取群照片列表(倒序,支持分页)。每张照片含 AI 识别描述和访问 URL。

查询参数

参数类型必填说明
limitint选填每页数量(默认50,最大200)
beforeint选填分页游标,照片ID

响应字段(每张照片)

字段类型说明
idint照片ID
user_idint上传者ID
usernamestring上传者昵称
file_pathstring存储相对路径
urlstring访问 URL(/podcast/{file_path})
descriptionstringAI 识别描述
created_atint时间戳
POST /api/pai/{pai_id}/memos 创建备忘/计划

创建备忘录或计划事项。AI 可调用此接口,将用户语音中提到的计划/备忘写入,供后续查询和提醒。

请求体(JSON)

参数类型必填说明
user_idint必填所属用户ID
contentstring必填备忘/计划内容
memo_typestring选填memo=备忘录(默认)/ plan=计划事项
due_atint选填计划时间(Unix秒,仅 plan 类型用)
created_by_aibool选填是否 AI 创建(true 时写入1)

请求示例

POST /podcast/api/pai/1/memos
{
  "user_id": 1001,
  "content": "明天上午10点在深圳湾公园集合拍日出",
  "memo_type": "plan",
  "due_at": 1784300000,
  "created_by_ai": true
}
GET /api/pai/{pai_id}/memos 查询备忘/计划

查询备忘/计划列表。AI 可调用此接口了解用户的待办事项。

查询参数

参数类型必填说明
user_idint选填按用户过滤(不传=全群)
memo_typestring选填memo / plan(不传=全部)
statusint选填1待办(默认) / 0已完成 / -1已删除 / -2全部

响应字段(每条备忘)

字段类型说明
idint备忘ID
user_id, username混合所属用户
memo_typestringmemo / plan
contentstring内容
due_atint计划时间(仅 plan)
statusint1待办 0已完成 -1已删除
created_by_aiint1=AI创建 0=用户创建
created_at, updated_atint时间戳
PUT /api/pai/{pai_id}/memos/{memo_id} 修改备忘/计划
权限:请求体须传 user_id,仅备忘创建者或派群主可修改;备忘不存在返回 404;越权返回 403 仅创建者或群主可修改备忘。

修改备忘/计划,支持部分字段更新。AI 可将待办标记为已完成(status=0),或修改计划时间。

请求体(JSON)

参数类型必填说明
user_idint必填操作者用户ID,用于所有者校验(创建者或派群主可操作)
contentstring可选修改内容
memo_typestring可选memo / plan
due_atint可选计划时间(Unix秒)
statusint可选1待办 / 0已完成 / -1已删除

请求示例(标记为已完成)

PUT /podcast/api/pai/1/memos/5
{ "user_id": 12345, "status": 0 }
DELETE /api/pai/{pai_id}/memos/{memo_id} 删除备忘/计划
权限:Query 须传 user_id,仅备忘创建者或派群主可删除;备忘不存在返回 404;越权返回 403 仅创建者或群主可删除备忘。

软删除备忘/计划(status=-1)。AI 可调用此接口清理过期或无效事项。

Query 参数

参数类型必填说明
user_idint必填操作者用户ID,用于所有者校验(创建者或派群主可操作)
GET /api/pai/{pai_id}/ai-context AI获取群上下文

AI 工具调用接口。返回群的完整上下文:群信息 + 群主AI信息 + 成员列表 + 最近消息 + 最近照片。如传 user_id,同时返回该用户的待办备忘/计划。

使用场景:群主 AI 定期或被触发时调用此接口,了解群内最新动态(谁说了什么、谁发了什么照片、有什么待办),然后决定是否主动发言或 @某人。

查询参数

参数类型必填说明
pai_idint必填路径参数,派ID
user_idint选填传入则返回该用户的待办备忘
msg_limitint选填最近消息数量(默认20)
photo_limitint选填最近照片数量(默认5)

响应结构

{
  "pai": {
    "id": 1,
    "name": "深圳湾摄影爱好者",
    "creator_id": 1001,
    "ai_id": 25,
    "ai_info": { "id": 25, "name": "派派", "avatar_url": "..." },
    "status": 1
  },
  "members": [...],
  "member_count": 3,
  "recent_messages": [...],
  "recent_photos": [...],
  "my_memos": [...]  // 仅传 user_id 时返回
}
POST /api/pai/{pai_id}/ai-send AI主动发消息(@某人/@所有人)

AI 工具调用接口。群主 AI 主动在群里发消息,支持 @某人 / @所有人。消息以 msg_type=ai 写入,前端可特殊渲染(如带 AI 头像、高亮 @)。

权限校验:服务端会校验 ai_id 必须是该派的群主 AI,否则返回 403。这确保只有被指定的群主 AI 能主动发言。

请求体(JSON)

参数类型必填说明
ai_idint必填群主 AI 角色ID(必须是该派指定的 ai_id)
contentstring必填消息内容
mention_user_idsstring选填@用户ID列表逗号分隔,"all"=@所有人

请求示例(@某人)

POST /podcast/api/pai/1/ai-send
{
  "ai_id": 25,
  "content": "@1001 你昨天发的日落照片真美!打算周末还去吗?",
  "mention_user_ids": "1001"
}

请求示例(@所有人)

POST /podcast/api/pai/1/ai-send
{
  "ai_id": 25,
  "content": "@所有人 周六早上6点深圳湾公园拍日出,要来的报个名~",
  "mention_user_ids": "all"
}

响应示例

{
  "ok": true,
  "message_id": 50,
  "created_at": 1784213600
}

错误响应

{ "error": "该AI不是本派的群主AI" }  // 403
GET /api/pai-talk-rule 获取 AI群聊引导规则(rules/talk_rule_pai.md),公开接口

采访系统(caifang)

AI 记者采访全流程:专题、会话、提问、语音、成稿与播客转化。

GET /api/caifang/topics /api/caifang/topics

见实现。

POST /api/caifang/topic /api/caifang/topic

请求参数 (JSON Body)

参数类型必填说明
titlestring必填标题
organizerstring必填
intervieweestring必填
backgroundstring必填
host_ai_idint必填
GET /api/caifang/topic/{tid} /api/caifang/topic/{tid}

路径参数

参数类型必填说明
tidstring必填路径参数

Query 参数

参数类型必填说明
tidint必填查询参数
GET /api/caifang/list/banner 专题列表页通用 baner(懒生成,缓存)。
GET /api/caifang/topic/{tid}/banner 专题详情页 baner(懒生成,缓存)。

路径参数

参数类型必填说明
tidstring必填路径参数

Query 参数

参数类型必填说明
tidint必填查询参数
GET /api/caifang/profile/stats 「我的」页统计卡:所有专题数 / 采访数 / 播客数 / 通稿数 / 浏览量(仅 AI采访 场景

「我的」页统计卡:所有专题数 / 采访数 / 播客数 / 通稿数 / 浏览量(仅 AI采访 场景)。

GET /api/caifang/profile/podcasts 「我的播客」:仅 AI采访 场景播客,按时间倒序,支持 offset/limit 懒加载。
PUT /api/caifang/topic/{tid} /api/caifang/topic/{tid}

路径参数

参数类型必填说明
tidstring必填路径参数

Query 参数

参数类型必填说明
tidint必填查询参数

请求参数 (JSON Body)

参数类型必填说明
titlestring必填标题
organizerstring必填
intervieweestring必填
backgroundstring必填
host_ai_idint必填
rules_mdstring必填
statusint必填状态(1=启用 0=停用)
POST /api/caifang/topic/{tid}/chat 记者端逐步上报「记者与AI需求收集对话」,累积写入 AI采访记者对话记录 md。

路径参数

参数类型必填说明
tidstring必填路径参数

Query 参数

参数类型必填说明
tidint必填查询参数

请求参数 (JSON Body)

参数类型必填说明
recordstring必填
DELETE /api/caifang/topic/{tid} /api/caifang/topic/{tid}

路径参数

参数类型必填说明
tidstring必填路径参数

Query 参数

参数类型必填说明
tidint必填查询参数
POST /api/caifang/topic/{tid}/invites 专题详情页「添加采访嘉宾」:新增一位待采访嘉宾并生成其专属转发链接。

路径参数

参数类型必填说明
tidstring必填路径参数

Query 参数

参数类型必填说明
tidint必填查询参数

请求参数 (JSON Body)

参数类型必填说明
namestring必填名称
biostring必填简介
phonestring必填
PUT /api/caifang/topic/{tid}/invites/{iid} 更新邀请记录的名字/身份描述/状态(已转发/未转发/已接访/已忽略)。

路径参数

参数类型必填说明
tidstring必填路径参数
iidstring必填路径参数

Query 参数

参数类型必填说明
tidint必填查询参数
iidint必填查询参数

请求参数 (JSON Body)

参数类型必填说明
namestring必填名称
biostring必填简介
phonestring必填
statusint必填状态(1=启用 0=停用)
GET /api/caifang/topic/{tid}/config 记者端「记者与AI」需求收集对话的 S2S 配置。

路径参数

参数类型必填说明
tidstring必填路径参数

Query 参数

参数类型必填说明
tidint必填查询参数
GET /api/caifang/entry 通过 hash 解析专题(公开,供被采访者打开链接后展示标题)。

响应示例

{
  "ok": true,
  "topic": { "id": 3, "title": "AI 创业者访谈", "status": 1 },
  "invite": { }
}
POST /api/caifang/session 被采访者在某专题下创建(或复用)自己的采访会话。

请求参数 (JSON Body)

参数类型必填说明
topic_idint必填专题 ID
invite_hashstring必填邀请链接 hash
GET /api/caifang/session/{sid}/config 被采访端「AI与被采访者」采访对话的 S2S 配置(基于专题 rules_md)。

路径参数

参数类型必填说明
sidstring必填路径参数

Query 参数

参数类型必填说明
sidint必填查询参数
POST /api/caifang/session/{sid}/progress 保存采访对话进度(断点续访)。

路径参数

参数类型必填说明
sidstring必填路径参数

Query 参数

参数类型必填说明
sidint必填查询参数

请求参数 (JSON Body)

参数类型必填说明
recordstring必填
POST /api/caifang/session/{sid}/image 嘉宾端上传采访对话图片:base64 → 解码 → 落盘 → 返回可访问 URL。

路径参数

参数类型必填说明
sidstring必填路径参数

Query 参数

参数类型必填说明
sidint必填查询参数

请求参数 (JSON Body)

参数类型必填说明
image_base64string必填
POST /api/caifang/session/{sid}/voice 保存6秒语音:解码→48k→ASR→写入 xd_voices,并关联到会话。

路径参数

参数类型必填说明
sidstring必填路径参数

Query 参数

参数类型必填说明
sidint必填查询参数

请求参数 (JSON Body)

参数类型必填说明
audio_base64string必填
POST /api/caifang/session/{sid}/user_voices 嘉宾端采访中采集的用户语音(>3秒),采访结束时一并上传。

路径参数

参数类型必填说明
sidstring必填路径参数

Query 参数

参数类型必填说明
sidint必填查询参数

请求参数 (JSON Body)

参数类型必填说明
voicesstring必填
GET /api/caifang/session/{sid}/user_voices 记者端查看某位嘉宾(会话)采集到的用户语音列表。

路径参数

参数类型必填说明
sidstring必填路径参数

Query 参数

参数类型必填说明
sidint必填查询参数
POST /api/caifang/session/{sid}/user_voices/{idx}/edit 记者编辑某条采访采集的用户语音的转写文本,并落库到 xd_voices 成为可用音色。

路径参数

参数类型必填说明
sidstring必填路径参数
idxstring必填路径参数

Query 参数

参数类型必填说明
sidint必填查询参数
idxint必填查询参数

请求参数 (JSON Body)

参数类型必填说明
transcriptstring必填
POST /api/caifang/session/{sid}/finish 采访结束:保存转写与总结,将会话/专题置为 done。

路径参数

参数类型必填说明
sidstring必填路径参数

Query 参数

参数类型必填说明
sidint必填查询参数

请求参数 (JSON Body)

参数类型必填说明
transcriptstring必填
summarystring必填
POST /api/caifang/session/{sid}/podcast 记者端生成播客后回写 podcast_hash 到会话。

路径参数

参数类型必填说明
sidstring必填路径参数

Query 参数

参数类型必填说明
sidint必填查询参数

请求参数 (JSON Body)

参数类型必填说明
podcast_idint必填
POST /api/caifang/session/{sid}/podcast-rules 生成「播客生成规则文档」(由专题策划规则+采访规则+采访对话记录生成,已生成返回缓存)。

路径参数

参数类型必填说明
sidstring必填路径参数

Query 参数

参数类型必填说明
sidint必填查询参数

请求体:见实现(form-data / 二进制或空请求体)。

POST /api/caifang/session/{sid}/press 生成会话新闻通稿(已生成则返回缓存;body 传 force=true 强制重新生成)。

路径参数

参数类型必填说明
sidstring必填路径参数

Query 参数

参数类型必填说明
sidint必填查询参数

请求体:见实现(form-data / 二进制或空请求体)。

GET /api/caifang/session/{sid}/podcast-rules 读取「播客生成规则文档」(未生成则先生成)。

路径参数

参数类型必填说明
sidstring必填路径参数

Query 参数

参数类型必填说明
sidint必填查询参数
PUT /api/caifang/session/{sid}/podcast-rules 保存(编辑后)的「播客生成规则文档」。

路径参数

参数类型必填说明
sidstring必填路径参数

Query 参数

参数类型必填说明
sidint必填查询参数

请求参数 (JSON Body)

参数类型必填说明
rules_mdstring必填
GET /api/caifang/session/{sid}/press-rules 读取「新闻通稿生成规则文档」(未生成时返回默认撰写要求,供编辑)。

路径参数

参数类型必填说明
sidstring必填路径参数

Query 参数

参数类型必填说明
sidint必填查询参数
PUT /api/caifang/session/{sid}/press-rules 保存(编辑后)的「新闻通稿生成规则文档」。

路径参数

参数类型必填说明
sidstring必填路径参数

Query 参数

参数类型必填说明
sidint必填查询参数

请求参数 (JSON Body)

参数类型必填说明
rules_mdstring必填
GET /api/caifang/topic/{tid}/articles 专题文章列表(供「转发到微信好友」的文章详情页使用):该专题下所有已完成采访的会话及其新闻通稿。

路径参数

参数类型必填说明
tidstring必填路径参数

Query 参数

参数类型必填说明
tidint必填查询参数
GET /api/caifang/article/{sid} 公开新闻页(转发到微信好友后,好友未登录打开即可看到完整新闻):

公开新闻页(转发到微信好友后,好友未登录打开即可看到完整新闻): 返回新闻正文 + 标题 + 受访人 + 关联播客音频,供独立的用户视角文章页渲染。

路径参数

参数类型必填说明
sidstring必填路径参数

Query 参数

参数类型必填说明
sidint必填查询参数
GET /api/caifang/session/{sid}/detail 播客详情页数据:会话 + 专题 + 播客 + 通稿。

路径参数

参数类型必填说明
sidstring必填路径参数

Query 参数

参数类型必填说明
sidint必填查询参数
PUT /api/caifang/session/{sid}/ai 保存播客详情页选中的主持人/嘉宾 AI(覆盖专题默认角色)。

路径参数

参数类型必填说明
sidstring必填路径参数

Query 参数

参数类型必填说明
sidint必填查询参数

请求参数 (JSON Body)

参数类型必填说明
host_ai_idint必填
guest_ai_idint必填
GET /api/caifang/guests 记者端「采访嘉宾AI」:返回记者名下所有专题的采访嘉宾(会话)列表。

记者端「采访嘉宾AI」:返回记者名下所有专题的采访嘉宾(会话)列表。 每个嘉宾对应一次采访会话,含其 6 秒语音信息,供记者查看/编辑。

POST /api/caifang/session/{sid}/voice_edit 记者编辑嘉宾 6 秒语音的转写文本并保存(同步到 xd_voices.transcript)。

路径参数

参数类型必填说明
sidstring必填路径参数

Query 参数

参数类型必填说明
sidint必填查询参数

请求参数 (JSON Body)

参数类型必填说明
transcriptstring必填
GET /api/caifang/voice/sample 按 voice_id 返回音色音频文件(记者端播放嘉宾语音用)。

场景对话规则

所有应用的聊天对话规则(一对一对话、群聊引导、主持仪式、采访等)统一由播客场景规则管理。 管理员在播客控制台 → 管理员专区 → AI对话规则配置中按场景维护规则文档。 每个场景下可配置多条规则(不同标题区分用途),S2S 服务在调用对话时自动从数据库加载对应规则。

设计理念:
  • 统一管理:所有对话规则集中在 talk_rule 表,按 podcast_type(播客场景)分类
  • 场景维度:每个场景(如自由场景、旅游、采访、爱在一起等)下可有多条规则
  • 标题区分:规则通过 title 字段区分用途,如「AI旅伴与用户聊天规则」「AI群聊引导规则」「AI主持仪式规则」
  • S2S 自动加载:S2S 群聊服务自动从数据库加载标题含"群聊"的规则作为 system prompt,无需应用端传参
  • 第三方应用:通过以下两个 GET 接口拉取规则,用作自管 LLM 的 system prompt
鉴权要求:使用 X-Api-Key Header 或 ?api_key= Query 参数传递播客 API Key。

同行对讲(一对一)

同行对讲功能:查询附近派友 → 加好友 → 通过后一对一聊天。对方离线时由对方指定的AI代答,对方在线时由对方自己回复。

在线判断机制:用户在APP内时定期调用 POST /api/walkie/heartbeat(建议30秒一次),更新 pai_user_online.last_active。5分钟内有活跃=在线,超过5分钟=离线,发消息时由对方AI代答。
AI代答机制:用户通过心跳接口设置 ai_id(自己的代答AI)。好友发消息时,若用户离线且有代答AI,服务端自动调用 LLM 以用户口吻生成简短回复(50字内),写入消息表(is_ai_reply=1)。
POST /api/walkie/heartbeat 心跳(上线/更新定位)

用户在APP内时定期调用(建议30秒一次),更新在线状态和位置。也可设置自己的代答AI。

请求体(JSON)

参数类型必填说明
user_idint必填用户ID
latitudefloat选填当前纬度
longitudefloat选填当前经度
ai_idint选填用户指定的代答AI角色ID

请求示例

POST /podcast/api/walkie/heartbeat
{
  "user_id": 1001,
  "latitude": 22.5128,
  "longitude": 113.9380,
  "ai_id": 25
}
GET /api/walkie/nearby 附近派友

基于经纬度查询 radius 米内的用户(排除自己),返回在线状态和距离。用户需先调心跳上报位置才能被查到。

查询参数

参数类型必填说明
latfloat必填当前纬度
lngfloat必填当前经度
radiusint选填搜索半径(米,默认1000)
user_idint选填当前用户ID(排除自己)

响应示例

{
  "total": 1,
  "users": [
    {
      "user_id": 1002,
      "username": "小李",
      "latitude": 22.5130,
      "longitude": 113.9385,
      "ai_id": 30,
      "distance": 55,
      "online": true,
      "last_active": 1784214000
    }
  ]
}
POST /api/walkie/friend-request 发送好友请求

向附近派友发送好友请求,待对方通过后才能一对一聊天。

请求体(JSON)

参数类型必填说明
user_idint必填发起者用户ID
friend_idint必填被请求者用户ID
notestring选填请求备注(如"我也是来拍日落的")

错误响应

{ "error": "已发送过请求,待对方处理" }  // 400
{ "error": "已经是好友" }              // 400
{ "error": "不能加自己为好友" }          // 400
POST /api/walkie/friend-handle 通过/拒绝好友请求

处理别人发来的好友请求。通过后双向建立好友关系,可直接开始聊天。

请求体(JSON)

参数类型必填说明
request_idint必填好友请求ID
user_idint必填处理者用户ID(必须是被请求者)
actionstring必填accept=通过 / reject=拒绝
GET /api/walkie/friends 好友列表

返回当前用户的好友列表,含在线状态。

查询参数

参数类型必填说明
user_idint必填用户ID

响应字段

字段类型说明
user_id, username混合好友信息
became_friends_atint成为好友时间
onlinebool是否在线
GET /api/walkie/friend-requests 待处理好友请求

返回别人发给当前用户的、待处理的好友请求列表。

查询参数

参数类型必填说明
user_idint必填用户ID

响应字段

字段类型说明
idint请求ID(用于friend-handle)
from_user_id, from_username混合请求者信息
notestring请求备注
created_atint请求时间
GET /api/walkie/messages 一对一聊天记录

获取与某好友的聊天记录(双向),按时间正序返回。调用此接口会自动将发给当前用户的未读消息标记为已读。支持分页。

查询参数

参数类型必填说明
user_idint必填当前用户ID
friend_idint必填对方用户ID
limitint选填每页数量(默认50,最大200)
beforeint选填分页游标,消息ID

消息字段

字段类型说明
id, from_user, to_user混合消息基本信息
content, msg_type混合内容/类型(text/ai)
is_ai_replyint1=对方AI代答 0=真人
ai_idint代答AI角色ID(仅AI回复)
created_at, read_atint发送/已读时间
from_username, to_usernamestring双方昵称
POST /api/walkie/messages 发送一对一消息(含AI代答)

给好友发消息。核心逻辑:好友在线(5分钟内活跃)→ 消息直接送达,等好友拉取;好友离线且有代答AI → 服务端自动调用 LLM 以好友口吻生成简短回复,写入消息表。

请求体(JSON)

参数类型必填说明
user_idint必填发送者用户ID
to_userint必填接收者用户ID(必须是好友)
contentstring必填消息内容

响应(三种情况)

// 1. 对方在线:消息送达,等对方拉取
{ "ok": true, "message_id": 10, "created_at": 1784214100, "delivered": "online" }

// 2. 对方离线+有代答AI:自动生成AI回复
{
  "ok": true,
  "message_id": 10,
  "created_at": 1784214100,
  "delivered": "ai_reply",
  "ai_message_id": 11,
  "ai_reply": "你好呀,我现在暂时不在,我是TA的AI助手。你说的日落照片我也想看看呢,稍后TA回来一定回复你~"
}

// 3. 对方离线+无代答AI:消息存库,等对方上线拉取
{ "ok": true, "message_id": 10, "created_at": 1784214100, "delivered": "offline" }

错误响应

{ "error": "非好友,无法发送" }  // 403

S2S 语音群聊/对讲(落盘+通知模式)

API Key:派群聊与对讲 WS 接口使用与上方相同的公用 Key(APP 专属 Key)。Key 是 APP 维度的,与用户无关——开发者创建 APP 时由管理员分配,每个 APP 一个专属 Key,所有用户共用此 Key 访问。连接时 api_key 放 query string,user_id 传当前业务用户 ID(由 APP 传入,代表当前操作的用户)。
核心设计原则:服务端生成 AI 回复后落盘入库,通过 WS 推送轻量 JSON 通知给在线用户,客户端收到通知后主动调 HTTP 接口拉取音频和文本。服务端不推音频流,只推小 JSON 通知,减轻服务端压力。

AI 触发条件(群聊):① @AI 或 @所有人 ② 文本含「AI/思思/小助手」等关键词 ③ 群聊冷场超 180 秒。冷却 60 秒防刷屏。
AI 代答条件(对讲):对方离线(5分钟无活跃)且对方已配置代答 AI(pai_user_online.ai_id)。
WS wss://dev.nice-ai.cn/s2s/pai/{pai_id}?user_id=xxx&api_key=xxx 派群聊语音通道(落盘+通知)

连接后进入派群聊通道。客户端发送文本/语音消息,服务端入库并广播 JSON 通知给所有在线成员。触发 AI 回复时,服务端调 LLM 生成文本 + TTS 合成音频落盘,再推送通知,客户端按需拉取音频。

认证要求:使用公用 Key(APP 专属 Key)。Key 与用户无关,user_id 由 APP 传入代表当前业务用户。连接前该用户需为该派的成员。

路径参数

参数类型必填说明
pai_idint必填派 ID(路径中)
user_idint必填用户 ID(query,由 APP 传入)
api_keystring必填公用 Key(APP 专属 Key,query,由管理员分配)

连接错误场景

错误消息触发条件
认证失败:API Key 无效api_key 不存在/已停用/已过期/额度耗尽
派不存在pai_id 无效
派已解散派 status != 1
派已被管理员禁用派 enabled == 0
你不是该派成员,请先加派用户不是该派成员

客户端 → 服务端(JSON)

type字段说明
send_texttext, mention_user_ids, mention_ai发送文本消息。text 上限 500 字符,超出自动截断。mention_user_ids 逗号分隔用户 ID 或 "all";mention_ai=true 强制触发 AI
send_voiceaudio_base64, duration发送语音消息。audio_base64=wav base64,duration=秒。服务端调 ASR 识别后入库
ping—心跳,服务端回 pong

服务端 → 客户端(JSON)

// 1. 新消息通知(用户消息 / AI回复都走这个)
{
  "type": "new_msg",
  "msg_id": 12,
  "sender_type": "ai",          // "ai" 或 "user"
  "user_id": 0,                // AI消息时为0
  "username": "安娜",
  "msg_type": "ai",             // text/ai/system
  "text": "大家好呀!今天天气真不错...",
  "audio_url": "",             // 空,客户端按 msg_id 调 HTTP 拉
  "audio_ready": 1,            // 1=音频已落盘可拉取, 0=纯文本或语音识别中
  "audio_duration": 11.67,
  "voice_origin": 1,          // 仅 send_voice 消息:1=语音消息(非AI合成)
  "voice_duration": 5.3,         // 仅 send_voice 消息:用户原始语音时长(秒)
  "ai_id": 25,               // 仅AI消息
  "ai_name": "安娜",
  "created_at": 1784225289
}

// 2. 系统通知(进群/离群)
{ "type": "system", "text": "大明哥 进入了派", user_id: 4, "username": "大明哥", "created_at": 1784225286 }

// 3. 心跳响应
{ "type": "pong" }

// 4. 错误
{ "type": "error", "message": "认证失败:API Key 无效" }
字段说明:
  • audio_ready=0 时:可能是纯文本消息(无音频),也可能是语音消息识别中(用户发的 send_voice,ASR 识别后入库,但服务端不合成 AI 音频)。客户端可通过 voice_origin=1 区分:有此字段=语音消息,无此字段=纯文本/AI回复
  • voice_origin 和 voice_duration 仅在 send_voice 消息通知中出现,AI 回复和 send_text 消息无这两个字段
  • audio_duration 在语音消息中表示用户原始语音时长;在 AI 回复中表示AI 合成音频时长

AI 回复音频拉取

收到 audio_ready=1 的 new_msg 后,调 GET /api/pai/messages/{msg_id}/audio 拉取 MP3 音频(支持 Range 范围请求)。

连接生命周期

  • 连接成功 → 广播 system 通知「XX 进入了派」
  • 空闲 600 秒自动断开(发 error 后关闭)
  • 断开 → 广播 system 通知「XX 离开了派」
WS wss://dev.nice-ai.cn/s2s/walkie?user_id=xxx&api_key=xxx 派友对讲语音通道(落盘+通知+AI代答)

一对一语音对讲通道。发送消息时:对方在线 → 直接推送通知;对方离线且有代答AI → 服务端自动调 LLM+TTS 生成代答回复并落盘,推送通知给发送者。

认证要求:使用公用 Key(APP 专属 Key)。Key 是 APP 维度的,与用户无关。

路径参数

参数类型必填说明
user_idint必填当前用户 ID(query)
api_keystring必填S2S APP Key(query,由管理员分配)

客户端 → 服务端(JSON)

type字段说明
send_textto_user, text给好友发文本。to_user 必须是好友
send_voiceto_user, audio_base64, duration给好友发语音。服务端 ASR 识别后入库
ping—心跳

服务端 → 客户端(JSON)

// 1. 新消息通知(含发送者自己收到的回执 + AI代答)
{
  "type": "new_msg",
  "msg_id": 6,
  "sender_type": "ai",          // "ai"=代答, "user"=真人
  "from_user": 9999,
  "from_username": "用户9999",
  "msg_type": "ai",
  "text": "大明哥好呀!最近挺顺的...",
  "audio_url": "",
  "audio_ready": 1,
  "audio_duration": 10.92,
  "voice_origin": 1,          // 仅 send_voice 消息:1=语音消息(非AI合成)
  "voice_duration": 5.3,         // 仅 send_voice 消息:用户原始语音时长(秒)
  "ai_id": 25,
  "ai_name": "安娜",
  "created_at": 1784225289
}

// 2. 好友上下线通知
{ "type": "online_status", "user_id": 9999, "username": "用户9999", "online": true }

// 3. 心跳/错误
{ "type": "pong" }
{ "type": "error", "message": "对方不是你的好友" }
字段说明:
  • voice_origin 和 voice_duration 仅在 send_voice 消息通知中出现,AI 代答和 send_text 消息无这两个字段
  • 对讲中 from_user 在 AI 代答消息中为被代答方(即接收方)的 user_id,而非 AI 的 id
  • text 上限 500 字符,超出自动截断(与派群聊一致)

AI 代答音频拉取

收到 sender_type=ai 的 new_msg 后,调 GET /api/walkie/messages/{msg_id}/audio 拉取 MP3 音频(支持 Range)。

代答 AI 配置

用户通过 POST /api/walkie/heartbeat 设置 ai_id 字段,配置自己的代答 AI。好友给你发消息时若你离线,由该 AI 代答。

GET /api/pai/messages/{msg_id}/audio 拉取派群聊 AI 回复音频(MP3,支持 Range)

客户端收到 WS new_msg 通知(audio_ready=1)后,调本接口拉取 AI 回复的 MP3 音频。支持 HTTP Range 范围请求,便于流式播放。

路径参数

参数类型必填说明
msg_idint必填消息 ID(pai_message.id)

响应

  • 200 OK:完整 MP3 文件,Content-Type: audio/mpeg,Content-Length 为文件大小
  • 206 Partial Content:Range 请求命中的字节范围,Content-Range: bytes start-end/total
  • 404 Not Found:消息不存在或无音频

示例(完整下载)

GET /podcast/api/pai/messages/12/audio
→ 200 OK
  Content-Type: audio/mpeg
  Content-Length: 187628
  Accept-Ranges: bytes
  <187628 字节 MP3 数据>

示例(Range 范围请求,流式播放)

GET /podcast/api/pai/messages/12/audio
Range: bytes=0-1023
→ 206 Partial Content
  Content-Type: audio/mpeg
  Content-Length: 1024
  Content-Range: bytes 0-1023/187628
  <1024 字节 MP3 数据>

音频规格

  • 格式:MP3(MPEG ADTS, layer III)
  • 采样率:48 kHz
  • 声道:Stereo
  • 比特率:64 kbps
  • 来源:VoxCPM2-Fast TTS(48kHz PCM → MP3 编码)
GET /api/walkie/messages/{msg_id}/audio 拉取对讲 AI 代答音频(MP3,支持 Range)

客户端收到 WS new_msg 通知(sender_type=ai 且 audio_ready=1)后,调本接口拉取 AI 代答的 MP3 音频。支持 Range 范围请求。

路径参数

参数类型必填说明
msg_idint必填消息 ID(pai_friend_message.id)

响应

  • 200 OK:完整 MP3 文件,Content-Type: audio/mpeg
  • 206 Partial Content:Range 请求命中的字节范围
  • 404 Not Found:消息不存在或无音频

示例

GET /podcast/api/walkie/messages/6/audio
→ 200 OK
  Content-Type: audio/mpeg
  Content-Length: 175724
  Accept-Ranges: bytes
  <175724 字节 MP3 数据>

明星频道(star)

明星艺人主页、音色、合约与邀请(/star 小程序独立域)。

GET /star/api/star/info 获取明星艺人主页信息(支持 star_id 或 user_id 参数)

获取明星艺人主页信息(支持 star_id 或 user_id 参数)

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)
star_idint可选明星 ID(默认 0)

响应示例

{
  "star": {
    "id": 12, "user_id": 88, "name": "晓松", "gender": "male",
    "avatar_url": "https://...jpg", "bio": "资深音乐人",
    "voice_count": 5, "role_count": 3, "call_count": 120,
    "total_revenue": 860.0, "contract_status": 1
  }
}
PUT /star/api/star/info 更新明星艺人资料(名称、性别、头像、简介)

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)

请求参数 (JSON Body)

参数类型必填说明
namestring必填名称
genderstring必填性别
avatar_urlstring必填头像 URL
avatar_datastring必填
biostring必填简介
POST /star/api/star/bio/generate AI生成明星艺人简介:搜索百度百科→抓取→总结200字以内

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)

请求参数 (JSON Body)

参数类型必填说明
namestring必填名称
GET /star/api/star/voices 获取明星艺人的音色列表

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)
DELETE /star/api/star/voice/{voice_id} 删除明星艺人音色

路径参数

参数类型必填说明
voice_idint必填音色 ID

Query 参数

参数类型必填说明
voice_idint必填音色 ID
user_idint可选用户 ID(默认 0)
PUT /star/api/star/voice/{voice_id} 更新明星艺人音色(名称、音色说明)

路径参数

参数类型必填说明
voice_idint必填音色 ID

Query 参数

参数类型必填说明
voice_idint必填音色 ID
user_idint可选用户 ID(默认 0)

请求参数 (JSON Body)

参数类型必填说明
voice_namestring必填
descriptionstring必填
POST /star/api/star/voice/{voice_id}/audio 上传(覆盖)音色音频文件。音频统一存 USER_VOICES_DIR(音色存储统一目录铁律),

上传(覆盖)音色音频文件。音频统一存 USER_VOICES_DIR(音色存储统一目录铁律), 经 ffmpeg 转码(loudnorm 响度归一化 + 48kHz 单声道 16-bit PCM)后存绝对路径。

路径参数

参数类型必填说明
voice_idint必填音色 ID

Query 参数

参数类型必填说明
voice_idint必填音色 ID
user_idint可选用户 ID(默认 0)

请求体:见实现(form-data / 二进制或空请求体)。

GET /star/api/star/contracts 获取明星艺人的合约列表

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)
GET /star/api/categories 获取所有明星分类(公开接口,用于九宫格展示)——以 xd_ai_type 的 star 组为准
GET /star/api/stars 获取明星艺人列表(公开接口,可按分类筛选、关键词搜索、分页)

Query 参数

参数类型必填说明
categorystring可选标签类别(identity/expertise/audience)
keywordstring可选搜索关键词
pageint可选页码(默认 1)
page_sizeint可选每页条数(默认 20)
GET /star/api/star/{star_id}/ais 获取某个明星艺人的AI分身列表(公开接口)

路径参数

参数类型必填说明
star_idint必填明星 ID

Query 参数

参数类型必填说明
star_idint必填明星 ID
user_idint可选用户 ID(默认 0)
POST /star/api/star/invite/accept 接受邀请:将当前用户绑定到指定明星

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)

请求参数 (JSON Body)

参数类型必填说明
star_idint必填明星 ID
GET /star/api/star/records 获取明星艺人的收入/使用记录

Query 参数

参数类型必填说明
user_idint可选用户 ID(默认 0)
record_typestring可选查询参数
pageint可选页码(默认 1)
page_sizeint可选每页条数(默认 20)
GET /star/api/star/contract_md 获取合约MD文档

图片生成 API

Z-Image-Turbo 生图服务(阿里通义 6B DiT,8 步蒸馏)的对外 HTTP API,基础地址:https://dev.nice-ai.cn/ai_image。 专注文生图,原生分辨率 512~2048,中英文文字渲染与提示词遵循是强项(适合封面/海报/招牌等含文字场景)。

特性:
  • 极速生成:8 步蒸馏推理,单张约 2~5 秒
  • 中英文文字渲染:可准确生成含中文招牌、标题、海报文字的图片
  • CFG-free:蒸馏模型不支持负向提示词(negative_prompt 会被忽略)
  • 文件大小控制:传 max_size_kb 自动压缩到目标大小(并转 JPEG)
  • 不支持图生图:Z-Image-Turbo 为纯文生图模型,/ai_image/generate_edit 返回 501;如有编辑需求需另行部署 Qwen-Image-Edit
POST /ai_image/generate 文生图

根据文字描述生成图片,返回 base64 编码的图片数据。

请求参数

参数类型必填默认值说明
promptstring必填—图片描述(中文/英文均可,如"夏日迪士尼乐园亲子分享")
widthint可选1024输出宽度(64~2048,自动对齐 16 的倍数)
heightint可选1024输出高度(64~2048)
num_inference_stepsint可选8推理步数(8 步蒸馏版默认 8 即为最佳工作点)
seedint可选随机随机种子,固定后同 prompt 生成同一张图
max_size_kbint可选0压缩到指定 KB 以内(>0 时输出转 JPEG)
rawboolean可选falsetrue 时直接返回图片二进制(Content-Type: image/jpeg),不走 JSON
兼容说明:旧版接口的 negative_prompt、guidance_scale 参数仍可传入但会被忽略(Z-Image-Turbo 为 CFG-free 蒸馏模型,无负向提示机制)。

响应

{
  "image": "iVBORw0KGgoAAAANS...",   // base64 编码的图片数据
  "format": "png",                    // png 或 jpeg(压缩时)
  "width": 1024,
  "height": 1024,
  "elapsed": 5.87                     // 生成耗时(秒)
}

curl 示例

curl https://dev.nice-ai.cn/ai_image/generate \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "夏日迪士尼乐园亲子分享",
    "width": 1024,
    "height": 1024,
    "num_inference_steps": 20
  }'
POST /ai_image/generate_edit 图生图(当前引擎不支持,返回 501)

保留的兼容端点。当前引擎 Z-Image-Turbo 为纯文生图模型,不支持图生图/编辑,调用此端点固定返回 501:

{
  "error": "Z-Image-Turbo does not support image editing (text-to-image only). For image editing use Qwen-Image-Edit or FLUX.2-Kontext deployment."
}
编辑需求怎么办:如需"根据一张图 + 指令重绘/局部修改"能力,需另行部署 Qwen-Image-Edit(阿里通义官方推荐的编辑模型)或 FLUX.2-Kontext。需要时联系管理员部署,API 路径可保持不变(仅更换后端引擎)。
服务特性:生成耗时约 2~8 秒(8 步蒸馏,取决于分辨率),客户端请设置 ≥60s 超时。服务的并发能力有限(单卡串行),高并发场景请在客户端做排队。同服务的内网直连地址为 http://127.0.0.1:8555(仅本机可用,路径不带 /ai_image 前缀)。

LLM API 接口

OpenAI 兼容的 LLM HTTP API 服务,基础地址:https://dev.nice-ai.cn/llm/v1。 完全兼容 OpenAI 接口规范,可直接使用官方 SDK 或任何支持 OpenAI 的工具接入。

特性:
  • OpenAI 兼容:完全遵循 /v1/chat/completions 接口规范
  • 流式与非流式:支持 SSE 流式响应和一次性返回
  • 多模态输入:支持图片理解(image_url 格式)
  • 思考模式:代码生成接口默认开启 enable_thinking,返回带思考过程的响应
  • Tool Call:支持 OpenAI 兼容的函数调用
认证方式:使用 Bearer Token 认证,在请求头 Authorization 中传入 API Key(sk-nice-xxx)。 API Key 在管理后台(https://dev.nice-ai.cn/llm/)创建并管理。
POST /llm/v1/chat/completions 对话补全(免思考)

创建聊天补全,兼容 OpenAI 接口。此端点默认关闭思考模式,适用于普通对话、语音助手等场景。

请求参数

参数类型必填默认值说明
modelstring可选Nice-ai模型名
messagesarray必填—消息数组,含 role 和 content。支持多模态(image_url)
streamboolean可选false是否流式返回(SSE)
temperaturefloat可选0.7采样温度,0-2
max_tokensint可选4096最大生成 token 数
toolsarray可选—OpenAI 兼容的函数调用工具定义

curl 示例

curl https://dev.nice-ai.cn/llm/v1/chat/completions \
  -H "Authorization: Bearer sk-nice-xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "Nice-ai",
    "messages": [
      {"role": "user", "content": "你好"}
    ]
  }'

Python SDK 示例

pip install openai
from openai import OpenAI

client = OpenAI(
    api_key="sk-nice-xxx",
    base_url="https://dev.nice-ai.cn/llm/v1",
)

resp = client.chat.completions.create(
    model="Nice-ai",
    messages=[{"role": "user", "content": "你好"}],
)
print(resp.choices[0].message.content)

流式响应示例

stream = client.chat.completions.create(
    model="Nice-ai",
    messages=[{"role": "user", "content": "讲个故事"}],
    stream=True,
)

for chunk in stream:
    delta = chunk.choices[0].delta.content
    if delta:
        print(delta, end="", flush=True)

多模态(图片理解)示例

resp = client.chat.completions.create(
    model="Nice-ai",
    messages=[{
        "role": "user",
        "content": [
            {"type": "text", "text": "描述这张图片"},
            {"type": "image_url", "image_url": {
                "url": "https://example.com/image.png"
            }},
        ],
    }],
)
print(resp.choices[0].message.content)

响应示例(非流式)

{
  "id": "chatcmpl-xxx",
  "object": "chat.completion",
  "created": 1700000000,
  "model": "Nice-ai",
  "choices": [{
    "index": 0,
    "message": {
      "role": "assistant",
      "content": "你好!有什么可以帮你的吗?"
    },
    "finish_reason": "stop"
  }],
  "usage": {
    "prompt_tokens": 20,
    "completion_tokens": 15,
    "total_tokens": 35
  }
}

SSE 数据格式(流式)

每条消息以 data: 开头,以 \n\n 结尾,最后以 data: [DONE] 表示结束:

data: {"id":"chatcmpl-xxx","object":"chat.completion.chunk","choices":[{"delta":{"content":"你"},"index":0}]}

data: {"id":"chatcmpl-xxx","object":"chat.completion.chunk","choices":[{"delta":{"content":"好"},"index":0}]}

data: [DONE]
POST /llm/v1/code/chat/completions 代码生成(默认开启思考模式)

代码生成专用接口,默认开启思考模式(enable_thinking=True)。 开发者使用此接口可获得带思考过程的代码生成响应,模型会先思考再输出代码,生成质量更高。

与 /v1/chat/completions 的区别:
  • 此端点强制开启 enable_thinking,模型会先进行思考再输出回复
  • 请求参数与 /v1/chat/completions 完全相同,无需额外传参
  • 流式响应中,思考内容会作为 content 的一部分输出(部分模型可能通过 reasoning_content 字段返回,Gateway 已统一合并到 content)
  • 适用于 Cursor、Continue、Trae 等代码生成场景

请求参数

参数类型必填默认值说明
modelstring可选Nice-ai模型名
messagesarray必填—消息数组,含 role 和 content
streamboolean可选false是否流式返回(SSE)
temperaturefloat可选0.7采样温度,0-2
max_tokensint可选4096最大生成 token 数

curl 示例

curl https://dev.nice-ai.cn/llm/v1/code/chat/completions \
  -H "Authorization: Bearer sk-nice-xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "Nice-ai",
    "messages": [
      {"role": "user", "content": "写一个快速排序的Python实现"}
    ],
    "stream": true
  }'

Python SDK 示例

from openai import OpenAI

client = OpenAI(
    api_key="sk-nice-xxx",
    base_url="https://dev.nice-ai.cn/llm/v1",
)

# 注意:endpoint 路径为 /v1/code/chat/completions
# 使用 OpenAI SDK 时需自定义路径
import httpx

resp = httpx.post(
    "https://dev.nice-ai.cn/llm/v1/code/chat/completions",
    headers={
        "Authorization": "Bearer sk-nice-xxx",
        "Content-Type": "application/json",
    },
    json={
        "model": "Nice-ai",
        "messages": [{"role": "user", "content": "写一个快速排序的Python实现"}],
        "stream": False,
    },
    timeout=120,
)
print(resp.json()["choices"][0]["message"]["content"])

开发工具配置

在主流代码生成工具中配置使用:

Cursor
  • 打开 Settings → Models
  • 开启 Override OpenAI Base URL
  • 填入 Base URL:https://dev.nice-ai.cn/llm/v1
  • 填入你的 API Key,模型名填 Nice-ai
VS Code (Continue 插件)

编辑 ~/.continue/config.json:

{
  "models": [{
    "title": "Nice LLM",
    "provider": "openai",
    "model": "Nice-ai",
    "apiBase": "https://dev.nice-ai.cn/llm/v1",
    "apiKey": "sk-nice-xxx"
  }]
}
Trae
  • 进入 模型设置 → 自定义
  • API 地址填入:https://dev.nice-ai.cn/llm/v1
  • API Key 填入你的密钥
  • 模型名填 Nice-ai

响应示例(非流式)

{
  "id": "chatcmpl-xxx",
  "object": "chat.completion",
  "created": 1700000000,
  "model": "Nice-ai",
  "choices": [{
    "index": 0,
    "message": {
      "role": "assistant",
      "content": "下面是快速排序的 Python 实现...\n\n```python\ndef quicksort(arr):\n    if len(arr) <= 1:\n        return arr\n    pivot = arr[len(arr) // 2]\n    left = [x for x in arr if x < pivot]\n    middle = [x for x in arr if x == pivot]\n    right = [x for x in arr if x > pivot]\n    return quicksort(left) + middle + quicksort(right)\n```"
    },
    "finish_reason": "stop"
  }],
  "usage": {
    "prompt_tokens": 15,
    "completion_tokens": 355,
    "total_tokens": 370
  }
}
GET /llm/v1/models 获取可用模型列表

获取可用模型列表,需在请求头携带 API Key。

请求示例

curl https://dev.nice-ai.cn/llm/v1/models \
  -H "Authorization: Bearer sk-nice-xxx"

响应示例

{
  "object": "list",
  "data": [{
    "id": "Nice-ai",
    "object": "model",
    "created": 1700000000,
    "owned_by": "nice",
    "max_length": 32768
  }]
}

内容安全审核(ai_safe)

通用文本安全审核服务,供各应用端验证用户输入文本是否安全(昵称/评论/弹幕/聊天消息等)。
基础地址:https://dev.nice-ai.cn/ai_safe(无需鉴权,内网直连 http://127.0.0.1:8465)

POST /ai_safe/api/check 文本安全审核(黑名单 + LLM 语义双层)

双层审核策略:第一层本地黑名单词库(内置词 + bad_words.txt 热加载,毫秒级拦截已知词);第二层 LLM 语义判定(拦截谐音、拼音缩写、拆字、新梗等变体绕过)。相同文本自动缓存判定结果,不重复调用。

请求参数

参数类型必填默认值说明
textstring必填—待审核文本,超过 2000 字符截断;空文本直接放行
scenestring可选general审核场景:general 通用公开文本(评论/发帖/弹幕)、nickname 昵称姓名(最严,会被展示或语音播报)、chat 聊天消息(尺度略宽,放行"卧槽"等轻度口头禅)

curl 示例

curl https://dev.nice-ai.cn/ai_safe/api/check \
  -H "Content-Type: application/json" \
  -d '{"text": "你这个傻逼", "scene": "general"}'

响应参数

字段类型说明
codeint0 成功;1 失败(message 为原因)
data.safeboolean是否安全
data.reasonstring不安全时的简短原因(LLM 判定时返回,黑名单命中为固定文案)
data.hitsarray黑名单命中的词列表(仅 source=blacklist 时非空)
data.sourcestring判定来源:blacklist 黑名单拦截 / llm LLM 语义判定 / pass 空文本直接放行

响应示例

// 黑名单拦截(毫秒级)
{"code": 0, "data": {"safe": false, "reason": "包含不当用语", "hits": ["傻逼"], "source": "blacklist"}}

// LLM 语义拦截(谐音/缩写变体)
{"code": 0, "data": {"safe": false, "reason": "包含侮辱性脏话及拼音缩写攻击", "hits": [], "source": "llm"}}

// 放行
{"code": 0, "data": {"safe": true, "reason": "", "hits": [], "source": "llm"}}
GET /ai_safe/api/scenes 获取审核场景列表

请求示例

curl https://dev.nice-ai.cn/ai_safe/api/scenes

响应示例

{
  "code": 0,
  "data": [
    {"scene": "general",  "name": "通用文本", "desc": "评论/发帖/弹幕/表单等公开可见文本"},
    {"scene": "nickname", "name": "昵称姓名", "desc": "昵称/姓名/称呼等短文本,会被展示或语音播报"},
    {"scene": "chat",     "name": "聊天消息", "desc": "私聊/对话消息,尺度略宽于公开发布"}
  ]
}
GET /ai_safe/healthz 审核服务健康检查

请求示例

curl https://dev.nice-ai.cn/ai_safe/healthz

响应示例

{
  "code": 0,
  "data": {
    "service": "ai_safe",
    "port": 8465,
    "bad_words": 96,
    "cache_size": 2,
    "scenes": ["general", "nickname", "chat"]
  }
}

飞宴 · 时光电台(对外三参数 API)

应用端只传三个参数 —— date(历史日期)、from(落款人 / 请客方)、 to(长辈方 / 被请客方)—— 就能拿到由朱时茂主持的、 指定某一天的整台怀旧电台音频:从整点报时、主持人开场,到各档节目 (新闻和报纸摘要 / 历史上的今天 / 经典歌曲欣赏 / 影视剧场 / 今日生活 / 春晚记忆 / 诗词赏析 / 经典小品 / 特色菜讲解…), 一路播到收尾。默认从头播到尾,也可以按节目单点选某一档单独播放。

音频特性:全部为 mp3、边合成边播(chunked 流式,首包约 1~2 秒,不必等整台合成完); 档位已落盘时直接读盘并支持 Range(可拖放、可续播)。 同一天同一档的文稿与音频全平台共用一份缓存,多人同时收听不会重复消耗。
基础地址:https://dev.nice-ai.cn/feiyan/api/radio
响应里的 stream_url 已经是完整地址,可直接塞给 <audio> 播放,无需自行拼前缀。
同一句话会对着谁讲:from / to 会写进主持人开场词,例如实测输出 「这里是飞宴人民广播电台,我是朱时茂。节目刚开始,信箱里就到了一封信,落款是「小伟」。 信里说,有几句话想借着电波,说给爸爸妈妈听…」
年代风格按日期自动匹配:1979 年 → 七十年代(粮票、半导体、样板戏…的口吻与串场)。
GET 请求参数(三个必传) 所有接口通用

Query 参数

参数类型必填说明
datestring必填历史日期 YYYY-MM-DD;《人民日报》版面自 1946-05-15 起
fromstring必填落款人 / 请客方,如「小伟」「儿子小伟」
tostring必填长辈方 / 被请客方,如「爸爸妈妈」「奶奶」
clockstring选填报时时刻 HH:MM,默认 20:00(开头会先报「现在时间…」)
apikeystring选填平台后台配置了 radio_api_key 时必须带上(也可放在 X-Api-Key 头里)
参数缺一不可:缺少必传项会直接返回 {"code":1,"msg":"…"}, 例如 {"code":1,"msg":"from 必传:落款人(请客方)"}。
GET /feiyan/api/radio 节目单 + 每档播放地址

返回这台的段落序列(报时 / 开场 / 各档节目 / 垫乐 / 广告 / 收尾)与 可按点选的节目清单,应用端据此渲染列表、点谁播谁。

请求示例

curl "https://dev.nice-ai.cn/feiyan/api/radio?date=1979-05-15&from=小伟&to=爸爸妈妈"

响应示例(实测节选)

{
  "code": 0,
  "data": {
    "date": "1979-05-15",
    "from": "小伟",
    "to": "爸爸妈妈",
    "clock": "20:00",
    "host": "朱时茂",
    "era": "1970",
    "style": "七十年代",
    "stream_url": "https://dev.nice-ai.cn/feiyan/api/radio/stream?date=1979-05-15&from=小伟&to=爸爸妈妈&clock=20:00",
    "lineup": [
      { "index": 0, "type": "open", "est": 20 },
      { "index": 1, "type": "program", "key": "time_radio",
        "name": "新闻和报纸摘要", "est": 300,
        "stream_url": "https://dev.nice-ai.cn/feiyan/api/radio/program/time_radio/stream?date=1979-05-15&from=小伟&to=爸爸妈妈" }
    ],
    "programs": [
      { "key": "time_radio", "name": "新闻和报纸摘要", "est": 300, "stream_url": "…" },
      { "key": "today_history", "name": "历史上的今天", "est": 95, "stream_url": "…" },
      { "key": "star_song", "name": "经典歌曲欣赏", "est": 240, "stream_url": "…" }
    ]
  }
}
实测该参数组合:29 个段落、8 档可点播节目,主持人 朱时茂、年代风格「七十年代」。 lineup 里的 type 依次为 chime(整点报时)/ time_signal / cnr(电台开场曲)/ open / program / music / ad / close。
GET /feiyan/api/radio/stream 整台音频(默认从头播到尾)

返回整台的 audio/mpeg 音频流:整点报时 → 主持人开场(含落款人 / 长辈方)→ 各档节目 → 收尾。 直接作为 src 播放即可。

请求示例

curl -o radio.mp3 "https://dev.nice-ai.cn/feiyan/api/radio/stream?date=1979-05-15&from=小伟&to=爸爸妈妈"

响应

项说明
Content-Typeaudio/mpeg
传输方式chunked 流式(X-Accel-Buffering: no,nginx 不缓冲,边生成边播)
首包耗时实测 约 0.03 秒(报时音效先出,人声随后到)
提示:整台时长可观(约十几分钟),播放器不要设置过短的超时; 边播边生成,中途断开也不会影响已经落盘的档位。
GET /feiyan/api/radio/program/{key}/stream 按节目单点选某一档

key 取 /feiyan/api/radio 返回的 programs[].key(如 time_radio、today_history、star_song、 drama、life、chunwan、poetry、skit、dish_story)。

请求示例

curl -o news.mp3 "https://dev.nice-ai.cn/feiyan/api/radio/program/time_radio/stream?date=1979-05-15&from=小伟&to=爸爸妈妈"

响应

同样是 audio/mpeg;该档已落盘时直接读盘返回(带 Content-Length 与 Accept-Ranges,可拖放 / 断点续播),未落盘则边合成边播。 实测「新闻和报纸摘要」首包 0.027 秒、24.6 秒输出约 2 MB。

GET /feiyan/api/radio/timeline 时间轴与字幕(边播边更新)

返回这台节目每段的真实起止秒与字幕文本,边生成边追加 —— 应用端据此做字幕对齐, 不必自己估算时长。

请求示例

curl "https://dev.nice-ai.cn/feiyan/api/radio/timeline?date=1979-05-15"

响应示例(实测节选)

{
  "code": 0,
  "data": {
    "total": 57.3,
    "items": [
      { "type": "chime", "name": "整点报时", "start": 0, "dur": 12.4, "text": "" },
      { "type": "time_signal", "name": "整点报时", "text": "现在时间,一九七九年五月十五日,二十点零分。" },
      { "type": "cnr", "name": "电台开场曲", "text": "" },
      { "type": "open", "name": "开场",
        "text": "这里是飞宴人民广播电台,我是朱时茂。节目刚开始,信箱里就到了一封信,落款是\"小伟\"。信里说,有几句话想借着电波,说给爸爸妈妈听:…" }
    ]
  }
}
用法:先请求 /stream 开始播放,再按 1~2 秒的间隔轮询本接口取最新时间轴 (total 会逐步增长),用 start / dur 对齐当前播放位置切换字幕。 该日期还没播过时返回空列表并附 hint。
GET 接入示例 小程序 / H5 直接可用

① 整台从头播到尾

const API = 'https://dev.nice-ai.cn/feiyan/api/radio';
const q = 'date=1979-05-15&from=' + encodeURIComponent('小伟')
        + '&to=' + encodeURIComponent('爸爸妈妈');

// 节目单(拿主持人 / 年代风格 / 可点播清单)
const info = await (await fetch(`${API}?${q}`)).json();
document.getElementById('host').textContent = info.data.host;   // 朱时茂

// 直接播整台(浏览器 / H5)
document.getElementById('player').src = info.data.stream_url;

// 微信小程序:innerAudioContext
// const a = wx.createInnerAudioContext();
// a.src = info.data.stream_url;  a.play();

② 按节目单点选播放

// info.data.programs = [{ key, name, est, stream_url }, ...]
info.data.programs.forEach(function (p) {
  // 渲染成列表,点谁就把 p.stream_url 交给播放器
  // 例:第三档「经典歌曲欣赏」
});
// 也可以直接拼:`${API}/program/${key}/stream?${q}`
两个小提醒: ① 音频是流式生成的,播放器别设过短的超时; ② 同一 date + from + to 反复请求不会重复消耗 —— 文稿与音频都有缓存。