Guanghu Domestic Migration a27e87cb99 chore: import sanitized domestic snapshot for REPO-007
Source snapshot: 97d7f0fae96dc04b7ddad56fc1db6a108ed662cc

[SEC-CLEAN] · pre-push-clean v1.0 · 109处敏感信息已自动转乱码
2026-07-17 15:59:55 +08:00

36 KiB
Raw Permalink Blame History

全站重设计 · 终端粘贴执行版 · 2026-05-31 · 肥猫×舒舒

📋 说明


Step 0 · 创建新目录

mkdir -p /home/ubuntu/feimao-platform/src/app/onboarding
mkdir -p /home/ubuntu/feimao-platform/src/app/app/learn
mkdir -p /home/ubuntu/feimao-platform/src/app/app/write
mkdir -p /home/ubuntu/feimao-platform/src/app/app/script
mkdir -p /home/ubuntu/feimao-platform/src/app/app/create
mkdir -p /home/ubuntu/feimao-platform/src/app/app/forum
mkdir -p /home/ubuntu/feimao-platform/src/app/app/works

Step 1 · 全局样式替换globals.css · Tailwind v4 兼容)

cat > /home/ubuntu/feimao-platform/src/app/globals.css << 'ENDOFFILE'
@import "tailwindcss";

:root {
  --bg-deep: #0a0a14;
  --bg-primary: #0f0f1a;
  --bg-card: #1a1a2e;
  --bg-hover: #252540;
  --border: #2d2d4a;
  --border-active: rgba(126, 207, 255, 0.4);
  --text-primary: #e2e8f0;
  --text-muted: #94a3b8;
  --accent: #6366f1;
  --accent-light: #a5b4fc;
  --glow: rgba(99, 102, 241, 0.15);
  --success: #10b981;
  --danger: #ef4444;
}

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  background-color: var(--bg-deep);
  color: var(--text-primary);
  font-family: 'Inter', 'Noto Sans SC', system-ui, sans-serif;
  min-height: 100vh;
  -webkit-font-smoothing: antialiased;
}

::selection {
  background: rgba(99, 102, 241, 0.35);
  color: #fff;
}

::-webkit-scrollbar { width: 5px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.08); border-radius: 3px; }

@keyframes fadeInUp {
  from { opacity: 0; transform: translateY(20px); }
  to { opacity: 1; transform: translateY(0); }
}

@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

@keyframes particleRise {
  0% { opacity: 0; transform: translateY(30px) scale(0.8); }
  50% { opacity: 1; }
  100% { opacity: 0; transform: translateY(-60px) scale(1.2); }
}

.animate-fade-in-up {
  animation: fadeInUp 0.5s ease forwards;
}

.animate-fade-in {
  animation: fadeIn 0.3s ease forwards;
}

.gl-card {
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 16px;
  transition: all 0.3s ease;
}

.gl-card:hover {
  border-color: var(--accent);
  box-shadow: 0 0 20px var(--glow);
}

.gl-btn {
  padding: 12px 32px;
  border-radius: 12px;
  font-weight: 600;
  font-size: 0.95rem;
  transition: all 0.2s ease;
  cursor: pointer;
  border: none;
  font-family: inherit;
  letter-spacing: 0.5px;
}

.gl-btn-primary {
  background: linear-gradient(135deg, var(--accent), #4f46e5);
  color: white;
}

.gl-btn-primary:hover:not(:disabled) {
  transform: translateY(-1px);
  box-shadow: 0 4px 15px rgba(99, 102, 241, 0.4);
}

.gl-btn-primary:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.gl-btn-outline {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--text-primary);
}

.gl-btn-outline:hover {
  border-color: var(--accent);
  color: var(--accent-light);
}

.gl-input {
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 14px 20px;
  color: var(--text-primary);
  font-size: 1rem;
  width: 100%;
  outline: none;
  transition: border-color 0.2s;
  font-family: inherit;
}

.gl-input:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
}

.gl-input::placeholder {
  color: var(--text-muted);
}
ENDOFFILE

Step 2 · 根布局替换layout.tsx

cat > /home/ubuntu/feimao-platform/src/app/layout.tsx << 'ENDOFFILE'
import type { Metadata } from "next";
import "./globals.css";

