Skip to content

Commit 4fad926

Browse files
authored
feat: add flat ai speaking teacher features (#2166)
1 parent 2a6c824 commit 4fad926

File tree

104 files changed

+15518
-10377
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

104 files changed

+15518
-10377
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
2+
13
## [2.3.5](https://github.com/netless-io/flat/compare/v2.3.4...v2.3.5) (2024-09-13)
24

35

cspell.config.js

+15
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,21 @@ module.exports = {
183183

184184
// esbuild config
185185
"metafile",
186+
187+
// ai teacher
188+
"SVGAI",
189+
"peppa",
190+
"Peppa",
191+
"Tian",
192+
"RTCAI",
193+
"Secens",
194+
"haimianbaby",
195+
"ironman",
196+
"luotianxiaoyi",
197+
"spongebob",
198+
"sillybear",
199+
"Tasker",
200+
"Secen",
186201
],
187202
flagWords: ["fuck", "bitch", "asshole", "bullshit", "crap", "suck", "wtf"],
188203
dictionaries: [

desktop/main-app/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "flat",
33
"productName": "Flat",
4-
"version": "2.3.5",
4+
"version": "2.3.6",
55
"private": true,
66
"description": "",
77
"homepage": "https://github.com/netless-io/flat",

desktop/renderer-app/src/components/MainPageLayoutWrapper.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export const MainPageLayoutWrapper = observer(function MainPageLayoutWrap({ chil
3030
routeConfig.SmallClassPage,
3131
routeConfig.OneToOnePage,
3232
routeConfig.ReplayPage,
33+
routeConfig.AIPage,
3334
].some(({ path }) => {
3435
return !!matchPath(location.pathname, {
3536
path,

desktop/renderer-app/src/route-config.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { LoginPage } from "@netless/flat-pages/src/LoginPage";
88
import { BigClassPage } from "@netless/flat-pages/src/BigClassPage";
99
import { SmallClassPage } from "@netless/flat-pages/src/SmallClassPage";
1010
import { OneToOnePage } from "@netless/flat-pages/src/OneToOnePage";
11+
import { AIPage } from "@netless/flat-pages/src/AIPage";
1112
import { ModifyPeriodicRoomPage } from "@netless/flat-pages/src/ModifyPeriodicRoomPage";
1213
import { PeriodicRoomDetailPage } from "@netless/flat-pages/src/PeriodicRoomDetailPage";
1314
import { GeneralSettingPage } from "@netless/flat-pages/src/UserSettingPage/GeneralSettingPage";
@@ -32,6 +33,7 @@ export enum RouteNameType {
3233
SmallClassPage = "SmallClassPage",
3334
BigClassPage = "BigClassPage",
3435
OneToOnePage = "OneToOnePage",
36+
AIPage = "AIPage",
3537
RoomDetailPage = "RoomDetailPage",
3638
UserScheduledPage = "UserScheduledPage",
3739
PeriodicRoomDetailPage = "PeriodicRoomDetailPage",
@@ -55,7 +57,8 @@ export enum RouteNameType {
5557
export type ClassRouteName =
5658
| RouteNameType.SmallClassPage
5759
| RouteNameType.OneToOnePage
58-
| RouteNameType.BigClassPage;
60+
| RouteNameType.BigClassPage
61+
| RouteNameType.AIPage;
5962

6063
export const routeConfig = {
6164
[RouteNameType.SplashPage]: {
@@ -83,6 +86,11 @@ export const routeConfig = {
8386
path: "/classroom/OneToOne/:roomUUID/:ownerUUID/",
8487
component: OneToOnePage,
8588
},
89+
[RouteNameType.AIPage]: {
90+
title: "AIPage",
91+
path: "/classroom/AIPage/:roomUUID/:ownerUUID/",
92+
component: AIPage,
93+
},
8694
[RouteNameType.BigClassPage]: {
8795
title: "BigClassPage",
8896
path: "/classroom/BigClass/:roomUUID/:ownerUUID/",

desktop/renderer-app/src/stores/ipc-store.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ export class IPCStore {
5353
}
5454
case routeConfig[RouteNameType.BigClassPage].path:
5555
case routeConfig[RouteNameType.SmallClassPage].path:
56-
case routeConfig[RouteNameType.OneToOnePage].path: {
56+
case routeConfig[RouteNameType.OneToOnePage].path:
57+
case routeConfig[RouteNameType.AIPage].path: {
5758
this.ipcAsyncByMainWindow("set-win-size", {
5859
...constants.PageSize.Class,
5960
autoCenter: true,
@@ -116,6 +117,7 @@ export class IPCStore {
116117
case routeConfig[RouteNameType.BigClassPage].path:
117118
case routeConfig[RouteNameType.SmallClassPage].path:
118119
case routeConfig[RouteNameType.OneToOnePage].path:
120+
case routeConfig[RouteNameType.AIPage].path:
119121
case routeConfig[RouteNameType.ReplayPage].path: {
120122
this.ipcAsyncByMainWindow("intercept-native-window-close", {
121123
intercept: false,
@@ -154,6 +156,7 @@ export class IPCStore {
154156
routeConfig.LoginPage,
155157
routeConfig.BigClassPage,
156158
routeConfig.OneToOnePage,
159+
routeConfig.AIPage,
157160
routeConfig.SmallClassPage,
158161
].some(({ path }) => {
159162
return !!matchPath(pathname, {

desktop/renderer-app/src/utils/hooks/use-url-app-launcher.ts

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export function useURLAppLauncher(): void {
2121
RouteNameType.SmallClassPage,
2222
RouteNameType.OneToOnePage,
2323
RouteNameType.BigClassPage,
24+
RouteNameType.AIPage,
2425
];
2526

2627
for (const name of classPages) {

docs/releases/v2.3.6/en.md

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
2+
## Features
3+
4+
1. Added flat ai speaking teacher features

docs/releases/v2.3.6/zh.md

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
## 新增
2+
1. flat口语老师功能
3+
4+

packages/flat-components/src/components/ChatPanel/ChatMessage/style.less

+1-1
Original file line numberDiff line numberDiff line change
@@ -190,4 +190,4 @@
190190
.chat-user-status.is-teacher {
191191
color: var(--grey-6);
192192
}
193-
}
193+
}

packages/flat-components/src/components/ChatPanel/ChatMessageList/index.tsx

+32-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useEffect, useRef, useState } from "react";
1+
import React, { useEffect, useMemo, useRef, useState } from "react";
22
import { useUpdate } from "react-use";
33
import { Observer, observer } from "mobx-react-lite";
44
import {
@@ -183,3 +183,34 @@ export const ChatMessageList = /* @__PURE__ */ observer<ChatMessageListProps>(
183183
);
184184
},
185185
);
186+
187+
export const ReadOnlyChatMessageList = /* @__PURE__ */ observer<ChatMessageListProps>(
188+
function ReadOnlyChatMessageList({ userUUID, messages, getUserByUUID, generateAvatar }) {
189+
const showMessages = useMemo(() => {
190+
return messages
191+
.reduce((preValue, curValue, curIndex: number) => {
192+
if (curIndex === 0) {
193+
preValue.unshift(curValue);
194+
} else if (preValue[0].timestamp < curValue.timestamp) {
195+
preValue.unshift(curValue);
196+
}
197+
return preValue;
198+
}, [] as ChatMsg[])
199+
.slice(0, 2);
200+
}, [messages]);
201+
return (
202+
<div className="chat-message-list">
203+
{showMessages.map(message => (
204+
<ChatMessage
205+
key={message.uuid}
206+
generateAvatar={generateAvatar}
207+
message={message}
208+
messageUser={getUserByUUID(message.senderID)}
209+
userUUID={userUUID}
210+
onMount={() => void 0}
211+
/>
212+
))}
213+
</div>
214+
);
215+
},
216+
);

packages/flat-components/src/components/ChatPanel/ChatMessages/index.tsx

+17-8
Original file line numberDiff line numberDiff line change
@@ -5,31 +5,40 @@ import chatMessagesDefaultDarkSVG from "./icons/chat-messages-default-dark.svg";
55
import React, { useContext } from "react";
66
import { observer } from "mobx-react-lite";
77
import { ChatTypeBox, ChatTypeBoxProps } from "../ChatTypeBox";
8-
import { ChatMessageList, ChatMessageListProps } from "../ChatMessageList";
8+
import { ChatMessageList, ChatMessageListProps, ReadOnlyChatMessageList } from "../ChatMessageList";
99
import { DarkModeContext } from "../../FlatThemeProvider";
1010

11-
export type ChatMessagesProps = ChatTypeBoxProps & ChatMessageListProps;
11+
export type ChatMessagesProps = ChatTypeBoxProps & ChatMessageListProps & { readOnly?: boolean };
1212

1313
export const ChatMessages = /* @__PURE__ */ observer<ChatMessagesProps>(function ChatMessages({
1414
messages,
15+
readOnly,
1516
...restProps
1617
}) {
1718
const isDark = useContext(DarkModeContext);
18-
1919
return (
2020
<div className="chat-messages-wrap">
2121
<div className="chat-messages">
2222
{messages.length > 0 ? (
2323
<div className="chat-messages-box">
24-
<ChatMessageList messages={messages} {...restProps} />
24+
{readOnly ? (
25+
<ReadOnlyChatMessageList messages={messages} {...restProps} />
26+
) : (
27+
<ChatMessageList messages={messages} {...restProps} />
28+
)}
2529
</div>
2630
) : (
27-
<div className="chat-messages-default">
28-
<img src={isDark ? chatMessagesDefaultDarkSVG : chatMessagesDefaultSVG} />
29-
</div>
31+
(!readOnly && (
32+
<div className="chat-messages-default">
33+
<img
34+
src={isDark ? chatMessagesDefaultDarkSVG : chatMessagesDefaultSVG}
35+
/>
36+
</div>
37+
)) ||
38+
null
3039
)}
3140
</div>
32-
<ChatTypeBox {...restProps} />
41+
{!!restProps.onMessageSend && <ChatTypeBox {...restProps} />}
3342
</div>
3443
);
3544
});

packages/flat-components/src/components/ChatPanel/ChatTypeBox/index.tsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export interface ChatTypeBoxProps {
1111
isCreator: boolean;
1212
isBan: boolean;
1313
onBanChange: () => void;
14-
onMessageSend: (text: string) => Promise<void>;
14+
onMessageSend?: (text: string) => Promise<void>;
1515
}
1616

1717
export const ChatTypeBox = /* @__PURE__ */ observer<ChatTypeBoxProps>(function ChatTypeBox({
@@ -32,6 +32,9 @@ export const ChatTypeBox = /* @__PURE__ */ observer<ChatTypeBoxProps>(function C
3232
if (isSending || trimmedText.length <= 0) {
3333
return;
3434
}
35+
if (!onMessageSend) {
36+
return;
37+
}
3538

3639
updateSending(true);
3740

packages/flat-components/src/components/ChatPanel/index.tsx

+16-11
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import { ChatTabTitle, ChatTabTitleProps } from "./ChatTabTitle";
99
export type ChatPanelProps = {
1010
totalUserCount?: number;
1111
onClickTotalUsersCount?: () => void;
12+
readOnly?: boolean;
13+
cc?: React.ReactNode;
1214
} & ChatTabTitleProps &
1315
Omit<ChatMessagesProps, "visible">;
1416

@@ -17,17 +19,20 @@ export const ChatPanel = /* @__PURE__ */ observer<ChatPanelProps>(function ChatP
1719

1820
return (
1921
<div className="chat-panel">
20-
<div className="chat-panel-header">
21-
<ChatTabTitle>
22-
<span>{t("messages")}</span>
23-
</ChatTabTitle>
24-
{props.totalUserCount && (
25-
<span className="chat-tab-subtitle" onClick={props.onClickTotalUsersCount}>
26-
{t("total-users-count", { count: props.totalUserCount })}
27-
</span>
28-
)}
29-
</div>
30-
<ChatMessages {...props} visible />
22+
{!props.readOnly && (
23+
<div className="chat-panel-header">
24+
<ChatTabTitle>
25+
<span>{t("messages")}</span>
26+
</ChatTabTitle>
27+
{props.totalUserCount && (
28+
<span className="chat-tab-subtitle" onClick={props.onClickTotalUsersCount}>
29+
{t("total-users-count", { count: props.totalUserCount })}
30+
</span>
31+
)}
32+
</div>
33+
)}
34+
<ChatMessages {...props} visible readOnly={props.readOnly} />
35+
{props.cc && <div className="chat-panel-cc">{props.cc}</div>}
3136
</div>
3237
);
3338
});

packages/flat-components/src/components/ChatPanel/style.less

+20-1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,25 @@
4444
.ReactVirtualized__List {
4545
outline: none;
4646
}
47+
48+
.chat-panel-cc {
49+
position: absolute;
50+
bottom: 50px;
51+
right: 4px;
52+
53+
.video-avatar-chat-msg-btn {
54+
width: 40px;
55+
height: 40px;
56+
border-radius: 20px;
57+
border: none;
58+
display: flex;
59+
flex-direction: column;
60+
justify-content: center;
61+
align-items: center;
62+
cursor: pointer;
63+
pointer-events: all;
64+
}
65+
}
4766
}
4867

4968
.chat-panel-header {
@@ -84,4 +103,4 @@
84103
.chat-panel-header {
85104
border-bottom-color: var(--grey-8);
86105
}
87-
}
106+
}

packages/flat-components/src/components/ChatPanel/types.ts

+10-1
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,13 @@ export type ChatMsgUserGuide = {
1818
senderID: string;
1919
};
2020

21-
export type ChatMsg = ChatMsgRoomMessage | ChatMsgNotice | ChatMsgBan | ChatMsgUserGuide;
21+
export type AIChatMsgUser = ChatMsgRoomMessage & {
22+
isFinal: boolean;
23+
};
24+
25+
export type ChatMsg =
26+
| ChatMsgRoomMessage
27+
| ChatMsgNotice
28+
| ChatMsgBan
29+
| ChatMsgUserGuide
30+
| AIChatMsgUser;

0 commit comments

Comments
 (0)