Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion extensions/llamacpp-extension/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"description": "Environmental variables for llama.cpp(KEY=VALUE), separated by ';'",
"controllerType": "input",
"controllerProps": {
"value": "none",
"value": "",
"placeholder": "Eg. GGML_VK_VISIBLE_DEVICES=0,1",
"type": "text",
"textAlign": "right"
Expand Down
17 changes: 9 additions & 8 deletions extensions/llamacpp-extension/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1082,17 +1082,18 @@ export default class llamacpp_extension extends AIEngine {

// If we reach here, download completed successfully (including validation)
// The downloadFiles function only returns successfully if all files downloaded AND validated
events.emit(DownloadEvent.onFileDownloadAndVerificationSuccess, {
modelId,
downloadType: 'Model'
events.emit(DownloadEvent.onFileDownloadAndVerificationSuccess, {
modelId,
downloadType: 'Model',
})
} catch (error) {
logger.error('Error downloading model:', modelId, opts, error)
const errorMessage =
error instanceof Error ? error.message : String(error)

// Check if this is a cancellation
const isCancellationError = errorMessage.includes('Download cancelled') ||
const isCancellationError =
errorMessage.includes('Download cancelled') ||
errorMessage.includes('Validation cancelled') ||
errorMessage.includes('Hash computation cancelled') ||
errorMessage.includes('cancelled') ||
Expand Down Expand Up @@ -1372,7 +1373,7 @@ export default class llamacpp_extension extends AIEngine {
envs['LLAMA_API_KEY'] = api_key

// set user envs
this.parseEnvFromString(envs, this.llamacpp_env)
if (this.llamacpp_env) this.parseEnvFromString(envs, this.llamacpp_env)

// model option is required
// NOTE: model_path and mmproj_path can be either relative to Jan's data folder or absolute path
Expand Down Expand Up @@ -1751,7 +1752,7 @@ export default class llamacpp_extension extends AIEngine {
}
// set envs
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
await this.ensureBackendReady(backend, version)
Expand All @@ -1767,7 +1768,7 @@ export default class llamacpp_extension extends AIEngine {
return dList
} catch (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')
}
}

Expand Down Expand Up @@ -1876,7 +1877,7 @@ export default class llamacpp_extension extends AIEngine {
logger.info(
`Using explicit key_length: ${keyLen}, value_length: ${valLen}`
)
headDim = (keyLen + valLen)
headDim = keyLen + valLen
} else {
// Fall back to embedding_length estimation
const embeddingLen = Number(meta[`${arch}.embedding_length`])
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"download:lib": "node ./scripts/download-lib.mjs",
"download:bin": "node ./scripts/download-bin.mjs",
"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": "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",
Expand Down
7 changes: 6 additions & 1 deletion src-tauri/capabilities/system-monitor-window.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@
"core:window:allow-set-theme",
"log:default",
"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"
]
}
9 changes: 8 additions & 1 deletion web-app/src/components/ui/hover-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,14 @@ import { cn } from '@/lib/utils'
function HoverCard({
...props
}: 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({
Expand Down
60 changes: 39 additions & 21 deletions web-app/src/containers/ChatInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,9 @@
const servers = await getConnectedServers()
setConnectedServers(servers)
} catch (error) {
console.error('Failed to get connected servers:', error)
setConnectedServers([])
}

Check warning on line 90 in web-app/src/containers/ChatInput.tsx

View workflow job for this annotation

GitHub Actions / coverage-check

88-90 lines are not covered with tests
}

checkConnectedServers()
Expand All @@ -107,19 +107,25 @@
if (selectedProvider === 'llamacpp') {
const hasLocalMmproj = await checkMmprojExists(selectedModel.id)
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)
} else {
setHasMmproj(false)
}

Check warning on line 119 in web-app/src/containers/ChatInput.tsx

View workflow job for this annotation

GitHub Actions / coverage-check

113-119 lines are not covered with tests
} catch (error) {
console.error('Error checking mmproj:', error)
setHasMmproj(false)
}

Check warning on line 123 in web-app/src/containers/ChatInput.tsx

View workflow job for this annotation

GitHub Actions / coverage-check

121-123 lines are not covered with tests
}
}

