mirror of
https://github.com/luckyyzh/pi-agent-integrated.git
synced 2026-10-03 11:09:34 +00:00
feat: integrate Pi backend and Pi Web
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { getFileIcon } from "./FileIcons";
|
||||
import { useI18n } from "@/hooks/useI18n";
|
||||
|
||||
export interface Tab {
|
||||
id: string;
|
||||
label: string;
|
||||
filePath: string;
|
||||
sourceSessionId?: string | null;
|
||||
initialDisplayMode?: "source" | "preview" | "diff";
|
||||
}
|
||||
|
||||
interface Props {
|
||||
tabs: Tab[];
|
||||
activeTabId: string;
|
||||
onSelectTab: (id: string) => void;
|
||||
onCloseTab: (id: string) => void;
|
||||
}
|
||||
|
||||
export function TabBar({ tabs, activeTabId, onSelectTab, onCloseTab }: Props) {
|
||||
const { t } = useI18n();
|
||||
const [hoveredClose, setHoveredClose] = useState<string | null>(null);
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "flex-end",
|
||||
background: "var(--bg-panel)",
|
||||
overflowX: "auto",
|
||||
flexShrink: 0,
|
||||
height: 36,
|
||||
}}
|
||||
>
|
||||
{tabs.map((tab) => {
|
||||
const isActive = tab.id === activeTabId;
|
||||
return (
|
||||
<div
|
||||
key={tab.id}
|
||||
onClick={() => onSelectTab(tab.id)}
|
||||
onMouseDown={(e) => {
|
||||
if (e.button === 1) e.preventDefault();
|
||||
}}
|
||||
onAuxClick={(e) => {
|
||||
if (e.button !== 1) return;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
onCloseTab(tab.id);
|
||||
}}
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: 6,
|
||||
height: 36,
|
||||
paddingLeft: 12,
|
||||
paddingRight: 6,
|
||||
borderRight: "1px solid var(--border)",
|
||||
background: isActive ? "var(--bg)" : "var(--bg-panel)",
|
||||
cursor: "pointer",
|
||||
fontSize: 12,
|
||||
color: isActive ? "var(--text)" : "var(--text-muted)",
|
||||
whiteSpace: "nowrap",
|
||||
maxWidth: 180,
|
||||
minWidth: 80,
|
||||
flexShrink: 0,
|
||||
userSelect: "none",
|
||||
transition: "background 0.1s, color 0.1s",
|
||||
}}
|
||||
>
|
||||
<span style={{ flexShrink: 0, opacity: isActive ? 1 : 0.7, display: "flex", alignItems: "center" }}>
|
||||
{getFileIcon(tab.label, 13)}
|
||||
</span>
|
||||
<span
|
||||
style={{
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
flex: 1,
|
||||
fontWeight: isActive ? 500 : 400,
|
||||
}}
|
||||
title={tab.filePath}
|
||||
>
|
||||
{tab.label}
|
||||
</span>
|
||||
<button
|
||||
onClick={(e) => { e.stopPropagation(); onCloseTab(tab.id); }}
|
||||
onMouseEnter={() => setHoveredClose(tab.id)}
|
||||
onMouseLeave={() => setHoveredClose(null)}
|
||||
style={{
|
||||
display: "flex", alignItems: "center", justifyContent: "center",
|
||||
width: 24, height: 24,
|
||||
background: hoveredClose === tab.id ? "var(--bg-hover)" : "transparent",
|
||||
border: "none",
|
||||
borderRadius: 4,
|
||||
color: hoveredClose === tab.id ? "var(--text)" : "var(--text-dim)",
|
||||
cursor: "pointer",
|
||||
padding: 0,
|
||||
flexShrink: 0,
|
||||
transition: "background 0.1s, color 0.1s",
|
||||
}}
|
||||
title={t("i18n.close")}
|
||||
aria-label={`${t("i18n.close")} ${tab.label}`}
|
||||
>
|
||||
<svg width="11" height="11" viewBox="0 0 10 10" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" aria-hidden="true">
|
||||
<line x1="2" y1="2" x2="8" y2="8" />
|
||||
<line x1="8" y1="2" x2="2" y2="8" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user