{#if loaded} { const selected = backgroundInput.files?.[0]; backgroundInput.value = ''; if (!selected || readOnly || loading) return; loading = true; const candidate = URL.createObjectURL(selected); try { if (!['image/png', 'image/jpeg', 'image/webp', 'image/gif'].includes(selected.type)) { throw new Error($i18n.t('Background image must be PNG, JPEG, WebP, or GIF.')); } if (selected.size > 5 * 1024 * 1024) throw new Error($i18n.t('Background image must be at most 5 MiB.')); const image = new Image(); image.src = candidate; await image.decode(); if (image.naturalWidth * image.naturalHeight > 25_000_000) { throw new Error($i18n.t('Background image must be at most 25 megapixels.')); } clearBackgroundPreview(); backgroundPreview = candidate; backgroundFile = selected; } catch (error) { URL.revokeObjectURL(candidate); toast.error(error instanceof Error ? error.message : $i18n.t('Invalid background image.')); } finally { loading = false; } }} /> { allowNavigation = true; afterDiscard?.(); }} /> { if (edit && model?.id) { try { await updateModelAccessGrants(localStorage.token, model.id, model.name, accessGrants); model.access_grants = structuredClone(accessGrants); toast.success($i18n.t('Saved')); } catch (error) { accessGrants = structuredClone(model.access_grants ?? []); toast.error(`${error}`); } } }} /> {#if onBack} { discardThen(() => { allowNavigation = true; onBack?.(); }); }} > {$i18n.t('Back')} {/if} {#if edit && model?.version_id && !readOnly} {/if} {#if !historical && !readOnly} {#if $user?.permissions?.sharing?.models || $user?.role === 'admin'} (showAccessControlModal = true)} /> {/if} {/if} {#if selectingVersion} {:else if selectedVersion} {#key selectedVersion.id} {/key} historyMenu.requestPromotion()} > {$i18n.t('Set as Production')} {/if} { let reader = new FileReader(); reader.onload = (event) => { let originalImageUrl = `${event.target?.result}`; // For animated formats (gif, webp), skip resizing to preserve animation const fileType = (inputFiles[0] as any)?.['type']; if (fileType === 'image/gif' || fileType === 'image/webp') { info.meta.profile_image_url = originalImageUrl; inputFiles = null; filesInputElement.value = ''; return; } const img = new Image(); img.src = originalImageUrl; img.onload = function () { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); // Calculate the aspect ratio of the image const aspectRatio = img.width / img.height; // Calculate the new width and height to fit within 100x100 let newWidth, newHeight; if (aspectRatio > 1) { newWidth = 250 * aspectRatio; newHeight = 250; } else { newWidth = 250; newHeight = 250 / aspectRatio; } // Set the canvas size canvas.width = 250; canvas.height = 250; // Calculate the position to center the image const offsetX = (250 - newWidth) / 2; const offsetY = (250 - newHeight) / 2; // Draw the image on the canvas ctx.drawImage(img, offsetX, offsetY, newWidth, newHeight); // Get the base64 representation of the compressed image const compressedSrc = canvas.toDataURL('image/webp', 0.8); // Display the compressed image info.meta.profile_image_url = compressedSrc; inputFiles = null; filesInputElement.value = ''; }; }; if ( inputFiles && inputFiles.length > 0 && ['image/gif', 'image/webp', 'image/jpeg', 'image/png', 'image/svg+xml'].includes( (inputFiles[0] as any)?.['type'] ) ) { reader.readAsDataURL(inputFiles[0]); } else { console.log(`Unsupported File Type '${(inputFiles[0] as any)?.['type']}'.`); inputFiles = null; } }} /> {#if !edit || (edit && model)} { submitHandler(); }} > {#if backgroundPreview || info.meta.background_image_url} {:else} {/if} { filesInputElement.click(); }} > {#if info.meta.profile_image_url} {:else} {/if} {#if info.meta.profile_image_url && info.meta.profile_image_url !== `${WEBUI_BASE_URL}/static/favicon.png`} { info.meta.profile_image_url = `${WEBUI_BASE_URL}/static/favicon.png`; }} type="button" > {$i18n.t('Reset')} {/if} {#if editingLocale} setLocalizedField( 'name', (e.currentTarget as HTMLInputElement).value )} /> {:else} {/if} {#if !readOnly} {#if loading}{/if} {#if backgroundPreview || info.meta.background_image_url} backgroundInput.click()}>{$i18n.t('Change')} { clearBackgroundPreview(); backgroundFile = null; info.meta.background_image_url = null; }}>{$i18n.t('Remove')} {:else} backgroundInput.click()} > {$i18n.t('Add background')} {/if} {/if} {#if editingLocale} setLocalizedField('name', name)}> {$i18n.t('Copy default')} {#if localizedField('name')} clearLocalizedField('name')}> {$i18n.t('Use default')} {/if} {/if} {#if editingLocale} {$i18n.t('Description ({{language}})', { language: editingLocaleLabel || editingLocale })} setLocalizedField('description', info.meta.description ?? '')} > {$i18n.t('Copy default')} {#if localizedField('description')} clearLocalizedField('description')}> {$i18n.t('Use default')} {/if} {:else} { enableDescription = !enableDescription; }} > {$i18n.t('Description')} {#if !enableDescription} {$i18n.t('Default')} {:else} {$i18n.t('Custom')} {/if} {/if} {#if editingLocale} setLocalizedField( 'description', (e.currentTarget as HTMLTextAreaElement).value )} /> {:else if enableDescription} {/if} {#if preset} {/if} { const tagName = e.detail; info.meta.tags = info.meta.tags.filter((tag) => tag.name !== tagName); }} on:add={(e) => { const tagName = e.detail; if (!(info?.meta?.tags ?? null)) { info.meta.tags = [{ name: tagName }]; } else { info.meta.tags = [...info.meta.tags, { name: tagName }]; } }} /> {#if !readOnly && !historical} {$i18n.t('Compare revision')} {/if} {#if showSystemPrompt || !system.trim()} {#if chatVariablesPreview.fields.length > 0 || chatVariablesPreview.userFields.length > 0 || chatVariablesPreview.warnings.length > 0} {$i18n.t('Detected Variables')} {#if chatVariablesPreview.fields.length + chatVariablesPreview.userFields.length > 0} {chatVariablesPreview.fields.length + chatVariablesPreview.userFields.length} {/if} {#if chatVariablesPreview.fields.length > 0} {$i18n.t('Chat Variables')} {#each chatVariablesPreview.fields as field} {field.key} {field.type} {#if field.required} required {/if} {/each} {/if} {#if chatVariablesPreview.userFields.length > 0} {$i18n.t('User Variables')} {#each chatVariablesPreview.userFields as field} {field.key} {/each} {/if} {#if chatVariablesPreview.warnings.length > 0} {#each chatVariablesPreview.warnings as warning} {warning} {/each} {/if} {/if} {:else} { showSystemPrompt = true; }} class="w-full cursor-text text-start line-clamp-5 whitespace-pre-wrap break-words text-[0.8125rem] font-normal text-gray-900 dark:text-gray-100" > {system} {/if} {#if system.trim()} { showSystemPrompt = !showSystemPrompt; }} > {showSystemPrompt ? $i18n.t('Show less') : $i18n.t('Show more')} {/if} (showAdvanced = !showAdvanced)} > {modifiedParamCount ? $i18n.t('{{count}} modified', { count: modifiedParamCount }) : $i18n.t('Default')} {#if showAdvanced} {/if} {#if !editingLocale} {:else if Object.keys(params.model_controls ?? {}).length} {@const fields = modelControlTranslationSource(params.model_controls ?? {})} {@const customized = Object.keys(fields).some((key) => info.meta.i18n?.[editingLocale]?.[key]?.trim() )} {#each Object.entries(fields) as [key, original] (key)} setLocalizedField( key, (event.currentTarget as HTMLTextAreaElement).value )} /> {/each} {#if customized} Object.keys(fields).forEach(clearLocalizedField)} >{$i18n.t('Use default')} {/if} {/if} {#if editingLocale} $i18n.t(key) )} bind:localizedPromptSuggestions={info.meta.i18n} locale={editingLocale} localeLabel={editingLocaleLabel} disabled={readOnly} /> {:else if info.meta.suggestion_prompts != null} { customPromptDraft = structuredClone(info.meta.suggestion_prompts); info.meta.suggestion_prompts = null; }}>{$i18n.t('Use default')} {:else} {$i18n.t('Using default prompt suggestions')} { info.meta.suggestion_prompts = customPromptDraft ?? [ { content: '', title: ['', ''] } ]; }}>{$i18n.t('Customize')} {/if} {#if ($functions ?? []).some((func) => func.type === 'filter')} func.type === 'filter')} disabled={readOnly} /> {/if} {#if ($functions ?? []).some((func) => func.type === 'action')} func.type === 'action')} disabled={readOnly} /> {/if} {#if $config?.audio?.realtime?.enabled} {$i18n.t('Realtime Voice')} {/if} {$i18n.t('TTS Voice')} {#if capabilities.terminal && hasTerminal} {/if} (showPreview = !showPreview)} > {$i18n.t('JSON Preview')} {showPreview ? $i18n.t('Hide') : $i18n.t('Show')} { const copied = await copyToClipboard( JSON.stringify(readOnly ? model : modelInfo, null, 2) ); if (copied) { toast.success($i18n.t('Copied to clipboard')); } }} > {$i18n.t('Copy')} {#if showPreview} {/if} {/if} {#if !readOnly && !historical && (!edit || model)} {#if edit} {/if} {edit ? $i18n.t('Save & Update') : $i18n.t('Save & Create')} {#if loading}{/if} {/if} {:else} {$i18n.t('Loading...')} {/if}