checkMmprojSupport()
}, [selectedModel?.id, selectedProvider])
}, [selectedModel?.capabilities, selectedModel?.id, selectedProvider])

// Check if there are active MCP servers
const hasActiveMCPServers = connectedServers.length > 0 || tools.length > 0
Expand All @@ -130,8 +136,8 @@
return
}
if (!prompt.trim() && uploadedFiles.length === 0) {
return
}

Check warning on line 140 in web-app/src/containers/ChatInput.tsx

View workflow job for this annotation

GitHub Actions / coverage-check

139-140 lines are not covered with tests
setMessage('')
sendMessage(
prompt,
Expand Down Expand Up @@ -172,8 +178,8 @@

useEffect(() => {
if (tooltipToolsAvailable && dropdownToolsAvailable) {
setTooltipToolsAvailable(false)
}

Check warning on line 182 in web-app/src/containers/ChatInput.tsx

View workflow job for this annotation

GitHub Actions / coverage-check

181-182 lines are not covered with tests
}, [dropdownToolsAvailable, tooltipToolsAvailable])

// Focus when thread changes
Expand All @@ -195,39 +201,39 @@

const stopStreaming = useCallback(
(threadId: string) => {
abortControllers[threadId]?.abort()
cancelToolCall?.()
},

Check warning on line 206 in web-app/src/containers/ChatInput.tsx

View workflow job for this annotation

GitHub Actions / coverage-check

204-206 lines are not covered with tests
[abortControllers, cancelToolCall]
)

const fileInputRef = useRef<HTMLInputElement>(null)

const handleAttachmentClick = () => {
fileInputRef.current?.click()
}

Check warning on line 214 in web-app/src/containers/ChatInput.tsx

View workflow job for this annotation

GitHub Actions / coverage-check

213-214 lines are not covered with tests

const handleRemoveFile = (indexToRemove: number) => {
setUploadedFiles((prev) =>
prev.filter((_, index) => index !== indexToRemove)
)
}

Check warning on line 220 in web-app/src/containers/ChatInput.tsx

View workflow job for this annotation

GitHub Actions / coverage-check

217-220 lines are not covered with tests

const getFileTypeFromExtension = (fileName: string): string => {
const extension = fileName.toLowerCase().split('.').pop()
switch (extension) {
case 'jpg':
case 'jpeg':
return 'image/jpeg'
case 'png':
return 'image/png'
default:
return ''
}
}

Check warning on line 233 in web-app/src/containers/ChatInput.tsx

View workflow job for this annotation

GitHub Actions / coverage-check

223-233 lines are not covered with tests

const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const files = e.target.files

Check warning on line 236 in web-app/src/containers/ChatInput.tsx

View workflow job for this annotation

GitHub Actions / coverage-check

236 line is not covered with tests

if (files && files.length > 0) {
const maxSize = 10 * 1024 * 1024 // 10MB in bytes
Expand Down Expand Up @@ -535,29 +541,41 @@
)}
{/* File attachment - show only for models with mmproj */}
{hasMmproj && (
<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"
onClick={handleAttachmentClick}
>
<IconPhoto size={18} className="text-main-view-fg/50" />
<input
type="file"
ref={fileInputRef}
className="hidden"
multiple
onChange={handleFileChange}
/>
</div>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<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"
onClick={handleAttachmentClick}
>
<IconPhoto
size={18}
className="text-main-view-fg/50"
/>
<input
type="file"
ref={fileInputRef}
className="hidden"
multiple
onChange={handleFileChange}
/>
</div>
</TooltipTrigger>
<TooltipContent>
<p>{t('vision')}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
{/* 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" />
</div> */}
{selectedModel?.capabilities?.includes('embeddings') && (
<TooltipProvider>
<Tooltip>
<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
size={18}
className="text-main-view-fg/50"
Expand Down Expand Up @@ -601,7 +619,7 @@
return (
<div
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'
)}
>
Expand Down Expand Up @@ -632,7 +650,7 @@
<TooltipProvider>
<Tooltip>
<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
size={18}
className="text-main-view-fg/50"
Expand All @@ -649,7 +667,7 @@
<TooltipProvider>
<Tooltip>
<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
size={18}
className="text-main-view-fg/50"
Expand Down
16 changes: 9 additions & 7 deletions web-app/src/containers/DropdownModelProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -414,13 +414,15 @@ const DropdownModelProvider = ({
</span>
</button>
</PopoverTrigger>
{currentModel?.settings && provider && (
<ModelSetting
model={currentModel as Model}
provider={provider}
smallIcon
/>
)}
{currentModel?.settings &&
provider &&
provider.provider === 'llamacpp' && (
<ModelSetting
model={currentModel as Model}
provider={provider}
smallIcon
/>
)}
<ModelSupportStatus
modelId={selectedModel?.id}
provider={selectedProvider}
Expand Down
81 changes: 15 additions & 66 deletions web-app/src/containers/ModelInfoHoverCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import {
} from '@/components/ui/hover-card'
import { IconInfoCircle } from '@tabler/icons-react'
import { CatalogModel, ModelQuant } from '@/services/models'
import { extractDescription } from '@/lib/models'

interface ModelInfoHoverCardProps {
model: CatalogModel
variant?: ModelQuant
isDefaultVariant?: boolean
defaultModelQuantizations: string[]
modelSupportStatus: Record<string, string>
onCheckModelSupport: (variant: ModelQuant) => void
Expand All @@ -19,12 +19,12 @@ interface ModelInfoHoverCardProps {
export const ModelInfoHoverCard = ({
model,
variant,
isDefaultVariant,
defaultModelQuantizations,
modelSupportStatus,
onCheckModelSupport,
children,
}: ModelInfoHoverCardProps) => {
const isVariantMode = !!variant
const displayVariant =
variant ||
model.quants.find((m) =>
Expand Down Expand Up @@ -95,8 +95,8 @@ export const ModelInfoHoverCard = ({
{children || (
<div className="cursor-pointer">
<IconInfoCircle
size={14}
className="mt-0.5 text-main-view-fg/50 hover:text-main-view-fg/80 transition-colors"
size={isDefaultVariant ? 20 : 14}
className="mt-0.5 text-main-view-fg/80 hover:text-main-view-fg/80 transition-colors"
/>
</div>
)}
Expand All @@ -106,10 +106,10 @@ export const ModelInfoHoverCard = ({
{/* Header */}
<div className="border-b border-main-view-fg/10 pb-3">
<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>
<p className="text-xs text-main-view-fg/60 mt-1">
{isVariantMode
{!isDefaultVariant
? 'Model Variant Information'
: 'Model Information'}
</p>
Expand All @@ -118,57 +118,21 @@ export const ModelInfoHoverCard = ({
{/* Main Info Grid */}
<div className="grid grid-cols-2 gap-3 text-xs">
<div className="space-y-2">
{isVariantMode ? (
<>
<div>
<span className="text-main-view-fg/50 block">
File Size
</span>
<span className="text-main-view-fg font-medium mt-1 inline-block">
{variant.file_size}
</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 className="space-y-2">
{!isVariantMode && (
<>
<div>
<span className="text-main-view-fg/50 block">
Default Size
{isDefaultVariant
? 'Maybe Default Quantization'
: 'Quantization'}
</span>
<span className="text-main-view-fg font-medium mt-1 inline-block">
{displayVariant?.file_size || 'N/A'}
{variant?.model_id.split('-').pop()?.toUpperCase() || 'N/A'}
</span>
</div>
)}
</>
</div>

<div className="space-y-2">
<div>
<span className="text-main-view-fg/50 block">
Compatibility
Expand Down Expand Up @@ -204,21 +168,6 @@ export const ModelInfoHoverCard = ({
</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>
</HoverCardContent>
</HoverCard>
Expand Down
Loading
Loading