├── packages
├── database
│ ├── data
│ │ └── .gitkeep
│ ├── .gitignore
│ ├── README.md
│ ├── .yarn
│ │ └── install-state.gz
│ ├── package.json
│ └── src
│ │ ├── email.js
│ │ └── agents.js
└── artifacts
│ ├── README.md
│ └── package.json
├── .vscode
├── extensions.json
├── snippet.code-snippets
├── settings.json
└── launch.json
├── .eslintignore
├── api
├── screenshot.png
├── screenshot-chat.png
├── src
│ ├── middleware
│ │ └── auth.ts
│ ├── config
│ │ └── index.ts
│ ├── routes
│ │ ├── knowledge.ts
│ │ └── provider.ts
│ └── index.ts
├── tsconfig.json
└── package.json
├── .yarnrc.yml
├── src
├── main
│ ├── resources
│ │ └── icon.ico
│ ├── electron.d.ts
│ ├── constant.ts
│ ├── env.d.ts
│ ├── utils
│ │ ├── locales.ts
│ │ ├── windowUtil.ts
│ │ ├── file.ts
│ │ ├── index.ts
│ │ ├── aes.ts
│ │ └── zip.ts
│ ├── config.ts
│ └── services
│ │ └── CacheService.ts
└── renderer
│ ├── src
│ ├── assets
│ │ ├── images
│ │ │ ├── avatar.png
│ │ │ ├── logo.png
│ │ │ ├── apps
│ │ │ │ ├── nm.webp
│ │ │ │ ├── devv.png
│ │ │ │ ├── doubao.png
│ │ │ │ ├── felo.png
│ │ │ │ ├── gemini.png
│ │ │ │ ├── hika.webp
│ │ │ │ ├── kimi.jpg
│ │ │ │ ├── poe.webp
│ │ │ │ ├── wanzhi.jpg
│ │ │ │ ├── yuewen.png
│ │ │ │ ├── zhihu.png
│ │ │ │ ├── baidu-ai.png
│ │ │ │ ├── genspark.jpg
│ │ │ │ ├── metaso.webp
│ │ │ │ ├── qingyan.png
│ │ │ │ ├── tiangong.png
│ │ │ │ ├── yuanbao.png
│ │ │ │ ├── duckduckgo.webp
│ │ │ │ ├── perplexity.webp
│ │ │ │ ├── sensetime.png
│ │ │ │ ├── sparkdesk.png
│ │ │ │ ├── thinkany.webp
│ │ │ │ ├── baixiaoying.webp
│ │ │ │ ├── github-copilot.webp
│ │ │ │ ├── huggingchat.svg
│ │ │ │ └── bolt.svg
│ │ │ ├── models
│ │ │ │ ├── 360.png
│ │ │ │ ├── ai21.png
│ │ │ │ ├── dbrx.png
│ │ │ │ ├── flux.png
│ │ │ │ ├── grok.png
│ │ │ │ ├── ibm.png
│ │ │ │ ├── jina.png
│ │ │ │ ├── luma.png
│ │ │ │ ├── palm.png
│ │ │ │ ├── qwen.png
│ │ │ │ ├── step.png
│ │ │ │ ├── suno.png
│ │ │ │ ├── tele.png
│ │ │ │ ├── vidu.png
│ │ │ │ ├── yi.png
│ │ │ │ ├── adept.png
│ │ │ │ ├── aimass.png
│ │ │ │ ├── claude.png
│ │ │ │ ├── cohere.png
│ │ │ │ ├── dalle.png
│ │ │ │ ├── doubao.png
│ │ │ │ ├── gemini.png
│ │ │ │ ├── gemma.png
│ │ │ │ ├── google.png
│ │ │ │ ├── gpt_4.png
│ │ │ │ ├── gpt_o1.png
│ │ │ │ ├── gryphe.png
│ │ │ │ ├── hailuo.png
│ │ │ │ ├── keling.png
│ │ │ │ ├── llama.png
│ │ │ │ ├── llava.png
│ │ │ │ ├── magic.png
│ │ │ │ ├── nvidia.png
│ │ │ │ ├── wenxin.png
│ │ │ │ ├── zhipu.png
│ │ │ │ ├── 360_dark.png
│ │ │ │ ├── ai21_dark.png
│ │ │ │ ├── baichuan.png
│ │ │ │ ├── bigcode.png
│ │ │ │ ├── bigcode.webp
│ │ │ │ ├── chatglm.png
│ │ │ │ ├── chatgpt.jpeg
│ │ │ │ ├── codegeex.png
│ │ │ │ ├── cohere.webp
│ │ │ │ ├── copilot.png
│ │ │ │ ├── dbrx_dark.png
│ │ │ │ ├── deepseek.png
│ │ │ │ ├── dianxin.png
│ │ │ │ ├── embedding.png
│ │ │ │ ├── flux_dark.png
│ │ │ │ ├── gpt_3.5.png
│ │ │ │ ├── gpt_dark.png
│ │ │ │ ├── grok_dark.png
│ │ │ │ ├── hunyuan.png
│ │ │ │ ├── ibm_dark.png
│ │ │ │ ├── internlm.png
│ │ │ │ ├── jina_dark.png
│ │ │ │ ├── luma_dark.png
│ │ │ │ ├── mediatek.png
│ │ │ │ ├── microsoft.png
│ │ │ │ ├── minicpm.webp
│ │ │ │ ├── minimax.png
│ │ │ │ ├── mixtral.png
│ │ │ │ ├── moonshot.png
│ │ │ │ ├── palm_dark.png
│ │ │ │ ├── pixtral.png
│ │ │ │ ├── qwen_dark.png
│ │ │ │ ├── rakutenai.png
│ │ │ │ ├── sparkdesk.png
│ │ │ │ ├── stability.png
│ │ │ │ ├── step_dark.png
│ │ │ │ ├── suno_dark.png
│ │ │ │ ├── tele_dark.png
│ │ │ │ ├── upstage.png
│ │ │ │ ├── vidu_dark.png
│ │ │ │ ├── yi_dark.png
│ │ │ │ ├── adept_dark.png
│ │ │ │ ├── aimass_dark.png
│ │ │ │ ├── aisingapore.png
│ │ │ │ ├── claude_dark.png
│ │ │ │ ├── cohere_dark.png
│ │ │ │ ├── dalle_dark.png
│ │ │ │ ├── doubao_dark.png
│ │ │ │ ├── flashaudio.png
│ │ │ │ ├── gemini_dark.png
│ │ │ │ ├── gemma_dark.png
│ │ │ │ ├── gryphe_dark.png
│ │ │ │ ├── hailuo_dark.png
│ │ │ │ ├── huggingface.png
│ │ │ │ ├── keling_dark.png
│ │ │ │ ├── llama_dark.png
│ │ │ │ ├── llava_dark.png
│ │ │ │ ├── magic_dark.png
│ │ │ │ ├── midjourney.png
│ │ │ │ ├── nvidia_dark.png
│ │ │ │ ├── wenxin_dark.png
│ │ │ │ ├── zhipu_dark.png
│ │ │ │ ├── baichuan_dark.png
│ │ │ │ ├── bigcode_dark.png
│ │ │ │ ├── bigcode_dark.webp
│ │ │ │ ├── chatglm_dark.png
│ │ │ │ ├── codegeex_dark.png
│ │ │ │ ├── copilot_dark.png
│ │ │ │ ├── deepseek_dark.png
│ │ │ │ ├── dianxin_dark.png
│ │ │ │ ├── hunyuan_dark.png
│ │ │ │ ├── internlm_dark.png
│ │ │ │ ├── mediatek_dark.png
│ │ │ │ ├── microsoft_dark.png
│ │ │ │ ├── minimax_dark.png
│ │ │ │ ├── mixtral_dark.png
│ │ │ │ ├── moonshot_dark.png
│ │ │ │ ├── nousresearch.png
│ │ │ │ ├── pixtral_dark.png
│ │ │ │ ├── rakutenai_dark.png
│ │ │ │ ├── sparkdesk_dark.png
│ │ │ │ ├── stability_dark.png
│ │ │ │ ├── upstage_dark.png
│ │ │ │ ├── aisingapore_dark.png
│ │ │ │ ├── flashaudio_dark.png
│ │ │ │ ├── huggingface_dark.png
│ │ │ │ └── midjourney_dark.png
│ │ │ ├── providers
│ │ │ │ ├── doubao.png
│ │ │ │ ├── gemini.png
│ │ │ │ ├── github.png
│ │ │ │ ├── google.png
│ │ │ │ ├── grok.png
│ │ │ │ ├── groq.png
│ │ │ │ ├── jina.png
│ │ │ │ ├── lepton.png
│ │ │ │ ├── nvidia.png
│ │ │ │ ├── ollama.png
│ │ │ │ ├── openai.png
│ │ │ │ ├── step.png
│ │ │ │ ├── zhipu.png
│ │ │ │ ├── aihubmix.jpg
│ │ │ │ ├── baichuan.png
│ │ │ │ ├── bailian.png
│ │ │ │ ├── deepseek.png
│ │ │ │ ├── minimax.png
│ │ │ │ ├── mistral.png
│ │ │ │ ├── moonshot.png
│ │ │ │ ├── ocoolai.png
│ │ │ │ ├── openai.jpeg
│ │ │ │ ├── silicon.png
│ │ │ │ ├── together.png
│ │ │ │ ├── zero-one.png
│ │ │ │ ├── anthropic.png
│ │ │ │ ├── bytedance.png
│ │ │ │ ├── dashscope.png
│ │ │ │ ├── fireworks.png
│ │ │ │ ├── graph-rag.png
│ │ │ │ ├── hyperbolic.png
│ │ │ │ ├── infini-ai.png
│ │ │ │ ├── mixedbread.png
│ │ │ │ ├── openrouter.png
│ │ │ │ ├── perplexity.png
│ │ │ │ └── volcengine.png
│ │ │ └── paintings
│ │ │ │ ├── image-size-1-2.svg
│ │ │ │ ├── image-size-3-2.svg
│ │ │ │ ├── image-size-3-4.svg
│ │ │ │ ├── image-size-16-9.svg
│ │ │ │ ├── image-size-9-16.svg
│ │ │ │ └── image-size-1-1.svg
│ │ ├── fonts
│ │ │ ├── ubuntu
│ │ │ │ ├── Ubuntu-Bold.ttf
│ │ │ │ ├── Ubuntu-Light.ttf
│ │ │ │ ├── Ubuntu-Italic.ttf
│ │ │ │ ├── Ubuntu-Medium.ttf
│ │ │ │ ├── Ubuntu-Regular.ttf
│ │ │ │ ├── Ubuntu-BoldItalic.ttf
│ │ │ │ ├── Ubuntu-LightItalic.ttf
│ │ │ │ ├── Ubuntu-MediumItalic.ttf
│ │ │ │ └── ubuntu.css
│ │ │ └── icon-fonts
│ │ │ │ ├── iconfont.woff2
│ │ │ │ └── iconfont.css
│ │ └── styles
│ │ │ ├── scrollbar.scss
│ │ │ └── ant.scss
│ ├── hooks
│ │ ├── useAvatar.ts
│ │ ├── useModel.ts
│ │ ├── useRuntime.ts
│ │ ├── useOllama.ts
│ │ ├── useScrollPosition.ts
│ │ ├── useStore.ts
│ │ ├── useAgents.ts
│ │ ├── useBridge.ts
│ │ ├── useSettings.ts
│ │ └── usePaintings.ts
│ ├── main.tsx
│ ├── config
│ │ ├── env.ts
│ │ ├── constant.ts
│ │ ├── tools.ts
│ │ └── prompts.ts
│ ├── components
│ │ ├── Icons
│ │ │ ├── CopyIcon.tsx
│ │ │ ├── VisionIcon.tsx
│ │ │ └── WebSearchIcon.tsx
│ │ ├── Avatar
│ │ │ └── ModelAvatar.tsx
│ │ ├── EmojiPicker
│ │ │ └── index.tsx
│ │ ├── IndicatorLight.tsx
│ │ ├── ModelTags.tsx
│ │ ├── Popups
│ │ │ ├── TemplatePopup.tsx
│ │ │ ├── SearchPopup.tsx
│ │ │ ├── AppStorePopover.tsx
│ │ │ └── PromptPopup.tsx
│ │ ├── Scrollbar
│ │ │ └── index.tsx
│ │ └── ListItem
│ │ │ └── index.tsx
│ ├── providers
│ │ ├── index.d.ts
│ │ └── ProviderFactory.ts
│ ├── pages
│ │ ├── home
│ │ │ ├── Markdown
│ │ │ │ ├── Link.tsx
│ │ │ │ ├── SvgPreview.tsx
│ │ │ │ ├── Mermaid.tsx
│ │ │ │ ├── Artifacts.tsx
│ │ │ │ └── ImagePreview.tsx
│ │ │ ├── Messages
│ │ │ │ ├── MessageError.tsx
│ │ │ │ ├── NarrowLayout.tsx
│ │ │ │ ├── MessageErrorBoundary.tsx
│ │ │ │ ├── Prompt.tsx
│ │ │ │ ├── MessageAttachments.tsx
│ │ │ │ └── MessageContent.tsx
│ │ │ ├── Inputbar
│ │ │ │ ├── SendMessageButton.tsx
│ │ │ │ ├── AttachmentPreview.tsx
│ │ │ │ └── AttachmentButton.tsx
│ │ │ ├── Chat.tsx
│ │ │ └── components
│ │ │ │ └── SelectModelButton.tsx
│ │ ├── agents
│ │ │ └── components
│ │ │ │ └── AddAgentCard.tsx
│ │ ├── settings
│ │ │ └── ProviderSettings
│ │ │ │ ├── OllamaSettings.tsx
│ │ │ │ └── GraphRAGSettings.tsx
│ │ └── apps
│ │ │ └── App.tsx
│ ├── services
│ │ ├── ProviderService.ts
│ │ ├── NavigationService.ts
│ │ ├── ModelService.ts
│ │ ├── ImageStorage.ts
│ │ ├── EventService.ts
│ │ └── TranslateService.ts
│ ├── init.ts
│ ├── utils
│ │ ├── oauth.ts
│ │ ├── export.ts
│ │ ├── style.ts
│ │ └── formula.ts
│ ├── env.d.ts
│ ├── i18n
│ │ └── index.ts
│ ├── databases
│ │ └── index.ts
│ ├── store
│ │ ├── paintings.ts
│ │ ├── index.ts
│ │ └── agents.ts
│ └── context
│ │ ├── ThemeProvider.tsx
│ │ └── AntdProvider.tsx
│ └── index.html
├── .prettierignore
├── .prettierrc
├── docs
└── sponsor.md
├── tsconfig.json
├── resources
└── js
│ ├── utils.js
│ └── bridge.js
├── .editorconfig
├── dev-app-update.yml
├── tsconfig.web.json
├── tsconfig.node.json
├── .gitignore
├── .yarn
└── patches
│ ├── @llm-tools-embedjs-npm-0.1.25-ec5645cf36.patch
│ ├── openai-npm-4.76.2-8ff1374617.patch
│ ├── @llm-tools-embedjs-loader-markdown-npm-0.1.25-d1d536d640.patch
│ ├── @llm-tools-embedjs-libsql-npm-0.1.25-fad000d74c.patch
│ ├── pdf-parse-npm-1.1.1-04a6109b2a.patch
│ └── @llm-tools-embedjs-utils-npm-0.1.25-fd8fe8a193.patch
├── .eslintrc.cjs
├── scripts
├── notarize.js
├── utils.js
├── version.js
├── build-npm.js
├── replace-spaces.js
├── after-pack.js
└── remove-locales.js
├── .github
└── ISSUE_TEMPLATE
│ ├── #1_feature_request.yml
│ ├── #2_question.yml
│ ├── 1_feature_request.yml
│ ├── 2_question.yml
│ ├── #0_bug_report.yml
│ └── 0_bug_report.yml
├── CONTRIBUTING.md
├── electron.vite.config.ts
└── CODE_OF_CONDUCT.md
/packages/database/data/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/artifacts/README.md:
--------------------------------------------------------------------------------
1 | # Cherry Studio Artifacts
2 |
--------------------------------------------------------------------------------
/packages/database/.gitignore:
--------------------------------------------------------------------------------
1 | data/*
2 | !data/.gitkeep
3 |
4 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": ["dbaeumer.vscode-eslint"]
3 | }
4 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | out
4 | .gitignore
5 | scripts/cloudflare-worker.js
6 |
--------------------------------------------------------------------------------
/api/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/api/screenshot.png
--------------------------------------------------------------------------------
/.yarnrc.yml:
--------------------------------------------------------------------------------
1 | enableImmutableInstalls: false
2 |
3 | httpTimeout: 300000
4 |
5 | nodeLinker: node-modules
6 |
--------------------------------------------------------------------------------
/api/screenshot-chat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/api/screenshot-chat.png
--------------------------------------------------------------------------------
/packages/database/README.md:
--------------------------------------------------------------------------------
1 | # Cherry Studio Database
2 |
3 | Cherry Studio 依赖的数据文件由这个数据库来生成,数据库文件请联系开发者获取
4 |
--------------------------------------------------------------------------------
/src/main/resources/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/main/resources/icon.ico
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | out
2 | dist
3 | pnpm-lock.yaml
4 | LICENSE.md
5 | tsconfig.json
6 | tsconfig.*.json
7 | CHANGELOG*.md
8 | agents.json
9 |
--------------------------------------------------------------------------------
/packages/database/.yarn/install-state.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/packages/database/.yarn/install-state.gz
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/avatar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/avatar.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/logo.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/apps/nm.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/apps/nm.webp
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/apps/devv.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/apps/devv.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/apps/doubao.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/apps/doubao.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/apps/felo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/apps/felo.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/apps/gemini.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/apps/gemini.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/apps/hika.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/apps/hika.webp
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/apps/kimi.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/apps/kimi.jpg
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/apps/poe.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/apps/poe.webp
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/apps/wanzhi.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/apps/wanzhi.jpg
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/apps/yuewen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/apps/yuewen.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/apps/zhihu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/apps/zhihu.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/360.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/360.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/ai21.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/ai21.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/dbrx.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/dbrx.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/flux.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/flux.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/grok.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/grok.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/ibm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/ibm.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/jina.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/jina.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/luma.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/luma.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/palm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/palm.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/qwen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/qwen.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/step.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/step.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/suno.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/suno.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/tele.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/tele.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/vidu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/vidu.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/yi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/yi.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/apps/baidu-ai.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/apps/baidu-ai.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/apps/genspark.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/apps/genspark.jpg
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/apps/metaso.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/apps/metaso.webp
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/apps/qingyan.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/apps/qingyan.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/apps/tiangong.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/apps/tiangong.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/apps/yuanbao.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/apps/yuanbao.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/adept.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/adept.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/aimass.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/aimass.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/claude.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/claude.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/cohere.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/cohere.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/dalle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/dalle.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/doubao.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/doubao.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/gemini.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/gemini.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/gemma.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/gemma.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/google.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/google.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/gpt_4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/gpt_4.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/gpt_o1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/gpt_o1.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/gryphe.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/gryphe.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/hailuo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/hailuo.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/keling.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/keling.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/llama.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/llama.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/llava.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/llava.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/magic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/magic.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/nvidia.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/nvidia.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/wenxin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/wenxin.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/zhipu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/zhipu.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/apps/duckduckgo.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/apps/duckduckgo.webp
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/apps/perplexity.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/apps/perplexity.webp
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/apps/sensetime.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/apps/sensetime.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/apps/sparkdesk.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/apps/sparkdesk.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/apps/thinkany.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/apps/thinkany.webp
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/360_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/360_dark.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/ai21_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/ai21_dark.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/baichuan.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/baichuan.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/bigcode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/bigcode.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/bigcode.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/bigcode.webp
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/chatglm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/chatglm.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/chatgpt.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/chatgpt.jpeg
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/codegeex.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/codegeex.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/cohere.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/cohere.webp
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/copilot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/copilot.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/dbrx_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/dbrx_dark.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/deepseek.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/deepseek.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/dianxin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/dianxin.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/embedding.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/embedding.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/flux_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/flux_dark.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/gpt_3.5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/gpt_3.5.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/gpt_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/gpt_dark.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/grok_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/grok_dark.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/hunyuan.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/hunyuan.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/ibm_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/ibm_dark.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/internlm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/internlm.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/jina_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/jina_dark.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/luma_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/luma_dark.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/mediatek.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/mediatek.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/microsoft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/microsoft.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/minicpm.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/minicpm.webp
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/minimax.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/minimax.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/mixtral.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/mixtral.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/moonshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/moonshot.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/palm_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/palm_dark.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/pixtral.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/pixtral.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/qwen_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/qwen_dark.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/rakutenai.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/rakutenai.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/sparkdesk.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/sparkdesk.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/stability.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/stability.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/step_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/step_dark.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/suno_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/suno_dark.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/tele_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/tele_dark.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/upstage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/upstage.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/vidu_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/vidu_dark.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/yi_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/yi_dark.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/providers/doubao.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/providers/doubao.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/providers/gemini.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/providers/gemini.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/providers/github.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/providers/github.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/providers/google.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/providers/google.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/providers/grok.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/providers/grok.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/providers/groq.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/providers/groq.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/providers/jina.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/providers/jina.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/providers/lepton.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/providers/lepton.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/providers/nvidia.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/providers/nvidia.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/providers/ollama.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/providers/ollama.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/providers/openai.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/providers/openai.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/providers/step.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/providers/step.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/providers/zhipu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/providers/zhipu.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/fonts/ubuntu/Ubuntu-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/fonts/ubuntu/Ubuntu-Bold.ttf
--------------------------------------------------------------------------------
/src/renderer/src/assets/fonts/ubuntu/Ubuntu-Light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/fonts/ubuntu/Ubuntu-Light.ttf
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/apps/baixiaoying.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/apps/baixiaoying.webp
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/adept_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/adept_dark.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/aimass_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/aimass_dark.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/aisingapore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/aisingapore.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/claude_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/claude_dark.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/cohere_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/cohere_dark.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/dalle_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/dalle_dark.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/doubao_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/doubao_dark.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/flashaudio.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/flashaudio.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/gemini_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/gemini_dark.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/gemma_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/gemma_dark.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/gryphe_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/gryphe_dark.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/hailuo_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/hailuo_dark.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/huggingface.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/huggingface.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/keling_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/keling_dark.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/llama_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/llama_dark.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/llava_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/llava_dark.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/magic_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/magic_dark.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/midjourney.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/midjourney.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/nvidia_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/nvidia_dark.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/wenxin_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/wenxin_dark.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/zhipu_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/zhipu_dark.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/providers/aihubmix.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/providers/aihubmix.jpg
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/providers/baichuan.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/providers/baichuan.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/providers/bailian.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/providers/bailian.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/providers/deepseek.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/providers/deepseek.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/providers/minimax.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/providers/minimax.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/providers/mistral.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/providers/mistral.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/providers/moonshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/providers/moonshot.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/providers/ocoolai.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/providers/ocoolai.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/providers/openai.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/providers/openai.jpeg
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/providers/silicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/providers/silicon.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/providers/together.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/providers/together.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/providers/zero-one.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/providers/zero-one.png
--------------------------------------------------------------------------------
/src/main/electron.d.ts:
--------------------------------------------------------------------------------
1 | declare global {
2 | namespace Electron {
3 | interface App {
4 | isQuitting: boolean
5 | }
6 | }
7 | }
8 |
9 | export {}
10 |
--------------------------------------------------------------------------------
/src/renderer/src/assets/fonts/icon-fonts/iconfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/fonts/icon-fonts/iconfont.woff2
--------------------------------------------------------------------------------
/src/renderer/src/assets/fonts/ubuntu/Ubuntu-Italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/fonts/ubuntu/Ubuntu-Italic.ttf
--------------------------------------------------------------------------------
/src/renderer/src/assets/fonts/ubuntu/Ubuntu-Medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/fonts/ubuntu/Ubuntu-Medium.ttf
--------------------------------------------------------------------------------
/src/renderer/src/assets/fonts/ubuntu/Ubuntu-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/fonts/ubuntu/Ubuntu-Regular.ttf
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/apps/github-copilot.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/apps/github-copilot.webp
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/baichuan_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/baichuan_dark.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/bigcode_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/bigcode_dark.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/bigcode_dark.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/bigcode_dark.webp
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/chatglm_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/chatglm_dark.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/codegeex_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/codegeex_dark.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/copilot_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/copilot_dark.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/deepseek_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/deepseek_dark.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/dianxin_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/dianxin_dark.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/hunyuan_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/hunyuan_dark.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/internlm_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/internlm_dark.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/mediatek_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/mediatek_dark.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/microsoft_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/microsoft_dark.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/minimax_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/minimax_dark.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/mixtral_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/mixtral_dark.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/moonshot_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/moonshot_dark.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/nousresearch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/nousresearch.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/pixtral_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/pixtral_dark.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/rakutenai_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/rakutenai_dark.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/sparkdesk_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/sparkdesk_dark.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/stability_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/stability_dark.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/upstage_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/upstage_dark.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/providers/anthropic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/providers/anthropic.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/providers/bytedance.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/providers/bytedance.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/providers/dashscope.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/providers/dashscope.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/providers/fireworks.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/providers/fireworks.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/providers/graph-rag.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/providers/graph-rag.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/providers/hyperbolic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/providers/hyperbolic.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/providers/infini-ai.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/providers/infini-ai.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/providers/mixedbread.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/providers/mixedbread.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/providers/openrouter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/providers/openrouter.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/providers/perplexity.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/providers/perplexity.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/providers/volcengine.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/providers/volcengine.png
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "semi": false,
4 | "printWidth": 120,
5 | "trailingComma": "none",
6 | "endOfLine": "lf",
7 | "bracketSameLine": true
8 | }
9 |
--------------------------------------------------------------------------------
/src/renderer/src/assets/fonts/ubuntu/Ubuntu-BoldItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/fonts/ubuntu/Ubuntu-BoldItalic.ttf
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/aisingapore_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/aisingapore_dark.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/flashaudio_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/flashaudio_dark.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/huggingface_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/huggingface_dark.png
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/models/midjourney_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/images/models/midjourney_dark.png
--------------------------------------------------------------------------------
/src/main/constant.ts:
--------------------------------------------------------------------------------
1 | export const isMac = process.platform === 'darwin'
2 | export const isWin = process.platform === 'win32'
3 | export const isLinux = process.platform === 'linux'
4 |
--------------------------------------------------------------------------------
/src/renderer/src/assets/fonts/ubuntu/Ubuntu-LightItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/fonts/ubuntu/Ubuntu-LightItalic.ttf
--------------------------------------------------------------------------------
/src/renderer/src/assets/fonts/ubuntu/Ubuntu-MediumItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tufeiping/api-for-cherrystudio/HEAD/src/renderer/src/assets/fonts/ubuntu/Ubuntu-MediumItalic.ttf
--------------------------------------------------------------------------------
/docs/sponsor.md:
--------------------------------------------------------------------------------
1 | # Sponsor
2 |
3 |
4 |

5 |
6 |
--------------------------------------------------------------------------------
/src/renderer/src/hooks/useAvatar.ts:
--------------------------------------------------------------------------------
1 | import { useAppSelector } from '@renderer/store'
2 |
3 | export default function useAvatar() {
4 | return useAppSelector((state) => state.runtime.avatar)
5 | }
6 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "files": [],
3 | "references": [
4 | {
5 | "path": "./tsconfig.node.json"
6 | },
7 | {
8 | "path": "./tsconfig.web.json"
9 | }
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/resources/js/utils.js:
--------------------------------------------------------------------------------
1 | export function getQueryParam(paramName) {
2 | const url = new URL(window.location.href)
3 | const params = new URLSearchParams(url.search)
4 | return params.get(paramName)
5 | }
6 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
--------------------------------------------------------------------------------
/src/main/env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | interface ImportMetaEnv {
4 | VITE_MAIN_BUNDLE_ID: string
5 | }
6 |
7 | interface ImportMeta {
8 | readonly env: ImportMetaEnv
9 | }
10 |
--------------------------------------------------------------------------------
/dev-app-update.yml:
--------------------------------------------------------------------------------
1 | # provider: generic
2 | # url: http://127.0.0.1:8080
3 | # updaterCacheDirName: cherry-studio-updater
4 | # provider: github
5 | # repo: cherry-studio
6 | # owner: kangfenmao
7 | provider: generic
8 | url: https://cherrystudio.ocool.online
9 |
--------------------------------------------------------------------------------
/src/renderer/src/main.tsx:
--------------------------------------------------------------------------------
1 | import './assets/styles/index.scss'
2 | import './init'
3 |
4 | import ReactDOM from 'react-dom/client'
5 |
6 | import App from './App'
7 |
8 | ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render()
9 |
--------------------------------------------------------------------------------
/src/renderer/src/config/env.ts:
--------------------------------------------------------------------------------
1 | export { default as UserAvatar } from '@renderer/assets/images/avatar.png'
2 | export { default as AppLogo } from '@renderer/assets/images/logo.png'
3 |
4 | export const APP_NAME = 'Cherry Studio'
5 | export const isLocalAi = false
6 |
--------------------------------------------------------------------------------
/src/renderer/src/hooks/useModel.ts:
--------------------------------------------------------------------------------
1 | import { useProviders } from './useProvider'
2 |
3 | export function useModel(id?: string) {
4 | const { providers } = useProviders()
5 | const allModels = providers.map((p) => p.models).flat()
6 | return allModels.find((m) => m.id === id)
7 | }
8 |
--------------------------------------------------------------------------------
/src/renderer/src/components/Icons/CopyIcon.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 |
3 | const CopyIcon: FC, HTMLElement>> = (props) => {
4 | return
5 | }
6 |
7 | export default CopyIcon
8 |
--------------------------------------------------------------------------------
/api/src/middleware/auth.ts:
--------------------------------------------------------------------------------
1 | import { NextFunction, Request, Response } from 'express'
2 |
3 | export const authMiddleware = (req: Request, res: Response, next: NextFunction) => {
4 | const apiKey = req.headers['x-api-key']
5 |
6 | if (!apiKey || apiKey !== process.env.API_KEY) {
7 | return res.status(401).json({ error: 'Unauthorized' })
8 | }
9 |
10 | next()
11 | }
12 |
--------------------------------------------------------------------------------
/packages/database/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@cherry-studio/database",
3 | "packageManager": "yarn@4.3.1",
4 | "dependencies": {
5 | "@cherry-studio/database": "file:",
6 | "csv-parser": "^3.0.0",
7 | "sqlite3": "^5.1.7"
8 | },
9 | "scripts": {
10 | "agents": "node src/agents.js",
11 | "email": "yarn csv && node src/email.js",
12 | "csv": "node src/csv.js"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/api/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2022",
4 | "module": "NodeNext",
5 | "moduleResolution": "NodeNext",
6 | "outDir": "./dist",
7 | "rootDir": "./src",
8 | "strict": true,
9 | "esModuleInterop": true,
10 | "skipLibCheck": true,
11 | "forceConsistentCasingInFileNames": true
12 | },
13 | "include": ["src/**/*"],
14 | "exclude": ["node_modules"]
15 | }
16 |
--------------------------------------------------------------------------------
/src/renderer/src/providers/index.d.ts:
--------------------------------------------------------------------------------
1 | import type { Assistant, Metrics } from '@renderer/types'
2 |
3 | interface ChunkCallbackData {
4 | text?: string
5 | usage?: OpenAI.Completions.CompletionUsage
6 | metrics?: Metrics
7 | }
8 |
9 | interface CompletionsParams {
10 | messages: Message[]
11 | assistant: Assistant
12 | onChunk: ({ text, usage }: ChunkCallbackData) => void
13 | onFilterMessages: (messages: Message[]) => void
14 | }
15 |
--------------------------------------------------------------------------------
/src/renderer/src/pages/home/Markdown/Link.tsx:
--------------------------------------------------------------------------------
1 | import { omit } from 'lodash'
2 | import React from 'react'
3 |
4 | const Link: React.FC = (props: React.AnchorHTMLAttributes) => {
5 | if (props.href?.startsWith('#')) {
6 | return {props.children}
7 | }
8 |
9 | return e.stopPropagation()} />
10 | }
11 |
12 | export default Link
13 |
--------------------------------------------------------------------------------
/src/renderer/src/services/ProviderService.ts:
--------------------------------------------------------------------------------
1 | import i18n from '@renderer/i18n'
2 | import store from '@renderer/store'
3 |
4 | export function getProviderName(id: string) {
5 | const provider = store.getState().llm.providers.find((p) => p.id === id)
6 | if (!provider) {
7 | return ''
8 | }
9 |
10 | if (provider.isSystem) {
11 | return i18n.t(`provider.${provider.id}`, { defaultValue: provider.name })
12 | }
13 |
14 | return provider?.name
15 | }
16 |
--------------------------------------------------------------------------------
/src/renderer/src/init.ts:
--------------------------------------------------------------------------------
1 | import KeyvStorage from '@kangfenmao/keyv-storage'
2 |
3 | import { startAutoSync } from './services/BackupService'
4 | import store from './store'
5 |
6 | function initKeyv() {
7 | window.keyv = new KeyvStorage()
8 | window.keyv.init()
9 | }
10 |
11 | function initAutoSync() {
12 | const { webdavAutoSync } = store.getState().settings
13 | if (webdavAutoSync) {
14 | startAutoSync()
15 | }
16 | }
17 |
18 | initKeyv()
19 | initAutoSync()
20 |
--------------------------------------------------------------------------------
/tsconfig.web.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@electron-toolkit/tsconfig/tsconfig.web.json",
3 | "include": [
4 | "src/renderer/src/**/*",
5 | "src/preload/*.d.ts",
6 | "local/src/renderer/**/*",
7 | "packages/shared/**/*"
8 | ],
9 | "compilerOptions": {
10 | "composite": true,
11 | "jsx": "react-jsx",
12 | "baseUrl": ".",
13 | "paths": {
14 | "@renderer/*": ["src/renderer/src/*"],
15 | "@shared/*": ["packages/shared/*"]
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/renderer/src/pages/home/Markdown/SvgPreview.tsx:
--------------------------------------------------------------------------------
1 | const SvgPreview = ({ children }: { children: string }) => {
2 | return (
3 |
13 | )
14 | }
15 |
16 | export default SvgPreview
17 |
--------------------------------------------------------------------------------
/.vscode/snippet.code-snippets:
--------------------------------------------------------------------------------
1 | {
2 | "Function component": {
3 | "prefix": "rnfc",
4 | "body": [
5 | "import { FC } from 'react'",
6 | "import styled from 'styled-components'",
7 | "",
8 | "const $1: FC = () => {",
9 | " return ",
10 | "}",
11 | "",
12 | "const Container = styled.div``",
13 | "",
14 | "export default $1",
15 | ""
16 | ],
17 | "description": "Function component"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/renderer/src/services/NavigationService.ts:
--------------------------------------------------------------------------------
1 | import { NavigateFunction } from 'react-router-dom'
2 |
3 | interface INavigationService {
4 | navigate: NavigateFunction | null
5 | setNavigate: (navigateFunc: NavigateFunction) => void
6 | }
7 |
8 | const NavigationService: INavigationService = {
9 | navigate: null,
10 |
11 | setNavigate: (navigateFunc: NavigateFunction): void => {
12 | NavigationService.navigate = navigateFunc
13 | }
14 | }
15 |
16 | export default NavigationService
17 |
--------------------------------------------------------------------------------
/src/renderer/src/components/Icons/VisionIcon.tsx:
--------------------------------------------------------------------------------
1 | import { EyeOutlined } from '@ant-design/icons'
2 | import React, { FC } from 'react'
3 | import styled from 'styled-components'
4 |
5 | const VisionIcon: FC, HTMLElement>> = (props) => {
6 | return
7 | }
8 |
9 | const Icon = styled(EyeOutlined)`
10 | color: var(--color-primary);
11 | font-size: 14px;
12 | margin-left: 4px;
13 | `
14 |
15 | export default VisionIcon
16 |
--------------------------------------------------------------------------------
/src/main/utils/locales.ts:
--------------------------------------------------------------------------------
1 | import EnUs from '../../renderer/src/i18n/locales/en-us.json'
2 | import JaJP from '../../renderer/src/i18n/locales/ja-jp.json'
3 | import RuRu from '../../renderer/src/i18n/locales/ru-ru.json'
4 | import ZhCn from '../../renderer/src/i18n/locales/zh-cn.json'
5 | import ZhTw from '../../renderer/src/i18n/locales/zh-tw.json'
6 |
7 | const locales = {
8 | 'en-US': EnUs,
9 | 'zh-CN': ZhCn,
10 | 'zh-TW': ZhTw,
11 | 'ja-JP': JaJP,
12 | 'ru-RU': RuRu
13 | }
14 |
15 | export { locales }
16 |
--------------------------------------------------------------------------------
/src/renderer/src/components/Icons/WebSearchIcon.tsx:
--------------------------------------------------------------------------------
1 | import { GlobalOutlined } from '@ant-design/icons'
2 | import React, { FC } from 'react'
3 | import styled from 'styled-components'
4 |
5 | const WebSearchIcon: FC, HTMLElement>> = (props) => {
6 | return
7 | }
8 |
9 | const Icon = styled(GlobalOutlined)`
10 | color: var(--color-link);
11 | font-size: 12px;
12 | margin-left: 4px;
13 | `
14 |
15 | export default WebSearchIcon
16 |
--------------------------------------------------------------------------------
/src/main/utils/windowUtil.ts:
--------------------------------------------------------------------------------
1 | function isTilingWindowManager() {
2 | if (process.platform === 'darwin') {
3 | return false
4 | }
5 |
6 | if (process.platform !== 'linux') {
7 | return true
8 | }
9 |
10 | const desktopEnv = process.env.XDG_CURRENT_DESKTOP?.toLowerCase()
11 | const tilingSystems = ['hyprland', 'i3', 'sway', 'bspwm', 'dwm', 'awesome', 'qtile', 'herbstluftwm', 'xmonad']
12 |
13 | return tilingSystems.some((system) => desktopEnv?.includes(system))
14 | }
15 |
16 | export { isTilingWindowManager }
17 |
--------------------------------------------------------------------------------
/src/renderer/src/hooks/useRuntime.ts:
--------------------------------------------------------------------------------
1 | import i18n from '@renderer/i18n'
2 | import store, { useAppSelector } from '@renderer/store'
3 |
4 | export function useRuntime() {
5 | return useAppSelector((state) => state.runtime)
6 | }
7 |
8 | export function modelGenerating() {
9 | const generating = store.getState().runtime.generating
10 |
11 | if (generating) {
12 | window.message.warning({ content: i18n.t('message.switch.disabled'), key: 'model-generating' })
13 | return Promise.reject()
14 | }
15 |
16 | return Promise.resolve()
17 | }
18 |
--------------------------------------------------------------------------------
/src/renderer/src/services/ModelService.ts:
--------------------------------------------------------------------------------
1 | import store from '@renderer/store'
2 | import { Model } from '@renderer/types'
3 | import { pick } from 'lodash'
4 |
5 | export const getModelUniqId = (m?: Model) => {
6 | return m?.id ? JSON.stringify(pick(m, ['id', 'provider'])) : ''
7 | }
8 |
9 | export const hasModel = (m?: Model) => {
10 | const allModels = store
11 | .getState()
12 | .llm.providers.filter((p) => p.enabled)
13 | .map((p) => p.models)
14 | .flat()
15 |
16 | return allModels.find((model) => model.id === m?.id)
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/config.ts:
--------------------------------------------------------------------------------
1 | import { app } from 'electron'
2 |
3 | import { getDataPath } from './utils'
4 |
5 | const isDev = process.env.NODE_ENV === 'development'
6 |
7 | if (isDev) {
8 | app.setPath('userData', app.getPath('userData') + 'Dev')
9 | }
10 |
11 | export const DATA_PATH = getDataPath()
12 |
13 | export const titleBarOverlayDark = {
14 | height: 40,
15 | color: '#00000000',
16 | symbolColor: '#ffffff'
17 | }
18 |
19 | export const titleBarOverlayLight = {
20 | height: 40,
21 | color: '#00000000',
22 | symbolColor: '#000000'
23 | }
24 |
--------------------------------------------------------------------------------
/src/renderer/src/config/constant.ts:
--------------------------------------------------------------------------------
1 | export const DEFAULT_TEMPERATURE = 0.7
2 | export const DEFAULT_CONTEXTCOUNT = 5
3 | export const DEFAULT_MAX_TOKENS = 4096
4 | export const FONT_FAMILY =
5 | "Ubuntu, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif"
6 |
7 | export const platform = window.electron?.process?.platform
8 | export const isMac = platform === 'darwin'
9 | export const isWindows = platform === 'win32' || platform === 'win64'
10 | export const isLinux = platform === 'linux'
11 |
--------------------------------------------------------------------------------
/src/main/utils/file.ts:
--------------------------------------------------------------------------------
1 | import { audioExts, documentExts, imageExts, textExts, videoExts } from '@shared/config/constant'
2 | import { FileTypes } from '@types'
3 |
4 | export function getFileType(ext: string): FileTypes {
5 | ext = ext.toLowerCase()
6 | if (imageExts.includes(ext)) return FileTypes.IMAGE
7 | if (videoExts.includes(ext)) return FileTypes.VIDEO
8 | if (audioExts.includes(ext)) return FileTypes.AUDIO
9 | if (textExts.includes(ext)) return FileTypes.TEXT
10 | if (documentExts.includes(ext)) return FileTypes.DOCUMENT
11 | return FileTypes.OTHER
12 | }
13 |
--------------------------------------------------------------------------------
/src/renderer/src/utils/oauth.ts:
--------------------------------------------------------------------------------
1 | export const oauthWithSiliconFlow = async (setKey) => {
2 | const clientId = 'SFrugiu0ezVmREv8BAU6GV'
3 | const ACCOUNT_ENDPOINT = 'https://account.siliconflow.cn'
4 | const authUrl = `${ACCOUNT_ENDPOINT}/oauth?client_id=${clientId}`
5 | const popup = window.open(authUrl, 'oauthPopup', 'width=600,height=600')
6 | window.addEventListener('message', (event) => {
7 | if (event.data.length > 0 && event.data[0]['secretKey'] !== undefined) {
8 | setKey(event.data[0]['secretKey'])
9 | popup?.close()
10 | }
11 | })
12 | }
13 |
--------------------------------------------------------------------------------
/src/renderer/src/pages/home/Markdown/Mermaid.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react'
2 |
3 | import MermaidPopup from './MermaidPopup'
4 |
5 | interface Props {
6 | chart: string
7 | }
8 |
9 | const Mermaid: React.FC = ({ chart }) => {
10 | useEffect(() => {
11 | window?.mermaid?.contentLoaded()
12 | }, [])
13 |
14 | const onPreview = () => {
15 | MermaidPopup.show({ chart })
16 | }
17 |
18 | return (
19 |
20 | {chart}
21 |
22 | )
23 | }
24 |
25 | export default Mermaid
26 |
--------------------------------------------------------------------------------
/src/renderer/src/env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import type KeyvStorage from '@kangfenmao/keyv-storage'
4 | import { MessageInstance } from 'antd/es/message/interface'
5 | import { HookAPI } from 'antd/es/modal/useModal'
6 |
7 | interface ImportMetaEnv {
8 | VITE_RENDERER_INTEGRATED_MODEL: string
9 | }
10 |
11 | interface ImportMeta {
12 | readonly env: ImportMetaEnv
13 | }
14 |
15 | declare global {
16 | interface Window {
17 | root: HTMLElement
18 | message: MessageInstance
19 | modal: HookAPI
20 | keyv: KeyvStorage
21 | mermaid: any
22 | store: any
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/renderer/src/pages/home/Messages/MessageError.tsx:
--------------------------------------------------------------------------------
1 | import { Message } from '@renderer/types'
2 | import { Alert } from 'antd'
3 | import { t } from 'i18next'
4 | import { FC } from 'react'
5 |
6 | import Markdown from '../Markdown/Markdown'
7 |
8 | const MessageError: FC<{ message: Message }> = ({ message }) => {
9 | return (
10 | <>
11 |
16 |
17 | >
18 | )
19 | }
20 |
21 | export default MessageError
22 |
--------------------------------------------------------------------------------
/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@electron-toolkit/tsconfig/tsconfig.node.json",
3 | "include": [
4 | "electron.vite.config.*",
5 | "src/main/**/*",
6 | "src/preload/**/*",
7 | "src/main/env.d.ts",
8 | "src/renderer/src/types/index.ts",
9 | "packages/shared/**/*"
10 | ],
11 | "compilerOptions": {
12 | "composite": true,
13 | "types": [
14 | "electron-vite/node"
15 | ],
16 | "baseUrl": ".",
17 | "paths": {
18 | "@main/*": ["src/main/*"],
19 | "@types": ["src/renderer/src/types/index.ts"],
20 | "@shared/*": ["packages/shared/*"]
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/utils/index.ts:
--------------------------------------------------------------------------------
1 | import fs from 'node:fs'
2 | import path from 'node:path'
3 |
4 | import { app } from 'electron'
5 |
6 | export function getResourcePath() {
7 | return path.join(app.getAppPath(), 'resources')
8 | }
9 |
10 | export function getDataPath() {
11 | const dataPath = path.join(app.getPath('userData'), 'Data')
12 | if (!fs.existsSync(dataPath)) {
13 | fs.mkdirSync(dataPath, { recursive: true })
14 | }
15 | return dataPath
16 | }
17 |
18 | export function getInstanceName(baseURL: string) {
19 | try {
20 | return new URL(baseURL).host.split('.')[0]
21 | } catch (error) {
22 | return ''
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/packages/artifacts/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@cherry-studio/artifacts",
3 | "version": "0.1.0",
4 | "description": "Cherry Studio Artifacts",
5 | "main": "index.js",
6 | "homepage": "https://github.com/kangfenmao/cherry-studio/blob/main/npm/artifacts",
7 | "publishConfig": {
8 | "access": "public",
9 | "registry": "https://registry.npmjs.org/"
10 | },
11 | "scripts": {
12 | "test": "echo \"Error: no test specified\" && exit 1"
13 | },
14 | "keywords": [
15 | "artifacts"
16 | ],
17 | "author": "kangfenmao",
18 | "license": "ISC",
19 | "dependencies": {
20 | "@cherry-studio/artifacts": "file:"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log*
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 | .yarn-cache
10 |
11 | # Editor directories and files
12 | .vscode/*
13 | !.vscode/extensions.json
14 | .idea
15 | .DS_Store
16 | *.suo
17 | *.ntvs*
18 | *.njsproj
19 | *.sln
20 | *.sw?
21 |
22 | # Yarn
23 | .pnp.*
24 | .yarn/*
25 | !.yarn/patches
26 | !.yarn/plugins
27 | !.yarn/releases
28 | !.yarn/sdks
29 | !.yarn/versions
30 |
31 | # Windows
32 | Thumbs.db
33 |
34 | # Project
35 | node_modules
36 | dist
37 | out
38 | build/icons
39 | stats.html
40 |
41 | # ENV
42 | .env
43 | .env.*
44 |
45 | # Local
46 | local
47 |
--------------------------------------------------------------------------------
/src/renderer/src/pages/home/Inputbar/SendMessageButton.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 |
3 | interface Props {
4 | disabled: boolean
5 | sendMessage: () => void
6 | }
7 |
8 | const SendMessageButton: FC = ({ disabled, sendMessage }) => {
9 | return (
10 |
21 | )
22 | }
23 |
24 | export default SendMessageButton
25 |
--------------------------------------------------------------------------------
/src/renderer/src/pages/home/Messages/NarrowLayout.tsx:
--------------------------------------------------------------------------------
1 | import { useSettings } from '@renderer/hooks/useSettings'
2 | import { FC, HTMLAttributes } from 'react'
3 | import styled from 'styled-components'
4 |
5 | interface Props extends HTMLAttributes {
6 | children: React.ReactNode
7 | }
8 |
9 | const NarrowLayout: FC = ({ children, ...props }) => {
10 | const { narrowMode } = useSettings()
11 |
12 | if (narrowMode) {
13 | return {children}
14 | }
15 |
16 | return children
17 | }
18 |
19 | const Container = styled.div`
20 | max-width: 800px;
21 | width: 100%;
22 | margin: 0 auto;
23 | `
24 |
25 | export default NarrowLayout
26 |
--------------------------------------------------------------------------------
/src/renderer/src/hooks/useOllama.ts:
--------------------------------------------------------------------------------
1 | import store, { useAppSelector } from '@renderer/store'
2 | import { setOllamaKeepAliveTime } from '@renderer/store/llm'
3 | import { useDispatch } from 'react-redux'
4 |
5 | export function useOllamaSettings() {
6 | const settings = useAppSelector((state) => state.llm.settings.ollama)
7 | const dispatch = useDispatch()
8 |
9 | return { ...settings, setKeepAliveTime: (time: number) => dispatch(setOllamaKeepAliveTime(time)) }
10 | }
11 |
12 | export function getOllamaSettings() {
13 | return store.getState().llm.settings.ollama
14 | }
15 |
16 | export function getOllamaKeepAliveTime() {
17 | return store.getState().llm.settings.ollama.keepAliveTime + 'm'
18 | }
19 |
--------------------------------------------------------------------------------
/src/renderer/src/hooks/useScrollPosition.ts:
--------------------------------------------------------------------------------
1 | import { throttle } from 'lodash'
2 | import { useEffect, useRef } from 'react'
3 |
4 | export default function useScrollPosition(key: string) {
5 | const containerRef = useRef(null)
6 | const scrollKey = `scroll:${key}`
7 |
8 | const handleScroll = throttle(() => {
9 | const position = containerRef.current?.scrollTop ?? 0
10 | window.keyv.set(scrollKey, position)
11 | }, 100)
12 |
13 | useEffect(() => {
14 | const scroll = () => containerRef.current?.scrollTo({ top: window.keyv.get(scrollKey) || 0 })
15 | scroll()
16 | setTimeout(scroll, 50)
17 | }, [scrollKey])
18 |
19 | return { containerRef, handleScroll }
20 | }
21 |
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/paintings/image-size-1-2.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/paintings/image-size-3-2.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/paintings/image-size-3-4.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/paintings/image-size-16-9.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/paintings/image-size-9-16.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/paintings/image-size-1-1.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/renderer/src/i18n/index.ts:
--------------------------------------------------------------------------------
1 | import i18n from 'i18next'
2 | import { initReactI18next } from 'react-i18next'
3 |
4 | import enUS from './locales/en-us.json'
5 | import jaJP from './locales/ja-jp.json'
6 | import ruRU from './locales/ru-ru.json'
7 | import zhCN from './locales/zh-cn.json'
8 | import zhTW from './locales/zh-tw.json'
9 |
10 | const resources = {
11 | 'en-US': enUS,
12 | 'zh-CN': zhCN,
13 | 'zh-TW': zhTW,
14 | 'ja-JP': jaJP,
15 | 'ru-RU': ruRU
16 | }
17 |
18 | i18n.use(initReactI18next).init({
19 | resources,
20 | lng: localStorage.getItem('language') || navigator.language || 'en-US',
21 | fallbackLng: 'en-US',
22 | interpolation: {
23 | escapeValue: false
24 | }
25 | })
26 |
27 | export default i18n
28 |
--------------------------------------------------------------------------------
/src/renderer/src/components/Avatar/ModelAvatar.tsx:
--------------------------------------------------------------------------------
1 | import { getModelLogo } from '@renderer/config/models'
2 | import { Model } from '@renderer/types'
3 | import { Avatar, AvatarProps } from 'antd'
4 | import { first } from 'lodash'
5 | import { FC } from 'react'
6 |
7 | interface Props {
8 | model: Model
9 | size: number
10 | props?: AvatarProps
11 | }
12 |
13 | const ModelAvatar: FC = ({ model, size, props }) => {
14 | return (
15 |
19 | {first(model?.name)}
20 |
21 | )
22 | }
23 |
24 | export default ModelAvatar
25 |
--------------------------------------------------------------------------------
/.yarn/patches/@llm-tools-embedjs-npm-0.1.25-ec5645cf36.patch:
--------------------------------------------------------------------------------
1 | diff --git a/src/core/rag-embedding.js b/src/core/rag-embedding.js
2 | index 50c3c4064af17bc4c7c46554d8f2419b3afceb0e..632c9b2e04d2e0e3bb09ef1cd8f29d2560e6afc1 100644
3 | --- a/src/core/rag-embedding.js
4 | +++ b/src/core/rag-embedding.js
5 | @@ -1,10 +1,8 @@
6 | export class RAGEmbedding {
7 | static singleton;
8 | static async init(embeddingModel) {
9 | - if (!this.singleton) {
10 | - await embeddingModel.init();
11 | - this.singleton = new RAGEmbedding(embeddingModel);
12 | - }
13 | + await embeddingModel.init();
14 | + this.singleton = new RAGEmbedding(embeddingModel);
15 | }
16 | static getInstance() {
17 | return RAGEmbedding.singleton;
18 |
--------------------------------------------------------------------------------
/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: ['unused-imports', 'simple-import-sort'],
3 | extends: [
4 | 'eslint:recommended',
5 | 'plugin:react/recommended',
6 | 'plugin:react/jsx-runtime',
7 | 'plugin:react-hooks/recommended',
8 | '@electron-toolkit/eslint-config-ts/recommended',
9 | '@electron-toolkit/eslint-config-prettier'
10 | ],
11 | rules: {
12 | '@typescript-eslint/explicit-function-return-type': 'off',
13 | '@typescript-eslint/no-explicit-any': 'off',
14 | 'unused-imports/no-unused-imports': 'error',
15 | '@typescript-eslint/no-non-null-asserted-optional-chain': 'off',
16 | 'react/prop-types': 'off',
17 | 'simple-import-sort/imports': 'error',
18 | 'simple-import-sort/exports': 'error',
19 | 'react/no-is-mounted': 'off'
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/renderer/src/providers/ProviderFactory.ts:
--------------------------------------------------------------------------------
1 | import { Provider } from '@renderer/types'
2 |
3 | import AnthropicProvider from './AnthropicProvider'
4 | import BaseProvider from './BaseProvider'
5 | import GeminiProvider from './GeminiProvider'
6 | import OpenAIProvider from './OpenAIProvider'
7 |
8 | export default class ProviderFactory {
9 | static create(provider: Provider): BaseProvider {
10 | switch (provider.type) {
11 | case 'anthropic':
12 | return new AnthropicProvider(provider)
13 | case 'gemini':
14 | return new GeminiProvider(provider)
15 | default:
16 | return new OpenAIProvider(provider)
17 | }
18 | }
19 | }
20 |
21 | export function isOpenAIProvider(provider: Provider) {
22 | return !['anthropic', 'gemini'].includes(provider.type)
23 | }
24 |
--------------------------------------------------------------------------------
/src/renderer/src/config/tools.ts:
--------------------------------------------------------------------------------
1 | import { Model } from '@renderer/types'
2 | import { ChatCompletionTool } from 'openai/resources'
3 |
4 | import { isWebSearchModel } from './models'
5 |
6 | export function getWebSearchTools(model: Model): ChatCompletionTool[] {
7 | if (model && model.provider === 'zhipu') {
8 | if (isWebSearchModel(model)) {
9 | if (model.id === 'glm-4-alltools') {
10 | return [
11 | {
12 | type: 'web_browser'
13 | } as unknown as ChatCompletionTool
14 | ]
15 | }
16 | return [
17 | {
18 | type: 'web_search',
19 | web_search: {
20 | enable: true,
21 | search_result: true
22 | }
23 | } as unknown as ChatCompletionTool
24 | ]
25 | }
26 | }
27 |
28 | return []
29 | }
30 |
--------------------------------------------------------------------------------
/src/renderer/src/components/EmojiPicker/index.tsx:
--------------------------------------------------------------------------------
1 | import { useTheme } from '@renderer/context/ThemeProvider'
2 | import { FC, useEffect, useRef } from 'react'
3 |
4 | interface Props {
5 | onEmojiClick: (emoji: string) => void
6 | }
7 |
8 | const EmojiPicker: FC = ({ onEmojiClick }) => {
9 | const { theme } = useTheme()
10 | const ref = useRef(null)
11 |
12 | useEffect(() => {
13 | if (ref.current) {
14 | ref.current.addEventListener('emoji-click', (event: any) => {
15 | event.stopPropagation()
16 | onEmojiClick(event.detail.emoji.unicode)
17 | })
18 | }
19 | }, [onEmojiClick])
20 |
21 | // @ts-ignore next-line
22 | return
23 | }
24 |
25 | export default EmojiPicker
26 |
--------------------------------------------------------------------------------
/scripts/notarize.js:
--------------------------------------------------------------------------------
1 | require('dotenv').config()
2 | const { notarize } = require('@electron/notarize')
3 |
4 | exports.default = async function notarizing(context) {
5 | if (context.electronPlatformName !== 'darwin') {
6 | return
7 | }
8 |
9 | if (!process.env.APPLE_ID || !process.env.APPLE_APP_SPECIFIC_PASSWORD || !process.env.APPLE_TEAM_ID) {
10 | console.log('Skipping notarization')
11 | return
12 | }
13 |
14 | const appName = context.packager.appInfo.productFilename
15 | const appPath = `${context.appOutDir}/${appName}.app`
16 |
17 | await notarize({
18 | appPath,
19 | appBundleId: 'com.kangfenmao.CherryStudio',
20 | appleId: process.env.APPLE_ID,
21 | appleIdPassword: process.env.APPLE_APP_SPECIFIC_PASSWORD,
22 | teamId: process.env.APPLE_TEAM_ID
23 | })
24 |
25 | console.log(' • Notarized app:', appPath)
26 | }
27 |
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/apps/huggingchat.svg:
--------------------------------------------------------------------------------
1 |
15 |
--------------------------------------------------------------------------------
/src/renderer/src/services/ImageStorage.ts:
--------------------------------------------------------------------------------
1 | import db from '@renderer/databases'
2 | import { convertToBase64 } from '@renderer/utils'
3 |
4 | const IMAGE_PREFIX = 'image://'
5 |
6 | export default class ImageStorage {
7 | static async set(key: string, file: File) {
8 | const id = IMAGE_PREFIX + key
9 | try {
10 | const base64Image = await convertToBase64(file)
11 | if (typeof base64Image === 'string') {
12 | if (await db.settings.get(id)) {
13 | db.settings.update(id, { value: base64Image })
14 | return
15 | }
16 | await db.settings.add({ id, value: base64Image })
17 | }
18 | } catch (error) {
19 | console.error('Error storing the image', error)
20 | }
21 | }
22 |
23 | static async get(key: string): Promise {
24 | const id = IMAGE_PREFIX + key
25 | return (await db.settings.get(id))?.value
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/renderer/src/components/IndicatorLight.tsx:
--------------------------------------------------------------------------------
1 | // src/renderer/src/components/IndicatorLight.tsx
2 | import React from 'react'
3 | import styled from 'styled-components'
4 |
5 | interface IndicatorLightProps {
6 | color: string
7 | }
8 |
9 | const Light = styled.div<{ color: string }>`
10 | width: 8px;
11 | height: 8px;
12 | border-radius: 50%;
13 | background-color: ${({ color }) => color};
14 | box-shadow: 0 0 6px ${({ color }) => color};
15 | animation: pulse 2s infinite;
16 |
17 | @keyframes pulse {
18 | 0% {
19 | opacity: 1;
20 | }
21 | 50% {
22 | opacity: 0.6;
23 | }
24 | 100% {
25 | opacity: 1;
26 | }
27 | }
28 | `
29 |
30 | const IndicatorLight: React.FC = ({ color }) => {
31 | const actualColor = color === 'green' ? '#22c55e' : color
32 | return
33 | }
34 |
35 | export default IndicatorLight
36 |
--------------------------------------------------------------------------------
/src/main/utils/aes.ts:
--------------------------------------------------------------------------------
1 | import * as crypto from 'crypto'
2 |
3 | // 定义密钥和初始化向量(IV)
4 | const secretKey = 'kDQvWz5slot3syfucoo53X6KKsEUJoeFikpiUWRJTLIo3zcUPpFvEa009kK13KCr'
5 | const iv = Buffer.from('Cherry Studio', 'hex')
6 |
7 | // 加密函数
8 | export function encrypt(text: string): { iv: string; encryptedData: string } {
9 | const cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(secretKey), iv)
10 | let encrypted = cipher.update(text, 'utf8', 'hex')
11 | encrypted += cipher.final('hex')
12 | return {
13 | iv: iv.toString('hex'),
14 | encryptedData: encrypted
15 | }
16 | }
17 |
18 | // 解密函数
19 | export function decrypt(encryptedData: string, iv: string): string {
20 | const decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(secretKey), Buffer.from(iv, 'hex'))
21 | let decrypted = decipher.update(encryptedData, 'hex', 'utf8')
22 | decrypted += decipher.final('utf8')
23 | return decrypted
24 | }
25 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/#1_feature_request.yml:
--------------------------------------------------------------------------------
1 | name: 💡 功能建议
2 | description: 为项目提出新的想法
3 | title: '[功能]: '
4 | labels: ['enhancement']
5 | body:
6 | - type: markdown
7 | attributes:
8 | value: |
9 | 感谢您花时间提出新的功能建议!
10 |
11 | - type: textarea
12 | id: problem
13 | attributes:
14 | label: 您的功能建议是否与某个问题相关?
15 | description: 请简明扼要地描述您遇到的问题
16 | placeholder: 我总是感到沮丧,因为...
17 | validations:
18 | required: true
19 |
20 | - type: textarea
21 | id: solution
22 | attributes:
23 | label: 请描述您希望实现的解决方案
24 | description: 请简明扼要地描述您希望发生的情况
25 | validations:
26 | required: true
27 |
28 | - type: textarea
29 | id: alternatives
30 | attributes:
31 | label: 请描述您考虑过的其他方案
32 | description: 请简明扼要地描述您考虑过的任何其他解决方案或功能
33 |
34 | - type: textarea
35 | id: additional
36 | attributes:
37 | label: 其他补充信息
38 | description: 在此添加任何其他与功能建议相关的上下文或截图
39 |
--------------------------------------------------------------------------------
/src/renderer/src/hooks/useStore.ts:
--------------------------------------------------------------------------------
1 | import { useAppDispatch, useAppSelector } from '@renderer/store'
2 | import { setShowAssistants, setShowTopics, toggleShowAssistants, toggleShowTopics } from '@renderer/store/settings'
3 |
4 | export function useShowAssistants() {
5 | const showAssistants = useAppSelector((state) => state.settings.showAssistants)
6 | const dispatch = useAppDispatch()
7 |
8 | return {
9 | showAssistants,
10 | setShowAssistants: (show: boolean) => dispatch(setShowAssistants(show)),
11 | toggleShowAssistants: () => dispatch(toggleShowAssistants())
12 | }
13 | }
14 |
15 | export function useShowTopics() {
16 | const showTopics = useAppSelector((state) => state.settings.showTopics)
17 | const dispatch = useAppDispatch()
18 |
19 | return {
20 | showTopics,
21 | setShowTopics: (show: boolean) => dispatch(setShowTopics(show)),
22 | toggleShowTopics: () => dispatch(toggleShowTopics())
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/renderer/src/services/EventService.ts:
--------------------------------------------------------------------------------
1 | import Emittery from 'emittery'
2 |
3 | export const EventEmitter = new Emittery()
4 |
5 | export const EVENT_NAMES = {
6 | SEND_MESSAGE: 'SEND_MESSAGE',
7 | RECEIVE_MESSAGE: 'RECEIVE_MESSAGE',
8 | AI_AUTO_RENAME: 'AI_AUTO_RENAME',
9 | CLEAR_MESSAGES: 'CLEAR_MESSAGES',
10 | ADD_ASSISTANT: 'ADD_ASSISTANT',
11 | EDIT_MESSAGE: 'EDIT_MESSAGE',
12 | REGENERATE_MESSAGE: 'REGENERATE_MESSAGE',
13 | CHAT_COMPLETION_PAUSED: 'CHAT_COMPLETION_PAUSED',
14 | ESTIMATED_TOKEN_COUNT: 'ESTIMATED_TOKEN_COUNT',
15 | SHOW_ASSISTANTS: 'SHOW_ASSISTANTS',
16 | SHOW_CHAT_SETTINGS: 'SHOW_CHAT_SETTINGS',
17 | SHOW_TOPIC_SIDEBAR: 'SHOW_TOPIC_SIDEBAR',
18 | SWITCH_TOPIC_SIDEBAR: 'SWITCH_TOPIC_SIDEBAR',
19 | NEW_CONTEXT: 'NEW_CONTEXT',
20 | NEW_BRANCH: 'NEW_BRANCH',
21 | EXPORT_TOPIC_IMAGE: 'EXPORT_TOPIC_IMAGE',
22 | LOCATE_MESSAGE: 'LOCATE_MESSAGE',
23 | ADD_NEW_TOPIC: 'ADD_NEW_TOPIC',
24 | RESEND_MESSAGE: 'RESEND_MESSAGE'
25 | }
26 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.formatOnSave": true,
3 | "editor.codeActionsOnSave": {
4 | "source.fixAll.eslint": "explicit"
5 | },
6 | "search.exclude": {
7 | "**/dist/**": true
8 | },
9 | "[javascript]": {
10 | "editor.defaultFormatter": "esbenp.prettier-vscode"
11 | },
12 | "[typescript]": {
13 | "editor.defaultFormatter": "esbenp.prettier-vscode"
14 | },
15 | "[typescriptreact]": {
16 | "editor.defaultFormatter": "esbenp.prettier-vscode"
17 | },
18 | "[json]": {
19 | "editor.defaultFormatter": "esbenp.prettier-vscode"
20 | },
21 | "[jsonc]": {
22 | "editor.defaultFormatter": "esbenp.prettier-vscode"
23 | },
24 | "[css]": {
25 | "editor.defaultFormatter": "esbenp.prettier-vscode"
26 | },
27 | "[scss]": {
28 | "editor.defaultFormatter": "esbenp.prettier-vscode"
29 | },
30 | "[markdown]": {
31 | "files.trimTrailingWhitespace": false
32 | },
33 | "i18n-ally.localesPaths": ["src/renderer/src/i18n"]
34 | }
35 |
--------------------------------------------------------------------------------
/packages/database/src/email.js:
--------------------------------------------------------------------------------
1 | const sqlite3 = require('sqlite3').verbose()
2 |
3 | // 连接到数据库
4 | const db = new sqlite3.Database('./data/CherryStudio.sqlite3', (err) => {
5 | if (err) {
6 | console.error('Error connecting to the database:', err.message)
7 | return
8 | }
9 | })
10 |
11 | // 查询数据并转换为JSON
12 | db.all('SELECT * FROM emails WHERE sent = 0', [], (err, rows) => {
13 | if (err) {
14 | console.error('Error querying the database:', err.message)
15 | return
16 | }
17 |
18 | for (const row of rows) {
19 | console.log(row.email)
20 | // Update row set sent = 1
21 | db.run('UPDATE emails SET sent = 1 WHERE id = ?', [row.id], (err) => {
22 | if (err) {
23 | console.error('Error updating the database:', err.message)
24 | return
25 | }
26 | })
27 | }
28 |
29 | // 关闭数据库连接
30 | db.close((err) => {
31 | if (err) {
32 | console.error('Error closing the database:', err.message)
33 | return
34 | }
35 | })
36 | })
37 |
--------------------------------------------------------------------------------
/resources/js/bridge.js:
--------------------------------------------------------------------------------
1 | ;(() => {
2 | let messageId = 0
3 | const pendingCalls = new Map()
4 |
5 | function api(method, ...args) {
6 | const id = messageId++
7 | return new Promise((resolve, reject) => {
8 | pendingCalls.set(id, { resolve, reject })
9 | window.parent.postMessage({ id, type: 'api-call', method, args }, '*')
10 | })
11 | }
12 |
13 | window.addEventListener('message', (event) => {
14 | if (event.data.type === 'api-response') {
15 | const { id, result, error } = event.data
16 | const pendingCall = pendingCalls.get(id)
17 | if (pendingCall) {
18 | if (error) {
19 | pendingCall.reject(new Error(error))
20 | } else {
21 | pendingCall.resolve(result)
22 | }
23 | pendingCalls.delete(id)
24 | }
25 | }
26 | })
27 |
28 | window.api = new Proxy(
29 | {},
30 | {
31 | get: (target, prop) => {
32 | return (...args) => api(prop, ...args)
33 | }
34 | }
35 | )
36 | })()
37 |
--------------------------------------------------------------------------------
/api/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cherry-studio-api",
3 | "version": "1.0.0",
4 | "type": "module",
5 | "description": "API service for Cherry Studio knowledge base",
6 | "main": "dist/index.js",
7 | "scripts": {
8 | "dev": "tsx watch src/index.ts",
9 | "build": "tsc",
10 | "start": "node dist/index.js"
11 | },
12 | "dependencies": {
13 | "@llm-tools/embedjs": "^0.1.25",
14 | "@llm-tools/embedjs-libsql": "^0.1.25",
15 | "@llm-tools/embedjs-openai": "^0.1.25",
16 | "cherry-studio-api": "file:",
17 | "cors": "^2.8.5",
18 | "dotenv": "^16.0.3",
19 | "express": "^4.18.2",
20 | "express-rate-limit": "^6.7.0",
21 | "fs-extra": "^11.2.0",
22 | "helmet": "^6.0.1",
23 | "level": "^9.0.0",
24 | "openai": "^4.28.0",
25 | "sqlite3": "^5.1.7"
26 | },
27 | "devDependencies": {
28 | "@types/cors": "^2.8.13",
29 | "@types/express": "^4.17.17",
30 | "@types/fs-extra": "^11.0.4",
31 | "@types/node": "^18.15.11",
32 | "tsx": "^4.7.1",
33 | "typescript": "^5.0.4"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/#2_question.yml:
--------------------------------------------------------------------------------
1 | name: ❓ 提问
2 | description: 提出一个问题或寻求帮助
3 | title: '[问题]: '
4 | labels: ['question']
5 | body:
6 | - type: markdown
7 | attributes:
8 | value: |
9 | 感谢您的提问!请尽可能详细地描述您的问题,这样我们才能更好地帮助您。
10 |
11 | - type: textarea
12 | id: question
13 | attributes:
14 | label: 您的问题
15 | description: 请详细描述您的问题
16 | placeholder: 请尽可能清楚地说明您的问题...
17 | validations:
18 | required: true
19 |
20 | - type: textarea
21 | id: context
22 | attributes:
23 | label: 相关背景
24 | description: 请提供一些背景信息,帮助我们更好地理解您的问题
25 | placeholder: 例如:使用场景、已尝试的解决方案等
26 |
27 | - type: textarea
28 | id: additional
29 | attributes:
30 | label: 补充信息
31 | description: 任何其他相关的信息、截图或代码示例
32 | render: shell
33 |
34 | - type: dropdown
35 | id: priority
36 | attributes:
37 | label: 优先级
38 | description: 这个问题对您来说有多紧急?
39 | options:
40 | - 低 (有空再看)
41 | - 中 (希望尽快得到答复)
42 | - 高 (阻碍工作进行)
43 | validations:
44 | required: true
45 |
--------------------------------------------------------------------------------
/.yarn/patches/openai-npm-4.76.2-8ff1374617.patch:
--------------------------------------------------------------------------------
1 | diff --git a/core.js b/core.js
2 | index 30c91e66bf595a66c09eb3dbcbda7d58154865f5..b511ff24ea1891904c60174c6ed26ecdd4d5ac51 100644
3 | --- a/core.js
4 | +++ b/core.js
5 | @@ -156,7 +156,7 @@ class APIClient {
6 | Accept: 'application/json',
7 | 'Content-Type': 'application/json',
8 | 'User-Agent': this.getUserAgent(),
9 | - ...getPlatformHeaders(),
10 | + // ...getPlatformHeaders(),
11 | ...this.authHeaders(opts),
12 | };
13 | }
14 | diff --git a/core.mjs b/core.mjs
15 | index ac267bcfcff44b1f7c9bea5513bba94726a31795..dd5bd9f29609d3f0eea4bd5b225f302893df14ad 100644
16 | --- a/core.mjs
17 | +++ b/core.mjs
18 | @@ -149,7 +149,7 @@ export class APIClient {
19 | Accept: 'application/json',
20 | 'Content-Type': 'application/json',
21 | 'User-Agent': this.getUserAgent(),
22 | - ...getPlatformHeaders(),
23 | + // ...getPlatformHeaders(),
24 | ...this.authHeaders(opts),
25 | };
26 | }
27 |
--------------------------------------------------------------------------------
/src/renderer/src/databases/index.ts:
--------------------------------------------------------------------------------
1 | import { FileType, KnowledgeItem, Topic } from '@renderer/types'
2 | import { Dexie, type EntityTable } from 'dexie'
3 |
4 | // Database declaration (move this to its own module also)
5 | export const db = new Dexie('CherryStudio') as Dexie & {
6 | files: EntityTable
7 | topics: EntityTable, 'id'>
8 | settings: EntityTable<{ id: string; value: any }, 'id'>
9 | knowledge_notes: EntityTable
10 | }
11 |
12 | db.version(1).stores({
13 | files: 'id, name, origin_name, path, size, ext, type, created_at, count'
14 | })
15 |
16 | db.version(2).stores({
17 | files: 'id, name, origin_name, path, size, ext, type, created_at, count',
18 | topics: '&id, messages',
19 | settings: '&id, value'
20 | })
21 |
22 | db.version(3).stores({
23 | files: 'id, name, origin_name, path, size, ext, type, created_at, count',
24 | topics: '&id, messages',
25 | settings: '&id, value',
26 | knowledge_notes: '&id, baseId, type, content, created_at, updated_at'
27 | })
28 |
29 | export default db
30 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "name": "Debug Main Process",
6 | "type": "node",
7 | "request": "launch",
8 | "cwd": "${workspaceRoot}",
9 | "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron-vite",
10 | "windows": {
11 | "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron-vite.cmd"
12 | },
13 | "runtimeArgs": ["--sourcemap"],
14 | "env": {
15 | "REMOTE_DEBUGGING_PORT": "9222"
16 | }
17 | },
18 | {
19 | "name": "Debug Renderer Process",
20 | "port": 9222,
21 | "request": "attach",
22 | "type": "chrome",
23 | "webRoot": "${workspaceFolder}/src/renderer",
24 | "timeout": 60000,
25 | "presentation": {
26 | "hidden": true
27 | }
28 | }
29 | ],
30 | "compounds": [
31 | {
32 | "name": "Debug All",
33 | "configurations": ["Debug Main Process", "Debug Renderer Process"],
34 | "presentation": {
35 | "order": 1
36 | }
37 | }
38 | ]
39 | }
40 |
--------------------------------------------------------------------------------
/api/src/config/index.ts:
--------------------------------------------------------------------------------
1 | import os from 'node:os'
2 | import path from 'node:path'
3 |
4 | import dotenv from 'dotenv'
5 |
6 | dotenv.config()
7 |
8 | // 获取用户数据目录
9 | const getUserDataPath = () => {
10 | const platform = process.platform
11 |
12 | switch (platform) {
13 | case 'win32':
14 | // Windows: C:\Users\\AppData\Roaming\CherryStudio
15 | return path.join(process.env.APPDATA || '', 'CherryStudio')
16 | case 'darwin':
17 | // macOS: ~/Library/Application Support/CherryStudio
18 | return path.join(os.homedir(), 'Library/Application Support/CherryStudio')
19 | default:
20 | // Linux: ~/.config/CherryStudio
21 | return path.join(os.homedir(), '.config/CherryStudio')
22 | }
23 | }
24 |
25 | const config = {
26 | port: process.env.PORT || 3000,
27 | userDataPath: process.env.DATA_PATH || getUserDataPath(),
28 | openaiApiKey: process.env.OPENAI_API_KEY,
29 | openaiBaseUrl: process.env.OPENAI_BASE_URL || 'https://api.openai.com/v1',
30 | rateLimit: {
31 | windowMs: 15 * 60 * 1000,
32 | max: 100
33 | }
34 | }
35 |
36 | export default config
37 |
--------------------------------------------------------------------------------
/src/renderer/src/utils/export.ts:
--------------------------------------------------------------------------------
1 | import db from '@renderer/databases'
2 | import { Message, Topic } from '@renderer/types'
3 |
4 | export const messageToMarkdown = (message: Message) => {
5 | const roleText = message.role === 'user' ? '🧑💻 User' : '🤖 Assistant'
6 | const titleSection = `### ${roleText}`
7 | const contentSection = message.content
8 |
9 | return [titleSection, '', contentSection].join('\n')
10 | }
11 |
12 | export const messagesToMarkdown = (messages: Message[]) => {
13 | return messages.map((message) => messageToMarkdown(message)).join('\n\n---\n\n')
14 | }
15 |
16 | export const topicToMarkdown = async (topic: Topic) => {
17 | const topicName = `# ${topic.name}`
18 | const topicMessages = await db.topics.get(topic.id)
19 |
20 | if (topicMessages) {
21 | return topicName + '\n\n' + messagesToMarkdown(topicMessages.messages)
22 | }
23 |
24 | return ''
25 | }
26 |
27 | export const exportTopicAsMarkdown = async (topic: Topic) => {
28 | const fileName = topic.name + '.md'
29 | const markdown = await topicToMarkdown(topic)
30 | window.api.file.save(fileName, markdown)
31 | }
32 |
--------------------------------------------------------------------------------
/src/renderer/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
9 |
10 |
31 |
32 |
33 |
34 |
35 |
36 |

37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/src/renderer/src/assets/styles/scrollbar.scss:
--------------------------------------------------------------------------------
1 | :root {
2 | --color-scrollbar-thumb: rgba(255, 255, 255, 0.15);
3 | --color-scrollbar-thumb-hover: rgba(255, 255, 255, 0.2);
4 | --color-scrollbar-thumb-right: rgba(255, 255, 255, 0.18);
5 | --color-scrollbar-thumb-right-hover: rgba(255, 255, 255, 0.25);
6 | }
7 |
8 | body[theme-mode='light'] {
9 | --color-scrollbar-thumb: rgba(0, 0, 0, 0.15);
10 | --color-scrollbar-thumb-hover: rgba(0, 0, 0, 0.2);
11 | --color-scrollbar-thumb-right: rgba(0, 0, 0, 0.18);
12 | --color-scrollbar-thumb-right-hover: rgba(0, 0, 0, 0.25);
13 | }
14 |
15 | /* 全局初始化滚动条样式 */
16 | ::-webkit-scrollbar {
17 | width: 6px;
18 | height: 6px;
19 | }
20 |
21 | ::-webkit-scrollbar-track {
22 | background: transparent;
23 | }
24 |
25 | ::-webkit-scrollbar-thumb {
26 | border-radius: 10px;
27 | background: var(--color-scrollbar-thumb);
28 | &:hover {
29 | background: var(--color-scrollbar-thumb-hover);
30 | }
31 | }
32 |
33 | pre::-webkit-scrollbar-thumb {
34 | border-radius: 0;
35 | background: rgba(0, 0, 0, 0.08);
36 | &:hover {
37 | background: rgba(0, 0, 0, 0.15);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/renderer/src/pages/home/Messages/MessageErrorBoundary.tsx:
--------------------------------------------------------------------------------
1 | import { Alert } from 'antd'
2 | import React from 'react'
3 | import { useTranslation } from 'react-i18next'
4 |
5 | interface Props {
6 | fallback?: React.ReactNode
7 | children: React.ReactNode
8 | }
9 |
10 | interface State {
11 | hasError: boolean
12 | }
13 |
14 | const ErrorFallback = ({ fallback }: { fallback?: React.ReactNode }) => {
15 | const { t } = useTranslation()
16 | return (
17 | fallback || (
18 |
19 | )
20 | )
21 | }
22 |
23 | class MessageErrorBoundary extends React.Component {
24 | constructor(props: Props) {
25 | super(props)
26 | this.state = { hasError: false }
27 | }
28 |
29 | static getDerivedStateFromError() {
30 | return { hasError: true }
31 | }
32 |
33 | render() {
34 | if (this.state.hasError) {
35 | return
36 | }
37 | return this.props.children
38 | }
39 | }
40 |
41 | export default MessageErrorBoundary
42 |
--------------------------------------------------------------------------------
/src/renderer/src/hooks/useAgents.ts:
--------------------------------------------------------------------------------
1 | import { useAppDispatch, useAppSelector } from '@renderer/store'
2 | import { addAgent, removeAgent, updateAgent, updateAgents, updateAgentSettings } from '@renderer/store/agents'
3 | import { Agent, AssistantSettings } from '@renderer/types'
4 |
5 | export function useAgents() {
6 | const agents = useAppSelector((state) => state.agents.agents)
7 | const dispatch = useAppDispatch()
8 |
9 | return {
10 | agents,
11 | updateAgents: (agents: Agent[]) => dispatch(updateAgents(agents)),
12 | addAgent: (agent: Agent) => dispatch(addAgent(agent)),
13 | removeAgent: (id: string) => dispatch(removeAgent({ id }))
14 | }
15 | }
16 |
17 | export function useAgent(id: string) {
18 | const agent = useAppSelector((state) => state.agents.agents.find((a) => a.id === id) as Agent)
19 | const dispatch = useAppDispatch()
20 |
21 | return {
22 | agent,
23 | updateAgent: (agent: Agent) => dispatch(updateAgent(agent)),
24 | updateAgentSettings: (settings: Partial) => {
25 | dispatch(updateAgentSettings({ assistantId: agent.id, settings }))
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/utils/zip.ts:
--------------------------------------------------------------------------------
1 | import util from 'node:util'
2 | import zlib from 'node:zlib'
3 |
4 | import logger from 'electron-log'
5 |
6 | // 将 zlib 的 gzip 和 gunzip 方法转换为 Promise 版本
7 | const gzipPromise = util.promisify(zlib.gzip)
8 | const gunzipPromise = util.promisify(zlib.gunzip)
9 |
10 | /**
11 | * 压缩字符串
12 | * @param {string} string - 要压缩的 JSON 字符串
13 | * @returns {Promise} 压缩后的 Buffer
14 | */
15 | export async function compress(str) {
16 | try {
17 | const buffer = Buffer.from(str, 'utf-8')
18 | const compressedBuffer = await gzipPromise(buffer)
19 | return compressedBuffer
20 | } catch (error) {
21 | logger.error('Compression failed:', error)
22 | throw error
23 | }
24 | }
25 |
26 | /**
27 | * 解压缩 Buffer 到 JSON 字符串
28 | * @param {Buffer} compressedBuffer - 压缩的 Buffer
29 | * @returns {Promise} 解压缩后的 JSON 字符串
30 | */
31 | export async function decompress(compressedBuffer) {
32 | try {
33 | const buffer = await gunzipPromise(compressedBuffer)
34 | return buffer.toString('utf-8')
35 | } catch (error) {
36 | logger.error('Decompression failed:', error)
37 | throw error
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/renderer/src/assets/styles/ant.scss:
--------------------------------------------------------------------------------
1 | #inputbar {
2 | resize: none;
3 | }
4 |
5 | .ant-image-preview-switch-left {
6 | -webkit-app-region: no-drag;
7 | }
8 |
9 | .ant-btn:not(:disabled):focus-visible {
10 | outline: none;
11 | }
12 |
13 | .ant-segmented-group {
14 | gap: 4px;
15 | }
16 |
17 | .minapp-drawer {
18 | max-width: calc(100vw - var(--sidebar-width));
19 | .ant-drawer-content-wrapper {
20 | box-shadow: none;
21 | }
22 | .ant-drawer-header {
23 | position: absolute;
24 | -webkit-app-region: drag;
25 | min-height: calc(var(--navbar-height) + 0.5px);
26 | width: calc(100vw - var(--sidebar-width));
27 | margin-top: -0.5px;
28 | border-bottom: none;
29 | }
30 | .ant-drawer-body {
31 | padding: 0;
32 | margin-top: var(--navbar-height);
33 | overflow: hidden;
34 | @extend #content-container;
35 | }
36 | .minapp-mask {
37 | background-color: transparent !important;
38 | }
39 | }
40 |
41 | .ant-drawer-header {
42 | -webkit-app-region: no-drag;
43 | }
44 |
45 | .message-attachments {
46 | .ant-upload-list-item:hover {
47 | background-color: initial !important;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/.yarn/patches/@llm-tools-embedjs-loader-markdown-npm-0.1.25-d1d536d640.patch:
--------------------------------------------------------------------------------
1 | diff --git a/src/markdown-loader.js b/src/markdown-loader.js
2 | index 8a17cb7f5a68d90d2be21682db6e95ce22a3e71c..9ee868ef9d4ff3dc914b3abc3c8006deb1e9c6c6 100644
3 | --- a/src/markdown-loader.js
4 | +++ b/src/markdown-loader.js
5 | @@ -1,5 +1,4 @@
6 | import { micromark } from 'micromark';
7 | -import { mdxJsx } from 'micromark-extension-mdx-jsx';
8 | import { gfmHtml, gfm } from 'micromark-extension-gfm';
9 | import createDebugMessages from 'debug';
10 | import fs from 'node:fs';
11 | @@ -21,7 +20,7 @@ export class MarkdownLoader extends BaseLoader {
12 | ? (await getSafe(this.filePathOrUrl, { format: 'buffer' })).body
13 | : await stream2buffer(fs.createReadStream(this.filePathOrUrl));
14 | this.debug('MarkdownLoader stream created');
15 | - const result = micromark(buffer, { extensions: [gfm(), mdxJsx()], htmlExtensions: [gfmHtml()] });
16 | + const result = micromark(buffer, { extensions: [gfm()], htmlExtensions: [gfmHtml()] });
17 | this.debug('Markdown parsed...');
18 | const webLoader = new WebLoader({
19 | urlOrContent: result,
20 |
--------------------------------------------------------------------------------
/src/renderer/src/components/ModelTags.tsx:
--------------------------------------------------------------------------------
1 | import { isEmbeddingModel, isVisionModel, isWebSearchModel } from '@renderer/config/models'
2 | import { Model } from '@renderer/types'
3 | import { isFreeModel } from '@renderer/utils'
4 | import { Tag } from 'antd'
5 | import { FC } from 'react'
6 | import { useTranslation } from 'react-i18next'
7 |
8 | import VisionIcon from './Icons/VisionIcon'
9 | import WebSearchIcon from './Icons/WebSearchIcon'
10 |
11 | interface ModelTagsProps {
12 | model: Model
13 | showFree?: boolean
14 | }
15 |
16 | const ModelTags: FC = ({ model, showFree = true }) => {
17 | const { t } = useTranslation()
18 | return (
19 | <>
20 | {isVisionModel(model) && }
21 | {isWebSearchModel(model) && }
22 | {showFree && isFreeModel(model) && (
23 |
24 | {t('models.free')}
25 |
26 | )}
27 | {isEmbeddingModel(model) && (
28 |
29 | {t('models.embedding')}
30 |
31 | )}
32 | >
33 | )
34 | }
35 |
36 | export default ModelTags
37 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Cherry Studio 贡献者指南
2 |
3 | 欢迎来到 Cherry Studio 的贡献者社区!我们致力于将 Cherry Studio 打造成一个长期提供价值的项目,并希望邀请更多的开发者加入我们的行列。无论您是经验丰富的开发者还是刚刚起步的初学者,您的贡献都将帮助我们更好地服务用户,提升软件质量。
4 |
5 | ## 如何贡献
6 |
7 | 以下是您可以参与的几种方式:
8 |
9 | 1. **贡献代码**:帮助我们开发新功能或优化现有代码。请确保您的代码符合我们的编码标准,并通过所有测试。
10 |
11 | 2. **修复 BUG**:如果您发现了 BUG,欢迎提交修复方案。请在提交前确认问题已被解决,并附上相关测试。
12 |
13 | 3. **维护 Issue**:协助我们管理 GitHub 上的 issue,帮助标记、分类和解决问题。
14 |
15 | 4. **产品设计**:参与产品设计讨论,帮助我们改进用户体验和界面设计。
16 |
17 | 5. **编写文档**:帮助我们完善用户手册、API 文档和开发者指南。
18 |
19 | 6. **社区维护**:参与社区讨论,帮助解答用户问题,促进社区活跃。
20 |
21 | 7. **推广使用**:通过博客、社交媒体等渠道推广 Cherry Studio,吸引更多用户和开发者。
22 |
23 | ## 开始贡献
24 |
25 | 1. **Fork 仓库**:在 GitHub 上 fork 我们的仓库,并将其克隆到本地。
26 |
27 | 2. **创建分支**:为您要进行的更改创建一个新的分支。
28 |
29 | 3. **提交更改**:在本地进行更改并提交。请确保您的提交信息清晰明了。
30 |
31 | 4. **发起 Pull Request**:将您的更改推送到 GitHub,并发起 Pull Request。请描述您的更改内容和原因。
32 |
33 | ### 其他建议
34 |
35 | - **联系开发者**:在提交 PR 之前,您可以先和开发者进行联系,共同探讨或者获取帮助。
36 | - **成为核心开发者**:如果您能够稳定为项目贡献,恭喜您可以成为项目核心开发者,获取到项目成员身份。
37 |
38 | ## 联系我们
39 |
40 | 如果您有任何问题或建议,欢迎通过以下方式联系我们:
41 |
42 | - 微信:kangfenmao
43 | - [GitHub Issues](https://github.com/kangfenmao/cherry-studio/issues)
44 |
45 | 感谢您的支持和贡献!我们期待与您一起将 Cherry Studio 打造成更好的产品。
46 |
--------------------------------------------------------------------------------
/src/renderer/src/pages/agents/components/AddAgentCard.tsx:
--------------------------------------------------------------------------------
1 | import { PlusOutlined } from '@ant-design/icons'
2 | import { useTranslation } from 'react-i18next'
3 | import styled from 'styled-components'
4 |
5 | interface AddAgentCardProps {
6 | onClick: () => void
7 | className?: string
8 | }
9 |
10 | const AddAgentCard = ({ onClick, className }: AddAgentCardProps) => {
11 | const { t } = useTranslation()
12 |
13 | return (
14 |
15 |
16 | {t('agents.add.title')}
17 |
18 | )
19 | }
20 |
21 | const StyledCard = styled.div`
22 | width: 100%;
23 | height: 180px;
24 | display: flex;
25 | flex-direction: column;
26 | align-items: center;
27 | justify-content: center;
28 | background-color: var(--color-background);
29 | border-radius: 15px;
30 | border: 1px dashed var(--color-border);
31 | cursor: pointer;
32 | transition: all 0.3s ease;
33 | color: var(--color-text-soft);
34 |
35 | &:hover {
36 | border-color: var(--color-primary);
37 | color: var(--color-primary);
38 | }
39 | `
40 |
41 | export default AddAgentCard
42 |
--------------------------------------------------------------------------------
/scripts/utils.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const path = require('path')
3 | const os = require('os')
4 |
5 | function downloadNpmPackage(packageName, url) {
6 | const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'npm-download-'))
7 |
8 | const targetDir = path.join('./node_modules/', packageName)
9 | const filename = packageName.replace('/', '-') + '.tgz'
10 |
11 | // Skip if directory already exists
12 | if (fs.existsSync(targetDir)) {
13 | console.log(`${targetDir} already exists, skipping download...`)
14 | return
15 | }
16 |
17 | try {
18 | console.log(`Downloading ${packageName}...`, url)
19 | const { execSync } = require('child_process')
20 | execSync(`curl --fail -o ${filename} ${url}`)
21 |
22 | console.log(`Extracting ${filename}...`)
23 | execSync(`tar -xvf ${filename}`)
24 | execSync(`rm -rf ${filename}`)
25 | execSync(`mv package ${targetDir}`)
26 | } catch (error) {
27 | console.error(`Error processing ${packageName}: ${error.message}`)
28 | if (fs.existsSync(filename)) {
29 | fs.unlinkSync(filename)
30 | }
31 | throw error
32 | }
33 |
34 | fs.rmSync(tempDir, { recursive: true, force: true })
35 | }
36 |
37 | module.exports = {
38 | downloadNpmPackage
39 | }
40 |
--------------------------------------------------------------------------------
/scripts/version.js:
--------------------------------------------------------------------------------
1 | const { execSync } = require('child_process')
2 | const fs = require('fs')
3 |
4 | // 执行命令并返回输出
5 | function exec(command) {
6 | return execSync(command, { encoding: 'utf8' }).trim()
7 | }
8 |
9 | // 获取命令行参数
10 | const args = process.argv.slice(2)
11 | const versionType = args[0] || 'patch'
12 | const shouldPush = args.includes('push')
13 |
14 | // 验证版本类型
15 | if (!['patch', 'minor', 'major'].includes(versionType)) {
16 | console.error('Invalid version type. Use patch, minor, or major.')
17 | process.exit(1)
18 | }
19 |
20 | // 更新版本
21 | exec(`yarn version ${versionType} --immediate`)
22 |
23 | // 读取更新后的 package.json 获取新版本号
24 | const updatedPackageJson = JSON.parse(fs.readFileSync('package.json', 'utf8'))
25 | const newVersion = updatedPackageJson.version
26 |
27 | // Git 操作
28 | exec('git add .')
29 | exec(`git commit -m "chore(version): ${newVersion}"`)
30 | exec(`git tag -a v${newVersion} -m "Version ${newVersion}"`)
31 |
32 | console.log(`Version bumped to ${newVersion}`)
33 |
34 | if (shouldPush) {
35 | console.log('Pushing to remote...')
36 | exec('git push && git push --tags')
37 | console.log('Pushed to remote.')
38 | } else {
39 | console.log('Changes are committed locally. Use "git push && git push --tags" to push to remote.')
40 | }
41 |
--------------------------------------------------------------------------------
/.yarn/patches/@llm-tools-embedjs-libsql-npm-0.1.25-fad000d74c.patch:
--------------------------------------------------------------------------------
1 | diff --git a/src/libsql-db.js b/src/libsql-db.js
2 | index 58c42e4910bd0e53bc497ff9b9702b1f7a961266..250bc97c50a9b790e8798441d904d040f2d2af43 100644
3 | --- a/src/libsql-db.js
4 | +++ b/src/libsql-db.js
5 | @@ -41,9 +41,9 @@ export class LibSqlDb {
6 | }
7 | async similaritySearch(query, k) {
8 | const statement = `SELECT id, pageContent, uniqueLoaderId, source, metadata,
9 | - vector_distance_cos(vector, vector32('[${query.join(',')}]'))
10 | + vector_distance_cos(vector, vector32('[${query.join(',')}]')) as distance
11 | FROM ${this.tableName}
12 | - ORDER BY vector_distance_cos(vector, vector32('[${query.join(',')}]')) ASC
13 | + ORDER BY distance ASC
14 | LIMIT ${k};`;
15 | this.debug(`Executing statement - ${truncateCenterString(statement, 700)}`);
16 | const results = await this.client.execute(statement);
17 | @@ -52,7 +52,7 @@ export class LibSqlDb {
18 | return {
19 | metadata,
20 | pageContent: result.pageContent.toString(),
21 | - score: 1,
22 | + score: 1 - result.distance,
23 | };
24 | });
25 | }
26 |
--------------------------------------------------------------------------------
/src/renderer/src/services/TranslateService.ts:
--------------------------------------------------------------------------------
1 | import i18n from '@renderer/i18n'
2 | import store from '@renderer/store'
3 |
4 | import { fetchTranslate } from './ApiService'
5 | import { getDefaultTopic } from './AssistantService'
6 | import { getDefaultTranslateAssistant } from './AssistantService'
7 | import { getUserMessage } from './MessagesService'
8 |
9 | export const translateText = async (text: string, targetLanguage: string) => {
10 | const translateModel = store.getState().llm.translateModel
11 |
12 | if (!translateModel) {
13 | window.message.error({
14 | content: i18n.t('translate.error.not_configured'),
15 | key: 'translate-message'
16 | })
17 | return Promise.reject(new Error(i18n.t('translate.error.not_configured')))
18 | }
19 |
20 | const assistant = getDefaultTranslateAssistant(targetLanguage, text)
21 | const message = getUserMessage({
22 | assistant,
23 | topic: getDefaultTopic('default'),
24 | type: 'text',
25 | content: text
26 | })
27 |
28 | const translatedText = await fetchTranslate({ message, assistant })
29 |
30 | const trimmedText = translatedText.trim()
31 |
32 | if (!trimmedText) {
33 | return Promise.reject(new Error(i18n.t('translate.error.failed')))
34 | }
35 |
36 | return trimmedText
37 | }
38 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/1_feature_request.yml:
--------------------------------------------------------------------------------
1 | name: 💡 Feature Request
2 | description: Suggest an idea for this project
3 | title: '[Feature]: '
4 | labels: ['enhancement']
5 | body:
6 | - type: markdown
7 | attributes:
8 | value: |
9 | Thanks for taking the time to suggest a new feature!
10 |
11 | - type: textarea
12 | id: problem
13 | attributes:
14 | label: Is your feature request related to a problem?
15 | description: A clear and concise description of what the problem is
16 | placeholder: I'm always frustrated when...
17 | validations:
18 | required: true
19 |
20 | - type: textarea
21 | id: solution
22 | attributes:
23 | label: Describe the solution you'd like
24 | description: A clear and concise description of what you want to happen
25 | validations:
26 | required: true
27 |
28 | - type: textarea
29 | id: alternatives
30 | attributes:
31 | label: Describe alternatives you've considered
32 | description: A clear and concise description of any alternative solutions or features you've considered
33 |
34 | - type: textarea
35 | id: additional
36 | attributes:
37 | label: Additional Context
38 | description: Add any other context or screenshots about the feature request here
39 |
--------------------------------------------------------------------------------
/src/renderer/src/pages/home/Messages/Prompt.tsx:
--------------------------------------------------------------------------------
1 | import AssistantSettingsPopup from '@renderer/pages/settings/AssistantSettings'
2 | import { Assistant } from '@renderer/types'
3 | import { FC } from 'react'
4 | import { useTranslation } from 'react-i18next'
5 | import styled from 'styled-components'
6 |
7 | interface Props {
8 | assistant: Assistant
9 | }
10 |
11 | const Prompt: FC = ({ assistant }) => {
12 | const { t } = useTranslation()
13 |
14 | const prompt = assistant.prompt || t('chat.default.description')
15 |
16 | if (!prompt) {
17 | return null
18 | }
19 |
20 | return (
21 | AssistantSettingsPopup.show({ assistant })}>
22 | {prompt}
23 |
24 | )
25 | }
26 |
27 | const Container = styled.div`
28 | padding: 10px 20px;
29 | background-color: var(--color-background-soft);
30 | margin-bottom: 20px;
31 | margin: 4px 20px 0 20px;
32 | border-radius: 6px;
33 | cursor: pointer;
34 | border: 0.5px solid var(--color-border);
35 | `
36 |
37 | const Text = styled.div`
38 | color: var(--color-text-2);
39 | font-size: 12px;
40 | display: -webkit-box;
41 | -webkit-line-clamp: 2;
42 | -webkit-box-orient: vertical;
43 | overflow: hidden;
44 | `
45 |
46 | export default Prompt
47 |
--------------------------------------------------------------------------------
/src/renderer/src/pages/settings/ProviderSettings/OllamaSettings.tsx:
--------------------------------------------------------------------------------
1 | import { useOllamaSettings } from '@renderer/hooks/useOllama'
2 | import { InputNumber } from 'antd'
3 | import { FC, useState } from 'react'
4 | import { useTranslation } from 'react-i18next'
5 | import styled from 'styled-components'
6 |
7 | import { SettingHelpText, SettingHelpTextRow, SettingSubtitle } from '..'
8 |
9 | const OllamSettings: FC = () => {
10 | const { keepAliveTime, setKeepAliveTime } = useOllamaSettings()
11 | const [keepAliveMinutes, setKeepAliveMinutes] = useState(keepAliveTime)
12 | const { t } = useTranslation()
13 |
14 | return (
15 |
16 | {t('ollama.keep_alive_time.title')}
17 | setKeepAliveMinutes(Number(e))}
21 | onBlur={() => setKeepAliveTime(keepAliveMinutes)}
22 | suffix={t('ollama.keep_alive_time.placeholder')}
23 | step={5}
24 | />
25 |
26 | {t('ollama.keep_alive_time.description')}
27 |
28 |
29 | )
30 | }
31 |
32 | const Container = styled.div``
33 |
34 | export default OllamSettings
35 |
--------------------------------------------------------------------------------
/.yarn/patches/pdf-parse-npm-1.1.1-04a6109b2a.patch:
--------------------------------------------------------------------------------
1 | diff --git a/lib/pdf-parse.js b/lib/pdf-parse.js
2 | index 96bfbc705dcb4fb73cb077a75f02c115371b3477..6d02d2bb426063c3a31cb740c3d86841de162a22 100644
3 | --- a/lib/pdf-parse.js
4 | +++ b/lib/pdf-parse.js
5 | @@ -21,12 +21,12 @@ function render_page(pageData) {
6 | for (let item of textContent.items) {
7 | if (lastY == item.transform[5] || !lastY){
8 | text += item.str;
9 | - }
10 | + }
11 | else{
12 | text += '\n' + item.str;
13 | - }
14 | + }
15 | lastY = item.transform[5];
16 | - }
17 | + }
18 | //let strings = textContent.items.map(item => item.str);
19 | //let text = strings.join("\n");
20 | //text = text.replace(/[ ]+/ig," ");
21 | @@ -60,7 +60,7 @@ async function PDF(dataBuffer, options) {
22 | if (typeof options.version != 'string') options.version = DEFAULT_OPTIONS.version;
23 | if (options.version == 'default') options.version = DEFAULT_OPTIONS.version;
24 |
25 | - PDFJS = PDFJS ? PDFJS : require(`./pdf.js/${options.version}/build/pdf.js`);
26 | + PDFJS = PDFJS ? PDFJS : require(`./pdf.js/v1.10.100/build/pdf.js`);
27 |
28 | ret.version = PDFJS.version;
29 |
30 |
--------------------------------------------------------------------------------
/src/renderer/src/assets/fonts/ubuntu/ubuntu.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'Ubuntu';
3 | src: url('Ubuntu-Regular.ttf') format('truetype');
4 | font-weight: normal;
5 | font-style: normal;
6 | }
7 |
8 | @font-face {
9 | font-family: 'Ubuntu';
10 | src: url('Ubuntu-Italic.ttf') format('truetype');
11 | font-weight: normal;
12 | font-style: italic;
13 | }
14 |
15 | @font-face {
16 | font-family: 'Ubuntu';
17 | src: url('Ubuntu-Bold.ttf') format('truetype');
18 | font-weight: bold;
19 | font-style: normal;
20 | }
21 |
22 | @font-face {
23 | font-family: 'Ubuntu';
24 | src: url('Ubuntu-BoldItalic.ttf') format('truetype');
25 | font-weight: bold;
26 | font-style: italic;
27 | }
28 |
29 | @font-face {
30 | font-family: 'Ubuntu';
31 | src: url('Ubuntu-Light.ttf') format('truetype');
32 | font-weight: 300;
33 | font-style: normal;
34 | }
35 |
36 | @font-face {
37 | font-family: 'Ubuntu';
38 | src: url('Ubuntu-LightItalic.ttf') format('truetype');
39 | font-weight: 300;
40 | font-style: italic;
41 | }
42 |
43 | @font-face {
44 | font-family: 'Ubuntu';
45 | src: url('Ubuntu-Medium.ttf') format('truetype');
46 | font-weight: 500;
47 | font-style: normal;
48 | }
49 |
50 | @font-face {
51 | font-family: 'Ubuntu';
52 | src: url('Ubuntu-MediumItalic.ttf') format('truetype');
53 | font-weight: 500;
54 | font-style: italic;
55 | }
56 |
--------------------------------------------------------------------------------
/src/renderer/src/hooks/useBridge.ts:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react'
2 |
3 | export function useBridge() {
4 | useEffect(() => {
5 | const handleMessage = async (event: MessageEvent) => {
6 | const targetOrigin = { targetOrigin: '*' }
7 |
8 | try {
9 | if (event.origin !== 'file://') {
10 | return
11 | }
12 |
13 | const { type, method, args, id } = event.data
14 |
15 | if (type !== 'api-call' || !window.api) {
16 | return
17 | }
18 |
19 | const apiMethod = window.api[method]
20 |
21 | if (typeof apiMethod !== 'function') {
22 | return
23 | }
24 |
25 | event.source?.postMessage(
26 | {
27 | id,
28 | type: 'api-response',
29 | result: await apiMethod(...args)
30 | },
31 | targetOrigin
32 | )
33 | } catch (error) {
34 | event.source?.postMessage(
35 | {
36 | id: event.data?.id,
37 | type: 'api-response',
38 | error: error instanceof Error ? error.message : String(error)
39 | },
40 | targetOrigin
41 | )
42 | }
43 | }
44 |
45 | window.addEventListener('message', handleMessage)
46 |
47 | return () => {
48 | window.removeEventListener('message', handleMessage)
49 | }
50 | }, [])
51 | }
52 |
--------------------------------------------------------------------------------
/packages/database/src/agents.js:
--------------------------------------------------------------------------------
1 | const sqlite3 = require('sqlite3').verbose()
2 | const fs = require('fs')
3 |
4 | // 连接到数据库
5 | const db = new sqlite3.Database('./data/CherryStudio.sqlite3', (err) => {
6 | if (err) {
7 | console.error('Error connecting to the database:', err.message)
8 | return
9 | }
10 | console.log('Connected to the database.')
11 | })
12 |
13 | // 查询数据并转换为JSON
14 | db.all('SELECT * FROM agents', [], (err, rows) => {
15 | if (err) {
16 | console.error('Error querying the database:', err.message)
17 | return
18 | }
19 |
20 | // 将 ID 类型转换为字符串
21 | for (const row of rows) {
22 | row.id = row.id.toString()
23 | row.group = row.group.toString().split(',')
24 | row.group = row.group.map((item) => item.trim().replace('\r\n', ''))
25 | }
26 |
27 | // 将查询结果转换为JSON字符串
28 | const jsonData = JSON.stringify(rows, null, 2)
29 |
30 | // 将JSON数据写入文件
31 | fs.writeFile('../../src/renderer/src/config/agents.json', jsonData, (err) => {
32 | if (err) {
33 | console.error('Error writing to file:', err.message)
34 | return
35 | }
36 | console.log('Data has been written to agents.json')
37 | })
38 |
39 | // 关闭数据库连接
40 | db.close((err) => {
41 | if (err) {
42 | console.error('Error closing the database:', err.message)
43 | return
44 | }
45 | console.log('Database connection closed.')
46 | })
47 | })
48 |
--------------------------------------------------------------------------------
/src/renderer/src/utils/style.ts:
--------------------------------------------------------------------------------
1 | type ClassValue = string | number | boolean | undefined | null | ClassDictionary | ClassArray
2 |
3 | interface ClassDictionary {
4 | [id: string]: any
5 | }
6 |
7 | interface ClassArray extends Array {}
8 |
9 | // Example:
10 | // classNames('foo', 'bar'); // => 'foo bar'
11 | // classNames('foo', { bar: true }); // => 'foo bar'
12 | // classNames({ foo: true, bar: false }); // => 'foo'
13 | // classNames(['foo', 'bar']); // => 'foo bar'
14 | // classNames('foo', null, 'bar'); // => 'foo bar'
15 | // classNames({ message: true, 'message-assistant': true }); // => 'message message-assistant'
16 |
17 | /**
18 | * 生成 class 字符串
19 | * @param args
20 | * @returns
21 | */
22 | export function classNames(...args: ClassValue[]): string {
23 | const classes: string[] = []
24 |
25 | args.forEach((arg) => {
26 | if (!arg) return
27 |
28 | if (typeof arg === 'string' || typeof arg === 'number') {
29 | classes.push(arg.toString())
30 | } else if (Array.isArray(arg)) {
31 | const inner = classNames(...arg)
32 | if (inner) {
33 | classes.push(inner)
34 | }
35 | } else if (typeof arg === 'object') {
36 | Object.entries(arg).forEach(([key, value]) => {
37 | if (value) {
38 | classes.push(key)
39 | }
40 | })
41 | }
42 | })
43 |
44 | return classes.filter(Boolean).join(' ')
45 | }
46 |
--------------------------------------------------------------------------------
/src/renderer/src/pages/apps/App.tsx:
--------------------------------------------------------------------------------
1 | import MinApp from '@renderer/components/MinApp'
2 | import { MinAppType } from '@renderer/types'
3 | import { FC } from 'react'
4 | import styled from 'styled-components'
5 |
6 | interface Props {
7 | app: MinAppType
8 | onClick?: () => void
9 | size?: number
10 | }
11 |
12 | const App: FC = ({ app, onClick, size = 60 }) => {
13 | const handleClick = () => {
14 | MinApp.start(app)
15 | onClick?.()
16 | }
17 |
18 | return (
19 |
20 |
28 | {app.name}
29 |
30 | )
31 | }
32 |
33 | const Container = styled.div`
34 | display: flex;
35 | flex-direction: column;
36 | justify-content: center;
37 | align-items: center;
38 | cursor: pointer;
39 | overflow: hidden;
40 | `
41 |
42 | const AppIcon = styled.img`
43 | border-radius: 16px;
44 | user-select: none;
45 | -webkit-user-drag: none;
46 | `
47 |
48 | const AppTitle = styled.div`
49 | font-size: 12px;
50 | margin-top: 5px;
51 | color: var(--color-text-soft);
52 | text-align: center;
53 | user-select: none;
54 | white-space: nowrap;
55 | `
56 |
57 | export default App
58 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/2_question.yml:
--------------------------------------------------------------------------------
1 | name: ❓ Question
2 | description: Ask a question or seek help
3 | title: '[Question]: '
4 | labels: ['question']
5 | body:
6 | - type: markdown
7 | attributes:
8 | value: |
9 | Thanks for asking a question! Please provide as much detail as possible so we can better assist you.
10 |
11 | - type: textarea
12 | id: question
13 | attributes:
14 | label: Your Question
15 | description: Please describe your question in detail
16 | placeholder: Please explain your question as clearly as possible...
17 | validations:
18 | required: true
19 |
20 | - type: textarea
21 | id: context
22 | attributes:
23 | label: Context
24 | description: Please provide some background information to help us better understand your question
25 | placeholder: "For example: use case, solutions you've tried, etc."
26 |
27 | - type: textarea
28 | id: additional
29 | attributes:
30 | label: Additional Information
31 | description: Any other relevant information, screenshots, or code examples
32 | render: shell
33 |
34 | - type: dropdown
35 | id: priority
36 | attributes:
37 | label: Priority
38 | description: How urgent is this question for you?
39 | options:
40 | - Low (Can wait)
41 | - Medium (Would like a response soon)
42 | - High (Blocking progress)
43 | validations:
44 | required: true
45 |
--------------------------------------------------------------------------------
/src/renderer/src/hooks/useSettings.ts:
--------------------------------------------------------------------------------
1 | import store, { useAppDispatch, useAppSelector } from '@renderer/store'
2 | import {
3 | SendMessageShortcut,
4 | setSendMessageShortcut as _setSendMessageShortcut,
5 | setTheme,
6 | SettingsState,
7 | setTopicPosition,
8 | setTray,
9 | setWindowStyle
10 | } from '@renderer/store/settings'
11 | import { ThemeMode } from '@renderer/types'
12 |
13 | export function useSettings() {
14 | const settings = useAppSelector((state) => state.settings)
15 | const dispatch = useAppDispatch()
16 |
17 | return {
18 | ...settings,
19 | setSendMessageShortcut(shortcut: SendMessageShortcut) {
20 | dispatch(_setSendMessageShortcut(shortcut))
21 | },
22 | setTray(isActive: boolean) {
23 | dispatch(setTray(isActive))
24 | },
25 | setTheme(theme: ThemeMode) {
26 | dispatch(setTheme(theme))
27 | },
28 | setWindowStyle(windowStyle: 'transparent' | 'opaque') {
29 | dispatch(setWindowStyle(windowStyle))
30 | },
31 | setTopicPosition(topicPosition: 'left' | 'right') {
32 | dispatch(setTopicPosition(topicPosition))
33 | }
34 | }
35 | }
36 |
37 | export function useMessageStyle() {
38 | const { messageStyle } = useSettings()
39 | const isBubbleStyle = messageStyle === 'bubble'
40 |
41 | return {
42 | isBubbleStyle
43 | }
44 | }
45 |
46 | export const getStoreSetting = (key: keyof SettingsState) => {
47 | return store.getState().settings[key]
48 | }
49 |
--------------------------------------------------------------------------------
/src/renderer/src/pages/settings/ProviderSettings/GraphRAGSettings.tsx:
--------------------------------------------------------------------------------
1 | import MinApp from '@renderer/components/MinApp'
2 | import { MinAppType, Provider } from '@renderer/types'
3 | import { Button } from 'antd'
4 | import { FC } from 'react'
5 | import { useTranslation } from 'react-i18next'
6 | import styled from 'styled-components'
7 |
8 | import { SettingSubtitle } from '..'
9 |
10 | interface Props {
11 | provider: Provider
12 | }
13 |
14 | const GraphRAGSettings: FC = ({ provider }) => {
15 | const apiUrl = provider.apiHost
16 | const modalId = provider.models.filter((model) => model.id.includes('global'))[0]?.id
17 | const { t } = useTranslation()
18 |
19 | const onShowGraphRAG = async () => {
20 | const { appPath } = await window.api.getAppInfo()
21 | const url = `file://${appPath}/resources/graphrag.html?apiUrl=${apiUrl}&modelId=${modalId}`
22 |
23 | const app: MinAppType = {
24 | name: t('words.knowledgeGraph'),
25 | logo: '',
26 | url
27 | }
28 |
29 | MinApp.start(app)
30 | }
31 |
32 | if (!modalId) {
33 | return null
34 | }
35 |
36 | return (
37 |
38 | {t('words.knowledgeGraph')}
39 |
42 |
43 | )
44 | }
45 |
46 | const Container = styled.div``
47 |
48 | export default GraphRAGSettings
49 |
--------------------------------------------------------------------------------
/scripts/build-npm.js:
--------------------------------------------------------------------------------
1 | const { downloadNpmPackage } = require('./utils')
2 |
3 | async function downloadNpm(platform) {
4 | if (!platform || platform === 'mac') {
5 | downloadNpmPackage(
6 | '@libsql/darwin-arm64',
7 | 'https://registry.npmjs.org/@libsql/darwin-arm64/-/darwin-arm64-0.4.7.tgz'
8 | )
9 | downloadNpmPackage('@libsql/darwin-x64', 'https://registry.npmjs.org/@libsql/darwin-x64/-/darwin-x64-0.4.7.tgz')
10 | }
11 |
12 | if (!platform || platform === 'linux') {
13 | downloadNpmPackage(
14 | '@libsql/linux-arm64-gnu',
15 | 'https://registry.npmjs.org/@libsql/linux-arm64-gnu/-/linux-arm64-gnu-0.4.7.tgz'
16 | )
17 | downloadNpmPackage(
18 | '@libsql/linux-arm64-musl',
19 | 'https://registry.npmjs.org/@libsql/linux-arm64-musl/-/linux-arm64-musl-0.4.7.tgz'
20 | )
21 | downloadNpmPackage(
22 | '@libsql/linux-x64-gnu',
23 | 'https://registry.npmjs.org/@libsql/linux-x64-gnu/-/linux-x64-gnu-0.4.7.tgz'
24 | )
25 | downloadNpmPackage(
26 | '@libsql/linux-x64-musl',
27 | 'https://registry.npmjs.org/@libsql/linux-x64-musl/-/linux-x64-musl-0.4.7.tgz'
28 | )
29 | }
30 |
31 | if (!platform || platform === 'windows') {
32 | downloadNpmPackage(
33 | '@libsql/win32-x64-msvc',
34 | 'https://registry.npmjs.org/@libsql/win32-x64-msvc/-/win32-x64-msvc-0.4.7.tgz'
35 | )
36 | }
37 | }
38 |
39 | const platformArg = process.argv[2]
40 | downloadNpm(platformArg)
41 |
--------------------------------------------------------------------------------
/src/renderer/src/pages/home/Messages/MessageAttachments.tsx:
--------------------------------------------------------------------------------
1 | import FileManager from '@renderer/services/FileManager'
2 | import { FileTypes, Message } from '@renderer/types'
3 | import { Image as AntdImage, Upload } from 'antd'
4 | import { FC } from 'react'
5 | import styled from 'styled-components'
6 |
7 | interface Props {
8 | message: Message
9 | }
10 |
11 | const MessageAttachments: FC = ({ message }) => {
12 | if (!message.files) {
13 | return null
14 | }
15 |
16 | if (message?.files && message.files[0]?.type === FileTypes.IMAGE) {
17 | return (
18 |
19 | {message.files?.map((image) => )}
20 |
21 | )
22 | }
23 |
24 | return (
25 |
26 | ({
30 | uid: file.id,
31 | url: 'file://' + FileManager.getSafePath(file),
32 | status: 'done',
33 | name: file.origin_name
34 | }))}
35 | />
36 |
37 | )
38 | }
39 |
40 | const Container = styled.div`
41 | display: flex;
42 | flex-direction: row;
43 | gap: 10px;
44 | margin-top: 8px;
45 | `
46 |
47 | const Image = styled(AntdImage)`
48 | border-radius: 10px;
49 | `
50 |
51 | export default MessageAttachments
52 |
--------------------------------------------------------------------------------
/src/renderer/src/pages/home/Inputbar/AttachmentPreview.tsx:
--------------------------------------------------------------------------------
1 | import FileManager from '@renderer/services/FileManager'
2 | import { FileType } from '@renderer/types'
3 | import { Upload } from 'antd'
4 | import { isEmpty } from 'lodash'
5 | import { FC } from 'react'
6 | import styled from 'styled-components'
7 |
8 | interface Props {
9 | files: FileType[]
10 | setFiles: (files: FileType[]) => void
11 | }
12 |
13 | const AttachmentPreview: FC = ({ files, setFiles }) => {
14 | if (isEmpty(files)) {
15 | return null
16 | }
17 |
18 | return (
19 |
20 |
21 | 20 ? 'text' : 'picture-card'}
23 | fileList={files.map((file) => ({
24 | uid: file.id,
25 | url: 'file://' + FileManager.getSafePath(file),
26 | status: 'done',
27 | name: file.name
28 | }))}
29 | onRemove={(item) => setFiles(files.filter((file) => item.uid !== file.id))}
30 | />
31 |
32 |
33 | )
34 | }
35 |
36 | const Container = styled.div`
37 | display: flex;
38 | flex-direction: row;
39 | gap: 10px;
40 | padding: 10px 0;
41 | background: var(--color-background);
42 | border-top: 1px solid var(--color-border-mute);
43 | `
44 |
45 | const ContentContainer = styled.div`
46 | max-height: 40vh;
47 | width: 100%;
48 | overflow-y: auto;
49 | padding: 0 20px;
50 | `
51 |
52 | export default AttachmentPreview
53 |
--------------------------------------------------------------------------------
/src/renderer/src/assets/images/apps/bolt.svg:
--------------------------------------------------------------------------------
1 |
19 |
--------------------------------------------------------------------------------
/src/renderer/src/components/Popups/TemplatePopup.tsx:
--------------------------------------------------------------------------------
1 | import { Modal } from 'antd'
2 | import { useState } from 'react'
3 |
4 | import { Box } from '../Layout'
5 | import { TopView } from '../TopView'
6 |
7 | interface ShowParams {
8 | title: string
9 | }
10 |
11 | interface Props extends ShowParams {
12 | resolve: (data: any) => void
13 | }
14 |
15 | const PopupContainer: React.FC = ({ title, resolve }) => {
16 | const [open, setOpen] = useState(true)
17 |
18 | const onOk = () => {
19 | setOpen(false)
20 | }
21 |
22 | const onCancel = () => {
23 | setOpen(false)
24 | }
25 |
26 | const onClose = () => {
27 | resolve({})
28 | }
29 |
30 | TemplatePopup.hide = onCancel
31 |
32 | return (
33 |
41 | Name
42 |
43 | )
44 | }
45 |
46 | const TopViewKey = 'TemplatePopup'
47 |
48 | export default class TemplatePopup {
49 | static topviewId = 0
50 | static hide() {
51 | TopView.hide(TopViewKey)
52 | }
53 | static show(props: ShowParams) {
54 | return new Promise((resolve) => {
55 | TopView.show(
56 | {
59 | resolve(v)
60 | TopView.hide(TopViewKey)
61 | }}
62 | />,
63 | TopViewKey
64 | )
65 | })
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/renderer/src/utils/formula.ts:
--------------------------------------------------------------------------------
1 | export function escapeDollarNumber(text: string) {
2 | let escapedText = ''
3 |
4 | for (let i = 0; i < text.length; i += 1) {
5 | let char = text[i]
6 | const nextChar = text[i + 1] || ' '
7 |
8 | if (char === '$' && nextChar >= '0' && nextChar <= '9') {
9 | char = '\\$'
10 | }
11 |
12 | escapedText += char
13 | }
14 |
15 | return escapedText
16 | }
17 |
18 | export function escapeBrackets(text: string) {
19 | const pattern = /(```[\s\S]*?```|`.*?`)|\\\[([\s\S]*?[^\\])\\\]|\\\((.*?)\\\)/g
20 | return text.replace(pattern, (match, codeBlock, squareBracket, roundBracket) => {
21 | if (codeBlock) {
22 | return codeBlock
23 | } else if (squareBracket) {
24 | return `
25 | $$
26 | ${squareBracket}
27 | $$
28 | `
29 | } else if (roundBracket) {
30 | return `$${roundBracket}$`
31 | }
32 | return match
33 | })
34 | }
35 |
36 | export function extractTitle(html: string): string | null {
37 | const titleRegex = /(.*?)<\/title>/i
38 | const match = html.match(titleRegex)
39 |
40 | if (match && match[1]) {
41 | return match[1].trim()
42 | }
43 |
44 | return null
45 | }
46 |
47 | export function removeSvgEmptyLines(text: string): string {
48 | // 用正则表达式匹配