Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
0f563f8
🚀 Feature: add database support for default models
LedjoLleshaj Jan 31, 2026
623648e
🚀 Feature: expose default model selection endpoints
LedjoLleshaj Jan 31, 2026
01e748a
🚀 Feature: UI for default model selection
LedjoLleshaj Jan 31, 2026
2fe7bab
test: integration tests for default model selection
LedjoLleshaj Jan 31, 2026
111aef6
📚 Docs: updated files inside llm/
LedjoLleshaj Jan 31, 2026
e6de5d2
🧩 Fix: default model selection and implement auto-default logic
LedjoLleshaj Jan 31, 2026
d24ea7f
📐 Refactor: ran make format and pre-merge
LedjoLleshaj Jan 31, 2026
a0219c5
🚀 Feature: add database support for default models
LedjoLleshaj Jan 31, 2026
43ca5bc
🚀 Feature: expose default model selection endpoints
LedjoLleshaj Jan 31, 2026
07b537c
🚀 Feature: UI for default model selection
LedjoLleshaj Jan 31, 2026
a2ac623
test: integration tests for default model selection
LedjoLleshaj Jan 31, 2026
b4505ab
📚 Docs: updated files inside llm/
LedjoLleshaj Jan 31, 2026
0c1ce1c
🧩 Fix: default model selection and implement auto-default logic
LedjoLleshaj Jan 31, 2026
ffead9f
📐 Refactor: ran make format and pre-merge
LedjoLleshaj Jan 31, 2026
e304733
Merge branch 'feature-model-selection' of https://github.com/LedjoLle…
LedjoLleshaj Feb 1, 2026
4170eb4
unit tests and small format fixes
LedjoLleshaj Feb 1, 2026
980f549
UI fixes
LedjoLleshaj Feb 1, 2026
181f8d7
fix coderabbit suggestions
LedjoLleshaj Feb 1, 2026
4b1d483
📐 Refactor: Model Card
LedjoLleshaj Feb 2, 2026
e2ea5ce
fix coderabbit suggestions
LedjoLleshaj Feb 2, 2026
1a7d4ae
last coderabbit fixes
LedjoLleshaj Feb 2, 2026
23f258d
Merge branch 'develop' into feature-model-selection
nicofretti Feb 2, 2026
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
28 changes: 27 additions & 1 deletion app.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
from lib.errors import BlockExecutionError, BlockNotFoundError, ValidationError
from lib.job_processor import process_job_in_thread
from lib.job_queue import JobQueue
from lib.llm_config import LLMConfigManager, LLMConfigNotFoundError
from lib.llm_config import LLMConfigError, LLMConfigManager, LLMConfigNotFoundError
from lib.storage import Storage
from lib.templates import template_registry
from lib.workflow import Pipeline as WorkflowPipeline
Expand Down Expand Up @@ -698,6 +698,19 @@ async def delete_llm_model(name: str) -> dict[str, str]:
raise HTTPException(status_code=404, detail=e.message)


@api_router.put("/llm-models/{name}/default")
async def set_default_llm_model(name: str) -> dict[str, str]:
"""set default llm model"""
try:
await llm_config_manager.set_default_llm_model(name)
return {"message": "llm model set as default successfully"}
except LLMConfigNotFoundError as e:
raise HTTPException(status_code=404, detail=e.message)
except LLMConfigError as e:
logger.exception(f"failed to set default llm model {name}")
raise HTTPException(status_code=400, detail=e.message) from e


@api_router.post("/llm-models/test")
async def test_llm_connection(config: LLMModelConfig) -> ConnectionTestResult:
"""test llm connection"""
Expand Down Expand Up @@ -753,6 +766,19 @@ async def delete_embedding_model(name: str) -> dict[str, str]:
raise HTTPException(status_code=404, detail=e.message)


@api_router.put("/embedding-models/{name}/default")
async def set_default_embedding_model(name: str) -> dict[str, str]:
"""set default embedding model"""
try:
await llm_config_manager.set_default_embedding_model(name)
return {"message": "embedding model set as default successfully"}
except LLMConfigNotFoundError as e:
raise HTTPException(status_code=404, detail=e.message)
except LLMConfigError as e:
logger.exception(f"failed to set default embedding model {name}")
raise HTTPException(status_code=400, detail=e.message) from e


