diff --git a/client/web/src/plugin/common/reg.ts b/client/web/src/plugin/common/reg.ts
index 3302010bdbd..c8b95e5b862 100644
--- a/client/web/src/plugin/common/reg.ts
+++ b/client/web/src/plugin/common/reg.ts
@@ -219,6 +219,15 @@ export const [pluginRootRoute, regPluginRootRoute] = buildRegList<{
component: React.ComponentType;
}>();
+/**
+ * 注册独立面板路由
+ */
+export const [pluginPanelRoute, regPluginPanelRoute] = buildRegList<{
+ name: string;
+ path: string;
+ component: React.ComponentType;
+}>();
+
export interface BasePluginPanelActionProps {
/**
* 唯一标识
diff --git a/client/web/src/routes/Panel/index.tsx b/client/web/src/routes/Panel/index.tsx
index 7ed1987c52e..a073f90fb50 100644
--- a/client/web/src/routes/Panel/index.tsx
+++ b/client/web/src/routes/Panel/index.tsx
@@ -9,6 +9,7 @@ import { GroupDetail } from '@/components/modals/GroupDetail';
import { useParams } from 'react-router';
import { NotFound } from '@/components/NotFound';
import { Group } from '../Main/Content/Group';
+import { pluginPanelRoute } from '@/plugin/common';
const GroupDetailRoute = React.memo(() => {
const { groupId } = useParams<{ groupId: string }>();
@@ -39,6 +40,21 @@ const PanelRoute: React.FC = React.memo(() => {
/>
} />
+
+ {pluginPanelRoute.map((r, i) => (
+
+ ))}
+
+ }
+ />
+
diff --git a/client/web/src/utils/window-helper.ts b/client/web/src/utils/window-helper.ts
index dcd8829f74d..98623f65a17 100644
--- a/client/web/src/utils/window-helper.ts
+++ b/client/web/src/utils/window-helper.ts
@@ -72,7 +72,7 @@ class PanelWindowManager {
return this.openedPanelWindows[url];
}
- const win = openInNewWindow(url);
+ const win = openInNewWindow(url, options);
if (!win) {
throw new Error('Create window failed');
}
diff --git a/client/web/tailchat.d.ts b/client/web/tailchat.d.ts
index a0332ea687e..abeca8663ad 100644
--- a/client/web/tailchat.d.ts
+++ b/client/web/tailchat.d.ts
@@ -100,6 +100,8 @@ declare module '@capital/common' {
export const postMessageEvent: any;
+ export const panelWindowManager: any;
+
export const getServiceUrl: () => string;
export const getCachedUserInfo: (
@@ -192,6 +194,10 @@ declare module '@capital/common' {
export const isDevelopment: boolean;
+ export const setWebviewKernel: any;
+
+ export const resetWebviewKernel: any;
+
export const navigate: any;
export const useLocation: any;
@@ -306,6 +312,10 @@ declare module '@capital/common' {
export const regPluginRootRoute: any;
+ export const pluginPanelRoute: any;
+
+ export const regPluginPanelRoute: any;
+
export const pluginPanelActions: any;
export const regPluginPanelAction: (
@@ -384,6 +394,10 @@ declare module '@capital/common' {
export const regLoginAction: any;
+ export const pluginChatInputPasteHandler: any;
+
+ export const regChatInputPasteHandler: any;
+
export const useGroupIdContext: () => string;
export const useGroupPanelContext: () => {
@@ -607,4 +621,8 @@ declare module '@capital/component' {
export const NotFound: any;
export const withKeepAliveOverlay: any;
+
+ export const AvatarUploader: any;
+
+ export const ImageUploader: any;
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index bf14b3a4b6e..0ae83f69f7f 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -2047,6 +2047,9 @@ importers:
react:
specifier: 18.2.0
version: 18.2.0
+ react-router:
+ specifier: ^6.8.1
+ version: 6.11.0(react@18.2.0)
styled-components:
specifier: ^5.3.6
version: 5.3.10(react-dom@18.2.0)(react-is@18.2.0)(react@18.2.0)
@@ -9605,7 +9608,6 @@ packages:
/@remix-run/router@1.6.0:
resolution: {integrity: sha512-N13NRw3T2+6Xi9J//3CGLsK2OqC8NMme3d/YX+nh05K9YHWGcv8DycHJrqGScSP4T75o8IN6nqIMhVFU8ohg8w==}
engines: {node: '>=14'}
- dev: false
/@rollup/plugin-babel@5.3.1(@babel/core@7.21.0)(rollup@2.79.1):
resolution: {integrity: sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==}
@@ -29372,7 +29374,6 @@ packages:
dependencies:
'@remix-run/router': 1.6.0
react: 18.2.0
- dev: false
/react-side-effect@2.1.2(react@18.2.0):
resolution: {integrity: sha512-PVjOcvVOyIILrYoyGEpDN3vmYNLdy1CajSFNt4TDsVQC5KpTijDvWVoR+/7Rz2xT978D8/ZtFceXxzsPwZEDvw==}
diff --git a/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/package.json b/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/package.json
index 061d036dae9..94de5fa2f17 100644
--- a/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/package.json
+++ b/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/package.json
@@ -19,6 +19,7 @@
"@types/lodash": "^4.14.196",
"@types/styled-components": "^5.1.26",
"react": "18.2.0",
+ "react-router": "^6.8.1",
"styled-components": "^5.3.6"
}
}
diff --git a/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/components/LivekitView.tsx b/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/components/LivekitView.tsx
index 1ee4c5d0934..a6311d54d66 100644
--- a/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/components/LivekitView.tsx
+++ b/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/components/LivekitView.tsx
@@ -12,50 +12,23 @@ interface LivekitViewProps {
url: string;
}
const _LivekitView: React.FC = React.memo((props) => {
- const [preJoinChoices, setPreJoinChoices] = useState<
- LocalUserChoices | undefined
- >(undefined);
const { setActive, setDeactive } = useLivekitState();
- const handleError = useEvent((err: Error) => {
- showErrorToasts('error while setting up prejoin');
- console.log('error while setting up prejoin', err);
- });
-
- const handleJoin = useEvent(async (userChoices: LocalUserChoices) => {
+ const handleJoin = useEvent(async () => {
await setDeactive(); // 先退出之前的房间
-
- setPreJoinChoices(userChoices);
setActive(props.url);
});
const handleLeave = useEvent(() => {
- setPreJoinChoices(undefined);
setDeactive();
});
return (
-
- {props.roomName && preJoinChoices ? (
-
- ) : (
-
- )}
-
+
);
});
_LivekitView.displayName = 'LivekitView';
@@ -63,3 +36,62 @@ _LivekitView.displayName = 'LivekitView';
export const LivekitView = withKeepAliveOverlay(_LivekitView, {
cacheId: (props) => props.url,
});
+
+interface PureLivekitViewProps {
+ roomName: string;
+ onJoin?: () => Promise;
+ onLeave?: () => void;
+}
+/**
+ * Without context just for meeting view
+ */
+export const PureLivekitView: React.FC = React.memo(
+ (props) => {
+ const [preJoinChoices, setPreJoinChoices] = useState<
+ LocalUserChoices | undefined
+ >(undefined);
+
+ const handleError = useEvent((err: Error) => {
+ showErrorToasts('error while setting up prejoin');
+ console.log('error while setting up prejoin', err);
+ });
+
+ const handleJoin = useEvent(async (userChoices: LocalUserChoices) => {
+ await props.onJoin?.();
+
+ setPreJoinChoices(userChoices);
+ });
+
+ const handleLeave = useEvent(() => {
+ props.onLeave?.();
+ setPreJoinChoices(undefined);
+ });
+
+ return (
+
+ {props.roomName && preJoinChoices ? (
+
+ ) : (
+
+ )}
+
+ );
+ }
+);
+PureLivekitView.displayName = 'PureLivekitView';
diff --git a/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/components/lib/PreJoinView.tsx b/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/components/lib/PreJoinView.tsx
index 9e6d17399d4..8d04eae85db 100644
--- a/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/components/lib/PreJoinView.tsx
+++ b/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/components/lib/PreJoinView.tsx
@@ -42,7 +42,7 @@ const DEFAULT_USER_CHOICES = {
/** @public */
export type PreJoinProps = Omit<
React.HTMLAttributes,
- 'onSubmit'
+ 'onSubmit' | 'onError'
> & {
roomName: string;
/** This function is called with the `LocalUserChoices` if validation is passed. */
diff --git a/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/index.tsx b/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/index.tsx
index 3ea9fc4eb19..759cc51849e 100644
--- a/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/index.tsx
+++ b/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/index.tsx
@@ -2,6 +2,9 @@ import {
regCustomPanel,
regGroupPanel,
regGroupPanelBadge,
+ regPluginPanelAction,
+ regPluginPanelRoute,
+ panelWindowManager,
} from '@capital/common';
import { Loadable } from '@capital/component';
import { useIconIsShow } from './navbar/useIconIsShow';
@@ -11,13 +14,22 @@ const PLUGIN_ID = 'com.msgbyte.livekit';
console.log(`Plugin ${PLUGIN_ID} is loaded`);
+const LivekitPanel = Loadable(() => import('./group/LivekitPanel'), {
+ componentName: `${PLUGIN_ID}:LivekitPanel`,
+});
+
+const LivekitMeetingPanel = Loadable(
+ () => import('./panel/LivekitMeetingPanel'),
+ {
+ componentName: `${PLUGIN_ID}:LivekitMeetingPanel`,
+ }
+);
+
regGroupPanel({
name: `${PLUGIN_ID}/livekitPanel`,
label: Translate.voiceChannel,
provider: PLUGIN_ID,
- render: Loadable(() => import('./group/LivekitPanel'), {
- componentName: `${PLUGIN_ID}:LivekitPanel`,
- }),
+ render: LivekitPanel,
});
regGroupPanelBadge({
@@ -45,3 +57,26 @@ regCustomPanel({
),
useIsShow: useIconIsShow,
});
+
+regPluginPanelRoute({
+ name: `${PLUGIN_ID}/livekitPanel`,
+ path: `/${PLUGIN_ID}/meeting/:meetingId`,
+ component: LivekitMeetingPanel,
+});
+
+// 发起私信会议
+regPluginPanelAction({
+ name: `${PLUGIN_ID}/groupAction`,
+ label: Translate.startCall,
+ position: 'dm',
+ icon: 'mdi:video-box',
+ onClick: ({ converseId }) => {
+ panelWindowManager.open(
+ `/panel/plugin/${PLUGIN_ID}/meeting/${converseId}`,
+ {
+ width: 1280,
+ height: 768,
+ }
+ );
+ },
+});
diff --git a/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/panel/LivekitMeetingPanel.tsx b/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/panel/LivekitMeetingPanel.tsx
new file mode 100644
index 00000000000..33cd0c923f6
--- /dev/null
+++ b/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/panel/LivekitMeetingPanel.tsx
@@ -0,0 +1,16 @@
+import React from 'react';
+import { PureLivekitView } from '../components/LivekitView';
+import { useParams } from 'react-router';
+
+const LivekitMeetingPanel: React.FC = React.memo(() => {
+ const { meetingId } = useParams<{ meetingId: string }>();
+
+ return (
+
+ );
+});
+LivekitMeetingPanel.displayName = 'LivekitMeetingPanel';
+
+export default LivekitMeetingPanel;
diff --git a/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/translate.ts b/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/translate.ts
index e3d26d797f4..996705f8331 100644
--- a/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/translate.ts
+++ b/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/translate.ts
@@ -65,4 +65,8 @@ export const Translate = {
'zh-CN': '当前浏览器不支持视图全屏',
'en-US': 'Current browser does not support DOM full screen',
}),
+ startCall: localTrans({
+ 'zh-CN': '发起/加入通话',
+ 'en-US': 'Start/Join Call',
+ }),
};
diff --git a/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/types/tailchat.d.ts b/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/types/tailchat.d.ts
index 0a5ac1de0df..abeca8663ad 100644
--- a/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/types/tailchat.d.ts
+++ b/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/types/tailchat.d.ts
@@ -100,6 +100,8 @@ declare module '@capital/common' {
export const postMessageEvent: any;
+ export const panelWindowManager: any;
+
export const getServiceUrl: () => string;
export const getCachedUserInfo: (
@@ -153,7 +155,9 @@ declare module '@capital/common' {
deps?: React.DependencyList
) => [{ loading: boolean; value?: any }, T];
- export const useEvent: any;
+ export const useEvent: any>(
+ fn: T
+ ) => T;
export const uploadFile: any;
@@ -184,6 +188,16 @@ declare module '@capital/common' {
export const useWatch: any;
+ export const parseUrlStr: (originUrl: string) => string;
+
+ export const useUpdateRef: (state: T) => React.MutableRefObject;
+
+ export const isDevelopment: boolean;
+
+ export const setWebviewKernel: any;
+
+ export const resetWebviewKernel: any;
+
export const navigate: any;
export const useLocation: any;
@@ -202,7 +216,12 @@ declare module '@capital/common' {
export const getTextColorHex: any;
- export const useCurrentUserInfo: any;
+ export const useCurrentUserInfo: () => {
+ email?: string;
+ nickname?: string;
+ discriminator: string;
+ avatar?: string;
+ };
export const createPluginRequest: (pluginName: string) => {
get: (actionName: string, config?: any) => Promise;
@@ -211,6 +230,8 @@ declare module '@capital/common' {
export const postRequest: any;
+ export const BaseCardPayload: any;
+
export const pluginCustomPanel: any;
export const regCustomPanel: (info: {
@@ -221,13 +242,15 @@ declare module '@capital/common' {
| 'navbar-more'
| 'navbar-group'
| 'navbar-personal';
- icon: string;
+ icon: string | React.ComponentType;
name: string;
label: string;
render: React.ComponentType;
/**
* hooks determine whether to render
*
+ * Only available in position: `navbar-more` | `navbar-group` | `navbar-personal`
+ *
* @default
* () => true
*/
@@ -289,6 +312,10 @@ declare module '@capital/common' {
export const regPluginRootRoute: any;
+ export const pluginPanelRoute: any;
+
+ export const regPluginPanelRoute: any;
+
export const pluginPanelActions: any;
export const regPluginPanelAction: (
@@ -355,6 +382,10 @@ declare module '@capital/common' {
export const regPluginInboxItemMap: any;
+ export const pluginCardItemMap: any;
+
+ export const regPluginCardItem: any;
+
export const pluginGroupConfigItems: any;
export const regPluginGroupConfigItem: any;
@@ -363,6 +394,10 @@ declare module '@capital/common' {
export const regLoginAction: any;
+ export const pluginChatInputPasteHandler: any;
+
+ export const regChatInputPasteHandler: any;
+
export const useGroupIdContext: () => string;
export const useGroupPanelContext: () => {
@@ -455,6 +490,8 @@ declare module '@capital/component' {
export const useChatInputActionContext: any;
+ export const GroupPanelContainer: any;
+
export const GroupExtraDataPanel: any;
export const Image: any;
@@ -581,5 +618,11 @@ declare module '@capital/component' {
export const NoData: any;
- export const GroupPanelContainer: any;
+ export const NotFound: any;
+
+ export const withKeepAliveOverlay: any;
+
+ export const AvatarUploader: any;
+
+ export const ImageUploader: any;
}
diff --git a/server/services/core/gateway.service.ts b/server/services/core/gateway.service.ts
index 2f2144f70b7..c17072945c1 100644
--- a/server/services/core/gateway.service.ts
+++ b/server/services/core/gateway.service.ts
@@ -318,7 +318,7 @@ export default class ApiService extends TcService {
use: [
serve('public', {
cacheControl: true,
- maxAge: '1d', // 1 day for public file
+ maxAge: '1d', // 1 day for public file, include plugins
setHeaders(res: ServerResponse, path: string, stat: any) {
res.setHeader('Access-Control-Allow-Origin', '*'); // 允许跨域
},