| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332 |
- import { useState, useCallback } from "react";
- import type { IWbw } from "./Wbw/WbwWord"
- import { paliEndingGrammar, paliEndingType } from "../general/PaliEnding";
- import { useIntl } from "react-intl";
- import { _useAppSelector } from "../../hooks";
- import { _siteInfo } from "../../reducers/layout";
- // 类型定义
- export interface WbwElement<T> {
- value: T;
- status: number;
- }
- interface StreamController {
- addData: (jsonlLine: string) => void;
- complete: () => void;
- }
- interface ProcessWbwStreamOptions {
- modelId: string;
- data: IWbw[];
- endingType: string[];
- endingGrammar: string[];
- onProgress?: (data: IWbw[], isComplete: boolean) => void;
- onComplete?: (finalData: IWbw[]) => void;
- onError?: (error: string) => void;
- }
- /**
- * 处理JSONL流式输出的函数
- */
- export const processWbwStream = async ({
- modelId,
- data,
- endingType,
- endingGrammar,
- onProgress,
- onComplete,
- onError,
- }: ProcessWbwStreamOptions): Promise<{
- success: boolean;
- data?: IWbw[];
- error?: string;
- }> => {
- if (typeof import.meta.env.VITE_REACT_APP_OPENAI_PROXY === "undefined") {
- console.error("no REACT_APP_OPENAI_PROXY");
- const error = "API配置错误";
- onError?.(error);
- return { success: false, error };
- }
- const sys_prompt = `
- 你是一个巴利语专家。用户提供的jsonl 数据 是巴利文句子的全部单词
- 请根据每个单词的拼写 real.value 填写如下字段
- 巴利单词的词典原型:parent.value
- 单词的中文意思:meaning.value
- 巴利单词的拆分:factors.value
- 语尾请加[]
- 拆分后每个组成部分的中文意思factorMeaning.value
- 请按照下表填写巴利语单词的类型 type.value
- \`\`\`csv
- ${endingType.join("\n")}
- \`\`\`
- 请按照下表填写巴利语单词的语法信息 grammar.value
- 名词和形容词填写 性,数,格
- 动词填写 人称,数,时态语气
- 用 $ 作为分隔符
- \`\`\`csv
- ${endingGrammar.join("\n")}
- \`\`\`
- 直接输出JSONL格式数据
- `;
- const jsonl = data.map((obj) => JSON.stringify(obj)).join("\n");
- const prompt = `
- \`\`\`jsonl
- ${jsonl}
- \`\`\`
- `;
- console.debug("ai wbw system prompt", sys_prompt, prompt);
- try {
- const payload = {
- model: "grok-3", // 或者从models数组中获取实际模型名称
- messages: [
- {
- role: "system",
- content: sys_prompt,
- },
- { role: "user", content: prompt },
- ],
- stream: true,
- temperature: 0.3,
- max_tokens: 4000,
- };
- const url = import.meta.env.VITE_REACT_APP_OPENAI_PROXY;
- const requestData = {
- model_id: modelId,
- payload: payload,
- };
- console.info("api request", url, requestData);
- const response = await fetch(url, {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- Authorization: `Bearer AIzaSyCzr8KqEdaQ3cRCxsFwSHh8c7kF3RZTZWw`,
- },
- body: JSON.stringify(requestData),
- });
- if (!response.ok) {
- throw new Error(`HTTP error! status: ${response.status}`);
- }
- const reader = response.body?.getReader();
- if (!reader) {
- throw new Error("无法获取响应流");
- }
- const decoder = new TextDecoder();
- let buffer = "";
- let jsonlBuffer = ""; // 用于累积JSONL内容
- const resultData: IWbw[] = [];
- // 创建流控制器
- const streamController: StreamController = {
- addData: (jsonlLine: string) => {
- try {
- // 解析JSONL行
- const parsedData = JSON.parse(jsonlLine.trim());
- console.info("ai wbw stream ok", parsedData);
- // 转换为IWbw格式
- const wbwData: IWbw = {
- book: parsedData.book || 0,
- para: parsedData.para || 0,
- sn: parsedData.sn || [],
- word: parsedData.word || { value: "", status: 0 },
- real: parsedData.real || { value: null, status: 0 },
- meaning: parsedData.meaning,
- type: parsedData.type,
- grammar: parsedData.grammar,
- style: parsedData.style,
- case: parsedData.case,
- parent: parsedData.parent,
- parent2: parsedData.parent2,
- grammar2: parsedData.grammar2,
- factors: parsedData.factors,
- factorMeaning: parsedData.factorMeaning,
- relation: parsedData.relation,
- note: parsedData.note,
- bookMarkColor: parsedData.bookMarkColor,
- bookMarkText: parsedData.bookMarkText,
- locked: parsedData.locked || false,
- confidence: parsedData.confidence || 0.5,
- attachments: parsedData.attachments,
- hasComment: parsedData.hasComment,
- grammarId: parsedData.grammarId,
- bookName: parsedData.bookName,
- editor: parsedData.editor,
- created_at: parsedData.created_at,
- updated_at: parsedData.updated_at,
- };
- resultData.push(wbwData);
- // 调用进度回调
- onProgress?.(resultData, false);
- } catch (e) {
- console.warn("解析JSONL行失败:", e, "内容:", jsonlLine);
- }
- },
- complete: () => {
- onProgress?.(resultData, true);
- onComplete?.(resultData);
- },
- };
- try {
- while (true) {
- const { done, value } = await reader.read();
- if (done) {
- // 处理最后的缓冲内容
- if (jsonlBuffer.trim()) {
- const lines = jsonlBuffer.trim().split("\n");
- for (const line of lines) {
- if (line.trim()) {
- streamController.addData(line);
- }
- }
- }
- streamController.complete();
- return { success: true, data: resultData };
- }
- buffer += decoder.decode(value, { stream: true });
- const lines = buffer.split("\n");
- buffer = lines.pop() || "";
- for (const line of lines) {
- if (line.trim() === "") continue;
- if (line.startsWith("data: ")) {
- const data = line.slice(6);
- if (data === "[DONE]") {
- // 处理剩余的JSONL内容
- if (jsonlBuffer.trim()) {
- const jsonlLines = jsonlBuffer.trim().split("\n");
- for (const jsonlLine of jsonlLines) {
- if (jsonlLine.trim()) {
- streamController.addData(jsonlLine);
- }
- }
- }
- streamController.complete();
- return { success: true, data: resultData };
- }
- try {
- const parsed = JSON.parse(data);
- const delta = parsed.choices?.[0]?.delta;
- if (delta?.content) {
- // 累积内容到JSONL缓冲区
- jsonlBuffer += delta.content;
- // 检查是否有完整的JSONL行
- const jsonlLines = jsonlBuffer.split("\n");
- // 保留最后一行(可能不完整)
- jsonlBuffer = jsonlLines.pop() || "";
- // 处理完整的行
- for (const jsonlLine of jsonlLines) {
- if (jsonlLine.trim()) {
- streamController.addData(jsonlLine);
- }
- }
- }
- } catch (e) {
- console.warn("解析SSE数据失败:", e);
- }
- }
- }
- }
- } catch (error) {
- console.error("读取流数据失败:", error);
- const errorMessage = "读取响应流失败";
- onError?.(errorMessage);
- return { success: false, error: errorMessage };
- }
- } catch (error) {
- console.error("API调用失败:", error);
- const errorMessage = "API调用失败,请重试";
- onError?.(errorMessage);
- return { success: false, error: errorMessage };
- }
- };
- /**
- * React Hook 用法示例
- */
- export const useWbwStreamProcessor = () => {
- const [isProcessing, setIsProcessing] = useState<boolean>(false);
- const [wbwData, setWbwData] = useState<IWbw[]>([]);
- const [error, setError] = useState<string>();
- const intl = useIntl(); // 在Hook中使用
- const endingType = paliEndingType.map((item) => {
- return (
- intl.formatMessage({ id: `dict.fields.type.${item}.label` }) +
- `:.${item}.`
- );
- });
- const endingGrammar = paliEndingGrammar.map((item) => {
- return (
- intl.formatMessage({ id: `dict.fields.type.${item}.label` }) +
- `:.${item}.`
- );
- });
- const processStream = useCallback(
- async (modelId: string, data: IWbw[]) => {
- setIsProcessing(true);
- setWbwData([]);
- setError(undefined);
- const result = await processWbwStream({
- modelId,
- data,
- endingType,
- endingGrammar,
- onProgress: (data, _isComplete) => {
- console.info("onProgress", data);
- setWbwData([...data]); // 创建新数组触发重渲染
- },
- onComplete: (finalData) => {
- setWbwData(finalData);
- setIsProcessing(false);
- },
- onError: (errorMessage) => {
- setError(errorMessage);
- setIsProcessing(false);
- },
- });
- if (!result.success) {
- setError(result.error || "处理失败");
- setIsProcessing(false);
- }
- return result;
- },
- [endingGrammar, endingType]
- );
- return {
- processStream,
- isProcessing,
- wbwData,
- error,
- clearData: useCallback(() => {
- setWbwData([]);
- setError(undefined);
- }, []),
- };
- };
|