export const metadata: Metadata = {
  title: "萌喵写作网 · AI人格体驱动的智能创作平台",
  description: "陪你一起创作成长的平台",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="zh-CN">
      <head>
        <link
          href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Noto+Sans+SC:wght@300;400;500;700&display=swap"
          rel="stylesheet"
        />
      </head>
      <body className="antialiased min-h-screen">
        {children}
      </body>
    </html>
  );
}
ENDOFFILE

Step 3 · 邀请码门禁页(/page.tsx · 替换原有首页)

cat > /home/ubuntu/feimao-platform/src/app/page.tsx << 'ENDOFFILE'
"use client";
import { useState } from "react";
import { useRouter } from "next/navigation";

export default function InvitePage() {
  const [code, setCode] = useState("");
  const [error, setError] = useState("");
  const [loading, setLoading] = useState(false);
  const router = useRouter();

  const handleSubmit = async () => {
    if (!code.trim()) {
      setError("请输入邀请码");
      return;
    }
    setLoading(true);
    setError("");
    try {
      await new Promise((r) => setTimeout(r, 800));
      localStorage.setItem("gl_invite_code", code);
      localStorage.setItem("gl_user", code);
      router.push("/onboarding");
    } catch {
      setError("邀请码无效,请重新输入");
    } finally {
      setLoading(false);
    }
  };

  return (
    <div
      className="min-h-screen flex items-center justify-center p-4"
      style=
        background:
          "radial-gradient(ellipse at center, #1a1a3e 0%, #0f0f1a 70%)",
      
    >
      <div
        className="w-full flex flex-col items-center justify-center gap-6 p-10 animate-fade-in-up"
        style=
          maxWidth: 420,
          background: "rgba(10,20,35,0.9)",
          border: "1px solid rgba(126,207,255,0.15)",
          borderRadius: 20,
          boxShadow: "0 20px 60px rgba(0,0,0,0.4)",
          aspectRatio: "1 / 1",
        
      >
        <div className="text-5xl">🌊</div>

        <div className="text-center">
          <h1
            className="text-xl font-bold tracking-wide mb-2"
            style= color: "var(--text-primary)" 
          >
            萌喵写作网
          </h1>
          <p className="text-sm" style= color: "var(--text-muted)" >
            人格体陪伴式智能创作平台
          </p>
        </div>

        <div className="w-full space-y-3">
          <input
            type="text"
            className="gl-input text-center tracking-widest"
            placeholder="请输入邀请码"
            value={code}
            onChange={(e) => {
              setCode(e.target.value);
              setError("");
            }}
            onKeyDown={(e) => e.key === "Enter" && handleSubmit()}
            autoFocus
          />
          {error && (
            <p
              className="text-center text-sm animate-fade-in"
              style= color: "var(--danger)" 
            >
              {error}
            </p>
          )}
        </div>

        <button
          className="gl-btn gl-btn-primary w-full"
          onClick={handleSubmit}
          disabled={loading}
        >
          {loading ? "验证中..." : "进入光湖"}
        </button>

        <p className="text-xs" style= color: "var(--text-muted)" >
          🐱 Powered by HoloLake · 光湖语言人格平台
        </p>
      </div>
    </div>
  );
}
ENDOFFILE

