Skip to content
Open
Show file tree
Hide file tree
Changes from 6 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
12 changes: 11 additions & 1 deletion invokeai/app/services/model_records/model_records_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from pathlib import Path
from typing import List, Optional, Set, Union

from pydantic import BaseModel, Field
from pydantic import BaseModel, Field, field_validator

from invokeai.app.services.shared.pagination import PaginatedResults
from invokeai.app.util.model_exclude_null import BaseModelExcludeNull
Expand Down Expand Up @@ -77,6 +77,16 @@ class ModelRecordChanges(BaseModelExcludeNull):
source: Optional[str] = Field(description="original source of the model", default=None)
source_type: Optional[ModelSourceType] = Field(description="type of model source", default=None)
source_api_response: Optional[str] = Field(description="metadata from remote source", default=None)
source_url: Optional[str] = Field(description="Optional URL for the model (e.g. download page)", default=None)

@field_validator("source_url", mode="before")
@classmethod
def validate_source_url(cls, v: Optional[str]) -> Optional[str]:
if v is not None and v != "":
if not v.startswith(("https://", "http://")):
raise ValueError("source_url must be an http or https URL")
return v or None

name: Optional[str] = Field(description="Name of the model.", default=None)
path: Optional[str] = Field(description="Path to the model.", default=None)
description: Optional[str] = Field(description="Model description", default=None)
Expand Down
15 changes: 14 additions & 1 deletion invokeai/backend/model_manager/configs/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
Type,
)

from pydantic import BaseModel, ConfigDict, Field, Tag
from pydantic import BaseModel, ConfigDict, Field, Tag, field_validator
from pydantic_core import PydanticUndefined

from invokeai.app.util.misc import uuid_string
Expand Down Expand Up @@ -77,6 +77,19 @@ class Config_Base(ABC, BaseModel):
default=None,
description="The original API response from the source, as stringified JSON.",
)
source_url: str | None = Field(
default=None,
description="Optional URL for the model (e.g. download page or model page).",
)

@field_validator("source_url", mode="before")
@classmethod
def validate_source_url(cls, v: str | None) -> str | None:
if v is not None and v != "":
if not v.startswith(("https://", "http://")):
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GPT rose a concern for this and model_records_base.py, if you call .startswith() inside a mode="before" validator without first checking the raw value is a string. A PATCH body like {"source_url": 123} will hit AttributeError instead of a normal validation error, which is likely to surface as a 500 instead of a clean 4xx.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

raise ValueError("source_url must be an http or https URL")
return v or None

cover_image: str | None = Field(
default=None,
description="Url for image to preview model",
Expand Down
1 change: 1 addition & 0 deletions invokeai/frontend/web/public/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1192,6 +1192,7 @@
"settings": "Settings",
"simpleModelPlaceholder": "URL or path to a local file or diffusers folder",
"source": "Source",
"sourceUrl": "Source URL",
"sigLip": "SigLIP",
"spandrelImageToImage": "Image to Image (Spandrel)",
"starterBundles": "Starter Bundles",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,12 @@ export const ModelEdit = memo(({ modelConfig }: Props) => {
<Textarea {...form.register('description')} minH={32} />
</FormControl>
</Flex>
<Flex gap="4" alignItems="center">
<FormControl flexDir="column" alignItems="flex-start" gap={1}>
<FormLabel>{t('modelManager.sourceUrl')}</FormLabel>
<Input {...form.register('source_url')} size="md" placeholder="https://" />
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add type="url" here for some basic client-side validation until we put real effort into better overall model edit validation.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

</FormControl>
</Flex>
<Heading as="h3" fontSize="md" mt="4">
{t('modelManager.modelSettings')}
</Heading>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import { Flex, Heading, Spacer, Text } from '@invoke-ai/ui-library';
import { Flex, Heading, Link, Spacer, Text } from '@invoke-ai/ui-library';
import { useIsModelManagerEnabled } from 'features/modelManagerV2/hooks/useIsModelManagerEnabled';
import ModelImageUpload from 'features/modelManagerV2/subpanels/ModelPanel/Fields/ModelImageUpload';
import type { PropsWithChildren } from 'react';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import type { AnyModelConfig } from 'services/api/types';

const isSafeUrl = (url: string): boolean => {
return url.startsWith('https://') || url.startsWith('http://');
};

type Props = PropsWithChildren<{
modelConfig: AnyModelConfig;
}>;
Expand All @@ -30,6 +34,14 @@ export const ModelHeader = memo(({ modelConfig, children }: Props) => {
{t('modelManager.source')}: {modelConfig.source}
</Text>
)}
{'source_url' in modelConfig && modelConfig.source_url && isSafeUrl(modelConfig.source_url) && (
<Text variant="subtext" noOfLines={1} wordBreak="break-all">
{t('modelManager.sourceUrl')}:{' '}
<Link href={modelConfig.source_url} isExternal color="invokeBlue.300">
{modelConfig.source_url}
</Link>
</Text>
)}
<Text noOfLines={3}>{modelConfig.description}</Text>
</Flex>
</Flex>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -657,6 +657,7 @@ describe('Graph', () => {
source: '/home/bat/invokeai-4.0.0/models/sdxl/main/stable-diffusion-xl-1.0-inpainting-0.1',
source_type: 'path',
source_api_response: null,
source_url: null,
cover_image: null,
type: 'main',
trigger_phrases: null,
Expand Down
Loading
Loading