Skip to content

Commit

Permalink
Merge pull request render-examples#35 from aivy-tokyo/feat/bug-fix-0901
Browse files Browse the repository at this point in the history
Feat/bug fix 0901
  • Loading branch information
Nobuhiko Futagami authored Sep 2, 2023
2 parents 09b24a0 + d5933bf commit 146a976
Show file tree
Hide file tree
Showing 36 changed files with 1,672 additions and 889 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,6 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts

# Sentry Auth Token
.sentryclirc
5 changes: 3 additions & 2 deletions components/AuthGuard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useRouter } from "next/router";
import { PropsWithChildren, useEffect, useState } from "react";
import { userIdAtom, userInfoAtom } from "../utils/atoms";
import { fetchUserId } from "../features/fetchUserId";
import * as Sentry from "@sentry/nextjs";
import { UserInfo } from "@/entities/UserInfo";

export const AuthGuard: React.FC<PropsWithChildren> = ({ children }) => {
Expand All @@ -24,7 +25,7 @@ export const AuthGuard: React.FC<PropsWithChildren> = ({ children }) => {
fetchUserId().then((userId) => {
setUserId(userId);
}).catch((error) => {
console.error("Error:", error);
Sentry.captureException(error);
});
}

Expand Down Expand Up @@ -56,7 +57,7 @@ export const AuthGuard: React.FC<PropsWithChildren> = ({ children }) => {
setUserInfo(userInfo);
})
.catch((error) => {
console.error("Error:", error);
Sentry.captureException(error);
});
}, [router, setUserInfo, userId]);

Expand Down
6 changes: 5 additions & 1 deletion components/BottomUi.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ type Props = {
handleChangeUserMessage: (e: ChangeEvent<HTMLInputElement>) => void;
sendChat: (message: string) => void;
setIsMicRecording: Dispatch<SetStateAction<boolean>>;
roleOfAi?: string;
roleOfUser?: string;
};