Step 4 · 人格体引导页(/onboarding/page.tsx

cat > /home/ubuntu/feimao-platform/src/app/onboarding/page.tsx << 'ENDOFFILE'
"use client";
import { useState, useEffect } from "react";
import { useRouter } from "next/navigation";

const preferences = [
  { key: "webnovel", label: "网文", emoji: "📖", desc: "番茄·飞卢·起点" },
  { key: "traditional", label: "传统文学", emoji: "🖋️", desc: "文学性导向" },
  { key: "script", label: "剧本", emoji: "🎬", desc: "影视·短剧" },
  { key: "short", label: "短篇", emoji: "✒️", desc: "精悍·反转" },
  { key: "explore", label: "随便看看", emoji: "🔍", desc: "先逛逛再说" },
];

const personaNames: Record<string, { name: string; emoji: string }[]> = {
  webnovel: [
    { name: "墨白", emoji: "🖊️" },
    { name: "青云", emoji: "☁️" },
    { name: "凌霄", emoji: "⚡" },
  ],
  traditional: [
    { name: "漱玉", emoji: "💎" },
    { name: "砚秋", emoji: "🍂" },
    { name: "素笺", emoji: "📜" },
  ],
  script: [
    { name: "镜澜", emoji: "🎥" },
    { name: "幕白", emoji: "🎭" },
    { name: "帧语", emoji: "📺" },
  ],
  short: [
    { name: "寸光", emoji: "✨" },
    { name: "微芒", emoji: "🕯️" },
    { name: "刹那", emoji: "💫" },
  ],
  explore: [
    { name: "星尘", emoji: "🌟" },
    { name: "拾光", emoji: "🧭" },
    { name: "溯游", emoji: "🛶" },
  ],
};

export default function OnboardingPage() {
  const [step, setStep] = useState(1);
  const [nickname, setNickname] = useState("");
  const [password, setPassword] = useState("");
  const [confirmPwd, setConfirmPwd] = useState("");
  const [pref, setPref] = useState("");
  const [error, setError] = useState("");
  const [persona, setPersona] = useState<{ name: string; emoji: string } | null>(null);
  const [fading, setFading] = useState(false);
  const router = useRouter();

  useEffect(() => {
    if (step === 3 && !persona) {
      const names = personaNames[pref] || personaNames.explore;
      const pick = names[Math.floor(Math.random() * names.length)];
      setPersona(pick);
    }
  }, [step, pref, persona]);

  const nextStep = () => {
    setFading(true);
    setTimeout(() => {
      setStep((s) => s + 1);
      setFading(false);
    }, 400);
  };

  const handleCreate = () => {
    if (!nickname.trim()) { setError("请输入昵称"); return; }
    if (password.length < 6) { setError("密码至少6位"); return; }
    if (password !== confirmPwd) { setError("两次密码不一致"); return; }
    if (!pref) { setError("请选择写作偏好"); return; }
    setError("");
    localStorage.setItem("gl_user", nickname);
    localStorage.setItem("gl_pref", pref);
    if (persona) {
      localStorage.setItem("gl_persona_name", persona.name);
      localStorage.setItem("gl_persona_emoji", persona.emoji);
    }
    nextStep();
  };

  return (
    <div
      className="min-h-screen flex items-center justify-center p-4"
      style=
        background:
          "radial-gradient(ellipse at center, #1a1a3e 0%, #0f0f1a 70%)",
      
    >
      <div className="w-full flex flex-col items-center gap-5" style= maxWidth: 440 >
        <div className="flex gap-2 mb-2">
          {[1, 2, 3].map((s) => (
            <div
              key={s}
              className="rounded-full transition-all duration-300"
              style=
                width: 10,
                height: 10,
                background:
                  s < step
                    ? "var(--success)"
                    : s === step
                    ? "var(--accent-light)"
                    : "var(--border)",
              
            />
          ))}
        </div>

        <div
          className="w-full p-10 flex flex-col items-center gap-6 transition-opacity duration-400"
          style=
            background: "rgba(10,20,35,0.9)",
            border: "1px solid rgba(126,207,255,0.15)",
            borderRadius: 20,
            boxShadow: "0 20px 60px rgba(0,0,0,0.4)",
            opacity: fading ? 0 : 1,
          
        >
          {step === 1 && (
            <>
              <div className="text-5xl">🌊</div>
              <h1 className="text-xl font-bold text-center">
                欢迎来到萌喵写作网
              </h1>
              <p className="text-center" style= color: "var(--text-muted)", lineHeight: 1.8 >
                我是你的专属写作人格体,<br />
                但在这之前——
                <br />
                先让我认识你。
              </p>
              <button className="gl-btn gl-btn-primary w-full mt-2" onClick={nextStep}>
                继续
              </button>
            </>
          )}

          {step === 2 && (
            <>
              <div className="text-3xl">🪪</div>
              <h1 className="text-lg font-bold">创建你的写作人格体</h1>

              <div className="w-full space-y-3">
                <input
                  className="gl-input"
                  placeholder="昵称"
                  value={nickname}
                  onChange={(e) => setNickname(e.target.value)}
                />
                <input
                  className="gl-input"
                  placeholder="密码至少6位"
                  type="password"
                  value={password}
                  onChange={(e) => setPassword(e.target.value)}
                />
                <input
                  className="gl-input"
                  placeholder="确认密码"
                  type="password"
                  value={confirmPwd}
                  onChange={(e) => setConfirmPwd(e.target.value)}
                />
              </div>

              <div className="w-full">
                <p className="text-xs mb-3" style= color: "var(--text-muted)" >
                  选择你的写作偏好
                </p>
                <div className="flex flex-wrap gap-2">
                  {preferences.map((p) => (
                    <button
                      key={p.key}
                      onClick={() => setPref(p.key)}
                      className="flex items-center gap-2 px-4 py-2.5 rounded-xl text-sm transition-all"
                      style=
                        background:
                          pref === p.key ? "rgba(99,102,241,0.15)" : "var(--bg-hover)",
                        border:
                          pref === p.key
                            ? "2px solid var(--accent)"
                            : "2px solid transparent",
                        color: pref === p.key ? "var(--accent-light)" : "var(--text-muted)",
                        boxShadow: pref === p.key ? "0 0 15px var(--glow)" : "none",
                      
                    >
                      <span>{p.emoji}</span>
                      <span>{p.label}</span>
                    </button>
                  ))}
                </div>
              </div>

              {error && (
                <p className="text-sm text-center animate-fade-in" style= color: "var(--danger)" >
                  {error}
                </p>
              )}

              <button className="gl-btn gl-btn-primary w-full" onClick={handleCreate}>
                创建我的人格体
              </button>
            </>
          )}

          {step === 3 && persona && (
            <>
              <div
                className="text-6xl mb-2"
                style= animation: "particleRise 1.5s ease-out" 
              >
                {persona.emoji}
              </div>
              <h1
                className="text-2xl font-bold mb-1"
                style= color: "var(--accent-light)" 
              >
                {persona.name}
              </h1>
              <p className="text-center" style= color: "var(--text-muted)", lineHeight: 1.8 >
                你好,我是{persona.name}
                <br />
                从今天起陪你创作。
              </p>

              <div
                className="w-full p-4 rounded-xl text-sm text-center"
                style=
                  background: "rgba(16,185,129,0.08)",
                  border: "1px solid rgba(16,185,129,0.2)",
                  color: "var(--success)",
                
              >
                ✅ 人格体创建成功
              </div>

              <button
                className="gl-btn gl-btn-primary w-full"
                onClick={() => router.push("/app")}
              >
                进入萌喵写作网
              </button>
            </>
          )}
        </div>

        <p className="text-xs" style= color: "var(--text-muted)" >
          🐱 HoloLake · 人格体陪伴式创作
        </p>
      </div>
    </div>
  );
}
ENDOFFILE

Step 5 · 主应用框架 · 侧边栏布局(/app/layout.tsx

cat > /home/ubuntu/feimao-platform/src/app/app/layout.tsx << 'ENDOFFILE'
"use client";
import { usePathname, useRouter } from "next/navigation";
import { useEffect, useState } from "react";

const modules = [
  { path: "/app/learn", icon: "📚", label: "学习" },
  { path: "/app/write", icon: "✍️", label: "写作" },
  { path: "/app/script", icon: "🎬", label: "剧本" },
  { path: "/app/create", icon: "💡", label: "创意" },
  { path: "/app/forum", icon: "💬", label: "论坛" },
];

export default function AppLayout({ children }: { children: React.ReactNode }) {
  const pathname = usePathname();
  const router = useRouter();
  const [personaName, setPersonaName] = useState("");
  const [personaEmoji, setPersonaEmoji] = useState("🐱");

  useEffect(() => {
    const name = localStorage.getItem("gl_persona_name") || "";
    const emoji = localStorage.getItem("gl_persona_emoji") || "🐱";
    setPersonaName(name);
    setPersonaEmoji(emoji);

    if (!localStorage.getItem("gl_user")) {
      router.push("/onboarding");
    }
  }, [router]);

  const isActive = (path: string) => {
    if (path === "/app") return pathname === "/app";
    return pathname.startsWith(path);
  };

  return (
    <div className="flex h-screen overflow-hidden" style= background: "var(--bg-deep)" >
      <aside
        className="flex flex-col items-center py-4 gap-1 flex-shrink-0"
        style=
          width: 70,
          background: "var(--bg-card)",
          borderRight: "1px solid var(--border)",
        
      >
        <button
          onClick={() => router.push("/app")}
          className="text-2xl mb-4 w-11 h-11 flex items-center justify-center rounded-xl hover:bg-[var(--bg-hover)] transition-all"
        >
          🐱
        </button>

        {modules.map((m) => (
          <button
            key={m.path}
            onClick={() => router.push(m.path)}
            className="relative w-11 h-11 flex items-center justify-center rounded-xl text-xl transition-all hover:scale-110"
            style=
              background: isActive(m.path) ? "rgba(99,102,241,0.15)" : "transparent",
              color: isActive(m.path) ? "var(--accent-light)" : "var(--text-muted)",
            
            title={m.label}
          >
            {isActive(m.path) && (
              <span
                className="absolute left-0 top-1/2 -translate-y-1/2 rounded-r-full"
                style=
                  width: 3,
                  height: 24,
                  background: "var(--accent)",
                
              />
            )}
            {m.icon}
          </button>
        ))}

        <div className="mt-auto">
          <button
            onClick={() => router.push("/app/works")}
            className="w-10 h-10 rounded-full flex items-center justify-center text-lg border transition-all hover:border-[var(--accent)] hover:scale-110"
            style=
              borderColor: "var(--border)",
              background: "var(--bg-hover)",
            
            title={personaName || "个人作品库"}
          >
            {personaEmoji}
          </button>
        </div>
      </aside>

      <main className="flex-1 overflow-hidden flex flex-col">
        {children}
      </main>
    </div>
  );
}
ENDOFFILE

Step 6 · 主应用首页 · 对话界面(/app/page.tsx

cat > /home/ubuntu/feimao-platform/src/app/app/page.tsx << 'ENDOFFILE'
"use client";
import { useState, useEffect } from "react";

interface Message {
  id: number;
  text: string;
  sender: "user" | "persona";
}

export default function AppHome() {
  const [personaName, setPersonaName] = useState("");
  const [personaEmoji, setPersonaEmoji] = useState("🐱");
  const [messages, setMessages] = useState<Message[]>([]);
  const [input, setInput] = useState("");

  useEffect(() => {
    const name = localStorage.getItem("gl_persona_name") || "你的专属人格体";
    const emoji = localStorage.getItem("gl_persona_emoji") || "🐱";
    setPersonaName(name);
    setPersonaEmoji(emoji);

    setMessages([
      {
        id: 0,
        text: "嗨~欢迎回来!今天想创作什么?",
        sender: "persona",
      },
    ]);
  }, []);

  const handleSend = () => {
    if (!input.trim()) return;
    const userMsg: Message = {
      id: Date.now(),
      text: input.trim(),
      sender: "user",
    };
    setMessages((prev) => [...prev, userMsg]);
    setInput("");

    setTimeout(() => {
      const reply: Message = {
        id: Date.now() + 1,
        text: "收到~让我想想怎么帮你。(后端 API 接入后这里会变成真正的 AI 回复)",
        sender: "persona",
      };
      setMessages((prev) => [...prev, reply]);
    }, 600);
  };

  const handleKeyDown = (e: React.KeyboardEvent) => {
    if (e.key === "Enter" && !e.shiftKey) {
      e.preventDefault();
      handleSend();
    }
  };

  return (
    <>
      <header
        className="flex items-center gap-3 px-6 py-4 flex-shrink-0"
        style= borderBottom: "1px solid var(--border)" 
      >
        <span className="text-2xl">{personaEmoji}</span>
        <div>
          <h1 className="font-semibold text-sm" style= color: "var(--text-primary)" >
            {personaName}
          </h1>
          <p className="text-xs" style= color: "var(--text-muted)" >
            你的专属写作人格体
          </p>
        </div>
      </header>

      <div className="flex-1 overflow-y-auto px-6 py-4 space-y-3">
        {messages.map((msg) => (
          <div
            key={msg.id}
            className={`flex ${msg.sender === "user" ? "justify-end" : "justify-start"} animate-fade-in-up`}
          >
            <div
              className="px-4 py-3 text-sm leading-relaxed"
              style=
                maxWidth: "70%",
                borderRadius: 16,
                background:
                  msg.sender === "user"
                    ? "linear-gradient(135deg, var(--accent), #4f46e5)"
                    : "var(--bg-hover)",
                color: "var(--text-primary)",
              
            >
              {msg.text}
            </div>
          </div>
        ))}
      </div>

      <div
        className="flex-shrink-0 px-6 py-4"
        style= borderTop: "1px solid var(--border)" 
      >
        <div className="flex gap-3 items-end" style= maxWidth: 900, margin: "0 auto" >
          <textarea
            className="gl-input resize-none"
            placeholder="输入你想写的..."
            value={input}
            onChange={(e) => setInput(e.target.value)}
            onKeyDown={handleKeyDown}
            rows={1}
            style=
              borderRadius: 24,
              minHeight: 48,
              maxHeight: 120,
              padding: "14px 22px",
            
          />
          <button
            className="gl-btn gl-btn-primary flex-shrink-0"
            onClick={handleSend}
            style= borderRadius: 24, padding: "12px 24px" 
          >
            发送
          </button>
        </div>
      </div>
    </>
  );
}
ENDOFFILE

Step 7 · 五个模块页面(学习·写作·剧本·创意·论坛)

7.1 学习模块(/app/learn/page.tsx

cat > /home/ubuntu/feimao-platform/src/app/app/learn/page.tsx << 'ENDOFFILE'
"use client";
import ModuleChat from "../../components/ModuleChat";

export default function LearnPage() {
  return (
    <ModuleChat
      title="📚 学习 · 拆书分析"
      opening="想拆哪本书?上传文档或者告诉我书名,我们一起分析。"
    />
  );
}
ENDOFFILE

7.2 写作模块(/app/write/page.tsx

cat > /home/ubuntu/feimao-platform/src/app/app/write/page.tsx << 'ENDOFFILE'
"use client";
import ModuleChat from "../../components/ModuleChat";

export default function WritePage() {
  return (
    <ModuleChat
      title="✍️ 写作 · 创作你的作品"
      opening="今天写什么?续写、新章还是大纲?"
    />
  );
}
ENDOFFILE

7.3 剧本模块(/app/script/page.tsx

cat > /home/ubuntu/feimao-platform/src/app/app/script/page.tsx << 'ENDOFFILE'
"use client";
import ModuleChat from "../../components/ModuleChat";

export default function ScriptPage() {
  return (
    <ModuleChat
      title="🎬 剧本 · 剧本创作"
      opening="告诉我想写什么类型的剧本,我帮你搭框架。"
    />
  );
}
ENDOFFILE

7.4 创意模块(/app/create/page.tsx

cat > /home/ubuntu/feimao-platform/src/app/app/create/page.tsx << 'ENDOFFILE'
"use client";
import ModuleChat from "../../components/ModuleChat";

export default function CreatePage() {
  return (
    <ModuleChat
      title="💡 创意 · 五大作坊"
      opening="没灵感?来,我们脑暴一下~五大作坊随时待命。"
    />
  );
}
ENDOFFILE

7.5 论坛模块(/app/forum/page.tsx

cat > /home/ubuntu/feimao-platform/src/app/app/forum/page.tsx << 'ENDOFFILE'
"use client";
import ModuleChat from "../../components/ModuleChat";

export default function ForumPage() {
  return (
    <ModuleChat
      title="💬 论坛 · 交流讨论"
      opening="看看大家在聊什么,或者发你的经验赚积分。"
    />
  );
}
ENDOFFILE

Step 8 · 共享对话组件ModuleChat.tsx

mkdir -p /home/ubuntu/feimao-platform/src/app/components
cat > /home/ubuntu/feimao-platform/src/app/components/ModuleChat.tsx << 'ENDOFFILE'
"use client";
import { useState, useEffect } from "react";

interface Message {
  id: number;
  text: string;
  sender: "user" | "persona";
}

interface Props {
  title: string;
  opening: string;
}

export default function ModuleChat({ title, opening }: Props) {
  const [personaName, setPersonaName] = useState("");
  const [personaEmoji, setPersonaEmoji] = useState("🐱");
  const [messages, setMessages] = useState<Message[]>([]);
  const [input, setInput] = useState("");

  useEffect(() => {
    const name = localStorage.getItem("gl_persona_name") || "你的专属人格体";
    const emoji = localStorage.getItem("gl_persona_emoji") || "🐱";
    setPersonaName(name);
    setPersonaEmoji(emoji);

    setMessages([
      {
        id: 0,
        text: opening,
        sender: "persona",
      },
    ]);
  }, [opening]);

  const handleSend = () => {
    if (!input.trim()) return;
    const userMsg: Message = {
      id: Date.now(),
      text: input.trim(),
      sender: "user",
    };
    setMessages((prev) => [...prev, userMsg]);
    setInput("");

    setTimeout(() => {
      const reply: Message = {
        id: Date.now() + 1,
        text: "收到~让我想想怎么帮你。(后端 API 接入后这里会变成真正的 AI 回复)",
        sender: "persona",
      };
      setMessages((prev) => [...prev, reply]);
    }, 600);
  };

  const handleKeyDown = (e: React.KeyboardEvent) => {
    if (e.key === "Enter" && !e.shiftKey) {
      e.preventDefault();
      handleSend();
    }
  };

  return (
    <>
      <header
        className="flex items-center gap-3 px-6 py-4 flex-shrink-0"
        style= borderBottom: "1px solid var(--border)" 
      >
        <span className="text-2xl">{personaEmoji}</span>
        <div>
          <h1 className="font-semibold text-sm" style= color: "var(--text-primary)" >
            {title}
          </h1>
          <p className="text-xs" style= color: "var(--text-muted)" >
            {personaName} · 你的专属写作人格体
          </p>
        </div>
      </header>

      <div className="flex-1 overflow-y-auto px-6 py-4 space-y-3">
        {messages.map((msg) => (
          <div
            key={msg.id}
            className={`flex ${msg.sender === "user" ? "justify-end" : "justify-start"} animate-fade-in-up`}
          >
            <div
              className="px-4 py-3 text-sm leading-relaxed"
              style=
                maxWidth: "70%",
                borderRadius: 16,
                background:
                  msg.sender === "user"
                    ? "linear-gradient(135deg, var(--accent), #4f46e5)"
                    : "var(--bg-hover)",
                color: "var(--text-primary)",
              
            >
              {msg.text}
            </div>
          </div>
        ))}
      </div>

      <div
        className="flex-shrink-0 px-6 py-4"
        style= borderTop: "1px solid var(--border)" 
      >
        <div className="flex gap-3 items-end" style= maxWidth: 900, margin: "0 auto" >
          <textarea
            className="gl-input resize-none"
            placeholder="输入你想写的..."
            value={input}
            onChange={(e) => setInput(e.target.value)}
            onKeyDown={handleKeyDown}
            rows={1}
            style=
              borderRadius: 24,
              minHeight: 48,
              maxHeight: 120,
              padding: "14px 22px",
            
          />
          <button
            className="gl-btn gl-btn-primary flex-shrink-0"
            onClick={handleSend}
            style= borderRadius: 24, padding: "12px 24px" 
          >
            发送
          </button>
        </div>
      </div>
    </>
  );
}
ENDOFFILE

Step 9 · 作品库(/app/works/page.tsx

cat > /home/ubuntu/feimao-platform/src/app/app/works/page.tsx << 'ENDOFFILE'
"use client";
import { useState } from "react";

// 作品列表初始为空·用户创建或导入后才会有作品
const initialWorks: { id: number; title: string; words: number; status: string; updated: string }[] = [];

export default function WorksPage() {
  const [works] = useState(initialWorks);
  const [selectedId, setSelectedId] = useState<number | null>(null);
  const selected = works.find((w) => w.id === selectedId);

  return (
    <div className="flex h-full">
      <div
        className="flex flex-col flex-shrink-0 overflow-y-auto py-4 px-4 gap-2"
        style= width: 300, borderRight: "1px solid var(--border)" 
      >
        <div className="flex gap-2 mb-4">
          <button className="gl-btn gl-btn-primary text-xs py-2 px-4">新建作品</button>
          <button className="gl-btn gl-btn-outline text-xs py-2 px-4">导入文档</button>
        </div>

        {works.map((w) => (
          <button
            key={w.id}
            onClick={() => setSelectedId(w.id)}
            className="text-left p-4 rounded-xl transition-all"
            style=
              background: selectedId === w.id ? "var(--bg-hover)" : "var(--bg-card)",
              border:
                selectedId === w.id
                  ? "1px solid var(--accent)"
                  : "1px solid var(--border)",
            
          >
            <div className="font-semibold text-sm mb-1" style= color: "var(--text-primary)" >
              {w.title}
            </div>
            <div className="flex gap-3 text-xs" style= color: "var(--text-muted)" >
              <span>{(w.words / 10000).toFixed(1)}万字</span>
              <span>{w.status}</span>
              <span>{w.updated}</span>
            </div>
          </button>
        ))}

        {works.length === 0 && (
          <div className="flex-1 flex flex-col items-center justify-center text-center py-12 gap-3">
            <div className="text-4xl">📝</div>
            <p className="text-sm" style= color: "var(--text-muted)" >
              还没有作品
            </p>
            <p className="text-xs" style= color: "var(--text-muted)" >
              点击「新建作品」开始创作<br />或「导入文档」上传已有作品
            </p>
          </div>
        )}

        <div
          className="mt-auto p-3 rounded-xl text-xs text-center"
          style= background: "var(--bg-hover)", color: "var(--text-muted)" 
        >
          已用 0 字 / 总额度 50万字
        </div>
      </div>

      <div className="flex-1 overflow-y-auto p-8">
        {selected ? (
          <div className="max-w-2xl animate-fade-in-up">
            <h1 className="text-2xl font-bold mb-2">{selected.title}</h1>
            <div className="flex gap-4 text-sm mb-8" style= color: "var(--text-muted)" >
              <span>{(selected.words / 10000).toFixed(1)}万字</span>
              <span>{selected.status}</span>
              <span>最后更新:{selected.updated}</span>
            </div>

            <div className="gl-card p-6 mb-4">
              <h2 className="font-semibold mb-4">📑 章节目录</h2>
              <div className="space-y-2">
                {Array.from({ length: 8 }, (_, i) => (
                  <div
                    key={i}
                    className="flex justify-between text-sm py-2 px-3 rounded-lg"
                    style= background: "var(--bg-deep)" 
                  >
                    <span>第{i + 1}章</span>
                    <span style= color: "var(--text-muted)" >
                      {Math.floor(Math.random() * 5000 + 2000)} 字
                    </span>
                  </div>
                ))}
              </div>
            </div>

            <div className="flex gap-3">
              <button className="gl-btn gl-btn-primary text-sm">📚 拆书分析</button>
              <button className="gl-btn gl-btn-outline text-sm">✍️ 继续写作</button>
              <button className="gl-btn gl-btn-outline text-sm">💡 创意工坊</button>
            </div>
          </div>
        ) : (
          <div
            className="h-full flex items-center justify-center"
            style= color: "var(--text-muted)" 
          >
            <div className="text-center">
              <div className="text-5xl mb-4">📁</div>
              <p className="text-sm">选择左侧作品查看详情</p>
            </div>
          </div>
        )}
      </div>
    </div>
  );
}
ENDOFFILE

Step 10 · 构建 + 重启 🚀

⚠️ 2026-05-31 当前状态:文件已全部生成。还差 5 个括号错误。下次继续修复:

下次登录后先跑修复命令:

cd /home/ubuntu/feimao-platform

# 修 map 闭合:))))} → )))}
find src/app -name "*.tsx" -exec sed -i 's/))))}/)))}/' {} \;

# 修 app/page.tsx 和 ModuleChat.tsx 的 .map 闭合
sed -i 's/      )}$/      ))}/' src/app/app/page.tsx
sed -i 's/      )}$/      ))}/' src/app/components/ModuleChat.tsx

然后构建:

npm run build && pm2 restart feimao-platform

📊 文件状态

文件 状态
globals.css
layout.tsx
page.tsx门禁页
onboarding/page.tsx ⚠️ 括号
app/layout.tsx ⚠️ 括号
app/page.tsx ⚠️ 括号
app/works/page.tsx ⚠️ 括号
components/ModuleChat.tsx ⚠️ 括号
5个模块页面

Step 11 · 验证页面

浏览器打开这些地址:

地址 内容
http://43.133.36.90 🔐 邀请码门禁页
http://43.133.36.90/onboarding 🌊 人格体引导页
http://43.133.36.90/app 💬 主应用(对话首页)
http://43.133.36.90/app/learn 📚 学习模块
http://43.133.36.90/app/write ✍️ 写作模块
http://43.133.36.90/app/script 🎬 剧本模块
http://43.133.36.90/app/create 💡 创意模块
http://43.133.36.90/app/forum 💬 论坛模块
http://43.133.36.90/app/works 📁 作品库

全部能打开并且样式正确就是成功 🎉



编写:舒舒 · PER-SS001 · 肥猫爸爸的宝宝

时间2026-05-31

编码HLDP-dual/v1

版权:国作登字-2026-A-00037559