@api_router.post("/embedding-models/test")
async def test_embedding_connection(
config: EmbeddingModelConfig,
Expand Down
159 changes: 159 additions & 0 deletions frontend/src/components/settings/ModelCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import type { ReactNode } from "react";
import { Box, Text, Button, IconButton, Spinner, Tooltip } from "@primer/react";
import {
TrashIcon,
PencilIcon,
CheckCircleIcon,
CheckCircleFillIcon,
StarIcon,
} from "@primer/octicons-react";
import type { LLMModelConfig, EmbeddingModelConfig } from "../../types";

interface ModelCardStatus {
isDefault: boolean;
isTesting: boolean;
isSettingDefault: boolean;
}

interface ModelCardActions {
onSetDefault: () => void;
onTest: () => void;
onEdit: () => void;
onDelete: () => void;
}

interface ModelCardProps<T extends LLMModelConfig | EmbeddingModelConfig> {
model: T;
status: ModelCardStatus;
actions: ModelCardActions;
extraDetails?: ReactNode;
}

export function ModelCard<T extends LLMModelConfig | EmbeddingModelConfig>({
model,
status,
actions,
extraDetails,
}: ModelCardProps<T>) {
const { isDefault, isTesting, isSettingDefault } = status;
const { onSetDefault, onTest, onEdit, onDelete } = actions;

return (
<Box
sx={{
p: 3,
border: "1px solid",
borderColor: "border.default",
borderRadius: 2,
bg: "canvas.subtle",
transition: "all 0.2s",
}}
>
<Box sx={{ display: "flex", alignItems: "start", justifyContent: "space-between" }}>
<Box sx={{ flex: 1, display: "flex", flexDirection: "column" }}>
{/* name and badges row */}
<Box sx={{ display: "flex", alignItems: "center", gap: 2, mb: 1 }}>
<Text sx={{ fontWeight: "bold", fontSize: 2, color: "fg.default" }}>{model.name}</Text>
<Box
sx={{
px: 2,
py: 1,
borderRadius: 2,
bg: "accent.subtle",
color: "accent.fg",
fontSize: 0,
fontWeight: "semibold",
}}
>
{model.provider}
</Box>
{/* isDefault renders the default badge with CheckCircleFillIcon to visually distinguish the selected model */}
{isDefault && (
<Box
sx={{
px: 2,
py: 1,
borderRadius: 2,
border: "1px solid",
borderColor: "success.emphasis",
color: "success.fg",
fontSize: 0,
fontWeight: "semibold",
display: "flex",
alignItems: "center",
gap: 1,
}}
>
<CheckCircleFillIcon size={12} />
Default
</Box>
)}
</Box>

{/* model details - model.model_name and model.endpoint; extraDetails may be appended for additional info like embedding dimensions */}
<Text sx={{ fontSize: 1, color: "fg.muted", mb: 1 }}>
model: {model.model_name}
{extraDetails}
</Text>
<Text sx={{ fontSize: 1, color: "fg.muted", fontFamily: "mono" }}>{model.endpoint}</Text>
</Box>

{/* action buttons */}
<Box sx={{ display: "flex", gap: 2 }}>
{!isDefault && (
<Tooltip aria-label="Set as default model" direction="s">
<Button
size="small"
variant="default"
onClick={onSetDefault}
disabled={isSettingDefault}
sx={{
color: "attention.fg",
borderColor: "attention.emphasis",
"&:hover:not(:disabled)": {
bg: "attention.subtle",
borderColor: "attention.emphasis",
color: "attention.fg",
},
}}
>
<Box sx={{ display: "flex", alignItems: "center", gap: 1 }}>
{isSettingDefault ? <Spinner size="small" /> : <StarIcon size={16} />}
<Text>{isSettingDefault ? "Setting..." : "Set Default"}</Text>
</Box>
</Button>
</Tooltip>
)}
<Button
size="small"
variant="default"
onClick={onTest}
disabled={isTesting}
sx={{
color: isTesting ? "fg.muted" : "success.fg",
borderColor: isTesting ? "border.default" : "success.emphasis",
"&:hover:not(:disabled)": {
bg: "success.subtle",
borderColor: "success.emphasis",
color: "success.fg",
},
}}
>
<Box sx={{ display: "flex", alignItems: "center", gap: 1 }}>
{isTesting ? <Spinner size="small" /> : <CheckCircleIcon size={16} />}
<Text>{isTesting ? "Testing..." : "Test"}</Text>
</Box>
</Button>
<IconButton icon={PencilIcon} aria-label="edit" size="small" onClick={onEdit} />
<IconButton
icon={TrashIcon}
aria-label="delete"
size="small"
variant="danger"
onClick={onDelete}
/>
</Box>
</Box>
</Box>
);
}
Loading
Loading