From 68920996768dd940c9f9eb4d5684a90af16e3ee5 Mon Sep 17 00:00:00 2001 From: rdmclin2 Date: Tue, 25 Jun 2024 00:01:40 +0800 Subject: [PATCH 01/16] =?UTF-8?q?:sparkles:=20feat:=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=20trpc=20upload=20=E8=B0=83=E7=94=A8=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 47 ++++++++++++++----------- src/libs/trpc/client.ts | 13 +++++++ src/panels/RolePanel/RoleEdit/index.tsx | 4 ++- src/server/routers/edge/upload.ts | 16 +++++++++ src/server/routers/index.ts | 13 +++++++ src/server/trpc.ts | 26 ++++++++++++++ 6 files changed, 97 insertions(+), 22 deletions(-) create mode 100644 src/libs/trpc/client.ts create mode 100644 src/server/routers/edge/upload.ts create mode 100644 src/server/routers/index.ts create mode 100644 src/server/trpc.ts diff --git a/package.json b/package.json index e7144fab..076b7065 100644 --- a/package.json +++ b/package.json @@ -62,23 +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", + "@gltf-transform/core": "^4.0.2", "@icons-pack/react-simple-icons": "^9.5.0", - "@lobehub/icons": "^1.22.1", + "@lobehub/icons": "^1.23.0", "@lobehub/tts": "^1.24.1", - "@lobehub/ui": "^1.141.3", + "@lobehub/ui": "^1.144.5", "@pixiv/three-vrm": "2.1.2", "@pixiv/three-vrm-core": "2.1.2", "@react-spring/web": "^9.7.3", + "@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", @@ -93,34 +96,37 @@ "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", - "openai": "^4.47.1", + "openai": "^4.52.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-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", + "ws": "^8.17.1", + "zod": "^3.23.8", "zustand": "^4.5.2" }, "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", @@ -132,19 +138,18 @@ "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", - "prettier": "^3.2.5", + "lint-staged": "^15.2.7", + "prettier": "^3.3.2", "remark": "^14.0.3", - "remark-cli": "^12.0.0", + "remark-cli": "^12.0.1", "semantic-release": "^24.0.0", "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" }, diff --git a/src/libs/trpc/client.ts b/src/libs/trpc/client.ts new file mode 100644 index 00000000..24eee2ef --- /dev/null +++ b/src/libs/trpc/client.ts @@ -0,0 +1,13 @@ +import { createTRPCClient, httpBatchLink } from '@trpc/client'; +import superjson from 'superjson'; + +import type { EdgeRouter } from '@/server/routers'; + +export const edgeClient = createTRPCClient({ + links: [ + httpBatchLink({ + transformer: superjson, + url: '/trpc/edge', + }), + ], +}); diff --git a/src/panels/RolePanel/RoleEdit/index.tsx b/src/panels/RolePanel/RoleEdit/index.tsx index 26216ce4..2c22fedd 100644 --- a/src/panels/RolePanel/RoleEdit/index.tsx +++ b/src/panels/RolePanel/RoleEdit/index.tsx @@ -4,6 +4,8 @@ import { TabsNav } from '@lobehub/ui'; import classNames from 'classnames'; import React, { useState } from 'react'; +import SubmitAgentButton from '@/features/Actions/SubmitAgentButton'; + import Info from './Info'; import Model from './Model'; import Role from './Role'; @@ -43,7 +45,7 @@ const RolePanel = (props: RolePanelProps) => { label: '3D 模型', }, ]} - // tabBarExtraContent={} + tabBarExtraContent={} onChange={(key) => { setTab(key); }} diff --git a/src/server/routers/edge/upload.ts b/src/server/routers/edge/upload.ts new file mode 100644 index 00000000..b238fd80 --- /dev/null +++ b/src/server/routers/edge/upload.ts @@ -0,0 +1,16 @@ +import { z } from 'zod'; + +import { S3 } from '@/server/s3'; +import { publicProcedure, router } from '@/server/trpc'; + +export const uploadRouter = router({ + createS3PreSignedUrl: publicProcedure + .input(z.object({ pathname: z.string() })) + .mutation(async ({ input }) => { + const s3 = new S3(); + + return await s3.createPreSignedUrl(input.pathname); + }), +}); + +export type FileRouter = typeof uploadRouter; diff --git a/src/server/routers/index.ts b/src/server/routers/index.ts new file mode 100644 index 00000000..37575c73 --- /dev/null +++ b/src/server/routers/index.ts @@ -0,0 +1,13 @@ +/** + * This file contains the root router of lobe chat tRPC-backend + */ +import { publicProcedure, router } from '@/server/trpc'; + +import { uploadRouter } from './edge/upload'; + +export const edgeRouter = router({ + healthcheck: publicProcedure.query(() => "i'm live!"), + upload: uploadRouter, +}); + +export type EdgeRouter = typeof edgeRouter; diff --git a/src/server/trpc.ts b/src/server/trpc.ts new file mode 100644 index 00000000..5310780c --- /dev/null +++ b/src/server/trpc.ts @@ -0,0 +1,26 @@ +import { initTRPC } from '@trpc/server'; +import superjson from 'superjson'; + +/** + * Initialization of tRPC backend + * Should be done only once per backend! + */ +const t = initTRPC.create({ + /** + * @link https://trpc.io/docs/v11/error-formatting + */ + errorFormatter({ shape }) { + return shape; + }, + /** + * @link https://trpc.io/docs/v11/data-transformers + */ + transformer: superjson, +}); + +/** + * Export reusable router and procedure helpers + * that can be used throughout the router + */ +export const router = t.router; +export const publicProcedure = t.procedure; From 73f149a7c1f7f3702584a54ab7be709bd62e5cf0 Mon Sep 17 00:00:00 2001 From: rdmclin2 Date: Tue, 25 Jun 2024 00:02:33 +0800 Subject: [PATCH 02/16] =?UTF-8?q?:sparkles:=20feat:=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=20trpc=20upload=20=E8=B0=83=E7=94=A8=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Actions/SubmitAgentButton/SubmitAgentModal.tsx | 5 +++-- src/features/vrmViewer/model.ts | 12 ++++++------ src/{utils => server}/s3.ts | 0 src/services/upload.ts | 9 ++++----- 4 files changed, 13 insertions(+), 13 deletions(-) rename src/{utils => server}/s3.ts (100%) diff --git a/src/features/Actions/SubmitAgentButton/SubmitAgentModal.tsx b/src/features/Actions/SubmitAgentButton/SubmitAgentModal.tsx index d03626fd..2b385e99 100644 --- a/src/features/Actions/SubmitAgentButton/SubmitAgentModal.tsx +++ b/src/features/Actions/SubmitAgentButton/SubmitAgentModal.tsx @@ -23,6 +23,7 @@ const SubmitAgentModal = memo(({ open, onCancel }) => { const isFormPass = Boolean( currentAgent.greeting && + currentAgent.systemRole && meta.name && meta.description && meta.avatar && @@ -39,11 +40,11 @@ const SubmitAgentModal = memo(({ open, onCancel }) => { const mime = arr[0].match(/:(.*?);/)[1]; const uint8Array = Uint8Array.from(binaryString, (char) => char.charCodeAt(0)); // base64 - const { success, url } = await upload( + const { success, pathname } = await upload( new File([uint8Array], `${agentId}-avatar.png`, { type: mime }), ); if (success) { - avatarUrl = url; + avatarUrl = pathname; } } const body = [ diff --git a/src/features/vrmViewer/model.ts b/src/features/vrmViewer/model.ts index 1e098eb9..3d8303ca 100644 --- a/src/features/vrmViewer/model.ts +++ b/src/features/vrmViewer/model.ts @@ -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'; diff --git a/src/utils/s3.ts b/src/server/s3.ts similarity index 100% rename from src/utils/s3.ts rename to src/server/s3.ts diff --git a/src/services/upload.ts b/src/services/upload.ts index b2f7b253..49257421 100644 --- a/src/services/upload.ts +++ b/src/services/upload.ts @@ -1,6 +1,6 @@ import dayjs from 'dayjs'; -import { S3 } from '@/utils/s3'; +import { edgeClient } from '@/libs/trpc/client'; import { uuid } from '@/utils/uuid'; export const upload = async (file: File) => { @@ -8,10 +8,9 @@ export const upload = async (file: File) => { const folderName = `files/${dateFolder}`; // e.g., "uploads/2023-10-10" const fileName = `${uuid()}.${file.name.split('.').at(-1)}`; - const key = `${folderName}/${fileName}`; + const pathname = `${folderName}/${fileName}`; - const s3 = new S3(); - const url = await s3.createPreSignedUrl(key); + const url = await edgeClient.upload.createS3PreSignedUrl.mutate({ pathname }); const res = await fetch(url, { body: file.stream(), @@ -22,7 +21,7 @@ export const upload = async (file: File) => { }); if (res.ok) { - return { success: true, message: 'File uploaded successfully', url: key }; + return { success: true, message: 'File uploaded successfully', pathname: pathname }; } else { throw new Error('Upload Error'); } From 29418239258196708372a839eeab1cc89a829fab Mon Sep 17 00:00:00 2001 From: rdmclin2 Date: Tue, 25 Jun 2024 00:09:54 +0800 Subject: [PATCH 03/16] =?UTF-8?q?:sparkles:=20feat:=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=20trpc=20upload=20=E8=B0=83=E7=94=A8=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .eslintignore | 4 +- .../VRMAnimationLoaderPluginOptions.ts | 2 - src/lib/VRMAnimation/utils/linearstep.ts | 5 -- src/lib/VRMAnimation/utils/saturate.ts | 1 - .../VMDAnimation/loadVMDAnimation.ts | 0 .../VMDAnimation/vmd2vrmanim.binding.ts | 52 +++++++++++-------- src/{lib => libs}/VMDAnimation/vmd2vrmanim.ts | 1 + .../VMDAnimation/vrm-ik-handler.ts | 3 +- .../VRMAnimation/VRMAnimation.ts | 0 .../VRMAnimation/VRMAnimationLoaderPlugin.ts | 1 + .../VRMAnimationLoaderPluginOptions.ts | 1 + .../VRMAnimation/VRMCVRMAnimation.ts | 0 .../VRMAnimation/loadVRMAnimation.ts | 1 + .../VRMAnimation/utils/arrayChunk.ts | 2 +- .../VRMLookAtSmoother.ts | 33 ++++-------- .../VRMLookAtSmootherLoaderPlugin.ts | 13 ++--- 16 files changed, 54 insertions(+), 65 deletions(-) delete mode 100644 src/lib/VRMAnimation/VRMAnimationLoaderPluginOptions.ts delete mode 100644 src/lib/VRMAnimation/utils/linearstep.ts delete mode 100644 src/lib/VRMAnimation/utils/saturate.ts rename src/{lib => libs}/VMDAnimation/loadVMDAnimation.ts (100%) rename src/{lib => libs}/VMDAnimation/vmd2vrmanim.binding.ts (95%) rename src/{lib => libs}/VMDAnimation/vmd2vrmanim.ts (99%) rename src/{lib => libs}/VMDAnimation/vrm-ik-handler.ts (99%) rename src/{lib => libs}/VRMAnimation/VRMAnimation.ts (100%) rename src/{lib => libs}/VRMAnimation/VRMAnimationLoaderPlugin.ts (99%) create mode 100644 src/libs/VRMAnimation/VRMAnimationLoaderPluginOptions.ts rename src/{lib => libs}/VRMAnimation/VRMCVRMAnimation.ts (100%) rename src/{lib => libs}/VRMAnimation/loadVRMAnimation.ts (99%) rename src/{lib => libs}/VRMAnimation/utils/arrayChunk.ts (93%) rename src/{lib => libs}/VRMLookAtSmootherLoaderPlugin/VRMLookAtSmoother.ts (86%) rename src/{lib => libs}/VRMLookAtSmootherLoaderPlugin/VRMLookAtSmootherLoaderPlugin.ts (68%) diff --git a/.eslintignore b/.eslintignore index b2452033..c083e2fc 100644 --- a/.eslintignore +++ b/.eslintignore @@ -29,4 +29,6 @@ logs # misc # add other ignore file below -.next \ No newline at end of file +.next + +libs diff --git a/src/lib/VRMAnimation/VRMAnimationLoaderPluginOptions.ts b/src/lib/VRMAnimation/VRMAnimationLoaderPluginOptions.ts deleted file mode 100644 index 681da88d..00000000 --- a/src/lib/VRMAnimation/VRMAnimationLoaderPluginOptions.ts +++ /dev/null @@ -1,2 +0,0 @@ -export interface VRMAnimationLoaderPluginOptions { -} diff --git a/src/lib/VRMAnimation/utils/linearstep.ts b/src/lib/VRMAnimation/utils/linearstep.ts deleted file mode 100644 index 02d59c18..00000000 --- a/src/lib/VRMAnimation/utils/linearstep.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { saturate } from './saturate'; - -export const linearstep = (a: number, b: number, t: number) => ( - saturate((t - a) / (b - a)) -); diff --git a/src/lib/VRMAnimation/utils/saturate.ts b/src/lib/VRMAnimation/utils/saturate.ts deleted file mode 100644 index a94b00fc..00000000 --- a/src/lib/VRMAnimation/utils/saturate.ts +++ /dev/null @@ -1 +0,0 @@ -export const saturate = (x: number) => Math.min(Math.max(x, 0.0), 1.0); diff --git a/src/lib/VMDAnimation/loadVMDAnimation.ts b/src/libs/VMDAnimation/loadVMDAnimation.ts similarity index 100% rename from src/lib/VMDAnimation/loadVMDAnimation.ts rename to src/libs/VMDAnimation/loadVMDAnimation.ts diff --git a/src/lib/VMDAnimation/vmd2vrmanim.binding.ts b/src/libs/VMDAnimation/vmd2vrmanim.binding.ts similarity index 95% rename from src/lib/VMDAnimation/vmd2vrmanim.binding.ts rename to src/libs/VMDAnimation/vmd2vrmanim.binding.ts index 31413dbd..ca0c9490 100644 --- a/src/lib/VMDAnimation/vmd2vrmanim.binding.ts +++ b/src/libs/VMDAnimation/vmd2vrmanim.binding.ts @@ -8,6 +8,7 @@ import { Vector3, VectorKeyframeTrack, } from 'three'; + import { convert as convertSync } from './vmd2vrmanim'; import VRMIKHandler from './vrm-ik-handler'; @@ -17,23 +18,36 @@ export interface AnimationData { } export interface Timeline { - name: VRMHumanBoneName | VRMExpressionPresetName; - type: string; isIK?: boolean; + name: VRMHumanBoneName | VRMExpressionPresetName; times: number[]; + type: string; values: number[]; } export interface VRMOffsets { hipsOffset?: number[]; leftFootOffset?: number[]; - rightFootOffset?: number[]; leftToeOffset?: number[]; + rightFootOffset?: number[]; rightToeOffset?: number[]; } -export function convert(buffer: ArrayBufferLike, vrm?: VRM) { - return convertSync(buffer, vrm ? toOffset(vrm) : undefined); +const tempV3 = new Vector3(); + +function calculatePosition(from?: Object3D | null, to?: Object3D | null) { + if (!from || !to) return; + let current: Object3D | null = to; + const chain: Object3D[] = [to]; + while (current.parent && current !== from) { + chain.push(current.parent); + current = current.parent; + } + if (current === null) return; + chain.reverse(); + const position = tempV3.set(0, 0, 0); + for (const node of chain) position.add(node.position); + return position.toArray(); } export function toOffset(vrm: VRM): VRMOffsets { @@ -56,20 +70,8 @@ export function toOffset(vrm: VRM): VRMOffsets { }; } -const tempV3 = new Vector3(); -function calculatePosition(from?: Object3D | null, to?: Object3D | null) { - if (!from || !to) return; - let current: Object3D | null = to; - const chain: Object3D[] = [to]; - while (current.parent && current !== from) { - chain.push(current.parent); - current = current.parent; - } - if (current == null) return; - chain.reverse(); - const position = tempV3.set(0, 0, 0); - for (const node of chain) position.add(node.position); - return position.toArray(); +export function convert(buffer: ArrayBufferLike, vrm?: VRM) { + return convertSync(buffer, vrm ? toOffset(vrm) : undefined); } export function bindToVRM(data: AnimationData, vrm: VRM) { @@ -97,19 +99,23 @@ export function bindToVRM(data: AnimationData, vrm: VRM) { } break; } - default: + default: { continue; + } } switch (type) { - case 'morph': + case 'morph': { tracks.push(new NumberKeyframeTrack(srcName, times, values)); break; - case 'position': + } + case 'position': { tracks.push(new VectorKeyframeTrack(`${srcName}.position`, times, values)); break; - case 'rotation': + } + case 'rotation': { tracks.push(new QuaternionKeyframeTrack(`${srcName}.quaternion`, times, values)); break; + } } } return new AnimationClip(`clip${Date.now()}`, data.duration, tracks); diff --git a/src/lib/VMDAnimation/vmd2vrmanim.ts b/src/libs/VMDAnimation/vmd2vrmanim.ts similarity index 99% rename from src/lib/VMDAnimation/vmd2vrmanim.ts rename to src/libs/VMDAnimation/vmd2vrmanim.ts index a02dfd38..94164a40 100644 --- a/src/lib/VMDAnimation/vmd2vrmanim.ts +++ b/src/libs/VMDAnimation/vmd2vrmanim.ts @@ -4,6 +4,7 @@ import { } from '@pixiv/three-vrm-core'; import { CharsetEncoder, Parser, VmdFile } from 'mmd-parser'; import { MathUtils, Quaternion, Vector3 } from 'three'; + import { AnimationData, Timeline, VRMOffsets } from './vmd2vrmanim.binding'; export function isTruely(x: T): x is Exclude { diff --git a/src/lib/VMDAnimation/vrm-ik-handler.ts b/src/libs/VMDAnimation/vrm-ik-handler.ts similarity index 99% rename from src/lib/VMDAnimation/vrm-ik-handler.ts rename to src/libs/VMDAnimation/vrm-ik-handler.ts index fa9b2fa1..6d089c35 100644 --- a/src/lib/VMDAnimation/vrm-ik-handler.ts +++ b/src/libs/VMDAnimation/vrm-ik-handler.ts @@ -1,7 +1,8 @@ -import { clampVector3ByRadian } from '@/utils/three-helpers'; import { VRM, VRMHumanBoneName } from '@pixiv/three-vrm'; import { Bone, MathUtils, Object3D, Quaternion, Vector3 } from 'three'; +import { clampVector3ByRadian } from '@/utils/three-helpers'; + const BoneNames = VRMHumanBoneName; const boneNameOrder: VRMHumanBoneName[] = [ BoneNames.Chest, diff --git a/src/lib/VRMAnimation/VRMAnimation.ts b/src/libs/VRMAnimation/VRMAnimation.ts similarity index 100% rename from src/lib/VRMAnimation/VRMAnimation.ts rename to src/libs/VRMAnimation/VRMAnimation.ts diff --git a/src/lib/VRMAnimation/VRMAnimationLoaderPlugin.ts b/src/libs/VRMAnimation/VRMAnimationLoaderPlugin.ts similarity index 99% rename from src/lib/VRMAnimation/VRMAnimationLoaderPlugin.ts rename to src/libs/VRMAnimation/VRMAnimationLoaderPlugin.ts index 47f1e2b6..74afdde1 100644 --- a/src/lib/VRMAnimation/VRMAnimationLoaderPlugin.ts +++ b/src/libs/VRMAnimation/VRMAnimationLoaderPlugin.ts @@ -2,6 +2,7 @@ import { GLTF as GLTFSchema } from '@gltf-transform/core'; import { VRMHumanBoneName, VRMHumanBoneParentMap } from '@pixiv/three-vrm'; import * as THREE from 'three'; import { GLTF, GLTFLoaderPlugin, GLTFParser } from 'three/examples/jsm/loaders/GLTFLoader'; + import { VRMAnimation } from './VRMAnimation'; import { VRMAnimationLoaderPluginOptions } from './VRMAnimationLoaderPluginOptions'; import { VRMCVRMAnimation } from './VRMCVRMAnimation'; diff --git a/src/libs/VRMAnimation/VRMAnimationLoaderPluginOptions.ts b/src/libs/VRMAnimation/VRMAnimationLoaderPluginOptions.ts new file mode 100644 index 00000000..224b49f3 --- /dev/null +++ b/src/libs/VRMAnimation/VRMAnimationLoaderPluginOptions.ts @@ -0,0 +1 @@ +export interface VRMAnimationLoaderPluginOptions {} diff --git a/src/lib/VRMAnimation/VRMCVRMAnimation.ts b/src/libs/VRMAnimation/VRMCVRMAnimation.ts similarity index 100% rename from src/lib/VRMAnimation/VRMCVRMAnimation.ts rename to src/libs/VRMAnimation/VRMCVRMAnimation.ts diff --git a/src/lib/VRMAnimation/loadVRMAnimation.ts b/src/libs/VRMAnimation/loadVRMAnimation.ts similarity index 99% rename from src/lib/VRMAnimation/loadVRMAnimation.ts rename to src/libs/VRMAnimation/loadVRMAnimation.ts index 4fa4f12d..1285deca 100644 --- a/src/lib/VRMAnimation/loadVRMAnimation.ts +++ b/src/libs/VRMAnimation/loadVRMAnimation.ts @@ -1,4 +1,5 @@ import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'; + import { VRMAnimation } from './VRMAnimation'; import { VRMAnimationLoaderPlugin } from './VRMAnimationLoaderPlugin'; diff --git a/src/lib/VRMAnimation/utils/arrayChunk.ts b/src/libs/VRMAnimation/utils/arrayChunk.ts similarity index 93% rename from src/lib/VRMAnimation/utils/arrayChunk.ts rename to src/libs/VRMAnimation/utils/arrayChunk.ts index d96b34e1..ab5a845b 100644 --- a/src/lib/VRMAnimation/utils/arrayChunk.ts +++ b/src/libs/VRMAnimation/utils/arrayChunk.ts @@ -13,7 +13,7 @@ export function arrayChunk(array: ArrayLike, every: number): T[][] { let current: T[] = []; let remaining = 0; - for (let i = 0; i < N; i ++) { + for (let i = 0; i < N; i++) { const el = array[i]; if (remaining <= 0) { diff --git a/src/lib/VRMLookAtSmootherLoaderPlugin/VRMLookAtSmoother.ts b/src/libs/VRMLookAtSmootherLoaderPlugin/VRMLookAtSmoother.ts similarity index 86% rename from src/lib/VRMLookAtSmootherLoaderPlugin/VRMLookAtSmoother.ts rename to src/libs/VRMLookAtSmootherLoaderPlugin/VRMLookAtSmoother.ts index a36efd88..a83b5627 100644 --- a/src/lib/VRMLookAtSmootherLoaderPlugin/VRMLookAtSmoother.ts +++ b/src/libs/VRMLookAtSmootherLoaderPlugin/VRMLookAtSmoother.ts @@ -1,5 +1,5 @@ -import { VRMHumanoid, VRMLookAt, VRMLookAtApplier } from "@pixiv/three-vrm"; -import * as THREE from "three"; +import { VRMHumanoid, VRMLookAt, VRMLookAtApplier } from '@pixiv/three-vrm'; +import * as THREE from 'three'; /** サッケードが発生するまでの最小間隔 */ const SACCADE_MIN_INTERVAL = 0.5; @@ -98,35 +98,25 @@ export class VRMLookAtSmoother extends VRMLookAt { const userRatio = 1.0 - THREE.MathUtils.smoothstep( - Math.sqrt( - yawAnimation * yawAnimation + pitchAnimation * pitchAnimation - ), + Math.sqrt(yawAnimation * yawAnimation + pitchAnimation * pitchAnimation), 30.0, - 90.0 + 90.0, ); // yawFrame / pitchFrame に結果を代入 - yawFrame = THREE.MathUtils.lerp( - yawAnimation, - 0.6 * this._yawDamped, - userRatio - ); - pitchFrame = THREE.MathUtils.lerp( - pitchAnimation, - 0.6 * this._pitchDamped, - userRatio - ); + yawFrame = THREE.MathUtils.lerp(yawAnimation, 0.6 * this._yawDamped, userRatio); + pitchFrame = THREE.MathUtils.lerp(pitchAnimation, 0.6 * this._pitchDamped, userRatio); // 頭も回す _eulerA.set( -this._pitchDamped * THREE.MathUtils.DEG2RAD, this._yawDamped * THREE.MathUtils.DEG2RAD, 0.0, - VRMLookAt.EULER_ORDER + VRMLookAt.EULER_ORDER, ); _quatA.setFromEuler(_eulerA); - const head = this.humanoid.getRawBoneNode("head")!; + const head = this.humanoid.getRawBoneNode('head')!; this._tempFirstPersonBoneQuat.copy(head.quaternion); head.quaternion.slerp(_quatA, 0.4); head.updateMatrixWorld(); @@ -134,10 +124,7 @@ export class VRMLookAtSmoother extends VRMLookAt { if (this.enableSaccade) { // サッケードの移動方向を計算 - if ( - SACCADE_MIN_INTERVAL < this._saccadeTimer && - Math.random() < SACCADE_PROC - ) { + if (SACCADE_MIN_INTERVAL < this._saccadeTimer && Math.random() < SACCADE_PROC) { this._saccadeYaw = (2.0 * Math.random() - 1.0) * SACCADE_RADIUS; this._saccadePitch = (2.0 * Math.random() - 1.0) * SACCADE_RADIUS; this._saccadeTimer = 0.0; @@ -167,7 +154,7 @@ export class VRMLookAtSmoother extends VRMLookAt { /** renderしたあとに叩いて頭の回転をもとに戻す */ public revertFirstPersonBoneQuat(): void { if (this.userTarget) { - const head = this.humanoid.getNormalizedBoneNode("head")!; + const head = this.humanoid.getNormalizedBoneNode('head')!; head.quaternion.copy(this._tempFirstPersonBoneQuat); } } diff --git a/src/lib/VRMLookAtSmootherLoaderPlugin/VRMLookAtSmootherLoaderPlugin.ts b/src/libs/VRMLookAtSmootherLoaderPlugin/VRMLookAtSmootherLoaderPlugin.ts similarity index 68% rename from src/lib/VRMLookAtSmootherLoaderPlugin/VRMLookAtSmootherLoaderPlugin.ts rename to src/libs/VRMLookAtSmootherLoaderPlugin/VRMLookAtSmootherLoaderPlugin.ts index bba26c97..5eb85905 100644 --- a/src/lib/VRMLookAtSmootherLoaderPlugin/VRMLookAtSmootherLoaderPlugin.ts +++ b/src/libs/VRMLookAtSmootherLoaderPlugin/VRMLookAtSmootherLoaderPlugin.ts @@ -1,14 +1,11 @@ -import { - VRMHumanoid, - VRMLookAt, - VRMLookAtLoaderPlugin, -} from "@pixiv/three-vrm"; -import { GLTF } from "three/examples/jsm/loaders/GLTFLoader"; -import { VRMLookAtSmoother } from "./VRMLookAtSmoother"; +import { VRMHumanoid, VRMLookAt, VRMLookAtLoaderPlugin } from '@pixiv/three-vrm'; +import { GLTF } from 'three/examples/jsm/loaders/GLTFLoader'; + +import { VRMLookAtSmoother } from './VRMLookAtSmoother'; export class VRMLookAtSmootherLoaderPlugin extends VRMLookAtLoaderPlugin { public get name(): string { - return "VRMLookAtSmootherLoaderPlugin"; + return 'VRMLookAtSmootherLoaderPlugin'; } public async afterRoot(gltf: GLTF): Promise { From 66fad94dde8abf37fd11742710c0b4a75a7eb074 Mon Sep 17 00:00:00 2001 From: rdmclin2 Date: Tue, 25 Jun 2024 22:53:19 +0800 Subject: [PATCH 04/16] =?UTF-8?q?:sparkles:=20feat:=20=E6=88=90=E5=8A=9F?= =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E6=96=87=E4=BB=B6=E5=88=B0=20S3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 + src/app/trpc/edge/[trpc]/route.ts | 22 ++++++++++++++++++++++ src/libs/logger/index.ts | 5 +++++ src/server/s3.ts | 3 --- src/services/upload.ts | 9 ++++++--- 5 files changed, 34 insertions(+), 6 deletions(-) create mode 100644 src/app/trpc/edge/[trpc]/route.ts create mode 100644 src/libs/logger/index.ts diff --git a/package.json b/package.json index 076b7065..383645a2 100644 --- a/package.json +++ b/package.json @@ -99,6 +99,7 @@ "next": "^14.2.4", "next-pwa": "^5.6.0", "openai": "^4.52.0", + "pino": "^9.2.0", "polished": "^4.3.1", "query-string": "^9.0.0", "react": "^18.3.1", diff --git a/src/app/trpc/edge/[trpc]/route.ts b/src/app/trpc/edge/[trpc]/route.ts new file mode 100644 index 00000000..89b84244 --- /dev/null +++ b/src/app/trpc/edge/[trpc]/route.ts @@ -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 }; diff --git a/src/libs/logger/index.ts b/src/libs/logger/index.ts new file mode 100644 index 00000000..6bf5148d --- /dev/null +++ b/src/libs/logger/index.ts @@ -0,0 +1,5 @@ +import Pino from 'pino'; + +export const pino = Pino({ + level: process.env.LOG_LEVEL ? process.env.LOG_LEVEL : 'info', +}); diff --git a/src/server/s3.ts b/src/server/s3.ts index f8ecadc4..7d9f8ccf 100644 --- a/src/server/s3.ts +++ b/src/server/s3.ts @@ -21,9 +21,6 @@ export class S3 { }); } - public getClient() { - return this.client; - } public async createPreSignedUrl(key: string): Promise { const command = new PutObjectCommand({ ACL: 'public-read', diff --git a/src/services/upload.ts b/src/services/upload.ts index 49257421..b34c10a3 100644 --- a/src/services/upload.ts +++ b/src/services/upload.ts @@ -5,19 +5,22 @@ import { uuid } from '@/utils/uuid'; export const upload = async (file: File) => { const dateFolder = dayjs().format('YYYY/MM/DD'); // 使用当前日期作为文件夹名称 - const folderName = `files/${dateFolder}`; // e.g., "uploads/2023-10-10" + const folderName = `files/${dateFolder}`; // e.g., "files/2023/10/10" const fileName = `${uuid()}.${file.name.split('.').at(-1)}`; const pathname = `${folderName}/${fileName}`; const url = await edgeClient.upload.createS3PreSignedUrl.mutate({ pathname }); + const formData = new FormData(); + formData.append('file', file); + const res = await fetch(url, { - body: file.stream(), + body: file, headers: { 'Content-Type': file.type, }, - method: 'POST', + method: 'PUT', }); if (res.ok) { From 5291b9740b519e237b938878df3f6da2a638678d Mon Sep 17 00:00:00 2001 From: rdmclin2 Date: Tue, 25 Jun 2024 23:39:55 +0800 Subject: [PATCH 05/16] =?UTF-8?q?:sparkles:=20feat:=20=E4=B8=8A=E4=BC=A0?= =?UTF-8?q?=E5=B0=81=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/constants/agent.ts | 1 + .../SubmitAgentButton/SubmitAgentModal.tsx | 31 +++++++++++++++---- src/services/upload.ts | 6 +++- src/types/agent.ts | 2 +- 4 files changed, 32 insertions(+), 8 deletions(-) diff --git a/src/constants/agent.ts b/src/constants/agent.ts index b5cc21d5..b9a98ff7 100644 --- a/src/constants/agent.ts +++ b/src/constants/agent.ts @@ -39,6 +39,7 @@ export const DEFAULT_AGENT_CONFIG: Agent = { name: '自定义角色', description: '这是一个自定义角色', avatar: DEFAULT_AGENT_AVATAR_URL, + cover: '', gender: GenderEnum.FEMALE, readme: '', }, diff --git a/src/features/Actions/SubmitAgentButton/SubmitAgentModal.tsx b/src/features/Actions/SubmitAgentButton/SubmitAgentModal.tsx index 2b385e99..5568f143 100644 --- a/src/features/Actions/SubmitAgentButton/SubmitAgentModal.tsx +++ b/src/features/Actions/SubmitAgentButton/SubmitAgentModal.tsx @@ -14,11 +14,12 @@ import AgentCard from '@/components/agent/AgentCard'; import { AGENTS_INDEX_GITHUB_ISSUE } from '@/constants/url'; import { upload } from '@/services/upload'; import { agentSelectors, useAgentStore } from '@/store/agent'; +import { Agent } from '@/types/agent'; const SubmitAgentModal = memo(({ 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 isFormPass = Boolean( @@ -28,7 +29,8 @@ const SubmitAgentModal = memo(({ open, onCancel }) => { meta.description && meta.avatar && meta.cover && - meta.gender, + meta.gender && + meta.model, ); const handleSubmit = async () => { @@ -40,13 +42,30 @@ const SubmitAgentModal = memo(({ open, onCancel }) => { const mime = arr[0].match(/:(.*?);/)[1]; const uint8Array = Uint8Array.from(binaryString, (char) => char.charCodeAt(0)); // base64 - const { success, pathname } = await upload( + const { success, url } = await upload( new File([uint8Array], `${agentId}-avatar.png`, { type: mime }), ); if (success) { - avatarUrl = pathname; + avatarUrl = url; } } + + let coverUrl = meta.cover; + if (meta.cover.includes('base64')) { + const arr = meta.cover.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}-cover.png`, { type: mime }), + ); + if (success) { + coverUrl = url; + } + } + const body = [ '### systemRole', currentAgent.systemRole, @@ -55,7 +74,7 @@ const SubmitAgentModal = memo(({ open, onCancel }) => { '### avatar', avatarUrl, '### cover', - meta.cover, + coverUrl, '### name', meta.name, '### description', @@ -113,7 +132,7 @@ const SubmitAgentModal = memo(({ open, onCancel }) => { icon={} onClick={() => { const randomId = Math.random().toString(36).slice(7); - setAgentId(`vidol-agent-${randomId}`); + setAgentId(randomId); }} > diff --git a/src/services/upload.ts b/src/services/upload.ts index b34c10a3..1e1e1a67 100644 --- a/src/services/upload.ts +++ b/src/services/upload.ts @@ -24,7 +24,11 @@ export const upload = async (file: File) => { }); if (res.ok) { - return { success: true, message: 'File uploaded successfully', pathname: pathname }; + return { + success: true, + message: 'File uploaded successfully', + url: `https://r2.vidol.chat/${pathname}`, + }; } else { throw new Error('Upload Error'); } diff --git a/src/types/agent.ts b/src/types/agent.ts index 0398b44e..8d8bf67b 100644 --- a/src/types/agent.ts +++ b/src/types/agent.ts @@ -32,7 +32,7 @@ export interface AgentMeta { /** * 封面图片路径 */ - cover?: string; + cover: string; /** * 角色描述 */ From 3220cae92a097249d100573b4ce97479fd009eb9 Mon Sep 17 00:00:00 2001 From: rdmclin2 Date: Wed, 26 Jun 2024 23:49:33 +0800 Subject: [PATCH 06/16] feat: upload vrm file --- package.json | 1 + .../SubmitAgentButton/SubmitAgentModal.tsx | 45 +++++++++++++++---- src/services/upload.ts | 1 + src/utils/file.ts | 4 ++ 4 files changed, 43 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 383645a2..0ee82ee5 100644 --- a/package.json +++ b/package.json @@ -93,6 +93,7 @@ "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", diff --git a/src/features/Actions/SubmitAgentButton/SubmitAgentModal.tsx b/src/features/Actions/SubmitAgentButton/SubmitAgentModal.tsx index 5568f143..7a4deffe 100644 --- a/src/features/Actions/SubmitAgentButton/SubmitAgentModal.tsx +++ b/src/features/Actions/SubmitAgentButton/SubmitAgentModal.tsx @@ -6,19 +6,24 @@ import { useTheme } from 'antd-style'; import isEqual from 'fast-deep-equal'; import { kebabCase } from 'lodash-es'; import { Dices } from 'lucide-react'; +import mime from 'mime'; import qs from 'query-string'; import { 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 { agentSelectors, useAgentStore } from '@/store/agent'; import { Agent } from '@/types/agent'; +import { isLocalModelPath } from '@/utils/file'; +import storage from '@/utils/storage'; const SubmitAgentModal = memo(({ open, onCancel }) => { const [agentId, setAgentId] = useState(''); const theme = useTheme(); + const [loading, setLoading] = useState(false); const currentAgent: Agent = useAgentStore((s) => agentSelectors.currentAgentItem(s), isEqual); const { meta } = currentAgent; @@ -29,21 +34,23 @@ const SubmitAgentModal = memo(({ open, onCancel }) => { meta.description && meta.avatar && meta.cover && - meta.gender && meta.model, ); const handleSubmit = async () => { + setLoading(true); 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 mimeType = 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 }), + new File([uint8Array], `${agentId}-avatar.${mime.getExtension(mimeType)}`, { + type: mimeType, + }), ); if (success) { avatarUrl = url; @@ -55,26 +62,44 @@ const SubmitAgentModal = memo(({ open, onCancel }) => { const arr = meta.cover.split('base64,'); const binaryString = atob(arr[1]); // @ts-ignore - const mime = arr[0]?.match(/:(.*?);/)[1]; + const mimeType = arr[0]?.match(/:(.*?);/)[1]; const uint8Array = Uint8Array.from(binaryString, (char) => char.charCodeAt(0)); // base64 const { success, url } = await upload( - new File([uint8Array], `${agentId}-cover.png`, { type: mime }), + new File([uint8Array], `${agentId}-cover.${mime.getExtension(mimeType)}`, { + type: mimeType, + }), ); if (success) { coverUrl = url; } } + let modelUrl = meta.model; + // 本地模型上传 + if (modelUrl && isLocalModelPath(modelUrl)) { + const modelBlob = await storage.getItem(modelUrl); + if (modelBlob) { + const { success, url } = await upload( + new File([modelBlob], `${agentId}-model.vrm`, { type: 'application/octet-stream' }), + ); + if (success) { + modelUrl = url; + } + } + } + const body = [ '### systemRole', currentAgent.systemRole, '### agentId', - kebabCase(agentId), + agentId, '### avatar', avatarUrl, '### cover', coverUrl, + '### model', + modelUrl, '### name', meta.name, '### description', @@ -87,6 +112,7 @@ const SubmitAgentModal = memo(({ open, onCancel }) => { }); window.open(url, '_blank'); + setLoading(false); }; return ( @@ -99,6 +125,7 @@ const SubmitAgentModal = memo(({ open, onCancel }) => { onClick={handleSubmit} size={'large'} type={'primary'} + loading={loading} > 提交助手 @@ -110,13 +137,15 @@ const SubmitAgentModal = memo(({ open, onCancel }) => { {!isFormPass && ( )} + + * agentId 助手标识符 @@ -132,7 +161,7 @@ const SubmitAgentModal = memo(({ open, onCancel }) => { icon={} onClick={() => { const randomId = Math.random().toString(36).slice(7); - setAgentId(randomId); + setAgentId(kebabCase(randomId)); }} > diff --git a/src/services/upload.ts b/src/services/upload.ts index 1e1e1a67..f593737e 100644 --- a/src/services/upload.ts +++ b/src/services/upload.ts @@ -6,6 +6,7 @@ import { uuid } from '@/utils/uuid'; export const upload = async (file: File) => { const dateFolder = dayjs().format('YYYY/MM/DD'); // 使用当前日期作为文件夹名称 const folderName = `files/${dateFolder}`; // e.g., "files/2023/10/10" + console.log('filename', file.name); const fileName = `${uuid()}.${file.name.split('.').at(-1)}`; const pathname = `${folderName}/${fileName}`; diff --git a/src/utils/file.ts b/src/utils/file.ts index 651e4c2a..338c70e1 100644 --- a/src/utils/file.ts +++ b/src/utils/file.ts @@ -6,6 +6,10 @@ export const getModelPathByAgentId = (agentId: string) => { return `${MODEL_SCHEMA}://${agentId}`; }; +export const isLocalModelPath = (path: string) => { + return path.startsWith(MODEL_SCHEMA); +}; + export const getAudioPathByDanceId = (danceId: string) => { return `${AUDIO_SCHEMA}://${danceId}`; }; From 4b7d2889357cf50527e25e1c48439c241695be31 Mon Sep 17 00:00:00 2001 From: rdmclin2 Date: Tue, 2 Jul 2024 22:13:02 +0800 Subject: [PATCH 07/16] =?UTF-8?q?:sparkles:=20feat:=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=20required=20=E6=A0=87=E7=AD=BE=20&=20=E6=8A=BD=E5=8F=96=20bas?= =?UTF-8?q?e64ToFile=20=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SubmitAgentButton/SubmitAgentModal.tsx | 28 ++++------------- src/panels/RolePanel/RoleEdit/Info/index.tsx | 30 +++++++++++++++---- src/panels/RolePanel/RoleEdit/Model/index.tsx | 7 ++++- src/panels/RolePanel/RoleEdit/Role/index.tsx | 1 + src/utils/imageToBase64.ts | 13 ++++++++ 5 files changed, 50 insertions(+), 29 deletions(-) diff --git a/src/features/Actions/SubmitAgentButton/SubmitAgentModal.tsx b/src/features/Actions/SubmitAgentButton/SubmitAgentModal.tsx index 7a4deffe..bce53963 100644 --- a/src/features/Actions/SubmitAgentButton/SubmitAgentModal.tsx +++ b/src/features/Actions/SubmitAgentButton/SubmitAgentModal.tsx @@ -6,7 +6,6 @@ import { useTheme } from 'antd-style'; import isEqual from 'fast-deep-equal'; import { kebabCase } from 'lodash-es'; import { Dices } from 'lucide-react'; -import mime from 'mime'; import qs from 'query-string'; import { memo, useState } from 'react'; import { Flexbox } from 'react-layout-kit'; @@ -18,6 +17,7 @@ import { upload } from '@/services/upload'; import { agentSelectors, useAgentStore } from '@/store/agent'; import { Agent } from '@/types/agent'; import { isLocalModelPath } from '@/utils/file'; +import { base64ToFile } from '@/utils/imageToBase64'; import storage from '@/utils/storage'; const SubmitAgentModal = memo(({ open, onCancel }) => { @@ -41,17 +41,8 @@ const SubmitAgentModal = memo(({ open, onCancel }) => { setLoading(true); let avatarUrl = meta.avatar; if (meta.avatar.includes('base64')) { - const arr = meta.avatar.split('base64,'); - const binaryString = atob(arr[1]); - // @ts-ignore - const mimeType = arr[0].match(/:(.*?);/)[1]; - const uint8Array = Uint8Array.from(binaryString, (char) => char.charCodeAt(0)); - // base64 - const { success, url } = await upload( - new File([uint8Array], `${agentId}-avatar.${mime.getExtension(mimeType)}`, { - type: mimeType, - }), - ); + const file = base64ToFile(meta.avatar, `${agentId}-avatar`); + const { success, url } = await upload(file); if (success) { avatarUrl = url; } @@ -59,17 +50,8 @@ const SubmitAgentModal = memo(({ open, onCancel }) => { let coverUrl = meta.cover; if (meta.cover.includes('base64')) { - const arr = meta.cover.split('base64,'); - const binaryString = atob(arr[1]); - // @ts-ignore - const mimeType = arr[0]?.match(/:(.*?);/)[1]; - const uint8Array = Uint8Array.from(binaryString, (char) => char.charCodeAt(0)); - // base64 - const { success, url } = await upload( - new File([uint8Array], `${agentId}-cover.${mime.getExtension(mimeType)}`, { - type: mimeType, - }), - ); + const file = base64ToFile(meta.avatar, `${agentId}-cover`); + const { success, url } = await upload(file); if (success) { coverUrl = url; } diff --git a/src/panels/RolePanel/RoleEdit/Info/index.tsx b/src/panels/RolePanel/RoleEdit/Info/index.tsx index 8337fc05..072674c3 100644 --- a/src/panels/RolePanel/RoleEdit/Info/index.tsx +++ b/src/panels/RolePanel/RoleEdit/Info/index.tsx @@ -52,33 +52,52 @@ const Info = (props: InfoProps) => { const [form] = Form.useForm(); return ( -
+
- + - + - + { diff --git a/src/panels/RolePanel/RoleEdit/Model/index.tsx b/src/panels/RolePanel/RoleEdit/Model/index.tsx index c13c0e51..d859f1fe 100644 --- a/src/panels/RolePanel/RoleEdit/Model/index.tsx +++ b/src/panels/RolePanel/RoleEdit/Model/index.tsx @@ -43,7 +43,12 @@ const Model = (props: ModelProps) => {
- +
diff --git a/src/panels/RolePanel/RoleEdit/Role/index.tsx b/src/panels/RolePanel/RoleEdit/Role/index.tsx index 1e849695..fb1217b7 100644 --- a/src/panels/RolePanel/RoleEdit/Role/index.tsx +++ b/src/panels/RolePanel/RoleEdit/Role/index.tsx @@ -36,6 +36,7 @@ const Info = (props: InfoProps) => { diff --git a/src/utils/imageToBase64.ts b/src/utils/imageToBase64.ts index 2bffe43f..4ca1ce43 100644 --- a/src/utils/imageToBase64.ts +++ b/src/utils/imageToBase64.ts @@ -1,3 +1,5 @@ +import mime from 'mime'; + export const imageToBase64 = ({ size, img, @@ -81,3 +83,14 @@ export const blobToDataURI = (blob: Blob) => { reader.readAsDataURL(blob); }); }; + +export const base64ToFile = (base64: string, fileName: string) => { + const arr = base64.split('base64,'); + const binaryString = atob(arr[1]); + // @ts-ignore + const mimeType = arr[0].match(/:(.*?);/)[1]; + const uint8Array = Uint8Array.from(binaryString, (char) => char.charCodeAt(0)); + return new File([uint8Array], `${fileName}.${mime.getExtension(mimeType)}`, { + type: mimeType, + }); +}; From 8c7112e07b2ff6143bf6126a6a9f7a91d2d877dc Mon Sep 17 00:00:00 2001 From: rdmclin2 Date: Tue, 2 Jul 2024 23:51:49 +0800 Subject: [PATCH 08/16] =?UTF-8?q?:sparkles:=20feat:=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=20useUploadAgent=20=E6=96=B9=E6=B3=95=E4=B8=8A=E4=BC=A0?= =?UTF-8?q?=E8=A7=92=E8=89=B2=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SubmitAgentButton/SubmitAgentModal.tsx | 84 ++++++-------- src/hooks/useUploadAgent.tsx | 104 ++++++++++++++++++ src/services/upload.ts | 29 ++--- 3 files changed, 154 insertions(+), 63 deletions(-) create mode 100644 src/hooks/useUploadAgent.tsx diff --git a/src/features/Actions/SubmitAgentButton/SubmitAgentModal.tsx b/src/features/Actions/SubmitAgentButton/SubmitAgentModal.tsx index bce53963..91a03df1 100644 --- a/src/features/Actions/SubmitAgentButton/SubmitAgentModal.tsx +++ b/src/features/Actions/SubmitAgentButton/SubmitAgentModal.tsx @@ -1,32 +1,30 @@ '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 } 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'; -import { isLocalModelPath } from '@/utils/file'; -import { base64ToFile } from '@/utils/imageToBase64'; -import storage from '@/utils/storage'; const SubmitAgentModal = memo(({ open, onCancel }) => { const [agentId, setAgentId] = useState(''); const theme = useTheme(); - const [loading, setLoading] = useState(false); const currentAgent: Agent = useAgentStore((s) => agentSelectors.currentAgentItem(s), isEqual); const { meta } = currentAgent; + const { uploading, uploadAgentData, percent } = useUploadAgent(); + const isFormPass = Boolean( currentAgent.greeting && currentAgent.systemRole && @@ -38,38 +36,7 @@ const SubmitAgentModal = memo(({ open, onCancel }) => { ); const handleSubmit = async () => { - setLoading(true); - let avatarUrl = meta.avatar; - if (meta.avatar.includes('base64')) { - const file = base64ToFile(meta.avatar, `${agentId}-avatar`); - const { success, url } = await upload(file); - if (success) { - avatarUrl = url; - } - } - - let coverUrl = meta.cover; - if (meta.cover.includes('base64')) { - const file = base64ToFile(meta.avatar, `${agentId}-cover`); - const { success, url } = await upload(file); - if (success) { - coverUrl = url; - } - } - - let modelUrl = meta.model; - // 本地模型上传 - if (modelUrl && isLocalModelPath(modelUrl)) { - const modelBlob = await storage.getItem(modelUrl); - if (modelBlob) { - const { success, url } = await upload( - new File([modelBlob], `${agentId}-model.vrm`, { type: 'application/octet-stream' }), - ); - if (success) { - modelUrl = url; - } - } - } + const { avatarUrl, coverUrl, modelUrl } = await uploadAgentData(agentId, meta); const body = [ '### systemRole', @@ -94,23 +61,42 @@ const SubmitAgentModal = memo(({ open, onCancel }) => { }); window.open(url, '_blank'); - setLoading(false); }; return ( + + + 上传封面 + + + + 上传头像 + + + + 上传模型 + + + } > - 提交助手 - + + } onCancel={onCancel} open={open} diff --git a/src/hooks/useUploadAgent.tsx b/src/hooks/useUploadAgent.tsx new file mode 100644 index 00000000..cb2885ba --- /dev/null +++ b/src/hooks/useUploadAgent.tsx @@ -0,0 +1,104 @@ +import { useState } from 'react'; + +import { upload } from '@/services/upload'; +import { AgentMeta } from '@/types/agent'; +import { isLocalModelPath } from '@/utils/file'; +import { base64ToFile } from '@/utils/imageToBase64'; +import storage from '@/utils/storage'; + +export const useUploadAgent = () => { + const [uploading, setUploading] = useState(false); + const [avatarProgress, setAvatarProgress] = useState(0); + const [coverProgress, setCoverProgress] = useState(0); + const [modelProgress, setModelProgress] = useState(0); + + const uploadAgentData = async (agentId: string, meta: AgentMeta) => { + setUploading(true); + setAvatarProgress(0); + setCoverProgress(0); + setModelProgress(0); + + const avatarPromise = new Promise((resolve) => { + const avatarUrl = meta.avatar; + if (meta.avatar.includes('base64')) { + const file = base64ToFile(meta.avatar, `${agentId}-avatar`); + upload(file, { + onProgress: (loaded: number, total: number) => { + setAvatarProgress(Math.ceil((loaded / total) * 100)); + }, + }) + .then((url) => resolve(url)) + .catch(() => resolve(avatarUrl)); + } else { + resolve(avatarUrl); + } + }); + + const coverPromise = new Promise((resolve) => { + const coverUrl = meta.cover; + if (meta.cover.includes('base64')) { + const file = base64ToFile(meta.cover, `${agentId}-cover`); + upload(file, { + onProgress: (loaded: number, total: number) => { + setCoverProgress(Math.ceil((loaded / total) * 100)); + }, + }) + .then((url) => resolve(url)) + .catch(() => resolve(coverUrl)); + } else { + resolve(coverUrl); + } + }); + + const modelPromise = new Promise((resolve) => { + const modelUrl = meta.model; + if (modelUrl && isLocalModelPath(modelUrl)) { + storage + .getItem(modelUrl) + .then((modelBlob) => { + if (modelBlob) { + upload( + new File([modelBlob], `${agentId}-model.vrm`, { type: 'application/octet-stream' }), + { + onProgress: (loaded: number, total: number) => { + setModelProgress(Math.ceil((loaded / total) * 100)); + }, + }, + ) + .then((url) => resolve(url)) + .catch(() => resolve(modelUrl)); + } else { + resolve(modelUrl); + } + }) + .catch(() => resolve(modelUrl)); + } else { + resolve(modelUrl); + } + }); + + try { + const [avatarUrl, coverUrl, modelUrl] = await Promise.all([ + avatarPromise, + coverPromise, + modelPromise, + ]); + return { avatarUrl, coverUrl, modelUrl }; + } catch (e) { + console.error(e); + return { avatarUrl: '', coverUrl: '', modelUrl: '' }; + } finally { + setUploading(false); + } + }; + + return { + uploading: uploading, + percent: { + avatar: avatarProgress, + cover: coverProgress, + model: modelProgress, + }, + uploadAgentData, + }; +}; diff --git a/src/services/upload.ts b/src/services/upload.ts index f593737e..60ad9170 100644 --- a/src/services/upload.ts +++ b/src/services/upload.ts @@ -1,12 +1,17 @@ +import axios from 'axios'; import dayjs from 'dayjs'; import { edgeClient } from '@/libs/trpc/client'; import { uuid } from '@/utils/uuid'; -export const upload = async (file: File) => { +export const upload = async ( + file: File, + handlers: { + onProgress?: (loaded: number, total: number) => void; + }, +) => { const dateFolder = dayjs().format('YYYY/MM/DD'); // 使用当前日期作为文件夹名称 const folderName = `files/${dateFolder}`; // e.g., "files/2023/10/10" - console.log('filename', file.name); const fileName = `${uuid()}.${file.name.split('.').at(-1)}`; const pathname = `${folderName}/${fileName}`; @@ -16,21 +21,17 @@ export const upload = async (file: File) => { const formData = new FormData(); formData.append('file', file); - const res = await fetch(url, { - body: file, + await axios.put(url, formData, { headers: { 'Content-Type': file.type, }, - method: 'PUT', + onUploadProgress: (e) => { + if (e.lengthComputable) { + console.log(e.loaded, e.total); + handlers.onProgress?.(e.loaded, e.total || file.size); + } + }, }); - if (res.ok) { - return { - success: true, - message: 'File uploaded successfully', - url: `https://r2.vidol.chat/${pathname}`, - }; - } else { - throw new Error('Upload Error'); - } + return `https://r2.vidol.chat/${pathname}`; }; From 7c2822cc756126a45f63605eb39d3768e35be7d6 Mon Sep 17 00:00:00 2001 From: rdmclin2 Date: Wed, 3 Jul 2024 00:07:09 +0800 Subject: [PATCH 09/16] =?UTF-8?q?:sparkles:=20fix:=20=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useUploadAgent.tsx | 12 ++++++------ src/services/upload.ts | 9 +++------ 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/hooks/useUploadAgent.tsx b/src/hooks/useUploadAgent.tsx index cb2885ba..13b0bfbd 100644 --- a/src/hooks/useUploadAgent.tsx +++ b/src/hooks/useUploadAgent.tsx @@ -23,8 +23,8 @@ export const useUploadAgent = () => { if (meta.avatar.includes('base64')) { const file = base64ToFile(meta.avatar, `${agentId}-avatar`); upload(file, { - onProgress: (loaded: number, total: number) => { - setAvatarProgress(Math.ceil((loaded / total) * 100)); + onProgress: (progress: number) => { + setAvatarProgress(progress); }, }) .then((url) => resolve(url)) @@ -39,8 +39,8 @@ export const useUploadAgent = () => { if (meta.cover.includes('base64')) { const file = base64ToFile(meta.cover, `${agentId}-cover`); upload(file, { - onProgress: (loaded: number, total: number) => { - setCoverProgress(Math.ceil((loaded / total) * 100)); + onProgress: (progress) => { + setCoverProgress(progress); }, }) .then((url) => resolve(url)) @@ -60,8 +60,8 @@ export const useUploadAgent = () => { upload( new File([modelBlob], `${agentId}-model.vrm`, { type: 'application/octet-stream' }), { - onProgress: (loaded: number, total: number) => { - setModelProgress(Math.ceil((loaded / total) * 100)); + onProgress: (progress) => { + setModelProgress(progress); }, }, ) diff --git a/src/services/upload.ts b/src/services/upload.ts index 60ad9170..0bf49fbc 100644 --- a/src/services/upload.ts +++ b/src/services/upload.ts @@ -7,7 +7,7 @@ import { uuid } from '@/utils/uuid'; export const upload = async ( file: File, handlers: { - onProgress?: (loaded: number, total: number) => void; + onProgress?: (progress: number) => void; }, ) => { const dateFolder = dayjs().format('YYYY/MM/DD'); // 使用当前日期作为文件夹名称 @@ -25,11 +25,8 @@ export const upload = async ( headers: { 'Content-Type': file.type, }, - onUploadProgress: (e) => { - if (e.lengthComputable) { - console.log(e.loaded, e.total); - handlers.onProgress?.(e.loaded, e.total || file.size); - } + onUploadProgress: ({ progress }) => { + handlers.onProgress?.(progress ? Math.ceil(progress * 100) : 0); }, }); From 146a5c10cdf08a6755e1f2ea751ac1dea2f5b0e2 Mon Sep 17 00:00:00 2001 From: rdmclin2 Date: Wed, 3 Jul 2024 23:13:39 +0800 Subject: [PATCH 10/16] =?UTF-8?q?:sparkles:=20feat:=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E6=9B=B4=E5=A4=9A=E9=85=8D=E7=BD=AE=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SubmitAgentButton/SubmitAgentModal.tsx | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/features/Actions/SubmitAgentButton/SubmitAgentModal.tsx b/src/features/Actions/SubmitAgentButton/SubmitAgentModal.tsx index 91a03df1..3042d14d 100644 --- a/src/features/Actions/SubmitAgentButton/SubmitAgentModal.tsx +++ b/src/features/Actions/SubmitAgentButton/SubmitAgentModal.tsx @@ -1,7 +1,7 @@ 'use client'; import { Alert, Icon, Modal, type ModalProps } from '@lobehub/ui'; -import { Button, Divider, Input, Popover, Progress, 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'; @@ -32,27 +32,36 @@ const SubmitAgentModal = memo(({ open, onCancel }) => { meta.description && meta.avatar && meta.cover && - meta.model, + meta.model && + meta.gender, ); const handleSubmit = async () => { const { avatarUrl, coverUrl, modelUrl } = await uploadAgentData(agentId, meta); const body = [ - '### systemRole', - currentAgent.systemRole, '### agentId', agentId, '### avatar', avatarUrl, '### cover', coverUrl, + '### systemRole', + currentAgent.systemRole, + '### greeting', + currentAgent.greeting, '### model', modelUrl, '### name', meta.name, '### description', meta.description, + '### gender', + meta.gender, + '### tts', + currentAgent.tts, + '### touch', + currentAgent.touch, ].join('\n\n'); const url = qs.stringifyUrl({ @@ -71,17 +80,18 @@ const SubmitAgentModal = memo(({ open, onCancel }) => { open={uploading} title={ + 上传处理中,请勿关闭页面... - 上传封面 + 上传封面 - 上传头像 + 上传头像 - 上传模型 + 上传模型 } From 6ca10cec5c3303a30be340f7750bdbbcd252e641 Mon Sep 17 00:00:00 2001 From: rdmclin2 Date: Wed, 3 Jul 2024 23:28:04 +0800 Subject: [PATCH 11/16] =?UTF-8?q?:sparkles:=20feat:=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=20Agent=20=E5=A4=A7=E6=A8=A1=E5=9E=8B=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SubmitAgentButton/SubmitAgentModal.tsx | 10 +++++-- src/types/agent.ts | 5 ++-- src/types/llm.ts | 28 +++++++++++++++++++ 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src/features/Actions/SubmitAgentButton/SubmitAgentModal.tsx b/src/features/Actions/SubmitAgentButton/SubmitAgentModal.tsx index 3042d14d..eddf15f3 100644 --- a/src/features/Actions/SubmitAgentButton/SubmitAgentModal.tsx +++ b/src/features/Actions/SubmitAgentButton/SubmitAgentModal.tsx @@ -50,7 +50,7 @@ const SubmitAgentModal = memo(({ open, onCancel }) => { currentAgent.systemRole, '### greeting', currentAgent.greeting, - '### model', + '### modelUrl', modelUrl, '### name', meta.name, @@ -59,9 +59,13 @@ const SubmitAgentModal = memo(({ open, onCancel }) => { '### gender', meta.gender, '### tts', - currentAgent.tts, + JSON.stringify(currentAgent.tts), '### touch', - currentAgent.touch, + JSON.stringify(currentAgent.touch), + '### model', + currentAgent.model, + '### params', + JSON.stringify(currentAgent.params), ].join('\n\n'); const url = qs.stringifyUrl({ diff --git a/src/types/agent.ts b/src/types/agent.ts index 4b943786..097ffb5d 100644 --- a/src/types/agent.ts +++ b/src/types/agent.ts @@ -1,4 +1,5 @@ -import { ChatStreamPayload } from './openai/chat'; +import { LLMParams } from '@/types/llm'; + import { TouchActionConfig } from './touch'; import { TTS } from './tts'; @@ -89,7 +90,7 @@ export interface Agent { /** * 语言模型配置 */ - params?: Partial; + params?: LLMParams; /** * 角色设定 */ diff --git a/src/types/llm.ts b/src/types/llm.ts index 041966ca..24c87caf 100644 --- a/src/types/llm.ts +++ b/src/types/llm.ts @@ -42,3 +42,31 @@ export interface ChatModelCard { */ vision?: boolean; } + +// 语言模型的设置参数 +export interface LLMParams { + /** + * 控制生成文本中的惩罚系数,用于减少重复性 + * @default 0 + */ + frequency_penalty?: number; + /** + * 生成文本的最大长度 + */ + max_tokens?: number; + /** + * 控制生成文本中的惩罚系数,用于减少主题的变化 + * @default 0 + */ + presence_penalty?: number; + /** + * 生成文本的随机度量,用于控制文本的创造性和多样性 + * @default 0.6 + */ + temperature?: number; + /** + * 控制生成文本中最高概率的单个 token + * @default 1 + */ + top_p?: number; +} From a75de1b469b10e93c0424cf2a31539687cf2ea9e Mon Sep 17 00:00:00 2001 From: rdmclin2 Date: Fri, 5 Jul 2024 22:35:04 +0800 Subject: [PATCH 12/16] =?UTF-8?q?:sparkles:=20feat:=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E6=A8=A1=E5=9E=8B=E5=88=86=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/constants/agent.ts | 12 +++++++ .../SubmitAgentButton/SubmitAgentModal.tsx | 4 +++ .../RoleEdit/Info/RoleCategory/index.tsx | 31 +++++++++++++++++++ src/panels/RolePanel/RoleEdit/Info/index.tsx | 6 +++- 4 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 src/panels/RolePanel/RoleEdit/Info/RoleCategory/index.tsx diff --git a/src/constants/agent.ts b/src/constants/agent.ts index 87d8eba5..1dc25de6 100644 --- a/src/constants/agent.ts +++ b/src/constants/agent.ts @@ -56,3 +56,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 }, +]; diff --git a/src/features/Actions/SubmitAgentButton/SubmitAgentModal.tsx b/src/features/Actions/SubmitAgentButton/SubmitAgentModal.tsx index eddf15f3..0fa3bdea 100644 --- a/src/features/Actions/SubmitAgentButton/SubmitAgentModal.tsx +++ b/src/features/Actions/SubmitAgentButton/SubmitAgentModal.tsx @@ -56,6 +56,10 @@ const SubmitAgentModal = memo(({ open, onCancel }) => { meta.name, '### description', meta.description, + '### category', + meta.category, + '### readme', + meta.readme, '### gender', meta.gender, '### tts', diff --git a/src/panels/RolePanel/RoleEdit/Info/RoleCategory/index.tsx b/src/panels/RolePanel/RoleEdit/Info/RoleCategory/index.tsx new file mode 100644 index 00000000..ac629fd5 --- /dev/null +++ b/src/panels/RolePanel/RoleEdit/Info/RoleCategory/index.tsx @@ -0,0 +1,31 @@ +import { Select } from 'antd'; +import React, { CSSProperties, memo } from 'react'; + +import { AGENT_CATEGORY_OPTIONS } from '@/constants/agent'; +import { agentSelectors, useAgentStore } from '@/store/agent'; + +interface Props { + className?: string; + style?: CSSProperties; +} + +export default memo((props) => { + const { style, className } = props; + const [meta, updateAgentMeta] = useAgentStore((s) => [ + agentSelectors.currentAgentMeta(s), + s.updateAgentMeta, + ]); + + return ( +