工作階段管理

建立具有工作階段列表和載入功能的對話歷史 UI。

管理對話歷史,讓使用者可以檢視過去的對話並從上次中斷的地方繼續。

概觀

Lens OS 會自動儲存對話。你可以使用 Sessions API 讓使用者:

  • 檢視過去對話的列表
  • 載入並繼續先前的對話
  • 開始新的對話

歷史側邊欄狀態

狀態管理
interface Session {
  id: string;
  title: string;
  messageCount: number;
  created_at: string;
}

const [sessions, setSessions] = useState<Session[]>([]);
const [showHistory, setShowHistory] = useState(false);
const [loadingHistory, setLoadingHistory] = useState(false);

取得工作階段

載入工作階段列表
const loadSessions = async () => {
  setLoadingHistory(true);
  try {
    const res = await fetch("/api/lens/agent/sessions");
    if (res.ok) {
      const data = await res.json();
      if (data.success) {
        setSessions(data.sessions);
      }
    }
  } finally {
    setLoadingHistory(false);
  }
};

// Load when opening sidebar
const toggleHistory = () => {
  const opening = !showHistory;
  setShowHistory(opening);
  if (opening) loadSessions();
};

載入工作階段

載入並還原對話
const { loadSession, sessionId } = useLensAgent({...});

const handleLoadSession = async (targetId: string) => {
  const res = await fetch(`/api/lens/agent/sessions/${targetId}`);
  if (!res.ok) return;

  const data = await res.json();
  if (data.success) {
    // Messages may need to be reversed
    loadSession(targetId, data.messages.reverse());
    setShowHistory(false);
  }
};

格式化日期

日期格式化輔助函式
const formatDate = (dateStr: string, id?: string) => {
  let date = new Date(dateStr);

  // Fallback: extract timestamp from session ID
  if (isNaN(date.getTime()) && id) {
    const match = id.match(/^session-(\d+)-/);
    if (match) date = new Date(Number(match[1]));
  }

  if (isNaN(date.getTime())) return "";

  const now = new Date();
  const diffDays = Math.floor(
    (now.getTime() - date.getTime()) / (1000 * 60 * 60 * 24)
  );

  if (diffDays === 0) return "今天";
  if (diffDays === 1) return "昨天";
  if (diffDays < 7) return `${diffDays} 天前`;

  return date.toLocaleDateString("zh-TW", {
    month: "short",
    day: "numeric",
  });
};

UI 實作

歷史側邊欄
{/* History Button */}
<button onClick={toggleHistory}>
  <svg /* icon */ />
  歷史
</button>

{/* Sidebar Backdrop */}
{showHistory && (
  <div
    className="fixed inset-0 bg-black/20 z-40"
    onClick={() => setShowHistory(false)}
  />
)}

{/* Sidebar */}
<div className={`
  fixed top-0 left-0 h-full w-72 bg-white shadow-lg z-50
  transform transition-transform
  ${showHistory ? "translate-x-0" : "-translate-x-full"}
`}>
  {/* Header */}
  <div className="flex items-center justify-between p-4 border-b">
    <span className="font-semibold">對話歷史</span>
    <button onClick={() => setShowHistory(false)}>✕</button>
  </div>

  {/* List */}
  <div className="overflow-auto h-[calc(100%-60px)]">
    {loadingHistory ? (
      <div className="p-4 text-center text-gray-400">載入中...</div>
    ) : sessions.length === 0 ? (
      <div className="p-4 text-center text-gray-400">沒有對話歷史</div>
    ) : (
      sessions.map((s) => (
        <div
          key={s.id}
          className={`
            p-4 border-b cursor-pointer hover:bg-gray-50
            ${s.id === sessionId ? "bg-blue-50" : ""}
          `}
          onClick={() => handleLoadSession(s.id)}
        >
          <div className="font-medium truncate">
            {s.title || "新對話"}
          </div>
          <div className="text-xs text-gray-400 mt-1 flex justify-between">
            <span>{formatDate(s.created_at, s.id)}</span>
            <span>{s.messageCount} 則訊息</span>
          </div>
        </div>
      ))
    )}
  </div>
</div>

新工作階段

開始新對話
const { newSession } = useLensAgent({...});

const handleNewSession = () => {
  newSession();
  // Clear tool cards and other state
  setToolCards([]);
};

完整範例

完整實作
export function LensChat({ userId }: { userId: string }) {
  const [showHistory, setShowHistory] = useState(false);
  const [sessions, setSessions] = useState<Session[]>([]);
  const [loadingHistory, setLoadingHistory] = useState(false);

  const {
    messages,
    sessionId,
    loadSession,
    newSession,
    // ...
  } = useLensAgent({ endpoint: "/api/lens/agent/chat", userId });

  const loadSessions = async () => {
    setLoadingHistory(true);
    const res = await fetch("/api/lens/agent/sessions");
    const data = await res.json();
    if (data.success) setSessions(data.sessions);
    setLoadingHistory(false);
  };

  const toggleHistory = () => {
    setShowHistory(!showHistory);
    if (!showHistory) loadSessions();
  };

  const handleLoadSession = async (targetId: string) => {
    const res = await fetch(`/api/lens/agent/sessions/${targetId}`);
    const data = await res.json();
    if (data.success) {
      loadSession(targetId, data.messages.reverse());
      setShowHistory(false);
    }
  };

  return (
    <div className="relative">
      {/* Main Chat UI */}
      {/* ... */}

      {/* History Sidebar */}
      {/* ... */}
    </div>
  );
}

Note

工作階段由 Lens OS 自動管理。你不需要手動處理工作階段的建立或儲存。