nomadweb/frontend/src/components/AiAssistantClient.tsx
eric 0ce48023ae Polish explore hubs: feedback API, live gates, AI tips, empty states.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-30 20:45:58 -05:00

107 lines
3.3 KiB
TypeScript
Raw 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.

"use client";
import { useState } from "react";
import Link from "next/link";
import { api } from "@/lib/api";
import { useI18n } from "@/lib/i18n";
import { useToast } from "@/lib/toast";
import RingNext from "@/components/RingNext";
import { useRingSteps } from "@/lib/rings";
const SUGGESTIONS = [
"预算 6000,想找网速好的东南亚城市",
"适合第一次远程办公的城市?",
"清迈和巴厘岛怎么选?",
];
export default function AiAssistantClient() {
const { t } = useI18n();
const { toast } = useToast();
const rings = useRingSteps();
const [message, setMessage] = useState("");
const [reply, setReply] = useState("");
const [cities, setCities] = useState<{ slug: string; name: string; emoji: string }[]>([]);
const [loading, setLoading] = useState(false);
const ask = async (text?: string) => {
const q = (text ?? message).trim();
if (!q) {
toast("先写一个问题", "info");
return;
}
setMessage(q);
setLoading(true);
try {
const res = await api.askAssistant(q);
setReply(res.reply);
setCities(res.cities || []);
} catch {
setReply(t.ai.fail);
setCities([]);
} finally {
setLoading(false);
}
};
return (
<div className="community-page">
<div className="container">
<nav className="detail-nav">
<Link href="/">{t.common.backHome}</Link>
<span> · </span>
<Link href="/next-stop">{t.ai.nextStop}</Link>
</nav>
<div className="section-header reveal">
<span className="section-tag">{t.ai.tag}</span>
<h1>{t.ai.title}</h1>
<p>{t.ai.subtitle}</p>
</div>
<div className="ai-suggestions reveal">
{SUGGESTIONS.map((s) => (
<button key={s} type="button" className="filter-btn" disabled={loading} onClick={() => void ask(s)}>
{s}
</button>
))}
</div>
<div className="community-compose reveal">
<textarea
rows={4}
placeholder={t.ai.placeholder}
value={message}
onChange={(e) => setMessage(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) void ask();
}}
/>
<button type="button" className="btn btn-primary" disabled={loading} onClick={() => void ask()}>
{loading ? "思考中…" : t.ai.ask}
</button>
<p className="section-desc">Ctrl/⌘ + Enter 发送</p>
</div>
{reply && (
<div className="ai-reply reveal">
<p>{reply}</p>
<div className="ai-cities">
{cities.map((c) => (
<Link key={c.slug} href={`/destinations/${c.slug}`} className="btn btn-sm">
{c.emoji} {c.name}
</Link>
))}
<Link href="/next-stop" className="btn btn-sm">
{t.ai.nextStop}
</Link>
<Link href="/compare" className="btn btn-sm">
城市对比
</Link>
<Link href="/plan" className="btn btn-sm">
写入计划
</Link>
</div>
</div>
)}
<RingNext steps={rings.afterDiscover} />
</div>
</div>
);
}