mirror of
https://github.com/memohai/Memoh.git
synced 2026-04-25 07:00:48 +09:00
feat(web): add sidebar collapse functionality (#314)
* feat: add Supermarket integration (MCP & Skill marketplace) (#309) * feat: add Supermarket integration (MCP & Skill marketplace) Backend: - Add [supermarket] config section with base_url (default: supermarket.memoh.ai) - Add SupermarketHandler with proxy endpoints for MCPs, Skills, and Tags - Add install endpoints: POST /bots/:id/supermarket/install-mcp (creates MCP connection with env vars) and install-skill (downloads tar.gz, extracts to container via gRPC) - Register handler in FX wiring, generate Swagger docs and TypeScript SDK Frontend: - Add /settings/supermarket route with Store icon in sidebar - Create supermarket page with search, tag filtering, MCP and Skill sections - Add MCP/Skill card components with tag badges and install buttons - Add install dialogs: MCP (bot selector + env var form), Skill (bot selector) - Add i18n entries for en.json and zh.json * fix: improve supermarket install UX - Create BotSelect component with avatar + name using UI Select - Replace NativeSelect in install dialogs and usage page with BotSelect - Change MCP install flow: navigate to bot detail MCP tab with pre-filled draft instead of direct install, letting users review before saving - Move Supermarket sidebar entry between Browser and Usage * web: remove supermarket page top tag selector bar Drop the horizontal tag chips and getSupermarketTags fetch; keep search and tag filter via card tag clicks with clearable badge. * web: add homepage link to supermarket MCP and Skill cards Show an external-link icon next to the card title when homepage is available, opening in a new tab on click. * refactor: move skills directory from .skills to skills and enrich prompt - Change skills storage path from `/data/.skills` to `/data/skills` - Add usage instructions and directory location to the Skills section in the system prompt * feat(web): add Activity Bar and right sidebar panel to chat page Replace the old file manager panel with a multi-tab right sidebar system: - Activity Bar with Terminal, Files, and Info tabs - Resizable right panel with tab switching - Extract shared Terminal component from bot-terminal.vue - Add bottom preview layout mode to FileManager - Delete session button with confirmation dialog - Fix FileManager scroll in flex column layout (min-h-0) * feat(web): add session-type-aware UI for chat interface - Make IM/heartbeat/schedule/subagent sessions read-only (hide input box) - Render heartbeat user messages as info blocks with trigger metadata and link to heartbeat logs - Render schedule user messages as info blocks with task metadata and link to schedule settings - Render subagent user messages as full-width markdown boxes - Add clickable spawn task results to navigate to subagent sessions * feat(web): add sidebar collapse functionality - Add SidebarRail for edge drag-to-collapse interaction in both sidebars - Center align icons when collapsed in bot list and settings sidebar - Hide text labels and dropdown menus in collapsed state - Keep create bot button in header * fix(web): ensure secondary sidebar remains visible after page refresh When the primary sidebar was collapsed, refreshing the page would cause the secondary sidebar (e.g., providers list) to disappear. The MasterDetailSidebarLayout had its own SidebarProvider but didn't set default-open, causing it to read the same sidebar_state cookie and follow the primary sidebar's collapsed state. Fix by explicitly setting :default-open="true" on the secondary SidebarProvider to ensure it always stays expanded. --------- Co-authored-by: Acbox Liu <acbox0328@gmail.com>
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
<template>
|
||||
<SidebarProvider class="min-h-[initial]! absolute inset-0 ">
|
||||
<SidebarProvider
|
||||
class="min-h-[initial]! absolute inset-0 "
|
||||
:default-open="true"
|
||||
>
|
||||
<Sidebar
|
||||
class="relative! **:[[role=navigation]]:relative! sidebar-container h-full!"
|
||||
>
|
||||
|
||||
@@ -27,20 +27,22 @@
|
||||
:tooltip="item.title"
|
||||
:is-active="isItemActive(item.name)"
|
||||
:aria-current="isItemActive(item.name) ? 'page' : undefined"
|
||||
class="h-9 gap-2 relative before:absolute before:w-0.5 before:top-1.5 before:bottom-1.5 before:left-0 before:rounded-full data-[active=true]:before:bg-[#8B56E3]"
|
||||
class="h-9 gap-2 relative before:absolute before:w-0.5 before:top-1.5 before:bottom-1.5 before:left-0 before:rounded-full data-[active=true]:before:bg-[#8B56E3] group-data-[collapsible=icon]:justify-center group-data-[collapsible=icon]:px-0"
|
||||
@click="router.push({ name: item.name })"
|
||||
>
|
||||
<component
|
||||
:is="item.icon"
|
||||
class="size-3.5 ml-1.5"
|
||||
class="size-3.5 ml-1.5 group-data-[collapsible=icon]:ml-0"
|
||||
/>
|
||||
<span class="text-xs font-medium">{{ item.title }}</span>
|
||||
<span class="text-xs font-medium group-data-[collapsible=icon]:hidden">{{ item.title }}</span>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
</SidebarGroupContent>
|
||||
</SidebarGroup>
|
||||
</SidebarContent>
|
||||
|
||||
<SidebarRail />
|
||||
</Sidebar>
|
||||
</aside>
|
||||
</template>
|
||||
@@ -61,6 +63,7 @@ import {
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
SidebarRail,
|
||||
} from '@memohai/ui'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
<button
|
||||
:class="[
|
||||
'group/bot flex items-center gap-2.5 w-full h-[38px] px-2.5 rounded-lg transition-colors',
|
||||
'group-data-[collapsible=icon]:justify-center group-data-[collapsible=icon]:px-0',
|
||||
isActive
|
||||
? 'bg-background'
|
||||
: bot.status === 'error'
|
||||
@@ -15,7 +16,7 @@
|
||||
:disabled="bot.status === 'error'"
|
||||
@click="handleSelect"
|
||||
>
|
||||
<div class="size-[26px] shrink-0 rounded-full border border-border bg-accent overflow-hidden p-px">
|
||||
<div class="size-[26px] shrink-0 rounded-full border border-border bg-accent overflow-hidden p-px group-data-[collapsible=icon]:mx-auto">
|
||||
<img
|
||||
v-if="bot.avatar_url"
|
||||
:src="bot.avatar_url"
|
||||
@@ -29,42 +30,44 @@
|
||||
{{ avatarFallback }}
|
||||
</span>
|
||||
</div>
|
||||
<span class="truncate text-xs font-medium text-foreground leading-[18px] flex-1 text-left">
|
||||
<span class="truncate text-xs font-medium text-foreground leading-[18px] flex-1 text-left group-data-[collapsible=icon]:hidden">
|
||||
{{ bot.display_name || bot.id }}
|
||||
</span>
|
||||
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger
|
||||
as-child
|
||||
@click.stop
|
||||
>
|
||||
<span
|
||||
class="shrink-0 size-6 flex items-center justify-center rounded text-muted-foreground opacity-0 group-hover/bot:opacity-100 hover:text-foreground hover:bg-accent transition-opacity"
|
||||
<div class="group-data-[collapsible=icon]:hidden">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger
|
||||
as-child
|
||||
@click.stop
|
||||
>
|
||||
<Ellipsis
|
||||
class="size-3"
|
||||
/>
|
||||
</span>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
align="start"
|
||||
side="bottom"
|
||||
@click.stop
|
||||
>
|
||||
<DropdownMenuItem @click.stop="handleTogglePin">
|
||||
<Pin
|
||||
class="size-3 mr-2"
|
||||
/>
|
||||
{{ pinned ? $t('common.unpin') : $t('common.pin') }}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem @click.stop="handleDetails">
|
||||
<Settings
|
||||
class="size-3 mr-2"
|
||||
/>
|
||||
{{ $t('common.details') }}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<span
|
||||
class="shrink-0 size-6 flex items-center justify-center rounded text-muted-foreground opacity-0 group-hover/bot:opacity-100 hover:text-foreground hover:bg-accent transition-opacity"
|
||||
>
|
||||
<Ellipsis
|
||||
class="size-3"
|
||||
/>
|
||||
</span>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
align="start"
|
||||
side="bottom"
|
||||
@click.stop
|
||||
>
|
||||
<DropdownMenuItem @click.stop="handleTogglePin">
|
||||
<Pin
|
||||
class="size-3 mr-2"
|
||||
/>
|
||||
{{ pinned ? $t('common.unpin') : $t('common.pin') }}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem @click.stop="handleDetails">
|
||||
<Settings
|
||||
class="size-3 mr-2"
|
||||
/>
|
||||
{{ $t('common.details') }}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</button>
|
||||
</SidebarMenuButton>
|
||||
</template>
|
||||
|
||||
@@ -9,17 +9,17 @@
|
||||
{{ t('sidebar.bots') }}
|
||||
</span>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="ml-auto mr-1.5 size-6 text-muted-foreground hover:text-foreground group-data-[collapsible=icon]:ml-0 group-data-[collapsible=icon]:mr-0"
|
||||
:aria-label="t('bots.createBot')"
|
||||
@click="router.push({ name: 'bots' })"
|
||||
>
|
||||
<Plus
|
||||
class="size-3.5"
|
||||
/>
|
||||
</Button>
|
||||
<div class="flex items-center gap-1 ml-auto mr-1.5 group-data-[collapsible=icon]:ml-0 group-data-[collapsible=icon]:mr-0">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="size-6 text-muted-foreground hover:text-foreground"
|
||||
:aria-label="t('bots.createBot')"
|
||||
@click="router.push({ name: 'bots' })"
|
||||
>
|
||||
<Plus class="size-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</SidebarHeader>
|
||||
|
||||
@@ -53,6 +53,8 @@
|
||||
</SidebarGroup>
|
||||
</SidebarContent>
|
||||
|
||||
<SidebarRail />
|
||||
|
||||
<SidebarFooter class="relative border-0 px-2 pb-3.5 pt-2.5">
|
||||
<div class="pointer-events-none absolute -top-[120px] left-0 h-[153px] w-full bg-linear-to-t from-(--sidebar-background) from-18% to-transparent z-10 group-data-[collapsible=icon]:hidden" />
|
||||
<SidebarMenu class="gap-2.5">
|
||||
@@ -81,14 +83,14 @@
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton
|
||||
:tooltip="t('sidebar.settings')"
|
||||
class="h-9 px-2.5"
|
||||
class="h-9 px-2.5 group-data-[collapsible=icon]:justify-center group-data-[collapsible=icon]:px-0"
|
||||
:is-active="isSettingsActive"
|
||||
@click="router.push('/settings')"
|
||||
>
|
||||
<Settings
|
||||
class="size-3.5"
|
||||
/>
|
||||
<span class="text-xs font-medium">{{ t('sidebar.settings') }}</span>
|
||||
<span class="text-xs font-medium group-data-[collapsible=icon]:hidden">{{ t('sidebar.settings') }}</span>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
@@ -117,6 +119,7 @@ import {
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
SidebarRail,
|
||||
} from '@memohai/ui'
|
||||
import { Plus, LoaderCircle, Settings } from 'lucide-vue-next'
|
||||
import BotItem from './bot-item.vue'
|
||||
|
||||
Reference in New Issue
Block a user