feat: v0.1封版——沉浸式对话MVP,AI动态提问,探索中loading动画
This commit is contained in:
parent
133e417298
commit
a7b746df5f
@ -210,7 +210,7 @@ function QuestionView({ question, onAnswer, questionIndex, total }) {
|
|||||||
|
|
||||||
// ── 主页面 ────────────────────────────────────────────────
|
// ── 主页面 ────────────────────────────────────────────────
|
||||||
export default function ChatPage() {
|
export default function ChatPage() {
|
||||||
const [stage, setStage] = useState('intro'); // intro | chat | loading | done
|
const [stage, setStage] = useState('intro'); // intro | chat | waiting | loading | done
|
||||||
const [question, setQuestion] = useState('');
|
const [question, setQuestion] = useState('');
|
||||||
const [qIndex, setQIndex] = useState(0);
|
const [qIndex, setQIndex] = useState(0);
|
||||||
const [total, setTotal] = useState(30);
|
const [total, setTotal] = useState(30);
|
||||||
@ -236,6 +236,7 @@ export default function ChatPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleAnswer = async (answer) => {
|
const handleAnswer = async (answer) => {
|
||||||
|
setStage('waiting'); // 显示探索中
|
||||||
try {
|
try {
|
||||||
const r = await fetch('/api/chat', {
|
const r = await fetch('/api/chat', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@ -250,9 +251,11 @@ export default function ChatPage() {
|
|||||||
}
|
}
|
||||||
setQuestion(d.reply);
|
setQuestion(d.reply);
|
||||||
setQIndex((i) => i + 1);
|
setQIndex((i) => i + 1);
|
||||||
|
setStage('chat');
|
||||||
} catch {
|
} catch {
|
||||||
setQuestion('网络有点问题,我们继续。你刚才的回答我已经记住了。');
|
setQuestion('网络有点问题,我们继续。你刚才的回答我已经记住了。');
|
||||||
setQIndex((i) => i + 1);
|
setQIndex((i) => i + 1);
|
||||||
|
setStage('chat');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -260,6 +263,22 @@ export default function ChatPage() {
|
|||||||
<>
|
<>
|
||||||
<Starfield />
|
<Starfield />
|
||||||
{stage === 'intro' && <Intro onDone={startChat} />}
|
{stage === 'intro' && <Intro onDone={startChat} />}
|
||||||
|
{stage === 'waiting' && (
|
||||||
|
<div className="relative z-10 flex h-screen h-dvh items-center justify-center">
|
||||||
|
<div
|
||||||
|
className="text-base tracking-[0.3em] text-white/60"
|
||||||
|
style={{ animation: 'breathe 2.4s ease-in-out infinite' }}
|
||||||
|
>
|
||||||
|
探索中……
|
||||||
|
</div>
|
||||||
|
<style>{`
|
||||||
|
@keyframes breathe {
|
||||||
|
0%, 100% { opacity: 0.2; }
|
||||||
|
50% { opacity: 0.9; }
|
||||||
|
}
|
||||||
|
`}</style>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{stage === 'loading' && (
|
{stage === 'loading' && (
|
||||||
<div className="relative z-10 flex h-screen h-dvh items-center justify-center">
|
<div className="relative z-10 flex h-screen h-dvh items-center justify-center">
|
||||||
<div className="text-sm tracking-widest text-white/30 animate-pulse">正在连接...</div>
|
<div className="text-sm tracking-widest text-white/30 animate-pulse">正在连接...</div>
|
||||||
|
|||||||
59
server.js
59
server.js
@ -17,7 +17,7 @@ const opening = '开始前,我想先简单认识你一下。就像朋友聊天
|
|||||||
|
|
||||||
const questionBank = [
|
const questionBank = [
|
||||||
'你今年大概在哪个年龄段?比如20岁以下、20-29、30-39这样。',
|
'你今年大概在哪个年龄段?比如20岁以下、20-29、30-39这样。',
|
||||||
'你平时怎么称呼自己更舒服?',
|
'你是男生还是女生呀?',
|
||||||
'你现在主要在做什么?上学、上班、自己做事,还是在休息调整?',
|
'你现在主要在做什么?上学、上班、自己做事,还是在休息调整?',
|
||||||
'最近这几天,有没有一件小事让你心情变好?',
|
'最近这几天,有没有一件小事让你心情变好?',
|
||||||
'如果只说一件事,你现在最发愁的是什么?',
|
'如果只说一件事,你现在最发愁的是什么?',
|
||||||
@ -60,21 +60,25 @@ function loadSystemPrompt() {
|
|||||||
async function callAI(messages) {
|
async function callAI(messages) {
|
||||||
const key = process.env.YCAPIS_API_KEY;
|
const key = process.env.YCAPIS_API_KEY;
|
||||||
if (!key) return null;
|
if (!key) return null;
|
||||||
const res = await fetch('https://ycapis.com/v1/chat/completions', {
|
try {
|
||||||
method: 'POST',
|
const res = await fetch('https://ycapis.com/v1/responses', {
|
||||||
headers: {
|
method: 'POST',
|
||||||
'Content-Type': 'application/json',
|
headers: {
|
||||||
Authorization: `Bearer ${key}`,
|
'Content-Type': 'application/json',
|
||||||
},
|
Authorization: `Bearer ${key}`,
|
||||||
body: JSON.stringify({
|
},
|
||||||
model: 'gpt-5.3-codex',
|
body: JSON.stringify({
|
||||||
temperature: 0.7,
|
model: 'gpt-5.3-codex',
|
||||||
messages,
|
input: messages.map(m => ({ role: m.role, content: m.content })),
|
||||||
}),
|
max_output_tokens: 300,
|
||||||
});
|
}),
|
||||||
if (!res.ok) return null;
|
});
|
||||||
const data = await res.json();
|
if (!res.ok) return null;
|
||||||
return data?.choices?.[0]?.message?.content || null;
|
const data = await res.json();
|
||||||
|
return data?.output?.[0]?.content?.[0]?.text || null;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function previewReport(session) {
|
function previewReport(session) {
|
||||||
@ -118,7 +122,7 @@ app.prepare().then(() => {
|
|||||||
|
|
||||||
server.post('/api/session/new', (req, res) => {
|
server.post('/api/session/new', (req, res) => {
|
||||||
const sessionId = `lgj_${Date.now().toString(36)}`;
|
const sessionId = `lgj_${Date.now().toString(36)}`;
|
||||||
sessions.set(sessionId, { index: 0, answers: [], skips: 0, prompt: loadSystemPrompt() });
|
sessions.set(sessionId, { index: 0, answers: [], questions: [], skips: 0, prompt: loadSystemPrompt() });
|
||||||
res.json({ sessionId, opening, total: 30 });
|
res.json({ sessionId, opening, total: 30 });
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -134,25 +138,28 @@ app.prepare().then(() => {
|
|||||||
s.index += 1;
|
s.index += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const done = s.index >= questionBank.length;
|
const done = s.index >= 30;
|
||||||
if (done) {
|
if (done) {
|
||||||
return res.json({ done: true, reply: '你已经完成全部对话。接下来我会为你生成专属灵镜报告。' });
|
return res.json({ done: true, reply: '你已经完成全部对话。接下来我会为你生成专属灵镜报告。' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const progress = s.index > 0 && s.index % 5 === 0 ? `你已经完成第${Math.ceil(s.index / 6)}/5阶段了。` : '';
|
const progress = s.index > 0 && s.index % 6 === 0 ? `(你已经完成第${Math.ceil(s.index / 6)}/5阶段了。)` : '';
|
||||||
const bridge = userAnswer ? '我听懂了。' : '我们继续。';
|
|
||||||
const nextQ = questionBank[s.index];
|
|
||||||
|
|
||||||
let aiReply = `${bridge}${progress ? ` ${progress}` : ''} ${nextQ}`.trim();
|
|
||||||
|
|
||||||
|
// 全部交给AI动态生成,不用硬编码题库
|
||||||
const ds = await callAI([
|
const ds = await callAI([
|
||||||
{ role: 'system', content: s.prompt },
|
{ role: 'system', content: s.prompt },
|
||||||
{ role: 'assistant', content: opening },
|
{ role: 'assistant', content: opening },
|
||||||
...s.answers.slice(-8).map((a) => ({ role: 'user', content: a })),
|
...s.answers.flatMap((a, i) => [
|
||||||
{ role: 'user', content: `请按规则继续下一问。当前问题建议:${nextQ}` },
|
{ role: 'assistant', content: s.questions[i] || '' },
|
||||||
|
{ role: 'user', content: a }
|
||||||
|
]).filter(m => m.content),
|
||||||
|
{ role: 'user', content: `[系统提示] 用户刚才回答了:"${userAnswer}"。${progress}请用温暖口语接住他的回答,然后继续问下一个问题。注意:只问一个问题,用大白话,不用专业术语。当前已问第${s.index}题,共30题。` }
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (ds) aiReply = ds;
|
const aiReply = ds || '嗯,我听到了。我们继续,你现在主要在做什么?上学、上班、还是在休整?';
|
||||||
|
|
||||||
|
// 记录AI问的问题
|
||||||
|
s.questions.push(aiReply);
|
||||||
|
|
||||||
return res.json({ done: false, reply: aiReply, index: s.index + 1, total: 30 });
|
return res.json({ done: false, reply: aiReply, index: s.index + 1, total: 30 });
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user