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: support voice input(alone with starting the migration to tailwind css) #4351

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 1 addition & 6 deletions app/components/chat.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
box-shadow: var(--card-shadow);
transition: width ease 0.3s;
align-items: center;
height: 16px;
height: 24px;
width: var(--icon-width);
overflow: hidden;

Expand All @@ -68,7 +68,6 @@

.text {
white-space: nowrap;
padding-left: 5px;
opacity: 0;
transform: translateX(-5px);
transition: all ease 0.3s;
Expand Down Expand Up @@ -610,10 +609,6 @@
.chat-input-send {
background-color: var(--primary);
color: white;

position: absolute;
right: 30px;
bottom: 32px;
}

@media only screen and (max-width: 600px) {
Expand Down
51 changes: 28 additions & 23 deletions app/components/chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ import { ExportMessageModal } from "./exporter";
import { getClientConfig } from "../config/client";
import { useAllModels } from "../utils/hooks";
import { MultimodalContent } from "../client/api";

import SpeechRecorder from "./chat/speechRecorder";
const Markdown = dynamic(async () => (await import("./markdown")).Markdown, {
loading: () => <LoadingIcon />,
});
Expand Down Expand Up @@ -347,7 +347,7 @@ function ChatAction(props: {
full: 16,
icon: 16,
});

const [isActive, setIsActive] = useState(false);
function updateWidth() {
if (!iconRef.current || !textRef.current) return;
const getWidth = (dom: HTMLDivElement) => dom.getBoundingClientRect().width;
Expand All @@ -361,25 +361,22 @@ function ChatAction(props: {

return (
<div
className={`${styles["chat-input-action"]} clickable`}
className={`${styles["chat-input-action"]} clickable group`}
onClick={() => {
props.onClick();
setTimeout(updateWidth, 1);
}}
onMouseEnter={updateWidth}
onTouchStart={updateWidth}
style={
{
"--icon-width": `${width.icon}px`,
"--full-width": `${width.full}px`,
} as React.CSSProperties
}
>
<div ref={iconRef} className={styles["icon"]}>
{props.icon}
</div>
<div className={styles["text"]} ref={textRef}>
{props.text}
<div className="flex">
<div ref={iconRef} className={styles["icon"]}>
{props.icon}
</div>
<div
className={`${styles["text"]} transition-all duration-1000 w-0 group-hover:w-[60px]`}
ref={textRef}
>
{props.text}
</div>
</div>
</div>
);
Expand Down Expand Up @@ -424,6 +421,7 @@ export function ChatActions(props: {
showPromptModal: () => void;
scrollToBottom: () => void;
showPromptHints: () => void;
setUserInput: (text: string) => void;
hitBottom: boolean;
uploading: boolean;
}) {
Expand Down Expand Up @@ -1462,6 +1460,7 @@ function _Chat() {
scrollToBottom={scrollToBottom}
hitBottom={hitBottom}
uploading={uploading}
setUserInput={setUserInput}
showPromptHints={() => {
// Click again to close
if (promptHints.length > 0) {
Expand Down Expand Up @@ -1522,13 +1521,19 @@ function _Chat() {
})}
</div>
)}
<IconButton
icon={<SendWhiteIcon />}
text={Locale.Chat.Send}
className={styles["chat-input-send"]}
type="primary"
onClick={() => doSubmit(userInput)}
/>
<div className="flex gap-2 absolute left-[30px] bottom-[32px]">
<SpeechRecorder textUpdater={setUserInput}></SpeechRecorder>
</div>

<div className="flex gap-2 absolute right-[30px] bottom-[32px]">
<IconButton
icon={<SendWhiteIcon />}
text={Locale.Chat.Send}
className={styles["chat-input-send"]}
type="primary"
onClick={() => doSubmit(userInput)}
/>
</div>
</label>
</div>

Expand Down
64 changes: 64 additions & 0 deletions app/components/chat/speechRecorder.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import React, { useState, useEffect } from "react";
import VoiceIcon from "@/app/icons/voice.svg";
import { getLang, formatLang } from "@/app/locales";
type SpeechRecognitionType =
| typeof window.SpeechRecognition
| typeof window.webkitSpeechRecognition;

export default function SpeechRecorder({
textUpdater,
onStop,
}: {
textUpdater: (text: string) => void;
onStop?: () => void;
}) {
const [speechRecognition, setSpeechRecognition] =
useState<SpeechRecognitionType | null>(null);
const [isRecording, setIsRecording] = useState(false);
useEffect(() => {
if ("SpeechRecognition" in window) {
setSpeechRecognition(new (window as any).SpeechRecognition());
} else if ("webkitSpeechRecognition" in window) {
setSpeechRecognition(new (window as any).webkitSpeechRecognition());
}
}, []);
return (
<>
{speechRecognition && (
<div>
<button
onClick={() => {
if (!isRecording && speechRecognition) {
speechRecognition.continuous = true;
speechRecognition.lang = formatLang(getLang());
console.log(speechRecognition.lang);
speechRecognition.interimResults = true;
speechRecognition.start();
speechRecognition.onresult = function (event: any) {
console.log(event);
var transcript = event.results[0][0].transcript;
console.log(transcript);
textUpdater(transcript);
};
setIsRecording(true);
} else {
speechRecognition.stop();
setIsRecording(false);
}
}}
>
{isRecording ? (
<button className="p-2 rounded-full bg-blue-500 hover:bg-blue-600 ring-4 ring-blue-200 transition animate-pulse">
<VoiceIcon fill={"white"} />
</button>
) : (
<button className="p-2 rounded-full bg-zinc-100 hover:bg-zinc-200 transition">
<VoiceIcon fill={"#8282A5"} />
</button>
)}
</button>
</div>
)}
</>
);
}
11 changes: 11 additions & 0 deletions app/icons/voice.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
29 changes: 29 additions & 0 deletions app/locales/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,28 @@ export const ALL_LANG_OPTIONS: Record<Lang, string> = {
sk: "Slovensky",
};

const LANG_CODE_MAPPING = {
cn: "zh-CN",
en: "en-US",
tw: "zh-TW",
pt: "pt-BR",
jp: "ja-JP",
ko: "ko-KR",
id: "id-ID",
fr: "fr-FR",
es: "es-ES",
it: "it-IT",
tr: "tr-TR",
de: "de-DE",
vi: "vi-VN",
ru: "ru-RU",
cs: "cs-CZ",
no: "nb-NO",
ar: "ar-SA",
bn: "bn-BD",
sk: "sk-SK",
};

const LANG_KEY = "lang";
const DEFAULT_LANG = "en";

Expand All @@ -81,6 +103,13 @@ merge(fallbackLang, targetLang);

export default fallbackLang as LocaleType;

export const formatLang = (languageCode: string) => {
return (
LANG_CODE_MAPPING[languageCode as keyof typeof LANG_CODE_MAPPING] ||
languageCode
);
};

function getItem(key: string) {
try {
return localStorage.getItem(key);
Expand Down
3 changes: 3 additions & 0 deletions app/styles/globals.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@import "./animation.scss";
@import "./window.scss";

Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,19 +50,22 @@
"@types/react-dom": "^18.2.7",
"@types/react-katex": "^3.0.0",
"@types/spark-md5": "^3.0.4",
"autoprefixer": "^10.4.18",
"cross-env": "^7.0.3",
"eslint": "^8.49.0",
"eslint-config-next": "13.4.19",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-prettier": "^4.2.1",
"husky": "^8.0.0",
"lint-staged": "^13.2.2",
"postcss": "^8.4.35",
"prettier": "^3.0.2",
"tailwindcss": "^3.4.1",
"typescript": "5.2.2",
"webpack": "^5.88.1"
},
"resolutions": {
"lint-staged/yaml": "^2.2.2"
},
}
"packageManager": "[email protected]"
}
6 changes: 6 additions & 0 deletions postcss.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
15 changes: 15 additions & 0 deletions tailwind.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./app/**/*.{js,ts,jsx,tsx,mdx}",
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}",

// Or if using `src` directory:
"./src/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
extend: {},
},
plugins: [],
}
8 changes: 7 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@
"@/*": ["./*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "app/calcTextareaHeight.ts"],
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
"app/calcTextareaHeight.ts"
],
"exclude": ["node_modules"]
}