feat(chat): show playful teacher introductions on hover
This commit is contained in:
1 parent
b26e25c9ed
commit
772da4f8e4
16 files changed
+433
-62
No files matched your search
@@ -1,16 +1,25 @@
|
||||
import { ArrowUpRight, Loader2, X } from 'lucide-react';
|
||||
import type { Ref } from 'react';
|
||||
import { useState, type Ref } from 'react';
|
||||
import { Provider as TooltipProvider } from '@radix-ui/react-tooltip';
|
||||
import { TeacherAvatar } from './TeacherAvatar';
|
||||
import { TeacherIntroduction } from './TeacherIntroduction';
|
||||
import type { useTeacherCompanion } from './use-teacher-companion';
|
||||
|
||||
type Companion = ReturnType<typeof useTeacherCompanion>;
|
||||
export function TeacherCompanion({ companion, open, onOpen, bubbleRef }: {
|
||||
export function TeacherCompanion({ companion, open, onOpen, onToggle, bubbleRef, introductionRef }: {
|
||||
companion: Companion;
|
||||
open: boolean;
|
||||
onOpen(agentId?: string): void;
|
||||
onToggle?(agentId?: string): void;
|
||||
bubbleRef?: Ref<HTMLDivElement>;
|
||||
introductionRef?: Ref<HTMLDivElement>;
|
||||
}) {
|
||||
const { definition, invitation, welcome, loading, enabled, checking, error } = companion;
|
||||
const [introductionId, setIntroductionId] = useState<string | null>(null);
|
||||
const introductionProps = (id: string) => ({
|
||||
open: introductionId === id,
|
||||
onOpenChange: (next: boolean) => setIntroductionId((current) => next ? id : current === id ? null : current),
|
||||
});
|
||||
const name = definition?.name || '智能体';
|
||||
const activeId = companion.selectedAgent?.teacher_id;
|
||||
const agents = companion.selectedAgent && !companion.agents.some((item) => item.teacher_id === activeId)
|
||||
@@ -20,23 +29,26 @@ export function TeacherCompanion({ companion, open, onOpen, bubbleRef }: {
|
||||
: checking ? '智能体在看看你最近的进展…'
|
||||
: error || (!enabled ? '智能体暂未开放,仍可查看以往讨论' : open ? '我们慢慢聊,想到什么都可以说' : '点我聊聊,想到什么都可以说');
|
||||
return (
|
||||
<TooltipProvider delayDuration={180} skipDelayDuration={100}>
|
||||
<section className="teacher-companion no-drag" aria-label="智能体陪伴" data-testid="teacher-companion">
|
||||
<nav className="consultation-agent-tabs" aria-label="已下发智能体">
|
||||
{agents.map((agent) => <button key={agent.teacher_id} type="button" className="teacher-companion-person"
|
||||
title={agent.definition.description || agent.definition.name} aria-label={agents.length === 1 ? '智能体' : `与${agent.definition.name}聊天`}
|
||||
{agents.map((agent) => <TeacherIntroduction key={agent.teacher_id} definition={agent.definition} contentRef={introductionRef} {...introductionProps(`agent:${agent.teacher_id}`)}>
|
||||
<button type="button" className="teacher-companion-person"
|
||||
aria-label={agents.length === 1 ? '智能体' : `与${agent.definition.name}聊天`}
|
||||
aria-expanded={open && activeId === agent.teacher_id} aria-pressed={open && activeId === agent.teacher_id}
|
||||
aria-controls="coding-consultation-dock" data-agent-id={agent.teacher_id}
|
||||
onMouseDown={(event) => event.preventDefault()} onClick={() => onOpen(agent.teacher_id)}>
|
||||
onMouseDown={(event) => event.preventDefault()} onClick={() => (onToggle ?? onOpen)(agent.teacher_id)}>
|
||||
<span className="teacher-companion-avatar"><TeacherAvatar definition={agent.definition} />
|
||||
{activeId === agent.teacher_id && invitation && <span className="teacher-companion-unread" aria-hidden="true" />}</span>
|
||||
<span className="teacher-companion-name">{agent.definition.name}</span>
|
||||
{activeId === agent.teacher_id && checking && <Loader2 className="h-3 w-3 shrink-0 animate-spin text-muted-foreground motion-reduce:animate-none" aria-label="智能体正在思考" />}
|
||||
</button>)}
|
||||
{(!agents.length || (!activeId && companion.topic)) && <button type="button" className="teacher-companion-person" title={status} aria-label="智能体" aria-expanded={open} aria-controls="coding-consultation-dock" onMouseDown={(event) => event.preventDefault()} onClick={() => onOpen()}>
|
||||
</button></TeacherIntroduction>)}
|
||||
{(!agents.length || (!activeId && companion.topic)) && <TeacherIntroduction definition={definition} contentRef={introductionRef} {...introductionProps('history')}>
|
||||
<button type="button" className="teacher-companion-person" aria-description={status} aria-label="智能体" aria-expanded={open} aria-controls="coding-consultation-dock" onMouseDown={(event) => event.preventDefault()} onClick={() => (onToggle ?? onOpen)()}>
|
||||
<span className="teacher-companion-avatar"><TeacherAvatar definition={definition} />{invitation && <span className="teacher-companion-unread" aria-hidden="true" />}</span>
|
||||
<span className="teacher-companion-name">{name}</span>
|
||||
{checking && <Loader2 className="h-3 w-3 shrink-0 animate-spin text-muted-foreground motion-reduce:animate-none" aria-label="智能体正在思考" />}
|
||||
</button>}
|
||||
</button></TeacherIntroduction>}
|
||||
</nav>
|
||||
{(invitation || welcome) && <div ref={bubbleRef} className="teacher-companion-presence" data-testid="teacher-presence">
|
||||
<div className="teacher-companion-bubble" data-testid="teacher-invitation" data-bubble-kind={invitation ? 'check-in' : 'welcome'}>
|
||||
@@ -55,5 +67,6 @@ export function TeacherCompanion({ companion, open, onOpen, bubbleRef }: {
|
||||
</button>
|
||||
</div>}
|
||||
</section>
|
||||
</TooltipProvider>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user