Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Implement api for rebuilding Vector Store #9133

9 changes: 8 additions & 1 deletion apps/app/public/static/locales/en_US/admin.json
Original file line number Diff line number Diff line change
Expand Up @@ -1135,6 +1135,13 @@
},
"ai_integration": {
"ai_integration": "AI Integration",
"disable_mode_explanation": "Currently, AI integration is disabled. To enable it, please set the environment variable <code>AI_ENABLED</code> to true."
"disable_mode_explanation": "Currently, AI integration is disabled. To enable it, please set the environment variable <code>AI_ENABLED</code> to true.",
"ai_search_management": "AI search management",
"rebuild_vector_store": "Rebuild Vector Store",
"rebuild_vector_store_label": "Rebuild",
"rebuild_vector_store_explanation1": "Delete the existing Vector Store and recreate the Vector Store on the public page.",
"rebuild_vector_store_explanation2": "This process may take several minutes.",
"rebuild_vector_store_succeeded": "Vector Store rebuild succeeded",
"rebuild_vector_store_failed": "Vector Store rebuild failed"
}
}
9 changes: 8 additions & 1 deletion apps/app/public/static/locales/fr_FR/admin.json
Original file line number Diff line number Diff line change
Expand Up @@ -1134,6 +1134,13 @@
},
"ai_integration": {
"ai_integration": "Intégration de l'IA",
"disable_mode_explanation": "Actuellement, l'intégration de l'IA est désactivée. Pour l'activer, veuillez définir la variable d'environnement <code>AI_ENABLED</code> sur true"
"disable_mode_explanation": "Actuellement, l'intégration de l'IA est désactivée. Pour l'activer, veuillez définir la variable d'environnement <code>AI_ENABLED</code> sur true",
"ai_search_management": "Gestion de la recherche par l'IA",
"rebuild_vector_store": "Reconstruire le magasin Vector",
"rebuild_vector_store_label": "Reconstruire",
"rebuild_vector_store_explanation1": "Supprimez le Vector Store existant et recréez le Vector Store sur la page publique.",
"rebuild_vector_store_explanation2": "Ce processus peut prendre plusieurs minutes.",
"rebuild_vector_store_succeeded": "La reconstruction du magasin vectoriel a réussi",
"rebuild_vector_store_failed": "Échec de la reconstruction du magasin de vecteurs"
}
}
9 changes: 8 additions & 1 deletion apps/app/public/static/locales/ja_JP/admin.json
Original file line number Diff line number Diff line change
Expand Up @@ -1145,6 +1145,13 @@
},
"ai_integration": {
"ai_integration": "AI 連携",
"disable_mode_explanation": "現在、AI 連携は無効になっています。有効にする場合は環境変数 <code>AI_ENABLED</code> を true に設定してください。"
"disable_mode_explanation": "現在、AI 連携は無効になっています。有効にする場合は環境変数 <code>AI_ENABLED</code> を true に設定してください。",
"ai_search_management": "AI 検索管理",
"rebuild_vector_store": "Vector Store のリビルド",
"rebuild_vector_store_label": "リビルド",
"rebuild_vector_store_explanation1": "既存の Vector Store を削除し、公開ページの Vector Store を再作成します。",
"rebuild_vector_store_explanation2": "この作業には数分かかる可能性があります。",
"rebuild_vector_store_succeeded": "Vector Store のリビルドに成功しました",
"rebuild_vector_store_failed": "Vector Store のリビルドに失敗しました"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

クリック時に表示するメッセージは成功可否ではなく「リビルドを受け付けた」という内容ではないか?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

修正しました

}
}
9 changes: 8 additions & 1 deletion apps/app/public/static/locales/zh_CN/admin.json
Original file line number Diff line number Diff line change
Expand Up @@ -1144,6 +1144,13 @@
},
"ai_integration": {
"ai_integration": "AI 集成",
"disable_mode_explanation": "目前,AI 集成已禁用。要启用它,请将环境变量 <code>AI_ENABLED</code> 设置为 true”"
"disable_mode_explanation": "目前,AI 集成已禁用。要启用它,请将环境变量 <code>AI_ENABLED</code> 设置为 true",
"ai_search_management": "AI 搜索管理",
"rebuild_vector_store": "重建矢量商店",
"rebuild_vector_store_label": "重建",
"rebuild_vector_store_explanation1": "删除现有的矢量存储,在公共页面上重新创建矢量存储。",
"rebuild_vector_store_explanation2": "这个过程可能需要几分钟。",
"rebuild_vector_store_succeeded": "矢量存储器重建成功",
"rebuild_vector_store_failed": "向量存储区重建失败"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { useCallback } from 'react';

import { useTranslation } from 'react-i18next';

import { apiv3Put } from '~/client/util/apiv3-client';
import { toastSuccess, toastError } from '~/client/util/toastr';


export const AiIntegration = (): JSX.Element => {
const { t } = useTranslation('admin');

const clickRebuildVectorStoreButtonHandler = useCallback(async() => {
try {
await apiv3Put('/ai-integration/rebuild-vector-store');
toastSuccess(t('ai_integration.rebuild_vector_store_succeeded'));
}
catch {
toastError(t('ai_integration.rebuild_vector_store_failed'));
}
}, [t]);

return (
<div data-testid="admin-ai-integration">
<h2 className="admin-setting-header">{ t('ai_integration.ai_search_management') }</h2>

<div className="row">
<label className="col-md-3 col-form-label text-start text-md-end">{ t('ai_integration.rebuild_vector_store_label') }</label>
<div className="col-md-8">
{/* TODO: https://redmine.weseek.co.jp/issues/153978 */}
<button
type="submit"
className="btn btn-primary"
onClick={clickRebuildVectorStoreButtonHandler}
>
{t('ai_integration.rebuild_vector_store')}
</button>

<p className="form-text text-muted">
{t('ai_integration.rebuild_vector_store_explanation1')}<br />
{t('ai_integration.rebuild_vector_store_explanation2')}<br />
</p>
</div>
</div>
</div>
);
};
5 changes: 5 additions & 0 deletions apps/app/src/interfaces/ai.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const AiServiceType = {
OPEN_AI: 'openai',
} as const;

export const aiServiceTypes = Object.values(AiServiceType);
5 changes: 3 additions & 2 deletions apps/app/src/pages/admin/ai-integration.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { retrieveServerSideProps } from '../../utils/admin-page-util';

const AdminLayout = dynamic(() => import('~/components/Layout/AdminLayout'), { ssr: false });
const ForbiddenPage = dynamic(() => import('~/client/components/Admin/ForbiddenPage').then(mod => mod.ForbiddenPage), { ssr: false });
const AiIntegration = dynamic(() => import('~/client/components/Admin/AiIntegration/AiIntegration').then(mod => mod.AiIntegration), { ssr: false });
const AiIntegrationDisableMode = dynamic(
() => import('~/client/components/Admin/AiIntegration/AiIntegrationDisableMode').then(mod => mod.AiIntegrationDisableMode), { ssr: false },
);
Expand All @@ -21,7 +22,7 @@ type Props = CommonProps & {
aiEnabled: boolean,
};

const AdminAiIntegrationPage: NextPage<Props> = (props) => {
const AdminAiIntegrationPage: NextPage<Props> = (props: Props) => {
const { t } = useTranslation('admin');

const title = t('ai_integration.ai_integration');
Expand All @@ -37,7 +38,7 @@ const AdminAiIntegrationPage: NextPage<Props> = (props) => {
<title>{headTitle}</title>
</Head>
{props.aiEnabled
? <></> // TODO: implement admin page
? <AiIntegration />
: <AiIntegrationDisableMode />
}
</AdminLayout>
Expand Down
26 changes: 26 additions & 0 deletions apps/app/src/server/middlewares/certify-ai-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type { NextFunction, Request, Response } from 'express';

import { aiServiceTypes } from '~/interfaces/ai';
import { configManager } from '~/server/service/config-manager';
import loggerFactory from '~/utils/logger';

const logger = loggerFactory('growi:middlewares:certify-ai-service');

export const certifyAiService = (req: Request, res: Response & { apiv3Err }, next: NextFunction): void => {
const aiEnabled = configManager.getConfig('crowi', 'app:aiEnabled');
const aiServiceType = configManager.getConfig('crowi', 'app:aiServiceType');

if (!aiEnabled) {
const message = 'AI_ENABLED is not true';
logger.error(message);
return res.apiv3Err(message, 400);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

403 かなあ
400 だとパラメータ次第でリクエスト通りそうな印象

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

修正しました

}

if (aiServiceType == null || !aiServiceTypes.includes(aiServiceType)) {
const message = 'AI_SERVICE_TYPE is missing or contains an invalid value';
logger.error(message);
return res.apiv3Err(message, 400);
}

next();
};
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI 機能が利用可能かどうかを判断するための express middleware

Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@ import type { ValidationChain } from 'express-validator';
import { body } from 'express-validator';

import type Crowi from '~/server/crowi';
import { apiV3FormValidator } from '~/server/middlewares/apiv3-form-validator';
import { certifyAiService } from '~/server/middlewares/certify-ai-service';
import { configManager } from '~/server/service/config-manager';
import { openaiClient } from '~/server/service/openai';
import { getOrCreateChatAssistant } from '~/server/service/openai/assistant';
import loggerFactory from '~/utils/logger';

import { apiV3FormValidator } from '../../../middlewares/apiv3-form-validator';

import type { ApiV3Response } from '../interfaces/apiv3-response';

const logger = loggerFactory('growi:routes:apiv3:openai:chat');
const logger = loggerFactory('growi:routes:apiv3:ai-integration:chat');

type ReqBody = {
userMessage: string,
Expand All @@ -32,7 +34,7 @@ export const chatHandlersFactory: ChatHandlersFactory = (crowi) => {
];

return [
accessTokenParser, loginRequiredStrictly, validator, apiV3FormValidator,
accessTokenParser, loginRequiredStrictly, certifyAiService, validator, apiV3FormValidator,
async(req: Req, res: ApiV3Response) => {
const vectorStoreId = configManager.getConfig('crowi', 'app:openaiVectorStoreId');
if (vectorStoreId == null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import express from 'express';

import { chatHandlersFactory } from './chat';
import { rebuildVectorStoreHandlersFactory } from './rebuild-vector-store';

const router = express.Router();

module.exports = (crowi) => {
router.post('/chat', chatHandlersFactory(crowi));
router.put('/rebuild-vector-store', rebuildVectorStoreHandlersFactory(crowi));
return router;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import type { Request, RequestHandler } from 'express';
import type { ValidationChain } from 'express-validator';

import type Crowi from '~/server/crowi';
import { certifyAiService } from '~/server/middlewares/certify-ai-service';
import loggerFactory from '~/utils/logger';

import { apiV3FormValidator } from '../../../middlewares/apiv3-form-validator';
import type { ApiV3Response } from '../interfaces/apiv3-response';

const logger = loggerFactory('growi:routes:apiv3:ai-integration:rebuild-vector-store');

type RebuildVectorStoreFactory = (crowi: Crowi) => RequestHandler[];

export const rebuildVectorStoreHandlersFactory: RebuildVectorStoreFactory = (crowi) => {
const accessTokenParser = require('~/server/middlewares/access-token-parser')(crowi);
const loginRequiredStrictly = require('~/server/middlewares/login-required')(crowi);
const adminRequired = require('~/server/middlewares/admin-required')(crowi);

const validator: ValidationChain[] = [
//
];

return [
accessTokenParser, loginRequiredStrictly, adminRequired, certifyAiService, validator, apiV3FormValidator,
async(req: Request, res: ApiV3Response) => {
return res.apiv3({});
},
];
};
4 changes: 2 additions & 2 deletions apps/app/src/server/routes/apiv3/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import injectUserRegistrationOrderByTokenMiddleware from '../../middlewares/inje
import * as loginFormValidator from '../../middlewares/login-form-validator';
import * as registerFormValidator from '../../middlewares/register-form-validator';

import aiIntegration from './ai-integration';
import g2gTransfer from './g2g-transfer';
import importRoute from './import';
import openai from './openai';
import pageListing from './page-listing';
import securitySettings from './security-settings';
import * as userActivation from './user-activation';
Expand Down Expand Up @@ -120,7 +120,7 @@ module.exports = (crowi, app) => {
router.use('/questionnaire', require('~/features/questionnaire/server/routes/apiv3/questionnaire')(crowi));
router.use('/templates', require('~/features/templates/server/routes/apiv3')(crowi));

router.use('/openai', openai(crowi));
router.use('/ai-integration', aiIntegration(crowi));

return [router, routerForAdmin, routerForAuth];
};
Loading