diff --git a/apps/cli/src/ai/tool-adapter.ts b/apps/cli/src/ai/tool-adapter.ts index 1f4d5347..c4655b06 100644 --- a/apps/cli/src/ai/tool-adapter.ts +++ b/apps/cli/src/ai/tool-adapter.ts @@ -10,7 +10,7 @@ import type { ToolExecutionContext, SemanticSearchToolService, RawMessage, - TimeFilter, + ToolTimeRange, } from '@openchatlab/tools' import { CoreDataProvider } from '@openchatlab/tools' import type { DatabaseAdapter } from '@openchatlab/core' @@ -50,7 +50,7 @@ export interface ServerToolContext { /** 当前用户平台 id(昵称匿名化 owner 识别) */ ownerPlatformId?: string /** 会话时间范围筛选(来自请求参数,供证据类工具继承) */ - timeFilter?: TimeFilter + timeFilter?: ToolTimeRange /** 关键词搜索消息条数上限 */ maxMessagesLimit?: number } diff --git a/apps/desktop/main/ai/tools/worker-data-provider.ts b/apps/desktop/main/ai/tools/worker-data-provider.ts index 3f4b6506..aacf83d2 100644 --- a/apps/desktop/main/ai/tools/worker-data-provider.ts +++ b/apps/desktop/main/ai/tools/worker-data-provider.ts @@ -11,7 +11,7 @@ import type { SearchMessagesResult, MemberStatItem, SchemaTableInfo, - TimeFilter, + ToolTimeRange, ChatOverviewResult, MemberInfo, NameHistoryItem, @@ -52,7 +52,7 @@ export class WorkerDataProvider implements ToolDataProvider { async searchMessages( keywords: string[], - options?: { timeFilter?: TimeFilter; limit?: number; senderId?: number } + options?: { timeFilter?: ToolTimeRange; limit?: number; senderId?: number } ): Promise { const result = await this.run(() => workerManager.searchMessages( @@ -69,7 +69,7 @@ export class WorkerDataProvider implements ToolDataProvider { async deepSearchMessages( keywords: string[], - options?: { timeFilter?: TimeFilter; limit?: number; senderId?: number } + options?: { timeFilter?: ToolTimeRange; limit?: number; senderId?: number } ): Promise { const result = await this.run(() => workerManager.deepSearchMessages( @@ -95,7 +95,7 @@ export class WorkerDataProvider implements ToolDataProvider { return mapSearchMessages(messages) } - async getRecentMessages(options?: { timeFilter?: TimeFilter; limit?: number }): Promise { + async getRecentMessages(options?: { timeFilter?: ToolTimeRange; limit?: number }): Promise { const result = await this.run(() => workerManager.getRecentMessages(this.sessionId, options?.timeFilter, options?.limit ?? 50) ) @@ -123,7 +123,7 @@ export class WorkerDataProvider implements ToolDataProvider { })) } - async getMemberStats(options?: { timeFilter?: TimeFilter; top?: number }): Promise { + async getMemberStats(options?: { timeFilter?: ToolTimeRange; top?: number }): Promise { const top = options?.top ?? 20 const members = await this.run(() => workerManager.getMemberActivity(this.sessionId, options?.timeFilter)) return members.slice(0, top).map((m: any) => ({ @@ -137,7 +137,10 @@ export class WorkerDataProvider implements ToolDataProvider { return this.run(() => workerManager.getMemberNameHistory(this.sessionId, memberId)) } - async getTimeStats(type: 'hourly' | 'weekday' | 'daily', options?: { timeFilter?: TimeFilter }): Promise { + async getTimeStats( + type: 'hourly' | 'weekday' | 'daily', + options?: { timeFilter?: ToolTimeRange } + ): Promise { const filter = options?.timeFilter switch (type) { case 'weekday': @@ -154,7 +157,7 @@ export class WorkerDataProvider implements ToolDataProvider { return this.run(() => workerManager.getSegmentMessages(this.sessionId, segmentId, limit)) } - async getSegmentSummaries(options?: { limit?: number; timeFilter?: TimeFilter }): Promise { + async getSegmentSummaries(options?: { limit?: number; timeFilter?: ToolTimeRange }): Promise { return this.run(() => workerManager.getSegmentSummaries(this.sessionId, { limit: options?.limit, @@ -166,7 +169,7 @@ export class WorkerDataProvider implements ToolDataProvider { async getConversationBetween( memberId1: number, memberId2: number, - timeFilter?: TimeFilter, + timeFilter?: ToolTimeRange, limit?: number ): Promise { const result = await this.run(() => diff --git a/packages/config/src/loader.ts b/packages/config/src/loader.ts index c63b8348..fba0ea32 100644 --- a/packages/config/src/loader.ts +++ b/packages/config/src/loader.ts @@ -148,14 +148,11 @@ export function writeConfigField(section: string, key: string, value: string | n fs.writeFileSync(CONFIG_TOML, lines.join('\n'), 'utf-8') } -/** - * 简单的深度合并(仅处理纯对象,不处理数组) - */ function deepMerge(base: Record, override: Record): Record { const result = { ...base } for (const [key, value] of Object.entries(override)) { - if (value !== undefined && value !== null && typeof value === 'object' && !Array.isArray(value)) { - result[key] = deepMerge((result[key] as Record) ?? {}, value as Record) + if (value !== null && typeof value === 'object' && !Array.isArray(value)) { + result[key] = { ...(result[key] as object), ...(value as object) } } else if (value !== undefined) { result[key] = value } diff --git a/packages/core/src/ai/chart-capability.ts b/packages/core/src/ai/chart-capability.ts deleted file mode 100644 index 306984f4..00000000 --- a/packages/core/src/ai/chart-capability.ts +++ /dev/null @@ -1 +0,0 @@ -export const CHART_CAPABILITY_SKILL_ID = 'chart_runtime' diff --git a/packages/core/src/ai/index.ts b/packages/core/src/ai/index.ts index 1d1350c8..3fbaf425 100644 --- a/packages/core/src/ai/index.ts +++ b/packages/core/src/ai/index.ts @@ -4,8 +4,12 @@ // 内置工具目录 export type { ToolCategory, BuiltinToolCatalogEntry } from './tool-catalog' -export { BUILTIN_TOOL_CATALOG, normalizeBuiltinToolName, normalizeBuiltinToolNames } from './tool-catalog' -export { CHART_CAPABILITY_SKILL_ID } from './chart-capability' +export { + BUILTIN_TOOL_CATALOG, + normalizeBuiltinToolName, + normalizeBuiltinToolNames, + CHART_CAPABILITY_SKILL_ID, +} from './tool-catalog' // LLM 模型系统类型 export type { diff --git a/packages/core/src/ai/tool-catalog.ts b/packages/core/src/ai/tool-catalog.ts index 7fcd85ae..ab3a7ad0 100644 --- a/packages/core/src/ai/tool-catalog.ts +++ b/packages/core/src/ai/tool-catalog.ts @@ -25,6 +25,8 @@ export function normalizeBuiltinToolNames(toolNames: readonly string[]): string[ return Array.from(new Set(toolNames.map(normalizeBuiltinToolName))) } +export const CHART_CAPABILITY_SKILL_ID = 'chart_runtime' + export const BUILTIN_TOOL_CATALOG: BuiltinToolCatalogEntry[] = [ // Core 工具 { name: 'get_chat_overview', category: 'core' }, diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 55431281..b88ed1d1 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -6,14 +6,7 @@ */ // 抽象接口 -export type { - DatabaseAdapter, - PreparedStatement, - RunResult, - PathProvider, - NotificationBus, - NotificationPayload, -} from './interfaces' +export type { DatabaseAdapter, PreparedStatement, RunResult, PathProvider } from './interfaces' // 查询工具 export { diff --git a/packages/core/src/interfaces/index.ts b/packages/core/src/interfaces/index.ts index 01f24f46..6cae6200 100644 --- a/packages/core/src/interfaces/index.ts +++ b/packages/core/src/interfaces/index.ts @@ -1,3 +1,2 @@ export type { DatabaseAdapter, PreparedStatement, RunResult } from './database-adapter' export type { PathProvider } from './path-provider' -export type { NotificationBus, NotificationPayload } from './notification-bus' diff --git a/packages/core/src/interfaces/notification-bus.ts b/packages/core/src/interfaces/notification-bus.ts deleted file mode 100644 index 92ed2d0c..00000000 --- a/packages/core/src/interfaces/notification-bus.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * 通知总线抽象接口 - * - * 统一不同运行环境下的异步事件通知方式: - * - Electron:通过 BrowserWindow.webContents.send(IPC)通知前端 - * - Node 独立运行:通过 EventEmitter 或 SSE 通知消费者 - * - 浏览器:通过 postMessage / CustomEvent 通知 UI - */ - -export type NotificationPayload = Record - -export interface NotificationBus { - /** 发送通知事件 */ - emit(event: string, payload?: NotificationPayload): void - - /** 监听通知事件 */ - on(event: string, handler: (payload?: NotificationPayload) => void): void - - /** 取消监听 */ - off(event: string, handler: (payload?: NotificationPayload) => void): void -} diff --git a/packages/core/src/query/__tests__/message-query-functions.test.ts b/packages/core/src/query/__tests__/message-query-functions.test.ts index 974489a1..f1dafcba 100644 --- a/packages/core/src/query/__tests__/message-query-functions.test.ts +++ b/packages/core/src/query/__tests__/message-query-functions.test.ts @@ -9,7 +9,7 @@ import { describe, it } from 'node:test' import assert from 'node:assert/strict' -import type { AsyncSqlExecutor } from '../executor' +import type { AsyncSqlExecutor } from '../message-query-functions' import type { FullMessageRow } from '../message-sql' import { fetchMessagesBefore, diff --git a/packages/core/src/query/executor.ts b/packages/core/src/query/executor.ts deleted file mode 100644 index 41765bf5..00000000 --- a/packages/core/src/query/executor.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Async SQL executor abstraction. - * - * Provides a platform-agnostic async interface for executing SQL queries. - * - Electron: wraps synchronous `better-sqlite3` calls via `Promise.resolve()` - * - CLI Web: wraps `pluginQuery` HTTP calls (natively async) - * - Server direct: wraps `DatabaseAdapter` for `/api/v1` or AI tool use - */ - -export interface AsyncSqlExecutor { - all(sql: string, params?: unknown[]): Promise - get(sql: string, params?: unknown[]): Promise -} diff --git a/packages/core/src/query/index.ts b/packages/core/src/query/index.ts index cc08cedc..9bc47dd6 100644 --- a/packages/core/src/query/index.ts +++ b/packages/core/src/query/index.ts @@ -123,9 +123,6 @@ export { } from './message-sql' export type { FullMessageRow, MappedMessage, MsgQueryConditions } from './message-sql' -// Async SQL executor abstraction -export type { AsyncSqlExecutor } from './executor' - // Shared async message query functions (platform-agnostic) export { fetchMessagesBefore, @@ -138,7 +135,12 @@ export { fetchRecentTextMessages, fetchConversationBetween, } from './message-query-functions' -export type { AsyncPaginatedMessages, AsyncMessagesWithTotal, AsyncConversationData } from './message-query-functions' +export type { + AsyncSqlExecutor, + AsyncPaginatedMessages, + AsyncMessagesWithTotal, + AsyncConversationData, +} from './message-query-functions' // Member write operations (merge, delete, update aliases, DDL migration) export { updateMemberAliases, mergeMembers, deleteMember, ensureAliasesColumn, ensureAvatarColumn } from './member-ops' diff --git a/packages/core/src/query/message-query-functions.ts b/packages/core/src/query/message-query-functions.ts index b7c3a348..f6d91ea7 100644 --- a/packages/core/src/query/message-query-functions.ts +++ b/packages/core/src/query/message-query-functions.ts @@ -9,7 +9,6 @@ */ import type { TimeFilter } from '@openchatlab/shared-types' -import type { AsyncSqlExecutor } from './executor' import { FULL_MSG_SELECT, FULL_MSG_FROM, @@ -20,6 +19,11 @@ import { type MappedMessage, } from './message-sql' +export interface AsyncSqlExecutor { + all(sql: string, params?: unknown[]): Promise + get(sql: string, params?: unknown[]): Promise +} + // ==================== Result types ==================== export interface AsyncPaginatedMessages { diff --git a/packages/tools/src/definitions/analysis-tools.test.ts b/packages/tools/src/definitions/analysis-tools.test.ts index 542d231b..bb0ad25e 100644 --- a/packages/tools/src/definitions/analysis-tools.test.ts +++ b/packages/tools/src/definitions/analysis-tools.test.ts @@ -6,7 +6,7 @@ import { getSegmentSummariesTool } from './get-segment-summaries' import { searchMessagesTool } from './search-messages' import { schemaTool, sqlQueryTool } from './sql-query' import { SQL_TOOL_DEFS, createSqlToolDefinition } from '../sql' -import type { RawMessage, ToolDataProvider, ToolExecutionContext, TimeFilter } from '../types' +import type { RawMessage, ToolDataProvider, ToolExecutionContext, ToolTimeRange } from '../types' function createContext( dataProvider: Partial, @@ -28,7 +28,7 @@ function createSqlTool(name: string) { describe('high-risk analysis tool definitions', () => { it('search_messages passes filters to the provider and returns expanded context messages', async () => { - const contextFilter: TimeFilter = { startTs: 1710000000, endTs: 1710000100 } + const contextFilter: ToolTimeRange = { startTs: 1710000000, endTs: 1710000100 } const searchCalls: Array<{ keywords: string[]; options: unknown }> = [] const contextCalls: Array<{ ids: number[]; before: number; after: number }> = [] const expandedMessages: RawMessage[] = [ @@ -109,8 +109,8 @@ describe('high-risk analysis tool definitions', () => { }) it('get_segment_summaries filters empty and non-matching summaries after over-fetching', async () => { - const calls: Array<{ limit?: number; timeFilter?: TimeFilter }> = [] - const contextFilter: TimeFilter = { startTs: 1704067200, endTs: 1704153600 } + const calls: Array<{ limit?: number; timeFilter?: ToolTimeRange }> = [] + const contextFilter: ToolTimeRange = { startTs: 1704067200, endTs: 1704153600 } const context = createContext( { async getSegmentSummaries(options) { diff --git a/packages/tools/src/index.ts b/packages/tools/src/index.ts index 12deefcb..4a22b591 100644 --- a/packages/tools/src/index.ts +++ b/packages/tools/src/index.ts @@ -56,7 +56,7 @@ export type { SearchMessagesResult, MemberStatItem, SchemaTableInfo, - TimeFilter, + ToolTimeRange, ToolCategory, TruncationStrategy, ChatOverviewResult, diff --git a/packages/tools/src/providers/core-data-provider.ts b/packages/tools/src/providers/core-data-provider.ts index 66c23d2c..51413ec2 100644 --- a/packages/tools/src/providers/core-data-provider.ts +++ b/packages/tools/src/providers/core-data-provider.ts @@ -30,7 +30,7 @@ import type { SearchMessagesResult, MemberStatItem, SchemaTableInfo, - TimeFilter, + ToolTimeRange, ChatOverviewResult, MemberInfo, NameHistoryItem, @@ -45,7 +45,7 @@ export class CoreDataProvider implements ToolDataProvider { async searchMessages( keywords: string[], - options?: { timeFilter?: TimeFilter; limit?: number; senderId?: number } + options?: { timeFilter?: ToolTimeRange; limit?: number; senderId?: number } ): Promise { const result = searchMessagesByKeywords(this.db, keywords, { startTs: options?.timeFilter?.startTs, @@ -68,7 +68,7 @@ export class CoreDataProvider implements ToolDataProvider { async deepSearchMessages( keywords: string[], - options?: { timeFilter?: TimeFilter; limit?: number; senderId?: number } + options?: { timeFilter?: ToolTimeRange; limit?: number; senderId?: number } ): Promise { return this.searchMessages(keywords, options) } @@ -81,7 +81,7 @@ export class CoreDataProvider implements ToolDataProvider { return coreGetSearchMessageContext(this.db, messageIds, contextBefore, contextAfter) } - async getRecentMessages(options?: { timeFilter?: TimeFilter; limit?: number }): Promise { + async getRecentMessages(options?: { timeFilter?: ToolTimeRange; limit?: number }): Promise { const messages = coreGetRecentMessages(this.db, { limit: options?.limit ?? 50 }) return { messages: messages.map((m) => ({ @@ -108,7 +108,7 @@ export class CoreDataProvider implements ToolDataProvider { return getMembersWithAliases(this.db) } - async getMemberStats(options?: { timeFilter?: TimeFilter; top?: number }): Promise { + async getMemberStats(options?: { timeFilter?: ToolTimeRange; top?: number }): Promise { const top = options?.top ?? 20 const members = getMemberActivity(this.db, options?.timeFilter) return members.slice(0, top).map((m) => ({ @@ -122,7 +122,10 @@ export class CoreDataProvider implements ToolDataProvider { return coreGetMemberNameHistory(this.db, memberId) } - async getTimeStats(type: 'hourly' | 'weekday' | 'daily', options?: { timeFilter?: TimeFilter }): Promise { + async getTimeStats( + type: 'hourly' | 'weekday' | 'daily', + options?: { timeFilter?: ToolTimeRange } + ): Promise { const filter = options?.timeFilter switch (type) { case 'weekday': @@ -139,14 +142,14 @@ export class CoreDataProvider implements ToolDataProvider { return coreGetSegmentMessages(this.db, segmentId, limit) } - async getSegmentSummaries(options?: { limit?: number; timeFilter?: TimeFilter }): Promise { + async getSegmentSummaries(options?: { limit?: number; timeFilter?: ToolTimeRange }): Promise { return coreGetSegmentSummaries(this.db, options) } async getConversationBetween( memberId1: number, memberId2: number, - timeFilter?: TimeFilter, + timeFilter?: ToolTimeRange, limit?: number ): Promise { return coreGetConversationBetween(this.db, memberId1, memberId2, timeFilter, limit) diff --git a/packages/tools/src/types.ts b/packages/tools/src/types.ts index 2100acb2..6249b533 100644 --- a/packages/tools/src/types.ts +++ b/packages/tools/src/types.ts @@ -43,7 +43,7 @@ export interface JsonSchema { // ==================== Time Filter ==================== -export interface TimeFilter { +export interface ToolTimeRange { startTs: number endTs: number } @@ -156,17 +156,17 @@ export interface ToolDataProvider { // === 基础查询 === searchMessages( keywords: string[], - options?: { timeFilter?: TimeFilter; limit?: number; senderId?: number } + options?: { timeFilter?: ToolTimeRange; limit?: number; senderId?: number } ): Promise deepSearchMessages( keywords: string[], - options?: { timeFilter?: TimeFilter; limit?: number; senderId?: number } + options?: { timeFilter?: ToolTimeRange; limit?: number; senderId?: number } ): Promise getSearchMessageContext(messageIds: number[], contextBefore: number, contextAfter: number): Promise - getRecentMessages(options?: { timeFilter?: TimeFilter; limit?: number }): Promise + getRecentMessages(options?: { timeFilter?: ToolTimeRange; limit?: number }): Promise getMessageContext(messageIds: number[], contextSize: number): Promise @@ -176,23 +176,23 @@ export interface ToolDataProvider { // === 成员相关 === getMembers(): Promise - getMemberStats(options?: { timeFilter?: TimeFilter; top?: number }): Promise + getMemberStats(options?: { timeFilter?: ToolTimeRange; top?: number }): Promise getMemberNameHistory(memberId: number): Promise // === 时间统计 === - getTimeStats(type: 'hourly' | 'weekday' | 'daily', options?: { timeFilter?: TimeFilter }): Promise + getTimeStats(type: 'hourly' | 'weekday' | 'daily', options?: { timeFilter?: ToolTimeRange }): Promise // === 段落相关 === getSegmentMessages(segmentId: number, limit?: number): Promise - getSegmentSummaries(options?: { limit?: number; timeFilter?: TimeFilter }): Promise + getSegmentSummaries(options?: { limit?: number; timeFilter?: ToolTimeRange }): Promise // === 对话查询 === getConversationBetween( memberId1: number, memberId2: number, - timeFilter?: TimeFilter, + timeFilter?: ToolTimeRange, limit?: number ): Promise @@ -288,7 +288,7 @@ export interface SegmentResult { export interface ToolExecutionContext { sessionId: string locale?: string - timeFilter?: TimeFilter + timeFilter?: ToolTimeRange abortSignal?: AbortSignal /** 抽象查询接口 */ dataProvider?: ToolDataProvider diff --git a/packages/tools/src/utils/time-params.ts b/packages/tools/src/utils/time-params.ts index 834b33ce..2e0394f9 100644 --- a/packages/tools/src/utils/time-params.ts +++ b/packages/tools/src/utils/time-params.ts @@ -4,7 +4,7 @@ * 从 Electron 工具提取的共享实用函数,处理 start_time/end_time 字符串参数。 */ -import type { TimeFilter } from '../types' +import type { ToolTimeRange } from '../types' export interface ExtendedTimeParams { start_time?: string @@ -13,12 +13,12 @@ export interface ExtendedTimeParams { /** * 解析时间参数,返回时间过滤器 - * 优先级: start_time/end_time > contextTimeFilter + * 优先级: start_time/end_time > contextToolTimeRange */ export function parseExtendedTimeParams( params: ExtendedTimeParams, - contextTimeFilter?: TimeFilter -): TimeFilter | undefined { + contextToolTimeRange?: ToolTimeRange +): ToolTimeRange | undefined { if (params.start_time || params.end_time) { let startTs: number | undefined let endTs: number | undefined @@ -45,5 +45,5 @@ export function parseExtendedTimeParams( } } - return contextTimeFilter + return contextToolTimeRange } diff --git a/src/composables/index.ts b/src/composables/index.ts index 2a002bdc..f36e81ed 100644 --- a/src/composables/index.ts +++ b/src/composables/index.ts @@ -1,7 +1,6 @@ /** * Composables 统一导出 */ -export { useAsyncData, useMultipleAsyncData } from './useAsyncData' export { usePageAnchors, type AnchorItem } from './usePageAnchors' export { useAIChat, type ChatMessage, type SourceMessage } from './useAIChat' export { useToast } from './useToast' diff --git a/src/composables/useAsyncData.ts b/src/composables/useAsyncData.ts deleted file mode 100644 index b607f9ac..00000000 --- a/src/composables/useAsyncData.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { ref, watch, type Ref, type ComputedRef } from 'vue' -import type { TimeFilter } from '@openchatlab/shared-types' - -/** - * 异步数据加载 Composable - * 统一处理数据加载、状态管理和错误处理 - */ - -interface UseAsyncDataOptions { - /** 是否立即加载数据(默认 true) */ - immediate?: boolean - /** 是否深度监听 timeFilter(默认 true) */ - deep?: boolean - /** 加载失败时的错误处理 */ - onError?: (error: Error) => void - /** 默认值 */ - defaultValue?: T -} - -interface UseAsyncDataReturn { - /** 数据 */ - data: Ref - /** 是否正在加载 */ - isLoading: Ref - /** 错误信息 */ - error: Ref - /** 重新加载 */ - reload: () => Promise -} - -/** - * 通用异步数据加载 composable - * @param fetchFn 数据获取函数 - * @param sessionId 会话 ID(响应式) - * @param timeFilter 时间筛选条件(可选,响应式) - * @param options 配置选项 - */ -export function useAsyncData( - fetchFn: (sessionId: string, timeFilter?: TimeFilter) => Promise, - sessionId: Ref | ComputedRef, - timeFilter?: Ref | ComputedRef, - options: UseAsyncDataOptions = {} -): UseAsyncDataReturn { - const { immediate = true, deep = true, onError, defaultValue } = options - - const data = ref(defaultValue ?? null) as Ref - const isLoading = ref(false) - const error = ref(null) - - async function load() { - const sid = sessionId.value - if (!sid) return - - isLoading.value = true - error.value = null - - try { - data.value = await fetchFn(sid, timeFilter?.value) - } catch (e) { - const err = e instanceof Error ? e : new Error(String(e)) - error.value = err - if (onError) { - onError(err) - } else { - console.error('数据加载失败:', err) - } - } finally { - isLoading.value = false - } - } - - // 监听 sessionId 和 timeFilter 变化 - watch( - () => [sessionId.value, timeFilter?.value], - () => { - if (sessionId.value) { - load() - } - }, - { immediate, deep } - ) - - return { - data, - isLoading, - error, - reload: load, - } -} - -/** - * 批量加载多个异步数据 - * @param loaders 加载函数数组 - */ -export function useMultipleAsyncData(loaders: Array<() => Promise>) { - const isLoading = ref(false) - - async function loadAll() { - isLoading.value = true - try { - await Promise.all(loaders.map((loader) => loader())) - } finally { - isLoading.value = false - } - } - - return { - isLoading, - loadAll, - } -} diff --git a/src/composables/useToast.ts b/src/composables/useToast.ts index 4b48e058..bef24d69 100644 --- a/src/composables/useToast.ts +++ b/src/composables/useToast.ts @@ -1,69 +1,26 @@ import { useToast as useNuxtToast } from '@nuxt/ui/composables' -interface AppToastOptions { - description?: string - duration?: number - actions?: Array<{ - label: string - icon?: string - onClick: () => void - }> -} - -interface AppToastPayload extends AppToastOptions { +type ToastPayload = { title: string color?: 'primary' | 'success' | 'warning' | 'error' | 'neutral' + description?: string + duration?: number + actions?: Array<{ label: string; icon?: string; onClick: () => void }> } -const DEFAULT_TOAST_DURATION = 2000 +const DEFAULT_DURATION = 2000 export function useToast() { const toast = useNuxtToast() - - function add(payload: AppToastPayload) { - toast.add({ - ...payload, - duration: payload.duration ?? DEFAULT_TOAST_DURATION, - }) - } - - function success(title: string, options: AppToastOptions = {}) { - add({ - title, - color: 'success', - ...options, - }) - } - - function fail(title: string, options: AppToastOptions = {}) { - add({ - title, - color: 'error', - ...options, - }) - } - - function info(title: string, options: AppToastOptions = {}) { - add({ - title, - color: 'primary', - ...options, - }) - } - - function warn(title: string, options: AppToastOptions = {}) { - add({ - title, - color: 'warning', - ...options, - }) - } - + const add = (payload: ToastPayload) => toast.add({ duration: DEFAULT_DURATION, ...payload }) return { add, - success, - fail, - info, - warn, + success: (title: string, opts: Omit = {}) => + add({ title, color: 'success', ...opts }), + fail: (title: string, opts: Omit = {}) => add({ title, color: 'error', ...opts }), + info: (title: string, opts: Omit = {}) => + add({ title, color: 'primary', ...opts }), + warn: (title: string, opts: Omit = {}) => + add({ title, color: 'warning', ...opts }), } }