const BottomUi = ({
Expand All @@ -39,6 +41,8 @@ const BottomUi = ({
isMicRecording,
sendChat,
setIsMicRecording,
roleOfAi,
roleOfUser,
}: Props) => {
const chatLogs = useAtomValue(chatLogAtom);
const [isChatLogExpanded, setIsChatLogExpanded] = useState<boolean>(false);
Expand Down Expand Up @@ -80,7 +84,7 @@ const BottomUi = ({
: "py-1 mask-top-fadeout top-0 absolute justify-end"
}`}
>
<SpeechTextArea chatLogs={chatLogs} />
<SpeechTextArea chatLogs={chatLogs} roleOfAi={roleOfAi} roleOfUser={roleOfUser}/>
</div>
</div>

Expand Down
10 changes: 8 additions & 2 deletions components/ChatHint.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import { Situation } from "@/features/situations";

type ChatHintProps = {
description: string;
hint: string;
situation: Situation;
};
export const ChatHint: React.FC<ChatHintProps> = ({ description, hint }) => (
export const ChatHint: React.FC<ChatHintProps> = ({ description, hint, situation }) => (
<div className="hint-container opacity-80 w-screen h-screen -z-0 top-12 flex fixed">
{/* ヒント領域のコンテナ。画面いっぱいに広げて、中のヒント領域をflex/items-centerで画面の中央に配置(他のUIをさわれなくならないよう微調整済み) */}
<div className="w-full flex items-center max-w-[900px] justify-center m-auto">
<div className="flex flex-col justify-center items-center w-[95%] h-[120px] bg-black text-white rounded-3xl m-auto relative -top-8 px-5 py-3 overflow-y-scroll">
<div className="flex flex-col justify-center items-center w-[95%] h-[150px] bg-black text-white rounded-3xl m-auto relative -top-8 px-5 py-3 overflow-y-scroll">
<h2 className=" font-extrabold text-lg">{situation.title}</h2>
<h3 className=" text-sm">{situation.description}</h3>
<p className="text-sm mb-5">{situation.roleOfUser}としてAILLAと会話してください。</p>
{/* 説明文: ヒントの説明を表示、太字 */}
<p className="text-lg font-bold">{description}</p>
{/* ヒント: ヒントの内容を表示、斜体 */}
Expand Down
67 changes: 44 additions & 23 deletions components/FirstGreeting.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { PropsWithChildren, useContext, useState, useCallback} from "react";
import { PropsWithChildren, useContext, useState, useCallback } from "react";
import { ViewerContext } from "../features/vrmViewer/viewerContext";
import { speakFirstConversation } from "../features/speakFirstConversation";
import { useAtomValue } from "jotai";
import { userInfoAtom, textToSpeechApiTypeAtom } from "../utils/atoms";
import * as Sentry from "@sentry/nextjs";

export const FirstGreeting: React.FC<PropsWithChildren> = ({ children }) => {
const { viewer } = useContext(ViewerContext);
Expand All @@ -13,20 +14,25 @@ export const FirstGreeting: React.FC<PropsWithChildren> = ({ children }) => {
const [startButtonClicked, setStartButtonClicked] = useState<boolean>(false);
// 最初の挨拶をしたかどうかの状態管理
const [firstGreetingDone, setFirstGreetingDone] = useState<boolean>(false);
// 話しているテキストの状態管理
const [currentText, setCurrentText] = useState<string>("");

const greet = useCallback(async () => {
if (viewer.model && !firstGreetingDone) {
try {
await viewer.model.resumeAudio();//MEMO:iOSだとAudioContextのstateが'suspended'になり、音声が再生できないことへの対策。
await viewer.model.resumeAudio(); //MEMO:iOSだとAudioContextのstateが'suspended'になり、音声が再生できないことへの対策。
setStartButtonClicked(true);

await speakFirstConversation({
viewerModel: viewer.model,
userName: userInfo?.name || "",
textToSpeechApiType,
onSpeaking: setCurrentText,
onSpeakingEnd: () => setCurrentText(""),
});
setFirstGreetingDone(true);
} catch (error) {
console.error(error);
Sentry.captureException(error);
}
}
}, [firstGreetingDone, textToSpeechApiType, userInfo?.name, viewer.model]);
Expand All @@ -36,33 +42,48 @@ export const FirstGreeting: React.FC<PropsWithChildren> = ({ children }) => {
setFirstGreetingDone(true);
}, [viewer.model]);

if (!viewer.model) {
return <></>;
}

if (!firstGreetingDone) {
return (
<>
<div
className={`
fixed top-0 flex justify-center items-center h-screen w-full bg-opacity-60
fixed top-0 flex flex-col justify-end items-center h-screen w-full py-10 bg-opacity-60
${startButtonClicked ? "bg-transparent" : "bg-black"}
`}
>
{startButtonClicked ?
(
<button
className="btn btn-secondary is-rounded is-large is-fullwidth"
onClick={() => handleSkipFirstGreeting()}
>
スキップする
</button>
)
:
(
<button
className="btn btn-secondary is-rounded is-large is-fullwidth"
onClick={greet}
>
AILLAと英会話を始めましょう!
</button>
)}
>
<div className="p-10">
{startButtonClicked ? (
currentText && (
<p className="text-white text-center text-xs font-bold bg-black bg-opacity-60 p-3 rounded">
{currentText}
</p>
)
) : (
<p className="text-white text-center text-xs font-bold">
マナーモード設定してる場合は
<br />
音声が発音されません
</p>
)}
</div>
<div className="flex flex-col justify-end items-center">
{startButtonClicked ? (
<button
className="btn btn-primary btn-xs"
onClick={() => handleSkipFirstGreeting()}
>
スキップする
</button>
) : (
<button className="btn btn-primary" onClick={greet}>
AILLAと英会話を始めましょう!
</button>
)}
</div>
</div>
</>
);
Expand Down
5 changes: 5 additions & 0 deletions components/HeaderLabel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { PropsWithChildren } from "react";

export const HeaderLabel: React.FC<PropsWithChildren> = ({ children }) => (
<h2 className="text-sm text-white font-bold mt-5 mb-3">{children}</h2>
);
109 changes: 46 additions & 63 deletions components/SettingContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,36 @@
import React, { ChangeEvent } from 'react';
import { signOut } from 'next-auth/react';
import { useSetAtom } from 'jotai';
import { FaRegTimesCircle } from 'react-icons/fa';
import { avatarPathAtom, backgroundImagePathAtom, textToSpeechApiTypeAtom } from '../utils/atoms';
import { avatars, backgroundImages, textToSpeechApiTypeList } from '../utils/constants';
import { SelectedLanguageType, TextToSpeechApiType } from '../utils/types';
import UserInfo from './UserInfo';
import React, { ChangeEvent } from "react";
import { signOut } from "next-auth/react";
import { useAtom, useSetAtom } from "jotai";
import { MdClose } from "react-icons/md";
import {
avatarPathAtom,
backgroundImagePathAtom,
textToSpeechApiTypeAtom,
} from "../utils/atoms";
import {
avatars,
backgroundImages,
textToSpeechApiTypeList,
} from "../utils/constants";
import { TextToSpeechApiType } from "../utils/types";
import UserInfo from "./UserInfo";
import { HeaderLabel } from "./HeaderLabel";

interface SettingContainerProps {
onClose: () => void;
}

export const SettingContainer: React.FC<SettingContainerProps> = ({ onClose }) => {
const setAvatarPath = useSetAtom(avatarPathAtom);
const setBackgroundImagePath = useSetAtom(backgroundImagePathAtom);
const setTextToSpeechApiType = useSetAtom(textToSpeechApiTypeAtom);

export const SettingContainer: React.FC<SettingContainerProps> = ({
onClose,
}) => {
const [avatarPath, setAvatarPath] = useAtom(avatarPathAtom);
const [backgroundImagePath, setBackgroundImagePath] = useAtom(
backgroundImagePathAtom
);
const [textToSpeechApiType, setTextToSpeechApiType] = useAtom(
textToSpeechApiTypeAtom
);

const handleChangeAvatar = (e: ChangeEvent<HTMLSelectElement>) => {
setAvatarPath(e.target.value);
};
Expand All @@ -28,56 +43,30 @@ export const SettingContainer: React.FC<SettingContainerProps> = ({ onClose }) =
setTextToSpeechApiType(e.target.value as TextToSpeechApiType);
};


return (
<div className="w-screen h-screen opacity-90 bg-black z-30 top-0 fixed text-white overflow-y-scroll">
<div className="flex justify-end fixed top-0 right-0 mt-2 cursor-pointer">
<FaRegTimesCircle
className="text-white text-[34px] "
<div className="w-screen h-screen opacity-90 bg-black z-30 top-0 fixed overflow-y-scroll">
<div className="fixed top-0 right-0 p-3">
<button
className="text-white btn btn-circle btn-outline btn-xs"
onClick={onClose}
/>
>
<MdClose />
</button>
</div>
<div className="flex justify-center">
<div className="flex flex-col items-center w-[300px] py-10">
{/* 言語選択UI ※現状多言語対応がないためコメントアウト */}
{/* <div className="mb-10 w-full">
<h2 className="mb-3 font-bold">言語を選ぶ</h2>
<div className="flex justify-between">
<button
className={`${
selectedLanguage === "English"
? "bg-blue-400"
: "bg-stone-400"
} w-[120px] px-5 py-1 mr-5 text-center rounded-md`}
onClick={() => setSelectedLanguage("English")}
>
English
</button>
<button
className={`${
selectedLanguage === "中文" ? "bg-blue-400" : "bg-stone-400"
} w-[120px] px-5 py-1 text-center rounded-md`}
onClick={() => setSelectedLanguage("中文")}
>
中文
</button>
</div>
</div> */}
{/* ユーザー情報表示・更新UI */}
<div className="w-full">
<div className="w-full pb-3 border-b-2">
<UserInfo />
</div>
{/* アバターの変更UI */}
<div className="w-full">
<h2 className="font-bold mb-5">アバターを選ぶ</h2>
<HeaderLabel>アバターを選ぶ</HeaderLabel>
<select
className="rounded-md p-2 mb-5"
placeholder="選択する"
className="select select-bordered w-full max-w-xs"
onChange={(e) => handleChangeAvatar(e)}
value={avatarPath}
>
<option value="" disabled selected>
選択してください
</option>
{avatars.map((avatar, index) => {
return (
<option key={index} value={avatar.path}>
Expand All @@ -89,15 +78,12 @@ export const SettingContainer: React.FC<SettingContainerProps> = ({ onClose }) =
</div>
{/* 背景の変更UI */}
<div className="w-full">
<h2 className="font-bold mb-5">背景を選ぶ</h2>
<HeaderLabel>背景を選ぶ</HeaderLabel>
<select
className="rounded-md p-2 mb-5"
placeholder="選択する"
className="select select-bordered w-full max-w-xs"
onChange={(e) => handleChangeBackgroundImage(e)}
value={backgroundImagePath}
>
<option value="" disabled selected>
選択してください
</option>
{backgroundImages.map((image, index) => {
return (
<option key={index} value={image.path}>
Expand All @@ -109,15 +95,12 @@ export const SettingContainer: React.FC<SettingContainerProps> = ({ onClose }) =
</div>
{/* 背景の変更UI */}
<div className="w-full">
<h2 className="font-bold mb-5">合成音声の種類を選ぶ</h2>
<HeaderLabel>合成音声の種類を選ぶ</HeaderLabel>
<select
className="rounded-md p-2 mb-5"
placeholder="選択する"
className="select select-bordered w-full max-w-xs"
onChange={(e) => handleChangeVoiceApi(e)}
value={textToSpeechApiType}
>
<option value="" disabled selected>
選択してください
</option>
{textToSpeechApiTypeList.map((textToSpeechApi, index) => {
return (
<option key={index} value={textToSpeechApi.value}>
Expand All @@ -128,7 +111,7 @@ export const SettingContainer: React.FC<SettingContainerProps> = ({ onClose }) =
</select>
</div>
{/* サインアウトボタン */}
<div className="w-full">
<div className="w-full my-10">
<button
className="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded"
onClick={() => signOut()}
Expand Down
23 changes: 18 additions & 5 deletions components/SpeechTextArea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,33 @@ import React from 'react';
interface ChatLog {
role: string;
content: string;

}

interface SpeechTextAreaProps {
chatLogs: ChatLog[];
roleOfAi?: string;
roleOfUser?: string;
}

export const SpeechTextArea: React.FC<SpeechTextAreaProps> = ({ chatLogs }) => {
export const SpeechTextArea: React.FC<SpeechTextAreaProps> = ({ chatLogs,roleOfAi, roleOfUser }) => {
return (
<>
{chatLogs.map((chatLog, id) => (
<div key={id} className={`chat mb-1 flex text-white max-w-[900px] ${chatLog.role === 'user' ? 'chat-end justify-end' : 'chat-start'}`}>
<p className={`${chatLog.role === 'user' ? 'chat-bubble bg-gray-100 text-gray-600' : 'chat-bubble bg-gray-800 text-white'} max-w-[80vw] w-fit`}>
{chatLog.content}
</p>
<div key={id} className='flex flex-col'>
{
roleOfAi && roleOfUser && (
chatLog.role === "user" ?
<p className='self-end'>{roleOfUser}</p>
:
<p>{roleOfAi}</p>
)
}
<div className={`chat mb-3 flex text-white max-w-[900px] ${chatLog.role === 'user' ? 'chat-end justify-end' : 'chat-start'}`}>
<p className={`${chatLog.role === 'user' ? 'chat-bubble bg-gray-100 text-gray-600' : 'chat-bubble bg-gray-800 text-white'} max-w-[80vw] w-fit`}>
{chatLog.content}
</p>
</div>
</div>
))}
</>
Expand Down
Loading

0 comments on commit 146a976

Please sign in to comment.