chat worked
This commit is contained in:
@@ -26,18 +26,18 @@
|
||||
// [/SECTION]
|
||||
|
||||
// [SECTION: STATE]
|
||||
let filterText = "";
|
||||
let currentPage = 0;
|
||||
let pageSize = 20;
|
||||
let sortColumn: keyof DashboardMetadata = "title";
|
||||
let sortDirection: "asc" | "desc" = "asc";
|
||||
let filterText = $state("");
|
||||
let currentPage = $state(0);
|
||||
let pageSize = $state(20);
|
||||
let sortColumn: keyof DashboardMetadata = $state("title");
|
||||
let sortDirection: "asc" | "desc" = $state("asc");
|
||||
// [/SECTION]
|
||||
|
||||
// [SECTION: UI STATE]
|
||||
let showGitManager = false;
|
||||
let gitDashboardId: number | null = null;
|
||||
let gitDashboardTitle = "";
|
||||
let validatingIds: Set<number> = new Set();
|
||||
let showGitManager = $state(false);
|
||||
let gitDashboardId: number | null = $state(null);
|
||||
let gitDashboardTitle = $state("");
|
||||
let validatingIds: Set<number> = $state(new Set());
|
||||
// [/SECTION]
|
||||
|
||||
// [DEF:handleValidate:Function]
|
||||
@@ -48,7 +48,7 @@
|
||||
if (validatingIds.has(dashboard.id)) return;
|
||||
|
||||
validatingIds.add(dashboard.id);
|
||||
validatingIds = validatingIds; // Trigger reactivity
|
||||
validatingIds = new Set(validatingIds);
|
||||
|
||||
try {
|
||||
// TODO: Get provider_id from settings or prompt user
|
||||
@@ -83,7 +83,7 @@
|
||||
toast(e.message || "Validation failed to start", "error");
|
||||
} finally {
|
||||
validatingIds.delete(dashboard.id);
|
||||
validatingIds = validatingIds;
|
||||
validatingIds = new Set(validatingIds);
|
||||
}
|
||||
}
|
||||
// [/DEF:handleValidate:Function]
|
||||
@@ -221,14 +221,14 @@
|
||||
type="checkbox"
|
||||
checked={allSelected}
|
||||
indeterminate={someSelected && !allSelected}
|
||||
on:change={(e) =>
|
||||
onchange={(e) =>
|
||||
handleSelectAll((e.target as HTMLInputElement).checked)}
|
||||
class="h-4 w-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500"
|
||||
/>
|
||||
</th>
|
||||
<th
|
||||
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:text-gray-700 transition-colors"
|
||||
on:click={() => handleSort("title")}
|
||||
onclick={() => handleSort("title")}
|
||||
>
|
||||
{$t.dashboard.title}
|
||||
{sortColumn === "title"
|
||||
@@ -239,7 +239,7 @@
|
||||
</th>
|
||||
<th
|
||||
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:text-gray-700 transition-colors"
|
||||
on:click={() => handleSort("last_modified")}
|
||||
onclick={() => handleSort("last_modified")}
|
||||
>
|
||||
{$t.dashboard.last_modified}
|
||||
{sortColumn === "last_modified"
|
||||
@@ -250,7 +250,7 @@
|
||||
</th>
|
||||
<th
|
||||
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:text-gray-700 transition-colors"
|
||||
on:click={() => handleSort("status")}
|
||||
onclick={() => handleSort("status")}
|
||||
>
|
||||
{$t.dashboard.status}
|
||||
{sortColumn === "status"
|
||||
@@ -276,7 +276,7 @@
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedIds.includes(dashboard.id)}
|
||||
on:change={(e) =>
|
||||
onchange={(e) =>
|
||||
handleSelectionChange(
|
||||
dashboard.id,
|
||||
(e.target as HTMLInputElement).checked,
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
|
||||
// [/SECTION]
|
||||
|
||||
let selectedTargetUuid = "";
|
||||
let selectedTargetUuid = $state("");
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
// [DEF:resolve:Function]
|
||||
@@ -94,7 +94,7 @@
|
||||
<div class="mt-5 sm:mt-6 sm:grid sm:grid-cols-2 sm:gap-3 sm:grid-flow-row-dense">
|
||||
<button
|
||||
type="button"
|
||||
on:click={resolve}
|
||||
onclick={resolve}
|
||||
disabled={!selectedTargetUuid}
|
||||
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:col-start-2 sm:text-sm disabled:bg-gray-400"
|
||||
>
|
||||
@@ -102,7 +102,7 @@
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
on:click={cancel}
|
||||
onclick={cancel}
|
||||
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:col-start-1 sm:text-sm"
|
||||
>
|
||||
Cancel Migration
|
||||
|
||||
@@ -27,10 +27,10 @@
|
||||
// [/SECTION]
|
||||
|
||||
// [SECTION: STATE]
|
||||
let branches = [];
|
||||
let loading = false;
|
||||
let showCreate = false;
|
||||
let newBranchName = '';
|
||||
let branches = $state([]);
|
||||
let loading = $state(false);
|
||||
let showCreate = $state(false);
|
||||
let newBranchName = $state('');
|
||||
// [/SECTION]
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
@@ -129,7 +129,7 @@
|
||||
<div class="flex-grow">
|
||||
<Select
|
||||
bind:value={currentBranch}
|
||||
on:change={handleSelect}
|
||||
onchange={handleSelect}
|
||||
disabled={loading}
|
||||
options={branches.map(b => ({ value: b.name, label: b.name }))}
|
||||
/>
|
||||
@@ -138,7 +138,7 @@
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
on:click={() => showCreate = !showCreate}
|
||||
onclick={() => showCreate = !showCreate}
|
||||
disabled={loading}
|
||||
class="text-blue-600"
|
||||
>
|
||||
@@ -158,7 +158,7 @@
|
||||
<Button
|
||||
variant="primary"
|
||||
size="sm"
|
||||
on:click={handleCreate}
|
||||
onclick={handleCreate}
|
||||
disabled={loading || !newBranchName}
|
||||
isLoading={loading}
|
||||
class="bg-green-600 hover:bg-green-700"
|
||||
@@ -168,7 +168,7 @@
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
on:click={() => showCreate = false}
|
||||
onclick={() => showCreate = false}
|
||||
disabled={loading}
|
||||
>
|
||||
{$t.common.cancel}
|
||||
@@ -178,4 +178,4 @@
|
||||
</div>
|
||||
<!-- [/SECTION] -->
|
||||
|
||||
<!-- [/DEF:BranchSelector:Component] -->
|
||||
<!-- [/DEF:BranchSelector:Component] -->
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
// [/SECTION]
|
||||
|
||||
// [SECTION: STATE]
|
||||
let history = [];
|
||||
let loading = false;
|
||||
let history = $state([]);
|
||||
let loading = $state(false);
|
||||
// [/SECTION]
|
||||
|
||||
// [DEF:onMount:Function]
|
||||
@@ -66,7 +66,7 @@
|
||||
<h3 class="text-sm font-semibold text-gray-400 uppercase tracking-wider">
|
||||
{$t.git.history}
|
||||
</h3>
|
||||
<Button variant="ghost" size="sm" on:click={loadHistory} class="text-blue-600">
|
||||
<Button variant="ghost" size="sm" onclick={loadHistory} class="text-blue-600">
|
||||
{$t.git.refresh}
|
||||
</Button>
|
||||
</div>
|
||||
@@ -95,4 +95,4 @@
|
||||
</div>
|
||||
<!-- [/SECTION] -->
|
||||
|
||||
<!-- [/DEF:CommitHistory:Component] -->
|
||||
<!-- [/DEF:CommitHistory:Component] -->
|
||||
|
||||
@@ -24,12 +24,12 @@
|
||||
// [/SECTION]
|
||||
|
||||
// [SECTION: STATE]
|
||||
let message = "";
|
||||
let committing = false;
|
||||
let status = null;
|
||||
let diff = "";
|
||||
let loading = false;
|
||||
let generatingMessage = false;
|
||||
let message = $state("");
|
||||
let committing = $state(false);
|
||||
let status = $state(null);
|
||||
let diff = $state("");
|
||||
let loading = $state(false);
|
||||
let generatingMessage = $state(false);
|
||||
// [/SECTION]
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
@@ -153,7 +153,7 @@
|
||||
>Commit Message</label
|
||||
>
|
||||
<button
|
||||
on:click={handleGenerateMessage}
|
||||
onclick={handleGenerateMessage}
|
||||
disabled={generatingMessage || loading}
|
||||
class="text-xs text-blue-600 hover:text-blue-800 disabled:opacity-50 flex items-center"
|
||||
>
|
||||
@@ -243,13 +243,13 @@
|
||||
|
||||
<div class="flex justify-end space-x-3 mt-6 pt-4 border-t">
|
||||
<button
|
||||
on:click={() => (show = false)}
|
||||
onclick={() => (show = false)}
|
||||
class="px-4 py-2 text-gray-600 hover:bg-gray-100 rounded"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
on:click={handleCommit}
|
||||
onclick={handleCommit}
|
||||
disabled={committing ||
|
||||
!message ||
|
||||
loading ||
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
// [SECTION: STATE]
|
||||
const dispatch = createEventDispatcher();
|
||||
/** @type {Object.<string, 'mine' | 'theirs' | 'manual'>} */
|
||||
let resolutions = {};
|
||||
let resolutions = $state({});
|
||||
// [/SECTION]
|
||||
|
||||
// [DEF:resolve:Function]
|
||||
@@ -126,7 +126,7 @@
|
||||
] === 'mine'
|
||||
? 'bg-blue-600 text-white'
|
||||
: 'bg-gray-50 hover:bg-blue-50 text-blue-600'}"
|
||||
on:click={() =>
|
||||
onclick={() =>
|
||||
resolve(conflict.file_path, "mine")}
|
||||
>
|
||||
Keep Mine
|
||||
@@ -148,7 +148,7 @@
|
||||
] === 'theirs'
|
||||
? 'bg-green-600 text-white'
|
||||
: 'bg-gray-50 hover:bg-green-50 text-green-600'}"
|
||||
on:click={() =>
|
||||
onclick={() =>
|
||||
resolve(conflict.file_path, "theirs")}
|
||||
>
|
||||
Keep Theirs
|
||||
@@ -161,13 +161,13 @@
|
||||
|
||||
<div class="flex justify-end space-x-3 pt-4 border-t">
|
||||
<button
|
||||
on:click={() => (show = false)}
|
||||
onclick={() => (show = false)}
|
||||
class="px-4 py-2 text-gray-600 hover:bg-gray-100 rounded transition-colors"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
on:click={handleSave}
|
||||
onclick={handleSave}
|
||||
class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors shadow-sm"
|
||||
>
|
||||
Resolve & Continue
|
||||
|
||||
@@ -22,10 +22,10 @@
|
||||
// [/SECTION]
|
||||
|
||||
// [SECTION: STATE]
|
||||
let environments = [];
|
||||
let selectedEnv = "";
|
||||
let loading = false;
|
||||
let deploying = false;
|
||||
let environments = $state([]);
|
||||
let selectedEnv = $state("");
|
||||
let loading = $state(false);
|
||||
let deploying = $state(false);
|
||||
// [/SECTION]
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
@@ -108,7 +108,7 @@
|
||||
</p>
|
||||
<div class="flex justify-end">
|
||||
<button
|
||||
on:click={() => (show = false)}
|
||||
onclick={() => (show = false)}
|
||||
class="px-4 py-2 bg-gray-200 text-gray-800 rounded hover:bg-gray-300"
|
||||
>
|
||||
Close
|
||||
@@ -133,13 +133,13 @@
|
||||
|
||||
<div class="flex justify-end space-x-3">
|
||||
<button
|
||||
on:click={() => (show = false)}
|
||||
onclick={() => (show = false)}
|
||||
class="px-4 py-2 text-gray-600 hover:bg-gray-100 rounded"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
on:click={handleDeploy}
|
||||
onclick={handleDeploy}
|
||||
disabled={deploying || !selectedEnv}
|
||||
class="px-4 py-2 bg-green-600 text-white rounded hover:bg-green-700 disabled:opacity-50 flex items-center"
|
||||
>
|
||||
|
||||
@@ -35,20 +35,20 @@
|
||||
// [/SECTION]
|
||||
|
||||
// [SECTION: STATE]
|
||||
let currentBranch = 'main';
|
||||
let showCommitModal = false;
|
||||
let showDeployModal = false;
|
||||
let currentBranch = $state('main');
|
||||
let showCommitModal = $state(false);
|
||||
let showDeployModal = $state(false);
|
||||
let showHistory = true;
|
||||
let showConflicts = false;
|
||||
let showConflicts = $state(false);
|
||||
let conflicts = [];
|
||||
let loading = false;
|
||||
let initialized = false;
|
||||
let checkingStatus = true;
|
||||
let loading = $state(false);
|
||||
let initialized = $state(false);
|
||||
let checkingStatus = $state(true);
|
||||
|
||||
// Initialization form state
|
||||
let configs = [];
|
||||
let selectedConfigId = "";
|
||||
let remoteUrl = "";
|
||||
let configs = $state([]);
|
||||
let selectedConfigId = $state("");
|
||||
let remoteUrl = $state("");
|
||||
// [/SECTION]
|
||||
|
||||
// [DEF:checkStatus:Function]
|
||||
@@ -167,7 +167,7 @@
|
||||
<PageHeader title="{$t.git.management}: {dashboardTitle}">
|
||||
<div slot="subtitle" class="text-sm text-gray-500">ID: {dashboardId}</div>
|
||||
<div slot="actions">
|
||||
<button on:click={() => show = false} class="text-gray-400 hover:text-gray-600 transition-colors">
|
||||
<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">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
@@ -203,7 +203,7 @@
|
||||
/>
|
||||
|
||||
<Button
|
||||
on:click={handleInit}
|
||||
onclick={handleInit}
|
||||
disabled={loading || configs.length === 0}
|
||||
isLoading={loading}
|
||||
class="w-full"
|
||||
@@ -226,14 +226,14 @@
|
||||
<h3 class="text-sm font-semibold text-gray-400 uppercase tracking-wider mb-3">{$t.git.actions}</h3>
|
||||
<Button
|
||||
variant="secondary"
|
||||
on:click={handleSync}
|
||||
onclick={handleSync}
|
||||
disabled={loading}
|
||||
class="w-full"
|
||||
>
|
||||
{$t.git.sync}
|
||||
</Button>
|
||||
<Button
|
||||
on:click={() => showCommitModal = true}
|
||||
onclick={() => showCommitModal = true}
|
||||
disabled={loading}
|
||||
class="w-full"
|
||||
>
|
||||
@@ -242,7 +242,7 @@
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<Button
|
||||
variant="ghost"
|
||||
on:click={handlePull}
|
||||
onclick={handlePull}
|
||||
disabled={loading}
|
||||
class="border border-gray-200"
|
||||
>
|
||||
@@ -250,7 +250,7 @@
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
on:click={handlePush}
|
||||
onclick={handlePush}
|
||||
disabled={loading}
|
||||
class="border border-gray-200"
|
||||
>
|
||||
@@ -263,7 +263,7 @@
|
||||
<h3 class="text-sm font-semibold text-gray-400 uppercase tracking-wider mb-3">{$t.git.deployment}</h3>
|
||||
<Button
|
||||
variant="primary"
|
||||
on:click={() => showDeployModal = true}
|
||||
onclick={() => showDeployModal = true}
|
||||
disabled={loading}
|
||||
class="w-full bg-green-600 hover:bg-green-700 focus-visible:ring-green-500"
|
||||
>
|
||||
@@ -300,4 +300,4 @@
|
||||
/>
|
||||
<!-- [/SECTION] -->
|
||||
|
||||
<!-- [/DEF:GitManager:Component] -->
|
||||
<!-- [/DEF:GitManager:Component] -->
|
||||
|
||||
@@ -11,13 +11,17 @@
|
||||
|
||||
/** @type {Object} */
|
||||
let {
|
||||
documentation = null,
|
||||
content = "",
|
||||
type = 'markdown',
|
||||
format = 'text',
|
||||
onSave = async () => {},
|
||||
onCancel = () => {},
|
||||
} = $props();
|
||||
|
||||
let previewDoc = $derived(documentation || content);
|
||||
|
||||
let isSaving = false;
|
||||
let isSaving = $state(false);
|
||||
|
||||
async function handleSave() {
|
||||
isSaving = true;
|
||||
@@ -31,14 +35,14 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if documentation}
|
||||
{#if previewDoc}
|
||||
<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-2xl max-h-[90vh] flex flex-col">
|
||||
<h3 class="text-lg font-semibold mb-4">{$t.llm.doc_preview_title}</h3>
|
||||
|
||||
<div class="flex-1 overflow-y-auto mb-6 prose prose-sm max-w-none border rounded p-4 bg-gray-50">
|
||||
<h4 class="text-md font-bold text-gray-800 mb-2">{$t.llm.dataset_desc}</h4>
|
||||
<p class="text-gray-700 mb-4 whitespace-pre-wrap">{documentation.description || 'No description generated.'}</p>
|
||||
<p class="text-gray-700 mb-4 whitespace-pre-wrap">{previewDoc.description || 'No description generated.'}</p>
|
||||
|
||||
<h4 class="text-md font-bold text-gray-800 mb-2">{$t.llm.column_doc}</h4>
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
@@ -49,7 +53,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200">
|
||||
{#each Object.entries(documentation.columns || {}) as [name, desc]}
|
||||
{#each Object.entries(previewDoc.columns || {}) as [name, desc]}
|
||||
<tr>
|
||||
<td class="px-3 py-2 text-sm font-mono text-gray-900">{name}</td>
|
||||
<td class="px-3 py-2 text-sm text-gray-700">{desc}</td>
|
||||
@@ -62,14 +66,14 @@
|
||||
<div class="flex justify-end gap-3">
|
||||
<button
|
||||
class="px-4 py-2 border rounded hover:bg-gray-50"
|
||||
on:click={onCancel}
|
||||
onclick={onCancel}
|
||||
disabled={isSaving}
|
||||
>
|
||||
{$t.llm.cancel}
|
||||
</button>
|
||||
<button
|
||||
class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 disabled:opacity-50"
|
||||
on:click={handleSave}
|
||||
onclick={handleSave}
|
||||
disabled={isSaving}
|
||||
>
|
||||
{isSaving ? $t.llm.applying : $t.llm.apply_doc}
|
||||
@@ -79,4 +83,4 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- [/DEF:DocPreview:Component] -->
|
||||
<!-- [/DEF:DocPreview:Component] -->
|
||||
|
||||
@@ -7,12 +7,12 @@
|
||||
-->
|
||||
|
||||
<script>
|
||||
import { onMount } from "svelte";
|
||||
import { t } from "../../lib/i18n";
|
||||
import { requestApi } from "../../lib/api";
|
||||
|
||||
/** @type {Array} */
|
||||
let { providers = [], onSave = () => {} } = $props();
|
||||
export let providers = [];
|
||||
export let onSave = () => {};
|
||||
|
||||
let editingProvider = null;
|
||||
let showForm = false;
|
||||
@@ -43,8 +43,18 @@
|
||||
}
|
||||
|
||||
function handleEdit(provider) {
|
||||
console.log("[ProviderConfig][Action] Editing provider", provider?.id);
|
||||
editingProvider = provider;
|
||||
formData = { ...provider, api_key: "" }; // Don't populate key for security
|
||||
// Normalize provider fields to editable form shape.
|
||||
formData = {
|
||||
name: provider?.name ?? "",
|
||||
provider_type: provider?.provider_type ?? "openai",
|
||||
base_url: provider?.base_url ?? "https://api.openai.com/v1",
|
||||
api_key: "",
|
||||
default_model: provider?.default_model ?? "gpt-4o",
|
||||
is_active: Boolean(provider?.is_active),
|
||||
};
|
||||
testStatus = { type: "", message: "" };
|
||||
showForm = true;
|
||||
}
|
||||
|
||||
@@ -121,19 +131,22 @@
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h2 class="text-xl font-bold">{$t.llm.providers_title}</h2>
|
||||
<button
|
||||
type="button"
|
||||
class="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700 transition"
|
||||
on:click={() => {
|
||||
on:click|preventDefault={() => {
|
||||
resetForm();
|
||||
showForm = true;
|
||||
}}
|
||||
>
|
||||
{$t.llm.add_provider}
|
||||
{$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"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
>
|
||||
<div class="bg-white p-6 rounded-lg shadow-xl w-full max-w-md">
|
||||
<h3 class="text-lg font-semibold mb-4">
|
||||
@@ -241,23 +254,26 @@
|
||||
|
||||
<div class="mt-6 flex justify-between gap-2">
|
||||
<button
|
||||
type="button"
|
||||
class="px-4 py-2 border rounded hover:bg-gray-50 flex-1"
|
||||
on:click={() => {
|
||||
on:click|preventDefault={() => {
|
||||
showForm = false;
|
||||
}}
|
||||
>
|
||||
{$t.llm.cancel}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="px-4 py-2 bg-gray-600 text-white rounded hover:bg-gray-700 flex-1"
|
||||
disabled={isTesting}
|
||||
on:click={testConnection}
|
||||
on:click|preventDefault={testConnection}
|
||||
>
|
||||
{isTesting ? $t.llm.testing : $t.llm.test}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 flex-1"
|
||||
on:click={handleSubmit}
|
||||
on:click|preventDefault={handleSubmit}
|
||||
>
|
||||
{$t.llm.save}
|
||||
</button>
|
||||
@@ -286,14 +302,16 @@
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
type="button"
|
||||
class="text-sm text-blue-600 hover:underline"
|
||||
on:click={() => handleEdit(provider)}
|
||||
on:click|preventDefault|stopPropagation={() => handleEdit(provider)}
|
||||
>
|
||||
{$t.common.edit}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class={`text-sm ${provider.is_active ? "text-orange-600" : "text-green-600"} hover:underline`}
|
||||
on:click={() => toggleActive(provider)}
|
||||
on:click|preventDefault|stopPropagation={() => toggleActive(provider)}
|
||||
>
|
||||
{provider.is_active ? "Deactivate" : "Activate"}
|
||||
</button>
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
// [DEF:frontend.src.components.llm.__tests__.provider_config_integration:Module]
|
||||
// @TIER: STANDARD
|
||||
// @SEMANTICS: llm, provider-config, integration-test, edit-flow
|
||||
// @PURPOSE: Protect edit-button interaction contract in LLM provider settings UI.
|
||||
// @LAYER: UI Tests
|
||||
// @RELATION: VERIFIES -> frontend/src/components/llm/ProviderConfig.svelte
|
||||
// @INVARIANT: Edit action keeps explicit click handler and opens normalized edit form.
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
const COMPONENT_PATH = path.resolve(
|
||||
process.cwd(),
|
||||
'src/components/llm/ProviderConfig.svelte',
|
||||
);
|
||||
|
||||
// [DEF:provider_config_edit_contract_tests:Function]
|
||||
// @TIER: STANDARD
|
||||
// @PURPOSE: Validate edit button handler wiring and normalized edit form state mapping.
|
||||
// @PRE: ProviderConfig component source exists in expected path.
|
||||
// @POST: Contract checks ensure edit click cannot degrade into no-op flow.
|
||||
describe('ProviderConfig edit interaction contract', () => {
|
||||
it('keeps explicit edit click handler with guarded button semantics', () => {
|
||||
const source = fs.readFileSync(COMPONENT_PATH, 'utf-8');
|
||||
|
||||
expect(source).toContain('type="button"');
|
||||
expect(source).toContain(
|
||||
"on:click|preventDefault|stopPropagation={() => handleEdit(provider)}",
|
||||
);
|
||||
});
|
||||
|
||||
it('normalizes provider payload into editable form shape', () => {
|
||||
const source = fs.readFileSync(COMPONENT_PATH, 'utf-8');
|
||||
|
||||
expect(source).toContain('formData = {');
|
||||
expect(source).toContain('name: provider?.name ?? ""');
|
||||
expect(source).toContain('provider_type: provider?.provider_type ?? "openai"');
|
||||
expect(source).toContain('default_model: provider?.default_model ?? "gpt-4o"');
|
||||
expect(source).toContain('showForm = true;');
|
||||
});
|
||||
});
|
||||
// [/DEF:provider_config_edit_contract_tests:Function]
|
||||
// [/DEF:frontend.src.components.llm.__tests__.provider_config_integration:Module]
|
||||
@@ -31,8 +31,8 @@
|
||||
path = '',
|
||||
} = $props();
|
||||
|
||||
let isUploading = false;
|
||||
let dragOver = false;
|
||||
let isUploading = $state(false);
|
||||
let dragOver = $state(false);
|
||||
|
||||
async function handleUpload() {
|
||||
const file = fileInput.files[0];
|
||||
@@ -94,9 +94,9 @@
|
||||
<div
|
||||
class="mt-1 flex justify-center px-6 pt-5 pb-6 border-2 border-dashed rounded-md transition-colors
|
||||
{dragOver ? 'border-indigo-500 bg-indigo-50' : 'border-gray-300'}"
|
||||
on:dragover|preventDefault={() => dragOver = true}
|
||||
on:dragleave|preventDefault={() => dragOver = false}
|
||||
on:drop|preventDefault={handleDrop}
|
||||
ondragover={(event) => { event.preventDefault(); dragOver = true; }}
|
||||
ondragleave={(event) => { event.preventDefault(); dragOver = false; }}
|
||||
ondrop={(event) => { event.preventDefault(); handleDrop(event); }}
|
||||
>
|
||||
<div class="space-y-1 text-center">
|
||||
<svg class="mx-auto h-12 w-12 text-gray-400" stroke="currentColor" fill="none" viewBox="0 0 48 48" aria-hidden="true">
|
||||
@@ -111,7 +111,7 @@
|
||||
type="file"
|
||||
class="sr-only"
|
||||
bind:this={fileInput}
|
||||
on:change={handleUpload}
|
||||
onchange={handleUpload}
|
||||
disabled={isUploading}
|
||||
>
|
||||
</label>
|
||||
@@ -132,4 +132,4 @@
|
||||
<!-- [/SECTION: TEMPLATE] -->
|
||||
|
||||
|
||||
<!-- [/DEF:FileUpload:Component] -->
|
||||
<!-- [/DEF:FileUpload:Component] -->
|
||||
|
||||
Reference in New Issue
Block a user