Skip to content

Conversation

@olesho
Copy link
Collaborator

@olesho olesho commented Oct 29, 2025

Summary of what we fixed:

  1. Added constructors to OpenAISettings and other provider classes
  2. Fixed import paths:
    - BaseProviderSettings: Logger path (already correct)
    - BrowsingHistorySettings: Changed ../../../../ to ../../../../../ for Common import
  3. Added type casts for refreshModelSelectOptions calls in GroqSettings and LiteLLMSettings
  4. Added override modifier to OpenRouterSettings.cleanup()
  5. Added static method updateOpenRouterModels to SettingsDialog
  6. Re-exported isVectorDBEnabled from SettingsDialog for backward compatibility
  7. Fixed Dockerfile to delete out/Default before rebuilding, ensuring new files in BUILD.gn are recognized

Summary by CodeRabbit

  • New Features
    • Added comprehensive settings UI for AI Chat with support for multiple LLM providers (OpenAI, Groq, LiteLLM, OpenRouter).
    • Added advanced configuration options for browsing history management, tracing, vector database integration, MCP, and evaluation services.
    • Added model selector with support for different model tiers and persistent settings storage.

@claude
Copy link

claude bot commented Oct 29, 2025

Claude finished @olesho's task —— View job


PR Review: Refactored Settings UI

I've completed a comprehensive review of this Settings UI refactor. This is an impressive modularization effort that transforms a monolithic 3788-line dialog into a well-structured, maintainable architecture.

Review Tasks:

  • Read repository guidelines and setup
  • Review main SettingsDialog.ts changes
  • Examine new modular architecture
  • Check component quality and best practices
  • Security assessment (API keys, input validation)
  • Performance considerations
  • Test coverage analysis
  • Provide detailed feedback

Architectural Improvements

Excellent modularization: The refactor breaks down a massive monolithic file into a clean, organized structure:

ui/settings/
├── providers/          # Provider-specific settings (OpenAI, LiteLLM, Groq, OpenRouter)
├── advanced/          # Advanced features (MCP, VectorDB, Tracing, Evaluation)
├── components/        # Reusable UI components
├── utils/            # Utilities (validation, storage, styles)
├── types.ts          # Comprehensive TypeScript interfaces
└── constants.ts      # Central configuration constants

Strong inheritance pattern: BaseProviderSettings provides excellent polymorphic foundation with consistent interface across providers (render(), save(), updateModelSelectors()).

Proper separation of concerns: Clean separation between UI rendering, data persistence, validation, and business logic.

Code Quality

TypeScript usage: Excellent type safety with comprehensive interfaces in types.ts - proper function signature types and model definitions.

Error handling: Good defensive programming in front_end/panels/ai_chat/ui/settings/utils/validation.ts:59-69 with try-catch for URL validation.

Consistent patterns: All providers follow the same architectural pattern, making the codebase predictable and maintainable.

Clean abstractions: The storage utilities in utils/storage.ts provide clean wrappers around localStorage with proper type handling.

Security Assessment

API key handling: Proper security practices:

  • API keys use type="password" inputs (front_end/panels/ai_chat/ui/settings/providers/OpenAISettings.ts:54)
  • Stored in localStorage (appropriate for browser extension context)
  • Input validation with validateApiKey() function

Input validation: Good validation utilities for API keys and endpoints, though basic.

No obvious vulnerabilities: No signs of XSS risks, SQL injection, or credential leakage.

⚠️ Areas for Improvement

Performance Considerations

  1. Memory management: The dialog creates many DOM elements and event listeners. The cleanup mechanism in SettingsDialog.ts:465-480 is good, but consider:

    // Could benefit from WeakMap for listener tracking
    private listenerRefs = new WeakMap<HTMLElement, () => void>();
  2. API rate limiting: Multiple providers fetch models on change (SettingsDialog.ts:210-268). Consider debouncing or caching strategies.

Code Structure Issues

  1. Large method: SettingsDialog.show() is still quite large (481 lines). Consider breaking it down further:

    // Suggested refactor
    static async show(...args) {
      const dialog = this.createDialog();
      const controller = new SettingsController(dialog, ...args);
      return controller.initialize();
    }
  2. Magic numbers: Status update interval hardcoded to 10 seconds in MCPSettings.ts. Should be a named constant.

Error Handling

  1. Missing error boundaries: Model fetching failures are logged but UI doesn't show user-friendly errors.

  2. localStorage failures: No handling for localStorage quota exceeded scenarios.

Type Safety

  1. Any types: Found (this.miniModelSelector as any).value in BaseProviderSettings.ts:57. Should be properly typed:
    interface ModelSelectorElement extends HTMLElement {
      value: string;
    }

📋 Missing Test Coverage

The refactor doesn't include tests for the new modular components. Critical areas needing tests:

  • Provider settings validation logic
  • Storage utilities error handling
  • Model fetching and caching behavior
  • Dialog state management

🔧 Recommendations

  1. Add unit tests for the new utility functions and provider classes
  2. Consider implementing a proper state management pattern instead of direct localStorage access throughout
  3. Add error boundaries for better UX when API calls fail
  4. Extract dialog controller to reduce SettingsDialog.show() complexity
  5. Add JSDoc comments to public methods for better developer experience

Overall Assessment

This is a high-quality refactor that significantly improves the codebase's maintainability and organization. The modular architecture is well-designed, follows solid OOP principles, and maintains backward compatibility. The security practices are appropriate for a browser extension context.

Code Quality: A-
Architecture: A
Security: B+
Performance: B

The refactor successfully achieves its goals while introducing minimal risk. The few issues identified are mostly minor improvements rather than critical problems.


@coderabbitai
Copy link

coderabbitai bot commented Oct 29, 2025

Walkthrough

This pull request adds a comprehensive settings UI system for the AI chat panel, including provider-specific configuration components (OpenAI, LiteLLM, Groq, OpenRouter), advanced settings modules (MCP, tracing, vector database, evaluation), factory utilities for creating model selectors and UI components, and storage/validation helpers with i18n support.

Changes

Cohort / File(s) Summary
Build configuration
front_end/panels/ai_chat/BUILD.gn
Adds new UI settings TypeScript files to build sources and metadata lists
Type definitions & constants
front_end/panels/ai_chat/ui/settings/types.ts, front_end/panels/ai_chat/ui/settings/constants.ts, front_end/panels/ai_chat/ui/settings/i18n-strings.ts
Exports provider/model types, storage keys, model cache duration, and localized UI strings for settings dialogs
Advanced settings components
front_end/panels/ai_chat/ui/settings/advanced/BrowsingHistorySettings.ts, front_end/panels/ai_chat/ui/settings/advanced/EvaluationSettings.ts, front_end/panels/ai_chat/ui/settings/advanced/MCPSettings.ts, front_end/panels/ai_chat/ui/settings/advanced/TracingSettings.ts, front_end/panels/ai_chat/ui/settings/advanced/VectorDBSettings.ts
Renders and manages specialized settings: browsing history clearing, evaluation service connection/status, MCP integration with periodic status polling, Langfuse tracing configuration, and Milvus vector database setup with auto-save
UI component factories
front_end/panels/ai_chat/ui/settings/components/AdvancedToggle.ts, front_end/panels/ai_chat/ui/settings/components/ModelSelectorFactory.ts, front_end/panels/ai_chat/ui/settings/components/SettingsHeader.ts, front_end/panels/ai_chat/ui/settings/components/SettingsFooter.ts
Creates reusable UI elements: advanced settings toggle with storage persistence, dynamic model selector with option refresh, settings header/footer with callbacks and status messaging
Provider settings base & implementations
front_end/panels/ai_chat/ui/settings/providers/BaseProviderSettings.ts, front_end/panels/ai_chat/ui/settings/providers/OpenAISettings.ts, front_end/panels/ai_chat/ui/settings/providers/LiteLLMSettings.ts, front_end/panels/ai_chat/ui/settings/providers/GroqSettings.ts, front_end/panels/ai_chat/ui/settings/providers/OpenRouterSettings.ts
Abstract base class for provider settings; concrete implementations with API key input, model fetching, dynamic model selection, OAuth support (OpenRouter), custom model management (LiteLLM), and localStorage persistence
Utilities
front_end/panels/ai_chat/ui/settings/utils/storage.ts, front_end/panels/ai_chat/ui/settings/utils/validation.ts, front_end/panels/ai_chat/ui/settings/utils/styles.ts
Typed localStorage helper functions, model validation and endpoint URL verification, and centralized CSS stylesheet generation for settings dialog

Sequence Diagram

sequenceDiagram
    participant User
    participant SettingsDialog
    participant ProviderSettings
    participant Storage
    participant LLMClient
    
    User->>SettingsDialog: Open settings
    SettingsDialog->>Storage: Load saved provider & API keys
    SettingsDialog->>ProviderSettings: Initialize provider UI
    ProviderSettings->>Storage: Load provider config
    
    User->>ProviderSettings: Enter API key
    ProviderSettings->>Storage: Auto-save API key
    
    User->>ProviderSettings: Click "Fetch Models"
    ProviderSettings->>LLMClient: Fetch provider models
    LLMClient-->>ProviderSettings: Model list
    ProviderSettings->>Storage: Cache models + timestamp
    ProviderSettings->>ProviderSettings: Update model selectors
    
    User->>ProviderSettings: Select mini/nano models
    ProviderSettings->>Storage: Store selection
    
    User->>SettingsDialog: Click Save
    SettingsDialog->>ProviderSettings: Call save()
    ProviderSettings->>Storage: Persist final config
    SettingsDialog->>User: Settings saved ✓
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60–90 minutes

Areas requiring extra attention:

  • MCPSettings.ts — Dense component with real-time status polling (10-second timer), multi-section rendering (header, status display, action buttons, connection management, config options), error formatting utilities, and complex MCP status rendering with per-server rows and OAuth handling
  • OpenRouterSettings.ts — Async OAuth flow with event handlers, model caching with timestamp-based refresh logic, and conditional UI branching between API key and OAuth modes
  • LiteLLMSettings.ts — Custom model list management (add/test/remove), dynamic model fetching via callback, and integration with custom model storage persistence
  • Advanced settings components (Evaluation, Tracing, VectorDB) — Async operations, periodic status updates via setInterval, test-connection flows, and transient status messaging with 3-second timeouts
  • Storage and validation utilities — Ensure consistent default value handling and URL validation logic across providers

