docs: update changelog for v1.4.11 release
This commit is contained in:
@@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
---
|
||||
|
||||
## [1.4.11] — 2026-02-25
|
||||
|
||||
> ### 🐛 Settings Persistence Fix
|
||||
>
|
||||
> Fixes routing strategy and wildcard aliases not saving after page refresh.
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **Routing Strategy Not Saved After Refresh (#134)** — Added `fallbackStrategy`, `wildcardAliases`, and `stickyRoundRobinLimit` to the Zod validation schema. These fields were silently stripped during validation, preventing them from being persisted to the database
|
||||
|
||||
### 📝 Notes
|
||||
|
||||
- **#135 Closed** — Feature request for proxy configuration (global + per-provider) was already implemented in v1.4.10
|
||||
|
||||
---
|
||||
|
||||
## [1.4.10] — 2026-02-25
|
||||
|
||||
> ### 🔒 Proxy Visibility + Bug Fixes
|
||||
|
||||
@@ -99,8 +99,8 @@ export default function ChatTesterMode() {
|
||||
|
||||
steps.push({
|
||||
id: 1,
|
||||
name: "Client Request",
|
||||
description: "The request body as your client would send it",
|
||||
name: t("clientRequest"),
|
||||
description: t("clientRequestDescription"),
|
||||
format: clientFormat,
|
||||
content: JSON.stringify(clientRequest, null, 2),
|
||||
status: "done",
|
||||
@@ -117,8 +117,8 @@ export default function ChatTesterMode() {
|
||||
|
||||
steps.push({
|
||||
id: 2,
|
||||
name: "Format Detected",
|
||||
description: "OmniRoute auto-detects the API format from the request structure",
|
||||
name: t("formatDetected"),
|
||||
description: t("formatDetectedDescription"),
|
||||
format: detectedFormat,
|
||||
content: JSON.stringify(
|
||||
{ detectedFormat, clientFormat, match: detectedFormat === clientFormat },
|
||||
@@ -143,8 +143,8 @@ export default function ChatTesterMode() {
|
||||
|
||||
steps.push({
|
||||
id: 3,
|
||||
name: "OpenAI Intermediate",
|
||||
description: "All formats are first normalized to OpenAI format (the universal bridge)",
|
||||
name: t("openaiIntermediate"),
|
||||
description: t("openaiIntermediateDescription"),
|
||||
format: "openai",
|
||||
content: JSON.stringify(toOpenaiData.result || toOpenaiData, null, 2),
|
||||
status: toOpenaiData.success ? "done" : "error",
|
||||
@@ -166,8 +166,8 @@ export default function ChatTesterMode() {
|
||||
|
||||
steps.push({
|
||||
id: 4,
|
||||
name: "Provider Format",
|
||||
description: `OpenAI format is translated to the provider's native format`,
|
||||
name: t("providerFormat"),
|
||||
description: t("providerFormatDescription"),
|
||||
format: targetFmt,
|
||||
content: JSON.stringify(providerTargetData.result || providerTargetData, null, 2),
|
||||
status: providerTargetData.success ? "done" : "error",
|
||||
@@ -181,18 +181,21 @@ export default function ChatTesterMode() {
|
||||
});
|
||||
|
||||
if (!sendRes.ok) {
|
||||
const errData = await sendRes.json().catch(() => ({ error: "Request failed" }));
|
||||
const errData = await sendRes.json().catch(() => ({ error: t("requestFailed") }));
|
||||
steps.push({
|
||||
id: 5,
|
||||
name: "Provider Response",
|
||||
description: "The raw response from the provider API",
|
||||
name: t("providerResponse"),
|
||||
description: t("providerResponseRawDescription"),
|
||||
format: targetFmt,
|
||||
content: JSON.stringify(errData, null, 2),
|
||||
status: "error",
|
||||
});
|
||||
setChatHistory((prev) => [
|
||||
...prev,
|
||||
{ role: "assistant", content: `Error: ${errData.error || "Request failed"}` },
|
||||
{
|
||||
role: "assistant",
|
||||
content: t("errorMessage", { message: errData.error || t("requestFailed") }),
|
||||
},
|
||||
]);
|
||||
} else {
|
||||
// Read streaming response
|
||||
@@ -208,8 +211,8 @@ export default function ChatTesterMode() {
|
||||
|
||||
steps.push({
|
||||
id: 5,
|
||||
name: "Provider Response",
|
||||
description: "The raw SSE stream from the provider API",
|
||||
name: t("providerResponse"),
|
||||
description: t("providerResponseSseDescription"),
|
||||
format: targetFmt,
|
||||
content:
|
||||
fullResponse.slice(0, 5000) + (fullResponse.length > 5000 ? "\n... (truncated)" : ""),
|
||||
@@ -220,19 +223,22 @@ export default function ChatTesterMode() {
|
||||
const assistantText = extractAssistantText(fullResponse);
|
||||
setChatHistory((prev) => [
|
||||
...prev,
|
||||
{ role: "assistant", content: assistantText || "(No text extracted)" },
|
||||
{ role: "assistant", content: assistantText || t("noTextExtracted") },
|
||||
]);
|
||||
}
|
||||
} catch (err) {
|
||||
steps.push({
|
||||
id: steps.length + 1,
|
||||
name: "Error",
|
||||
description: "An unexpected error occurred",
|
||||
name: t("error"),
|
||||
description: t("unexpectedError"),
|
||||
format: "error",
|
||||
content: JSON.stringify({ error: err.message }, null, 2),
|
||||
status: "error",
|
||||
});
|
||||
setChatHistory((prev) => [...prev, { role: "assistant", content: `Error: ${err.message}` }]);
|
||||
setChatHistory((prev) => [
|
||||
...prev,
|
||||
{ role: "assistant", content: t("errorMessage", { message: err.message }) },
|
||||
]);
|
||||
}
|
||||
|
||||
setPipeline(steps);
|
||||
@@ -250,13 +256,10 @@ export default function ChatTesterMode() {
|
||||
</span>
|
||||
<div>
|
||||
<p className="font-medium text-text-main mb-0.5">{t("pipelineDebugger")}</p>
|
||||
<p>{t("chatTesterDescription")}</p>
|
||||
<p>
|
||||
Send messages as a specific client format and see how each step of the translation
|
||||
pipeline works. The right panel shows the full flow:{" "}
|
||||
<strong className="text-text-main">
|
||||
Client Request → Format Detection → OpenAI Intermediate → Provider Format → Response
|
||||
</strong>
|
||||
. Click any step to inspect the data at that stage.
|
||||
<strong className="text-text-main">{t("chatTesterFlow")}</strong>.{" "}
|
||||
{t("clickStepToInspect")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -270,7 +273,7 @@ export default function ChatTesterMode() {
|
||||
<div className="flex flex-col sm:flex-row gap-3">
|
||||
<div className="flex-1">
|
||||
<label className="block text-xs font-medium text-text-muted mb-1 uppercase tracking-wider">
|
||||
Client Format
|
||||
{t("clientFormat")}
|
||||
</label>
|
||||
<Select
|
||||
value={clientFormat}
|
||||
@@ -282,7 +285,7 @@ export default function ChatTesterMode() {
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<label className="block text-xs font-medium text-text-muted mb-1 uppercase tracking-wider">
|
||||
Provider
|
||||
{t("provider")}
|
||||
</label>
|
||||
<Select
|
||||
value={provider}
|
||||
@@ -293,7 +296,7 @@ export default function ChatTesterMode() {
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-text-muted mb-1 uppercase tracking-wider">
|
||||
Model
|
||||
{t("model")}
|
||||
</label>
|
||||
<div className="relative">
|
||||
<input
|
||||
@@ -301,7 +304,7 @@ export default function ChatTesterMode() {
|
||||
value={model}
|
||||
onChange={(e) => setModel(e.target.value)}
|
||||
list="model-suggestions"
|
||||
placeholder="Select or type a model name..."
|
||||
placeholder={t("modelPlaceholder")}
|
||||
className="w-full bg-bg-subtle border border-border rounded-lg px-3 py-2 text-sm text-text-main placeholder:text-text-muted focus:outline-none focus:border-primary transition-colors"
|
||||
/>
|
||||
<datalist id="model-suggestions">
|
||||
@@ -322,13 +325,10 @@ export default function ChatTesterMode() {
|
||||
<span className="material-symbols-outlined text-[48px] mb-3 opacity-30">
|
||||
chat
|
||||
</span>
|
||||
<p className="text-sm font-medium mb-1">
|
||||
Send a message to see the translation pipeline
|
||||
</p>
|
||||
<p className="text-sm font-medium mb-1">{t("sendMessageToSeePipeline")}</p>
|
||||
<p className="text-xs text-center max-w-xs">
|
||||
Your message will be formatted as a{" "}
|
||||
<strong>{FORMAT_META[clientFormat]?.label}</strong> request, translated through
|
||||
the pipeline, and sent to the selected provider.
|
||||
{t("chatMessageHintPrefix")} <strong>{FORMAT_META[clientFormat]?.label}</strong>{" "}
|
||||
{t("chatMessageHintSuffix")}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
@@ -346,8 +346,8 @@ export default function ChatTesterMode() {
|
||||
>
|
||||
<p className="text-[10px] font-semibold text-text-muted mb-1 uppercase">
|
||||
{msg.role === "user"
|
||||
? `You (${FORMAT_META[clientFormat]?.label})`
|
||||
: "Assistant"}
|
||||
? t("youWithFormat", { format: FORMAT_META[clientFormat]?.label })
|
||||
: t("assistant")}
|
||||
</p>
|
||||
<p className="whitespace-pre-wrap">{msg.content}</p>
|
||||
</div>
|
||||
@@ -364,7 +364,7 @@ export default function ChatTesterMode() {
|
||||
value={message}
|
||||
onChange={(e) => setMessage(e.target.value)}
|
||||
onKeyDown={(e) => e.key === "Enter" && !e.shiftKey && handleSend()}
|
||||
placeholder="Type a message..."
|
||||
placeholder={t("typeMessage")}
|
||||
className="flex-1 bg-bg-subtle border border-border rounded-lg px-3 py-2 text-sm text-text-main placeholder:text-text-muted focus:outline-none focus:border-primary transition-colors"
|
||||
disabled={sending}
|
||||
/>
|
||||
@@ -374,7 +374,7 @@ export default function ChatTesterMode() {
|
||||
loading={sending}
|
||||
disabled={!message.trim() || sending}
|
||||
>
|
||||
Send
|
||||
{t("send")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -391,9 +391,7 @@ export default function ChatTesterMode() {
|
||||
</span>
|
||||
<h3 className="text-sm font-semibold text-text-main">{t("translationPipeline")}</h3>
|
||||
</div>
|
||||
<p className="text-xs text-text-muted">
|
||||
Click on any step to inspect the data at that stage
|
||||
</p>
|
||||
<p className="text-xs text-text-muted">{t("clickStepToInspect")}</p>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
@@ -403,11 +401,8 @@ export default function ChatTesterMode() {
|
||||
<span className="material-symbols-outlined text-[48px] mb-3 opacity-30">
|
||||
account_tree
|
||||
</span>
|
||||
<p className="text-sm font-medium mb-1">Pipeline visualization</p>
|
||||
<p className="text-xs text-center max-w-xs">
|
||||
Send a message to see how your request flows through detection → translation →
|
||||
provider call.
|
||||
</p>
|
||||
<p className="text-sm font-medium mb-1">{t("pipelineVisualization")}</p>
|
||||
<p className="text-xs text-center max-w-xs">{t("pipelineVisualizationHint")}</p>
|
||||
</div>
|
||||
</Card>
|
||||
) : (
|
||||
|
||||
@@ -61,10 +61,10 @@ export default function LiveMonitorMode() {
|
||||
<div>
|
||||
<p className="font-medium text-text-main mb-0.5">{t("realtime")}</p>
|
||||
<p>
|
||||
{t("liveMonitorDescription")}{" "}
|
||||
{t("liveMonitorDescriptionPrefix")}{" "}
|
||||
<strong className="text-text-main">{t("chatTester")}</strong>,{" "}
|
||||
<strong className="text-text-main">{t("testBench")}</strong>, {t("or")}{" "}
|
||||
{t("externalApiCalls").toLowerCase()} {t("toGenerateEvents")}
|
||||
<strong className="text-text-main">{t("testBench")}</strong>
|
||||
{t("liveMonitorDescriptionSuffix")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -124,9 +124,7 @@ export default function LiveMonitorMode() {
|
||||
monitoring
|
||||
</span>
|
||||
<p className="text-sm font-medium mb-1">{t("noTranslations")}</p>
|
||||
<p className="text-xs text-center max-w-sm">
|
||||
{t("eventsAppearHint")}
|
||||
</p>
|
||||
<p className="text-xs text-center max-w-sm">{t("eventsAppearHint")}</p>
|
||||
<div className="flex flex-wrap gap-2 mt-3 text-xs">
|
||||
<span className="px-2 py-1 rounded-md bg-bg-subtle border border-border">
|
||||
{t("chatTesterTab")}
|
||||
|
||||
Reference in New Issue
Block a user