Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import Convert from '@/fronted/pages/convert/Convert';
import { ErrorBoundary } from 'react-error-boundary';
import FallBack from '@/fronted/components/FallBack';
import Eb from '@/fronted/components/Eb';
import ASRSetting from './fronted/pages/setting/ASRSetting';

const api = window.electron;
const App = () => {
Expand Down Expand Up @@ -90,6 +91,10 @@ const App = () => {
<Route
path="open-ai"
element={<Eb><OpenAiSetting /></Eb>}
/>
<Route
path="asr"
element={<Eb><ASRSetting /></Eb>}
/>
<Route
path="storage"
Expand Down
55 changes: 55 additions & 0 deletions src/backend/services/SparkASR/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// api.ts
import axios, { CancelTokenSource } from 'axios';
import fs from "fs";
import { generateSigna } from './auth';
import { GetResultRequest, GetResultResponse, UploadRequest, UploadResponse } from './types';
const BASE_URL = 'https://raasr.xfyun.cn/v2/api';


export async function uploadFile(request: UploadRequest , filePath:string, appId: string, secretKey: string): Promise<UploadResponse> {
const ts = Math.floor(Date.now() / 1000);
const signa = generateSigna(appId, secretKey, ts);
const readStream = fs.createReadStream(filePath);
const url = `${BASE_URL}/upload`;
//?signa=${signa}&appId=${appId}&ts=${ts}
const searchParams = new URLSearchParams();
searchParams.append('signa', signa);
searchParams.append('appId', appId);
searchParams.append('ts', ts+"");
Object.keys(request).forEach(key => {
searchParams.append(key, request[key as 'fileName']);
});
/* const response = await fetchWithErrorHandling(url, {
method: 'POST',
headers: {
'Content-Type': 'application/octet-stream',
},
body: readStream
}); */
const resp = await axios.post(url+"?" + searchParams.toString(),readStream,{
headers: {
'Content-Type': 'application/octet-stream',
}
})
return resp.data;
}

export async function getResult(request: GetResultRequest, appId: string, secretKey: string , cancelTokenSource:CancelTokenSource): Promise<GetResultResponse> {
const ts = Math.floor(Date.now() / 1000);
const signa = generateSigna(appId, secretKey, ts);
const url = `${BASE_URL}/getResult`;
const searchParams = new URLSearchParams();
searchParams.append('signa', signa);
searchParams.append('appId', appId);
searchParams.append('ts', ts+"");
searchParams.append('orderId', request.orderId);
const response = await axios.get(url+"?" + searchParams.toString(), {
cancelToken: cancelTokenSource.token,
});
return response.data;
}


export function delay(time:number){
return new Promise(resolve => setTimeout(resolve, time));
}
9 changes: 9 additions & 0 deletions src/backend/services/SparkASR/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// auth.ts
import crypto from 'crypto';

export function generateSigna(appId: string, secretKey: string, ts: number): string {
const baseString = appId + ts;
const md5Hash = crypto.createHash('md5').update(baseString).digest('hex');
const hmac = crypto.createHmac('sha1', secretKey).update(md5Hash).digest('base64');
return hmac
}
107 changes: 107 additions & 0 deletions src/backend/services/SparkASR/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// index.ts
import { CancelTokenSource } from "axios";
import fs from "fs";
import { delay, getResult, uploadFile } from "./api";
import { GetResultResponse, OrderResult } from "./types";

export * from "./types";

function getFileSize(filePath: string) {
const stats = fs.statSync(filePath);
return stats.size;
}
function reduceByKey<T>(arr: T[], key: keyof T) {
return arr.reduce((pre, item) => {
const keyValue = item[key];
return (pre + keyValue) as string;
}, "");
}

function transformResp(resultResponse: GetResultResponse) {
const orderResult = resultResponse.content?.orderResult;
if (!orderResult) {
throw new Error("获取结果失败");
}
const result = JSON.parse(orderResult) as OrderResult;

const segments = result.lattice2?.map((item) => {
const text = item.json_1best.st.rt.reduce((stPre, stItem) => {
return (
stPre +
stItem.ws.reduce((wsPre, wsItem) => {
return wsPre + reduceByKey(wsItem.cw, "w");
}, "")
);
}, "");
return {
seek: 0,
text,
start: Number(item.begin)/1000,
end: Number(item.end)/1000,
};
});

const res = {
language: "zh",
duration: segments[segments.length - 1]?.end ?? 0,
text: segments.map((item) => item.text).join(""),
segments,
};
return res;
}

/**
* 使用科大讯飞语音转录进行语音识别
* @link 文档 https://www.xfyun.cn/doc/asr/ifasr_new/API.html#_2%E3%80%81%E6%9F%A5%E8%AF%A2%E7%BB%93%E6%9E%9C
* @param filePath
*/
export async function sparkASRRequest(
filePath: string,
appId: string,
secretKey: string,
cancelToken?:CancelTokenSource

) {
const fileSize = await getFileSize(filePath);
const fileName = filePath.split("/").pop();
const uploadResponse = await uploadFile(
{
fileName,
fileSize: fileSize,
duration: 60,
audioMode: "fileStream",
language:"en"
},
filePath,
appId,
secretKey
);

if (uploadResponse.code !== "000000") {
throw new Error("上传失败:" + uploadResponse.descInfo);
}
await delay(3000);

let resultResponse = await getResult(
{
orderId: uploadResponse.content.orderId,
},
appId,
secretKey,
cancelToken
);
let status = resultResponse.content?.orderInfo?.status;
while (status == 3) {
resultResponse = await getResult(
{
orderId: uploadResponse.content.orderId,
},
appId,
secretKey,
cancelToken
);
status = resultResponse.content?.orderInfo?.status;
await delay(1000);
}
return transformResp(resultResponse);
}
Loading