Initial commit: Memind H5 portal with MindSpace, Plaza, and agent jobs.

Track application source and tests; exclude local env, user workspaces, and runtime data via .gitignore.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
John
2026-06-15 15:04:43 -07:00
commit 2e14873f2d
272 changed files with 64133 additions and 0 deletions
+145
View File
@@ -0,0 +1,145 @@
import { useCallback, useEffect } from 'react';
import { createPortal } from 'react-dom';
import { useVoiceSession } from '../hooks/useVoiceSession';
import { isVoiceInputAvailable } from '../voice/capabilities';
import { VoiceWaveform } from './VoiceWaveform';
export function VoiceInputDialog({
open,
disabled = false,
onClose,
onSend,
onError,
}: {
open: boolean;
disabled?: boolean;
onClose: () => void;
onSend: (text: string) => void;
onError?: (message: string) => void;
}) {
const { phase, text, analyser, liveRecognition, updateText, stopListening, finishFallbackRecording } =
useVoiceSession({
active: open && !disabled,
onError,
});
const handleClose = useCallback(() => {
stopListening();
onClose();
}, [onClose, stopListening]);
useEffect(() => {
if (!open) return undefined;
const previousOverflow = document.body.style.overflow;
document.body.style.overflow = 'hidden';
const onKeyDown = (event: KeyboardEvent) => {
if (event.key === 'Escape') handleClose();
};
window.addEventListener('keydown', onKeyDown);
return () => {
document.body.style.overflow = previousOverflow;
window.removeEventListener('keydown', onKeyDown);
};
}, [open, handleClose]);
if (!open) return null;
const listening = phase === 'listening';
const transcribing = phase === 'transcribing';
const busy = phase === 'requesting' || transcribing;
const canSend = Boolean(text.trim()) && !busy && !disabled;
const handleSend = () => {
const value = text.trim();
if (!value || disabled) return;
stopListening();
onSend(value);
onClose();
};
const statusText =
phase === 'requesting'
? '正在准备麦克风…'
: listening
? liveRecognition
? '正在聆听,文字会实时显示'
: '正在录音,完成后点击「完成识别」'
: transcribing
? '正在识别…'
: '可编辑识别结果后发送';
const supported = isVoiceInputAvailable();
if (!supported) {
return createPortal(
<div className="voice-dialog-backdrop" role="presentation" onClick={handleClose}>
<section className="voice-dialog" role="dialog" aria-modal="true" onClick={(e) => e.stopPropagation()}>
<p className="voice-dialog-status"></p>
<div className="voice-dialog-actions">
<button type="button" className="ghost-btn" onClick={handleClose}>
</button>
</div>
</section>
</div>,
document.body,
);
}
return createPortal(
<div className="voice-dialog-backdrop" role="presentation" onClick={handleClose}>
<section
className="voice-dialog"
role="dialog"
aria-modal="true"
aria-labelledby="voice-dialog-title"
onClick={(event) => event.stopPropagation()}
>
<div className="voice-dialog-header">
<div>
<p className="voice-dialog-eyebrow"></p>
<h3 id="voice-dialog-title"></h3>
</div>
<button type="button" className="voice-dialog-close" onClick={handleClose} aria-label="关闭">
</button>
</div>
<VoiceWaveform analyser={analyser} active={listening || phase === 'requesting'} />
<p className="voice-dialog-status" role="status">
{transcribing ? <span className="voice-btn-spinner" aria-hidden="true" /> : null}
{statusText}
</p>
<textarea
className="voice-dialog-input input"
rows={4}
placeholder={listening ? '识别文字会出现在这里…' : '输入或编辑要发送的内容…'}
value={text}
disabled={busy}
onChange={(event) => updateText(event.target.value)}
/>
<div className="voice-dialog-actions">
<button type="button" className="ghost-btn" onClick={handleClose}>
</button>
{!liveRecognition && listening ? (
<button
type="button"
className="ghost-btn"
disabled={transcribing}
onClick={() => void finishFallbackRecording()}
>
</button>
) : null}
<button type="button" className="send-btn" disabled={!canSend} onClick={handleSend}>
</button>
</div>
</section>
</div>,
document.body,
);
}