From acdb8d8bafe86e145a16626b6034f21490840404 Mon Sep 17 00:00:00 2001 From: okamoto-aws <140481730+okamoto-aws@users.noreply.github.com> Date: Tue, 5 Nov 2024 10:14:52 +0900 Subject: [PATCH 01/15] Add Claude 3.5 Sonnet v2 CRI and Claude 3.5 Haiku (#700) --- docs/DEPLOY_OPTION.md | 6 ++++++ packages/cdk/lambda/utils/models.ts | 24 ++++++++++++++++++++++++ packages/cdk/lib/construct/api.ts | 4 ++++ 3 files changed, 34 insertions(+) diff --git a/docs/DEPLOY_OPTION.md b/docs/DEPLOY_OPTION.md index 14e1a4b4..e4c337cb 100644 --- a/docs/DEPLOY_OPTION.md +++ b/docs/DEPLOY_OPTION.md @@ -412,10 +412,13 @@ PromptFlow チャットユースケースでは、作成済みの Prompt Flow ``` "anthropic.claude-3-5-sonnet-20241022-v2:0", +"anthropic.claude-3-5-haiku-20241022-v1:0", "anthropic.claude-3-5-sonnet-20240620-v1:0", "anthropic.claude-3-opus-20240229-v1:0", "anthropic.claude-3-sonnet-20240229-v1:0", "anthropic.claude-3-haiku-20240307-v1:0", +"us.anthropic.claude-3-5-sonnet-20241022-v2:0", +"us.anthropic.claude-3-5-haiku-20241022-v1:0", "us.anthropic.claude-3-5-sonnet-20240620-v1:0", "us.anthropic.claude-3-opus-20240229-v1:0", "us.anthropic.claude-3-sonnet-20240229-v1:0", @@ -489,6 +492,7 @@ PromptFlow チャットユースケースでは、作成済みの Prompt Flow "modelRegion": "us-west-2", "modelIds": [ "anthropic.claude-3-5-sonnet-20241022-v2:0", + "anthropic.claude-3-5-haiku-20241022-v1:0", "anthropic.claude-3-5-sonnet-20240620-v1:0", "anthropic.claude-3-opus-20240229-v1:0", "anthropic.claude-3-sonnet-20240229-v1:0", @@ -512,6 +516,8 @@ PromptFlow チャットユースケースでは、作成済みの Prompt Flow ```bash "modelRegion": "us-west-2", "modelIds": [ + "us.anthropic.claude-3-5-sonnet-20241022-v2:0", + "us.anthropic.claude-3-5-haiku-20241022-v1:0", "us.anthropic.claude-3-5-sonnet-20240620-v1:0", "us.anthropic.claude-3-opus-20240229-v1:0", "us.anthropic.claude-3-sonnet-20240229-v1:0", diff --git a/packages/cdk/lambda/utils/models.ts b/packages/cdk/lambda/utils/models.ts index f48f2ebf..a3ab00d2 100644 --- a/packages/cdk/lambda/utils/models.ts +++ b/packages/cdk/lambda/utils/models.ts @@ -578,6 +578,30 @@ export const BEDROCK_TEXT_GEN_MODELS: { extractConverseOutputText: extractConverseOutputText, extractConverseStreamOutputText: extractConverseStreamOutputText, }, + 'us.anthropic.claude-3-5-sonnet-20241022-v2:0': { + defaultParams: CLAUDE_DEFAULT_PARAMS, + usecaseParams: USECASE_DEFAULT_PARAMS, + createConverseCommandInput: createConverseCommandInput, + createConverseStreamCommandInput: createConverseStreamCommandInput, + extractConverseOutputText: extractConverseOutputText, + extractConverseStreamOutputText: extractConverseStreamOutputText, + }, + 'anthropic.claude-3-5-haiku-20241022-v1:0': { + defaultParams: CLAUDE_DEFAULT_PARAMS, + usecaseParams: USECASE_DEFAULT_PARAMS, + createConverseCommandInput: createConverseCommandInput, + createConverseStreamCommandInput: createConverseStreamCommandInput, + extractConverseOutputText: extractConverseOutputText, + extractConverseStreamOutputText: extractConverseStreamOutputText, + }, + 'us.anthropic.claude-3-5-haiku-20241022-v1:0': { + defaultParams: CLAUDE_DEFAULT_PARAMS, + usecaseParams: USECASE_DEFAULT_PARAMS, + createConverseCommandInput: createConverseCommandInput, + createConverseStreamCommandInput: createConverseStreamCommandInput, + extractConverseOutputText: extractConverseOutputText, + extractConverseStreamOutputText: extractConverseStreamOutputText, + }, 'anthropic.claude-3-5-sonnet-20240620-v1:0': { defaultParams: CLAUDE_DEFAULT_PARAMS, usecaseParams: USECASE_DEFAULT_PARAMS, diff --git a/packages/cdk/lib/construct/api.ts b/packages/cdk/lib/construct/api.ts index 6e571dcc..088fcf85 100644 --- a/packages/cdk/lib/construct/api.ts +++ b/packages/cdk/lib/construct/api.ts @@ -72,10 +72,13 @@ export class Api extends Construct { // Validate Model Names const supportedModelIds = [ 'anthropic.claude-3-5-sonnet-20241022-v2:0', + 'anthropic.claude-3-5-haiku-20241022-v1:0', 'anthropic.claude-3-5-sonnet-20240620-v1:0', 'anthropic.claude-3-opus-20240229-v1:0', 'anthropic.claude-3-sonnet-20240229-v1:0', 'anthropic.claude-3-haiku-20240307-v1:0', + 'us.anthropic.claude-3-5-sonnet-20241022-v2:0', + 'us.anthropic.claude-3-5-haiku-20241022-v1:0', 'us.anthropic.claude-3-5-sonnet-20240620-v1:0', 'us.anthropic.claude-3-opus-20240229-v1:0', 'us.anthropic.claude-3-sonnet-20240229-v1:0', @@ -120,6 +123,7 @@ export class Api extends Construct { 'anthropic.claude-3-opus-20240229-v1:0', 'anthropic.claude-3-sonnet-20240229-v1:0', 'anthropic.claude-3-haiku-20240307-v1:0', + 'us.anthropic.claude-3-5-sonnet-20241022-v2:0', 'us.anthropic.claude-3-5-sonnet-20240620-v1:0', 'us.anthropic.claude-3-opus-20240229-v1:0', 'us.anthropic.claude-3-sonnet-20240229-v1:0', From 78eea2afd105830f28cedc310ae9daf1edcaf399 Mon Sep 17 00:00:00 2001 From: watany <76135106+watany-dev@users.noreply.github.com> Date: Wed, 6 Nov 2024 21:16:35 +0900 Subject: [PATCH 02/15] Add Claude CRI on APAC (#704) --- docs/DEPLOY_OPTION.md | 9 +++++++++ packages/cdk/lambda/utils/models.ts | 24 ++++++++++++++++++++++++ packages/cdk/lib/construct/api.ts | 6 ++++++ 3 files changed, 39 insertions(+) diff --git a/docs/DEPLOY_OPTION.md b/docs/DEPLOY_OPTION.md index e4c337cb..0b7425a9 100644 --- a/docs/DEPLOY_OPTION.md +++ b/docs/DEPLOY_OPTION.md @@ -374,6 +374,9 @@ PromptFlow チャットユースケースでは、作成済みの Prompt Flow "eu.anthropic.claude-3-5-sonnet-20240620-v1:0", "eu.anthropic.claude-3-sonnet-20240229-v1:0", "eu.anthropic.claude-3-haiku-20240307-v1:0", +"apac.anthropic.claude-3-haiku-20240307-v1:0", +"apac.anthropic.claude-3-sonnet-20240229-v1:0", +"apac.anthropic.claude-3-5-sonnet-20240620-v1:0", "us.meta.llama3-2-90b-instruct-v1:0", "us.meta.llama3-2-11b-instruct-v1:0", ``` @@ -395,6 +398,9 @@ PromptFlow チャットユースケースでは、作成済みの Prompt Flow "eu.anthropic.claude-3-5-sonnet-20240620-v1:0", "eu.anthropic.claude-3-sonnet-20240229-v1:0", "eu.anthropic.claude-3-haiku-20240307-v1:0", + "apac.anthropic.claude-3-haiku-20240307-v1:0", + "apac.anthropic.claude-3-sonnet-20240229-v1:0", + "apac.anthropic.claude-3-5-sonnet-20240620-v1:0", "us.meta.llama3-2-90b-instruct-v1:0", "us.meta.llama3-2-11b-instruct-v1:0", ] @@ -426,6 +432,9 @@ PromptFlow チャットユースケースでは、作成済みの Prompt Flow "eu.anthropic.claude-3-5-sonnet-20240620-v1:0", "eu.anthropic.claude-3-sonnet-20240229-v1:0", "eu.anthropic.claude-3-haiku-20240307-v1:0", +"apac.anthropic.claude-3-haiku-20240307-v1:0", +"apac.anthropic.claude-3-sonnet-20240229-v1:0", +"apac.anthropic.claude-3-5-sonnet-20240620-v1:0", "amazon.titan-text-premier-v1:0", "us.meta.llama3-2-90b-instruct-v1:0", "us.meta.llama3-2-11b-instruct-v1:0", diff --git a/packages/cdk/lambda/utils/models.ts b/packages/cdk/lambda/utils/models.ts index a3ab00d2..0ac1e327 100644 --- a/packages/cdk/lambda/utils/models.ts +++ b/packages/cdk/lambda/utils/models.ts @@ -626,6 +626,14 @@ export const BEDROCK_TEXT_GEN_MODELS: { extractConverseOutputText: extractConverseOutputText, extractConverseStreamOutputText: extractConverseStreamOutputText, }, + 'apac.anthropic.claude-3-5-sonnet-20240620-v1:0': { + defaultParams: CLAUDE_DEFAULT_PARAMS, + usecaseParams: USECASE_DEFAULT_PARAMS, + createConverseCommandInput: createConverseCommandInput, + createConverseStreamCommandInput: createConverseStreamCommandInput, + extractConverseOutputText: extractConverseOutputText, + extractConverseStreamOutputText: extractConverseStreamOutputText, + }, 'anthropic.claude-3-opus-20240229-v1:0': { defaultParams: CLAUDE_DEFAULT_PARAMS, usecaseParams: USECASE_DEFAULT_PARAMS, @@ -666,6 +674,14 @@ export const BEDROCK_TEXT_GEN_MODELS: { extractConverseOutputText: extractConverseOutputText, extractConverseStreamOutputText: extractConverseStreamOutputText, }, + 'apac.anthropic.claude-3-sonnet-20240229-v1:0': { + defaultParams: CLAUDE_DEFAULT_PARAMS, + usecaseParams: USECASE_DEFAULT_PARAMS, + createConverseCommandInput: createConverseCommandInput, + createConverseStreamCommandInput: createConverseStreamCommandInput, + extractConverseOutputText: extractConverseOutputText, + extractConverseStreamOutputText: extractConverseStreamOutputText, + }, 'anthropic.claude-3-haiku-20240307-v1:0': { defaultParams: CLAUDE_DEFAULT_PARAMS, usecaseParams: USECASE_DEFAULT_PARAMS, @@ -690,6 +706,14 @@ export const BEDROCK_TEXT_GEN_MODELS: { extractConverseOutputText: extractConverseOutputText, extractConverseStreamOutputText: extractConverseStreamOutputText, }, + 'apac.anthropic.claude-3-haiku-20240307-v1:0': { + defaultParams: CLAUDE_DEFAULT_PARAMS, + usecaseParams: USECASE_DEFAULT_PARAMS, + createConverseCommandInput: createConverseCommandInput, + createConverseStreamCommandInput: createConverseStreamCommandInput, + extractConverseOutputText: extractConverseOutputText, + extractConverseStreamOutputText: extractConverseStreamOutputText, + }, 'anthropic.claude-v2:1': { defaultParams: CLAUDE_DEFAULT_PARAMS, usecaseParams: USECASE_DEFAULT_PARAMS, diff --git a/packages/cdk/lib/construct/api.ts b/packages/cdk/lib/construct/api.ts index 088fcf85..61ca0976 100644 --- a/packages/cdk/lib/construct/api.ts +++ b/packages/cdk/lib/construct/api.ts @@ -86,6 +86,9 @@ export class Api extends Construct { 'eu.anthropic.claude-3-5-sonnet-20240620-v1:0', 'eu.anthropic.claude-3-sonnet-20240229-v1:0', 'eu.anthropic.claude-3-haiku-20240307-v1:0', + 'apac.anthropic.claude-3-5-sonnet-20240620-v1:0', + 'apac.anthropic.claude-3-sonnet-20240229-v1:0', + 'apac.anthropic.claude-3-haiku-20240307-v1:0', 'anthropic.claude-v2:1', 'anthropic.claude-v2', 'anthropic.claude-instant-v1', @@ -131,6 +134,9 @@ export class Api extends Construct { 'eu.anthropic.claude-3-5-sonnet-20240620-v1:0', 'eu.anthropic.claude-3-sonnet-20240229-v1:0', 'eu.anthropic.claude-3-haiku-20240307-v1:0', + 'apac.anthropic.claude-3-5-sonnet-20240620-v1:0', + 'apac.anthropic.claude-3-sonnet-20240229-v1:0', + 'apac.anthropic.claude-3-haiku-20240307-v1:0', 'us.meta.llama3-2-11b-instruct-v1:0', 'us.meta.llama3-2-90b-instruct-v1:0', ]; From a6b25dc4cf887f7ffdbd867020e5b74674b00976 Mon Sep 17 00:00:00 2001 From: Taichiro Suzuki Date: Thu, 7 Nov 2024 15:15:18 +0900 Subject: [PATCH 03/15] =?UTF-8?q?=E4=B8=80=E7=95=AA=E4=B8=8A/=E4=B8=80?= =?UTF-8?q?=E7=95=AA=E4=B8=8B=E3=81=AB=E3=82=B9=E3=82=AF=E3=83=AD=E3=83=BC?= =?UTF-8?q?=E3=83=AB=E3=83=9C=E3=82=BF=E3=83=B3=20(#706)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Taichiro Suzuki --- packages/web/src/App.tsx | 19 ++- .../web/src/components/ScrollTopBottom.tsx | 42 +++++ .../src/hooks/{useScroll.ts => useFollow.ts} | 26 +-- packages/web/src/hooks/useObserveScreen.ts | 42 ----- packages/web/src/hooks/useScreen.ts | 157 ++++++++++++++++++ packages/web/src/pages/AgentChatPage.tsx | 10 +- packages/web/src/pages/ChatPage.tsx | 10 +- packages/web/src/pages/PromptFlowChatPage.tsx | 10 +- .../web/src/pages/RagKnowledgeBasePage.tsx | 10 +- packages/web/src/pages/RagPage.tsx | 10 +- packages/web/src/pages/SharedChatPage.tsx | 5 + 11 files changed, 262 insertions(+), 79 deletions(-) create mode 100644 packages/web/src/components/ScrollTopBottom.tsx rename packages/web/src/hooks/{useScroll.ts => useFollow.ts} (66%) delete mode 100644 packages/web/src/hooks/useObserveScreen.ts create mode 100644 packages/web/src/hooks/useScreen.ts diff --git a/packages/web/src/App.tsx b/packages/web/src/App.tsx index a4734df1..f8c63ef0 100644 --- a/packages/web/src/App.tsx +++ b/packages/web/src/App.tsx @@ -1,4 +1,4 @@ -import React, { useMemo } from 'react'; +import React, { useMemo, useEffect } from 'react'; import { useLocation } from 'react-router-dom'; import { PiList, @@ -28,7 +28,7 @@ import useChatList from './hooks/useChatList'; import PopupInterUseCasesDemo from './components/PopupInterUseCasesDemo'; import useInterUseCases from './hooks/useInterUseCases'; import { MODELS } from './hooks/useModel'; -import useObserveScreen from './hooks/useObserveScreen'; +import useScreen from './hooks/useScreen'; const ragEnabled: boolean = import.meta.env.VITE_APP_RAG_ENABLED === 'true'; const ragKnowledgeBaseEnabled: boolean = @@ -173,7 +173,8 @@ const App: React.FC = () => { const { pathname } = useLocation(); const { getChatTitle } = useChatList(); const { isShow } = useInterUseCases(); - const { handleScroll } = useObserveScreen(); + const { screen, notifyScreen, scrollTopAnchorRef, scrollBottomAnchorRef } = + useScreen(); const label = useMemo(() => { const chatId = extractChatId(pathname); @@ -185,11 +186,20 @@ const App: React.FC = () => { } }, [pathname, getChatTitle]); + // 画面間遷移時にスクロールイベントが発火しない場合 (ページ最上部からページ最上部への移動など) + // 最上部/最下部の判定がされないので、pathname の変化に応じて再判定する + useEffect(() => { + if (screen.current) { + notifyScreen(screen.current); + } + }, [pathname, screen, notifyScreen]); + return (
+ ref={screen}>
+
+
); diff --git a/packages/web/src/components/ScrollTopBottom.tsx b/packages/web/src/components/ScrollTopBottom.tsx new file mode 100644 index 00000000..f479becc --- /dev/null +++ b/packages/web/src/components/ScrollTopBottom.tsx @@ -0,0 +1,42 @@ +import React, { useMemo } from 'react'; +import { BaseProps } from '../@types/common'; +import { PiArrowLineDownFill, PiArrowLineUpFill } from 'react-icons/pi'; +import useScreen from '../hooks/useScreen'; + +type Props = BaseProps; + +const ScrollTopBottom: React.FC = (props) => { + const { isAtBottom, isAtTop, scrollToBottom, scrollToTop } = useScreen(); + + // 最下部までスクロールが可能かどうか + // すでに到達している場合は不可 + const scrollToBottomAvailable = useMemo(() => { + return !isAtBottom; + }, [isAtBottom]); + + // 最上部までスクロール可能かどうか + // すでに到達している場合は不可 + const scrollToTopAvailable = useMemo(() => { + return !isAtTop; + }, [isAtTop]); + + return ( +
+ + +
+ ); +}; + +export default ScrollTopBottom; diff --git a/packages/web/src/hooks/useScroll.ts b/packages/web/src/hooks/useFollow.ts similarity index 66% rename from packages/web/src/hooks/useScroll.ts rename to packages/web/src/hooks/useFollow.ts index 4d444ecf..d954b9e1 100644 --- a/packages/web/src/hooks/useScroll.ts +++ b/packages/web/src/hooks/useFollow.ts @@ -1,35 +1,27 @@ -import { useRef, useEffect, useState, useCallback } from 'react'; -import useObserveScreen from './useObserveScreen'; +import { useRef, useEffect, useState } from 'react'; +import useScreen from './useScreen'; -const useScroll = () => { - const { isAtBottom } = useObserveScreen(); +const useFollow = () => { + const { isAtBottom, scrollToBottom } = useScreen(); // スクロールされる要素が含まれる要素 // サイズが動的に変更されることが想定される // チャットのページであればメッセージを wrap した要素 const scrollableContainer = useRef(null); - // スクロール下部 - const scrolledAnchor = useRef(null); - // フォローするか否か // ページ最下部まで到達している場合はフォローする // そうでない場合 (手動で上にスクロールした場合) はフォローしないようにする const [following, setFollowing] = useState(true); - // フォロー中であれば最下部までスクロールする - const scrollToBottom = useCallback(() => { - if (scrolledAnchor.current && following) { - scrolledAnchor.current.scrollIntoView(); - } - }, [scrolledAnchor, following]); - // scrollableContainer のサイズ変更を監視 useEffect(() => { if (scrollableContainer.current) { const observer = new ResizeObserver(() => { // 画面サイズ変更されたらフォローする - scrollToBottom(); + if (following) { + scrollToBottom(); + } }); observer.observe(scrollableContainer.current); @@ -48,10 +40,8 @@ const useScroll = () => { return { setFollowing, - scrollToBottom, scrollableContainer, - scrolledAnchor, }; }; -export default useScroll; +export default useFollow; diff --git a/packages/web/src/hooks/useObserveScreen.ts b/packages/web/src/hooks/useObserveScreen.ts deleted file mode 100644 index 14a40c93..00000000 --- a/packages/web/src/hooks/useObserveScreen.ts +++ /dev/null @@ -1,42 +0,0 @@ -import React from 'react'; -import { create } from 'zustand'; - -const useObserveScreenStore = create<{ - isAtBottom: boolean; - setIsAtBottom: (newIsAtBottom: boolean) => void; -}>((set) => { - const setIsAtBottom = (newIsAtBottom: boolean) => { - set(() => { - return { - isAtBottom: newIsAtBottom, - }; - }); - }; - - return { - isAtBottom: false, - setIsAtBottom, - }; -}); - -const useObserveScreen = () => { - const { isAtBottom, setIsAtBottom } = useObserveScreenStore(); - const handleScroll = (e: React.UIEvent) => { - const div = e.target as HTMLDivElement; - // 最下部に到達している時に isAtBottom を true に - // 小数点が省略されることがあるため、1.0 の余裕を設ける - if (div.clientHeight + div.scrollTop + 1.0 >= div.scrollHeight) { - setIsAtBottom(true); - } else { - setIsAtBottom(false); - } - }; - - return { - handleScroll, - isAtBottom, - setIsAtBottom, - }; -}; - -export default useObserveScreen; diff --git a/packages/web/src/hooks/useScreen.ts b/packages/web/src/hooks/useScreen.ts new file mode 100644 index 00000000..bbd7d3a3 --- /dev/null +++ b/packages/web/src/hooks/useScreen.ts @@ -0,0 +1,157 @@ +import { useEffect, useRef, useCallback } from 'react'; +import { create } from 'zustand'; + +const useScreenStore = create<{ + isAtBottom: boolean; + isAtTop: boolean; + setIsAtBottom: (newIsAtBottom: boolean) => void; + setIsAtTop: (newIsAtTop: boolean) => void; + scrollTopAnchor: HTMLDivElement | null; + scrollBottomAnchor: HTMLDivElement | null; + setScrollTopAnchor: (newScrollTopAnchor: HTMLDivElement | null) => void; + setScrollBottomAnchor: (newScrollBottomAnchor: HTMLDivElement | null) => void; +}>((set) => { + const setIsAtBottom = (newIsAtBottom: boolean) => { + set(() => { + return { + isAtBottom: newIsAtBottom, + }; + }); + }; + + const setIsAtTop = (newIsAtTop: boolean) => { + set(() => { + return { + isAtTop: newIsAtTop, + }; + }); + }; + + const setScrollTopAnchor = (newScrollTopAnchor: HTMLDivElement | null) => { + set(() => { + return { + scrollTopAnchor: newScrollTopAnchor, + }; + }); + }; + + const setScrollBottomAnchor = ( + newScrollBottomAnchor: HTMLDivElement | null + ) => { + set(() => { + return { + scrollBottomAnchor: newScrollBottomAnchor, + }; + }); + }; + + return { + isAtBottom: false, + isAtTop: false, + setIsAtBottom, + setIsAtTop, + scrollTopAnchor: null, + scrollBottomAnchor: null, + setScrollTopAnchor, + setScrollBottomAnchor, + }; +}); + +const useScreen = () => { + const { + isAtBottom, + isAtTop, + setIsAtBottom, + setIsAtTop, + scrollTopAnchor, + setScrollTopAnchor, + scrollBottomAnchor, + setScrollBottomAnchor, + } = useScreenStore(); + + const screen = useRef(null); + + // スクリーンのサイズや位置が変わったことを通知する関数 + // 初期時、スクロール時に呼ばれる + // チャットの要素読み込み完了時には自動で下までスクロールされるため、そこでも呼ばれる + const notifyScreen = useCallback( + (div: HTMLDivElement) => { + // 最下部に到達している時に isAtBottom を true に + // 小数点が省略されることがあるため、1.0 の余裕を設ける + if (div.clientHeight + div.scrollTop + 1.0 >= div.scrollHeight) { + setIsAtBottom(true); + } else { + setIsAtBottom(false); + } + + // 最上部に到達している時に isAtTop を true に + // 小数点が省略されることがあるため、1.0 の余裕を設ける + if (div.scrollTop <= 1.0) { + setIsAtTop(true); + } else { + setIsAtTop(false); + } + }, + [setIsAtBottom, setIsAtTop] + ); + + // screen (App.tsx に定義されている) が設定された際に scroll イベントの listener を設定する + useEffect(() => { + const current = screen.current; + + if (!current) return; + + const handleScrollInner = () => { + notifyScreen(current); + }; + + screen.current.addEventListener('scroll', handleScrollInner); + notifyScreen(current); + + return () => { + current.removeEventListener('scroll', handleScrollInner); + }; + }, [screen, notifyScreen]); + + const scrollTopAnchorRef = useRef(null); + const scrollBottomAnchorRef = useRef(null); + + useEffect(() => { + if (scrollTopAnchorRef.current) { + setScrollTopAnchor(scrollTopAnchorRef.current); + } + }, [scrollTopAnchorRef, setScrollTopAnchor]); + + useEffect(() => { + if (scrollBottomAnchorRef.current) { + setScrollBottomAnchor(scrollBottomAnchorRef.current); + } + }, [scrollBottomAnchorRef, setScrollBottomAnchor]); + + const scrollToBottom = useCallback(() => { + if (scrollBottomAnchor) { + scrollBottomAnchor.scrollIntoView(); + } + }, [scrollBottomAnchor]); + + const scrollToTop = useCallback(() => { + if (scrollTopAnchor) { + scrollTopAnchor.scrollIntoView(); + } + }, [scrollTopAnchor]); + + return { + screen, + notifyScreen, + isAtBottom, + isAtTop, + setIsAtBottom, + setIsAtTop, + scrollTopAnchorRef, + scrollBottomAnchorRef, + scrollToBottom, + scrollToTop, + }; +}; + +export default useScreen; diff --git a/packages/web/src/pages/AgentChatPage.tsx b/packages/web/src/pages/AgentChatPage.tsx index 261d3432..a28f3c05 100644 --- a/packages/web/src/pages/AgentChatPage.tsx +++ b/packages/web/src/pages/AgentChatPage.tsx @@ -5,7 +5,8 @@ import useChat from '../hooks/useChat'; import useChatList from '../hooks/useChatList'; import ChatMessage from '../components/ChatMessage'; import Select from '../components/Select'; -import useScroll from '../hooks/useScroll'; +import ScrollTopBottom from '../components/ScrollTopBottom'; +import useFollow from '../hooks/useFollow'; import { create } from 'zustand'; import BedrockIcon from '../assets/bedrock.svg?react'; import { AgentPageQueryParams } from '../@types/navigate'; @@ -76,7 +77,7 @@ const AgentChatPage: React.FC = () => { postChat, updateSystemContextByModel, } = useChat(pathname, chatId); - const { scrollableContainer, scrolledAnchor, setFollowing } = useScroll(); + const { scrollableContainer, setFollowing } = useFollow(); const { getChatTitle } = useChatList(); const { agentNames: availableModels } = MODELS; const modelId = getModelId(); @@ -215,7 +216,10 @@ const AgentChatPage: React.FC = () => { ))} -
+ +
+ +
{ } = useChat(pathname, chatId); const { createShareId, findShareId, deleteShareId } = useChatApi(); const { createSystemContext } = useSystemContextApi(); - const { scrollableContainer, scrolledAnchor, setFollowing } = useScroll(); + const { scrollableContainer, setFollowing } = useFollow(); const { getChatTitle } = useChatList(); const { modelIds: availableModels } = MODELS; const { data: share, mutate: reloadShare } = findShareId(chatId); @@ -430,7 +431,10 @@ const ChatPage: React.FC = () => {
))}
-
+ +
+ +
{isEmpty && !loadingMessages && !chatId && ( diff --git a/packages/web/src/pages/PromptFlowChatPage.tsx b/packages/web/src/pages/PromptFlowChatPage.tsx index fd7cd9b1..ae27f015 100644 --- a/packages/web/src/pages/PromptFlowChatPage.tsx +++ b/packages/web/src/pages/PromptFlowChatPage.tsx @@ -4,7 +4,8 @@ import InputChatContent from '../components/InputChatContent'; import usePromptFlowChat from '../hooks/usePromptFlowChat'; import ChatMessage from '../components/ChatMessage'; import Select from '../components/Select'; -import useScroll from '../hooks/useScroll'; +import ScrollTopBottom from '../components/ScrollTopBottom'; +import useFollow from '../hooks/useFollow'; import { create } from 'zustand'; import BedrockIcon from '../assets/bedrock.svg?react'; @@ -39,7 +40,7 @@ const PromptFlowChatPage: React.FC = () => { clear: clearChat, } = usePromptFlowChat(); - const { scrollableContainer, scrolledAnchor, setFollowing } = useScroll(); + const { scrollableContainer, setFollowing } = useFollow(); useEffect(() => { setFlow(availableFlows[0]); @@ -100,7 +101,10 @@ const PromptFlowChatPage: React.FC = () => {
))}
-
+ +
+ +
{ const { getModelId, setModelId } = useChat(pathname); const { postMessage, clear, loading, messages, isEmpty } = useRagKnowledgeBase(pathname); - const { scrollableContainer, scrolledAnchor, setFollowing } = useScroll(); + const { scrollableContainer, setFollowing } = useFollow(); const { modelIds: availableModels } = MODELS; const modelId = getModelId(); @@ -118,7 +119,10 @@ const RagKnowledgeBasePage: React.FC = () => {
))}
-
+ +
+ +
{ const { pathname, search } = useLocation(); const { getModelId, setModelId } = useChat(pathname); const { postMessage, clear, loading, messages, isEmpty } = useRag(pathname); - const { scrollableContainer, scrolledAnchor, setFollowing } = useScroll(); + const { scrollableContainer, setFollowing } = useFollow(); const { modelIds: availableModels } = MODELS; const modelId = getModelId(); @@ -130,7 +131,10 @@ const RagPage: React.FC = () => {
))}
-
+ +
+ +
{ const { shareId } = useParams(); @@ -85,6 +86,10 @@ const SharedChatPage: React.FC = () => {
))} + +
+ +
)} From 95c22a3bc84cf184be6aefa352adcbff1c68f765 Mon Sep 17 00:00:00 2001 From: Toshiki Watanabe <37267851+toshikwa@users.noreply.github.com> Date: Mon, 11 Nov 2024 11:14:47 +0900 Subject: [PATCH 04/15] Add page number for knowledge base response (#707) --- packages/web/src/hooks/useRag.ts | 4 ++- packages/web/src/hooks/useRagKnowledgeBase.ts | 32 +++++++++++++++---- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/packages/web/src/hooks/useRag.ts b/packages/web/src/hooks/useRag.ts index 2a798c94..50f79eda 100644 --- a/packages/web/src/hooks/useRag.ts +++ b/packages/web/src/hooks/useRag.ts @@ -18,7 +18,9 @@ const uniqueKeyOfItem = (item: RetrieveResultItem): string => { return `${uri}_${pageNumber}`; }; -const arrangeItems = (items: RetrieveResultItem[]): RetrieveResultItem[] => { +export const arrangeItems = ( + items: RetrieveResultItem[] +): RetrieveResultItem[] => { const res: Record = {}; for (const item of items) { diff --git a/packages/web/src/hooks/useRagKnowledgeBase.ts b/packages/web/src/hooks/useRagKnowledgeBase.ts index e144f9b2..69490726 100644 --- a/packages/web/src/hooks/useRagKnowledgeBase.ts +++ b/packages/web/src/hooks/useRagKnowledgeBase.ts @@ -5,6 +5,7 @@ import { getPrompter } from '../prompts'; import { RetrieveResultItem } from '@aws-sdk/client-kendra'; import { ShownMessage } from 'generative-ai-use-cases-jp'; import { cleanEncode } from '../utils/URLUtils'; +import { arrangeItems } from './useRag'; // s3:/// から https://s3..amazonaws.com// に変換する const convertS3UriToUrl = (s3Uri: string, region: string): string => { @@ -99,19 +100,30 @@ const useRagKnowledgeBase = (id: string) => { retrievedItems.data.retrievalResults!.map((r, idx) => { const sourceUri = r.metadata?.['x-amz-bedrock-kb-source-uri']?.toString() ?? ''; + const pageNumber = + r.metadata?.['x-amz-bedrock-kb-document-page-number']; return { Content: r.content?.text ?? '', DocumentId: `${idx}`, DocumentTitle: sourceUri.split('/').pop(), DocumentURI: convertS3UriToUrl(sourceUri, modelRegion), + DocumentAttributes: pageNumber + ? [ + { + Key: '_excerpt_page_number', + Value: { LongValue: Number(pageNumber) }, + }, + ] + : [], }; }); + const items = arrangeItems(retrievedItemsKendraFormat); updateSystemContext( prompter.ragPrompt({ promptType: 'SYSTEM_CONTEXT', - referenceItems: retrievedItemsKendraFormat, + referenceItems: items, }) ); @@ -129,13 +141,21 @@ const useRagKnowledgeBase = (id: string) => { }, (message: string) => { // 後処理:Footnote の付与 - const footnote = retrievedItemsKendraFormat + const footnote = items .map((item, idx) => { - const encodedURI = item.DocumentURI - ? cleanEncode(item.DocumentURI) - : ''; + // 参考にしたページ番号がある場合は、アンカーリンクとして設定する + const _excerpt_page_number = item.DocumentAttributes?.find( + (attr) => attr.Key === '_excerpt_page_number' + )?.Value?.LongValue; return message.includes(`[^${idx}]`) - ? `[^${idx}]: [${item.DocumentTitle}](${encodedURI})` + ? `[^${idx}]: [${item.DocumentTitle}${ + _excerpt_page_number + ? `(${_excerpt_page_number} ページ)` + : '' + }]( + ${item.DocumentURI ? cleanEncode(item.DocumentURI) : ''}${ + _excerpt_page_number ? `#page=${_excerpt_page_number}` : '' + })` : ''; }) .filter((x) => x) From 4992bc89ebebea48ce297ed31bf94e97a8f000c8 Mon Sep 17 00:00:00 2001 From: Taichiro Suzuki Date: Mon, 11 Nov 2024 23:13:23 +0900 Subject: [PATCH 05/15] =?UTF-8?q?=E3=82=B7=E3=82=B9=E3=83=86=E3=83=A0?= =?UTF-8?q?=E3=82=B3=E3=83=B3=E3=83=86=E3=82=AD=E3=82=B9=E3=83=88=E3=82=92?= =?UTF-8?q?=E4=BC=9A=E8=A9=B1=E5=B1=A5=E6=AD=B4=E3=81=A8=E3=82=B7=E3=82=A7?= =?UTF-8?q?=E3=82=A2=E3=83=9A=E3=83=BC=E3=82=B8=E3=81=8B=E3=82=89=E4=BF=9D?= =?UTF-8?q?=E5=AD=98=E5=8F=AF=E8=83=BD=E3=81=AB=20(#709)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Taichiro Suzuki --- packages/web/src/components/ChatMessage.tsx | 22 ++++-- .../web/src/components/ModalSystemContext.tsx | 70 +++++++++++++++++++ packages/web/src/pages/ChatPage.tsx | 58 ++++----------- packages/web/src/pages/SharedChatPage.tsx | 37 +++++++++- 4 files changed, 134 insertions(+), 53 deletions(-) create mode 100644 packages/web/src/components/ModalSystemContext.tsx diff --git a/packages/web/src/components/ChatMessage.tsx b/packages/web/src/components/ChatMessage.tsx index eebaf8f1..72ebe4db 100644 --- a/packages/web/src/components/ChatMessage.tsx +++ b/packages/web/src/components/ChatMessage.tsx @@ -3,8 +3,9 @@ import { useLocation } from 'react-router-dom'; import Markdown from './Markdown'; import ButtonCopy from './ButtonCopy'; import ButtonFeedback from './ButtonFeedback'; +import ButtonIcon from './ButtonIcon'; import ZoomUpImage from './ZoomUpImage'; -import { PiUserFill, PiChalkboardTeacher } from 'react-icons/pi'; +import { PiUserFill, PiChalkboardTeacher, PiFloppyDisk } from 'react-icons/pi'; import { BaseProps } from '../@types/common'; import { ShownMessage, @@ -22,6 +23,8 @@ type Props = BaseProps & { chatContent?: ShownMessage; loading?: boolean; hideFeedback?: boolean; + setSaveSystemContext?: (s: string) => void; + setShowSystemContextModal?: (value: boolean) => void; }; const ChatMessage: React.FC = (props) => { @@ -146,7 +149,7 @@ const ChatMessage: React.FC = (props) => {
)} -
+
{chatContent?.trace && (
@@ -217,16 +220,23 @@ const ChatMessage: React.FC = (props) => { )} {chatContent?.role === 'assistant' && ( -
+
{chatContent?.llmType}
)}
-
- {(chatContent?.role === 'user' || chatContent?.role === 'system') && ( -
+
+ {chatContent?.role === 'system' && ( + { + props.setSaveSystemContext?.(chatContent?.content || ''); + props.setShowSystemContextModal?.(true); + }}> + + )} {chatContent?.role === 'assistant' && !props.loading && diff --git a/packages/web/src/components/ModalSystemContext.tsx b/packages/web/src/components/ModalSystemContext.tsx new file mode 100644 index 00000000..ec771915 --- /dev/null +++ b/packages/web/src/components/ModalSystemContext.tsx @@ -0,0 +1,70 @@ +import React from 'react'; +import ModalDialog from './ModalDialog'; +import { BaseProps } from '../@types/common'; +import Textarea from './Textarea'; +import Button from './Button'; + +type Props = BaseProps & { + showSystemContextModal: boolean; + saveSystemContext: string; + saveSystemContextTitle: string; + setShowSystemContextModal: (show: boolean) => void; + setSaveSystemContext: (systemContext: string) => void; + setSaveSystemContextTitle: (title: string) => void; + onCreateSystemContext: () => void; +}; + +const ModalSystemContext: React.FC = (props) => { + return ( + <> + { + props.setShowSystemContextModal(false); + }}> +
タイトル
+ +