Bring QA (0.6.9) changes to dev (#6296)

* fix: check for env value before setting (#6266)

* fix: check for env value before setting

* Use empty instead of none

* fix: update linux build script to be consistent with CI (#6269)

The local build script for Linux was failing due to a bundling error. This commit updates the `build:tauri:linux` script in `package.json` to be consistent with the CI build pipeline, which resolves the issue.

The updated script now includes:
- **`NO_STRIP=1`**: This environment variable prevents the `linuxdeploy` utility from stripping debugging symbols, which was a potential cause of the bundling failure.
- **`--verbose`**: This flag provides more detailed output during the build, which can be useful for debugging similar issues in the future.

* fix: compatibility imported model

* fix: update copy mmproj setting desc

* fix: toggle vision for remote model

* chore: add tooltip visions

* chore: show model setting only for local provider

* fix/update-ui-info

* chore: update filter hub while searching

* fix: system monitor window permission

* chore: update credit description

---------

Co-authored-by: Akarshan Biswas <akarshan.biswas@gmail.com>
Co-authored-by: Faisal Amir <urmauur@gmail.com>
Co-authored-by: Minh141120 <minh.itptit@gmail.com>
Co-authored-by: Nguyen Ngoc Minh <91668012+Minh141120@users.noreply.github.com>
This commit is contained in:
Dinh Long Nguyen 2025-08-26 15:35:56 +07:00 committed by GitHub
parent b5fbba6c81
commit 02f7b88dab
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 185 additions and 171 deletions

View File

@ -16,7 +16,7 @@
"description": "Environmental variables for llama.cpp(KEY=VALUE), separated by ';'", "description": "Environmental variables for llama.cpp(KEY=VALUE), separated by ';'",
"controllerType": "input", "controllerType": "input",
"controllerProps": { "controllerProps": {
"value": "none", "value": "",
"placeholder": "Eg. GGML_VK_VISIBLE_DEVICES=0,1", "placeholder": "Eg. GGML_VK_VISIBLE_DEVICES=0,1",
"type": "text", "type": "text",
"textAlign": "right" "textAlign": "right"

View File

@ -1084,7 +1084,7 @@ export default class llamacpp_extension extends AIEngine {
// The downloadFiles function only returns successfully if all files downloaded AND validated // The downloadFiles function only returns successfully if all files downloaded AND validated
events.emit(DownloadEvent.onFileDownloadAndVerificationSuccess, { events.emit(DownloadEvent.onFileDownloadAndVerificationSuccess, {
modelId, modelId,
downloadType: 'Model' downloadType: 'Model',
}) })
} catch (error) { } catch (error) {
logger.error('Error downloading model:', modelId, opts, error) logger.error('Error downloading model:', modelId, opts, error)
@ -1092,7 +1092,8 @@ export default class llamacpp_extension extends AIEngine {
error instanceof Error ? error.message : String(error) error instanceof Error ? error.message : String(error)
// Check if this is a cancellation // Check if this is a cancellation
const isCancellationError = errorMessage.includes('Download cancelled') || const isCancellationError =
errorMessage.includes('Download cancelled') ||
errorMessage.includes('Validation cancelled') || errorMessage.includes('Validation cancelled') ||
errorMessage.includes('Hash computation cancelled') || errorMessage.includes('Hash computation cancelled') ||
errorMessage.includes('cancelled') || errorMessage.includes('cancelled') ||
@ -1372,7 +1373,7 @@ export default class llamacpp_extension extends AIEngine {
envs['LLAMA_API_KEY'] = api_key envs['LLAMA_API_KEY'] = api_key
// set user envs // set user envs
this.parseEnvFromString(envs, this.llamacpp_env) if (this.llamacpp_env) this.parseEnvFromString(envs, this.llamacpp_env)
// model option is required // model option is required
// NOTE: model_path and mmproj_path can be either relative to Jan's data folder or absolute path // NOTE: model_path and mmproj_path can be either relative to Jan's data folder or absolute path
@ -1751,7 +1752,7 @@ export default class llamacpp_extension extends AIEngine {
} }
// set envs // set envs
const envs: Record<string, string> = {} const envs: Record<string, string> = {}
this.parseEnvFromString(envs, this.llamacpp_env) if (this.llamacpp_env) this.parseEnvFromString(envs, this.llamacpp_env)
// Ensure backend is downloaded and ready before proceeding // Ensure backend is downloaded and ready before proceeding
await this.ensureBackendReady(backend, version) await this.ensureBackendReady(backend, version)
@ -1767,7 +1768,7 @@ export default class llamacpp_extension extends AIEngine {
return dList return dList
} catch (error) { } catch (error) {
logger.error('Failed to query devices:\n', error) logger.error('Failed to query devices:\n', error)
throw new Error("Failed to load llamacpp backend") throw new Error('Failed to load llamacpp backend')
} }
} }
@ -1876,7 +1877,7 @@ export default class llamacpp_extension extends AIEngine {
logger.info( logger.info(
`Using explicit key_length: ${keyLen}, value_length: ${valLen}` `Using explicit key_length: ${keyLen}, value_length: ${valLen}`
) )
headDim = (keyLen + valLen) headDim = keyLen + valLen
} else { } else {
// Fall back to embedding_length estimation // Fall back to embedding_length estimation
const embeddingLen = Number(meta[`${arch}.embedding_length`]) const embeddingLen = Number(meta[`${arch}.embedding_length`])

View File

@ -22,7 +22,7 @@
"download:lib": "node ./scripts/download-lib.mjs", "download:lib": "node ./scripts/download-lib.mjs",
"download:bin": "node ./scripts/download-bin.mjs", "download:bin": "node ./scripts/download-bin.mjs",
"build:tauri:win32": "yarn download:bin && yarn tauri build", "build:tauri:win32": "yarn download:bin && yarn tauri build",
"build:tauri:linux": "yarn download:bin && ./src-tauri/build-utils/shim-linuxdeploy.sh yarn tauri build && ./src-tauri/build-utils/buildAppImage.sh", "build:tauri:linux": "yarn download:bin && NO_STRIP=1 ./src-tauri/build-utils/shim-linuxdeploy.sh yarn tauri build --verbose && ./src-tauri/build-utils/buildAppImage.sh",
"build:tauri:darwin": "yarn tauri build --target universal-apple-darwin", "build:tauri:darwin": "yarn tauri build --target universal-apple-darwin",
"build:tauri": "yarn build:icon && yarn copy:assets:tauri && run-script-os", "build:tauri": "yarn build:icon && yarn copy:assets:tauri && run-script-os",
"build:tauri:plugin:api": "cd src-tauri/plugins && yarn install && yarn workspaces foreach -Apt run build", "build:tauri:plugin:api": "cd src-tauri/plugins && yarn install && yarn workspaces foreach -Apt run build",

View File

@ -9,6 +9,11 @@
"core:window:allow-set-theme", "core:window:allow-set-theme",
"log:default", "log:default",
"core:webview:allow-create-webview-window", "core:webview:allow-create-webview-window",
"core:window:allow-set-focus" "core:window:allow-set-focus",
"hardware:allow-get-system-info",
"hardware:allow-get-system-usage",
"llamacpp:allow-get-devices",
"llamacpp:allow-read-gguf-metadata",
"deep-link:allow-get-current"
] ]
} }

View File

@ -6,7 +6,14 @@ import { cn } from '@/lib/utils'
function HoverCard({ function HoverCard({
...props ...props
}: React.ComponentProps<typeof HoverCardPrimitive.Root>) { }: React.ComponentProps<typeof HoverCardPrimitive.Root>) {
return <HoverCardPrimitive.Root data-slot="hover-card" {...props} /> return (
<HoverCardPrimitive.Root
openDelay={0}
closeDelay={0}
data-slot="hover-card"
{...props}
/>
)
} }
function HoverCardTrigger({ function HoverCardTrigger({

View File

@ -107,9 +107,15 @@ const ChatInput = ({ model, className, initialMessage }: ChatInputProps) => {
if (selectedProvider === 'llamacpp') { if (selectedProvider === 'llamacpp') {
const hasLocalMmproj = await checkMmprojExists(selectedModel.id) const hasLocalMmproj = await checkMmprojExists(selectedModel.id)
setHasMmproj(hasLocalMmproj) setHasMmproj(hasLocalMmproj)
} else { }
// For non-llamacpp providers, only check vision capability // For non-llamacpp providers, only check vision capability
else if (
selectedProvider !== 'llamacpp' &&
selectedModel?.capabilities?.includes('vision')
) {
setHasMmproj(true) setHasMmproj(true)
} else {
setHasMmproj(false)
} }
} catch (error) { } catch (error) {
console.error('Error checking mmproj:', error) console.error('Error checking mmproj:', error)
@ -119,7 +125,7 @@ const ChatInput = ({ model, className, initialMessage }: ChatInputProps) => {
} }
checkMmprojSupport() checkMmprojSupport()
}, [selectedModel?.id, selectedProvider]) }, [selectedModel?.capabilities, selectedModel?.id, selectedProvider])
// Check if there are active MCP servers // Check if there are active MCP servers
const hasActiveMCPServers = connectedServers.length > 0 || tools.length > 0 const hasActiveMCPServers = connectedServers.length > 0 || tools.length > 0
@ -535,29 +541,41 @@ const ChatInput = ({ model, className, initialMessage }: ChatInputProps) => {
)} )}
{/* File attachment - show only for models with mmproj */} {/* File attachment - show only for models with mmproj */}
{hasMmproj && ( {hasMmproj && (
<div <TooltipProvider>
className="h-6 p-1 flex items-center justify-center rounded-sm hover:bg-main-view-fg/10 transition-all duration-200 ease-in-out gap-1" <Tooltip>
onClick={handleAttachmentClick} <TooltipTrigger asChild>
> <div
<IconPhoto size={18} className="text-main-view-fg/50" /> className="h-7 p-1 flex items-center justify-center rounded-sm hover:bg-main-view-fg/10 transition-all duration-200 ease-in-out gap-1"
<input onClick={handleAttachmentClick}
type="file" >
ref={fileInputRef} <IconPhoto
className="hidden" size={18}
multiple className="text-main-view-fg/50"
onChange={handleFileChange} />
/> <input
</div> type="file"
ref={fileInputRef}
className="hidden"
multiple
onChange={handleFileChange}
/>
</div>
</TooltipTrigger>
<TooltipContent>
<p>{t('vision')}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
)} )}
{/* Microphone - always available - Temp Hide */} {/* Microphone - always available - Temp Hide */}
{/* <div className="h-6 p-1 flex items-center justify-center rounded-sm hover:bg-main-view-fg/10 transition-all duration-200 ease-in-out gap-1"> {/* <div className="h-7 p-1 flex items-center justify-center rounded-sm hover:bg-main-view-fg/10 transition-all duration-200 ease-in-out gap-1">
<IconMicrophone size={18} className="text-main-view-fg/50" /> <IconMicrophone size={18} className="text-main-view-fg/50" />
</div> */} </div> */}
{selectedModel?.capabilities?.includes('embeddings') && ( {selectedModel?.capabilities?.includes('embeddings') && (
<TooltipProvider> <TooltipProvider>
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<div className="h-6 p-1 flex items-center justify-center rounded-sm hover:bg-main-view-fg/10 transition-all duration-200 ease-in-out gap-1"> <div className="h-7 p-1 flex items-center justify-center rounded-sm hover:bg-main-view-fg/10 transition-all duration-200 ease-in-out gap-1">
<IconCodeCircle2 <IconCodeCircle2
size={18} size={18}
className="text-main-view-fg/50" className="text-main-view-fg/50"
@ -601,7 +619,7 @@ const ChatInput = ({ model, className, initialMessage }: ChatInputProps) => {
return ( return (
<div <div
className={cn( className={cn(
'h-6 p-1 flex items-center justify-center rounded-sm hover:bg-main-view-fg/10 transition-all duration-200 ease-in-out gap-1 cursor-pointer relative', 'h-7 p-1 flex items-center justify-center rounded-sm hover:bg-main-view-fg/10 transition-all duration-200 ease-in-out gap-1 cursor-pointer relative',
isOpen && 'bg-main-view-fg/10' isOpen && 'bg-main-view-fg/10'
)} )}
> >
@ -632,7 +650,7 @@ const ChatInput = ({ model, className, initialMessage }: ChatInputProps) => {
<TooltipProvider> <TooltipProvider>
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<div className="h-6 p-1 flex items-center justify-center rounded-sm hover:bg-main-view-fg/10 transition-all duration-200 ease-in-out gap-1"> <div className="h-7 p-1 flex items-center justify-center rounded-sm hover:bg-main-view-fg/10 transition-all duration-200 ease-in-out gap-1">
<IconWorld <IconWorld
size={18} size={18}
className="text-main-view-fg/50" className="text-main-view-fg/50"
@ -649,7 +667,7 @@ const ChatInput = ({ model, className, initialMessage }: ChatInputProps) => {
<TooltipProvider> <TooltipProvider>
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<div className="h-6 p-1 flex items-center justify-center rounded-sm hover:bg-main-view-fg/10 transition-all duration-200 ease-in-out gap-1"> <div className="h-7 p-1 flex items-center justify-center rounded-sm hover:bg-main-view-fg/10 transition-all duration-200 ease-in-out gap-1">
<IconAtom <IconAtom
size={18} size={18}
className="text-main-view-fg/50" className="text-main-view-fg/50"

View File

@ -414,13 +414,15 @@ const DropdownModelProvider = ({
</span> </span>
</button> </button>
</PopoverTrigger> </PopoverTrigger>
{currentModel?.settings && provider && ( {currentModel?.settings &&
<ModelSetting provider &&
model={currentModel as Model} provider.provider === 'llamacpp' && (
provider={provider} <ModelSetting
smallIcon model={currentModel as Model}
/> provider={provider}
)} smallIcon
/>
)}
<ModelSupportStatus <ModelSupportStatus
modelId={selectedModel?.id} modelId={selectedModel?.id}
provider={selectedProvider} provider={selectedProvider}

View File

@ -5,11 +5,11 @@ import {
} from '@/components/ui/hover-card' } from '@/components/ui/hover-card'
import { IconInfoCircle } from '@tabler/icons-react' import { IconInfoCircle } from '@tabler/icons-react'
import { CatalogModel, ModelQuant } from '@/services/models' import { CatalogModel, ModelQuant } from '@/services/models'
import { extractDescription } from '@/lib/models'
interface ModelInfoHoverCardProps { interface ModelInfoHoverCardProps {
model: CatalogModel model: CatalogModel
variant?: ModelQuant variant?: ModelQuant
isDefaultVariant?: boolean
defaultModelQuantizations: string[] defaultModelQuantizations: string[]
modelSupportStatus: Record<string, string> modelSupportStatus: Record<string, string>
onCheckModelSupport: (variant: ModelQuant) => void onCheckModelSupport: (variant: ModelQuant) => void
@ -19,12 +19,12 @@ interface ModelInfoHoverCardProps {
export const ModelInfoHoverCard = ({ export const ModelInfoHoverCard = ({
model, model,
variant, variant,
isDefaultVariant,
defaultModelQuantizations, defaultModelQuantizations,
modelSupportStatus, modelSupportStatus,
onCheckModelSupport, onCheckModelSupport,
children, children,
}: ModelInfoHoverCardProps) => { }: ModelInfoHoverCardProps) => {
const isVariantMode = !!variant
const displayVariant = const displayVariant =
variant || variant ||
model.quants.find((m) => model.quants.find((m) =>
@ -95,8 +95,8 @@ export const ModelInfoHoverCard = ({
{children || ( {children || (
<div className="cursor-pointer"> <div className="cursor-pointer">
<IconInfoCircle <IconInfoCircle
size={14} size={isDefaultVariant ? 20 : 14}
className="mt-0.5 text-main-view-fg/50 hover:text-main-view-fg/80 transition-colors" className="mt-0.5 text-main-view-fg/80 hover:text-main-view-fg/80 transition-colors"
/> />
</div> </div>
)} )}
@ -106,10 +106,10 @@ export const ModelInfoHoverCard = ({
{/* Header */} {/* Header */}
<div className="border-b border-main-view-fg/10 pb-3"> <div className="border-b border-main-view-fg/10 pb-3">
<h4 className="text-sm font-semibold text-main-view-fg"> <h4 className="text-sm font-semibold text-main-view-fg">
{isVariantMode ? variant.model_id : model.model_name} {!isDefaultVariant ? variant?.model_id : model?.model_name}
</h4> </h4>
<p className="text-xs text-main-view-fg/60 mt-1"> <p className="text-xs text-main-view-fg/60 mt-1">
{isVariantMode {!isDefaultVariant
? 'Model Variant Information' ? 'Model Variant Information'
: 'Model Information'} : 'Model Information'}
</p> </p>
@ -118,57 +118,21 @@ export const ModelInfoHoverCard = ({
{/* Main Info Grid */} {/* Main Info Grid */}
<div className="grid grid-cols-2 gap-3 text-xs"> <div className="grid grid-cols-2 gap-3 text-xs">
<div className="space-y-2"> <div className="space-y-2">
{isVariantMode ? ( <>
<> <div>
<div> <span className="text-main-view-fg/50 block">
<span className="text-main-view-fg/50 block"> {isDefaultVariant
File Size ? 'Maybe Default Quantization'
</span> : 'Quantization'}
<span className="text-main-view-fg font-medium mt-1 inline-block"> </span>
{variant.file_size} <span className="text-main-view-fg font-medium mt-1 inline-block">
</span> {variant?.model_id.split('-').pop()?.toUpperCase() || 'N/A'}
</div> </span>
<div> </div>
<span className="text-main-view-fg/50 block"> </>
Quantization
</span>
<span className="text-main-view-fg font-medium mt-1 inline-block">
{variant.model_id.split('-').pop()?.toUpperCase() ||
'N/A'}
</span>
</div>
</>
) : (
<>
<div>
<span className="text-main-view-fg/50 block">
Downloads
</span>
<span className="text-main-view-fg font-medium mt-1 inline-block">
{model.downloads?.toLocaleString() || '0'}
</span>
</div>
<div>
<span className="text-main-view-fg/50 block">Variants</span>
<span className="text-main-view-fg font-medium mt-1 inline-block">
{model.quants?.length || 0}
</span>
</div>
</>
)}
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
{!isVariantMode && (
<div>
<span className="text-main-view-fg/50 block">
Default Size
</span>
<span className="text-main-view-fg font-medium mt-1 inline-block">
{displayVariant?.file_size || 'N/A'}
</span>
</div>
)}
<div> <div>
<span className="text-main-view-fg/50 block"> <span className="text-main-view-fg/50 block">
Compatibility Compatibility
@ -204,21 +168,6 @@ export const ModelInfoHoverCard = ({
</div> </div>
</div> </div>
)} )}
{/* Content Section */}
<div className="border-t border-main-view-fg/10 pt-3">
<h5 className="text-xs font-medium text-main-view-fg/70 mb-1">
{isVariantMode ? 'Download URL' : 'Description'}
</h5>
<div className="text-xs text-main-view-fg/60 bg-main-view-fg/5 rounded p-2">
{isVariantMode ? (
<div className="font-mono break-all">{variant.path}</div>
) : (
extractDescription(model?.description) ||
'No description available'
)}
</div>
</div>
</div> </div>
</HoverCardContent> </HoverCardContent>
</HoverCard> </HoverCard>

View File

@ -7,7 +7,8 @@ import {
TooltipTrigger, TooltipTrigger,
} from '@/components/ui/tooltip' } from '@/components/ui/tooltip'
import { isModelSupported } from '@/services/models' import { isModelSupported } from '@/services/models'
import { getJanDataFolderPath, joinPath } from '@janhq/core' import { getJanDataFolderPath, joinPath, fs } from '@janhq/core'
import { invoke } from '@tauri-apps/api/core'
interface ModelSupportStatusProps { interface ModelSupportStatusProps {
modelId: string | undefined modelId: string | undefined
@ -31,12 +32,12 @@ export const ModelSupportStatus = ({
async ( async (
id: string, id: string,
ctxSize: number ctxSize: number
): Promise<'RED' | 'YELLOW' | 'GREEN'> => { ): Promise<'RED' | 'YELLOW' | 'GREEN' | null> => {
try { try {
// Get Jan's data folder path and construct the full model file path
// Following the llamacpp extension structure: <Jan's data folder>/llamacpp/models/<modelId>/model.gguf
const janDataFolder = await getJanDataFolderPath() const janDataFolder = await getJanDataFolderPath()
const modelFilePath = await joinPath([
// First try the standard downloaded model path
const ggufModelPath = await joinPath([
janDataFolder, janDataFolder,
'llamacpp', 'llamacpp',
'models', 'models',
@ -44,14 +45,47 @@ export const ModelSupportStatus = ({
'model.gguf', 'model.gguf',
]) ])
return await isModelSupported(modelFilePath, ctxSize) // Check if the standard model.gguf file exists
if (await fs.existsSync(ggufModelPath)) {
return await isModelSupported(ggufModelPath, ctxSize)
}
// If model.gguf doesn't exist, try reading from model.yml (for imported models)
const modelConfigPath = await joinPath([
janDataFolder,
'llamacpp',
'models',
id,
'model.yml',
])
if (!(await fs.existsSync(modelConfigPath))) {
console.error(
`Neither model.gguf nor model.yml found for model: ${id}`
)
return null
}
// Read the model configuration to get the actual model path
const modelConfig = await invoke<{ model_path: string }>('read_yaml', {
path: `llamacpp/models/${id}/model.yml`,
})
// Handle both absolute and relative paths
const actualModelPath =
modelConfig.model_path.startsWith('/') ||
modelConfig.model_path.match(/^[A-Za-z]:/)
? modelConfig.model_path // absolute path, use as-is
: await joinPath([janDataFolder, modelConfig.model_path]) // relative path, join with data folder
return await isModelSupported(actualModelPath, ctxSize)
} catch (error) { } catch (error) {
console.error( console.error(
'Error checking model support with constructed path:', 'Error checking model support with path resolution:',
error error
) )
// If path construction or model support check fails, assume not supported // If path construction or model support check fails, assume not supported
return 'RED' return null
} }
}, },
[] []

View File

@ -7,11 +7,7 @@ import {
DialogTrigger, DialogTrigger,
} from '@/components/ui/dialog' } from '@/components/ui/dialog'
import { Switch } from '@/components/ui/switch' import { Switch } from '@/components/ui/switch'
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from '@/components/ui/tooltip'
import { useModelProvider } from '@/hooks/useModelProvider' import { useModelProvider } from '@/hooks/useModelProvider'
import { import {
IconPencil, IconPencil,
@ -19,7 +15,7 @@ import {
IconTool, IconTool,
// IconWorld, // IconWorld,
// IconAtom, // IconAtom,
IconCodeCircle2, // IconCodeCircle2,
} from '@tabler/icons-react' } from '@tabler/icons-react'
import { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import { useTranslation } from '@/i18n/react-i18next-compat' import { useTranslation } from '@/i18n/react-i18next-compat'
@ -177,24 +173,16 @@ export const DialogEditModel = ({
{t('providers:editModel.vision')} {t('providers:editModel.vision')}
</span> </span>
</div> </div>
<Tooltip> <Switch
<TooltipTrigger> id="vision-capability"
<Switch checked={capabilities.vision}
id="vision-capability" onCheckedChange={(checked) =>
checked={capabilities.vision} handleCapabilityChange('vision', checked)
disabled={true} }
onCheckedChange={(checked) => />
handleCapabilityChange('vision', checked)
}
/>
</TooltipTrigger>
<TooltipContent>
{t('providers:editModel.notAvailable')}
</TooltipContent>
</Tooltip>
</div> </div>
<div className="flex items-center justify-between"> {/* <div className="flex items-center justify-between">
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<IconCodeCircle2 className="size-4 text-main-view-fg/70" /> <IconCodeCircle2 className="size-4 text-main-view-fg/70" />
<span className="text-sm"> <span className="text-sm">
@ -216,7 +204,7 @@ export const DialogEditModel = ({
{t('providers:editModel.notAvailable')} {t('providers:editModel.notAvailable')}
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
</div> </div> */}
{/* <div className="flex items-center justify-between"> {/* <div className="flex items-center justify-between">
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">

View File

@ -241,7 +241,7 @@ export const useModelProvider = create<ModelProviderState>()(
} }
// Migrate model settings // Migrate model settings
if (provider.models) { if (provider.models && provider.provider === 'llamacpp') {
provider.models.forEach((model) => { provider.models.forEach((model) => {
if (!model.settings) model.settings = {} if (!model.settings) model.settings = {}

View File

@ -37,7 +37,7 @@
"reportAnIssueDesc": "Found a bug? Help us out by filing an issue on GitHub.", "reportAnIssueDesc": "Found a bug? Help us out by filing an issue on GitHub.",
"reportIssue": "Report Issue", "reportIssue": "Report Issue",
"credits": "Credits", "credits": "Credits",
"creditsDesc1": "Jan is built with ❤️ by the Menlo Team.", "creditsDesc1": "👋 Jan is built with ❤️ by the Menlo Research team.",
"creditsDesc2": "Special thanks to our open-source dependencies—especially llama.cpp and Tauri—and to our amazing AI community.", "creditsDesc2": "Special thanks to our open-source dependencies—especially llama.cpp and Tauri—and to our amazing AI community.",
"appVersion": "App Version", "appVersion": "App Version",
"dataFolder": { "dataFolder": {
@ -234,7 +234,7 @@
"reportAnIssueDesc": "Found a bug? Help us out by filing an issue on GitHub.", "reportAnIssueDesc": "Found a bug? Help us out by filing an issue on GitHub.",
"reportIssue": "Report Issue", "reportIssue": "Report Issue",
"credits": "Credits", "credits": "Credits",
"creditsDesc1": "Jan is built with ❤️ by the Menlo Team.", "creditsDesc1": "👋 Jan is built with ❤️ by the Menlo Research team.",
"creditsDesc2": "Special thanks to our open-source dependencies—especially llama.cpp and Tauri—and to our amazing AI community." "creditsDesc2": "Special thanks to our open-source dependencies—especially llama.cpp and Tauri—and to our amazing AI community."
}, },
"extensions": { "extensions": {

View File

@ -353,12 +353,7 @@ function Hub() {
// Immediately set local downloading state // Immediately set local downloading state
addLocalDownloadingModel(modelId) addLocalDownloadingModel(modelId)
const mmprojPath = model.mmproj_models?.[0]?.path const mmprojPath = model.mmproj_models?.[0]?.path
pullModelWithMetadata( pullModelWithMetadata(modelId, modelUrl, mmprojPath, huggingfaceToken)
modelId,
modelUrl,
mmprojPath,
huggingfaceToken
)
} }
return ( return (
@ -399,13 +394,13 @@ function Hub() {
) )
} }
}, [ }, [
localDownloadingModels,
downloadProcesses, downloadProcesses,
llamaProvider?.models, llamaProvider?.models,
isRecommendedModel, isRecommendedModel,
downloadButtonRef,
localDownloadingModels,
addLocalDownloadingModel,
t, t,
addLocalDownloadingModel,
huggingfaceToken,
handleUseModel, handleUseModel,
]) ])
@ -482,9 +477,9 @@ function Hub() {
const isLastStep = currentStepIndex === steps.length - 1 const isLastStep = currentStepIndex === steps.length - 1
const renderFilter = () => { const renderFilter = () => {
if (searchValue.length === 0) return (
return ( <>
<> {searchValue.length === 0 && (
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger> <DropdownMenuTrigger>
<span className="flex cursor-pointer items-center gap-1 px-2 py-1 rounded-sm bg-main-view-fg/15 text-sm outline-none text-main-view-fg font-medium"> <span className="flex cursor-pointer items-center gap-1 px-2 py-1 rounded-sm bg-main-view-fg/15 text-sm outline-none text-main-view-fg font-medium">
@ -509,17 +504,18 @@ function Hub() {
))} ))}
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
<div className="flex items-center gap-2"> )}
<Switch <div className="flex items-center gap-2">
checked={showOnlyDownloaded} <Switch
onCheckedChange={setShowOnlyDownloaded} checked={showOnlyDownloaded}
/> onCheckedChange={setShowOnlyDownloaded}
<span className="text-xs text-main-view-fg/70 font-medium whitespace-nowrap"> />
{t('hub:downloaded')} <span className="text-xs text-main-view-fg/70 font-medium whitespace-nowrap">
</span> {t('hub:downloaded')}
</div> </span>
</> </div>
) </>
)
} }
return ( return (
@ -661,6 +657,18 @@ function Hub() {
defaultModelQuantizations={ defaultModelQuantizations={
defaultModelQuantizations defaultModelQuantizations
} }
variant={
filteredModels[
virtualItem.index
].quants.find((m) =>
defaultModelQuantizations.some((e) =>
m.model_id.toLowerCase().includes(e)
)
) ??
filteredModels[virtualItem.index]
.quants?.[0]
}
isDefaultVariant={true}
modelSupportStatus={modelSupportStatus} modelSupportStatus={modelSupportStatus}
onCheckModelSupport={checkModelSupport} onCheckModelSupport={checkModelSupport}
/> />

View File

@ -584,10 +584,12 @@ function ProviderDetail() {
} }
actions={ actions={
<div className="flex items-center gap-0.5"> <div className="flex items-center gap-0.5">
<DialogEditModel {provider && provider.provider !== 'llamacpp' && (
provider={provider} <DialogEditModel
modelId={model.id} provider={provider}
/> modelId={model.id}
/>
)}
{model.settings && ( {model.settings && (
<ModelSetting <ModelSetting
provider={provider} provider={provider}

View File

@ -491,7 +491,7 @@ export const checkMmprojExistsAndUpdateOffloadMMprojSetting = async (
key: 'offload_mmproj', key: 'offload_mmproj',
title: 'Offload MMProj', title: 'Offload MMProj',
description: description:
'Offload multimodal projection layers to GPU', 'Offload multimodal projection model to GPU',
controller_type: 'checkbox', controller_type: 'checkbox',
controller_props: { controller_props: {
value: true, value: true,