mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-08 06:19:05 +08:00
Merge branch 'main' of github.com:CherryHQ/cherry-studio into v2
This commit is contained in:
commit
8a9b633af2
22
.github/workflows/delete-branch.yml
vendored
Normal file
22
.github/workflows/delete-branch.yml
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
name: Delete merged branch
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types:
|
||||||
|
- closed
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
delete-branch:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
if: github.event.pull_request.merged == true && github.event.pull_request.head.repo.full_name == github.repository
|
||||||
|
steps:
|
||||||
|
- name: Delete merged branch
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
github.rest.git.deleteRef({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
ref: `heads/${context.payload.pull_request.head.ref}`,
|
||||||
|
})
|
||||||
@ -128,16 +128,13 @@ afterSign: scripts/notarize.js
|
|||||||
artifactBuildCompleted: scripts/artifact-build-completed.js
|
artifactBuildCompleted: scripts/artifact-build-completed.js
|
||||||
releaseInfo:
|
releaseInfo:
|
||||||
releaseNotes: |
|
releaseNotes: |
|
||||||
✨ 新功能:
|
🎨 界面优化:
|
||||||
- 新增 CherryIN 服务商
|
- 优化了多个组件的布局和间距,提升视觉体验
|
||||||
- 新增 AiOnly AI 服务商
|
- 改进了导航栏和标签栏的样式显示
|
||||||
- 更新 MCP 服务器卡片布局和样式,改为列表视图
|
- MCP 服务器卡片宽度调整为 100%,提高响应式布局效果
|
||||||
|
- 优化了笔记侧边栏的滚动行为
|
||||||
|
|
||||||
🐛 问题修复:
|
🐛 问题修复:
|
||||||
- 修复 QwenMT 模型的翻译内容处理逻辑
|
- 修复了小应用打开功能无法正常工作的问题
|
||||||
- 修复无法将外部笔记添加到知识库的问题
|
- 修复了助手更新时 ID 丢失导致更新失败的问题
|
||||||
|
- 确保助手更新时 ID 字段为必填项,防止数据错误
|
||||||
🚀 性能优化:
|
|
||||||
- 提升输入框响应速度
|
|
||||||
- 优化模型切换性能
|
|
||||||
- 改进翻译功能的引用和邮件格式处理
|
|
||||||
|
|||||||
@ -18,6 +18,7 @@ interface ItemRendererProps<T> {
|
|||||||
transform?: Transform | null
|
transform?: Transform | null
|
||||||
transition?: string | null
|
transition?: string | null
|
||||||
listeners?: DraggableSyntheticListeners
|
listeners?: DraggableSyntheticListeners
|
||||||
|
itemStyle?: React.CSSProperties
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ItemRenderer<T>({
|
export function ItemRenderer<T>({
|
||||||
@ -31,6 +32,7 @@ export function ItemRenderer<T>({
|
|||||||
transform,
|
transform,
|
||||||
transition,
|
transition,
|
||||||
listeners,
|
listeners,
|
||||||
|
itemStyle,
|
||||||
...props
|
...props
|
||||||
}: ItemRendererProps<T>) {
|
}: ItemRendererProps<T>) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -45,13 +47,17 @@ export function ItemRenderer<T>({
|
|||||||
}
|
}
|
||||||
}, [dragOverlay])
|
}, [dragOverlay])
|
||||||
|
|
||||||
const wrapperStyle = {
|
const style = {
|
||||||
transition,
|
transition,
|
||||||
transform: CSS.Transform.toString(transform ?? null)
|
transform: CSS.Transform.toString(transform ?? null)
|
||||||
} as React.CSSProperties
|
} as React.CSSProperties
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ItemWrapper ref={ref} data-index={index} className={cn({ dragOverlay: dragOverlay })} style={{ ...wrapperStyle }}>
|
<ItemWrapper
|
||||||
|
ref={ref}
|
||||||
|
data-index={index}
|
||||||
|
className={cn({ dragOverlay: dragOverlay })}
|
||||||
|
style={{ ...style, ...itemStyle }}>
|
||||||
<DraggableItem
|
<DraggableItem
|
||||||
className={cn({ dragging: dragging, dragOverlay: dragOverlay, ghost: ghost })}
|
className={cn({ dragging: dragging, dragOverlay: dragOverlay, ghost: ghost })}
|
||||||
{...listeners}
|
{...listeners}
|
||||||
|
|||||||
@ -65,6 +65,8 @@ interface SortableProps<T> {
|
|||||||
className?: string
|
className?: string
|
||||||
/** Item list style */
|
/** Item list style */
|
||||||
listStyle?: React.CSSProperties
|
listStyle?: React.CSSProperties
|
||||||
|
/** Item style */
|
||||||
|
itemStyle?: React.CSSProperties
|
||||||
/** Item gap */
|
/** Item gap */
|
||||||
gap?: number | string
|
gap?: number | string
|
||||||
/** Restrictions, shortcuts for some modifiers */
|
/** Restrictions, shortcuts for some modifiers */
|
||||||
@ -91,6 +93,7 @@ function Sortable<T>({
|
|||||||
showGhost = false,
|
showGhost = false,
|
||||||
className,
|
className,
|
||||||
listStyle,
|
listStyle,
|
||||||
|
itemStyle,
|
||||||
gap,
|
gap,
|
||||||
restrictions,
|
restrictions,
|
||||||
modifiers: customModifiers
|
modifiers: customModifiers
|
||||||
@ -199,19 +202,19 @@ function Sortable<T>({
|
|||||||
renderItem={renderItem}
|
renderItem={renderItem}
|
||||||
useDragOverlay={useDragOverlay}
|
useDragOverlay={useDragOverlay}
|
||||||
showGhost={showGhost}
|
showGhost={showGhost}
|
||||||
|
itemStyle={itemStyle}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</ListWrapper>
|
</ListWrapper>
|
||||||
</SortableContext>
|
</SortableContext>
|
||||||
|
|
||||||
{useDragOverlay
|
{useDragOverlay &&
|
||||||
? createPortal(
|
createPortal(
|
||||||
<DragOverlay adjustScale dropAnimation={dropAnimation}>
|
<DragOverlay adjustScale dropAnimation={dropAnimation}>
|
||||||
{activeItem ? <ItemRenderer item={activeItem} renderItem={renderItem} dragOverlay /> : null}
|
{activeItem && <ItemRenderer item={activeItem} renderItem={renderItem} itemStyle={itemStyle} dragOverlay />}
|
||||||
</DragOverlay>,
|
</DragOverlay>,
|
||||||
document.body
|
document.body
|
||||||
)
|
)}
|
||||||
: null}
|
|
||||||
</DndContext>
|
</DndContext>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,6 +10,7 @@ interface SortableItemProps<T> {
|
|||||||
renderItem: RenderItemType<T>
|
renderItem: RenderItemType<T>
|
||||||
useDragOverlay?: boolean
|
useDragOverlay?: boolean
|
||||||
showGhost?: boolean
|
showGhost?: boolean
|
||||||
|
itemStyle?: React.CSSProperties
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SortableItem<T>({
|
export function SortableItem<T>({
|
||||||
@ -18,7 +19,8 @@ export function SortableItem<T>({
|
|||||||
index,
|
index,
|
||||||
renderItem,
|
renderItem,
|
||||||
useDragOverlay = true,
|
useDragOverlay = true,
|
||||||
showGhost = true
|
showGhost = true,
|
||||||
|
itemStyle
|
||||||
}: SortableItemProps<T>) {
|
}: SortableItemProps<T>) {
|
||||||
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
|
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
|
||||||
id
|
id
|
||||||
@ -36,6 +38,7 @@ export function SortableItem<T>({
|
|||||||
transform={transform}
|
transform={transform}
|
||||||
transition={transition}
|
transition={transition}
|
||||||
listeners={listeners}
|
listeners={listeners}
|
||||||
|
itemStyle={itemStyle}
|
||||||
{...attributes}
|
{...attributes}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
|
import { isNewApiProvider } from '@renderer/config/providers'
|
||||||
import type { Provider } from '@renderer/types'
|
import type { Provider } from '@renderer/types'
|
||||||
|
|
||||||
import { AihubmixAPIClient } from './aihubmix/AihubmixAPIClient'
|
import { AihubmixAPIClient } from './aihubmix/AihubmixAPIClient'
|
||||||
@ -45,7 +46,7 @@ export class ApiClientFactory {
|
|||||||
return instance
|
return instance
|
||||||
}
|
}
|
||||||
|
|
||||||
if (provider.id === 'new-api') {
|
if (isNewApiProvider(provider)) {
|
||||||
logger.debug(`Creating NewAPIClient for provider: ${provider.id}`)
|
logger.debug(`Creating NewAPIClient for provider: ${provider.id}`)
|
||||||
instance = new NewAPIClient(provider) as BaseApiClient
|
instance = new NewAPIClient(provider) as BaseApiClient
|
||||||
return instance
|
return instance
|
||||||
|
|||||||
@ -67,7 +67,9 @@ vi.mock('@renderer/config/models', () => ({
|
|||||||
silicon: [],
|
silicon: [],
|
||||||
defaultModel: []
|
defaultModel: []
|
||||||
},
|
},
|
||||||
isOpenAIModel: vi.fn(() => false)
|
isOpenAIModel: vi.fn(() => false),
|
||||||
|
glm45FlashModel: {},
|
||||||
|
qwen38bModel: {}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
describe('ApiClientFactory', () => {
|
describe('ApiClientFactory', () => {
|
||||||
|
|||||||
@ -35,18 +35,8 @@ vi.mock('@renderer/config/models', () => ({
|
|||||||
findTokenLimit: vi.fn().mockReturnValue(4096),
|
findTokenLimit: vi.fn().mockReturnValue(4096),
|
||||||
isFunctionCallingModel: vi.fn().mockReturnValue(false),
|
isFunctionCallingModel: vi.fn().mockReturnValue(false),
|
||||||
DEFAULT_MAX_TOKENS: 4096,
|
DEFAULT_MAX_TOKENS: 4096,
|
||||||
qwen38bModel: {
|
qwen38bModel: {},
|
||||||
id: 'Qwen/Qwen3-8B',
|
glm45FlashModel: {}
|
||||||
name: 'Qwen3-8B',
|
|
||||||
provider: 'cherryai',
|
|
||||||
group: 'Qwen'
|
|
||||||
},
|
|
||||||
glm45FlashModel: {
|
|
||||||
id: 'glm-4.5-flash',
|
|
||||||
name: 'GLM-4.5-Flash',
|
|
||||||
provider: 'cherryai',
|
|
||||||
group: 'GLM-4.5'
|
|
||||||
}
|
|
||||||
}))
|
}))
|
||||||
|
|
||||||
vi.mock('@renderer/services/AssistantService', () => ({
|
vi.mock('@renderer/services/AssistantService', () => ({
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import {
|
|||||||
type ProviderSettingsMap
|
type ProviderSettingsMap
|
||||||
} from '@cherrystudio/ai-core/provider'
|
} from '@cherrystudio/ai-core/provider'
|
||||||
import { isOpenAIChatCompletionOnlyModel } from '@renderer/config/models'
|
import { isOpenAIChatCompletionOnlyModel } from '@renderer/config/models'
|
||||||
|
import { isNewApiProvider } from '@renderer/config/providers'
|
||||||
import {
|
import {
|
||||||
getAwsBedrockAccessKeyId,
|
getAwsBedrockAccessKeyId,
|
||||||
getAwsBedrockRegion,
|
getAwsBedrockRegion,
|
||||||
@ -65,7 +66,7 @@ function handleSpecialProviders(model: Model, provider: Provider): Provider {
|
|||||||
if (provider.id === 'aihubmix') {
|
if (provider.id === 'aihubmix') {
|
||||||
return aihubmixProviderCreator(model, provider)
|
return aihubmixProviderCreator(model, provider)
|
||||||
}
|
}
|
||||||
if (provider.id === 'new-api') {
|
if (isNewApiProvider(provider)) {
|
||||||
return newApiResolverCreator(model, provider)
|
return newApiResolverCreator(model, provider)
|
||||||
}
|
}
|
||||||
if (provider.id === 'vertexai') {
|
if (provider.id === 'vertexai') {
|
||||||
|
|||||||
@ -113,6 +113,8 @@ export function getReasoningEffort(assistant: Assistant, model: Model): Reasonin
|
|||||||
return {
|
return {
|
||||||
enable_thinking: true
|
enable_thinking: true
|
||||||
}
|
}
|
||||||
|
case SystemProviderIds.hunyuan:
|
||||||
|
case SystemProviderIds['tencent-cloud-ti']:
|
||||||
case SystemProviderIds.doubao:
|
case SystemProviderIds.doubao:
|
||||||
return {
|
return {
|
||||||
thinking: {
|
thinking: {
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import { DEFAULT_MIN_APPS } from '@renderer/config/minapps'
|
import { DEFAULT_MIN_APPS } from '@renderer/config/minapps'
|
||||||
import type { MinAppType } from '@renderer/types'
|
import type { MinAppType } from '@renderer/types'
|
||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import styled from 'styled-components'
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
app: MinAppType
|
app: MinAppType
|
||||||
@ -11,31 +10,52 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const MinAppIcon: FC<Props> = ({ app, size = 48, style, sidebar = false }) => {
|
const MinAppIcon: FC<Props> = ({ app, size = 48, style, sidebar = false }) => {
|
||||||
|
// First try to find in DEFAULT_MIN_APPS for predefined styling
|
||||||
const _app = DEFAULT_MIN_APPS.find((item) => item.id === app.id)
|
const _app = DEFAULT_MIN_APPS.find((item) => item.id === app.id)
|
||||||
|
|
||||||
if (!_app) {
|
// If found in DEFAULT_MIN_APPS, use predefined styling
|
||||||
return null
|
if (_app) {
|
||||||
|
return (
|
||||||
|
<img
|
||||||
|
src={_app.logo}
|
||||||
|
className="select-none rounded-2xl"
|
||||||
|
style={{
|
||||||
|
border: _app.bodered ? '0.5px solid var(--color-border)' : 'none',
|
||||||
|
width: `${size}px`,
|
||||||
|
height: `${size}px`,
|
||||||
|
backgroundColor: _app.background,
|
||||||
|
userSelect: 'none',
|
||||||
|
...(sidebar ? {} : app.style),
|
||||||
|
...style
|
||||||
|
}}
|
||||||
|
draggable={false}
|
||||||
|
alt={app.name || 'MinApp Icon'}
|
||||||
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
// If not found in DEFAULT_MIN_APPS but app has logo, use it (for temporary apps)
|
||||||
<Container
|
if (app.logo) {
|
||||||
src={_app.logo}
|
return (
|
||||||
style={{
|
<img
|
||||||
border: _app.bodered ? '0.5px solid var(--color-border)' : 'none',
|
src={app.logo}
|
||||||
width: `${size}px`,
|
className="select-none rounded-2xl"
|
||||||
height: `${size}px`,
|
style={{
|
||||||
backgroundColor: _app.background,
|
border: 'none',
|
||||||
...(sidebar ? {} : app.style),
|
width: `${size}px`,
|
||||||
...style
|
height: `${size}px`,
|
||||||
}}
|
backgroundColor: 'transparent',
|
||||||
/>
|
userSelect: 'none',
|
||||||
)
|
...(sidebar ? {} : app.style),
|
||||||
|
...style
|
||||||
|
}}
|
||||||
|
draggable={false}
|
||||||
|
alt={app.name || 'MinApp Icon'}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const Container = styled.img`
|
|
||||||
border-radius: 16px;
|
|
||||||
user-select: none;
|
|
||||||
-webkit-user-drag: none;
|
|
||||||
`
|
|
||||||
|
|
||||||
export default MinAppIcon
|
export default MinAppIcon
|
||||||
|
|||||||
@ -1,15 +1,11 @@
|
|||||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||||
|
|
||||||
exports[`MinAppIcon > should render correctly with various props 1`] = `
|
exports[`MinAppIcon > should render correctly with various props 1`] = `
|
||||||
.c0 {
|
|
||||||
border-radius: 16px;
|
|
||||||
user-select: none;
|
|
||||||
-webkit-user-drag: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
<img
|
<img
|
||||||
class="c0"
|
alt="Test App"
|
||||||
|
class="select-none rounded-2xl"
|
||||||
|
draggable="false"
|
||||||
src="/test-logo-1.png"
|
src="/test-logo-1.png"
|
||||||
style="border: 0.5px solid var(--color-border); width: 64px; height: 64px; background-color: rgb(240, 240, 240); opacity: 0.8; transform: scale(1.1); margin-top: 10px;"
|
style="border: 0.5px solid var(--color-border); width: 64px; height: 64px; background-color: rgb(240, 240, 240); user-select: none; opacity: 0.8; transform: scale(1.1); margin-top: 10px;"
|
||||||
/>
|
/>
|
||||||
`;
|
`;
|
||||||
|
|||||||
@ -181,7 +181,7 @@ const PopupContainer: React.FC<Props> = ({ model, filter: baseFilter, showTagFil
|
|||||||
key: `provider-${p.id}`,
|
key: `provider-${p.id}`,
|
||||||
type: 'group',
|
type: 'group',
|
||||||
name: getFancyProviderName(p),
|
name: getFancyProviderName(p),
|
||||||
actions: (
|
actions: p.id !== 'cherryai' && (
|
||||||
<Tooltip title={t('navigate.provider_settings')} mouseEnterDelay={0.5} mouseLeaveDelay={0}>
|
<Tooltip title={t('navigate.provider_settings')} mouseEnterDelay={0.5} mouseLeaveDelay={0}>
|
||||||
<Settings2
|
<Settings2
|
||||||
size={12}
|
size={12}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { PlusOutlined } from '@ant-design/icons'
|
import { PlusOutlined } from '@ant-design/icons'
|
||||||
import { Sortable, useDndReorder } from '@cherrystudio/ui'
|
import { Sortable, useDndReorder } from '@cherrystudio/ui'
|
||||||
|
import { loggerService } from '@logger'
|
||||||
import HorizontalScrollContainer from '@renderer/components/HorizontalScrollContainer'
|
import HorizontalScrollContainer from '@renderer/components/HorizontalScrollContainer'
|
||||||
import { isMac } from '@renderer/config/constant'
|
import { isMac } from '@renderer/config/constant'
|
||||||
import { DEFAULT_MIN_APPS } from '@renderer/config/minapps'
|
import { DEFAULT_MIN_APPS } from '@renderer/config/minapps'
|
||||||
@ -12,9 +13,11 @@ import tabsService from '@renderer/services/TabsService'
|
|||||||
import { useAppDispatch, useAppSelector } from '@renderer/store'
|
import { useAppDispatch, useAppSelector } from '@renderer/store'
|
||||||
import type { Tab } from '@renderer/store/tabs'
|
import type { Tab } from '@renderer/store/tabs'
|
||||||
import { addTab, removeTab, setActiveTab, setTabs } from '@renderer/store/tabs'
|
import { addTab, removeTab, setActiveTab, setTabs } from '@renderer/store/tabs'
|
||||||
|
import type { MinAppType } from '@renderer/types'
|
||||||
import { classNames } from '@renderer/utils'
|
import { classNames } from '@renderer/utils'
|
||||||
import { ThemeMode } from '@shared/data/preference/preferenceTypes'
|
import { ThemeMode } from '@shared/data/preference/preferenceTypes'
|
||||||
import { Tooltip } from 'antd'
|
import { Tooltip } from 'antd'
|
||||||
|
import type { LRUCache } from 'lru-cache'
|
||||||
import {
|
import {
|
||||||
FileSearch,
|
FileSearch,
|
||||||
Folder,
|
Folder,
|
||||||
@ -45,14 +48,40 @@ interface TabsContainerProps {
|
|||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}
|
}
|
||||||
|
|
||||||
const getTabIcon = (tabId: string, minapps: any[]): React.ReactNode | undefined => {
|
const logger = loggerService.withContext('TabContainer')
|
||||||
|
|
||||||
|
const getTabIcon = (
|
||||||
|
tabId: string,
|
||||||
|
minapps: MinAppType[],
|
||||||
|
minAppsCache?: LRUCache<string, MinAppType>
|
||||||
|
): React.ReactNode | undefined => {
|
||||||
// Check if it's a minapp tab (format: apps:appId)
|
// Check if it's a minapp tab (format: apps:appId)
|
||||||
if (tabId.startsWith('apps:')) {
|
if (tabId.startsWith('apps:')) {
|
||||||
const appId = tabId.replace('apps:', '')
|
const appId = tabId.replace('apps:', '')
|
||||||
const app = [...DEFAULT_MIN_APPS, ...minapps].find((app) => app.id === appId)
|
let app = [...DEFAULT_MIN_APPS, ...minapps].find((app) => app.id === appId)
|
||||||
|
|
||||||
|
// If not found in permanent apps, search in temporary apps cache
|
||||||
|
// The cache stores apps opened via openSmartMinapp() for top navbar mode
|
||||||
|
// These are temporary MinApps that were opened but not yet saved to user's config
|
||||||
|
// The cache is LRU (Least Recently Used) with max size from settings
|
||||||
|
// Cache validity: Apps in cache are currently active/recently used, not outdated
|
||||||
|
if (!app && minAppsCache) {
|
||||||
|
app = minAppsCache.get(appId)
|
||||||
|
|
||||||
|
// Defensive programming: If app not found in cache but tab exists,
|
||||||
|
// the cache entry may have been evicted due to LRU policy
|
||||||
|
// Log warning for debugging potential sync issues
|
||||||
|
if (!app) {
|
||||||
|
logger.warn(`MinApp ${appId} not found in cache, using fallback icon`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (app) {
|
if (app) {
|
||||||
return <MinAppIcon size={14} app={app} />
|
return <MinAppIcon size={14} app={app} />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fallback: If no app found (cache evicted), show default icon
|
||||||
|
return <LayoutGrid size={14} />
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (tabId) {
|
switch (tabId) {
|
||||||
@ -94,7 +123,7 @@ const TabsContainer: React.FC<TabsContainerProps> = ({ children }) => {
|
|||||||
const activeTabId = useAppSelector((state) => state.tabs.activeTabId)
|
const activeTabId = useAppSelector((state) => state.tabs.activeTabId)
|
||||||
const isFullscreen = useFullscreen()
|
const isFullscreen = useFullscreen()
|
||||||
const { settedTheme, toggleTheme } = useTheme()
|
const { settedTheme, toggleTheme } = useTheme()
|
||||||
const { hideMinappPopup } = useMinappPopup()
|
const { hideMinappPopup, minAppsCache } = useMinappPopup()
|
||||||
const { minapps } = useMinapps()
|
const { minapps } = useMinapps()
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
@ -112,8 +141,23 @@ const TabsContainer: React.FC<TabsContainerProps> = ({ children }) => {
|
|||||||
// Check if it's a minapp tab
|
// Check if it's a minapp tab
|
||||||
if (tabId.startsWith('apps:')) {
|
if (tabId.startsWith('apps:')) {
|
||||||
const appId = tabId.replace('apps:', '')
|
const appId = tabId.replace('apps:', '')
|
||||||
const app = [...DEFAULT_MIN_APPS, ...minapps].find((app) => app.id === appId)
|
let app = [...DEFAULT_MIN_APPS, ...minapps].find((app) => app.id === appId)
|
||||||
return app ? app.name : 'MinApp'
|
|
||||||
|
// If not found in permanent apps, search in temporary apps cache
|
||||||
|
// This ensures temporary MinApps display proper titles while being used
|
||||||
|
// The LRU cache automatically manages app lifecycle and prevents memory leaks
|
||||||
|
if (!app && minAppsCache) {
|
||||||
|
app = minAppsCache.get(appId)
|
||||||
|
|
||||||
|
// Defensive programming: If app not found in cache but tab exists,
|
||||||
|
// the cache entry may have been evicted due to LRU policy
|
||||||
|
if (!app) {
|
||||||
|
logger.warn(`MinApp ${appId} not found in cache, using fallback title`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return app name if found, otherwise use fallback with appId
|
||||||
|
return app ? app.name : `MinApp-${appId}`
|
||||||
}
|
}
|
||||||
return getTitleLabel(tabId)
|
return getTitleLabel(tabId)
|
||||||
}
|
}
|
||||||
@ -196,7 +240,7 @@ const TabsContainer: React.FC<TabsContainerProps> = ({ children }) => {
|
|||||||
renderItem={(tab) => (
|
renderItem={(tab) => (
|
||||||
<Tab key={tab.id} active={tab.id === activeTabId} onClick={() => handleTabClick(tab)}>
|
<Tab key={tab.id} active={tab.id === activeTabId} onClick={() => handleTabClick(tab)}>
|
||||||
<TabHeader>
|
<TabHeader>
|
||||||
{tab.id && <TabIcon>{getTabIcon(tab.id, minapps)}</TabIcon>}
|
{tab.id && <TabIcon>{getTabIcon(tab.id, minapps, minAppsCache)}</TabIcon>}
|
||||||
<TabTitle>{getTabTitle(tab.id)}</TabTitle>
|
<TabTitle>{getTabTitle(tab.id)}</TabTitle>
|
||||||
</TabHeader>
|
</TabHeader>
|
||||||
{tab.id !== 'home' && (
|
{tab.id !== 'home' && (
|
||||||
@ -259,7 +303,7 @@ const TabsBar = styled.div<{ $isFullscreen: boolean }>`
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 5px;
|
gap: 5px;
|
||||||
padding-left: ${({ $isFullscreen }) => (!$isFullscreen && isMac ? 'env(titlebar-area-x)' : '15px')};
|
padding-left: ${({ $isFullscreen }) => (!$isFullscreen && isMac ? 'calc(env(titlebar-area-x) + 4px)' : '15px')};
|
||||||
padding-right: ${({ $isFullscreen }) => ($isFullscreen ? '12px' : '0')};
|
padding-right: ${({ $isFullscreen }) => ($isFullscreen ? '12px' : '0')};
|
||||||
height: var(--navbar-height);
|
height: var(--navbar-height);
|
||||||
min-height: ${({ $isFullscreen }) => (!$isFullscreen && isMac ? 'env(titlebar-area-height)' : '')};
|
min-height: ${({ $isFullscreen }) => (!$isFullscreen && isMac ? 'env(titlebar-area-height)' : '')};
|
||||||
|
|||||||
@ -88,6 +88,7 @@ const NavbarCenterContainer = styled.div`
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0 ${isMac ? '20px' : 0};
|
padding: 0 ${isMac ? '20px' : 0};
|
||||||
|
padding-left: 10px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: var(--color-text-1);
|
color: var(--color-text-1);
|
||||||
position: relative;
|
position: relative;
|
||||||
@ -108,7 +109,8 @@ const NavbarMainContainer = styled.div<{ $isFullscreen: boolean }>`
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 0 ${isMac ? '20px' : 0};
|
padding-right: ${isMac ? '20px' : 0};
|
||||||
|
padding-left: 10px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: var(--color-text-1);
|
color: var(--color-text-1);
|
||||||
padding-right: ${({ $isFullscreen }) => ($isFullscreen ? '12px' : isWin ? '140px' : isLinux ? '120px' : '12px')};
|
padding-right: ${({ $isFullscreen }) => ($isFullscreen ? '12px' : isWin ? '140px' : isLinux ? '120px' : '12px')};
|
||||||
|
|||||||
@ -93,7 +93,17 @@ export function isSupportedThinkingTokenModel(model?: Model): boolean {
|
|||||||
// Specifically for DeepSeek V3.1. White list for now
|
// Specifically for DeepSeek V3.1. White list for now
|
||||||
if (isDeepSeekHybridInferenceModel(model)) {
|
if (isDeepSeekHybridInferenceModel(model)) {
|
||||||
return (
|
return (
|
||||||
['openrouter', 'dashscope', 'modelscope', 'doubao', 'silicon', 'nvidia', 'ppio'] satisfies SystemProviderId[]
|
[
|
||||||
|
'openrouter',
|
||||||
|
'dashscope',
|
||||||
|
'modelscope',
|
||||||
|
'doubao',
|
||||||
|
'silicon',
|
||||||
|
'nvidia',
|
||||||
|
'ppio',
|
||||||
|
'hunyuan',
|
||||||
|
'tencent-cloud-ti'
|
||||||
|
] satisfies SystemProviderId[]
|
||||||
).some((id) => id === model.provider)
|
).some((id) => id === model.provider)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -131,16 +131,6 @@ export const SYSTEM_PROVIDERS_CONFIG: Record<SystemProviderId, SystemProvider> =
|
|||||||
isSystem: true,
|
isSystem: true,
|
||||||
enabled: false
|
enabled: false
|
||||||
},
|
},
|
||||||
ppio: {
|
|
||||||
id: 'ppio',
|
|
||||||
name: 'PPIO',
|
|
||||||
type: 'openai',
|
|
||||||
apiKey: '',
|
|
||||||
apiHost: 'https://api.ppinfra.com/v3/openai/',
|
|
||||||
models: SYSTEM_MODELS.ppio,
|
|
||||||
isSystem: true,
|
|
||||||
enabled: false
|
|
||||||
},
|
|
||||||
alayanew: {
|
alayanew: {
|
||||||
id: 'alayanew',
|
id: 'alayanew',
|
||||||
name: 'AlayaNew',
|
name: 'AlayaNew',
|
||||||
@ -151,16 +141,6 @@ export const SYSTEM_PROVIDERS_CONFIG: Record<SystemProviderId, SystemProvider> =
|
|||||||
isSystem: true,
|
isSystem: true,
|
||||||
enabled: false
|
enabled: false
|
||||||
},
|
},
|
||||||
qiniu: {
|
|
||||||
id: 'qiniu',
|
|
||||||
name: 'Qiniu',
|
|
||||||
type: 'openai',
|
|
||||||
apiKey: '',
|
|
||||||
apiHost: 'https://api.qnaigc.com',
|
|
||||||
models: SYSTEM_MODELS.qiniu,
|
|
||||||
isSystem: true,
|
|
||||||
enabled: false
|
|
||||||
},
|
|
||||||
dmxapi: {
|
dmxapi: {
|
||||||
id: 'dmxapi',
|
id: 'dmxapi',
|
||||||
name: 'DMXAPI',
|
name: 'DMXAPI',
|
||||||
@ -171,6 +151,16 @@ export const SYSTEM_PROVIDERS_CONFIG: Record<SystemProviderId, SystemProvider> =
|
|||||||
isSystem: true,
|
isSystem: true,
|
||||||
enabled: false
|
enabled: false
|
||||||
},
|
},
|
||||||
|
aionly: {
|
||||||
|
id: 'aionly',
|
||||||
|
name: 'AIOnly',
|
||||||
|
type: 'openai',
|
||||||
|
apiKey: '',
|
||||||
|
apiHost: 'https://api.aiionly.com',
|
||||||
|
models: SYSTEM_MODELS.aionly,
|
||||||
|
isSystem: true,
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
burncloud: {
|
burncloud: {
|
||||||
id: 'burncloud',
|
id: 'burncloud',
|
||||||
name: 'BurnCloud',
|
name: 'BurnCloud',
|
||||||
@ -231,6 +221,26 @@ export const SYSTEM_PROVIDERS_CONFIG: Record<SystemProviderId, SystemProvider> =
|
|||||||
isSystem: true,
|
isSystem: true,
|
||||||
enabled: false
|
enabled: false
|
||||||
},
|
},
|
||||||
|
ppio: {
|
||||||
|
id: 'ppio',
|
||||||
|
name: 'PPIO',
|
||||||
|
type: 'openai',
|
||||||
|
apiKey: '',
|
||||||
|
apiHost: 'https://api.ppinfra.com/v3/openai/',
|
||||||
|
models: SYSTEM_MODELS.ppio,
|
||||||
|
isSystem: true,
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
|
qiniu: {
|
||||||
|
id: 'qiniu',
|
||||||
|
name: 'Qiniu',
|
||||||
|
type: 'openai',
|
||||||
|
apiKey: '',
|
||||||
|
apiHost: 'https://api.qnaigc.com',
|
||||||
|
models: SYSTEM_MODELS.qiniu,
|
||||||
|
isSystem: true,
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
openrouter: {
|
openrouter: {
|
||||||
id: 'openrouter',
|
id: 'openrouter',
|
||||||
name: 'OpenRouter',
|
name: 'OpenRouter',
|
||||||
@ -605,16 +615,6 @@ export const SYSTEM_PROVIDERS_CONFIG: Record<SystemProviderId, SystemProvider> =
|
|||||||
models: SYSTEM_MODELS['poe'],
|
models: SYSTEM_MODELS['poe'],
|
||||||
isSystem: true,
|
isSystem: true,
|
||||||
enabled: false
|
enabled: false
|
||||||
},
|
|
||||||
aionly: {
|
|
||||||
id: 'aionly',
|
|
||||||
name: 'AIOnly',
|
|
||||||
type: 'openai',
|
|
||||||
apiKey: '',
|
|
||||||
apiHost: 'https://api.aiionly.com',
|
|
||||||
models: SYSTEM_MODELS.aionly,
|
|
||||||
isSystem: true,
|
|
||||||
enabled: false
|
|
||||||
}
|
}
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
@ -1368,3 +1368,7 @@ const SUPPORT_GEMINI_NATIVE_WEB_SEARCH_PROVIDERS = ['gemini', 'vertexai'] as con
|
|||||||
export const isGeminiWebSearchProvider = (provider: Provider) => {
|
export const isGeminiWebSearchProvider = (provider: Provider) => {
|
||||||
return SUPPORT_GEMINI_NATIVE_WEB_SEARCH_PROVIDERS.some((id) => id === provider.id)
|
return SUPPORT_GEMINI_NATIVE_WEB_SEARCH_PROVIDERS.some((id) => id === provider.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const isNewApiProvider = (provider: Provider) => {
|
||||||
|
return ['new-api', 'cherryin'].includes(provider.id)
|
||||||
|
}
|
||||||
|
|||||||
@ -1,12 +1,15 @@
|
|||||||
import { usePreference } from '@data/hooks/usePreference'
|
import { usePreference } from '@data/hooks/usePreference'
|
||||||
import { DEFAULT_MIN_APPS } from '@renderer/config/minapps'
|
import { DEFAULT_MIN_APPS } from '@renderer/config/minapps'
|
||||||
import { useMinapps } from '@renderer/hooks/useMinapps'
|
import { useMinapps } from '@renderer/hooks/useMinapps'
|
||||||
|
import NavigationService from '@renderer/services/NavigationService'
|
||||||
import TabsService from '@renderer/services/TabsService'
|
import TabsService from '@renderer/services/TabsService'
|
||||||
import type { MinAppType } from '@renderer/types'
|
import type { MinAppType } from '@renderer/types'
|
||||||
import { clearWebviewState } from '@renderer/utils/webviewStateManager'
|
import { clearWebviewState } from '@renderer/utils/webviewStateManager'
|
||||||
import { LRUCache } from 'lru-cache'
|
import { LRUCache } from 'lru-cache'
|
||||||
import { useCallback } from 'react'
|
import { useCallback } from 'react'
|
||||||
|
|
||||||
|
import { useNavbarPosition } from './useNavbar'
|
||||||
|
|
||||||
let minAppsCache: LRUCache<string, MinAppType>
|
let minAppsCache: LRUCache<string, MinAppType>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -34,6 +37,7 @@ export const useMinappPopup = () => {
|
|||||||
setMinappShow
|
setMinappShow
|
||||||
} = useMinapps()
|
} = useMinapps()
|
||||||
const [maxKeepAliveMinapps] = usePreference('feature.minapp.max_keep_alive')
|
const [maxKeepAliveMinapps] = usePreference('feature.minapp.max_keep_alive')
|
||||||
|
const { isTopNavbar } = useNavbarPosition()
|
||||||
|
|
||||||
const createLRUCache = useCallback(() => {
|
const createLRUCache = useCallback(() => {
|
||||||
return new LRUCache<string, MinAppType>({
|
return new LRUCache<string, MinAppType>({
|
||||||
@ -165,6 +169,33 @@ export const useMinappPopup = () => {
|
|||||||
setMinappShow(false)
|
setMinappShow(false)
|
||||||
}, [minappShow, openedOneOffMinapp, setOpenedOneOffMinapp, setCurrentMinappId, setMinappShow])
|
}, [minappShow, openedOneOffMinapp, setOpenedOneOffMinapp, setCurrentMinappId, setMinappShow])
|
||||||
|
|
||||||
|
/** Smart open minapp that adapts to navbar position */
|
||||||
|
const openSmartMinapp = useCallback(
|
||||||
|
(config: MinAppType, keepAlive: boolean = false) => {
|
||||||
|
if (isTopNavbar) {
|
||||||
|
// For top navbar mode, need to add to cache first for temporary apps
|
||||||
|
const cacheApp = minAppsCache.get(config.id)
|
||||||
|
if (!cacheApp) {
|
||||||
|
// Add temporary app to cache so MinAppPage can find it
|
||||||
|
minAppsCache.set(config.id, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set current minapp and show state
|
||||||
|
setCurrentMinappId(config.id)
|
||||||
|
setMinappShow(true)
|
||||||
|
|
||||||
|
// Then navigate to the app tab using NavigationService
|
||||||
|
if (NavigationService.navigate) {
|
||||||
|
NavigationService.navigate(`/apps/${config.id}`)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// For side navbar, use the traditional popup system
|
||||||
|
openMinapp(config, keepAlive)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[isTopNavbar, openMinapp, setCurrentMinappId, setMinappShow]
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
openMinapp,
|
openMinapp,
|
||||||
openMinappKeepAlive,
|
openMinappKeepAlive,
|
||||||
@ -172,6 +203,7 @@ export const useMinappPopup = () => {
|
|||||||
closeMinapp,
|
closeMinapp,
|
||||||
hideMinappPopup,
|
hideMinappPopup,
|
||||||
closeAllMinapps,
|
closeAllMinapps,
|
||||||
|
openSmartMinapp,
|
||||||
// Expose cache instance for TabsService integration
|
// Expose cache instance for TabsService integration
|
||||||
minAppsCache
|
minAppsCache
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { RowFlex } from '@cherrystudio/ui'
|
|||||||
import { usePreference } from '@data/hooks/usePreference'
|
import { usePreference } from '@data/hooks/usePreference'
|
||||||
import { Navbar, NavbarLeft, NavbarRight } from '@renderer/components/app/Navbar'
|
import { Navbar, NavbarLeft, NavbarRight } from '@renderer/components/app/Navbar'
|
||||||
import SearchPopup from '@renderer/components/Popups/SearchPopup'
|
import SearchPopup from '@renderer/components/Popups/SearchPopup'
|
||||||
import { isLinux, isWin } from '@renderer/config/constant'
|
import { isLinux, isMac, isWin } from '@renderer/config/constant'
|
||||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||||
import { modelGenerating } from '@renderer/hooks/useModel'
|
import { modelGenerating } from '@renderer/hooks/useModel'
|
||||||
import { useShortcut } from '@renderer/hooks/useShortcuts'
|
import { useShortcut } from '@renderer/hooks/useShortcuts'
|
||||||
@ -84,7 +84,14 @@ const HeaderNavbar: FC<Props> = ({ activeAssistant, setActiveAssistant, activeTo
|
|||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
{!showAssistants && (
|
{!showAssistants && (
|
||||||
<NavbarLeft style={{ justifyContent: 'flex-start', borderRight: 'none', padding: '0 10px', minWidth: 'auto' }}>
|
<NavbarLeft
|
||||||
|
style={{
|
||||||
|
justifyContent: 'flex-start',
|
||||||
|
borderRight: 'none',
|
||||||
|
paddingLeft: 0,
|
||||||
|
paddingRight: 10,
|
||||||
|
minWidth: 'auto'
|
||||||
|
}}>
|
||||||
<Tooltip title={t('navbar.show_sidebar')} mouseEnterDelay={0.8}>
|
<Tooltip title={t('navbar.show_sidebar')} mouseEnterDelay={0.8}>
|
||||||
<NavbarIcon onClick={() => toggleShowAssistants()}>
|
<NavbarIcon onClick={() => toggleShowAssistants()}>
|
||||||
<PanelRightClose size={18} />
|
<PanelRightClose size={18} />
|
||||||
@ -104,7 +111,7 @@ const HeaderNavbar: FC<Props> = ({ activeAssistant, setActiveAssistant, activeTo
|
|||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
</NavbarLeft>
|
</NavbarLeft>
|
||||||
)}
|
)}
|
||||||
<RowFlex className="items-center gap-1.5">
|
<RowFlex className="items-center gap-1.5" style={{ marginLeft: !isMac ? 16 : 0 }}>
|
||||||
<SelectModelButton assistant={assistant} />
|
<SelectModelButton assistant={assistant} />
|
||||||
</RowFlex>
|
</RowFlex>
|
||||||
<NavbarRight
|
<NavbarRight
|
||||||
@ -112,7 +119,7 @@ const HeaderNavbar: FC<Props> = ({ activeAssistant, setActiveAssistant, activeTo
|
|||||||
justifyContent: 'flex-end',
|
justifyContent: 'flex-end',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
paddingRight: isWin || isLinux ? '144px' : '6px'
|
paddingRight: isWin || isLinux ? '144px' : '15px'
|
||||||
}}
|
}}
|
||||||
className="home-navbar-right">
|
className="home-navbar-right">
|
||||||
<RowFlex className="items-center gap-1.5">
|
<RowFlex className="items-center gap-1.5">
|
||||||
|
|||||||
@ -394,7 +394,7 @@ const SettingsTab: FC<Props> = (props) => {
|
|||||||
<SettingDivider />
|
<SettingDivider />
|
||||||
</SettingGroup>
|
</SettingGroup>
|
||||||
</CollapsibleSettingGroup>
|
</CollapsibleSettingGroup>
|
||||||
<CollapsibleSettingGroup title={t('settings.math.title')} defaultExpanded={true}>
|
<CollapsibleSettingGroup title={t('settings.math.title')} defaultExpanded={false}>
|
||||||
<SettingGroup>
|
<SettingGroup>
|
||||||
<SettingRow>
|
<SettingRow>
|
||||||
{/* <SettingRowTitleSmall>{t('settings.math.engine.label')}</SettingRowTitleSmall> */}
|
{/* <SettingRowTitleSmall>{t('settings.math.engine.label')}</SettingRowTitleSmall> */}
|
||||||
@ -421,7 +421,7 @@ const SettingsTab: FC<Props> = (props) => {
|
|||||||
<SettingDivider />
|
<SettingDivider />
|
||||||
</SettingGroup>
|
</SettingGroup>
|
||||||
</CollapsibleSettingGroup>
|
</CollapsibleSettingGroup>
|
||||||
<CollapsibleSettingGroup title={t('chat.settings.code.title')} defaultExpanded={true}>
|
<CollapsibleSettingGroup title={t('chat.settings.code.title')} defaultExpanded={false}>
|
||||||
<SettingGroup>
|
<SettingGroup>
|
||||||
<SettingRow>
|
<SettingRow>
|
||||||
{/* <SettingRowTitleSmall>{t('message.message.code_style')}</SettingRowTitleSmall> */}
|
{/* <SettingRowTitleSmall>{t('message.message.code_style')}</SettingRowTitleSmall> */}
|
||||||
@ -551,7 +551,7 @@ const SettingsTab: FC<Props> = (props) => {
|
|||||||
</SettingGroup>
|
</SettingGroup>
|
||||||
<SettingDivider />
|
<SettingDivider />
|
||||||
</CollapsibleSettingGroup>
|
</CollapsibleSettingGroup>
|
||||||
<CollapsibleSettingGroup title={t('settings.messages.input.title')} defaultExpanded={true}>
|
<CollapsibleSettingGroup title={t('settings.messages.input.title')} defaultExpanded={false}>
|
||||||
<SettingGroup>
|
<SettingGroup>
|
||||||
<SettingRow>
|
<SettingRow>
|
||||||
<SettingRowTitleSmall>{t('settings.messages.input.show_estimated_tokens')}</SettingRowTitleSmall>
|
<SettingRowTitleSmall>{t('settings.messages.input.show_estimated_tokens')}</SettingRowTitleSmall>
|
||||||
|
|||||||
@ -45,11 +45,20 @@ const MinAppPage: FC = () => {
|
|||||||
}
|
}
|
||||||
}, [isTopNavbar])
|
}, [isTopNavbar])
|
||||||
|
|
||||||
// Find the app from all available apps
|
// Find the app from all available apps (including cached ones)
|
||||||
const app = useMemo(() => {
|
const app = useMemo(() => {
|
||||||
if (!appId) return null
|
if (!appId) return null
|
||||||
return [...DEFAULT_MIN_APPS, ...minapps].find((app) => app.id === appId)
|
|
||||||
}, [appId, minapps])
|
// First try to find in default and custom mini-apps
|
||||||
|
let foundApp = [...DEFAULT_MIN_APPS, ...minapps].find((app) => app.id === appId)
|
||||||
|
|
||||||
|
// If not found and we have cache, try to find in cache (for temporary apps)
|
||||||
|
if (!foundApp && minAppsCache) {
|
||||||
|
foundApp = minAppsCache.get(appId)
|
||||||
|
}
|
||||||
|
|
||||||
|
return foundApp
|
||||||
|
}, [appId, minapps, minAppsCache])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// If app not found, redirect to apps list
|
// If app not found, redirect to apps list
|
||||||
|
|||||||
@ -113,7 +113,7 @@ const NotesSidebar: FC<NotesSidebarProps> = ({
|
|||||||
const targetScrollTop = elementOffsetTop - (containerHeight - elementHeight) / 2
|
const targetScrollTop = elementOffsetTop - (containerHeight - elementHeight) / 2
|
||||||
scrollContainer.scrollTo({
|
scrollContainer.scrollTo({
|
||||||
top: Math.max(0, targetScrollTop),
|
top: Math.max(0, targetScrollTop),
|
||||||
behavior: 'smooth'
|
behavior: 'instant'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -305,7 +305,7 @@ const ZhipuPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const createNewPainting = () => {
|
const handleAddPainting = () => {
|
||||||
if (generating) return
|
if (generating) return
|
||||||
const newPainting = getNewPainting()
|
const newPainting = getNewPainting()
|
||||||
const addedPainting = addPainting('zhipu_paintings', newPainting)
|
const addedPainting = addPainting('zhipu_paintings', newPainting)
|
||||||
@ -340,12 +340,12 @@ const ZhipuPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<Navbar>
|
<Navbar>
|
||||||
<NavbarCenter>
|
<NavbarCenter style={{ borderRight: 'none' }}>{t('paintings.title')}</NavbarCenter>
|
||||||
<Title>{t('title.paintings')}</Title>
|
|
||||||
</NavbarCenter>
|
|
||||||
{isMac && (
|
{isMac && (
|
||||||
<NavbarRight>
|
<NavbarRight style={{ justifyContent: 'flex-end' }}>
|
||||||
<Button type="text" icon={<PlusOutlined />} onClick={createNewPainting} disabled={generating} />
|
<Button size="small" className="nodrag" icon={<PlusOutlined />} onClick={handleAddPainting}>
|
||||||
|
{t('paintings.button.new.image')}
|
||||||
|
</Button>
|
||||||
</NavbarRight>
|
</NavbarRight>
|
||||||
)}
|
)}
|
||||||
</Navbar>
|
</Navbar>
|
||||||
@ -480,7 +480,7 @@ const ZhipuPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
selectedPainting={painting}
|
selectedPainting={painting}
|
||||||
onSelectPainting={onSelectPainting}
|
onSelectPainting={onSelectPainting}
|
||||||
onDeletePainting={onDeletePainting}
|
onDeletePainting={onDeletePainting}
|
||||||
onNewPainting={createNewPainting}
|
onNewPainting={handleAddPainting}
|
||||||
/>
|
/>
|
||||||
</ContentContainer>
|
</ContentContainer>
|
||||||
</Container>
|
</Container>
|
||||||
@ -554,12 +554,6 @@ const ToolbarMenu = styled.div`
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
`
|
`
|
||||||
|
|
||||||
const Title = styled.h1`
|
|
||||||
margin: 0;
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 600;
|
|
||||||
`
|
|
||||||
|
|
||||||
const ProviderTitleContainer = styled.div`
|
const ProviderTitleContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|||||||
@ -16,7 +16,7 @@ import { UpgradeChannel } from '@shared/data/preference/preferenceTypes'
|
|||||||
import { ThemeMode } from '@shared/data/preference/preferenceTypes'
|
import { ThemeMode } from '@shared/data/preference/preferenceTypes'
|
||||||
import { Avatar, Button, Progress, Radio, Row, Tag, Tooltip } from 'antd'
|
import { Avatar, Button, Progress, Radio, Row, Tag, Tooltip } from 'antd'
|
||||||
import { debounce } from 'lodash'
|
import { debounce } from 'lodash'
|
||||||
import { Bug, FileCheck, Github, Globe, Mail, Rss } from 'lucide-react'
|
import { Bug, FileCheck, Globe, Mail, Rss } from 'lucide-react'
|
||||||
import { BadgeQuestionMark } from 'lucide-react'
|
import { BadgeQuestionMark } from 'lucide-react'
|
||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
@ -38,7 +38,7 @@ const AboutSettings: FC = () => {
|
|||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
// const dispatch = useAppDispatch()
|
// const dispatch = useAppDispatch()
|
||||||
// const { update } = useRuntime()
|
// const { update } = useRuntime()
|
||||||
const { openMinapp } = useMinappPopup()
|
const { openSmartMinapp } = useMinappPopup()
|
||||||
|
|
||||||
const { appUpdateState, updateAppUpdateState } = useAppUpdateState()
|
const { appUpdateState, updateAppUpdateState } = useAppUpdateState()
|
||||||
|
|
||||||
@ -87,7 +87,7 @@ const AboutSettings: FC = () => {
|
|||||||
|
|
||||||
const showLicense = async () => {
|
const showLicense = async () => {
|
||||||
const { appPath } = await window.api.getAppInfo()
|
const { appPath } = await window.api.getAppInfo()
|
||||||
openMinapp({
|
openSmartMinapp({
|
||||||
id: 'cherrystudio-license',
|
id: 'cherrystudio-license',
|
||||||
name: t('settings.about.license.title'),
|
name: t('settings.about.license.title'),
|
||||||
url: `file://${appPath}/resources/cherry-studio/license.html`,
|
url: `file://${appPath}/resources/cherry-studio/license.html`,
|
||||||
@ -97,7 +97,7 @@ const AboutSettings: FC = () => {
|
|||||||
|
|
||||||
const showReleases = async () => {
|
const showReleases = async () => {
|
||||||
const { appPath } = await window.api.getAppInfo()
|
const { appPath } = await window.api.getAppInfo()
|
||||||
openMinapp({
|
openSmartMinapp({
|
||||||
id: 'cherrystudio-releases',
|
id: 'cherrystudio-releases',
|
||||||
name: t('settings.about.releases.title'),
|
name: t('settings.about.releases.title'),
|
||||||
url: `file://${appPath}/resources/cherry-studio/releases.html?theme=${theme === ThemeMode.dark ? 'dark' : 'light'}`,
|
url: `file://${appPath}/resources/cherry-studio/releases.html?theme=${theme === ThemeMode.dark ? 'dark' : 'light'}`,
|
||||||
@ -313,7 +313,7 @@ const AboutSettings: FC = () => {
|
|||||||
<SettingDivider />
|
<SettingDivider />
|
||||||
<SettingRow>
|
<SettingRow>
|
||||||
<SettingRowTitle>
|
<SettingRowTitle>
|
||||||
<Github size={18} />
|
<GithubOutlined size={18} />
|
||||||
{t('settings.about.feedback.title')}
|
{t('settings.about.feedback.title')}
|
||||||
</SettingRowTitle>
|
</SettingRowTitle>
|
||||||
<Button onClick={() => onOpenWebsite('https://github.com/CherryHQ/cherry-studio/issues/new/choose')}>
|
<Button onClick={() => onOpenWebsite('https://github.com/CherryHQ/cherry-studio/issues/new/choose')}>
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { InfoCircleOutlined } from '@ant-design/icons'
|
|||||||
import { RowFlex } from '@cherrystudio/ui'
|
import { RowFlex } from '@cherrystudio/ui'
|
||||||
import { Switch } from '@cherrystudio/ui'
|
import { Switch } from '@cherrystudio/ui'
|
||||||
import { usePreference } from '@data/hooks/usePreference'
|
import { usePreference } from '@data/hooks/usePreference'
|
||||||
|
import { AppLogo } from '@renderer/config/env'
|
||||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||||
import { useMinappPopup } from '@renderer/hooks/useMinappPopup'
|
import { useMinappPopup } from '@renderer/hooks/useMinappPopup'
|
||||||
import { Button, Space, Tooltip } from 'antd'
|
import { Button, Space, Tooltip } from 'antd'
|
||||||
@ -18,7 +19,7 @@ const JoplinSettings: FC = () => {
|
|||||||
|
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const { openMinapp } = useMinappPopup()
|
const { openSmartMinapp } = useMinappPopup()
|
||||||
|
|
||||||
const handleJoplinTokenChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleJoplinTokenChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
setJoplinToken(e.target.value)
|
setJoplinToken(e.target.value)
|
||||||
@ -64,10 +65,11 @@ const JoplinSettings: FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleJoplinHelpClick = () => {
|
const handleJoplinHelpClick = () => {
|
||||||
openMinapp({
|
openSmartMinapp({
|
||||||
id: 'joplin-help',
|
id: 'joplin-help',
|
||||||
name: 'Joplin Help',
|
name: 'Joplin Help',
|
||||||
url: 'https://joplinapp.org/help/apps/clipper'
|
url: 'https://joplinapp.org/help/apps/clipper',
|
||||||
|
logo: AppLogo
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { RowFlex } from '@cherrystudio/ui'
|
|||||||
import { Switch } from '@cherrystudio/ui'
|
import { Switch } from '@cherrystudio/ui'
|
||||||
import { usePreference } from '@data/hooks/usePreference'
|
import { usePreference } from '@data/hooks/usePreference'
|
||||||
import { Client } from '@notionhq/client'
|
import { Client } from '@notionhq/client'
|
||||||
|
import { AppLogo } from '@renderer/config/env'
|
||||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||||
import { useMinappPopup } from '@renderer/hooks/useMinappPopup'
|
import { useMinappPopup } from '@renderer/hooks/useMinappPopup'
|
||||||
import { Button, Space, Tooltip } from 'antd'
|
import { Button, Space, Tooltip } from 'antd'
|
||||||
@ -19,7 +20,7 @@ const NotionSettings: FC = () => {
|
|||||||
|
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const { openMinapp } = useMinappPopup()
|
const { openSmartMinapp } = useMinappPopup()
|
||||||
|
|
||||||
const handleNotionTokenChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleNotionTokenChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
setNotionApiKey(e.target.value)
|
setNotionApiKey(e.target.value)
|
||||||
@ -60,10 +61,11 @@ const NotionSettings: FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleNotionTitleClick = () => {
|
const handleNotionTitleClick = () => {
|
||||||
openMinapp({
|
openSmartMinapp({
|
||||||
id: 'notion-help',
|
id: 'notion-help',
|
||||||
name: 'Notion Help',
|
name: 'Notion Help',
|
||||||
url: 'https://docs.cherry-ai.com/advanced-basic/notion'
|
url: 'https://docs.cherry-ai.com/advanced-basic/notion',
|
||||||
|
logo: AppLogo
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { usePreference } from '@data/hooks/usePreference'
|
|||||||
import { S3BackupManager } from '@renderer/components/S3BackupManager'
|
import { S3BackupManager } from '@renderer/components/S3BackupManager'
|
||||||
import { S3BackupModal, useS3BackupModal } from '@renderer/components/S3Modals'
|
import { S3BackupModal, useS3BackupModal } from '@renderer/components/S3Modals'
|
||||||
import Selector from '@renderer/components/Selector'
|
import Selector from '@renderer/components/Selector'
|
||||||
|
import { AppLogo } from '@renderer/config/env'
|
||||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||||
import { useMinappPopup } from '@renderer/hooks/useMinappPopup'
|
import { useMinappPopup } from '@renderer/hooks/useMinappPopup'
|
||||||
import { startAutoSync, stopAutoSync } from '@renderer/services/BackupService'
|
import { startAutoSync, stopAutoSync } from '@renderer/services/BackupService'
|
||||||
@ -33,7 +34,7 @@ const S3Settings: FC = () => {
|
|||||||
|
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { openMinapp } = useMinappPopup()
|
const { openSmartMinapp } = useMinappPopup()
|
||||||
|
|
||||||
const { s3Sync } = useAppSelector((state) => state.backup)
|
const { s3Sync } = useAppSelector((state) => state.backup)
|
||||||
|
|
||||||
@ -49,10 +50,11 @@ const S3Settings: FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleTitleClick = () => {
|
const handleTitleClick = () => {
|
||||||
openMinapp({
|
openSmartMinapp({
|
||||||
id: 's3-help',
|
id: 's3-help',
|
||||||
name: 'S3 Compatible Storage Help',
|
name: 'S3 Compatible Storage Help',
|
||||||
url: 'https://docs.cherry-ai.com/data-settings/s3-compatible'
|
url: 'https://docs.cherry-ai.com/data-settings/s3-compatible',
|
||||||
|
logo: AppLogo
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { InfoCircleOutlined } from '@ant-design/icons'
|
|||||||
import { RowFlex } from '@cherrystudio/ui'
|
import { RowFlex } from '@cherrystudio/ui'
|
||||||
import { usePreference } from '@data/hooks/usePreference'
|
import { usePreference } from '@data/hooks/usePreference'
|
||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
|
import { AppLogo } from '@renderer/config/env'
|
||||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||||
import { useMinappPopup } from '@renderer/hooks/useMinappPopup'
|
import { useMinappPopup } from '@renderer/hooks/useMinappPopup'
|
||||||
import { Button, Space, Tooltip } from 'antd'
|
import { Button, Space, Tooltip } from 'antd'
|
||||||
@ -19,7 +20,7 @@ const SiyuanSettings: FC = () => {
|
|||||||
const [siyuanBoxId, setSiyuanBoxId] = usePreference('data.integration.siyuan.box_id')
|
const [siyuanBoxId, setSiyuanBoxId] = usePreference('data.integration.siyuan.box_id')
|
||||||
const [siyuanRootPath, setSiyuanRootPath] = usePreference('data.integration.siyuan.root_path')
|
const [siyuanRootPath, setSiyuanRootPath] = usePreference('data.integration.siyuan.root_path')
|
||||||
|
|
||||||
const { openMinapp } = useMinappPopup()
|
const { openSmartMinapp } = useMinappPopup()
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
|
|
||||||
@ -40,10 +41,11 @@ const SiyuanSettings: FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleSiyuanHelpClick = () => {
|
const handleSiyuanHelpClick = () => {
|
||||||
openMinapp({
|
openSmartMinapp({
|
||||||
id: 'siyuan-help',
|
id: 'siyuan-help',
|
||||||
name: 'Siyuan Help',
|
name: 'Siyuan Help',
|
||||||
url: 'https://docs.cherry-ai.com/advanced-basic/siyuan'
|
url: 'https://docs.cherry-ai.com/advanced-basic/siyuan',
|
||||||
|
logo: AppLogo
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { InfoCircleOutlined } from '@ant-design/icons'
|
import { InfoCircleOutlined } from '@ant-design/icons'
|
||||||
import { RowFlex } from '@cherrystudio/ui'
|
import { RowFlex } from '@cherrystudio/ui'
|
||||||
import { usePreference } from '@data/hooks/usePreference'
|
import { usePreference } from '@data/hooks/usePreference'
|
||||||
|
import { AppLogo } from '@renderer/config/env'
|
||||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||||
import { useMinappPopup } from '@renderer/hooks/useMinappPopup'
|
import { useMinappPopup } from '@renderer/hooks/useMinappPopup'
|
||||||
import { Button, Space, Tooltip } from 'antd'
|
import { Button, Space, Tooltip } from 'antd'
|
||||||
@ -13,7 +14,7 @@ import { SettingDivider, SettingGroup, SettingRow, SettingRowTitle, SettingTitle
|
|||||||
const YuqueSettings: FC = () => {
|
const YuqueSettings: FC = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const { openMinapp } = useMinappPopup()
|
const { openSmartMinapp } = useMinappPopup()
|
||||||
|
|
||||||
const [yuqueToken, setYuqueToken] = usePreference('data.integration.yuque.token')
|
const [yuqueToken, setYuqueToken] = usePreference('data.integration.yuque.token')
|
||||||
const [yuqueUrl, setYuqueUrl] = usePreference('data.integration.yuque.url')
|
const [yuqueUrl, setYuqueUrl] = usePreference('data.integration.yuque.url')
|
||||||
@ -63,10 +64,11 @@ const YuqueSettings: FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleYuqueHelpClick = () => {
|
const handleYuqueHelpClick = () => {
|
||||||
openMinapp({
|
openSmartMinapp({
|
||||||
id: 'yuque-help',
|
id: 'yuque-help',
|
||||||
name: 'Yuque Help',
|
name: 'Yuque Help',
|
||||||
url: 'https://www.yuque.com/settings/tokens'
|
url: 'https://www.yuque.com/settings/tokens',
|
||||||
|
logo: AppLogo
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -185,7 +185,7 @@ const CardContainer = styled.div<{ $isActive: boolean }>`
|
|||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
height: 125px;
|
height: 125px;
|
||||||
opacity: ${(props) => (props.$isActive ? 1 : 0.6)};
|
opacity: ${(props) => (props.$isActive ? 1 : 0.6)};
|
||||||
width: calc(100vw - var(--settings-width) - 40px);
|
width: 100%;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
|
|||||||
@ -252,7 +252,8 @@ const McpServersList: FC = () => {
|
|||||||
onSortEnd={onSortEnd}
|
onSortEnd={onSortEnd}
|
||||||
layout="list"
|
layout="list"
|
||||||
horizontal={false}
|
horizontal={false}
|
||||||
listStyle={{ display: 'flex', flexDirection: 'column' }}
|
listStyle={{ display: 'flex', flexDirection: 'column', width: '100%' }}
|
||||||
|
itemStyle={{ width: '100%' }}
|
||||||
gap="12px"
|
gap="12px"
|
||||||
restrictions={{ scrollableAncestor: true }}
|
restrictions={{ scrollableAncestor: true }}
|
||||||
useDragOverlay
|
useDragOverlay
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import {
|
|||||||
isVisionModel,
|
isVisionModel,
|
||||||
isWebSearchModel
|
isWebSearchModel
|
||||||
} from '@renderer/config/models'
|
} from '@renderer/config/models'
|
||||||
|
import { isNewApiProvider } from '@renderer/config/providers'
|
||||||
import { useDynamicLabelWidth } from '@renderer/hooks/useDynamicLabelWidth'
|
import { useDynamicLabelWidth } from '@renderer/hooks/useDynamicLabelWidth'
|
||||||
import type { Model, ModelCapability, ModelType, Provider } from '@renderer/types'
|
import type { Model, ModelCapability, ModelType, Provider } from '@renderer/types'
|
||||||
import { getDefaultGroupName, getDifference, getUnion, uniqueObjectArray } from '@renderer/utils'
|
import { getDefaultGroupName, getDifference, getUnion, uniqueObjectArray } from '@renderer/utils'
|
||||||
@ -69,7 +70,7 @@ const ModelEditContent: FC<ModelEditContentProps & ModalProps> = ({ provider, mo
|
|||||||
id: formValues.id || model.id,
|
id: formValues.id || model.id,
|
||||||
name: formValues.name || model.name,
|
name: formValues.name || model.name,
|
||||||
group: formValues.group || model.group,
|
group: formValues.group || model.group,
|
||||||
endpoint_type: provider.id === 'new-api' ? formValues.endpointType : model.endpoint_type,
|
endpoint_type: isNewApiProvider(provider) ? formValues.endpointType : model.endpoint_type,
|
||||||
capabilities: overrides?.capabilities ?? modelCapabilities,
|
capabilities: overrides?.capabilities ?? modelCapabilities,
|
||||||
supported_text_delta: overrides?.supported_text_delta ?? supportedTextDelta,
|
supported_text_delta: overrides?.supported_text_delta ?? supportedTextDelta,
|
||||||
pricing: {
|
pricing: {
|
||||||
@ -88,7 +89,7 @@ const ModelEditContent: FC<ModelEditContentProps & ModalProps> = ({ provider, mo
|
|||||||
id: values.id || model.id,
|
id: values.id || model.id,
|
||||||
name: values.name || model.name,
|
name: values.name || model.name,
|
||||||
group: values.group || model.group,
|
group: values.group || model.group,
|
||||||
endpoint_type: provider.id === 'new-api' ? values.endpointType : model.endpoint_type,
|
endpoint_type: isNewApiProvider(provider) ? values.endpointType : model.endpoint_type,
|
||||||
capabilities: modelCapabilities,
|
capabilities: modelCapabilities,
|
||||||
supported_text_delta: supportedTextDelta,
|
supported_text_delta: supportedTextDelta,
|
||||||
pricing: {
|
pricing: {
|
||||||
@ -238,7 +239,7 @@ const ModelEditContent: FC<ModelEditContentProps & ModalProps> = ({ provider, mo
|
|||||||
<Modal title={t('models.edit')} footer={null} transitionName="animation-move-down" centered {...props}>
|
<Modal title={t('models.edit')} footer={null} transitionName="animation-move-down" centered {...props}>
|
||||||
<Form
|
<Form
|
||||||
form={form}
|
form={form}
|
||||||
labelCol={{ flex: provider.id === 'new-api' ? labelWidth : '110px' }}
|
labelCol={{ flex: isNewApiProvider(provider) ? labelWidth : '110px' }}
|
||||||
labelAlign="left"
|
labelAlign="left"
|
||||||
colon={false}
|
colon={false}
|
||||||
style={{ marginTop: 15 }}
|
style={{ marginTop: 15 }}
|
||||||
@ -300,7 +301,7 @@ const ModelEditContent: FC<ModelEditContentProps & ModalProps> = ({ provider, mo
|
|||||||
tooltip={t('settings.models.add.group_name.tooltip')}>
|
tooltip={t('settings.models.add.group_name.tooltip')}>
|
||||||
<Input placeholder={t('settings.models.add.group_name.placeholder')} spellCheck={false} />
|
<Input placeholder={t('settings.models.add.group_name.placeholder')} spellCheck={false} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
{provider.id === 'new-api' && (
|
{isNewApiProvider(provider) && (
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="endpointType"
|
name="endpointType"
|
||||||
label={t('settings.models.add.endpoint_type.label')}
|
label={t('settings.models.add.endpoint_type.label')}
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import ModelIdWithTags from '@renderer/components/ModelIdWithTags'
|
|||||||
import CustomTag from '@renderer/components/Tags/CustomTag'
|
import CustomTag from '@renderer/components/Tags/CustomTag'
|
||||||
import { DynamicVirtualList } from '@renderer/components/VirtualList'
|
import { DynamicVirtualList } from '@renderer/components/VirtualList'
|
||||||
import { getModelLogo } from '@renderer/config/models'
|
import { getModelLogo } from '@renderer/config/models'
|
||||||
|
import { isNewApiProvider } from '@renderer/config/providers'
|
||||||
import FileItem from '@renderer/pages/files/FileItem'
|
import FileItem from '@renderer/pages/files/FileItem'
|
||||||
import NewApiBatchAddModelPopup from '@renderer/pages/settings/ProviderSettings/ModelList/NewApiBatchAddModelPopup'
|
import NewApiBatchAddModelPopup from '@renderer/pages/settings/ProviderSettings/ModelList/NewApiBatchAddModelPopup'
|
||||||
import type { Model, Provider } from '@renderer/types'
|
import type { Model, Provider } from '@renderer/types'
|
||||||
@ -92,7 +93,7 @@ const ManageModelsList: React.FC<ManageModelsListProps> = ({ modelGroups, provid
|
|||||||
// 添加整组
|
// 添加整组
|
||||||
const wouldAddModels = models.filter((model) => !isModelInProvider(provider, model.id))
|
const wouldAddModels = models.filter((model) => !isModelInProvider(provider, model.id))
|
||||||
|
|
||||||
if (provider.id === 'new-api') {
|
if (isNewApiProvider(provider)) {
|
||||||
if (wouldAddModels.every(isValidNewApiModel)) {
|
if (wouldAddModels.every(isValidNewApiModel)) {
|
||||||
wouldAddModels.forEach(onAddModel)
|
wouldAddModels.forEach(onAddModel)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import {
|
|||||||
isWebSearchModel,
|
isWebSearchModel,
|
||||||
SYSTEM_MODELS
|
SYSTEM_MODELS
|
||||||
} from '@renderer/config/models'
|
} from '@renderer/config/models'
|
||||||
|
import { isNewApiProvider } from '@renderer/config/providers'
|
||||||
import { useProvider } from '@renderer/hooks/useProvider'
|
import { useProvider } from '@renderer/hooks/useProvider'
|
||||||
import NewApiAddModelPopup from '@renderer/pages/settings/ProviderSettings/ModelList/NewApiAddModelPopup'
|
import NewApiAddModelPopup from '@renderer/pages/settings/ProviderSettings/ModelList/NewApiAddModelPopup'
|
||||||
import NewApiBatchAddModelPopup from '@renderer/pages/settings/ProviderSettings/ModelList/NewApiBatchAddModelPopup'
|
import NewApiBatchAddModelPopup from '@renderer/pages/settings/ProviderSettings/ModelList/NewApiBatchAddModelPopup'
|
||||||
@ -130,7 +131,7 @@ const PopupContainer: React.FC<Props> = ({ providerId, resolve }) => {
|
|||||||
const onAddModel = useCallback(
|
const onAddModel = useCallback(
|
||||||
(model: Model) => {
|
(model: Model) => {
|
||||||
if (!isEmpty(model.name)) {
|
if (!isEmpty(model.name)) {
|
||||||
if (provider.id === 'new-api') {
|
if (isNewApiProvider(provider)) {
|
||||||
if (model.supported_endpoint_types && model.supported_endpoint_types.length > 0) {
|
if (model.supported_endpoint_types && model.supported_endpoint_types.length > 0) {
|
||||||
addModel({
|
addModel({
|
||||||
...model,
|
...model,
|
||||||
@ -161,7 +162,7 @@ const PopupContainer: React.FC<Props> = ({ providerId, resolve }) => {
|
|||||||
content: t('settings.models.manage.add_listed.confirm'),
|
content: t('settings.models.manage.add_listed.confirm'),
|
||||||
centered: true,
|
centered: true,
|
||||||
onOk: () => {
|
onOk: () => {
|
||||||
if (provider.id === 'new-api') {
|
if (isNewApiProvider(provider)) {
|
||||||
if (models.every(isValidNewApiModel)) {
|
if (models.every(isValidNewApiModel)) {
|
||||||
wouldAddModel.forEach(onAddModel)
|
wouldAddModel.forEach(onAddModel)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { Flex } from '@cherrystudio/ui'
|
|||||||
import CollapsibleSearchBar from '@renderer/components/CollapsibleSearchBar'
|
import CollapsibleSearchBar from '@renderer/components/CollapsibleSearchBar'
|
||||||
import { LoadingIcon, StreamlineGoodHealthAndWellBeing } from '@renderer/components/Icons'
|
import { LoadingIcon, StreamlineGoodHealthAndWellBeing } from '@renderer/components/Icons'
|
||||||
import CustomTag from '@renderer/components/Tags/CustomTag'
|
import CustomTag from '@renderer/components/Tags/CustomTag'
|
||||||
import { PROVIDER_URLS } from '@renderer/config/providers'
|
import { isNewApiProvider, PROVIDER_URLS } from '@renderer/config/providers'
|
||||||
import { useProvider } from '@renderer/hooks/useProvider'
|
import { useProvider } from '@renderer/hooks/useProvider'
|
||||||
import { getProviderLabel } from '@renderer/i18n/label'
|
import { getProviderLabel } from '@renderer/i18n/label'
|
||||||
import { SettingHelpLink, SettingHelpText, SettingHelpTextRow, SettingSubtitle } from '@renderer/pages/settings'
|
import { SettingHelpLink, SettingHelpText, SettingHelpTextRow, SettingSubtitle } from '@renderer/pages/settings'
|
||||||
@ -87,7 +87,7 @@ const ModelList: React.FC<ModelListProps> = ({ providerId }) => {
|
|||||||
}, [provider.id])
|
}, [provider.id])
|
||||||
|
|
||||||
const onAddModel = useCallback(() => {
|
const onAddModel = useCallback(() => {
|
||||||
if (provider.id === 'new-api') {
|
if (isNewApiProvider(provider)) {
|
||||||
NewApiAddModelPopup.show({ title: t('settings.models.add.add_model'), provider })
|
NewApiAddModelPopup.show({ title: t('settings.models.add.add_model'), provider })
|
||||||
} else {
|
} else {
|
||||||
AddModelPopup.show({ title: t('settings.models.add.add_model'), provider })
|
AddModelPopup.show({ title: t('settings.models.add.add_model'), provider })
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { Flex } from '@cherrystudio/ui'
|
|||||||
import { TopView } from '@renderer/components/TopView'
|
import { TopView } from '@renderer/components/TopView'
|
||||||
import { endpointTypeOptions } from '@renderer/config/endpointTypes'
|
import { endpointTypeOptions } from '@renderer/config/endpointTypes'
|
||||||
import { isNotSupportedTextDelta } from '@renderer/config/models'
|
import { isNotSupportedTextDelta } from '@renderer/config/models'
|
||||||
|
import { isNewApiProvider } from '@renderer/config/providers'
|
||||||
import { useDynamicLabelWidth } from '@renderer/hooks/useDynamicLabelWidth'
|
import { useDynamicLabelWidth } from '@renderer/hooks/useDynamicLabelWidth'
|
||||||
import { useProvider } from '@renderer/hooks/useProvider'
|
import { useProvider } from '@renderer/hooks/useProvider'
|
||||||
import type { EndpointType, Model, Provider } from '@renderer/types'
|
import type { EndpointType, Model, Provider } from '@renderer/types'
|
||||||
@ -62,7 +63,7 @@ const PopupContainer: React.FC<Props> = ({ title, provider, resolve, model, endp
|
|||||||
provider: provider.id,
|
provider: provider.id,
|
||||||
name: values.name ? values.name : id.toUpperCase(),
|
name: values.name ? values.name : id.toUpperCase(),
|
||||||
group: values.group ?? getDefaultGroupName(id),
|
group: values.group ?? getDefaultGroupName(id),
|
||||||
endpoint_type: provider.id === 'new-api' ? values.endpointType : undefined
|
endpoint_type: isNewApiProvider(provider) ? values.endpointType : undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
addModel({ ...model, supported_text_delta: !isNotSupportedTextDelta(model) })
|
addModel({ ...model, supported_text_delta: !isNotSupportedTextDelta(model) })
|
||||||
|
|||||||
@ -339,6 +339,12 @@ const TranslatePage: FC = () => {
|
|||||||
setTargetLanguage(source)
|
setTargetLanguage(source)
|
||||||
}, [couldExchangeAuto, detectedLanguage, sourceLanguage, t, targetLanguage])
|
}, [couldExchangeAuto, detectedLanguage, sourceLanguage, t, targetLanguage])
|
||||||
|
|
||||||
|
// Clear translation content when component mounts
|
||||||
|
useEffect(() => {
|
||||||
|
setText('')
|
||||||
|
setTranslatedContent('')
|
||||||
|
}, [setText, setTranslatedContent])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
isEmpty(text) && setTranslatedContent('')
|
isEmpty(text) && setTranslatedContent('')
|
||||||
}, [setTranslatedContent, text])
|
}, [setTranslatedContent, text])
|
||||||
|
|||||||
@ -2499,6 +2499,7 @@ const migrateConfig = {
|
|||||||
'157': (state: RootState) => {
|
'157': (state: RootState) => {
|
||||||
try {
|
try {
|
||||||
addProvider(state, 'aionly')
|
addProvider(state, 'aionly')
|
||||||
|
state.llm.providers = moveProvider(state.llm.providers, 'aionly', 10)
|
||||||
|
|
||||||
const cherryinProvider = state.llm.providers.find((provider) => provider.id === 'cherryin')
|
const cherryinProvider = state.llm.providers.find((provider) => provider.id === 'cherryin')
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user