├── .gitattributes ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── binary ├── config │ ├── __init__.py │ └── config.py ├── constants │ ├── __init__.py │ └── constants.py ├── controller │ └── __init__.py ├── main.py ├── memory │ ├── __init__.py │ ├── base.py │ └── local_memory.py ├── planner │ └── __init__.py ├── provider │ ├── __init__.py │ ├── image │ │ ├── __init__.py │ │ └── image_ocr.py │ └── video │ │ ├── __init__.py │ │ ├── video_clip.py │ │ ├── video_easyocr_extract.py │ │ ├── video_frame_extract.py │ │ └── video_record.py ├── requirements.txt ├── res │ └── tool │ │ ├── general.cfg │ │ └── subfinder │ │ └── test.srt ├── runner │ ├── __init__.py │ └── observe_mac_desktop_runner.py └── utils │ ├── __init__.py │ ├── dict_utils.py │ ├── encoding_utils.py │ ├── file_utils.py │ ├── gui_utils.py │ ├── json_utils.py │ ├── singleton.py │ └── string_utils.py ├── docs └── demo_01.png ├── gui ├── .editorconfig ├── .erb │ ├── configs │ │ ├── .eslintrc │ │ ├── webpack.config.base.ts │ │ ├── webpack.config.eslint.ts │ │ ├── webpack.config.main.prod.ts │ │ ├── webpack.config.preload.dev.ts │ │ ├── webpack.config.renderer.dev.dll.ts │ │ ├── webpack.config.renderer.dev.ts │ │ ├── webpack.config.renderer.prod.ts │ │ └── webpack.paths.ts │ ├── img │ │ ├── erb-banner.svg │ │ └── erb-logo.png │ ├── mocks │ │ └── fileMock.js │ └── scripts │ │ ├── .eslintrc │ │ ├── check-build-exists.ts │ │ ├── check-native-dep.js │ │ ├── check-node-env.js │ │ ├── check-port-in-use.js │ │ ├── clean.js │ │ ├── delete-source-maps.js │ │ ├── electron-rebuild.js │ │ ├── link-modules.ts │ │ ├── notarize.js │ │ └── typeorm-create.js ├── .eslintignore ├── .eslintrc.js ├── .gitattributes ├── .gitignore ├── .husky │ └── pre-commit ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── assets │ ├── assets.d.ts │ ├── background │ │ ├── Room01.jpg │ │ ├── Room02.jpeg │ │ ├── Room03.png │ │ ├── Room04.png │ │ ├── School01.jpg │ │ ├── School02.jpg │ │ ├── Street01.png │ │ ├── Street02.jpg │ │ ├── Street03.jpg │ │ ├── Street04.jpg │ │ └── Street05.jpg │ ├── entitlements.mac.plist │ ├── icon.icns │ ├── icon.ico │ ├── icon.png │ ├── icon.svg │ ├── icons │ │ ├── 1024x1024.png │ │ ├── 128x128.png │ │ ├── 16x16.png │ │ ├── 24x24.png │ │ ├── 256x256.png │ │ ├── 32x32.png │ │ ├── 48x48.png │ │ ├── 512x512.png │ │ ├── 64x64.png │ │ └── 96x96.png │ ├── images │ │ └── character │ │ │ ├── character-model-icon.png │ │ │ ├── fubuki.png │ │ │ └── yatuoli.jpeg │ ├── js │ │ ├── live2d.min.js │ │ └── live2dcubismcore.min.js │ ├── models │ │ ├── Hiyori │ │ │ ├── Hiyori.2048 │ │ │ │ ├── texture_00.png │ │ │ │ └── texture_01.png │ │ │ ├── Hiyori.moc3 │ │ │ ├── Hiyori.model3.json │ │ │ ├── Hiyori.physics3.json │ │ │ ├── Hiyori.pose3.json │ │ │ ├── Hiyori.userdata3.json │ │ │ └── motions │ │ │ │ ├── Hiyori_m01.motion3.json │ │ │ │ ├── Hiyori_m02.motion3.json │ │ │ │ ├── Hiyori_m03.motion3.json │ │ │ │ ├── Hiyori_m04.motion3.json │ │ │ │ ├── Hiyori_m05.motion3.json │ │ │ │ ├── Hiyori_m06.motion3.json │ │ │ │ ├── Hiyori_m07.motion3.json │ │ │ │ ├── Hiyori_m08.motion3.json │ │ │ │ ├── Hiyori_m09.motion3.json │ │ │ │ └── Hiyori_m10.motion3.json │ │ ├── atri │ │ │ ├── atri.model3.json │ │ │ ├── atri_8.4096 │ │ │ │ ├── texture_00.png │ │ │ │ └── texture_01.png │ │ │ ├── atri_8.cdi3.json │ │ │ ├── atri_8.moc3 │ │ │ ├── atri_8.physics3.json │ │ │ ├── dec-l.motion3.json │ │ │ ├── dec-r.motion3.json │ │ │ ├── expression1.exp3.json │ │ │ ├── expression10.exp3.json │ │ │ ├── expression11.exp3.json │ │ │ ├── expression12.exp3.json │ │ │ ├── expression13.exp3.json │ │ │ ├── expression14.exp3.json │ │ │ ├── expression15.exp3.json │ │ │ ├── expression16.exp3.json │ │ │ ├── expression2.exp3.json │ │ │ ├── expression3.exp3.json │ │ │ ├── expression4.exp3.json │ │ │ ├── expression5.exp3.json │ │ │ ├── expression6.exp3.json │ │ │ ├── expression7.exp3.json │ │ │ ├── expression8.exp3.json │ │ │ ├── expression9.exp3.json │ │ │ └── model.motion3.json │ │ └── chuixue_3 │ │ │ ├── chuixue_3.moc3 │ │ │ ├── chuixue_3.model3.json │ │ │ ├── chuixue_3.physics3.json │ │ │ ├── motions │ │ │ ├── complete.motion3.json │ │ │ ├── home.motion3.json │ │ │ ├── idle.motion3.json │ │ │ ├── login.motion3.json │ │ │ ├── mail.motion3.json │ │ │ ├── main_1.motion3.json │ │ │ ├── main_2.motion3.json │ │ │ ├── main_3.motion3.json │ │ │ ├── mission.motion3.json │ │ │ ├── mission_complete.motion3.json │ │ │ ├── touch_body.motion3.json │ │ │ ├── touch_head.motion3.json │ │ │ ├── touch_special.motion3.json │ │ │ └── wedding.motion3.json │ │ │ └── textures │ │ │ └── texture_00.png │ └── vosk │ │ └── vosk-model-small-cn-0.3.tar.gz ├── package.json ├── postcss.config.js ├── src │ ├── __tests__ │ │ └── App.test.tsx │ ├── main │ │ ├── apps │ │ │ ├── control │ │ │ │ └── control.ts │ │ │ └── environment │ │ │ │ └── liveRoomEnvironment.ts │ │ ├── domain │ │ │ ├── dao │ │ │ │ ├── characterChatHistoryDao.ts │ │ │ │ ├── characterDao.ts │ │ │ │ ├── characterModelDao.ts │ │ │ │ └── systemSettingDao.ts │ │ │ ├── dto │ │ │ │ ├── UploadFileDTO.ts │ │ │ │ ├── characterChatHistoryDTO.ts │ │ │ │ ├── characterDTO.ts │ │ │ │ ├── characterModelDTO.ts │ │ │ │ ├── llmConnectDTO.ts │ │ │ │ ├── systemSettingDTO.ts │ │ │ │ └── voiceDTO.ts │ │ │ ├── entitys │ │ │ │ ├── character.ts │ │ │ │ ├── characterChatHistory.ts │ │ │ │ ├── characterDefinition.ts │ │ │ │ ├── characterModel.ts │ │ │ │ └── systemSetting.ts │ │ │ └── service │ │ │ │ ├── characterChatHistoryService.ts │ │ │ │ ├── characterModelService.ts │ │ │ │ ├── characterService.ts │ │ │ │ ├── fileService.ts │ │ │ │ ├── liveClientMangeService.ts │ │ │ │ ├── systemSettingService.ts │ │ │ │ ├── visionService.ts │ │ │ │ └── voiceService.ts │ │ ├── framework │ │ │ ├── bridge │ │ │ │ └── PythonBridge.ts │ │ │ ├── dependencyInjector.ts │ │ │ ├── ipcHandlers.ts │ │ │ ├── live │ │ │ │ ├── base.ts │ │ │ │ └── client │ │ │ │ │ └── bilibiliClient.ts │ │ │ ├── orm │ │ │ │ ├── baseDao.ts │ │ │ │ ├── baseEntity.ts │ │ │ │ └── database.ts │ │ │ ├── queue │ │ │ │ └── queue.ts │ │ │ ├── tts │ │ │ │ └── voiceClient.ts │ │ │ └── vision │ │ │ │ └── OCRTextStorage.ts │ │ ├── main.ts │ │ ├── menu.ts │ │ ├── preload.ts │ │ ├── route │ │ │ └── route.ts │ │ └── util.ts │ └── renderer │ │ ├── App.css │ │ ├── App.tsx │ │ ├── Types.ts │ │ ├── components │ │ ├── character │ │ │ ├── CharacterCardMenu.tsx │ │ │ ├── CharacterSetting.tsx │ │ │ ├── CharacterSettingCss.tsx │ │ │ └── DeleteCharacter.tsx │ │ ├── common │ │ │ ├── background │ │ │ │ └── Background.tsx │ │ │ ├── button │ │ │ │ ├── LLMConnectivityCheckButton.tsx │ │ │ │ └── LiveConnectivityCheckButton.tsx │ │ │ ├── card │ │ │ │ └── CharacterCard.tsx │ │ │ ├── collapse │ │ │ │ ├── Collapse.tsx │ │ │ │ └── CollapseCss.tsx │ │ │ ├── icon │ │ │ │ ├── CompanyIcon.tsx │ │ │ │ ├── GalleryIcon.tsx │ │ │ │ ├── ModelIcon.tsx │ │ │ │ ├── MusicIcon.tsx │ │ │ │ └── VideoIcon.tsx │ │ │ ├── label │ │ │ │ └── FormLabel.tsx │ │ │ └── text │ │ │ │ └── ScrollingTextDisplay.tsx │ │ ├── creativeworkshop │ │ │ ├── CharacterModel.tsx │ │ │ ├── EditCharacterModel.tsx │ │ │ └── VoiceModel.tsx │ │ ├── home │ │ │ ├── HomeLiveSetting.tsx │ │ │ └── HomeOtherSetting.tsx │ │ ├── live2d │ │ │ └── Live2D.tsx │ │ ├── livepage │ │ │ └── LiveView.tsx │ │ ├── memory │ │ │ └── ChatHistroy.tsx │ │ ├── menu │ │ │ └── MainMenu.tsx │ │ ├── mssage │ │ │ ├── AudioPlayerQueue.tsx │ │ │ ├── ChatMessage.tsx │ │ │ └── WebSocketMessage.tsx │ │ └── system │ │ │ ├── BaseSettings.tsx │ │ │ ├── LLMSettings.tsx │ │ │ ├── LLMSettingsCss.tsx │ │ │ ├── LiveSetting.tsx │ │ │ ├── live │ │ │ └── BilibiliLiveSettings.tsx │ │ │ └── llm │ │ │ ├── HunyuanAgentSettings.tsx │ │ │ ├── OllamaSettings.tsx │ │ │ ├── OpenAISettings.tsx │ │ │ └── ZhiPuSettings.tsx │ │ ├── constants │ │ ├── GlobalConstants.ts │ │ └── OptionConstants.ts │ │ ├── features │ │ ├── agent │ │ │ └── AgentHandle.ts │ │ ├── character │ │ │ ├── CharacterChatHandle.ts │ │ │ ├── CharacterDefinitionHandle.ts │ │ │ └── CharacterHandle.ts │ │ ├── charactermodel │ │ │ ├── CharacterModelHandle.ts │ │ │ ├── CharacterModelLoader.ts │ │ │ └── live2d │ │ │ │ └── Live2dModelLoader.ts │ │ ├── file │ │ │ └── FileUtils.ts │ │ ├── live │ │ │ └── LiveClienHandle.ts │ │ ├── llm │ │ │ ├── Base.ts │ │ │ ├── Client.ts │ │ │ ├── Handle.ts │ │ │ └── impl │ │ │ │ ├── HunyuanAgent.ts │ │ │ │ ├── Ollama.ts │ │ │ │ ├── Openai.ts │ │ │ │ └── Zhipu.ts │ │ ├── memory │ │ │ └── ChatHistroyHandle.ts │ │ ├── system │ │ │ └── SystemSettingHandle.ts │ │ ├── utils │ │ │ ├── AutoMapperUtils.ts │ │ │ ├── MapUtils.ts │ │ │ └── Queue.ts │ │ ├── vision │ │ │ └── VisionHandle.ts │ │ ├── voice │ │ │ └── VoiceHandle.ts │ │ └── websocket │ │ │ └── webSocketClient.ts │ │ ├── hooks │ │ └── UseScrollingText.ts │ │ ├── index.ejs │ │ ├── index.tsx │ │ ├── pages │ │ ├── CharacterSpace.tsx │ │ ├── CreativeWorkshop.tsx │ │ ├── EditCharacterSpace.tsx │ │ ├── Home.tsx │ │ └── SystemSetting.tsx │ │ ├── preload.d.ts │ │ └── types │ │ └── types.ts ├── tailwind.config.js ├── tsconfig.json └── yarn.lock └── mods └── Subnautica ├── AtlasHub.sln ├── AtlasPAD ├── AtlasPAD.csproj ├── Client │ └── VtuberSamaClient.cs ├── Configuration │ ├── client.json │ └── prompts.json ├── Items │ └── Equipment │ │ └── YeetKnifePrefab.cs ├── Listeners │ └── ItemScannerListener.cs ├── Localization │ ├── Chinese (Simplified).json │ └── English.json ├── Patches │ └── PDAScannerPatcher.cs ├── Plugin.cs ├── Prompt │ └── PromptTemplate.cs ├── app.config └── packages.config ├── IndigocoderLib ├── ImageHelper.cs ├── IndigocoderLib.projitems ├── IndigocoderLib.shproj ├── JsonConfigUtils.cs ├── LocalizationUtils.cs ├── PiracyDetector.cs └── Utilities.cs ├── SuitLib ├── API │ ├── JsonGloves.cs │ ├── JsonLoader.cs │ ├── JsonSuit.cs │ ├── ModdedGloves.cs │ ├── ModdedSuit.cs │ ├── ModdedSuitsManager.cs │ └── PrefabComponentAssigner.cs ├── Main.cs ├── Patches │ ├── Player_Patches.cs │ └── Stillsuit_Patches.cs ├── Properties │ └── AssemblyInfo.cs ├── SuitLib.csproj ├── app.config ├── packages.config └── public │ └── ReadMe.txt ├── TodoList ├── Main_Plugin.cs ├── Monobehaviors │ ├── ApplySNFont.cs │ ├── TodoInputField.cs │ ├── TodoItem.cs │ └── uGUI_TodoTab.cs ├── Options.cs ├── Patches │ ├── AutoTodoItems │ │ ├── CompoundGoalPatches.cs │ │ ├── RadioPatches.cs │ │ ├── StoryGoalManagerPatches.cs │ │ └── uGUI_SceneIntroPatches.cs │ ├── MapModCompatibilityPatches.cs │ ├── PlayerPatches.cs │ └── uGUI_PDAPatches.cs ├── Properties │ └── AssemblyInfo.cs ├── TodoList.csproj ├── TodoListSaveData.cs ├── TodoOptions.cs ├── app.config └── packages.config └── WarpStabilizationSuit ├── Assets ├── Textures │ ├── player_02_reinforced_suit_01_arms_WARP.png │ ├── player_02_reinforced_suit_01_arms_spec_WARP.png │ ├── player_02_reinforced_suit_01_body_WARP.png │ └── player_02_reinforced_suit_01_body_spec_WARP.png ├── WarpStabilizationGloves.png ├── warpStabilizationEncyBanner.png ├── warpStabilizationPopup.png └── warpStabilizationSuit.png ├── Items ├── Gloves_Craftable.cs └── Suit_Craftable.cs ├── Main_Plugin.cs ├── Patches ├── EncySetup.cs ├── PDAScanner_Patch.cs ├── Player.cs ├── Player_Patches.cs ├── RangedAttackLast_Patch.cs ├── WarpBall_Patch.cs └── Warper_Patch.cs ├── Properties └── AssemblyInfo.cs ├── Suit_ModOptions.cs ├── WarpStabilizationSuit.csproj ├── app.config ├── packages.config └── public ├── Assets ├── Textures │ ├── player_02_reinforced_suit_01_arms_WARP.png │ ├── player_02_reinforced_suit_01_arms_spec_WARP.png │ ├── player_02_reinforced_suit_01_body_WARP.png │ └── player_02_reinforced_suit_01_body_spec_WARP.png ├── WarpStabilizationGloves.png ├── warpStabilizationEncyBanner.png ├── warpStabilizationPopup.png └── warpStabilizationSuit.png └── Recipes ├── EasySuitRecipe.json └── HardSuitRecipe.json /Makefile: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Westworld-AI/VTube-Sama-App/5caa02321642411d77e09f445776f91ee7646b54/Makefile -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VTube-Sama Beta 2 | 3 | VTube-Sama-App 是一个超低上手成本的 AI Vtube项目,支持OpenAI、Ollama、智谱,可在Mac、Windows 安装使用。 4 | 5 | # 模块说明 6 | 7 | ```text 8 | ├── binary # 视觉模块 9 | ├── gui # GUI 10 | ├── mods # 关联的游戏模组 11 | 12 | ``` 13 | # 功能点 14 | 15 | TODO 16 | 17 | ![demo_01.png](docs/demo_01.png) 18 | 19 | # 安装文档 20 | 21 | ## GUI 22 | 23 | - 安装依赖 24 | ```bash 25 | cd gui 26 | yarn install && npm run electron-rebuild 27 | ``` 28 | 29 | - 启动程序 30 | ```bash 31 | yarn start 32 | ``` 33 | 34 | - 打包成桌面程序 35 | ```bash 36 | yarn package 37 | ``` -------------------------------------------------------------------------------- /binary/config/__init__.py: -------------------------------------------------------------------------------- 1 | from .config import Config 2 | 3 | __all__ = [ 4 | "Config", 5 | ] 6 | -------------------------------------------------------------------------------- /binary/constants/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Westworld-AI/VTube-Sama-App/5caa02321642411d77e09f445776f91ee7646b54/binary/constants/__init__.py -------------------------------------------------------------------------------- /binary/constants/constants.py: -------------------------------------------------------------------------------- 1 | 2 | # Local memory 3 | AUGMENTED_IMAGES_MEM_BUCKET = 'augmented_screen_shot_path' 4 | 5 | MESSAGE_CONSTRUCTION_MODE_TRIPART = 'tripartite' 6 | 7 | ENVIRONMENT_NAME = 'env_name' 8 | ENVIRONMENT_WINDOW_NAME_PATTERN = 'win_name_pattern' 9 | ENVIRONMENT_SHORT_NAME = 'env_short_name' 10 | ENVIRONMENT_SUB_PATH = 'sub_path' -------------------------------------------------------------------------------- /binary/controller/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Westworld-AI/VTube-Sama-App/5caa02321642411d77e09f445776f91ee7646b54/binary/controller/__init__.py -------------------------------------------------------------------------------- /binary/main.py: -------------------------------------------------------------------------------- 1 | # main.py 2 | from uvicorn import run 3 | 4 | from config import Config 5 | from runner import observe_mac_desktop_runner 6 | 7 | config = Config() 8 | 9 | if __name__ == "__main__": 10 | config.load_env_config() 11 | config.set_fixed_seed() 12 | observe_mac_desktop_runner.entry(None) 13 | run("main:app", host="127.0.0.1", port=8881, reload=True) 14 | -------------------------------------------------------------------------------- /binary/memory/__init__.py: -------------------------------------------------------------------------------- 1 | from .base import BaseMemory 2 | from .local_memory import LocalMemory 3 | 4 | __all__ = [ 5 | "BaseMemory", 6 | "LocalMemory" 7 | ] 8 | -------------------------------------------------------------------------------- /binary/memory/base.py: -------------------------------------------------------------------------------- 1 | import abc 2 | from typing import ( 3 | Any, 4 | Iterable, 5 | List, 6 | Dict, 7 | Union, 8 | Tuple, 9 | Optional, 10 | ) 11 | 12 | from config import Config 13 | 14 | Image = Any 15 | 16 | config = Config() 17 | 18 | 19 | class BaseMemory: 20 | """Base class for all memories.""" 21 | 22 | @abc.abstractmethod 23 | def add( 24 | self, 25 | **kwargs, 26 | ) -> None: 27 | """Add data to memory. 28 | 29 | Args: 30 | **kwargs: Other keyword arguments that subclasses might use. 31 | """ 32 | pass 33 | 34 | @abc.abstractmethod 35 | def similarity_search( 36 | self, 37 | data: Union[str, Image], 38 | top_k: int, 39 | **kwargs: Any, 40 | ) -> List[Union[str, Image]]: 41 | """Retrieve the keys from the store. 42 | 43 | Args: 44 | data: the query data. 45 | top_k: the number of results to return. 46 | **kwargs: Other keyword arguments that subclasses might use. 47 | 48 | Returns: 49 | the corresponding values from the memory. 50 | """ 51 | pass 52 | 53 | @abc.abstractmethod 54 | def add_recent_history( 55 | self, 56 | key: str, 57 | info: Any, 58 | ) -> None: 59 | pass 60 | 61 | @abc.abstractmethod 62 | def get_recent_history( 63 | self, 64 | key: str, 65 | k: int = 1, 66 | ) -> List[Any]: 67 | pass 68 | 69 | @abc.abstractmethod 70 | def add_summarization(self, hidden_state: str) -> None: 71 | pass 72 | 73 | @abc.abstractmethod 74 | def get_summarization(self) -> str: 75 | pass 76 | 77 | @abc.abstractmethod 78 | def load(self) -> None: 79 | """Load the memory from persistence.""" 80 | 81 | @abc.abstractmethod 82 | def save(self) -> None: 83 | """Save the memory to persistence.""" 84 | -------------------------------------------------------------------------------- /binary/planner/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Westworld-AI/VTube-Sama-App/5caa02321642411d77e09f445776f91ee7646b54/binary/planner/__init__.py -------------------------------------------------------------------------------- /binary/provider/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Westworld-AI/VTube-Sama-App/5caa02321642411d77e09f445776f91ee7646b54/binary/provider/__init__.py -------------------------------------------------------------------------------- /binary/provider/image/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Westworld-AI/VTube-Sama-App/5caa02321642411d77e09f445776f91ee7646b54/binary/provider/image/__init__.py -------------------------------------------------------------------------------- /binary/provider/image/image_ocr.py: -------------------------------------------------------------------------------- 1 | from http import HTTPStatus 2 | 3 | import dashscope 4 | 5 | from config import Config 6 | from log import Logger 7 | from memory import LocalMemory 8 | 9 | config = Config() 10 | logger = Logger() 11 | memory = LocalMemory() 12 | 13 | PROMPT = """ 14 | 假设你是一个在PC上集成了“你画我猜”的有用的AI助手,可以处理游戏中的各种任务。您的高级功能使您能够处理和解释游戏截图和其他相关信息。 15 | 您应该只以下面描述的格式回复,不要输出评论或其他信息。 16 | ``` 17 | 描述: 请以分析和描述截图图像,然后提供整体图像描述, 18 | 可能物品或者生物: 当前画作中的物品和生物,如“桌子上有一只猫”,否则只输出“null”。 19 | 对话框: 如果截图中有一些对话框,提取对话文本,如“店主:你想买什么?”,否则只输出“null”。 20 | 游戏人物意图:描述当前人物的意图 21 | 其他:不属于上述类别的其他信息。如果它们都不适用,则只输出“null”。 22 | ``` 23 | """ 24 | 25 | 26 | class ImageOcrProvider: 27 | 28 | def __call__(self, 29 | *args, 30 | init=False, 31 | **kwargs): 32 | 33 | screen_shot_paths = memory.get_recent_history("screen_shot_path") 34 | image_file_path = "file://" + screen_shot_paths[0] 35 | print("image_file_path", image_file_path) 36 | 37 | dashscope.api_key = config.dashscope_api_key 38 | messages = [ 39 | { 40 | "role": "user", 41 | "content": [ 42 | {"image": image_file_path}, 43 | {"text": PROMPT} 44 | ] 45 | } 46 | ] 47 | response = dashscope.MultiModalConversation.call(model='qwen-vl-plus', 48 | messages=messages) 49 | 50 | if response.status_code == HTTPStatus.OK: # 如果调用成功,则打印response 51 | ocr_result = response.output.choices[0].message.content[0]['text'] 52 | res_params = { 53 | "ocr_result": ocr_result, 54 | } 55 | memory.update_info_history(res_params) 56 | else: # 如果调用失败 57 | print("MultiModalConversation call failed", response.code) # 错误码 58 | print(response.message) # 错误信息 59 | -------------------------------------------------------------------------------- /binary/provider/video/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Westworld-AI/VTube-Sama-App/5caa02321642411d77e09f445776f91ee7646b54/binary/provider/video/__init__.py -------------------------------------------------------------------------------- /binary/provider/video/video_clip.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Dict 2 | import time 3 | 4 | from log import Logger 5 | from memory import LocalMemory 6 | from provider.video.video_record import VideoRecordProvider 7 | 8 | logger = Logger() 9 | memory = LocalMemory() 10 | 11 | 12 | class VideoClipProvider: 13 | def __init__(self, video_record: VideoRecordProvider): 14 | super(VideoClipProvider, self).__init__() 15 | self.video_record = video_record 16 | 17 | def __call__(self, 18 | *args, 19 | init=False, 20 | **kwargs): 21 | 22 | if init: 23 | start_frame_id = self.video_record.get_current_frame_id() 24 | time.sleep(2) 25 | end_frame_id = self.video_record.get_current_frame_id() 26 | video_clip_path = self.video_record.get_video(start_frame_id, end_frame_id) 27 | screen_shot_image_path = self.video_record.extract_last_frame(video_clip_path) 28 | 29 | logger.write( 30 | f"Initiate video clip path from the screen shot by frame id ({start_frame_id}, {end_frame_id}).") 31 | 32 | res_params = { 33 | "video_clip_path": video_clip_path, 34 | "start_frame_id": start_frame_id, 35 | "end_frame_id": end_frame_id, 36 | "screen_shot_path": screen_shot_image_path, 37 | } 38 | 39 | else: 40 | start_frame_id = memory.get_recent_history("start_frame_id")[-1] 41 | end_frame_id = memory.get_recent_history("end_frame_id")[-1] 42 | video_clip_path = self.video_record.get_video(start_frame_id, end_frame_id) 43 | 44 | logger.write(f"Get video clip path from the memory by frame id ({start_frame_id}, {end_frame_id}).") 45 | 46 | res_params = { 47 | "video_clip_path": video_clip_path, 48 | } 49 | 50 | memory.update_info_history(res_params) 51 | 52 | return res_params 53 | -------------------------------------------------------------------------------- /binary/requirements.txt: -------------------------------------------------------------------------------- 1 | backoff==2.2.1 2 | python-dotenv==1.0.0 3 | pyautogui==0.9.54 4 | pydirectinput==1.0.4; sys_platform == "win32" 5 | ahk==1.3.0; sys_platform == "win32" 6 | ahk[binary]; sys_platform == "win32" 7 | opencv-python==4.10.0.82 8 | opencv-contrib-python===4.10.0.82 9 | pillow==10.3.0 10 | Multi-Template-Matching==1.6.6 11 | imageio[ffmpeg] 12 | mss==9.0.1 13 | aiohttp 14 | easyocr==1.7.1 15 | spacy==3.7.2 16 | chardet==5.2.0 17 | pyobjc-framework-Quartz==10.0; sys_platform == "Darwin" 18 | pyobjc-framework-Cocoa==10.0; sys_platform == "Darwin" 19 | dataclass_wizard==0.22.3 20 | dill==0.3.8 21 | uvicorn 22 | pyinstaller 23 | colorama 24 | dashscope -------------------------------------------------------------------------------- /binary/res/tool/subfinder/test.srt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Westworld-AI/VTube-Sama-App/5caa02321642411d77e09f445776f91ee7646b54/binary/res/tool/subfinder/test.srt -------------------------------------------------------------------------------- /binary/runner/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Westworld-AI/VTube-Sama-App/5caa02321642411d77e09f445776f91ee7646b54/binary/runner/__init__.py -------------------------------------------------------------------------------- /binary/utils/__init__.py: -------------------------------------------------------------------------------- 1 | from utils.singleton import AbstractSingleton, Singleton 2 | 3 | __all__ = [ 4 | "AbstractSingleton", 5 | "Singleton", 6 | ] 7 | -------------------------------------------------------------------------------- /binary/utils/dict_utils.py: -------------------------------------------------------------------------------- 1 | def kget(obj, *keys, default=None): 2 | for key in keys: 3 | try: 4 | obj = obj[key] 5 | except (KeyError, IndexError): 6 | return default 7 | return obj 8 | -------------------------------------------------------------------------------- /binary/utils/file_utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | 4 | def assemble_project_path(path): 5 | """Assemble a path relative to the project root directory""" 6 | if not os.path.isabs(path): 7 | path = os.path.join(get_project_root(), path) 8 | return path 9 | 10 | 11 | def gen_relative_project_path(path): 12 | 13 | root = get_project_root() 14 | 15 | if root not in path: 16 | raise ValueError('Path to convert should be within the project root.') 17 | 18 | path = path.replace(root, '.').replace('.\\', '') 19 | return path 20 | 21 | 22 | def exists_in_project_path(path): 23 | return os.path.exists(assemble_project_path(path)) 24 | 25 | 26 | def get_project_root(): 27 | path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 28 | path = os.path.dirname(path) # get to parent, outside of project code path" 29 | return path 30 | 31 | 32 | def read_resource_file(path): 33 | 34 | assert "./res/" in path, 'Path should include ./res/' 35 | 36 | with open(assemble_project_path(path), "r", encoding="utf-8") as fd: 37 | return fd.read() 38 | -------------------------------------------------------------------------------- /binary/utils/singleton.py: -------------------------------------------------------------------------------- 1 | """A singleton metaclass for ensuring only one instance of a class.""" 2 | import abc 3 | 4 | 5 | class Singleton(abc.ABCMeta, type): 6 | """ 7 | Singleton metaclass for ensuring only one instance of a class. 8 | """ 9 | 10 | _instances = {} 11 | 12 | def __call__(cls, *args, **kwargs): 13 | """Call method for the singleton metaclass.""" 14 | if cls not in cls._instances: 15 | cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) 16 | return cls._instances[cls] 17 | 18 | 19 | class AbstractSingleton(abc.ABC, metaclass=Singleton): 20 | """ 21 | Abstract singleton class for ensuring only one instance of a class. 22 | """ 23 | 24 | pass 25 | -------------------------------------------------------------------------------- /binary/utils/string_utils.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | import sys 3 | import re 4 | 5 | 6 | def hash_text_sha256(text: str) -> str: 7 | hash_object = hashlib.sha256(text.encode()) 8 | return hash_object.hexdigest() 9 | 10 | 11 | def contains_regex_characters(s): 12 | # Pattern to match special regex characters 13 | regex_chars_pattern = r'[\.\^\$\*\+\?\{\}\[\]\\|()]' 14 | 15 | # Search for special regex characters in the string 16 | return re.search(regex_chars_pattern, s) 17 | 18 | 19 | def strip_anchor_chars(s): 20 | # Strip the first character if it's '^' 21 | if s.startswith('^'): 22 | s = s[1:] 23 | 24 | # Strip the last character if it's '$' 25 | if s.endswith('$'): 26 | s = s[:-1] 27 | 28 | return s 29 | 30 | 31 | def replace_unsupported_chars(text, replacement='?'): 32 | encoding = sys.getdefaultencoding() # Get the default system encoding 33 | return text.encode(encoding, errors='replace').decode(encoding).replace('\ufffd', replacement) 34 | -------------------------------------------------------------------------------- /docs/demo_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Westworld-AI/VTube-Sama-App/5caa02321642411d77e09f445776f91ee7646b54/docs/demo_01.png -------------------------------------------------------------------------------- /gui/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /gui/.erb/configs/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-console": "off", 4 | "global-require": "off", 5 | "import/no-dynamic-require": "off" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /gui/.erb/configs/webpack.config.base.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Base webpack config used across other specific configs 3 | */ 4 | 5 | import webpack from 'webpack'; 6 | import webpackPaths from './webpack.paths'; 7 | import { dependencies as externals } from '../../release/app/package.json'; 8 | 9 | const configuration: webpack.Configuration = { 10 | externals: [...Object.keys(externals || {})], 11 | 12 | stats: 'errors-only', 13 | 14 | module: { 15 | rules: [ 16 | { 17 | test: /\.[jt]sx?$/, 18 | exclude: /node_modules/, 19 | use: { 20 | loader: 'ts-loader', 21 | options: { 22 | // Remove this line to enable type checking in webpack builds 23 | transpileOnly: true 24 | } 25 | } 26 | } 27 | ] 28 | }, 29 | 30 | output: { 31 | path: webpackPaths.srcPath, 32 | // https://github.com/webpack/webpack/issues/1114 33 | library: { 34 | type: 'commonjs2' 35 | } 36 | }, 37 | 38 | /** 39 | * Determine the array of extensions that should be used to resolve modules. 40 | */ 41 | resolve: { 42 | extensions: ['.js', '.jsx', '.json', '.ts', '.tsx', '.mjs'], 43 | modules: [webpackPaths.srcPath, 'node_modules'], 44 | alias: { 45 | 'bilibili-live-ws/browser': require.resolve('bilibili-live-ws/browser.js'), 46 | }, 47 | }, 48 | 49 | plugins: [ 50 | new webpack.EnvironmentPlugin({ 51 | NODE_ENV: 'production' 52 | }) 53 | ] 54 | }; 55 | 56 | export default configuration; 57 | -------------------------------------------------------------------------------- /gui/.erb/configs/webpack.config.eslint.ts: -------------------------------------------------------------------------------- 1 | /* eslint import/no-unresolved: off, import/no-self-import: off */ 2 | 3 | module.exports = require('./webpack.config.renderer.dev').default; 4 | -------------------------------------------------------------------------------- /gui/.erb/configs/webpack.config.main.prod.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Webpack config for production electron main process 3 | */ 4 | 5 | import path from 'path'; 6 | import webpack from 'webpack'; 7 | import { merge } from 'webpack-merge'; 8 | import TerserPlugin from 'terser-webpack-plugin'; 9 | import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'; 10 | import baseConfig from './webpack.config.base'; 11 | import webpackPaths from './webpack.paths'; 12 | import checkNodeEnv from '../scripts/check-node-env'; 13 | import deleteSourceMaps from '../scripts/delete-source-maps'; 14 | 15 | checkNodeEnv('production'); 16 | deleteSourceMaps(); 17 | 18 | const devtoolsConfig = 19 | process.env.DEBUG_PROD === 'true' 20 | ? { 21 | devtool: 'source-map', 22 | } 23 | : {}; 24 | 25 | const configuration: webpack.Configuration = { 26 | ...devtoolsConfig, 27 | 28 | mode: 'production', 29 | 30 | target: 'electron-main', 31 | 32 | entry: { 33 | main: path.join(webpackPaths.srcMainPath, 'main.ts'), 34 | preload: path.join(webpackPaths.srcMainPath, 'preload.ts'), 35 | }, 36 | 37 | output: { 38 | path: webpackPaths.distMainPath, 39 | filename: '[name].js', 40 | }, 41 | 42 | optimization: { 43 | minimizer: [ 44 | new TerserPlugin({ 45 | parallel: true, 46 | }), 47 | ], 48 | }, 49 | 50 | plugins: [ 51 | new BundleAnalyzerPlugin({ 52 | analyzerMode: process.env.ANALYZE === 'true' ? 'server' : 'disabled', 53 | }), 54 | 55 | /** 56 | * Create global constants which can be configured at compile time. 57 | * 58 | * Useful for allowing different behaviour between development builds and 59 | * release builds 60 | * 61 | * NODE_ENV should be production so that modules do not perform certain 62 | * development checks 63 | */ 64 | new webpack.EnvironmentPlugin({ 65 | NODE_ENV: 'production', 66 | DEBUG_PROD: false, 67 | START_MINIMIZED: false, 68 | }), 69 | ], 70 | 71 | /** 72 | * Disables webpack processing of __dirname and __filename. 73 | * If you run the bundle in node.js it falls back to these values of node.js. 74 | * https://github.com/webpack/webpack/issues/2010 75 | */ 76 | node: { 77 | __dirname: false, 78 | __filename: false, 79 | }, 80 | }; 81 | 82 | export default merge(baseConfig, configuration); 83 | -------------------------------------------------------------------------------- /gui/.erb/configs/webpack.config.preload.dev.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import webpack from 'webpack'; 3 | import { merge } from 'webpack-merge'; 4 | import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'; 5 | import baseConfig from './webpack.config.base'; 6 | import webpackPaths from './webpack.paths'; 7 | import checkNodeEnv from '../scripts/check-node-env'; 8 | 9 | // When an ESLint server is running, we can't set the NODE_ENV so we'll check if it's 10 | // at the dev webpack config is not accidentally run in a production environment 11 | if (process.env.NODE_ENV === 'production') { 12 | checkNodeEnv('development'); 13 | } 14 | 15 | const configuration: webpack.Configuration = { 16 | devtool: 'inline-source-map', 17 | 18 | mode: 'development', 19 | 20 | target: 'electron-preload', 21 | 22 | entry: path.join(webpackPaths.srcMainPath, 'preload.ts'), 23 | 24 | output: { 25 | path: webpackPaths.dllPath, 26 | filename: 'preload.js', 27 | }, 28 | 29 | plugins: [ 30 | new BundleAnalyzerPlugin({ 31 | analyzerMode: process.env.ANALYZE === 'true' ? 'server' : 'disabled', 32 | }), 33 | 34 | /** 35 | * Create global constants which can be configured at compile time. 36 | * 37 | * Useful for allowing different behaviour between development builds and 38 | * release builds 39 | * 40 | * NODE_ENV should be production so that modules do not perform certain 41 | * development checks 42 | * 43 | * By default, use 'development' as NODE_ENV. This can be overriden with 44 | * 'staging', for example, by changing the ENV variables in the npm scripts 45 | */ 46 | new webpack.EnvironmentPlugin({ 47 | NODE_ENV: 'development', 48 | }), 49 | 50 | new webpack.LoaderOptionsPlugin({ 51 | debug: true, 52 | }), 53 | ], 54 | 55 | /** 56 | * Disables webpack processing of __dirname and __filename. 57 | * If you run the bundle in node.js it falls back to these values of node.js. 58 | * https://github.com/webpack/webpack/issues/2010 59 | */ 60 | node: { 61 | __dirname: false, 62 | __filename: false, 63 | }, 64 | 65 | watch: true, 66 | }; 67 | 68 | export default merge(baseConfig, configuration); 69 | -------------------------------------------------------------------------------- /gui/.erb/configs/webpack.config.renderer.dev.dll.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Builds the DLL for development electron renderer process 3 | */ 4 | 5 | import webpack from 'webpack'; 6 | import path from 'path'; 7 | import { merge } from 'webpack-merge'; 8 | import baseConfig from './webpack.config.base'; 9 | import webpackPaths from './webpack.paths'; 10 | import { dependencies } from '../../package.json'; 11 | import checkNodeEnv from '../scripts/check-node-env'; 12 | 13 | checkNodeEnv('development'); 14 | 15 | const dist = webpackPaths.dllPath; 16 | 17 | const configuration: webpack.Configuration = { 18 | context: webpackPaths.rootPath, 19 | 20 | devtool: 'eval', 21 | 22 | mode: 'development', 23 | 24 | target: 'electron-renderer', 25 | 26 | externals: ['fsevents', 'crypto-browserify'], 27 | 28 | /** 29 | * Use `module` from `webpack.config.renderer.dev.js` 30 | */ 31 | module: require('./webpack.config.renderer.dev').default.module, 32 | 33 | entry: { 34 | renderer: Object.keys(dependencies || {}), 35 | }, 36 | 37 | output: { 38 | path: dist, 39 | filename: '[name].dev.dll.js', 40 | library: { 41 | name: 'renderer', 42 | type: 'var', 43 | }, 44 | }, 45 | 46 | plugins: [ 47 | new webpack.DllPlugin({ 48 | path: path.join(dist, '[name].json'), 49 | name: '[name]', 50 | }), 51 | 52 | /** 53 | * Create global constants which can be configured at compile time. 54 | * 55 | * Useful for allowing different behaviour between development builds and 56 | * release builds 57 | * 58 | * NODE_ENV should be production so that modules do not perform certain 59 | * development checks 60 | */ 61 | new webpack.EnvironmentPlugin({ 62 | NODE_ENV: 'development', 63 | }), 64 | 65 | new webpack.LoaderOptionsPlugin({ 66 | debug: true, 67 | options: { 68 | context: webpackPaths.srcPath, 69 | output: { 70 | path: webpackPaths.dllPath, 71 | }, 72 | }, 73 | }), 74 | ], 75 | }; 76 | 77 | export default merge(baseConfig, configuration); 78 | -------------------------------------------------------------------------------- /gui/.erb/configs/webpack.paths.ts: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const rootPath = path.join(__dirname, '../..'); 4 | 5 | const dllPath = path.join(__dirname, '../dll'); 6 | 7 | const srcPath = path.join(rootPath, 'src'); 8 | const srcMainPath = path.join(srcPath, 'main'); 9 | const srcRendererPath = path.join(srcPath, 'renderer'); 10 | 11 | const releasePath = path.join(rootPath, 'release'); 12 | const appPath = path.join(releasePath, 'app'); 13 | const appPackagePath = path.join(appPath, 'package.json'); 14 | const appNodeModulesPath = path.join(appPath, 'node_modules'); 15 | const srcNodeModulesPath = path.join(srcPath, 'node_modules'); 16 | 17 | const distPath = path.join(appPath, 'dist'); 18 | const distMainPath = path.join(distPath, 'main'); 19 | const distRendererPath = path.join(distPath, 'renderer'); 20 | 21 | const buildPath = path.join(releasePath, 'build'); 22 | 23 | export default { 24 | rootPath, 25 | dllPath, 26 | srcPath, 27 | srcMainPath, 28 | srcRendererPath, 29 | releasePath, 30 | appPath, 31 | appPackagePath, 32 | appNodeModulesPath, 33 | srcNodeModulesPath, 34 | distPath, 35 | distMainPath, 36 | distRendererPath, 37 | buildPath, 38 | }; 39 | -------------------------------------------------------------------------------- /gui/.erb/img/erb-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Westworld-AI/VTube-Sama-App/5caa02321642411d77e09f445776f91ee7646b54/gui/.erb/img/erb-logo.png -------------------------------------------------------------------------------- /gui/.erb/mocks/fileMock.js: -------------------------------------------------------------------------------- 1 | export default 'test-file-stub'; 2 | -------------------------------------------------------------------------------- /gui/.erb/scripts/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-console": "off", 4 | "global-require": "off", 5 | "import/no-dynamic-require": "off", 6 | "import/no-extraneous-dependencies": "off" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /gui/.erb/scripts/check-build-exists.ts: -------------------------------------------------------------------------------- 1 | // Check if the renderer and main bundles are built 2 | import path from 'path'; 3 | import chalk from 'chalk'; 4 | import fs from 'fs'; 5 | import webpackPaths from '../configs/webpack.paths'; 6 | 7 | const mainPath = path.join(webpackPaths.distMainPath, 'main.js'); 8 | const rendererPath = path.join(webpackPaths.distRendererPath, 'renderer.js'); 9 | 10 | if (!fs.existsSync(mainPath)) { 11 | throw new Error( 12 | chalk.whiteBright.bgRed.bold( 13 | 'The main process is not built yet. Build it by running "npm run build:main"' 14 | ) 15 | ); 16 | } 17 | 18 | if (!fs.existsSync(rendererPath)) { 19 | throw new Error( 20 | chalk.whiteBright.bgRed.bold( 21 | 'The renderer process is not built yet. Build it by running "npm run build:renderer"' 22 | ) 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /gui/.erb/scripts/check-native-dep.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import chalk from 'chalk'; 3 | import { execSync } from 'child_process'; 4 | import { dependencies } from '../../package.json'; 5 | 6 | if (dependencies) { 7 | const dependenciesKeys = Object.keys(dependencies); 8 | const nativeDeps = fs 9 | .readdirSync('node_modules') 10 | .filter((folder) => fs.existsSync(`node_modules/${folder}/binding.gyp`)); 11 | if (nativeDeps.length === 0) { 12 | process.exit(0); 13 | } 14 | try { 15 | // Find the reason for why the dependency is installed. If it is installed 16 | // because of a devDependency then that is okay. Warn when it is installed 17 | // because of a dependency 18 | const { dependencies: dependenciesObject } = JSON.parse( 19 | execSync(`npm ls ${nativeDeps.join(' ')} --json`).toString() 20 | ); 21 | const rootDependencies = Object.keys(dependenciesObject); 22 | const filteredRootDependencies = rootDependencies.filter((rootDependency) => 23 | dependenciesKeys.includes(rootDependency) 24 | ); 25 | if (filteredRootDependencies.length > 0) { 26 | const plural = filteredRootDependencies.length > 1; 27 | console.log(` 28 | ${chalk.whiteBright.bgYellow.bold( 29 | 'Webpack does not work with native dependencies.' 30 | )} 31 | ${chalk.bold(filteredRootDependencies.join(', '))} ${ 32 | plural ? 'are native dependencies' : 'is a native dependency' 33 | } and should be installed inside of the "./release/app" folder. 34 | First, uninstall the packages from "./package.json": 35 | ${chalk.whiteBright.bgGreen.bold('npm uninstall your-package')} 36 | ${chalk.bold( 37 | 'Then, instead of installing the package to the root "./package.json":' 38 | )} 39 | ${chalk.whiteBright.bgRed.bold('npm install your-package')} 40 | ${chalk.bold('Install the package to "./release/app/package.json"')} 41 | ${chalk.whiteBright.bgGreen.bold( 42 | 'cd ./release/app && npm install your-package' 43 | )} 44 | Read more about native dependencies at: 45 | ${chalk.bold( 46 | 'https://electron-react-boilerplate.js.org/docs/adding-dependencies/#module-structure' 47 | )} 48 | `); 49 | process.exit(1); 50 | } 51 | } catch (e) { 52 | console.log('Native dependencies could not be checked'); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /gui/.erb/scripts/check-node-env.js: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | 3 | export default function checkNodeEnv(expectedEnv) { 4 | if (!expectedEnv) { 5 | throw new Error('"expectedEnv" not set'); 6 | } 7 | 8 | if (process.env.NODE_ENV !== expectedEnv) { 9 | console.log( 10 | chalk.whiteBright.bgRed.bold( 11 | `"process.env.NODE_ENV" must be "${expectedEnv}" to use this webpack config` 12 | ) 13 | ); 14 | process.exit(2); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /gui/.erb/scripts/check-port-in-use.js: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | import detectPort from 'detect-port'; 3 | 4 | const port = process.env.PORT || '1212'; 5 | 6 | detectPort(port, (err, availablePort) => { 7 | if (port !== String(availablePort)) { 8 | throw new Error( 9 | chalk.whiteBright.bgRed.bold( 10 | `Port "${port}" on "localhost" is already in use. Please use another port. ex: PORT=4343 npm start` 11 | ) 12 | ); 13 | } else { 14 | process.exit(0); 15 | } 16 | }); 17 | -------------------------------------------------------------------------------- /gui/.erb/scripts/clean.js: -------------------------------------------------------------------------------- 1 | import rimraf from 'rimraf'; 2 | import process from 'process'; 3 | import webpackPaths from '../configs/webpack.paths'; 4 | 5 | const args = process.argv.slice(2); 6 | const commandMap = { 7 | dist: webpackPaths.distPath, 8 | release: webpackPaths.releasePath, 9 | dll: webpackPaths.dllPath, 10 | }; 11 | 12 | args.forEach((x) => { 13 | const pathToRemove = commandMap[x]; 14 | if (pathToRemove !== undefined) { 15 | rimraf.sync(pathToRemove); 16 | } 17 | }); 18 | -------------------------------------------------------------------------------- /gui/.erb/scripts/delete-source-maps.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import rimraf from 'rimraf'; 3 | import webpackPaths from '../configs/webpack.paths'; 4 | 5 | export default function deleteSourceMaps() { 6 | rimraf.sync(path.join(webpackPaths.distMainPath, '*.js.map')); 7 | rimraf.sync(path.join(webpackPaths.distRendererPath, '*.js.map')); 8 | } 9 | -------------------------------------------------------------------------------- /gui/.erb/scripts/electron-rebuild.js: -------------------------------------------------------------------------------- 1 | import { execSync } from 'child_process'; 2 | import fs from 'fs'; 3 | import { dependencies } from '../../release/app/package.json'; 4 | import webpackPaths from '../configs/webpack.paths'; 5 | 6 | if ( 7 | Object.keys(dependencies || {}).length > 0 && 8 | fs.existsSync(webpackPaths.appNodeModulesPath) 9 | ) { 10 | const electronRebuildCmd = 11 | '../../node_modules/.bin/electron-rebuild --force --types prod,dev,optional --module-dir .'; 12 | const cmd = 13 | process.platform === 'win32' 14 | ? electronRebuildCmd.replace(/\//g, '\\') 15 | : electronRebuildCmd; 16 | execSync(cmd, { 17 | cwd: webpackPaths.appPath, 18 | stdio: 'inherit', 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /gui/.erb/scripts/link-modules.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import webpackPaths from '../configs/webpack.paths'; 3 | 4 | const { srcNodeModulesPath } = webpackPaths; 5 | const { appNodeModulesPath } = webpackPaths; 6 | 7 | if (!fs.existsSync(srcNodeModulesPath) && fs.existsSync(appNodeModulesPath)) { 8 | fs.symlinkSync(appNodeModulesPath, srcNodeModulesPath, 'junction'); 9 | } 10 | -------------------------------------------------------------------------------- /gui/.erb/scripts/notarize.js: -------------------------------------------------------------------------------- 1 | const { notarize } = require('electron-notarize'); 2 | const { build } = require('../../package.json'); 3 | 4 | exports.default = async function notarizeMacos(context) { 5 | const { electronPlatformName, appOutDir } = context; 6 | if (electronPlatformName !== 'darwin') { 7 | return; 8 | } 9 | 10 | if (process.env.CI !== 'true') { 11 | console.warn('Skipping notarizing step. Packaging is not running in CI'); 12 | return; 13 | } 14 | 15 | if (!('APPLE_ID' in process.env && 'APPLE_ID_PASS' in process.env)) { 16 | console.warn( 17 | 'Skipping notarizing step. APPLE_ID and APPLE_ID_PASS env variables must be set' 18 | ); 19 | return; 20 | } 21 | 22 | const appName = context.packager.appInfo.productFilename; 23 | 24 | await notarize({ 25 | appBundleId: build.appId, 26 | appPath: `${appOutDir}/${appName}.app`, 27 | appleId: process.env.APPLE_ID, 28 | appleIdPassword: process.env.APPLE_ID_PASS, 29 | }); 30 | }; 31 | -------------------------------------------------------------------------------- /gui/.erb/scripts/typeorm-create.js: -------------------------------------------------------------------------------- 1 | // typeorm-create.js 2 | const { exec } = require('child_process'); 3 | const migrationName = process.argv[2]; 4 | 5 | // Make sure the migration name is provided 6 | if (!migrationName) { 7 | throw new Error('Error: Migration name not provided!'); 8 | } 9 | 10 | const command = `node ./release/app/node_modules/typeorm/cli.js migration:create src/main/migrations/${migrationName}`; 11 | 12 | // Execute the TypeORM command 13 | exec(command, (error, stdout, stderr) => { 14 | if (error) { 15 | console.error(`An error occurred: ${error.message}`); 16 | return; 17 | } 18 | if (stderr) { 19 | console.error(`Error output: ${stderr}`); 20 | } 21 | console.log(`Migration created: ${stdout}`); 22 | }); 23 | -------------------------------------------------------------------------------- /gui/.eslintignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Coverage directory used by tools like istanbul 11 | coverage 12 | .eslintcache 13 | 14 | # Dependency directory 15 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 16 | node_modules 17 | 18 | # OSX 19 | .DS_Store 20 | 21 | release/app/dist 22 | release/build 23 | .erb/dll 24 | 25 | .idea 26 | npm-debug.log.* 27 | *.css.d.ts 28 | *.sass.d.ts 29 | *.scss.d.ts 30 | 31 | # eslint ignores hidden directories by default: 32 | # https://github.com/eslint/eslint/issues/8429 33 | !.erb 34 | -------------------------------------------------------------------------------- /gui/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: 'erb', 3 | rules: { 4 | // A temporary hack related to IDE not resolving correct package.json 5 | 'import/no-extraneous-dependencies': 'off', 6 | 'import/no-unresolved': 'error', 7 | // Since React 17 and typescript 4.1 you can safely disable the rule 8 | 'react/react-in-jsx-scope': 'off', 9 | }, 10 | parserOptions: { 11 | ecmaVersion: 2020, 12 | sourceType: 'module', 13 | project: './tsconfig.json', 14 | tsconfigRootDir: __dirname, 15 | createDefaultProgram: true, 16 | }, 17 | settings: { 18 | 'import/resolver': { 19 | // See https://github.com/benmosher/eslint-plugin-import/issues/1396#issuecomment-575727774 for line below 20 | node: {}, 21 | webpack: { 22 | config: require.resolve('./.erb/configs/webpack.config.eslint.ts'), 23 | }, 24 | typescript: {}, 25 | }, 26 | 'import/parsers': { 27 | '@typescript-eslint/parser': ['.ts', '.tsx'], 28 | }, 29 | }, 30 | }; 31 | -------------------------------------------------------------------------------- /gui/.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | *.exe binary 3 | *.png binary 4 | *.jpg binary 5 | *.jpeg binary 6 | *.ico binary 7 | *.icns binary 8 | *.eot binary 9 | *.otf binary 10 | *.ttf binary 11 | *.woff binary 12 | *.woff2 binary 13 | -------------------------------------------------------------------------------- /gui/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Coverage directory used by tools like istanbul 11 | coverage 12 | .eslintcache 13 | 14 | # Dependency directory 15 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 16 | node_modules 17 | 18 | # OSX 19 | .DS_Store 20 | 21 | release/app/dist 22 | release/build 23 | .erb/dll 24 | 25 | .idea 26 | npm-debug.log.* 27 | *.css.d.ts 28 | *.sass.d.ts 29 | *.scss.d.ts 30 | -------------------------------------------------------------------------------- /gui/.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /gui/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-present Electron React Boilerplate 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /gui/README.md: -------------------------------------------------------------------------------- 1 | ## Starting Development 2 | 3 | Start the app in the `dev` environment: 4 | 5 | ```bash 6 | yarn install && npm run electron-rebuild 7 | ``` 8 | 9 | Start the app in the `dev` environment: 10 | 11 | ```bash 12 | yarn start 13 | ``` 14 | 15 | ## Packaging for Production 16 | 17 | To package apps for the local platform: 18 | 19 | ```bash 20 | yarn package 21 | ``` 22 | -------------------------------------------------------------------------------- /gui/assets/assets.d.ts: -------------------------------------------------------------------------------- 1 | type Styles = Record; 2 | 3 | declare module '*.svg' { 4 | const content: string; 5 | export default content; 6 | } 7 | 8 | declare module '*.png' { 9 | const content: string; 10 | export default content; 11 | } 12 | 13 | declare module '*.jpg' { 14 | const content: string; 15 | export default content; 16 | } 17 | 18 | declare module '*.scss' { 19 | const content: Styles; 20 | export default content; 21 | } 22 | 23 | declare module '*.sass' { 24 | const content: Styles; 25 | export default content; 26 | } 27 | 28 | declare module '*.css' { 29 | const content: Styles; 30 | export default content; 31 | } 32 | -------------------------------------------------------------------------------- /gui/assets/background/Room01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Westworld-AI/VTube-Sama-App/5caa02321642411d77e09f445776f91ee7646b54/gui/assets/background/Room01.jpg -------------------------------------------------------------------------------- /gui/assets/background/Room02.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Westworld-AI/VTube-Sama-App/5caa02321642411d77e09f445776f91ee7646b54/gui/assets/background/Room02.jpeg -------------------------------------------------------------------------------- /gui/assets/background/Room03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Westworld-AI/VTube-Sama-App/5caa02321642411d77e09f445776f91ee7646b54/gui/assets/background/Room03.png -------------------------------------------------------------------------------- /gui/assets/background/Room04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Westworld-AI/VTube-Sama-App/5caa02321642411d77e09f445776f91ee7646b54/gui/assets/background/Room04.png -------------------------------------------------------------------------------- /gui/assets/background/School01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Westworld-AI/VTube-Sama-App/5caa02321642411d77e09f445776f91ee7646b54/gui/assets/background/School01.jpg -------------------------------------------------------------------------------- /gui/assets/background/School02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Westworld-AI/VTube-Sama-App/5caa02321642411d77e09f445776f91ee7646b54/gui/assets/background/School02.jpg -------------------------------------------------------------------------------- /gui/assets/background/Street01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Westworld-AI/VTube-Sama-App/5caa02321642411d77e09f445776f91ee7646b54/gui/assets/background/Street01.png -------------------------------------------------------------------------------- /gui/assets/background/Street02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Westworld-AI/VTube-Sama-App/5caa02321642411d77e09f445776f91ee7646b54/gui/assets/background/Street02.jpg -------------------------------------------------------------------------------- /gui/assets/background/Street03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Westworld-AI/VTube-Sama-App/5caa02321642411d77e09f445776f91ee7646b54/gui/assets/background/Street03.jpg -------------------------------------------------------------------------------- /gui/assets/background/Street04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Westworld-AI/VTube-Sama-App/5caa02321642411d77e09f445776f91ee7646b54/gui/assets/background/Street04.jpg -------------------------------------------------------------------------------- /gui/assets/background/Street05.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Westworld-AI/VTube-Sama-App/5caa02321642411d77e09f445776f91ee7646b54/gui/assets/background/Street05.jpg -------------------------------------------------------------------------------- /gui/assets/entitlements.mac.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.cs.allow-unsigned-executable-memory 6 | 7 | com.apple.security.cs.allow-jit 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /gui/assets/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Westworld-AI/VTube-Sama-App/5caa02321642411d77e09f445776f91ee7646b54/gui/assets/icon.icns -------------------------------------------------------------------------------- /gui/assets/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Westworld-AI/VTube-Sama-App/5caa02321642411d77e09f445776f91ee7646b54/gui/assets/icon.ico -------------------------------------------------------------------------------- /gui/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Westworld-AI/VTube-Sama-App/5caa02321642411d77e09f445776f91ee7646b54/gui/assets/icon.png -------------------------------------------------------------------------------- /gui/assets/icons/1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Westworld-AI/VTube-Sama-App/5caa02321642411d77e09f445776f91ee7646b54/gui/assets/icons/1024x1024.png -------------------------------------------------------------------------------- /gui/assets/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Westworld-AI/VTube-Sama-App/5caa02321642411d77e09f445776f91ee7646b54/gui/assets/icons/128x128.png -------------------------------------------------------------------------------- /gui/assets/icons/16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Westworld-AI/VTube-Sama-App/5caa02321642411d77e09f445776f91ee7646b54/gui/assets/icons/16x16.png -------------------------------------------------------------------------------- /gui/assets/icons/24x24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Westworld-AI/VTube-Sama-App/5caa02321642411d77e09f445776f91ee7646b54/gui/assets/icons/24x24.png -------------------------------------------------------------------------------- /gui/assets/icons/256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Westworld-AI/VTube-Sama-App/5caa02321642411d77e09f445776f91ee7646b54/gui/assets/icons/256x256.png -------------------------------------------------------------------------------- /gui/assets/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Westworld-AI/VTube-Sama-App/5caa02321642411d77e09f445776f91ee7646b54/gui/assets/icons/32x32.png -------------------------------------------------------------------------------- /gui/assets/icons/48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Westworld-AI/VTube-Sama-App/5caa02321642411d77e09f445776f91ee7646b54/gui/assets/icons/48x48.png -------------------------------------------------------------------------------- /gui/assets/icons/512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Westworld-AI/VTube-Sama-App/5caa02321642411d77e09f445776f91ee7646b54/gui/assets/icons/512x512.png -------------------------------------------------------------------------------- /gui/assets/icons/64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Westworld-AI/VTube-Sama-App/5caa02321642411d77e09f445776f91ee7646b54/gui/assets/icons/64x64.png -------------------------------------------------------------------------------- /gui/assets/icons/96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Westworld-AI/VTube-Sama-App/5caa02321642411d77e09f445776f91ee7646b54/gui/assets/icons/96x96.png -------------------------------------------------------------------------------- /gui/assets/images/character/character-model-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Westworld-AI/VTube-Sama-App/5caa02321642411d77e09f445776f91ee7646b54/gui/assets/images/character/character-model-icon.png -------------------------------------------------------------------------------- /gui/assets/images/character/fubuki.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Westworld-AI/VTube-Sama-App/5caa02321642411d77e09f445776f91ee7646b54/gui/assets/images/character/fubuki.png -------------------------------------------------------------------------------- /gui/assets/images/character/yatuoli.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Westworld-AI/VTube-Sama-App/5caa02321642411d77e09f445776f91ee7646b54/gui/assets/images/character/yatuoli.jpeg -------------------------------------------------------------------------------- /gui/assets/models/Hiyori/Hiyori.2048/texture_00.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Westworld-AI/VTube-Sama-App/5caa02321642411d77e09f445776f91ee7646b54/gui/assets/models/Hiyori/Hiyori.2048/texture_00.png -------------------------------------------------------------------------------- /gui/assets/models/Hiyori/Hiyori.2048/texture_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Westworld-AI/VTube-Sama-App/5caa02321642411d77e09f445776f91ee7646b54/gui/assets/models/Hiyori/Hiyori.2048/texture_01.png -------------------------------------------------------------------------------- /gui/assets/models/Hiyori/Hiyori.moc3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Westworld-AI/VTube-Sama-App/5caa02321642411d77e09f445776f91ee7646b54/gui/assets/models/Hiyori/Hiyori.moc3 -------------------------------------------------------------------------------- /gui/assets/models/Hiyori/Hiyori.model3.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": 3, 3 | "FileReferences": { 4 | "Moc": "Hiyori.moc3", 5 | "Textures": [ 6 | "Hiyori.2048/texture_00.png", 7 | "Hiyori.2048/texture_01.png" 8 | ], 9 | "Physics": "Hiyori.physics3.json", 10 | "Pose": "Hiyori.pose3.json", 11 | "UserData": "Hiyori.userdata3.json", 12 | "Motions": { 13 | "Idle": [ 14 | { 15 | "File": "motions/Hiyori_m01.motion3.json", 16 | "FadeInTime": 0.5, 17 | "FadeOutTime": 0.5 18 | }, 19 | { 20 | "File": "motions/Hiyori_m02.motion3.json", 21 | "FadeInTime": 0.5, 22 | "FadeOutTime": 0.5 23 | }, 24 | { 25 | "File": "motions/Hiyori_m03.motion3.json", 26 | "FadeInTime": 0.5, 27 | "FadeOutTime": 0.5 28 | }, 29 | { 30 | "File": "motions/Hiyori_m05.motion3.json", 31 | "FadeInTime": 0.5, 32 | "FadeOutTime": 0.5 33 | }, 34 | { 35 | "File": "motions/Hiyori_m06.motion3.json", 36 | "FadeInTime": 0.5, 37 | "FadeOutTime": 0.5 38 | }, 39 | { 40 | "File": "motions/Hiyori_m07.motion3.json", 41 | "FadeInTime": 0.5, 42 | "FadeOutTime": 0.5 43 | }, 44 | { 45 | "File": "motions/Hiyori_m08.motion3.json", 46 | "FadeInTime": 0.5, 47 | "FadeOutTime": 0.5 48 | }, 49 | { 50 | "File": "motions/Hiyori_m09.motion3.json", 51 | "FadeInTime": 0.5, 52 | "FadeOutTime": 0.5 53 | }, 54 | { 55 | "File": "motions/Hiyori_m10.motion3.json", 56 | "FadeInTime": 0.5, 57 | "FadeOutTime": 0.5 58 | } 59 | ], 60 | "TapBody": [ 61 | { 62 | "File": "motions/Hiyori_m04.motion3.json", 63 | "FadeInTime": 0.5, 64 | "FadeOutTime": 0.5 65 | } 66 | ] 67 | } 68 | }, 69 | "Groups": [ 70 | { 71 | "Target": "Parameter", 72 | "Name": "LipSync", 73 | "Ids": [ 74 | "ParamMouthOpenY" 75 | ] 76 | }, 77 | { 78 | "Target": "Parameter", 79 | "Name": "EyeBlink", 80 | "Ids": [ 81 | "ParamEyeLOpen", 82 | "ParamEyeROpen" 83 | ] 84 | } 85 | ], 86 | "HitAreas": [ 87 | { 88 | "Id": "HitArea", 89 | "Name": "Body" 90 | } 91 | ] 92 | } -------------------------------------------------------------------------------- /gui/assets/models/Hiyori/Hiyori.pose3.json: -------------------------------------------------------------------------------- 1 | { 2 | "Type": "Live2D Pose", 3 | "FadeInTime": 0.5, 4 | "Groups": [ 5 | [ 6 | { 7 | "Id": "PartArmA", 8 | "Link": [] 9 | }, 10 | { 11 | "Id": "PartArmB", 12 | "Link": [] 13 | } 14 | ] 15 | ] 16 | } -------------------------------------------------------------------------------- /gui/assets/models/Hiyori/Hiyori.userdata3.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": 3, 3 | "Meta": { 4 | "UserDataCount": 7, 5 | "TotalUserDataSize": 35 6 | }, 7 | "UserData": [ 8 | { 9 | "Target": "ArtMesh", 10 | "Id": "ArtMesh93", 11 | "Value": "ribon" 12 | }, 13 | { 14 | "Target": "ArtMesh", 15 | "Id": "ArtMesh94", 16 | "Value": "ribon" 17 | }, 18 | { 19 | "Target": "ArtMesh", 20 | "Id": "ArtMesh95", 21 | "Value": "ribon" 22 | }, 23 | { 24 | "Target": "ArtMesh", 25 | "Id": "ArtMesh57", 26 | "Value": "ribon" 27 | }, 28 | { 29 | "Target": "ArtMesh", 30 | "Id": "ArtMesh58", 31 | "Value": "ribon" 32 | }, 33 | { 34 | "Target": "ArtMesh", 35 | "Id": "ArtMesh59", 36 | "Value": "ribon" 37 | }, 38 | { 39 | "Target": "ArtMesh", 40 | "Id": "ArtMesh60", 41 | "Value": "ribon" 42 | } 43 | ] 44 | } -------------------------------------------------------------------------------- /gui/assets/models/atri/atri.model3.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": 3, 3 | "FileReferences": { 4 | "Moc": "atri_8.moc3", 5 | "Textures": [ 6 | "atri_8.4096/texture_00.png", 7 | "atri_8.4096/texture_01.png" 8 | ], 9 | "Physics": "atri_8.physics3.json", 10 | "DisplayInfo": "atri_8.cdi3.json" 11 | }, 12 | "Groups": [ 13 | { 14 | "Target": "Parameter", 15 | "Name": "EyeBlink", 16 | "Ids": [ 17 | "ParamEyeLOpen", 18 | "ParamEyeROpen" 19 | ] 20 | }, 21 | { 22 | "Target": "Parameter", 23 | "Name": "LipSync", 24 | "Ids": [ 25 | "ParamMouthForm", 26 | "ParamMouthOpenY" 27 | ] 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /gui/assets/models/atri/atri_8.4096/texture_00.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Westworld-AI/VTube-Sama-App/5caa02321642411d77e09f445776f91ee7646b54/gui/assets/models/atri/atri_8.4096/texture_00.png -------------------------------------------------------------------------------- /gui/assets/models/atri/atri_8.4096/texture_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Westworld-AI/VTube-Sama-App/5caa02321642411d77e09f445776f91ee7646b54/gui/assets/models/atri/atri_8.4096/texture_01.png -------------------------------------------------------------------------------- /gui/assets/models/atri/atri_8.moc3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Westworld-AI/VTube-Sama-App/5caa02321642411d77e09f445776f91ee7646b54/gui/assets/models/atri/atri_8.moc3 -------------------------------------------------------------------------------- /gui/assets/models/atri/dec-l.motion3.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": 3, 3 | "Meta": { 4 | "Duration": 3.333, 5 | "Fps": 30.0, 6 | "Loop": true, 7 | "AreBeziersRestricted": false, 8 | "CurveCount": 2, 9 | "TotalSegmentCount": 14, 10 | "TotalPointCount": 40, 11 | "UserDataCount": 0, 12 | "TotalUserDataSize": 0 13 | }, 14 | "Curves": [ 15 | { 16 | "Target": "Parameter", 17 | "Id": "Param4", 18 | "Segments": [ 19 | 0, 20 | 0, 21 | 1, 22 | 0.167, 23 | 0, 24 | 0.333, 25 | -30, 26 | 0.5, 27 | -30, 28 | 1, 29 | 0.667, 30 | -30, 31 | 0.833, 32 | 30, 33 | 1, 34 | 30, 35 | 1, 36 | 1.167, 37 | 30, 38 | 1.333, 39 | 20, 40 | 1.5, 41 | 0, 42 | 1, 43 | 1.667, 44 | -20, 45 | 1.833, 46 | -30, 47 | 2, 48 | -30, 49 | 1, 50 | 2.167, 51 | -30, 52 | 2.333, 53 | 30, 54 | 2.5, 55 | 30, 56 | 1, 57 | 2.667, 58 | 30, 59 | 2.833, 60 | 0, 61 | 3, 62 | 0, 63 | 0, 64 | 3.333, 65 | 0 66 | ] 67 | }, 68 | { 69 | "Target": "Parameter", 70 | "Id": "Param5", 71 | "Segments": [ 72 | 0, 73 | 0, 74 | 1, 75 | 0.167, 76 | 0, 77 | 0.333, 78 | -30, 79 | 0.5, 80 | -30, 81 | 1, 82 | 0.667, 83 | -30, 84 | 0.833, 85 | 30, 86 | 1, 87 | 30, 88 | 1, 89 | 1.167, 90 | 30, 91 | 1.333, 92 | 20, 93 | 1.5, 94 | 0, 95 | 1, 96 | 1.667, 97 | -20, 98 | 1.833, 99 | -30, 100 | 2, 101 | -30, 102 | 1, 103 | 2.167, 104 | -30, 105 | 2.333, 106 | 30, 107 | 2.5, 108 | 30, 109 | 1, 110 | 2.667, 111 | 30, 112 | 2.833, 113 | 0, 114 | 3, 115 | 0, 116 | 0, 117 | 3.333, 118 | 0 119 | ] 120 | } 121 | ] 122 | } -------------------------------------------------------------------------------- /gui/assets/models/atri/dec-r.motion3.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": 3, 3 | "Meta": { 4 | "Duration": 3.333, 5 | "Fps": 30.0, 6 | "Loop": true, 7 | "AreBeziersRestricted": false, 8 | "CurveCount": 2, 9 | "TotalSegmentCount": 14, 10 | "TotalPointCount": 40, 11 | "UserDataCount": 0, 12 | "TotalUserDataSize": 0 13 | }, 14 | "Curves": [ 15 | { 16 | "Target": "Parameter", 17 | "Id": "Param6", 18 | "Segments": [ 19 | 0, 20 | 0, 21 | 1, 22 | 0.167, 23 | 0, 24 | 0.333, 25 | -30, 26 | 0.5, 27 | -30, 28 | 1, 29 | 0.667, 30 | -30, 31 | 0.833, 32 | 30, 33 | 1, 34 | 30, 35 | 1, 36 | 1.167, 37 | 30, 38 | 1.333, 39 | 20, 40 | 1.5, 41 | 0, 42 | 1, 43 | 1.667, 44 | -20, 45 | 1.833, 46 | -30, 47 | 2, 48 | -30, 49 | 1, 50 | 2.167, 51 | -30, 52 | 2.333, 53 | 30, 54 | 2.5, 55 | 30, 56 | 1, 57 | 2.667, 58 | 30, 59 | 2.833, 60 | 0, 61 | 3, 62 | 0, 63 | 0, 64 | 3.333, 65 | 0 66 | ] 67 | }, 68 | { 69 | "Target": "Parameter", 70 | "Id": "Param7", 71 | "Segments": [ 72 | 0, 73 | 0, 74 | 1, 75 | 0.167, 76 | 0, 77 | 0.333, 78 | -30, 79 | 0.5, 80 | -30, 81 | 1, 82 | 0.667, 83 | -30, 84 | 0.833, 85 | 30, 86 | 1, 87 | 30, 88 | 1, 89 | 1.167, 90 | 30, 91 | 1.333, 92 | 20, 93 | 1.5, 94 | 0, 95 | 1, 96 | 1.667, 97 | -20, 98 | 1.833, 99 | -30, 100 | 2, 101 | -30, 102 | 1, 103 | 2.167, 104 | -30, 105 | 2.333, 106 | 30, 107 | 2.5, 108 | 30, 109 | 1, 110 | 2.667, 111 | 30, 112 | 2.833, 113 | 0, 114 | 3, 115 | 0, 116 | 0, 117 | 3.333, 118 | 0 119 | ] 120 | } 121 | ] 122 | } -------------------------------------------------------------------------------- /gui/assets/models/atri/expression1.exp3.json: -------------------------------------------------------------------------------- 1 | { 2 | "Type": "Live2D Expression", 3 | "Parameters": [ 4 | { 5 | "Id": "ParamCheek", 6 | "Value": 1, 7 | "Blend": "Add" 8 | } 9 | ] 10 | } -------------------------------------------------------------------------------- /gui/assets/models/atri/expression10.exp3.json: -------------------------------------------------------------------------------- 1 | { 2 | "Type": "Live2D Expression", 3 | "Parameters": [ 4 | { 5 | "Id": "Param37", 6 | "Value": 30, 7 | "Blend": "Add" 8 | } 9 | ] 10 | } -------------------------------------------------------------------------------- /gui/assets/models/atri/expression11.exp3.json: -------------------------------------------------------------------------------- 1 | { 2 | "Type": "Live2D Expression", 3 | "Parameters": [ 4 | { 5 | "Id": "Param38", 6 | "Value": 30, 7 | "Blend": "Add" 8 | } 9 | ] 10 | } -------------------------------------------------------------------------------- /gui/assets/models/atri/expression12.exp3.json: -------------------------------------------------------------------------------- 1 | { 2 | "Type": "Live2D Expression", 3 | "Parameters": [ 4 | { 5 | "Id": "Param39", 6 | "Value": -30, 7 | "Blend": "Add" 8 | } 9 | ] 10 | } -------------------------------------------------------------------------------- /gui/assets/models/atri/expression13.exp3.json: -------------------------------------------------------------------------------- 1 | { 2 | "Type": "Live2D Expression", 3 | "Parameters": [ 4 | { 5 | "Id": "Param39", 6 | "Value": 30, 7 | "Blend": "Add" 8 | } 9 | ] 10 | } -------------------------------------------------------------------------------- /gui/assets/models/atri/expression14.exp3.json: -------------------------------------------------------------------------------- 1 | { 2 | "Type": "Live2D Expression", 3 | "Parameters": [ 4 | { 5 | "Id": "Param9", 6 | "Value": 30, 7 | "Blend": "Add" 8 | } 9 | ] 10 | } -------------------------------------------------------------------------------- /gui/assets/models/atri/expression15.exp3.json: -------------------------------------------------------------------------------- 1 | { 2 | "Type": "Live2D Expression", 3 | "Parameters": [ 4 | { 5 | "Id": "Param10", 6 | "Value": 30, 7 | "Blend": "Add" 8 | } 9 | ] 10 | } -------------------------------------------------------------------------------- /gui/assets/models/atri/expression16.exp3.json: -------------------------------------------------------------------------------- 1 | { 2 | "Type": "Live2D Expression", 3 | "Parameters": [ 4 | { 5 | "Id": "Param36", 6 | "Value": 30, 7 | "Blend": "Add" 8 | }, 9 | { 10 | "Id": "Param10", 11 | "Value": 30, 12 | "Blend": "Add" 13 | }, 14 | { 15 | "Id": "Param21", 16 | "Value": 30, 17 | "Blend": "Add" 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /gui/assets/models/atri/expression2.exp3.json: -------------------------------------------------------------------------------- 1 | { 2 | "Type": "Live2D Expression", 3 | "Parameters": [ 4 | { 5 | "Id": "Param21", 6 | "Value": 30, 7 | "Blend": "Add" 8 | } 9 | ] 10 | } -------------------------------------------------------------------------------- /gui/assets/models/atri/expression3.exp3.json: -------------------------------------------------------------------------------- 1 | { 2 | "Type": "Live2D Expression", 3 | "Parameters": [ 4 | { 5 | "Id": "Param18", 6 | "Value": 30, 7 | "Blend": "Add" 8 | } 9 | ] 10 | } -------------------------------------------------------------------------------- /gui/assets/models/atri/expression4.exp3.json: -------------------------------------------------------------------------------- 1 | { 2 | "Type": "Live2D Expression", 3 | "Parameters": [ 4 | { 5 | "Id": "Param17", 6 | "Value": 30, 7 | "Blend": "Add" 8 | } 9 | ] 10 | } -------------------------------------------------------------------------------- /gui/assets/models/atri/expression5.exp3.json: -------------------------------------------------------------------------------- 1 | { 2 | "Type": "Live2D Expression", 3 | "Parameters": [ 4 | { 5 | "Id": "Param19", 6 | "Value": 30, 7 | "Blend": "Add" 8 | } 9 | ] 10 | } -------------------------------------------------------------------------------- /gui/assets/models/atri/expression6.exp3.json: -------------------------------------------------------------------------------- 1 | { 2 | "Type": "Live2D Expression", 3 | "Parameters": [ 4 | { 5 | "Id": "Param20", 6 | "Value": 30, 7 | "Blend": "Add" 8 | } 9 | ] 10 | } -------------------------------------------------------------------------------- /gui/assets/models/atri/expression7.exp3.json: -------------------------------------------------------------------------------- 1 | { 2 | "Type": "Live2D Expression", 3 | "Parameters": [ 4 | { 5 | "Id": "Param22", 6 | "Value": 30, 7 | "Blend": "Add" 8 | } 9 | ] 10 | } -------------------------------------------------------------------------------- /gui/assets/models/atri/expression8.exp3.json: -------------------------------------------------------------------------------- 1 | { 2 | "Type": "Live2D Expression", 3 | "Parameters": [ 4 | { 5 | "Id": "Param31", 6 | "Value": 30, 7 | "Blend": "Add" 8 | } 9 | ] 10 | } -------------------------------------------------------------------------------- /gui/assets/models/atri/expression9.exp3.json: -------------------------------------------------------------------------------- 1 | { 2 | "Type": "Live2D Expression", 3 | "Parameters": [ 4 | { 5 | "Id": "Param36", 6 | "Value": 30, 7 | "Blend": "Add" 8 | } 9 | ] 10 | } -------------------------------------------------------------------------------- /gui/assets/models/atri/model.motion3.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": 3, 3 | "Meta": { 4 | "Duration": 3.333, 5 | "Fps": 30.0, 6 | "Loop": true, 7 | "AreBeziersRestricted": false, 8 | "CurveCount": 0, 9 | "TotalSegmentCount": 0, 10 | "TotalPointCount": 0, 11 | "UserDataCount": 0, 12 | "TotalUserDataSize": 0 13 | }, 14 | "Curves": [] 15 | } -------------------------------------------------------------------------------- /gui/assets/models/chuixue_3/chuixue_3.moc3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Westworld-AI/VTube-Sama-App/5caa02321642411d77e09f445776f91ee7646b54/gui/assets/models/chuixue_3/chuixue_3.moc3 -------------------------------------------------------------------------------- /gui/assets/models/chuixue_3/chuixue_3.model3.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": 3, 3 | "FileReferences": { 4 | "Moc": "chuixue_3.moc3", 5 | "Textures": [ 6 | "textures/texture_00.png" 7 | ], 8 | "Physics": "chuixue_3.physics3.json", 9 | "Motions": { 10 | "": [ 11 | { 12 | "File": "motions/complete.motion3.json" 13 | }, 14 | { 15 | "File": "motions/home.motion3.json" 16 | }, 17 | { 18 | "File": "motions/idle.motion3.json" 19 | }, 20 | { 21 | "File": "motions/login.motion3.json" 22 | }, 23 | { 24 | "File": "motions/mail.motion3.json" 25 | }, 26 | { 27 | "File": "motions/main_1.motion3.json" 28 | }, 29 | { 30 | "File": "motions/main_2.motion3.json" 31 | }, 32 | { 33 | "File": "motions/main_3.motion3.json" 34 | }, 35 | { 36 | "File": "motions/mission_complete.motion3.json" 37 | }, 38 | { 39 | "File": "motions/mission.motion3.json" 40 | }, 41 | { 42 | "File": "motions/touch_body.motion3.json" 43 | }, 44 | { 45 | "File": "motions/touch_head.motion3.json" 46 | }, 47 | { 48 | "File": "motions/touch_special.motion3.json" 49 | }, 50 | { 51 | "File": "motions/wedding.motion3.json" 52 | } 53 | ] 54 | } 55 | }, 56 | "Groups": [ 57 | { 58 | "Target": "Parameter", 59 | "Name": "LipSync", 60 | "Ids": [ 61 | "ParamMouthOpenY1" 62 | ] 63 | } 64 | ] 65 | } 66 | -------------------------------------------------------------------------------- /gui/assets/models/chuixue_3/textures/texture_00.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Westworld-AI/VTube-Sama-App/5caa02321642411d77e09f445776f91ee7646b54/gui/assets/models/chuixue_3/textures/texture_00.png -------------------------------------------------------------------------------- /gui/assets/vosk/vosk-model-small-cn-0.3.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Westworld-AI/VTube-Sama-App/5caa02321642411d77e09f445776f91ee7646b54/gui/assets/vosk/vosk-model-small-cn-0.3.tar.gz -------------------------------------------------------------------------------- /gui/postcss.config.js: -------------------------------------------------------------------------------- 1 | /* eslint global-require: off, import/no-extraneous-dependencies: off */ 2 | 3 | module.exports = { 4 | plugins: [require('tailwindcss'), require('autoprefixer')], 5 | }; 6 | -------------------------------------------------------------------------------- /gui/src/__tests__/App.test.tsx: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom'; 2 | import { render } from '@testing-library/react'; 3 | import App from '../renderer/App'; 4 | 5 | describe('App', () => { 6 | it('should render', () => { 7 | expect(render()).toBeTruthy(); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /gui/src/main/domain/dao/characterChatHistoryDao.ts: -------------------------------------------------------------------------------- 1 | import { Connection, Not, Equal } from 'typeorm'; 2 | import { BaseDao } from '../../framework/orm/baseDao'; 3 | import { CharacterChatHistory } from '../entitys/characterChatHistory'; 4 | 5 | export class CharacterChatHistoryDao extends BaseDao { 6 | 7 | constructor(connection: Connection) { 8 | super(connection, CharacterChatHistory); 9 | } 10 | 11 | async findByCharacterId(characterId: number, page: number, pageSize: number, 12 | order: Record = { createTime: 'DESC' }): Promise { 13 | // 计算跳过的条目数量 14 | const skip: number = (page - 1) * pageSize; 15 | // 使用find方法,并传入skip和take选项进行分页 16 | return this.repository.find({ 17 | where: { 18 | character_id: characterId 19 | }, 20 | take: pageSize, 21 | skip: skip, 22 | order: order 23 | }); 24 | } 25 | 26 | async scrollReadData(characterId: number, pageSize: number): Promise { 27 | // 利用降序查询获取最新的条目 28 | const latestMassages = await this.repository.find({ 29 | where: { 30 | character_id: characterId, 31 | answer: Not(Equal('')) 32 | }, 33 | take: pageSize, 34 | order: { 35 | createTime: 'DESC' // 降序查询 36 | } 37 | }); 38 | // 使用Array.reverse()将数组顺序反转为升序 39 | return latestMassages.reverse(); 40 | } 41 | 42 | async findCountByCharacterId(characterId: number) { 43 | return await this.repository.count({ 44 | where: { 45 | character_id: characterId 46 | } 47 | }); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /gui/src/main/domain/dao/characterDao.ts: -------------------------------------------------------------------------------- 1 | import { Connection } from 'typeorm'; 2 | import { Character } from '../entitys/character'; 3 | import { BaseDao } from '../../framework/orm/baseDao'; 4 | 5 | export class CharacterDao extends BaseDao { 6 | 7 | constructor(connection: Connection) { 8 | super(connection, Character); 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /gui/src/main/domain/dao/characterModelDao.ts: -------------------------------------------------------------------------------- 1 | import {Connection} from 'typeorm'; 2 | import {BaseDao} from "../../framework/orm/baseDao"; 3 | import {CharacterModel} from "../entitys/characterModel"; 4 | 5 | export class CharacterModelDao extends BaseDao { 6 | 7 | constructor(connection: Connection) { 8 | super(connection, CharacterModel); 9 | } 10 | 11 | async findByInternal() { 12 | return this.repository.find({ 13 | where: { 14 | category: "internal" 15 | } 16 | }); 17 | } 18 | 19 | async findByCustom() { 20 | return this.repository.find({ 21 | where: { 22 | category: "custom" 23 | } 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /gui/src/main/domain/dao/systemSettingDao.ts: -------------------------------------------------------------------------------- 1 | import {Connection, Repository} from 'typeorm'; 2 | import {SystemSetting} from "../entitys/systemSetting"; 3 | import {BaseDao} from "../../framework/orm/baseDao"; 4 | 5 | export class SystemSettingDao extends BaseDao { 6 | 7 | constructor(connection: Connection) { 8 | super(connection, SystemSetting); 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /gui/src/main/domain/dto/UploadFileDTO.ts: -------------------------------------------------------------------------------- 1 | export class UploadFileResultDTO { 2 | 3 | isError:boolean; 4 | fileName: string; 5 | filePath: string; 6 | 7 | 8 | constructor(isError: boolean, fileName: string, filePath: string) { 9 | this.isError = isError; 10 | this.fileName = fileName; 11 | this.filePath = filePath; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /gui/src/main/domain/dto/characterChatHistoryDTO.ts: -------------------------------------------------------------------------------- 1 | enum ChatHistorySourceEnum { 2 | Client, 3 | Bilibili, 4 | Douyin 5 | } 6 | 7 | export class CharacterChatHistoryDTO { 8 | id?: number; 9 | character_id?: number; 10 | source?: string; 11 | sender?: string; 12 | question?: string; 13 | answer?: string; 14 | } 15 | 16 | export class FindPageByCharacterIdDTO{ 17 | characterId?: number; 18 | page?: number; 19 | pageSize?: number; 20 | order?: Record; 21 | } 22 | 23 | export class FindScrollReadDataDTO{ 24 | characterId?: number; 25 | pageSize?: number; 26 | } 27 | -------------------------------------------------------------------------------- /gui/src/main/domain/dto/llmConnectDTO.ts: -------------------------------------------------------------------------------- 1 | export class LLMConnectListDTO { 2 | id?: string; 3 | name?: string; 4 | 5 | constructor(id: string, name: string) { 6 | this.id = id; 7 | this.name = name; 8 | } 9 | } 10 | 11 | export class LLMConnectDTO { 12 | id: string; 13 | name: string; 14 | model_name: string; 15 | llm_type: string; 16 | max_token: number; 17 | temperature: number; 18 | top_p: number; 19 | expand: any 20 | 21 | constructor(id: string, name: string, model_name: string, llm_type: string, max_token: number, temperature: number, top_p: number, expand: any) { 22 | this.id = id; 23 | this.name = name; 24 | this.model_name = model_name; 25 | this.llm_type = llm_type; 26 | this.max_token = max_token; 27 | this.temperature = temperature; 28 | this.top_p = top_p; 29 | this.expand = expand; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /gui/src/main/domain/dto/systemSettingDTO.ts: -------------------------------------------------------------------------------- 1 | import { SystemSetting } from '../entitys/systemSetting'; 2 | 3 | class SystemSettingDTO { 4 | id?: number; 5 | language?: string; 6 | home_setting?: any; 7 | llm_setting?: any; 8 | live_setting?: any; 9 | 10 | static toDTO(systemSetting: SystemSetting) { 11 | const systemSettingDTO = new SystemSettingDTO(); 12 | systemSettingDTO.id = systemSetting.id; 13 | systemSettingDTO.language = systemSetting.language; 14 | // systemSettingDTO.home_setting = { 15 | // character_id: systemSetting.home_setting['character_id'] 16 | // }; 17 | systemSettingDTO.home_setting = systemSetting.home_setting; 18 | systemSettingDTO.llm_setting = systemSetting.llm_setting; 19 | systemSettingDTO.live_setting = systemSetting.live_setting; 20 | return systemSettingDTO; 21 | } 22 | } 23 | 24 | class HomeSettingDTO { 25 | 26 | character_id: string; 27 | background_src: string; 28 | live_client: string; 29 | constructor(character_id: string, background_src: string, live_client: string) { 30 | this.character_id = character_id; 31 | this.background_src = background_src; 32 | this.live_client = live_client; 33 | } 34 | } 35 | 36 | class LLMSettingDTO { 37 | 38 | llm_type: string; 39 | enabled: boolean; 40 | extended_attributes: any; 41 | 42 | constructor(llm_type: string, enabled: boolean, extended_attributes: any) { 43 | this.llm_type = llm_type; 44 | this.enabled = enabled; 45 | this.extended_attributes = extended_attributes; 46 | } 47 | } 48 | 49 | class LiveSettingDTO { 50 | live_type: string; 51 | enabled: boolean; 52 | extended_attributes: any; 53 | 54 | constructor(live_type: string, enabled: boolean, extended_attributes: any) { 55 | this.live_type = live_type; 56 | this.enabled = enabled; 57 | this.extended_attributes = extended_attributes; 58 | } 59 | } 60 | 61 | 62 | export { LLMSettingDTO, SystemSettingDTO, LiveSettingDTO, HomeSettingDTO }; 63 | -------------------------------------------------------------------------------- /gui/src/main/domain/dto/voiceDTO.ts: -------------------------------------------------------------------------------- 1 | export class VoiceSynthesisDTO { 2 | 3 | voiceType: string; 4 | voiceId: string; 5 | text: string; 6 | config: Map; 7 | 8 | 9 | constructor(voiceType: string, voiceId: string, text: string, config: Map) { 10 | this.voiceType = voiceType; 11 | this.voiceId = voiceId; 12 | this.text = text; 13 | this.config = config; 14 | } 15 | } 16 | 17 | export class VoiceOptionDTO { 18 | 19 | id: string; 20 | name: string; 21 | 22 | constructor(id: string, name: string) { 23 | this.id = id; 24 | this.name = name; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /gui/src/main/domain/entitys/character.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata'; // 最顶部添加这行 2 | import {Entity, Column} from 'typeorm'; 3 | import {BaseEntity} from "../../framework/orm/baseEntity"; 4 | 5 | @Entity({ 6 | name: "character" 7 | }) 8 | export class Character extends BaseEntity { 9 | 10 | @Column({ 11 | type: 'varchar', 12 | length: 50, 13 | nullable: false, 14 | comment: '角色名称' 15 | }) 16 | name: string | undefined; 17 | 18 | @Column({ 19 | type: 'varchar', 20 | length: 100, 21 | nullable: true, 22 | comment: '角色描述' 23 | }) 24 | describe: string | undefined; 25 | 26 | @Column({ 27 | type: 'varchar', 28 | length: 225, 29 | nullable: true, 30 | comment: '角色头像' 31 | }) 32 | avatar: string | undefined; 33 | 34 | @Column({ 35 | type: 'json', 36 | nullable: false, 37 | comment: '基础配置', 38 | default: () => "'{}'" 39 | }) 40 | basic_setting: any; 41 | 42 | @Column({ 43 | type: 'json', 44 | nullable: false, 45 | comment: '角色设定', 46 | default: () => "'{}'" 47 | }) 48 | definition_setting: any; 49 | 50 | @Column({ 51 | type: 'json', 52 | nullable: false, 53 | comment: '语音设定', 54 | default: () => "'{}'" 55 | }) 56 | voice_setting: any; 57 | 58 | @Column({ 59 | type: 'json', 60 | nullable: false, 61 | comment: '直播配置', 62 | default: () => "'{}'" 63 | }) 64 | live_setting: any; 65 | 66 | } 67 | -------------------------------------------------------------------------------- /gui/src/main/domain/entitys/characterChatHistory.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata'; // 最顶部添加这行 2 | import {Entity, PrimaryGeneratedColumn, Column} from 'typeorm'; 3 | import {BaseEntity} from "../../framework/orm/baseEntity"; 4 | 5 | @Entity({ 6 | name: "character_chat_history" 7 | }) 8 | export class CharacterChatHistory extends BaseEntity { 9 | 10 | @Column({ 11 | type: 'int', 12 | nullable: true, 13 | comment: '角色ID' 14 | }) 15 | character_id: number; 16 | 17 | @Column({ 18 | type: 'varchar', 19 | length: 10, 20 | nullable: true, 21 | comment: '来源:client、b站、抖音' 22 | }) 23 | source: string; 24 | 25 | @Column({ 26 | type: 'varchar', 27 | length: 50, 28 | nullable: true, 29 | comment: '发送者' 30 | }) 31 | sender: string; 32 | 33 | @Column({ 34 | type: 'varchar', 35 | length: 225, 36 | nullable: true, 37 | comment: '问题' 38 | }) 39 | question: string; 40 | 41 | @Column({ 42 | type: 'varchar', 43 | length: 225, 44 | nullable: true, 45 | comment: '答案' 46 | }) 47 | answer: string; 48 | 49 | } 50 | -------------------------------------------------------------------------------- /gui/src/main/domain/entitys/characterDefinition.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata'; // 最顶部添加这行 2 | import {Entity, Column} from 'typeorm'; 3 | import {BaseEntity} from "../../framework/orm/baseEntity"; 4 | 5 | @Entity({ 6 | name: "character_definition" 7 | }) 8 | export class CharacterDefinition extends BaseEntity { 9 | 10 | @Column({ 11 | type: 'varchar', 12 | length: 50, 13 | nullable: true, 14 | comment: '角色名称' 15 | }) 16 | name: string; 17 | 18 | @Column({ 19 | type: 'varchar', 20 | nullable: true, 21 | comment: '角色基本信息定义' 22 | }) 23 | persona: string; 24 | 25 | @Column({ 26 | type: 'text', 27 | nullable: true, 28 | comment: '角色的性格简短描述' 29 | }) 30 | personality: string; 31 | 32 | @Column({ 33 | type: 'text', 34 | nullable: false, 35 | comment: '角色的对话的情况和背景', 36 | default: '' 37 | }) 38 | scenario: string; 39 | 40 | @Column({ 41 | type: 'text', 42 | nullable: false, 43 | comment: '对话范例', 44 | default: '' 45 | }) 46 | examples_of_dialogue: string; 47 | 48 | } 49 | -------------------------------------------------------------------------------- /gui/src/main/domain/entitys/characterModel.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata'; // 最顶部添加这行 2 | import { Entity, Column } from 'typeorm'; 3 | import { BaseEntity } from '../../framework/orm/baseEntity'; 4 | 5 | @Entity({ 6 | name: 'character_model' 7 | }) 8 | export class CharacterModel extends BaseEntity { 9 | 10 | 11 | @Column({ 12 | type: 'varchar', 13 | length: 50, 14 | nullable: true, 15 | comment: '模型名称' 16 | }) 17 | name: string; 18 | 19 | @Column({ 20 | type: 'varchar', 21 | length: 10, 22 | nullable: true, 23 | comment: '模型类型:internal、custom' 24 | }) 25 | category: string; 26 | 27 | @Column({ 28 | type: 'varchar', 29 | length: 10, 30 | nullable: true, 31 | comment: '模型类型:live2d、vrm' 32 | }) 33 | type: string; 34 | 35 | @Column({ 36 | type: 'varchar', 37 | length: 225, 38 | nullable: true, 39 | comment: '头像路径' 40 | }) 41 | icon_path: string; 42 | 43 | @Column({ 44 | type: 'varchar', 45 | length: 225, 46 | nullable: true, 47 | comment: '模型文件路径' 48 | }) 49 | model_path: string; 50 | 51 | @Column({ 52 | type: 'json', 53 | nullable: true, 54 | comment: '闲置动画' 55 | }) 56 | idles: any; 57 | 58 | @Column({ 59 | type: 'varchar', 60 | nullable: true, 61 | comment: '嘴唇同步参数' 62 | }) 63 | lip_sync_id: string; 64 | 65 | @Column({ 66 | type: 'json', 67 | nullable: true, 68 | comment: '表情映射' 69 | }) 70 | expression_mapping: any; 71 | 72 | @Column({ 73 | type: 'json', 74 | nullable: true, 75 | comment: '动作映射' 76 | }) 77 | action_mapping: any; 78 | 79 | } 80 | -------------------------------------------------------------------------------- /gui/src/main/domain/entitys/systemSetting.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata'; // 最顶部添加这行 2 | import {Entity, Column} from 'typeorm'; 3 | import {BaseEntity} from "../../framework/orm/baseEntity"; 4 | 5 | @Entity({ 6 | name: "system_setting" 7 | }) 8 | export class SystemSetting extends BaseEntity { 9 | 10 | @Column({ 11 | type: 'varchar', 12 | length: 10, 13 | nullable: true, 14 | default: "zh_Hans", 15 | comment: '语言:en、zh_Hans' 16 | }) 17 | language: string; 18 | 19 | @Column({ 20 | type: 'json', 21 | nullable: false, 22 | comment: 'home系统配置', 23 | default: () => "'{}'" 24 | }) 25 | home_setting: any; 26 | 27 | @Column({ 28 | type: 'json', 29 | nullable: false, 30 | comment: '语言模型配置', 31 | default: () => "'{}'" 32 | }) 33 | llm_setting: any; 34 | 35 | @Column({ 36 | type: 'json', 37 | nullable: false, 38 | comment: '直播配置', 39 | default: () => "'{}'" 40 | }) 41 | live_setting: any; 42 | 43 | } 44 | -------------------------------------------------------------------------------- /gui/src/main/domain/service/characterChatHistoryService.ts: -------------------------------------------------------------------------------- 1 | import {injector} from "../../framework/dependencyInjector"; 2 | import {CharacterChatHistoryDao} from "../dao/characterChatHistoryDao"; 3 | import {CharacterChatHistory} from "../entitys/characterChatHistory"; 4 | import {CharacterChatHistoryDTO, FindPageByCharacterIdDTO, FindScrollReadDataDTO} from "../dto/characterChatHistoryDTO"; 5 | 6 | export class CharacterChatHistoryService { 7 | private characterChatHistoryDao: CharacterChatHistoryDao; 8 | 9 | constructor() { 10 | this.characterChatHistoryDao = injector.resolve(CharacterChatHistoryDao); 11 | } 12 | 13 | async create(characterChatHistoryDTO: CharacterChatHistoryDTO): Promise { 14 | const newCharacter = await this.characterChatHistoryDao.create(characterChatHistoryDTO); 15 | return newCharacter; 16 | } 17 | 18 | async update(characterChatHistoryDTO: CharacterChatHistoryDTO): Promise { 19 | await this.characterChatHistoryDao.update(characterChatHistoryDTO.id, characterChatHistoryDTO); 20 | } 21 | 22 | async findAll(): Promise { 23 | return await this.characterChatHistoryDao.findAll(); 24 | } 25 | 26 | async findById(id: number): Promise { 27 | return await this.characterChatHistoryDao.findById(id); 28 | } 29 | 30 | async delete(id: number): Promise { 31 | await this.characterChatHistoryDao.delete(id); 32 | } 33 | 34 | async findByCharacterId(findPageByCharacterIdDTO: FindPageByCharacterIdDTO): Promise { 35 | return await this.characterChatHistoryDao.findByCharacterId(findPageByCharacterIdDTO.characterId, findPageByCharacterIdDTO.page, findPageByCharacterIdDTO.pageSize, findPageByCharacterIdDTO.order); 36 | } 37 | 38 | async findCountByCharacterId(characterId?: number): Promise { 39 | return await this.characterChatHistoryDao.findCountByCharacterId(characterId); 40 | } 41 | 42 | async scrollReadData(findScrollReadDataDTO: FindScrollReadDataDTO): Promise { 43 | return await this.characterChatHistoryDao.scrollReadData(findScrollReadDataDTO.characterId, findScrollReadDataDTO.pageSize); 44 | } 45 | 46 | } 47 | 48 | -------------------------------------------------------------------------------- /gui/src/main/domain/service/characterModelService.ts: -------------------------------------------------------------------------------- 1 | import { injector } from '../../framework/dependencyInjector'; 2 | import { CharacterModelDao } from '../dao/characterModelDao'; 3 | import { CharacterModelDTO } from '../dto/characterModelDTO'; 4 | import { AutoMapper } from '../../util'; 5 | 6 | export class CharacterModelService { 7 | private characterModelDao: CharacterModelDao; 8 | 9 | constructor() { 10 | this.characterModelDao = injector.resolve(CharacterModelDao); 11 | } 12 | 13 | async create(characterModelDTO: CharacterModelDTO): Promise { 14 | const newCharacter = await this.characterModelDao.create(characterModelDTO); 15 | return AutoMapper.map(newCharacter, CharacterModelDTO); 16 | } 17 | 18 | async update(characterModelDTO: CharacterModelDTO): Promise { 19 | await this.characterModelDao.update(characterModelDTO.id, characterModelDTO); 20 | // 这里不需要转换,因为update可能不返回实体 21 | } 22 | 23 | async findAll(): Promise { 24 | const characters = await this.characterModelDao.findAll(); 25 | return AutoMapper.mapArray(characters, CharacterModelDTO); 26 | } 27 | 28 | async findByInternal(): Promise { 29 | const characters = await this.characterModelDao.findByInternal(); 30 | return AutoMapper.mapArray(characters, CharacterModelDTO); 31 | } 32 | 33 | async findByCustom(): Promise { 34 | const characters = await this.characterModelDao.findByCustom(); 35 | return AutoMapper.mapArray(characters, CharacterModelDTO); 36 | } 37 | 38 | async findById(id: number): Promise { 39 | const character = await this.characterModelDao.findById(id); 40 | if (character) { 41 | return AutoMapper.map(character, CharacterModelDTO); 42 | } 43 | return null; 44 | } 45 | 46 | async delete(id: number): Promise { 47 | await this.characterModelDao.delete(id); 48 | // 删除操作不需要转换DTO 49 | } 50 | } 51 | 52 | -------------------------------------------------------------------------------- /gui/src/main/domain/service/characterService.ts: -------------------------------------------------------------------------------- 1 | import { injector } from '../../framework/dependencyInjector'; 2 | import { CharacterDao } from '../dao/characterDao'; 3 | import { CharacterDTO, CharacterFormDTO } from '../dto/characterDTO'; 4 | import { Character } from '../entitys/character'; 5 | import { CharacterModelService } from './characterModelService'; 6 | import { CharacterModelDTO } from '../dto/characterModelDTO'; 7 | 8 | export class CharacterService { 9 | private characterDao: CharacterDao; 10 | private characterModelService: CharacterModelService; 11 | 12 | constructor() { 13 | this.characterDao = injector.resolve(CharacterDao); 14 | this.characterModelService = new CharacterModelService(); 15 | } 16 | 17 | async create(characterData: CharacterDTO): Promise { 18 | const newCharacter = await this.characterDao.create(characterData); 19 | return newCharacter; 20 | } 21 | 22 | async update(characterData: CharacterDTO): Promise { 23 | await this.characterDao.update(characterData.id, characterData); 24 | } 25 | 26 | async findAll(): Promise { 27 | const characters = await this.characterDao.findAll(); 28 | for (let character of characters) { 29 | const character_model_id = character.basic_setting.character_model_id; 30 | const characterModelDTO = await this.characterModelService.findById(character_model_id); 31 | if (characterModelDTO) { 32 | character.avatar = characterModelDTO.icon_path; 33 | } 34 | } 35 | return characters; 36 | } 37 | 38 | async findById(id: number): Promise { 39 | return await this.characterDao.findById(id); 40 | } 41 | 42 | // 删除角色 43 | async delete(id: number): Promise { 44 | await this.characterDao.delete(id); 45 | } 46 | } 47 | 48 | -------------------------------------------------------------------------------- /gui/src/main/domain/service/liveClientMangeService.ts: -------------------------------------------------------------------------------- 1 | import { LiveSettingDTO } from '../dto/systemSettingDTO'; 2 | import { liveRoomManage, LiveRoomManage } from '../../apps/environment/liveRoomEnvironment'; 3 | 4 | export class LiveRoomManageService { 5 | 6 | private liveRoomManage: LiveRoomManage; 7 | 8 | constructor() { 9 | this.liveRoomManage = liveRoomManage; 10 | } 11 | 12 | async start(liveSettingDTO: LiveSettingDTO): Promise { 13 | await this.liveRoomManage.start(liveSettingDTO); 14 | } 15 | 16 | async shutdown(): Promise { 17 | await this.liveRoomManage.shutdown(); 18 | } 19 | 20 | async check_connect(liveSettingDTO: LiveSettingDTO) { 21 | return this.liveRoomManage.check_connect(liveSettingDTO); 22 | } 23 | 24 | async restart(): Promise { 25 | this.liveRoomManage.restart(); 26 | } 27 | } 28 | 29 | -------------------------------------------------------------------------------- /gui/src/main/domain/service/systemSettingService.ts: -------------------------------------------------------------------------------- 1 | import {injector} from "../../framework/dependencyInjector"; 2 | import {SystemSettingDao} from "../dao/systemSettingDao"; 3 | import {SystemSettingDTO} from "../dto/systemSettingDTO"; 4 | import {SystemSetting} from "../entitys/systemSetting"; 5 | 6 | export class SystemSettingService { 7 | private systemSettingDao: SystemSettingDao; 8 | 9 | constructor() { 10 | this.systemSettingDao = injector.resolve(SystemSettingDao); 11 | } 12 | 13 | async create(systemSettingDTO: SystemSettingDTO): Promise { 14 | return await this.systemSettingDao.create(systemSettingDTO); 15 | } 16 | 17 | async findAll(): Promise { 18 | return await this.systemSettingDao.findAll(); 19 | } 20 | 21 | async findById(id: number): Promise { 22 | return await this.systemSettingDao.findById(id); 23 | } 24 | 25 | async update(systemSettingDTO: SystemSettingDTO): Promise { 26 | await this.systemSettingDao.update(systemSettingDTO.id, systemSettingDTO); 27 | } 28 | 29 | async delete(id: number): Promise { 30 | await this.systemSettingDao.delete(id); 31 | } 32 | } 33 | 34 | -------------------------------------------------------------------------------- /gui/src/main/domain/service/visionService.ts: -------------------------------------------------------------------------------- 1 | import ocrStorage from "../../framework/vision/OCRTextStorage"; 2 | 3 | export class VisionService { 4 | push(context: string): void { 5 | ocrStorage.push(context); 6 | } 7 | 8 | getLast(): string | undefined { 9 | const last = ocrStorage.getLatest(); 10 | if (last) { 11 | return last.context; 12 | } else { 13 | return undefined; 14 | } 15 | } 16 | 17 | getNewest(number: number): string[] { 18 | const newest = ocrStorage.getNewest(number); 19 | if (newest) { 20 | return newest.map((item: any) => item.context); 21 | } else { 22 | return []; 23 | } 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /gui/src/main/framework/bridge/PythonBridge.ts: -------------------------------------------------------------------------------- 1 | import { spawn, ChildProcessWithoutNullStreams } from 'child_process'; 2 | 3 | type MessageCallback = (message: any) => void; 4 | 5 | class PythonBridge { 6 | private pythonProcess: ChildProcessWithoutNullStreams; 7 | private onMessageCallback?: MessageCallback; 8 | 9 | constructor(pythonScriptPath: string) { 10 | this.pythonProcess = spawn('python', [pythonScriptPath]); 11 | this.pythonProcess.stdout.setEncoding('utf8'); 12 | 13 | this.pythonProcess.stdout.on('data', (data: string) => { 14 | try { 15 | const message = JSON.parse(data); 16 | if (this.onMessageCallback) { 17 | this.onMessageCallback(message); 18 | } 19 | } catch (error) { 20 | console.error('Error parsing JSON from python script:', error); 21 | } 22 | }); 23 | 24 | this.pythonProcess.stderr.on('data', (data: string) => { 25 | console.error(`Python stderr: ${data}`); 26 | }); 27 | } 28 | 29 | send(message: object): void { 30 | const messageString = JSON.stringify(message); 31 | this.pythonProcess.stdin.write(messageString + '\n'); 32 | } 33 | 34 | onMessage(callback: MessageCallback): void { 35 | this.onMessageCallback = callback; 36 | } 37 | 38 | close(): void { 39 | this.pythonProcess.kill(); 40 | } 41 | } 42 | 43 | export default PythonBridge; 44 | -------------------------------------------------------------------------------- /gui/src/main/framework/dependencyInjector.ts: -------------------------------------------------------------------------------- 1 | // 提供了一个用于依赖注入的简单容器 2 | type Constructor = { new (...args: any[]): T }; 3 | type DependencyContainer = Map, any>; 4 | 5 | class DependencyInjector { 6 | private dependencies: DependencyContainer = new Map(); 7 | 8 | public register(constructor: Constructor, implementation: T): void { 9 | this.dependencies.set(constructor, implementation); 10 | } 11 | 12 | public resolve(constructor: Constructor): T { 13 | const dependency = this.dependencies.get(constructor); 14 | if (!dependency) { 15 | throw new Error(`Dependency not found: ${constructor.name}`); 16 | } 17 | return dependency; 18 | } 19 | } 20 | 21 | export const injector = new DependencyInjector(); 22 | -------------------------------------------------------------------------------- /gui/src/main/framework/ipcHandlers.ts: -------------------------------------------------------------------------------- 1 | // ipcHandlers.ts: 主进程的IPC事件处理程序,负责根据请求调用特定实体的数据库操作 2 | import {ipcMain} from 'electron'; 3 | import {getDefaultEntityOperations} from "../route/route"; 4 | 5 | export class IpcHandlers { 6 | constructor() { 7 | // 注册'handleDatabaseEvents'作为处理数据库操作请求的回调函数 8 | ipcMain.handle('database', this.handleDatabaseEvents); 9 | } 10 | 11 | async handleDatabaseEvents(event, {entityName, type, payload}) { 12 | 13 | // 获取适合给定实体的操作类(如果没有特定的类,将返回基本实现) 14 | const operationsClass = getDefaultEntityOperations(entityName); 15 | const operations = new operationsClass(); 16 | 17 | // 检查实体是否具有所请求的操作,如果有,则调用它 18 | if (operations[type]) { 19 | return operations[type](payload); 20 | } else { 21 | // 如果没有这样的操作,则抛出错误 22 | return Promise.reject(`Unsupported operation type: ${type} for entity: ${entityName}`); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /gui/src/main/framework/live/base.ts: -------------------------------------------------------------------------------- 1 | enum LiveEventTypeEnum { 2 | DANMAKU = 'danmaku', 3 | FOLLOW_LIVE_ROOM = 'follow_live_room', 4 | GIFT = 'gift', 5 | INTERACT_WORD = 'interact_word', 6 | LIKE_CLICK = 'like_click', 7 | } 8 | 9 | enum LiveEventPriorityEnum { 10 | 11 | // 普通用户弹幕 12 | DEFAULT_DANMAKU = 3, 13 | 14 | // 关注直播间 15 | FOLLOW_LIVE_ROOM = 1, 16 | 17 | // 送礼 18 | GIFT = 1, 19 | 20 | // 关注直播间 21 | INTERACT_WORD = 2, 22 | 23 | // 点赞 24 | LIKE_CLICK = 4, 25 | } 26 | 27 | interface LiveEvent { 28 | 29 | /** 30 | * 用户ID 31 | */ 32 | user_id: string; 33 | 34 | /** 35 | * 用户名称 36 | */ 37 | user_name: string; 38 | 39 | /** 40 | * 事件类型 41 | */ 42 | type: LiveEventTypeEnum; 43 | 44 | /** 45 | * 优先级 46 | */ 47 | priority: LiveEventPriorityEnum; 48 | 49 | /** 50 | * 内容 51 | */ 52 | content?: string; 53 | 54 | /** 55 | * 额外属性 56 | */ 57 | attribute?: any; 58 | 59 | } 60 | 61 | abstract class BaseLiveClient { 62 | 63 | /** 64 | * 测试连通性 65 | */ 66 | abstract check_connect(): Promise ; 67 | 68 | /** 69 | * 开启监听 70 | */ 71 | abstract start(): Promise; 72 | 73 | /** 74 | * 关闭监听 75 | */ 76 | abstract shutdown(): void; 77 | 78 | /** 79 | * 重启监听 80 | */ 81 | abstract restart(): void; 82 | 83 | } 84 | 85 | abstract class BaseLiveListener { 86 | 87 | /** 88 | * 收到弹幕 89 | */ 90 | abstract on_danmaku(liveEvent: LiveEvent): void; 91 | 92 | /** 93 | * 收到礼物 94 | */ 95 | abstract on_gift(liveEvent: LiveEvent): void; 96 | 97 | /** 98 | * 进入直播间 99 | */ 100 | abstract on_interact_word(liveEvent: LiveEvent): void; 101 | 102 | /** 103 | * 关注直播间 104 | */ 105 | abstract on_follow_live_room(liveEvent: LiveEvent): void; 106 | 107 | /** 108 | * 点赞 109 | */ 110 | abstract on_like_click(liveEvent: LiveEvent): void; 111 | 112 | } 113 | 114 | export { BaseLiveClient, BaseLiveListener, LiveEvent, LiveEventTypeEnum ,LiveEventPriorityEnum}; 115 | -------------------------------------------------------------------------------- /gui/src/main/framework/orm/baseDao.ts: -------------------------------------------------------------------------------- 1 | import { Repository, Connection, FindManyOptions, DeepPartial } from 'typeorm'; 2 | 3 | export class BaseDao { 4 | protected repository: Repository; 5 | 6 | constructor(connection: Connection, entity: new () => T) { 7 | this.repository = connection.getRepository(entity); 8 | } 9 | 10 | // 创建实体 11 | async create(entityData: DeepPartial): Promise { 12 | const entity = this.repository.create(entityData); 13 | return this.repository.save(entity); 14 | } 15 | 16 | // 获取所有实体 17 | async findAll(options?: FindManyOptions): Promise { 18 | return this.repository.find(options); 19 | } 20 | 21 | // 根据ID获取实体 22 | async findById(id: number): Promise { 23 | return this.repository.findOne({ 24 | where: { 25 | id: id 26 | } 27 | }); 28 | } 29 | 30 | // 更新实体信息 31 | async update(id: number, entityData: DeepPartial): Promise { 32 | await this.repository.update(id, entityData); 33 | } 34 | 35 | // 删除实体 36 | async delete(id: number): Promise { 37 | await this.repository.delete(id); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /gui/src/main/framework/orm/baseEntity.ts: -------------------------------------------------------------------------------- 1 | import { 2 | PrimaryGeneratedColumn, 3 | CreateDateColumn, 4 | UpdateDateColumn, 5 | BeforeInsert, 6 | BeforeUpdate 7 | } from 'typeorm'; 8 | 9 | export abstract class BaseEntity { 10 | @PrimaryGeneratedColumn() 11 | id: number; 12 | 13 | @CreateDateColumn({type: 'datetime', nullable: false}) 14 | createTime: Date; 15 | 16 | @UpdateDateColumn({type: 'datetime', nullable: false}) 17 | updateTime: Date; 18 | 19 | @BeforeInsert() 20 | setCreateTime(): void { 21 | this.createTime = new Date(); 22 | } 23 | 24 | @BeforeUpdate() 25 | setUpdateTime(): void { 26 | this.updateTime = new Date(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /gui/src/main/framework/queue/queue.ts: -------------------------------------------------------------------------------- 1 | // 优先级队列 2 | class PriorityQueue { 3 | private queue: { priority: number; data: T }[] = []; 4 | 5 | enqueue(data: T, priority: number) { 6 | const item = { priority, data }; 7 | const index = this.queue.findIndex(i => i.priority > priority); 8 | if (index === -1) { 9 | this.queue.push(item); 10 | } else { 11 | this.queue.splice(index, 0, item); 12 | } 13 | } 14 | 15 | dequeue() { 16 | return this.queue.shift()?.data; 17 | } 18 | 19 | isEmpty() { 20 | return this.queue.length === 0; 21 | } 22 | } 23 | 24 | // 消费者 25 | type Consumer = (data: T) => void; 26 | 27 | // 事件发射器 28 | class EventEmitter { 29 | private consumers: Consumer[] = []; 30 | 31 | subscribe(consumer: Consumer) { 32 | this.consumers.push(consumer); 33 | } 34 | 35 | unsubscribe(consumer: Consumer) { 36 | this.consumers = this.consumers.filter(c => c !== consumer); 37 | } 38 | 39 | emit(data: T) { 40 | this.consumers.forEach(c => c(data)); 41 | } 42 | } 43 | 44 | // 优先级队列事件发射器 45 | class PriorityQueueEmitter { 46 | private queue = new PriorityQueue(); 47 | emitter = new EventEmitter(); 48 | 49 | enqueue(data: T, priority: number) { 50 | this.queue.enqueue(data, priority); 51 | this.emitter.emit(data); 52 | } 53 | 54 | subscribe(consumer: Consumer) { 55 | this.emitter.subscribe(consumer); 56 | } 57 | 58 | unsubscribe(consumer: Consumer) { 59 | this.emitter.unsubscribe(consumer); 60 | } 61 | } 62 | 63 | export { PriorityQueue, PriorityQueueEmitter }; 64 | -------------------------------------------------------------------------------- /gui/src/main/framework/vision/OCRTextStorage.ts: -------------------------------------------------------------------------------- 1 | interface OCRText { 2 | context: string; 3 | createTime: Date; 4 | } 5 | 6 | class OCRTextStorage { 7 | private texts: OCRText[]; 8 | private maxStorage: number; 9 | 10 | constructor(maxStorage: number = 10) { 11 | this.texts = []; 12 | this.maxStorage = maxStorage; // 设置最大存储条数 13 | } 14 | 15 | // 上传文本 16 | push(context: string): void { 17 | const newText = {context, createTime: new Date()}; 18 | 19 | // 如果当前文本数量已达到最大限制,移除最旧的文本 20 | if (this.texts.length >= this.maxStorage) { 21 | this.texts.shift(); // 移除最旧的文本 22 | } 23 | 24 | this.texts.push(newText); // 添加新文本 25 | } 26 | 27 | // 获取最新的OCR文本 28 | getLatest(): OCRText | null { 29 | if (this.texts.length === 0) { 30 | return null; // 如果没有文本,返回 null 31 | } 32 | return this.texts[this.texts.length - 1]; // 返回最新的文本 33 | } 34 | 35 | getNewest(n: number): OCRText[] { 36 | if (n <= 0) { 37 | return []; // 如果x小于等于0,返回空数组 38 | } 39 | return this.texts.slice(-n); // 返回最新的x条文本 40 | } 41 | } 42 | 43 | // 示例用法 44 | const ocrStorage = new OCRTextStorage(); 45 | export default ocrStorage; 46 | -------------------------------------------------------------------------------- /gui/src/main/preload.ts: -------------------------------------------------------------------------------- 1 | import { app, contextBridge, ipcRenderer, IpcRendererEvent } from 'electron'; 2 | 3 | export type Channels = 'ipc-example'; 4 | 5 | contextBridge.exposeInMainWorld('electron', { 6 | ipcRenderer: { 7 | sendMessage(channel: Channels, args: unknown[]) { 8 | ipcRenderer.send(channel, args); 9 | }, 10 | on(channel: Channels, func: (...args: unknown[]) => void) { 11 | const subscription = (_event: IpcRendererEvent, ...args: unknown[]) => 12 | func(...args); 13 | ipcRenderer.on(channel, subscription); 14 | 15 | return () => ipcRenderer.removeListener(channel, subscription); 16 | }, 17 | once(channel: Channels, func: (...args: unknown[]) => void) { 18 | ipcRenderer.once(channel, (_event, ...args) => func(...args)); 19 | }, 20 | invoke: (entityName: string, type: string, payload) => { 21 | return ipcRenderer.invoke('database', { entityName, type, payload }); 22 | }, 23 | 24 | invokeOpenNewWindow: (...args: unknown[]) => ipcRenderer.invoke('open-new-window', ...args), 25 | 26 | removeListener: (channel: Channels, func: (...args: any[]) => void) => ipcRenderer.removeListener(channel, func), 27 | 28 | removeAllListeners: (channel: Channels) => ipcRenderer.removeAllListeners(channel) 29 | } 30 | }); 31 | 32 | contextBridge.exposeInMainWorld('electronAPI', { 33 | getBasePath: () => ipcRenderer.invoke('getBasePath'), 34 | getVoskModelPath: (voskModelPath: String) => ipcRenderer.invoke('getBaseVoskModelPath', voskModelPath) 35 | }); 36 | 37 | interface Message { 38 | type: string; 39 | data: any; 40 | } 41 | 42 | contextBridge.exposeInMainWorld('messageApi', { 43 | ipcRenderer: { 44 | sendMessageToChildWindows: (context: Message) => ipcRenderer.send('notify-children', context), 45 | sendToMain: (channel: string, data: any) => ipcRenderer.send(channel, data), 46 | receiveFromMain: (channel: string, func: (...args: any[]) => void) => { 47 | ipcRenderer.on(channel, (_, ...args) => func(...args)); 48 | } 49 | } 50 | }); 51 | -------------------------------------------------------------------------------- /gui/src/main/route/route.ts: -------------------------------------------------------------------------------- 1 | // entityMap.ts: 映射实体名称到其数据库操作类 2 | // ...导入其他实体操作类 3 | 4 | import {CharacterService} from "../domain/service/characterService"; 5 | import {VoiceService} from "../domain/service/voiceService"; 6 | import {SystemSettingService} from "../domain/service/systemSettingService"; 7 | import {CharacterChatHistoryService} from "../domain/service/characterChatHistoryService"; 8 | import {FileService} from "../domain/service/fileService"; 9 | import {CharacterModelService} from "../domain/service/characterModelService"; 10 | import {LiveRoomManageService} from "../domain/service/liveClientMangeService"; 11 | import {VisionService} from "../domain/service/visionService"; 12 | 13 | export const EntityMap = { 14 | CharacterService: CharacterService, 15 | VoiceService: VoiceService, 16 | SystemSettingService: SystemSettingService, 17 | CharacterChatHistoryService: CharacterChatHistoryService, 18 | FileService: FileService, 19 | CharacterModelService: CharacterModelService, 20 | LiveRoomManageService: LiveRoomManageService, 21 | VisionService: VisionService, 22 | }; 23 | 24 | // 如果没有为特定实体定义操作类,则会返回基本实现。 25 | export function getDefaultEntityOperations(entityName: string) { 26 | return EntityMap[entityName]; 27 | } 28 | -------------------------------------------------------------------------------- /gui/src/main/util.ts: -------------------------------------------------------------------------------- 1 | /* eslint import/prefer-default-export: off, import/no-mutable-exports: off */ 2 | import { URL } from 'url'; 3 | import path from 'path'; 4 | import 'reflect-metadata'; 5 | 6 | 7 | let resolveHtmlPath: (htmlFileName: string) => string; 8 | 9 | if (process.env.NODE_ENV === 'development') { 10 | const port = process.env.PORT || 1212; 11 | resolveHtmlPath = (htmlFileName: string) => { 12 | const url = new URL(`http://localhost:${port}`); 13 | url.pathname = htmlFileName; 14 | return url.href; 15 | }; 16 | } else { 17 | resolveHtmlPath = (htmlFileName: string) => { 18 | return `file://${path.resolve(__dirname, '../renderer/', htmlFileName)}`; 19 | }; 20 | } 21 | 22 | class AutoMapper { 23 | // 泛型方法map,用于将一个对象映射为另一个类型的实例 24 | static map(source: T, targetClass: { new(): V }): V { 25 | const targetInstance = new targetClass(); 26 | Object.keys(targetInstance).forEach(key => { 27 | const metadataValue = Reflect.getMetadata(key, targetInstance); 28 | if (metadataValue) { 29 | // 如果DTO中的字段通过Reflect定义了特殊元数据,可以在此处添加处理逻辑 30 | } 31 | // 使用Object.prototype.hasOwnProperty.call确保适用于所有对象 32 | if (Object.prototype.hasOwnProperty.call(source, key)) { 33 | targetInstance[key] = source[key]; 34 | } 35 | }); 36 | return targetInstance; 37 | } 38 | 39 | static mapArray(sourceArray: T[], targetClass: { new(): V }): V[] { 40 | return sourceArray.map(source => this.map(source, targetClass)); 41 | } 42 | } 43 | 44 | export { resolveHtmlPath, AutoMapper }; 45 | -------------------------------------------------------------------------------- /gui/src/renderer/App.tsx: -------------------------------------------------------------------------------- 1 | import {HashRouter as Router, Routes, Route, Outlet, useLocation} from 'react-router-dom'; 2 | import './App.css'; 3 | import Home from './pages/Home'; 4 | import CharacterSpace from './pages/CharacterSpace'; 5 | import EditCharacterSpace from './pages/EditCharacterSpace'; 6 | import MainMenu from './components/menu/MainMenu'; 7 | import CreativeWorkshop from './pages/CreativeWorkshop'; 8 | import SystemSetting from './pages/SystemSetting'; 9 | import LiveView from './components/livepage/LiveView'; 10 | import {NextUIProvider} from "@nextui-org/system"; 11 | 12 | 13 | const MainApp = () => { 14 | 15 | const location = useLocation(); // 使用 useLocation 钩子获取当前路径 16 | const pathName = location.pathname; 17 | 18 | // 检查当前路径是否为首页,如果是,则不加入额外的类名 19 | const contentClassName = pathName === '/' || pathName === '/home' ? '' : 'mt-16 min-h-[280px]'; 20 | 21 | return ( 22 | 23 |
24 |
25 |
26 | 27 |
28 |
29 |
30 | { 31 | pathName === '/' || pathName === '/home' ? <> :
32 | } 33 | 34 |
35 | 36 | ); 37 | }; 38 | 39 | const LiveApp = () => { 40 | return ( 41 | 42 | ); 43 | }; 44 | 45 | export default function App() { 46 | return ( 47 | 48 | 49 | }> 50 | }/> 51 | }/> 52 | }/> 53 | }/> 54 | }/> 55 | }/> 56 | 57 | }> 58 | }/> 59 | 60 | 61 | 62 | ); 63 | } 64 | -------------------------------------------------------------------------------- /gui/src/renderer/Types.ts: -------------------------------------------------------------------------------- 1 | interface AudioFile { 2 | content: string; 3 | url: string; 4 | action: string; 5 | } 6 | 7 | export { AudioFile }; 8 | -------------------------------------------------------------------------------- /gui/src/renderer/components/character/CharacterSettingCss.tsx: -------------------------------------------------------------------------------- 1 | export const settingContentStyle = { 2 | marginTop: '10px' 3 | } 4 | 5 | export const leftPanelStyle = { 6 | width: "100%", 7 | height: "calc(100vh - 20px)", 8 | overflowY: "auto", 9 | overflowX: "hidden", 10 | paddingBottom: "150px" 11 | }; 12 | 13 | export const rightPanelStyle = { 14 | width: "100%", 15 | height: "90%", 16 | }; 17 | 18 | export const chatTestStyle = { 19 | width: "100%", 20 | height: "10%", 21 | }; 22 | 23 | 24 | -------------------------------------------------------------------------------- /gui/src/renderer/components/character/DeleteCharacter.tsx: -------------------------------------------------------------------------------- 1 | import {DeleteOutlined} from "@ant-design/icons"; 2 | import {Button, Modal} from "antd"; 3 | import {useState} from "react"; 4 | import characterHandle from "../../features/character/characterHandle"; 5 | 6 | interface DeleteCharacterProps { 7 | characterId: number; 8 | characterName: string; 9 | onRefreshCharacter: () => void; 10 | } 11 | 12 | const DeleteCharacter: React.FC = ({characterId, characterName, onRefreshCharacter}) => { 13 | 14 | const [open, setOpen] = useState(false); 15 | const [loading, setLoading] = useState(false); 16 | 17 | const onDelete = () => { 18 | characterHandle.delete(characterId).then(() => { 19 | setLoading(false) 20 | setOpen(false) 21 | onRefreshCharacter() 22 | } 23 | ).catch((error) => { 24 | setOpen(false) 25 | setLoading(false); // 确保即使请求失败也会停止加载状态 26 | }); 27 | } 28 | 29 | const onOpen = () => { 30 | setOpen(true) 31 | } 32 | 33 | const onClose = () => { 34 | setLoading(false) 35 | setOpen(false) 36 | } 37 | 38 | 39 | return <> 40 | 41 | 47 | 是 48 | , 49 | , 52 | ]} 53 | > 54 |

请确定是否要删除 {characterName}

55 |
56 | 57 | } 58 | 59 | export default DeleteCharacter; 60 | -------------------------------------------------------------------------------- /gui/src/renderer/components/common/background/Background.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | interface BackgroundProps { 4 | imageSrc: string; 5 | } 6 | 7 | const Background: React.FC = ({ imageSrc }) => { 8 | // 如果 imageSrc 等于 "greenScreen",则展示一个纯绿色背景的div,否则,展示img标签 9 | const content = (imageSrc === 'greenScreen') ? ( 10 |
19 | ) : ( 20 | 28 | ); 29 | 30 | return ( 31 |
{content}
32 | ); 33 | }; 34 | 35 | export default Background; 36 | -------------------------------------------------------------------------------- /gui/src/renderer/components/common/button/LLMConnectivityCheckButton.tsx: -------------------------------------------------------------------------------- 1 | import {Alert, Button} from "antd"; 2 | import {useEffect, useState} from "react"; 3 | import {LLMClientConfig} from "../../../features/llm/base"; 4 | import llmClient from "../../../features/llm/client"; 5 | 6 | interface LLmClientParamProps { 7 | llmType: string, 8 | llmClientConfigs: LLMClientConfig[] 9 | } 10 | 11 | interface ConnectivityCheckProps { 12 | onClick: () => LLmClientParamProps; 13 | onConnectivityCheckRes: (isError: boolean) => void; 14 | } 15 | 16 | const LLMConnectivityCheckButton: React.FC = 17 | ({ 18 | onClick, 19 | onConnectivityCheckRes 20 | }) => { 21 | const [isError, setIsError] = useState(false); 22 | const [msg, setMsg] = useState(""); 23 | const [logs, setLogs] = useState(""); 24 | const [loading, setLoading] = useState(false); 25 | 26 | const onConnectivityCheck = () => { 27 | setLoading(true); 28 | const llmClientParamProps = onClick() 29 | const llmType = llmClientParamProps.llmType 30 | const llmClientConfigs = llmClientParamProps.llmClientConfigs 31 | llmClient.batchConnectivityCheck(llmType, llmClientConfigs).then(res => { 32 | setIsError(res.isError); 33 | setMsg(res.msg); 34 | setLogs(res.logs); 35 | onConnectivityCheckRes(res.isError); 36 | setLoading(false); 37 | }) 38 | } 39 | 40 | return ( 41 |
42 | 43 | {msg && ( 44 | 50 | )} 51 |
52 | ); 53 | }; 54 | 55 | export default LLMConnectivityCheckButton; 56 | -------------------------------------------------------------------------------- /gui/src/renderer/components/common/button/LiveConnectivityCheckButton.tsx: -------------------------------------------------------------------------------- 1 | import { Alert, Button } from 'antd'; 2 | import { useEffect, useState } from 'react'; 3 | import { LLMClientConfig } from '../../../features/llm/base'; 4 | import llmClient from '../../../features/llm/client'; 5 | import { LiveSettingDTO } from '../../../../main/domain/dto/systemSettingDTO'; 6 | import liveClienHandle from '../../../features/live/LiveClienHandle'; 7 | 8 | interface LiveClientParamProps { 9 | liveType: string, 10 | liveSettingDTO: LiveSettingDTO 11 | } 12 | 13 | interface ConnectivityCheckProps { 14 | onClick: () => LiveClientParamProps; 15 | onConnectivityCheckRes: (isError: boolean) => void; 16 | } 17 | 18 | const LiveConnectivityCheckButton: React.FC = 19 | ({ 20 | onClick, 21 | onConnectivityCheckRes 22 | }) => { 23 | const [isError, setIsError] = useState(false); 24 | const [msg, setMsg] = useState(''); 25 | const [logs, setLogs] = useState(''); 26 | const [loading, setLoading] = useState(false); 27 | 28 | const onConnectivityCheck = () => { 29 | setLoading(true); 30 | const llmClientParamProps = onClick(); 31 | const liveSettingDTO = llmClientParamProps.liveSettingDTO; 32 | liveClienHandle.check_connect(liveSettingDTO).then(isConnect => { 33 | const isError = !isConnect; 34 | setIsError(isError); 35 | setMsg(isError ? '连接失败,请检查cookie是否正确' : '连接成功'); 36 | setLogs(''); 37 | onConnectivityCheckRes(isError); 38 | setLoading(false); 39 | }); 40 | }; 41 | 42 | return ( 43 |
44 | 45 | {msg && ( 46 | 52 | )} 53 |
54 | ); 55 | }; 56 | 57 | export default LiveConnectivityCheckButton; 58 | -------------------------------------------------------------------------------- /gui/src/renderer/components/common/card/CharacterCard.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Card, Flex, Tag } from 'antd'; 3 | import { characterDefaultImageSrc } from '../../../constants/GlobalConstants'; 4 | import {Image} from "@nextui-org/react"; 5 | 6 | 7 | const imgStyle: React.CSSProperties = { 8 | display: 'block', 9 | width: '50%', 10 | height: '50%' 11 | }; 12 | 13 | interface CharacterModelProps { 14 | id: Number; 15 | name: string; 16 | type: string; 17 | icon_path: string; 18 | } 19 | 20 | interface CharacterCardMenuProps { 21 | prefix: string; 22 | characterModelDTO: CharacterModelProps; 23 | onClick: (event) => void; 24 | } 25 | 26 | const CharacterCard: React.FC = ({ prefix, characterModelDTO, onClick }) => { 27 | return ( 28 | 41 | 42 | avatar 47 | 48 |
{characterModelDTO.name}
49 | {characterModelDTO.type} 50 |
51 |
52 |
53 | ); 54 | }; 55 | 56 | export default CharacterCard; 57 | -------------------------------------------------------------------------------- /gui/src/renderer/components/common/collapse/Collapse.tsx: -------------------------------------------------------------------------------- 1 | import { SwitchClickEventHandler } from 'antd/lib/switch'; 2 | import { Space, Switch } from 'antd'; 3 | import ModelIcon from '../icon/ModelIcon'; 4 | import CompanyIcon from '../icon/CompanyIcon'; 5 | import { collapseHeaderExtra, collapseHeaderLabel } from './CollapseCss'; 6 | 7 | export const genExtra = (enabled: boolean, onClick: SwitchClickEventHandler) => ( 8 |
9 | 10 |
11 | ); 12 | 13 | export const genLLMHeard = (name: string, type: string) => { 14 | return ( 15 | 16 | 17 |

{name}

18 |
19 | ); 20 | }; 21 | 22 | export const genCompanyHeard = (name: string, type: string) => { 23 | return ( 24 | 25 | 26 |

{name}

27 |
28 | ); 29 | }; 30 | 31 | 32 | -------------------------------------------------------------------------------- /gui/src/renderer/components/common/collapse/CollapseCss.tsx: -------------------------------------------------------------------------------- 1 | import { css } from '@emotion/css'; 2 | 3 | // 样式:整个 Collapse 组件的容器 4 | export const collapseContainer = css` 5 | .ant-collapse > .ant-collapse-item > .ant-collapse-header { 6 | display: flex; 7 | align-items: center; 8 | justify-content: space-between; 9 | } 10 | 11 | // 调整箭头的位置使其垂直居中 12 | .ant-collapse > .ant-collapse-item > .ant-collapse-header .ant-collapse-arrow { 13 | line-height: inherit; 14 | vertical-align: baseline; 15 | } 16 | 17 | // 使 label 和 extra content 对齐 18 | .ant-collapse .ant-collapse-extra { 19 | display: flex; 20 | align-items: center; 21 | } 22 | `; 23 | 24 | // 样式:Collapse.Panel 的 header 中包含 label 和 extra 的部分 25 | export const collapseHeaderContent = css` 26 | display: flex; 27 | justify-content: space-between; 28 | width: 100%; 29 | align-items: center; 30 | `; 31 | 32 | // 样式:为 label 和 extra 提供的空间,使其在内容之间对齐 33 | export const collapseHeaderLabel = css` 34 | flex: 1; 35 | `; 36 | 37 | export const collapseHeaderExtra = css` 38 | display: flex; 39 | align-items: center; 40 | `; 41 | -------------------------------------------------------------------------------- /gui/src/renderer/components/common/icon/CompanyIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { SiBilibili } from 'react-icons/si'; 3 | import { FaTiktok } from 'react-icons/fa'; 4 | 5 | interface CompanyIconProps { 6 | name: string; 7 | size: number; 8 | } 9 | 10 | const CompanyIcon: React.FC = ( 11 | { 12 | name, 13 | size 14 | }) => { 15 | if (!name) return; 16 | // currently supported models, maybe not in its own provider 17 | if (name.startsWith('blibili')) return ; 18 | if (name.startsWith('douyin') || name.startsWith('tiktok ')) return ; 19 | return <>; 20 | } 21 | ; 22 | 23 | export default CompanyIcon; 24 | -------------------------------------------------------------------------------- /gui/src/renderer/components/common/icon/GalleryIcon.tsx: -------------------------------------------------------------------------------- 1 | export const GalleryIcon = (props) => ( 2 | 22 | ); 23 | -------------------------------------------------------------------------------- /gui/src/renderer/components/common/icon/MusicIcon.tsx: -------------------------------------------------------------------------------- 1 | export const MusicIcon = (props) => ( 2 | 15 | ); 16 | -------------------------------------------------------------------------------- /gui/src/renderer/components/common/icon/VideoIcon.tsx: -------------------------------------------------------------------------------- 1 | export const VideoIcon = (props) => ( 2 | 17 | ); 18 | -------------------------------------------------------------------------------- /gui/src/renderer/components/common/label/FormLabel.tsx: -------------------------------------------------------------------------------- 1 | interface FormLabelProps { 2 | title: string; // 字段的名称 3 | description: string; // 字段的描述 4 | } 5 | 6 | const FormLabel: React.FC = ({title, description}) => { 7 | return ( 8 |
9 |
{title}
10 |
{description}
11 |
12 | ); 13 | }; 14 | 15 | export default FormLabel 16 | -------------------------------------------------------------------------------- /gui/src/renderer/components/common/text/ScrollingTextDisplay.tsx: -------------------------------------------------------------------------------- 1 | // ScrollingTextDisplay.tsx 2 | import React from 'react'; 3 | import {Subject} from 'rxjs'; 4 | import {useScrollingText} from '../../../hooks/UseScrollingText'; 5 | 6 | interface ScrollingTextDisplayProps { 7 | chatStream: React.MutableRefObject>; 8 | } 9 | 10 | const ScrollingTextDisplay: React.FC = ({chatStream}) => { 11 | const displayedText = useScrollingText(chatStream); 12 | // 当 displayedText 有数据时,添加 w-full 类,否则为空字符串 13 | const widthClassName = displayedText ? 'w-full' : ''; 14 | return ( 15 |
16 | {displayedText ? ( 17 |
19 | {displayedText} 20 |
21 | ) : <>} 22 |
23 | ); 24 | }; 25 | 26 | export default ScrollingTextDisplay; 27 | -------------------------------------------------------------------------------- /gui/src/renderer/components/creativeworkshop/VoiceModel.tsx: -------------------------------------------------------------------------------- 1 | import {useEffect, useState} from 'react'; 2 | import {Breadcrumb, Button, Card, Col, Empty, Form, FormProps, Input, Modal, Result, Row} from "antd"; 3 | import {SettingOutlined, EditOutlined, SearchOutlined} from "@ant-design/icons"; 4 | import {useNavigate} from "react-router-dom"; 5 | import characterHandle from "../../features/character/characterHandle"; 6 | import {CharacterDTO} from "../../../main/domain/dto/characterDTO"; 7 | import DeleteCharacter from "../character/DeleteCharacter"; 8 | 9 | type CreateCharacter = { 10 | name?: string; 11 | describe?: string; 12 | }; 13 | 14 | const VoiceModel = () => { 15 | 16 | const [cards, setCards] = useState([]); 17 | const navigate = useNavigate(); 18 | const [loading, setLoading] = useState(false); 19 | const [open, setOpen] = useState(false); 20 | const [form] = Form.useForm(); // Form 实例 21 | 22 | useEffect(() => { 23 | loadCharacters(); 24 | }, []); // 25 | 26 | const loadCharacters = async () => { 27 | try { 28 | characterHandle.findAll().then((result: CharacterDTO[]) => { 29 | setCards(result); 30 | }) 31 | } catch (error) { 32 | console.error('Failed to fetch characters:', error); 33 | } 34 | }; 35 | 36 | const onRefreshCharacter = () => { 37 | loadCharacters(); // 38 | }; 39 | 40 | const showAddCharacterModel = () => { 41 | setOpen(true); 42 | }; 43 | 44 | const closeAddCharacterModel = () => { 45 | setOpen(false); 46 | }; 47 | 48 | const onCreateCharacter: FormProps["onFinish"] = (values) => { 49 | setLoading(true); 50 | try { 51 | const newEntity = characterHandle.create(values) 52 | newEntity.then((result: CharacterDTO) => { 53 | setLoading(false); 54 | setOpen(false); 55 | onRefreshCharacter() 56 | navigate(`/edit-character-space?id=${result.id}`); 57 | console.log('Entity created:', result); 58 | }) 59 | } catch (error) { 60 | console.error('Error creating entity:', error); 61 | } 62 | }; 63 | 64 | return ( 65 | 70 | ) 71 | }; 72 | 73 | export default VoiceModel; 74 | -------------------------------------------------------------------------------- /gui/src/renderer/components/home/HomeOtherSetting.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { Card, Form, Input, Select } from 'antd'; 3 | import { CharacterFormDTO } from '../../../main/domain/dto/characterDTO'; 4 | import { BackgroundOption } from '../../constants/OptionConstants'; 5 | import { LLMSettingDTO } from '../../../main/domain/dto/systemSettingDTO'; 6 | 7 | const homeLiveSettingDiv = { 8 | width: '300px', 9 | marginRight: '30px' 10 | }; 11 | 12 | interface HomeOtherSetting { 13 | currentBackground: string; 14 | backgroundOption: BackgroundOption[]; 15 | setBackground: (backgroundSrc: string) => void; 16 | } 17 | 18 | const HomeOtherSetting: React.FC = ({ backgroundOption, setBackground, currentBackground }) => { 19 | 20 | const [form] = Form.useForm(); 21 | 22 | useEffect(() => { 23 | form.setFieldValue('background', currentBackground); 24 | }, []); 25 | 26 | const onBackgroundChange = (value: string) => { 27 | setBackground(value); 28 | }; 29 | 30 | 31 | return ( 32 |
33 |
43 | 44 | 45 | label='id' 46 | name='id' 47 | hidden={true} 48 | > 49 | 50 | 51 | 52 | label='背景图片' 53 | name='background' 54 | > 55 | 60 | 61 | 62 |
63 |
64 | ); 65 | }; 66 | 67 | export default HomeOtherSetting; 68 | -------------------------------------------------------------------------------- /gui/src/renderer/components/menu/MainMenu.tsx: -------------------------------------------------------------------------------- 1 | import {AppstoreOutlined, HomeOutlined, DropboxOutlined, SettingOutlined} from '@ant-design/icons'; 2 | import {useNavigate} from 'react-router-dom'; 3 | import {Tab, Tabs} from "@nextui-org/tabs"; 4 | 5 | // 使用Segmented组件代替Tabs 6 | const MainMenu = () => { 7 | const navigate = useNavigate(); 8 | const menuItems = [ 9 | { 10 | label: ( 11 |
12 | 13 | Home 14 |
15 | ), 16 | value: 'home' 17 | }, 18 | { 19 | label: ( 20 |
21 | 22 | 角色管理 23 |
24 | ), 25 | value: 'character-space' 26 | }, 27 | { 28 | label: ( 29 |
30 | 31 | 创意工坊 32 |
33 | ), 34 | value: 'creative-workshop' 35 | }, 36 | { 37 | label: ( 38 |
39 | 40 | 系统设置 41 |
42 | ), 43 | value: 'system-setting' 44 | } 45 | ]; 46 | 47 | const onTabChange = (key: React.Key) => { 48 | navigate(`/${key}`); 49 | }; 50 | 51 | return ( 52 | onTabChange(key)}> 54 | { 55 | menuItems.map((item) => ( 56 | 57 | )) 58 | } 59 | 60 | ); 61 | }; 62 | export default MainMenu; 63 | -------------------------------------------------------------------------------- /gui/src/renderer/components/mssage/AudioPlayerQueue.tsx: -------------------------------------------------------------------------------- 1 | // AudioPlayerQueue.tsx 2 | import React, { useEffect, useRef, useState } from 'react'; 3 | import voiceHandle from '../../features/voice/voiceHandle'; 4 | import { AudioFile } from '../../Types'; 5 | 6 | interface AudioPlayerQueueProps { 7 | queue: AudioFile[]; 8 | onEnd: () => void; 9 | onAudioLoad: (audioElement: HTMLAudioElement, audioFile: AudioFile) => void; 10 | } 11 | 12 | const AudioPlayerQueue: React.FC = ({ queue, onEnd, onAudioLoad }) => { 13 | const audioRef = useRef(null); 14 | const [audioPlay, setAudioPlay] = useState(false); 15 | 16 | useEffect(() => { 17 | const audio = audioRef.current; 18 | 19 | let audioFile: AudioFile | null = null; 20 | if (audio && queue.length > 0 && !audioPlay) { 21 | audioFile = queue[0]; 22 | audio.src = audioFile.url; 23 | setAudioPlay(true); 24 | audio.play().catch((error) => { 25 | voiceHandle.deleteVoiceFile(audio.src); 26 | console.error('Audio playback failed:', error); 27 | setAudioPlay(false); 28 | onEnd(); 29 | }); 30 | } 31 | // 设置audio元素的loadeddata事件回调,当音频加载完成后触发 32 | const handleLoadedData = () => { 33 | if (audio && audioFile) { 34 | onAudioLoad(audio, audioFile); 35 | } 36 | }; 37 | if (audio) { 38 | audio.addEventListener('loadeddata', handleLoadedData); 39 | } 40 | 41 | return () => { 42 | if (audio) { 43 | audio.removeEventListener('loadeddata', handleLoadedData); 44 | } 45 | }; 46 | }, [queue]); // 当队列改变时重新执行此effect 47 | 48 | useEffect(() => { 49 | const audio = audioRef.current; 50 | const handleEnd = () => { 51 | setAudioPlay(false); 52 | onEnd(); 53 | if (audioRef.current) { 54 | voiceHandle.deleteVoiceFile(audioRef.current.src); 55 | } 56 | }; 57 | if (audio) { 58 | audio.addEventListener('ended', handleEnd); 59 | // 清除ended事件监听器 60 | return () => audio.removeEventListener('ended', handleEnd); 61 | } 62 | }, [queue, onEnd]); // 使用queue和onEnd作为依赖 63 | 64 | return