Skip to content

Commit

Permalink
Merge pull request aws-samples#227 from aws-samples/fearure/inter-use…
Browse files Browse the repository at this point in the history
…-case-demo

ユースケース間連携デモ
  • Loading branch information
tbrand authored Dec 12, 2023
2 parents 2923ecc + 7c459a3 commit a417f83
Show file tree
Hide file tree
Showing 17 changed files with 502 additions and 57 deletions.
54 changes: 54 additions & 0 deletions packages/web/src/@types/navigate.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type InterUseCaseState<T extends Record<string, unknown> = any> = {
// key に設定先の画面項目を設定
// useLocationのstateで管理している項目のみ指定可能
[key in keyof T]: {
// value に設定したい値を設定
// 遷移元の画面項目の値を埋め込みたい場合は、{interUseCasesKey}を設定することで埋め込み可能
// 例) contextに設定されている値を埋め込みたい場合は、{context}を設定する
value: string;
};
};
export type InterUseCase = {
title: string;
description: string;
path: string;
state?: InterUseCaseState;
};

export type ChatPageLocationState = {
content: string;
};

export type EditorialPageLocationState = {
sentence: string;
};

export type GenerateImagePageLocationState = {
content: string;
};

export type GenerateTextPageLocationState = {
information: string;
context: string;
};

export type RagPageLocationState = {
content: stiring;
};

export type SummarizePageLocationState = {
sentence: string;
additionalContext: string;
};

export type TranslatePageLocationState = {
sentence: string;
additionalContext: string;
language: string;
};

export type WebContentPageLocationState = {
url: string;
context: string;
};
4 changes: 4 additions & 0 deletions packages/web/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import { Amplify, I18n } from 'aws-amplify';
import '@aws-amplify/ui-react/styles.css';
import useDrawer from './hooks/useDrawer';
import useConversation from './hooks/useConversation';
import PopupInterUseCasesDemo from './components/PopupInterUseCasesDemo';
import useInterUseCases from './hooks/useInterUseCases';

