You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

241 lines
5.0 KiB

<script setup lang="ts">
import type { AgentSessionItem } from "~/composables/useAgentSessions";
const props = defineProps<{
session: AgentSessionItem;
active: boolean;
}>();
const emit = defineEmits<{
select: [];
rename: [title: string];
delete: [];
}>();
const showMenu = ref(false);
const isEditing = ref(false);
const editTitle = ref("");
const menuRef = ref<HTMLElement | null>(null);
function startEdit() {
editTitle.value = props.session.title;
isEditing.value = true;
showMenu.value = false;
nextTick(() => {
const input = menuRef.value?.querySelector("input");
input?.focus();
input?.select();
});
}
function confirmEdit() {
const title = editTitle.value.trim();
if (title && title !== props.session.title) {
emit("rename", title);
}
isEditing.value = false;
}
function cancelEdit() {
isEditing.value = false;
}
function handleKeydown(e: KeyboardEvent) {
if (e.key === "Enter") {
e.preventDefault();
confirmEdit();
} else if (e.key === "Escape") {
cancelEdit();
}
}
function handleClickOutside(e: MouseEvent) {
if (showMenu.value && menuRef.value && !menuRef.value.contains(e.target as Node)) {
showMenu.value = false;
}
}
onMounted(() => {
document.addEventListener("click", handleClickOutside);
});
onUnmounted(() => {
document.removeEventListener("click", handleClickOutside);
});
function formatTime(dateStr: string): string {
const d = new Date(dateStr);
const now = new Date();
const diff = now.getTime() - d.getTime();
const day = 86400000;
if (diff < day && now.getDate() === d.getDate()) {
return d.toLocaleTimeString("zh-CN", { hour: "2-digit", minute: "2-digit" });
}
if (diff < 2 * day) return "昨天";
if (diff < 7 * day) return `${Math.floor(diff / day)}天前`;
return d.toLocaleDateString("zh-CN", { month: "short", day: "numeric" });
}
</script>
<template>
<div
ref="menuRef"
class="sidebar-item"
:class="{ active }"
@click="!isEditing && emit('select')"
>
<template v-if="isEditing">
<input
v-model="editTitle"
class="edit-input"
@keydown="handleKeydown"
@blur="confirmEdit"
/>
</template>
<template v-else>
<Icon name="lucide:message-square" class="item-icon" />
<div class="item-content">
<div class="item-title">{{ session.title || "新对话" }}</div>
<div class="item-time">{{ formatTime(session.lastActiveAt) }}</div>
</div>
<button class="item-menu-btn" @click.stop="showMenu = !showMenu">
<Icon name="lucide:more-horizontal" />
</button>
<div v-if="showMenu" class="item-dropdown">
<button class="dropdown-action" @click.stop="startEdit">
<Icon name="lucide:pencil" />
重命名
</button>
<button class="dropdown-action danger" @click.stop="emit('delete')">
<Icon name="lucide:trash-2" />
删除
</button>
</div>
</template>
</div>
</template>
<style scoped>
.sidebar-item {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 10px;
border-radius: 8px;
cursor: pointer;
transition: background 0.15s;
position: relative;
}
.sidebar-item:hover {
background: var(--color-surface-soft);
}
.sidebar-item.active {
background: var(--color-surface-soft);
}
.item-icon {
flex-shrink: 0;
font-size: 15px;
color: var(--color-muted);
}
.item-content {
flex: 1;
min-width: 0;
}
.item-title {
font-size: 14px;
color: var(--color-ink);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.item-time {
font-size: 11px;
color: var(--color-muted);
margin-top: 1px;
}
.item-menu-btn {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
border: none;
background: none;
border-radius: 6px;
cursor: pointer;
color: var(--color-muted);
font-size: 14px;
opacity: 0;
transition: opacity 0.15s, background 0.15s;
}
.sidebar-item:hover .item-menu-btn,
.sidebar-item.active .item-menu-btn {
opacity: 1;
}
.item-menu-btn:hover {
background: var(--color-surface-card);
color: var(--color-ink);
}
.item-dropdown {
position: absolute;
top: 100%;
right: 4px;
margin-top: 4px;
min-width: 140px;
background: var(--color-surface-card);
border: 1px solid var(--color-hairline);
border-radius: 10px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
z-index: 30;
padding: 4px;
}
.dropdown-action {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
padding: 8px 10px;
border: none;
background: none;
border-radius: 6px;
font-size: 13px;
color: var(--color-body);
cursor: pointer;
transition: background 0.15s;
}
.dropdown-action:hover {
background: var(--color-surface-soft);
}
.dropdown-action.danger {
color: #c0392b;
}
.dropdown-action.danger:hover {
background: rgba(192, 57, 43, 0.08);
}
.edit-input {
flex: 1;
padding: 4px 8px;
border: 1px solid var(--color-primary);
border-radius: 6px;
font-size: 14px;
color: var(--color-ink);
background: var(--color-surface-card);
outline: none;
}
</style>