122 lines
4.8 KiB
TypeScript
122 lines
4.8 KiB
TypeScript
"use client";
|
||
|
||
import { useState } from "react";
|
||
import { useTranslation } from "@/i18n/navigation";
|
||
import Footer from "@/app/components/Footer";
|
||
import { apiFetch } from "@/app/lib/api-client";
|
||
|
||
interface AssistantCity {
|
||
id: string;
|
||
slug?: string;
|
||
name: string;
|
||
costPerMonth?: number;
|
||
internetSpeed?: number;
|
||
overallScore?: number;
|
||
}
|
||
|
||
export default function AIPage() {
|
||
const { t } = useTranslation("ai");
|
||
const [query, setQuery] = useState("");
|
||
const [loading, setLoading] = useState(false);
|
||
const [answer, setAnswer] = useState("");
|
||
const [cities, setCities] = useState<AssistantCity[]>([]);
|
||
|
||
const exampleQueries = [
|
||
"我预算每月800刀,想亚洲热带,有稳定网,适合freelance写作,推荐城市+理由",
|
||
"帮我写下一站介绍文案",
|
||
"生成3月清迈行程计划",
|
||
];
|
||
|
||
const handleSubmit = async (e: React.FormEvent) => {
|
||
e.preventDefault();
|
||
if (!query.trim()) return;
|
||
setLoading(true);
|
||
try {
|
||
const data = await apiFetch<{ answer: string; cities: AssistantCity[] }>("/api/ai/assistant", {
|
||
method: "POST",
|
||
body: JSON.stringify({ question: query }),
|
||
});
|
||
setAnswer(data.answer);
|
||
setCities(data.cities || []);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
return (
|
||
<div className="min-h-screen bg-[#fafafa] dark:bg-gray-950">
|
||
<div className="max-w-2xl mx-auto px-4 sm:px-6 py-12 sm:py-16">
|
||
<div className="text-center mb-10">
|
||
<h1 className="text-2xl sm:text-3xl font-bold text-gray-900 dark:text-gray-100">
|
||
{t("title")}
|
||
</h1>
|
||
<p className="mt-2 text-gray-500 dark:text-gray-400">
|
||
{t("subtitle")}
|
||
</p>
|
||
</div>
|
||
|
||
<div className="rounded-2xl bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-700 p-6">
|
||
<form onSubmit={handleSubmit} className="space-y-4">
|
||
<textarea
|
||
value={query}
|
||
onChange={(e) => setQuery(e.target.value)}
|
||
placeholder={t("placeholder")}
|
||
rows={4}
|
||
className="w-full rounded-xl border border-gray-200 dark:border-gray-700 px-4 py-3 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 placeholder-gray-400 resize-none focus:outline-none focus:ring-2 focus:ring-[#ff4d4f]/20 focus:border-[#ff4d4f]"
|
||
/>
|
||
<button
|
||
type="submit"
|
||
disabled={loading || !query.trim()}
|
||
className="w-full py-3 rounded-xl bg-[#ff4d4f] text-white font-medium hover:bg-[#ff3333] disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
||
>
|
||
{loading ? t("thinking") : t("ask")}
|
||
</button>
|
||
</form>
|
||
|
||
{(answer || cities.length > 0) && (
|
||
<div className="mt-6 rounded-2xl border border-gray-100 bg-gray-50 p-4 dark:border-gray-800 dark:bg-gray-800">
|
||
{answer && <p className="leading-7 text-gray-700 dark:text-gray-300">{answer}</p>}
|
||
{cities.length > 0 && (
|
||
<div className="mt-4 grid gap-3 sm:grid-cols-3">
|
||
{cities.map((city) => (
|
||
<div key={city.id} className="rounded-xl bg-white p-3 text-sm shadow-sm dark:bg-gray-900">
|
||
<p className="font-semibold text-gray-900 dark:text-gray-100">{city.name}</p>
|
||
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||
¥{Number(city.costPerMonth || 0).toLocaleString()}/月 · {city.internetSpeed || 0}Mbps
|
||
</p>
|
||
<p className="mt-1 text-xs text-[#ff4d4f]">评分 {city.overallScore || "-"}/5</p>
|
||
</div>
|
||
))}
|
||
</div>
|
||
)}
|
||
</div>
|
||
)}
|
||
|
||
<div className="mt-6 pt-6 border-t border-gray-100 dark:border-gray-800">
|
||
<p className="text-sm font-medium text-gray-700 dark:text-gray-300 mb-3">
|
||
{t("examples")}
|
||
</p>
|
||
<div className="space-y-2">
|
||
{exampleQueries.map((q, i) => (
|
||
<button
|
||
key={i}
|
||
type="button"
|
||
onClick={() => setQuery(q)}
|
||
className="block w-full text-left px-4 py-3 rounded-xl bg-gray-50 dark:bg-gray-800 text-sm text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-gray-900 dark:hover:text-gray-100 transition-colors"
|
||
>
|
||
{q}
|
||
</button>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<p className="mt-6 text-center text-xs text-gray-400 dark:text-gray-500">
|
||
AI 助手已接入本机城市数据库,后续可替换为真实模型和用户画像推荐。
|
||
</p>
|
||
</div>
|
||
<Footer />
|
||
</div>
|
||
);
|
||
}
|