assistant/renderer/components/Composer.tsx

86 lines
3.1 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useEffect, useRef, useState } from 'react';
import { ArrowUp, Play, RefreshCw, Square } from './Icons';
import type { Task } from '../types';
export function Composer({ task, busy, onSend, onResume, onTrackNow, onTrackStop }: {
task: Task | null;
busy: boolean;
onSend: (text: string) => void;
onResume: () => void;
onTrackNow: () => void;
onTrackStop: () => void;
}) {
const [text, setText] = useState('');
const composing = useRef(false);
const area = useRef<HTMLTextAreaElement>(null);
useEffect(() => {
const element = area.current;
if (!element) return;
element.style.height = 'auto';
element.style.height = `${Math.min(200, Math.max(48, element.scrollHeight))}px`;
}, [text]);
const submit = (): void => {
const value = text.trim();
if (!value || busy) return;
setText('');
onSend(value);
};
const running = task?.status === 'running';
const placeholder = task?.status === 'paused'
? '在这里回答助手的问题…'
: task?.status === 'collecting'
? '补充助手需要的资料…'
: '例如:帮我填今天的查课日志,班级计科2301班,听课记录还好';
return (
<div className="composer-area">
<div className="composer">
<textarea
ref={area}
value={text}
placeholder={placeholder}
rows={1}
disabled={busy}
onChange={event => setText(event.target.value)}
onCompositionStart={() => { composing.current = true; }}
onCompositionEnd={() => { composing.current = false; }}
onKeyDown={event => {
const isEnter = event.key === 'Enter' || event.keyCode === 13;
if (isEnter && !event.shiftKey && !composing.current && !event.nativeEvent.isComposing) {
event.preventDefault();
submit();
}
}}
aria-label="输入消息"
data-testid="composer-input"
/>
<button type="button" className="send-button" aria-label="发送" disabled={busy || !text.trim()} onClick={submit} data-testid="composer-send">
{running ? <Square size={16} /> : <ArrowUp size={16} />}
</button>
</div>
<div className="composer-toolbar">
<span className="composer-hint">Enter 发送,Shift + Enter 换行</span>
<span className="spacer" />
{task && ['paused', 'failed', 'blocked'].includes(task.status) && (
<button type="button" className="secondary-button small" onClick={onResume} disabled={busy} data-testid="resume">
<Play size={14} />继续执行
</button>
)}
{task?.status === 'submitted' && (
<>
<button type="button" className="secondary-button small" onClick={onTrackNow} disabled={busy} data-testid="track-now">
<RefreshCw size={14} />立即回查
</button>
{task.next_check_at && (
<button type="button" className="text-action" onClick={onTrackStop} disabled={busy} data-testid="track-stop">停止回查</button>
)}
</>
)}
</div>
</div>
);
}