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 #93

Merged
merged 18 commits into from
Jul 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,6 @@ logs

# misc
# add other ignore file below
.next
.next

libs
55 changes: 32 additions & 23 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
"type-check": "tsc --noEmit"
},
"lint-staged": {
"*.md": [
"*.mdx": [
"remark --quiet --output --",
"prettier --write --no-error-on-unmatched-pattern"
],
Expand All @@ -62,24 +62,26 @@
},
"dependencies": {
"@ant-design/icons": "^5.3.7",
"@aws-sdk/client-s3": "^3.592.0",
"@aws-sdk/client-s3": "^3.600.0",
"@aws-sdk/s3-request-presigner": "^3.600.0",
"@dnd-kit/core": "^6.1.0",
"@dnd-kit/utilities": "^3.2.2",
"@gltf-transform/core": "^4.0.0",
"@icons-pack/react-simple-icons": "^9.5.0",
"@lobehub/icons": "^1.22.1",
"@lobehub/tts": "^1.24.1",
"@lobehub/ui": "^1.141.3",
"@gltf-transform/core": "^4.0.2",
"@icons-pack/react-simple-icons": "^9.6.0",
"@lobehub/icons": "^1.24.0",
"@lobehub/tts": "^1.24.2",
"@lobehub/ui": "^1.145.5",
"@pixiv/three-vrm": "2.1.2",
"@pixiv/three-vrm-core": "2.1.2",
"@react-spring/web": "^9.7.3",
"@types/numeral": "^2.0.5",
"@trpc/client": "next",
"@trpc/next": "next",
"@trpc/server": "next",
"@types/react-speech-recognition": "^3.9.5",
"@vercel/analytics": "^1.3.1",
"ahooks": "^3.8.0",
"ai": "^2.2.37",
"antd": "~5.18.0",
"antd": "~5.18.3",
"antd-style": "^3.6.2",
"axios": "^1.7.2",
"buffer": "^6.0.3",
Expand All @@ -91,41 +93,49 @@
"localforage": "^1.10.0",
"lodash-es": "^4.17.21",
"lucide-react": "^0.395.0",
"mime": "^4.0.3",
"mmd-parser": "^1.0.4",
"modern-screenshot": "^4.4.39",
"nanoid": "^5.0.7",
"next": "^14.2.3",
"next": "^14.2.4",
"next-pwa": "^5.6.0",
"numeral": "^2.0.6",
"openai": "^4.47.1",
"openai": "^4.52.1",
"pino": "^9.2.0",
"polished": "^4.3.1",
"query-string": "^9.0.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-intersection-observer": "^9.10.2",
"react-intersection-observer": "^9.10.3",
"react-layout-kit": "^1.9.0",
"react-lazy-load": "^4.0.1",
"react-virtuoso": "^4.7.11",
"remark": "^15.0.1",
"remark-gfm": "^3.0.1",
"remark-html": "^16.0.1",
"superjson": "^2.2.1",
"swr": "^2.2.5",
"three": "^0.165.0",
"ua-parser-js": "^1.0.37",
"ua-parser-js": "^1.0.38",
"url-join": "^5.0.0",
"utility-types": "^3.11.0",
"uuid": "^10.0.0",
"ws": "^8.17.0",
"zustand": "^4.5.2"
"ws": "^8.17.1",
"zod": "^3.23.8",
"zustand": "^4.5.4"
},
"devDependencies": {
"@commitlint/cli": "^19.3.0",
"@ducanh2912/next-pwa": "^10.2.7",
"@lobehub/lint": "^1.23.4",
"@next/bundle-analyzer": "^14.2.3",
"@peculiar/webcrypto": "^1.4.6",
"@testing-library/jest-dom": "^6.4.5",
"@next/bundle-analyzer": "^14.2.4",
"@next/eslint-plugin-next": "^14.2.4",
"@peculiar/webcrypto": "^1.5.0",
"@testing-library/jest-dom": "^6.4.6",
"@testing-library/react": "^16.0.0",
"@types/lodash-es": "^4.17.12",
"@types/node": "20.14.2",
"@types/numeral": "^2.0.5",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@types/three": "^0.165.0",
Expand All @@ -134,8 +144,7 @@
"commitlint": "^19.3.0",
"dpdm": "^3.14.0",
"eslint": "^8.57.0",
"eslint-config-next": "14.2.4",
"glob": "^10.4.1",
"glob": "^10.4.2",
"husky": "^9.0.11",
"jsdom": "^24.1.0",
"lint-staged": "^15.2.5",
Expand All @@ -144,9 +153,9 @@
"remark-cli": "^12.0.0",
"semantic-release": "^21.1.2",
"stylelint": "^15.11.0",
"tsx": "^4.11.0",
"typescript": "^5.4.5",
"vite": "^5.2.11",
"tsx": "^4.15.7",
"typescript": "^5.5.2",
"vite": "^5.3.1",
"vitest": "~1.6.0",
"vitest-canvas-mock": "^0.3.3"
},
Expand Down
22 changes: 22 additions & 0 deletions src/app/trpc/edge/[trpc]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { fetchRequestHandler } from '@trpc/server/adapters/fetch';
import type { NextRequest } from 'next/server';

import { pino } from '@/libs/logger';
import { edgeRouter } from '@/server/routers';

export const runtime = 'edge';

const handler = (req: NextRequest) =>
fetchRequestHandler({
endpoint: '/trpc/edge',

onError: ({ error, path }) => {
pino.info(`Error in tRPC handler (edge) on path: ${path}`);
console.error(error);
},

req,
router: edgeRouter,
});

export { handler as GET, handler as POST };
14 changes: 14 additions & 0 deletions src/constants/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ export const DEFAULT_AGENT_CONFIG: Agent = {
name: '自定义角色',
description: '这是一个自定义角色',
avatar: DEFAULT_AGENT_AVATAR_URL,
cover: '',
gender: GenderEnum.FEMALE,
category: CategoryEnum.ANIME,
readme: '',
},
touch: DEFAULT_TOUCH_ACTION_CONFIG_FEMALE,
Expand All @@ -55,3 +57,15 @@ export const AGENT_GENDER_OPTIONS = [
{ label: '男性', value: GenderEnum.MALE },
{ label: '其他', value: GenderEnum.OTHER },
];

export const AGENT_CATEGORY_OPTIONS = [
{ label: '动物', value: CategoryEnum.ANIMAL },
{ label: '动漫', value: CategoryEnum.ANIME },
{ label: '书籍', value: CategoryEnum.BOOK },
{ label: '游戏', value: CategoryEnum.GAME },
{ label: '历史', value: CategoryEnum.HISTORY },
{ label: '电影', value: CategoryEnum.MOVIE },
{ label: '现实', value: CategoryEnum.REALISTIC },
{ label: 'Vroid', value: CategoryEnum.VROID },
{ label: 'VTuber', value: CategoryEnum.VTUBER },
];
2 changes: 2 additions & 0 deletions src/constants/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ export const AGENT_INDEX_URL = 'https://vidol-market.lobehub.com/agents/index.js

export const DANCE_INDEX_URL = 'https://vidol-market.lobehub.com/dances/index.json';

export const OSS_PREFIX = 'https://r2.vidol.chat';

export const COOKIE_CACHE_DAYS = 30;

export const LOADING_FLAG = '...';
Expand Down
Empty file removed src/constants/dance.ts
Empty file.
2 changes: 1 addition & 1 deletion src/features/Actions/Agent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ const ThemeButton = memo(() => {
}}
trigger={['click']}
>
<ActionIcon icon={PlusCircle} />
<ActionIcon icon={PlusCircle} title="创建角色" />
</Popover>
);
});
Expand Down
102 changes: 68 additions & 34 deletions src/features/Actions/SubmitAgentButton/SubmitAgentModal.tsx
Original file line number Diff line number Diff line change
@@ -1,64 +1,74 @@
'use client';

