feat(assistant): add multi-dialog UX, task-aware llm settings, and i18n cleanup

This commit is contained in:
2026-02-23 23:45:01 +03:00
parent ab1c87ffba
commit 7df7b4f98c
30 changed files with 1145 additions and 221 deletions

View File

@@ -11,6 +11,7 @@
<script lang="ts">
// [SECTION: IMPORTS]
import { onMount, createEventDispatcher } from 'svelte';
import { t } from '../lib/i18n';
// [/SECTION]
// [SECTION: PROPS]
@@ -48,7 +49,7 @@
value={selectedId}
on:change={handleSelect}
>
<option value="" disabled>-- Choose an environment --</option>
<option value="" disabled>{$t.common?.choose_environment || "-- Choose an environment --"}</option>
{#each environments as env}
<option value={env.id}>{env.name} ({env.url})</option>
{/each}
@@ -57,4 +58,4 @@
<!-- [/SECTION] -->
<!-- [/DEF:EnvSelector:Component] -->
<!-- [/DEF:EnvSelector:Component] -->

View File

@@ -16,6 +16,7 @@
import { gitService } from "../../services/gitService";
import { addToast as toast } from "../../lib/toasts.js";
import { api } from "../../lib/api";
import { t } from "../../lib/i18n";
// [/SECTION]
// [SECTION: PROPS]
@@ -49,10 +50,10 @@
`/git/repositories/${dashboardId}/generate-message`,
);
message = data.message;
toast("Commit message generated", "success");
toast($t.git?.commit_message_generated || "Commit message generated", "success");
} catch (e) {
console.error(`[CommitModal][Coherence:Failed] ${e.message}`);
toast(e.message || "Failed to generate message", "error");
toast(e.message || ($t.git?.commit_message_failed || "Failed to generate message"), "error");
} finally {
generatingMessage = false;
}
@@ -93,7 +94,7 @@
if (!diff) diff = "";
} catch (e) {
console.error(`[CommitModal][Coherence:Failed] ${e.message}`);
toast("Failed to load changes", "error");
toast($t.git?.load_changes_failed || "Failed to load changes", "error");
} finally {
loading = false;
}
@@ -114,7 +115,7 @@
committing = true;
try {
await gitService.commit(dashboardId, message, []);
toast("Changes committed successfully", "success");
toast($t.git?.commit_success || "Changes committed successfully", "success");
dispatch("commit");
show = false;
message = "";
@@ -141,7 +142,7 @@
<div
class="bg-white p-6 rounded-lg shadow-xl w-full max-w-4xl max-h-[90vh] flex flex-col"
>
<h2 class="text-xl font-bold mb-4">Commit Changes</h2>
<h2 class="text-xl font-bold mb-4">{$t.git?.commit || "Commit Changes"}</h2>
<div class="flex flex-col md:flex-row gap-4 flex-1 overflow-hidden">
<!-- Left: Message and Files -->
@@ -150,7 +151,7 @@
<div class="flex justify-between items-center mb-1">
<label
class="block text-sm font-medium text-gray-700"
>Commit Message</label
>{$t.git?.commit_message || "Commit Message"}</label
>
<button
onclick={handleGenerateMessage}
@@ -158,16 +159,16 @@
class="text-xs text-blue-600 hover:text-blue-800 disabled:opacity-50 flex items-center"
>
{#if generatingMessage}
<span class="animate-spin mr-1"></span> Generating...
<span class="animate-spin mr-1"></span> {$t.mapper?.generating || "Generating..."}
{:else}
<span class="mr-1"></span> Generate with AI
<span class="mr-1"></span> {$t.git?.generate_with_ai || "Generate with AI"}
{/if}
</button>
</div>
<textarea
bind:value={message}
class="w-full border rounded p-2 h-32 focus:ring-2 focus:ring-blue-500 outline-none resize-none"
placeholder="Describe your changes..."
placeholder={$t.git?.describe_changes || "Describe your changes..."}
></textarea>
</div>
@@ -176,7 +177,7 @@
<h3
class="text-sm font-bold text-gray-500 uppercase mb-2"
>
Changed Files
{$t.git?.changed_files || "Changed Files"}
</h3>
<ul class="text-xs space-y-1">
{#each status.staged_files as file}
@@ -218,14 +219,14 @@
<div
class="bg-gray-200 px-3 py-1 text-xs font-bold text-gray-600 border-b"
>
Changes Preview
{$t.git?.changes_preview || "Changes Preview"}
</div>
<div class="flex-1 overflow-auto p-2">
{#if loading}
<div
class="flex items-center justify-center h-full text-gray-500"
>
Loading diff...
{$t.git?.loading_diff || "Loading diff..."}
</div>
{:else if diff}
<pre
@@ -234,7 +235,7 @@
<div
class="flex items-center justify-center h-full text-gray-500 italic"
>
No changes detected
{$t.git?.no_changes || "No changes detected"}
</div>
{/if}
</div>
@@ -246,7 +247,7 @@
onclick={() => (show = false)}
class="px-4 py-2 text-gray-600 hover:bg-gray-100 rounded"
>
Cancel
{$t.common?.cancel || "Cancel"}
</button>
<button
onclick={handleCommit}
@@ -257,7 +258,7 @@
status?.staged_files?.length === 0)}
class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 disabled:opacity-50"
>
{committing ? "Committing..." : "Commit"}
{committing ? ($t.git?.committing || "Committing...") : ($t.git?.commit || "Commit")}
</button>
</div>
</div>

View File

@@ -14,6 +14,7 @@
import { onMount, createEventDispatcher } from "svelte";
import { gitService } from "../../services/gitService";
import { addToast as toast } from "../../lib/toasts.js";
import { t } from "../../lib/i18n";
// [/SECTION]
// [SECTION: PROPS]
@@ -55,7 +56,7 @@
);
} catch (e) {
console.error(`[DeploymentModal][Coherence:Failed] ${e.message}`);
toast("Failed to load environments", "error");
toast($t.migration?.loading_envs_failed || "Failed to load environments", "error");
} finally {
loading = false;
}
@@ -76,7 +77,7 @@
try {
const result = await gitService.deploy(dashboardId, selectedEnv);
toast(
result.message || "Deployment triggered successfully",
result.message || ($t.git?.deploy_success || "Deployment triggered successfully"),
"success",
);
dispatch("deploy");
@@ -98,26 +99,26 @@
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-96">
<h2 class="text-xl font-bold mb-4">Deploy Dashboard</h2>
<h2 class="text-xl font-bold mb-4">{$t.git?.deploy || "Deploy Dashboard"}</h2>
{#if loading}
<p class="text-gray-500">Loading environments...</p>
<p class="text-gray-500">{$t.migration?.loading_envs || "Loading environments..."}</p>
{:else if environments.length === 0}
<p class="text-red-500 mb-4">
No deployment environments configured.
{$t.git?.no_deploy_envs || "No deployment environments configured."}
</p>
<div class="flex justify-end">
<button
onclick={() => (show = false)}
class="px-4 py-2 bg-gray-200 text-gray-800 rounded hover:bg-gray-300"
>
Close
{$t.common?.close || "Close"}
</button>
</div>
{:else}
<div class="mb-6">
<label class="block text-sm font-medium text-gray-700 mb-2"
>Select Target Environment</label
>{$t.migration?.target_env || "Select Target Environment"}</label
>
<select
bind:value={selectedEnv}
@@ -136,7 +137,7 @@
onclick={() => (show = false)}
class="px-4 py-2 text-gray-600 hover:bg-gray-100 rounded"
>
Cancel
{$t.common?.cancel || "Cancel"}
</button>
<button
onclick={handleDeploy}
@@ -164,9 +165,9 @@
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
Deploying...
{$t.git?.deploying || "Deploying..."}
{:else}
Deploy
{$t.git?.deploy || "Deploy"}
{/if}
</button>
</div>

View File

@@ -82,13 +82,13 @@
*/
async function handleInit() {
if (!selectedConfigId || !remoteUrl) {
toast('Please select a Git server and provide remote URL', 'error');
toast($t.git?.init_validation_error || 'Please select a Git server and provide remote URL', 'error');
return;
}
loading = true;
try {
await gitService.initRepository(dashboardId, selectedConfigId, remoteUrl);
toast('Repository initialized successfully', 'success');
toast($t.git?.init_success || 'Repository initialized successfully', 'success');
initialized = true;
} catch (e) {
toast(e.message, 'error');
@@ -110,7 +110,7 @@
// Try to get selected environment from localStorage (set by EnvSelector)
const sourceEnvId = localStorage.getItem('selected_env_id');
await gitService.sync(dashboardId, sourceEnvId);
toast('Dashboard state synced to Git', 'success');
toast($t.git?.sync_success || 'Dashboard state synced to Git', 'success');
} catch (e) {
toast(e.message, 'error');
} finally {
@@ -129,7 +129,7 @@
loading = true;
try {
await gitService.push(dashboardId);
toast('Changes pushed to remote', 'success');
toast($t.git?.push_success || 'Changes pushed to remote', 'success');
} catch (e) {
toast(e.message, 'error');
} finally {
@@ -148,7 +148,7 @@
loading = true;
try {
await gitService.pull(dashboardId);
toast('Changes pulled from remote', 'success');
toast($t.git?.pull_success || 'Changes pulled from remote', 'success');
} catch (e) {
toast(e.message, 'error');
} finally {
@@ -164,8 +164,8 @@
{#if show}
<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-2xl w-full max-w-4xl max-h-[90vh] overflow-y-auto">
<PageHeader title="{$t.git.management}: {dashboardTitle}">
<div slot="subtitle" class="text-sm text-gray-500">ID: {dashboardId}</div>
<PageHeader title={`${$t.git?.management || "Git Management"}: ${dashboardTitle}`}>
<div slot="subtitle" class="text-sm text-gray-500">{$t.common?.id || "ID"}: {dashboardId}</div>
<div slot="actions">
<button onclick={() => show = false} class="text-gray-400 hover:text-gray-600 transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
@@ -193,13 +193,13 @@
options={configs.map(c => ({ value: c.id, label: `${c.name} (${c.provider})` }))}
/>
{#if configs.length === 0}
<p class="text-xs text-red-500 -mt-4">No Git servers configured. Go to Settings -> Git to add one.</p>
<p class="text-xs text-red-500 -mt-4">{$t.git?.no_servers_configured || "No Git servers configured. Go to Settings -> Git to add one."}</p>
{/if}
<Input
label={$t.git.remote_url}
bind:value={remoteUrl}
placeholder="https://github.com/org/repo.git"
placeholder={$t.git?.remote_url_placeholder || "https://github.com/org/repo.git"}
/>
<Button

View File

@@ -29,6 +29,20 @@
let testStatus = { type: "", message: "" };
let isTesting = false;
function isMultimodalModel(modelName) {
const token = (modelName || "").toLowerCase();
if (!token) return false;
return (
token.includes("gpt-4o") ||
token.includes("gpt-4.1") ||
token.includes("vision") ||
token.includes("vl") ||
token.includes("gemini") ||
token.includes("claude-3") ||
token.includes("claude-sonnet-4")
);
}
function resetForm() {
formData = {
name: "",
@@ -295,6 +309,13 @@
>
{provider.is_active ? $t.llm.active : "Inactive"}
</span>
<span
class={`text-xs px-2 py-0.5 rounded-full ${isMultimodalModel(provider.default_model) ? "bg-sky-100 text-sky-800" : "bg-amber-100 text-amber-800"}`}
>
{isMultimodalModel(provider.default_model)
? ($t.llm?.multimodal || "Multimodal")
: ($t.llm?.text_only || "Text only")}
</span>
</div>
<div class="text-sm text-gray-500">
{provider.provider_type}{provider.default_model}

View File

@@ -31,7 +31,7 @@
// @POST: A new connection is created via the connection service and a success event is dispatched.
async function handleSubmit() {
if (!name || !host || !database || !username || !password) {
addToast('Please fill in all required fields', 'warning');
addToast($t.connections?.required_fields || 'Please fill in all required fields', 'warning');
return;
}
@@ -40,7 +40,7 @@
const newConnection = await createConnection({
name, type, host, port: Number(port), database, username, password
});
addToast('Connection created successfully', 'success');
addToast($t.connections?.created_success || 'Connection created successfully', 'success');
dispatch('success', newConnection);
resetForm();
} catch (e) {
@@ -70,10 +70,10 @@
<!-- [SECTION: TEMPLATE] -->
<Card title={$t.connections?.add_new || "Add New Connection"}>
<form on:submit|preventDefault={handleSubmit} class="space-y-6">
<Input label={$t.connections?.name || "Connection Name"} bind:value={name} placeholder="e.g. Production DWH" />
<Input label={$t.connections?.name || "Connection Name"} bind:value={name} placeholder={$t.connections?.name_placeholder || "e.g. Production DWH"} />
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<Input label={$t.connections?.host || "Host"} bind:value={host} placeholder="10.0.0.1" />
<Input label={$t.connections?.host || "Host"} bind:value={host} placeholder={$t.connections?.host_placeholder || "10.0.0.1"} />
<Input label={$t.connections?.port || "Port"} type="number" bind:value={port} />
</div>
@@ -92,4 +92,4 @@
</form>
</Card>
<!-- [/SECTION] -->
<!-- [/DEF:ConnectionForm:Component] -->
<!-- [/DEF:ConnectionForm:Component] -->

View File

@@ -28,7 +28,7 @@
try {
connections = await getConnections();
} catch (e) {
addToast('Failed to fetch connections', 'error');
addToast($t.connections?.fetch_failed || 'Failed to fetch connections', 'error');
} finally {
isLoading = false;
}
@@ -40,11 +40,11 @@
// @PRE: id is provided and user confirms deletion.
// @POST: Connection is deleted from backend and list is reloaded.
async function handleDelete(id) {
if (!confirm('Are you sure you want to delete this connection?')) return;
if (!confirm($t.connections?.delete_confirm || 'Are you sure you want to delete this connection?')) return;
try {
await deleteConnection(id);
addToast('Connection deleted', 'success');
addToast($t.connections?.deleted_success || 'Connection deleted', 'success');
await fetchConnections();
} catch (e) {
addToast(e.message, 'error');
@@ -85,4 +85,4 @@
</ul>
</Card>
<!-- [/SECTION] -->
<!-- [/DEF:ConnectionList:Component] -->
<!-- [/DEF:ConnectionList:Component] -->

View File

@@ -12,6 +12,7 @@
import { runTask, getTaskStatus } from '../../services/toolsService.js';
import { selectedTask } from '../../lib/stores.js';
import { addToast } from '../../lib/toasts.js';
import { t } from '../../lib/i18n';
// [/SECTION]
let envs = [];
@@ -35,7 +36,7 @@
try {
envs = await api.getEnvironmentsList();
} catch (e) {
addToast('Failed to fetch environments', 'error');
addToast($t.debug?.fetch_env_failed || 'Failed to fetch environments', 'error');
}
}
// [/DEF:fetchEnvironments:Function]
@@ -54,7 +55,7 @@
let params = { action };
if (action === 'test-db-api') {
if (!sourceEnv || !targetEnv) {
addToast('Source and Target environments are required', 'warning');
addToast($t.debug?.source_target_required || 'Source and Target environments are required', 'warning');
isRunning = false;
return;
}
@@ -64,7 +65,7 @@
params.target_env = tEnv.name;
} else {
if (!selectedEnv || !datasetId) {
addToast('Environment and Dataset ID are required', 'warning');
addToast($t.debug?.env_dataset_required || 'Environment and Dataset ID are required', 'warning');
isRunning = false;
return;
}
@@ -101,11 +102,11 @@
clearInterval(pollInterval);
isRunning = false;
results = task.result;
addToast('Debug task completed', 'success');
addToast($t.debug?.completed || 'Debug task completed', 'success');
} else if (task.status === 'FAILED') {
clearInterval(pollInterval);
isRunning = false;
addToast('Debug task failed', 'error');
addToast($t.debug?.failed || 'Debug task failed', 'error');
}
} catch (e) {
clearInterval(pollInterval);
@@ -120,31 +121,31 @@
<div class="space-y-6">
<div class="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
<h3 class="text-lg font-medium text-gray-900 mb-4">System Diagnostics</h3>
<h3 class="text-lg font-medium text-gray-900 mb-4">{$t.debug?.title || 'System Diagnostics'}</h3>
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700">Debug Action</label>
<label class="block text-sm font-medium text-gray-700">{$t.debug?.action || 'Debug Action'}</label>
<select bind:value={action} class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
<option value="test-db-api">Test Database API (Compare Envs)</option>
<option value="get-dataset-structure">Get Dataset Structure (JSON)</option>
<option value="test-db-api">{$t.debug?.test_db_api || 'Test Database API (Compare Envs)'}</option>
<option value="get-dataset-structure">{$t.debug?.get_dataset_structure || 'Get Dataset Structure (JSON)'}</option>
</select>
</div>
{#if action === 'test-db-api'}
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label for="src-env" class="block text-sm font-medium text-gray-700">Source Environment</label>
<label for="src-env" class="block text-sm font-medium text-gray-700">{$t.migration?.source_env || 'Source Environment'}</label>
<select id="src-env" bind:value={sourceEnv} class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
<option value="" disabled>-- Select Source --</option>
<option value="" disabled>{$t.debug?.select_source || '-- Select Source --'}</option>
{#each envs as env}
<option value={env.id}>{env.name}</option>
{/each}
</select>
</div>
<div>
<label for="tgt-env" class="block text-sm font-medium text-gray-700">Target Environment</label>
<label for="tgt-env" class="block text-sm font-medium text-gray-700">{$t.migration?.target_env || 'Target Environment'}</label>
<select id="tgt-env" bind:value={targetEnv} class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
<option value="" disabled>-- Select Target --</option>
<option value="" disabled>{$t.debug?.select_target || '-- Select Target --'}</option>
{#each envs as env}
<option value={env.id}>{env.name}</option>
{/each}
@@ -154,16 +155,16 @@
{:else}
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label for="debug-env" class="block text-sm font-medium text-gray-700">Environment</label>
<label for="debug-env" class="block text-sm font-medium text-gray-700">{$t.dashboard?.environment || 'Environment'}</label>
<select id="debug-env" bind:value={selectedEnv} class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
<option value="" disabled>-- Select Environment --</option>
<option value="" disabled>{$t.common?.choose_environment || '-- Select Environment --'}</option>
{#each envs as env}
<option value={env.id}>{env.name}</option>
{/each}
</select>
</div>
<div>
<label for="debug-ds-id" class="block text-sm font-medium text-gray-700">Dataset ID</label>
<label for="debug-ds-id" class="block text-sm font-medium text-gray-700">{$t.mapper?.dataset_id || 'Dataset ID'}</label>
<input type="number" id="debug-ds-id" bind:value={datasetId} class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" />
</div>
</div>
@@ -171,7 +172,7 @@
<div class="mt-4 flex justify-end">
<button on:click={handleRunDebug} disabled={isRunning} class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-50">
{isRunning ? 'Running...' : 'Run Diagnostics'}
{isRunning ? ($t.dashboard?.running || 'Running...') : ($t.debug?.run || 'Run Diagnostics')}
</button>
</div>
</div>
@@ -179,7 +180,7 @@
{#if results}
<div class="bg-white shadow overflow-hidden sm:rounded-md border border-gray-200">
<div class="px-4 py-5 sm:px-6 bg-gray-50 border-b border-gray-200">
<h3 class="text-lg leading-6 font-medium text-gray-900">Debug Output</h3>
<h3 class="text-lg leading-6 font-medium text-gray-900">{$t.debug?.output || 'Debug Output'}</h3>
</div>
<div class="p-4">
<pre class="text-xs text-gray-600 bg-gray-900 text-green-400 p-4 rounded-md overflow-x-auto h-96">{JSON.stringify(results, null, 2)}</pre>
@@ -187,4 +188,4 @@
</div>
{/if}
</div>
<!-- [/DEF:DebugTool:Component] -->
<!-- [/DEF:DebugTool:Component] -->

View File

@@ -106,7 +106,7 @@
const activeProvider = providers.find(p => p.is_active);
if (!activeProvider) {
addToast('No active LLM provider found', 'error');
addToast($t.mapper?.errors?.no_active_llm_provider || 'No active LLM provider found', 'error');
return;
}
@@ -117,9 +117,9 @@
});
selectedTask.set(task);
addToast('Documentation generation started', 'success');
addToast($t.mapper?.success?.docs_started || 'Documentation generation started', 'success');
} catch (e) {
addToast(e.message || 'Failed to start documentation generation', 'error');
addToast(e.message || $t.mapper?.errors?.docs_start_failed || 'Failed to start documentation generation', 'error');
} finally {
isGeneratingDocs = false;
}
@@ -130,9 +130,9 @@
try {
await api.put(`/mappings/datasets/${datasetId}/metadata`, doc);
generatedDoc = null;
addToast('Documentation applied successfully', 'success');
addToast($t.mapper?.success?.docs_applied || 'Documentation applied successfully', 'success');
} catch (err) {
addToast(err.message || 'Failed to apply documentation', 'error');
addToast(err.message || $t.mapper?.errors?.docs_apply_failed || 'Failed to apply documentation', 'error');
}
}
@@ -212,7 +212,7 @@
label={$t.mapper.excel_path}
type="text"
bind:value={excelPath}
placeholder="/path/to/mapping.xlsx"
placeholder={$t.mapper?.excel_placeholder || "/path/to/mapping.xlsx"}
/>
</div>
{/if}
@@ -224,9 +224,9 @@
disabled={isGeneratingDocs || isRunning}
>
{#if isGeneratingDocs}
<span class="animate-spin mr-1"></span> Generating...
<span class="animate-spin mr-1"></span> {$t.mapper?.generating || "Generating..."}
{:else}
<span class="mr-1"></span> Generate Docs
<span class="mr-1"></span> {$t.datasets?.generate_docs || "Generate Docs"}
{/if}
</Button>
<Button
@@ -247,4 +247,4 @@
/>
</div>
<!-- [/SECTION] -->
<!-- [/DEF:MapperTool:Component] -->
<!-- [/DEF:MapperTool:Component] -->