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/package.json b/package.json index cd401045..fbe4df34 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "type-check": "tsc --noEmit" }, "lint-staged": { - "*.md": [ + "*.mdx": [ "remark --quiet --output --", "prettier --write --no-error-on-unmatched-pattern" ], @@ -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", @@ -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", @@ -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", @@ -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" }, 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/constants/agent.ts b/src/constants/agent.ts index 1001fcf7..99dcfeb0 100644 --- a/src/constants/agent.ts +++ b/src/constants/agent.ts @@ -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, @@ -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 }, +]; diff --git a/src/constants/common.ts b/src/constants/common.ts index 7ba16a20..c0a6dc91 100644 --- a/src/constants/common.ts +++ b/src/constants/common.ts @@ -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 = '...'; diff --git a/src/constants/dance.ts b/src/constants/dance.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/src/features/Actions/Agent.tsx b/src/features/Actions/Agent.tsx index cd0b7079..4c2527f7 100644 --- a/src/features/Actions/Agent.tsx +++ b/src/features/Actions/Agent.tsx @@ -50,7 +50,7 @@ const ThemeButton = memo(() => { }} trigger={['click']} > - + ); }); diff --git a/src/features/Actions/SubmitAgentButton/SubmitAgentModal.tsx b/src/features/Actions/SubmitAgentButton/SubmitAgentModal.tsx index d03626fd..1f39ffdd 100644 --- a/src/features/Actions/SubmitAgentButton/SubmitAgentModal.tsx +++ b/src/features/Actions/SubmitAgentButton/SubmitAgentModal.tsx @@ -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(({ 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({ @@ -73,15 +83,37 @@ const SubmitAgentModal = memo(({ open, onCancel }) => { + 上传处理中,请勿关闭页面... + + + 上传封面 + + + + 上传头像 + + + + 上传模型 + + + } > - 提交助手 - + + } onCancel={onCancel} open={open} @@ -90,13 +122,15 @@ const SubmitAgentModal = memo(({ open, onCancel }) => { {!isFormPass && ( )} + + * agentId 助手标识符 @@ -112,7 +146,7 @@ const SubmitAgentModal = memo(({ open, onCancel }) => { icon={} onClick={() => { const randomId = Math.random().toString(36).slice(7); - setAgentId(`vidol-agent-${randomId}`); + setAgentId(kebabCase(randomId)); }} > 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/hooks/useUploadAgent.tsx b/src/hooks/useUploadAgent.tsx new file mode 100644 index 00000000..13b0bfbd --- /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: (progress: number) => { + setAvatarProgress(progress); + }, + }) + .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: (progress) => { + setCoverProgress(progress); + }, + }) + .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: (progress) => { + setModelProgress(progress); + }, + }, + ) + .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/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 { 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/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/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 ( +