|
@@ -1,287 +0,0 @@
|
|
|
-import { BASE_URL } from "@/config/index";
|
|
|
-import type { ICharacter, TEntityVoiceCloneRecord } from "@/types/index";
|
|
|
-import { useAudioPlayer } from "@/utils/audio";
|
|
|
-import { getHeaders, service } from "@/utils/http";
|
|
|
-import Taro from "@tarojs/taro";
|
|
|
-
|
|
|
-import { ModelUserProfile, TModelGenQrCodeRequest } from "@/types/index";
|
|
|
-import { getChatSession } from "@/store/chat";
|
|
|
-import { TextDecoder } from "text-encoding-shim";
|
|
|
-import JsonChunkParser from "@/utils/jsonChunkParser";
|
|
|
-
|
|
|
-// FIXME: react hook warning, not top level
|
|
|
-const { playChunk, pushChunk2Quene, setFistChunk, stopPlayChunk } =
|
|
|
- useAudioPlayer();
|
|
|
-
|
|
|
-export const batchGet = (profileIds: string[], config?: any) => {
|
|
|
- return service.post<{ profileIds: string[] }, ICharacter[]>(
|
|
|
- `/v1/character/batchGet`,
|
|
|
- { profileIds },
|
|
|
- config
|
|
|
- );
|
|
|
-};
|
|
|
-export const getVoiceCloneHistory = (profileId: string) => {
|
|
|
- return service.get<TEntityVoiceCloneRecord[]>(`/v1/audio/voiceClone`);
|
|
|
-};
|
|
|
-export const voiceCloneConfirm = (voiceName: string) => {
|
|
|
- return service.post<any>(`/v1/audio/${voiceName}/confirm`);
|
|
|
-};
|
|
|
-export const listCharacter = () => {
|
|
|
- return service.get<ICharacter[]>(`/v1/character`);
|
|
|
-};
|
|
|
-export const getCharacterById = (profileId: string) => {
|
|
|
- return service.get<ICharacter>(`/v1/character/${profileId}`);
|
|
|
-};
|
|
|
-export const deleteCharacterById = (profileId: string) => {
|
|
|
- return service.delete<null>(`/v1/character/${profileId}`);
|
|
|
-};
|
|
|
-export const getProfileById = (profileId: string) => {
|
|
|
- return service.get<ModelUserProfile>(`/v1/character/${profileId}/profile`);
|
|
|
-};
|
|
|
-
|
|
|
-export const saveCharacter = (data: ICharacter) => {
|
|
|
- return service.post<ICharacter, ICharacter>("/v1/character", data);
|
|
|
-};
|
|
|
-export type TCloneVoice = {
|
|
|
- profileId: string;
|
|
|
- key?: string;
|
|
|
- value: string;
|
|
|
-};
|
|
|
-export const cloneVoice = (data: TCloneVoice) => {
|
|
|
- return service.post<{ value: string; key?: string }, string>(
|
|
|
- `/v1/audio/voiceClone`,
|
|
|
- {
|
|
|
- value: data.value,
|
|
|
- key: data.key,
|
|
|
- }
|
|
|
- );
|
|
|
-};
|
|
|
-export const greeting = (profileId: string, configs?: any) => {
|
|
|
- return service.post<unknown, { audio: string; session: string }>(
|
|
|
- `/v1/character/${profileId}/greeting`,
|
|
|
- {},
|
|
|
- configs
|
|
|
- );
|
|
|
-};
|
|
|
-
|
|
|
-const chatRequest = (
|
|
|
- profileId: string,
|
|
|
- session: string,
|
|
|
- arrayBuffer: ArrayBuffer | string
|
|
|
-) => {
|
|
|
- const fileContent = arrayBuffer;
|
|
|
- const data = {
|
|
|
- value: fileContent,
|
|
|
- key: process.env.TARO_ENV == "h5" ? "wav" : "mp3",
|
|
|
- };
|
|
|
- let isFirstChunk = true;
|
|
|
- if (process.env.TARO_ENV == "h5") {
|
|
|
- fetch(`${BASE_URL}v1/character/${profileId}/chat?session=${session}`, {
|
|
|
- headers: {
|
|
|
- ...getHeaders(),
|
|
|
- "Content-Type": "application/json",
|
|
|
- },
|
|
|
- body: JSON.stringify(data),
|
|
|
- method: "post",
|
|
|
- })
|
|
|
- .then((_data) => {
|
|
|
- return _data.body?.getReader();
|
|
|
- })
|
|
|
- .then(async (reader) => {
|
|
|
- console.log("[ reader ] >", reader);
|
|
|
- if (!reader) {
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- // const buffers: ArrayBuffer[] = [];
|
|
|
- while (true) {
|
|
|
- const { done, value } = await reader.read();
|
|
|
- // console.log("[ done ] >", done);
|
|
|
- // console.log("[ value ] >", value);
|
|
|
- if (done) break;
|
|
|
- // buffers.push(value.buffer);
|
|
|
- if (isFirstChunk) {
|
|
|
- setFistChunk(value.buffer);
|
|
|
- isFirstChunk = false;
|
|
|
- } else {
|
|
|
- pushChunk2Quene(value.buffer);
|
|
|
- }
|
|
|
- playChunk();
|
|
|
- }
|
|
|
- // 测试音频数据是否正确
|
|
|
- // console.log("buffers", buffers.length);
|
|
|
- // const blob = new Blob(buffers, { type: "audio/wav" });
|
|
|
- // const audioUrl = URL.createObjectURL(blob);
|
|
|
- // console.log("audioUrl", audioUrl);
|
|
|
- // const a = document.createElement("a");
|
|
|
- // a.href = audioUrl;
|
|
|
- // a.download = "audio.wav";
|
|
|
- // a.click();
|
|
|
- });
|
|
|
- } else {
|
|
|
- const reqTask = Taro.request({
|
|
|
- url: `${BASE_URL}v1/character/${profileId}/chat?session=${session}`,
|
|
|
- data: data,
|
|
|
- enableChunked: true,
|
|
|
- method: "POST",
|
|
|
- header: {
|
|
|
- ...getHeaders(),
|
|
|
- },
|
|
|
- responseType: "arraybuffer",
|
|
|
- success: function (res) {
|
|
|
- console.log("服务端响应 >>", res);
|
|
|
- // 如果没有识别或发生错误,则停止播放(恢复默认状态)
|
|
|
- if (res.header["Content-Type"]?.indexOf("application/json") > -1) {
|
|
|
- stopPlayChunk();
|
|
|
- }
|
|
|
- },
|
|
|
- });
|
|
|
- // reqTask.
|
|
|
- reqTask.onChunkReceived((chunk) => {
|
|
|
- // console.log(chunk)
|
|
|
- if (isFirstChunk) {
|
|
|
- isFirstChunk = false;
|
|
|
- setFistChunk(chunk.data, reqTask);
|
|
|
- } else {
|
|
|
- pushChunk2Quene(chunk.data);
|
|
|
- }
|
|
|
- playChunk();
|
|
|
- });
|
|
|
- // todo: 需要返回 reqTask 用于停止发出请求
|
|
|
- // reqTask.abort();
|
|
|
- }
|
|
|
-};
|
|
|
-
|
|
|
-function arrayBufferToHex(buffer: ArrayBuffer) {
|
|
|
- return Array.from(new Uint8Array(buffer))
|
|
|
- .map((b) => b.toString(16).padStart(2, "0"))
|
|
|
- .join("");
|
|
|
-}
|
|
|
-// 暂时将流式播放的逻辑放在此处,后期考虑移到具体文件内
|
|
|
-export const chat = (
|
|
|
- profileId: string,
|
|
|
- session: string,
|
|
|
- content: string | ArrayBuffer
|
|
|
-) => {
|
|
|
- if (content instanceof ArrayBuffer) {
|
|
|
- // console.log("[ 测试 ] >", arrayBufferToHex(content));
|
|
|
- chatRequest(profileId, session, arrayBufferToHex(content));
|
|
|
- return;
|
|
|
- }
|
|
|
- const fs = Taro.getFileSystemManager();
|
|
|
-
|
|
|
- fs.readFile({
|
|
|
- filePath: content,
|
|
|
- encoding: "hex", // 以二进制方式读取文件
|
|
|
- success: (res) => {
|
|
|
- const fileContent = res.data;
|
|
|
- console.log(
|
|
|
- "%c [ fileContent ]: ",
|
|
|
- "color: #bf2c9f; background: pink; font-size: 13px;",
|
|
|
- fileContent
|
|
|
- );
|
|
|
- chatRequest(profileId, session, fileContent);
|
|
|
- },
|
|
|
- fail: (err) => {
|
|
|
- console.error("读取文件失败", err);
|
|
|
- },
|
|
|
- });
|
|
|
-};
|
|
|
-
|
|
|
-export const share = (profileId: string, platform: string) => {
|
|
|
- return service.get<{
|
|
|
- referralCode?: string;
|
|
|
- url?: string;
|
|
|
- }>(`/v1/character/${profileId}/share`, {
|
|
|
- params: {
|
|
|
- platform,
|
|
|
- },
|
|
|
- });
|
|
|
-};
|
|
|
-
|
|
|
-// 获取克隆声音状态
|
|
|
-export const getVoiceCloneStatus = (profileId: string, voiceName: string) => {
|
|
|
- return service.get<TEntityVoiceCloneRecord>(
|
|
|
- `/v1/character/${profileId}/voiceClone/${voiceName}`
|
|
|
- );
|
|
|
-};
|
|
|
-// 获取微信小程序二维码
|
|
|
-export const genCharacterQrcode = (
|
|
|
- profileId: string,
|
|
|
- params: TModelGenQrCodeRequest
|
|
|
-) => {
|
|
|
- return service.post<any, string>(`/v1/character/${profileId}/qrcode`, params);
|
|
|
-};
|
|
|
-
|
|
|
-export type TTextChatParams = {
|
|
|
- profileId: string;
|
|
|
- params: {
|
|
|
- message?: string;
|
|
|
- model?: string;
|
|
|
- };
|
|
|
- onStart: () => void;
|
|
|
- onReceived: (m: { content: string; reasoningContent: string }) => void;
|
|
|
- onFinished: () => void;
|
|
|
- onError: () => void;
|
|
|
-};
|
|
|
-
|
|
|
-export const textChat = ({
|
|
|
- profileId,
|
|
|
- params,
|
|
|
- onStart,
|
|
|
- onReceived,
|
|
|
- onFinished,
|
|
|
- onError,
|
|
|
-}: TTextChatParams) => {
|
|
|
- onStart();
|
|
|
- const session = getChatSession();
|
|
|
- // todo: 需要加上错误处理,当错误时清掉 onStart 中创建的 气泡框
|
|
|
- if (!session) {
|
|
|
- return;
|
|
|
- }
|
|
|
- let reqTask: Taro.RequestTask<any>;
|
|
|
- const jsonParser = new JsonChunkParser();
|
|
|
- jsonParser.onParseComplete((m) => {
|
|
|
- console.log("okok", m);
|
|
|
- onFinished();
|
|
|
- });
|
|
|
- const onChunkReceived = (chunk: any) => {
|
|
|
- // console.log(chunk);
|
|
|
- const uint8Array = new Uint8Array(chunk.data);
|
|
|
- var string = new TextDecoder("utf-8").decode(uint8Array);
|
|
|
- // console.log(string);
|
|
|
- jsonParser.parseChunk(string, (m) => {
|
|
|
- // console.log(m);
|
|
|
- onReceived(m);
|
|
|
- });
|
|
|
- };
|
|
|
- try {
|
|
|
- reqTask = Taro.request({
|
|
|
- url: `${BASE_URL}v1/character/${profileId}/textChat?session=${session}`,
|
|
|
- data: params,
|
|
|
- enableChunked: true,
|
|
|
- method: "POST",
|
|
|
- header: {
|
|
|
- ...getHeaders(),
|
|
|
- },
|
|
|
- responseType: "arraybuffer",
|
|
|
- success: function (res) {
|
|
|
- console.log("服务端响应 >>", res);
|
|
|
- // 如果没有识别或发生错误,则停止播放(恢复默认状态)
|
|
|
- if (res.header["Content-Type"]?.indexOf("application/json") > -1) {
|
|
|
- }
|
|
|
- },
|
|
|
- });
|
|
|
-
|
|
|
- // reqTask.
|
|
|
- reqTask.onChunkReceived(onChunkReceived);
|
|
|
- } catch (e) {
|
|
|
- onError();
|
|
|
- }
|
|
|
-
|
|
|
- const stopChunk = () => {
|
|
|
- reqTask.offChunkReceived(onChunkReceived);
|
|
|
- };
|
|
|
-
|
|
|
- return stopChunk;
|
|
|
-};
|