feat: add minapps
@ -56,4 +56,4 @@ electronDownload:
|
|||||||
afterSign: scripts/notarize.js
|
afterSign: scripts/notarize.js
|
||||||
releaseInfo:
|
releaseInfo:
|
||||||
releaseNotes: |
|
releaseNotes: |
|
||||||
添加 MiniMax 服务商
|
新增AI小程序模块
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -62,10 +62,33 @@ export function createMainWindow() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
mainWindow.webContents.setWindowOpenHandler((details) => {
|
mainWindow.webContents.setWindowOpenHandler((details) => {
|
||||||
|
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)
|
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({
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
BIN
src/renderer/src/assets/images/apps/360-ai.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
src/renderer/src/assets/images/apps/baidu-ai.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
src/renderer/src/assets/images/apps/baixiaoying.webp
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
src/renderer/src/assets/images/apps/kimi.jpg
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
src/renderer/src/assets/images/apps/yuewen.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
src/renderer/src/assets/images/models/yi.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
@ -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 |
|
Before Width: | Height: | Size: 6.5 KiB |
BIN
src/renderer/src/assets/images/providers/moonshot.jpg
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
src/renderer/src/assets/images/providers/yi.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
@ -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 |
@ -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;
|
||||||
|
|||||||
7
src/renderer/src/components/Icons/CopyIcon.tsx
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { FC } from 'react'
|
||||||
|
|
||||||
|
const CopyIcon: FC = () => {
|
||||||
|
return <i className="iconfont icon-copy" />
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CopyIcon
|
||||||
@ -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()
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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: {
|
||||||
|
|||||||
49
src/renderer/src/pages/apps/App.tsx
Normal 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
|
||||||
77
src/renderer/src/pages/apps/AppsPage.tsx
Normal 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
|
||||||
@ -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) {
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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
|
||||||
|
}
|
||||||
|
|||||||