feat: 发布 v0.5.0 加密存储与匿名同步
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
'use client';
|
||||
import { Search } from 'lucide-react';
|
||||
import { Bot, Search } from 'lucide-react';
|
||||
import { useState, useCallback } from 'react';
|
||||
import Link from 'next/link';
|
||||
|
||||
@@ -53,6 +53,13 @@ export default function QuestionnaireList() {
|
||||
<div className="container px-4 py-6 max-w-6xl mx-auto">
|
||||
<h1 className="text-2xl font-medium mb-6">{t('title')}</h1>
|
||||
|
||||
<div className="mb-5 flex gap-3 border bg-muted/30 p-3 text-sm leading-6 text-muted-foreground">
|
||||
<Bot className="mt-0.5 h-4 w-4 shrink-0 text-foreground" />
|
||||
<p>
|
||||
测评完成后可以复制完整记录给 ChatGPT 解读;如果想长期追踪变化,可以在对话中明确说“请记住我的测评背景和后续变化”。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Search bar */}
|
||||
<div className="mb-4 relative">
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import Link from 'next/link';
|
||||
import { ReactNode } from 'react';
|
||||
import { Bot, Copy, Download, FileText } from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { useScopedI18n } from '@/locales/client';
|
||||
import { Copy, Download, FileText } from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
import { Questionnaire } from '@/types';
|
||||
import { AssessmentRecord } from '@/lib/assessment-types';
|
||||
import { recordToMarkdown } from '@/lib/assessment-export';
|
||||
@@ -19,7 +19,25 @@ interface ResultContainerProps {
|
||||
profileName?: string;
|
||||
}
|
||||
|
||||
export function ResultContainer({ title, id, children, questionnaire, answers, questionnaireResults, record, profileName = '未命名档案' }: ResultContainerProps) {
|
||||
function aiPrompt() {
|
||||
return [
|
||||
'请根据以下测评记录,用中文进行温和、非诊断式解读。',
|
||||
'请重点分析:主要倾向、可能优势、需要留意的风险、后续可执行建议。',
|
||||
'如果我明确要求你记住,请只记住适合长期追踪的测评背景和变化,不要记住真实身份信息。',
|
||||
'注意:这不是医学或心理诊断。',
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
export function ResultContainer({
|
||||
title,
|
||||
id,
|
||||
children,
|
||||
questionnaire,
|
||||
answers,
|
||||
questionnaireResults,
|
||||
record,
|
||||
profileName = '未命名档案',
|
||||
}: ResultContainerProps) {
|
||||
const t = useScopedI18n(
|
||||
'component.questionnaire.result.public.resultContainer'
|
||||
);
|
||||
@@ -37,7 +55,7 @@ export function ResultContainer({ title, id, children, questionnaire, answers, q
|
||||
|
||||
const buildResultMarkdown = () => {
|
||||
if (record) {
|
||||
return recordToMarkdown(record, profileName);
|
||||
return `${aiPrompt()}\n\n${recordToMarkdown(record, profileName)}`;
|
||||
}
|
||||
if (!questionnaire || !answers || !questionnaireResults) {
|
||||
return null;
|
||||
@@ -45,7 +63,7 @@ export function ResultContainer({ title, id, children, questionnaire, answers, q
|
||||
|
||||
const currentTime = new Date().toLocaleString();
|
||||
|
||||
let resultData = `# ${t('copyTemplate.title')}\n\n`;
|
||||
let resultData = `${aiPrompt()}\n\n# ${t('copyTemplate.title')}\n\n`;
|
||||
resultData += `## ${t('copyTemplate.basicInfo')}\n`;
|
||||
resultData += `- ${t('copyTemplate.questionnaireName')}: ${questionnaire.title}\n`;
|
||||
resultData += `- ${t('copyTemplate.questionnaireId')}: ${id}\n`;
|
||||
@@ -96,10 +114,11 @@ export function ResultContainer({ title, id, children, questionnaire, answers, q
|
||||
URL.revokeObjectURL(url);
|
||||
toast.success(t('downloadResultDataSuccess'));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex justify-center items-center min-h-screen md:p-4 p-2">
|
||||
<div className="max-w-6xl w-full bg-white rounded-lg shadow-lg md:p-8 p-4 border">
|
||||
<h1 className="text-2xl font-bold mb-6">
|
||||
<div className="flex min-h-screen items-center justify-center p-2 md:p-4">
|
||||
<div className="w-full max-w-6xl rounded-lg border bg-white p-4 shadow-lg md:p-8">
|
||||
<h1 className="mb-6 text-2xl font-bold">
|
||||
{title} - {t('resultText')}
|
||||
</h1>
|
||||
|
||||
@@ -107,29 +126,36 @@ export function ResultContainer({ title, id, children, questionnaire, answers, q
|
||||
<div className="space-y-6">{children}</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col sm:flex-row sm:justify-between gap-4 mt-8">
|
||||
<div className="mb-6 flex gap-3 border bg-muted/30 p-3 text-sm leading-6 text-muted-foreground">
|
||||
<Bot className="mt-0.5 h-4 w-4 shrink-0 text-foreground" />
|
||||
<p>
|
||||
可以复制完整记录给 ChatGPT 解读。若想长期追踪,可以在 ChatGPT 中明确说“请记住我的测评背景和后续变化”;不建议提交真实姓名、手机号等身份信息。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 flex flex-col gap-4 sm:flex-row sm:justify-between">
|
||||
<Button variant="outline" className="w-full sm:w-auto">
|
||||
<Link href={`/questionnaire/${id}`}>{t('backToDetail')}</Link>
|
||||
</Button>
|
||||
<div className="flex flex-col sm:flex-row gap-2 w-full sm:w-auto">
|
||||
<div className="flex w-full flex-col gap-2 sm:w-auto sm:flex-row">
|
||||
<Button variant="outline" onClick={handleCopyResultLink} className="w-full sm:w-auto">
|
||||
<Copy className="w-4 h-4 mr-2" />
|
||||
<Copy className="mr-2 h-4 w-4" />
|
||||
{t('copyResultLink')}
|
||||
</Button>
|
||||
<Button
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={handleCopyResultData}
|
||||
onClick={handleCopyResultData}
|
||||
className="w-full sm:w-auto"
|
||||
>
|
||||
<FileText className="w-4 h-4 mr-2" />
|
||||
{t('copyResultData')}
|
||||
<FileText className="mr-2 h-4 w-4" />
|
||||
复制给 AI 解读
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={handleDownloadResultData}
|
||||
className="w-full sm:w-auto"
|
||||
>
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
<Download className="mr-2 h-4 w-4" />
|
||||
{t('downloadResultData')}
|
||||
</Button>
|
||||
<Button asChild className="w-full sm:w-auto">
|
||||
|
||||
@@ -14,6 +14,7 @@ import { ProfilePicker } from '@/components/records/ProfilePicker';
|
||||
import { AssessmentProfile } from '@/lib/assessment-types';
|
||||
import { addAssessmentRecord, ensureActiveProfile } from '@/lib/assessment-db';
|
||||
import { buildScoreSummary } from '@/lib/score-summary';
|
||||
import { syncAnonymousRecord } from '@/lib/anonymous-client';
|
||||
|
||||
interface QuestionnaireProps {
|
||||
questionnaire: QuestionnaireType;
|
||||
@@ -209,6 +210,9 @@ export function Questionnaire({
|
||||
retestSuitable: questionnaire.evaluation?.retestSuitable,
|
||||
recommendedInterval: questionnaire.evaluation?.recommendedInterval,
|
||||
});
|
||||
void syncAnonymousRecord(record).catch((error) => {
|
||||
console.error('Failed to sync anonymous record:', error);
|
||||
});
|
||||
saveResult(id, resultAnswers, profile.id, record.id);
|
||||
|
||||
router.push(`/questionnaire/${id}/result`);
|
||||
|
||||
Reference in New Issue
Block a user