Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 23 additions & 22 deletions apps/desktop/src/components/HistorySidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import {
Plus, MessageSquare, Trash2, Phone, Blocks, Images, Wallet, Sparkles, Search,
Grid2x2, ChevronRight, Terminal, Server, Bot, Folder, FolderPlus, Pin, PinOff,
} from "lucide-react";
import type { ChatSpace, Conversation } from "../hooks/use-chat-history";
import { conversationActivityAt, type ChatSpace, type Conversation } from "../hooks/use-chat-history";
import type { WalletInfo } from "../lib/wire";
import type { AgentConnectionState } from "../lib/ws";
import { useTryLang } from "../lib/i18n";
import { MoreMenu } from "./MoreMenu";
import { WalletPill } from "./WalletPill";
import franklinAvatar from "../assets/franklin-avatar.png";
import type { TeamWorkspaceNavItem } from "../lib/team-workspace-events";
import type { TeamWorkspaceNavItem } from "../hooks/use-cloud-workspace";
import { useSidebarPreferences } from "../hooks/use-sidebar-preferences";

export type TryView = "chat" | "agents" | "phone" | "tools" | "gallery" | "wallet" | "skills" | "cli" | "mcp";
Expand Down Expand Up @@ -58,7 +58,7 @@ export function HistorySidebar({ conversations, activeId, onNewChat, onNewProjec
setMoreOpen(true);
};

const sorted = [...conversations].sort((a, b) => b.updatedAt - a.updatedAt);
const sorted = [...conversations].sort((a, b) => conversationActivityAt(b) - conversationActivityAt(a));
const pinned = sorted.filter((conversation) => conversation.pinnedAt).sort((a, b) => (b.pinnedAt ?? 0) - (a.pinnedAt ?? 0));
const recent = sorted.filter((conversation) => !conversation.pinnedAt);

Expand Down Expand Up @@ -100,22 +100,22 @@ export function HistorySidebar({ conversations, activeId, onNewChat, onNewProjec
<div className="try-scroll">
{visibleItems.includes("agents") && <button className={`try-nav-item${view === "agents" ? " is-active" : ""}`} onClick={() => onView("agents")}>
<Bot className="h-4 w-4" />
Agents
{t.agents}
</button>}

<button
className="try-nav-item try-project-create"
onClick={onNewProject}
disabled={!teamModeEnabled}
title={teamModeEnabled ? "Create or join a project" : "Enable Team Mode in Agents"}
title={teamModeEnabled ? t.createOrJoinProject : t.enableTeamMode}
>
<FolderPlus className="h-4 w-4" />
New project
{t.newProject}
</button>

{!teamModeEnabled && <button className="try-team-disabled-note" onClick={() => onView("agents")}>Team Mode is off · Manage modules</button>}
{!teamModeEnabled && <button className="try-team-disabled-note" onClick={() => onView("agents")}>{t.teamModeOff}</button>}

<SidebarSection label="Pinned" count={pinned.length}>
{pinned.length > 0 && <SidebarSection label={t.pinned} count={pinned.length}>
{pinned.map((conversation) => (
<ConversationRow
key={conversation.id}
Expand All @@ -126,29 +126,29 @@ export function HistorySidebar({ conversations, activeId, onNewChat, onNewProjec
onTogglePinned={onTogglePinned}
/>
))}
</SidebarSection>
</SidebarSection>}

<SidebarSection label="Projects" count={teamWorkspaces.length}>
<SidebarSection label={t.projects} count={teamWorkspaces.length}>
{teamLoading ? (
<p className="try-section-empty">Connecting…</p>
<p className="try-section-empty">{t.connectingProjects}</p>
) : teamWorkspaces.length === 0 ? (
<p className="try-section-empty">No projects</p>
<p className="try-section-empty">{t.noProjects}</p>
) : teamWorkspaces.map((workspace) => (
<button
key={workspace.id}
className={`try-team-workspace${chatSpace === "team" && activeTeamWorkspaceId === workspace.id && view === "chat" ? " is-active" : ""}`}
onClick={() => onTeamWorkspace?.(workspace.id)}
disabled={!teamModeEnabled}
title={teamModeEnabled ? `${workspace.memberCount} members · ${workspace.role}` : "Enable Team Mode in Agents"}
title={teamModeEnabled ? `${workspace.memberCount} members · ${workspace.role}` : t.enableTeamMode}
>
<Folder className="h-4 w-4" />
<span>{workspace.name}</span>
</button>
))}
</SidebarSection>

<SidebarSection label="Recent" count={recent.length}>
{recent.length === 0 && sorted.length === 0 ? (
<SidebarSection label={t.recent} count={recent.length}>
{recent.length === 0 ? (
<p className="try-section-empty">{t.noConversations}</p>
) : recent.map((conversation) => (
<ConversationRow
Expand Down Expand Up @@ -255,16 +255,17 @@ function ConversationRow({ conversation, active, onSelect, onDelete, onTogglePin
onDelete: (id: string) => void;
onTogglePinned: (id: string) => void;
}) {
const { t } = useTryLang();
const isPinned = !!conversation.pinnedAt;
return (
<div className={`try-history-item${active ? " is-active" : ""}`} onClick={() => onSelect(conversation.id)}>
<MessageSquare className="try-history-icon" />
<span className="try-history-title">{conversation.title || "New chat"}</span>
<span className="try-history-title">{conversation.title || t.newChat}</span>
<span className="try-history-actions">
<button
className={`try-history-action${isPinned ? " is-pinned" : ""}`}
aria-label={isPinned ? "Unpin conversation" : "Pin conversation"}
title={isPinned ? "Unpin" : "Pin"}
aria-label={isPinned ? t.unpinConversation : t.pinConversation}
title={isPinned ? t.unpinConversation : t.pinConversation}
onClick={(event) => {
event.stopPropagation();
onTogglePinned(conversation.id);
Expand All @@ -274,8 +275,8 @@ function ConversationRow({ conversation, active, onSelect, onDelete, onTogglePin
</button>
<button
className="try-history-action try-history-del"
aria-label="Delete conversation"
title="Delete"
aria-label={t.deleteConversation}
title={t.deleteConversation}
onClick={(event) => {
event.stopPropagation();
onDelete(conversation.id);
Expand Down Expand Up @@ -305,7 +306,7 @@ function SearchModal({
const q = query.trim().toLowerCase();
const results = q
? conversations.filter((c) => c.title.toLowerCase().includes(q))
: [...conversations].sort((a, b) => b.updatedAt - a.updatedAt);
: [...conversations].sort((a, b) => conversationActivityAt(b) - conversationActivityAt(a));

useEffect(() => {
const onKey = (e: KeyboardEvent) => {
Expand Down Expand Up @@ -339,7 +340,7 @@ function SearchModal({
results.map((c) => (
<button key={c.id} className="try-search-result" onClick={() => onPick(c.id)}>
<MessageSquare className="try-history-icon" />
<span className="try-history-title">{c.title || "New chat"}</span>
<span className="try-history-title">{c.title || t.newChat}</span>
</button>
))
)}
Expand Down
19 changes: 17 additions & 2 deletions apps/desktop/src/hooks/use-chat-history.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,18 @@ export interface Conversation {
space?: ChatSpace;
/** Unix timestamp used to order conversations pinned in the sidebar. */
pinnedAt?: number;
/**
* Last time the conversation itself changed (messages, title, media).
* Distinct from `updatedAt`, which doubles as the cloud-sync revision and so
* has to move for metadata-only edits like pinning. Sidebar ordering reads
* this; records written before it existed fall back to `updatedAt`.
*/
activityAt?: number;
}

/** Sidebar ordering key. Falls back for conversations saved before `activityAt`. */
export function conversationActivityAt(conversation: Conversation): number {
return conversation.activityAt ?? conversation.updatedAt;
}

type Setter = ChatMessage[] | ((prev: ChatMessage[]) => ChatMessage[]);
Expand Down Expand Up @@ -151,7 +163,8 @@ export function useChatHistory(address: string | null, space: ChatSpace = "perso

const renameChat = useCallback((id: string, title: string) => {
const clean = title.trim().slice(0, 80) || "New chat";
setConversations((prev) => prev.map((c) => (c.id === id ? { ...c, title: clean, updatedAt: Date.now() } : c)));
const now = Date.now();
setConversations((prev) => prev.map((c) => (c.id === id ? { ...c, title: clean, updatedAt: now, activityAt: now } : c)));
}, []);

const deleteChat = useCallback(
Expand All @@ -174,7 +187,7 @@ export function useChatHistory(address: string | null, space: ChatSpace = "perso
if (activeBySpaceRef.current[space] === convId) setActiveId(null);
return prev.filter((c) => c.id !== convId);
}
const updated = { ...cur, messages: msgs, updatedAt: Date.now() };
const updated = { ...cur, messages: msgs, updatedAt: Date.now(), activityAt: Date.now() };
return prev.map((c) => (c.id === convId ? updated : c));
});
},
Expand Down Expand Up @@ -218,6 +231,7 @@ export function useChatHistory(address: string | null, space: ChatSpace = "perso
title: titleFrom(msgs),
createdAt: now,
updatedAt: now,
activityAt: now,
messages: msgs,
space: pendingSpaceRef.current[id] ?? space,
};
Expand All @@ -229,6 +243,7 @@ export function useChatHistory(address: string | null, space: ChatSpace = "perso
messages: msgs,
title: cur.title && cur.title !== "New chat" ? cur.title : titleFrom(msgs),
updatedAt: now,
activityAt: now,
};
arr = prev.map((c) => (c.id === id ? updated : c));
}
Expand Down
9 changes: 9 additions & 0 deletions apps/desktop/src/hooks/use-cloud-workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ export interface CloudWorkspace {
id: string; name: string; createdAt: string; updatedAt?: string; version: number; runtime: string;
role: CloudMember["role"]; members: CloudMember[];
}
/** Flattened workspace shape the sidebar renders. */
export interface TeamWorkspaceNavItem {
id: string;
name: string;
role: CloudMember["role"];
memberCount: number;
version: number;
}

export interface CloudMessage {
id: string; role: "user" | "assistant"; authorId: string; authorName: string; content: string; createdAt: string;
}
Expand Down
56 changes: 52 additions & 4 deletions apps/desktop/src/lib/i18n.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,19 @@ export const TRY_LANGS: { id: TryLang; label: string }[] = [
export interface TryDict {
newChat: string;
noConversations: string;
history: string;
pinned: string;
recent: string;
projects: string;
agents: string;
newProject: string;
noProjects: string;
connectingProjects: string;
createOrJoinProject: string;
enableTeamMode: string;
teamModeOff: string;
pinConversation: string;
unpinConversation: string;
deleteConversation: string;
connectWallet: string;
connecting: string;
installWallet: string;
Expand Down Expand Up @@ -151,7 +163,19 @@ export interface TryDict {
const en: TryDict = {
newChat: "New chat",
noConversations: "No conversations yet.",
history: "History",
pinned: "Pinned",
recent: "Recent",
projects: "Projects",
agents: "Agents",
newProject: "New project",
noProjects: "No projects",
connectingProjects: "Connecting…",
createOrJoinProject: "Create or join a project",
enableTeamMode: "Enable Team Mode in Agents",
teamModeOff: "Team Mode is off · Manage modules",
pinConversation: "Pin conversation",
unpinConversation: "Unpin conversation",
deleteConversation: "Delete conversation",
connectWallet: "Connect wallet",
connecting: "Connecting…",
installWallet: "Install a wallet",
Expand Down Expand Up @@ -290,7 +314,19 @@ const en: TryDict = {
const zh: TryDict = {
newChat: "新对话",
noConversations: "还没有对话。",
history: "历史记录",
pinned: "已置顶",
recent: "最近",
projects: "项目",
agents: "智能体",
newProject: "新建项目",
noProjects: "暂无项目",
connectingProjects: "连接中…",
createOrJoinProject: "创建或加入项目",
enableTeamMode: "在「智能体」中开启团队模式",
teamModeOff: "团队模式已关闭 · 管理模块",
pinConversation: "置顶对话",
unpinConversation: "取消置顶",
deleteConversation: "删除对话",
connectWallet: "连接钱包",
connecting: "连接中…",
installWallet: "安装钱包",
Expand Down Expand Up @@ -429,7 +465,19 @@ const zh: TryDict = {
const es: TryDict = {
newChat: "Nuevo chat",
noConversations: "Aún no hay conversaciones.",
history: "Historial",
pinned: "Fijados",
recent: "Recientes",
projects: "Proyectos",
agents: "Agentes",
newProject: "Nuevo proyecto",
noProjects: "Sin proyectos",
connectingProjects: "Conectando…",
createOrJoinProject: "Crear o unirse a un proyecto",
enableTeamMode: "Activa el Modo Equipo en Agentes",
teamModeOff: "Modo Equipo desactivado · Gestionar módulos",
pinConversation: "Fijar conversación",
unpinConversation: "Dejar de fijar",
deleteConversation: "Eliminar conversación",
connectWallet: "Conectar billetera",
connecting: "Conectando…",
installWallet: "Instalar billetera",
Expand Down
36 changes: 0 additions & 36 deletions apps/desktop/src/lib/team-workspace-events.ts

This file was deleted.

Loading
Loading