visuddhinanda пре 7 месеци
родитељ
комит
6dbcaeb9c4
1 измењених фајлова са 300 додато и 0 уклоњено
  1. 300 0
      dashboard-v4/dashboard/src/components/template/AIWbw.ts

+ 300 - 0
dashboard-v4/dashboard/src/components/template/AIWbw.ts

@@ -0,0 +1,300 @@
+import { useState, useCallback } from "react";
+import { IWbw } from "./Wbw/WbwWord";
+import { paliEndingType } from "../general/PaliEnding";
+import { useIntl } from "react-intl";
+
+// 类型定义
+export interface WbwElement<T> {
+  value: T;
+  status: number;
+}
+
+interface StreamController {
+  addData: (jsonlLine: string) => void;
+  complete: () => void;
+}
+
+interface ProcessWbwStreamOptions {
+  modelId: string;
+  prompt: string;
+  endingType: string[];
+  onProgress?: (data: IWbw[], isComplete: boolean) => void;
+  onComplete?: (finalData: IWbw[]) => void;
+  onError?: (error: string) => void;
+}
+
+/**
+ * 处理JSONL流式输出的函数
+ */
+export const processWbwStream = async ({
+  modelId,
+  prompt,
+  endingType,
+  onProgress,
+  onComplete,
+  onError,
+}: ProcessWbwStreamOptions): Promise<{
+  success: boolean;
+  data?: IWbw[];
+  error?: string;
+}> => {
+  if (typeof process.env.REACT_APP_OPENAI_PROXY === "undefined") {
+    console.error("no REACT_APP_OPENAI_PROXY");
+    const error = "API配置错误";
+    onError?.(error);
+    return { success: false, error };
+  }
+
+  try {
+    const payload = {
+      model: "grok-3", // 或者从models数组中获取实际模型名称
+      messages: [
+        {
+          role: "system",
+          content: `你是一个巴利语专家,
+        用户提供的json 数据 是巴利文句子的全部单词
+          请根据每个单词的拼写 real.value 填写如下字段
+单词的中文意思:meaning.value
+巴利单词的拆分:factors.value  
+语尾请加[]
+拆分后每个组成部分的中文意思factorMeaning.value
+请按照下表填写巴利语单词的类型 type.value
+\`\`\`csv
+${endingType.join("\n")}
+\`\`\`
+ 直接输出JSONL格式数据
+`,
+        },
+        { role: "user", content: prompt },
+      ],
+      stream: true,
+      temperature: 0.3,
+      max_tokens: 4000,
+    };
+
+    const url = process.env.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("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 processStream = useCallback(async (modelId: string, prompt: string) => {
+    setIsProcessing(true);
+    setWbwData([]);
+    setError(undefined);
+
+    const result = await processWbwStream({
+      modelId,
+      prompt,
+      endingType,
+      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;
+  }, []);
+
+  return {
+    processStream,
+    isProcessing,
+    wbwData,
+    error,
+    clearData: useCallback(() => {
+      setWbwData([]);
+      setError(undefined);
+    }, []),
+  };
+};