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:重构 Agent 和 Dance Panel #21

Merged
merged 12 commits into from
Apr 14, 2024
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ Please be aware that LobeVidol is currently under active development, and feedba
| [@lobehub/tts][lobe-tts-link] | [lobehub/lobe-tts][lobe-tts-github] | High-quality & reliable TTS/STT React Hooks library | [![][lobe-tts-shield]][lobe-tts-link] |
| [@lobehub/lint][lobe-lint-link] | [lobehub/lobe-lint][lobe-lint-github] | Configurations for ESlint, Stylelint, Commitlint, Prettier, Remark, and Semantic Release for LobeHub. | [![][lobe-lint-shield]][lobe-lint-link] |

- **[Vidol chat market](https://github.com/v-idol/vidol-chat-agents)** - This is the Market Index of Vidol Chat. Vidol accesses index.json from this repo to show user the list of available agents.
- **[Vidol market](https://github.com/v-idol/vidol-chat-agents)** - This is the Market Index of Vidol Chat. Vidol accesses index.json from this repo to show user the list of available agents and dances.
- **[Vidol agent sample](https://github.com/v-idol/vidol-agent-sample)** - This is the sample repo to define an AI agent in Vidol.
- **[Vidol dance sample](https://github.com/v-idol/vidol-dance-sample)** - This is the sample repo to define a dance in Vidol.

Expand Down
49 changes: 49 additions & 0 deletions src/app/home/QuickSwitch/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
'use client';

import { Avatar } from '@lobehub/ui';

import { sessionSelectors, useSessionStore } from '@/store/session';

import { useStyles } from './style';

const AvatarSize = 64;

const QuickSwitch = () => {
const { styles } = useStyles({ avatarSize: AvatarSize });
const [sessionList, getAgentById] = useSessionStore((s) => [
s.sessionList,
sessionSelectors.getAgentById(s),
]);
const [switchSession, activeId] = useSessionStore((s) => [s.switchSession, s.activeId]);

return (
<div className={styles.sidebar}>
{/*<div className={styles.header}>聊天列表</div>*/}
<div className={styles.list}>
{sessionList.map((item) => {
const agent = getAgentById(item.agentId);
if (!agent) return null;
const isActive = activeId === agent.agentId;
return (
<div key={agent.agentId} style={{ position: 'relative' }}>
<Avatar
className={isActive ? styles.active : ''}
onClick={() => switchSession(agent.agentId)}
size={AvatarSize}
src={agent.meta.avatar}
/>
{isActive ? (
<>
{/*<div className={styles.satellite} />*/}
<div className={styles.orbit} />
</>
) : null}
</div>
);
})}
</div>
</div>
);
};

export default QuickSwitch;
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,22 @@ export const useStyles = createStyles(({ css, token }) => ({
border: 3px solid ${token.colorPrimary}; /* 轨道颜色和透明度 */
border-radius: 50%;
`,

roleSelect: css`
sidebar: css`
position: fixed;
top: 64px;
left: 0;
`,

header: css`
display: flex;
align-items: center;
justify-content: space-between;

padding: 16px;

border-bottom: 1px solid ${token.colorBorder};
`,
list: css`
overflow: auto;
display: grid;
grid-auto-flow: row;
Expand All @@ -51,7 +61,6 @@ export const useStyles = createStyles(({ css, token }) => ({
grid-template-rows: repeat(auto-fill, 64px);
justify-items: center;

height: calc(100vh - 64px - 64px);
padding: 32px;
`,
}));
46 changes: 0 additions & 46 deletions src/app/home/RoleSelect/index.tsx

This file was deleted.

14 changes: 3 additions & 11 deletions src/app/home/apps.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
// @ts-ignore
import { AgentPanel, ChatPanel, ConfigPanel, DancePanel, MarketPanel, RolePanel } from '@/panels';
import { AgentPanel, ChatPanel, ConfigPanel, DancePanel, RolePanel } from '@/panels';

export const apps = [
{
avatar:
'https://registry.npmmirror.com/@lobehub/assets-emoji/latest/files/assets/card-index.webp',
component: <AgentPanel />,
key: 'agent',
label: '角色订阅',
label: '角色',
},
{
avatar:
'https://registry.npmmirror.com/@lobehub/assets-emoji/latest/files/assets/folding-hand-fan.webp',
component: <DancePanel />,
key: 'dance',
label: '舞蹈',
label: '跳舞',
},
{
avatar:
Expand All @@ -23,14 +23,6 @@ export const apps = [
key: 'chat',
label: '聊天',
},
{
avatar:
'https://registry.npmmirror.com/@lobehub/assets-emoji/latest/files/assets/convenience-store.webp',
component: <MarketPanel />,
key: 'market',
label: '商店',
},

{
avatar:
'https://registry.npmmirror.com/@lobehub/assets-emoji/latest/files/assets/black-nib.webp',
Expand Down
7 changes: 3 additions & 4 deletions src/app/home/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@

import Background from '@/app/home/Background';
import Dialog from '@/app/home/Dialog';
import Docker from '@/app/home/Docker';
import Header from '@/app/home/Header';
import RoleSelect from '@/app/home/RoleSelect';
import QuickSwitch from '@/app/home/QuickSwitch';
import VirtualIdol from '@/app/home/VirtualIdol';
import { apps } from '@/app/home/apps';
import { useConfigStore } from '@/store/config';
import { PanelKey } from '@/types/config';

import Docker from './Docker';

const Desktop = () => {
const [panel] = useConfigStore((s) => [s.panel]);
return (
Expand All @@ -29,7 +28,7 @@ const Desktop = () => {
})}
</div>
<Docker />
<RoleSelect />
<QuickSwitch />
<Dialog />
<Background />
</div>
Expand Down
4 changes: 2 additions & 2 deletions src/app/metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@ const metadata: Metadata = {
{
alt: title,
height: 360,
url: 'https://registry.npmmirror.com/@lobehub/assets-favicons/latest/files/assets/og-480x270.png',
url: 'https://registry.npmmirror.com/@lobehub/assets-favicons/latest/files/assets/og-vidol-480x270.png',
width: 480,
},
{
alt: title,
height: 720,
url: 'https://registry.npmmirror.com/@lobehub/assets-favicons/latest/files/assets/og-960x540.png',
url: 'https://registry.npmmirror.com/@lobehub/assets-favicons/latest/files/assets/og-vidol-960x540.png',
width: 960,
},
],
Expand Down
2 changes: 1 addition & 1 deletion src/components/DanceInfo/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const DanceInfo = (props: DanceInfoProps) => {
return (
<div className={styles.container}>
<Center className={styles.header} gap={16}>
<Avatar avatar={cover} background={theme.colorFillTertiary} shape="square" size={120} />
<Avatar avatar={cover} background={theme.colorFillTertiary} shape="square" size={180} />
<div className={styles.title}>{name}</div>
<div className={styles.actions}>
<Space>{actions}</Space>
Expand Down
36 changes: 36 additions & 0 deletions src/components/GridList/ListItem/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { CheckCircleFilled } from '@ant-design/icons';
import { Typography } from 'antd';
import classNames from 'classnames';
import React, { memo } from 'react';

import { useStyles } from './style';

const { Text } = Typography;

interface AvatarProps {
active?: boolean;
avatar: string;
checked?: boolean;
className?: string;
onClick?: () => void;
style?: React.CSSProperties;
title: string;
}

const ListItem = (props: AvatarProps) => {
const { avatar, title, style, className, onClick, active = false, checked = false } = props;
const { styles } = useStyles({ active, avatar });
return (
<div style={style} className={classNames(className, styles.item)} onClick={onClick}>
<div className={styles.avatar} />
<div className={styles.info}>
<Text ellipsis={{ tooltip: title }} className={styles.title}>
{title}
</Text>
{checked ? <CheckCircleFilled className={styles.check} /> : null}
</div>
</div>
);
};

export default memo(ListItem);
47 changes: 47 additions & 0 deletions src/components/GridList/ListItem/style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { createStyles } from 'antd-style';
import { rgba } from 'polished';

interface ListItemProps {
active: boolean;
avatar: string;
}

export const useStyles = createStyles(({ css, token }, { active, avatar }: ListItemProps) => ({
item: css`
cursor: pointer;

position: relative;

width: 100%;
height: 100%;

background: url(${avatar}) no-repeat center center;
background-size: cover;
border: 2px solid ${active ? token.colorPrimary : token.colorFillTertiary};
`,
avatar: css``,

info: css`
position: absolute;
bottom: 0;

display: flex;
align-items: center;
justify-content: space-between;

width: 100%;
height: 24px;
padding: 4px;

background-color: ${rgba(token.colorBgContainer, 0.8)};
backdrop-filter: saturate(180%) blur(2px);
`,
title: css`
font-size: ${token.sizeSM}px;
line-height: 16px;
`,
check: css`
font-size: ${token.sizeSM}px;
color: ${token.colorSuccessText};
`,
}));
83 changes: 83 additions & 0 deletions src/components/GridList/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { Icon } from '@lobehub/ui';
import { Empty, Space } from 'antd';
import classNames from 'classnames';
import { Loader2 } from 'lucide-react';
import React, { memo } from 'react';
import { Center } from 'react-layout-kit';

import ListItem from './ListItem';
import { useStyles } from './style';

interface Item {
avatar: string;
id: string;
name: string;
}

interface GridListProps {
className?: string;
empty?: {
actions?: React.ReactNode[];
};
isActivated?: (id: string) => boolean;
isChecked?: (id: string) => boolean;
items: Item[];
loading?: boolean;
onClick?: (id: string) => void;
style?: React.CSSProperties;
}

const GridList = (props: GridListProps) => {
const {
items,
className,
style,
onClick,
isActivated,
isChecked,
loading = false,
empty,
} = props;
const { styles } = useStyles();

const Loading = () => (
<Center gap={16} horizontal className={styles.loading}>
<Icon icon={Loader2} spin />
加载中...
</Center>
);

const List = () => (
<div className={styles.list}>
{items.map((item) => {
const { avatar, name, id } = item;
return (
<ListItem
key={id}
title={name}
avatar={avatar}
onClick={() => {
if (onClick) onClick(id);
}}
active={isActivated ? isActivated(id) : false}
checked={isChecked ? isChecked(id) : false}
/>
);
})}
</div>
);

const EmptyList = () => (
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="暂无数据">
{empty?.actions ? <Space>{empty.actions}</Space> : null}
</Empty>
);

return (
<div className={classNames(className, styles.grid)} style={style}>
{loading ? <Loading /> : items.length === 0 ? <EmptyList /> : <List />}
</div>
);
};

export default memo(GridList);
Loading
Loading