Merge pull request #6726 from github-roushan/dropdown-ui

UI enhancement for projects
This commit is contained in:
Faisal Amir 2025-10-06 10:54:57 +07:00 committed by GitHub
commit 80ee8fd2b2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 191 additions and 117 deletions

View File

@ -229,7 +229,7 @@ function DropdownMenuSubContent({
<DropdownMenuPrimitive.SubContent
data-slot="dropdown-menu-sub-content"
className={cn(
'bg-main-view text-main-view-fg border-main-view-fg/5 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-[51] min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg',
'bg-main-view text-main-view-fg border-main-view-fg/5 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-[51] min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-y-auto max-h-[var(--radix-dropdown-menu-content-available-height)] rounded-md border p-1 shadow-lg',
className
)}
{...props}

View File

@ -237,13 +237,13 @@ const SortableItem = memo(
<DropdownMenuSub>
<DropdownMenuSubTrigger className="gap-2">
<IconFolder size={16} />
<span>Add to project</span>
<span>{t('common:projects.addToProject')}</span>
</DropdownMenuSubTrigger>
<DropdownMenuSubContent>
{availableProjects.length === 0 ? (
<DropdownMenuItem disabled>
<span className="text-left-panel-fg/50">
No projects available
{t('common:projects.noProjectsAvailable')}
</span>
</DropdownMenuItem>
) : (
@ -262,32 +262,29 @@ const SortableItem = memo(
</DropdownMenuItem>
))
)}
{thread.metadata?.project && (
<>
<DropdownMenuSeparator />
<DropdownMenuItem
onClick={(e) => {
e.stopPropagation()
// Remove project from metadata
const projectName = thread.metadata?.project?.name
updateThread(thread.id, {
metadata: {
...thread.metadata,
project: undefined,
},
})
toast.success(
`Thread removed from "${projectName}" successfully`
)
}}
>
<IconX size={16} />
<span>Remove from project</span>
</DropdownMenuItem>
</>
)}
</DropdownMenuSubContent>
</DropdownMenuSub>
{thread.metadata?.project && (
<DropdownMenuItem
onClick={(e) => {
e.stopPropagation()
// Remove project from metadata
const projectName = thread.metadata?.project?.name
updateThread(thread.id, {
metadata: {
...thread.metadata,
project: undefined,
},
})
toast.success(
`Thread removed from "${projectName}" successfully`
)
}}
>
<IconX size={16} />
<span>{t('common:projects.removeFromProject')}</span>
</DropdownMenuItem>
)}
<DropdownMenuSeparator />
<DeleteThreadDialog
thread={thread}

View File

@ -272,9 +272,12 @@
"thread": "Thread",
"threads": "Threads",
"updated": "Aktualisiert:",
"collapseThreads": "Threads einklappen",
"expandThreads": "Threads ausklappen",
"update": "Aktualisieren"
"collapseProject": "Projekt einklappen",
"expandProject": "Projekt ausklappen",
"update": "Aktualisieren",
"searchProjects": "Projekte durchsuchen...",
"noProjectsFound": "Keine Projekte gefunden",
"tryDifferentSearch": "Versuchen Sie einen anderen Suchbegriff"
},
"toast": {
"allThreadsUnfavorited": {
@ -356,80 +359,6 @@
"downloadAndVerificationComplete": {
"title": "Download abgeschlossen",
"description": "Modell \"{{item}}\" erfolgreich heruntergeladen und verifiziert"
},
"projectCreated": {
"title": "Projekt erstellt",
"description": "Projekt \"{{projectName}}\" erfolgreich erstellt"
},
"projectRenamed": {
"title": "Projekt umbenannt",
"description": "Projekt von \"{{oldName}}\" zu \"{{newName}}\" umbenannt"
},
"projectDeleted": {
"title": "Projekt gelöscht",
"description": "Projekt \"{{projectName}}\" erfolgreich gelöscht"
},
"projectAlreadyExists": {
"title": "Projekt existiert bereits",
"description": "Projekt \"{{projectName}}\" existiert bereits"
},
"projectDeleteFailed": {
"title": "Löschen fehlgeschlagen",
"description": "Projekt konnte nicht gelöscht werden. Bitte versuchen Sie es erneut."
},
"threadAssignedToProject": {
"title": "Thread zugewiesen",
"description": "Thread erfolgreich zu \"{{projectName}}\" hinzugefügt"
},
"threadRemovedFromProject": {
"title": "Thread entfernt",
"description": "Thread erfolgreich von \"{{projectName}}\" entfernt"
}
},
"projects": {
"title": "Projekte",
"addProject": "Projekt hinzufügen",
"addToProject": "Zu Projekt hinzufügen",
"removeFromProject": "Von Projekt entfernen",
"createNewProject": "Neues Projekt erstellen",
"editProject": "Projekt bearbeiten",
"deleteProject": "Projekt löschen",
"projectName": "Projektname",
"enterProjectName": "Projektname eingeben...",
"noProjectsAvailable": "Keine Projekte verfügbar",
"noProjectsYet": "Noch keine Projekte",
"noProjectsYetDesc": "Starten Sie ein neues Projekt, indem Sie auf die Schaltfläche Projekt hinzufügen klicken.",
"projectNotFound": "Projekt nicht gefunden",
"projectNotFoundDesc": "Das gesuchte Projekt existiert nicht oder wurde gelöscht.",
"deleteProjectDialog": {
"title": "Projekt löschen",
"description": "Sind Sie sicher, dass Sie dieses Projekt löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.",
"deleteButton": "Löschen",
"successWithName": "Projekt \"{{projectName}}\" erfolgreich gelöscht",
"successWithoutName": "Projekt erfolgreich gelöscht",
"error": "Projekt konnte nicht gelöscht werden. Bitte versuchen Sie es erneut.",
"ariaLabel": "{{projectName}} löschen"
},
"addProjectDialog": {
"createTitle": "Neues Projekt erstellen",
"editTitle": "Projekt bearbeiten",
"nameLabel": "Projektname",
"namePlaceholder": "Projektname eingeben...",
"createButton": "Erstellen",
"updateButton": "Aktualisieren",
"alreadyExists": "Projekt \"{{projectName}}\" existiert bereits",
"createSuccess": "Projekt \"{{projectName}}\" erfolgreich erstellt",
"renameSuccess": "Projekt von \"{{oldName}}\" zu \"{{newName}}\" umbenannt"
},
"noConversationsIn": "Keine Gespräche in {{projectName}}",
"startNewConversation": "Starten Sie ein neues Gespräch mit {{projectName}} unten",
"conversationsIn": "Gespräche in {{projectName}}",
"conversationsDescription": "Klicken Sie auf ein Gespräch, um weiterzuchatten, oder starten Sie unten ein neues.",
"thread": "Thread",
"threads": "Threads",
"updated": "Aktualisiert:",
"collapseThreads": "Threads einklappen",
"expandThreads": "Threads ausklappen",
"update": "Aktualisieren"
}
}

View File

@ -282,9 +282,12 @@
"thread": "thread",
"threads": "threads",
"updated": "Updated:",
"collapseThreads": "Collapse threads",
"expandThreads": "Expand threads",
"update": "Update"
"collapseProject": "Collapse project",
"expandProject": "Expand project",
"update": "Update",
"searchProjects": "Search projects...",
"noProjectsFound": "No projects found",
"tryDifferentSearch": "Try a different search term"
},
"toast": {
"allThreadsUnfavorited": {

View File

@ -354,8 +354,11 @@
"thread": "utas",
"threads": "utas",
"updated": "Diperbarui:",
"collapseThreads": "Tutup utas",
"expandThreads": "Buka utas",
"update": "Perbarui"
"collapseProject": "Tutup proyek",
"expandProject": "Buka proyek",
"update": "Perbarui",
"searchProjects": "Cari proyek...",
"noProjectsFound": "Tidak ada proyek ditemukan",
"tryDifferentSearch": "Coba kata kunci pencarian lain"
}
}