const ragEnabled: boolean = import.meta.env.VITE_APP_RAG_ENABLED === 'true';
const selfSignUpEnabled: boolean =
Expand Down Expand Up @@ -131,6 +133,7 @@ const App: React.FC = () => {
const { switchOpen: switchDrawer } = useDrawer();
const { pathname } = useLocation();
const { getConversationTitle } = useConversation();
const { isShow } = useInterUseCases();

const label = useMemo(() => {
const chatId = extractChatId(pathname);
Expand Down Expand Up @@ -173,6 +176,7 @@ const App: React.FC = () => {
<div className="w-10" />
</header>

{isShow && <PopupInterUseCasesDemo />}
<div
className="text-aws-font-color screen:h-full overflow-hidden overflow-y-auto"
id="main">
Expand Down
11 changes: 10 additions & 1 deletion packages/web/src/components/ButtonCopy.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
import React, { useCallback, useState } from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import ButtonIcon from './ButtonIcon';
import { BaseProps } from '../@types/common';
import { PiCheck, PiClipboard } from 'react-icons/pi';
import copy from 'copy-to-clipboard';
import useInterUseCases from '../hooks/useInterUseCases';

type Props = BaseProps & {
text: string;
interUseCasesKey?: string;
};

const ButtonCopy: React.FC<Props> = (props) => {
const [showsCheck, setshowsCheck] = useState(false);
const { setCopyTemporary } = useInterUseCases();

useEffect(() => {
if (props.interUseCasesKey) {
setCopyTemporary(props.interUseCasesKey, props.text);
}
}, [props.interUseCasesKey, props.text, setCopyTemporary]);

const copyMessage = useCallback((message: string) => {
copy(message);
Expand Down
89 changes: 89 additions & 0 deletions packages/web/src/components/PopupInterUseCasesDemo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import React, { useState } from 'react';
import { BaseProps } from '../@types/common';
import { PiArrowFatRightFill, PiCaretDown, PiX } from 'react-icons/pi';
import ButtonIcon from './ButtonIcon';
import useInterUseCases from '../hooks/useInterUseCases';

type Props = BaseProps;

const PopupInterUseCasesDemo: React.FC<Props> = () => {
const {
setIsShow,
title,
useCases,
currentIndex,
setCurrentIndex,
navigateUseCase,
} = useInterUseCases();
const [isOpen, setIsOpen] = useState(true);

return (
<div className="fixed top-0 z-50 ml-10 w-11/12 pt-1 lg:left-1/3 lg:w-1/2 lg:p-3">
<div className="border-aws-squid-ink/50 relative rounded border bg-white p-3 shadow-lg">
<div className="flex w-full">
<div
className=" flex w-full cursor-pointer items-center rounded font-bold hover:bg-gray-200"
onClick={() => {
setIsOpen(!isOpen);
}}>
<PiCaretDown
className={`transition ${!isOpen && 'rotate-180'} mr-2`}
/>
{title}
</div>
<ButtonIcon
className={`right-1 top-1 -m-1 `}
onClick={() => {
setIsShow(false);
}}>
<PiX />
</ButtonIcon>
</div>

<div
className={`origin-top transition ${
isOpen ? 'visible mt-3 ' : 'h-0 scale-y-0'
}`}>
<div className=" grid grid-cols-3 gap-2 text-sm">
{useCases.map((usecase, idx) => (
<div
key={idx}
className={`${
idx === currentIndex &&
'border-aws-squid-ink border font-bold'
}
${
idx > currentIndex + 1 || idx < currentIndex - 1
? 'opacity-50 hover:brightness-100'
: 'cursor-pointer hover:brightness-75 '
}
bg-aws-smile relative mx-2 rounded p-1 px-2 text-white shadow `}
onClick={() => {
if (idx > currentIndex + 1 || idx < currentIndex - 1) {
return;
}
setCurrentIndex(idx);
navigateUseCase(idx);
}}>
<div>{usecase.title}</div>
<PiArrowFatRightFill
className={`text-aws-squid-ink absolute -right-5 top-2 ${
useCases.length - 1 === idx && 'invisible'
}`}
/>
</div>
))}
</div>
<div className="mt-2 rounded border p-2 text-xs">
{useCases[currentIndex].description &&
useCases[currentIndex].description
.split('\n')
.map((s, idx) => <div key={idx}>{s}</div>)}
</div>
</div>
</div>
</div>
);
};

export default PopupInterUseCasesDemo;
4 changes: 4 additions & 0 deletions packages/web/src/components/TextEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { ErrorBoundary } from './ErrorBoundary';
import { PiTrash } from 'react-icons/pi';
import HighlightWithinTextarea from 'react-highlight-within-textarea';
import 'draft-js/dist/Draft.css';
import ButtonCopy from './ButtonCopy';

type Props = RowItemProps & {
value: string;
Expand Down Expand Up @@ -48,6 +49,9 @@ const Texteditor: React.FC<Props> = (props) => {
props.onChange(value);
}}
/>
<div className="flex w-full justify-end">
<ButtonCopy text={props.value} />
</div>
</ErrorBoundary>
</div>
<div className="mb-2 rounded border border-black/30 p-1.5 outline-none lg:ml-2">
Expand Down
111 changes: 111 additions & 0 deletions packages/web/src/hooks/useInterUseCases.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { produce } from 'immer';
import { useNavigate } from 'react-router-dom';
import { create } from 'zustand';
import { InterUseCase } from '../@types/navigate';

const useInterUseCasesState = create<{
isShow: boolean;
setIsShow: (b: boolean) => void;
title: string;
useCases: InterUseCase[];
setUseCases: (title: string, usecases: InterUseCase[]) => void;
currentIndex: number;
setCurrentIndex: (n: number) => void;
copyTemporary: {
[key: string]: string;
};
setCopyTemporary: (key: string, value: string) => void;
}>((set, get) => {
return {
isShow: false,
setIsShow: (b) => {
set(() => ({
isShow: b,
}));
},
title: '',
useCases: [],
setUseCases: (title, useCases) => {
set(() => ({
title,
useCases,
}));
},
currentIndex: -1,
setCurrentIndex: (n: number) => {
set(() => ({
currentIndex: n,
}));
},
copyTemporary: {},
setCopyTemporary: (key, value) => {
const tmp = produce(get().copyTemporary, (draft) => {
draft[key] = value;
});
set(() => ({
copyTemporary: tmp,
}));
},
};
});

const useInterUseCases = () => {
const navigate = useNavigate();
const {
isShow,
setIsShow,
title,
useCases,
setUseCases,
currentIndex,
setCurrentIndex,
copyTemporary,
setCopyTemporary,
} = useInterUseCasesState();

const navigateUseCase_ = (usecase: InterUseCase) => {
const state: Record<string, string> = {};

Object.entries(usecase.state ?? {}).forEach(([key, { value }]) => {
let replacedValue = value;

// 遷移元の画面項目の値を埋め込む処理
// value 内の{}で囲われたキー名を取得し、copyTemporary から当該のキー名の値を取得して、置換する
// 例) {context} が設定されている場合は、copyTemporary から context の値を取得し、{context} をその値で置換する。
const matches = value.match(/\{(.+?)\}/g);
matches?.forEach((m) => {
replacedValue = replacedValue.replace(
m,
copyTemporary[m.replace(/({|})/g, '')]
);
});

state[key] = replacedValue;
});

navigate(usecase.path, {
state,
});
};

return {
isShow,
setIsShow,
title,
useCases,
setUseCases,
currentIndex,
setCurrentIndex,
setCopyTemporary,
navigateUseCase: (idx: number) => {
navigateUseCase_(useCases[idx]);
},
init: (title: string, usecasesList: InterUseCase[]) => {
setCurrentIndex(0);
setUseCases(title, usecasesList);
navigateUseCase_(usecasesList[0]);
},
};
};

export default useInterUseCases;
5 changes: 3 additions & 2 deletions packages/web/src/pages/ChatPage.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useLocation, useParams } from 'react-router-dom';
import { Location, useLocation, useParams } from 'react-router-dom';
import InputChatContent from '../components/InputChatContent';
import useChat from '../hooks/useChat';
import useConversation from '../hooks/useConversation';
Expand All @@ -8,6 +8,7 @@ import PromptList from '../components/PromptList';
import useScroll from '../hooks/useScroll';
import { create } from 'zustand';
import { ReactComponent as BedrockIcon } from '../assets/bedrock.svg';
import { ChatPageLocationState } from '../@types/navigate';

type StateType = {
content: string;
Expand All @@ -27,7 +28,7 @@ const useChatPageState = create<StateType>((set) => {

const ChatPage: React.FC = () => {
const { content, setContent } = useChatPageState();
const { state, pathname } = useLocation();
const { state, pathname } = useLocation() as Location<ChatPageLocationState>;
const { chatId } = useParams();

const {
Expand Down
5 changes: 3 additions & 2 deletions packages/web/src/pages/EditorialPage.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useCallback, useEffect, useMemo } from 'react';
import { useLocation } from 'react-router-dom';
import { Location, useLocation } from 'react-router-dom';
import Card from '../components/Card';
import Button from '../components/Button';
import Textarea from '../components/Textarea';
Expand All @@ -10,6 +10,7 @@ import Texteditor from '../components/TextEditor';
import { DocumentComment } from 'generative-ai-use-cases-jp';
import debounce from 'lodash.debounce';
import { editorialPrompt } from '../prompts';
import { EditorialPageLocationState } from '../@types/navigate';

const REGEX_BRACKET = /\{(?:[^{}])*\}/g;
const REGEX_ZENKAKU =
Expand Down Expand Up @@ -75,7 +76,7 @@ const EditorialPage: React.FC = () => {
clear,
} = useEditorialPageState();

const { state } = useLocation();
const { state } = useLocation() as Location<EditorialPageLocationState>;
const { pathname } = useLocation();
const { loading, messages, postChat, clear: clearChat } = useChat(pathname);

Expand Down
6 changes: 4 additions & 2 deletions packages/web/src/pages/GenerateImagePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ import SketchPad from '../components/SketchPad';
import ModalDialog from '../components/ModalDialog';
import { produce } from 'immer';
import Help from '../components/Help';
import { useLocation } from 'react-router-dom';
import { Location, useLocation } from 'react-router-dom';
import useChat from '../hooks/useChat';
import Base64Image from '../components/Base64Image';
import { AxiosError } from 'axios';
import { GenerateImagePageLocationState } from '../@types/navigate';

const MAX_SAMPLE = 7;

Expand Down Expand Up @@ -212,7 +213,8 @@ const GenerateImagePage: React.FC = () => {
clear,
} = useGenerateImagePageState();

const { pathname, state } = useLocation();
const { pathname, state } =
useLocation() as Location<GenerateImagePageLocationState>;
const { generate } = useImage();
const { loading: loadingChat, clear: clearChat } = useChat(pathname);

Expand Down
Loading

0 comments on commit a417f83

Please sign in to comment.