Просмотр исходного кода

translate inner comments to English

Yuki Takei 1 год назад
Родитель
Сommit
679ddb48f5

+ 32 - 32
apps/app/src/features/openai/server/routes/edit/editor-stream-processor.ts

@@ -10,19 +10,19 @@ import { EditorAssistantDiffSchema } from './schema';
 
 const logger = loggerFactory('growi:routes:apiv3:openai:edit:editor-stream-processor');
 
-// 型定義
+// Type definitions
 type EditorAssistantMessage = z.infer<typeof EditorAssistantMessageSchema>;
 type EditorAssistantDiff = z.infer<typeof EditorAssistantDiffSchema>;
 
 /**
- * 型ガード: メッセージ型かどうかを判定する
+ * Type guard: Check if item is a message type
  */
 const isMessageItem = (item: unknown): item is EditorAssistantMessage => {
   return typeof item === 'object' && item !== null && 'message' in item;
 };
 
 /**
- * 型ガード: 差分型かどうかを判定する
+ * Type guard: Check if item is a diff type
  */
 const isDiffItem = (item: unknown): item is EditorAssistantDiff => {
   return typeof item === 'object' && item !== null
@@ -30,23 +30,23 @@ const isDiffItem = (item: unknown): item is EditorAssistantDiff => {
 };
 
 /**
- * EditorAssistant用のストリームデータプロセッサ
- * JSONストリームから編集用のメッセージと差分を抽出する
+ * Editor Stream Processor
+ * Extracts messages and diffs from JSON stream for editor
  */
 export class EditorStreamProcessor {
 
-  // 最終応答データ
+  // Final response data
   private message: string | null = null;
 
   private replacements: EditorAssistantDiff[] = [];
 
-  // 前回のコンテンツの最終要素のインデックス
+  // Index of the last element in previous content
   private lastContentIndex = -1;
 
-  // 最後に送信した差分インデックス
+  // Last sent diff index
   private lastSentDiffIndex = -1;
 
-  // 送信済みの差分キー
+  // Set of sent diff keys
   private sentDiffKeys = new Set<string>();
 
   constructor(private sseHelper: SseHelper) {
@@ -54,8 +54,8 @@ export class EditorStreamProcessor {
   }
 
   /**
-   * JSONデータを処理する
-   * @param jsonString JSON文字列
+   * Process JSON data
+   * @param jsonString JSON string
    */
   process(jsonString: string): void {
     try {
@@ -65,10 +65,10 @@ export class EditorStreamProcessor {
       if (parsedJson?.contents && Array.isArray(parsedJson.contents)) {
         const contents = parsedJson.contents;
 
-        // 現在のコンテンツの最終要素のインデックス
+        // Index of the last element in current content
         const currentContentIndex = contents.length - 1;
 
-        // メッセージの処理
+        // Process message
         let messageUpdated = false;
         for (let i = contents.length - 1; i >= 0; i--) {
           const item = contents[i];
@@ -81,11 +81,11 @@ export class EditorStreamProcessor {
           }
         }
 
-        // 差分の処理
+        // Process diffs
         let diffUpdated = false;
         let processedDiffIndex = -1;
 
-        // 差分が含まれているか確認
+        // Check if diffs are included
         for (let i = 0; i < contents.length; i++) {
           const item = contents[i];
           if (!isDiffItem(item)) continue;
@@ -96,11 +96,11 @@ export class EditorStreamProcessor {
           const diff = validDiff.data;
           const key = this.getDiffKey(diff);
 
-          // この差分がすでに送信済みかチェック
+          // Check if this diff has already been sent
           if (this.sentDiffKeys.has(key)) continue;
 
-          // 最終要素が変わった場合、または最後から2番目以前の要素の場合
-          // → 差分が完成したと判断
+          // If the last element has changed, or if this is not the last element
+          // → Consider the diff as finalized
           if (i < currentContentIndex || currentContentIndex > this.lastContentIndex) {
             this.replacements.push(diff);
             this.sentDiffKeys.add(key);
@@ -109,29 +109,29 @@ export class EditorStreamProcessor {
           }
         }
 
-        // 最終インデックスを更新
+        // Update last index
         this.lastContentIndex = currentContentIndex;
 
-        // 更新通知
+        // Send notifications
         if (messageUpdated) {
-          // メッセージは更新されたらすぐに通知
+          // Notify immediately if message is updated
           this.notifyClient();
         }
         else if (diffUpdated && processedDiffIndex > this.lastSentDiffIndex) {
-          // 差分は新しいインデックスの差分が確定した場合のみ通知
+          // For diffs, only notify if a new index diff is confirmed
           this.lastSentDiffIndex = processedDiffIndex;
           this.notifyClient();
         }
       }
     }
     catch (e) {
-      // パースエラーは無視(不完全なJSONなので)
+      // Ignore parse errors (expected for incomplete JSON)
       logger.debug('JSON parsing error (expected for partial data):', e);
     }
   }
 
   /**
-   * 差分の一意キーを生成
+   * Generate unique key for a diff
    */
   private getDiffKey(diff: EditorAssistantDiff): string {
     if ('insert' in diff) return `insert-${diff.insert}`;
@@ -141,7 +141,7 @@ export class EditorStreamProcessor {
   }
 
   /**
-   * クライアントに通知
+   * Notify the client
    */
   private notifyClient(): void {
     this.sseHelper.writeData({
@@ -153,18 +153,18 @@ export class EditorStreamProcessor {
   }
 
   /**
-   * 最終結果を送信
+   * Send final result
    */
   sendFinalResult(rawBuffer: string): void {
     try {
       const repairedJson = jsonrepair(rawBuffer);
       const parsedJson = JSON.parse(repairedJson);
 
-      // 最後のデータから全ての差分を取得
+      // Get all diffs from the final data
       if (parsedJson?.contents && Array.isArray(parsedJson.contents)) {
         const contents = parsedJson.contents;
 
-        // 未送信の差分があれば追加
+        // Add any unsent diffs
         for (const item of contents) {
           if (!isDiffItem(item)) continue;
 
@@ -174,7 +174,7 @@ export class EditorStreamProcessor {
           const diff = validDiff.data;
           const key = this.getDiffKey(diff);
 
-          // まだ送信していない差分を追加
+          // Add any diffs that haven't been sent yet
           if (!this.sentDiffKeys.has(key)) {
             this.replacements.push(diff);
             this.sentDiffKeys.add(key);
@@ -182,7 +182,7 @@ export class EditorStreamProcessor {
         }
       }
 
-      // 最終通知(isDoneフラグ付き)
+      // Final notification (with isDone flag)
       this.sseHelper.writeData({
         editorResponse: {
           message: this.message || '',
@@ -194,7 +194,7 @@ export class EditorStreamProcessor {
     catch (e) {
       logger.debug('Failed to parse final JSON response:', e);
 
-      // エラー時も最終通知
+      // Send final notification even on error
       this.sseHelper.writeData({
         editorResponse: {
           message: this.message || '',
@@ -206,7 +206,7 @@ export class EditorStreamProcessor {
   }
 
   /**
-   * リソースを解放
+   * Release resources
    */
   destroy(): void {
     this.message = null;

+ 25 - 25
apps/app/src/features/openai/server/routes/edit/index.ts

@@ -7,7 +7,7 @@ import { zodResponseFormat } from 'openai/helpers/zod';
 import type { MessageDelta } from 'openai/resources/beta/threads/messages.mjs';
 import { z } from 'zod';
 
-// 必要なインポート
+// Necessary imports
 import { getOrCreateEditorAssistant } from '~/features/openai/server/services/assistant';
 import type Crowi from '~/server/crowi';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
@@ -29,7 +29,7 @@ import { EditorAssistantDiffSchema, EditorAssistantMessageSchema } from './schem
 const logger = loggerFactory('growi:routes:apiv3:openai:message');
 
 // -----------------------------------------------------------------------------
-// 型定義
+// Type definitions
 // -----------------------------------------------------------------------------
 
 const EditorAssistantResponseSchema = z.object({
@@ -50,18 +50,18 @@ type Req = Request<undefined, Response, ReqBody> & {
 
 
 // -----------------------------------------------------------------------------
-// エンドポイントハンドラーファクトリ
+// Endpoint handler factory
 // -----------------------------------------------------------------------------
 
 type PostMessageHandlersFactory = (crowi: Crowi) => RequestHandler[];
 
 /**
- * エディタアシスタントのエンドポイントハンドラを作成する
+ * Create endpoint handlers for editor assistant
  */
 export const postMessageToEditHandlersFactory: PostMessageHandlersFactory = (crowi) => {
   const loginRequiredStrictly = require('~/server/middlewares/login-required')(crowi);
 
-  // バリデータ設定
+  // Validator setup
   const validator: ValidationChain[] = [
     body('userMessage')
       .isString()
@@ -82,23 +82,23 @@ export const postMessageToEditHandlersFactory: PostMessageHandlersFactory = (cro
     async(req: Req, res: ApiV3Response) => {
       const { userMessage, markdown, threadId } = req.body;
 
-      // パラメータチェック
+      // Parameter check
       if (threadId == null) {
         return res.apiv3Err(new ErrorV3('threadId is not set', MessageErrorCode.THREAD_ID_IS_NOT_SET), 400);
       }
 
-      // サービスチェック
+      // Service check
       const openaiService = getOpenaiService();
       if (openaiService == null) {
         return res.apiv3Err(new ErrorV3('GROWI AI is not enabled'), 501);
       }
 
-      // SSEヘルパーとストリームプロセッサの初期化
+      // Initialize SSE helper and stream processor
       const sseHelper = new SseHelper(res);
       const streamProcessor = new EditorStreamProcessor(sseHelper);
 
       try {
-        // レスポンスヘッダー設定
+        // Set response headers
         res.writeHead(200, {
           'Content-Type': 'text/event-stream;charset=utf-8',
           'Cache-Control': 'no-cache, no-transform',
@@ -106,11 +106,11 @@ export const postMessageToEditHandlersFactory: PostMessageHandlersFactory = (cro
 
         let rawBuffer = '';
 
-        // アシスタント取得とスレッド処理
+        // Get assistant and process thread
         const assistant = await getOrCreateEditorAssistant();
         const thread = await openaiClient.beta.threads.retrieve(threadId);
 
-        // ストリーム作成
+        // Create stream
         const stream = openaiClient.beta.threads.runs.stream(thread.id, {
           assistant_id: assistant.id,
           additional_messages: [
@@ -149,24 +149,24 @@ export const postMessageToEditHandlersFactory: PostMessageHandlersFactory = (cro
           response_format: zodResponseFormat(EditorAssistantResponseSchema, 'editor_assistant_response'),
         });
 
-        // メッセージデルタハンドラ
+        // Message delta handler
         const messageDeltaHandler = async(delta: MessageDelta) => {
           const content = delta.content?.[0];
 
-          // アノテーション処理
+          // Process annotations
           if (content?.type === 'text' && content?.text?.annotations != null) {
             await replaceAnnotationWithPageLink(content, req.user.lang);
           }
 
-          // テキスト処理
+          // Process text
           if (content?.type === 'text' && content.text?.value) {
             const chunk = content.text.value;
             rawBuffer += chunk;
 
-            // JSONプロセッサでデータを処理
+            // Process data with JSON processor
             streamProcessor.process(rawBuffer);
 
-            // 元のデルタも送信
+            // Also send original delta
             sseHelper.writeData(delta);
           }
           else {
@@ -174,10 +174,10 @@ export const postMessageToEditHandlersFactory: PostMessageHandlersFactory = (cro
           }
         };
 
-        // イベントハンドラ登録
+        // Register event handlers
         stream.on('messageDelta', messageDeltaHandler);
 
-        // Runエラーハンドラ
+        // Run error handler
         stream.on('event', (delta) => {
           if (delta.event === 'thread.run.failed') {
             const errorMessage = delta.data.last_error?.message;
@@ -188,29 +188,29 @@ export const postMessageToEditHandlersFactory: PostMessageHandlersFactory = (cro
           }
         });
 
-        // 完了ハンドラ
+        // Completion handler
         stream.once('messageDone', () => {
-          // 最終結果を処理して送信
+          // Process and send final result
           streamProcessor.sendFinalResult(rawBuffer);
 
-          // ストリームのクリーンアップ
+          // Clean up stream
           streamProcessor.destroy();
           stream.off('messageDelta', messageDeltaHandler);
           sseHelper.end();
         });
 
-        // エラーハンドラ
+        // Error handler
         stream.once('error', (err) => {
           logger.error('Stream error:', err);
 
-          // クリーンアップ
+          // Clean up
           streamProcessor.destroy();
           stream.off('messageDelta', messageDeltaHandler);
           sseHelper.writeError('An error occurred while processing your request');
           sseHelper.end();
         });
 
-        // クライアント切断時のクリーンアップ
+        // Clean up on client disconnect
         req.on('close', () => {
           streamProcessor.destroy();
 
@@ -223,7 +223,7 @@ export const postMessageToEditHandlersFactory: PostMessageHandlersFactory = (cro
         });
       }
       catch (err) {
-        // エラー発生時のクリーンアップと応答
+        // Clean up and respond on error
         logger.error('Error in edit handler:', err);
         streamProcessor.destroy();
         return res.status(500).send(err.message);

+ 2 - 2
apps/app/src/features/openai/server/routes/edit/schema.ts

@@ -1,10 +1,10 @@
 import { z } from 'zod';
 
 // -----------------------------------------------------------------------------
-// 型定義
+// Type definitions
 // -----------------------------------------------------------------------------
 
-// スキーマ定義
+// Schema definitions
 export const EditorAssistantMessageSchema = z.object({
   message: z.string().describe('A friendly message explaining what changes were made or suggested'),
 });

+ 9 - 9
apps/app/src/features/openai/server/routes/utils/sse-helper.ts

@@ -3,28 +3,28 @@ import type { Response } from 'express';
 import type { StreamErrorCode } from '../../../interfaces/message-error';
 
 /**
- * SSE通信を簡略化するためのインターフェース
+ * Interface to simplify SSE communication
  */
 export interface ISseHelper {
   /**
-   * SSEフォーマットでデータを送信する
+   * Send data in SSE format
    */
   writeData(data: unknown): void;
 
   /**
-   * SSEフォーマットでエラーを送信する
+   * Send error in SSE format
    */
   writeError(message: string, code?: StreamErrorCode): void;
 
   /**
-   * レスポンスを終了する
+   * End the response
    */
   end(): void;
 }
 
 /**
- * SSEヘルパークラス
- * レスポンスオブジェクトにSSEフォーマットでデータを書き込む機能を提供
+ * SSE Helper Class
+ * Provides functionality to write data to response object in SSE format
  */
 export class SseHelper implements ISseHelper {
 
@@ -33,21 +33,21 @@ export class SseHelper implements ISseHelper {
   }
 
   /**
-   * SSEフォーマットでデータを送信する
+   * Send data in SSE format
    */
   writeData(data: unknown): void {
     this.res.write(`data: ${JSON.stringify(data)}\n\n`);
   }
 
   /**
-   * SSEフォーマットでエラーを送信する
+   * Send error in SSE format
    */
   writeError(message: string, code?: StreamErrorCode): void {
     this.res.write(`error: ${JSON.stringify({ code, message })}\n\n`);
   }
 
   /**
-   * レスポンスを終了する
+   * End the response
    */
   end(): void {
     this.res.end();