chore: update ji local changes

This commit is contained in:
吉鑫升 committed 2026-06-03 18:57:49 +08:00
1 parent f590f672be
commit e2a962de32
9 files changed
+185 -38

No files matched your search

@@ -213,7 +213,7 @@ export function RecentChatList() {
className="block w-full whitespace-nowrap group-hover/side-menu-item:overflow-hidden"
href={pathOfThread(thread)}
>
{desensitize(titleOfThread(thread))}
{desensitize(titleOfThread(thread, t.pages.newChat))}
</Link>
</SidebarMenuButton>
{env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY !== "true" && (
@@ -236,7 +236,7 @@ export function RecentChatList() {
onSelect={() =>
handleRenameClick(
thread.thread_id,
titleOfThread(thread),
titleOfThread(thread, t.pages.newChat),
)
}
>
+34 -8
View File
@@ -64,24 +64,50 @@ export async function loginByUsername(
return response.json() as Promise<LoginByUsernameResponse>;
}
// The upstream token issuer and the verifier (token_info_url) run on separate
// servers whose clocks can drift ~3-4s apart. A token verified right after it
// is issued can look "not yet valid / expired" to the verifier, which surfaces
// as a 401 here. That window closes on its own once real time advances past the
// skew, so we silently retry the exchange a couple of times before giving up —
// the LoginPage keeps showing its loading state, so the user never notices.
const TOKEN_LOGIN_MAX_ATTEMPTS = 3; // initial try + 2 retries → covers up to ~6s skew
const TOKEN_LOGIN_RETRY_DELAY_MS = 3000;
const wait = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));
export async function loginByToken(
token: string,
): Promise<LoginByUsernameResponse> {
// Token-exchange login: the gateway resolves the upstream token to a username
// and signs the matching local account in.
const response = await apiFetch(
`${getBackendBaseURL()}/api/v1/auth/login/token?token=${encodeURIComponent(token)}`,
{
const url = `${getBackendBaseURL()}/api/v1/auth/login/token?token=${encodeURIComponent(token)}`;
let lastError = "Login failed.";
for (let attempt = 1; attempt <= TOKEN_LOGIN_MAX_ATTEMPTS; attempt += 1) {
const response = await apiFetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
},
);
if (!response.ok) {
});
if (response.ok) {
return response.json() as Promise<LoginByUsernameResponse>;
}
const error = await response.json().catch(() => null);
throw new Error(extractErrorMessage(error));
lastError = extractErrorMessage(error);
// Only 401 is plausibly the transient clock-skew case and worth waiting on.
// 403 (disabled), 429 (rate-limited), 500 (misconfigured) won't self-heal,
// so fail fast instead of stalling the user.
const isTransient = response.status === 401;
if (!isTransient || attempt === TOKEN_LOGIN_MAX_ATTEMPTS) {
throw new Error(lastError);
}
await wait(TOKEN_LOGIN_RETRY_DELAY_MS);
}
return response.json() as Promise<LoginByUsernameResponse>;
// Unreachable: the loop either returns or throws above.
throw new Error(lastError);
}
+17 -2
View File
@@ -62,6 +62,21 @@ export function textOfMessage(message: Message) {
return null;
}
export function titleOfThread(thread: AgentThread) {
return thread.values?.title ?? "Untitled";
export function titleOfThread(thread: AgentThread, fallback = "Untitled") {
const title = thread.values?.title;
if (title) return title;
// No title yet (e.g. the very first turn is still streaming, or the backend
// title pass hasn't finished). Derive a sensible display title from the first
// user message so the user never sees a bare "Untitled" placeholder.
const messages = thread.values?.messages;
if (Array.isArray(messages)) {
const firstHuman = messages.find((m) => m.type === "human");
const text = firstHuman ? textOfMessage(firstHuman)?.trim() : null;
if (text) {
return text.length > 50 ? `${text.slice(0, 50).trimEnd()}…` : text;
}
}
return fallback;
}
+1 -1
View File
@@ -71,7 +71,7 @@ export default function ChatsPage() {
<Link key={thread.thread_id} href={pathOfThread(thread)}>
<div className="flex flex-col gap-2 border-b p-4">
<div>
<div>{desensitize(titleOfThread(thread))}</div>
<div>{desensitize(titleOfThread(thread, t.pages.newChat))}</div>
</div>
{thread.updated_at && (
<div className="text-muted-foreground text-sm">