import { Alert, Icon, Modal, type ModalProps } from '@lobehub/ui';
import { Button, Divider, Input, Space } from 'antd';
import { Button, Divider, Input, Popover, Progress, Space, Typography } from 'antd';
import { useTheme } from 'antd-style';
import isEqual from 'fast-deep-equal';
import { kebabCase } from 'lodash-es';
import { Dices } from 'lucide-react';
import qs from 'query-string';
import { memo, useState } from 'react';
import React, { memo, useState } from 'react';
import { Flexbox } from 'react-layout-kit';

import AgentCard from '@/components/agent/AgentCard';
import SystemRole from '@/components/agent/SystemRole';
import { AGENTS_INDEX_GITHUB_ISSUE } from '@/constants/url';
import { upload } from '@/services/upload';
import { useUploadAgent } from '@/hooks/useUploadAgent';
import { agentSelectors, useAgentStore } from '@/store/agent';
import { Agent } from '@/types/agent';

const SubmitAgentModal = memo<ModalProps>(({ open, onCancel }) => {
const [agentId, setAgentId] = useState('');
const theme = useTheme();
const currentAgent = useAgentStore(agentSelectors.currentAgentItem, isEqual);
const currentAgent: Agent = useAgentStore((s) => agentSelectors.currentAgentItem(s), isEqual);
const { meta } = currentAgent;

const { uploading, uploadAgentData, percent } = useUploadAgent();

const isFormPass = Boolean(
currentAgent.greeting &&
currentAgent.systemRole &&
meta.name &&
meta.description &&
meta.avatar &&
meta.cover &&
meta.gender,
meta.model,
);

const handleSubmit = async () => {
let avatarUrl = meta.avatar;
if (meta.avatar.includes('base64')) {
const arr = meta.avatar.split('base64,');
const binaryString = atob(arr[1]);
// @ts-ignore
const mime = arr[0].match(/:(.*?);/)[1];
const uint8Array = Uint8Array.from(binaryString, (char) => char.charCodeAt(0));
// base64
const { success, url } = await upload(
new File([uint8Array], `${agentId}-avatar.png`, { type: mime }),
);
if (success) {
avatarUrl = url;
}
}
const { avatarUrl, coverUrl, modelUrl } = await uploadAgentData(agentId, meta);

const body = [
'### systemRole',
currentAgent.systemRole,
'### agentId',
kebabCase(agentId),
agentId,
'### avatar',
avatarUrl,
'### cover',
meta.cover,
coverUrl,
'### systemRole',
currentAgent.systemRole,
'### greeting',
currentAgent.greeting,
'### modelUrl',
modelUrl,
'### name',
meta.name,
'### description',
meta.description,
'### category',
meta.category,
'### readme',
meta.readme,
'### gender',
meta.gender,
'### tts',
JSON.stringify(currentAgent.tts),
'### touch',
JSON.stringify(currentAgent.touch),
'### model',
currentAgent.model,
'### params',
JSON.stringify(currentAgent.params),
].join('\n\n');

const url = qs.stringifyUrl({
Expand All @@ -73,15 +83,37 @@ const SubmitAgentModal = memo<ModalProps>(({ open, onCancel }) => {
<Modal
allowFullscreen
footer={
<Button
block
disabled={!isFormPass || !agentId}
onClick={handleSubmit}
size={'large'}
type={'primary'}
<Popover
open={uploading}
title={
<Flexbox>
<Typography.Text type={'secondary'}>上传处理中,请勿关闭页面...</Typography.Text>
<Space>
<Progress steps={30} percent={percent.cover} size="small" />
<Typography.Text style={{ fontSize: 12 }}>上传封面</Typography.Text>
</Space>
<Space>
<Progress steps={30} percent={percent.avatar} size="small" />
<Typography.Text style={{ fontSize: 12 }}>上传头像</Typography.Text>
</Space>
<Space>
<Progress steps={30} percent={percent.model} size="small" />
<Typography.Text style={{ fontSize: 12 }}>上传模型</Typography.Text>
</Space>
</Flexbox>
}
>
提交助手
</Button>
<Button
block
disabled={!isFormPass || !agentId}
onClick={handleSubmit}
size={'large'}
type={'primary'}
loading={uploading}
>
提交助手
</Button>
</Popover>
}
onCancel={onCancel}
open={open}
Expand All @@ -90,13 +122,15 @@ const SubmitAgentModal = memo<ModalProps>(({ open, onCancel }) => {
<Flexbox gap={16}>
{!isFormPass && (
<Alert
message={'请补全助手信息后提交,需要包含名称、描述、头像和封面'}
message={'请补全助手信息后提交,需要包含名称、描述、头像、封面、招呼和 3D 模型'}
showIcon
type={'warning'}
/>
)}
<AgentCard agent={currentAgent} />
<Divider style={{ margin: '8px 0' }} />
<SystemRole systemRole={currentAgent.systemRole} />
<Divider style={{ margin: '8px 0' }} />
<strong>
<span style={{ color: theme.colorError, marginRight: 4 }}>*</span>
agentId 助手标识符
Expand All @@ -112,7 +146,7 @@ const SubmitAgentModal = memo<ModalProps>(({ open, onCancel }) => {
icon={<Icon icon={Dices} />}
onClick={() => {
const randomId = Math.random().toString(36).slice(7);
setAgentId(`vidol-agent-${randomId}`);
setAgentId(kebabCase(randomId));
}}
></Button>
</Space.Compact>
Expand Down
12 changes: 6 additions & 6 deletions src/features/vrmViewer/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import { VRM, VRMLoaderPlugin, VRMUtils } from '@pixiv/three-vrm';
import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';

import { convert } from '@/lib/VMDAnimation/vmd2vrmanim';
import { bindToVRM, toOffset } from '@/lib/VMDAnimation/vmd2vrmanim.binding';
import IKHandler from '@/lib/VMDAnimation/vrm-ik-handler';
import { VRMAnimation } from '@/lib/VRMAnimation/VRMAnimation';
import { loadVRMAnimation } from '@/lib/VRMAnimation/loadVRMAnimation';
import { VRMLookAtSmootherLoaderPlugin } from '@/lib/VRMLookAtSmootherLoaderPlugin/VRMLookAtSmootherLoaderPlugin';
import { convert } from '@/libs/VMDAnimation/vmd2vrmanim';
import { bindToVRM, toOffset } from '@/libs/VMDAnimation/vmd2vrmanim.binding';
import IKHandler from '@/libs/VMDAnimation/vrm-ik-handler';
import { VRMAnimation } from '@/libs/VRMAnimation/VRMAnimation';
import { loadVRMAnimation } from '@/libs/VRMAnimation/loadVRMAnimation';
import { VRMLookAtSmootherLoaderPlugin } from '@/libs/VRMLookAtSmootherLoaderPlugin/VRMLookAtSmootherLoaderPlugin';
import { Screenplay } from '@/types/touch';

import { EmoteController } from '../emoteController/emoteController';
Expand Down
Loading
Loading