semantic update

This commit is contained in:
2026-02-20 10:41:15 +03:00
parent d7e4919d54
commit af74841765
18 changed files with 1856 additions and 1326 deletions

View File

@@ -73,6 +73,40 @@
- 📝 Recursive helper to write entity tree to Markdown with tier badges and enhanced details.
- ƒ **to_dict** (`Function`) `[TRIVIAL]`
- 📝 Auto-detected function (orphan)
- 📦 **PluginExampleShot** (`Module`)
- 📝 Reference implementation of a plugin following GRACE standards.
- 🔗 IMPLEMENTS -> `[DEF:Std:Plugin]`
- ƒ **get_schema** (`Function`)
- 📝 Defines input validation schema.
- ƒ **execute** (`Function`)
- 📝 Core plugin logic with structured logging and progress reporting.
- ƒ **id** (`Function`) `[TRIVIAL]`
- 📝 Auto-detected function (orphan)
- ƒ **name** (`Function`) `[TRIVIAL]`
- 📝 Auto-detected function (orphan)
- ƒ **description** (`Function`) `[TRIVIAL]`
- 📝 Auto-detected function (orphan)
- ƒ **version** (`Function`) `[TRIVIAL]`
- 📝 Auto-detected function (orphan)
- 📦 **BackendRouteShot** (`Module`)
- 📝 Reference implementation of a task-based route using GRACE-Poly.
- 🔗 IMPLEMENTS -> `[DEF:Std:API_FastAPI]`
- ƒ **create_task** (`Function`)
- 📝 Create and start a new task using TaskManager. Non-blocking.
- 🔗 CALLS -> `task_manager.create_task`
- 🧩 **FrontendComponentShot** (`Component`)
- 📝 Reference implementation of a task-spawning component using
- 🏗️ Layer: UI
- 📥 Props: plugin_id: any, params: any
- ⬅️ READS_FROM `lib`
- ⬅️ READS_FROM `t`
- 📦 **DashboardTypes** (`Module`) `[TRIVIAL]`
- 📝 TypeScript interfaces for Dashboard entities
- 🏗️ Layer: Domain
- 🧩 **Counter** (`Component`) `[TRIVIAL]`
- 📝 Simple counter demo component
- 🏗️ Layer: UI
- ➡️ WRITES_TO `state`
- 📦 **stores_module** (`Module`)
- 📝 Global state management using Svelte stores.
- 🏗️ Layer: UI-State
@@ -116,6 +150,11 @@
- 📝 Generic request wrapper.
- 📦 **api** (`Data`)
- 📝 API client object with specific methods.
- 📦 **Utils** (`Module`) `[TRIVIAL]`
- 📝 General utility functions (class merging)
- 🏗️ Layer: Infra
- ƒ **cn** (`Function`) `[TRIVIAL]`
- 📝 Auto-detected function (orphan)
- 🗄️ **authStore** (`Store`)
- 📝 Manages the global authentication state on the frontend.
- 🏗️ Layer: Feature
@@ -131,9 +170,9 @@
- 📝 Clears authentication state and storage.
- ƒ **setLoading** (`Function`)
- 📝 Updates the loading state.
- 📦 **debounce** (`Module`) `[TRIVIAL]`
- 📝 Auto-generated module for frontend/src/lib/utils/debounce.js
- 🏗️ Layer: Unknown
- 📦 **Debounce** (`Module`) `[TRIVIAL]`
- 📝 Debounce utility for limiting function execution rate
- 🏗️ Layer: Infra
- ƒ **debounce** (`Function`) `[TRIVIAL]`
- 📝 Auto-detected function (orphan)
- 🗄️ **taskDrawer** (`Store`) `[CRITICAL]`
@@ -181,8 +220,9 @@
- 📝 Unit tests for sidebar store
- 🏗️ Layer: Domain (Tests)
- ƒ **test_sidebar_initial_state** (`Function`)
- ƒ **test_toggleSidebar** (`Function`)
- ƒ **test_setActiveItem** (`Function`)
- ƒ **test_toggleSidebar** (`Function`)
- ƒ **test_setActiveItem** (`Function`)
- ƒ **test_mobile_functions** (`Function`)
- 📦 **frontend.src.lib.stores.__tests__.test_activity** (`Module`)
- 📝 Unit tests for activity store
- 🏗️ Layer: UI
@@ -202,7 +242,9 @@
- 🧩 **Select** (`Component`) `[TRIVIAL]`
- 📝 Standardized dropdown selection component.
- 🏗️ Layer: Atom
- 📥 Props: label: string , value: string | number , disabled: boolean
- ⬅️ READS_FROM `lib`
- ➡️ WRITES_TO `bindable`
- ➡️ WRITES_TO `props`
- 📦 **ui** (`Module`) `[TRIVIAL]`
- 📝 Central export point for standardized UI components.
- 🏗️ Layer: Atom
@@ -210,21 +252,26 @@
- 🧩 **PageHeader** (`Component`) `[TRIVIAL]`
- 📝 Standardized page header with title and action area.
- 🏗️ Layer: Atom
- 📥 Props: title: string
- ⬅️ READS_FROM `lib`
- ➡️ WRITES_TO `props`
- 🧩 **Card** (`Component`) `[TRIVIAL]`
- 📝 Standardized container with padding and elevation.
- 🏗️ Layer: Atom
- 📥 Props: title: string
- ⬅️ READS_FROM `lib`
- ➡️ WRITES_TO `props`
- 🧩 **Button** (`Component`) `[TRIVIAL]`
- 📝 Define component interface and default values.
- 📝 Define component interface and default values (Svelte 5 Runes).
- 🏗️ Layer: Atom
- 🔒 Invariant: Supports accessible labels and keyboard navigation.
- 📥 Props: isLoading: boolean , disabled: boolean
- ⬅️ READS_FROM `lib`
- ➡️ WRITES_TO `props`
- 🧩 **Input** (`Component`) `[TRIVIAL]`
- 📝 Standardized text input component with label and error handling.
- 🏗️ Layer: Atom
- 🔒 Invariant: Consistent spacing and focus states.
- 📥 Props: label: string , value: string , placeholder: string , error: string , disabled: boolean
- ⬅️ READS_FROM `lib`
- ➡️ WRITES_TO `bindable`
- ➡️ WRITES_TO `props`
- 🧩 **LanguageSwitcher** (`Component`) `[TRIVIAL]`
- 📝 Dropdown component to switch between supported languages.
- 🏗️ Layer: Atom
@@ -293,10 +340,9 @@
- 📝 Display page hierarchy navigation
- 🏗️ Layer: UI
- 🔒 Invariant: Always shows current page path
- 📥 Props: maxVisible: any
- ⬅️ READS_FROM `app`
- ⬅️ READS_FROM `lib`
- READS_FROM `page`
- WRITES_TO `props`
- 📦 **Breadcrumbs** (`Module`) `[TRIVIAL]`
- 📝 Auto-generated module for frontend/src/lib/components/layout/Breadcrumbs.svelte
- 🏗️ Layer: Unknown
@@ -328,6 +374,12 @@
- 📝 Auto-detected function (orphan)
- ƒ **disconnectWebSocket** (`Function`) `[TRIVIAL]`
- 📝 Auto-detected function (orphan)
- 📦 **ErrorPage** (`Page`)
- 📝 Global error page displaying HTTP status and messages
- 🏗️ Layer: UI
- 📦 **RootLayoutConfig** (`Module`) `[TRIVIAL]`
- 📝 Root layout configuration (SPA mode)
- 🏗️ Layer: Infra
- 📦 **HomePage** (`Page`) `[CRITICAL]`
- 📝 Redirect to Dashboard Hub as per UX requirements
- 🏗️ Layer: UI
@@ -692,8 +744,9 @@
- 🧩 **PasswordPrompt** (`Component`)
- 📝 A modal component to prompt the user for database passwords when a migration task is paused.
- 🏗️ Layer: UI
- 📥 Props: show: any, databases: any, errorMessage: any
- ⚡ Events: cancel, resume
- ➡️ WRITES_TO `props`
- ⬅️ READS_FROM `effect`
- ƒ **handleSubmit** (`Function`)
- 📝 Validates and dispatches the passwords to resume the task.
- ƒ **handleCancel** (`Function`)
@@ -703,6 +756,7 @@
- 🏗️ Layer: Feature
- 🔒 Invariant: Each source database can be mapped to one target database.
- ⚡ Events: update
- ➡️ WRITES_TO `props`
- ƒ **updateMapping** (`Function`)
- 📝 Updates a mapping for a specific source database.
- ƒ **getSuggestion** (`Function`)
@@ -711,13 +765,12 @@
- 📝 Displays detailed logs for a specific task inline or in a modal using TaskLogPanel.
- 🏗️ Layer: UI
- 🔒 Invariant: Real-time logs are always appended without duplicates.
- 📥 Props: show: any, inline: any, taskId: any, taskStatus: any, realTimeLogs: any
- ⚡ Events: close
- READS_FROM `t`
- WRITES_TO `bindable`
- ➡️ WRITES_TO `props`
- ➡️ WRITES_TO `state`
- 📦 **handleRealTimeLogs** (`Action`)
- 📝 Append real-time logs as they arrive from WebSocket, preventing duplicates */
- ƒ **fetchLogs** (`Function`)
- 📝 Fetches logs for the current task from API (polling fallback).
- 📦 **TaskLogViewer** (`Module`) `[TRIVIAL]`
- 📝 Auto-generated module for frontend/src/components/TaskLogViewer.svelte
- 🏗️ Layer: Unknown
@@ -732,8 +785,8 @@
- 📝 Prompts the user to provide a database mapping when one is missing during migration.
- 🏗️ Layer: Feature
- 🔒 Invariant: Modal blocks migration progress until resolved or cancelled.
- 📥 Props: show: boolean , sourceDbName: string , sourceDbUuid: string
- ⚡ Events: cancel, resolve
- ➡️ WRITES_TO `props`
- ƒ **resolve** (`Function`)
- 📝 Dispatches the resolution event with the selected mapping.
- ƒ **cancel** (`Function`)
@@ -742,10 +795,10 @@
- 📝 Displays a grid of dashboards with selection and pagination.
- 🏗️ Layer: Component
- 🔒 Invariant: Selected IDs must be a subset of available dashboards.
- 📥 Props: dashboards: DashboardMetadata[] , selectedIds: number[] , environmentId: string
- ⚡ Events: selectionChanged
- ➡️ WRITES_TO `props`
- ➡️ WRITES_TO `derived`
- ➡️ WRITES_TO `t`
- ⬅️ READS_FROM `t`
- ƒ **handleValidate** (`Function`)
- 📝 Triggers dashboard validation task.
- ƒ **handleSort** (`Function`)
@@ -816,8 +869,8 @@
- 🧩 **TaskList** (`Component`)
- 📝 Displays a list of tasks with their status and execution details.
- 🏗️ Layer: Component
- 📥 Props: tasks: Array<any> , loading: boolean
- ⚡ Events: select
- ➡️ WRITES_TO `props`
- ➡️ WRITES_TO `t`
- ⬅️ READS_FROM `t`
- ƒ **getStatusColor** (`Function`)
@@ -829,8 +882,8 @@
- 🧩 **DynamicForm** (`Component`)
- 📝 Generates a form dynamically based on a JSON schema.
- 🏗️ Layer: UI
- 📥 Props: schema: any
- ⚡ Events: submit
- ➡️ WRITES_TO `props`
- ƒ **handleSubmit** (`Function`)
- 📝 Dispatches the submit event with the form data.
- ƒ **initializeForm** (`Function`)
@@ -839,8 +892,8 @@
- 📝 Provides a UI component for selecting source and target environments.
- 🏗️ Layer: Feature
- 🔒 Invariant: Source and target environments must be selectable from the list of configured environments.
- 📥 Props: label: string , selectedId: string
- ⚡ Events: change
- ➡️ WRITES_TO `props`
- ƒ **handleSelect** (`Function`)
- 📝 Dispatches the selection change event.
- 🧩 **ProtectedRoute** (`Component`) `[TRIVIAL]`
@@ -850,11 +903,13 @@
- ⬅️ READS_FROM `app`
- ⬅️ READS_FROM `auth`
- 🧩 **TaskLogPanel** (`Component`)
- 📝 Component properties and state.
- 📝 Combines log filtering and display into a single cohesive dark-themed panel.
- 🏗️ Layer: UI
- 🔒 Invariant: Must always display logs in chronological order and respect auto-scroll preference.
- 📥 Props: logs: any, autoScroll: any
- ⚡ Events: filterChange
- ➡️ WRITES_TO `bindable`
- ➡️ WRITES_TO `props`
- ➡️ WRITES_TO `state`
- 📦 **TaskLogPanel** (`Module`) `[TRIVIAL]`
- 📝 Auto-generated module for frontend/src/components/tasks/TaskLogPanel.svelte
- 🏗️ Layer: Unknown
@@ -869,7 +924,9 @@
- 🧩 **LogFilterBar** (`Component`)
- 📝 Compact filter toolbar for logs — level, source, and text search in a single dense row.
- 🏗️ Layer: UI
- 📥 Props: availableSources: any, selectedLevel: any, selectedSource: any, searchText: any
- ➡️ WRITES_TO `bindable`
- ➡️ WRITES_TO `props`
- ➡️ WRITES_TO `derived`
- 📦 **LogFilterBar** (`Module`) `[TRIVIAL]`
- 📝 Auto-generated module for frontend/src/components/tasks/LogFilterBar.svelte
- 🏗️ Layer: Unknown
@@ -884,21 +941,15 @@
- 🧩 **LogEntryRow** (`Component`)
- 📝 Renders a single log entry with stacked layout optimized for narrow drawer panels.
- 🏗️ Layer: UI
- 📥 Props: log: any, showSource: any
- ➡️ WRITES_TO `props`
- ➡️ WRITES_TO `derived`
- ƒ **formatTime** (`Function`)
- 📝 Format ISO timestamp to HH:MM:SS */
- 📦 **LogEntryRow** (`Module`) `[TRIVIAL]`
- 📝 Auto-generated module for frontend/src/components/tasks/LogEntryRow.svelte
- 🏗️ Layer: Unknown
- ƒ **getLevelClass** (`Function`) `[TRIVIAL]`
- 📝 Auto-detected function (orphan)
- ƒ **getSourceClass** (`Function`) `[TRIVIAL]`
- 📝 Auto-detected function (orphan)
- 🧩 **FileList** (`Component`)
- 📝 Displays a table of files with metadata and actions.
- 🏗️ Layer: UI
- 📥 Props: files: any
- ⚡ Events: delete, navigate
- ➡️ WRITES_TO `props`
- ➡️ WRITES_TO `t`
- ⬅️ READS_FROM `t`
- ƒ **isDirectory** (`Function`)
@@ -911,6 +962,7 @@
- 📝 Provides a form for uploading files to a specific category.
- 🏗️ Layer: UI
- ⚡ Events: uploaded
- ➡️ WRITES_TO `props`
- ⬅️ READS_FROM `t`
- ➡️ WRITES_TO `t`
- ƒ **handleUpload** (`Function`)
@@ -964,7 +1016,7 @@
- 🧩 **CommitHistory** (`Component`)
- 📝 Displays the commit history for a specific dashboard.
- 🏗️ Layer: Component
- 📥 Props: dashboardId: any
- ➡️ WRITES_TO `props`
- ⬅️ READS_FROM `t`
- ➡️ WRITES_TO `t`
- ƒ **onMount** (`Function`)
@@ -975,8 +1027,9 @@
- 📝 Modal for deploying a dashboard to a target environment.
- 🏗️ Layer: Component
- 🔒 Invariant: Cannot deploy without a selected environment.
- 📥 Props: dashboardId: any, show: any
- ⚡ Events: deploy
- ➡️ WRITES_TO `props`
- ⬅️ READS_FROM `effect`
- 📦 **loadStatus** (`Watcher`)
- ƒ **loadEnvironments** (`Function`)
- 📝 Fetch available environments from API.
@@ -986,8 +1039,8 @@
- 📝 UI for resolving merge conflicts (Keep Mine / Keep Theirs).
- 🏗️ Layer: Component
- 🔒 Invariant: User must resolve all conflicts before saving.
- 📥 Props: conflicts: any, show: any
- ⚡ Events: resolve
- ➡️ WRITES_TO `props`
- ƒ **resolve** (`Function`)
- 📝 Set resolution strategy for a file.
- ƒ **handleSave** (`Function`)
@@ -995,8 +1048,9 @@
- 🧩 **CommitModal** (`Component`)
- 📝 Модальное окно для создания коммита с просмотром изменений (diff).
- 🏗️ Layer: Component
- 📥 Props: dashboardId: any, show: any
- ⚡ Events: commit
- ➡️ WRITES_TO `props`
- ⬅️ READS_FROM `effect`
- ƒ **handleGenerateMessage** (`Function`)
- 📝 Generates a commit message using LLM.
- ƒ **loadStatus** (`Function`)
@@ -1006,8 +1060,8 @@
- 🧩 **BranchSelector** (`Component`)
- 📝 UI для выбора и создания веток Git.
- 🏗️ Layer: Component
- 📥 Props: dashboardId: any, currentBranch: any
- ⚡ Events: change
- ➡️ WRITES_TO `props`
- ⬅️ READS_FROM `t`
- ƒ **onMount** (`Function`)
- 📝 Load branches when component is mounted.
@@ -1022,7 +1076,7 @@
- 🧩 **GitManager** (`Component`)
- 📝 Центральный компонент для управления Git-операциями конкретного дашборда.
- 🏗️ Layer: Component
- 📥 Props: dashboardId: any, dashboardTitle: any, show: any
- ➡️ WRITES_TO `props`
- ➡️ WRITES_TO `t`
- ⬅️ READS_FROM `t`
- ƒ **checkStatus** (`Function`)
@@ -1038,7 +1092,7 @@
- 🧩 **DocPreview** (`Component`)
- 📝 UI component for previewing generated dataset documentation before saving.
- 🏗️ Layer: UI
- 📥 Props: documentation: any, onSave: any, onCancel: any
- ➡️ WRITES_TO `props`
- ➡️ WRITES_TO `t`
- ⬅️ READS_FROM `t`
- 📦 **DocPreview** (`Module`) `[TRIVIAL]`
@@ -1049,7 +1103,7 @@
- 🧩 **ProviderConfig** (`Component`)
- 📝 UI form for managing LLM provider configurations.
- 🏗️ Layer: UI
- 📥 Props: providers: any, onSave: any
- ➡️ WRITES_TO `props`
- ➡️ WRITES_TO `t`
- ⬅️ READS_FROM `t`
- 📦 **ProviderConfig** (`Module`) `[TRIVIAL]`
@@ -1099,14 +1153,14 @@
- 📝 Provides a WebSocket endpoint for real-time log streaming of a task with server-side filtering.
- 📦 **StaticFiles** (`Mount`)
- 📝 Mounts the frontend build directory to serve static assets.
- ƒ **serve_spa** (`Function`)
- 📝 Serves the SPA frontend for any path not matched by API routes.
- ƒ **read_root** (`Function`)
- 📝 A simple root endpoint to confirm that the API is running when frontend is missing.
- ƒ **network_error_handler** (`Function`) `[TRIVIAL]`
- 📝 Auto-detected function (orphan)
- ƒ **matches_filters** (`Function`) `[TRIVIAL]`
- 📝 Auto-detected function (orphan)
- ƒ **serve_spa** (`Function`) `[TRIVIAL]`
- 📝 Auto-detected function (orphan)
- 📦 **Dependencies** (`Module`)
- 📝 Manages creation and provision of shared application dependencies, such as PluginLoader and TaskManager, to avoid circular imports.
- 🏗️ Layer: Core

