|
|
@@ -4,7 +4,7 @@ import { Readable, Transform, Writable } from 'stream';
|
|
|
import { pipeline } from 'stream/promises';
|
|
|
|
|
|
import type {
|
|
|
- IUser, Ref, Lang, IPage,
|
|
|
+ IUser, Ref, Lang, IPage, Nullable,
|
|
|
} from '@growi/core';
|
|
|
import {
|
|
|
PageGrant, getIdForRef, getIdStringForRef, isPopulated, type IUserHasId,
|
|
|
@@ -15,6 +15,8 @@ import escapeStringRegexp from 'escape-string-regexp';
|
|
|
import createError from 'http-errors';
|
|
|
import mongoose, { type HydratedDocument, type Types } from 'mongoose';
|
|
|
import { type OpenAI, toFile } from 'openai';
|
|
|
+import { type ChatCompletionChunk } from 'openai/resources/chat/completions';
|
|
|
+import { type Stream } from 'openai/streaming';
|
|
|
|
|
|
import ExternalUserGroupRelation from '~/features/external-user-group/server/models/external-user-group-relation';
|
|
|
import ThreadRelationModel, { type ThreadRelationDocument } from '~/features/openai/server/models/thread-relation';
|
|
|
@@ -45,7 +47,7 @@ import { convertMarkdownToHtml } from '../utils/convert-markdown-to-html';
|
|
|
import { generateGlobPatterns } from '../utils/generate-glob-patterns';
|
|
|
import { isVectorStoreCompatible } from '../utils/is-vector-store-compatible';
|
|
|
|
|
|
-import { getClient } from './client-delegator';
|
|
|
+import { getClient, isStreamResponse } from './client-delegator';
|
|
|
import { openaiApiErrorHandler } from './openai-api-error-handler';
|
|
|
import { replaceAnnotationWithPageLink } from './replace-annotation-with-page-link';
|
|
|
|
|
|
@@ -72,6 +74,10 @@ const convertPathPatternsToRegExp = (pagePathPatterns: string[]): Array<string |
|
|
|
};
|
|
|
|
|
|
export interface IOpenaiService {
|
|
|
+ generateAndProcessPreMessage(
|
|
|
+ message: string,
|
|
|
+ deltaProcessor: (delta: ChatCompletionChunk.Choice.Delta) => void,
|
|
|
+ ): Promise<Nullable<Stream<OpenAI.Chat.Completions.ChatCompletionChunk>>>
|
|
|
createThread(userId: string, type: ThreadType, aiAssistantId?: string, initialUserMessage?: string): Promise<ThreadRelationDocument>;
|
|
|
getThreadsByAiAssistantId(aiAssistantId: string): Promise<ThreadRelationDocument[]>
|
|
|
deleteThread(threadRelationId: string): Promise<ThreadRelationDocument>;
|
|
|
@@ -108,7 +114,41 @@ class OpenaiService implements IOpenaiService {
|
|
|
return getClient({ openaiServiceType });
|
|
|
}
|
|
|
|
|
|
- private async generateThreadTitle(message: string): Promise<string | null> {
|
|
|
+ async generateAndProcessPreMessage(
|
|
|
+ message: string,
|
|
|
+ deltaProcessor: (delta: ChatCompletionChunk.Choice.Delta) => void,
|
|
|
+ ): Promise<Nullable<Stream<OpenAI.Chat.Completions.ChatCompletionChunk>>> {
|
|
|
+ const systemMessage = [
|
|
|
+ "Generate a message briefly confirming the user's question.",
|
|
|
+ 'Please generate up to 20 characters',
|
|
|
+ ].join('');
|
|
|
+
|
|
|
+ const preMessageCompletion = await this.client.chatCompletion({
|
|
|
+ stream: true,
|
|
|
+ model: 'gpt-4.1-nano',
|
|
|
+ messages: [
|
|
|
+ {
|
|
|
+ role: 'system',
|
|
|
+ content: systemMessage,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ role: 'user',
|
|
|
+ content: message,
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ });
|
|
|
+
|
|
|
+ if (!isStreamResponse(preMessageCompletion)) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ for await (const chunk of preMessageCompletion) {
|
|
|
+ const delta = chunk.choices[0].delta;
|
|
|
+ deltaProcessor(delta);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private async generateThreadTitle(message: string): Promise<Nullable<string>> {
|
|
|
const systemMessage = [
|
|
|
'Create a brief title (max 5 words) from your message.',
|
|
|
'Respond in the same language the user uses in their input.',
|
|
|
@@ -129,8 +169,10 @@ class OpenaiService implements IOpenaiService {
|
|
|
],
|
|
|
});
|
|
|
|
|
|
- const threadTitle = threadTitleCompletion.choices[0].message.content;
|
|
|
- return threadTitle;
|
|
|
+ if (!isStreamResponse(threadTitleCompletion)) {
|
|
|
+ const threadTitle = threadTitleCompletion.choices[0].message.content;
|
|
|
+ return threadTitle;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
async createThread(userId: string, type: ThreadType, aiAssistantId?: string, initialUserMessage?: string): Promise<ThreadRelationDocument> {
|