mirror of https://github.com/msgbyte/tailchat
feat: 增加声网插件基本功能集成
parent
2a0695014a
commit
63ee943eec
@ -0,0 +1,14 @@
|
|||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
externalDeps: [
|
||||||
|
'react',
|
||||||
|
'react-router',
|
||||||
|
'axios',
|
||||||
|
'styled-components',
|
||||||
|
'zustand',
|
||||||
|
'zustand/middleware/immer',
|
||||||
|
],
|
||||||
|
pluginRoot: path.resolve(__dirname, './web'),
|
||||||
|
outDir: path.resolve(__dirname, '../../public'),
|
||||||
|
};
|
@ -0,0 +1,20 @@
|
|||||||
|
import { db } from 'tailchat-server-sdk';
|
||||||
|
const { getModelForClass, prop, modelOptions, TimeStamps } = db;
|
||||||
|
|
||||||
|
@modelOptions({
|
||||||
|
options: {
|
||||||
|
customName: 'p_agora',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export class Agora extends TimeStamps implements db.Base {
|
||||||
|
_id: db.Types.ObjectId;
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AgoraDocument = db.DocumentType<Agora>;
|
||||||
|
|
||||||
|
const model = getModelForClass(Agora);
|
||||||
|
|
||||||
|
export type AgoraModel = typeof model;
|
||||||
|
|
||||||
|
export default model;
|
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"name": "tailchat-plugin-agora",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "index.js",
|
||||||
|
"author": "moonrailgun",
|
||||||
|
"description": "为Tailchat增加声网音视频通信功能",
|
||||||
|
"license": "MIT",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"build:web": "ministar buildPlugin all",
|
||||||
|
"build:web:watch": "ministar watchPlugin all"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/react": "18.0.20",
|
||||||
|
"mini-star": "*"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"tailchat-server-sdk": "*"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
import { TcService, TcDbService } from 'tailchat-server-sdk';
|
||||||
|
import type { AgoraDocument, AgoraModel } from '../models/agora';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 声网音视频
|
||||||
|
*
|
||||||
|
* 为Tailchat增加声网音视频通信功能
|
||||||
|
*/
|
||||||
|
interface AgoraService
|
||||||
|
extends TcService,
|
||||||
|
TcDbService<AgoraDocument, AgoraModel> {}
|
||||||
|
class AgoraService extends TcService {
|
||||||
|
get serviceName() {
|
||||||
|
return 'plugin:com.msgbyte.agora';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 声网服务端的appid
|
||||||
|
*/
|
||||||
|
get serverAppId() {
|
||||||
|
return process.env.AGORA_APP_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 声网服务端的app证书
|
||||||
|
*/
|
||||||
|
get serverAppCertificate() {
|
||||||
|
return process.env.AGORA_APP_CERT;
|
||||||
|
}
|
||||||
|
|
||||||
|
onInit() {
|
||||||
|
// this.registerLocalDb(require('../models/agora').default);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AgoraService;
|
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"label": "声网音视频",
|
||||||
|
"name": "com.msgbyte.agora",
|
||||||
|
"url": "{BACKEND}/plugins/com.msgbyte.agora/index.js",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"author": "moonrailgun",
|
||||||
|
"description": "为Tailchat增加声网音视频通信功能",
|
||||||
|
"requireRestart": true
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"name": "@plugins/com.msgbyte.agora",
|
||||||
|
"main": "src/index.tsx",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"description": "为Tailchat增加声网音视频通信功能",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"sync:declaration": "tailchat declaration github"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"agora-rtc-react": "^1.1.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/styled-components": "^5.1.26",
|
||||||
|
"react": "18.2.0",
|
||||||
|
"styled-components": "^5.3.6"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,65 @@
|
|||||||
|
import { IconBtn } from '@capital/component';
|
||||||
|
import type { ICameraVideoTrack, IMicrophoneAudioTrack } from 'agora-rtc-react';
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { useClient } from './client';
|
||||||
|
|
||||||
|
export const Controls: React.FC<{
|
||||||
|
tracks: [IMicrophoneAudioTrack, ICameraVideoTrack];
|
||||||
|
setStart: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
|
onClose: () => void;
|
||||||
|
}> = React.memo((props) => {
|
||||||
|
const client = useClient();
|
||||||
|
const { tracks, setStart } = props;
|
||||||
|
const [trackState, setTrackState] = useState({ video: true, audio: true });
|
||||||
|
|
||||||
|
const mute = async (type: 'audio' | 'video') => {
|
||||||
|
if (type === 'audio') {
|
||||||
|
await tracks[0].setEnabled(!trackState.audio);
|
||||||
|
setTrackState((ps) => {
|
||||||
|
return { ...ps, audio: !ps.audio };
|
||||||
|
});
|
||||||
|
} else if (type === 'video') {
|
||||||
|
await tracks[1].setEnabled(!trackState.video);
|
||||||
|
setTrackState((ps) => {
|
||||||
|
return { ...ps, video: !ps.video };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const leaveChannel = async () => {
|
||||||
|
await client.leave();
|
||||||
|
client.removeAllListeners();
|
||||||
|
// we close the tracks to perform cleanup
|
||||||
|
tracks[0].close();
|
||||||
|
tracks[1].close();
|
||||||
|
setStart(false);
|
||||||
|
props.onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="controller">
|
||||||
|
<IconBtn
|
||||||
|
icon={trackState.audio ? 'mdi:video' : 'mdi:video-off'}
|
||||||
|
title={trackState.audio ? '关闭摄像头' : '开启摄像头'}
|
||||||
|
size="large"
|
||||||
|
onClick={() => mute('video')}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<IconBtn
|
||||||
|
icon={trackState.video ? 'mdi:microphone' : 'mdi:microphone-off'}
|
||||||
|
title={trackState.video ? '关闭麦克风' : '开启麦克风'}
|
||||||
|
size="large"
|
||||||
|
onClick={() => mute('audio')}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<IconBtn
|
||||||
|
icon="mdi:phone-remove-outline"
|
||||||
|
title="挂断"
|
||||||
|
danger={true}
|
||||||
|
size="large"
|
||||||
|
onClick={leaveChannel}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
Controls.displayName = 'Controls';
|
@ -0,0 +1,40 @@
|
|||||||
|
import {
|
||||||
|
AgoraVideoPlayer,
|
||||||
|
IAgoraRTCRemoteUser,
|
||||||
|
ICameraVideoTrack,
|
||||||
|
IMicrophoneAudioTrack,
|
||||||
|
} from 'agora-rtc-react';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export const Videos: React.FC<{
|
||||||
|
users: IAgoraRTCRemoteUser[];
|
||||||
|
tracks: [IMicrophoneAudioTrack, ICameraVideoTrack];
|
||||||
|
}> = React.memo((props) => {
|
||||||
|
const { users, tracks } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="videos">
|
||||||
|
{/* AgoraVideoPlayer component takes in the video track to render the stream,
|
||||||
|
you can pass in other props that get passed to the rendered div */}
|
||||||
|
<AgoraVideoPlayer className="vid" videoTrack={tracks[1]} />
|
||||||
|
|
||||||
|
{users.length > 0 &&
|
||||||
|
users.map((user) => {
|
||||||
|
if (user.videoTrack) {
|
||||||
|
return (
|
||||||
|
<AgoraVideoPlayer
|
||||||
|
className="vid"
|
||||||
|
videoTrack={user.videoTrack}
|
||||||
|
key={user.uid}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
Videos.displayName = 'Videos';
|
@ -0,0 +1,17 @@
|
|||||||
|
import {
|
||||||
|
ClientConfig,
|
||||||
|
createClient,
|
||||||
|
createMicrophoneAndCameraTracks,
|
||||||
|
} from 'agora-rtc-react';
|
||||||
|
|
||||||
|
const config: ClientConfig = {
|
||||||
|
mode: 'rtc',
|
||||||
|
codec: 'vp8',
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO 应该从本地设置或者远程中获取
|
||||||
|
export const appId = ''; //ENTER APP ID HERE
|
||||||
|
export const token = '';
|
||||||
|
|
||||||
|
export const useClient = createClient(config);
|
||||||
|
export const useMicrophoneAndCameraTracks = createMicrophoneAndCameraTracks();
|
@ -0,0 +1,34 @@
|
|||||||
|
import { showToasts } from '@capital/common';
|
||||||
|
import { PortalAdd, PortalRemove, ErrorBoundary } from '@capital/component';
|
||||||
|
import React from 'react';
|
||||||
|
import { FloatMeetingWindow } from './window';
|
||||||
|
|
||||||
|
let currentMeeting: string | null = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO
|
||||||
|
*
|
||||||
|
* 启动快速会议
|
||||||
|
*
|
||||||
|
* 表现形式是在浏览器内有个小的浮动窗口
|
||||||
|
*/
|
||||||
|
export function startFastMeeting(meetingId: string) {
|
||||||
|
if (currentMeeting) {
|
||||||
|
showToasts('当前已有正在进行中的通话, 请先结束上一场通话');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentMeeting = meetingId;
|
||||||
|
|
||||||
|
const key = PortalAdd(
|
||||||
|
<ErrorBoundary>
|
||||||
|
<FloatMeetingWindow
|
||||||
|
meetingId={meetingId}
|
||||||
|
onClose={() => {
|
||||||
|
PortalRemove(key);
|
||||||
|
currentMeeting = null;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ErrorBoundary>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,164 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { getJWTUserInfo } from '@capital/common';
|
||||||
|
import type { IAgoraRTCRemoteUser } from 'agora-rtc-react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import {
|
||||||
|
appId,
|
||||||
|
token,
|
||||||
|
useClient,
|
||||||
|
useMicrophoneAndCameraTracks,
|
||||||
|
} from './client';
|
||||||
|
import { Videos } from './Videos';
|
||||||
|
import { Controls } from './Controls';
|
||||||
|
|
||||||
|
const FloatWindow = styled.div`
|
||||||
|
z-index: 100;
|
||||||
|
position: fixed;
|
||||||
|
background-color: var(--tc-content-background-color);
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
min-height: 240px;
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.body {
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
.videos {
|
||||||
|
height: 70vh;
|
||||||
|
align-self: flex-start;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(440px, 1fr));
|
||||||
|
justify-items: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.vid {
|
||||||
|
height: 95%;
|
||||||
|
width: 95%;
|
||||||
|
position: relative;
|
||||||
|
background-color: black;
|
||||||
|
border-width: 1px;
|
||||||
|
border-color: #38373a;
|
||||||
|
border-style: solid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.controller {
|
||||||
|
text-align: center;
|
||||||
|
padding: 10px 0;
|
||||||
|
|
||||||
|
* + * {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.folder-btn {
|
||||||
|
background-color: var(--tc-content-background-color);
|
||||||
|
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2);
|
||||||
|
position: absolute;
|
||||||
|
bottom: -30px;
|
||||||
|
height: 30px;
|
||||||
|
line-height: 30px;
|
||||||
|
left: 50%;
|
||||||
|
width: 60px;
|
||||||
|
margin-left: -30px;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 0 0 3px 3px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 音视频会议弹窗
|
||||||
|
*/
|
||||||
|
export const FloatMeetingWindow: React.FC<{
|
||||||
|
meetingId: string;
|
||||||
|
onClose: () => void;
|
||||||
|
}> = React.memo((props) => {
|
||||||
|
const [folder, setFolder] = useState(false);
|
||||||
|
const client = useClient();
|
||||||
|
const { ready, tracks } = useMicrophoneAndCameraTracks();
|
||||||
|
const channelName = props.meetingId;
|
||||||
|
const [users, setUsers] = useState<IAgoraRTCRemoteUser[]>([]);
|
||||||
|
const [start, setStart] = useState<boolean>(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// function to initialise the SDK
|
||||||
|
const init = async (channel: string) => {
|
||||||
|
client.on('user-published', async (user, mediaType) => {
|
||||||
|
await client.subscribe(user, mediaType);
|
||||||
|
console.log('subscribe success');
|
||||||
|
if (mediaType === 'video') {
|
||||||
|
setUsers((prevUsers) => {
|
||||||
|
return [...prevUsers, user];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (mediaType === 'audio') {
|
||||||
|
user.audioTrack?.play();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on('user-unpublished', (user, type) => {
|
||||||
|
console.log('unpublished', user, type);
|
||||||
|
if (type === 'audio') {
|
||||||
|
user.audioTrack?.stop();
|
||||||
|
}
|
||||||
|
if (type === 'video') {
|
||||||
|
setUsers((prevUsers) => {
|
||||||
|
return prevUsers.filter((User) => User.uid !== user.uid);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on('user-left', (user) => {
|
||||||
|
console.log('leaving', user);
|
||||||
|
setUsers((prevUsers) => {
|
||||||
|
return prevUsers.filter((User) => User.uid !== user.uid);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const { _id } = await getJWTUserInfo();
|
||||||
|
await client.join(appId, channel, token, _id);
|
||||||
|
if (tracks) {
|
||||||
|
await client.publish([tracks[0], tracks[1]]);
|
||||||
|
}
|
||||||
|
setStart(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (ready && tracks) {
|
||||||
|
console.log('init ready');
|
||||||
|
init(channelName);
|
||||||
|
}
|
||||||
|
}, [channelName, client, ready, tracks]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FloatWindow
|
||||||
|
style={{
|
||||||
|
transform: folder ? 'translateY(-100%)' : 'none',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="body">
|
||||||
|
{start && tracks && <Videos users={users} tracks={tracks} />}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="controller">
|
||||||
|
{ready && tracks && (
|
||||||
|
<Controls
|
||||||
|
tracks={tracks}
|
||||||
|
setStart={setStart}
|
||||||
|
onClose={props.onClose}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="folder-btn" onClick={() => setFolder(!folder)}>
|
||||||
|
{folder ? '展开' : '收起'}
|
||||||
|
</div>
|
||||||
|
</FloatWindow>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
FloatMeetingWindow.displayName = 'FloatMeetingWindow';
|
@ -0,0 +1,27 @@
|
|||||||
|
import { regPluginPanelAction } from '@capital/common';
|
||||||
|
import { openConfirmModal } from '@capital/component';
|
||||||
|
|
||||||
|
console.log('Plugin 声网音视频 is loaded');
|
||||||
|
|
||||||
|
async function startFastMeeting(meetingId: string) {
|
||||||
|
const module = await import('./FloatWindow');
|
||||||
|
module.startFastMeeting(meetingId); // 仅用于测试
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发起群组会议
|
||||||
|
regPluginPanelAction({
|
||||||
|
name: 'plugin:com.msgbyte.meeting/groupAction',
|
||||||
|
label: '发起通话',
|
||||||
|
position: 'group',
|
||||||
|
icon: 'mdi:video-box',
|
||||||
|
onClick: ({ groupId, panelId }) => {
|
||||||
|
openConfirmModal({
|
||||||
|
title: '发起通话',
|
||||||
|
content: '是否通过声网插件在当前会话开启音视频通讯?',
|
||||||
|
onConfirm: async () => {
|
||||||
|
// startFastMeeting(`${groupId}|${panelId}`);
|
||||||
|
startFastMeeting('123456'); // for test
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"jsx": "react",
|
||||||
|
"importsNotUsedAsValues": "error"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,485 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
|
||||||
|
/// <reference types="react" />
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 该文件由 Tailchat 自动生成
|
||||||
|
* 用于插件的类型声明
|
||||||
|
* 生成命令: pnpm run plugins:declaration:generate
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tailchat 通用
|
||||||
|
*/
|
||||||
|
declare module '@capital/common' {
|
||||||
|
export const useGroupPanelParams: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打开模态框
|
||||||
|
* @deprecated 请从 @capital/component 引入
|
||||||
|
*/
|
||||||
|
export const openModal: (
|
||||||
|
content: React.ReactNode,
|
||||||
|
|
||||||
|
props?: {
|
||||||
|
/**
|
||||||
|
* 是否显示右上角的关闭按钮
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
closable?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 遮罩层是否可关闭
|
||||||
|
*/
|
||||||
|
maskClosable?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关闭modal的回调
|
||||||
|
*/
|
||||||
|
onCloseModal?: () => void;
|
||||||
|
}
|
||||||
|
) => number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated 请从 @capital/component 引入
|
||||||
|
*/
|
||||||
|
export const closeModal: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated 请从 @capital/component 引入
|
||||||
|
*/
|
||||||
|
export const ModalWrapper: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated 请从 @capital/component 引入
|
||||||
|
*/
|
||||||
|
export const useModalContext: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated 请从 @capital/component 引入
|
||||||
|
*/
|
||||||
|
export const openConfirmModal: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated 请从 @capital/component 引入
|
||||||
|
*/
|
||||||
|
export const openReconfirmModal: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated 请从 @capital/component 引入
|
||||||
|
*/
|
||||||
|
export const Loadable: any;
|
||||||
|
|
||||||
|
export const getGlobalState: any;
|
||||||
|
|
||||||
|
export const useGlobalSocketEvent: <T>(
|
||||||
|
eventName: string,
|
||||||
|
callback: (data: T) => void
|
||||||
|
) => void;
|
||||||
|
|
||||||
|
export const getJWTUserInfo: () => Promise<{
|
||||||
|
_id?: string;
|
||||||
|
nickname?: string;
|
||||||
|
email?: string;
|
||||||
|
avatar?: string;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export const dataUrlToFile: any;
|
||||||
|
|
||||||
|
export const urlSearchStringify: any;
|
||||||
|
|
||||||
|
export const urlSearchParse: any;
|
||||||
|
|
||||||
|
export const appendUrlSearch: any;
|
||||||
|
|
||||||
|
export const getServiceWorkerRegistration: any;
|
||||||
|
|
||||||
|
export const getServiceUrl: () => string;
|
||||||
|
|
||||||
|
export const getCachedUserInfo: (
|
||||||
|
userId: string,
|
||||||
|
refetch?: boolean
|
||||||
|
) => Promise<{
|
||||||
|
_id: string;
|
||||||
|
email: string;
|
||||||
|
nickname: string;
|
||||||
|
discriminator: string;
|
||||||
|
avatar: string | null;
|
||||||
|
temporary: boolean;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export const getCachedConverseInfo: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 本地翻译
|
||||||
|
* @example
|
||||||
|
* localTrans({'zh-CN': '你好', 'en-US': 'Hello'});
|
||||||
|
*
|
||||||
|
* @param trans 翻译对象
|
||||||
|
*/
|
||||||
|
export const localTrans: (trans: Record<'zh-CN' | 'en-US', string>) => string;
|
||||||
|
|
||||||
|
export const getLanguage: any;
|
||||||
|
|
||||||
|
export const sharedEvent: any;
|
||||||
|
|
||||||
|
export const useAsync: <T extends (...args: any[]) => Promise<any>>(
|
||||||
|
fn: T,
|
||||||
|
deps?: React.DependencyList
|
||||||
|
) => { loading: boolean; value?: any; error?: Error };
|
||||||
|
|
||||||
|
export const useAsyncFn: <T extends (...args: any[]) => Promise<any>>(
|
||||||
|
fn: T,
|
||||||
|
deps?: React.DependencyList
|
||||||
|
) => [{ loading: boolean; value?: any; error?: Error }, T];
|
||||||
|
|
||||||
|
export const useAsyncRefresh: <T extends (...args: any[]) => Promise<any>>(
|
||||||
|
fn: T,
|
||||||
|
deps?: React.DependencyList
|
||||||
|
) => { loading: boolean; value?: any; error?: Error; refresh: () => void };
|
||||||
|
|
||||||
|
export const useAsyncRequest: <T extends (...args: any[]) => Promise<any>>(
|
||||||
|
fn: T,
|
||||||
|
deps?: React.DependencyList
|
||||||
|
) => [{ loading: boolean; value?: any }, T];
|
||||||
|
|
||||||
|
export const uploadFile: any;
|
||||||
|
|
||||||
|
export const showToasts: (
|
||||||
|
message: string,
|
||||||
|
type?: 'info' | 'success' | 'error' | 'warning'
|
||||||
|
) => void;
|
||||||
|
|
||||||
|
export const showSuccessToasts: any;
|
||||||
|
|
||||||
|
export const showErrorToasts: (error: any) => void;
|
||||||
|
|
||||||
|
export const fetchAvailableServices: any;
|
||||||
|
|
||||||
|
export const isValidStr: (str: any) => str is string;
|
||||||
|
|
||||||
|
export const useGroupPanelInfo: any;
|
||||||
|
|
||||||
|
export const sendMessage: any;
|
||||||
|
|
||||||
|
export const showMessageTime: any;
|
||||||
|
|
||||||
|
export const useLocation: any;
|
||||||
|
|
||||||
|
export const useNavigate: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated please use createMetaFormSchema from @capital/component
|
||||||
|
*/
|
||||||
|
export const createFastFormSchema: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated please use metaFormFieldSchema from @capital/component
|
||||||
|
*/
|
||||||
|
export const fieldSchema: any;
|
||||||
|
|
||||||
|
export const useCurrentUserInfo: any;
|
||||||
|
|
||||||
|
export const createPluginRequest: (pluginName: string) => {
|
||||||
|
get: (actionName: string, config?: any) => Promise<any>;
|
||||||
|
post: (actionName: string, data?: any, config?: any) => Promise<any>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const postRequest: any;
|
||||||
|
|
||||||
|
export const pluginCustomPanel: any;
|
||||||
|
|
||||||
|
export const regCustomPanel: any;
|
||||||
|
|
||||||
|
export const pluginGroupPanel: any;
|
||||||
|
|
||||||
|
export const regGroupPanel: any;
|
||||||
|
|
||||||
|
export const messageInterpreter: {
|
||||||
|
name?: string;
|
||||||
|
explainMessage: (message: string) => React.ReactNode;
|
||||||
|
}[];
|
||||||
|
|
||||||
|
export const regMessageInterpreter: (interpreter: {
|
||||||
|
name?: string;
|
||||||
|
explainMessage: (message: string) => React.ReactNode;
|
||||||
|
}) => void;
|
||||||
|
|
||||||
|
export const getMessageRender: (message: string) => React.ReactNode;
|
||||||
|
|
||||||
|
export const regMessageRender: (
|
||||||
|
render: (message: string) => React.ReactNode
|
||||||
|
) => void;
|
||||||
|
|
||||||
|
export const getMessageTextDecorators: any;
|
||||||
|
|
||||||
|
export const regMessageTextDecorators: any;
|
||||||
|
|
||||||
|
export const ChatInputActionContextProps: any;
|
||||||
|
|
||||||
|
export const pluginChatInputActions: any;
|
||||||
|
|
||||||
|
export const regChatInputAction: any;
|
||||||
|
|
||||||
|
export const regSocketEventListener: (item: {
|
||||||
|
eventName: string;
|
||||||
|
eventFn: (...args: any[]) => void;
|
||||||
|
}) => void;
|
||||||
|
|
||||||
|
export const pluginColorScheme: any;
|
||||||
|
|
||||||
|
export const regPluginColorScheme: any;
|
||||||
|
|
||||||
|
export const pluginInspectServices: any;
|
||||||
|
|
||||||
|
export const regInspectService: any;
|
||||||
|
|
||||||
|
export const pluginMessageExtraParsers: any;
|
||||||
|
|
||||||
|
export const regMessageExtraParser: any;
|
||||||
|
|
||||||
|
export const pluginRootRoute: any;
|
||||||
|
|
||||||
|
export const regPluginRootRoute: any;
|
||||||
|
|
||||||
|
export const pluginPanelActions: any;
|
||||||
|
|
||||||
|
interface BasePluginPanelActionProps {
|
||||||
|
/**
|
||||||
|
* 唯一标识
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
/**
|
||||||
|
* 显示名
|
||||||
|
*/
|
||||||
|
label: string;
|
||||||
|
/**
|
||||||
|
* 来自iconify的图标标识
|
||||||
|
*/
|
||||||
|
icon: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const regPluginPanelAction: (
|
||||||
|
action:
|
||||||
|
| {
|
||||||
|
name: string;
|
||||||
|
label: string;
|
||||||
|
icon: string;
|
||||||
|
position: 'group';
|
||||||
|
onClick: (ctx: { groupId: string; panelId: string }) => void;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
name: string;
|
||||||
|
label: string;
|
||||||
|
icon: string;
|
||||||
|
position: 'dm';
|
||||||
|
onClick: (ctx: { converseId: string }) => void;
|
||||||
|
}
|
||||||
|
) => void;
|
||||||
|
|
||||||
|
export const pluginPermission: any;
|
||||||
|
|
||||||
|
export const regPluginPermission: (permission: {
|
||||||
|
/**
|
||||||
|
* 权限唯一key, 用于写入数据库
|
||||||
|
* 如果为插件则权限点应当符合命名规范, 如: plugin.com.msgbyte.github.manage
|
||||||
|
*/
|
||||||
|
key: string;
|
||||||
|
/**
|
||||||
|
* 权限点显示名称
|
||||||
|
*/
|
||||||
|
title: string;
|
||||||
|
/**
|
||||||
|
* 权限描述
|
||||||
|
*/
|
||||||
|
desc: string;
|
||||||
|
/**
|
||||||
|
* 是否默认开启
|
||||||
|
*/
|
||||||
|
default: boolean;
|
||||||
|
/**
|
||||||
|
* 是否依赖其他权限点
|
||||||
|
*/
|
||||||
|
required?: string[];
|
||||||
|
}) => void;
|
||||||
|
|
||||||
|
export const pluginGroupPanelBadges: any;
|
||||||
|
|
||||||
|
export const regGroupPanelBadge: any;
|
||||||
|
|
||||||
|
export const pluginGroupTextPanelExtraMenus: any;
|
||||||
|
|
||||||
|
export const regPluginGroupTextPanelExtraMenu: any;
|
||||||
|
|
||||||
|
export const useGroupIdContext: () => string;
|
||||||
|
|
||||||
|
export const useGroupPanelContext: () => {
|
||||||
|
groupId: string;
|
||||||
|
panelId: string;
|
||||||
|
} | null;
|
||||||
|
|
||||||
|
export const useSocketContext: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tailchat 组件
|
||||||
|
*/
|
||||||
|
declare module '@capital/component' {
|
||||||
|
export const Button: any;
|
||||||
|
|
||||||
|
export const Checkbox: any;
|
||||||
|
|
||||||
|
export const Input: any;
|
||||||
|
|
||||||
|
export const Divider: any;
|
||||||
|
|
||||||
|
export const Space: any;
|
||||||
|
|
||||||
|
export const Menu: any;
|
||||||
|
|
||||||
|
export const Table: any;
|
||||||
|
|
||||||
|
export const Switch: any;
|
||||||
|
|
||||||
|
export const Tooltip: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @link https://ant.design/components/notification-cn/
|
||||||
|
*/
|
||||||
|
export const notification: any;
|
||||||
|
|
||||||
|
export const Empty: React.FC<
|
||||||
|
React.PropsWithChildren<{
|
||||||
|
prefixCls?: string;
|
||||||
|
className?: string;
|
||||||
|
style?: React.CSSProperties;
|
||||||
|
imageStyle?: React.CSSProperties;
|
||||||
|
image?: React.ReactNode;
|
||||||
|
description?: React.ReactNode;
|
||||||
|
}>
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const TextArea: any;
|
||||||
|
|
||||||
|
export const Avatar: any;
|
||||||
|
|
||||||
|
export const SensitiveText: React.FC<{ className?: string; text: string }>;
|
||||||
|
|
||||||
|
export const Icon: React.FC<{ icon: string } & React.SVGProps<SVGSVGElement>>;
|
||||||
|
|
||||||
|
export const CopyableText: React.FC<{
|
||||||
|
className?: string;
|
||||||
|
style?: React.CSSProperties;
|
||||||
|
config?:
|
||||||
|
| boolean
|
||||||
|
| {
|
||||||
|
text?: string;
|
||||||
|
onCopy?: (event?: React.MouseEvent<HTMLDivElement>) => void;
|
||||||
|
icon?: React.ReactNode;
|
||||||
|
tooltips?: boolean | React.ReactNode;
|
||||||
|
format?: 'text/plain' | 'text/html';
|
||||||
|
};
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export const WebFastForm: any;
|
||||||
|
|
||||||
|
export const WebMetaForm: any;
|
||||||
|
|
||||||
|
export const createMetaFormSchema: any;
|
||||||
|
|
||||||
|
export const metaFormFieldSchema: any;
|
||||||
|
|
||||||
|
export const Image: any;
|
||||||
|
|
||||||
|
export const IconBtn: React.FC<{
|
||||||
|
icon: string;
|
||||||
|
className?: string;
|
||||||
|
iconClassName?: string;
|
||||||
|
size?: 'small' | 'middle' | 'large';
|
||||||
|
shape?: 'circle' | 'square';
|
||||||
|
title?: string;
|
||||||
|
danger?: boolean;
|
||||||
|
onClick?: React.MouseEventHandler<HTMLElement>;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export const PillTabs: any;
|
||||||
|
|
||||||
|
export const PillTabPane: any;
|
||||||
|
|
||||||
|
export const LoadingSpinner: React.FC<{ tip?: string }>;
|
||||||
|
|
||||||
|
export const FullModalField: any;
|
||||||
|
|
||||||
|
export const DefaultFullModalInputEditorRender: any;
|
||||||
|
|
||||||
|
export const DefaultFullModalTextAreaEditorRender: any;
|
||||||
|
|
||||||
|
export const openModal: (
|
||||||
|
content: React.ReactNode,
|
||||||
|
|
||||||
|
props?: {
|
||||||
|
/**
|
||||||
|
* 是否显示右上角的关闭按钮
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
closable?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 遮罩层是否可关闭
|
||||||
|
*/
|
||||||
|
maskClosable?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关闭modal的回调
|
||||||
|
*/
|
||||||
|
onCloseModal?: () => void;
|
||||||
|
}
|
||||||
|
) => number;
|
||||||
|
|
||||||
|
export const closeModal: any;
|
||||||
|
|
||||||
|
export const ModalWrapper: any;
|
||||||
|
|
||||||
|
export const useModalContext: any;
|
||||||
|
|
||||||
|
export const openConfirmModal: any;
|
||||||
|
|
||||||
|
export const openReconfirmModal: any;
|
||||||
|
|
||||||
|
export const Loadable: any;
|
||||||
|
|
||||||
|
export const Loading: React.FC<{
|
||||||
|
spinning: boolean;
|
||||||
|
className?: string;
|
||||||
|
style?: React.CSSProperties;
|
||||||
|
children?: React.ReactNode;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export const LoadingOnFirst: React.FC<{
|
||||||
|
spinning: boolean;
|
||||||
|
className?: string;
|
||||||
|
style?: React.CSSProperties;
|
||||||
|
children?: React.ReactNode;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export const SidebarView: any;
|
||||||
|
|
||||||
|
export const GroupPanelSelector: any;
|
||||||
|
|
||||||
|
export const Emoji: any;
|
||||||
|
|
||||||
|
export const PortalAdd: any;
|
||||||
|
|
||||||
|
export const PortalRemove: any;
|
||||||
|
|
||||||
|
export const ErrorBoundary: any;
|
||||||
|
|
||||||
|
export const UserAvatar: any;
|
||||||
|
|
||||||
|
export const UserName: React.FC<{
|
||||||
|
userId: string;
|
||||||
|
className?: string;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export const Markdown: any;
|
||||||
|
}
|
Loading…
Reference in New Issue