View File

@ -272,9 +272,12 @@
"thread": "wątek",
"threads": "wątki",
"updated": "Zaktualizowano:",
"collapseThreads": "Zwiń wątki",
"expandThreads": "Rozwiń wątki",
"update": "Aktualizuj"
"collapseProject": "Zwiń projekt",
"expandProject": "Rozwiń projekt",
"update": "Aktualizuj",
"searchProjects": "Szukaj projektów...",
"noProjectsFound": "Nie znaleziono projektów",
"tryDifferentSearch": "Spróbuj innego wyszukiwania"
},
"toast": {
"allThreadsUnfavorited": {

View File

@ -199,6 +199,35 @@
"title": "Cài đặt mô hình - {{modelId}}",
"description": "Định cấu hình cài đặt mô hình để tối ưu hóa hiệu suất và hành vi."
},
"projects": {
"title": "Dự án",
"addProject": "Thêm dự án",
"editProject": "Chỉnh sửa dự án",
"deleteProject": "Xóa dự án",
"projectName": "Tên dự án",
"enterProjectName": "Nhập tên dự án",
"noProjectsYet": "Chưa có dự án nào",
"noProjectsYetDesc": "Tạo dự án đầu tiên của bạn để tổ chức các cuộc trò chuyện.",
"projectNotFound": "Không tìm thấy dự án",
"projectNotFoundDesc": "Dự án mà bạn đang tìm kiếm không tồn tại.",
"deleteProjectConfirm": "Bạn có chắc chắn muốn xóa dự án này không? Hành động này không thể hoàn tác.",
"addToProject": "Thêm vào dự án",
"removeFromProject": "Xóa khỏi dự án",
"noConversationsIn": "Chưa có cuộc trò chuyện nào trong {{projectName}}",
"startNewConversation": "Bắt đầu một cuộc trò chuyện mới với {{projectName}} bên dưới",
"conversationsIn": "Cuộc trò chuyện trong {{projectName}}",
"conversationsDescription": "Nhấp vào bất kỳ cuộc trò chuyện nào để tiếp tục trò chuyện hoặc bắt đầu một cuộc trò chuyện mới bên dưới.",
"thread": "chủ đề",
"threads": "chủ đề",
"updated": "Đã cập nhật:",
"collapseProject": "Thu gọn dự án",
"expandProject": "Mở rộng dự án",
"update": "Cập nhật",
"noProjectsAvailable": "Không có dự án nào",
"searchProjects": "Tìm kiếm dự án...",
"noProjectsFound": "Không tìm thấy dự án nào",
"tryDifferentSearch": "Thử từ khóa tìm kiếm khác"
},
"dialogs": {
"changeDataFolder": {
"title": "Thay đổi vị trí thư mục dữ liệu",

View File

@ -199,6 +199,35 @@
"title": "模型设置 - {{modelId}}",
"description": "配置模型设置以优化性能和行为。"
},
"projects": {
"title": "项目",
"addProject": "添加项目",
"editProject": "编辑项目",
"deleteProject": "删除项目",
"projectName": "项目名称",
"enterProjectName": "输入项目名称",
"noProjectsYet": "还没有项目",
"noProjectsYetDesc": "创建您的第一个项目来组织对话。",
"projectNotFound": "未找到项目",
"projectNotFoundDesc": "您正在查找的项目不存在。",
"deleteProjectConfirm": "您确定要删除此项目吗?此操作无法撤销。",
"addToProject": "添加到项目",
"removeFromProject": "从项目中删除",
"noConversationsIn": "{{projectName}} 中还没有对话",
"startNewConversation": "在下方开始与 {{projectName}} 的新对话",
"conversationsIn": "{{projectName}} 中的对话",
"conversationsDescription": "点击任何对话以继续聊天,或在下方开始新的对话。",
"thread": "线程",
"threads": "线程",
"updated": "已更新:",
"collapseProject": "收起项目",
"expandProject": "展开项目",
"update": "更新",
"noProjectsAvailable": "没有可用的项目",
"searchProjects": "搜索项目...",
"noProjectsFound": "未找到项目",
"tryDifferentSearch": "尝试不同的搜索词"
},
"dialogs": {
"changeDataFolder": {
"title": "更改数据文件夹位置",

View File

@ -199,6 +199,35 @@
"title": "模型設定 - {{modelId}}",
"description": "設定模型設定以最佳化效能和行為。"
},
"projects": {
"title": "專案",
"addProject": "新增專案",
"editProject": "編輯專案",
"deleteProject": "刪除專案",
"projectName": "專案名稱",
"enterProjectName": "輸入專案名稱",
"noProjectsYet": "尚無專案",
"noProjectsYetDesc": "建立您的第一個專案來組織對話。",
"projectNotFound": "找不到專案",
"projectNotFoundDesc": "您正在尋找的專案不存在。",
"deleteProjectConfirm": "您確定要刪除此專案嗎?此操作無法復原。",
"addToProject": "加入專案",
"removeFromProject": "從專案中移除",
"noConversationsIn": "{{projectName}} 中尚無對話",
"startNewConversation": "在下方開始與 {{projectName}} 的新對話",
"conversationsIn": "{{projectName}} 中的對話",
"conversationsDescription": "點擊任何對話以繼續聊天,或在下方開始新的對話。",
"thread": "執行緒",
"threads": "執行緒",
"updated": "已更新:",
"collapseProject": "收合專案",
"expandProject": "展開專案",
"update": "更新",
"noProjectsAvailable": "沒有可用的專案",
"searchProjects": "搜尋專案...",
"noProjectsFound": "找不到專案",
"tryDifferentSearch": "嘗試不同的搜尋詞"
},
"dialogs": {
"changeDataFolder": {
"title": "變更資料夾位置",

View File

@ -14,6 +14,8 @@ import {
IconFolder,
IconChevronDown,
IconChevronRight,
IconSearch,
IconX,
} from '@tabler/icons-react'
import AddProjectDialog from '@/containers/dialogs/AddProjectDialog'
import { DeleteProjectDialog } from '@/containers/dialogs/DeleteProjectDialog'
@ -42,6 +44,7 @@ function ProjectContent() {
const [expandedProjects, setExpandedProjects] = useState<Set<string>>(
new Set()
)
const [searchQuery, setSearchQuery] = useState('')
const handleDelete = (id: string) => {
setDeletingId(id)
@ -93,6 +96,16 @@ function ProjectContent() {
})
}
// Filter projects based on search query
const filteredProjects = useMemo(() => {
if (!searchQuery.trim()) {
return folders
}
return folders.filter((folder) =>
folder.name.toLowerCase().includes(searchQuery.toLowerCase())
)
}, [folders, searchQuery])
return (
<div className="flex h-full flex-col justify-center">
<HeaderPage>
@ -113,6 +126,33 @@ function ProjectContent() {
</HeaderPage>
<div className="h-full overflow-y-auto flex flex-col">
<div className="p-4 w-full md:w-3/4 mx-auto mt-2">
{/* Search Bar */}
{folders.length > 0 && (
<div className="mb-4">
<div className="relative">
<IconSearch
size={18}
className="absolute left-3 top-1/2 transform -translate-y-1/2 text-main-view-fg/50"
/>
<input
type="text"
placeholder={t('projects.searchProjects')}
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="w-full pl-10 pr-4 py-2.5 bg-main-view-fg/5 border border-main-view-fg/10 rounded-lg text-main-view-fg placeholder:text-main-view-fg/50 focus:outline-none focus:ring-2 focus:ring-main-view-fg/20 focus:border-main-view-fg/20 transition-all"
/>
{searchQuery && (
<button
onClick={() => setSearchQuery('')}
className="absolute right-3 top-1/2 transform -translate-y-1/2 text-main-view-fg/50 hover:text-main-view-fg transition-colors"
>
<IconX size={18} />
</button>
)}
</div>
</div>
)}
{folders.length === 0 ? (
<div className="flex flex-col items-center justify-center py-12 text-center">
<IconFolder size={48} className="text-main-view-fg/30 mb-4" />
@ -123,9 +163,19 @@ function ProjectContent() {
{t('projects.noProjectsYetDesc')}
</p>
</div>
) : filteredProjects.length === 0 ? (
<div className="flex flex-col items-center justify-center py-12 text-center">
<IconSearch size={48} className="text-main-view-fg/30 mb-4" />
<h3 className="text-lg font-medium text-main-view-fg/60 mb-2">
{t('projects.noProjectsFound')}
</h3>
<p className="text-main-view-fg/50 text-sm">
{t('projects.tryDifferentSearch')}
</p>
</div>
) : (
<div className="space-y-3">
{folders
{filteredProjects
.slice()
.sort((a, b) => b.updated_at - a.updated_at)
.map((folder) => {
@ -172,8 +222,8 @@ function ProjectContent() {
className="size-8 cursor-pointer flex items-center justify-center rounded-md hover:bg-main-view-fg/10 transition-all duration-200 ease-in-out mr-1"
title={
isExpanded
? t('projects.collapseThreads')
: t('projects.expandThreads')
? t('projects.collapseProject')
: t('projects.expandProject')
}
onClick={() => toggleProjectExpansion(folder.id)}
>
@ -218,7 +268,9 @@ function ProjectContent() {
{/* Thread List */}
{isExpanded && projectThreads.length > 0 && (
<div className="mt-3 pl-2">
<div
className="mt-3 pl-2 pr-2 max-h-[190px] overflow-y-auto overflow-x-hidden [&::-webkit-scrollbar]:w-1.5 [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:bg-main-view-fg/20 [&::-webkit-scrollbar-thumb]:rounded-full hover:[&::-webkit-scrollbar-thumb]:bg-main-view-fg/30"
>
<ThreadList
threads={projectThreads}
variant="project"