chat worked

This commit is contained in:
2026-02-23 20:20:25 +03:00
parent e432915ec3
commit f0831d5d28
29 changed files with 1033 additions and 196 deletions

View File

@@ -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] -->

View File

@@ -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>

View File

@@ -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]