Possibly related PRs

Suggested reviewers

  • tysonthomas9

Poem

🐰 A rabbit's settings song:
Dropdowns dance and toggles twirl,
MCP connects and models swirl,
LocalStorage holds our keys so tight,
Provider buttons glow just right,
Advanced settings now in sight! 🎯

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "Refactored Settings UI" directly corresponds to the primary changes in this pull request. The changeset introduces a comprehensive, modular settings UI system for the AI chat panel, consisting of multiple new component classes (BrowsingHistorySettings, EvaluationSettings, MCPSettings, TracingSettings, VectorDBSettings), utility modules (storage, validation, styles, components), provider-specific settings classes (OpenAI, Groq, LiteLLM, OpenRouter), and supporting infrastructure (types, constants, i18n strings). The term "Refactored" accurately reflects that this represents a restructuring and reorganization of the settings UI architecture, and "Settings UI" clearly identifies the subject matter. The title is concise, specific enough to convey the main change, and avoids vague terminology.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chore/refactor-ui

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 23

🧹 Nitpick comments (59)
front_end/panels/ai_chat/BUILD.gn (2)

229-248: Keep _ai_chat_sources in sync with module sources.

The same note applies here; after renaming i18n strings file, ensure this list mirrors sources. Consider factoring a shared array to avoid maintenance overhead.


43-62: Remove conditional guidance about ModuleUIStrings rename; reframe consolidation as preventative refactoring.

The i18n file remains named "ui/settings/i18n-strings.ts" (no rename to ModuleUIStrings.ts is in progress). Both settings file lists are currently identical with no drift. Consider consolidating the repeated list into a shared variable to prevent future maintenance issues.

front_end/panels/ai_chat/ui/settings/components/SettingsHeader.ts (2)

10-12: Fix JSDoc hyphen formatting.

Remove hyphens before @param descriptions to satisfy jsdoc rule.

- * @param container - Parent element to append the header to
- * @param onClose - Callback function when close button is clicked
+ * @param container Parent element to append the header to
+ * @param onClose Callback invoked when the close button is clicked

29-31: Localize the close button label.

Add a UIStrings key (e.g., closeSettingsAriaLabel) and use it here.

-  closeButton.setAttribute('aria-label', 'Close settings');
+  closeButton.setAttribute('aria-label', i18nString(UIStrings.closeSettingsAriaLabel));
front_end/panels/ai_chat/ui/settings/utils/styles.ts (1)

509-513: Guard against duplicate style injection.

If dialog opens multiple times, this can append multiple identical <style> tags. Add an id and check before injecting.

-export function applySettingsStyles(dialogElement: HTMLElement): void {
-  const styleElement = document.createElement('style');
-  styleElement.textContent = getSettingsStyles();
-  dialogElement.appendChild(styleElement);
-}
+export function applySettingsStyles(dialogElement: HTMLElement): void {
+  const STYLE_ID = 'ai-chat-settings-styles';
+  if (!dialogElement.querySelector(`#${STYLE_ID}`)) {
+    const styleElement = document.createElement('style');
+    styleElement.id = STYLE_ID;
+    styleElement.textContent = getSettingsStyles();
+    dialogElement.appendChild(styleElement);
+  }
+}
front_end/panels/ai_chat/ui/settings/components/AdvancedToggle.ts (4)

5-7: Import UIStrings for localization.

Use shared ModuleUIStrings to localize label/hint.

-import { ADVANCED_SETTINGS_ENABLED_KEY } from '../constants.js';
-import { getStorageBoolean, setStorageBoolean } from '../utils/storage.js';
+import { ADVANCED_SETTINGS_ENABLED_KEY } from '../constants.js';
+import { getStorageBoolean, setStorageBoolean } from '../utils/storage.js';
+import { UIStrings, i18nString } from '../ModuleUIStrings.js';

20-23: Fix JSDoc hyphens.

Remove hyphens before @param descriptions.

- * @param container - Parent element to append the toggle to
- * @param onChange - Callback function when toggle state changes
+ * @param container Parent element to append the toggle to
+ * @param onChange Callback invoked when toggle state changes

43-53: Localize label/hint and add ARIA wiring.

  • Localize textContent via UIStrings (add keys: advancedSettingsLabel, advancedSettingsHint).
  • Use a unique id and connect ARIA attributes; expose expanded state.
-  advancedToggleCheckbox.id = 'advanced-settings-toggle';
+  const toggleId = 'advanced-settings-toggle';
+  advancedToggleCheckbox.id = toggleId;
   advancedToggleCheckbox.className = 'advanced-settings-checkbox';
   advancedToggleCheckbox.checked = getStorageBoolean(ADVANCED_SETTINGS_ENABLED_KEY, false);
+  advancedToggleCheckbox.setAttribute('aria-expanded', String(advancedToggleCheckbox.checked));
   advancedToggleContainer.appendChild(advancedToggleCheckbox);

   const advancedToggleLabel = document.createElement('label');
-  advancedToggleLabel.htmlFor = 'advanced-settings-toggle';
+  advancedToggleLabel.htmlFor = toggleId;
   advancedToggleLabel.className = 'advanced-settings-label';
-  advancedToggleLabel.textContent = '⚙️ Advanced Settings';
+  advancedToggleLabel.textContent = i18nString(UIStrings.advancedSettingsLabel);
   advancedToggleContainer.appendChild(advancedToggleLabel);

   const advancedToggleHint = document.createElement('div');
   advancedToggleHint.className = 'settings-hint';
-  advancedToggleHint.textContent =
-    'Show advanced configuration options (Browsing History, Vector DB, Tracing, Evaluation)';
+  advancedToggleHint.textContent = i18nString(UIStrings.advancedSettingsHint);
   advancedToggleSection.appendChild(advancedToggleHint);

55-60: Keep ARIA in sync with state.

Update aria-expanded on change.

   advancedToggleCheckbox.addEventListener('change', () => {
     const isEnabled = advancedToggleCheckbox.checked;
+    advancedToggleCheckbox.setAttribute('aria-expanded', String(isEnabled));
     setStorageBoolean(ADVANCED_SETTINGS_ENABLED_KEY, isEnabled);
     onChange(isEnabled);
   });
front_end/panels/ai_chat/ui/settings/advanced/BrowsingHistorySettings.ts (2)

41-46: Announce status to assistive tech and use UIStrings.

-    this.statusMessage = document.createElement('div');
+    this.statusMessage = document.createElement('div');
     this.statusMessage.className = 'settings-status history-status';
     this.statusMessage.style.display = 'none';
-    this.statusMessage.textContent = i18nString('historyCleared');
+    this.statusMessage.setAttribute('aria-live', 'polite');
+    this.statusMessage.textContent = i18nString(UIStrings.historyCleared);

48-75: Add confirm dialog and disable button during async.

Avoid accidental data loss and prevent double clicks. Localize button label.