Binary file not shown.

View File

@@ -241,6 +241,10 @@ frontend_path = project_root / "frontend" / "build"
if frontend_path.exists():
app.mount("/_app", StaticFiles(directory=str(frontend_path / "_app")), name="static")
# [DEF:serve_spa:Function]
# @PURPOSE: Serves the SPA frontend for any path not matched by API routes.
# @PRE: frontend_path exists.
# @POST: Returns the requested file or index.html.
@app.get("/{file_path:path}", include_in_schema=False)
async def serve_spa(file_path: str):
# Only serve SPA for non-API paths

View File

@@ -18,3 +18,4 @@ def __getattr__(name):
from .resource_service import ResourceService
return ResourceService
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
# [/DEF:backend.src.services:Module]

Binary file not shown.

View File

@@ -13,8 +13,8 @@
const dispatch = createEventDispatcher();
let passwords = {};
let submitting = false;
let passwords = $state({});
let submitting = $state(false);
// [DEF:handleSubmit:Function]
// @PURPOSE: Validates and dispatches the passwords to resume the task.
@@ -69,7 +69,7 @@
<div
class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"
aria-hidden="true"
on:click={handleCancel}
onclick={handleCancel}
></div>
<span
@@ -126,7 +126,7 @@
{/if}
<form
on:submit|preventDefault={handleSubmit}
onsubmit={(e) => { e.preventDefault(); handleSubmit(); }}
class="space-y-4"
>
{#each databases as dbName}
@@ -158,7 +158,7 @@
<button
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-indigo-600 text-base font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:ml-3 sm:w-auto sm:text-sm disabled:opacity-50"
on:click={handleSubmit}
onclick={handleSubmit}
disabled={submitting}
>
{submitting ? "Resuming..." : "Resume Migration"}
@@ -166,7 +166,7 @@
<button
type="button"
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"
on:click={handleCancel}
onclick={handleCancel}
disabled={submitting}
>
Cancel

View File

@@ -123,7 +123,7 @@
<span>{error}</span>
<button
class="bg-terminal-surface text-terminal-text-subtle border border-terminal-border rounded-md px-3 py-1 text-xs cursor-pointer transition-all hover:bg-terminal-border hover:text-terminal-text-bright"
on:click={handleRefresh}>Retry</button
onclick={handleRefresh}>Retry</button
>
</div>
{:else}
@@ -149,11 +149,11 @@
<div
class="fixed inset-0 bg-gray-500/75 transition-opacity"
aria-hidden="true"
on:click={() => {
onclick={() => {
show = false;
dispatch("close");
}}
on:keydown={(e) => e.key === "Escape" && (show = false)}
onkeydown={(e) => e.key === "Escape" && (show = false)}
role="presentation"
></div>
@@ -170,7 +170,7 @@
</h3>
<button
class="text-gray-500 hover:text-gray-300"
on:click={() => {
onclick={() => {
show = false;
dispatch("close");
}}

View File

@@ -7,67 +7,74 @@
-->
<script>
import { onMount } from 'svelte';
import { t } from '../../lib/i18n';
import { requestApi } from '../../lib/api';
import { onMount } from "svelte";
import { t } from "../../lib/i18n";
import { requestApi } from "../../lib/api";
/** @type {Array} */
let {
provider,
config = {},
} = $props();
let { providers = [], onSave = () => {} } = $props();
let editingProvider = null;
let showForm = false;
let formData = {
name: '',
provider_type: 'openai',
base_url: 'https://api.openai.com/v1',
api_key: '',
default_model: 'gpt-4o',
is_active: true
name: "",
provider_type: "openai",
base_url: "https://api.openai.com/v1",
api_key: "",
default_model: "gpt-4o",
is_active: true,
};
let testStatus = { type: '', message: '' };
let testStatus = { type: "", message: "" };
let isTesting = false;
function resetForm() {
formData = {
name: '',
provider_type: 'openai',
base_url: 'https://api.openai.com/v1',
api_key: '',
default_model: 'gpt-4o',
is_active: true
name: "",
provider_type: "openai",
base_url: "https://api.openai.com/v1",
api_key: "",
default_model: "gpt-4o",
is_active: true,
};
editingProvider = null;
testStatus = { type: '', message: '' };
testStatus = { type: "", message: "" };
}
function handleEdit(provider) {
editingProvider = provider;
formData = { ...provider, api_key: '' }; // Don't populate key for security
formData = { ...provider, api_key: "" }; // Don't populate key for security
showForm = true;
}
async function testConnection() {
console.log("[ProviderConfig][Action] Testing connection", formData);
isTesting = true;
testStatus = { type: 'info', message: $t.llm.testing };
testStatus = { type: "info", message: $t.llm.testing };
try {
const endpoint = editingProvider ? `/llm/providers/${editingProvider.id}/test` : '/llm/providers/test';
const result = await requestApi(endpoint, 'POST', formData);
const endpoint = editingProvider
? `/llm/providers/${editingProvider.id}/test`
: "/llm/providers/test";
const result = await requestApi(endpoint, "POST", formData);
if (result.success) {
testStatus = { type: 'success', message: $t.llm.connection_success };
testStatus = { type: "success", message: $t.llm.connection_success };
} else {
testStatus = { type: 'error', message: $t.llm.connection_failed.replace('{error}', result.error || 'Unknown error') };
testStatus = {
type: "error",
message: $t.llm.connection_failed.replace(
"{error}",
result.error || "Unknown error",
),
};
}
} catch (err) {
testStatus = { type: 'error', message: $t.llm.connection_failed.replace('{error}', err.message) };
testStatus = {
type: "error",
message: $t.llm.connection_failed.replace("{error}", err.message),
};
} finally {
isTesting = false;
}
@@ -75,8 +82,10 @@
async function handleSubmit() {
console.log("[ProviderConfig][Action] Submitting provider config");
const method = editingProvider ? 'PUT' : 'POST';
const endpoint = editingProvider ? `/llm/providers/${editingProvider.id}` : '/llm/providers';
const method = editingProvider ? "PUT" : "POST";
const endpoint = editingProvider
? `/llm/providers/${editingProvider.id}`
: "/llm/providers";
// When editing, only include api_key if user entered a new one
const submitData = { ...formData };
@@ -97,9 +106,9 @@
async function toggleActive(provider) {
try {
await requestApi(`/llm/providers/${provider.id}`, 'PUT', {
await requestApi(`/llm/providers/${provider.id}`, "PUT", {
...provider,
is_active: !provider.is_active
is_active: !provider.is_active,
});
onSave();
} catch (err) {
@@ -111,28 +120,53 @@
<div class="p-4">
<div class="flex justify-between items-center mb-6">
<h2 class="text-xl font-bold">{$t.llm.providers_title}</h2>
<button
<button
class="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700 transition"
on:click={() => { resetForm(); showForm = true; }}
on:click={() => {
resetForm();
showForm = true;
}}
>
{$t.llm.add_provider}
</button>
</div>
{#if showForm}
<div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div
class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"
>
<div class="bg-white p-6 rounded-lg shadow-xl w-full max-w-md">
<h3 class="text-lg font-semibold mb-4">{editingProvider ? $t.llm.edit_provider : $t.llm.new_provider}</h3>
<h3 class="text-lg font-semibold mb-4">
{editingProvider ? $t.llm.edit_provider : $t.llm.new_provider}
</h3>
<div class="space-y-4">
<div>
<label for="provider-name" class="block text-sm font-medium text-gray-700">{$t.llm.name}</label>
<input id="provider-name" type="text" bind:value={formData.name} class="mt-1 block w-full border rounded-md p-2" placeholder="e.g. My OpenAI" />
<label
for="provider-name"
class="block text-sm font-medium text-gray-700"
>{$t.llm.name}</label
>
<input
id="provider-name"
type="text"
bind:value={formData.name}
class="mt-1 block w-full border rounded-md p-2"
placeholder="e.g. My OpenAI"
/>
</div>
<div>
<label for="provider-type" class="block text-sm font-medium text-gray-700">{$t.llm.type}</label>
<select id="provider-type" bind:value={formData.provider_type} class="mt-1 block w-full border rounded-md p-2">
<label
for="provider-type"
class="block text-sm font-medium text-gray-700"
>{$t.llm.type}</label
>
<select
id="provider-type"
bind:value={formData.provider_type}
class="mt-1 block w-full border rounded-md p-2"
>
<option value="openai">OpenAI</option>
<option value="openrouter">OpenRouter</option>
<option value="kilo">Kilo</option>
@@ -140,47 +174,88 @@
</div>
<div>
<label for="provider-base-url" class="block text-sm font-medium text-gray-700">{$t.llm.base_url}</label>
<input id="provider-base-url" type="text" bind:value={formData.base_url} class="mt-1 block w-full border rounded-md p-2" />
<label
for="provider-base-url"
class="block text-sm font-medium text-gray-700"
>{$t.llm.base_url}</label
>
<input
id="provider-base-url"
type="text"
bind:value={formData.base_url}
class="mt-1 block w-full border rounded-md p-2"
/>
</div>
<div>
<label for="provider-api-key" class="block text-sm font-medium text-gray-700">{$t.llm.api_key}</label>
<input id="provider-api-key" type="password" bind:value={formData.api_key} class="mt-1 block w-full border rounded-md p-2" placeholder={editingProvider ? "••••••••" : "sk-..."} />
<label
for="provider-api-key"
class="block text-sm font-medium text-gray-700"
>{$t.llm.api_key}</label
>
<input
id="provider-api-key"
type="password"
bind:value={formData.api_key}
class="mt-1 block w-full border rounded-md p-2"
placeholder={editingProvider ? "••••••••" : "sk-..."}
/>
</div>
<div>
<label for="provider-default-model" class="block text-sm font-medium text-gray-700">{$t.llm.default_model}</label>
<input id="provider-default-model" type="text" bind:value={formData.default_model} class="mt-1 block w-full border rounded-md p-2" placeholder="gpt-4o" />
<label
for="provider-default-model"
class="block text-sm font-medium text-gray-700"
>{$t.llm.default_model}</label
>
<input
id="provider-default-model"
type="text"
bind:value={formData.default_model}
class="mt-1 block w-full border rounded-md p-2"
placeholder="gpt-4o"
/>
</div>
<div class="flex items-center">
<input id="provider-active" type="checkbox" bind:checked={formData.is_active} class="mr-2" />
<label for="provider-active" class="text-sm font-medium text-gray-700">{$t.llm.active}</label>
<input
id="provider-active"
type="checkbox"
bind:checked={formData.is_active}
class="mr-2"
/>
<label
for="provider-active"
class="text-sm font-medium text-gray-700">{$t.llm.active}</label
>
</div>
</div>
{#if testStatus.message}
<div class={`mt-4 p-2 rounded text-sm ${testStatus.type === 'success' ? 'bg-green-100 text-green-800' : testStatus.type === 'error' ? 'bg-red-100 text-red-800' : 'bg-blue-100 text-blue-800'}`}>
<div
class={`mt-4 p-2 rounded text-sm ${testStatus.type === "success" ? "bg-green-100 text-green-800" : testStatus.type === "error" ? "bg-red-100 text-red-800" : "bg-blue-100 text-blue-800"}`}
>
{testStatus.message}
</div>
{/if}
<div class="mt-6 flex justify-between gap-2">
<button
<button
class="px-4 py-2 border rounded hover:bg-gray-50 flex-1"
on:click={() => { showForm = false; }}
on:click={() => {
showForm = false;
}}
>
{$t.llm.cancel}
</button>
<button
<button
class="px-4 py-2 bg-gray-600 text-white rounded hover:bg-gray-700 flex-1"
disabled={isTesting}
on:click={testConnection}
>
{isTesting ? $t.llm.testing : $t.llm.test}
</button>
<button
<button
class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 flex-1"
on:click={handleSubmit}
>
@@ -193,37 +268,45 @@
<div class="grid gap-4">
{#each providers as provider}
<div class="border rounded-lg p-4 flex justify-between items-center bg-white shadow-sm">
<div
class="border rounded-lg p-4 flex justify-between items-center bg-white shadow-sm"
>
<div>
<div class="font-bold flex items-center gap-2">
{provider.name}
<span class={`text-xs px-2 py-0.5 rounded-full ${provider.is_active ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-800'}`}>
{provider.is_active ? $t.llm.active : 'Inactive'}
<span
class={`text-xs px-2 py-0.5 rounded-full ${provider.is_active ? "bg-green-100 text-green-800" : "bg-gray-100 text-gray-800"}`}
>
{provider.is_active ? $t.llm.active : "Inactive"}
</span>
</div>
<div class="text-sm text-gray-500">{provider.provider_type}{provider.default_model}</div>
<div class="text-sm text-gray-500">
{provider.provider_type}{provider.default_model}
</div>
</div>
<div class="flex gap-2">
<button
<button
class="text-sm text-blue-600 hover:underline"
on:click={() => handleEdit(provider)}
>
{$t.common.edit}
</button>
<button
class={`text-sm ${provider.is_active ? 'text-orange-600' : 'text-green-600'} hover:underline`}
<button
class={`text-sm ${provider.is_active ? "text-orange-600" : "text-green-600"} hover:underline`}
on:click={() => toggleActive(provider)}
>
{provider.is_active ? 'Deactivate' : 'Activate'}
{provider.is_active ? "Deactivate" : "Activate"}
</button>
</div>
</div>
{:else}
<div class="text-center py-8 text-gray-500 border-2 border-dashed rounded-lg">
<div
class="text-center py-8 text-gray-500 border-2 border-dashed rounded-lg"
>
{$t.llm.no_providers}
</div>
{/each}
</div>
</div>
<!-- [/DEF:ProviderConfig:Component] -->
<!-- [/DEF:ProviderConfig:Component] -->

View File

@@ -74,7 +74,7 @@
class="bg-terminal-surface text-terminal-text-subtle border border-terminal-border rounded px-2 py-[0.3125rem] text-xs cursor-pointer shrink-0 appearance-none pr-6 focus:outline-none focus:border-primary-ring"
style="background-image: url(&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%2364748b' stroke-width='2'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E&quot;); background-repeat: no-repeat; background-position: right 0.375rem center;"
value={selectedLevel}
on:change={handleLevelChange}
onchange={handleLevelChange}
aria-label="Filter by level"
>
{#each levelOptions as option}
@@ -86,7 +86,7 @@
class="bg-terminal-surface text-terminal-text-subtle border border-terminal-border rounded px-2 py-[0.3125rem] text-xs cursor-pointer shrink-0 appearance-none pr-6 focus:outline-none focus:border-primary-ring"
style="background-image: url(&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%2364748b' stroke-width='2'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E&quot;); background-repeat: no-repeat; background-position: right 0.375rem center;"
value={selectedSource}
on:change={handleSourceChange}
onchange={handleSourceChange}
aria-label="Filter by source"
>
<option value="">All Sources</option>
@@ -114,7 +114,7 @@
class="w-full bg-terminal-surface text-terminal-text-bright border border-terminal-border rounded py-[0.3125rem] px-2 pl-7 text-xs placeholder:text-terminal-text-muted focus:outline-none focus:border-primary-ring"
placeholder="Search..."
value={searchText}
on:input={handleSearchChange}
oninput={handleSearchChange}
aria-label="Search logs"
/>
</div>
@@ -123,7 +123,7 @@
{#if hasActiveFilters}
<button
class="flex items-center justify-center p-[0.3125rem] bg-transparent border border-terminal-border rounded text-terminal-text-subtle shrink-0 cursor-pointer transition-all hover:text-log-error hover:border-log-error hover:bg-log-error/10"
on:click={clearFilters}
onclick={clearFilters}
aria-label="Clear filters"
>
<svg

View File

@@ -134,7 +134,7 @@
<button
class="flex items-center gap-1.5 bg-transparent border-none text-terminal-text-muted text-[0.6875rem] cursor-pointer py-px px-1.5 rounded transition-all hover:bg-terminal-surface hover:text-terminal-text-subtle
{autoScroll ? 'text-terminal-accent' : ''}"
on:click={toggleAutoScroll}
onclick={toggleAutoScroll}
aria-label="Toggle auto-scroll"
>
{#if autoScroll}

View File

@@ -1,10 +1,17 @@
<!-- [DEF:Counter:Component] -->
<!--
@TIER: TRIVIAL
@PURPOSE: Simple counter demo component
@LAYER: UI
-->
<script>
let count = $state(0)
let count = $state(0);
const increment = () => {
count += 1
}
count += 1;
};
</script>
<button onclick={increment}>
count is {count}
</button>
<!-- [/DEF:Counter:Component] -->

View File

@@ -21,13 +21,14 @@ describe('SidebarStore', () => {
describe('initial state', () => {
it('should have default values when no localStorage', () => {
const state = get(sidebarStore);
expect(state.isExpanded).toBe(true);
expect(state.activeCategory).toBe('dashboards');
expect(state.activeItem).toBe('/dashboards');
expect(state.isMobileOpen).toBe(false);
});
});
// [/DEF:test_sidebar_initial_state:Function]
// [DEF:test_toggleSidebar:Function]
// @TEST: toggleSidebar toggles isExpanded state
@@ -37,9 +38,9 @@ describe('SidebarStore', () => {
it('should toggle isExpanded from true to false', () => {
const initialState = get(sidebarStore);
expect(initialState.isExpanded).toBe(true);
toggleSidebar();
const newState = get(sidebarStore);
expect(newState.isExpanded).toBe(false);
});
@@ -47,11 +48,12 @@ describe('SidebarStore', () => {
it('should toggle isExpanded from false to true', () => {
toggleSidebar(); // Now false
toggleSidebar(); // Should be true again
const state = get(sidebarStore);
expect(state.isExpanded).toBe(true);
});
});
// [/DEF:test_toggleSidebar:Function]
// [DEF:test_setActiveItem:Function]
// @TEST: setActiveItem updates activeCategory and activeItem
@@ -60,7 +62,7 @@ describe('SidebarStore', () => {
describe('setActiveItem', () => {
it('should update activeCategory and activeItem', () => {
setActiveItem('datasets', '/datasets');
const state = get(sidebarStore);
expect(state.activeCategory).toBe('datasets');
expect(state.activeItem).toBe('/datasets');
@@ -68,12 +70,13 @@ describe('SidebarStore', () => {
it('should update to admin category', () => {
setActiveItem('admin', '/settings');
const state = get(sidebarStore);
expect(state.activeCategory).toBe('admin');
expect(state.activeItem).toBe('/settings');
});
});
// [/DEF:test_setActiveItem:Function]
// [DEF:test_mobile_functions:Function]
// @TEST: Mobile functions correctly update isMobileOpen
@@ -82,7 +85,7 @@ describe('SidebarStore', () => {
describe('mobile functions', () => {
it('should set isMobileOpen to true with setMobileOpen', () => {
setMobileOpen(true);
const state = get(sidebarStore);
expect(state.isMobileOpen).toBe(true);
});
@@ -90,7 +93,7 @@ describe('SidebarStore', () => {
it('should set isMobileOpen to false with closeMobile', () => {
setMobileOpen(true);
closeMobile();
const state = get(sidebarStore);
expect(state.isMobileOpen).toBe(false);
});
@@ -98,18 +101,19 @@ describe('SidebarStore', () => {
it('should toggle isMobileOpen with toggleMobileSidebar', () => {
const initialState = get(sidebarStore);
const initialMobileOpen = initialState.isMobileOpen;
toggleMobileSidebar();
const state1 = get(sidebarStore);
expect(state1.isMobileOpen).toBe(!initialMobileOpen);
toggleMobileSidebar();
const state2 = get(sidebarStore);
expect(state2.isMobileOpen).toBe(initialMobileOpen);
});
});
// [/DEF:test_mobile_functions:Function]
});
// [/DEF:frontend.src.lib.stores.__tests__.sidebar:Module]

View File

@@ -1,4 +1,9 @@
// [DEF:Utils:Module]
/**
* @TIER: TRIVIAL
* @PURPOSE: General utility functions (class merging)
* @LAYER: Infra
*
* Merges class names into a single string.
* @param {...(string | undefined | null | false)} inputs
* @returns {string}
@@ -6,3 +11,4 @@
export function cn(...inputs) {
return inputs.filter(Boolean).join(" ");
}
// [/DEF:Utils:Module]

View File

@@ -1,4 +1,9 @@
// [DEF:Debounce:Module]
/**
* @TIER: TRIVIAL
* @PURPOSE: Debounce utility for limiting function execution rate
* @LAYER: Infra
*
* Debounce utility function
* Delays the execution of a function until a specified time has passed since the last call
*
@@ -17,3 +22,4 @@ export function debounce(func, wait) {
timeout = setTimeout(later, wait);
};
}
// [/DEF:Debounce:Module]

View File

@@ -1,11 +1,24 @@
<!-- [DEF:ErrorPage:Page] -->
<script>
import { page } from '$app/stores';
/**
* @TIER: STANDARD
* @PURPOSE: Global error page displaying HTTP status and messages
* @LAYER: UI
* @UX_STATE: Error -> Displays error code and message with home link
*/
import { page } from "$app/stores";
</script>
<div class="container mx-auto p-4 text-center mt-20">
<h1 class="text-6xl font-bold text-gray-800 mb-4">{$page.status}</h1>
<p class="text-2xl text-gray-600 mb-8">{$page.error?.message || 'Page not found'}</p>
<a href="/" class="bg-blue-500 text-white px-6 py-3 rounded-lg hover:bg-blue-600 transition-colors">
<p class="text-2xl text-gray-600 mb-8">
{$page.error?.message || "Page not found"}
</p>
<a
href="/"
class="bg-blue-500 text-white px-6 py-3 rounded-lg hover:bg-blue-600 transition-colors"
>
Back to Dashboard
</a>
</div>
<!-- [/DEF:ErrorPage:Page] -->

View File

@@ -1,2 +1,9 @@
// [DEF:RootLayoutConfig:Module]
/**
* @TIER: TRIVIAL
* @PURPOSE: Root layout configuration (SPA mode)
* @LAYER: Infra
*/
export const ssr = false;
export const prerender = false;
// [/DEF:RootLayoutConfig:Module]

View File

@@ -1,3 +1,9 @@
// [DEF:DashboardTypes:Module]
/**
* @TIER: TRIVIAL
* @PURPOSE: TypeScript interfaces for Dashboard entities
* @LAYER: Domain
*/
export interface DashboardMetadata {
id: number;
title: string;
@@ -10,4 +16,5 @@ export interface DashboardSelection {
source_env_id: string;
target_env_id: string;
replace_db_config?: boolean;
}
}
// [/DEF:DashboardTypes:Module]

File diff suppressed because it is too large Load Diff