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
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,17 @@ interface RoleRow {
`,
styles: [
`
/* Theme-flip tokens: dark text/surfaces by default (light mode);
the dark-theme class restores the original light-on-dark look. */
:host-context(.dark-theme) {
--df-ai-fg: rgba(255, 255, 255, 0.9);
--df-ai-fg-muted: rgba(255, 255, 255, 0.7);
--df-ai-fg-faint: rgba(255, 255, 255, 0.5);
--df-ai-surface: rgba(255, 255, 255, 0.02);
--df-ai-surface-strong: rgba(255, 255, 255, 0.08);
--df-ai-border: rgba(255, 255, 255, 0.1);
}

.prereqs {
border: 1px solid rgba(96, 165, 250, 0.3);
background: rgba(96, 165, 250, 0.05);
Expand All @@ -190,7 +201,7 @@ interface RoleRow {
}
p {
margin: 0;
color: rgba(255, 255, 255, 0.75);
color: var(--df-ai-fg-muted, rgba(0, 0, 0, 0.7));
font-size: 15px;
line-height: 1.55;
}
Expand All @@ -199,7 +210,7 @@ interface RoleRow {
&__section {
padding: 1rem 1.25rem;
border-radius: 6px;
background: rgba(255, 255, 255, 0.02);
background: var(--df-ai-surface, rgba(0, 0, 0, 0.03));

&--missing {
background: rgba(220, 53, 69, 0.06);
Expand All @@ -226,7 +237,7 @@ interface RoleRow {
}

&__kind-icon {
color: rgba(255, 255, 255, 0.5);
color: var(--df-ai-fg-faint, rgba(0, 0, 0, 0.5));
font-size: 17px;
}

Expand All @@ -237,7 +248,7 @@ interface RoleRow {

&__count {
font-size: 14px;
color: rgba(255, 255, 255, 0.6);
color: var(--df-ai-fg-muted, rgba(0, 0, 0, 0.6));
}

&__action {
Expand Down Expand Up @@ -270,8 +281,8 @@ interface RoleRow {
align-items: center;
gap: 0.5rem;
padding: 0.55rem 1.05rem;
background: rgba(255, 255, 255, 0.04);
border: 1px solid rgba(255, 255, 255, 0.1);
background: var(--df-ai-surface-strong, rgba(0, 0, 0, 0.05));
border: 1px solid var(--df-ai-border, rgba(0, 0, 0, 0.12));
border-radius: 999px;
font: inherit;
font-size: 16px;
Expand All @@ -289,7 +300,7 @@ interface RoleRow {
&--selected {
border-color: #60a5fa;
background: rgba(96, 165, 250, 0.18);
color: #fff;
color: var(--df-ai-fg, rgba(0, 0, 0, 0.87));
}
}

Expand All @@ -304,13 +315,13 @@ interface RoleRow {
&__pick-hint {
margin: 0.625rem 0 0;
font-size: 14px;
color: rgba(255, 255, 255, 0.6);
color: var(--df-ai-fg-muted, rgba(0, 0, 0, 0.6));
font-style: italic;
}

&__link {
font-size: 12px;
color: rgba(255, 255, 255, 0.45);
color: var(--df-ai-fg-faint, rgba(0, 0, 0, 0.45));
text-decoration: none;

&:hover {
Expand All @@ -322,19 +333,19 @@ interface RoleRow {
&__hint {
margin: 0.5rem 0 0;
font-size: 13px;
color: rgba(255, 255, 255, 0.7);
color: var(--df-ai-fg-muted, rgba(0, 0, 0, 0.7));
line-height: 1.5;

code {
font-family: 'SFMono-Regular', Menlo, Consolas, monospace;
font-size: 12px;
padding: 0.1rem 0.375rem;
border-radius: 3px;
background: rgba(255, 255, 255, 0.08);
background: var(--df-ai-surface-strong, rgba(0, 0, 0, 0.06));
}

strong {
color: rgba(255, 255, 255, 0.9);
color: var(--df-ai-fg, rgba(0, 0, 0, 0.9));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -836,7 +836,8 @@ <h4 class="text-center" style="color: black !important">
<df-ai-model-picker
*ngIf="serviceForm.getRawValue().type === 'ai_connection'"
class="full-width"
[form]="serviceForm"></df-ai-model-picker>
[form]="serviceForm"
[serviceId]="edit && serviceData ? serviceData.id : null"></df-ai-model-picker>

<!-- AI Connection: allowed-roles multi-select. Replaces
the auto-rendered allowed_roles JSON text field.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,14 +87,25 @@ interface RoleRow {
`,
styles: [
`
/* Theme-flip tokens: dark text/surfaces by default (light mode);
the dark-theme class restores the original light-on-dark look. */
:host-context(.dark-theme) {
--df-ai-fg: rgba(255, 255, 255, 0.9);
--df-ai-fg-muted: rgba(255, 255, 255, 0.7);
--df-ai-fg-faint: rgba(255, 255, 255, 0.5);
--df-ai-surface: rgba(255, 255, 255, 0.02);
--df-ai-surface-strong: rgba(255, 255, 255, 0.04);
--df-ai-border: rgba(255, 255, 255, 0.08);
}

.allowed-roles {
display: flex;
flex-direction: column;
gap: 1rem;
padding: 1.5rem 1.75rem;
margin: 1rem 0;
background: rgba(255, 255, 255, 0.02);
border: 1px solid rgba(255, 255, 255, 0.08);
background: var(--df-ai-surface, rgba(0, 0, 0, 0.03));
border: 1px solid var(--df-ai-border, rgba(0, 0, 0, 0.12));
border-radius: 8px;
font-size: 16px;

Expand All @@ -117,7 +128,7 @@ interface RoleRow {

&__count {
font-size: 15px;
color: rgba(255, 255, 255, 0.6);
color: var(--df-ai-fg-muted, rgba(0, 0, 0, 0.6));
}

&__action {
Expand All @@ -133,7 +144,7 @@ interface RoleRow {
&__hint {
margin: 0;
font-size: 15px;
color: rgba(255, 255, 255, 0.75);
color: var(--df-ai-fg-muted, rgba(0, 0, 0, 0.7));
line-height: 1.55;
}

Expand All @@ -151,7 +162,7 @@ interface RoleRow {

&__loading,
&__empty {
color: rgba(255, 255, 255, 0.6);
color: var(--df-ai-fg-muted, rgba(0, 0, 0, 0.6));
font-style: italic;
font-size: 15px;
}
Expand All @@ -176,8 +187,8 @@ interface RoleRow {
align-items: center;
gap: 0.5rem;
padding: 0.55rem 1.05rem;
background: rgba(255, 255, 255, 0.04);
border: 1px solid rgba(255, 255, 255, 0.1);
background: var(--df-ai-surface-strong, rgba(0, 0, 0, 0.05));
border: 1px solid var(--df-ai-border, rgba(0, 0, 0, 0.12));
border-radius: 999px;
font: inherit;
font-size: 16px;
Expand All @@ -195,7 +206,7 @@ interface RoleRow {
&--selected {
border-color: #a78bfa;
background: rgba(167, 139, 250, 0.18);
color: #fff;
color: var(--df-ai-fg, rgba(0, 0, 0, 0.87));
}
}

Expand All @@ -209,7 +220,7 @@ interface RoleRow {

&__link {
font-size: 14px;
color: rgba(255, 255, 255, 0.5);
color: var(--df-ai-fg-faint, rgba(0, 0, 0, 0.5));
text-decoration: none;

&:hover {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,14 +139,25 @@ interface NormalizedModel {
`,
styles: [
`
/* Theme-flip tokens: dark text/surfaces by default (light mode);
the dark-theme class restores the original light-on-dark look. */
:host-context(.dark-theme) {
--df-ai-fg: rgba(255, 255, 255, 0.85);
--df-ai-fg-muted: rgba(255, 255, 255, 0.7);
--df-ai-fg-faint: rgba(255, 255, 255, 0.55);
--df-ai-surface: rgba(255, 255, 255, 0.02);
--df-ai-surface-strong: rgba(255, 255, 255, 0.08);
--df-ai-border: rgba(255, 255, 255, 0.08);
}

.model-picker {
display: flex;
flex-direction: column;
gap: 0.875rem;
padding: 1.25rem 1.5rem;
margin: 1rem 0;
background: rgba(255, 255, 255, 0.02);
border: 1px solid rgba(255, 255, 255, 0.08);
background: var(--df-ai-surface, rgba(0, 0, 0, 0.03));
border: 1px solid var(--df-ai-border, rgba(0, 0, 0, 0.12));
border-radius: 8px;
font-size: 16px;

Expand Down Expand Up @@ -186,14 +197,14 @@ interface NormalizedModel {
}
&__option-meta {
font-size: 14px;
color: rgba(255, 255, 255, 0.55);
color: var(--df-ai-fg-faint, rgba(0, 0, 0, 0.55));
font-family: 'SFMono-Regular', Menlo, Consolas, monospace;
}

&__hint {
margin: 0;
font-size: 15px;
color: rgba(255, 255, 255, 0.7);
color: var(--df-ai-fg-muted, rgba(0, 0, 0, 0.6));
font-style: italic;
line-height: 1.5;
}
Expand All @@ -211,15 +222,15 @@ interface NormalizedModel {
&__current {
margin: 0;
font-size: 15px;
color: rgba(255, 255, 255, 0.85);
color: var(--df-ai-fg, rgba(0, 0, 0, 0.85));
display: flex;
align-items: center;
gap: 0.5rem;

code {
font-family: 'SFMono-Regular', Menlo, Consolas, monospace;
font-size: 14px;
background: rgba(255, 255, 255, 0.08);
background: var(--df-ai-surface-strong, rgba(0, 0, 0, 0.06));
padding: 0.2rem 0.5rem;
border-radius: 3px;
}
Expand All @@ -235,6 +246,11 @@ export class DfAiModelPickerComponent implements OnInit {
/** Service form. Reads config.{provider, api_key, base_url, ...}, writes config.defaultModel. */
@Input({ required: true }) form!: FormGroup;

/** Service id when editing a saved AI Connection. Lets the backend fall
* back to the stored api_key when the form only holds the masked value,
* so "Fetch models" works after the connection has been saved. */
@Input() serviceId: number | null = null;

private http = inject(HttpClient);

loading = false;
Expand Down Expand Up @@ -297,14 +313,20 @@ export class DfAiModelPickerComponent implements OnInit {

// DF camel-cases /api/* response bodies, so saved connections may
// have baseUrl/apiKey on the form rather than base_url/api_key.
const body = {
// Never forward the redisplayed protection mask as a real key — null
// it and pass service_id so the backend uses the stored secret.
const rawApiKey = config.api_key ?? config.apiKey ?? null;
const body: Record<string, unknown> = {
provider,
api_key: config.api_key ?? config.apiKey ?? null,
api_key: rawApiKey === '**********' ? null : rawApiKey,
base_url: config.base_url ?? config.baseUrl ?? null,
organization_id: config.organization_id ?? config.organizationId ?? null,
extra_headers: config.extra_headers ?? config.extraHeaders ?? null,
timeout: config.timeout ?? null,
};
if (this.serviceId) {
body['service_id'] = this.serviceId;
}

this.http
.post<TestConnectionResponse>('/_internal/ai/test-connection', body)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,13 @@ interface TestConnectionResponse {
`,
styles: [
`
/* Theme-flip tokens: text defaults to dark (light mode); the
dark-theme class restores the original light text. */
:host-context(.dark-theme) {
--df-ai-fg: rgba(255, 255, 255, 0.9);
--df-ai-fg-muted: rgba(255, 255, 255, 0.75);
}

.test-conn {
display: flex;
flex-direction: column;
Expand Down Expand Up @@ -105,7 +112,7 @@ interface TestConnectionResponse {

&__detail {
flex: 1;
color: rgba(255, 255, 255, 0.9);
color: var(--df-ai-fg, rgba(0, 0, 0, 0.87));
display: flex;
flex-direction: column;
gap: 0.35rem;
Expand All @@ -117,7 +124,7 @@ interface TestConnectionResponse {

p {
margin: 0;
color: rgba(255, 255, 255, 0.75);
color: var(--df-ai-fg-muted, rgba(0, 0, 0, 0.7));
font-size: 15px;
}
}
Expand Down Expand Up @@ -168,7 +175,11 @@ export class DfAiTestConnectionComponent {
// edited connection's form has baseUrl/apiKey not base_url/api_key.
// Read both — picks whichever the form actually carries.
const provider = config.provider;
const apiKey = config.api_key ?? config.apiKey ?? null;
// The edit form redisplays a saved key as the protection mask. Never
// send the mask as a real key — null it so the backend falls back to
// the stored secret via service_id.
const rawApiKey = config.api_key ?? config.apiKey ?? null;
const apiKey = rawApiKey === '**********' ? null : rawApiKey;
const baseUrl = config.base_url ?? config.baseUrl ?? null;
const orgId = config.organization_id ?? config.organizationId ?? null;
const extraHeaders = config.extra_headers ?? config.extraHeaders ?? null;
Expand Down
Loading