fix(chat): choose the chart person before the first message
This commit is contained in:
@@ -0,0 +1,192 @@
|
||||
"use client";
|
||||
|
||||
import { Popover } from "@base-ui/react/popover";
|
||||
import { Check } from "lucide-react";
|
||||
import { useId, useRef, useState } from "react";
|
||||
|
||||
import {
|
||||
chatProfileChoice,
|
||||
chatProfileDisplayName,
|
||||
chatProfilePickerRows,
|
||||
chatProfileSelectedId,
|
||||
chatSessionSubjectLocked,
|
||||
type ChatProfileCatalogStatus,
|
||||
type ChatProfilePickerRow,
|
||||
} from "@/lib/chat-profile-picker-model";
|
||||
import type { ChartLibraryRecord, ChatSession } from "@/lib/home-types";
|
||||
|
||||
export type ChatProfilePickerProps = {
|
||||
readonly session: ChatSession;
|
||||
readonly library: readonly ChartLibraryRecord[];
|
||||
readonly status: ChatProfileCatalogStatus;
|
||||
readonly requestInFlight: boolean;
|
||||
readonly onBindEmpty: (chartProfileId: string) => Promise<boolean>;
|
||||
readonly onOpenNew: (chartProfileId: string) => Promise<boolean>;
|
||||
readonly onReload: () => void;
|
||||
};
|
||||
|
||||
export function ChatProfilePicker({
|
||||
session,
|
||||
library,
|
||||
status,
|
||||
requestInFlight,
|
||||
onBindEmpty,
|
||||
onOpenNew,
|
||||
onReload,
|
||||
}: ChatProfilePickerProps) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [pendingId, setPendingId] = useState<string | null>(null);
|
||||
const triggerRef = useRef<HTMLButtonElement | null>(null);
|
||||
const listId = useId();
|
||||
const label = chatProfileDisplayName({ session, status, library });
|
||||
const selectedId = chatProfileSelectedId(session);
|
||||
const rows = chatProfilePickerRows({ status, library, selectedId });
|
||||
const locked = chatSessionSubjectLocked(session);
|
||||
const deleted = status === "ready" && Boolean(selectedId) && selectedId !== "self" && !rows.some((row) => row.id === selectedId);
|
||||
|
||||
function closePicker() {
|
||||
setOpen(false);
|
||||
triggerRef.current?.focus();
|
||||
}
|
||||
|
||||
async function choose(row: ChatProfilePickerRow) {
|
||||
if (!row.complete || pendingId) return;
|
||||
const choice = chatProfileChoice({
|
||||
session,
|
||||
chartProfileId: row.id,
|
||||
requestInFlight,
|
||||
libraryReady: status === "ready",
|
||||
selectable: row.complete,
|
||||
});
|
||||
if (choice.action === "ignore") {
|
||||
if (row.selected) closePicker();
|
||||
return;
|
||||
}
|
||||
setPendingId(row.id);
|
||||
try {
|
||||
const ok = choice.action === "bind"
|
||||
? await onBindEmpty(choice.chartProfileId)
|
||||
: await onOpenNew(choice.chartProfileId);
|
||||
if (ok) closePicker();
|
||||
} finally {
|
||||
setPendingId(null);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Popover.Root
|
||||
open={open}
|
||||
onOpenChange={(next) => {
|
||||
setOpen(next);
|
||||
if (!next) triggerRef.current?.focus();
|
||||
}}
|
||||
>
|
||||
<Popover.Trigger
|
||||
ref={triggerRef}
|
||||
className="chat-header-chart chat-profile-picker-trigger"
|
||||
type="button"
|
||||
aria-expanded={open}
|
||||
aria-controls={listId}
|
||||
aria-label={`当前星盘:${label}`}
|
||||
>
|
||||
<span className="chat-profile-picker-name">{label}</span>
|
||||
</Popover.Trigger>
|
||||
<Popover.Portal>
|
||||
<Popover.Positioner className="chat-profile-picker-positioner" side="bottom" align="start" sideOffset={8} collisionPadding={12}>
|
||||
<Popover.Popup
|
||||
id={listId}
|
||||
className="chat-profile-picker-popup"
|
||||
onKeyDown={(event) => {
|
||||
if (event.key !== "Escape") return;
|
||||
event.preventDefault();
|
||||
closePicker();
|
||||
}}
|
||||
>
|
||||
<Popover.Title className="chat-profile-picker-title">当前星盘</Popover.Title>
|
||||
{status === "loading" ? <p className="chat-profile-picker-status" role="status">人物列表还在读</p> : null}
|
||||
{status === "failed" ? (
|
||||
<div className="chat-profile-picker-status" role="status">
|
||||
<p>人物列表暂时读不出来</p>
|
||||
<button className="chat-profile-picker-retry" type="button" onClick={onReload}>重试</button>
|
||||
</div>
|
||||
) : null}
|
||||
{deleted ? <p className="chat-profile-picker-status">这份资料已经删除,这段对话只能看。</p> : null}
|
||||
{rows.length > 0 ? (
|
||||
<div className="chat-profile-picker-options" role="group" aria-label="当前星盘">
|
||||
{rows.map((row) => (
|
||||
<ProfileRow
|
||||
key={row.id}
|
||||
row={row}
|
||||
locked={locked}
|
||||
requestInFlight={requestInFlight}
|
||||
pending={pendingId === row.id}
|
||||
onChoose={() => void choose(row)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
</Popover.Popup>
|
||||
</Popover.Positioner>
|
||||
</Popover.Portal>
|
||||
</Popover.Root>
|
||||
);
|
||||
}
|
||||
|
||||
function ProfileRow({
|
||||
row,
|
||||
locked,
|
||||
requestInFlight,
|
||||
pending,
|
||||
onChoose,
|
||||
}: {
|
||||
readonly row: ChatProfilePickerRow;
|
||||
readonly locked: boolean;
|
||||
readonly requestInFlight: boolean;
|
||||
readonly pending: boolean;
|
||||
readonly onChoose: () => void;
|
||||
}) {
|
||||
if (!row.complete) {
|
||||
return (
|
||||
<div className="chat-profile-picker-option" aria-disabled="true">
|
||||
<span className="chat-profile-picker-copy">
|
||||
<b>{row.name}</b>
|
||||
<small>{row.detail}</small>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (locked && !row.selected) {
|
||||
return (
|
||||
<button
|
||||
className="chat-profile-picker-option"
|
||||
type="button"
|
||||
disabled={requestInFlight || pending}
|
||||
aria-label={`${row.name},新建会话并使用此人物`}
|
||||
onClick={onChoose}
|
||||
>
|
||||
<span className="chat-profile-picker-copy">
|
||||
<b>{row.name}</b>
|
||||
<small>{requestInFlight ? "这段回答结束后再换" : "新建会话并使用此人物"}</small>
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
className="chat-profile-picker-option"
|
||||
type="button"
|
||||
aria-pressed={row.selected}
|
||||
data-selected={row.selected ? "" : undefined}
|
||||
disabled={pending}
|
||||
onClick={onChoose}
|
||||
>
|
||||
<span className="chat-profile-picker-copy">
|
||||
<b>{row.name}</b>
|
||||
<small>{row.detail}</small>
|
||||
</span>
|
||||
<Check className="chat-profile-picker-check" aria-hidden="true" />
|
||||
</button>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user