+
-
diff --git a/front_end/panels/ai_chat/ui/mcp/MCPConnectorsCatalogDialog.ts b/front_end/panels/ai_chat/ui/mcp/MCPConnectorsCatalogDialog.ts
index 2fdb23b95d..c5450d2f16 100644
--- a/front_end/panels/ai_chat/ui/mcp/MCPConnectorsCatalogDialog.ts
+++ b/front_end/panels/ai_chat/ui/mcp/MCPConnectorsCatalogDialog.ts
@@ -11,19 +11,19 @@ import mcpConnectorsCatalogDialogStyles from './mcpConnectorsCatalogDialog.css.j
const logger = createLogger('MCPConnectorsCatalogDialog');
const LOGO_URLS = {
- sentry: '/bundled/Images/sentry-mcp.svg',
- atlassian: '/bundled/Images/atlassian-mcp.svg',
- linear: '/bundled/Images/linear-mcp.svg',
- notion: '/bundled/Images/notion-mcp.svg',
- slack: '/bundled/Images/slack-mcp.svg',
- github: '/bundled/Images/github-mcp.svg',
- asana: '/bundled/Images/asana-mcp.svg',
- intercom: '/bundled/Images/intercom-mcp.svg',
- 'google-drive': '/bundled/Images/google-drive-mcp.svg',
- huggingface: '/bundled/Images/huggingface-mcp.svg',
- 'google-sheets': '/bundled/Images/google-sheets-mcp.svg',
- socket: '/bundled/Images/socket-mcp.svg',
- invideo: '/bundled/Images/invideo-mcp.svg',
+ sentry: new URL('../../../../Images/sentry-mcp.svg', import.meta.url).toString(),
+ atlassian: new URL('../../../../Images/atlassian-mcp.svg', import.meta.url).toString(),
+ linear: new URL('../../../../Images/linear-mcp.svg', import.meta.url).toString(),
+ notion: new URL('../../../../Images/notion-mcp.svg', import.meta.url).toString(),
+ slack: new URL('../../../../Images/slack-mcp.svg', import.meta.url).toString(),
+ github: new URL('../../../../Images/github-mcp.svg', import.meta.url).toString(),
+ asana: new URL('../../../../Images/asana-mcp.svg', import.meta.url).toString(),
+ intercom: new URL('../../../../Images/intercom-mcp.svg', import.meta.url).toString(),
+ 'google-drive': new URL('../../../../Images/google-drive-mcp.svg', import.meta.url).toString(),
+ huggingface: new URL('../../../../Images/huggingface-mcp.svg', import.meta.url).toString(),
+ 'google-sheets': new URL('../../../../Images/google-sheets-mcp.svg', import.meta.url).toString(),
+ socket: new URL('../../../../Images/socket-mcp.svg', import.meta.url).toString(),
+ invideo: new URL('../../../../Images/invideo-mcp.svg', import.meta.url).toString(),
} as const;
type MCPConnectorLogoId = keyof typeof LOGO_URLS;
diff --git a/front_end/panels/ai_chat/ui/message/MessageList.ts b/front_end/panels/ai_chat/ui/message/MessageList.ts
index 4bb3d7afc9..d722b5ecd8 100644
--- a/front_end/panels/ai_chat/ui/message/MessageList.ts
+++ b/front_end/panels/ai_chat/ui/message/MessageList.ts
@@ -11,7 +11,9 @@ const {customElement} = Decorators as any;
@customElement('ai-message-list')
export class MessageList extends HTMLElement {
static readonly litTagName = Lit.StaticHtml.literal`ai-message-list`;
- readonly #shadow = this.attachShadow({mode: 'open'});
+ // Use Light DOM
+ // readonly #shadow = this.attachShadow({mode: 'open'});
+ readonly #shadow = this;
// Public API properties (no decorators; manual setters trigger render)
#messages: ChatMessage[] = [];
@@ -27,11 +29,18 @@ export class MessageList extends HTMLElement {
// Internal state
#pinToBottom = true;
- #container?: HTMLElement;
#resizeObserver = new ResizeObserver(() => { if (this.#pinToBottom) this.#scrollToBottom(); });
- connectedCallback(): void { this.#render(); }
- disconnectedCallback(): void { this.#resizeObserver.disconnect(); }
+ connectedCallback(): void {
+ this.#render();
+ this.addEventListener('scroll', this.#onScroll);
+ this.#resizeObserver.observe(this);
+ }
+
+ disconnectedCallback(): void {
+ this.#resizeObserver.disconnect();
+ this.removeEventListener('scroll', this.#onScroll);
+ }
#onScroll = (e: Event) => {
const el = e.target as HTMLElement;
@@ -39,27 +48,28 @@ export class MessageList extends HTMLElement {
this.#pinToBottom = el.scrollTop + el.clientHeight + SCROLL_ROUNDING_OFFSET >= el.scrollHeight;
};
- #scrollToBottom(): void { if (this.#container) this.#container.scrollTop = this.#container.scrollHeight; }
+ #scrollToBottom(): void { this.scrollTop = this.scrollHeight; }
#render(): void {
- const refFn = (el?: Element) => {
- if (this.#container) { this.#resizeObserver.unobserve(this.#container); }
- this.#container = el as HTMLElement | undefined;
- if (this.#container) {
- this.#resizeObserver.observe(this.#container);
- this.#scrollToBottom();
- } else {
- this.#pinToBottom = true;
- }
- };
-
- // Container mode: project messages via slot from parent.
- Lit.render(html`
-
-
-
-
- `, this.#shadow, {host: this});
+ ai-message-list::-webkit-scrollbar { width: 4px; }
+ ai-message-list::-webkit-scrollbar-track { background: transparent; }
+ ai-message-list::-webkit-scrollbar-thumb { background-color: var(--color-scrollbar); border-radius: 4px; }
+ `;
+ this.prepend(style);
+ }
}
}
diff --git a/front_end/panels/ai_chat/ui/model_selector/ModelSelector.ts b/front_end/panels/ai_chat/ui/model_selector/ModelSelector.ts
index fdfbd73b50..9e196a03f2 100644
--- a/front_end/panels/ai_chat/ui/model_selector/ModelSelector.ts
+++ b/front_end/panels/ai_chat/ui/model_selector/ModelSelector.ts
@@ -3,6 +3,7 @@
// found in the LICENSE file.
import * as Lit from '../../../../ui/lit/lit.js';
+import '../common/Dropdown.js';
const {html, Decorators} = Lit;
const {customElement} = Decorators as any;
@@ -16,11 +17,6 @@ export class ModelSelector extends HTMLElement {
#options: ModelOption[] = [];
#selected: string | undefined;
#disabled = false;
- #open = false;
- #query = '';
- #highlighted = 0;
- #preferAbove = false;
- #forceSearchable = false;
get options(): ModelOption[] { return this.#options; }
set options(v: ModelOption[]) { this.#options = v || []; this.#render(); }
@@ -28,10 +24,6 @@ export class ModelSelector extends HTMLElement {
set selected(v: string | undefined) { this.#selected = v; this.#render(); }
get disabled(): boolean { return this.#disabled; }
set disabled(v: boolean) { this.#disabled = !!v; this.#render(); }
- get preferAbove(): boolean { return this.#preferAbove; }
- set preferAbove(v: boolean) { this.#preferAbove = !!v; this.#render(); }
- get forceSearchable(): boolean { return this.#forceSearchable; }
- set forceSearchable(v: boolean) { this.#forceSearchable = !!v; this.#render(); }
connectedCallback(): void { this.#render(); }
@@ -39,68 +31,45 @@ export class ModelSelector extends HTMLElement {
this.dispatchEvent(new CustomEvent('change', { bubbles: true, detail: { value }}));
}
- #toggle = (e: Event) => {
- e.preventDefault();
- if (this.#disabled) return;
- const wasOpen = this.#open;
- this.#open = !this.#open;
+ #handleChange = (value: string): void => {
+ this.#selected = value;
+ this.#emitChange(value);
this.#render();
- if (!wasOpen && this.#open) {
- // Notify host that the selector opened (used to lazily refresh models)
- this.dispatchEvent(new CustomEvent('model-selector-focus', {bubbles: true}));
- }
};
- #onSearch = (e: Event) => { this.#query = (e.target as HTMLInputElement).value; this.#highlighted = 0; this.#render(); };
- #onKeydown = (e: KeyboardEvent) => {
- const filtered = this.#filtered();
- if (e.key === 'ArrowDown') { e.preventDefault(); this.#highlighted = Math.min(this.#highlighted + 1, filtered.length - 1); this.#render(); }
- if (e.key === 'ArrowUp') { e.preventDefault(); this.#highlighted = Math.max(this.#highlighted - 1, 0); this.#render(); }
- if (e.key === 'Enter') { e.preventDefault(); const opt = filtered[this.#highlighted]; if (opt) { this.#selected = opt.value; this.#open = false; this.#emitChange(opt.value); this.#render(); } }
- if (e.key === 'Escape') { e.preventDefault(); this.#open = false; this.#render(); }
- };
-
- #filtered(): ModelOption[] {
- if (!this.#query) return this.#options;
- const q = this.#query.toLowerCase();
- return this.#options.filter(o => o.label.toLowerCase().includes(q) || o.value.toLowerCase().includes(q));
- }
- #isSearchable(): boolean { return this.#forceSearchable || (this.#options?.length || 0) >= 20; }
+ #handleFocus = (): void => {
+ // Notify host that the selector opened (used to lazily refresh models)
+ this.dispatchEvent(new CustomEvent('model-selector-focus', {bubbles: true}));
+ };
#render(): void {
- const selectedLabel = this.#options.find(o => o.value === this.#selected)?.label || this.#selected || 'Select Model';
- if (!this.#isSearchable()) {
- Lit.render(html`
-
- this.#emitChange((e.target as HTMLSelectElement).value)} @focus=${() => this.dispatchEvent(new CustomEvent('model-selector-focus', {bubbles: true}))}>
- ${this.#options.map(o => html`${o.label} `)}
-
-
- `, this, {host: this});
- return;
- }
-
- const filtered = this.#filtered();
Lit.render(html`
-
-
- ${selectedLabel}
- ${this.#open ? '▲' : '▼'}
-
- ${this.#open ? html`
-
e.stopPropagation()}>
-
-
- ${filtered.map((o, i) => html`
-
{ this.#selected = o.value; this.#open = false; this.#emitChange(o.value); this.#render(); }}
- @mouseenter=${() => this.#highlighted = i}
- >${o.label}
- `)}
- ${filtered.length === 0 ? html`
No matching models found
` : ''}
-
-
- ` : ''}
+
+
+
`, this, {host: this});
}
diff --git a/front_end/panels/ai_chat/ui/settings/advanced/EvaluationSettings.ts b/front_end/panels/ai_chat/ui/settings/advanced/EvaluationSettings.ts
index 4be7e44ff9..231e6e585a 100644
--- a/front_end/panels/ai_chat/ui/settings/advanced/EvaluationSettings.ts
+++ b/front_end/panels/ai_chat/ui/settings/advanced/EvaluationSettings.ts
@@ -24,9 +24,11 @@ const logger = createLogger('EvaluationSettings');
export class EvaluationSettings {
private container: HTMLElement;
private statusUpdateInterval: number | null = null;
- private evaluationEnabledCheckbox: HTMLInputElement | null = null;
+ private isEnabled: boolean = false;
+ private toggleElement: HTMLDivElement | null = null;
private evaluationEndpointInput: HTMLInputElement | null = null;
private evaluationSecretKeyInput: HTMLInputElement | null = null;
+ private configContainer: HTMLDivElement | null = null;
constructor(container: HTMLElement) {
this.container = container;
@@ -37,99 +39,53 @@ export class EvaluationSettings {
this.container.innerHTML = '';
this.container.className = 'settings-section evaluation-section';
- // Title
- const evaluationSectionTitle = document.createElement('h3');
- evaluationSectionTitle.className = 'settings-subtitle';
- evaluationSectionTitle.textContent = i18nString(UIStrings.evaluationSection);
- this.container.appendChild(evaluationSectionTitle);
-
// Get current evaluation configuration
const currentEvaluationConfig = getEvaluationConfig();
-
- // Evaluation enabled checkbox
- const evaluationEnabledContainer = document.createElement('div');
- evaluationEnabledContainer.className = 'evaluation-enabled-container';
- this.container.appendChild(evaluationEnabledContainer);
-
- this.evaluationEnabledCheckbox = document.createElement('input');
- this.evaluationEnabledCheckbox.type = 'checkbox';
- this.evaluationEnabledCheckbox.id = 'evaluation-enabled';
- this.evaluationEnabledCheckbox.className = 'evaluation-checkbox';
- this.evaluationEnabledCheckbox.checked = isEvaluationEnabled();
- evaluationEnabledContainer.appendChild(this.evaluationEnabledCheckbox);
-
- const evaluationEnabledLabel = document.createElement('label');
- evaluationEnabledLabel.htmlFor = 'evaluation-enabled';
- evaluationEnabledLabel.className = 'evaluation-label';
- evaluationEnabledLabel.textContent = i18nString(UIStrings.evaluationEnabled);
- evaluationEnabledContainer.appendChild(evaluationEnabledLabel);
-
- const evaluationEnabledHint = document.createElement('div');
- evaluationEnabledHint.className = 'settings-hint';
- evaluationEnabledHint.textContent = i18nString(UIStrings.evaluationEnabledHint);
- this.container.appendChild(evaluationEnabledHint);
-
- // Connection status indicator
- const connectionStatusContainer = document.createElement('div');
- connectionStatusContainer.className = 'connection-status-container';
- connectionStatusContainer.style.display = 'flex';
- connectionStatusContainer.style.alignItems = 'center';
- connectionStatusContainer.style.gap = '8px';
- connectionStatusContainer.style.marginTop = '8px';
- connectionStatusContainer.style.fontSize = '13px';
- this.container.appendChild(connectionStatusContainer);
-
- const connectionStatusDot = document.createElement('div');
- connectionStatusDot.className = 'connection-status-dot';
- connectionStatusDot.style.width = '8px';
- connectionStatusDot.style.height = '8px';
- connectionStatusDot.style.borderRadius = '50%';
- connectionStatusDot.style.flexShrink = '0';
- connectionStatusContainer.appendChild(connectionStatusDot);
-
- const connectionStatusText = document.createElement('span');
- connectionStatusText.className = 'connection-status-text';
- connectionStatusContainer.appendChild(connectionStatusText);
-
- // Function to update connection status
- const updateConnectionStatus = () => {
- const isConnected = isEvaluationConnected();
-
- logger.debug('Updating connection status', { isConnected });
-
- if (isConnected) {
- connectionStatusDot.style.backgroundColor = 'var(--color-accent-green)';
- connectionStatusText.textContent = 'Connected to evaluation server';
- connectionStatusText.style.color = 'var(--color-accent-green)';
- } else {
- connectionStatusDot.style.backgroundColor = 'var(--color-text-disabled)';
- connectionStatusText.textContent = 'Not connected';
- connectionStatusText.style.color = 'var(--color-text-disabled)';
- }
- };
-
- // Update status initially and when evaluation is enabled/disabled
- updateConnectionStatus();
-
- // Set up periodic status updates every 2 seconds
- this.statusUpdateInterval = setInterval(updateConnectionStatus, 2000);
-
- // Evaluation configuration container (shown when enabled)
- const evaluationConfigContainer = document.createElement('div');
- evaluationConfigContainer.className = 'evaluation-config-container';
- evaluationConfigContainer.style.display = this.evaluationEnabledCheckbox.checked ? 'block' : 'none';
- this.container.appendChild(evaluationConfigContainer);
+ this.isEnabled = isEvaluationEnabled();
+
+ // Header with toggle
+ const headerContainer = document.createElement('div');
+ headerContainer.className = 'settings-toggle-container';
+ this.container.appendChild(headerContainer);
+
+ const infoContainer = document.createElement('div');
+ infoContainer.className = 'settings-toggle-info';
+ headerContainer.appendChild(infoContainer);
+
+ const title = document.createElement('div');
+ title.className = 'settings-toggle-title';
+ title.textContent = i18nString(UIStrings.evaluationSection);
+ infoContainer.appendChild(title);
+
+ const description = document.createElement('div');
+ description.className = 'settings-toggle-description';
+ description.textContent = i18nString(UIStrings.evaluationEnabledHint);
+ infoContainer.appendChild(description);
+
+ // Toggle switch
+ this.toggleElement = document.createElement('div');
+ this.toggleElement.className = 'settings-toggle';
+ if (this.isEnabled) {
+ this.toggleElement.classList.add('active');
+ }
+ this.toggleElement.addEventListener('click', () => this.handleToggle());
+ headerContainer.appendChild(this.toggleElement);
+
+ // Configuration container (shown when enabled)
+ this.configContainer = document.createElement('div');
+ this.configContainer.className = 'evaluation-config-container';
+ this.configContainer.style.display = this.isEnabled ? 'flex' : 'none';
+ this.configContainer.style.flexDirection = 'column';
+ this.configContainer.style.gap = '20px';
+ this.configContainer.style.marginTop = '20px';
+ this.container.appendChild(this.configContainer);
// Client ID display (read-only)
- const clientIdLabel = document.createElement('div');
- clientIdLabel.className = 'settings-label';
- clientIdLabel.textContent = 'Client ID';
- evaluationConfigContainer.appendChild(clientIdLabel);
-
- const clientIdHint = document.createElement('div');
- clientIdHint.className = 'settings-hint';
- clientIdHint.textContent = 'Unique identifier for this DevTools instance';
- evaluationConfigContainer.appendChild(clientIdHint);
+ const clientIdGroup = this.createFieldGroup(
+ 'Client ID',
+ 'Unique identifier for this DevTools instance'
+ );
+ this.configContainer.appendChild(clientIdGroup.container);
const clientIdInput = document.createElement('input');
clientIdInput.type = 'text';
@@ -138,143 +94,155 @@ export class EvaluationSettings {
clientIdInput.readOnly = true;
clientIdInput.style.backgroundColor = 'var(--color-background-elevation-1)';
clientIdInput.style.cursor = 'default';
- evaluationConfigContainer.appendChild(clientIdInput);
+ clientIdGroup.container.appendChild(clientIdInput);
// Evaluation endpoint
- const evaluationEndpointLabel = document.createElement('div');
- evaluationEndpointLabel.className = 'settings-label';
- evaluationEndpointLabel.textContent = i18nString(UIStrings.evaluationEndpoint);
- evaluationConfigContainer.appendChild(evaluationEndpointLabel);
-
- const evaluationEndpointHint = document.createElement('div');
- evaluationEndpointHint.className = 'settings-hint';
- evaluationEndpointHint.textContent = i18nString(UIStrings.evaluationEndpointHint);
- evaluationConfigContainer.appendChild(evaluationEndpointHint);
+ const endpointGroup = this.createFieldGroup(
+ i18nString(UIStrings.evaluationEndpoint),
+ i18nString(UIStrings.evaluationEndpointHint)
+ );
+ this.configContainer.appendChild(endpointGroup.container);
this.evaluationEndpointInput = document.createElement('input');
this.evaluationEndpointInput.type = 'text';
this.evaluationEndpointInput.className = 'settings-input';
this.evaluationEndpointInput.placeholder = 'ws://localhost:8080';
this.evaluationEndpointInput.value = currentEvaluationConfig.endpoint || 'ws://localhost:8080';
- evaluationConfigContainer.appendChild(this.evaluationEndpointInput);
+ endpointGroup.container.appendChild(this.evaluationEndpointInput);
// Evaluation secret key
- const evaluationSecretKeyLabel = document.createElement('div');
- evaluationSecretKeyLabel.className = 'settings-label';
- evaluationSecretKeyLabel.textContent = i18nString(UIStrings.evaluationSecretKey);
- evaluationConfigContainer.appendChild(evaluationSecretKeyLabel);
-
- const evaluationSecretKeyHint = document.createElement('div');
- evaluationSecretKeyHint.className = 'settings-hint';
- evaluationSecretKeyHint.textContent = i18nString(UIStrings.evaluationSecretKeyHint);
- evaluationConfigContainer.appendChild(evaluationSecretKeyHint);
+ const secretKeyGroup = this.createFieldGroup(
+ i18nString(UIStrings.evaluationSecretKey),
+ i18nString(UIStrings.evaluationSecretKeyHint)
+ );
+ this.configContainer.appendChild(secretKeyGroup.container);
this.evaluationSecretKeyInput = document.createElement('input');
this.evaluationSecretKeyInput.type = 'password';
this.evaluationSecretKeyInput.className = 'settings-input';
- this.evaluationSecretKeyInput.placeholder = 'Optional secret key';
+ this.evaluationSecretKeyInput.placeholder = 'Evaluation secret key (optional)';
this.evaluationSecretKeyInput.value = currentEvaluationConfig.secretKey || '';
- evaluationConfigContainer.appendChild(this.evaluationSecretKeyInput);
-
- // Connection status message
- const connectionStatusMessage = document.createElement('div');
- connectionStatusMessage.className = 'settings-status';
- connectionStatusMessage.style.display = 'none';
- evaluationConfigContainer.appendChild(connectionStatusMessage);
-
- // Auto-connect when evaluation is enabled/disabled
- this.evaluationEnabledCheckbox.addEventListener('change', async () => {
- const isEnabled = this.evaluationEnabledCheckbox!.checked;
- evaluationConfigContainer.style.display = isEnabled ? 'block' : 'none';
-
- // Show connection status
- connectionStatusMessage.style.display = 'block';
-
- if (isEnabled) {
- // Auto-connect when enabled
- connectionStatusMessage.textContent = 'Connecting...';
- connectionStatusMessage.style.backgroundColor = 'var(--color-background-elevation-1)';
- connectionStatusMessage.style.color = 'var(--color-text-primary)';
-
- try {
- const endpoint = this.evaluationEndpointInput!.value.trim() || 'ws://localhost:8080';
- const secretKey = this.evaluationSecretKeyInput!.value.trim();
-
- // Update config and connect
- setEvaluationConfig({
- enabled: true,
- endpoint,
- secretKey
- });
-
- await connectToEvaluationService();
-
- // Update client ID display after connection
- const clientId = getEvaluationClientId();
- if (clientId) {
- clientIdInput.value = clientId;
- }
-
- connectionStatusMessage.textContent = '✓ Connected successfully';
- connectionStatusMessage.style.backgroundColor = 'var(--color-accent-green-background)';
- connectionStatusMessage.style.color = 'var(--color-accent-green)';
-
- // Update connection status indicator
- setTimeout(updateConnectionStatus, 500);
- } catch (error) {
- connectionStatusMessage.textContent = `✗ ${error instanceof Error ? error.message : 'Connection failed'}`;
- connectionStatusMessage.style.backgroundColor = 'var(--color-accent-red-background)';
- connectionStatusMessage.style.color = 'var(--color-accent-red)';
-
- // Uncheck the checkbox if connection failed
- this.evaluationEnabledCheckbox!.checked = false;
- evaluationConfigContainer.style.display = 'none';
+ secretKeyGroup.container.appendChild(this.evaluationSecretKeyInput);
+
+ // Footer with Test Connection button
+ const footer = document.createElement('div');
+ footer.className = 'settings-section-footer';
+ this.configContainer.appendChild(footer);
+
+ const testButton = document.createElement('button');
+ testButton.className = 'settings-button primary';
+ testButton.textContent = 'Test Connection';
+ testButton.addEventListener('click', () => this.testConnection(clientIdInput));
+ footer.appendChild(testButton);
+ }
+
+ private createFieldGroup(label: string, hint: string): { container: HTMLDivElement } {
+ const container = document.createElement('div');
+ container.style.display = 'flex';
+ container.style.flexDirection = 'column';
+ container.style.gap = '4px';
+
+ const labelEl = document.createElement('div');
+ labelEl.className = 'settings-label';
+ labelEl.textContent = label;
+ container.appendChild(labelEl);
+
+ const hintEl = document.createElement('div');
+ hintEl.className = 'settings-hint';
+ hintEl.textContent = hint;
+ container.appendChild(hintEl);
+
+ return { container };
+ }
+
+ private async handleToggle(): Promise
{
+ this.isEnabled = !this.isEnabled;
+
+ if (this.toggleElement) {
+ this.toggleElement.classList.toggle('active', this.isEnabled);
+ }
+
+ if (this.configContainer) {
+ this.configContainer.style.display = this.isEnabled ? 'flex' : 'none';
+ }
+
+ if (this.isEnabled) {
+ // Auto-connect when enabled
+ try {
+ const endpoint = this.evaluationEndpointInput?.value.trim() || 'ws://localhost:8080';
+ const secretKey = this.evaluationSecretKeyInput?.value.trim() || '';
+
+ setEvaluationConfig({
+ enabled: true,
+ endpoint,
+ secretKey
+ });
+
+ await connectToEvaluationService();
+ } catch (error) {
+ logger.error('Failed to connect to evaluation service', error);
+ // Revert toggle on failure
+ this.isEnabled = false;
+ if (this.toggleElement) {
+ this.toggleElement.classList.remove('active');
}
- } else {
- // Auto-disconnect when disabled
- connectionStatusMessage.textContent = 'Disconnecting...';
- connectionStatusMessage.style.backgroundColor = 'var(--color-background-elevation-1)';
- connectionStatusMessage.style.color = 'var(--color-text-primary)';
-
- try {
- disconnectFromEvaluationService();
-
- // Update config
- setEvaluationConfig({
- enabled: false,
- endpoint: this.evaluationEndpointInput!.value.trim() || 'ws://localhost:8080',
- secretKey: this.evaluationSecretKeyInput!.value.trim()
- });
-
- connectionStatusMessage.textContent = '✓ Disconnected';
- connectionStatusMessage.style.backgroundColor = 'var(--color-background-elevation-1)';
- connectionStatusMessage.style.color = 'var(--color-text-primary)';
-
- // Update connection status indicator
- updateConnectionStatus();
- } catch (error) {
- connectionStatusMessage.textContent = `✗ Disconnect error: ${error instanceof Error ? error.message : 'Unknown error'}`;
- connectionStatusMessage.style.backgroundColor = 'var(--color-accent-red-background)';
- connectionStatusMessage.style.color = 'var(--color-accent-red)';
+ if (this.configContainer) {
+ this.configContainer.style.display = 'none';
}
}
+ } else {
+ // Disconnect when disabled
+ try {
+ disconnectFromEvaluationService();
+ setEvaluationConfig({
+ enabled: false,
+ endpoint: this.evaluationEndpointInput?.value.trim() || 'ws://localhost:8080',
+ secretKey: this.evaluationSecretKeyInput?.value.trim() || ''
+ });
+ } catch (error) {
+ logger.error('Failed to disconnect from evaluation service', error);
+ }
+ }
+ }
- // Hide status message after 3 seconds
- setTimeout(() => {
- connectionStatusMessage.style.display = 'none';
- }, 3000);
- });
+ private async testConnection(clientIdInput: HTMLInputElement): Promise {
+ try {
+ const endpoint = this.evaluationEndpointInput?.value.trim() || 'ws://localhost:8080';
+ const secretKey = this.evaluationSecretKeyInput?.value.trim() || '';
+
+ setEvaluationConfig({
+ enabled: true,
+ endpoint,
+ secretKey
+ });
+
+ await connectToEvaluationService();
+
+ // Update client ID display after connection
+ const clientId = getEvaluationClientId();
+ if (clientId) {
+ clientIdInput.value = clientId;
+ }
+
+ // Update toggle state
+ this.isEnabled = true;
+ if (this.toggleElement) {
+ this.toggleElement.classList.add('active');
+ }
+
+ logger.info('Test connection successful');
+ } catch (error) {
+ logger.error('Test connection failed', error);
+ }
}
save(): void {
- // Evaluation settings are auto-saved on enable/disable toggle
- // Final save happens in the checkbox change handler
- if (!this.evaluationEnabledCheckbox || !this.evaluationEndpointInput || !this.evaluationSecretKeyInput) {
+ if (!this.evaluationEndpointInput || !this.evaluationSecretKeyInput) {
return;
}
setEvaluationConfig({
- enabled: this.evaluationEnabledCheckbox.checked,
+ enabled: this.isEnabled,
endpoint: this.evaluationEndpointInput.value.trim() || 'ws://localhost:8080',
secretKey: this.evaluationSecretKeyInput.value.trim()
});
diff --git a/front_end/panels/ai_chat/ui/settings/advanced/MCPSettings.ts b/front_end/panels/ai_chat/ui/settings/advanced/MCPSettings.ts
index cf59c7e913..f11b61e678 100644
--- a/front_end/panels/ai_chat/ui/settings/advanced/MCPSettings.ts
+++ b/front_end/panels/ai_chat/ui/settings/advanced/MCPSettings.ts
@@ -120,10 +120,10 @@ export class MCPSettings {
mcpDisconnectButton.style.backgroundColor = '#fee2e2';
mcpDisconnectButton.style.border = '1px solid #fecaca';
mcpDisconnectButton.style.color = '#dc2626';
- mcpDisconnectButton.style.padding = '6px 12px';
+ mcpDisconnectButton.style.padding = '8px 14px';
mcpDisconnectButton.style.borderRadius = '6px';
mcpDisconnectButton.style.cursor = 'pointer';
- mcpDisconnectButton.style.fontSize = '12px';
+ mcpDisconnectButton.style.fontSize = '14px';
mcpDisconnectButton.style.fontWeight = '500';
mcpDisconnectButton.addEventListener('click', async () => {
try {
@@ -143,10 +143,10 @@ export class MCPSettings {
mcpManageButton.style.backgroundColor = 'var(--color-background-elevation-1)';
mcpManageButton.style.border = '1px solid var(--color-details-hairline)';
mcpManageButton.style.color = 'var(--color-text-primary)';
- mcpManageButton.style.padding = '6px 12px';
+ mcpManageButton.style.padding = '8px 14px';
mcpManageButton.style.borderRadius = '6px';
mcpManageButton.style.cursor = 'pointer';
- mcpManageButton.style.fontSize = '12px';
+ mcpManageButton.style.fontSize = '14px';
mcpManageButton.style.fontWeight = '500';
mcpManageButton.addEventListener('click', () => {
this.onDialogHide();
@@ -161,10 +161,10 @@ export class MCPSettings {
mcpReconnectAllButton.style.backgroundColor = '#dbeafe';
mcpReconnectAllButton.style.border = '1px solid #bfdbfe';
mcpReconnectAllButton.style.color = '#1d4ed8';
- mcpReconnectAllButton.style.padding = '6px 12px';
+ mcpReconnectAllButton.style.padding = '8px 14px';
mcpReconnectAllButton.style.borderRadius = '6px';
mcpReconnectAllButton.style.cursor = 'pointer';
- mcpReconnectAllButton.style.fontSize = '12px';
+ mcpReconnectAllButton.style.fontSize = '14px';
mcpReconnectAllButton.style.fontWeight = '500';
mcpReconnectAllButton.addEventListener('click', async () => {
mcpReconnectAllButton.disabled = true;
diff --git a/front_end/panels/ai_chat/ui/settings/advanced/MemorySettings.ts b/front_end/panels/ai_chat/ui/settings/advanced/MemorySettings.ts
index fad66956a3..f8e20fdcb8 100644
--- a/front_end/panels/ai_chat/ui/settings/advanced/MemorySettings.ts
+++ b/front_end/panels/ai_chat/ui/settings/advanced/MemorySettings.ts
@@ -18,7 +18,8 @@ import type { FileSummary } from '../../../tools/FileStorageManager.js';
*/
export class MemorySettings {
private container: HTMLElement;
- private memoryEnabledCheckbox: HTMLInputElement | null = null;
+ private isEnabled: boolean = false;
+ private toggleElement: HTMLDivElement | null = null;
private blockListContainer: HTMLElement | null = null;
private blockManager: MemoryBlockManager;
private statusMessageElement: HTMLElement | null = null;
@@ -33,42 +34,43 @@ export class MemorySettings {
this.container.innerHTML = '';
this.container.className = 'settings-section memory-section';
+ // Get current state - default to enabled (true) if not set
+ const storedValue = localStorage.getItem(MEMORY_ENABLED_KEY);
+ this.isEnabled = storedValue !== 'false';
+
// Title
const memoryTitle = document.createElement('h3');
memoryTitle.textContent = i18nString(UIStrings.memoryLabel);
memoryTitle.classList.add('settings-subtitle');
this.container.appendChild(memoryTitle);
- // Memory enabled checkbox
- const memoryEnabledContainer = document.createElement('div');
- memoryEnabledContainer.className = 'tracing-enabled-container';
- this.container.appendChild(memoryEnabledContainer);
-
- this.memoryEnabledCheckbox = document.createElement('input');
- this.memoryEnabledCheckbox.type = 'checkbox';
- this.memoryEnabledCheckbox.id = 'memory-enabled';
- this.memoryEnabledCheckbox.className = 'tracing-checkbox';
- // Default to enabled (true) if not set
- const storedValue = localStorage.getItem(MEMORY_ENABLED_KEY);
- this.memoryEnabledCheckbox.checked = storedValue !== 'false';
- memoryEnabledContainer.appendChild(this.memoryEnabledCheckbox);
-
- const memoryEnabledLabel = document.createElement('label');
- memoryEnabledLabel.htmlFor = 'memory-enabled';
- memoryEnabledLabel.className = 'tracing-label';
- memoryEnabledLabel.textContent = i18nString(UIStrings.memoryEnabled);
- memoryEnabledContainer.appendChild(memoryEnabledLabel);
-
- const memoryEnabledHint = document.createElement('div');
- memoryEnabledHint.className = 'settings-hint';
- memoryEnabledHint.textContent = i18nString(UIStrings.memoryEnabledHint);
- this.container.appendChild(memoryEnabledHint);
-
- // Toggle memory and save to localStorage
- this.memoryEnabledCheckbox.addEventListener('change', () => {
- localStorage.setItem(MEMORY_ENABLED_KEY, this.memoryEnabledCheckbox!.checked.toString());
- this.updateBlockListVisibility();
- });
+ // Header with toggle
+ const headerContainer = document.createElement('div');
+ headerContainer.className = 'settings-toggle-container';
+ this.container.appendChild(headerContainer);
+
+ const infoContainer = document.createElement('div');
+ infoContainer.className = 'settings-toggle-info';
+ headerContainer.appendChild(infoContainer);
+
+ const title = document.createElement('div');
+ title.className = 'settings-toggle-title';
+ title.textContent = i18nString(UIStrings.memoryEnabled);
+ infoContainer.appendChild(title);
+
+ const description = document.createElement('div');
+ description.className = 'settings-toggle-description';
+ description.textContent = i18nString(UIStrings.memoryEnabledHint);
+ infoContainer.appendChild(description);
+
+ // Toggle switch
+ this.toggleElement = document.createElement('div');
+ this.toggleElement.className = 'settings-toggle';
+ if (this.isEnabled) {
+ this.toggleElement.classList.add('active');
+ }
+ this.toggleElement.addEventListener('click', () => this.handleToggle());
+ headerContainer.appendChild(this.toggleElement);
// Memory blocks list container
this.blockListContainer = document.createElement('div');
@@ -80,12 +82,26 @@ export class MemorySettings {
this.renderMemoryBlocks();
}
+ /**
+ * Handle toggle click
+ */
+ private handleToggle(): void {
+ this.isEnabled = !this.isEnabled;
+
+ if (this.toggleElement) {
+ this.toggleElement.classList.toggle('active', this.isEnabled);
+ }
+
+ localStorage.setItem(MEMORY_ENABLED_KEY, this.isEnabled.toString());
+ this.updateBlockListVisibility();
+ }
+
/**
* Update visibility of block list based on memory enabled state
*/
private updateBlockListVisibility(): void {
- if (this.blockListContainer && this.memoryEnabledCheckbox) {
- this.blockListContainer.style.display = this.memoryEnabledCheckbox.checked ? 'block' : 'none';
+ if (this.blockListContainer) {
+ this.blockListContainer.style.display = this.isEnabled ? 'block' : 'none';
}
}
@@ -112,14 +128,39 @@ export class MemorySettings {
this.blockListContainer.appendChild(this.statusMessageElement);
try {
- const blocks = await this.blockManager.getAllBlocks();
+ let blocks = await this.blockManager.getAllBlocks();
+ // Add dummy data for testing if no blocks exist
if (blocks.length === 0) {
- const emptyMessage = document.createElement('div');
- emptyMessage.className = 'memory-blocks-empty';
- emptyMessage.textContent = 'No memory blocks stored yet. Memory will be extracted from conversations automatically.';
- this.blockListContainer.appendChild(emptyMessage);
- return;
+ blocks = [
+ {
+ filename: 'memory_user.md',
+ type: 'user' as const,
+ label: 'User Preferences',
+ description: 'Personal preferences and settings',
+ content: '# User Preferences\n\n- Prefers dark mode\n- Uses TypeScript for all projects\n- Likes concise code comments',
+ charLimit: 10000,
+ updatedAt: Date.now() - 86400000, // yesterday
+ },
+ {
+ filename: 'memory_facts.md',
+ type: 'facts' as const,
+ label: 'Session Facts',
+ description: 'Facts learned from conversations',
+ content: '# Session Facts\n\n- Working on Browser Operator project\n- Uses React and Lit for UI components\n- DevTools frontend codebase',
+ charLimit: 10000,
+ updatedAt: Date.now() - 3600000, // 1 hour ago
+ },
+ {
+ filename: 'memory_project_browser-operator.md',
+ type: 'project' as const,
+ label: 'Browser Operator Project',
+ description: 'Project-specific memory',
+ content: '# Browser Operator\n\nAI-native browser with multi-agent framework.\n\n## Key Files\n- SettingsDialog.ts\n- MemorySettings.ts',
+ charLimit: 10000,
+ updatedAt: Date.now(), // now
+ },
+ ];
}
// Create block list
diff --git a/front_end/panels/ai_chat/ui/settings/advanced/TracingSettings.ts b/front_end/panels/ai_chat/ui/settings/advanced/TracingSettings.ts
index a9ffd5bd70..ff4f82affc 100644
--- a/front_end/panels/ai_chat/ui/settings/advanced/TracingSettings.ts
+++ b/front_end/panels/ai_chat/ui/settings/advanced/TracingSettings.ts
@@ -12,7 +12,9 @@ import { getTracingConfig, setTracingConfig, isTracingEnabled } from '../../../t
*/
export class TracingSettings {
private container: HTMLElement;
- private tracingEnabledCheckbox: HTMLInputElement | null = null;
+ private isEnabled: boolean = false;
+ private toggleElement: HTMLDivElement | null = null;
+ private configContainer: HTMLDivElement | null = null;
private endpointInput: HTMLInputElement | null = null;
private publicKeyInput: HTMLInputElement | null = null;
private secretKeyInput: HTMLInputElement | null = null;
@@ -26,182 +28,203 @@ export class TracingSettings {
this.container.innerHTML = '';
this.container.className = 'settings-section tracing-section';
- // Title
- const tracingSectionTitle = document.createElement('h3');
- tracingSectionTitle.className = 'settings-subtitle';
- tracingSectionTitle.textContent = i18nString(UIStrings.tracingSection);
- this.container.appendChild(tracingSectionTitle);
-
// Get current tracing configuration
const currentTracingConfig = getTracingConfig();
-
- // Tracing enabled checkbox
- const tracingEnabledContainer = document.createElement('div');
- tracingEnabledContainer.className = 'tracing-enabled-container';
- this.container.appendChild(tracingEnabledContainer);
-
- this.tracingEnabledCheckbox = document.createElement('input');
- this.tracingEnabledCheckbox.type = 'checkbox';
- this.tracingEnabledCheckbox.id = 'tracing-enabled';
- this.tracingEnabledCheckbox.className = 'tracing-checkbox';
- this.tracingEnabledCheckbox.checked = isTracingEnabled();
- tracingEnabledContainer.appendChild(this.tracingEnabledCheckbox);
-
- const tracingEnabledLabel = document.createElement('label');
- tracingEnabledLabel.htmlFor = 'tracing-enabled';
- tracingEnabledLabel.className = 'tracing-label';
- tracingEnabledLabel.textContent = i18nString(UIStrings.tracingEnabled);
- tracingEnabledContainer.appendChild(tracingEnabledLabel);
-
- const tracingEnabledHint = document.createElement('div');
- tracingEnabledHint.className = 'settings-hint';
- tracingEnabledHint.textContent = i18nString(UIStrings.tracingEnabledHint);
- this.container.appendChild(tracingEnabledHint);
-
- // Tracing configuration container (shown when enabled)
- const tracingConfigContainer = document.createElement('div');
- tracingConfigContainer.className = 'tracing-config-container';
- tracingConfigContainer.style.display = this.tracingEnabledCheckbox.checked ? 'block' : 'none';
- this.container.appendChild(tracingConfigContainer);
+ this.isEnabled = isTracingEnabled();
+
+ // Header with toggle
+ const headerContainer = document.createElement('div');
+ headerContainer.className = 'settings-toggle-container';
+ this.container.appendChild(headerContainer);
+
+ const infoContainer = document.createElement('div');
+ infoContainer.className = 'settings-toggle-info';
+ headerContainer.appendChild(infoContainer);
+
+ const title = document.createElement('div');
+ title.className = 'settings-toggle-title';
+ title.textContent = i18nString(UIStrings.tracingSection);
+ infoContainer.appendChild(title);
+
+ const description = document.createElement('div');
+ description.className = 'settings-toggle-description';
+ description.textContent = i18nString(UIStrings.tracingEnabledHint);
+ infoContainer.appendChild(description);
+
+ // Toggle switch
+ this.toggleElement = document.createElement('div');
+ this.toggleElement.className = 'settings-toggle';
+ if (this.isEnabled) {
+ this.toggleElement.classList.add('active');
+ }
+ this.toggleElement.addEventListener('click', () => this.handleToggle());
+ headerContainer.appendChild(this.toggleElement);
+
+ // Configuration container (shown when enabled)
+ this.configContainer = document.createElement('div');
+ this.configContainer.className = 'tracing-config-container';
+ this.configContainer.style.display = this.isEnabled ? 'flex' : 'none';
+ this.configContainer.style.flexDirection = 'column';
+ this.configContainer.style.gap = '20px';
+ this.configContainer.style.marginTop = '20px';
+ this.container.appendChild(this.configContainer);
// Langfuse endpoint
- const endpointLabel = document.createElement('div');
- endpointLabel.className = 'settings-label';
- endpointLabel.textContent = i18nString(UIStrings.langfuseEndpoint);
- tracingConfigContainer.appendChild(endpointLabel);
-
- const endpointHint = document.createElement('div');
- endpointHint.className = 'settings-hint';
- endpointHint.textContent = i18nString(UIStrings.langfuseEndpointHint);
- tracingConfigContainer.appendChild(endpointHint);
+ const endpointGroup = this.createFieldGroup(
+ i18nString(UIStrings.langfuseEndpoint),
+ i18nString(UIStrings.langfuseEndpointHint)
+ );
+ this.configContainer.appendChild(endpointGroup.container);
this.endpointInput = document.createElement('input');
this.endpointInput.className = 'settings-input';
this.endpointInput.type = 'text';
- this.endpointInput.placeholder = 'http://localhost:3000';
- this.endpointInput.value = currentTracingConfig.endpoint || 'http://localhost:3000';
- tracingConfigContainer.appendChild(this.endpointInput);
+ this.endpointInput.placeholder = 'Enter URL';
+ this.endpointInput.value = currentTracingConfig.endpoint || '';
+ endpointGroup.container.appendChild(this.endpointInput);
// Langfuse public key
- const publicKeyLabel = document.createElement('div');
- publicKeyLabel.className = 'settings-label';
- publicKeyLabel.textContent = i18nString(UIStrings.langfusePublicKey);
- tracingConfigContainer.appendChild(publicKeyLabel);
-
- const publicKeyHint = document.createElement('div');
- publicKeyHint.className = 'settings-hint';
- publicKeyHint.textContent = i18nString(UIStrings.langfusePublicKeyHint);
- tracingConfigContainer.appendChild(publicKeyHint);
+ const publicKeyGroup = this.createFieldGroup(
+ i18nString(UIStrings.langfusePublicKey),
+ i18nString(UIStrings.langfusePublicKeyHint)
+ );
+ this.configContainer.appendChild(publicKeyGroup.container);
this.publicKeyInput = document.createElement('input');
this.publicKeyInput.className = 'settings-input';
this.publicKeyInput.type = 'text';
- this.publicKeyInput.placeholder = 'pk-lf-...';
+ this.publicKeyInput.placeholder = 'Enter public key';
this.publicKeyInput.value = currentTracingConfig.publicKey || '';
- tracingConfigContainer.appendChild(this.publicKeyInput);
+ publicKeyGroup.container.appendChild(this.publicKeyInput);
// Langfuse secret key
- const secretKeyLabel = document.createElement('div');
- secretKeyLabel.className = 'settings-label';
- secretKeyLabel.textContent = i18nString(UIStrings.langfuseSecretKey);
- tracingConfigContainer.appendChild(secretKeyLabel);
-
- const secretKeyHint = document.createElement('div');
- secretKeyHint.className = 'settings-hint';
- secretKeyHint.textContent = i18nString(UIStrings.langfuseSecretKeyHint);
- tracingConfigContainer.appendChild(secretKeyHint);
+ const secretKeyGroup = this.createFieldGroup(
+ i18nString(UIStrings.langfuseSecretKey),
+ i18nString(UIStrings.langfuseSecretKeyHint)
+ );
+ this.configContainer.appendChild(secretKeyGroup.container);
this.secretKeyInput = document.createElement('input');
this.secretKeyInput.className = 'settings-input';
this.secretKeyInput.type = 'password';
- this.secretKeyInput.placeholder = 'sk-lf-...';
+ this.secretKeyInput.placeholder = 'Enter secret key';
this.secretKeyInput.value = currentTracingConfig.secretKey || '';
- tracingConfigContainer.appendChild(this.secretKeyInput);
-
- // Test connection button
- const testTracingButton = document.createElement('button');
- testTracingButton.className = 'settings-button test-button';
- testTracingButton.textContent = i18nString(UIStrings.testTracing);
- tracingConfigContainer.appendChild(testTracingButton);
-
- // Test status message
- const testTracingStatus = document.createElement('div');
- testTracingStatus.className = 'settings-status';
- testTracingStatus.style.display = 'none';
- tracingConfigContainer.appendChild(testTracingStatus);
-
- // Toggle tracing config visibility
- this.tracingEnabledCheckbox.addEventListener('change', () => {
- tracingConfigContainer.style.display = this.tracingEnabledCheckbox!.checked ? 'block' : 'none';
- });
-
- // Test tracing connection
- testTracingButton.addEventListener('click', async () => {
- testTracingButton.disabled = true;
- testTracingStatus.style.display = 'block';
- testTracingStatus.textContent = 'Testing connection...';
- testTracingStatus.style.backgroundColor = 'var(--color-background-elevation-1)';
- testTracingStatus.style.color = 'var(--color-text-primary)';
-
- try {
- const endpoint = this.endpointInput!.value.trim();
- const publicKey = this.publicKeyInput!.value.trim();
- const secretKey = this.secretKeyInput!.value.trim();
-
- if (!endpoint || !publicKey || !secretKey) {
- throw new Error('All fields are required for testing');
- }
+ secretKeyGroup.container.appendChild(this.secretKeyInput);
+
+ // Footer with Test Connection button
+ const footer = document.createElement('div');
+ footer.className = 'settings-section-footer';
+ this.configContainer.appendChild(footer);
+
+ const testButton = document.createElement('button');
+ testButton.className = 'settings-button primary';
+ testButton.textContent = 'Test Connection';
+ testButton.addEventListener('click', () => this.testConnection(testButton));
+ footer.appendChild(testButton);
+ }
- // Test the connection with a simple trace
- const testPayload = {
- batch: [{
- id: `test-${Date.now()}`,
- timestamp: new Date().toISOString(),
- type: 'trace-create',
- body: {
- id: `trace-test-${Date.now()}`,
- name: 'Connection Test',
- timestamp: new Date().toISOString()
- }
- }]
- };
-
- const response = await fetch(`${endpoint}/api/public/ingestion`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'Authorization': 'Basic ' + btoa(`${publicKey}:${secretKey}`)
- },
- body: JSON.stringify(testPayload)
- });
+ private createFieldGroup(label: string, hint: string): { container: HTMLDivElement } {
+ const container = document.createElement('div');
+ container.style.display = 'flex';
+ container.style.flexDirection = 'column';
+ container.style.gap = '4px';
+
+ const labelEl = document.createElement('div');
+ labelEl.className = 'settings-label';
+ labelEl.textContent = label;
+ container.appendChild(labelEl);
+
+ const hintEl = document.createElement('div');
+ hintEl.className = 'settings-hint';
+ hintEl.textContent = hint;
+ container.appendChild(hintEl);
+
+ return { container };
+ }
+
+ private handleToggle(): void {
+ this.isEnabled = !this.isEnabled;
- if (response.ok) {
- testTracingStatus.textContent = '✓ Connection successful';
- testTracingStatus.style.backgroundColor = 'var(--color-accent-green-background)';
- testTracingStatus.style.color = 'var(--color-accent-green)';
- } else {
- const errorText = await response.text();
- throw new Error(`HTTP ${response.status}: ${errorText}`);
+ if (this.toggleElement) {
+ this.toggleElement.classList.toggle('active', this.isEnabled);
+ }
+
+ if (this.configContainer) {
+ this.configContainer.style.display = this.isEnabled ? 'flex' : 'none';
+ }
+
+ // Auto-save toggle state
+ if (!this.isEnabled) {
+ setTracingConfig({ provider: 'disabled' });
+ }
+ }
+
+ private async testConnection(testButton: HTMLButtonElement): Promise {
+ testButton.disabled = true;
+ testButton.textContent = 'Testing...';
+
+ try {
+ const endpoint = this.endpointInput?.value.trim() || '';
+ const publicKey = this.publicKeyInput?.value.trim() || '';
+ const secretKey = this.secretKeyInput?.value.trim() || '';
+
+ if (!endpoint || !publicKey || !secretKey) {
+ throw new Error('All fields are required for testing');
+ }
+
+ // Test the connection with a simple trace
+ const testPayload = {
+ batch: [{
+ id: `test-${Date.now()}`,
+ timestamp: new Date().toISOString(),
+ type: 'trace-create',
+ body: {
+ id: `trace-test-${Date.now()}`,
+ name: 'Connection Test',
+ timestamp: new Date().toISOString()
+ }
+ }]
+ };
+
+ const response = await fetch(`${endpoint}/api/public/ingestion`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Authorization': 'Basic ' + btoa(`${publicKey}:${secretKey}`)
+ },
+ body: JSON.stringify(testPayload)
+ });
+
+ if (response.ok) {
+ testButton.textContent = 'Connected!';
+ // Enable toggle if not already
+ if (!this.isEnabled) {
+ this.isEnabled = true;
+ if (this.toggleElement) {
+ this.toggleElement.classList.add('active');
+ }
}
- } catch (error) {
- testTracingStatus.textContent = `✗ ${error instanceof Error ? error.message : 'Connection failed'}`;
- testTracingStatus.style.backgroundColor = 'var(--color-accent-red-background)';
- testTracingStatus.style.color = 'var(--color-accent-red)';
- } finally {
- testTracingButton.disabled = false;
- setTimeout(() => {
- testTracingStatus.style.display = 'none';
- }, 5000);
+ } else {
+ const errorText = await response.text();
+ throw new Error(`HTTP ${response.status}: ${errorText}`);
}
- });
+ } catch (error) {
+ testButton.textContent = 'Failed';
+ console.error('Tracing test failed:', error);
+ } finally {
+ setTimeout(() => {
+ testButton.disabled = false;
+ testButton.textContent = 'Test Connection';
+ }, 2000);
+ }
}
save(): void {
- if (!this.tracingEnabledCheckbox || !this.endpointInput || !this.publicKeyInput || !this.secretKeyInput) {
+ if (!this.endpointInput || !this.publicKeyInput || !this.secretKeyInput) {
return;
}
- if (this.tracingEnabledCheckbox.checked) {
+ if (this.isEnabled) {
const endpoint = this.endpointInput.value.trim();
const publicKey = this.publicKeyInput.value.trim();
const secretKey = this.secretKeyInput.value.trim();
diff --git a/front_end/panels/ai_chat/ui/settings/advanced/VectorDBSettings.ts b/front_end/panels/ai_chat/ui/settings/advanced/VectorDBSettings.ts
index 104a6c28a6..7dd9b743d1 100644
--- a/front_end/panels/ai_chat/ui/settings/advanced/VectorDBSettings.ts
+++ b/front_end/panels/ai_chat/ui/settings/advanced/VectorDBSettings.ts
@@ -19,7 +19,9 @@ import {
*/
export class VectorDBSettings {
private container: HTMLElement;
- private vectorDBEnabledCheckbox: HTMLInputElement | null = null;
+ private isEnabled: boolean = false;
+ private toggleElement: HTMLDivElement | null = null;
+ private configContainer: HTMLDivElement | null = null;
private vectorDBEndpointInput: HTMLInputElement | null = null;
private vectorDBApiKeyInput: HTMLInputElement | null = null;
private milvusPasswordInput: HTMLInputElement | null = null;
@@ -35,253 +37,238 @@ export class VectorDBSettings {
this.container.innerHTML = '';
this.container.className = 'settings-section vector-db-section';
- // Title
- const vectorDBTitle = document.createElement('h3');
- vectorDBTitle.textContent = i18nString(UIStrings.vectorDBLabel);
- vectorDBTitle.classList.add('settings-subtitle');
- this.container.appendChild(vectorDBTitle);
-
- // Vector DB enabled checkbox
- const vectorDBEnabledContainer = document.createElement('div');
- vectorDBEnabledContainer.className = 'tracing-enabled-container';
- this.container.appendChild(vectorDBEnabledContainer);
-
- this.vectorDBEnabledCheckbox = document.createElement('input');
- this.vectorDBEnabledCheckbox.type = 'checkbox';
- this.vectorDBEnabledCheckbox.id = 'vector-db-enabled';
- this.vectorDBEnabledCheckbox.className = 'tracing-checkbox';
- this.vectorDBEnabledCheckbox.checked = localStorage.getItem(VECTOR_DB_ENABLED_KEY) === 'true';
- vectorDBEnabledContainer.appendChild(this.vectorDBEnabledCheckbox);
-
- const vectorDBEnabledLabel = document.createElement('label');
- vectorDBEnabledLabel.htmlFor = 'vector-db-enabled';
- vectorDBEnabledLabel.className = 'tracing-label';
- vectorDBEnabledLabel.textContent = i18nString(UIStrings.vectorDBEnabled);
- vectorDBEnabledContainer.appendChild(vectorDBEnabledLabel);
-
- const vectorDBEnabledHint = document.createElement('div');
- vectorDBEnabledHint.className = 'settings-hint';
- vectorDBEnabledHint.textContent = i18nString(UIStrings.vectorDBEnabledHint);
- this.container.appendChild(vectorDBEnabledHint);
-
- // Vector DB configuration container (shown when enabled)
- const vectorDBConfigContainer = document.createElement('div');
- vectorDBConfigContainer.className = 'tracing-config-container';
- vectorDBConfigContainer.style.display = this.vectorDBEnabledCheckbox.checked ? 'block' : 'none';
- this.container.appendChild(vectorDBConfigContainer);
-
- // Vector DB Endpoint
- const vectorDBEndpointDiv = document.createElement('div');
- vectorDBEndpointDiv.classList.add('settings-field');
- vectorDBConfigContainer.appendChild(vectorDBEndpointDiv);
-
- const vectorDBEndpointLabel = document.createElement('label');
- vectorDBEndpointLabel.textContent = i18nString(UIStrings.vectorDBEndpoint);
- vectorDBEndpointLabel.classList.add('settings-label');
- vectorDBEndpointDiv.appendChild(vectorDBEndpointLabel);
-
- const vectorDBEndpointHint = document.createElement('div');
- vectorDBEndpointHint.textContent = i18nString(UIStrings.vectorDBEndpointHint);
- vectorDBEndpointHint.classList.add('settings-hint');
- vectorDBEndpointDiv.appendChild(vectorDBEndpointHint);
+ this.isEnabled = localStorage.getItem(VECTOR_DB_ENABLED_KEY) === 'true';
+
+ // Header with toggle
+ const headerContainer = document.createElement('div');
+ headerContainer.className = 'settings-toggle-container';
+ this.container.appendChild(headerContainer);
+
+ const infoContainer = document.createElement('div');
+ infoContainer.className = 'settings-toggle-info';
+ headerContainer.appendChild(infoContainer);
+
+ const title = document.createElement('div');
+ title.className = 'settings-toggle-title';
+ title.textContent = i18nString(UIStrings.vectorDBLabel);
+ infoContainer.appendChild(title);
+
+ const description = document.createElement('div');
+ description.className = 'settings-toggle-description';
+ description.textContent = i18nString(UIStrings.vectorDBEnabledHint);
+ infoContainer.appendChild(description);
+
+ // Toggle switch
+ this.toggleElement = document.createElement('div');
+ this.toggleElement.className = 'settings-toggle';
+ if (this.isEnabled) {
+ this.toggleElement.classList.add('active');
+ }
+ this.toggleElement.addEventListener('click', () => this.handleToggle());
+ headerContainer.appendChild(this.toggleElement);
+
+ // Configuration container (shown when enabled)
+ this.configContainer = document.createElement('div');
+ this.configContainer.className = 'vector-db-config-container';
+ this.configContainer.style.display = this.isEnabled ? 'flex' : 'none';
+ this.configContainer.style.flexDirection = 'column';
+ this.configContainer.style.gap = '20px';
+ this.configContainer.style.marginTop = '20px';
+ this.container.appendChild(this.configContainer);
+
+ // Milvus Endpoint
+ const endpointGroup = this.createFieldGroup(
+ i18nString(UIStrings.vectorDBEndpoint),
+ i18nString(UIStrings.vectorDBEndpointHint)
+ );
+ this.configContainer.appendChild(endpointGroup.container);
this.vectorDBEndpointInput = document.createElement('input');
this.vectorDBEndpointInput.classList.add('settings-input');
this.vectorDBEndpointInput.type = 'text';
- this.vectorDBEndpointInput.placeholder = 'http://localhost:19530';
+ this.vectorDBEndpointInput.placeholder = 'Enter URL';
this.vectorDBEndpointInput.value = localStorage.getItem(MILVUS_ENDPOINT_KEY) || '';
- vectorDBEndpointDiv.appendChild(this.vectorDBEndpointInput);
+ this.vectorDBEndpointInput.addEventListener('input', () => this.saveSettings());
+ endpointGroup.container.appendChild(this.vectorDBEndpointInput);
- // Vector DB API Key (Username)
- const vectorDBApiKeyDiv = document.createElement('div');
- vectorDBApiKeyDiv.classList.add('settings-field');
- vectorDBConfigContainer.appendChild(vectorDBApiKeyDiv);
-
- const vectorDBApiKeyLabel = document.createElement('label');
- vectorDBApiKeyLabel.textContent = i18nString(UIStrings.vectorDBApiKey);
- vectorDBApiKeyLabel.classList.add('settings-label');
- vectorDBApiKeyDiv.appendChild(vectorDBApiKeyLabel);
-
- const vectorDBApiKeyHint = document.createElement('div');
- vectorDBApiKeyHint.textContent = i18nString(UIStrings.vectorDBApiKeyHint);
- vectorDBApiKeyHint.classList.add('settings-hint');
- vectorDBApiKeyDiv.appendChild(vectorDBApiKeyHint);
+ // Milvus Username
+ const usernameGroup = this.createFieldGroup(
+ i18nString(UIStrings.vectorDBApiKey),
+ i18nString(UIStrings.vectorDBApiKeyHint)
+ );
+ this.configContainer.appendChild(usernameGroup.container);
this.vectorDBApiKeyInput = document.createElement('input');
this.vectorDBApiKeyInput.classList.add('settings-input');
this.vectorDBApiKeyInput.type = 'text';
- this.vectorDBApiKeyInput.placeholder = 'root';
+ this.vectorDBApiKeyInput.placeholder = 'Enter username';
this.vectorDBApiKeyInput.value = localStorage.getItem(MILVUS_USERNAME_KEY) || 'root';
- vectorDBApiKeyDiv.appendChild(this.vectorDBApiKeyInput);
-
- // Milvus Password
- const milvusPasswordDiv = document.createElement('div');
- milvusPasswordDiv.classList.add('settings-field');
- vectorDBConfigContainer.appendChild(milvusPasswordDiv);
+ this.vectorDBApiKeyInput.addEventListener('input', () => this.saveSettings());
+ usernameGroup.container.appendChild(this.vectorDBApiKeyInput);
- const milvusPasswordLabel = document.createElement('label');
- milvusPasswordLabel.textContent = i18nString(UIStrings.milvusPassword);
- milvusPasswordLabel.classList.add('settings-label');
- milvusPasswordDiv.appendChild(milvusPasswordLabel);
-
- const milvusPasswordHint = document.createElement('div');
- milvusPasswordHint.textContent = i18nString(UIStrings.milvusPasswordHint);
- milvusPasswordHint.classList.add('settings-hint');
- milvusPasswordDiv.appendChild(milvusPasswordHint);
+ // Milvus Password / API Token
+ const passwordGroup = this.createFieldGroup(
+ i18nString(UIStrings.milvusPassword),
+ i18nString(UIStrings.milvusPasswordHint)
+ );
+ this.configContainer.appendChild(passwordGroup.container);
this.milvusPasswordInput = document.createElement('input');
this.milvusPasswordInput.classList.add('settings-input');
this.milvusPasswordInput.type = 'password';
- this.milvusPasswordInput.placeholder = 'Milvus (self-hosted) or API token (cloud)';
- this.milvusPasswordInput.value = localStorage.getItem(MILVUS_PASSWORD_KEY) || 'Milvus';
- milvusPasswordDiv.appendChild(this.milvusPasswordInput);
+ this.milvusPasswordInput.placeholder = 'Enter password / api token';
+ this.milvusPasswordInput.value = localStorage.getItem(MILVUS_PASSWORD_KEY) || '';
+ this.milvusPasswordInput.addEventListener('input', () => this.saveSettings());
+ passwordGroup.container.appendChild(this.milvusPasswordInput);
// OpenAI API Key for embeddings
- const milvusOpenAIDiv = document.createElement('div');
- milvusOpenAIDiv.classList.add('settings-field');
- vectorDBConfigContainer.appendChild(milvusOpenAIDiv);
-
- const milvusOpenAILabel = document.createElement('label');
- milvusOpenAILabel.textContent = i18nString(UIStrings.milvusOpenAIKey);
- milvusOpenAILabel.classList.add('settings-label');
- milvusOpenAIDiv.appendChild(milvusOpenAILabel);
-
- const milvusOpenAIHint = document.createElement('div');
- milvusOpenAIHint.textContent = i18nString(UIStrings.milvusOpenAIKeyHint);
- milvusOpenAIHint.classList.add('settings-hint');
- milvusOpenAIDiv.appendChild(milvusOpenAIHint);
+ const openaiGroup = this.createFieldGroup(
+ i18nString(UIStrings.milvusOpenAIKey),
+ i18nString(UIStrings.milvusOpenAIKeyHint)
+ );
+ this.configContainer.appendChild(openaiGroup.container);
this.milvusOpenAIInput = document.createElement('input');
this.milvusOpenAIInput.classList.add('settings-input');
this.milvusOpenAIInput.type = 'password';
- this.milvusOpenAIInput.placeholder = 'sk-...';
+ this.milvusOpenAIInput.placeholder = 'Enter api key';
this.milvusOpenAIInput.value = localStorage.getItem(MILVUS_OPENAI_KEY) || '';
- milvusOpenAIDiv.appendChild(this.milvusOpenAIInput);
+ this.milvusOpenAIInput.addEventListener('input', () => this.saveSettings());
+ openaiGroup.container.appendChild(this.milvusOpenAIInput);
- // Vector DB Collection Name
- const vectorDBCollectionDiv = document.createElement('div');
- vectorDBCollectionDiv.classList.add('settings-field');
- vectorDBConfigContainer.appendChild(vectorDBCollectionDiv);
-
- const vectorDBCollectionLabel = document.createElement('label');
- vectorDBCollectionLabel.textContent = i18nString(UIStrings.vectorDBCollection);
- vectorDBCollectionLabel.classList.add('settings-label');
- vectorDBCollectionDiv.appendChild(vectorDBCollectionLabel);
-
- const vectorDBCollectionHint = document.createElement('div');
- vectorDBCollectionHint.textContent = i18nString(UIStrings.vectorDBCollectionHint);
- vectorDBCollectionHint.classList.add('settings-hint');
- vectorDBCollectionDiv.appendChild(vectorDBCollectionHint);
+ // Collection Name
+ const collectionGroup = this.createFieldGroup(
+ i18nString(UIStrings.vectorDBCollection),
+ i18nString(UIStrings.vectorDBCollectionHint)
+ );
+ this.configContainer.appendChild(collectionGroup.container);
this.vectorDBCollectionInput = document.createElement('input');
this.vectorDBCollectionInput.classList.add('settings-input');
this.vectorDBCollectionInput.type = 'text';
- this.vectorDBCollectionInput.placeholder = 'bookmarks';
+ this.vectorDBCollectionInput.placeholder = 'Enter collection name';
this.vectorDBCollectionInput.value = localStorage.getItem(MILVUS_COLLECTION_KEY) || 'bookmarks';
- vectorDBCollectionDiv.appendChild(this.vectorDBCollectionInput);
-
- // Test Vector DB Connection Button
- const vectorDBTestDiv = document.createElement('div');
- vectorDBTestDiv.classList.add('settings-field', 'test-connection-field');
- vectorDBConfigContainer.appendChild(vectorDBTestDiv);
-
- const vectorDBTestButton = document.createElement('button');
- vectorDBTestButton.classList.add('settings-button', 'test-button');
- vectorDBTestButton.setAttribute('type', 'button');
- vectorDBTestButton.textContent = i18nString(UIStrings.testVectorDBConnection);
- vectorDBTestDiv.appendChild(vectorDBTestButton);
-
- const vectorDBTestStatus = document.createElement('div');
- vectorDBTestStatus.classList.add('settings-status');
- vectorDBTestStatus.style.display = 'none';
- vectorDBTestDiv.appendChild(vectorDBTestStatus);
-
- // Toggle vector DB config visibility
- this.vectorDBEnabledCheckbox.addEventListener('change', () => {
- vectorDBConfigContainer.style.display = this.vectorDBEnabledCheckbox!.checked ? 'block' : 'none';
- localStorage.setItem(VECTOR_DB_ENABLED_KEY, this.vectorDBEnabledCheckbox!.checked.toString());
- });
-
- // Save Vector DB settings on input change
- const saveVectorDBSettings = () => {
- if (!this.vectorDBEnabledCheckbox || !this.vectorDBEndpointInput || !this.vectorDBApiKeyInput ||
- !this.milvusPasswordInput || !this.vectorDBCollectionInput || !this.milvusOpenAIInput) {
- return;
- }
+ this.vectorDBCollectionInput.addEventListener('input', () => this.saveSettings());
+ collectionGroup.container.appendChild(this.vectorDBCollectionInput);
+
+ // Footer with Test Connection button
+ const footer = document.createElement('div');
+ footer.className = 'settings-section-footer';
+ this.configContainer.appendChild(footer);
+
+ const testButton = document.createElement('button');
+ testButton.className = 'settings-button primary';
+ testButton.textContent = 'Test Connection';
+ testButton.addEventListener('click', () => this.testConnection(testButton));
+ footer.appendChild(testButton);
+ }
- localStorage.setItem(VECTOR_DB_ENABLED_KEY, this.vectorDBEnabledCheckbox.checked.toString());
- localStorage.setItem(MILVUS_ENDPOINT_KEY, this.vectorDBEndpointInput.value);
- localStorage.setItem(MILVUS_USERNAME_KEY, this.vectorDBApiKeyInput.value);
- localStorage.setItem(MILVUS_PASSWORD_KEY, this.milvusPasswordInput.value);
- localStorage.setItem(MILVUS_COLLECTION_KEY, this.vectorDBCollectionInput.value);
- localStorage.setItem(MILVUS_OPENAI_KEY, this.milvusOpenAIInput.value);
- };
-
- this.vectorDBEndpointInput.addEventListener('input', saveVectorDBSettings);
- this.vectorDBApiKeyInput.addEventListener('input', saveVectorDBSettings);
- this.milvusPasswordInput.addEventListener('input', saveVectorDBSettings);
- this.vectorDBCollectionInput.addEventListener('input', saveVectorDBSettings);
- this.milvusOpenAIInput.addEventListener('input', saveVectorDBSettings);
-
- // Test Vector DB connection
- vectorDBTestButton.addEventListener('click', async () => {
- if (!this.vectorDBEndpointInput || !this.vectorDBApiKeyInput ||
- !this.milvusPasswordInput || !this.vectorDBCollectionInput || !this.milvusOpenAIInput) {
- return;
- }
+ private createFieldGroup(label: string, hint: string): { container: HTMLDivElement } {
+ const container = document.createElement('div');
+ container.style.display = 'flex';
+ container.style.flexDirection = 'column';
+ container.style.gap = '4px';
- const endpoint = this.vectorDBEndpointInput.value.trim();
+ const labelEl = document.createElement('div');
+ labelEl.className = 'settings-label';
+ labelEl.textContent = label;
+ container.appendChild(labelEl);
- if (!endpoint) {
- vectorDBTestStatus.textContent = 'Please enter an endpoint URL';
- vectorDBTestStatus.style.color = 'var(--color-accent-red)';
- vectorDBTestStatus.style.display = 'block';
- setTimeout(() => {
- vectorDBTestStatus.style.display = 'none';
- }, 3000);
- return;
- }
+ const hintEl = document.createElement('div');
+ hintEl.className = 'settings-hint';
+ hintEl.textContent = hint;
+ container.appendChild(hintEl);
+
+ return { container };
+ }
+
+ private handleToggle(): void {
+ this.isEnabled = !this.isEnabled;
+
+ if (this.toggleElement) {
+ this.toggleElement.classList.toggle('active', this.isEnabled);
+ }
+
+ if (this.configContainer) {
+ this.configContainer.style.display = this.isEnabled ? 'flex' : 'none';
+ }
+
+ localStorage.setItem(VECTOR_DB_ENABLED_KEY, this.isEnabled.toString());
+ }
+
+ private saveSettings(): void {
+ if (!this.vectorDBEndpointInput || !this.vectorDBApiKeyInput ||
+ !this.milvusPasswordInput || !this.vectorDBCollectionInput || !this.milvusOpenAIInput) {
+ return;
+ }
+
+ localStorage.setItem(VECTOR_DB_ENABLED_KEY, this.isEnabled.toString());
+ localStorage.setItem(MILVUS_ENDPOINT_KEY, this.vectorDBEndpointInput.value);
+ localStorage.setItem(MILVUS_USERNAME_KEY, this.vectorDBApiKeyInput.value);
+ localStorage.setItem(MILVUS_PASSWORD_KEY, this.milvusPasswordInput.value);
+ localStorage.setItem(MILVUS_COLLECTION_KEY, this.vectorDBCollectionInput.value);
+ localStorage.setItem(MILVUS_OPENAI_KEY, this.milvusOpenAIInput.value);
+ }
- vectorDBTestButton.disabled = true;
- vectorDBTestStatus.textContent = i18nString(UIStrings.testingVectorDBConnection);
- vectorDBTestStatus.style.color = 'var(--color-text-secondary)';
- vectorDBTestStatus.style.display = 'block';
-
- try {
- // Import and test the Vector DB client
- const { VectorDBClient } = await import('../../../tools/VectorDBClient.js');
- const vectorClient = new VectorDBClient({
- endpoint,
- username: this.vectorDBApiKeyInput.value || 'root',
- password: this.milvusPasswordInput.value || 'Milvus',
- collection: this.vectorDBCollectionInput.value || 'bookmarks',
- openaiApiKey: this.milvusOpenAIInput.value || undefined
- });
-
- const testResult = await vectorClient.testConnection();
-
- if (testResult.success) {
- vectorDBTestStatus.textContent = i18nString(UIStrings.vectorDBConnectionSuccess);
- vectorDBTestStatus.style.color = 'var(--color-accent-green)';
- } else {
- vectorDBTestStatus.textContent = `${i18nString(UIStrings.vectorDBConnectionFailed)}: ${testResult.error}`;
- vectorDBTestStatus.style.color = 'var(--color-accent-red)';
+ private async testConnection(testButton: HTMLButtonElement): Promise {
+ if (!this.vectorDBEndpointInput || !this.vectorDBApiKeyInput ||
+ !this.milvusPasswordInput || !this.vectorDBCollectionInput || !this.milvusOpenAIInput) {
+ return;
+ }
+
+ const endpoint = this.vectorDBEndpointInput.value.trim();
+
+ if (!endpoint) {
+ testButton.textContent = 'Enter endpoint';
+ setTimeout(() => {
+ testButton.textContent = 'Test Connection';
+ }, 2000);
+ return;
+ }
+
+ testButton.disabled = true;
+ testButton.textContent = 'Testing...';
+
+ try {
+ // Import and test the Vector DB client
+ const { VectorDBClient } = await import('../../../tools/VectorDBClient.js');
+ const vectorClient = new VectorDBClient({
+ endpoint,
+ username: this.vectorDBApiKeyInput.value || 'root',
+ password: this.milvusPasswordInput.value || 'Milvus',
+ collection: this.vectorDBCollectionInput.value || 'bookmarks',
+ openaiApiKey: this.milvusOpenAIInput.value || undefined
+ });
+
+ const testResult = await vectorClient.testConnection();
+
+ if (testResult.success) {
+ testButton.textContent = 'Connected!';
+ // Enable toggle if not already
+ if (!this.isEnabled) {
+ this.isEnabled = true;
+ if (this.toggleElement) {
+ this.toggleElement.classList.add('active');
+ }
+ localStorage.setItem(VECTOR_DB_ENABLED_KEY, 'true');
}
- } catch (error: any) {
- vectorDBTestStatus.textContent = `${i18nString(UIStrings.vectorDBConnectionFailed)}: ${error.message}`;
- vectorDBTestStatus.style.color = 'var(--color-accent-red)';
- } finally {
- vectorDBTestButton.disabled = false;
- setTimeout(() => {
- vectorDBTestStatus.style.display = 'none';
- }, 5000);
+ } else {
+ testButton.textContent = 'Failed';
+ console.error('Vector DB test failed:', testResult.error);
}
- });
+ } catch (error: any) {
+ testButton.textContent = 'Failed';
+ console.error('Vector DB test error:', error.message);
+ } finally {
+ setTimeout(() => {
+ testButton.disabled = false;
+ testButton.textContent = 'Test Connection';
+ }, 2000);
+ }
}
save(): void {
- // Vector DB settings are auto-saved on input change
- // No need to save on dialog save
+ this.saveSettings();
}
cleanup(): void {
diff --git a/front_end/panels/ai_chat/ui/settings/utils/styles.ts b/front_end/panels/ai_chat/ui/settings/utils/styles.ts
index 3820374053..acabc0a342 100644
--- a/front_end/panels/ai_chat/ui/settings/utils/styles.ts
+++ b/front_end/panels/ai_chat/ui/settings/utils/styles.ts
@@ -8,55 +8,64 @@
export function getSettingsStyles(): string {
return `
.settings-dialog {
- color: var(--color-text-primary);
- background-color: var(--color-background);
+ color: hsl(var(--foreground));
+ background-color: hsl(var(--background));
}
.settings-content {
padding: 0;
max-width: 100%;
+ background: hsl(220 25% 97%); /* Slate 50 - #F7F9FC equivalent */
}
.settings-header {
display: flex;
justify-content: space-between;
align-items: center;
- padding: 16px 20px;
- border-bottom: 1px solid var(--color-details-hairline);
+ padding: 16px 24px;
+ border-bottom: 1px solid hsl(var(--border));
+ background: hsl(var(--background));
}
.settings-title {
- font-size: 18px;
- font-weight: 500;
+ font-size: 24px;
+ font-weight: 600;
margin: 0;
- color: var(--color-text-primary);
+ color: hsl(var(--foreground));
}
.settings-close-button {
background: none;
border: none;
- font-size: 20px;
+ font-size: 24px;
cursor: pointer;
- color: var(--color-text-secondary);
+ color: hsl(var(--muted-foreground));
padding: 4px 8px;
+ line-height: 1;
}
.settings-close-button:hover {
- color: var(--color-text-primary);
+ color: hsl(var(--foreground));
}
.provider-selection-section {
- padding: 16px 20px;
- border-bottom: 1px solid var(--color-details-hairline);
+ padding: 24px;
+ margin: 16px 24px;
+ border-radius: 6px;
+ border: 1px solid hsl(var(--border));
+ background: hsl(var(--background));
}
.provider-select {
- margin-top: 8px;
+ margin-top: 12px;
}
.provider-content {
- padding: 16px 20px;
- border-bottom: 1px solid var(--color-details-hairline);
+ padding: 24px;
+ margin: 0 24px 16px;
+ border-radius: 6px;
+ border: 1px solid hsl(var(--border));
+ background: hsl(var(--background));
}
.settings-section {
@@ -67,44 +76,44 @@ export function getSettingsStyles(): string {
font-size: 16px;
font-weight: 500;
margin: 0 0 12px 0;
- color: var(--color-text-primary);
+ color: hsl(var(--foreground));
}
.settings-label {
font-size: 14px;
font-weight: 500;
margin-bottom: 6px;
- color: var(--color-text-primary);
+ color: hsl(var(--foreground));
}
.settings-hint {
font-size: 12px;
- color: var(--color-text-secondary);
+ color: hsl(var(--muted-foreground));
margin-bottom: 8px;
}
.settings-description {
font-size: 14px;
- color: var(--color-text-secondary);
+ color: hsl(var(--muted-foreground));
margin: 4px 0 12px 0;
}
.settings-input, .settings-select {
width: 100%;
padding: 8px 12px;
- border-radius: 4px;
- border: 1px solid var(--color-details-hairline);
- background-color: var(--color-background-elevation-2);
- color: var(--color-text-primary);
+ border-radius: 6px;
+ border: 1px solid hsl(var(--border));
+ background-color: hsl(var(--background));
+ color: hsl(var(--foreground));
font-size: 14px;
box-sizing: border-box;
- height: 32px;
+ height: 40px;
}
.settings-input:focus, .settings-select:focus {
outline: none;
- border-color: var(--color-primary);
- box-shadow: 0 0 0 1px var(--color-primary-opacity-30);
+ border-color: hsl(var(--primary));
+ box-shadow: 0 0 0 2px hsl(var(--ring) / 0.2);
}
.settings-status {
@@ -255,8 +264,9 @@ export function getSettingsStyles(): string {
justify-content: flex-end;
align-items: center;
gap: 12px;
- padding: 16px 20px;
- border-top: 1px solid var(--color-details-hairline);
+ padding: 16px 24px;
+ border-top: 1px solid hsl(var(--border));
+ background: hsl(var(--background));
}
.save-status {
@@ -267,18 +277,22 @@ export function getSettingsStyles(): string {
.settings-button {
padding: 8px 16px;
- border-radius: 4px;
+ height: 40px;
+ border-radius: 6px;
font-size: 14px;
cursor: pointer;
transition: all 0.2s;
font-family: inherit;
- background-color: var(--color-background-elevation-1);
- border: 1px solid var(--color-details-hairline);
- color: var(--color-text-primary);
+ background-color: hsl(var(--background));
+ border: 1px solid hsl(var(--border));
+ color: hsl(var(--foreground));
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
}
.settings-button:hover {
- background-color: var(--color-background-elevation-2);
+ background-color: hsl(var(--secondary));
}
.settings-button:disabled {
@@ -289,13 +303,13 @@ export function getSettingsStyles(): string {
/* Add button styling */
.add-button {
min-width: 60px;
- border-radius: 4px;
- font-size: 12px;
- background-color: var(--color-background-elevation-1);
+ border-radius: 6px;
+ font-size: 14px;
+ background-color: hsl(var(--background));
}
.add-button:hover {
- background-color: var(--color-background-elevation-2);
+ background-color: hsl(var(--secondary));
}
/* Icon button styling */
@@ -333,26 +347,32 @@ export function getSettingsStyles(): string {
justify-content: center;
}
- /* Cancel button */
+ /* Cancel button - Outline variant */
.cancel-button {
- background-color: var(--color-background-elevation-1);
- border: 1px solid var(--color-details-hairline);
- color: var(--color-text-primary);
+ background-color: hsl(var(--background));
+ border: 1px solid hsl(var(--border));
+ color: hsl(var(--foreground));
+ height: 40px;
+ padding: 8px 16px;
+ border-radius: 6px;
}
.cancel-button:hover {
- background-color: var(--color-background-elevation-2);
+ background-color: hsl(var(--secondary));
}
- /* Save button */
+ /* Save button - Primary variant */
.save-button {
- background-color: var(--color-primary);
- border: 1px solid var(--color-primary);
- color: white;
+ background-color: hsl(var(--primary));
+ border: 1px solid hsl(var(--primary));
+ color: hsl(var(--primary-foreground));
+ height: 40px;
+ padding: 8px 16px;
+ border-radius: 6px;
}
.save-button:hover {
- background-color: var(--color-primary-variant);
+ background-color: hsl(var(--primary) / 0.9);
}
.clear-button {
diff --git a/front_end/panels/ai_chat/ui/version/VersionBanner.ts b/front_end/panels/ai_chat/ui/version/VersionBanner.ts
index 52bf18a2d1..e025f989dd 100644
--- a/front_end/panels/ai_chat/ui/version/VersionBanner.ts
+++ b/front_end/panels/ai_chat/ui/version/VersionBanner.ts
@@ -12,7 +12,9 @@ export interface VersionInfo { latestVersion: string; releaseUrl: string; isUpda
@customElement('ai-version-banner')
export class VersionBanner extends HTMLElement {
static readonly litTagName = Lit.StaticHtml.literal`ai-version-banner`;
- readonly #shadow = this.attachShadow({mode: 'open'});
+ // Use Light DOM
+ // readonly #shadow = this.attachShadow({mode: 'open'});
+ readonly #shadow = this;
// Manual properties
#info: VersionInfo | null = null;
@@ -32,7 +34,7 @@ export class VersionBanner extends HTMLElement {
const info = this.#info;
Lit.render(html`