-    clearHistoryButton.textContent = i18nString('clearHistoryButton');
+    clearHistoryButton.textContent = i18nString(UIStrings.clearHistoryButton);
@@
-    clearHistoryButton.addEventListener('click', async () => {
+    clearHistoryButton.addEventListener('click', async () => {
+      // Confirm destructive action
+      // Add UIStrings.clearHistoryConfirm: 'Clear all stored browsing history? This cannot be undone.'
+      if (!window.confirm(i18nString(UIStrings.clearHistoryConfirm))) {
+        return;
+      }
+      clearHistoryButton.disabled = true;
       try {
@@
-        if (this.statusMessage) {
+        if (this.statusMessage) {
           this.statusMessage.style.display = 'block';
@@
-        }
+        }
       } catch (error) {
         logger.error('Error clearing browsing history:', error);
       }
+      clearHistoryButton.disabled = false;
     });
front_end/panels/ai_chat/ui/settings/advanced/EvaluationSettings.ts (2)

123-177: Localize client ID labels and hints; avoid hard-coded placeholders.

Add UIStrings for clientIdLabel, clientIdHint, and clientIdAutoGenerated.

-    clientIdLabel.textContent = 'Client ID';
+    clientIdLabel.textContent = i18nString(UIStrings.clientIdLabel);
@@
-    clientIdHint.textContent = 'Unique identifier for this DevTools instance';
+    clientIdHint.textContent = i18nString(UIStrings.clientIdHint);
@@
-    clientIdInput.value = currentEvaluationConfig.clientId || 'Auto-generated on first connection';
+    clientIdInput.value = currentEvaluationConfig.clientId || i18nString(UIStrings.clientIdAutoGenerated);
@@
-    evaluationEndpointLabel.textContent = i18nString('evaluationEndpoint');
+    evaluationEndpointLabel.textContent = i18nString(UIStrings.evaluationEndpoint);
@@
-    evaluationEndpointHint.textContent = i18nString('evaluationEndpointHint');
+    evaluationEndpointHint.textContent = i18nString(UIStrings.evaluationEndpointHint);
@@
-    evaluationSecretKeyLabel.textContent = i18nString('evaluationSecretKey');
+    evaluationSecretKeyLabel.textContent = i18nString(UIStrings.evaluationSecretKey);
@@
-    evaluationSecretKeyHint.textContent = i18nString('evaluationSecretKeyHint');
+    evaluationSecretKeyHint.textContent = i18nString(UIStrings.evaluationSecretKeyHint);

36-39: Note on innerHTML lint warning.

this.container.innerHTML = '' is safe here (constant empty string). If the repo enforces the rule strictly, replace with a loop removing children.

-    this.container.innerHTML = '';
+    while (this.container.firstChild) {
+      this.container.removeChild(this.container.firstChild);
+    }
front_end/panels/ai_chat/ui/settings/advanced/TracingSettings.ts (3)

26-28: Avoid innerHTML; use safer DOM APIs.

Replace with replaceChildren() to eliminate XSS lint and avoid layout thrash.

-    this.container.innerHTML = '';
+    this.container.replaceChildren();

As per static analysis hints.


142-145: Localize user-visible strings.

Replace hard-coded English with i18n keys to pass l10n lint and for translation.

-      testTracingStatus.textContent = 'Testing connection...';
+      testTracingStatus.textContent = i18nString(UIStrings.testingTracingConnection);
@@
-          throw new Error('All fields are required for testing');
+          throw new Error(i18nString(UIStrings.tracingAllFieldsRequired));
@@
-          testTracingStatus.textContent = '✓ Connection successful';
+          testTracingStatus.textContent = i18nString(UIStrings.tracingConnectionSuccess);
@@
-          throw new Error(`HTTP ${response.status}: ${errorText}`);
+          throw new Error(i18nString(UIStrings.tracingHttpError, { PH1: String(response.status), PH2: errorText }));
@@
-        testTracingStatus.textContent = `✗ ${error instanceof Error ? error.message : 'Connection failed'}`;
+        testTracingStatus.textContent = error instanceof Error
+          ? error.message
+          : i18nString(UIStrings.tracingConnectionFailed);

Note: add the missing UIStrings in i18n-strings.ts.

Also applies to: 151-153, 178-185, 187-189


199-220: Validate on save; avoid silent no-op and stale config.

If enabled but incomplete, either disable tracing or surface error. Minimal option: disable.

   save(): void {
     if (!this.tracingEnabledCheckbox || !this.endpointInput || !this.publicKeyInput || !this.secretKeyInput) {
       return;
     }
 
     if (this.tracingEnabledCheckbox.checked) {
       const endpoint = this.endpointInput.value.trim();
       const publicKey = this.publicKeyInput.value.trim();
       const secretKey = this.secretKeyInput.value.trim();
 
-      if (endpoint && publicKey && secretKey) {
+      if (endpoint && publicKey && secretKey) {
         setTracingConfig({
           provider: 'langfuse',
           endpoint,
           publicKey,
           secretKey
         });
-      }
+      } else {
+        // Incomplete config; disable to avoid half-enabled state
+        setTracingConfig({ provider: 'disabled' });
+      }
     } else {
       setTracingConfig({ provider: 'disabled' });
     }
   }

Alternatively, bubble a validation error to the footer and keep previous config.

front_end/panels/ai_chat/ui/settings/advanced/VectorDBSettings.ts (2)

35-37: Avoid innerHTML; use replaceChildren().

Safer and passes XSS lint.

-    this.container.innerHTML = '';
+    this.container.replaceChildren();

As per static analysis hints.


236-242: Localize literals and avoid any in catch.

Use i18n for messages and prefer unknown with narrowing.

-        vectorDBTestStatus.textContent = 'Please enter an endpoint URL';
+        vectorDBTestStatus.textContent = i18nString(UIStrings.vectorDBEnterEndpoint);
@@
-      vectorDBTestStatus.textContent = i18nString('testingVectorDBConnection');
+      vectorDBTestStatus.textContent = i18nString(UIStrings.testingVectorDBConnection);
@@
-        if (testResult.success) {
-          vectorDBTestStatus.textContent = i18nString('vectorDBConnectionSuccess');
+        if (testResult.success) {
+          vectorDBTestStatus.textContent = i18nString(UIStrings.vectorDBConnectionSuccess);
           vectorDBTestStatus.style.color = 'var(--color-accent-green)';
         } else {
-          vectorDBTestStatus.textContent = `${i18nString('vectorDBConnectionFailed')}: ${testResult.error}`;
+          vectorDBTestStatus.textContent = `${i18nString(UIStrings.vectorDBConnectionFailed)}: ${testResult.error}`;
           vectorDBTestStatus.style.color = 'var(--color-accent-red)';
         }
-      } catch (error: any) {
-        vectorDBTestStatus.textContent = `${i18nString('vectorDBConnectionFailed')}: ${error.message}`;
+      } catch (error: unknown) {
+        const msg = error instanceof Error ? error.message : String(error);
+        vectorDBTestStatus.textContent = `${i18nString(UIStrings.vectorDBConnectionFailed)}: ${msg}`;
         vectorDBTestStatus.style.color = 'var(--color-accent-red)';
       } finally {

As per static analysis hints.

Also applies to: 246-249, 263-273

front_end/panels/ai_chat/ui/settings/components/SettingsFooter.ts (3)

18-21: Fix JSDoc formatting (no hyphen before descriptions).

Conforms to jsdoc/require-hyphen-before-param-description rule.

- * @param container - Parent element to append the footer to
- * @param onCancel - Callback function when cancel button is clicked
- * @param onSave - Callback function when save button is clicked
+ * @param container Parent element to append the footer to
+ * @param onCancel Callback function when cancel button is clicked
+ * @param onSave Callback function when save button is clicked
@@
- * @param statusElement - The status message element
- * @param message - Message to display
- * @param type - Type of message (info, success, error)
- * @param duration - How long to show the message (ms), 0 = don't auto-hide
+ * @param statusElement The status message element
+ * @param message Message to display
+ * @param type Type of message (info, success, error)
+ * @param duration How long to show the message (ms), 0 = don't auto-hide

As per static analysis hints.

Also applies to: 66-70


41-53: Localize button labels.

Replace literals with i18n strings.

-  cancelButton.textContent = 'Cancel';
+  cancelButton.textContent = i18nString(UIStrings.cancel);
@@
-  saveButton.textContent = 'Save';
+  saveButton.textContent = i18nString(UIStrings.save);

Note: import { i18nString, UIStrings } from '../i18n-strings.js'.


74-99: Remove redundant type annotation and cleanup overlapping timers.

Simplify parameter type and clear any previous hide timer to avoid premature hide.

-export function showFooterStatus(
+export function showFooterStatus(
   statusElement: HTMLElement,
   message: string,
   type: 'info' | 'success' | 'error' = 'info',
-  duration: number = 3000,
+  duration = 3000,
 ): void {
   statusElement.textContent = message;
   statusElement.style.display = 'block';
@@
-  if (duration > 0) {
-    setTimeout(() => {
-      statusElement.style.display = 'none';
-    }, duration);
-  }
+  if (duration > 0) {
+    // @ts-expect-error attach timer id on element
+    if ((statusElement as any).__footerTimerId) {
+      clearTimeout((statusElement as any).__footerTimerId);
+    }
+    // @ts-expect-error store timer id on element
+    (statusElement as any).__footerTimerId = setTimeout(() => {
+      statusElement.style.display = 'none';
+      // @ts-expect-error cleanup
+      (statusElement as any).__footerTimerId = undefined;
+    }, duration);
+  }

As per static analysis hints.

front_end/panels/ai_chat/ui/settings/types.ts (1)

36-43: Unify option shape with ModelOption for consistency.

If consumers can handle it, prefer ModelOption[] to keep type parity across UI.

-  options: Array<{value: string, label: string}>;
+  options: ModelOption[];

If not feasible now, add a TODO to migrate later.

front_end/panels/ai_chat/ui/settings/providers/GroqSettings.ts (3)

5-13: Fix import order and groupings to satisfy lint.

Place utility/type imports before provider class import, and separate groups with a blank line.

-import { BaseProviderSettings } from './BaseProviderSettings.js';
-import { createModelSelector, refreshModelSelectOptions } from '../components/ModelSelectorFactory.js';
-import { i18nString } from '../i18n-strings.js';
-import { getValidModelForProvider } from '../utils/validation.js';
-import { getStorageItem, setStorageItem } from '../utils/storage.js';
-import { GROQ_API_KEY_STORAGE_KEY, MINI_MODEL_STORAGE_KEY, NANO_MODEL_STORAGE_KEY } from '../constants.js';
-import type { UpdateModelOptionsFunction, GetModelOptionsFunction, AddCustomModelOptionFunction, RemoveCustomModelOptionFunction, ModelOption } from '../types.js';
-import { LLMClient } from '../../../LLM/LLMClient.js';
-import { createLogger } from '../../../core/Logger.js';
+import { createModelSelector, refreshModelSelectOptions } from '../components/ModelSelectorFactory.js';
+import { i18nString, UIStrings } from '../i18n-strings.js';
+import { getValidModelForProvider } from '../utils/validation.js';
+import { getStorageItem, setStorageItem } from '../utils/storage.js';
+import { GROQ_API_KEY_STORAGE_KEY, MINI_MODEL_STORAGE_KEY, NANO_MODEL_STORAGE_KEY } from '../constants.js';
+import type { UpdateModelOptionsFunction, GetModelOptionsFunction, AddCustomModelOptionFunction, RemoveCustomModelOptionFunction, ModelOption } from '../types.js';
+import { LLMClient } from '../../../LLM/LLMClient.js';
+import { createLogger } from '../../../core/Logger.js';
+
+import { BaseProviderSettings } from './BaseProviderSettings.js';

51-58: Use UIStrings with i18nString; avoid raw string keys and hardcoded text.

Switch to i18nString(UIStrings.key) and externalize “Model Size Selection” and placeholders.

- groqApiKeyLabel.textContent = i18nString('groqApiKeyLabel');
- groqApiKeyHint.textContent = i18nString('groqApiKeyHint');
+ groqApiKeyLabel.textContent = i18nString(UIStrings.groqApiKeyLabel);
+ groqApiKeyHint.textContent = i18nString(UIStrings.groqApiKeyHint);

- this.fetchModelsButton.textContent = i18nString('fetchGroqModelsButton');
+ this.fetchModelsButton.textContent = i18nString(UIStrings.fetchGroqModelsButton);

- this.fetchModelsStatus.textContent = i18nString('fetchingModels');
+ this.fetchModelsStatus.textContent = i18nString(UIStrings.fetchingModels);

- refreshModelSelectOptions(this.miniModelSelector as any, allGroqModels, miniModel, i18nString('defaultMiniOption'));
+ refreshModelSelectOptions(this.miniModelSelector as unknown as ModelSelectorElement, allGroqModels, miniModel, i18nString(UIStrings.defaultMiniOption));

- refreshModelSelectOptions(this.nanoModelSelector as any, allGroqModels, nanoModel, i18nString('defaultNanoOption'));
+ refreshModelSelectOptions(this.nanoModelSelector as unknown as ModelSelectorElement, allGroqModels, nanoModel, i18nString(UIStrings.defaultNanoOption));

- this.fetchModelsStatus.textContent = i18nString('fetchedModels', {PH1: actualModelCount});
+ this.fetchModelsStatus.textContent = i18nString(UIStrings.fetchedModels, {PH1: actualModelCount});

- groqModelSectionTitle.textContent = 'Model Size Selection';
+ // TODO: add UIStrings.modelSizeSelectionTitle in i18n-strings.ts
+ groqModelSectionTitle.textContent = i18nString(UIStrings.modelSizeSelectionTitle);

- i18nString('miniModelLabel'), i18nString('miniModelDescription'), i18nString('defaultMiniOption'),
+ i18nString(UIStrings.miniModelLabel), i18nString(UIStrings.miniModelDescription), i18nString(UIStrings.defaultMiniOption),

- i18nString('nanoModelLabel'), i18nString('nanoModelDescription'), i18nString('defaultNanoOption'),
+ i18nString(UIStrings.nanoModelLabel), i18nString(UIStrings.nanoModelDescription), i18nString(UIStrings.defaultNanoOption),

Please confirm UIStrings include all referenced keys or share the list so I can generate a patch to add the missing ones.

Also applies to: 75-76, 96-101, 127-131, 133-136, 197-204, 211-218


92-99: Wrap single-line if statements with braces.

Satisfies curly rule and improves readability.

- if (!this.fetchModelsButton || !this.fetchModelsStatus || !this.apiKeyInput) return;
+ if (!this.fetchModelsButton || !this.fetchModelsStatus || !this.apiKeyInput) {
+   return;
+ }

- if (!this.container) return;
+ if (!this.container) {
+   return;
+ }

Also applies to: 161-166

front_end/panels/ai_chat/ui/settings/components/ModelSelectorFactory.ts (2)

11-19: Fix JSDoc: remove hyphens before @param descriptions.

Unblocks jsdoc lint.

- * @param container - Parent element to append the selector to
- * @param labelText - Label text for the selector
- * @param description - Description text below the label
- * @param selectorType - Semantic identifier for the selector
- * @param modelOptions - Available model options
- * @param selectedModel - Currently selected model value
- * @param defaultOptionText - Text for the default option
- * @param onFocus - Optional callback for when the selector is opened/focused
+ * @param container Parent element to append the selector to
+ * @param labelText Label text for the selector
+ * @param description Description text below the label
+ * @param selectorType Semantic identifier for the selector
+ * @param modelOptions Available model options
+ * @param selectedModel Currently selected model value
+ * @param defaultOptionText Text for the default option
+ * @param onFocus Optional callback for when the selector is opened/focused
@@
- * @param select - The model selector element
- * @param models - New model options
- * @param currentValue - Current selected value
- * @param defaultLabel - Label for the default option
+ * @param select The model selector element
+ * @param models New model options
+ * @param currentValue Current selected value
+ * @param defaultLabel Label for the default option

Also applies to: 74-82


94-101: Minor style: drop unnecessary parens around single arrow params.

Keeps style consistent with lint.

- if (previousValue && opts.some((o) => o.value === previousValue)) {
+ if (previousValue && opts.some(o => o.value === previousValue)) {
@@
- } else if (currentValue && opts.some((o) => o.value === currentValue)) {
+ } else if (currentValue && opts.some(o => o.value === currentValue)) {
@@
- if (previousValue && Array.from(nativeSelect.options).some((opt) => opt.value === previousValue)) {
+ if (previousValue && Array.from(nativeSelect.options).some(opt => opt.value === previousValue)) {
- } else if (currentValue && Array.from(nativeSelect.options).some((opt) => opt.value === currentValue)) {
+ } else if (currentValue && Array.from(nativeSelect.options).some(opt => opt.value === currentValue)) {

Also applies to: 116-120

front_end/panels/ai_chat/ui/settings/providers/LiteLLMSettings.ts (8)

5-14: Fix import order; remove unused ModelOption; add UIStrings/type for selector.

Aligns with lint and prepares to eliminate any-casts.

-import { BaseProviderSettings } from './BaseProviderSettings.js';
-import { createModelSelector, refreshModelSelectOptions } from '../components/ModelSelectorFactory.js';
-import { i18nString } from '../i18n-strings.js';
+import { createModelSelector, refreshModelSelectOptions } from '../components/ModelSelectorFactory.js';
+import { i18nString, UIStrings } from '../i18n-strings.js';
 import { getValidModelForProvider } from '../utils/validation.js';
 import { getStorageItem, setStorageItem } from '../utils/storage.js';
 import { LITELLM_ENDPOINT_KEY, LITELLM_API_KEY_STORAGE_KEY, MINI_MODEL_STORAGE_KEY, NANO_MODEL_STORAGE_KEY } from '../constants.js';
-import type { UpdateModelOptionsFunction, GetModelOptionsFunction, AddCustomModelOptionFunction, RemoveCustomModelOptionFunction, FetchLiteLLMModelsFunction, ModelOption } from '../types.js';
+import type { UpdateModelOptionsFunction, GetModelOptionsFunction, AddCustomModelOptionFunction, RemoveCustomModelOptionFunction, FetchLiteLLMModelsFunction, ModelSelectorElement } from '../types.js';
 import { LLMClient } from '../../../LLM/LLMClient.js';
 import { createLogger } from '../../../core/Logger.js';
+
+import { BaseProviderSettings } from './BaseProviderSettings.js';

30-33: Drop trivial type annotation.

Let TS infer boolean.

-private testPassed: boolean = false;
+private testPassed = false;

47-57: Use UIStrings with i18nString; externalize hardcoded strings.

Replace string keys and literal messages with i18nString(UIStrings.*).

- litellmEndpointLabel.textContent = i18nString('litellmEndpointLabel');
- litellmEndpointHint.textContent = i18nString('litellmEndpointHint');
+ litellmEndpointLabel.textContent = i18nString(UIStrings.litellmEndpointLabel);
+ litellmEndpointHint.textContent = i18nString(UIStrings.litellmEndpointHint);
@@
- this.fetchModelsButton.textContent = i18nString('fetchModelsButton');
+ this.fetchModelsButton.textContent = i18nString(UIStrings.fetchModelsButton);
@@
- this.fetchModelsStatus.textContent = i18nString('fetchingModels');
+ this.fetchModelsStatus.textContent = i18nString(UIStrings.fetchingModels);
@@
- refreshModelSelectOptions(this.miniModelSelector as any, allLiteLLMModels, miniModel, i18nString('defaultMiniOption'));
+ refreshModelSelectOptions(this.miniModelSelector as unknown as ModelSelectorElement, allLiteLLMModels, miniModel, i18nString(UIStrings.defaultMiniOption));
@@
- refreshModelSelectOptions(this.nanoModelSelector as any, allLiteLLMModels, nanoModel, i18nString('defaultNanoOption'));
+ refreshModelSelectOptions(this.nanoModelSelector as unknown as ModelSelectorElement, allLiteLLMModels, nanoModel, i18nString(UIStrings.defaultNanoOption));
@@
- this.fetchModelsStatus.textContent = i18nString('wildcardModelsOnly');
+ this.fetchModelsStatus.textContent = i18nString(UIStrings.wildcardModelsOnly);
@@
- this.fetchModelsStatus.textContent = i18nString('wildcardAndCustomModels');
+ this.fetchModelsStatus.textContent = i18nString(UIStrings.wildcardAndCustomModels);
@@
- this.fetchModelsStatus.textContent = i18nString('wildcardAndOtherModels', {PH1: actualModelCount});
+ this.fetchModelsStatus.textContent = i18nString(UIStrings.wildcardAndOtherModels, {PH1: actualModelCount});
@@
- this.fetchModelsStatus.textContent = i18nString('fetchedModels', {PH1: actualModelCount});
+ this.fetchModelsStatus.textContent = i18nString(UIStrings.fetchedModels, {PH1: actualModelCount});
@@
- customModelsLabel.textContent = i18nString('customModelsLabel');
- customModelsHint.textContent = i18nString('customModelsHint');
+ customModelsLabel.textContent = i18nString(UIStrings.customModelsLabel);
+ customModelsHint.textContent = i18nString(UIStrings.customModelsHint);
@@
- addModelButton.textContent = i18nString('addButton');
+ addModelButton.textContent = i18nString(UIStrings.addButton);
@@
- testButton.setAttribute('aria-label', i18nString('testButton'));
+ testButton.setAttribute('aria-label', i18nString(UIStrings.testButton));
@@
- removeButton.setAttribute('aria-label', i18nString('removeButton'));
+ removeButton.setAttribute('aria-label', i18nString(UIStrings.removeButton));
@@
- throw new Error(i18nString('endpointRequired'));
+ throw new Error(i18nString(UIStrings.endpointRequired));
@@
- this.modelTestStatus.textContent = 'Please enter a model name';
+ this.modelTestStatus.textContent = i18nString(UIStrings.enterModelNamePrompt);
@@
- this.modelTestStatus.textContent = 'Testing model...';
+ this.modelTestStatus.textContent = i18nString(UIStrings.testingModel);
@@
- this.modelTestStatus.textContent = `Test passed: ${result.message}`;
+ this.modelTestStatus.textContent = i18nString(UIStrings.testPassedMessage, {PH1: result.message});
@@
- this.modelTestStatus.textContent = `Test failed: ${result.message}`;
+ this.modelTestStatus.textContent = i18nString(UIStrings.testFailedMessage, {PH1: result.message});
@@
- i18nString('miniModelLabel'), i18nString('miniModelDescription'), i18nString('defaultMiniOption'),
+ i18nString(UIStrings.miniModelLabel), i18nString(UIStrings.miniModelDescription), i18nString(UIStrings.defaultMiniOption),
@@
- i18nString('nanoModelLabel'), i18nString('nanoModelDescription'), i18nString('defaultNanoOption'),
+ i18nString(UIStrings.nanoModelLabel), i18nString(UIStrings.nanoModelDescription), i18nString(UIStrings.defaultNanoOption),

Please confirm UIStrings includes the referenced keys or share the file so I can add missing entries.

Also applies to: 76-85, 107-113, 123-131, 147-151, 154-172, 197-205, 228-235, 334-336, 349-355, 419-426, 429-433, 439-444, 466-473, 517-526, 531-540, 545-557


94-101: Add braces to single-line if/return and annotate function return types.

Unblocks curly and explicit return type rules.

- const updateFetchButtonState = () => {
+ const updateFetchButtonState = (): void => {
@@
- if (!this.fetchModelsButton || !this.fetchModelsStatus || !this.endpointInput || !this.apiKeyInput) return;
+ if (!this.fetchModelsButton || !this.fetchModelsStatus || !this.endpointInput || !this.apiKeyInput) {
+   return;
+ }
@@
- if (!this.customModelInput) return;
+ if (!this.customModelInput) {
+   return;
+ }
@@
-        try {
+        try {
@@
-        } finally {
+        } finally {
@@
-  private async testModelConnection(modelName: string): Promise<boolean> {
-    if (!this.modelTestStatus) return false;
+  private async testModelConnection(modelName: string): Promise<boolean> {
+    if (!this.modelTestStatus) {
+      return false;
+    }
@@
-      if (!endpoint) {
-        throw new Error(i18nString('endpointRequired'));
-      }
+      if (!endpoint) {
+        throw new Error(i18nString(UIStrings.endpointRequired));
+      }
@@
-  updateModelSelectors(): void {
-    if (!this.container) return;
+  updateModelSelectors(): void {
+    if (!this.container) {
+      return;
+    }

Also applies to: 119-122, 236-243, 369-376, 398-402, 418-426, 434-441, 466-473


497-515: Annotate async focus handler return type.

Satisfies explicit return type rule.

- const onLiteLLMSelectorFocus = async () => {
+ const onLiteLLMSelectorFocus = async (): Promise<void> => {

303-317: Avoid non-null assertion on DOM element.

Use a local const after null-check.

- // Clear existing list
- this.customModelsList.innerHTML = '';
+ // Clear existing list
+ const list = this.customModelsList;
+ if (!list) return;
+ list.innerHTML = '';
@@
- this.customModelsList!.appendChild(modelRow);
+ list.appendChild(modelRow);

338-346: Replace innerHTML SVG injection with createElementNS to silence unsafe-html rules.

Static strings are relatively safe, but using DOM APIs avoids the rule entirely.

- const checkIcon = document.createElement('span');
- checkIcon.className = 'check-icon';
- checkIcon.innerHTML = `
-   <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-     <path d="M12 5L6.5 10.5L4 8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-     <circle cx="8" cy="8" r="6.25" stroke="currentColor" stroke-width="1.5"/>
-   </svg>
- `;
+ const checkIcon = document.createElement('span');
+ checkIcon.className = 'check-icon';
+ const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
+ svg.setAttribute('width', '16'); svg.setAttribute('height', '16'); svg.setAttribute('viewBox', '0 0 16 16');
+ const p = document.createElementNS('http://www.w3.org/2000/svg', 'path');
+ p.setAttribute('d','M12 5L6.5 10.5L4 8'); p.setAttribute('stroke','currentColor'); p.setAttribute('stroke-width','1.5'); p.setAttribute('stroke-linecap','round'); p.setAttribute('stroke-linejoin','round');
+ const c = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
+ c.setAttribute('cx','8'); c.setAttribute('cy','8'); c.setAttribute('r','6.25'); c.setAttribute('stroke','currentColor'); c.setAttribute('stroke-width','1.5');
+ svg.appendChild(p); svg.appendChild(c); checkIcon.appendChild(svg);
@@
- const trashIcon = document.createElement('span');
- trashIcon.className = 'trash-icon';
- trashIcon.innerHTML = `
-   <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-     <path d="M6 2.5V2C6 1.44772 6.44772 1 7 1H9C9.55228 1 10 1.44772 10 2V2.5M2 2.5H14M12.5 2.5V13C12.5 13.5523 12.0523 14 11.5 14H4.5C3.94772 14 3.5 13.5523 3.5 13V2.5M5.5 5.5V11M8 5.5V11M10.5 5.5V11"
-             stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
-   </svg>
- `;
+ const trashIcon = document.createElement('span');
+ trashIcon.className = 'trash-icon';
+ // Build minimal trash icon or reuse existing icon component here...

Also applies to: 357-365


444-456: Simplify no-else-after-return in testModelConnection.

Conforms to lint and reduces nesting.

-      if (result.success) {
-        this.modelTestStatus.textContent = `Test passed: ${result.message}`;
-        this.modelTestStatus.style.backgroundColor = 'var(--color-accent-green-background)';
-        this.modelTestStatus.style.color = 'var(--color-accent-green)';
-        this.testPassed = true;
-        return true;
-      } else {
-        this.modelTestStatus.textContent = `Test failed: ${result.message}`;
-        this.modelTestStatus.style.backgroundColor = 'var(--color-accent-red-background)';
-        this.modelTestStatus.style.color = 'var(--color-accent-red)';
-        this.testPassed = false;
-        return false;
-      }
+      if (result.success) {
+        this.modelTestStatus.textContent = i18nString(UIStrings.testPassedMessage, {PH1: result.message});
+        this.modelTestStatus.style.backgroundColor = 'var(--color-accent-green-background)';
+        this.modelTestStatus.style.color = 'var(--color-accent-green)';
+        this.testPassed = true;
+        return true;
+      }
+      this.modelTestStatus.textContent = i18nString(UIStrings.testFailedMessage, {PH1: result.message});
+      this.modelTestStatus.style.backgroundColor = 'var(--color-accent-red-background)';
+      this.modelTestStatus.style.color = 'var(--color-accent-red)';
+      this.testPassed = false;
+      return false;
front_end/panels/ai_chat/ui/settings/utils/validation.ts (1)

59-69: Optional: accept schemeless endpoints.

Many users type host:port; consider auto-prefixing http:// for valid URL() parsing.

-export function validateEndpoint(endpoint: string): boolean {
+export function validateEndpoint(endpoint: string): boolean {
   if (!endpoint.trim()) {
     return false;
   }
   try {
-    new URL(endpoint);
+    const candidate = /^https?:\/\//i.test(endpoint) ? endpoint : `http://${endpoint}`;
+    new URL(candidate);
     return true;
   } catch {
     return false;
   }
 }
front_end/panels/ai_chat/ui/settings/providers/OpenAISettings.ts (3)

5-12: Fix import order and add UIStrings import.

-import { BaseProviderSettings } from './BaseProviderSettings.js';
-import { createModelSelector } from '../components/ModelSelectorFactory.js';
-import { i18nString } from '../i18n-strings.js';
+import { createModelSelector } from '../components/ModelSelectorFactory.js';
+import { i18nString, UIStrings } from '../i18n-strings.js';
 import { getValidModelForProvider } from '../utils/validation.js';
 import { getStorageItem, setStorageItem } from '../utils/storage.js';
 import { OPENAI_API_KEY_STORAGE_KEY, MINI_MODEL_STORAGE_KEY, NANO_MODEL_STORAGE_KEY } from '../constants.js';
 import type { GetModelOptionsFunction, AddCustomModelOptionFunction, RemoveCustomModelOptionFunction } from '../types.js';
+
+import { BaseProviderSettings } from './BaseProviderSettings.js';

41-50: Use UIStrings and externalize title.

- apiKeyLabel.textContent = i18nString('apiKeyLabel');
- apiKeyHint.textContent = i18nString('apiKeyHint');
+ apiKeyLabel.textContent = i18nString(UIStrings.apiKeyLabel);
+ apiKeyHint.textContent = i18nString(UIStrings.apiKeyHint);
@@
- this.apiKeyInput.placeholder = 'Enter your OpenAI API key';
+ // TODO: add UIStrings.openAiApiKeyPlaceholder
+ this.apiKeyInput.placeholder = i18nString(UIStrings.openAiApiKeyPlaceholder);
@@
- openaiModelSectionTitle.textContent = 'Model Size Selection';
+ // TODO: add UIStrings.modelSizeSelectionTitle
+ openaiModelSectionTitle.textContent = i18nString(UIStrings.modelSizeSelectionTitle);
@@
- i18nString('miniModelLabel'), i18nString('miniModelDescription'), i18nString('defaultMiniOption'),
+ i18nString(UIStrings.miniModelLabel), i18nString(UIStrings.miniModelDescription), i18nString(UIStrings.defaultMiniOption),
@@
- i18nString('nanoModelLabel'), i18nString('nanoModelDescription'), i18nString('defaultNanoOption'),
+ i18nString(UIStrings.nanoModelLabel), i18nString(UIStrings.nanoModelDescription), i18nString(UIStrings.defaultNanoOption),

Also applies to: 55-57, 91-95, 99-108, 111-120


68-76: Wrap single-line if with braces.

- if (!this.container) return;
+ if (!this.container) {
+   return;
+ }
front_end/panels/ai_chat/ui/settings/utils/storage.ts (3)

8-10: Remove trivially inferrable type on defaulted parameter to satisfy ESLint.

-export function getStorageItem(key: string, defaultValue: string = ''): string {
+export function getStorageItem(key: string, defaultValue = ''): string {
   return localStorage.getItem(key) || defaultValue;
 }

26-32: Same here: drop redundant boolean annotation on default param.

-export function getStorageBoolean(key: string, defaultValue: boolean = false): boolean {
+export function getStorageBoolean(key: string, defaultValue = false): boolean {
   const value = localStorage.getItem(key);
   if (value === null) {
     return defaultValue;
   }
   return value === 'true';
 }

15-21: Harden writes to handle quota/security exceptions; avoid breaking UI.

 export function setStorageItem(key: string, value: string): void {
-  if (value.trim()) {
-    localStorage.setItem(key, value);
-  } else {
-    localStorage.removeItem(key);
-  }
+  try {
+    if (value.trim()) {
+      localStorage.setItem(key, value);
+    } else {
+      localStorage.removeItem(key);
+    }
+  } catch {
+    // noop: storage unavailable or quota exceeded
+  }
 }
 ...
 export function setStorageJSON<T>(key: string, value: T): void {
-  localStorage.setItem(key, JSON.stringify(value));
+  try {
+    localStorage.setItem(key, JSON.stringify(value));
+  } catch {
+    // noop: storage unavailable or quota exceeded
+  }
 }

Also applies to: 59-61

front_end/panels/ai_chat/ui/settings/providers/OpenRouterSettings.ts (8)

5-13: Fix import order per lint (place BaseProviderSettings after sibling imports).

-import { BaseProviderSettings } from './BaseProviderSettings.js';
-import { createModelSelector } from '../components/ModelSelectorFactory.js';
+import { createModelSelector } from '../components/ModelSelectorFactory.js';
 import { i18nString } from '../i18n-strings.js';
 import { getValidModelForProvider } from '../utils/validation.js';
 import { getStorageItem, setStorageItem } from '../utils/storage.js';
 import { OPENROUTER_API_KEY_STORAGE_KEY, MINI_MODEL_STORAGE_KEY, NANO_MODEL_STORAGE_KEY, OPENROUTER_MODELS_CACHE_DURATION_MS } from '../constants.js';
 import type { UpdateModelOptionsFunction, GetModelOptionsFunction, AddCustomModelOptionFunction, RemoveCustomModelOptionFunction, ModelOption } from '../types.js';
 import { LLMClient } from '../../../LLM/LLMClient.js';
 import { createLogger } from '../../../core/Logger.js';
+import { BaseProviderSettings } from './BaseProviderSettings.js';

68-71: Replace innerHTML clearing with safe child removal to satisfy XSS/static checks.

-    this.container.innerHTML = '';
+    while (this.container.firstChild) {
+      this.container.removeChild(this.container.firstChild);
+    }

118-164: Prevent duplicating injected styles across renders; add id and guard.

-    const oauthStyles = document.createElement('style');
-    oauthStyles.textContent = `
+    if (!document.getElementById('openrouter-oauth-styles')) {
+      const oauthStyles = document.createElement('style');
+      oauthStyles.id = 'openrouter-oauth-styles';
+      oauthStyles.textContent = `
       .settings-divider {
         text-align: center;
         margin: 15px 0;
         color: var(--color-text-secondary);
         font-size: 12px;
         font-weight: bold;
       }
       .oauth-button-container {
         margin-bottom: 10px;
       }
       .oauth-button {
         background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
         color: white;
         border: none;
         padding: 10px 20px;
         border-radius: 6px;
         cursor: pointer;
         font-weight: 500;
         transition: all 0.3s ease;
         width: 100%;
         margin-bottom: 8px;
       }
       .oauth-button:hover {
         transform: translateY(-1px);
         box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
       }
       .oauth-button:disabled {
         opacity: 0.6;
         cursor: not-allowed;
         transform: none;
         box-shadow: none;
       }
       .oauth-button.disconnect {
         background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
       }
       .oauth-status {
         font-size: 12px;
         margin-top: 5px;
         padding: 5px 8px;
         border-radius: 4px;
         background: var(--color-background-highlight);
       }
-    `;
-    document.head.appendChild(oauthStyles);
+      `;
+      document.head.appendChild(oauthStyles);
+    }

191-199: Comply with curly rule; avoid single-line early-returns and if-statements without braces.

-    this.oauthButton.addEventListener('click', async () => {
-      if (!this.oauthButton) return;
+    this.oauthButton.addEventListener('click', async () => {
+      if (!this.oauthButton) {
+        return;
+      }
 ...
-      } finally {
-        this.oauthButton.disabled = false;
-        if (!await OAuth.isOAuthAuthenticated()) {
+      } finally {
+        this.oauthButton.disabled = false;
+        if (!await OAuth.isOAuthAuthenticated()) {
           this.oauthButton.textContent = 'Connect with OpenRouter';
           if (this.oauthStatus) {
             this.oauthStatus.style.display = 'none';
           }
         }
       }
     });
 ...
-    this.apiKeyInput.addEventListener('input', async () => {
-      if (!this.apiKeyInput) return;
+    this.apiKeyInput.addEventListener('input', async () => {
+      if (!this.apiKeyInput) {
+        return;
+      }
 ...
-      if (this.fetchModelsButton) {
-        this.fetchModelsButton.disabled = !this.apiKeyInput.value.trim();
-      }
+      if (this.fetchModelsButton) {
+        this.fetchModelsButton.disabled = !this.apiKeyInput.value.trim();
+      }
 ...
-    this.fetchModelsButton.addEventListener('click', async () => {
-      if (!this.fetchModelsButton || !this.fetchModelsStatus || !this.apiKeyInput) return;
+    this.fetchModelsButton.addEventListener('click', async () => {
+      if (!this.fetchModelsButton || !this.fetchModelsStatus || !this.apiKeyInput) {
+        return;
+      }

Also applies to: 225-232, 298-304, 312-315, 335-339, 456-458


245-249: Remove any by narrowing the queried element type; keep null-safe.

-      const chatPanel = document.querySelector('ai-chat-panel') as any;
-      if (chatPanel && typeof chatPanel.refreshCredentials === 'function') {
+      const chatPanel = document.querySelector('ai-chat-panel') as (HTMLElement & { refreshCredentials?: () => void }) | null;
+      if (chatPanel?.refreshCredentials) {
         chatPanel.refreshCredentials();
       }

Apply similarly at Lines 283-287.

Also applies to: 283-287


347-359: Consider OAuth path for model fetch/refresh; current logic requires an API key even when OAuth is connected.

If OpenRouterOAuth exposes a token or LLMClient supports OAuth tokens, add a branch:

  • If OAuth.isOAuthAuthenticated(): use OAuth token to fetch models.
  • Else: use API key.

Please confirm the supported method on LLMClient or OpenRouter provider for OAuth-based fetches and I can propose a concrete patch.

Also applies to: 434-441


80-86: i18n: pass UIStrings members instead of raw string keys to satisfy l10n rule.

-    openrouterApiKeyLabel.textContent = i18nString('openrouterApiKeyLabel');
+    openrouterApiKeyLabel.textContent = i18nString(UIStrings.openrouterApiKeyLabel);
 ...
-    openrouterApiKeyHint.textContent = i18nString('openrouterApiKeyHint');
+    openrouterApiKeyHint.textContent = i18nString(UIStrings.openrouterApiKeyHint);
 ...
-    this.fetchModelsButton.textContent = i18nString('fetchOpenRouterModelsButton');
+    this.fetchModelsButton.textContent = i18nString(UIStrings.fetchOpenRouterModelsButton);
-    this.fetchModelsStatus.textContent = i18nString('fetchingModels');
+    this.fetchModelsStatus.textContent = i18nString(UIStrings.fetchingModels);
 ...
-    this.fetchModelsStatus.textContent = i18nString('fetchedModels', {PH1: actualModelCount});
+    this.fetchModelsStatus.textContent = i18nString(UIStrings.fetchedModels, {PH1: actualModelCount});
 ...
-      i18nString('miniModelLabel'),
-      i18nString('miniModelDescription'),
+      i18nString(UIStrings.miniModelLabel),
+      i18nString(UIStrings.miniModelDescription),
 ...
-      i18nString('defaultMiniOption'),
+      i18nString(UIStrings.defaultMiniOption),
 ...
-      i18nString('nanoModelLabel'),
-      i18nString('nanoModelDescription'),
+      i18nString(UIStrings.nanoModelLabel),
+      i18nString(UIStrings.nanoModelDescription),
 ...
-      i18nString('defaultNanoOption'),
+      i18nString(UIStrings.defaultNanoOption),

Add import { UIStrings } from '../i18n-strings.js'; at the top. Apply similarly for any other i18nString usage in this file. As per coding guidelines.

Also applies to: 325-327, 339-343, 369-372, 489-506


361-364: Centralize cache/auth storage keys; avoid magic strings.

Define in constants.ts:

+export const OPENROUTER_MODELS_CACHE_KEY = 'openrouter_models_cache';
+export const OPENROUTER_MODELS_CACHE_TS_KEY = 'openrouter_models_cache_timestamp';
+export const OPENROUTER_AUTH_METHOD_KEY = 'openrouter_auth_method';

Then use:

- localStorage.setItem('openrouter_models_cache_timestamp', Date.now().toString());
+ localStorage.setItem(OPENROUTER_MODELS_CACHE_TS_KEY, Date.now().toString());
...
- localStorage.setItem('openrouter_models_cache', JSON.stringify(modelOptions));
- localStorage.setItem('openrouter_models_cache_timestamp', Date.now().toString());
+ localStorage.setItem(OPENROUTER_MODELS_CACHE_KEY, JSON.stringify(modelOptions));
+ localStorage.setItem(OPENROUTER_MODELS_CACHE_TS_KEY, Date.now().toString());

Also applies to: 443-449

front_end/panels/ai_chat/ui/settings/advanced/MCPSettings.ts (7)

56-66: Avoid innerHTML clearing; use safe child removal.

-    this.container.innerHTML = '';
+    while (this.container.firstChild) {
+      this.container.removeChild(this.container.firstChild);
+    }
     this.container.className = 'settings-section mcp-section';

333-345: Add explicit return type to the arrow handler; keeps lint happy.

-    const updateBudgetControls = () => {
+    const updateBudgetControls: () => void = () => {
       const maxTools = Math.max(1, Math.min(100, parseInt(mcpMaxToolsInput.value, 10) || 20));
       const maxMcp = Math.max(1, Math.min(50, parseInt(mcpMaxMcpInput.value, 10) || 8));
       setMCPConfig({
         maxToolsPerTurn: maxTools,
         maxMcpPerTurn: maxMcp,
       });
       this.onSettingsSaved();
     };

347-368: Conform to curly rule in formatMCPError; minor cleanup.

-  private formatMCPError(error: string, errorType?: string): {message: string, hint?: string} {
-    if (!errorType) return {message: error};
+  private formatMCPError(error: string, errorType?: string): {message: string, hint?: string} {
+    if (!errorType) {
+      return {message: error};
+    }
     switch (errorType) {
       case 'connection':
         return {message: `Connection failed: ${error}`, hint: 'Check if the MCP server is running and the endpoint URL is correct.'};
       ...
       default:
-        return {message: error};
+        return {message: error};
     }
   }

438-449: no-lonely-if: collapse the else { if (...) } into else-if for readability.

-      } else {
-        if (serverAuthError) {
+      } else if (serverAuthError) {
           statusDot.style.backgroundColor = '#ef4444';
           statusBadge.style.backgroundColor = '#fee2e2';
           statusBadge.style.color = '#991b1b';
           statusBadge.textContent = 'Auth Required';
-        } else {
+      } else {
           statusDot.style.backgroundColor = '#9ca3af';
           statusBadge.style.backgroundColor = '#f3f4f6';
           statusBadge.style.color = '#6b7280';
           statusBadge.textContent = 'Disconnected';
-        }
-      }
+      }

491-504: Remove non-null assertions; guard once and reuse a local.

-      this.mcpStatusDetails!.appendChild(row);
+      const detailsEl = this.mcpStatusDetails;
+      if (detailsEl) {
+        detailsEl.appendChild(row);
+      }
 ...
-        this.mcpStatusDetails!.appendChild(errorDetails);
+        if (detailsEl) {
+          detailsEl.appendChild(errorDetails);
+        }

375-383: Minor perf: compute auth errors map once; avoids per-server scans.

-    const appendServerRow = (server: typeof status.servers[number], isConnected: boolean) => {
+    const authErrorsById = new Map(getStoredAuthErrors().map(e => [e.serverId, e]));
+    const appendServerRow = (server: typeof status.servers[number], isConnected: boolean) => {
 ...
-      const authErrors = getStoredAuthErrors();
-      const serverAuthError = authErrors.find(error => error.serverId === server.id);
+      const serverAuthError = authErrorsById.get(server.id);

Also applies to: 515-543, 575-592


190-231: i18n: ensure i18nString receives UIStrings members, not raw strings; update calls accordingly.

Replace:

  • i18nString('mcpConnectionsHeader') → i18nString(UIStrings.mcpConnectionsHeader)
  • mcp...Hint/Labels/Button texts similarly.
    Add import { UIStrings } from '../i18n-strings.js';. As per coding guidelines.

Also applies to: 255-274, 296-330

front_end/panels/ai_chat/ui/settings/constants.ts (2)

28-29: Complement OpenRouter cache/auth keys so other modules avoid magic strings.

 export const OPENROUTER_MODELS_CACHE_DURATION_MS = 60 * 60 * 1000; // 60 minutes
+export const OPENROUTER_MODELS_CACHE_KEY = 'openrouter_models_cache';
+export const OPENROUTER_MODELS_CACHE_TS_KEY = 'openrouter_models_cache_timestamp';
+export const OPENROUTER_AUTH_METHOD_KEY = 'openrouter_auth_method';

19-24: Heads-up: API keys in localStorage persist and are exposed to any script running in the same origin; ensure threat model accepts this.

If untrusted content can execute, prefer a more protected store (e.g., chrome.storage with extension-scoped isolation) or limit surface by scoping the origin. Document this in SECURITY.md.

Also applies to: 33-39

Comment on lines +5 to +7
import { i18nString } from '../i18n-strings.js';
import * as Common from '../../../../../core/common/common.js';

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Import UIStrings and fix import order.

  • Place Common import first per import/order.
  • Import UIStrings and use it with i18nString.
-import { i18nString } from '../i18n-strings.js';
-import * as Common from '../../../../../core/common/common.js';
+import * as Common from '../../../../../core/common/common.js';
+import { UIStrings, i18nString } from '../ModuleUIStrings.js';

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 ESLint

[error] 6-6: ../../../../../core/common/common.js import should occur before import of ../i18n-strings.js

(import/order)

🤖 Prompt for AI Agents
In front_end/panels/ai_chat/ui/settings/advanced/BrowsingHistorySettings.ts
around lines 5 to 7, the import order should place the Common module before
local modules and you need to import UIStrings and use it with i18nString;
reorder imports so Common is first, add an import for UIStrings from the file
that defines the UI strings (e.g., import { UIStrings } from
'./BrowsingHistorySettings.i18n.js' or the correct path), then replace direct
string literals passed to i18nString with references into UIStrings (i.e.,
i18nString(UIStrings.someKey)) to comply with localization usage and
import/order rules.

Comment on lines +29 to +38
const historyTitle = document.createElement('h3');
historyTitle.className = 'settings-subtitle';
historyTitle.textContent = i18nString('browsingHistoryTitle');
this.container.appendChild(historyTitle);

// Description
const historyDescription = document.createElement('p');
historyDescription.className = 'settings-description';
historyDescription.textContent = i18nString('browsingHistoryDescription');
this.container.appendChild(historyDescription);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Use i18nString with UIStrings members.

Replace literal keys with UIStrings refs.

-    historyTitle.textContent = i18nString('browsingHistoryTitle');
+    historyTitle.textContent = i18nString(UIStrings.browsingHistoryTitle);
@@
-    historyDescription.textContent = i18nString('browsingHistoryDescription');
+    historyDescription.textContent = i18nString(UIStrings.browsingHistoryDescription);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const historyTitle = document.createElement('h3');
historyTitle.className = 'settings-subtitle';
historyTitle.textContent = i18nString('browsingHistoryTitle');
this.container.appendChild(historyTitle);
// Description
const historyDescription = document.createElement('p');
historyDescription.className = 'settings-description';
historyDescription.textContent = i18nString('browsingHistoryDescription');
this.container.appendChild(historyDescription);
const historyTitle = document.createElement('h3');
historyTitle.className = 'settings-subtitle';
historyTitle.textContent = i18nString(UIStrings.browsingHistoryTitle);
this.container.appendChild(historyTitle);
// Description
const historyDescription = document.createElement('p');
historyDescription.className = 'settings-description';
historyDescription.textContent = i18nString(UIStrings.browsingHistoryDescription);
this.container.appendChild(historyDescription);
🧰 Tools
🪛 ESLint

[error] 29-29: Prefer template literals over imperative DOM API calls

(rulesdir/no-imperative-dom-api)


[error] 31-31: Calling i18nString/i18nLazyString requires a UIStrings member as the first argument (e.g., UIStrings.someString).

(rulesdir/l10n-i18nString-call-only-with-uistrings)


[error] 35-35: Prefer template literals over imperative DOM API calls

(rulesdir/no-imperative-dom-api)


[error] 37-37: Calling i18nString/i18nLazyString requires a UIStrings member as the first argument (e.g., UIStrings.someString).

(rulesdir/l10n-i18nString-call-only-with-uistrings)

🤖 Prompt for AI Agents
In front_end/panels/ai_chat/ui/settings/advanced/BrowsingHistorySettings.ts
around lines 29 to 38, the i18nString calls use literal keys; replace those
literals with the UIStrings members (e.g. use
i18nString(UIStrings.browsingHistoryTitle) and
i18nString(UIStrings.browsingHistoryDescription)), and ensure UIStrings is
imported/available in this module if not already so the calls reference the
typed UIStrings constants instead of raw string keys.

Comment on lines +5 to +16
import { i18nString } from '../i18n-strings.js';
import {
getEvaluationConfig,
setEvaluationConfig,
isEvaluationEnabled,
connectToEvaluationService,
disconnectFromEvaluationService,
getEvaluationClientId,
isEvaluationConnected
} from '../../../common/EvaluationConfig.js';
import { createLogger } from '../../../core/Logger.js';

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Reorder imports and bring in UIStrings for proper i18n usage.

-import { i18nString } from '../i18n-strings.js';
+import { UIStrings, i18nString } from '../ModuleUIStrings.js';
@@
-import { createLogger } from '../../../core/Logger.js';
+import { createLogger } from '../../../core/Logger.js'; // keep Logger before i18n per import/order

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 ESLint

[error] 5-5: ../i18n-strings.js import should occur after import of ../../../core/Logger.js

(import/order)

🤖 Prompt for AI Agents
In front_end/panels/ai_chat/ui/settings/advanced/EvaluationSettings.ts around
lines 5–16, reorder the imports so related i18n imports come first, followed by
the EvaluationConfig utilities, then core modules (Logger); add an import for
the UIStrings export from ../i18n-strings.js (alongside the existing i18nString
import) and update usages to reference UIStrings for any hard-coded or
previously direct i18nString keys so the component uses the centralized
UIStrings definitions; ensure import ordering follows project lint rules and run
type checks.

Comment on lines +41 to +70
const evaluationSectionTitle = document.createElement('h3');
evaluationSectionTitle.className = 'settings-subtitle';
evaluationSectionTitle.textContent = i18nString('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('evaluationEnabled');
evaluationEnabledContainer.appendChild(evaluationEnabledLabel);

const evaluationEnabledHint = document.createElement('div');
evaluationEnabledHint.className = 'settings-hint';
evaluationEnabledHint.textContent = i18nString('evaluationEnabledHint');
this.container.appendChild(evaluationEnabledHint);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Replace literal i18n keys with UIStrings members.

These calls will fail the l10n rule.

-    evaluationSectionTitle.textContent = i18nString('evaluationSection');
+    evaluationSectionTitle.textContent = i18nString(UIStrings.evaluationSection);
@@
-    evaluationEnabledLabel.textContent = i18nString('evaluationEnabled');
+    evaluationEnabledLabel.textContent = i18nString(UIStrings.evaluationEnabled);
@@
-    evaluationEnabledHint.textContent = i18nString('evaluationEnabledHint');
+    evaluationEnabledHint.textContent = i18nString(UIStrings.evaluationEnabledHint);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const evaluationSectionTitle = document.createElement('h3');
evaluationSectionTitle.className = 'settings-subtitle';
evaluationSectionTitle.textContent = i18nString('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('evaluationEnabled');
evaluationEnabledContainer.appendChild(evaluationEnabledLabel);
const evaluationEnabledHint = document.createElement('div');
evaluationEnabledHint.className = 'settings-hint';
evaluationEnabledHint.textContent = i18nString('evaluationEnabledHint');
this.container.appendChild(evaluationEnabledHint);
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);
🧰 Tools
🪛 ESLint

[error] 41-41: Prefer template literals over imperative DOM API calls

(rulesdir/no-imperative-dom-api)


[error] 43-43: Calling i18nString/i18nLazyString requires a UIStrings member as the first argument (e.g., UIStrings.someString).

(rulesdir/l10n-i18nString-call-only-with-uistrings)


[error] 50-50: Prefer template literals over imperative DOM API calls

(rulesdir/no-imperative-dom-api)


[error] 64-64: Calling i18nString/i18nLazyString requires a UIStrings member as the first argument (e.g., UIStrings.someString).

(rulesdir/l10n-i18nString-call-only-with-uistrings)


[error] 67-67: Prefer template literals over imperative DOM API calls

(rulesdir/no-imperative-dom-api)


[error] 69-69: Calling i18nString/i18nLazyString requires a UIStrings member as the first argument (e.g., UIStrings.someString).

(rulesdir/l10n-i18nString-call-only-with-uistrings)

🤖 Prompt for AI Agents
In front_end/panels/ai_chat/ui/settings/advanced/EvaluationSettings.ts around
lines 41 to 70, the file currently calls i18nString with literal string keys
(e.g. 'evaluationSection', 'evaluationEnabled', 'evaluationEnabledHint') which
violates the l10n rule; replace these literal keys with members from the
UIStrings object (e.g. UIStrings.evaluationSection, UIStrings.evaluationEnabled,
UIStrings.evaluationEnabledHint), ensure UIStrings is imported/defined in the
module and that i18nString is invoked with the UIStrings member references
instead of raw string literals so the localization tooling can pick them up.

Comment on lines +94 to +109
// 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)';
}
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add explicit return type and localize connection status texts.

Also externalize the two status strings in ModuleUIStrings (e.g., evaluationConnectedStatus, evaluationDisconnectedStatus).

-    const updateConnectionStatus = () => {
+    const updateConnectionStatus = (): void => {
       const isConnected = isEvaluationConnected();
@@
-        connectionStatusText.textContent = 'Connected to evaluation server';
+        connectionStatusText.textContent = i18nString(UIStrings.evaluationConnectedStatus);
@@
-        connectionStatusText.textContent = 'Not connected';
+        connectionStatusText.textContent = i18nString(UIStrings.evaluationDisconnectedStatus);

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 ESLint

[error] 95-95: Missing return type on function.

(@typescript-eslint/explicit-function-return-type)

🤖 Prompt for AI Agents
In front_end/panels/ai_chat/ui/settings/advanced/EvaluationSettings.ts around
lines 94 to 109, add an explicit return type to updateConnectionStatus (e.g.,
updateConnectionStatus(): void) and ensure local typing for isConnected
(boolean); replace the hardcoded status strings 'Connected to evaluation server'
and 'Not connected' with localized values from ModuleUIStrings (e.g.,
ModuleUIStrings.evaluationConnectedStatus and
ModuleUIStrings.evaluationDisconnectedStatus), import ModuleUIStrings at the top
if not already imported, and use those localized strings when setting
connectionStatusText.textContent while keeping the same color and dot logic.

Comment on lines +284 to +293
// Update model selectors
this.updateModelSelectors();

// Hide status after a delay
setTimeout(() => {
if (this.modelTestStatus) {
this.modelTestStatus.style.display = 'none';
}
}, 3000);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Eliminate duplicate refresh + rebuild after fetch.

Avoid flicker by choosing one strategy (refresh or rebuild).

- // Update LiteLLM model selections
- this.updateModelSelectors();
+ // If you refreshed existing selectors above, do not rebuild here to prevent flicker.

Also applies to: 174-176

Comment on lines +42 to +44
// Dynamically imported OAuth module
private OpenRouterOAuth: any = null;

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Avoid any; type the OAuth surface explicitly and narrow the return type.

-  private OpenRouterOAuth: any = null;
+  type OpenRouterOAuthAPI = {
+    isOAuthAuthenticated(): Promise<boolean>;
+    startAuthFlow(): Promise<void>;
+    revokeToken(): Promise<void>;
+    switchToManualApiKey(): void;
+  };
+  private OpenRouterOAuth: OpenRouterOAuthAPI | null = null;
 ...
-  private async getOpenRouterOAuth(): Promise<any> {
+  private async getOpenRouterOAuth(): Promise<OpenRouterOAuthAPI> {
     if (!this.OpenRouterOAuth) {
       const module = await import('../../../auth/OpenRouterOAuth.js');
-      this.OpenRouterOAuth = module.OpenRouterOAuth;
+      this.OpenRouterOAuth = module.OpenRouterOAuth as OpenRouterOAuthAPI;
     }
     return this.OpenRouterOAuth;
   }

Also applies to: 60-66

🧰 Tools
🪛 ESLint

[error] 43-43: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)

🤖 Prompt for AI Agents
In front_end/panels/ai_chat/ui/settings/providers/OpenRouterSettings.ts around
lines 42-44 and 60-66, the dynamically imported OAuth module and related
variables are typed as any; replace these with a concrete interface that
describes the OAuth surface (methods, properties, and return types you use,
e.g., init, authorize, getToken, revokeToken, config shape). Change the private
OpenRouterOAuth property to that interface or null, and change any dynamic
import return types to Promise<ThatOAuthInterface | null> (or the module
namespace type if you import a named export), updating all usages to match the
narrowed types so callers use the defined methods/props instead of any.

Comment on lines +166 to +187
const updateOAuthButton = async () => {
const OAuth = await this.getOpenRouterOAuth();
if (await OAuth.isOAuthAuthenticated()) {
if (this.oauthButton) {
this.oauthButton.textContent = 'Disconnect OpenRouter';
this.oauthButton.classList.add('disconnect');
}
if (this.oauthStatus) {
this.oauthStatus.textContent = '✓ Connected via OpenRouter account';
this.oauthStatus.style.color = 'var(--color-accent-green)';
this.oauthStatus.style.display = 'block';
}
} else {
if (this.oauthButton) {
this.oauthButton.textContent = 'Connect with OpenRouter';
this.oauthButton.classList.remove('disconnect');
}
if (this.oauthStatus) {
this.oauthStatus.style.display = 'none';
}
}
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add explicit return type and enable fetch button when OAuth is connected (no API key).

-    const updateOAuthButton = async () => {
+    const updateOAuthButton: () => Promise<void> = async () => {
       const OAuth = await this.getOpenRouterOAuth();
       if (await OAuth.isOAuthAuthenticated()) {
         if (this.oauthButton) {
           this.oauthButton.textContent = 'Disconnect OpenRouter';
           this.oauthButton.classList.add('disconnect');
         }
+        if (this.fetchModelsButton) {
+          this.fetchModelsButton.disabled = false;
+        }
         if (this.oauthStatus) {
           this.oauthStatus.textContent = '✓ Connected via OpenRouter account';
           this.oauthStatus.style.color = 'var(--color-accent-green)';
           this.oauthStatus.style.display = 'block';
         }
       } else {
         if (this.oauthButton) {
           this.oauthButton.textContent = 'Connect with OpenRouter';
           this.oauthButton.classList.remove('disconnect');
         }
+        if (this.fetchModelsButton && this.apiKeyInput) {
+          this.fetchModelsButton.disabled = !this.apiKeyInput.value.trim();
+        }
         if (this.oauthStatus) {
           this.oauthStatus.style.display = 'none';
         }
       }
     };
🧰 Tools
🪛 ESLint

[error] 166-166: Missing return type on function.

(@typescript-eslint/explicit-function-return-type)

Comment on lines +39 to +41
dataset: {
modelType?: string;
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Satisfy member-delimiter-style: use commas in inline type.

-  dataset: {
-    modelType?: string;
-  };
+  dataset: {
+    modelType?: string,
+  };

As per static analysis hints.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
dataset: {
modelType?: string;
};
dataset: {
modelType?: string,
};
🧰 Tools
🪛 ESLint

[error] 40-40: Expected a comma.

(@stylistic/member-delimiter-style)

🤖 Prompt for AI Agents
In front_end/panels/ai_chat/ui/settings/types.ts around lines 39 to 41, the
inline type for dataset uses a semicolon as a member delimiter which violates
member-delimiter-style; replace the semicolon with a comma so the property is
comma-delimited (i.e., change "modelType?: string;" to "modelType?: string,") to
satisfy the lint rule.

Comment on lines +73 to +75
export function isVectorDBEnabled(): boolean {
return getStorageBoolean('ai_chat_vector_db_enabled', false);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Avoid hardcoded storage key; use the exported constant to prevent drift.

+import { VECTOR_DB_ENABLED_KEY } from '../constants.js';
 ...
 export function isVectorDBEnabled(): boolean {
-  return getStorageBoolean('ai_chat_vector_db_enabled', false);
+  return getStorageBoolean(VECTOR_DB_ENABLED_KEY, false);
 }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In front_end/panels/ai_chat/ui/settings/utils/storage.ts around lines 73 to 75,
the function isVectorDBEnabled() uses a hardcoded storage key string; replace
that string with the module-exported constant for the ai_chat vector DB key
(import it from its defining module if not already imported) and pass the same
default false value so the call becomes
getStorageBoolean(EXPORTED_CONSTANT_NAME, false). Ensure the import is
added/organized and tests/usage updated to reference the same constant to
prevent future drift.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants