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:
parent
b5fbba6c81
commit
02f7b88dab
@ -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"
|
||||||
|
|||||||
@ -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`])
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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({
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
@ -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}
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[]
|
[]
|
||||||
|
|||||||
@ -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">
|
||||||
|
|||||||
@ -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 = {}
|
||||||
|
|
||||||
|
|||||||
@ -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": {
|
||||||
|
|||||||
@ -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}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -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}
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user