feat: add minapps

This commit is contained in:
kangfenmao 2024-08-21 10:07:46 +08:00
parent 4225312d4a
commit 647dd3e751
29 changed files with 322 additions and 73 deletions

View File

@ -56,4 +56,4 @@ electronDownload:
afterSign: scripts/notarize.js afterSign: scripts/notarize.js
releaseInfo: releaseInfo:
releaseNotes: | releaseNotes: |
添加 MiniMax 服务商 新增AI小程序模块

View File

@ -58,7 +58,9 @@
window.api.minApp({ window.api.minApp({
url, url,
windowOptions: { windowOptions: {
title: node.properties.title title: node.properties.title,
width: 500,
height: 800
} }
}) })
}) })

View File

@ -62,10 +62,33 @@ export function createMainWindow() {
}) })
mainWindow.webContents.setWindowOpenHandler((details) => { mainWindow.webContents.setWindowOpenHandler((details) => {
shell.openExternal(details.url) const websiteReg = /accounts.google.com/i
if (websiteReg.test(details.url)) {
createMinappWindow({ url: details.url, windowOptions: { width: 1000, height: 680 } })
} else {
shell.openExternal(details.url)
}
return { action: 'deny' } return { action: 'deny' }
}) })
mainWindow.webContents.session.webRequest.onHeadersReceived({ urls: ['*://*/*'] }, (details, callback) => {
if (details.responseHeaders?.['X-Frame-Options']) {
delete details.responseHeaders['X-Frame-Options']
}
if (details.responseHeaders?.['x-frame-options']) {
delete details.responseHeaders['x-frame-options']
}
if (details.responseHeaders?.['Content-Security-Policy']) {
delete details.responseHeaders['Content-Security-Policy']
}
if (details.responseHeaders?.['content-security-policy']) {
delete details.responseHeaders['content-security-policy']
}
callback({ cancel: false, responseHeaders: details.responseHeaders })
})
// HMR for renderer base on electron-vite cli. // HMR for renderer base on electron-vite cli.
// Load the remote URL for development or the local html file for production. // Load the remote URL for development or the local html file for production.
if (is.dev && process.env['ELECTRON_RENDERER_URL']) { if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
@ -84,8 +107,8 @@ export function createMinappWindow({
url: string url: string
windowOptions?: Electron.BrowserWindowConstructorOptions windowOptions?: Electron.BrowserWindowConstructorOptions
}) { }) {
const width = 500 const width = 1000
const height = 800 const height = 680
const headerHeight = 40 const headerHeight = 40
const minappWindow = new BrowserWindow({ const minappWindow = new BrowserWindow({

View File

@ -6,6 +6,7 @@ import { PersistGate } from 'redux-persist/integration/react'
import Sidebar from './components/app/Sidebar' import Sidebar from './components/app/Sidebar'
import TopViewContainer from './components/TopView' import TopViewContainer from './components/TopView'
import AgentsPage from './pages/agents/AgentsPage' import AgentsPage from './pages/agents/AgentsPage'
import AppsPage from './pages/apps/AppsPage'
import HomePage from './pages/home/HomePage' import HomePage from './pages/home/HomePage'
import SettingsPage from './pages/settings/SettingsPage' import SettingsPage from './pages/settings/SettingsPage'
import TranslatePage from './pages/translate/TranslatePage' import TranslatePage from './pages/translate/TranslatePage'
@ -23,8 +24,9 @@ function App(): JSX.Element {
<Sidebar /> <Sidebar />
<Routes> <Routes>
<Route path="/" element={<HomePage />} /> <Route path="/" element={<HomePage />} />
<Route path="/apps" element={<AgentsPage />} /> <Route path="/agents" element={<AgentsPage />} />
<Route path="/translate" element={<TranslatePage />} /> <Route path="/translate" element={<TranslatePage />} />
<Route path="/apps" element={<AppsPage />} />
<Route path="/settings/*" element={<SettingsPage />} /> <Route path="/settings/*" element={<SettingsPage />} />
</Routes> </Routes>
</HashRouter> </HashRouter>

View File

@ -1,63 +1,64 @@
@font-face { @font-face {
font-family: "iconfont"; /* Project id 4563475 */ font-family: 'iconfont'; /* Project id 4563475 */
src: url('iconfont.woff2?t=1723186111414') format('woff2'), src: url('iconfont.woff2?t=1724204739157') format('woff2');
url('iconfont.woff?t=1723186111414') format('woff'),
url('iconfont.ttf?t=1723186111414') format('truetype');
} }
.iconfont { .iconfont {
font-family: "iconfont" !important; font-family: 'iconfont' !important;
font-size: 16px; font-size: 16px;
font-style: normal; font-style: normal;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.icon-business-smart-assistant:before {
content: '\e601';
}
.icon-copy:before { .icon-copy:before {
content: "\e6ae"; content: '\e6ae';
} }
.icon-ic_send:before { .icon-ic_send:before {
content: "\e795"; content: '\e795';
} }
.icon-dark1:before { .icon-dark1:before {
content: "\e72f"; content: '\e72f';
} }
.icon-theme-light:before { .icon-theme-light:before {
content: "\e6b7"; content: '\e6b7';
} }
.icon-translate_line:before { .icon-translate_line:before {
content: "\e7de"; content: '\e7de';
} }
.icon-history:before { .icon-history:before {
content: "\e758"; content: '\e758';
} }
.icon-hidesidebarhoriz:before { .icon-hidesidebarhoriz:before {
content: "\e8eb"; content: '\e8eb';
} }
.icon-showsidebarhoriz:before { .icon-showsidebarhoriz:before {
content: "\e944"; content: '\e944';
} }
.icon-a-addchat:before { .icon-a-addchat:before {
content: "\e658"; content: '\e658';
} }
.icon-appstore:before { .icon-appstore:before {
content: "\e792"; content: '\e792';
} }
.icon-chat:before { .icon-chat:before {
content: "\e615"; content: '\e615';
} }
.icon-setting:before { .icon-setting:before {
content: "\e78e"; content: '\e78e';
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -1,7 +0,0 @@
<svg width="600" height="600" viewBox="0 0 600 600" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="300" cy="300" r="300" fill="white"/>
<rect x="409.733" y="340.032" width="42.3862" height="151.648" rx="21.1931" fill="#003425"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M422.005 133.354C413.089 125.771 399.714 126.851 392.131 135.768L273.699 275.021C270.643 278.614 268.994 282.932 268.698 287.302C268.532 288.371 268.446 289.466 268.446 290.581V468.603C268.446 480.308 277.934 489.796 289.639 489.796C301.344 489.796 310.832 480.308 310.832 468.603V296.784L424.419 163.228C432.002 154.312 430.921 140.937 422.005 133.354Z" fill="#003425"/>
<rect x="113.972" y="134.25" width="42.3862" height="174.745" rx="21.1931" transform="rotate(-39.3441 113.972 134.25)" fill="#003425"/>
<circle cx="460.126" cy="279.278" r="25.9027" fill="#00DD20"/>
</svg>

Before

Width:  |  Height:  |  Size: 869 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -1,7 +0,0 @@
<svg width="600" height="600" viewBox="0 0 600 600" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="300" cy="300" r="300" fill="#003425"/>
<rect x="409.733" y="340.031" width="42.3862" height="151.648" rx="21.1931" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M422.005 133.354C413.089 125.771 399.714 126.851 392.131 135.767L273.699 275.021C270.643 278.614 268.994 282.932 268.698 287.302C268.532 288.371 268.446 289.466 268.446 290.581V468.603C268.446 480.308 277.934 489.796 289.639 489.796C301.344 489.796 310.832 480.308 310.832 468.603V296.784L424.419 163.228C432.002 154.312 430.921 140.937 422.005 133.354Z" fill="white"/>
<rect x="113.972" y="134.25" width="42.3862" height="174.745" rx="21.1931" transform="rotate(-39.3441 113.972 134.25)" fill="white"/>
<circle cx="460.126" cy="279.278" r="25.9027" fill="#00FF25"/>
</svg>

Before

Width:  |  Height:  |  Size: 865 B

View File

@ -1,7 +1,7 @@
@import './markdown.scss'; @import './markdown.scss';
@import './scrollbar.scss'; @import './scrollbar.scss';
@import '../fonts/icon-fonts/iconfont.css'; @import '../fonts/icon-fonts/iconfont.css';
@import '../fonts/Ubuntu/Ubuntu.css'; @import '../fonts/ubuntu/ubuntu.css';
:root { :root {
--color-white: #ffffff; --color-white: #ffffff;
@ -102,6 +102,14 @@ body[theme-mode='light'] {
font-weight: normal; font-weight: normal;
} }
*:focus {
outline: none;
}
* {
-webkit-tap-highlight-color: transparent;
}
ul { ul {
list-style: none; list-style: none;
} }
@ -175,6 +183,9 @@ body,
} }
.minapp-drawer { .minapp-drawer {
.ant-drawer-content-wrapper {
box-shadow: none;
}
.ant-drawer-header { .ant-drawer-header {
position: absolute; position: absolute;
-webkit-app-region: drag; -webkit-app-region: drag;

View File

@ -0,0 +1,7 @@
import { FC } from 'react'
const CopyIcon: FC = () => {
return <i className="iconfont icon-copy" />
}
export default CopyIcon

View File

@ -3,28 +3,25 @@ import { isMac, isWindows } from '@renderer/config/constant'
import { useBridge } from '@renderer/hooks/useBridge' import { useBridge } from '@renderer/hooks/useBridge'
import store from '@renderer/store' import store from '@renderer/store'
import { setMinappShow } from '@renderer/store/runtime' import { setMinappShow } from '@renderer/store/runtime'
import { MinAppType } from '@renderer/types'
import { Drawer } from 'antd' import { Drawer } from 'antd'
import { useRef, useState } from 'react' import { useRef, useState } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import { TopView } from '../TopView' import { TopView } from '../TopView'
interface ShowParams { interface Props {
title?: string app: MinAppType
url: string
}
interface Props extends ShowParams {
resolve: (data: any) => void resolve: (data: any) => void
} }
const PopupContainer: React.FC<Props> = ({ title, url, resolve }) => { const PopupContainer: React.FC<Props> = ({ app, resolve }) => {
const [open, setOpen] = useState(true) const [open, setOpen] = useState(true)
const iframeRef = useRef<HTMLIFrameElement>(null) const iframeRef = useRef<HTMLIFrameElement>(null)
useBridge() useBridge()
const canOpenExternalLink = url.startsWith('http://') || url.startsWith('https://') const canOpenExternalLink = app.url.startsWith('http://') || app.url.startsWith('https://')
const onClose = () => { const onClose = () => {
setOpen(false) setOpen(false)
@ -33,19 +30,19 @@ const PopupContainer: React.FC<Props> = ({ title, url, resolve }) => {
const onReload = () => { const onReload = () => {
if (iframeRef.current) { if (iframeRef.current) {
iframeRef.current.src = url iframeRef.current.src = app.url
} }
} }
const onOpenLink = () => { const onOpenLink = () => {
window.api.openWebsite(url) window.api.openWebsite(app.url)
} }
const Title = () => { const Title = () => {
return ( return (
<TitleContainer style={{ justifyContent: isWindows ? 'flex-start' : 'space-between' }}> <TitleContainer style={{ justifyContent: 'space-between' }}>
<TitleText>{title}</TitleText> <TitleText>{app.name}</TitleText>
<ButtonsGroup> <ButtonsGroup className={isWindows ? 'windows' : ''}>
<Button onClick={onReload}> <Button onClick={onReload}>
<ReloadOutlined /> <ReloadOutlined />
</Button> </Button>
@ -75,7 +72,7 @@ const PopupContainer: React.FC<Props> = ({ title, url, resolve }) => {
maskClosable={false} maskClosable={false}
closeIcon={null} closeIcon={null}
style={{ marginLeft: 'var(--sidebar-width)' }}> style={{ marginLeft: 'var(--sidebar-width)' }}>
<Frame src={url} ref={iframeRef} /> <Frame src={app.url} ref={iframeRef} />
</Drawer> </Drawer>
) )
} }
@ -84,6 +81,7 @@ const Frame = styled.iframe`
width: calc(100vw - var(--sidebar-width)); width: calc(100vw - var(--sidebar-width));
height: calc(100vh - var(--navbar-height)); height: calc(100vh - var(--navbar-height));
border: none; border: none;
background-color: white;
` `
const TitleContainer = styled.div` const TitleContainer = styled.div`
@ -113,6 +111,13 @@ const ButtonsGroup = styled.div`
align-items: center; align-items: center;
gap: 5px; gap: 5px;
-webkit-app-region: no-drag; -webkit-app-region: no-drag;
&.windows {
margin-right: ${isWindows ? '130px' : 0};
background-color: var(--color-background-mute);
border-radius: 50px;
padding: 0 3px;
overflow: hidden;
}
` `
const Button = styled.div` const Button = styled.div`
@ -139,12 +144,12 @@ export default class MinApp {
TopView.hide('MinApp') TopView.hide('MinApp')
store.dispatch(setMinappShow(false)) store.dispatch(setMinappShow(false))
} }
static start(props: ShowParams) { static start(app: MinAppType) {
store.dispatch(setMinappShow(true)) store.dispatch(setMinappShow(true))
return new Promise<any>((resolve) => { return new Promise<any>((resolve) => {
TopView.show( TopView.show(
<PopupContainer <PopupContainer
{...props} app={app}
resolve={(v) => { resolve={(v) => {
resolve(v) resolve(v)
this.close() this.close()

View File

@ -33,9 +33,9 @@ const Sidebar: FC = () => {
<i className="iconfont icon-chat"></i> <i className="iconfont icon-chat"></i>
</Icon> </Icon>
</StyledLink> </StyledLink>
<StyledLink to="/apps"> <StyledLink to="/agents">
<Icon className={isRoute('/apps')}> <Icon className={isRoute('/agents')}>
<i className="iconfont icon-appstore"></i> <i className="iconfont icon-business-smart-assistant"></i>
</Icon> </Icon>
</StyledLink> </StyledLink>
<StyledLink to="/translate"> <StyledLink to="/translate">
@ -43,6 +43,11 @@ const Sidebar: FC = () => {
<TranslationOutlined /> <TranslationOutlined />
</Icon> </Icon>
</StyledLink> </StyledLink>
<StyledLink to="/apps">
<Icon className={isRoute('/apps')}>
<i className="iconfont icon-appstore"></i>
</Icon>
</StyledLink>
</Menus> </Menus>
</MainMenus> </MainMenus>
<Menus> <Menus>
@ -62,6 +67,7 @@ const Container = styled.div`
align-items: center; align-items: center;
padding: 8px 0; padding: 8px 0;
width: var(--sidebar-width); width: var(--sidebar-width);
min-width: var(--sidebar-width);
height: ${isMac ? 'calc(100vh - var(--navbar-height))' : '100vh'}; height: ${isMac ? 'calc(100vh - var(--navbar-height))' : '100vh'};
-webkit-app-region: drag !important; -webkit-app-region: drag !important;
border-right: 0.5px solid var(--color-border); border-right: 0.5px solid var(--color-border);

View File

@ -1,3 +1,6 @@
import BaicuanAppLogo from '@renderer/assets/images/apps/baixiaoying.webp'
import KimiAppLogo from '@renderer/assets/images/apps/kimi.jpg'
import YuewenAppLogo from '@renderer/assets/images/apps/yuewen.png'
import BaichuanModelLogo from '@renderer/assets/images/models/baichuan.png' import BaichuanModelLogo from '@renderer/assets/images/models/baichuan.png'
import ChatGLMModelLogo from '@renderer/assets/images/models/chatglm.jpeg' import ChatGLMModelLogo from '@renderer/assets/images/models/chatglm.jpeg'
import ChatGPTModelLogo from '@renderer/assets/images/models/chatgpt.jpeg' import ChatGPTModelLogo from '@renderer/assets/images/models/chatgpt.jpeg'
@ -14,7 +17,7 @@ import MixtralModelLogo from '@renderer/assets/images/models/mixtral.jpeg'
import PalmModelLogo from '@renderer/assets/images/models/palm.svg' import PalmModelLogo from '@renderer/assets/images/models/palm.svg'
import QwenModelLogo from '@renderer/assets/images/models/qwen.png' import QwenModelLogo from '@renderer/assets/images/models/qwen.png'
import StepModelLogo from '@renderer/assets/images/models/step.jpg' import StepModelLogo from '@renderer/assets/images/models/step.jpg'
import YiModelLogo from '@renderer/assets/images/models/yi.svg' import YiModelLogo from '@renderer/assets/images/models/yi.png'
import AiHubMixProviderLogo from '@renderer/assets/images/providers/aihubmix.jpg' import AiHubMixProviderLogo from '@renderer/assets/images/providers/aihubmix.jpg'
import AnthropicProviderLogo from '@renderer/assets/images/providers/anthropic.jpeg' import AnthropicProviderLogo from '@renderer/assets/images/providers/anthropic.jpeg'
import BaichuanProviderLogo from '@renderer/assets/images/providers/baichuan.png' import BaichuanProviderLogo from '@renderer/assets/images/providers/baichuan.png'
@ -25,14 +28,14 @@ import GeminiProviderLogo from '@renderer/assets/images/providers/gemini.png'
import GraphRagProviderLogo from '@renderer/assets/images/providers/graph-rag.png' import GraphRagProviderLogo from '@renderer/assets/images/providers/graph-rag.png'
import GroqProviderLogo from '@renderer/assets/images/providers/groq.png' import GroqProviderLogo from '@renderer/assets/images/providers/groq.png'
import MinimaxProviderLogo from '@renderer/assets/images/providers/minimax.png' import MinimaxProviderLogo from '@renderer/assets/images/providers/minimax.png'
import MoonshotProviderLogo from '@renderer/assets/images/providers/moonshot.jpeg' import MoonshotProviderLogo from '@renderer/assets/images/providers/moonshot.jpg'
import MoonshotModelLogo from '@renderer/assets/images/providers/moonshot.jpeg' import MoonshotModelLogo from '@renderer/assets/images/providers/moonshot.jpg'
import OllamaProviderLogo from '@renderer/assets/images/providers/ollama.png' import OllamaProviderLogo from '@renderer/assets/images/providers/ollama.png'
import OpenAiProviderLogo from '@renderer/assets/images/providers/openai.png' import OpenAiProviderLogo from '@renderer/assets/images/providers/openai.png'
import OpenRouterProviderLogo from '@renderer/assets/images/providers/openrouter.png' import OpenRouterProviderLogo from '@renderer/assets/images/providers/openrouter.png'
import SiliconFlowProviderLogo from '@renderer/assets/images/providers/silicon.png' import SiliconFlowProviderLogo from '@renderer/assets/images/providers/silicon.png'
import StepFunProviderLogo from '@renderer/assets/images/providers/stepfun.png' import StepFunProviderLogo from '@renderer/assets/images/providers/stepfun.png'
import YiProviderLogo from '@renderer/assets/images/providers/yi.svg' import YiProviderLogo from '@renderer/assets/images/providers/yi.png'
import ZhipuProviderLogo from '@renderer/assets/images/providers/zhipu.png' import ZhipuProviderLogo from '@renderer/assets/images/providers/zhipu.png'
export function getProviderLogo(providerId: string) { export function getProviderLogo(providerId: string) {
@ -126,6 +129,11 @@ export const PROVIDER_CONFIG = {
apiKey: 'https://platform.openai.com/api-keys', apiKey: 'https://platform.openai.com/api-keys',
docs: 'https://platform.openai.com/docs', docs: 'https://platform.openai.com/docs',
models: 'https://platform.openai.com/docs/models' models: 'https://platform.openai.com/docs/models'
},
app: {
name: 'ChatGPT',
url: 'https://chatgpt.com/',
logo: OpenAiProviderLogo
} }
}, },
gemini: { gemini: {
@ -138,6 +146,11 @@ export const PROVIDER_CONFIG = {
apiKey: 'https://aistudio.google.com/app/apikey', apiKey: 'https://aistudio.google.com/app/apikey',
docs: 'https://ai.google.dev/gemini-api/docs', docs: 'https://ai.google.dev/gemini-api/docs',
models: 'https://ai.google.dev/gemini-api/docs/models/gemini' models: 'https://ai.google.dev/gemini-api/docs/models/gemini'
},
app: {
name: 'Gemini',
url: 'https://gemini.google.com/',
logo: GeminiProviderLogo
} }
}, },
silicon: { silicon: {
@ -150,6 +163,11 @@ export const PROVIDER_CONFIG = {
apiKey: 'https://cloud.siliconflow.cn/account/ak?referrer=clxty1xuy0014lvqwh5z50i88', apiKey: 'https://cloud.siliconflow.cn/account/ak?referrer=clxty1xuy0014lvqwh5z50i88',
docs: 'https://docs.siliconflow.cn/', docs: 'https://docs.siliconflow.cn/',
models: 'https://docs.siliconflow.cn/docs/model-names' models: 'https://docs.siliconflow.cn/docs/model-names'
},
app: {
name: 'SiliconFlow',
url: 'https://cloud.siliconflow.cn/playground/chat',
logo: SiliconFlowProviderLogo
} }
}, },
deepseek: { deepseek: {
@ -162,6 +180,11 @@ export const PROVIDER_CONFIG = {
apiKey: 'https://platform.deepseek.com/api_keys', apiKey: 'https://platform.deepseek.com/api_keys',
docs: 'https://platform.deepseek.com/api-docs/', docs: 'https://platform.deepseek.com/api-docs/',
models: 'https://platform.deepseek.com/api-docs/' models: 'https://platform.deepseek.com/api-docs/'
},
app: {
name: 'DeepSeek',
url: 'https://chat.deepseek.com/',
logo: DeepSeekProviderLogo
} }
}, },
yi: { yi: {
@ -174,6 +197,11 @@ export const PROVIDER_CONFIG = {
apiKey: 'https://platform.lingyiwanwu.com/apikeys', apiKey: 'https://platform.lingyiwanwu.com/apikeys',
docs: 'https://platform.lingyiwanwu.com/docs', docs: 'https://platform.lingyiwanwu.com/docs',
models: 'https://platform.lingyiwanwu.com/docs#%E6%A8%A1%E5%9E%8B' models: 'https://platform.lingyiwanwu.com/docs#%E6%A8%A1%E5%9E%8B'
},
app: {
name: 'Yi',
url: 'https://www.wanzhi.com/',
logo: YiProviderLogo
} }
}, },
zhipu: { zhipu: {
@ -186,6 +214,11 @@ export const PROVIDER_CONFIG = {
apiKey: 'https://open.bigmodel.cn/usercenter/apikeys', apiKey: 'https://open.bigmodel.cn/usercenter/apikeys',
docs: 'https://open.bigmodel.cn/dev/howuse/introduction', docs: 'https://open.bigmodel.cn/dev/howuse/introduction',
models: 'https://open.bigmodel.cn/modelcenter/square' models: 'https://open.bigmodel.cn/modelcenter/square'
},
app: {
name: '智谱',
url: 'https://chatglm.cn/main/alltoolsdetail',
logo: ZhipuProviderLogo
} }
}, },
moonshot: { moonshot: {
@ -198,6 +231,11 @@ export const PROVIDER_CONFIG = {
apiKey: 'https://platform.moonshot.cn/console/api-keys', apiKey: 'https://platform.moonshot.cn/console/api-keys',
docs: 'https://platform.moonshot.cn/docs/', docs: 'https://platform.moonshot.cn/docs/',
models: 'https://platform.moonshot.cn/docs/intro#%E6%A8%A1%E5%9E%8B%E5%88%97%E8%A1%A8' models: 'https://platform.moonshot.cn/docs/intro#%E6%A8%A1%E5%9E%8B%E5%88%97%E8%A1%A8'
},
app: {
name: 'Kimi',
url: 'https://kimi.moonshot.cn/',
logo: KimiAppLogo
} }
}, },
baichuan: { baichuan: {
@ -210,6 +248,11 @@ export const PROVIDER_CONFIG = {
apiKey: 'https://platform.baichuan-ai.com/console/apikey', apiKey: 'https://platform.baichuan-ai.com/console/apikey',
docs: 'https://platform.baichuan-ai.com/docs', docs: 'https://platform.baichuan-ai.com/docs',
models: 'https://platform.baichuan-ai.com/price' models: 'https://platform.baichuan-ai.com/price'
},
app: {
name: '百小应',
url: 'https://ying.baichuan-ai.com/chat',
logo: BaicuanAppLogo
} }
}, },
dashscope: { dashscope: {
@ -222,6 +265,11 @@ export const PROVIDER_CONFIG = {
apiKey: 'https://help.aliyun.com/zh/dashscope/developer-reference/acquisition-and-configuration-of-api-key', apiKey: 'https://help.aliyun.com/zh/dashscope/developer-reference/acquisition-and-configuration-of-api-key',
docs: 'https://help.aliyun.com/zh/dashscope/', docs: 'https://help.aliyun.com/zh/dashscope/',
models: 'https://dashscope.console.aliyun.com/model' models: 'https://dashscope.console.aliyun.com/model'
},
app: {
name: '通义千问',
url: 'https://tongyi.aliyun.com/qianwen/',
logo: QwenModelLogo
} }
}, },
stepfun: { stepfun: {
@ -234,6 +282,11 @@ export const PROVIDER_CONFIG = {
apiKey: 'https://platform.stepfun.com/interface-key', apiKey: 'https://platform.stepfun.com/interface-key',
docs: 'https://platform.stepfun.com/docs/overview/concept', docs: 'https://platform.stepfun.com/docs/overview/concept',
models: 'https://platform.stepfun.com/docs/llm/text' models: 'https://platform.stepfun.com/docs/llm/text'
},
app: {
name: '跃问',
url: 'https://yuewen.cn/chats/new',
logo: YuewenAppLogo
} }
}, },
doubao: { doubao: {
@ -246,6 +299,11 @@ export const PROVIDER_CONFIG = {
apiKey: 'https://console.volcengine.com/ark/region:ark+cn-beijing/apiKey', apiKey: 'https://console.volcengine.com/ark/region:ark+cn-beijing/apiKey',
docs: 'https://www.volcengine.com/docs/82379/1182403', docs: 'https://www.volcengine.com/docs/82379/1182403',
models: 'https://console.volcengine.com/ark/region:ark+cn-beijing/endpoint' models: 'https://console.volcengine.com/ark/region:ark+cn-beijing/endpoint'
},
app: {
name: '豆包',
url: 'https://www.doubao.com/chat/',
logo: DoubaoProviderLogo
} }
}, },
minimax: { minimax: {
@ -258,6 +316,11 @@ export const PROVIDER_CONFIG = {
apiKey: 'https://platform.minimaxi.com/user-center/basic-information/interface-key', apiKey: 'https://platform.minimaxi.com/user-center/basic-information/interface-key',
docs: 'https://platform.minimaxi.com/document/Announcement', docs: 'https://platform.minimaxi.com/document/Announcement',
models: 'https://platform.minimaxi.com/document/Models' models: 'https://platform.minimaxi.com/document/Models'
},
app: {
name: '海螺',
url: 'https://hailuoai.com/',
logo: HailuoModelLogo
} }
}, },
'graphrag-kylin-mountain': { 'graphrag-kylin-mountain': {
@ -288,6 +351,11 @@ export const PROVIDER_CONFIG = {
apiKey: 'https://console.groq.com/keys', apiKey: 'https://console.groq.com/keys',
docs: 'https://console.groq.com/docs/quickstart', docs: 'https://console.groq.com/docs/quickstart',
models: 'https://console.groq.com/docs/models' models: 'https://console.groq.com/docs/models'
},
app: {
name: 'Groq',
url: 'https://groq.com/',
logo: GroqProviderLogo
} }
}, },
ollama: { ollama: {
@ -311,6 +379,11 @@ export const PROVIDER_CONFIG = {
apiKey: 'https://console.anthropic.com/settings/keys', apiKey: 'https://console.anthropic.com/settings/keys',
docs: 'https://docs.anthropic.com/en/docs', docs: 'https://docs.anthropic.com/en/docs',
models: 'https://docs.anthropic.com/en/docs/about-claude/models' models: 'https://docs.anthropic.com/en/docs/about-claude/models'
},
app: {
name: 'Claude',
url: 'https://claude.ai/',
logo: AnthropicProviderLogo
} }
}, },
aihubmix: { aihubmix: {

View File

@ -0,0 +1,49 @@
import MinApp from '@renderer/components/MinApp'
import { useTheme } from '@renderer/providers/ThemeProvider'
import { MinAppType } from '@renderer/types'
import { FC } from 'react'
import styled from 'styled-components'
interface Props {
app: MinAppType
}
const App: FC<Props> = ({ app }) => {
const { theme } = useTheme()
const onClick = () => {
MinApp.start(app)
}
return (
<Container onClick={onClick}>
<AppIcon src={app.logo} style={{ border: theme === 'dark' ? 'none' : '1px solid var(--color-border' }} />
<AppTitle>{app.name}</AppTitle>
</Container>
)
}
const Container = styled.div`
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
cursor: pointer;
`
const AppIcon = styled.img`
width: 60px;
height: 60px;
border-radius: 16px;
user-select: none;
-webkit-user-drag: none;
`
const AppTitle = styled.div`
font-size: 12px;
margin-top: 5px;
color: var(--color-text-soft);
text-align: center;
`
export default App

View File

@ -0,0 +1,77 @@
import AiAssistantAppLogo from '@renderer/assets/images/apps/360-ai.png'
import BaiduAiAppLogo from '@renderer/assets/images/apps/baidu-ai.png'
import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar'
import { PROVIDER_CONFIG } from '@renderer/config/provider'
import { MinAppType } from '@renderer/types'
import { FC } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
import App from './App'
const _apps: MinAppType[] = [
{
name: 'AI 助手',
logo: AiAssistantAppLogo,
url: 'https://bot.360.com/'
},
{
name: '文心一言',
logo: BaiduAiAppLogo,
url: 'https://yiyan.baidu.com/'
}
]
const AppsPage: FC = () => {
const { t } = useTranslation()
const apps: MinAppType[] = (Object.entries(PROVIDER_CONFIG) as any[])
.filter(([, config]) => config.app)
.map(([key, config]) => ({ id: key, ...config.app }))
.concat(_apps)
return (
<Container>
<Navbar>
<NavbarCenter style={{ borderRight: 'none' }}>{t('agents.title')}</NavbarCenter>
</Navbar>
<ContentContainer>
<AppsContainer>
{apps.map((app) => (
<App key={app.name} app={app} />
))}
</AppsContainer>
</ContentContainer>
</Container>
)
}
const Container = styled.div`
display: flex;
flex: 1;
flex-direction: column;
height: 100%;
`
const ContentContainer = styled.div`
display: flex;
flex: 1;
flex-direction: row;
justify-content: center;
height: 100%;
overflow-y: scroll;
background-color: var(--color-background);
padding: 50px;
`
const AppsContainer = styled.div`
display: flex;
min-width: 900px;
max-width: 900px;
flex-direction: row;
flex-wrap: wrap;
align-content: flex-start;
gap: 50px;
`
export default AppsPage

View File

@ -1,5 +1,5 @@
import MinApp from '@renderer/components/MinApp' import MinApp from '@renderer/components/MinApp'
import { Provider } from '@renderer/types' import { MinAppType, Provider } from '@renderer/types'
import { Button } from 'antd' import { Button } from 'antd'
import { FC } from 'react' import { FC } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -19,7 +19,14 @@ const GraphRAGSettings: FC<Props> = ({ provider }) => {
const onShowGraphRAG = async () => { const onShowGraphRAG = async () => {
const { appPath } = await window.api.getAppInfo() const { appPath } = await window.api.getAppInfo()
const url = `file://${appPath}/resources/graphrag.html?apiUrl=${apiUrl}&modelId=${modalId}` const url = `file://${appPath}/resources/graphrag.html?apiUrl=${apiUrl}&modelId=${modalId}`
MinApp.start({ url, title: t('words.knowledgeGraph') })
const app: MinAppType = {
name: t('words.knowledgeGraph'),
logo: '',
url
}
MinApp.start(app)
} }
if (!modalId) { if (!modalId) {

View File

@ -1,12 +1,6 @@
import { import { CheckOutlined, SendOutlined, SettingOutlined, SwapOutlined, WarningOutlined } from '@ant-design/icons'
CheckOutlined,
CopyOutlined,
SendOutlined,
SettingOutlined,
SwapOutlined,
WarningOutlined
} from '@ant-design/icons'
import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar' import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar'
import CopyIcon from '@renderer/components/Icons/CopyIcon'
import { useDefaultModel } from '@renderer/hooks/useAssistant' import { useDefaultModel } from '@renderer/hooks/useAssistant'
import { fetchTranslate } from '@renderer/services/api' import { fetchTranslate } from '@renderer/services/api'
import { getDefaultAssistant } from '@renderer/services/assistant' import { getDefaultAssistant } from '@renderer/services/assistant'
@ -211,7 +205,7 @@ const TranslatePage: FC = () => {
<CopyButton <CopyButton
onClick={onCopy} onClick={onCopy}
disabled={!result} disabled={!result}
icon={copied ? <CheckOutlined style={{ color: 'var(--color-primary)' }} /> : <CopyOutlined />} icon={copied ? <CheckOutlined style={{ color: 'var(--color-primary)' }} /> : <CopyIcon />}
/> />
</OutputContainer> </OutputContainer>
</TranslateInputWrapper> </TranslateInputWrapper>

View File

@ -75,3 +75,9 @@ export type Agent = {
export type Suggestion = { export type Suggestion = {
content: string content: string
} }
export type MinAppType = {
name: string
logo: string
url: string
}