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
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>
|
|
|