# v0.4.29 (2026-05-10)

## Features
- Add Cline & Kilo Code tool cards
- Tailscale TUN mode for stable Funnel TLS
- Sort APIKEY providers by usage, collapse to top 20

## Improvements
- Local Material Symbols font (no Google Fonts)
- Docker base: Bun → Node 22-alpine
- MITM reads aliases from JSON cache (no native sqlite)
- Stream stall timeout (2 min) in open-sse

## Fixes
- Fal.ai key test: use stable models endpoint
This commit is contained in:
decolua
2026-05-10 21:56:40 +07:00
parent 52c38cf94c
commit 7ad538bcf2
41 changed files with 1171 additions and 391 deletions
@@ -222,7 +222,7 @@ export default function BasicChatPageClient() {
if (connections.length === 0) {
if (!cancelled) {
setProviderGroups([]);
setLoadError("Chưa có provider nào được connect.");
setLoadError("No providers connected yet.");
}
return;
}
@@ -293,12 +293,12 @@ export default function BasicChatPageClient() {
if (!cancelled) {
setProviderGroups(normalized);
if (normalized.length === 0) {
setLoadError("Đã có provider connect nhưng chưa lấy được model nào.");
setLoadError("Providers connected but no models available.");
}
}
} catch (error) {
if (!cancelled) {
setLoadError(textValue(error?.message) || "Không thể tải danh sách provider/model.");
setLoadError(textValue(error?.message) || "Failed to load providers/models.");
setProviderGroups([]);
}
} finally {
@@ -713,7 +713,7 @@ export default function BasicChatPageClient() {
messages: currentSession.messages.map((message) => (message.id === assistantMessageId ? { ...message, content: message.content || `Error: ${errorText}`, status: "error" } : message)),
updatedAt: new Date().toISOString(),
}));
setLoadError(errorText || "Không thể gửi tin nhắn.");
setLoadError(errorText || "Failed to send message.");
}
} finally {
setIsSending(false);
@@ -756,7 +756,7 @@ export default function BasicChatPageClient() {
<div className="absolute left-0 top-[calc(100%+10px)] z-30 w-[min(520px,calc(100vw-2rem))] overflow-hidden rounded-[20px] border border-white/10 bg-[#262626] shadow-2xl shadow-black/50">
<div className="border-b border-white/10 px-4 py-3">
<p className="text-xs uppercase tracking-[0.22em] text-white/45">Models</p>
<p className="text-sm text-white/75">Chỉ lấy từ provider đã connect</p>
<p className="text-sm text-white/75">Only from connected providers</p>
</div>
<div className="max-h-[60vh] overflow-y-auto p-2 custom-scrollbar">
{providerGroups.map((group) => (
@@ -815,7 +815,7 @@ export default function BasicChatPageClient() {
<div className="max-h-[48vh] space-y-2 overflow-y-auto p-1 custom-scrollbar">
{sessionItems.length === 0 ? (
<div className="rounded-[16px] border border-dashed border-white/10 bg-white/5 p-4 text-sm text-white/55">
Chưa có cuộc trò chuyện nào.
No conversations yet.
</div>
) : sessionItems.map((session) => {
const isActive = session.id === activeSessionId;