fix(agent): minimal safety net, zero-config router, LLM provider status UI
- Remove deterministic intent matching (keyword lists, infer_tool, fast_confirmation, negation guard, classification sets) - Embedding descriptions auto-generated from tool docstrings - LLM provider health endpoint GET /api/agent/llm-status - 3 error codes: LLM_PROVIDER_UNAVAILABLE, LLM_TIMEOUT, LLM_AUTH_ERROR - Frontend banner with auto-retry 30s + input disable - i18n for LLM status messages (assistant.json ru/en) - 138 passing backend tests
This commit is contained in:
@@ -736,7 +736,7 @@
|
||||
aria-label={$t.assistant?.input_placeholder || "Введите команду..."}
|
||||
bind:value={inputText}
|
||||
rows="1"
|
||||
placeholder={llmInputBlocked ? (model.llmBannerMessage || "LLM недоступен...") : ($t.assistant?.input_placeholder || "Введите команду...")}
|
||||
placeholder={llmInputBlocked ? (model.llmBannerMessage || $t.assistant?.llm_placeholder || "LLM недоступен...") : ($t.assistant?.input_placeholder || "Введите команду...")}
|
||||
class="min-h-[44px] max-h-[120px] w-full resize-none rounded-xl border border-border-strong bg-surface-page px-4 py-3 pr-12 text-sm text-text outline-none transition placeholder:text-text-subtle focus:border-primary-ring focus:ring-2 focus:ring-primary-light disabled:cursor-not-allowed disabled:opacity-50"
|
||||
onkeydown={handleKeydown}
|
||||
disabled={isInputLocked}
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
<!-- @UX_FEEDBACK Toast when status changes from unavailable to ok -->
|
||||
<!-- @UX_RECOVERY Retry button, Dismiss (×) button -->
|
||||
<script lang="ts">
|
||||
import { t } from "$lib/i18n/index.svelte.js";
|
||||
|
||||
let {
|
||||
status,
|
||||
message,
|
||||
@@ -53,7 +55,7 @@
|
||||
<p class="text-sm font-medium text-text">{message}</p>
|
||||
{#if retryCountdown > 0 && status !== "auth_error"}
|
||||
<p class="text-xs text-text-muted mt-1">
|
||||
Auto-retry через {retryCountdown}с…
|
||||
{t.assistant?.llm_auto_retry_countdown?.replace("{seconds}", String(retryCountdown)) || `Auto-retry через ${retryCountdown}с…`}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -63,13 +65,13 @@
|
||||
class="text-xs font-medium px-3 py-1.5 rounded-md bg-primary text-white hover:bg-primary-hover transition-colors"
|
||||
onclick={onRetry}
|
||||
>
|
||||
Retry now
|
||||
{t.assistant?.llm_retry_now || "Retry now"}
|
||||
</button>
|
||||
{/if}
|
||||
<button
|
||||
class="text-text-muted hover:text-text transition-colors text-lg leading-none px-1"
|
||||
onclick={onDismiss}
|
||||
aria-label="Dismiss"
|
||||
aria-label={t.assistant?.llm_dismiss || "Dismiss"}
|
||||
>
|
||||
×
|
||||
</button>
|
||||
|
||||
@@ -120,5 +120,13 @@
|
||||
"indicator_complete": "Completed",
|
||||
"time_just_now": "just now",
|
||||
"time_min": "m",
|
||||
"time_hr": "h"
|
||||
"time_hr": "h",
|
||||
"llm_unavailable": "LLM provider unavailable. Check upstream API connection.",
|
||||
"llm_timeout": "LLM provider not responding. Connection timeout.",
|
||||
"llm_auth_error": "LLM API key rejected. Check credentials.",
|
||||
"llm_unknown_status": "LLM status unknown.",
|
||||
"llm_auto_retry_countdown": "Auto-retry in {seconds}s…",
|
||||
"llm_retry_now": "Retry now",
|
||||
"llm_placeholder": "LLM unavailable...",
|
||||
"llm_dismiss": "Dismiss"
|
||||
}
|
||||
|
||||
@@ -120,5 +120,13 @@
|
||||
"indicator_complete": "Завершён",
|
||||
"time_just_now": "только что",
|
||||
"time_min": "мин",
|
||||
"time_hr": "ч"
|
||||
"time_hr": "ч",
|
||||
"llm_unavailable": "LLM провайдер недоступен. Проверьте подключение к upstream API.",
|
||||
"llm_timeout": "LLM провайдер не отвечает. Таймаут соединения.",
|
||||
"llm_auth_error": "API ключ LLM отклонён. Проверьте credentials.",
|
||||
"llm_unknown_status": "LLM статус неизвестен.",
|
||||
"llm_auto_retry_countdown": "Auto-retry через {seconds}с…",
|
||||
"llm_retry_now": "Retry now",
|
||||
"llm_placeholder": "LLM недоступен...",
|
||||
"llm_dismiss": "Скрыть"
|
||||
}
|
||||
|
||||
@@ -211,19 +211,20 @@ export class StreamProcessor {
|
||||
case "error":
|
||||
this.host.streamingState = "error";
|
||||
this.host.error = meta.detail ?? meta.code ?? "Unknown error";
|
||||
// LLM provider error — update health status for banner
|
||||
// LLM provider error — update health status for banner.
|
||||
// Messages come from host._bannerMessageForStatus() or meta.detail from backend.
|
||||
switch (meta.code) {
|
||||
case "LLM_PROVIDER_UNAVAILABLE":
|
||||
this.host.llmStatus = "unavailable";
|
||||
this.host.llmBannerMessage = meta.detail || "LLM провайдер недоступен";
|
||||
this.host.llmBannerMessage = meta.detail || "";
|
||||
break;
|
||||
case "LLM_TIMEOUT":
|
||||
this.host.llmStatus = "timeout";
|
||||
this.host.llmBannerMessage = meta.detail || "LLM провайдер не отвечает";
|
||||
this.host.llmBannerMessage = meta.detail || "";
|
||||
break;
|
||||
case "LLM_AUTH_ERROR":
|
||||
this.host.llmStatus = "auth_error";
|
||||
this.host.llmBannerMessage = meta.detail || "API ключ LLM отклонён";
|
||||
this.host.llmBannerMessage = meta.detail || "";
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -38,6 +38,7 @@ import {
|
||||
} from "$lib/api/assistant.js";
|
||||
import { addToast } from "$lib/toasts.svelte.js";
|
||||
import { log } from "$lib/cot-logger";
|
||||
import { t } from "$lib/i18n/index.svelte.js";
|
||||
import { type ConnectionManagerCallbacks, ConnectionManager } from "./AgentChat.ConnectionManager.svelte.js";
|
||||
import { type StreamProcessorHost, StreamProcessor } from "./AgentChat.StreamProcessor.svelte.js";
|
||||
import { LocalStorageManager } from "./AgentChat.LocalStorage.js";
|
||||
@@ -711,10 +712,10 @@ export class AgentChatModel {
|
||||
|
||||
private _bannerMessageForStatus(status: string): string {
|
||||
switch (status) {
|
||||
case "unavailable": return "LLM провайдер недоступен. Проверьте подключение к upstream API.";
|
||||
case "timeout": return "LLM провайдер не отвечает. Таймаут соединения.";
|
||||
case "auth_error": return "API ключ LLM отклонён. Проверьте credentials.";
|
||||
default: return "LLM статус неизвестен.";
|
||||
case "unavailable": return t.assistant?.llm_unavailable || "LLM провайдер недоступен. Проверьте подключение к upstream API.";
|
||||
case "timeout": return t.assistant?.llm_timeout || "LLM провайдер не отвечает. Таймаут соединения.";
|
||||
case "auth_error": return t.assistant?.llm_auth_error || "API ключ LLM отклонён. Проверьте credentials.";
|
||||
default: return t.assistant?.llm_unknown_status || "LLM статус неизвестен.";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user