mirror of
https://github.com/memohai/Memoh.git
synced 2026-04-25 07:00:48 +09:00
refactor(web): migrate all icons from FontAwesome to Lucide and remove dead code
Replace all FontAwesome icon usage across 80+ Vue files with lucide-vue-next components. Remove FontAwesome dependencies (@fortawesome/*) and global registration from main.ts. Delete unused components (data-table, warning-banner, session-metadata, bot-sidebar/bot-item in home, message-list, tts-provider-select), dead utilities (channel-icons.ts, custom-icons.ts), and stale assets (vue.svg). Update AGENTS.md to reflect the new icon strategy.
This commit is contained in:
+5
-3
@@ -16,7 +16,7 @@
|
||||
| Data Fetching | Pinia Colada (`@pinia/colada`) + `@memohai/sdk` |
|
||||
| Forms | vee-validate + `@vee-validate/zod` + Zod |
|
||||
| i18n | vue-i18n (en / zh) |
|
||||
| Icons | FontAwesome (primary: fas/far/fab) + lucide-vue-next (secondary) |
|
||||
| Icons | lucide-vue-next (primary) + `@memohai/icon` (brand/provider icons) |
|
||||
| Toast | vue-sonner |
|
||||
| Tables | @tanstack/vue-table |
|
||||
| Markdown | markstream-vue + Shiki + Mermaid + KaTeX |
|
||||
@@ -308,8 +308,9 @@ const form = useForm({
|
||||
|
||||
### Icon Usage
|
||||
|
||||
- **FontAwesome** (primary): Global `<FontAwesomeIcon :icon="['fas', 'robot']" />`, full `fas`/`far`/`fab` sets + custom search icons registered in `main.ts`
|
||||
- **Lucide** (secondary): Direct imports `<Sun />`, `<Moon />`, used for theme toggle
|
||||
- **Lucide** (primary): Direct component imports from `lucide-vue-next`. Example: `import { Plus, Search, Bot } from 'lucide-vue-next'` → `<Plus class="size-4" />`. Used for all UI icons (actions, navigation, status indicators, etc.).
|
||||
- **`@memohai/icon`** (brand icons): Workspace package (`packages/icons/`) providing AI provider, search engine, and channel platform SVG icons as Vue components. Example: `import { Openai, Claude } from '@memohai/icon'`.
|
||||
- **Do NOT use FontAwesome** for new code. Legacy FontAwesome usage remains only in commented-out code blocks. Always use Lucide for UI icons and `@memohai/icon` for brand logos.
|
||||
|
||||
### Notification Pattern
|
||||
|
||||
@@ -424,6 +425,7 @@ Chat supports two transport modes: **Server-Sent Events (SSE)** and **WebSocket*
|
||||
- Style with Tailwind utility classes; avoid `<style>` blocks. Follow the design system in `packages/ui/DESIGN.md`.
|
||||
- **Always use semantic color tokens** (`text-foreground`, `bg-card`, `border-border`, `text-muted-foreground`, `bg-accent`, etc.) instead of raw colors (`gray-*`, `bg-white`, `text-black`). Never introduce hardcoded Tailwind color classes for themed elements — this breaks dark mode consistency.
|
||||
- Use `@memohai/ui` components for all UI primitives; do not import Reka UI directly.
|
||||
- Use `lucide-vue-next` for all UI icons. Use `@memohai/icon` for brand/provider logos. **Never use FontAwesome** — do not add `<FontAwesomeIcon>`, do not import from `@fortawesome/*`, do not use inline SVG or base64-encoded SVG in templates.
|
||||
- Use Pinia Colada (`useQuery`/`useMutation`) for server state; use Pinia stores for client state only.
|
||||
- API calls must go through `@memohai/sdk`; never call `fetch()` directly.
|
||||
- All user-facing strings must use i18n keys (`t('key')`) — never hardcode text.
|
||||
|
||||
@@ -9,11 +9,6 @@
|
||||
"start": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^7.0.0",
|
||||
"@fortawesome/free-brands-svg-icons": "^7.0.0",
|
||||
"@fortawesome/free-regular-svg-icons": "^7.0.0",
|
||||
"@fortawesome/free-solid-svg-icons": "^7.0.0",
|
||||
"@fortawesome/vue-fontawesome": "^3.1.1",
|
||||
"@memohai/icon": "workspace:*",
|
||||
"@memohai/sdk": "workspace:*",
|
||||
"@memohai/ui": "workspace:*",
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
|
||||
|
Before Width: | Height: | Size: 496 B |
@@ -14,8 +14,7 @@
|
||||
class="w-full shadow-none! text-muted-foreground mb-4"
|
||||
variant="outline"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'plus']"
|
||||
<Plus
|
||||
class="mr-1"
|
||||
/> {{ $t('provider.addBtn') }}
|
||||
</Button>
|
||||
@@ -163,6 +162,7 @@ import { useMutation, useQueryCache } from '@pinia/colada'
|
||||
import { postProviders, postProvidersByIdImportModels } from '@memohai/sdk'
|
||||
import type { ProvidersCreateRequest } from '@memohai/sdk'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { Plus } from 'lucide-vue-next'
|
||||
import FormDialogShell from '@/components/form-dialog-shell/index.vue'
|
||||
import { useDialogMutation } from '@/composables/useDialogMutation'
|
||||
import SearchableSelectPopover from '@/components/searchable-select-popover/index.vue'
|
||||
|
||||
@@ -5,28 +5,22 @@
|
||||
alt="logo.png"
|
||||
class="w-6.5"
|
||||
>
|
||||
<img
|
||||
<LoaderCircle
|
||||
v-if="isLoading"
|
||||
src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBmaWxsPSIjOEI1Q0Y2IiBkPSJNMTIsMUExMSwxMSwwLDEsMCwyMywxMiwxMSwxMSwwLDAsMCwxMiwxWm0wLDE5YTgsOCwwLDEsMSw4LThBOCw4LDAsMCwxLDEyLDIwWiIgb3BhY2l0eT0iMC4yNSIvPjxwYXRoIGZpbGw9IiM4QjVDRjYiIGQ9Ik0xMC4xNCwxLjE2YTExLDExLDAsMCwwLTksOC45MkExLjU5LDEuNTksMCwwLDAsMi40NiwxMiwxLjUyLDEuNTIsMCwwLDAsNC4xMSwxMC43YTgsOCwwLDAsMSw2LjY2LTYuNjFBMS40MiwxLjQyLDAsMCwwLDEyLDIuNjloMEExLjU3LDEuNTcsMCwwLDAsMTAuMTQsMS4xNloiPjxhbmltYXRlVHJhbnNmb3JtIGF0dHJpYnV0ZU5hbWU9InRyYW5zZm9ybSIgZHVyPSIwLjc1cyIgcmVwZWF0Q291bnQ9ImluZGVmaW5pdGUiIHR5cGU9InJvdGF0ZSIgdmFsdWVzPSIwIDEyIDEyOzM2MCAxMiAxMiIvPjwvcGF0aD48L3N2Zz4="
|
||||
alt="loading"
|
||||
width="13"
|
||||
class="absolute bottom-0 right-0 bg-card p-0.5 rounded-full"
|
||||
>
|
||||
<img
|
||||
class="absolute bottom-0 right-0 bg-card p-0.5 rounded-full size-[13px] text-primary animate-spin"
|
||||
/>
|
||||
<CircleAlert
|
||||
v-if="error"
|
||||
src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iOSIgaGVpZ2h0PSI5IiB2aWV3Qm94PSIwIDAgOSA5IiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8bWFzayBpZD0icGF0aC0xLWluc2lkZS0xXzE0OF80NDgiIGZpbGw9IndoaXRlIj4KPHBhdGggZD0iTTQuNSA4LjI1QzIuNDI4OTMgOC4yNSAwLjc1IDYuNTcxMDUgMC43NSA0LjVDMC43NSAyLjQyODkzIDIuNDI4OTMgMC43NSA0LjUgMC43NUM2LjU3MTA1IDAuNzUgOC4yNSAyLjQyODkzIDguMjUgNC41QzguMjUgNi41NzEwNSA2LjU3MTA1IDguMjUgNC41IDguMjVaTTQuNSA3LjVDNi4xNTY4NiA3LjUgNy41IDYuMTU2ODYgNy41IDQuNUM3LjUgMi44NDMxNCA2LjE1Njg2IDEuNSA0LjUgMS41QzIuODQzMTQgMS41IDEuNSAyLjg0MzE0IDEuNSA0LjVDMS41IDYuMTU2ODYgMi44NDMxNCA3LjUgNC41IDcuNVpNNC4xMjUgNS42MjVINC44NzVWNi4zNzVINC4xMjVWNS42MjVaTTQuMTI1IDIuNjI1SDQuODc1VjQuODc1SDQuMTI1VjIuNjI1WiIvPgo8L21hc2s+CjxwYXRoIGQ9Ik00LjEyNSA1LjYyNVY0Ljg3NUgzLjM3NVY1LjYyNUg0LjEyNVpNNC44NzUgNS42MjVINS42MjVWNC44NzVINC44NzVWNS42MjVaTTQuODc1IDYuMzc1VjcuMTI1SDUuNjI1VjYuMzc1SDQuODc1Wk00LjEyNSA2LjM3NUgzLjM3NVY3LjEyNUg0LjEyNVY2LjM3NVpNNC4xMjUgMi42MjVWMS44NzVIMy4zNzVWMi42MjVINC4xMjVaTTQuODc1IDIuNjI1SDUuNjI1VjEuODc1SDQuODc1VjIuNjI1Wk00Ljg3NSA0Ljg3NVY1LjYyNUg1LjYyNVY0Ljg3NUg0Ljg3NVpNNC4xMjUgNC44NzVIMy4zNzVWNS42MjVINC4xMjVWNC44NzVaTTQuNSA4LjI1VjcuNUMyLjg0MzE1IDcuNSAxLjUgNi4xNTY4NCAxLjUgNC41SDAuNzVIMEMwIDYuOTg1MjYgMi4wMTQ3MiA5IDQuNSA5VjguMjVaTTAuNzUgNC41SDEuNUMxLjUgMi44NDMxNCAyLjg0MzE0IDEuNSA0LjUgMS41VjAuNzVWMEMyLjAxNDcyIDAgMCAyLjAxNDcyIDAgNC41SDAuNzVaTTQuNSAwLjc1VjEuNUM2LjE1Njg0IDEuNSA3LjUgMi44NDMxNSA3LjUgNC41SDguMjVIOUM5IDIuMDE0NzIgNi45ODUyNiAwIDQuNSAwVjAuNzVaTTguMjUgNC41SDcuNUM3LjUgNi4xNTY4NCA2LjE1Njg0IDcuNSA0LjUgNy41VjguMjVWOUM2Ljk4NTI2IDkgOSA2Ljk4NTI2IDkgNC41SDguMjVaTTQuNSA3LjVWOC4yNUM2LjU3MTA4IDguMjUgOC4yNSA2LjU3MTA4IDguMjUgNC41SDcuNUg2Ljc1QzYuNzUgNS43NDI2NSA1Ljc0MjY1IDYuNzUgNC41IDYuNzVWNy41Wk03LjUgNC41SDguMjVDOC4yNSAyLjQyODkzIDYuNTcxMDggMC43NSA0LjUgMC43NVYxLjVWMi4yNUM1Ljc0MjY1IDIuMjUgNi43NSAzLjI1NzM2IDYuNzUgNC41SDcuNVpNNC41IDEuNVYwLjc1QzIuNDI4OTMgMC43NSAwLjc1IDIuNDI4OTMgMC43NSA0LjVIMS41SDIuMjVDMi4yNSAzLjI1NzM2IDMuMjU3MzYgMi4yNSA0LjUgMi4yNVYxLjVaTTEuNSA0LjVIMC43NUMwLjc1IDYuNTcxMDggMi40Mjg5MyA4LjI1IDQuNSA4LjI1VjcuNVY2Ljc1QzMuMjU3MzYgNi43NSAyLjI1IDUuNzQyNjUgMi4yNSA0LjVIMS41Wk00LjEyNSA1LjYyNVY2LjM3NUg0Ljg3NVY1LjYyNVY0Ljg3NUg0LjEyNVY1LjYyNVpNNC44NzUgNS42MjVINC4xMjVWNi4zNzVINC44NzVINS42MjVWNS42MjVINC44NzVaTTQuODc1IDYuMzc1VjUuNjI1SDQuMTI1VjYuMzc1VjcuMTI1SDQuODc1VjYuMzc1Wk00LjEyNSA2LjM3NUg0Ljg3NVY1LjYyNUg0LjEyNUgzLjM3NVY2LjM3NUg0LjEyNVpNNC4xMjUgMi42MjVWMy4zNzVINC44NzVWMi42MjVWMS44NzVINC4xMjVWMi42MjVaTTQuODc1IDIuNjI1SDQuMTI1VjQuODc1SDQuODc1SDUuNjI1VjIuNjI1SDQuODc1Wk00Ljg3NSA0Ljg3NVY0LjEyNUg0LjEyNVY0Ljg3NVY1LjYyNUg0Ljg3NVY0Ljg3NVpNNC4xMjUgNC44NzVINC44NzVWMi42MjVINC4xMjVIMy4zNzVWNC44NzVINC4xMjVaIiBmaWxsPSIjNzM3MzczIiBtYXNrPSJ1cmwoI3BhdGgtMS1pbnNpZGUtMV8xNDhfNDQ4KSIvPgo8L3N2Zz4K"
|
||||
alt="error"
|
||||
width="13"
|
||||
class="absolute bottom-0 right-0 bg-card p-0.5 rounded-full"
|
||||
>
|
||||
class="absolute bottom-0 right-0 bg-card p-0.5 rounded-full size-[13px] text-muted-foreground"
|
||||
/>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { CircleAlert, LoaderCircle } from 'lucide-vue-next'
|
||||
|
||||
withDefaults(defineProps < { isLoading?: boolean ,error?:boolean} > (), {
|
||||
withDefaults(defineProps<{ isLoading?: boolean; error?: boolean }>(), {
|
||||
isLoading: true,
|
||||
error:false
|
||||
error: false,
|
||||
})
|
||||
|
||||
</script>
|
||||
@@ -1,77 +0,0 @@
|
||||
<script setup lang="ts" generic="TData, TValue">
|
||||
import type { ColumnDef } from '@tanstack/vue-table'
|
||||
import {
|
||||
FlexRender,
|
||||
getCoreRowModel,
|
||||
useVueTable,
|
||||
} from '@tanstack/vue-table'
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableEmpty,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from '@memohai/ui'
|
||||
|
||||
const props = defineProps<{
|
||||
columns: ColumnDef<TData, TValue>[]
|
||||
data: TData[]
|
||||
}>()
|
||||
|
||||
const table = useVueTable({
|
||||
get data() { return props.data },
|
||||
get columns() { return props.columns },
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="border rounded-md">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow
|
||||
v-for="headerGroup in table.getHeaderGroups()"
|
||||
:key="headerGroup.id"
|
||||
>
|
||||
<TableHead
|
||||
v-for="header in headerGroup.headers"
|
||||
:key="header.id"
|
||||
>
|
||||
<FlexRender
|
||||
v-if="!header.isPlaceholder"
|
||||
:render="header.column.columnDef.header"
|
||||
:props="header.getContext()"
|
||||
/>
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody class="[&_td]:py-4!">
|
||||
<template v-if="table.getRowModel().rows?.length">
|
||||
<TableRow
|
||||
v-for="row in table.getRowModel().rows"
|
||||
:key="row.id"
|
||||
:data-state="row.getIsSelected() ? 'selected' : undefined"
|
||||
>
|
||||
<TableCell
|
||||
v-for="cell in row.getVisibleCells()"
|
||||
:key="cell.id"
|
||||
>
|
||||
<FlexRender
|
||||
:render="cell.column.columnDef.cell"
|
||||
:props="cell.getContext()"
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</template>
|
||||
<template v-else>
|
||||
<TableEmpty :colspan="columns.length">
|
||||
No results.
|
||||
</TableEmpty>
|
||||
</template>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
|
||||
import { LoaderCircle, FolderOpen, Folder, File, Download, SquarePen, Trash2 } from 'lucide-vue-next'
|
||||
import {
|
||||
ContextMenu,
|
||||
ContextMenuContent,
|
||||
@@ -52,8 +52,7 @@ function handleClick(entry: HandlersFsFileInfo) {
|
||||
v-if="loading"
|
||||
class="flex items-center justify-center py-16 text-muted-foreground"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'spinner']"
|
||||
<LoaderCircle
|
||||
class="mr-2 size-4 animate-spin"
|
||||
/>
|
||||
{{ t('common.loading') }}
|
||||
@@ -63,8 +62,7 @@ function handleClick(entry: HandlersFsFileInfo) {
|
||||
v-else-if="sortedEntries.length === 0"
|
||||
class="flex flex-col items-center justify-center py-16 text-muted-foreground"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'folder-open']"
|
||||
<FolderOpen
|
||||
class="mb-2 size-8 opacity-40"
|
||||
/>
|
||||
<span>{{ t('bots.files.empty') }}</span>
|
||||
@@ -95,8 +93,8 @@ function handleClick(entry: HandlersFsFileInfo) {
|
||||
@click="handleClick(entry)"
|
||||
>
|
||||
<div class="flex flex-1 items-center gap-2 min-w-0">
|
||||
<FontAwesomeIcon
|
||||
:icon="entry.isDir ? ['fas', 'folder'] : ['fas', 'file']"
|
||||
<component
|
||||
:is="entry.isDir ? Folder : File"
|
||||
:class="entry.isDir ? 'text-blue-500' : 'text-muted-foreground'"
|
||||
class="size-4 shrink-0"
|
||||
/>
|
||||
@@ -115,15 +113,13 @@ function handleClick(entry: HandlersFsFileInfo) {
|
||||
v-if="!entry.isDir"
|
||||
@select="emit('download', entry)"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'download']"
|
||||
<Download
|
||||
class="mr-2 size-3.5"
|
||||
/>
|
||||
{{ t('bots.files.download') }}
|
||||
</ContextMenuItem>
|
||||
<ContextMenuItem @select="emit('rename', entry)">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'pen']"
|
||||
<SquarePen
|
||||
class="mr-2 size-3.5"
|
||||
/>
|
||||
{{ t('bots.files.rename') }}
|
||||
@@ -133,8 +129,7 @@ function handleClick(entry: HandlersFsFileInfo) {
|
||||
class="text-destructive focus:text-destructive"
|
||||
@select="emit('delete', entry)"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'trash']"
|
||||
<Trash2
|
||||
class="mr-2 size-3.5"
|
||||
/>
|
||||
{{ t('bots.files.delete') }}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
variant="outline"
|
||||
class="flex items-center gap-2"
|
||||
>
|
||||
<FontAwesomeIcon :icon="['fas', 'file-import']" />
|
||||
<FileInput />
|
||||
{{ $t('models.importModels') }}
|
||||
</Button>
|
||||
</template>
|
||||
@@ -33,6 +33,7 @@ import { useI18n } from 'vue-i18n'
|
||||
import { useMutation, useQueryCache } from '@pinia/colada'
|
||||
import { postProvidersByIdImportModels } from '@memohai/sdk'
|
||||
import { toast } from 'vue-sonner'
|
||||
import { FileInput } from 'lucide-vue-next'
|
||||
import { Button } from '@memohai/ui'
|
||||
import FormDialogShell from '@/components/form-dialog-shell/index.vue'
|
||||
import { useDialogMutation } from '@/composables/useDialogMutation'
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { X, Plus } from 'lucide-vue-next'
|
||||
import { Button, Input } from '@memohai/ui'
|
||||
|
||||
export interface KeyValuePair {
|
||||
@@ -67,7 +68,7 @@ function removeRow(index: number) {
|
||||
class="shrink-0 size-8 text-muted-foreground hover:text-destructive"
|
||||
@click="removeRow(index)"
|
||||
>
|
||||
<FontAwesomeIcon :icon="['fas', 'xmark']" />
|
||||
<X />
|
||||
</Button>
|
||||
</div>
|
||||
<Button
|
||||
@@ -78,8 +79,7 @@ function removeRow(index: number) {
|
||||
class="w-fit"
|
||||
@click="addRow"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'plus']"
|
||||
<Plus
|
||||
class="mr-1.5"
|
||||
/>
|
||||
{{ $t('common.add') }}
|
||||
|
||||
@@ -24,8 +24,7 @@
|
||||
</section>
|
||||
|
||||
<div class="fixed right-4 top-0 h-12 z-1000 md:hidden flex items-center">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'bars']"
|
||||
<Menu
|
||||
class="cursor-pointer p-2"
|
||||
@click="mobileOpen = !mobileOpen"
|
||||
/>
|
||||
@@ -62,6 +61,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Menu } from 'lucide-vue-next'
|
||||
import { ref } from 'vue'
|
||||
import {
|
||||
Sheet,
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
/**
|
||||
* Custom FontAwesome icon definitions for search providers
|
||||
* that don't have icons in the FA free icon packs.
|
||||
*
|
||||
* Sources:
|
||||
* - tavily, jina, exa, bocha, serper: brand SVGs provided externally
|
||||
* - duckduckgo, searxng, sogou: Simple Icons (https://simpleicons.org)
|
||||
*
|
||||
* Each icon is registered with the 'fac' (custom) prefix and can be used
|
||||
* as ['fac', 'icon-name'] in FontAwesomeIcon components.
|
||||
*/
|
||||
|
||||
import type { IconDefinition } from '@fortawesome/fontawesome-svg-core'
|
||||
|
||||
function defineCustomIcon(
|
||||
name: string,
|
||||
width: number,
|
||||
height: number,
|
||||
pathData: string,
|
||||
): IconDefinition {
|
||||
return {
|
||||
prefix: 'fac' as IconDefinition['prefix'],
|
||||
iconName: name as IconDefinition['iconName'],
|
||||
icon: [width, height, [], '', pathData],
|
||||
}
|
||||
}
|
||||
|
||||
export const facTavily = defineCustomIcon(
|
||||
'tavily',
|
||||
24,
|
||||
24,
|
||||
'M8.033 14.273a1.612 1.612 0 011.139.47l.04.042.044.043a1.61 1.61 0 010 2.277l-3.073 3.073.816.816c.6.6.303 1.627-.525 1.814l-5.159 1.165a1.07 1.07 0 01-.897-.2l-.102-.09a1.07 1.07 0 01-.289-1l1.164-5.158A1.079 1.079 0 013.006 17l.816.817 3.074-3.074a1.612 1.612 0 011.137-.47zM17.042 13.246c0-.85.935-1.366 1.653-.912l4.47 2.824c.336.212.503.562.503.911 0 .35-.167.7-.501.913l-4.472 2.824a1.079 1.079 0 01-1.654-.912v-1.155h-7.027c.37-.4.605-.902.677-1.438l.022-.232a2.65 2.65 0 00-.492-1.669h6.821v-1.154zM8.188 0c.35 0 .7.168.913.503l2.823 4.47a1.079 1.079 0 01-.911 1.655H9.857v6.692h-1.67a2.633 2.633 0 00-1.668.48V6.629H5.365c-.849 0-1.366-.936-.912-1.654L7.276.503A1.072 1.072 0 018.188 0z',
|
||||
)
|
||||
|
||||
export const facJina = defineCustomIcon(
|
||||
'jina',
|
||||
24,
|
||||
24,
|
||||
'M6.608 21.416a4.608 4.608 0 100-9.217 4.608 4.608 0 000 9.217zM20.894 2.015c.614 0 1.106.492 1.106 1.106v9.002c0 5.13-4.148 9.309-9.217 9.37v-9.355l-.03-9.032c0-.614.491-1.106 1.106-1.106h7.158l-.123.015z',
|
||||
)
|
||||
|
||||
export const facExa = defineCustomIcon(
|
||||
'exa',
|
||||
24,
|
||||
24,
|
||||
'M3 0h19v1.791L13.892 12 22 22.209V24H3V0zm9.62 10.348l6.589-8.557H6.03l6.59 8.557zM5.138 3.935v7.17h5.52l-5.52-7.17zm5.52 8.96h-5.52v7.17l5.52-7.17zM6.03 22.21l6.59-8.557 6.589 8.557H6.03z',
|
||||
)
|
||||
|
||||
export const facBocha = defineCustomIcon(
|
||||
'bocha',
|
||||
463,
|
||||
395,
|
||||
'M52.6531 54.652C91.561 35.683 138.473 51.843 157.46 90.756L197.998 173.838C207.483 193.277 199.421 216.728 179.991 226.217C179.983 226.221 179.975 226.225 179.967 226.229C160.513 235.713 137.057 227.633 127.563 208.177L52.6531 54.652Z M12 133.819C42.6115 103.22 92.214 103.22 122.826 133.819L190.618 201.583C205.907 216.865 205.918 241.653 190.642 256.949C190.634 256.957 190.626 256.965 190.618 256.973C175.313 272.272 150.511 272.272 135.205 256.973L12 133.819Z M292.854 68.333C380.336 68.333 451.253 139.222 451.253 226.667C451.253 269.36 429.304 312.74 401.821 341.217L390.442 327.576C370.921 304.18 363.989 272.751 371.853 243.309C373.277 237.973 373.99 233.071 373.99 228.602C373.99 183.499 337.411 146.935 292.289 146.935C247.168 146.935 210.589 183.499 210.589 228.602C210.589 273.705 247.168 310.269 292.289 310.269C300.737 310.269 308.884 308.987 316.549 306.608C339.517 299.479 364.544 305.399 381.889 322.065L401.821 341.217C373.52 367.628 334.626 385 292.854 385C205.373 385 134.456 314.112 134.456 226.667C134.456 139.222 205.373 68.333 292.854 68.333Z M134.456 10C177.736 10 212.821 45.102 212.821 88.401V144.932C212.821 188.232 177.736 223.333 134.456 223.333V10Z',
|
||||
)
|
||||
|
||||
export const facDuckduckgo = defineCustomIcon(
|
||||
'duckduckgo',
|
||||
24,
|
||||
24,
|
||||
'M12 0C5.37 0 0 5.37 0 12s5.37 12 12 12s12-5.37 12-12S18.63 0 12 0m0 .984C18.083.984 23.016 5.916 23.016 12S18.084 23.016 12 23.016S.984 18.084.984 12S5.916.984 12 .984m0 .938C6.434 1.922 1.922 6.434 1.922 12c0 4.437 2.867 8.205 6.85 9.55c-.237-.82-.776-2.753-1.6-6.052c-1.184-4.741-2.064-8.606 2.379-9.813c.047-.011.064-.064.03-.093c-.514-.467-1.382-.548-2.233-.38a.06.06 0 0 1-.07-.058c0-.011 0-.023.011-.035c.205-.286.572-.507.822-.64a1.8 1.8 0 0 0-.607-.335c-.059-.022-.059-.12-.006-.144q.008-.01.024-.012c1.749-.233 3.586.292 4.49 1.448a.1.1 0 0 0 .035.023c2.968.635 3.509 4.837 3.328 5.998a9.6 9.6 0 0 0 2.346-.576c.746-.286 1.008-.222 1.101-.053c.1.193-.018.513-.28.81c-.496.567-1.393 1.01-2.974 1.137c-.546.044-1.029.024-1.445.006c-.789-.035-1.339-.059-1.633.39c-.192.298-.041.998 1.487 1.22c1.09.157 2.078.047 2.798-.034c.643-.07 1.073-.118 1.172.069c.21.402-.996 1.207-3.066 1.224q-.238-.002-.467-.011c-1.283-.065-2.227-.414-2.816-.735a.1.1 0 0 1-.035-.017c-.105-.059-.31.045-.188.267c.07.134.444.478 1.004.776c-.058.466.087 1.184.338 2l.088-.016q.063-.015.134-.025c.507-.082.775.012.926.175c.717-.536 1.913-1.294 2.03-1.154c.583.694.66 2.332.53 2.99q-.006.018-.04.035c-.274.117-1.783-.296-1.783-.511c-.059-1.075-.26-1.173-.493-1.225h-.156a.1.1 0 0 1 .018.03l.052.12c.093.257.24 1.063.13 1.26c-.112.199-.835.297-1.284.303c-.443.006-.543-.158-.637-.408c-.07-.204-.103-.675-.103-.95a1 1 0 0 1 .012-.216c-.134.058-.333.193-.397.281c-.017.262-.017.682.123 1.149c.07.221-1.518 1.164-1.74.99c-.227-.181-.634-1.952-.459-2.67c-.187.017-.338.075-.42.191c-.367.508.093 2.933.582 3.248c.257.169 1.54-.553 2.176-1.095c.105.145.305.158.553.158c.326-.012.782-.06 1.103-.158c.192.45.423.972.613 1.388c4.47-1.032 7.803-5.037 7.803-9.82c0-5.566-4.512-10.078-10.078-10.078m1.791 5.646c-.42 0-.678.146-.795.332c-.023.047.047.094.094.07c.14-.075.357-.161.701-.156c.328.006.516.09.67.159l.023.01c.041.017.088-.03.059-.065c-.134-.18-.332-.35-.752-.35m-5.078.198a1.2 1.2 0 0 0-.522.082c-.454.169-.67.526-.67.76c0 .051.112.057.141.011c.081-.123.21-.31.617-.478c.408-.17.73-.146.951-.094c.047.012.083-.041.041-.07a1 1 0 0 0-.558-.211m5.434 1.423a.65.65 0 0 0-.655.647a.652.652 0 0 0 1.307 0a.646.646 0 0 0-.652-.647m.283.262h.008a.17.17 0 0 1 .17.17c0 .093-.077.17-.17.17a.17.17 0 0 1-.17-.17c0-.09.072-.165.162-.17m-5.358.076a.75.75 0 0 0-.758.758c0 .42.338.758.758.758s.758-.337.758-.758a.756.756 0 0 0-.758-.758m.328.303h.01a.199.199 0 1 1 0 .397a.195.195 0 0 1-.197-.198c0-.107.082-.194.187-.199',
|
||||
)
|
||||
|
||||
export const facSearxng = defineCustomIcon(
|
||||
'searxng',
|
||||
24,
|
||||
24,
|
||||
'm13.716 17.261l6.873 6.582L24 20.282l-6.824-6.536a9.1 9.1 0 0 0 1.143-4.43c0-5.055-4.105-9.159-9.16-9.159S0 4.261 0 9.316s4.104 9.159 9.159 9.159a9.1 9.1 0 0 0 4.557-1.214M9.159 2.773a6.546 6.546 0 0 1 6.543 6.543a6.545 6.545 0 0 1-6.543 6.542a6.545 6.545 0 0 1-6.542-6.542a6.545 6.545 0 0 1 6.542-6.543M7.26 5.713a4.065 4.065 0 0 1 4.744.747a4.06 4.06 0 0 1 .707 4.749l1.157.611a5.38 5.38 0 0 0-.935-6.282a5.38 5.38 0 0 0-6.274-.987z',
|
||||
)
|
||||
|
||||
export const facSogou = defineCustomIcon(
|
||||
'sogou',
|
||||
24,
|
||||
24,
|
||||
'M16.801 22.74L17.79 24c1.561-.676 2.926-1.62 4.051-2.851l-.946-1.318a10.3 10.3 0 0 1-4.08 2.909zM12 22.199c-5.775 0-10.455-4.619-10.455-10.35C1.545 6.15 6.225 1.53 12 1.53s10.456 4.65 10.456 10.35c0 2.55-.946 4.891-2.507 6.69l.945 1.261C22.801 17.729 24 14.939 24 11.88C24 5.295 18.63 0 12 0S0 5.311 0 11.85c0 6.57 5.37 11.88 12 11.88c1.71 0 3.33-.346 4.801-.99l-.961-1.26c-1.2.45-2.49.719-3.84.719m6-9.553c-2.25-1.86-5.34-2.101-7.801-3.556c-.75-.479-.148-1.395.602-1.425c2.699-.45 5.369.63 7.889 1.5l.151-2.655c-3.151-1.14-6.57-1.875-9.901-1.35c-1.2.3-2.4.675-3.254 1.56c-1.171 1.2-.961 3.36.389 4.32c2.236 1.755 5.176 2.011 7.621 3.36c.96.39.555 1.68-.391 1.77c-2.925.555-5.805-.721-8.325-2.1c-.03 1.02-.06 2.01-.06 3c3.195 1.409 6.75 2.069 10.2 1.529c1.17-.225 2.37-.6 3.225-1.454c1.229-1.2 1.111-3.511-.33-4.5H18z',
|
||||
)
|
||||
|
||||
export const facSerper = defineCustomIcon(
|
||||
'serper',
|
||||
70,
|
||||
70,
|
||||
'M35 0A35 35 0 1 1 35 70A35 35 0 1 1 35 0Z M35 6A29 29 0 1 0 35 64A29 29 0 1 0 35 6Z M35.699 17.526c1.8718 0 3.6377.3174 5.2979.9521 1.6764.6348 3.1412 1.5137 4.3945 2.6367 1.2533 1.1231 2.2054 2.4252 2.8564 3.9063.1465.3255.2198.6592.2198 1.001 0 .6673-.2442 1.2451-.7324 1.7334-.4721.472-1.0417.708-1.709.708-.4395 0-.8789-.1384-1.3184-.4151-.4394-.2929-.7487-.6429-.9277-1.0498-.6185-1.3997-1.652-2.5146-3.1006-3.3447-1.4323-.8301-3.0925-1.2451-4.9805-1.2451-.9277 0-1.9124.1139-2.9541.3418-1.0254.2278-1.9938.5778-2.9053 1.0498-.9114.4557-1.652 1.0335-2.2216 1.7334-.5697.6998-.8545 1.5137-.8545 2.4414 0 .7487.2441 1.3997.7324 1.9531.4883.5371 1.0335.9603 1.6357 1.2695 1.4161.6999 2.9786 1.1882 4.6875 1.4649 1.7253.2767 3.4668.5452 5.2246.8057 1.7579.2604 3.3936.7161 4.9073 1.3671.9603.4069 1.8799.9685 2.7588 1.6846.8789.7162 1.595 1.5869 2.1484 2.6123s.8301 2.2298.8301 3.6133c0 1.7904-.4313 3.361-1.294 4.7119-.8626 1.3509-1.9938 2.474-3.3935 3.3691-1.3998.8952-2.9216 1.5707-4.5655 2.0264-1.6276.4395-3.2226.6592-4.7851.6592-2.1159 0-4.1016-.3337-5.957-1.001-1.8555-.6836-3.475-1.6439-4.8584-2.8808-1.3835-1.2533-2.4414-2.7181-3.1739-4.3946-.1465-.3255-.2197-.651-.2197-.9765 0-.6674.236-1.237.708-1.709.4883-.4883 1.0661-.7324 1.7334-.7324.4557 0 .9033.1464 1.3428.4394.4394.2767.7405.6266.9033 1.0498.7162 1.6439 1.9287 2.946 3.6377 3.9063 1.709.944 3.6702 1.416 5.8838 1.416 1.3835 0 2.7751-.2035 4.1748-.6104 1.3997-.4231 2.5716-1.066 3.5156-1.9287.9603-.8789 1.4404-1.9938 1.4404-3.3447 0-.8626-.2929-1.5706-.8789-2.124-.5696-.5697-1.1962-1.001-1.8798-1.294-1.5463-.6673-3.1983-1.123-4.9561-1.3672-1.7578-.2604-3.5075-.5289-5.249-.8056-1.7416-.293-3.3692-.822-4.8828-1.5869-1.2858-.6511-2.4577-1.5951-3.5157-2.8321-1.0416-1.2532-1.5625-2.8401-1.5625-4.7607 0-1.6927.4069-3.182 1.2207-4.4678.8301-1.3021 1.9206-2.3926 3.2715-3.2715 1.3672-.8952 2.8646-1.5706 4.4922-2.0263 1.6276-.4558 3.2471-.6836 4.8584-.6836z',
|
||||
)
|
||||
|
||||
export const customSearchIcons = [
|
||||
facTavily,
|
||||
facJina,
|
||||
facExa,
|
||||
facBocha,
|
||||
facDuckduckgo,
|
||||
facSearxng,
|
||||
facSogou,
|
||||
facSerper,
|
||||
]
|
||||
@@ -8,15 +8,15 @@
|
||||
v-if="iconComponent"
|
||||
:size="iconSize"
|
||||
/>
|
||||
<FontAwesomeIcon
|
||||
<Globe
|
||||
v-else
|
||||
:icon="['fas', 'globe']"
|
||||
:class="iconSizeClass"
|
||||
/>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Globe } from 'lucide-vue-next'
|
||||
import { computed, type Component } from 'vue'
|
||||
import {
|
||||
Brave,
|
||||
|
||||
@@ -18,8 +18,7 @@
|
||||
<span class="truncate">
|
||||
{{ displayLabel || placeholder }}
|
||||
</span>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'magnifying-glass']"
|
||||
<Search
|
||||
class="ml-2 size-3.5 shrink-0 text-muted-foreground"
|
||||
/>
|
||||
</Button>
|
||||
@@ -30,8 +29,7 @@
|
||||
align="start"
|
||||
>
|
||||
<div class="flex items-center border-b px-3">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'magnifying-glass']"
|
||||
<Search
|
||||
class="mr-2 size-3.5 shrink-0 text-muted-foreground"
|
||||
/>
|
||||
<input
|
||||
@@ -80,9 +78,8 @@
|
||||
:class="{ 'bg-accent': selected === option.value }"
|
||||
@click="selectOption(option.value)"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
<Check
|
||||
v-if="selected === option.value"
|
||||
:icon="['fas', 'check']"
|
||||
class="size-3.5"
|
||||
/>
|
||||
<span
|
||||
@@ -118,6 +115,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Search, Check } from 'lucide-vue-next'
|
||||
import {
|
||||
Popover,
|
||||
PopoverTrigger,
|
||||
|
||||
@@ -6,8 +6,7 @@
|
||||
class="h-[53px] flex items-center gap-2.5 px-3.5 w-full border-b border-border text-foreground hover:bg-accent/50 transition-colors group-data-[collapsible=icon]:justify-center group-data-[collapsible=icon]:px-0"
|
||||
@click="router.push(backToChatRoute)"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'chevron-left']"
|
||||
<ChevronLeft
|
||||
class="size-3 shrink-0"
|
||||
/>
|
||||
<span class="text-xs font-semibold group-data-[collapsible=icon]:hidden">
|
||||
@@ -31,8 +30,8 @@
|
||||
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]"
|
||||
@click="router.push({ name: item.name })"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="item.icon"
|
||||
<component
|
||||
:is="item.icon"
|
||||
class="size-3.5 ml-1.5"
|
||||
/>
|
||||
<span class="text-xs font-medium">{{ item.title }}</span>
|
||||
@@ -47,10 +46,11 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { computed, type Component } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { ChevronLeft, Bot, Boxes, Globe, Brain, Volume2, Mail, AppWindow, ChartLine, Settings } from 'lucide-vue-next'
|
||||
import { useChatSelectionStore } from '@/store/chat-selection'
|
||||
import {
|
||||
Sidebar,
|
||||
@@ -89,51 +89,51 @@ function isItemActive(name: string): boolean {
|
||||
return route.name === name
|
||||
}
|
||||
|
||||
const navItems = computed(() => [
|
||||
const navItems = computed<{ title: string; name: string; icon: Component }[]>(() => [
|
||||
{
|
||||
title: t('sidebar.bots'),
|
||||
name: 'bots',
|
||||
icon: ['fas', 'robot'],
|
||||
icon: Bot,
|
||||
},
|
||||
{
|
||||
title: t('sidebar.providers'),
|
||||
name: 'providers',
|
||||
icon: ['fas', 'cubes'],
|
||||
icon: Boxes,
|
||||
},
|
||||
{
|
||||
title: t('sidebar.webSearch'),
|
||||
name: 'web-search',
|
||||
icon: ['fas', 'globe'],
|
||||
icon: Globe,
|
||||
},
|
||||
{
|
||||
title: t('sidebar.memory'),
|
||||
name: 'memory',
|
||||
icon: ['fas', 'brain'],
|
||||
icon: Brain,
|
||||
},
|
||||
{
|
||||
title: t('sidebar.speech'),
|
||||
name: 'speech',
|
||||
icon: ['fas', 'volume-high'],
|
||||
icon: Volume2,
|
||||
},
|
||||
{
|
||||
title: t('sidebar.email'),
|
||||
name: 'email',
|
||||
icon: ['fas', 'envelope'],
|
||||
icon: Mail,
|
||||
},
|
||||
{
|
||||
title: t('sidebar.browser'),
|
||||
name: 'browser',
|
||||
icon: ['fas', 'window-maximize'],
|
||||
icon: AppWindow,
|
||||
},
|
||||
{
|
||||
title: t('sidebar.usage'),
|
||||
name: 'usage',
|
||||
icon: ['fas', 'chart-line'],
|
||||
icon: ChartLine,
|
||||
},
|
||||
{
|
||||
title: t('sidebar.profile'),
|
||||
name: 'profile',
|
||||
icon: ['fas', 'gear'],
|
||||
icon: Settings,
|
||||
},
|
||||
])
|
||||
</script>
|
||||
|
||||
@@ -41,8 +41,7 @@
|
||||
<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"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'ellipsis']"
|
||||
<Ellipsis
|
||||
class="size-3"
|
||||
/>
|
||||
</span>
|
||||
@@ -53,15 +52,13 @@
|
||||
@click.stop
|
||||
>
|
||||
<DropdownMenuItem @click.stop="handleTogglePin">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'thumbtack']"
|
||||
<Pin
|
||||
class="size-3 mr-2"
|
||||
/>
|
||||
{{ pinned ? $t('common.unpin') : $t('common.pin') }}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem @click.stop="handleDetails">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'gear']"
|
||||
<Settings
|
||||
class="size-3 mr-2"
|
||||
/>
|
||||
{{ $t('common.details') }}
|
||||
@@ -80,6 +77,7 @@ import type { BotsBot } from '@memohai/sdk'
|
||||
import { useChatStore } from '@/store/chat-list'
|
||||
import { useAvatarInitials } from '@/composables/useAvatarInitials'
|
||||
import { usePinnedBots } from '@/composables/usePinnedBots'
|
||||
import { Ellipsis, Pin, Settings } from 'lucide-vue-next'
|
||||
import {
|
||||
SidebarMenuButton,
|
||||
DropdownMenu,
|
||||
|
||||
@@ -16,8 +16,7 @@
|
||||
:aria-label="t('bots.createBot')"
|
||||
@click="router.push({ name: 'bots' })"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'plus']"
|
||||
<Plus
|
||||
class="size-3.5"
|
||||
/>
|
||||
</Button>
|
||||
@@ -40,8 +39,7 @@
|
||||
v-if="isLoading"
|
||||
class="flex justify-center py-4"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'spinner']"
|
||||
<LoaderCircle
|
||||
class="size-4 animate-spin text-muted-foreground"
|
||||
/>
|
||||
</div>
|
||||
@@ -87,8 +85,7 @@
|
||||
:is-active="isSettingsActive"
|
||||
@click="router.push('/settings')"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'gear']"
|
||||
<Settings
|
||||
class="size-3.5"
|
||||
/>
|
||||
<span class="text-xs font-medium">{{ t('sidebar.settings') }}</span>
|
||||
@@ -121,6 +118,7 @@ import {
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
} from '@memohai/ui'
|
||||
import { Plus, LoaderCircle, Settings } from 'lucide-vue-next'
|
||||
import BotItem from './bot-item.vue'
|
||||
import { usePinnedBots } from '@/composables/usePinnedBots'
|
||||
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
<template>
|
||||
<div class="rounded-md border border-yellow-300/50 bg-yellow-50/70 p-3 text-xs text-yellow-800 dark:border-yellow-800/50 dark:bg-yellow-900/10 dark:text-yellow-200">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
@@ -13,28 +13,7 @@ import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
|
||||
import 'markstream-vue/index.css'
|
||||
import 'katex/dist/katex.min.css'
|
||||
|
||||
// Font Awesome
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
|
||||
import {
|
||||
fas
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
import {
|
||||
far
|
||||
} from '@fortawesome/free-regular-svg-icons'
|
||||
import { fab } from '@fortawesome/free-brands-svg-icons'
|
||||
import { customSearchIcons } from './components/search-provider-logo/custom-icons'
|
||||
|
||||
library.add(
|
||||
far,
|
||||
fab,
|
||||
fas,
|
||||
...customSearchIcons,
|
||||
)
|
||||
|
||||
createApp(App)
|
||||
.component('FontAwesomeIcon', FontAwesomeIcon)
|
||||
.use(createPinia().use(piniaPluginPersistedstate))
|
||||
.use(PiniaColada)
|
||||
.use(router)
|
||||
|
||||
@@ -77,8 +77,7 @@
|
||||
size="sm"
|
||||
@click="openAddDialog"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'plus']"
|
||||
<Plus
|
||||
class="mr-1.5 size-3.5"
|
||||
/>
|
||||
{{ $t('bots.access.addRule') }}
|
||||
@@ -113,8 +112,7 @@
|
||||
:aria-label="$t('bots.access.dragToReorder')"
|
||||
:disabled="isReordering"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'grip-vertical']"
|
||||
<GripVertical
|
||||
class="size-3.5"
|
||||
/>
|
||||
</button>
|
||||
@@ -167,8 +165,7 @@
|
||||
size="icon-sm"
|
||||
@click="openEditDialog(rule)"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'pen']"
|
||||
<SquarePen
|
||||
class="size-3.5"
|
||||
/>
|
||||
</Button>
|
||||
@@ -183,8 +180,7 @@
|
||||
size="icon-sm"
|
||||
class="text-destructive hover:text-destructive"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'trash']"
|
||||
<Trash2
|
||||
class="size-3.5"
|
||||
/>
|
||||
</Button>
|
||||
@@ -335,8 +331,7 @@
|
||||
@toggle="scopeOpen = ($event.target as HTMLDetailsElement).open"
|
||||
>
|
||||
<summary class="flex cursor-pointer items-center gap-1 text-xs font-medium text-foreground select-none list-none">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'chevron-right']"
|
||||
<ChevronRight
|
||||
class="size-3 transition-transform group-open:rotate-90"
|
||||
/>
|
||||
{{ $t('bots.access.sourceScopeTitle') }}
|
||||
@@ -401,8 +396,7 @@
|
||||
<summary
|
||||
class="cursor-pointer list-none text-xs font-medium text-muted-foreground hover:text-foreground select-none"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'chevron-right']"
|
||||
<ChevronRight
|
||||
class="mr-0.5 inline size-3 transition-transform group-open/conversation-manual:rotate-90"
|
||||
/>
|
||||
{{ $t('bots.access.manualConversationIds') }}
|
||||
@@ -520,7 +514,7 @@ import { useSortable } from '@vueuse/integrations/useSortable'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { toast } from 'vue-sonner'
|
||||
import { useQuery, useQueryCache } from '@pinia/colada'
|
||||
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
|
||||
import { Plus, GripVertical, SquarePen, Trash2, ChevronRight } from 'lucide-vue-next'
|
||||
import {
|
||||
Button,
|
||||
Input,
|
||||
|
||||
@@ -31,9 +31,8 @@
|
||||
class="shrink-0 text-xs"
|
||||
:title="hasIssue ? issueTitle : undefined"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
<LoaderCircle
|
||||
v-if="isPending"
|
||||
:icon="['fas', 'spinner']"
|
||||
class="mr-1 size-3 animate-spin"
|
||||
/>
|
||||
{{ statusLabel }}
|
||||
@@ -59,6 +58,7 @@ import {
|
||||
AvatarFallback,
|
||||
Badge,
|
||||
} from '@memohai/ui'
|
||||
import { LoaderCircle } from 'lucide-vue-next'
|
||||
import { computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
@@ -8,8 +8,7 @@
|
||||
v-if="isLoading && configuredChannels.length === 0"
|
||||
class="flex items-center justify-center h-full p-4"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'spinner']"
|
||||
<LoaderCircle
|
||||
class="size-4 animate-spin text-muted-foreground"
|
||||
/>
|
||||
</div>
|
||||
@@ -79,8 +78,7 @@
|
||||
size="sm"
|
||||
:disabled="unconfiguredChannels.length === 0 && !isLoading"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'plus']"
|
||||
<Plus
|
||||
class="mr-2 size-3"
|
||||
/>
|
||||
{{ $t('bots.channels.addChannel') }}
|
||||
@@ -136,6 +134,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { LoaderCircle, Plus } from 'lucide-vue-next'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import {
|
||||
Button,
|
||||
|
||||
@@ -129,8 +129,7 @@
|
||||
class="flex flex-col items-center justify-center py-12 text-center"
|
||||
>
|
||||
<div class="rounded-full bg-muted p-3 mb-4">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'compress']"
|
||||
<Minimize2
|
||||
class="size-6 text-muted-foreground"
|
||||
/>
|
||||
</div>
|
||||
@@ -231,6 +230,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Minimize2 } from 'lucide-vue-next'
|
||||
import { ref, reactive, computed, watch, onMounted } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { toast } from 'vue-sonner'
|
||||
|
||||
@@ -24,8 +24,7 @@
|
||||
size="sm"
|
||||
:disabled="!unboundProviders.length"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'plus']"
|
||||
<Plus
|
||||
class="mr-1.5"
|
||||
/>
|
||||
{{ $t('bots.email.addBinding') }}
|
||||
@@ -211,6 +210,7 @@ import {
|
||||
Switch,
|
||||
} from '@memohai/ui'
|
||||
import ConfirmPopover from '@/components/confirm-popover/index.vue'
|
||||
import { Plus } from 'lucide-vue-next'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { toast } from 'vue-sonner'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
@@ -129,8 +129,7 @@
|
||||
class="flex flex-col items-center justify-center py-12 text-center"
|
||||
>
|
||||
<div class="rounded-full bg-muted p-3 mb-4">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'heartbeat']"
|
||||
<HeartPulse
|
||||
class="size-6 text-muted-foreground"
|
||||
/>
|
||||
</div>
|
||||
@@ -235,6 +234,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { HeartPulse } from 'lucide-vue-next'
|
||||
import { ref, reactive, computed, watch, onMounted } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { toast } from 'vue-sonner'
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
size="icon-xs"
|
||||
aria-label="Search"
|
||||
>
|
||||
<FontAwesomeIcon :icon="['fas', 'magnifying-glass']" />
|
||||
<Search />
|
||||
</InputGroupButton>
|
||||
</InputGroupAddon>
|
||||
</InputGroup>
|
||||
@@ -82,8 +82,7 @@
|
||||
size="sm"
|
||||
@click="openCreateDialog"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'plus']"
|
||||
<Plus
|
||||
class="mr-1.5"
|
||||
/>
|
||||
{{ $t('common.add') }}
|
||||
@@ -309,9 +308,8 @@
|
||||
v-if="probing"
|
||||
class="mr-1.5"
|
||||
/>
|
||||
<FontAwesomeIcon
|
||||
<RefreshCw
|
||||
v-else
|
||||
:icon="['fas', 'rotate']"
|
||||
class="mr-1.5"
|
||||
/>
|
||||
{{ probing ? $t('mcp.probing') : $t('mcp.probe') }}
|
||||
@@ -329,7 +327,7 @@
|
||||
v-if="probeAuthRequired"
|
||||
class="text-xs text-amber-600 bg-amber-50 dark:bg-amber-900/20 rounded-md p-3 flex items-center gap-2"
|
||||
>
|
||||
<FontAwesomeIcon :icon="['fas', 'lock']" />
|
||||
<Lock />
|
||||
{{ $t('mcp.authRequired') }}
|
||||
</div>
|
||||
|
||||
@@ -415,7 +413,7 @@
|
||||
size="icon-xs"
|
||||
@click="copyText(oauthCallbackUrl); toast.success($t('common.copied'))"
|
||||
>
|
||||
<FontAwesomeIcon :icon="['far', 'copy']" />
|
||||
<Copy />
|
||||
</Button>
|
||||
</div>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
@@ -436,9 +434,8 @@
|
||||
v-if="oauthDiscovering || oauthAuthorizing"
|
||||
class="mr-1.5"
|
||||
/>
|
||||
<FontAwesomeIcon
|
||||
<KeyRound
|
||||
v-else
|
||||
:icon="['fas', 'key']"
|
||||
class="mr-1.5"
|
||||
/>
|
||||
{{ oauthDiscovering ? $t('mcp.oauth.discovering') : oauthAuthorizing ? $t('mcp.oauth.authorizing') : $t('mcp.oauth.authorize') }}
|
||||
@@ -466,8 +463,7 @@
|
||||
:key="tool.name"
|
||||
class="flex items-start gap-2 py-1.5 px-2 rounded text-xs hover:bg-accent/50"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'wrench']"
|
||||
<Wrench
|
||||
class="mt-1 text-muted-foreground shrink-0 text-xs"
|
||||
/>
|
||||
<div class="min-w-0">
|
||||
@@ -504,7 +500,7 @@
|
||||
>
|
||||
<EmptyHeader>
|
||||
<EmptyMedia variant="icon">
|
||||
<FontAwesomeIcon :icon="['fas', 'plug']" />
|
||||
<Plug />
|
||||
</EmptyMedia>
|
||||
</EmptyHeader>
|
||||
<EmptyTitle>{{ $t('mcp.emptyTitle') }}</EmptyTitle>
|
||||
@@ -639,6 +635,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Search, Plus, RefreshCw, Lock, Copy, KeyRound, Wrench, Plug } from 'lucide-vue-next'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { toast } from 'vue-sonner'
|
||||
|
||||
@@ -18,8 +18,7 @@
|
||||
:aria-label="$t('bots.memory.compact')"
|
||||
@click="openCompactDialog"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'brain']"
|
||||
<Brain
|
||||
class="size-3.5 text-primary"
|
||||
/>
|
||||
</Button>
|
||||
@@ -32,8 +31,7 @@
|
||||
:aria-label="$t('common.refresh')"
|
||||
@click="loadMemories"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'rotate']"
|
||||
<RefreshCw
|
||||
:class="{ 'animate-spin': loading }"
|
||||
class="size-3.5"
|
||||
/>
|
||||
@@ -41,8 +39,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="relative">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'magnifying-glass']"
|
||||
<Search
|
||||
class="absolute left-2.5 top-1/2 -translate-y-1/2 size-3 text-muted-foreground"
|
||||
/>
|
||||
<Input
|
||||
@@ -77,8 +74,7 @@
|
||||
@click="selectMemory(item)"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'file-lines']"
|
||||
<FileText
|
||||
class="size-3 shrink-0 opacity-70"
|
||||
/>
|
||||
<span class="truncate pr-4">{{ formatDate(item.created_at) }}</span>
|
||||
@@ -97,8 +93,7 @@
|
||||
class="w-full h-8 text-xs"
|
||||
@click="openNewMemoryDialog"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'plus']"
|
||||
<Plus
|
||||
class="mr-2 size-3"
|
||||
/>
|
||||
{{ $t('bots.memory.newMemory') }}
|
||||
@@ -112,8 +107,7 @@
|
||||
<div class="flex-1 flex flex-col min-h-0">
|
||||
<div class="p-3 border-b flex items-center justify-between bg-muted/30 shrink-0">
|
||||
<div class="flex items-center gap-3 min-w-0">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'file-lines']"
|
||||
<FileText
|
||||
class="size-4 text-muted-foreground shrink-0"
|
||||
/>
|
||||
<div class="min-w-0">
|
||||
@@ -129,8 +123,7 @@
|
||||
:aria-label="$t('common.copy')"
|
||||
@click="copyToClipboard(selectedMemory.id)"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'copy']"
|
||||
<Copy
|
||||
class="size-2.5"
|
||||
/>
|
||||
</button>
|
||||
@@ -151,8 +144,7 @@
|
||||
:disabled="actionLoading"
|
||||
:aria-label="$t('common.delete')"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['far', 'trash-can']"
|
||||
<Trash2
|
||||
class="size-3.5"
|
||||
/>
|
||||
</Button>
|
||||
@@ -223,8 +215,7 @@
|
||||
class="flex-1 flex flex-col items-center justify-center text-muted-foreground p-8 text-center"
|
||||
>
|
||||
<div class="size-12 rounded-full bg-muted flex items-center justify-center mb-4">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'brain']"
|
||||
<Brain
|
||||
class="size-6 opacity-20"
|
||||
/>
|
||||
</div>
|
||||
@@ -260,8 +251,7 @@
|
||||
class="text-xs h-8"
|
||||
@click="loadHistory"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'rotate']"
|
||||
<RefreshCw
|
||||
:class="{ 'animate-spin': historyLoading }"
|
||||
class="mr-1.5 size-3"
|
||||
/>
|
||||
@@ -292,9 +282,8 @@
|
||||
class="mt-1 size-4 shrink-0 rounded border border-primary flex items-center justify-center transition-colors"
|
||||
:class="selectedHistoryMessages.includes(msg) ? 'bg-primary text-primary-foreground' : 'bg-background'"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
<Check
|
||||
v-if="selectedHistoryMessages.includes(msg)"
|
||||
:icon="['fas', 'check']"
|
||||
class="size-2.5"
|
||||
/>
|
||||
</div>
|
||||
@@ -444,6 +433,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Brain, RefreshCw, Search, FileText, Plus, Copy, Trash2, Check } from 'lucide-vue-next'
|
||||
import { computed, ref, onMounted, watch } from 'vue'
|
||||
import { use } from 'echarts/core'
|
||||
import { CanvasRenderer } from 'echarts/renderers'
|
||||
|
||||
@@ -45,8 +45,7 @@
|
||||
class="flex flex-col items-center justify-center py-12 text-center"
|
||||
>
|
||||
<div class="rounded-full bg-muted p-3 mb-4">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'calendar-alt']"
|
||||
<Calendar
|
||||
class="size-6 text-muted-foreground"
|
||||
/>
|
||||
</div>
|
||||
@@ -159,6 +158,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Calendar } from 'lucide-vue-next'
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { toast } from 'vue-sonner'
|
||||
|
||||
@@ -11,8 +11,7 @@
|
||||
size="sm"
|
||||
@click="handleCreate"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'plus']"
|
||||
<Plus
|
||||
class="mr-2"
|
||||
/>
|
||||
{{ $t('bots.skills.addSkill') }}
|
||||
@@ -34,8 +33,7 @@
|
||||
class="flex flex-col items-center justify-center py-12 text-center"
|
||||
>
|
||||
<div class="rounded-full bg-muted p-3 mb-4">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'bolt']"
|
||||
<Zap
|
||||
class="size-6 text-muted-foreground"
|
||||
/>
|
||||
</div>
|
||||
@@ -73,8 +71,7 @@
|
||||
:title="$t('common.edit')"
|
||||
@click="handleEdit(skill)"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'pen-to-square']"
|
||||
<SquarePen
|
||||
class="size-3.5"
|
||||
/>
|
||||
</Button>
|
||||
@@ -91,8 +88,7 @@
|
||||
:disabled="isDeleting"
|
||||
:title="$t('common.delete')"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['far', 'trash-can']"
|
||||
<Trash2
|
||||
class="size-3.5"
|
||||
/>
|
||||
</Button>
|
||||
@@ -149,6 +145,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Plus, Zap, SquarePen, Trash2 } from 'lucide-vue-next'
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { toast } from 'vue-sonner'
|
||||
|
||||
@@ -18,24 +18,21 @@
|
||||
class="w-full justify-between font-normal"
|
||||
>
|
||||
<span class="flex items-center gap-2 truncate">
|
||||
<FontAwesomeIcon
|
||||
<AppWindow
|
||||
v-if="selected"
|
||||
:icon="['fas', 'window-maximize']"
|
||||
class="size-3.5 text-muted-foreground"
|
||||
/>
|
||||
<span class="truncate">{{ displayLabel || placeholder }}</span>
|
||||
</span>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'magnifying-glass']"
|
||||
<Search
|
||||
class="ml-2 size-3.5 shrink-0 text-muted-foreground"
|
||||
/>
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
<template #option-icon="{ option }">
|
||||
<FontAwesomeIcon
|
||||
<AppWindow
|
||||
v-if="option.value"
|
||||
:icon="['fas', 'window-maximize']"
|
||||
class="size-3.5 text-muted-foreground"
|
||||
/>
|
||||
</template>
|
||||
@@ -52,6 +49,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { AppWindow, Search } from 'lucide-vue-next'
|
||||
import { Button } from '@memohai/ui'
|
||||
import { computed } from 'vue'
|
||||
import type { BrowsercontextsBrowserContext } from '@memohai/sdk'
|
||||
|
||||
@@ -150,8 +150,8 @@
|
||||
:aria-pressed="!!visibleSecrets[key]"
|
||||
@click="visibleSecrets[key] = !visibleSecrets[key]"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', visibleSecrets[key] ? 'eye-slash' : 'eye']"
|
||||
<component
|
||||
:is="visibleSecrets[key] ? EyeOff : Eye"
|
||||
class="size-3.5"
|
||||
/>
|
||||
</button>
|
||||
@@ -262,6 +262,7 @@ import {
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
} from '@memohai/ui'
|
||||
import { Eye, EyeOff } from 'lucide-vue-next'
|
||||
import { reactive, watch, computed, ref } from 'vue'
|
||||
import { toast } from 'vue-sonner'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
<DialogTrigger as-child>
|
||||
<slot name="trigger">
|
||||
<Button variant="default">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'plus']"
|
||||
<Plus
|
||||
class="mr-1.5"
|
||||
/>
|
||||
{{ $t('bots.createBot') }}
|
||||
@@ -142,6 +141,7 @@ import {
|
||||
SelectValue,
|
||||
Spinner,
|
||||
} from '@memohai/ui'
|
||||
import { Plus } from 'lucide-vue-next'
|
||||
import { useForm } from 'vee-validate'
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
import z from 'zod'
|
||||
|
||||
@@ -18,24 +18,21 @@
|
||||
class="w-full justify-between font-normal"
|
||||
>
|
||||
<span class="flex items-center gap-2 truncate">
|
||||
<FontAwesomeIcon
|
||||
<Brain
|
||||
v-if="selected"
|
||||
:icon="['fas', 'brain']"
|
||||
class="size-3.5 text-primary"
|
||||
/>
|
||||
<span class="truncate">{{ displayLabel || placeholder }}</span>
|
||||
</span>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'magnifying-glass']"
|
||||
<Search
|
||||
class="ml-2 size-3.5 shrink-0 text-muted-foreground"
|
||||
/>
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
<template #option-icon="{ option }">
|
||||
<FontAwesomeIcon
|
||||
<Brain
|
||||
v-if="option.value"
|
||||
:icon="['fas', 'brain']"
|
||||
class="size-3.5 text-primary"
|
||||
/>
|
||||
</template>
|
||||
@@ -52,6 +49,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Brain, Search } from 'lucide-vue-next'
|
||||
import { Button } from '@memohai/ui'
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
@@ -1,235 +0,0 @@
|
||||
<template>
|
||||
<div class="space-y-1">
|
||||
<div
|
||||
v-for="(msg, idx) in messages"
|
||||
:key="msg.id || idx"
|
||||
class="group relative rounded-lg border p-3 transition-colors hover:bg-muted/50"
|
||||
>
|
||||
<div class="flex items-start gap-3">
|
||||
<!-- Role Icon -->
|
||||
<div
|
||||
class="flex size-8 shrink-0 items-center justify-center rounded-full"
|
||||
:class="roleIconClass(msg.role)"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="roleIcon(msg.role)"
|
||||
class="size-3.5 text-white"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="min-w-0 flex-1 space-y-1.5">
|
||||
<!-- Top row -->
|
||||
<div class="flex flex-wrap items-center gap-2 text-xs">
|
||||
<Badge
|
||||
:variant="roleBadgeVariant(msg.role)"
|
||||
class="text-xs font-medium"
|
||||
>
|
||||
{{ roleLabel(msg.role) }}
|
||||
</Badge>
|
||||
<span
|
||||
v-if="msg.sender_display_name"
|
||||
class="font-medium truncate max-w-[200px]"
|
||||
>
|
||||
{{ msg.sender_display_name }}
|
||||
</span>
|
||||
<Badge
|
||||
v-if="msg.platform"
|
||||
variant="outline"
|
||||
class="text-[10px] uppercase h-5"
|
||||
>
|
||||
{{ msg.platform }}
|
||||
</Badge>
|
||||
<span class="text-xs text-muted-foreground ml-auto shrink-0">
|
||||
{{ formatTime(msg.created_at) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Message content -->
|
||||
<div
|
||||
class="text-xs leading-relaxed"
|
||||
:class="{ 'font-mono text-xs': msg.role === 'tool' || msg.role === 'system' }"
|
||||
>
|
||||
<div
|
||||
class="whitespace-pre-wrap break-words [overflow-wrap:anywhere]"
|
||||
:class="{ 'line-clamp-4': !expandedIds.includes(msgKey(msg, idx)) }"
|
||||
>
|
||||
{{ formatContent(msg.content) }}
|
||||
</div>
|
||||
<button
|
||||
v-if="isContentLong(msg.content)"
|
||||
class="mt-1 text-xs text-primary hover:underline"
|
||||
@click="toggleExpand(msgKey(msg, idx))"
|
||||
>
|
||||
{{ expandedIds.includes(msgKey(msg, idx)) ? collapseLabel : expandLabel }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Usage -->
|
||||
<div
|
||||
v-if="hasUsage(msg.usage)"
|
||||
class="flex items-center gap-3 text-xs text-muted-foreground pt-1"
|
||||
>
|
||||
<span class="inline-flex items-center gap-1">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'chart-bar']"
|
||||
class="size-3"
|
||||
/>
|
||||
{{ formatUsage(msg.usage) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { Badge } from '@memohai/ui'
|
||||
import { formatDateTime } from '@/utils/date-time'
|
||||
|
||||
export interface MessageItem {
|
||||
id?: string
|
||||
role?: string
|
||||
content?: unknown
|
||||
sender_display_name?: string
|
||||
sender_avatar_url?: string
|
||||
platform?: string
|
||||
created_at?: string
|
||||
usage?: unknown
|
||||
metadata?: Record<string, unknown>
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
defineProps<{
|
||||
messages: MessageItem[]
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const expandedIds = ref<string[]>([])
|
||||
|
||||
const expandLabel = computed(() => t('bots.history.expandContent'))
|
||||
const collapseLabel = computed(() => t('bots.history.collapseContent'))
|
||||
|
||||
function msgKey(msg: MessageItem, idx: number): string {
|
||||
return msg.id || String(idx)
|
||||
}
|
||||
|
||||
function toggleExpand(id: string) {
|
||||
if (expandedIds.value.includes(id)) {
|
||||
expandedIds.value = expandedIds.value.filter(v => v !== id)
|
||||
} else {
|
||||
expandedIds.value = [...expandedIds.value, id]
|
||||
}
|
||||
}
|
||||
|
||||
function roleIcon(role?: string): [string, string] {
|
||||
switch (role) {
|
||||
case 'user': return ['fas', 'user']
|
||||
case 'assistant': return ['fas', 'robot']
|
||||
case 'tool': return ['fas', 'wrench']
|
||||
case 'system': return ['fas', 'laptop-code']
|
||||
default: return ['fas', 'circle-question']
|
||||
}
|
||||
}
|
||||
|
||||
function roleIconClass(role?: string): string {
|
||||
switch (role) {
|
||||
case 'user': return 'bg-blue-500'
|
||||
case 'assistant': return 'bg-emerald-500'
|
||||
case 'tool': return 'bg-amber-500'
|
||||
case 'system': return 'bg-slate-500'
|
||||
default: return 'bg-muted-foreground'
|
||||
}
|
||||
}
|
||||
|
||||
function roleBadgeVariant(role?: string): 'default' | 'secondary' | 'destructive' | 'outline' {
|
||||
switch (role) {
|
||||
case 'user': return 'default'
|
||||
case 'assistant': return 'secondary'
|
||||
case 'tool': return 'outline'
|
||||
case 'system': return 'outline'
|
||||
default: return 'outline'
|
||||
}
|
||||
}
|
||||
|
||||
function roleLabel(role?: string): string {
|
||||
const key = `bots.history.role.${role || 'system'}`
|
||||
const val = t(key)
|
||||
return val !== key ? val : (role || 'unknown')
|
||||
}
|
||||
|
||||
function formatTime(val?: string): string {
|
||||
return formatDateTime(val, { fallback: '-' })
|
||||
}
|
||||
|
||||
function formatContent(content: unknown): string {
|
||||
if (!content) return ''
|
||||
try {
|
||||
if (Array.isArray(content)) {
|
||||
const decoder = new TextDecoder()
|
||||
const str = decoder.decode(new Uint8Array(content as number[]))
|
||||
try {
|
||||
const parsed = JSON.parse(str)
|
||||
if (typeof parsed === 'string') return parsed
|
||||
return JSON.stringify(parsed, null, 2)
|
||||
} catch {
|
||||
return str
|
||||
}
|
||||
}
|
||||
if (typeof content === 'string') return content
|
||||
if (typeof content === 'object') return JSON.stringify(content, null, 2)
|
||||
return String(content)
|
||||
} catch {
|
||||
return String(content)
|
||||
}
|
||||
}
|
||||
|
||||
function isContentLong(content: unknown): boolean {
|
||||
const text = formatContent(content)
|
||||
return text.length > 300 || text.split('\n').length > 4
|
||||
}
|
||||
|
||||
function hasUsage(usage: unknown): boolean {
|
||||
if (!usage) return false
|
||||
if (Array.isArray(usage) && usage.length > 0) return true
|
||||
if (typeof usage === 'object' && Object.keys(usage as object).length > 0) return true
|
||||
return false
|
||||
}
|
||||
|
||||
function formatUsage(usage: unknown): string {
|
||||
if (!usage) return ''
|
||||
try {
|
||||
if (Array.isArray(usage)) {
|
||||
const decoder = new TextDecoder()
|
||||
const str = decoder.decode(new Uint8Array(usage as number[]))
|
||||
try {
|
||||
const parsed = JSON.parse(str)
|
||||
if (typeof parsed === 'object' && parsed !== null) {
|
||||
const parts: string[] = []
|
||||
for (const [k, v] of Object.entries(parsed)) {
|
||||
parts.push(`${k}: ${v}`)
|
||||
}
|
||||
return parts.join(' | ')
|
||||
}
|
||||
return str
|
||||
} catch {
|
||||
return str
|
||||
}
|
||||
}
|
||||
if (typeof usage === 'object') {
|
||||
const parts: string[] = []
|
||||
for (const [k, v] of Object.entries(usage as Record<string, unknown>)) {
|
||||
parts.push(`${k}: ${v}`)
|
||||
}
|
||||
return parts.join(' | ')
|
||||
}
|
||||
return String(usage)
|
||||
} catch {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -25,8 +25,7 @@
|
||||
/>
|
||||
<span class="truncate">{{ displayLabel || placeholder }}</span>
|
||||
</span>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'magnifying-glass']"
|
||||
<Search
|
||||
class="ml-2 size-3.5 shrink-0 text-muted-foreground"
|
||||
/>
|
||||
</Button>
|
||||
@@ -52,6 +51,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Search } from 'lucide-vue-next'
|
||||
import { Button } from '@memohai/ui'
|
||||
import { computed } from 'vue'
|
||||
import type { SearchprovidersGetResponse } from '@memohai/sdk'
|
||||
|
||||
@@ -17,24 +17,21 @@
|
||||
class="w-full justify-between font-normal"
|
||||
>
|
||||
<span class="flex items-center gap-2 truncate">
|
||||
<FontAwesomeIcon
|
||||
<Volume2
|
||||
v-if="selected"
|
||||
:icon="['fas', 'volume-high']"
|
||||
class="size-3.5 text-muted-foreground"
|
||||
/>
|
||||
<span class="truncate">{{ displayLabel || placeholder }}</span>
|
||||
</span>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'magnifying-glass']"
|
||||
<Search
|
||||
class="ml-2 size-3.5 shrink-0 text-muted-foreground"
|
||||
/>
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
<template #option-icon="{ option }">
|
||||
<FontAwesomeIcon
|
||||
<Volume2
|
||||
v-if="option.value"
|
||||
:icon="['fas', 'volume-high']"
|
||||
class="size-3.5 text-muted-foreground"
|
||||
/>
|
||||
</template>
|
||||
@@ -51,6 +48,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Volume2, Search } from 'lucide-vue-next'
|
||||
import { Button } from '@memohai/ui'
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
<template>
|
||||
<SearchableSelectPopover
|
||||
v-model="selected"
|
||||
:options="options"
|
||||
:placeholder="placeholder || ''"
|
||||
:aria-label="placeholder || 'Select TTS provider'"
|
||||
:search-placeholder="$t('speech.searchPlaceholder')"
|
||||
search-aria-label="Search TTS providers"
|
||||
:empty-text="$t('speech.emptyTitle')"
|
||||
:show-group-headers="false"
|
||||
>
|
||||
<template #trigger="{ open, displayLabel }">
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
:aria-expanded="open"
|
||||
:aria-label="placeholder || 'Select TTS provider'"
|
||||
class="w-full justify-between font-normal"
|
||||
>
|
||||
<span class="flex items-center gap-2 truncate">
|
||||
<FontAwesomeIcon
|
||||
v-if="selected"
|
||||
:icon="['fas', 'volume-high']"
|
||||
class="size-3.5 text-muted-foreground"
|
||||
/>
|
||||
<span class="truncate">{{ displayLabel || placeholder }}</span>
|
||||
</span>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'magnifying-glass']"
|
||||
class="ml-2 size-3.5 shrink-0 text-muted-foreground"
|
||||
/>
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
<template #option-icon="{ option }">
|
||||
<FontAwesomeIcon
|
||||
v-if="option.value"
|
||||
:icon="['fas', 'volume-high']"
|
||||
class="size-3.5 text-muted-foreground"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #option-label="{ option }">
|
||||
<span
|
||||
class="truncate"
|
||||
:class="{ 'text-muted-foreground': !option.value }"
|
||||
>
|
||||
{{ option.label }}
|
||||
</span>
|
||||
</template>
|
||||
</SearchableSelectPopover>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Button } from '@memohai/ui'
|
||||
import { computed } from 'vue'
|
||||
import type { TtsProviderResponse } from '@memohai/sdk'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import SearchableSelectPopover from '@/components/searchable-select-popover/index.vue'
|
||||
import type { SearchableSelectOption } from '@/components/searchable-select-popover/index.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
providers: TtsProviderResponse[]
|
||||
placeholder?: string
|
||||
}>()
|
||||
const { t } = useI18n()
|
||||
|
||||
const selected = defineModel<string>({ default: '' })
|
||||
|
||||
const options = computed<SearchableSelectOption[]>(() => {
|
||||
const noneOption: SearchableSelectOption = {
|
||||
value: '',
|
||||
label: t('common.none'),
|
||||
keywords: [t('common.none')],
|
||||
}
|
||||
const providerOptions = props.providers.map((provider) => ({
|
||||
value: provider.id || '',
|
||||
label: provider.name || provider.id || '',
|
||||
description: provider.provider,
|
||||
keywords: [provider.name ?? '', provider.provider ?? ''],
|
||||
}))
|
||||
return [noneOption, ...providerOptions]
|
||||
})
|
||||
</script>
|
||||
@@ -24,9 +24,8 @@
|
||||
v-if="isStarting"
|
||||
class="mr-1.5"
|
||||
/>
|
||||
<FontAwesomeIcon
|
||||
<QrCode
|
||||
v-else
|
||||
:icon="['fas', 'qrcode']"
|
||||
class="mr-1.5 size-3.5"
|
||||
/>
|
||||
{{ $t('bots.channels.weixinQr.startScan') }}
|
||||
@@ -57,8 +56,7 @@
|
||||
class="absolute inset-0 flex items-center justify-center rounded-lg bg-background/80"
|
||||
>
|
||||
<div class="text-center">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'mobile-screen']"
|
||||
<Smartphone
|
||||
class="size-8 text-primary mb-2"
|
||||
/>
|
||||
<p class="text-xs font-medium text-foreground">
|
||||
@@ -103,8 +101,7 @@
|
||||
class="flex flex-col items-center gap-3 py-4"
|
||||
>
|
||||
<div class="flex size-12 items-center justify-center rounded-full bg-green-100 dark:bg-green-900">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'check']"
|
||||
<Check
|
||||
class="size-5 text-green-600 dark:text-green-400"
|
||||
/>
|
||||
</div>
|
||||
@@ -132,6 +129,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { QrCode, Smartphone, Check } from 'lucide-vue-next'
|
||||
import { ref, computed, onUnmounted } from 'vue'
|
||||
import { Button, Spinner } from '@memohai/ui'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
@@ -21,8 +21,7 @@
|
||||
:disabled="!bot || botLifecyclePending"
|
||||
@click="handleEditAvatar"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'pen-to-square']"
|
||||
<SquarePen
|
||||
class="size-6 text-white"
|
||||
/>
|
||||
</button>
|
||||
@@ -70,8 +69,7 @@
|
||||
:aria-label="$t('common.edit')"
|
||||
@click="handleStartEditBotName"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'pen-to-square']"
|
||||
<SquarePen
|
||||
class="size-3.5"
|
||||
/>
|
||||
</Button>
|
||||
@@ -84,9 +82,8 @@
|
||||
class="text-xs"
|
||||
:title="hasIssue ? issueTitle : undefined"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
<LoaderCircle
|
||||
v-if="bot.status === 'creating' || bot.status === 'deleting'"
|
||||
:icon="['fas', 'spinner']"
|
||||
class="mr-1 size-3 animate-spin"
|
||||
/>
|
||||
{{ statusLabel }}
|
||||
@@ -218,6 +215,7 @@ import {
|
||||
SidebarMenuItem,
|
||||
Toggle
|
||||
} from '@memohai/ui'
|
||||
import { SquarePen, LoaderCircle } from 'lucide-vue-next'
|
||||
import { computed, ref, watch, onMounted, toValue } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { toast } from 'vue-sonner'
|
||||
|
||||
@@ -7,8 +7,7 @@
|
||||
</h2>
|
||||
<div class="flex items-center gap-3 ">
|
||||
<div class="relative">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'magnifying-glass']"
|
||||
<Search
|
||||
class="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground size-3.5"
|
||||
/>
|
||||
<Input
|
||||
@@ -41,7 +40,7 @@
|
||||
>
|
||||
<EmptyHeader>
|
||||
<EmptyMedia variant="icon">
|
||||
<FontAwesomeIcon :icon="['fas', 'robot']" />
|
||||
<Bot />
|
||||
</EmptyMedia>
|
||||
</EmptyHeader>
|
||||
<EmptyTitle>{{ $t('bots.emptyTitle') }}</EmptyTitle>
|
||||
@@ -61,6 +60,7 @@ import {
|
||||
EmptyMedia,
|
||||
EmptyTitle,
|
||||
} from '@memohai/ui'
|
||||
import { Search, Bot } from 'lucide-vue-next'
|
||||
import { ref, computed, watch, onUnmounted } from 'vue'
|
||||
import BotCard from './components/bot-card.vue'
|
||||
import CreateBot from './components/create-bot.vue'
|
||||
|
||||
@@ -14,8 +14,7 @@
|
||||
class="w-full shadow-none! text-muted-foreground mb-4"
|
||||
variant="outline"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'plus']"
|
||||
<Plus
|
||||
class="mr-1"
|
||||
/> {{ $t('browser.add') }}
|
||||
</Button>
|
||||
@@ -62,6 +61,7 @@ import { useMutation, useQueryCache } from '@pinia/colada'
|
||||
import { postBrowserContexts } from '@memohai/sdk'
|
||||
import type { BrowsercontextsCreateRequest } from '@memohai/sdk'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { Plus } from 'lucide-vue-next'
|
||||
import FormDialogShell from '@/components/form-dialog-shell/index.vue'
|
||||
import { useDialogMutation } from '@/composables/useDialogMutation'
|
||||
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
<div class="p-4">
|
||||
<section class="flex justify-between items-center">
|
||||
<div class="flex items-center gap-2">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'window-maximize']"
|
||||
<AppWindow
|
||||
class="size-5"
|
||||
/>
|
||||
<div>
|
||||
@@ -212,7 +211,7 @@
|
||||
variant="outline"
|
||||
:aria-label="$t('common.delete')"
|
||||
>
|
||||
<FontAwesomeIcon :icon="['far', 'trash-can']" />
|
||||
<Trash2 />
|
||||
</Button>
|
||||
</template>
|
||||
</ConfirmPopover>
|
||||
@@ -252,6 +251,7 @@ import { useDialogMutation } from '@/composables/useDialogMutation'
|
||||
import ConfirmPopover from '@/components/confirm-popover/index.vue'
|
||||
import LoadingButton from '@/components/loading-button/index.vue'
|
||||
import { resolveApiErrorMessage } from '@/utils/api-error'
|
||||
import { AppWindow, Trash2 } from 'lucide-vue-next'
|
||||
import { emptyTimezoneValue } from '@/utils/timezones'
|
||||
import TimezoneSelect from '@/components/timezone-select/index.vue'
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import { getBrowserContexts } from '@memohai/sdk'
|
||||
import type { BrowsercontextsBrowserContext } from '@memohai/sdk'
|
||||
import AddBrowserContext from './components/add-browser-context.vue'
|
||||
import ContextSetting from './components/context-setting.vue'
|
||||
import { AppWindow, Plus } from 'lucide-vue-next'
|
||||
import MasterDetailSidebarLayout from '@/components/master-detail-sidebar-layout/index.vue'
|
||||
|
||||
const { data: contextData } = useQuery({
|
||||
@@ -77,8 +78,7 @@ const openStatus = reactive({
|
||||
}
|
||||
}"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'window-maximize']"
|
||||
<AppWindow
|
||||
class="mr-2"
|
||||
/>
|
||||
{{ item.name }}
|
||||
@@ -105,7 +105,7 @@ const openStatus = reactive({
|
||||
>
|
||||
<EmptyHeader>
|
||||
<EmptyMedia variant="icon">
|
||||
<FontAwesomeIcon :icon="['fas', 'window-maximize']" />
|
||||
<AppWindow />
|
||||
</EmptyMedia>
|
||||
</EmptyHeader>
|
||||
<EmptyTitle>{{ $t('browser.emptyTitle') }}</EmptyTitle>
|
||||
@@ -115,8 +115,7 @@ const openStatus = reactive({
|
||||
variant="outline"
|
||||
@click="openStatus.addOpen = true"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'plus']"
|
||||
<Plus
|
||||
class="mr-1"
|
||||
/> {{ $t('browser.add') }}
|
||||
</Button>
|
||||
|
||||
@@ -14,8 +14,7 @@
|
||||
class="w-full shadow-none! text-muted-foreground mb-4"
|
||||
variant="outline"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'plus']"
|
||||
<Plus
|
||||
class="mr-1"
|
||||
/> {{ $t('email.add') }}
|
||||
</Button>
|
||||
@@ -99,6 +98,7 @@ import { useMutation, useQuery, useQueryCache } from '@pinia/colada'
|
||||
import { postEmailProviders, getEmailProvidersMeta } from '@memohai/sdk'
|
||||
import type { EmailCreateProviderRequest } from '@memohai/sdk'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { Plus } from 'lucide-vue-next'
|
||||
import FormDialogShell from '@/components/form-dialog-shell/index.vue'
|
||||
import { useDialogMutation } from '@/composables/useDialogMutation'
|
||||
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
<div class="p-4">
|
||||
<section class="flex justify-between items-center">
|
||||
<div class="flex items-center gap-2">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'envelope']"
|
||||
<Mail
|
||||
class="size-5"
|
||||
/>
|
||||
<div>
|
||||
@@ -78,8 +77,8 @@
|
||||
class="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
|
||||
@click="visibleSecrets[field.key] = !visibleSecrets[field.key]"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', visibleSecrets[field.key] ? 'eye-slash' : 'eye']"
|
||||
<component
|
||||
:is="visibleSecrets[field.key] ? EyeOff : Eye"
|
||||
class="size-3.5"
|
||||
/>
|
||||
</button>
|
||||
@@ -170,8 +169,7 @@
|
||||
:loading="authorizeLoading"
|
||||
@click="handleAuthorize"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'key']"
|
||||
<KeyRound
|
||||
class="mr-1.5"
|
||||
/>
|
||||
{{ $t('email.oauth.authorize') }}
|
||||
@@ -200,7 +198,7 @@
|
||||
type="button"
|
||||
variant="outline"
|
||||
>
|
||||
<FontAwesomeIcon :icon="['far', 'trash-can']" />
|
||||
<Trash2 />
|
||||
</Button>
|
||||
</template>
|
||||
</ConfirmPopover>
|
||||
@@ -231,6 +229,7 @@ import {
|
||||
Switch,
|
||||
Label,
|
||||
} from '@memohai/ui'
|
||||
import { Mail, Eye, EyeOff, KeyRound, Trash2 } from 'lucide-vue-next'
|
||||
import ConfirmPopover from '@/components/confirm-popover/index.vue'
|
||||
import LoadingButton from '@/components/loading-button/index.vue'
|
||||
import { computed, inject, reactive, ref, watch } from 'vue'
|
||||
|
||||
@@ -19,6 +19,7 @@ import { getEmailProviders } from '@memohai/sdk'
|
||||
import type { EmailProviderResponse } from '@memohai/sdk'
|
||||
import AddEmailProvider from './components/add-email-provider.vue'
|
||||
import ProviderSetting from './components/provider-setting.vue'
|
||||
import { Mail, Plus } from 'lucide-vue-next'
|
||||
import MasterDetailSidebarLayout from '@/components/master-detail-sidebar-layout/index.vue'
|
||||
|
||||
const { data: providerData } = useQuery({
|
||||
@@ -100,7 +101,7 @@ const openStatus = reactive({ addOpen: false })
|
||||
>
|
||||
<EmptyHeader>
|
||||
<EmptyMedia variant="icon">
|
||||
<FontAwesomeIcon :icon="['fas', 'envelope']" />
|
||||
<Mail />
|
||||
</EmptyMedia>
|
||||
</EmptyHeader>
|
||||
<EmptyTitle>{{ $t('email.emptyTitle') }}</EmptyTitle>
|
||||
@@ -110,8 +111,7 @@ const openStatus = reactive({ addOpen: false })
|
||||
variant="outline"
|
||||
@click="openStatus.addOpen = true"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'plus']"
|
||||
<Plus
|
||||
class="mr-1"
|
||||
/> {{ $t('email.add') }}
|
||||
</Button>
|
||||
|
||||
@@ -51,17 +51,14 @@
|
||||
:title="getContainerPath(att)"
|
||||
@click="handleOpenContainerFile(att)"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', fileIcon(att)]"
|
||||
<component
|
||||
:is="fileIconComponent(att)"
|
||||
class="size-4 text-muted-foreground"
|
||||
/>
|
||||
<span class="truncate max-w-[200px] font-mono text-xs">
|
||||
{{ getDisplayName(att) }}
|
||||
</span>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'arrow-up-right-from-square']"
|
||||
class="size-3 text-muted-foreground/60 shrink-0"
|
||||
/>
|
||||
<ExternalLink class="size-3 text-muted-foreground/60 shrink-0" />
|
||||
</button>
|
||||
|
||||
<!-- Downloadable file -->
|
||||
@@ -72,8 +69,8 @@
|
||||
rel="noopener noreferrer"
|
||||
class="flex items-center gap-2 px-3 py-2 rounded-lg border bg-muted/30 hover:bg-muted/60 transition-colors text-xs"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', fileIcon(att)]"
|
||||
<component
|
||||
:is="fileIconComponent(att)"
|
||||
class="size-4 text-muted-foreground"
|
||||
/>
|
||||
<span class="truncate max-w-[200px]">
|
||||
@@ -86,8 +83,8 @@
|
||||
v-else
|
||||
class="flex items-center gap-2 px-3 py-2 rounded-lg border bg-muted/30 text-xs text-muted-foreground"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', fileIcon(att)]"
|
||||
<component
|
||||
:is="fileIconComponent(att)"
|
||||
class="size-4"
|
||||
/>
|
||||
<span class="truncate max-w-[200px]">
|
||||
@@ -100,6 +97,8 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { inject } from 'vue'
|
||||
import { Music, Video, File as FileIcon, ExternalLink } from 'lucide-vue-next'
|
||||
import type { Component } from 'vue'
|
||||
import type { AttachmentBlock, AttachmentItem } from '@/store/chat-list'
|
||||
import { resolveUrl } from '../composables/useMediaGallery'
|
||||
import { openInFileManagerKey } from '../composables/useFileManagerProvider'
|
||||
@@ -164,10 +163,10 @@ function handleOpenContainerFile(att: AttachmentItem) {
|
||||
}
|
||||
}
|
||||
|
||||
function fileIcon(att: AttachmentItem): string {
|
||||
function fileIconComponent(att: AttachmentItem): Component {
|
||||
const type = String(att.type ?? '').toLowerCase()
|
||||
if (type === 'audio' || type === 'voice') return 'music'
|
||||
if (type === 'video') return 'video'
|
||||
return 'file'
|
||||
if (type === 'audio' || type === 'voice') return Music
|
||||
if (type === 'video') return Video
|
||||
return FileIcon
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,153 +0,0 @@
|
||||
<template>
|
||||
<section>
|
||||
<SidebarMenuButton
|
||||
as-child
|
||||
class="justify-start py-5! px-4"
|
||||
:disabled="bot.status === 'error'"
|
||||
>
|
||||
<Toggle
|
||||
:class="`p-2.75! border hover:border-border hover:bg-(--bot-item)! ${isActive ? 'border-border bg-(--bot-item)!' : 'border-transparent bg-transparent!'} h-[initial]! ${currentBotId === bot.id ? 'border-inherit' : ''}`"
|
||||
:model-value="isActive"
|
||||
@click="handleSelect(bot)"
|
||||
>
|
||||
<section
|
||||
v-if="bot.status === 'loading'"
|
||||
class="flex gap-2 overflow-hidden w-full items-center "
|
||||
>
|
||||
<ChatStatus />
|
||||
<section class="flex flex-col gap-0.5 flex-1">
|
||||
<h5 class="text-xs flex ">
|
||||
<span class="truncate flex-1 max-w-30">
|
||||
{{ bot.display_name || bot.id }}
|
||||
</span>
|
||||
|
||||
<time
|
||||
class="ml-auto flex-none"
|
||||
:datetime="create_date"
|
||||
>{{ create_date }}</time>
|
||||
</h5>
|
||||
|
||||
<p class="text-xs text-muted-foreground min-w-0 ">
|
||||
<i class="w-1.25 aspect-square bg-blue-500 rounded-full inline-block" />
|
||||
推理中
|
||||
</p>
|
||||
</section>
|
||||
</section>
|
||||
<section
|
||||
v-if="bot.status === 'ready'"
|
||||
class="flex gap-2 overflow-hidden w-full"
|
||||
>
|
||||
<ChatStatus :is-loading="false" />
|
||||
<section class="flex flex-col gap-0.5 flex-1">
|
||||
<section>
|
||||
<h5 class="text-xs flex flex-1">
|
||||
<span class="truncate flex-1 max-w-30">
|
||||
{{ bot.display_name || bot.id }}
|
||||
</span>
|
||||
<time
|
||||
class="ml-auto "
|
||||
:datetime="create_date"
|
||||
>{{ create_date }}</time>
|
||||
</h5>
|
||||
</section>
|
||||
|
||||
<p class="text-xs text-muted-foreground min-w-0 ">
|
||||
fewfwefewfewfewfwf
|
||||
</p>
|
||||
</section>
|
||||
</section>
|
||||
<section
|
||||
v-if="bot.status === 'error'"
|
||||
class="flex gap-2 overflow-hidden w-full"
|
||||
>
|
||||
<ChatStatus
|
||||
:is-loading="false"
|
||||
:error="bot.status === 'error'"
|
||||
/>
|
||||
<section class="flex flex-col gap-0.5 flex-1">
|
||||
<h5 class="text-xs flex">
|
||||
<span class="truncate flex-1 max-w-30">
|
||||
{{ bot.display_name || bot.id }}
|
||||
</span>
|
||||
<time
|
||||
class="ml-auto"
|
||||
:datetime="create_date"
|
||||
>{{ create_date }}</time>
|
||||
</h5>
|
||||
<p class="text-xs text-muted-foreground min-w-0 ">
|
||||
fewfwefewfewfewfwf
|
||||
</p>
|
||||
</section>
|
||||
</section>
|
||||
<section
|
||||
v-if="bot.status === 'no-setting'"
|
||||
class="flex gap-2 overflow-hidden w-full "
|
||||
>
|
||||
<ChatStatus :is-loading="false" />
|
||||
<section class="self-center flex-1">
|
||||
<h5 class="text-xs flex">
|
||||
<span class="truncate flex-1 max-w-30">
|
||||
{{ bot.display_name || bot.id }}
|
||||
</span>
|
||||
|
||||
<time
|
||||
class="ml-auto"
|
||||
:datetime="create_date"
|
||||
>{{ create_date }}</time>
|
||||
</h5>
|
||||
</section>
|
||||
</section>
|
||||
</Toggle>
|
||||
<!-- <Toggle
|
||||
v-if="bot.status === 'ready'"
|
||||
:class="`p-2.75! border border-border h-[initial]! bg-(--bot-item)! ${currentBotId === bot.id ? 'border-inherit' : ''}`"
|
||||
:model-value="isActive(bot.id as string).value"
|
||||
@click="handleSelect(bot)"
|
||||
>
|
||||
<section class="flex gap-2 overflow-hidden w-full">
|
||||
<ChatStatus />
|
||||
<section class="flex flex-col gap-0.5">
|
||||
<h5 class="text-xs">
|
||||
{{ bot.display_name || bot.id }}
|
||||
</h5>
|
||||
<p class="text-xs text-muted-foreground min-w-0 ">
|
||||
<i class="w-1.25 aspect-square bg-blue-500 rounded-full inline-block" />
|
||||
推理中
|
||||
</p>
|
||||
</section>
|
||||
</section>
|
||||
</Toggle> -->
|
||||
</SidebarMenuButton>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { BotsBot } from '@memohai/sdk'
|
||||
import { computed } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useChatStore } from '@/store/chat-list'
|
||||
import ChatStatus from '@/components/chat/chat-status/index.vue'
|
||||
import {
|
||||
Toggle,
|
||||
SidebarMenuButton,
|
||||
} from '@memohai/ui'
|
||||
import moment from 'moment'
|
||||
|
||||
const { bot } = defineProps<{ bot: BotsBot }>()
|
||||
const chatStore = useChatStore()
|
||||
|
||||
const create_date = computed(() => {
|
||||
return moment(bot?.updated_at ?? Date.now()).format('hh:ss')
|
||||
})
|
||||
|
||||
const { currentBotId } = storeToRefs(chatStore)
|
||||
|
||||
const isActive = computed(() => {
|
||||
return currentBotId.value === bot.id
|
||||
})
|
||||
|
||||
function handleSelect(bot: BotsBot) {
|
||||
chatStore.selectBot(bot.id)
|
||||
}
|
||||
|
||||
</script>
|
||||
@@ -1,192 +0,0 @@
|
||||
<template>
|
||||
<section :class="mountNode.id">
|
||||
<Teleport :to="mountNode.leftDefault">
|
||||
<SidebarProvider
|
||||
:open="sidebarOpen"
|
||||
>
|
||||
<SidebarInset>
|
||||
<Sidebar
|
||||
class="absolute! "
|
||||
collapsible="icon"
|
||||
>
|
||||
<SidebarHeader
|
||||
class="h-12 flex flex-rows justify-center"
|
||||
>
|
||||
<section class="flex items-center gap-2">
|
||||
<img
|
||||
src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTgiIGhlaWdodD0iMTgiIHZpZXdCb3g9IjAgMCAxOCAxOCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEuNSA5QzEuNSA2LjE4NzcgMS41IDQuNzgxNTUgMi4yMTYxOSAzLjc5NTgxQzIuNDQ3NDggMy40Nzc0NSAyLjcyNzQ1IDMuMTk3NDggMy4wNDU4MSAyLjk2NjE5QzQuMDMxNTUgMi4yNSA1LjQzNzcgMi4yNSA4LjI1IDIuMjVIOS43NUMxMi41NjIzIDIuMjUgMTMuOTY4NCAyLjI1IDE0Ljk1NDIgMi45NjYxOUMxNS4yNzI2IDMuMTk3NDggMTUuNTUyNSAzLjQ3NzQ1IDE1Ljc4MzggMy43OTU4MUMxNi41IDQuNzgxNTUgMTYuNSA2LjE4NzcgMTYuNSA5QzE2LjUgMTEuODEyMyAxNi41IDEzLjIxODQgMTUuNzgzOCAxNC4yMDQyQzE1LjU1MjUgMTQuNTIyNSAxNS4yNzI2IDE0LjgwMjUgMTQuOTU0MiAxNS4wMzM4QzEzLjk2ODQgMTUuNzUgMTIuNTYyMyAxNS43NSA5Ljc1IDE1Ljc1SDguMjVDNS40Mzc3IDE1Ljc1IDQuMDMxNTUgMTUuNzUgMy4wNDU4MSAxNS4wMzM4QzIuNzI3NDUgMTQuODAyNSAyLjQ0NzQ4IDE0LjUyMjUgMi4yMTYxOSAxNC4yMDQyQzEuNSAxMy4yMTg0IDEuNSAxMS44MTIzIDEuNSA5WiIgc3Ryb2tlPSIjMEEwQTBBIiBzdHJva2Utd2lkdGg9IjEuNSIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPgo8cGF0aCBkPSJNNy4xMjUgMi42MjVMNy4xMjUgMTUuMzc1IiBzdHJva2U9IiMwQTBBMEEiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+CjxwYXRoIGQ9Ik0zLjc1IDUuMjVDMy43NSA1LjI1IDQuNDM1NjYgNS4yNSA0Ljg3NSA1LjI1IiBzdHJva2U9IiMwQTBBMEEiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPHBhdGggZD0iTTMuNzUgOC4yNUg0Ljg3NSIgc3Ryb2tlPSIjMEEwQTBBIiBzdHJva2Utd2lkdGg9IjEuNSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+CjxwYXRoIGQ9Ik0xMi43NSA3LjVMMTEuODMwMSA4LjI5Mjg5QzExLjQ0MzQgOC42MjYyMyAxMS4yNSA4Ljc5Mjg5IDExLjI1IDlDMTEuMjUgOS4yMDcxMSAxMS40NDM0IDkuMzczNzcgMTEuODMwMSA5LjcwNzExTDEyLjc1IDEwLjUiIHN0cm9rZT0iIzBBMEEwQSIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPgo8L3N2Zz4K"
|
||||
alt="折叠"
|
||||
class="group-data-[collapsible=icon]:m-auto"
|
||||
@click="sidebarOpen = !sidebarOpen"
|
||||
>
|
||||
|
||||
<h3 class="font-medium text-xs group-data-[collapsible=icon]:hidden">
|
||||
{{ $t('sidebar.session') }}
|
||||
</h3>
|
||||
</section>
|
||||
</SidebarHeader>
|
||||
<Separator />
|
||||
<section
|
||||
class="not-group-data-[collapsible=icon]:hidden flex justify-center"
|
||||
style="writing-mode:sideways-rl"
|
||||
>
|
||||
<span class="flex-none w-[1em] mr-2.5 mt-4 text-muted-foreground text-xs">
|
||||
{{ $t('sidebar.session') }}
|
||||
</span>
|
||||
</section>
|
||||
<SidebarContent>
|
||||
<SidebarGroup class="group-data-[collapsible=icon]:invisible">
|
||||
<SidebarGroupContent>
|
||||
<SidebarMenu class="my-4">
|
||||
<InputGroup>
|
||||
<InputGroupInput placeholder="Search..." />
|
||||
<InputGroupAddon>
|
||||
<FontAwesomeIcon :icon="['fas', 'magnifying-glass']" />
|
||||
</InputGroupAddon>
|
||||
</InputGroup>
|
||||
</SidebarMenu>
|
||||
<h4 class="text-xs uppercase text-muted-foreground tracking-wide mb-2">
|
||||
我的SESSION
|
||||
</h4>
|
||||
<SidebarMenu ref="session-item">
|
||||
<SidebarMenuItem
|
||||
v-for="bot in bots"
|
||||
:key="bot.id"
|
||||
class="relative hover:[&_svg]:visible"
|
||||
>
|
||||
<!-- <SidebarMenuButton
|
||||
tooltip="afwef"
|
||||
class="py-5 text-muted-foreground relative before:absolute before:w-0.5! before:top-2 before:bottom-2 data-[active=true]:before:bg-[#8B56E3] hover:before:bg-[#8B56E3] before:left-0.5!"
|
||||
>
|
||||
<FontAwesomeIcon :icon="['fas', 'grip-vertical']" />
|
||||
<span>fwefwef</span>
|
||||
</SidebarMenuButton> -->
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'grip-vertical']"
|
||||
class="can-dragable cursor-pointer absolute top-0 bottom-0 m-auto w-1.5! left-1! invisible z-100 text-[#C7C7C7]!"
|
||||
/>
|
||||
|
||||
<BotItem :bot="bot" />
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
|
||||
<SidebarMenu>
|
||||
<div
|
||||
v-if="isLoading"
|
||||
class="flex justify-center py-4"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'spinner']"
|
||||
class="size-4 animate-spin text-muted-foreground"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="!isLoading && bots.length === 0"
|
||||
class="px-3 py-6 text-center text-xs text-muted-foreground"
|
||||
>
|
||||
{{ $t('bots.emptyTitle') }}
|
||||
</div>
|
||||
</SidebarMenu>
|
||||
</SidebarGroupContent>
|
||||
</SidebarGroup>
|
||||
</SidebarContent>
|
||||
<SidebarFooter class="group-data-[collapsible=icon]:invisible">
|
||||
<Button class="mb-4 justify-start gap-4">
|
||||
<FontAwesomeIcon :icon="['fas', 'plus']" />
|
||||
Session
|
||||
</Button>
|
||||
</SidebarFooter>
|
||||
</Sidebar>
|
||||
</SidebarInset>
|
||||
</SidebarProvider>
|
||||
</Teleport>
|
||||
<section class="hidden-clip-section" />
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, useTemplateRef, watch,ref } from 'vue'
|
||||
import { useQuery } from '@pinia/colada'
|
||||
import { getBotsQuery } from '@memohai/sdk/colada'
|
||||
import type { BotsBot } from '@memohai/sdk'
|
||||
import Sortable from 'sortablejs'
|
||||
import {
|
||||
SidebarMenu,
|
||||
SidebarMenuItem,
|
||||
SidebarHeader,
|
||||
SidebarProvider,
|
||||
SidebarContent,
|
||||
Sidebar,
|
||||
SidebarInset,
|
||||
Separator,
|
||||
SidebarFooter,
|
||||
Button,
|
||||
InputGroup,
|
||||
InputGroupInput,
|
||||
InputGroupAddon,
|
||||
SidebarGroup,
|
||||
SidebarGroupContent
|
||||
} from '@memohai/ui'
|
||||
import BotItem from './bot-item.vue'
|
||||
import useControlVisibleStatus from '@/utils/useControlVisibleStatus'
|
||||
const sidebarOpen = ref(true)
|
||||
|
||||
|
||||
const sessionItem = useTemplateRef('session-item')
|
||||
|
||||
watch(sessionItem, () => {
|
||||
const el = sessionItem.value?.$el
|
||||
if (sessionItem.value?.$el) {
|
||||
new Sortable(el, {
|
||||
animation: 150,
|
||||
handle: '.can-dragable'
|
||||
})
|
||||
}
|
||||
}, {
|
||||
immediate: true
|
||||
})
|
||||
|
||||
console.log(Sortable)
|
||||
|
||||
const { data: botData, isLoading } = useQuery(getBotsQuery())
|
||||
const bots = computed<BotsBot[]>(() => botData.value?.items?.concat({
|
||||
'id': '991cd528-0c10-41a0-93e6-a6a7006a433cd',
|
||||
'owner_user_id': '9b7390f6-a336-4c76-bf58-df616942c9a6',
|
||||
'type': 'personal',
|
||||
'display_name': 'feafew',
|
||||
'is_active': true,
|
||||
'allow_guest': false,
|
||||
'status': 'loading',
|
||||
'check_state': 'ok',
|
||||
'check_issue_count': 0,
|
||||
'created_at': '2026-03-23T10:14:13.269928+08:00',
|
||||
'updated_at': '2026-03-23T10:14:13.435601+08:00'
|
||||
} as BotsBot, {
|
||||
'id': '991cd528-0c10-41a0-93e6-a6a754006ac3cd',
|
||||
'owner_user_id': '9b7390f6-a336-4c76-bf58-df616942c9a6',
|
||||
'type': 'personal',
|
||||
'display_name': 'feafew',
|
||||
'is_active': true,
|
||||
'allow_guest': false,
|
||||
'status': 'error',
|
||||
'check_state': 'ok',
|
||||
'check_issue_count': 0,
|
||||
'created_at': '2026-03-23T10:14:13.269928+08:00',
|
||||
'updated_at': '2026-03-23T10:14:13.435601+08:00'
|
||||
} as BotsBot, {
|
||||
'id': '991cd528-0c10-41a0fewf-93e6-a6a754006ac3cd',
|
||||
'owner_user_id': '9b7390f6-a336-4c76-bf58-df616942c9a6',
|
||||
'type': 'personal',
|
||||
'display_name': 'feafew',
|
||||
'is_active': true,
|
||||
'allow_guest': false,
|
||||
'status': 'no-setting',
|
||||
'check_state': 'ok',
|
||||
'check_issue_count': 0,
|
||||
'created_at': '2026-03-23T10:14:13.269928+08:00',
|
||||
'updated_at': '2026-03-23T10:14:13.435601+08:00'
|
||||
} as BotsBot) ?? [])
|
||||
|
||||
|
||||
const mountNode = useControlVisibleStatus()
|
||||
</script>
|
||||
@@ -52,8 +52,7 @@
|
||||
v-if="loadingOlder"
|
||||
class="flex justify-center py-2"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'spinner']"
|
||||
<LoaderCircle
|
||||
class="size-3.5 animate-spin text-muted-foreground"
|
||||
/>
|
||||
</div>
|
||||
@@ -101,8 +100,8 @@
|
||||
:key="i"
|
||||
class="relative group flex items-center gap-1.5 px-2 py-1 rounded-md border bg-muted/40 text-xs"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', file.type.startsWith('image/') ? 'image' : 'file']"
|
||||
<component
|
||||
:is="file.type.startsWith('image/') ? ImageIcon : FileIcon"
|
||||
class="size-3 text-muted-foreground"
|
||||
/>
|
||||
<span class="truncate max-w-30">{{ file.name }}</span>
|
||||
@@ -112,8 +111,7 @@
|
||||
:aria-label="`${$t('common.delete')}: ${file.name}`"
|
||||
@click="pendingFiles.splice(i, 1)"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'xmark']"
|
||||
<X
|
||||
class="size-3"
|
||||
/>
|
||||
</button>
|
||||
@@ -144,8 +142,7 @@
|
||||
aria-label="Attach files"
|
||||
@click="fileInput?.click()"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'paperclip']"
|
||||
<Paperclip
|
||||
class="size-3.5"
|
||||
/>
|
||||
</Button>
|
||||
@@ -157,8 +154,7 @@
|
||||
:aria-label="$t('chat.files')"
|
||||
@click="fileManagerOpen = true"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'folder-open']"
|
||||
<FolderOpen
|
||||
class="size-3.5"
|
||||
/>
|
||||
</Button>
|
||||
@@ -171,8 +167,7 @@
|
||||
class="ml-auto bg-[#8B56E3]"
|
||||
@click="handleSend"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'paper-plane']"
|
||||
<Send
|
||||
class="size-2"
|
||||
/>
|
||||
{{ $t('chat.send') }}
|
||||
@@ -186,8 +181,7 @@
|
||||
aria-label="Stop generating response"
|
||||
@click="chatStore.abort()"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'spinner']"
|
||||
<LoaderCircle
|
||||
class="size-3.5 animate-spin"
|
||||
/>
|
||||
</Button>
|
||||
@@ -225,6 +219,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, nextTick, onMounted, provide, useTemplateRef, watchEffect} from 'vue'
|
||||
import { LoaderCircle, Image as ImageIcon, File as FileIcon, X, Paperclip, FolderOpen, Send } from 'lucide-vue-next'
|
||||
import { ScrollArea, Button, InputGroup, InputGroupAddon, InputGroupTextarea, Sheet, SheetContent, SheetHeader, SheetTitle, SheetDescription,Separator } from '@memohai/ui'
|
||||
import { useChatStore } from '@/store/chat-list'
|
||||
import { storeToRefs } from 'pinia'
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
>
|
||||
<FontAwesomeIcon :icon="['fas', 'arrow-rotate-right']" />
|
||||
<RefreshCw />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -25,4 +25,5 @@
|
||||
import ChatStatus from '@/components/chat/chat-status/index.vue'
|
||||
import ChatStep from '@/components/chat/chat-step/index.vue'
|
||||
import { Button } from '@memohai/ui'
|
||||
import { RefreshCw } from 'lucide-vue-next'
|
||||
</script>
|
||||
|
||||
@@ -19,8 +19,7 @@
|
||||
aria-label="Close"
|
||||
@click="close"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'xmark']"
|
||||
<X
|
||||
class="size-6"
|
||||
/>
|
||||
</button>
|
||||
@@ -33,8 +32,7 @@
|
||||
aria-label="Previous"
|
||||
@click.stop="prev"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'chevron-left']"
|
||||
<ChevronLeft
|
||||
class="size-6"
|
||||
/>
|
||||
</button>
|
||||
@@ -47,8 +45,7 @@
|
||||
aria-label="Next"
|
||||
@click.stop="next"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'chevron-right']"
|
||||
<ChevronRight
|
||||
class="size-6"
|
||||
/>
|
||||
</button>
|
||||
@@ -86,6 +83,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, watchEffect, onUnmounted, nextTick, ref } from 'vue'
|
||||
import { X, ChevronLeft, ChevronRight } from 'lucide-vue-next'
|
||||
|
||||
export interface MediaGalleryItem {
|
||||
src: string
|
||||
|
||||
@@ -148,8 +148,7 @@
|
||||
v-if="message.streaming && message.blocks.length === 0"
|
||||
class="flex items-center gap-2 text-xs text-muted-foreground h-6"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'spinner']"
|
||||
<LoaderCircle
|
||||
class="size-3.5 animate-spin"
|
||||
/>
|
||||
{{ $t('chat.thinking') }}
|
||||
@@ -167,6 +166,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { LoaderCircle } from 'lucide-vue-next'
|
||||
import { formatRelativeTime, formatDateTime } from '@/utils/date-time'
|
||||
import { Avatar, AvatarImage, AvatarFallback } from '@memohai/ui'
|
||||
import MarkdownRender, { enableKatex, enableMermaid } from 'markstream-vue'
|
||||
|
||||
@@ -22,8 +22,8 @@
|
||||
v-else
|
||||
class="flex items-center justify-center size-[26px] rounded-full bg-accent border border-border"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="iconDef"
|
||||
<component
|
||||
:is="iconComponent"
|
||||
class="size-2.5"
|
||||
:class="iconClass"
|
||||
/>
|
||||
@@ -62,7 +62,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { computed, type Component } from 'vue'
|
||||
import { HeartPulse, Clock, GitBranch, MessageSquare } from 'lucide-vue-next'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import type { SessionSummary } from '@/composables/api/useChat'
|
||||
import { Avatar, AvatarImage, AvatarFallback } from '@memohai/ui'
|
||||
@@ -86,12 +87,12 @@ const isIMSession = computed(() => {
|
||||
return ct !== '' && !WEB_CHANNELS.has(ct)
|
||||
})
|
||||
|
||||
const iconDef = computed<string[]>(() => {
|
||||
const iconComponent = computed<Component>(() => {
|
||||
switch (props.session.type) {
|
||||
case 'heartbeat': return ['fas', 'heart-pulse']
|
||||
case 'schedule': return ['fas', 'clock']
|
||||
case 'subagent': return ['fas', 'code-branch']
|
||||
default: return ['fas', 'message']
|
||||
case 'heartbeat': return HeartPulse
|
||||
case 'schedule': return Clock
|
||||
case 'subagent': return GitBranch
|
||||
default: return MessageSquare
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -1,194 +0,0 @@
|
||||
<template>
|
||||
<div
|
||||
class="flex flex-col h-full shrink-0 border-l border-border bg-sidebar transition-[width] duration-200"
|
||||
:class="sidebarOpen ? 'w-[240px]' : 'w-10'"
|
||||
>
|
||||
<div class="h-12 flex items-center shrink-0 px-3">
|
||||
<button
|
||||
class="shrink-0"
|
||||
@click="sidebarOpen = !sidebarOpen"
|
||||
>
|
||||
<img
|
||||
src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTgiIGhlaWdodD0iMTgiIHZpZXdCb3g9IjAgMCAxOCAxOCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTE2LjUgOUMxNi41IDYuMTg3NyAxNi41IDQuNzgxNTUgMTUuNzgzOCAzLjc5NTgxQzE1LjU1MjUgMy40Nzc0NSAxNS4yNzI2IDMuMTk3NDggMTQuOTU0MiAyLjk2NjE5QzEzLjk2ODQgMi4yNSAxMi41NjIzIDIuMjUgOS43NSAyLjI1SDguMjVDNS40Mzc3IDIuMjUgNC4wMzE1NSAyLjI1IDMuMDQ1ODEgMi45NjYxOUMyLjcyNzQ1IDMuMTk3NDggMi40NDc0OCAzLjQ3NzQ1IDIuMjE2MTkgMy43OTU4MUMxLjUgNC43ODE1NSAxLjUgNi4xODc3IDEuNSA5QzEuNSAxMS44MTIzIDEuNSAxMy4yMTg0IDIuMjE2MTkgMTQuMjA0MkMyLjQ0NzQ4IDE0LjUyMjUgMi43Mjc0NSAxNC44MDI1IDMuMDQ1ODEgMTUuMDMzOEM0LjAzMTU1IDE1Ljc1IDUuNDM3NyAxNS43NSA4LjI1IDE1Ljc1SDkuNzVDMTIuNTYyMyAxNS43NSAxMy45Njg0IDE1Ljc1IDE0Ljk1NDIgMTUuMDMzOEMxNS4yNzI2IDE0LjgwMjUgMTUuNTUyNSAxNC41MjI1IDE1Ljc4MzggMTQuMjA0MkMxNi41IDEzLjIxODQgMTYuNSAxMS44MTIzIDE2LjUgOVoiIHN0cm9rZT0iIzBBMEEwQSIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPHBhdGggZD0iTTEwLjg3NSAyLjYyNUwxMC44NzUgMTUuMzc1IiBzdHJva2U9IiMwQTBBMEEiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+CjxwYXRoIGQ9Ik0xNC4yNSA1LjI1SDEzLjEyNSIgc3Ryb2tlPSIjMEEwQTBBIiBzdHJva2Utd2lkdGg9IjEuNSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+CjxwYXRoIGQ9Ik0xNC4yNSA4LjI1SDEzLjEyNSIgc3Ryb2tlPSIjMEEwQTBBIiBzdHJva2Utd2lkdGg9IjEuNSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+CjxwYXRoIGQ9Ik02IDcuNUw2LjkxOTkxIDguMjkyODlDNy4zMDY2MyA4LjYyNjIzIDcuNSA4Ljc5Mjg5IDcuNSA5QzcuNSA5LjIwNzExIDcuMzA2NjQgOS4zNzM3NyA2LjkxOTkxIDkuNzA3MTFMNiAxMC41IiBzdHJva2U9IiMwQTBBMEEiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPC9zdmc+Cg=="
|
||||
alt="toggle"
|
||||
class="size-[18px] dark:invert"
|
||||
>
|
||||
</button>
|
||||
<h3
|
||||
v-if="sidebarOpen"
|
||||
class="font-medium text-xs ml-2"
|
||||
>
|
||||
Session 源数据
|
||||
</h3>
|
||||
</div>
|
||||
<Separator />
|
||||
|
||||
<template v-if="sidebarOpen">
|
||||
<ScrollArea class="flex-1">
|
||||
<form
|
||||
class="p-3 **:[label]:text-xs **:[label]:uppercase **:[label]:text-muted-foreground **:[label]:tracking-wide"
|
||||
>
|
||||
<section class="flex flex-col gap-6">
|
||||
<FormField
|
||||
v-slot="{ componentField }"
|
||||
name="bot"
|
||||
>
|
||||
<FormItem>
|
||||
<FormLabel>Bot</FormLabel>
|
||||
<FormControl>
|
||||
<InputGroup>
|
||||
<InputGroupInput v-bind="componentField" />
|
||||
<InputGroupAddon>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'wand-magic-sparkles']"
|
||||
class="size-3.5 text-muted-foreground"
|
||||
/>
|
||||
</InputGroupAddon>
|
||||
</InputGroup>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
|
||||
<FormField
|
||||
v-slot="{ componentField }"
|
||||
name="skills"
|
||||
>
|
||||
<FormItem>
|
||||
<FormLabel>Top Skills</FormLabel>
|
||||
<FormControl>
|
||||
<InputGroup>
|
||||
<InputGroupInput v-bind="componentField" />
|
||||
<InputGroupAddon>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'circle']"
|
||||
class="size-1.5 text-muted-foreground"
|
||||
/>
|
||||
</InputGroupAddon>
|
||||
</InputGroup>
|
||||
<InputGroup>
|
||||
<InputGroupInput v-bind="componentField" />
|
||||
<InputGroupAddon>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'circle']"
|
||||
class="size-1.5 text-muted-foreground"
|
||||
/>
|
||||
</InputGroupAddon>
|
||||
</InputGroup>
|
||||
<InputGroup>
|
||||
<InputGroupInput v-bind="componentField" />
|
||||
<InputGroupAddon>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'circle']"
|
||||
class="size-1.5 text-muted-foreground"
|
||||
/>
|
||||
</InputGroupAddon>
|
||||
</InputGroup>
|
||||
</FormControl>
|
||||
<p class="text-xs text-muted-foreground mt-4">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'sliders']"
|
||||
class="size-3 mr-1"
|
||||
/>
|
||||
管理全部 Skills(5)
|
||||
</p>
|
||||
</FormItem>
|
||||
</FormField>
|
||||
|
||||
<section>
|
||||
<h5 class="text-xs text-muted-foreground mb-2 uppercase tracking-wide">
|
||||
用量统计
|
||||
</h5>
|
||||
<Card class="p-4!">
|
||||
<CardContent class="p-0 flex flex-col gap-3 text-xs text-muted-foreground">
|
||||
<div class="flex justify-between">
|
||||
<span>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'arrow-up']"
|
||||
class="size-2.5 mr-1"
|
||||
/>输入Token
|
||||
</span>
|
||||
<data>4,281</data>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'arrow-down']"
|
||||
class="size-2.5 mr-1"
|
||||
/>输出Token
|
||||
</span>
|
||||
<data>2,104</data>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'coins']"
|
||||
class="size-2.5 mr-1"
|
||||
/>总成本
|
||||
</span>
|
||||
<data>$12.00</data>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</section>
|
||||
|
||||
<Separator />
|
||||
|
||||
<section class="flex flex-col items-start gap-1">
|
||||
<Button
|
||||
variant="link"
|
||||
class="text-muted-foreground text-xs p-0! h-auto"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'book']"
|
||||
class="size-3 mr-1"
|
||||
/>
|
||||
帮助文档
|
||||
</Button>
|
||||
<Button
|
||||
variant="link"
|
||||
class="text-destructive text-xs p-0! h-auto"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'trash']"
|
||||
class="size-3 mr-1"
|
||||
/>
|
||||
删除 Session
|
||||
</Button>
|
||||
</section>
|
||||
</section>
|
||||
</form>
|
||||
</ScrollArea>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<div
|
||||
class="flex-1 flex justify-center pt-4"
|
||||
style="writing-mode: sideways-rl"
|
||||
>
|
||||
<span class="text-xs text-muted-foreground">
|
||||
Session 源数据
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import {
|
||||
ScrollArea,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
InputGroup,
|
||||
InputGroupAddon,
|
||||
InputGroupInput,
|
||||
Card,
|
||||
CardContent,
|
||||
Button,
|
||||
} from '@memohai/ui'
|
||||
const sidebarOpen = ref(true)
|
||||
</script>
|
||||
@@ -36,8 +36,7 @@
|
||||
<div class="p-2 shrink-0">
|
||||
<InputGroup class="h-[30px]">
|
||||
<InputGroupAddon class="pl-2.5">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'magnifying-glass']"
|
||||
<Search
|
||||
class="size-[11px] text-muted-foreground"
|
||||
/>
|
||||
</InputGroupAddon>
|
||||
@@ -56,8 +55,7 @@
|
||||
:disabled="!currentBotId"
|
||||
@click="handleNewSession"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'plus']"
|
||||
<Plus
|
||||
class="size-3"
|
||||
/>
|
||||
{{ t('chat.newSession') }}
|
||||
@@ -68,15 +66,13 @@
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger as-child>
|
||||
<button class="flex items-center gap-1">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'browser']"
|
||||
<Globe
|
||||
class="size-2.5 text-muted-foreground"
|
||||
/>
|
||||
<span class="text-[10px] font-medium text-muted-foreground uppercase tracking-[0.7px]">
|
||||
{{ t('chat.sessionSourcePrefix') }}{{ filterLabel }}
|
||||
</span>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'chevron-down']"
|
||||
<ChevronDown
|
||||
class="size-2.5 text-muted-foreground"
|
||||
/>
|
||||
</button>
|
||||
@@ -87,9 +83,8 @@
|
||||
:key="opt.value ?? 'all'"
|
||||
@click="filterType = opt.value"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
<Check
|
||||
v-if="filterType === opt.value"
|
||||
:icon="['fas', 'check']"
|
||||
class="size-3 mr-2"
|
||||
/>
|
||||
<span :class="filterType !== opt.value ? 'ml-5' : ''">
|
||||
@@ -124,8 +119,7 @@
|
||||
v-if="loadingChats"
|
||||
class="flex justify-center py-4"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'spinner']"
|
||||
<LoaderCircle
|
||||
class="size-4 animate-spin text-muted-foreground"
|
||||
/>
|
||||
</div>
|
||||
@@ -137,6 +131,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { Search, Plus, Globe, ChevronDown, Check, LoaderCircle } from 'lucide-vue-next'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
<template>
|
||||
<Collapsible v-model:open="isOpen">
|
||||
<CollapsibleTrigger class="flex items-center gap-2 text-xs text-muted-foreground hover:text-foreground transition-colors cursor-pointer group">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'chevron-right']"
|
||||
<ChevronRight
|
||||
class="size-3 transition-transform"
|
||||
:class="{ 'rotate-90': isOpen }"
|
||||
/>
|
||||
<span class="flex items-center gap-1.5">
|
||||
<template v-if="streaming">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'spinner']"
|
||||
class="size-3 animate-spin"
|
||||
/>
|
||||
<LoaderCircle class="size-3 animate-spin" />
|
||||
{{ $t('chat.thinkingInProgress') }}
|
||||
</template>
|
||||
<template v-else>
|
||||
@@ -32,6 +28,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { ChevronRight, LoaderCircle } from 'lucide-vue-next'
|
||||
import { Collapsible, CollapsibleTrigger, CollapsibleContent } from '@memohai/ui'
|
||||
import type { ThinkingBlock } from '@/store/chat-list'
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<template>
|
||||
<div class="rounded-lg border bg-muted/30 text-xs overflow-hidden">
|
||||
<div class="flex items-center gap-2 px-3 py-2 bg-muted/50">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', block.done ? 'check' : 'spinner']"
|
||||
class="size-3"
|
||||
:class="block.done ? 'text-green-600 dark:text-green-400' : 'animate-spin text-muted-foreground'"
|
||||
<Check
|
||||
v-if="block.done"
|
||||
class="size-3 text-green-600 dark:text-green-400"
|
||||
/>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'window-maximize']"
|
||||
class="size-3 text-muted-foreground"
|
||||
<LoaderCircle
|
||||
v-else
|
||||
class="size-3 animate-spin text-muted-foreground"
|
||||
/>
|
||||
<AppWindow class="size-3 text-muted-foreground" />
|
||||
<span class="font-mono font-medium text-xs text-muted-foreground">
|
||||
{{ actionLabel }}
|
||||
</span>
|
||||
@@ -41,8 +41,7 @@
|
||||
v-model:open="resultOpen"
|
||||
>
|
||||
<CollapsibleTrigger class="flex items-center gap-1.5 px-3 py-1.5 text-xs text-muted-foreground hover:text-foreground cursor-pointer w-full">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'chevron-right']"
|
||||
<ChevronRight
|
||||
class="size-2.5 transition-transform"
|
||||
:class="{ 'rotate-90': resultOpen }"
|
||||
/>
|
||||
@@ -57,6 +56,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { Check, LoaderCircle, AppWindow, ChevronRight } from 'lucide-vue-next'
|
||||
import { Badge, Collapsible, CollapsibleTrigger, CollapsibleContent } from '@memohai/ui'
|
||||
import type { ToolCallBlock } from '@/store/chat-list'
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<template>
|
||||
<div class="rounded-lg border bg-muted/30 text-xs overflow-hidden">
|
||||
<div class="flex items-center gap-2 px-3 py-2 bg-muted/50">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', block.done ? 'check' : 'spinner']"
|
||||
class="size-3"
|
||||
:class="block.done ? 'text-green-600 dark:text-green-400' : 'animate-spin text-muted-foreground'"
|
||||
<Check
|
||||
v-if="block.done"
|
||||
class="size-3 text-green-600 dark:text-green-400"
|
||||
/>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'address-book']"
|
||||
class="size-3 text-muted-foreground"
|
||||
<LoaderCircle
|
||||
v-else
|
||||
class="size-3 animate-spin text-muted-foreground"
|
||||
/>
|
||||
<ContactRound class="size-3 text-muted-foreground" />
|
||||
<span class="font-mono font-medium text-xs text-muted-foreground">
|
||||
get_contacts
|
||||
</span>
|
||||
@@ -41,8 +41,7 @@
|
||||
v-model:open="contactsOpen"
|
||||
>
|
||||
<CollapsibleTrigger class="flex items-center gap-1.5 px-3 py-1.5 text-xs text-muted-foreground hover:text-foreground cursor-pointer w-full">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'chevron-right']"
|
||||
<ChevronRight
|
||||
class="size-2.5 transition-transform"
|
||||
:class="{ 'rotate-90': contactsOpen }"
|
||||
/>
|
||||
@@ -74,6 +73,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { Check, LoaderCircle, ContactRound, ChevronRight } from 'lucide-vue-next'
|
||||
import { Badge, Collapsible, CollapsibleTrigger, CollapsibleContent } from '@memohai/ui'
|
||||
import type { ToolCallBlock } from '@/store/chat-list'
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<template>
|
||||
<div class="rounded-lg border bg-muted/30 text-xs overflow-hidden">
|
||||
<div class="flex items-center gap-2 px-3 py-2 bg-muted/50">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', block.done ? 'check' : 'spinner']"
|
||||
class="size-3"
|
||||
:class="block.done ? 'text-green-600 dark:text-green-400' : 'animate-spin text-muted-foreground'"
|
||||
<Check
|
||||
v-if="block.done"
|
||||
class="size-3 text-green-600 dark:text-green-400"
|
||||
/>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'pen-to-square']"
|
||||
class="size-3 text-muted-foreground"
|
||||
<LoaderCircle
|
||||
v-else
|
||||
class="size-3 animate-spin text-muted-foreground"
|
||||
/>
|
||||
<SquarePen class="size-3 text-muted-foreground" />
|
||||
<button
|
||||
class="font-mono text-xs truncate hover:underline text-foreground cursor-pointer"
|
||||
:title="filePath"
|
||||
@@ -38,8 +38,7 @@
|
||||
v-model:open="diffOpen"
|
||||
>
|
||||
<CollapsibleTrigger class="flex items-center gap-1.5 px-3 py-1.5 text-xs text-muted-foreground hover:text-foreground cursor-pointer w-full">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'chevron-right']"
|
||||
<ChevronRight
|
||||
class="size-2.5 transition-transform"
|
||||
:class="{ 'rotate-90': diffOpen }"
|
||||
/>
|
||||
@@ -50,10 +49,7 @@
|
||||
v-if="shiki.loading.value"
|
||||
class="px-3 pb-2 text-xs text-muted-foreground"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'spinner']"
|
||||
class="size-3 animate-spin"
|
||||
/>
|
||||
<LoaderCircle class="size-3 animate-spin" />
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
@@ -67,6 +63,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, inject, watch } from 'vue'
|
||||
import { Check, LoaderCircle, SquarePen, ChevronRight } from 'lucide-vue-next'
|
||||
import { Badge, Collapsible, CollapsibleTrigger, CollapsibleContent } from '@memohai/ui'
|
||||
import type { ToolCallBlock } from '@/store/chat-list'
|
||||
import { openInFileManagerKey } from '../composables/useFileManagerProvider'
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<template>
|
||||
<div class="rounded-lg border bg-muted/30 text-xs overflow-hidden">
|
||||
<div class="flex items-center gap-2 px-3 py-2 bg-muted/50">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', block.done ? 'check' : 'spinner']"
|
||||
class="size-3"
|
||||
:class="block.done ? 'text-green-600 dark:text-green-400' : 'animate-spin text-muted-foreground'"
|
||||
<Check
|
||||
v-if="block.done"
|
||||
class="size-3 text-green-600 dark:text-green-400"
|
||||
/>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'envelope']"
|
||||
class="size-3 text-muted-foreground"
|
||||
<LoaderCircle
|
||||
v-else
|
||||
class="size-3 animate-spin text-muted-foreground"
|
||||
/>
|
||||
<Mail class="size-3 text-muted-foreground" />
|
||||
|
||||
<!-- send_email -->
|
||||
<template v-if="block.toolName === 'send_email'">
|
||||
@@ -85,8 +85,7 @@
|
||||
v-model:open="emailsOpen"
|
||||
>
|
||||
<CollapsibleTrigger class="flex items-center gap-1.5 px-3 py-1.5 text-xs text-muted-foreground hover:text-foreground cursor-pointer w-full">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'chevron-right']"
|
||||
<ChevronRight
|
||||
class="size-2.5 transition-transform"
|
||||
:class="{ 'rotate-90': emailsOpen }"
|
||||
/>
|
||||
@@ -115,8 +114,7 @@
|
||||
v-model:open="bodyOpen"
|
||||
>
|
||||
<CollapsibleTrigger class="flex items-center gap-1.5 px-3 py-1.5 text-xs text-muted-foreground hover:text-foreground cursor-pointer w-full">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'chevron-right']"
|
||||
<ChevronRight
|
||||
class="size-2.5 transition-transform"
|
||||
:class="{ 'rotate-90': bodyOpen }"
|
||||
/>
|
||||
@@ -131,6 +129,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { Check, LoaderCircle, Mail, ChevronRight } from 'lucide-vue-next'
|
||||
import { Badge, Collapsible, CollapsibleTrigger, CollapsibleContent } from '@memohai/ui'
|
||||
import type { ToolCallBlock } from '@/store/chat-list'
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<template>
|
||||
<div class="rounded-lg border bg-muted/30 text-xs overflow-hidden">
|
||||
<div class="flex items-center gap-2 px-3 py-2 bg-muted/50">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', block.done ? 'check' : 'spinner']"
|
||||
class="size-3"
|
||||
:class="block.done ? 'text-green-600 dark:text-green-400' : 'animate-spin text-muted-foreground'"
|
||||
<Check
|
||||
v-if="block.done"
|
||||
class="size-3 text-green-600 dark:text-green-400"
|
||||
/>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'terminal']"
|
||||
class="size-3 text-muted-foreground"
|
||||
<LoaderCircle
|
||||
v-else
|
||||
class="size-3 animate-spin text-muted-foreground"
|
||||
/>
|
||||
<Terminal class="size-3 text-muted-foreground" />
|
||||
<span
|
||||
class="font-mono text-xs truncate text-foreground"
|
||||
:title="command"
|
||||
@@ -51,8 +51,7 @@
|
||||
v-model:open="outputOpen"
|
||||
>
|
||||
<CollapsibleTrigger class="flex items-center gap-1.5 px-3 py-1.5 text-xs text-muted-foreground hover:text-foreground cursor-pointer w-full">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'chevron-right']"
|
||||
<ChevronRight
|
||||
class="size-2.5 transition-transform"
|
||||
:class="{ 'rotate-90': outputOpen }"
|
||||
/>
|
||||
@@ -78,6 +77,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { Check, LoaderCircle, Terminal, ChevronRight } from 'lucide-vue-next'
|
||||
import { Badge, Collapsible, CollapsibleTrigger, CollapsibleContent } from '@memohai/ui'
|
||||
import type { ToolCallBlock } from '@/store/chat-list'
|
||||
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
<template>
|
||||
<div class="rounded-lg border bg-muted/30 text-xs overflow-hidden">
|
||||
<div class="flex items-center gap-2 px-3 py-2 bg-muted/50">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', block.done ? 'check' : 'spinner']"
|
||||
class="size-3"
|
||||
:class="block.done ? 'text-green-600 dark:text-green-400' : 'animate-spin text-muted-foreground'"
|
||||
<Check
|
||||
v-if="block.done"
|
||||
class="size-3 text-green-600 dark:text-green-400"
|
||||
/>
|
||||
<LoaderCircle
|
||||
v-else
|
||||
class="size-3 animate-spin text-muted-foreground"
|
||||
/>
|
||||
<span class="font-mono font-medium text-xs">
|
||||
{{ block.toolName }}
|
||||
@@ -30,8 +33,7 @@
|
||||
v-model:open="inputOpen"
|
||||
>
|
||||
<CollapsibleTrigger class="flex items-center gap-1.5 px-3 py-1.5 text-xs text-muted-foreground hover:text-foreground cursor-pointer w-full">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'chevron-right']"
|
||||
<ChevronRight
|
||||
class="size-2.5 transition-transform"
|
||||
:class="{ 'rotate-90': inputOpen }"
|
||||
/>
|
||||
@@ -47,8 +49,7 @@
|
||||
v-model:open="resultOpen"
|
||||
>
|
||||
<CollapsibleTrigger class="flex items-center gap-1.5 px-3 py-1.5 text-xs text-muted-foreground hover:text-foreground cursor-pointer w-full border-t border-muted">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'chevron-right']"
|
||||
<ChevronRight
|
||||
class="size-2.5 transition-transform"
|
||||
:class="{ 'rotate-90': resultOpen }"
|
||||
/>
|
||||
@@ -63,6 +64,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { Check, LoaderCircle, ChevronRight } from 'lucide-vue-next'
|
||||
import { Badge, Collapsible, CollapsibleTrigger, CollapsibleContent } from '@memohai/ui'
|
||||
import type { ToolCallBlock } from '@/store/chat-list'
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<template>
|
||||
<div class="rounded-lg border bg-muted/30 text-xs overflow-hidden">
|
||||
<div class="flex items-center gap-2 px-3 py-2 bg-muted/50">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', block.done ? 'check' : 'spinner']"
|
||||
class="size-3"
|
||||
:class="block.done ? 'text-green-600 dark:text-green-400' : 'animate-spin text-muted-foreground'"
|
||||
<Check
|
||||
v-if="block.done"
|
||||
class="size-3 text-green-600 dark:text-green-400"
|
||||
/>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'folder']"
|
||||
class="size-3 text-muted-foreground"
|
||||
<LoaderCircle
|
||||
v-else
|
||||
class="size-3 animate-spin text-muted-foreground"
|
||||
/>
|
||||
<Folder class="size-3 text-muted-foreground" />
|
||||
<button
|
||||
class="font-mono text-xs truncate hover:underline text-foreground cursor-pointer"
|
||||
:title="dirPath"
|
||||
@@ -37,6 +37,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, inject } from 'vue'
|
||||
import { Check, LoaderCircle, Folder } from 'lucide-vue-next'
|
||||
import { Badge } from '@memohai/ui'
|
||||
import type { ToolCallBlock } from '@/store/chat-list'
|
||||
import { openInFileManagerKey } from '../composables/useFileManagerProvider'
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<template>
|
||||
<div class="rounded-lg border bg-muted/30 text-xs overflow-hidden">
|
||||
<div class="flex items-center gap-2 px-3 py-2 bg-muted/50">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', block.done ? 'check' : 'spinner']"
|
||||
class="size-3"
|
||||
:class="block.done ? 'text-green-600 dark:text-green-400' : 'animate-spin text-muted-foreground'"
|
||||
<Check
|
||||
v-if="block.done"
|
||||
class="size-3 text-green-600 dark:text-green-400"
|
||||
/>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'brain']"
|
||||
class="size-3 text-muted-foreground"
|
||||
<LoaderCircle
|
||||
v-else
|
||||
class="size-3 animate-spin text-muted-foreground"
|
||||
/>
|
||||
<Brain class="size-3 text-muted-foreground" />
|
||||
<span class="text-xs truncate text-foreground">
|
||||
{{ query }}
|
||||
</span>
|
||||
@@ -41,8 +41,7 @@
|
||||
v-model:open="resultsOpen"
|
||||
>
|
||||
<CollapsibleTrigger class="flex items-center gap-1.5 px-3 py-1.5 text-xs text-muted-foreground hover:text-foreground cursor-pointer w-full">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'chevron-right']"
|
||||
<ChevronRight
|
||||
class="size-2.5 transition-transform"
|
||||
:class="{ 'rotate-90': resultsOpen }"
|
||||
/>
|
||||
@@ -71,6 +70,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { Check, LoaderCircle, Brain, ChevronRight } from 'lucide-vue-next'
|
||||
import { Badge, Collapsible, CollapsibleTrigger, CollapsibleContent } from '@memohai/ui'
|
||||
import type { ToolCallBlock } from '@/store/chat-list'
|
||||
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
<template>
|
||||
<div class="rounded-lg border bg-muted/30 text-xs overflow-hidden">
|
||||
<div class="flex items-center gap-2 px-3 py-2 bg-muted/50">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', block.done ? 'check' : 'spinner']"
|
||||
class="size-3"
|
||||
:class="block.done ? 'text-green-600 dark:text-green-400' : 'animate-spin text-muted-foreground'"
|
||||
<Check
|
||||
v-if="block.done"
|
||||
class="size-3 text-green-600 dark:text-green-400"
|
||||
/>
|
||||
<LoaderCircle
|
||||
v-else
|
||||
class="size-3 animate-spin text-muted-foreground"
|
||||
/>
|
||||
|
||||
<!-- send -->
|
||||
<template v-if="block.toolName === 'send'">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'paper-plane']"
|
||||
class="size-3 text-muted-foreground"
|
||||
/>
|
||||
<Send class="size-3 text-muted-foreground" />
|
||||
<span
|
||||
v-if="platform || target"
|
||||
class="text-xs truncate text-foreground"
|
||||
@@ -36,10 +36,7 @@
|
||||
|
||||
<!-- react -->
|
||||
<template v-else>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'face-smile']"
|
||||
class="size-3 text-muted-foreground"
|
||||
/>
|
||||
<Smile class="size-3 text-muted-foreground" />
|
||||
<span
|
||||
v-if="emoji"
|
||||
class="text-xs"
|
||||
@@ -74,6 +71,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { Check, LoaderCircle, Send, Smile } from 'lucide-vue-next'
|
||||
import { Badge } from '@memohai/ui'
|
||||
import type { ToolCallBlock } from '@/store/chat-list'
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<template>
|
||||
<div class="rounded-lg border bg-muted/30 text-xs overflow-hidden">
|
||||
<div class="flex items-center gap-2 px-3 py-2 bg-muted/50">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', block.done ? 'check' : 'spinner']"
|
||||
class="size-3"
|
||||
:class="block.done ? 'text-green-600 dark:text-green-400' : 'animate-spin text-muted-foreground'"
|
||||
<Check
|
||||
v-if="block.done"
|
||||
class="size-3 text-green-600 dark:text-green-400"
|
||||
/>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'file-lines']"
|
||||
class="size-3 text-muted-foreground"
|
||||
<LoaderCircle
|
||||
v-else
|
||||
class="size-3 animate-spin text-muted-foreground"
|
||||
/>
|
||||
<FileText class="size-3 text-muted-foreground" />
|
||||
<button
|
||||
class="font-mono text-xs truncate hover:underline text-foreground cursor-pointer"
|
||||
:title="filePath"
|
||||
@@ -37,6 +37,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, inject } from 'vue'
|
||||
import { Check, LoaderCircle, FileText } from 'lucide-vue-next'
|
||||
import { Badge } from '@memohai/ui'
|
||||
import type { ToolCallBlock } from '@/store/chat-list'
|
||||
import { openInFileManagerKey } from '../composables/useFileManagerProvider'
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<template>
|
||||
<div class="rounded-lg border bg-muted/30 text-xs overflow-hidden">
|
||||
<div class="flex items-center gap-2 px-3 py-2 bg-muted/50">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', block.done ? 'check' : 'spinner']"
|
||||
class="size-3"
|
||||
:class="block.done ? 'text-green-600 dark:text-green-400' : 'animate-spin text-muted-foreground'"
|
||||
<Check
|
||||
v-if="block.done"
|
||||
class="size-3 text-green-600 dark:text-green-400"
|
||||
/>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'clock']"
|
||||
class="size-3 text-muted-foreground"
|
||||
<LoaderCircle
|
||||
v-else
|
||||
class="size-3 animate-spin text-muted-foreground"
|
||||
/>
|
||||
<Clock class="size-3 text-muted-foreground" />
|
||||
<span class="font-mono font-medium text-xs text-muted-foreground">
|
||||
{{ block.toolName }}
|
||||
</span>
|
||||
@@ -47,6 +47,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { Check, LoaderCircle, Clock } from 'lucide-vue-next'
|
||||
import { Badge } from '@memohai/ui'
|
||||
import type { ToolCallBlock } from '@/store/chat-list'
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<template>
|
||||
<div class="rounded-lg border bg-muted/30 text-xs overflow-hidden">
|
||||
<div class="flex items-center gap-2 px-3 py-2 bg-muted/50">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', block.done ? 'check' : 'spinner']"
|
||||
class="size-3"
|
||||
:class="block.done ? 'text-green-600 dark:text-green-400' : 'animate-spin text-muted-foreground'"
|
||||
<Check
|
||||
v-if="block.done"
|
||||
class="size-3 text-green-600 dark:text-green-400"
|
||||
/>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'wand-magic-sparkles']"
|
||||
class="size-3 text-muted-foreground"
|
||||
<LoaderCircle
|
||||
v-else
|
||||
class="size-3 animate-spin text-muted-foreground"
|
||||
/>
|
||||
<Sparkles class="size-3 text-muted-foreground" />
|
||||
<span
|
||||
v-if="skillName"
|
||||
class="text-xs truncate text-foreground"
|
||||
@@ -42,6 +42,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { Check, LoaderCircle, Sparkles } from 'lucide-vue-next'
|
||||
import { Badge } from '@memohai/ui'
|
||||
import type { ToolCallBlock } from '@/store/chat-list'
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<template>
|
||||
<div class="rounded-lg border bg-muted/30 text-xs overflow-hidden">
|
||||
<div class="flex items-center gap-2 px-3 py-2 bg-muted/50">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', block.done ? 'check' : 'spinner']"
|
||||
class="size-3"
|
||||
:class="block.done ? 'text-green-600 dark:text-green-400' : 'animate-spin text-muted-foreground'"
|
||||
<Check
|
||||
v-if="block.done"
|
||||
class="size-3 text-green-600 dark:text-green-400"
|
||||
/>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'code-branch']"
|
||||
class="size-3 text-violet-400"
|
||||
<LoaderCircle
|
||||
v-else
|
||||
class="size-3 animate-spin text-muted-foreground"
|
||||
/>
|
||||
<GitBranch class="size-3 text-violet-400" />
|
||||
<span class="font-mono font-medium text-xs text-foreground">
|
||||
spawn
|
||||
</span>
|
||||
@@ -58,8 +58,7 @@
|
||||
v-model:open="resultOpen"
|
||||
>
|
||||
<CollapsibleTrigger class="flex items-center gap-1.5 px-3 py-1.5 text-xs text-muted-foreground hover:text-foreground cursor-pointer w-full">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'chevron-right']"
|
||||
<ChevronRight
|
||||
class="size-2.5 transition-transform"
|
||||
:class="{ 'rotate-90': resultOpen }"
|
||||
/>
|
||||
@@ -73,10 +72,13 @@
|
||||
class="text-xs"
|
||||
>
|
||||
<div class="flex items-center gap-1.5 mb-0.5">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', result.success ? 'circle-check' : 'circle-xmark']"
|
||||
class="size-2.5"
|
||||
:class="result.success ? 'text-green-500' : 'text-red-500'"
|
||||
<CircleCheck
|
||||
v-if="result.success"
|
||||
class="size-2.5 text-green-500"
|
||||
/>
|
||||
<CircleX
|
||||
v-else
|
||||
class="size-2.5 text-red-500"
|
||||
/>
|
||||
<span class="font-mono text-foreground">#{{ idx + 1 }}</span>
|
||||
<span
|
||||
@@ -106,6 +108,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { Check, LoaderCircle, GitBranch, ChevronRight, CircleCheck, CircleX } from 'lucide-vue-next'
|
||||
import { Badge, Collapsible, CollapsibleTrigger, CollapsibleContent } from '@memohai/ui'
|
||||
import type { ToolCallBlock } from '@/store/chat-list'
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<template>
|
||||
<div class="rounded-lg border bg-muted/30 text-xs overflow-hidden">
|
||||
<div class="flex items-center gap-2 px-3 py-2 bg-muted/50">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', block.done ? 'check' : 'spinner']"
|
||||
class="size-3"
|
||||
:class="block.done ? 'text-green-600 dark:text-green-400' : 'animate-spin text-muted-foreground'"
|
||||
<Check
|
||||
v-if="block.done"
|
||||
class="size-3 text-green-600 dark:text-green-400"
|
||||
/>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'globe']"
|
||||
class="size-3 text-muted-foreground"
|
||||
<LoaderCircle
|
||||
v-else
|
||||
class="size-3 animate-spin text-muted-foreground"
|
||||
/>
|
||||
<Globe class="size-3 text-muted-foreground" />
|
||||
<a
|
||||
v-if="url"
|
||||
:href="url"
|
||||
@@ -48,8 +48,7 @@
|
||||
v-model:open="previewOpen"
|
||||
>
|
||||
<CollapsibleTrigger class="flex items-center gap-1.5 px-3 py-1.5 text-xs text-muted-foreground hover:text-foreground cursor-pointer w-full">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'chevron-right']"
|
||||
<ChevronRight
|
||||
class="size-2.5 transition-transform"
|
||||
:class="{ 'rotate-90': previewOpen }"
|
||||
/>
|
||||
@@ -81,6 +80,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { Check, LoaderCircle, Globe, ChevronRight } from 'lucide-vue-next'
|
||||
import { Badge, Collapsible, CollapsibleTrigger, CollapsibleContent } from '@memohai/ui'
|
||||
import type { ToolCallBlock } from '@/store/chat-list'
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<template>
|
||||
<div class="rounded-lg border bg-muted/30 text-xs overflow-hidden">
|
||||
<div class="flex items-center gap-2 px-3 py-2 bg-muted/50">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', block.done ? 'check' : 'spinner']"
|
||||
class="size-3"
|
||||
:class="block.done ? 'text-green-600 dark:text-green-400' : 'animate-spin text-muted-foreground'"
|
||||
<Check
|
||||
v-if="block.done"
|
||||
class="size-3 text-green-600 dark:text-green-400"
|
||||
/>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'magnifying-glass']"
|
||||
class="size-3 text-muted-foreground"
|
||||
<LoaderCircle
|
||||
v-else
|
||||
class="size-3 animate-spin text-muted-foreground"
|
||||
/>
|
||||
<Search class="size-3 text-muted-foreground" />
|
||||
<span class="text-xs truncate text-foreground">
|
||||
{{ query }}
|
||||
</span>
|
||||
@@ -41,8 +41,7 @@
|
||||
v-model:open="resultsOpen"
|
||||
>
|
||||
<CollapsibleTrigger class="flex items-center gap-1.5 px-3 py-1.5 text-xs text-muted-foreground hover:text-foreground cursor-pointer w-full">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'chevron-right']"
|
||||
<ChevronRight
|
||||
class="size-2.5 transition-transform"
|
||||
:class="{ 'rotate-90': resultsOpen }"
|
||||
/>
|
||||
@@ -79,6 +78,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { Check, LoaderCircle, Search, ChevronRight } from 'lucide-vue-next'
|
||||
import { Badge, Collapsible, CollapsibleTrigger, CollapsibleContent } from '@memohai/ui'
|
||||
import type { ToolCallBlock } from '@/store/chat-list'
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<template>
|
||||
<div class="rounded-lg border bg-muted/30 text-xs overflow-hidden">
|
||||
<div class="flex items-center gap-2 px-3 py-2 bg-muted/50">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', block.done ? 'check' : 'spinner']"
|
||||
class="size-3"
|
||||
:class="block.done ? 'text-green-600 dark:text-green-400' : 'animate-spin text-muted-foreground'"
|
||||
<Check
|
||||
v-if="block.done"
|
||||
class="size-3 text-green-600 dark:text-green-400"
|
||||
/>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'pen']"
|
||||
class="size-3 text-muted-foreground"
|
||||
<LoaderCircle
|
||||
v-else
|
||||
class="size-3 animate-spin text-muted-foreground"
|
||||
/>
|
||||
<SquarePen class="size-3 text-muted-foreground" />
|
||||
<button
|
||||
class="font-mono text-xs truncate hover:underline text-foreground cursor-pointer"
|
||||
:title="filePath"
|
||||
@@ -38,8 +38,7 @@
|
||||
v-model:open="contentOpen"
|
||||
>
|
||||
<CollapsibleTrigger class="flex items-center gap-1.5 px-3 py-1.5 text-xs text-muted-foreground hover:text-foreground cursor-pointer w-full">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'chevron-right']"
|
||||
<ChevronRight
|
||||
class="size-2.5 transition-transform"
|
||||
:class="{ 'rotate-90': contentOpen }"
|
||||
/>
|
||||
@@ -50,10 +49,7 @@
|
||||
v-if="shiki.loading.value"
|
||||
class="px-3 pb-2 text-xs text-muted-foreground"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'spinner']"
|
||||
class="size-3 animate-spin"
|
||||
/>
|
||||
<LoaderCircle class="size-3 animate-spin" />
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
@@ -67,6 +63,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, inject, watch } from 'vue'
|
||||
import { Check, LoaderCircle, SquarePen, ChevronRight } from 'lucide-vue-next'
|
||||
import { Badge, Collapsible, CollapsibleTrigger, CollapsibleContent } from '@memohai/ui'
|
||||
import type { ToolCallBlock } from '@/store/chat-list'
|
||||
import { openInFileManagerKey } from '../composables/useFileManagerProvider'
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
<div class="flex-1 flex flex-col min-w-0">
|
||||
<ChatArea />
|
||||
</div>
|
||||
<!-- <SessionMetadata /> -->
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
@@ -16,7 +15,6 @@ import { storeToRefs } from 'pinia'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useChatStore } from '@/store/chat-list'
|
||||
import SessionSidebar from './components/session-sidebar.vue'
|
||||
// import SessionMetadata from './components/session-metadata.vue'
|
||||
import ChatArea from './components/chat-area.vue'
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
@@ -5,8 +5,7 @@
|
||||
variant="outline"
|
||||
class="w-full mb-4 text-muted-foreground"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'plus']"
|
||||
<Plus
|
||||
class="mr-2"
|
||||
/>
|
||||
{{ $t('memory.add') }}
|
||||
@@ -69,6 +68,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Plus } from 'lucide-vue-next'
|
||||
import { reactive, ref } from 'vue'
|
||||
import {
|
||||
Button,
|
||||
|
||||
@@ -19,6 +19,7 @@ import { getMemoryProviders } from '@memohai/sdk'
|
||||
import type { MemoryprovidersGetResponse } from '@memohai/sdk'
|
||||
import AddMemoryProvider from './components/add-memory-provider.vue'
|
||||
import ProviderSetting from './components/provider-setting.vue'
|
||||
import { Brain, Plus } from 'lucide-vue-next'
|
||||
import MasterDetailSidebarLayout from '@/components/master-detail-sidebar-layout/index.vue'
|
||||
|
||||
const { data: providerData } = useQuery({
|
||||
@@ -69,8 +70,7 @@ const openStatus = reactive({ addOpen: false })
|
||||
:model-value="selectProvider(item.name).value"
|
||||
@update:model-value="(isSelect) => { if (isSelect) curProvider = item }"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'brain']"
|
||||
<Brain
|
||||
class="mr-2 size-4 text-primary"
|
||||
/>
|
||||
{{ item.name }}
|
||||
@@ -97,7 +97,7 @@ const openStatus = reactive({ addOpen: false })
|
||||
>
|
||||
<EmptyHeader>
|
||||
<EmptyMedia variant="icon">
|
||||
<FontAwesomeIcon :icon="['fas', 'brain']" />
|
||||
<Brain />
|
||||
</EmptyMedia>
|
||||
</EmptyHeader>
|
||||
<EmptyTitle>{{ $t('memory.emptyTitle') }}</EmptyTitle>
|
||||
@@ -108,8 +108,7 @@ const openStatus = reactive({ addOpen: false })
|
||||
class="w-full"
|
||||
@click="openStatus.addOpen=true"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'plus']"
|
||||
<Plus
|
||||
class="mr-2"
|
||||
/>
|
||||
{{ $t('memory.add') }}
|
||||
|
||||
@@ -5,14 +5,12 @@
|
||||
v-if="loading"
|
||||
class="mx-auto size-8"
|
||||
/>
|
||||
<FontAwesomeIcon
|
||||
<CircleCheck
|
||||
v-else-if="success"
|
||||
:icon="['fas', 'circle-check']"
|
||||
class="size-8 text-green-500"
|
||||
/>
|
||||
<FontAwesomeIcon
|
||||
<CircleX
|
||||
v-else
|
||||
:icon="['fas', 'circle-xmark']"
|
||||
class="size-8 text-destructive"
|
||||
/>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
@@ -27,6 +25,7 @@ import { onMounted, ref } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { Spinner } from '@memohai/ui'
|
||||
import { CircleCheck, CircleX } from 'lucide-vue-next'
|
||||
import { postBotsByBotIdMcpByIdOauthExchange } from '@memohai/sdk'
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
<template>
|
||||
<section>
|
||||
<h2 class="mb-2 flex items-center text-xs font-medium">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'plug']"
|
||||
<Plug
|
||||
class="mr-2 size-3.5"
|
||||
/>
|
||||
{{ $t('settings.bindCode') }}
|
||||
@@ -99,6 +98,7 @@ import {
|
||||
Separator,
|
||||
Spinner,
|
||||
} from '@memohai/ui'
|
||||
import { Plug } from 'lucide-vue-next'
|
||||
|
||||
interface BindCodeValue {
|
||||
token: string
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
<template>
|
||||
<section>
|
||||
<h2 class="mb-2 flex items-center text-xs font-medium">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'gear']"
|
||||
<Settings
|
||||
class="mr-2 size-3.5"
|
||||
/>
|
||||
{{ $t('settings.changePassword') }}
|
||||
@@ -54,6 +53,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Button, Input, Label, Separator, Spinner } from '@memohai/ui'
|
||||
import { Settings } from 'lucide-vue-next'
|
||||
|
||||
defineProps<{
|
||||
currentPassword: string
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
<template>
|
||||
<section>
|
||||
<h2 class="mb-2 flex items-center text-xs font-medium">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'user']"
|
||||
<User
|
||||
class="mr-2 size-3.5"
|
||||
/>
|
||||
{{ $t('settings.userProfile') }}
|
||||
@@ -75,6 +74,7 @@ import {
|
||||
Separator,
|
||||
Spinner,
|
||||
} from '@memohai/ui'
|
||||
import { User } from 'lucide-vue-next'
|
||||
import TimezoneSelect from '@/components/timezone-select/index.vue'
|
||||
|
||||
defineProps<{
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
:aria-label="$t('models.testModel')"
|
||||
@click="runTest"
|
||||
>
|
||||
<FontAwesomeIcon :icon="['fas', 'rotate']" />
|
||||
<RefreshCw />
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
@@ -71,7 +71,7 @@
|
||||
:aria-label="$t('common.edit')"
|
||||
@click="$emit('edit', model)"
|
||||
>
|
||||
<FontAwesomeIcon :icon="['fas', 'gear']" />
|
||||
<Settings />
|
||||
</Button>
|
||||
|
||||
<ConfirmPopover
|
||||
@@ -85,7 +85,7 @@
|
||||
variant="outline"
|
||||
:aria-label="$t('common.delete')"
|
||||
>
|
||||
<FontAwesomeIcon :icon="['far', 'trash-can']" />
|
||||
<Trash2 />
|
||||
</Button>
|
||||
</template>
|
||||
</ConfirmPopover>
|
||||
@@ -104,6 +104,7 @@ import {
|
||||
Button,
|
||||
Spinner,
|
||||
} from '@memohai/ui'
|
||||
import { RefreshCw, Settings, Trash2 } from 'lucide-vue-next'
|
||||
import ConfirmPopover from '@/components/confirm-popover/index.vue'
|
||||
import { postModelsByIdTest } from '@memohai/sdk'
|
||||
import type { ModelsGetResponse, ModelsTestResponse } from '@memohai/sdk'
|
||||
|
||||
@@ -19,8 +19,7 @@
|
||||
class="shadow-none mb-4"
|
||||
>
|
||||
<InputGroupAddon align="inline-start">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'magnifying-glass']"
|
||||
<Search
|
||||
class="text-muted-foreground"
|
||||
/>
|
||||
</InputGroupAddon>
|
||||
@@ -64,7 +63,7 @@
|
||||
>
|
||||
<EmptyHeader>
|
||||
<EmptyMedia variant="icon">
|
||||
<FontAwesomeIcon :icon="['fas', 'magnifying-glass']" />
|
||||
<Search />
|
||||
</EmptyMedia>
|
||||
</EmptyHeader>
|
||||
<EmptyTitle>{{ $t('models.searchNoResults') }}</EmptyTitle>
|
||||
@@ -77,7 +76,7 @@
|
||||
>
|
||||
<EmptyHeader>
|
||||
<EmptyMedia variant="icon">
|
||||
<FontAwesomeIcon :icon="['far', 'rectangle-list']" />
|
||||
<List />
|
||||
</EmptyMedia>
|
||||
</EmptyHeader>
|
||||
<EmptyTitle>{{ $t('models.emptyTitle') }}</EmptyTitle>
|
||||
@@ -101,6 +100,7 @@ import {
|
||||
InputGroupAddon,
|
||||
InputGroupInput,
|
||||
} from '@memohai/ui'
|
||||
import { Search, List } from 'lucide-vue-next'
|
||||
import CreateModel from '@/components/create-model/index.vue'
|
||||
import ImportModelsDialog from '@/components/import-models-dialog/index.vue'
|
||||
import ModelItem from './model-item.vue'
|
||||
|
||||
@@ -126,7 +126,7 @@
|
||||
:loading="authorizeLoading"
|
||||
@click="handleAuthorize"
|
||||
>
|
||||
<FontAwesomeIcon :icon="['fas', 'key']" />
|
||||
<KeyRound />
|
||||
{{ $t('provider.oauth.authorize') }}
|
||||
</LoadingButton>
|
||||
<LoadingButton
|
||||
@@ -150,9 +150,8 @@
|
||||
:disabled="!props.provider?.id"
|
||||
@click="runTest"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
<RefreshCw
|
||||
v-if="!testLoading"
|
||||
:icon="['fas', 'rotate']"
|
||||
/>
|
||||
{{ $t('provider.testConnection') }}
|
||||
</LoadingButton>
|
||||
@@ -169,7 +168,7 @@
|
||||
variant="outline"
|
||||
:aria-label="$t('common.delete')"
|
||||
>
|
||||
<FontAwesomeIcon :icon="['far', 'trash-can']" />
|
||||
<Trash2 />
|
||||
</Button>
|
||||
</template>
|
||||
</ConfirmPopover>
|
||||
@@ -227,6 +226,7 @@ import {
|
||||
FormLabel,
|
||||
FormItem,
|
||||
} from '@memohai/ui'
|
||||
import { KeyRound, RefreshCw, Trash2 } from 'lucide-vue-next'
|
||||
import ConfirmPopover from '@/components/confirm-popover/index.vue'
|
||||
import StatusDot from '@/components/status-dot/index.vue'
|
||||
import LoadingButton from '@/components/loading-button/index.vue'
|
||||
|
||||
@@ -20,6 +20,7 @@ import { getProviders } from '@memohai/sdk'
|
||||
import type { ProvidersGetResponse } from '@memohai/sdk'
|
||||
import AddProvider from '@/components/add-provider/index.vue'
|
||||
import ProviderIcon from '@/components/provider-icon/index.vue'
|
||||
import { List } from 'lucide-vue-next'
|
||||
import MasterDetailSidebarLayout from '@/components/master-detail-sidebar-layout/index.vue'
|
||||
|
||||
function getInitials(name: string | undefined) {
|
||||
@@ -151,7 +152,7 @@ const openStatus = reactive({
|
||||
>
|
||||
<EmptyHeader>
|
||||
<EmptyMedia variant="icon">
|
||||
<FontAwesomeIcon :icon="['far', 'rectangle-list']" />
|
||||
<List />
|
||||
</EmptyMedia>
|
||||
</EmptyHeader>
|
||||
<EmptyTitle>{{ $t('provider.emptyTitle') }}</EmptyTitle>
|
||||
|
||||
@@ -14,8 +14,7 @@
|
||||
class="w-full shadow-none! text-muted-foreground mb-4"
|
||||
variant="outline"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'plus']"
|
||||
<Plus
|
||||
class="mr-1"
|
||||
/> {{ $t('speech.add') }}
|
||||
</Button>
|
||||
@@ -99,6 +98,7 @@ import { useMutation, useQuery, useQueryCache } from '@pinia/colada'
|
||||
import { postTtsProviders, getTtsProvidersMeta } from '@memohai/sdk'
|
||||
import type { TtsCreateProviderRequest } from '@memohai/sdk'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { Plus } from 'lucide-vue-next'
|
||||
import FormDialogShell from '@/components/form-dialog-shell/index.vue'
|
||||
import { useDialogMutation } from '@/composables/useDialogMutation'
|
||||
|
||||
|
||||
@@ -209,8 +209,7 @@
|
||||
:disabled="!testText.trim() || testText.length > maxTestTextLen"
|
||||
@click="handleTest"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'play']"
|
||||
<Play
|
||||
class="mr-1.5"
|
||||
/>
|
||||
{{ $t('speech.test.generate') }}
|
||||
@@ -262,6 +261,7 @@ import {
|
||||
Textarea,
|
||||
Separator,
|
||||
} from '@memohai/ui'
|
||||
import { Play } from 'lucide-vue-next'
|
||||
import LoadingButton from '@/components/loading-button/index.vue'
|
||||
import type { TtsModelCapabilities, TtsVoiceInfo } from '@memohai/sdk'
|
||||
import { computed, onBeforeUnmount, reactive, ref, watch } from 'vue'
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
<template>
|
||||
<div class="p-4">
|
||||
<section class="flex items-center gap-3">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'volume-high']"
|
||||
<Volume2
|
||||
class="size-5"
|
||||
/>
|
||||
<div class="min-w-0">
|
||||
@@ -68,7 +67,7 @@
|
||||
:loading="importLoading"
|
||||
@click="handleImportModels"
|
||||
>
|
||||
<FontAwesomeIcon :icon="['fas', 'file-import']" />
|
||||
<FileInput />
|
||||
{{ $t('speech.importModels') }}
|
||||
</LoadingButton>
|
||||
<AddTtsModel
|
||||
@@ -102,8 +101,8 @@
|
||||
class="text-xs text-muted-foreground ml-2"
|
||||
>{{ model.model_id }}</span>
|
||||
</div>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', expandedModelId === model.id ? 'chevron-up' : 'chevron-down']"
|
||||
<component
|
||||
:is="expandedModelId === model.id ? ChevronUp : ChevronDown"
|
||||
class="size-3 text-muted-foreground"
|
||||
/>
|
||||
</button>
|
||||
@@ -136,7 +135,7 @@
|
||||
type="button"
|
||||
variant="outline"
|
||||
>
|
||||
<FontAwesomeIcon :icon="['far', 'trash-can']" />
|
||||
<Trash2 />
|
||||
</Button>
|
||||
</template>
|
||||
</ConfirmPopover>
|
||||
@@ -165,6 +164,7 @@ import {
|
||||
import ConfirmPopover from '@/components/confirm-popover/index.vue'
|
||||
import LoadingButton from '@/components/loading-button/index.vue'
|
||||
import ModelConfigEditor from './model-config-editor.vue'
|
||||
import { Volume2, FileInput, ChevronUp, ChevronDown, Trash2 } from 'lucide-vue-next'
|
||||
import AddTtsModel from './add-tts-model.vue'
|
||||
import { computed, inject, ref, watch } from 'vue'
|
||||
import { toast } from 'vue-sonner'
|
||||
|
||||
@@ -19,6 +19,7 @@ import { getTtsProviders } from '@memohai/sdk'
|
||||
import type { TtsProviderResponse } from '@memohai/sdk'
|
||||
import AddTtsProvider from './components/add-tts-provider.vue'
|
||||
import ProviderSetting from './components/provider-setting.vue'
|
||||
import { Volume2, Plus } from 'lucide-vue-next'
|
||||
import MasterDetailSidebarLayout from '@/components/master-detail-sidebar-layout/index.vue'
|
||||
|
||||
const { data: providerData } = useQuery({
|
||||
@@ -82,8 +83,7 @@ const openStatus = reactive({ addOpen: false })
|
||||
>
|
||||
<span class="relative shrink-0">
|
||||
<span class="flex size-7 items-center justify-center rounded-full bg-muted">
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'volume-high']"
|
||||
<Volume2
|
||||
class="size-3.5 text-muted-foreground"
|
||||
/>
|
||||
</span>
|
||||
@@ -116,7 +116,7 @@ const openStatus = reactive({ addOpen: false })
|
||||
>
|
||||
<EmptyHeader>
|
||||
<EmptyMedia variant="icon">
|
||||
<FontAwesomeIcon :icon="['fas', 'volume-high']" />
|
||||
<Volume2 />
|
||||
</EmptyMedia>
|
||||
</EmptyHeader>
|
||||
<EmptyTitle>{{ $t('speech.emptyTitle') }}</EmptyTitle>
|
||||
@@ -126,8 +126,7 @@ const openStatus = reactive({ addOpen: false })
|
||||
variant="outline"
|
||||
@click="openStatus.addOpen = true"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'plus']"
|
||||
<Plus
|
||||
class="mr-1"
|
||||
/> {{ $t('speech.add') }}
|
||||
</Button>
|
||||
|
||||
@@ -14,8 +14,7 @@
|
||||
class="w-full shadow-none! text-muted-foreground mb-4"
|
||||
variant="outline"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'plus']"
|
||||
<Plus
|
||||
class="mr-1"
|
||||
/> {{ $t('webSearch.add') }}
|
||||
</Button>
|
||||
@@ -107,6 +106,7 @@ import { useMutation, useQueryCache } from '@pinia/colada'
|
||||
import { postSearchProviders } from '@memohai/sdk'
|
||||
import type { SearchprovidersCreateRequest } from '@memohai/sdk'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { Plus } from 'lucide-vue-next'
|
||||
import FormDialogShell from '@/components/form-dialog-shell/index.vue'
|
||||
import { useDialogMutation } from '@/composables/useDialogMutation'
|
||||
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
variant="outline"
|
||||
:aria-label="$t('common.delete')"
|
||||
>
|
||||
<FontAwesomeIcon :icon="['far', 'trash-can']" />
|
||||
<Trash2 />
|
||||
</Button>
|
||||
</template>
|
||||
</ConfirmPopover>
|
||||
@@ -147,6 +147,7 @@ import ExaSettings from './exa-settings.vue'
|
||||
import BochaSettings from './bocha-settings.vue'
|
||||
import DuckduckgoSettings from './duckduckgo-settings.vue'
|
||||
import YandexSettings from './yandex-settings.vue'
|
||||
import { Trash2 } from 'lucide-vue-next'
|
||||
import SearchProviderLogo from '@/components/search-provider-logo/index.vue'
|
||||
import { computed, inject, ref, watch } from 'vue'
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
|
||||
@@ -20,6 +20,7 @@ import type { SearchprovidersGetResponse } from '@memohai/sdk'
|
||||
import AddSearchProvider from './components/add-search-provider.vue'
|
||||
import ProviderSetting from './components/provider-setting.vue'
|
||||
import SearchProviderLogo from '@/components/search-provider-logo/index.vue'
|
||||
import { Globe, Plus } from 'lucide-vue-next'
|
||||
import MasterDetailSidebarLayout from '@/components/master-detail-sidebar-layout/index.vue'
|
||||
|
||||
const { data: providerData } = useQuery({
|
||||
@@ -131,7 +132,7 @@ const openStatus = reactive({
|
||||
>
|
||||
<EmptyHeader>
|
||||
<EmptyMedia variant="icon">
|
||||
<FontAwesomeIcon :icon="['fas', 'globe']" />
|
||||
<Globe />
|
||||
</EmptyMedia>
|
||||
</EmptyHeader>
|
||||
<EmptyTitle>{{ $t('webSearch.emptyTitle') }}</EmptyTitle>
|
||||
@@ -141,8 +142,7 @@ const openStatus = reactive({
|
||||
variant="outline"
|
||||
@click="openStatus.addOpen=true"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="['fas', 'plus']"
|
||||
<Plus
|
||||
class="mr-1"
|
||||
/> {{ $t('webSearch.add') }}
|
||||
</Button>
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
/**
|
||||
* Local channel icons under public/channels/.
|
||||
* getChannelImage: URL to local icon when available.
|
||||
* getChannelIcon: FontAwesome fallback when no local image.
|
||||
*/
|
||||
|
||||
const LOCAL_CHANNEL_IMAGES: Record<string, string> = {
|
||||
feishu: '/channels/feishu.png',
|
||||
matrix: '/channels/matrix.svg',
|
||||
telegram: '/channels/telegram.webp',
|
||||
}
|
||||
|
||||
const CHANNEL_ICONS: Record<string, [string, string]> = {
|
||||
qq: ['fab', 'qq'],
|
||||
telegram: ['fab', 'telegram'],
|
||||
matrix: ['fas', 'hashtag'],
|
||||
feishu: ['fas', 'comment-dots'],
|
||||
web: ['fas', 'globe'],
|
||||
slack: ['fab', 'slack'],
|
||||
discord: ['fab', 'discord'],
|
||||
email: ['fas', 'envelope'],
|
||||
}
|
||||
|
||||
const DEFAULT_ICON: [string, string] = ['far', 'comment']
|
||||
|
||||
export function getChannelIcon(platformKey: string): [string, string] {
|
||||
if (!platformKey) return DEFAULT_ICON
|
||||
return CHANNEL_ICONS[platformKey] ?? DEFAULT_ICON
|
||||
}
|
||||
|
||||
export function getChannelImage(platformKey: string): string | null {
|
||||
if (!platformKey) return null
|
||||
return LOCAL_CHANNEL_IMAGES[platformKey] ?? null
|
||||
}
|
||||
Generated
-64
@@ -92,21 +92,6 @@ importers:
|
||||
|
||||
apps/web:
|
||||
dependencies:
|
||||
'@fortawesome/fontawesome-svg-core':
|
||||
specifier: ^7.0.0
|
||||
version: 7.2.0
|
||||
'@fortawesome/free-brands-svg-icons':
|
||||
specifier: ^7.0.0
|
||||
version: 7.2.0
|
||||
'@fortawesome/free-regular-svg-icons':
|
||||
specifier: ^7.0.0
|
||||
version: 7.2.0
|
||||
'@fortawesome/free-solid-svg-icons':
|
||||
specifier: ^7.0.0
|
||||
version: 7.2.0
|
||||
'@fortawesome/vue-fontawesome':
|
||||
specifier: ^3.1.1
|
||||
version: 3.1.3(@fortawesome/fontawesome-svg-core@7.2.0)(vue@3.5.26(typescript@5.9.3))
|
||||
'@memohai/icon':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/icons
|
||||
@@ -1356,32 +1341,6 @@ packages:
|
||||
'@floating-ui/vue@1.1.9':
|
||||
resolution: {integrity: sha512-BfNqNW6KA83Nexspgb9DZuz578R7HT8MZw1CfK9I6Ah4QReNWEJsXWHN+SdmOVLNGmTPDi+fDT535Df5PzMLbQ==}
|
||||
|
||||
'@fortawesome/fontawesome-common-types@7.2.0':
|
||||
resolution: {integrity: sha512-IpR0bER9FY25p+e7BmFH25MZKEwFHTfRAfhOyJubgiDnoJNsSvJ7nigLraHtp4VOG/cy8D7uiV0dLkHOne5Fhw==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
'@fortawesome/fontawesome-svg-core@7.2.0':
|
||||
resolution: {integrity: sha512-6639htZMjEkwskf3J+e6/iar+4cTNM9qhoWuRfj9F3eJD6r7iCzV1SWnQr2Mdv0QT0suuqU8BoJCZUyCtP9R4Q==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
'@fortawesome/free-brands-svg-icons@7.2.0':
|
||||
resolution: {integrity: sha512-VNG8xqOip1JuJcC3zsVsKRQ60oXG9+oYNDCosjoU/H9pgYmLTEwWw8pE0jhPz/JWdHeUuK6+NQ3qsM4gIbdbYQ==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
'@fortawesome/free-regular-svg-icons@7.2.0':
|
||||
resolution: {integrity: sha512-iycmlN51EULlQ4D/UU9WZnHiN0CvjJ2TuuCrAh+1MVdzD+4ViKYH2deNAll4XAAYlZa8WAefHR5taSK8hYmSMw==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
'@fortawesome/free-solid-svg-icons@7.2.0':
|
||||
resolution: {integrity: sha512-YTVITFGN0/24PxzXrwqCgnyd7njDuzp5ZvaCx5nq/jg55kUYd94Nj8UTchBdBofi/L0nwRfjGOg0E41d2u9T1w==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
'@fortawesome/vue-fontawesome@3.1.3':
|
||||
resolution: {integrity: sha512-OHHUTLPEzdwP8kcYIzhioUdUOjZ4zzmi+midwa4bqscza4OJCOvTKJEHkXNz8PgZ23kWci1HkKVX0bm8f9t9gQ==}
|
||||
peerDependencies:
|
||||
'@fortawesome/fontawesome-svg-core': ~1 || ~6 || ~7
|
||||
vue: '>= 3.0.0 < 4'
|
||||
|
||||
'@hey-api/codegen-core@0.7.0':
|
||||
resolution: {integrity: sha512-HglL4B4QwpzocE+c8qDU6XK8zMf8W8Pcv0RpFDYxHuYALWLTnpDUuEsglC7NQ4vC1maoXsBpMbmwpco0N4QviA==}
|
||||
engines: {node: '>=20.19.0'}
|
||||
@@ -5766,29 +5725,6 @@ snapshots:
|
||||
- '@vue/composition-api'
|
||||
- vue
|
||||
|
||||
'@fortawesome/fontawesome-common-types@7.2.0': {}
|
||||
|
||||
'@fortawesome/fontawesome-svg-core@7.2.0':
|
||||
dependencies:
|
||||
'@fortawesome/fontawesome-common-types': 7.2.0
|
||||
|
||||
'@fortawesome/free-brands-svg-icons@7.2.0':
|
||||
dependencies:
|
||||
'@fortawesome/fontawesome-common-types': 7.2.0
|
||||
|
||||
'@fortawesome/free-regular-svg-icons@7.2.0':
|
||||
dependencies:
|
||||
'@fortawesome/fontawesome-common-types': 7.2.0
|
||||
|
||||
'@fortawesome/free-solid-svg-icons@7.2.0':
|
||||
dependencies:
|
||||
'@fortawesome/fontawesome-common-types': 7.2.0
|
||||
|
||||
'@fortawesome/vue-fontawesome@3.1.3(@fortawesome/fontawesome-svg-core@7.2.0)(vue@3.5.26(typescript@5.9.3))':
|
||||
dependencies:
|
||||
'@fortawesome/fontawesome-svg-core': 7.2.0
|
||||
vue: 3.5.26(typescript@5.9.3)
|
||||
|
||||
'@hey-api/codegen-core@0.7.0(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@hey-api/types': 0.1.3(typescript@5.9.3)
|
||||
|
||||
Reference in New Issue
Block a user