mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-07-03 12:27:41 +08:00
refactor(renderer-config): dissolve config bucket into owning domains
Reorganize src/renderer/config per renderer-architecture §8 so it holds only app-global constants (constant.ts, env.ts). - Delete v1 dead code: provider catalog, model defaults, embeddings/translate/ webSearch presets, the mini-app catalog, and unused agent configs. - Relocate live config to its owning domain: PROVIDER_URLS -> ProviderSettings, builtin MCP servers -> McpSettings, code providers -> code page, OCR -> FileProcessingSettings, message menu bar -> chat message frame. - Sink model predicates (with modelReconcile/modelSearch) into utils/model with a curated named-export index; move tab/agent/sidebar helpers into utils. - Split the sidebar icon map into the component layer (components/app/ sidebarIcons) to remove the reverse config->component dependency, and drop the sidebar exports orphaned by #16413.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { Avatar, AvatarFallback } from '@cherrystudio/ui'
|
||||
import { getModelLogo } from '@renderer/config/models'
|
||||
import { getModelLogo } from '@renderer/utils/model'
|
||||
import { cn } from '@renderer/utils/style'
|
||||
import { first } from 'lodash'
|
||||
import type { FC } from 'react'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getMiniAppsLogo } from '@renderer/config/miniApps'
|
||||
import { getMiniAppsLogo } from '@renderer/components/Icons/miniAppsLogo'
|
||||
import type { MiniApp } from '@shared/data/types/miniApp'
|
||||
import type { FC } from 'react'
|
||||
|
||||
|
||||
@@ -4,25 +4,7 @@ import { describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import MiniAppIcon from '../MiniAppIcon'
|
||||
|
||||
vi.mock('@renderer/config/miniApps', () => ({
|
||||
allMiniApps: [
|
||||
{
|
||||
id: 'test-app-1',
|
||||
name: 'Test App 1',
|
||||
logo: '/test-logo-1.png',
|
||||
url: 'https://test1.com',
|
||||
bordered: true,
|
||||
background: '#f0f0f0'
|
||||
},
|
||||
{
|
||||
id: 'test-app-2',
|
||||
name: 'Test App 2',
|
||||
logo: '/test-logo-2.png',
|
||||
url: 'https://test2.com',
|
||||
bordered: false,
|
||||
background: undefined
|
||||
}
|
||||
],
|
||||
vi.mock('@renderer/components/Icons/miniAppsLogo', () => ({
|
||||
getMiniAppsLogo: (logo: unknown) => {
|
||||
if (logo !== 'compound-logo') return logo
|
||||
const CompoundLogo = ({
|
||||
|
||||
@@ -64,45 +64,6 @@ import {
|
||||
Zhida,
|
||||
Zhipu
|
||||
} from '@cherrystudio/ui/icons'
|
||||
import { PRESETS_MINI_APPS as SHARED_PRESETS } from '@shared/data/presets/miniApps'
|
||||
|
||||
/**
|
||||
* Legacy mini-app entity type used by the deprecated Redux slice and config layer.
|
||||
* The v2 MiniApp entity lives in @shared/data/types/miniApp.
|
||||
*/
|
||||
export type MiniAppType = {
|
||||
id: string
|
||||
name: string
|
||||
nameKey?: string
|
||||
supportedRegions?: string[]
|
||||
logo?: string
|
||||
url: string
|
||||
bordered?: boolean
|
||||
background?: string
|
||||
style?: Record<string, unknown>
|
||||
addTime?: string
|
||||
type?: 'Custom' | 'Default'
|
||||
}
|
||||
|
||||
// Renderer preset list, derived from the shared source of truth.
|
||||
// Custom mini apps are no longer file-backed in v2 — they live in the
|
||||
// `mini_app` SQLite table and are accessed via DataApi. Anything in v1's
|
||||
// `custom-miniApps.json` reaches v2 only through MiniAppMigrator.
|
||||
const PRESETS_MINI_APPS: MiniAppType[] = SHARED_PRESETS.map((app) => ({
|
||||
id: app.id,
|
||||
name: app.name,
|
||||
nameKey: app.nameKey,
|
||||
url: app.url,
|
||||
logo: app.logo,
|
||||
bordered: app.bordered,
|
||||
background: app.background,
|
||||
supportedRegions: app.supportedRegions,
|
||||
style: app.style
|
||||
}))
|
||||
|
||||
const allMiniApps = PRESETS_MINI_APPS
|
||||
|
||||
export { allMiniApps, PRESETS_MINI_APPS }
|
||||
|
||||
export function getMiniAppsLogo(LogoId: string | undefined): CompoundIcon | undefined {
|
||||
if (!LogoId) {
|
||||
@@ -1,3 +1,4 @@
|
||||
import type * as ModelModule from '@renderer/utils/model'
|
||||
import { type Model, MODEL_CAPABILITY, type UniqueModelId } from '@shared/data/types/model'
|
||||
import type { Provider } from '@shared/data/types/provider'
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
@@ -59,7 +60,8 @@ vi.mock('@cherrystudio/ui/icons', () => ({
|
||||
resolveIcon: () => null
|
||||
}))
|
||||
|
||||
vi.mock('@renderer/config/models/reasoning', () => ({
|
||||
vi.mock('@renderer/utils/model', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof ModelModule>()),
|
||||
getModelSupportedReasoningEffortOptions: () => undefined
|
||||
}))
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type * as ModelModule from '@renderer/utils/model'
|
||||
import type { Model, UniqueModelId } from '@shared/data/types/model'
|
||||
import type { Provider } from '@shared/data/types/provider'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
@@ -11,7 +12,8 @@ const { mockGetModelSupportedReasoningEffortOptions } = vi.hoisted(() => ({
|
||||
mockGetModelSupportedReasoningEffortOptions: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('@renderer/config/models/reasoning', () => ({
|
||||
vi.mock('@renderer/utils/model', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof ModelModule>()),
|
||||
getModelSupportedReasoningEffortOptions: mockGetModelSupportedReasoningEffortOptions
|
||||
}))
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@cherrystudio/ui'
|
||||
import { getModelDisplayTags, ModelTag } from '@renderer/components/Tags/Model'
|
||||
import { getModelSupportedReasoningEffortOptions } from '@renderer/config/models/reasoning'
|
||||
import { getModelSupportedReasoningEffortOptions } from '@renderer/utils/model'
|
||||
import type { Model } from '@shared/data/types/model'
|
||||
import type { Provider } from '@shared/data/types/provider'
|
||||
import type { TFunction } from 'i18next'
|
||||
|
||||
@@ -2,7 +2,7 @@ import { modelMatchesDisplayTag } from '@renderer/components/Tags/Model'
|
||||
import { useModels } from '@renderer/hooks/useModel'
|
||||
import { usePins } from '@renderer/hooks/usePins'
|
||||
import { useProviders } from '@renderer/hooks/useProvider'
|
||||
import { getSearchMatchScore } from '@renderer/utils/modelSearch'
|
||||
import { getSearchMatchScore } from '@renderer/utils/model'
|
||||
import { CHERRYAI_PROVIDER_ID } from '@shared/data/presets/cherryai'
|
||||
import { isUniqueModelId, type Model, parseUniqueModelId, type UniqueModelId } from '@shared/data/types/model'
|
||||
import type { Provider } from '@shared/data/types/provider'
|
||||
|
||||
@@ -1,20 +1,16 @@
|
||||
import { usePersistCache } from '@data/hooks/useCache'
|
||||
import { usePreference } from '@data/hooks/usePreference'
|
||||
import { SIDEBAR_ICON_COMPONENTS } from '@renderer/components/app/sidebarIcons'
|
||||
import {
|
||||
emitResourceListReveal,
|
||||
type ResourceListRevealSource
|
||||
} from '@renderer/components/chat/resources/resourceListRevealEvents'
|
||||
import {
|
||||
getOrderedVisibleSidebarIcons,
|
||||
getSidebarMenuPath,
|
||||
resolveSidebarActiveItem,
|
||||
SIDEBAR_ICON_COMPONENTS
|
||||
} from '@renderer/config/sidebar'
|
||||
import { clearTabInstanceMetadata } from '@renderer/config/tabInstanceMetadata'
|
||||
import { useTabs } from '@renderer/hooks/tab'
|
||||
import useAvatar from '@renderer/hooks/useAvatar'
|
||||
import { getSidebarIconLabelKey } from '@renderer/i18n/label'
|
||||
import { getDefaultRouteTitle } from '@renderer/utils/routeTitle'
|
||||
import { getOrderedVisibleSidebarIcons, getSidebarMenuPath, resolveSidebarActiveItem } from '@renderer/utils/sidebar'
|
||||
import { clearTabInstanceMetadata } from '@renderer/utils/tabInstanceMetadata'
|
||||
import type { SidebarIcon as SidebarIconType } from '@shared/data/preference/preferenceTypes'
|
||||
import type { Ref } from 'react'
|
||||
import { useCallback, useEffect, useLayoutEffect, useMemo, useState } from 'react'
|
||||
|
||||
@@ -193,7 +193,7 @@ vi.mock('react-i18next', () => ({
|
||||
})
|
||||
}))
|
||||
|
||||
import { resolveSidebarAppTabEntryUrl } from '@renderer/config/sidebar'
|
||||
import { resolveSidebarAppTabEntryUrl } from '@renderer/utils/sidebar'
|
||||
|
||||
import Sidebar from '../Sidebar'
|
||||
|
||||
|
||||
35
src/renderer/components/app/sidebarIcons.tsx
Normal file
35
src/renderer/components/app/sidebarIcons.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import { OpenClawSidebarIcon } from '@renderer/components/Icons/SvgIcon'
|
||||
import type { SidebarMenuItem } from '@renderer/components/Sidebar/types'
|
||||
import type { SidebarIcon } from '@shared/data/preference/preferenceTypes'
|
||||
import {
|
||||
Code,
|
||||
FileSearch,
|
||||
Folder,
|
||||
Languages,
|
||||
LayoutGrid,
|
||||
Library,
|
||||
MessageSquare,
|
||||
MousePointerClick,
|
||||
NotepadText,
|
||||
Palette
|
||||
} from 'lucide-react'
|
||||
|
||||
/**
|
||||
* Icon component for each sidebar app. Keyed by the `SidebarIcon` union so the
|
||||
* compiler enforces full coverage — adding a new sidebar app id without an icon
|
||||
* here is a type error. Kept in the component layer because the values are React
|
||||
* components; the navigation data and logic live in `@renderer/utils/sidebar`.
|
||||
*/
|
||||
export const SIDEBAR_ICON_COMPONENTS: Record<SidebarIcon, SidebarMenuItem['icon']> = {
|
||||
assistants: MessageSquare,
|
||||
agents: MousePointerClick,
|
||||
paintings: Palette,
|
||||
translate: Languages,
|
||||
store: Library,
|
||||
mini_app: LayoutGrid,
|
||||
knowledge: FileSearch,
|
||||
files: Folder,
|
||||
code_tools: Code,
|
||||
notes: NotepadText,
|
||||
openclaw: OpenClawSidebarIcon
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Checkbox, Tooltip } from '@cherrystudio/ui'
|
||||
import { getModelLogo } from '@renderer/config/models'
|
||||
import { useTheme } from '@renderer/hooks/useTheme'
|
||||
import type { Model } from '@renderer/types/model'
|
||||
import { getModelLogo } from '@renderer/utils/model'
|
||||
import { firstLetter, removeLeadingEmoji } from '@renderer/utils/naming'
|
||||
import dayjs from 'dayjs'
|
||||
import { Sparkle } from 'lucide-react'
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import type { MessageMenuBarScope } from '@renderer/config/registry/messageMenuBar'
|
||||
import { DEFAULT_MESSAGE_MENUBAR_SCOPE, getMessageMenuBarConfig } from '@renderer/config/registry/messageMenuBar'
|
||||
import type { MessageMenuBarScope } from '@renderer/components/chat/messages/frame/messageMenuBarConfig'
|
||||
import {
|
||||
DEFAULT_MESSAGE_MENUBAR_SCOPE,
|
||||
getMessageMenuBarConfig
|
||||
} from '@renderer/components/chat/messages/frame/messageMenuBarConfig'
|
||||
import { useTemporaryValue } from '@renderer/hooks/useTemporaryValue'
|
||||
import type { Topic } from '@renderer/types/topic'
|
||||
import { getComposerTextFromParts } from '@renderer/utils/message/composerTokens'
|
||||
|
||||
@@ -19,7 +19,7 @@ vi.mock('@cherrystudio/ui', () => ({
|
||||
Tooltip: ({ children }: { children: ReactNode }) => <>{children}</>
|
||||
}))
|
||||
|
||||
vi.mock('@renderer/config/models', () => ({
|
||||
vi.mock('@renderer/utils/model', () => ({
|
||||
getModelLogo: () => null
|
||||
}))
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import {
|
||||
DEFAULT_MESSAGE_MENUBAR_BUTTON_IDS,
|
||||
getMessageMenuBarConfig
|
||||
} from '@renderer/components/chat/messages/frame/messageMenuBarConfig'
|
||||
import { defaultMessageMenuConfig, type MessageListActions } from '@renderer/components/chat/messages/types'
|
||||
import { DEFAULT_MESSAGE_MENUBAR_BUTTON_IDS, getMessageMenuBarConfig } from '@renderer/config/registry/messageMenuBar'
|
||||
import { COMPOSER_CLIPBOARD_FRAGMENT_MIME } from '@renderer/utils/message/composerClipboard'
|
||||
import { fireEvent, render, screen } from '@testing-library/react'
|
||||
import type { ComponentProps, MouseEvent, ReactElement, ReactNode } from 'react'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { TopicType } from '@renderer/types/topic'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { getMessageMenuBarConfig } from '../messageMenuBar'
|
||||
import { getMessageMenuBarConfig } from '../messageMenuBarConfig'
|
||||
|
||||
describe('messageMenuBar registry', () => {
|
||||
// Regression: agent sessions don't mount the translation-overlay provider,
|
||||
@@ -1,10 +1,10 @@
|
||||
import { loggerService } from '@logger'
|
||||
import { CopyIcon, DeleteIcon, EditIcon, RefreshIcon } from '@renderer/components/Icons'
|
||||
import {
|
||||
DEFAULT_MESSAGE_MENUBAR_BUTTON_IDS,
|
||||
type MessageMenuBarButtonId,
|
||||
STREAMING_DISABLED_BUTTON_IDS
|
||||
} from '@renderer/config/registry/messageMenuBar'
|
||||
} from '@renderer/components/chat/messages/frame/messageMenuBarConfig'
|
||||
import { CopyIcon, DeleteIcon, EditIcon, RefreshIcon } from '@renderer/components/Icons'
|
||||
import { getMessageTitle } from '@renderer/services/MessagesService'
|
||||
import type { MessageExportView } from '@renderer/types/messageExport'
|
||||
import { formatErrorMessageWithPrefix } from '@renderer/utils/error'
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Avatar, AvatarFallback, AvatarImage, EmojiAvatar } from '@cherrystudio/ui'
|
||||
import { getModelLogo } from '@renderer/config/models'
|
||||
import { useTheme } from '@renderer/hooks/useTheme'
|
||||
import { useTimer } from '@renderer/hooks/useTimer'
|
||||
import { scrollIntoView } from '@renderer/utils/dom'
|
||||
import { getTextFromParts } from '@renderer/utils/message/partsHelpers'
|
||||
import { getModelLogo } from '@renderer/utils/model'
|
||||
import { firstLetter, isEmoji, removeLeadingEmoji } from '@renderer/utils/naming'
|
||||
import { CircleChevronDown } from 'lucide-react'
|
||||
import { type FC, type Ref, useCallback, useEffect, useRef, useState } from 'react'
|
||||
|
||||
@@ -7,7 +7,7 @@ const { mockIsGenerateImageModel, mockIsReasoningModel, mockIsSupportedToolUse }
|
||||
mockIsSupportedToolUse: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('@renderer/config/models', () => ({
|
||||
vi.mock('@renderer/utils/model', () => ({
|
||||
isGenerateImageModel: (...args: unknown[]) => mockIsGenerateImageModel(...args),
|
||||
isReasoningModel: (...args: unknown[]) => mockIsReasoningModel(...args)
|
||||
}))
|
||||
|
||||
@@ -9,6 +9,9 @@ import {
|
||||
MdiLightbulbOn90,
|
||||
MdiLightbulbQuestion
|
||||
} from '@renderer/components/Icons/SvgIcon'
|
||||
import { cacheService } from '@renderer/data/CacheService'
|
||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||
import type { ThinkingOption } from '@renderer/types/reasoning'
|
||||
import {
|
||||
getThinkModelType,
|
||||
isDoubaoThinkingAutoModel,
|
||||
@@ -17,10 +20,7 @@ import {
|
||||
isOpenAIWebSearchModel,
|
||||
isReasoningModel,
|
||||
MODEL_SUPPORTED_OPTIONS
|
||||
} from '@renderer/config/models'
|
||||
import { cacheService } from '@renderer/data/CacheService'
|
||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||
import type { ThinkingOption } from '@renderer/types/reasoning'
|
||||
} from '@renderer/utils/model'
|
||||
import type { Model } from '@shared/data/types/model'
|
||||
import type { FC, SVGProps } from 'react'
|
||||
import { useCallback, useEffect, useMemo } from 'react'
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||
import { useProvider } from '@renderer/hooks/useProvider'
|
||||
import { useWebSearchProviders } from '@renderer/hooks/useWebSearch'
|
||||
import { getEffectiveMcpMode } from '@renderer/utils/mcpMode'
|
||||
import { canModelUseAssistantWebSearch, hasModelBuiltinWebSearch } from '@renderer/utils/modelReconcile'
|
||||
import { canModelUseAssistantWebSearch, hasModelBuiltinWebSearch } from '@renderer/utils/model'
|
||||
import { getWebSearchProviderLogo } from '@renderer/utils/webSearchProviderMeta'
|
||||
import type { WebSearchProviderId } from '@shared/data/preference/preferenceTypes'
|
||||
import { isGemini3Model, isGeminiModel, isGPT5SeriesReasoningModel, isOpenAIWebSearchModel } from '@shared/utils/model'
|
||||
|
||||
@@ -50,7 +50,7 @@ vi.mock('@renderer/data/CacheService', () => ({
|
||||
}
|
||||
}))
|
||||
|
||||
vi.mock('@renderer/config/models', () => ({
|
||||
vi.mock('@renderer/utils/model', () => ({
|
||||
getThinkModelType: (...args: unknown[]) => mocks.getThinkModelType(...args),
|
||||
isDoubaoThinkingAutoModel: (...args: unknown[]) => mocks.isDoubaoThinkingAutoModel(...args),
|
||||
isFixedReasoningModel: (...args: unknown[]) => mocks.isFixedReasoningModel(...args),
|
||||
|
||||
@@ -66,32 +66,29 @@ vi.mock('@renderer/utils/api', () => ({
|
||||
splitApiKeyString: (value: string) => value.split(',').map((item) => item.trim())
|
||||
}))
|
||||
|
||||
vi.mock('@renderer/config/models', () => {
|
||||
const qwenModel = {
|
||||
id: 'qwen',
|
||||
name: 'Qwen',
|
||||
provider: 'cherryai',
|
||||
group: 'Qwen'
|
||||
}
|
||||
vi.mock('@renderer/utils/model', () => {
|
||||
const isFunctionCallingModel = (model?: Model) =>
|
||||
model?.capabilities.includes(MODEL_CAPABILITY.FUNCTION_CALL) ?? false
|
||||
const isOpenRouterBuiltInWebSearchModel = () => false
|
||||
const isWebSearchModel = (model?: Model) => model?.capabilities.includes(MODEL_CAPABILITY.WEB_SEARCH) ?? false
|
||||
// Mirror the real reconcile composition over the mocked predicates above.
|
||||
const hasModelBuiltinWebSearch = (model?: Model) => isWebSearchModel(model) || isOpenRouterBuiltInWebSearchModel()
|
||||
const canModelUseAssistantWebSearch = (model?: Model) =>
|
||||
hasModelBuiltinWebSearch(model) || isFunctionCallingModel(model)
|
||||
|
||||
return {
|
||||
qwenModel,
|
||||
SYSTEM_MODELS: new Proxy(
|
||||
{ defaultModel: [qwenModel] },
|
||||
{
|
||||
get: (target, prop) => (prop in target ? target[prop as keyof typeof target] : [])
|
||||
}
|
||||
),
|
||||
canModelUseAssistantWebSearch,
|
||||
getThinkModelType: () => 'default',
|
||||
isFunctionCallingModel: (model?: Model) => model?.capabilities.includes(MODEL_CAPABILITY.FUNCTION_CALL) ?? false,
|
||||
hasModelBuiltinWebSearch,
|
||||
isFunctionCallingModel,
|
||||
isGemini3Model: () => false,
|
||||
isGeminiModel: () => false,
|
||||
isGPT5SeriesReasoningModel: () => false,
|
||||
isOpenRouterBuiltInWebSearchModel: () => false,
|
||||
isOpenRouterBuiltInWebSearchModel,
|
||||
isOpenAIWebSearchModel: () => false,
|
||||
isSupportedReasoningEffortModel: () => false,
|
||||
isSupportedThinkingTokenModel: () => false,
|
||||
isWebSearchModel: (model?: Model) => model?.capabilities.includes(MODEL_CAPABILITY.WEB_SEARCH) ?? false,
|
||||
isWebSearchModel,
|
||||
MODEL_SUPPORTED_OPTIONS: { default: ['none'] },
|
||||
MODEL_SUPPORTED_REASONING_EFFORT: { default: ['none'] }
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ const { mockIsGenerateImageModel } = vi.hoisted(() => ({
|
||||
mockIsGenerateImageModel: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('@renderer/config/models', () => ({
|
||||
vi.mock('@renderer/utils/model', () => ({
|
||||
isGenerateImageModel: (...args: unknown[]) => mockIsGenerateImageModel(...args)
|
||||
}))
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { defineTool, registerTool, TopicType } from '@renderer/components/composer/tools/types'
|
||||
import { isGenerateImageModel } from '@renderer/config/models'
|
||||
import { isGenerateImageModel } from '@renderer/utils/model'
|
||||
import { Image } from 'lucide-react'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { defineTool, registerTool, type ToolRenderContext, TopicType } from '@renderer/components/composer/tools/types'
|
||||
import { permissionModeCards } from '@renderer/config/agent'
|
||||
import { defaultConfiguration } from '@renderer/hooks/agents/agentConfiguration'
|
||||
import { useAgent } from '@renderer/hooks/agents/useAgent'
|
||||
import { useUpdateAgent } from '@renderer/hooks/agents/useAgent'
|
||||
import type { PermissionMode } from '@renderer/types/agent'
|
||||
import { permissionModeCards } from '@renderer/utils/agent'
|
||||
import { FolderPen, Pointer, RefreshCcw, Route } from 'lucide-react'
|
||||
import type { ReactNode } from 'react'
|
||||
import { useCallback, useEffect, useMemo } from 'react'
|
||||
|
||||
@@ -34,7 +34,7 @@ import { type Topic, TopicType } from '@renderer/types/topic'
|
||||
import { buildFilePartsForAttachments } from '@renderer/utils/file/buildFileParts'
|
||||
import { getSendMessageShortcutLabel } from '@renderer/utils/input'
|
||||
import type { ComposerAttachment } from '@renderer/utils/message/composerAttachment'
|
||||
import { canModelUseAssistantWebSearch } from '@renderer/utils/modelReconcile'
|
||||
import { canModelUseAssistantWebSearch } from '@renderer/utils/model'
|
||||
import { getLeadingEmoji } from '@renderer/utils/naming'
|
||||
import { cn } from '@renderer/utils/style'
|
||||
import type { ComposerQueuedMessagePayload } from '@shared/ai/transport'
|
||||
|
||||
@@ -327,7 +327,12 @@ vi.mock('@renderer/components/resource', () => ({
|
||||
)
|
||||
}))
|
||||
|
||||
vi.mock('@renderer/config/models', () => ({
|
||||
vi.mock('@renderer/utils/model', () => ({
|
||||
// Mirrors the real reconcile logic using the mocked predicates below:
|
||||
// canModelUseAssistantWebSearch = isWebSearchModel || isOpenRouterBuiltInWebSearchModel || isFunctionCallingModel.
|
||||
// The first two predicates are stubbed to false here, so it reduces to the function-call check.
|
||||
canModelUseAssistantWebSearch: (currentModel?: Model) =>
|
||||
currentModel?.capabilities.includes(MODEL_CAPABILITY.FUNCTION_CALL) ?? false,
|
||||
getThinkModelType: () => 'default',
|
||||
isEmbeddingModel: () => false,
|
||||
isFunctionCallingModel: (currentModel?: Model) =>
|
||||
|
||||
@@ -11,7 +11,7 @@ const mocks = vi.hoisted(() => ({
|
||||
isGenerateImageModels: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('@renderer/config/models', () => mocks)
|
||||
vi.mock('@renderer/utils/model', () => mocks)
|
||||
|
||||
const model = (id: string) => ({ id }) as unknown as Model
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { isGenerateImageModel, isGenerateImageModels, isVisionModel, isVisionModels } from '@renderer/config/models'
|
||||
import { isGenerateImageModel, isGenerateImageModels, isVisionModel, isVisionModels } from '@renderer/utils/model'
|
||||
import type { Model } from '@shared/data/types/model'
|
||||
import { documentExts, imageExts, textExts } from '@shared/utils/file/fileExtensions'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { clearTabInstanceMetadata } from '@renderer/config/tabInstanceMetadata'
|
||||
import { usePersistCache } from '@renderer/data/hooks/useCache'
|
||||
import { useCommandHandler } from '@renderer/hooks/command'
|
||||
import { useTabs } from '@renderer/hooks/tab'
|
||||
import useMacTransparentWindow from '@renderer/hooks/useMacTransparentWindow'
|
||||
import { getDefaultRouteTitle, isPageTitledRoute } from '@renderer/utils/routeTitle'
|
||||
import { cn } from '@renderer/utils/style'
|
||||
import { clearTabInstanceMetadata } from '@renderer/utils/tabInstanceMetadata'
|
||||
import { useCallback, useEffect, useMemo } from 'react'
|
||||
|
||||
import Sidebar from '../app/Sidebar'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import EmojiIcon from '@renderer/components/EmojiIcon'
|
||||
import { getMiniAppsLogo } from '@renderer/config/miniApps'
|
||||
import { getMiniAppsLogo } from '@renderer/components/Icons/miniAppsLogo'
|
||||
import { cn } from '@renderer/utils/style'
|
||||
import { TAB_ICON_EMOJI_PREFIX } from '@renderer/utils/tabIcons'
|
||||
import type { FC } from 'react'
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { loggerService } from '@logger'
|
||||
import { resolveSidebarAppTabEntryUrl } from '@renderer/config/sidebar'
|
||||
import { usePersistCache } from '@renderer/data/hooks/useCache'
|
||||
import { type OpenTabOptions, TabsContext, type TabsContextValue } from '@renderer/hooks/tab'
|
||||
import { TabLruManager } from '@renderer/services/TabLruManager'
|
||||
import { getDefaultRouteTitle, isPageTitledRoute, isTopLevelRoute } from '@renderer/utils/routeTitle'
|
||||
import { resolveSidebarAppTabEntryUrl } from '@renderer/utils/sidebar'
|
||||
import type { Tab, TabSavedState } from '@shared/data/cache/cacheValueTypes'
|
||||
import { IpcChannel } from '@shared/IpcChannel'
|
||||
import type { ReactNode } from 'react'
|
||||
|
||||
@@ -38,7 +38,7 @@ vi.mock('@renderer/config/constant', () => ({
|
||||
platform: 'linux'
|
||||
}))
|
||||
|
||||
vi.mock('@renderer/config/miniApps', () => ({
|
||||
vi.mock('@renderer/components/Icons/miniAppsLogo', () => ({
|
||||
getMiniAppsLogo: () => undefined
|
||||
}))
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { resolveSidebarAppTabEntryUrl } from '@renderer/config/sidebar'
|
||||
import type { Tab } from '@renderer/hooks/tab'
|
||||
import { resolveSidebarAppTabEntryUrl } from '@renderer/utils/sidebar'
|
||||
import { IpcChannel } from '@shared/IpcChannel'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
import { resolveProviderIcon } from '@cherrystudio/ui/icons'
|
||||
import type { PermissionModeCard } from '@renderer/types/agent'
|
||||
import type { AgentBase, AgentConfiguration, AgentType } from '@shared/data/types/agent'
|
||||
|
||||
// Partial defaults — `name` and `model` are user-supplied at create time.
|
||||
// Workspace defaults belong to session creation, not to the agent blueprint.
|
||||
type PartialAgentBase = Partial<Omit<AgentBase, 'model'>>
|
||||
|
||||
export const DEFAULT_CLAUDE_CODE_CONFIG: PartialAgentBase = {} as const
|
||||
|
||||
export const DEFAULT_CHERRY_CLAW_CONFIG: PartialAgentBase & { configuration: AgentConfiguration } = {
|
||||
configuration: {
|
||||
permission_mode: 'bypassPermissions',
|
||||
max_turns: 100,
|
||||
env_vars: {},
|
||||
soul_enabled: true,
|
||||
scheduler_enabled: false,
|
||||
scheduler_type: 'interval',
|
||||
heartbeat_enabled: true,
|
||||
heartbeat_interval: 30
|
||||
}
|
||||
}
|
||||
|
||||
export const getAgentTypeAvatar = (type: AgentType) => {
|
||||
switch (type) {
|
||||
case 'claude-code':
|
||||
return resolveProviderIcon('anthropic')
|
||||
default:
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
export const permissionModeCards: PermissionModeCard[] = [
|
||||
{
|
||||
mode: 'default',
|
||||
// t('agent.settings.tooling.permissionMode.default.title')
|
||||
titleKey: 'agent.settings.tooling.permissionMode.default.title',
|
||||
titleFallback: 'Normal Mode',
|
||||
descriptionKey: 'agent.settings.tooling.permissionMode.default.description',
|
||||
descriptionFallback: 'Can read files freely. Asks before editing or running commands.'
|
||||
},
|
||||
{
|
||||
mode: 'plan',
|
||||
// t('agent.settings.tooling.permissionMode.plan.title')
|
||||
titleKey: 'agent.settings.tooling.permissionMode.plan.title',
|
||||
titleFallback: 'Plan Mode',
|
||||
descriptionKey: 'agent.settings.tooling.permissionMode.plan.description',
|
||||
descriptionFallback: 'Can only read files and make plans. Cannot edit files or run commands.'
|
||||
},
|
||||
{
|
||||
mode: 'acceptEdits',
|
||||
// t('agent.settings.tooling.permissionMode.acceptEdits.title')
|
||||
titleKey: 'agent.settings.tooling.permissionMode.acceptEdits.title',
|
||||
titleFallback: 'Auto-edit Mode',
|
||||
descriptionKey: 'agent.settings.tooling.permissionMode.acceptEdits.description',
|
||||
descriptionFallback: 'Can read and edit files freely. Asks before running commands.'
|
||||
},
|
||||
{
|
||||
mode: 'bypassPermissions',
|
||||
// t('agent.settings.tooling.permissionMode.bypassPermissions.title')
|
||||
titleKey: 'agent.settings.tooling.permissionMode.bypassPermissions.title',
|
||||
titleFallback: 'Full Auto Mode',
|
||||
descriptionKey: 'agent.settings.tooling.permissionMode.bypassPermissions.description',
|
||||
descriptionFallback: 'Can do everything without asking. Use with caution.',
|
||||
caution: true
|
||||
}
|
||||
]
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,10 +0,0 @@
|
||||
export * from './default'
|
||||
export * from './embedding'
|
||||
export * from './logo'
|
||||
export * from './openai'
|
||||
export * from './qwen'
|
||||
export * from './reasoning'
|
||||
export * from './tooluse'
|
||||
export * from './utils'
|
||||
export * from './vision'
|
||||
export * from './websearch'
|
||||
@@ -1,6 +0,0 @@
|
||||
import type { Model } from '@shared/data/types/model'
|
||||
import { isQwen35to39Model as sharedIsQwen35to39Model, isQwenMTModel as sharedIsQwenMTModel } from '@shared/utils/model'
|
||||
|
||||
export const isQwenMTModel = (model: Model): boolean => sharedIsQwenMTModel(model)
|
||||
|
||||
export const isQwen35to39Model = (model?: Model): boolean => (model ? sharedIsQwen35to39Model(model) : false)
|
||||
@@ -3,7 +3,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { useConversationNavigation } from '../useConversationNavigation'
|
||||
|
||||
// Drive the boundary over a fake tabs context; config/sidebar (the identity↔url registry)
|
||||
// Drive the boundary over a fake tabs context; utils/sidebar (the identity↔url registry)
|
||||
// runs for real, so these tests also lock the assistants/agents instanceKey wiring.
|
||||
const tabsMock = vi.hoisted(() => ({
|
||||
ctx: null as ReturnType<typeof makeCtx> | null,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { buildTabInstanceMetadata } from '@renderer/config/tabInstanceMetadata'
|
||||
import { isPageTitledRoute } from '@renderer/utils/routeTitle'
|
||||
import { emojiTabIcon } from '@renderer/utils/tabIcons'
|
||||
import { buildTabInstanceMetadata } from '@renderer/utils/tabInstanceMetadata'
|
||||
import type { Tab } from '@shared/data/cache/cacheValueTypes'
|
||||
import type { TabInstanceAppId } from '@shared/types/tabInstanceMetadata'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
@@ -21,7 +21,7 @@ import { usePreference } from '@data/hooks/usePreference'
|
||||
import { loggerService } from '@logger'
|
||||
import { useModelById } from '@renderer/hooks/useModel'
|
||||
import type { Assistant, AssistantSettings } from '@renderer/types/assistant'
|
||||
import { reconcileReasoningEffortForModel, reconcileWebSearchForModel } from '@renderer/utils/modelReconcile'
|
||||
import { reconcileReasoningEffortForModel, reconcileWebSearchForModel } from '@renderer/utils/model'
|
||||
import type { ConcreteApiPaths } from '@shared/data/api/apiTypes'
|
||||
import type { CreateAssistantDto, UpdateAssistantDto } from '@shared/data/api/schemas/assistants'
|
||||
import type { Model } from '@shared/data/types/model'
|
||||
|
||||
@@ -3,13 +3,13 @@ import {
|
||||
type ResourceListRevealSource
|
||||
} from '@renderer/components/chat/resources/resourceListRevealEvents'
|
||||
import { useWindowFrame } from '@renderer/components/chat/shell/WindowFrameContext'
|
||||
import { type TabsContextValue, useOptionalTabsContext } from '@renderer/hooks/tab'
|
||||
import {
|
||||
buildSidebarAppOpenMetadata,
|
||||
getSidebarApp,
|
||||
getSidebarAppTabInstanceKey,
|
||||
tabBelongsToApp
|
||||
} from '@renderer/config/sidebar'
|
||||
import { type TabsContextValue, useOptionalTabsContext } from '@renderer/hooks/tab'
|
||||
} from '@renderer/utils/sidebar'
|
||||
import type { SidebarIcon } from '@shared/data/preference/preferenceTypes'
|
||||
import { IpcChannel } from '@shared/IpcChannel'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
createRecentSessionEntryFromSession,
|
||||
upsertGlobalSearchRecentEntry
|
||||
} from '@renderer/components/GlobalSearch/globalSearchGroups'
|
||||
import { getTabInstanceKey } from '@renderer/config/tabInstanceMetadata'
|
||||
import { usePersistCache } from '@renderer/data/hooks/useCache'
|
||||
import { useInvalidateCache } from '@renderer/data/hooks/useDataApi'
|
||||
import { useAgent, useAgents } from '@renderer/hooks/agents/useAgent'
|
||||
@@ -21,6 +20,7 @@ import { buildAgentSessionTopicId } from '@renderer/utils/agentSession'
|
||||
import { formatErrorMessageWithPrefix } from '@renderer/utils/error'
|
||||
import { getDefaultRouteTitle } from '@renderer/utils/routeTitle'
|
||||
import { cn } from '@renderer/utils/style'
|
||||
import { getTabInstanceKey } from '@renderer/utils/tabInstanceMetadata'
|
||||
import type { AgentSessionEntity } from '@shared/data/api/schemas/agentSessions'
|
||||
import { AGENT_WORKSPACE_TYPE, type AgentSessionWorkspaceSource } from '@shared/data/api/schemas/agentWorkspaces'
|
||||
import { MIN_WINDOW_HEIGHT, SECOND_MIN_WINDOW_WIDTH } from '@shared/utils/window'
|
||||
|
||||
@@ -18,7 +18,6 @@ import ModelAvatar from '@renderer/components/Avatar/ModelAvatar'
|
||||
// resolved by tsgo on main's program (resolves on feat's); transitional, reverts
|
||||
// to the barrel once main converges with feat.
|
||||
import { ModelSelector } from '@renderer/components/Selector/model'
|
||||
import { CLAUDE_OFFICIAL_SUPPORTED_PROVIDERS, isSiliconAnthropicCompatibleModel } from '@renderer/config/codeProviders'
|
||||
import { isMac, isWin } from '@renderer/config/constant'
|
||||
import { usePersistCache } from '@renderer/data/hooks/useCache'
|
||||
import { useCodeCli } from '@renderer/hooks/useCodeCli'
|
||||
@@ -26,6 +25,10 @@ import { useModels } from '@renderer/hooks/useModel'
|
||||
import { getProviderDisplayName, useProviders } from '@renderer/hooks/useProvider'
|
||||
import { useTimer } from '@renderer/hooks/useTimer'
|
||||
import { ipcApi } from '@renderer/ipc'
|
||||
import {
|
||||
CLAUDE_OFFICIAL_SUPPORTED_PROVIDERS,
|
||||
isSiliconAnthropicCompatibleModel
|
||||
} from '@renderer/pages/code/codeProviders'
|
||||
import { loggerService } from '@renderer/services/LoggerService'
|
||||
import { EFFORT_RATIO } from '@renderer/types/reasoning'
|
||||
import { getThinkingBudget } from '@shared/ai/reasoningBudget'
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
QoderCli,
|
||||
QwenCode
|
||||
} from '@cherrystudio/ui/icons'
|
||||
import { CLAUDE_SUPPORTED_PROVIDERS } from '@renderer/config/codeProviders'
|
||||
import { CLAUDE_SUPPORTED_PROVIDERS } from '@renderer/pages/code/codeProviders'
|
||||
import { formatApiHost } from '@renderer/utils/api'
|
||||
import { sanitizeProviderName } from '@renderer/utils/naming'
|
||||
import type { EndpointType } from '@shared/data/types/model'
|
||||
|
||||
@@ -16,7 +16,6 @@ import {
|
||||
createRecentTopicEntryFromTopic,
|
||||
upsertGlobalSearchRecentEntry
|
||||
} from '@renderer/components/GlobalSearch/globalSearchGroups'
|
||||
import { getTabInstanceKey } from '@renderer/config/tabInstanceMetadata'
|
||||
import { usePersistCache } from '@renderer/data/hooks/useCache'
|
||||
import { useCommandHandler } from '@renderer/hooks/command'
|
||||
import { useCurrentTab, useCurrentTabId, useIsActiveTab, useTabSelfMetadata } from '@renderer/hooks/tab'
|
||||
@@ -29,6 +28,7 @@ import type { FileMetadata } from '@renderer/types/file'
|
||||
import type { Topic } from '@renderer/types/topic'
|
||||
import { getDefaultRouteTitle } from '@renderer/utils/routeTitle'
|
||||
import { cn } from '@renderer/utils/style'
|
||||
import { getTabInstanceKey } from '@renderer/utils/tabInstanceMetadata'
|
||||
import type { CherryMessagePart } from '@shared/data/types/message'
|
||||
import type { UniqueModelId } from '@shared/data/types/model'
|
||||
import { MIN_WINDOW_HEIGHT, SECOND_MIN_WINDOW_WIDTH } from '@shared/utils/window'
|
||||
|
||||
@@ -62,14 +62,10 @@ vi.mock('@renderer/components/Selector', () => ({
|
||||
ModelSelector: ({ trigger }: { trigger: ReactNode }) => <>{trigger}</>
|
||||
}))
|
||||
|
||||
vi.mock('@renderer/config/models', () => ({
|
||||
vi.mock('@renderer/utils/model', () => ({
|
||||
isVisionModel: vi.fn(() => false)
|
||||
}))
|
||||
|
||||
vi.mock('@renderer/config/models/_bridge', () => ({
|
||||
toSharedCompatModel: vi.fn(() => undefined)
|
||||
}))
|
||||
|
||||
vi.mock('@renderer/components/chat/messages/editing/MessageEditingContext', () => ({
|
||||
useMessageEditing: () => ({ editingMessageId: null, editingMessage: null, startEditing: vi.fn() })
|
||||
}))
|
||||
|
||||
@@ -33,7 +33,6 @@ import {
|
||||
toMessageListItem
|
||||
} from '@renderer/components/chat/messages/utils/messageListItem'
|
||||
import { ModelSelector } from '@renderer/components/Selector'
|
||||
import { isVisionModel } from '@renderer/config/models'
|
||||
import { useChatWrite } from '@renderer/hooks/chat/ChatWriteContext'
|
||||
import { useCommandHandler } from '@renderer/hooks/command'
|
||||
import { SiblingsContext } from '@renderer/hooks/SiblingsContext'
|
||||
@@ -45,6 +44,7 @@ import { formatErrorMessageWithPrefix, isAbortError } from '@renderer/utils/erro
|
||||
import { updateCodeBlock } from '@renderer/utils/markdown'
|
||||
import { createComposerRichClipboardContentFromParts } from '@renderer/utils/message/composerClipboard'
|
||||
import { getComposerTextFromParts } from '@renderer/utils/message/composerTokens'
|
||||
import { isVisionModel } from '@renderer/utils/model'
|
||||
import { translateText } from '@renderer/utils/translate'
|
||||
import type { TranslateLangCode } from '@shared/data/preference/preferenceTypes'
|
||||
import type { CherryMessagePart, CherryUIMessage, ModelSnapshot } from '@shared/data/types/message'
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import { usePreference } from '@data/hooks/usePreference'
|
||||
import { SIDEBAR_ICON_COMPONENTS } from '@renderer/components/app/sidebarIcons'
|
||||
import { CommandContextMenu, type CommandContextMenuExtraItem } from '@renderer/components/command'
|
||||
import App from '@renderer/components/MiniApp/MiniApp'
|
||||
import Scrollbar from '@renderer/components/Scrollbar'
|
||||
import { useMiniApps } from '@renderer/hooks/useMiniApps'
|
||||
import { getSidebarIconLabelKey } from '@renderer/i18n/label'
|
||||
import {
|
||||
getRequiredSidebarIconsVisible,
|
||||
getSidebarMenuPath,
|
||||
REQUIRED_SIDEBAR_ICONS,
|
||||
sanitizeSidebarIcons,
|
||||
SIDEBAR_ICON_COMPONENTS,
|
||||
SIDEBAR_ICON_ORDER
|
||||
} from '@renderer/config/sidebar'
|
||||
import { useMiniApps } from '@renderer/hooks/useMiniApps'
|
||||
import { getSidebarIconLabelKey } from '@renderer/i18n/label'
|
||||
} from '@renderer/utils/sidebar'
|
||||
import type { SidebarFavorite, SidebarIcon } from '@shared/data/preference/preferenceTypes'
|
||||
import type { MiniApp as MiniAppType } from '@shared/data/types/miniApp'
|
||||
import { useNavigate } from '@tanstack/react-router'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { loggerService } from '@logger'
|
||||
import { LogoAvatar } from '@renderer/components/Icons'
|
||||
import { getMiniAppsLogo } from '@renderer/config/miniApps'
|
||||
import { getMiniAppsLogo } from '@renderer/components/Icons/miniAppsLogo'
|
||||
import { useCurrentTab, useCurrentTabId } from '@renderer/hooks/tab'
|
||||
import { useOptionalTabsContext } from '@renderer/hooks/tab'
|
||||
import { useMiniAppPopup } from '@renderer/hooks/useMiniAppPopup'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Scrollbar, Sortable, Tooltip } from '@cherrystudio/ui'
|
||||
import { LogoAvatar } from '@renderer/components/Icons'
|
||||
import { getMiniAppsLogo } from '@renderer/config/miniApps'
|
||||
import { getMiniAppsLogo } from '@renderer/components/Icons/miniAppsLogo'
|
||||
import type { MiniApp } from '@shared/data/types/miniApp'
|
||||
import { ArrowLeftToLine, ArrowRightToLine } from 'lucide-react'
|
||||
import type { FC } from 'react'
|
||||
|
||||
@@ -30,7 +30,7 @@ vi.mock('@renderer/components/Icons', () => ({
|
||||
LogoAvatar: ({ logo }: { logo: string }) => <span data-testid={`logo-${logo}`} />
|
||||
}))
|
||||
|
||||
vi.mock('@renderer/config/miniApps', () => ({
|
||||
vi.mock('@renderer/components/Icons/miniAppsLogo', () => ({
|
||||
getMiniAppsLogo: () => undefined
|
||||
}))
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
} from '@cherrystudio/ui'
|
||||
import { loggerService } from '@logger'
|
||||
import { LogoAvatar } from '@renderer/components/Icons'
|
||||
import { getMiniAppsLogo } from '@renderer/config/miniApps'
|
||||
import { getMiniAppsLogo } from '@renderer/components/Icons/miniAppsLogo'
|
||||
import { useMiniApps } from '@renderer/hooks/useMiniApps'
|
||||
import { compressImage, convertToBase64 } from '@renderer/utils/image'
|
||||
import { uuid } from '@renderer/utils/uuid'
|
||||
|
||||
@@ -29,7 +29,7 @@ vi.mock('@renderer/components/Icons', () => ({
|
||||
LogoAvatar: ({ logo }: { logo: unknown }) => <img alt="miniapp-logo-preview" data-logo={String(logo)} />
|
||||
}))
|
||||
|
||||
vi.mock('@renderer/config/miniApps', () => ({
|
||||
vi.mock('@renderer/components/Icons/miniAppsLogo', () => ({
|
||||
getMiniAppsLogo: (logo?: string) => (logo === 'application' ? 'application-icon' : undefined)
|
||||
}))
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { isMac, isWin } from '@renderer/config/constant'
|
||||
import type {
|
||||
BuiltinOcrProvider,
|
||||
BuiltinOcrProviderId,
|
||||
@@ -10,8 +11,6 @@ import type {
|
||||
} from '@renderer/types/ocr'
|
||||
import { parseTranslateLangCode } from '@shared/data/preference/preferenceTypes'
|
||||
|
||||
import { isMac, isWin } from './constant'
|
||||
|
||||
const tesseract: OcrTesseractProvider = {
|
||||
id: 'tesseract',
|
||||
name: 'Tesseract',
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { CompoundIcon } from '@cherrystudio/ui'
|
||||
import { Application, Doc2x, Intel, Mineru, Mistral, Paddleocr, TesseractJs } from '@cherrystudio/ui/icons'
|
||||
import { isWin } from '@renderer/config/constant'
|
||||
import { TESSERACT_LANG_MAP } from '@renderer/config/ocr'
|
||||
import { TESSERACT_LANG_MAP } from '@renderer/pages/settings/FileProcessingSettings/ocr'
|
||||
import type { FileProcessorFeature, FileProcessorId } from '@shared/data/preference/preferenceTypes'
|
||||
import type { FileProcessorFeatureCapability, FileProcessorMerged } from '@shared/data/presets/fileProcessing'
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Badge, Button, Popover, PopoverContent, PopoverTrigger, Tabs, TabsList, TabsTrigger } from '@cherrystudio/ui'
|
||||
import CollapsibleSearchBar from '@renderer/components/CollapsibleSearchBar'
|
||||
import { builtinMcpServers } from '@renderer/config/builtinMcpServers'
|
||||
import { useMcpServers } from '@renderer/hooks/useMcpServer'
|
||||
import { getBuiltInMcpServerDescriptionLabelKey } from '@renderer/i18n/label'
|
||||
import { builtinMcpServers } from '@renderer/pages/settings/McpSettings/builtinMcpServers'
|
||||
import { cn } from '@renderer/utils/style'
|
||||
import { Check, Plus } from 'lucide-react'
|
||||
import type { FC } from 'react'
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
Tooltip
|
||||
} from '@cherrystudio/ui'
|
||||
import Scrollbar from '@renderer/components/Scrollbar'
|
||||
import { getModelLogo } from '@renderer/config/models'
|
||||
import type {
|
||||
ModelHealthCheckGenerationOutput,
|
||||
ModelHealthCheckSkipReason,
|
||||
@@ -18,6 +17,7 @@ import type {
|
||||
} from '@renderer/pages/settings/ProviderSettings/types/healthCheck'
|
||||
import { HealthStatus } from '@renderer/pages/settings/ProviderSettings/types/healthCheck'
|
||||
import { maskApiKey } from '@renderer/utils/api'
|
||||
import { getModelLogo } from '@renderer/utils/model'
|
||||
import { cn } from '@renderer/utils/style'
|
||||
import { CheckCircle2, Info, Loader2, XCircle } from 'lucide-react'
|
||||
import { type ReactNode, useEffect, useMemo, useState } from 'react'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Avatar, AvatarFallback, Button, RowFlex, Switch, Tooltip } from '@cherrystudio/ui'
|
||||
import { getModelLogo } from '@renderer/config/models'
|
||||
import { getModelLogo } from '@renderer/utils/model'
|
||||
import { cn } from '@renderer/utils/style'
|
||||
import type { Model } from '@shared/data/types/model'
|
||||
import { Settings, Trash2 } from 'lucide-react'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Alert, Button, Checkbox } from '@cherrystudio/ui'
|
||||
import { getModelLogo } from '@renderer/config/models'
|
||||
import { getModelLogo } from '@renderer/utils/model'
|
||||
import type { Model } from '@shared/data/types/model'
|
||||
import { parseUniqueModelId, type UniqueModelId } from '@shared/data/types/model'
|
||||
import { CheckCircle2, Plus, Trash2 } from 'lucide-react'
|
||||
|
||||
@@ -46,7 +46,7 @@ vi.mock('@cherrystudio/ui', async (importOriginal) => {
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('@renderer/config/models', async (importOriginal) => ({
|
||||
vi.mock('@renderer/utils/model', async (importOriginal) => ({
|
||||
...(await importOriginal<object>()),
|
||||
getModelLogo: () => null
|
||||
}))
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type * as ModelModule from '@renderer/utils/model'
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
@@ -46,7 +47,8 @@ vi.mock('@cherrystudio/ui', async (importOriginal) => {
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('@renderer/config/models', () => ({
|
||||
vi.mock('@renderer/utils/model', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof ModelModule>()),
|
||||
getModelLogo: () => null
|
||||
}))
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getSearchMatchScore } from '@renderer/utils/modelSearch'
|
||||
import { getSearchMatchScore } from '@renderer/utils/model'
|
||||
import type { Model } from '@shared/data/types/model'
|
||||
|
||||
export const isValidNewApiModel = (model: Model): boolean => !!(model.endpointTypes && model.endpointTypes.length > 0)
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Button, RowFlex } from '@cherrystudio/ui'
|
||||
import { resolveProviderIcon } from '@cherrystudio/ui/icons'
|
||||
import OauthButton from '@renderer/components/Oauth/OauthButton'
|
||||
import { PROVIDER_URLS } from '@renderer/config/providers'
|
||||
import { useProvider } from '@renderer/hooks/useProvider'
|
||||
import { getProviderLabelKey } from '@renderer/i18n/label'
|
||||
import { oauthCardClasses } from '@renderer/pages/settings/ProviderSettings/primitives/ProviderSettingsPrimitives'
|
||||
import { PROVIDER_URLS } from '@renderer/pages/settings/ProviderSettings/providerUrls'
|
||||
import { providerBills, providerCharge } from '@renderer/utils/oauth'
|
||||
import { hasApiKeys } from '@shared/utils/provider'
|
||||
import { CircleDollarSign, ReceiptText } from 'lucide-react'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { loggerService } from '@logger'
|
||||
import { PROVIDER_URLS } from '@renderer/config/providers'
|
||||
import { PROVIDER_URLS } from '@renderer/pages/settings/ProviderSettings/providerUrls'
|
||||
import { validateApiHost } from '@renderer/utils/api'
|
||||
import { ErrorCode, isDataApiError, isSerializedDataApiError, toDataApiError } from '@shared/data/api'
|
||||
import { ENDPOINT_TYPE } from '@shared/data/types/model'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { PROVIDER_URLS } from '@renderer/config/providers'
|
||||
import { useProviderAuthConfig } from '@renderer/hooks/useProvider'
|
||||
import { PROVIDER_URLS } from '@renderer/pages/settings/ProviderSettings/providerUrls'
|
||||
import type { Provider } from '@shared/data/types/provider'
|
||||
import { getProviderHostTopology } from '@shared/utils/providerTopology'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
@@ -1,686 +1,5 @@
|
||||
import { OpenAIServiceTiers, type SystemProvider, type SystemProviderId } from '@renderer/types/provider'
|
||||
import { CHERRYAI_API_BASE_URL, CHERRYAI_PROVIDER_ID, CHERRYAI_PROVIDER_NAME } from '@shared/data/presets/cherryai'
|
||||
|
||||
import { TOKENFLUX_HOST } from './constant'
|
||||
import { qwenModel, SYSTEM_MODELS } from './models'
|
||||
|
||||
export const CHERRYAI_PROVIDER: SystemProvider = {
|
||||
id: CHERRYAI_PROVIDER_ID as SystemProviderId,
|
||||
name: CHERRYAI_PROVIDER_NAME,
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: CHERRYAI_API_BASE_URL,
|
||||
models: [qwenModel],
|
||||
isSystem: true,
|
||||
enabled: true
|
||||
}
|
||||
|
||||
export const SYSTEM_PROVIDERS_CONFIG: Record<SystemProviderId, SystemProvider> = {
|
||||
cherryin: {
|
||||
id: 'cherryin',
|
||||
name: 'CherryIN',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'https://open.cherryin.cc',
|
||||
anthropicApiHost: 'https://open.cherryin.cc',
|
||||
models: [],
|
||||
isSystem: true,
|
||||
enabled: true
|
||||
},
|
||||
silicon: {
|
||||
id: 'silicon',
|
||||
name: 'Silicon',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'https://api.siliconflow.cn',
|
||||
anthropicApiHost: 'https://api.siliconflow.cn',
|
||||
models: SYSTEM_MODELS.silicon,
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
aihubmix: {
|
||||
id: 'aihubmix',
|
||||
name: 'AiHubMix',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'https://aihubmix.com',
|
||||
anthropicApiHost: 'https://aihubmix.com',
|
||||
models: SYSTEM_MODELS.aihubmix,
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
ovms: {
|
||||
id: 'ovms',
|
||||
name: 'OpenVINO Model Server',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'http://localhost:8000/v3/',
|
||||
models: SYSTEM_MODELS.ovms,
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
ocoolai: {
|
||||
id: 'ocoolai',
|
||||
name: 'ocoolAI',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'https://api.ocoolai.com',
|
||||
models: SYSTEM_MODELS.ocoolai,
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
zhipu: {
|
||||
id: 'zhipu',
|
||||
name: 'ZhiPu',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'https://open.bigmodel.cn/api/paas/v4/',
|
||||
anthropicApiHost: 'https://open.bigmodel.cn/api/anthropic',
|
||||
models: SYSTEM_MODELS.zhipu,
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
zai: {
|
||||
id: 'zai',
|
||||
name: 'Z.ai',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'https://api.z.ai/api/paas/v4/',
|
||||
anthropicApiHost: 'https://api.z.ai/api/anthropic',
|
||||
models: SYSTEM_MODELS.zai,
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
deepseek: {
|
||||
id: 'deepseek',
|
||||
name: 'deepseek',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'https://api.deepseek.com',
|
||||
anthropicApiHost: 'https://api.deepseek.com/anthropic',
|
||||
models: SYSTEM_MODELS.deepseek,
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
alayanew: {
|
||||
id: 'alayanew',
|
||||
name: 'AlayaNew',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'https://deepseek.alayanew.com',
|
||||
models: SYSTEM_MODELS.alayanew,
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
dmxapi: {
|
||||
id: 'dmxapi',
|
||||
name: 'DMXAPI',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'https://www.dmxapi.cn',
|
||||
anthropicApiHost: 'https://www.dmxapi.cn',
|
||||
models: SYSTEM_MODELS.dmxapi,
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
aionly: {
|
||||
id: 'aionly',
|
||||
name: 'AIOnly',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'https://api.aiionly.com',
|
||||
models: SYSTEM_MODELS.aionly,
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
burncloud: {
|
||||
id: 'burncloud',
|
||||
name: 'BurnCloud',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'https://ai.burncloud.com',
|
||||
models: SYSTEM_MODELS.burncloud,
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
tokenflux: {
|
||||
id: 'tokenflux',
|
||||
name: 'TokenFlux',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'https://api.tokenflux.ai/openai/v1',
|
||||
anthropicApiHost: 'https://api.tokenflux.ai/anthropic',
|
||||
models: SYSTEM_MODELS.tokenflux,
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
'302ai': {
|
||||
id: '302ai',
|
||||
name: '302.AI',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'https://api.302.ai',
|
||||
anthropicApiHost: 'https://api.302.ai',
|
||||
models: SYSTEM_MODELS['302ai'],
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
cephalon: {
|
||||
id: 'cephalon',
|
||||
name: 'Cephalon',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'https://cephalon.cloud/user-center/v1/model',
|
||||
models: SYSTEM_MODELS.cephalon,
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
lanyun: {
|
||||
id: 'lanyun',
|
||||
name: 'LANYUN',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'https://maas-api.lanyun.net',
|
||||
models: SYSTEM_MODELS.lanyun,
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
ph8: {
|
||||
id: 'ph8',
|
||||
name: 'PH8',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'https://ph8.co',
|
||||
models: SYSTEM_MODELS.ph8,
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
sophnet: {
|
||||
id: 'sophnet',
|
||||
name: 'SophNet',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'https://www.sophnet.com/api/open-apis/v1',
|
||||
models: [],
|
||||
isSystem: true,
|
||||
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
|
||||
},
|
||||
dashscope: {
|
||||
id: 'dashscope',
|
||||
name: 'Bailian',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'https://dashscope.aliyuncs.com/compatible-mode/v1/',
|
||||
anthropicApiHost: 'https://dashscope.aliyuncs.com/apps/anthropic',
|
||||
models: SYSTEM_MODELS.dashscope,
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
minimax: {
|
||||
id: 'minimax',
|
||||
name: 'MiniMax',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'https://api.minimaxi.com/v1/',
|
||||
anthropicApiHost: 'https://api.minimaxi.com/anthropic',
|
||||
models: SYSTEM_MODELS.minimax,
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
'minimax-global': {
|
||||
id: 'minimax-global',
|
||||
name: 'MiniMax Global',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'https://api.minimax.io/v1/',
|
||||
anthropicApiHost: 'https://api.minimax.io/anthropic',
|
||||
models: SYSTEM_MODELS['minimax-global'],
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
moonshot: {
|
||||
id: 'moonshot',
|
||||
name: 'Moonshot AI',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'https://api.moonshot.cn',
|
||||
anthropicApiHost: 'https://api.moonshot.cn/anthropic',
|
||||
models: SYSTEM_MODELS.moonshot,
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
qiniu: {
|
||||
id: 'qiniu',
|
||||
name: 'Qiniu',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'https://api.qnaigc.com',
|
||||
anthropicApiHost: 'https://api.qnaigc.com',
|
||||
models: SYSTEM_MODELS.qiniu,
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
openrouter: {
|
||||
id: 'openrouter',
|
||||
name: 'OpenRouter',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'https://openrouter.ai/api/v1/',
|
||||
// Anthropic-compatible endpoint for Agent mode (Claude Code SDK)
|
||||
// https://openrouter.ai/docs/guides/guides/coding-agents/claude-code-integration
|
||||
anthropicApiHost: 'https://openrouter.ai/api',
|
||||
models: SYSTEM_MODELS.openrouter,
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
'new-api': {
|
||||
id: 'new-api',
|
||||
name: 'New API',
|
||||
type: 'new-api',
|
||||
apiKey: '',
|
||||
apiHost: 'http://localhost:3000',
|
||||
anthropicApiHost: 'http://localhost:3000',
|
||||
models: SYSTEM_MODELS['new-api'],
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
ollama: {
|
||||
id: 'ollama',
|
||||
name: 'Ollama',
|
||||
type: 'ollama',
|
||||
apiKey: '',
|
||||
apiHost: 'http://localhost:11434',
|
||||
anthropicApiHost: 'http://localhost:11434',
|
||||
models: SYSTEM_MODELS.ollama,
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
lmstudio: {
|
||||
id: 'lmstudio',
|
||||
name: 'LM Studio',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'http://localhost:1234',
|
||||
anthropicApiHost: 'http://localhost:1234',
|
||||
models: SYSTEM_MODELS.lmstudio,
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
anthropic: {
|
||||
id: 'anthropic',
|
||||
name: 'Anthropic',
|
||||
type: 'anthropic',
|
||||
apiKey: '',
|
||||
apiHost: 'https://api.anthropic.com',
|
||||
models: SYSTEM_MODELS.anthropic,
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
openai: {
|
||||
id: 'openai',
|
||||
name: 'OpenAI',
|
||||
type: 'openai-response',
|
||||
apiKey: '',
|
||||
apiHost: 'https://api.openai.com',
|
||||
models: SYSTEM_MODELS.openai,
|
||||
isSystem: true,
|
||||
enabled: false,
|
||||
serviceTier: OpenAIServiceTiers.auto
|
||||
},
|
||||
'azure-openai': {
|
||||
id: 'azure-openai',
|
||||
name: 'Azure OpenAI',
|
||||
type: 'azure-openai',
|
||||
apiKey: '',
|
||||
apiHost: '',
|
||||
apiVersion: '',
|
||||
models: SYSTEM_MODELS['azure-openai'],
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
gemini: {
|
||||
id: 'gemini',
|
||||
name: 'Gemini',
|
||||
type: 'gemini',
|
||||
apiKey: '',
|
||||
apiHost: 'https://generativelanguage.googleapis.com',
|
||||
models: SYSTEM_MODELS.gemini,
|
||||
isSystem: true,
|
||||
enabled: false,
|
||||
isVertex: false
|
||||
},
|
||||
vertexai: {
|
||||
id: 'vertexai',
|
||||
name: 'VertexAI',
|
||||
type: 'vertexai',
|
||||
apiKey: '',
|
||||
apiHost: '',
|
||||
models: SYSTEM_MODELS.vertexai,
|
||||
isSystem: true,
|
||||
enabled: false,
|
||||
isVertex: true
|
||||
},
|
||||
github: {
|
||||
id: 'github',
|
||||
name: 'Github Models',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'https://models.github.ai/inference',
|
||||
models: SYSTEM_MODELS.github,
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
copilot: {
|
||||
id: 'copilot',
|
||||
name: 'Github Copilot',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'https://api.githubcopilot.com/',
|
||||
models: SYSTEM_MODELS.copilot,
|
||||
isSystem: true,
|
||||
enabled: false,
|
||||
isAuthed: false
|
||||
},
|
||||
doubao: {
|
||||
id: 'doubao',
|
||||
name: 'doubao',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'https://ark.cn-beijing.volces.com/api/v3/',
|
||||
models: SYSTEM_MODELS.doubao,
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
baichuan: {
|
||||
id: 'baichuan',
|
||||
name: 'BAICHUAN AI',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'https://api.baichuan-ai.com',
|
||||
models: SYSTEM_MODELS.baichuan,
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
stepfun: {
|
||||
id: 'stepfun',
|
||||
name: 'StepFun',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'https://api.stepfun.com',
|
||||
anthropicApiHost: 'https://api.stepfun.com',
|
||||
models: SYSTEM_MODELS.stepfun,
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
yi: {
|
||||
id: 'yi',
|
||||
name: 'Yi',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'https://api.lingyiwanwu.com',
|
||||
models: SYSTEM_MODELS.yi,
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
infini: {
|
||||
id: 'infini',
|
||||
name: 'Infini',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'https://cloud.infini-ai.com/maas',
|
||||
models: SYSTEM_MODELS.infini,
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
groq: {
|
||||
id: 'groq',
|
||||
name: 'Groq',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'https://api.groq.com/openai',
|
||||
models: SYSTEM_MODELS.groq,
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
together: {
|
||||
id: 'together',
|
||||
name: 'Together',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'https://api.together.xyz',
|
||||
models: SYSTEM_MODELS.together,
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
fireworks: {
|
||||
id: 'fireworks',
|
||||
name: 'Fireworks',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'https://api.fireworks.ai/inference',
|
||||
models: SYSTEM_MODELS.fireworks,
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
nvidia: {
|
||||
id: 'nvidia',
|
||||
name: 'nvidia',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'https://integrate.api.nvidia.com',
|
||||
models: SYSTEM_MODELS.nvidia,
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
grok: {
|
||||
id: 'grok',
|
||||
name: 'Grok',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'https://api.x.ai',
|
||||
models: SYSTEM_MODELS.grok,
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
hyperbolic: {
|
||||
id: 'hyperbolic',
|
||||
name: 'Hyperbolic',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'https://api.hyperbolic.xyz',
|
||||
models: SYSTEM_MODELS.hyperbolic,
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
mistral: {
|
||||
id: 'mistral',
|
||||
name: 'Mistral',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'https://api.mistral.ai',
|
||||
models: SYSTEM_MODELS.mistral,
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
jina: {
|
||||
id: 'jina',
|
||||
name: 'Jina',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'https://api.jina.ai',
|
||||
models: SYSTEM_MODELS.jina,
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
perplexity: {
|
||||
id: 'perplexity',
|
||||
name: 'Perplexity',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'https://api.perplexity.ai/',
|
||||
models: SYSTEM_MODELS.perplexity,
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
modelscope: {
|
||||
id: 'modelscope',
|
||||
name: 'ModelScope',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'https://api-inference.modelscope.cn/v1/',
|
||||
anthropicApiHost: 'https://api-inference.modelscope.cn',
|
||||
models: SYSTEM_MODELS.modelscope,
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
xirang: {
|
||||
id: 'xirang',
|
||||
name: 'Xirang',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'https://wishub-x1.ctyun.cn',
|
||||
models: SYSTEM_MODELS.xirang,
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
hunyuan: {
|
||||
id: 'hunyuan',
|
||||
name: 'hunyuan',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'https://api.hunyuan.cloud.tencent.com',
|
||||
models: SYSTEM_MODELS.hunyuan,
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
'tencent-cloud-ti': {
|
||||
id: 'tencent-cloud-ti',
|
||||
name: 'Tencent Cloud TI',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'https://api.lkeap.cloud.tencent.com',
|
||||
models: SYSTEM_MODELS['tencent-cloud-ti'],
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
'baidu-cloud': {
|
||||
id: 'baidu-cloud',
|
||||
name: 'Baidu Cloud',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'https://qianfan.baidubce.com/v2/',
|
||||
models: SYSTEM_MODELS['baidu-cloud'],
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
gpustack: {
|
||||
id: 'gpustack',
|
||||
name: 'GPUStack',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: '',
|
||||
models: SYSTEM_MODELS.gpustack,
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
voyageai: {
|
||||
id: 'voyageai',
|
||||
name: 'VoyageAI',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'https://api.voyageai.com',
|
||||
models: SYSTEM_MODELS.voyageai,
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
'aws-bedrock': {
|
||||
id: 'aws-bedrock',
|
||||
name: 'AWS Bedrock',
|
||||
type: 'aws-bedrock',
|
||||
apiKey: '',
|
||||
apiHost: '',
|
||||
models: SYSTEM_MODELS['aws-bedrock'],
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
poe: {
|
||||
id: 'poe',
|
||||
name: 'Poe',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'https://api.poe.com/v1/',
|
||||
models: SYSTEM_MODELS['poe'],
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
longcat: {
|
||||
id: 'longcat',
|
||||
name: 'LongCat',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'https://api.longcat.chat/openai',
|
||||
anthropicApiHost: 'https://api.longcat.chat/anthropic',
|
||||
models: SYSTEM_MODELS.longcat,
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
huggingface: {
|
||||
id: 'huggingface',
|
||||
name: 'Hugging Face',
|
||||
type: 'openai-response',
|
||||
apiKey: '',
|
||||
apiHost: 'https://router.huggingface.co/v1/',
|
||||
models: [],
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
gateway: {
|
||||
id: 'gateway',
|
||||
name: 'Vercel AI Gateway',
|
||||
type: 'gateway',
|
||||
apiKey: '',
|
||||
apiHost: 'https://ai-gateway.vercel.sh/v1/ai',
|
||||
models: [],
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
cerebras: {
|
||||
id: 'cerebras',
|
||||
name: 'Cerebras AI',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'https://api.cerebras.ai/v1',
|
||||
models: SYSTEM_MODELS.cerebras,
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
mimo: {
|
||||
id: 'mimo',
|
||||
name: 'Xiaomi MiMo',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'https://api.xiaomimimo.com',
|
||||
anthropicApiHost: 'https://api.xiaomimimo.com/anthropic',
|
||||
models: SYSTEM_MODELS.mimo,
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
}
|
||||
} as const
|
||||
|
||||
export const SYSTEM_PROVIDERS: SystemProvider[] = Object.values(SYSTEM_PROVIDERS_CONFIG)
|
||||
|
||||
export const NOT_SUPPORTED_RERANK_PROVIDERS = ['ollama', 'lmstudio'] as const satisfies SystemProviderId[]
|
||||
export const ONLY_SUPPORTED_DIMENSION_PROVIDERS = ['ollama', 'infini'] as const satisfies SystemProviderId[]
|
||||
import { TOKENFLUX_HOST } from '@renderer/config/constant'
|
||||
import type { SystemProviderId } from '@renderer/types/provider'
|
||||
|
||||
type ProviderUrls = {
|
||||
api: {
|
||||
@@ -11,11 +11,6 @@ vi.mock('../ModelService', () => ({
|
||||
readDefaultModel: vi.fn().mockResolvedValue(undefined)
|
||||
}))
|
||||
|
||||
// Mock CHERRYAI_PROVIDER
|
||||
vi.mock('@renderer/config/providers', () => ({
|
||||
CHERRYAI_PROVIDER: { id: 'cherryai', type: 'openai', apiHost: 'https://api.cherry-ai.com', models: [] }
|
||||
}))
|
||||
|
||||
// Mock LoggerService
|
||||
vi.mock('@renderer/services/LoggerService', () => ({
|
||||
loggerService: {
|
||||
|
||||
@@ -7,14 +7,6 @@ import { afterEach, beforeEach, describe, expect, it, test, vi } from 'vitest'
|
||||
|
||||
// --- Mocks Setup ---
|
||||
|
||||
// Add this before the test suites
|
||||
vi.mock('@renderer/config/miniApps', () => {
|
||||
return {
|
||||
PRESETS_MINI_APPS: [],
|
||||
allMiniApps: []
|
||||
}
|
||||
})
|
||||
|
||||
// Mock window.api
|
||||
beforeEach(() => {
|
||||
Object.defineProperty(window, 'api', {
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { SIDEBAR_ICON_COMPONENTS } from '@renderer/components/app/sidebarIcons'
|
||||
import { Library } from 'lucide-react'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import {
|
||||
getDefaultSidebarFavorites,
|
||||
getOrderedVisibleSidebarIcons,
|
||||
getRequiredSidebarIconsVisible,
|
||||
getSidebarMenuPath,
|
||||
resolveSidebarActiveItem,
|
||||
SIDEBAR_ICON_COMPONENTS,
|
||||
SIDEBAR_ICON_ORDER
|
||||
} from '../sidebar'
|
||||
|
||||
@@ -43,12 +42,6 @@ describe('sidebar config helpers', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('resets to default sidebar favorites without forcing non-default icons visible', () => {
|
||||
const reset = getDefaultSidebarFavorites()
|
||||
|
||||
expect(reset).toEqual(['assistants', 'agents', 'store', 'translate', 'mini_app'])
|
||||
})
|
||||
|
||||
it('resolves menu paths and active items with the paintings provider route', () => {
|
||||
expect(getSidebarMenuPath('paintings', 'zhipu')).toBe('/app/paintings/zhipu')
|
||||
expect(resolveSidebarActiveItem('/app/paintings/zhipu')).toBe('paintings')
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { PermissionModeCard } from '@renderer/types/agent'
|
||||
import type { AgentConfiguration } from '@shared/data/types/agent'
|
||||
|
||||
export const DEFAULT_AGENT_AVATAR = '🤖'
|
||||
@@ -9,3 +10,39 @@ export function getAgentAvatar(avatar?: unknown) {
|
||||
export function getAgentAvatarFromConfiguration(configuration?: Pick<AgentConfiguration, 'avatar'> | null) {
|
||||
return getAgentAvatar(configuration?.avatar)
|
||||
}
|
||||
|
||||
export const permissionModeCards: PermissionModeCard[] = [
|
||||
{
|
||||
mode: 'default',
|
||||
// t('agent.settings.tooling.permissionMode.default.title')
|
||||
titleKey: 'agent.settings.tooling.permissionMode.default.title',
|
||||
titleFallback: 'Normal Mode',
|
||||
descriptionKey: 'agent.settings.tooling.permissionMode.default.description',
|
||||
descriptionFallback: 'Can read files freely. Asks before editing or running commands.'
|
||||
},
|
||||
{
|
||||
mode: 'plan',
|
||||
// t('agent.settings.tooling.permissionMode.plan.title')
|
||||
titleKey: 'agent.settings.tooling.permissionMode.plan.title',
|
||||
titleFallback: 'Plan Mode',
|
||||
descriptionKey: 'agent.settings.tooling.permissionMode.plan.description',
|
||||
descriptionFallback: 'Can only read files and make plans. Cannot edit files or run commands.'
|
||||
},
|
||||
{
|
||||
mode: 'acceptEdits',
|
||||
// t('agent.settings.tooling.permissionMode.acceptEdits.title')
|
||||
titleKey: 'agent.settings.tooling.permissionMode.acceptEdits.title',
|
||||
titleFallback: 'Auto-edit Mode',
|
||||
descriptionKey: 'agent.settings.tooling.permissionMode.acceptEdits.description',
|
||||
descriptionFallback: 'Can read and edit files freely. Asks before running commands.'
|
||||
},
|
||||
{
|
||||
mode: 'bypassPermissions',
|
||||
// t('agent.settings.tooling.permissionMode.bypassPermissions.title')
|
||||
titleKey: 'agent.settings.tooling.permissionMode.bypassPermissions.title',
|
||||
titleFallback: 'Full Auto Mode',
|
||||
descriptionKey: 'agent.settings.tooling.permissionMode.bypassPermissions.description',
|
||||
descriptionFallback: 'Can do everything without asking. Use with caution.',
|
||||
caution: true
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { isFunctionCallingModel } from '@renderer/config/models'
|
||||
import { isFunctionCallingModel } from '@renderer/utils/model'
|
||||
import type { Model } from '@shared/data/types/model'
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { type Model, MODEL_CAPABILITY, type ModelCapability } from '@shared/data/types/model'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { canModelUseAssistantWebSearch, reconcileWebSearchForModel } from '../modelReconcile'
|
||||
import { canModelUseAssistantWebSearch, reconcileWebSearchForModel } from '../reconcile'
|
||||
|
||||
const createModel = (capabilities: ModelCapability[] = []): Model => ({
|
||||
id: 'provider::model',
|
||||
@@ -14,7 +14,7 @@ const createModel = (capabilities: ModelCapability[] = []): Model => ({
|
||||
isHidden: false
|
||||
})
|
||||
|
||||
describe('modelReconcile web search', () => {
|
||||
describe('reconcile web search', () => {
|
||||
it('rejects enabled web search when the next model cannot consume it', () => {
|
||||
const nextModel = createModel()
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { getSearchMatchScore, type ModelSearchField } from '../modelSearch'
|
||||
import { getSearchMatchScore, type ModelSearchField } from '../search'
|
||||
|
||||
describe('modelSearch', () => {
|
||||
describe('search', () => {
|
||||
const fields = [
|
||||
{ value: 'GPT-4o', weight: 0, allowAbbreviation: true },
|
||||
{ value: 'gpt-4o-mini', weight: 1, allowAbbreviation: true }
|
||||
30
src/renderer/utils/model/index.ts
Normal file
30
src/renderer/utils/model/index.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
// Curated public surface for the renderer model helpers.
|
||||
// Named re-exports only (no `export *`) per naming-conventions §5.
|
||||
|
||||
export { isEmbeddingModel, isRerankModel } from './embedding'
|
||||
export { getModelLogo } from './logo'
|
||||
export { isGPT5SeriesReasoningModel } from './openai'
|
||||
export {
|
||||
getModelSupportedReasoningEffortOptions,
|
||||
getThinkModelType,
|
||||
isDoubaoThinkingAutoModel,
|
||||
isFixedReasoningModel,
|
||||
isQwenReasoningModel,
|
||||
isReasoningModel,
|
||||
isSupportedReasoningEffortModel,
|
||||
isSupportedThinkingTokenModel,
|
||||
isSupportedThinkingTokenQwenModel,
|
||||
MODEL_SUPPORTED_OPTIONS,
|
||||
MODEL_SUPPORTED_REASONING_EFFORT
|
||||
} from './reasoning'
|
||||
export {
|
||||
canModelUseAssistantWebSearch,
|
||||
hasModelBuiltinWebSearch,
|
||||
reconcileReasoningEffortForModel,
|
||||
reconcileWebSearchForModel
|
||||
} from './reconcile'
|
||||
export { getSearchMatchScore } from './search'
|
||||
export { isFunctionCallingModel } from './tooluse'
|
||||
export { isGenerateImageModels, isVisionModels } from './utils'
|
||||
export { isGenerateImageModel, isVisionModel } from './vision'
|
||||
export { isOpenAIWebSearchModel, isOpenRouterBuiltInWebSearchModel, isWebSearchModel } from './websearch'
|
||||
@@ -13,21 +13,21 @@
|
||||
* patch needed". Callers compose multiple reconcile fns and only emit a
|
||||
* settings patch when at least one returned non-null.
|
||||
*/
|
||||
import {
|
||||
getThinkModelType,
|
||||
isFunctionCallingModel,
|
||||
isOpenRouterBuiltInWebSearchModel,
|
||||
isSupportedReasoningEffortModel,
|
||||
isSupportedThinkingTokenModel,
|
||||
isWebSearchModel,
|
||||
MODEL_SUPPORTED_OPTIONS,
|
||||
MODEL_SUPPORTED_REASONING_EFFORT
|
||||
} from '@renderer/config/models'
|
||||
import { cacheService } from '@renderer/data/CacheService'
|
||||
import type { AssistantSettings } from '@renderer/types/assistant'
|
||||
import type { ThinkingOption } from '@renderer/types/reasoning'
|
||||
import type { Model } from '@shared/data/types/model'
|
||||
|
||||
import {
|
||||
getThinkModelType,
|
||||
isSupportedReasoningEffortModel,
|
||||
isSupportedThinkingTokenModel,
|
||||
MODEL_SUPPORTED_OPTIONS,
|
||||
MODEL_SUPPORTED_REASONING_EFFORT
|
||||
} from './reasoning'
|
||||
import { isFunctionCallingModel } from './tooluse'
|
||||
import { isOpenRouterBuiltInWebSearchModel, isWebSearchModel } from './websearch'
|
||||
|
||||
export type ReasoningEffortPatch = {
|
||||
reasoning_effort?: string
|
||||
}
|
||||
@@ -1,26 +1,11 @@
|
||||
import { OpenClawSidebarIcon } from '@renderer/components/Icons/SvgIcon'
|
||||
import type { SidebarMenuItem } from '@renderer/components/Sidebar/types'
|
||||
import {
|
||||
buildTabInstanceMetadata,
|
||||
getTabInstanceAppId,
|
||||
getTabInstanceKey,
|
||||
hasTabInstanceMetadataForApp
|
||||
} from '@renderer/config/tabInstanceMetadata'
|
||||
} from '@renderer/utils/tabInstanceMetadata'
|
||||
import type { Tab } from '@shared/data/cache/cacheValueTypes'
|
||||
import type { SidebarIcon } from '@shared/data/preference/preferenceTypes'
|
||||
import { getDefaultValue } from '@shared/data/preference/preferenceUtils'
|
||||
import {
|
||||
Code,
|
||||
FileSearch,
|
||||
Folder,
|
||||
Languages,
|
||||
LayoutGrid,
|
||||
Library,
|
||||
MessageSquare,
|
||||
MousePointerClick,
|
||||
NotepadText,
|
||||
Palette
|
||||
} from 'lucide-react'
|
||||
|
||||
/**
|
||||
* Context passed to sidebar navigation handlers. Carries per-call state the
|
||||
@@ -51,7 +36,6 @@ export interface SidebarInstanceKey {
|
||||
|
||||
export interface SidebarApp {
|
||||
id: SidebarIcon
|
||||
icon: SidebarMenuItem['icon']
|
||||
routePrefix: string
|
||||
/** Url to open when no tab exists yet (defaults to `routePrefix`). */
|
||||
resolveUrl?: (ctx: SidebarNavContext) => string
|
||||
@@ -85,7 +69,6 @@ function isMessageOnlyConversationUrl(url: string): boolean {
|
||||
export const SIDEBAR_APPS: readonly SidebarApp[] = [
|
||||
{
|
||||
id: 'assistants',
|
||||
icon: MessageSquare,
|
||||
routePrefix: '/app/chat',
|
||||
instanceKey: {
|
||||
keyFromUrl: (url) => getNormalConversationSearchParamFromUrl(url, 'topicId'),
|
||||
@@ -95,7 +78,6 @@ export const SIDEBAR_APPS: readonly SidebarApp[] = [
|
||||
},
|
||||
{
|
||||
id: 'agents',
|
||||
icon: MousePointerClick,
|
||||
routePrefix: '/app/agents',
|
||||
instanceKey: {
|
||||
keyFromUrl: (url) => getNormalConversationSearchParamFromUrl(url, 'sessionId'),
|
||||
@@ -105,49 +87,40 @@ export const SIDEBAR_APPS: readonly SidebarApp[] = [
|
||||
},
|
||||
{
|
||||
id: 'paintings',
|
||||
icon: Palette,
|
||||
routePrefix: '/app/paintings',
|
||||
resolveUrl: ({ defaultPaintingProvider }) => `/app/paintings/${defaultPaintingProvider}`
|
||||
},
|
||||
{
|
||||
id: 'translate',
|
||||
icon: Languages,
|
||||
routePrefix: '/app/translate'
|
||||
},
|
||||
{
|
||||
id: 'store',
|
||||
icon: Library,
|
||||
routePrefix: '/app/library'
|
||||
},
|
||||
{
|
||||
id: 'mini_app',
|
||||
icon: LayoutGrid,
|
||||
routePrefix: '/app/mini-app',
|
||||
exactRouteFocus: true
|
||||
},
|
||||
{
|
||||
id: 'knowledge',
|
||||
icon: FileSearch,
|
||||
routePrefix: '/app/knowledge'
|
||||
},
|
||||
{
|
||||
id: 'files',
|
||||
icon: Folder,
|
||||
routePrefix: '/app/files'
|
||||
},
|
||||
{
|
||||
id: 'code_tools',
|
||||
icon: Code,
|
||||
routePrefix: '/app/code'
|
||||
},
|
||||
{
|
||||
id: 'notes',
|
||||
icon: NotepadText,
|
||||
routePrefix: '/app/notes'
|
||||
},
|
||||
{
|
||||
id: 'openclaw',
|
||||
icon: OpenClawSidebarIcon,
|
||||
routePrefix: '/app/openclaw'
|
||||
}
|
||||
]
|
||||
@@ -196,28 +169,6 @@ export function resolveSidebarAppTabEntryUrl(tab: Pick<Tab, 'metadata' | 'url'>)
|
||||
return tab.url
|
||||
}
|
||||
|
||||
/**
|
||||
* The tab id to focus on a sidebar click, or undefined if none exists. Apps with
|
||||
* sub-instances narrow the match to the last-focused key (so clicking returns to
|
||||
* that one); keyless apps focus any tab they own.
|
||||
*/
|
||||
export function findAppTabToFocus(app: SidebarApp, tabs: Tab[], ctx: SidebarNavContext): string | undefined {
|
||||
const key = app.instanceKey?.defaultKey(ctx)
|
||||
const existing = tabs.find(
|
||||
(t) =>
|
||||
t.type === 'route' &&
|
||||
(app.exactRouteFocus ? t.url === app.routePrefix : tabBelongsToApp(app, t.url)) &&
|
||||
(app.instanceKey && key ? getSidebarAppTabInstanceKey(app, t) === key : true)
|
||||
)
|
||||
return existing?.id
|
||||
}
|
||||
|
||||
/** The url to open when no owned tab exists yet (base route, resolveUrl, or routePrefix). */
|
||||
export function resolveAppOpenUrl(app: SidebarApp, ctx: SidebarNavContext): string {
|
||||
const key = app.instanceKey?.defaultKey(ctx)
|
||||
return app.instanceKey && key ? app.routePrefix : (app.resolveUrl?.(ctx) ?? app.routePrefix)
|
||||
}
|
||||
|
||||
export function buildSidebarAppOpenMetadata(app: SidebarApp, key?: string): Tab['metadata'] {
|
||||
if (!app.instanceKey || !key) return undefined
|
||||
if (app.id !== 'assistants' && app.id !== 'agents') return undefined
|
||||
@@ -239,22 +190,6 @@ export const REQUIRED_SIDEBAR_ICONS: SidebarIcon[] = ['assistants']
|
||||
|
||||
const sidebarIconSet = new Set<SidebarIcon>(SIDEBAR_ICON_ORDER)
|
||||
|
||||
export const SIDEBAR_ROUTE_PREFIX_MAP: Record<SidebarIcon, string> = SIDEBAR_APPS.reduce(
|
||||
(acc, app) => {
|
||||
acc[app.id] = app.routePrefix
|
||||
return acc
|
||||
},
|
||||
{} as Record<SidebarIcon, string>
|
||||
)
|
||||
|
||||
export const SIDEBAR_ICON_COMPONENTS: Record<SidebarIcon, SidebarMenuItem['icon']> = SIDEBAR_APPS.reduce(
|
||||
(acc, app) => {
|
||||
acc[app.id] = app.icon
|
||||
return acc
|
||||
},
|
||||
{} as Record<SidebarIcon, SidebarMenuItem['icon']>
|
||||
)
|
||||
|
||||
export function getSidebarMenuPath(icon: SidebarIcon, defaultPaintingProvider: string): string {
|
||||
const app = getSidebarApp(icon)
|
||||
if (!app) return ''
|
||||
@@ -302,7 +237,3 @@ export function getOrderedVisibleSidebarIcons(icons: readonly SidebarIcon[] | un
|
||||
|
||||
return visible
|
||||
}
|
||||
|
||||
export function getDefaultSidebarFavorites(): SidebarIcon[] {
|
||||
return getOrderedVisibleSidebarIcons(getDefaultValue('ui.sidebar.favorites'))
|
||||
}
|
||||
@@ -5,11 +5,11 @@ import { TabRouter } from '@renderer/components/layout/TabRouter'
|
||||
import { TITLE_BAR_HEIGHT_CLASS } from '@renderer/components/layout/titleBar'
|
||||
import MiniAppTabsPool from '@renderer/components/MiniApp/MiniAppTabsPool'
|
||||
import WindowControls, { useHasWindowControls } from '@renderer/components/WindowControls'
|
||||
import { clearTabInstanceMetadata } from '@renderer/config/tabInstanceMetadata'
|
||||
import { useTabs } from '@renderer/hooks/tab'
|
||||
import { useWindowInitData } from '@renderer/hooks/useWindowInitData'
|
||||
import { getDefaultRouteTitle, isPageTitledRoute } from '@renderer/utils/routeTitle'
|
||||
import { cn } from '@renderer/utils/style'
|
||||
import { clearTabInstanceMetadata } from '@renderer/utils/tabInstanceMetadata'
|
||||
import type { SubWindowInitData } from '@shared/types/subWindow'
|
||||
import { Activity, type CSSProperties, useEffect, useRef } from 'react'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user