koishi-plugin-media-luna 0.0.3 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/client/api.ts +36 -86
  2. package/client/components/ChannelsView.vue +28 -208
  3. package/client/components/GenerateView.vue +46 -11
  4. package/client/components/HistoryGallery.vue +47 -12
  5. package/client/components/PresetsView.vue +26 -200
  6. package/client/components/SettingsView.vue +26 -0
  7. package/client/components/TasksView.vue +15 -68
  8. package/client/composables/index.ts +14 -0
  9. package/client/composables/useDataFetch.ts +102 -0
  10. package/client/composables/useDialog.ts +58 -0
  11. package/client/composables/useLoading.ts +84 -0
  12. package/client/composables/usePagination.ts +110 -0
  13. package/client/constants/categories.ts +36 -0
  14. package/client/constants/index.ts +5 -0
  15. package/client/constants/phases.ts +44 -0
  16. package/client/styles/shared.css +42 -0
  17. package/client/types.ts +73 -0
  18. package/dist/index.js +1 -1
  19. package/dist/style.css +1 -1
  20. package/lib/core/api/plugin-api.d.ts.map +1 -1
  21. package/lib/core/api/plugin-api.js +31 -0
  22. package/lib/core/api/plugin-api.js.map +1 -1
  23. package/lib/core/medialuna.service.d.ts.map +1 -1
  24. package/lib/core/medialuna.service.js +21 -17
  25. package/lib/core/medialuna.service.js.map +1 -1
  26. package/lib/core/pipeline/generation-pipeline.d.ts +4 -0
  27. package/lib/core/pipeline/generation-pipeline.d.ts.map +1 -1
  28. package/lib/core/pipeline/generation-pipeline.js +61 -20
  29. package/lib/core/pipeline/generation-pipeline.js.map +1 -1
  30. package/lib/core/plugin-loader.d.ts +42 -0
  31. package/lib/core/plugin-loader.d.ts.map +1 -1
  32. package/lib/core/plugin-loader.js +231 -2
  33. package/lib/core/plugin-loader.js.map +1 -1
  34. package/lib/core/request.service.d.ts +4 -1
  35. package/lib/core/request.service.d.ts.map +1 -1
  36. package/lib/core/request.service.js +11 -1
  37. package/lib/core/request.service.js.map +1 -1
  38. package/lib/core/types.d.ts +10 -0
  39. package/lib/core/types.d.ts.map +1 -1
  40. package/lib/plugins/README.md +716 -0
  41. package/lib/plugins/cache/middleware.d.ts.map +1 -1
  42. package/lib/plugins/cache/middleware.js +2 -0
  43. package/lib/plugins/cache/middleware.js.map +1 -1
  44. package/lib/plugins/task/middleware.d.ts.map +1 -1
  45. package/lib/plugins/task/middleware.js +20 -2
  46. package/lib/plugins/task/middleware.js.map +1 -1
  47. package/lib/types/index.d.ts +4 -0
  48. package/lib/types/index.d.ts.map +1 -1
  49. package/package.json +1 -1
@@ -0,0 +1,102 @@
1
+ /**
2
+ * 数据获取 composable
3
+ * 结合 useLoading 和 usePagination,提供完整的数据获取能力
4
+ */
5
+ import { ref, Ref, watch, onMounted } from 'vue'
6
+ import { useLoading, UseLoadingOptions } from './useLoading'
7
+ import { usePagination, UsePaginationOptions } from './usePagination'
8
+
9
+ export interface UseDataFetchOptions<T> extends UseLoadingOptions, UsePaginationOptions {
10
+ /** 是否在挂载时自动获取 */
11
+ immediate?: boolean
12
+ /** 是否启用分页 */
13
+ paginated?: boolean
14
+ /** 数据获取函数 */
15
+ fetchFn: (params: { offset: number; limit: number }) => Promise<{ items: T[]; total: number } | T[]>
16
+ }
17
+
18
+ export interface UseDataFetchReturn<T> {
19
+ /** 数据列表 */
20
+ data: Ref<T[]>
21
+ /** 加载状态 */
22
+ loading: Ref<boolean>
23
+ /** 错误信息 */
24
+ error: Ref<string | null>
25
+ /** 分页信息(如果启用) */
26
+ pagination: ReturnType<typeof usePagination> | null
27
+ /** 刷新数据 */
28
+ refresh: () => Promise<void>
29
+ /** 重置并刷新(回到第一页) */
30
+ reset: () => Promise<void>
31
+ }
32
+
33
+ export function useDataFetch<T>(options: UseDataFetchOptions<T>): UseDataFetchReturn<T> {
34
+ const {
35
+ immediate = true,
36
+ paginated = false,
37
+ fetchFn,
38
+ ...loadingOptions
39
+ } = options
40
+
41
+ const data = ref<T[]>([]) as Ref<T[]>
42
+ const { loading, error, withLoading } = useLoading(loadingOptions)
43
+ const pagination = paginated ? usePagination(options) : null
44
+
45
+ const fetchData = async () => {
46
+ const params = pagination
47
+ ? { offset: pagination.offset.value, limit: pagination.pageSize.value }
48
+ : { offset: 0, limit: 1000 }
49
+
50
+ await withLoading(async () => {
51
+ const result = await fetchFn(params)
52
+
53
+ if (Array.isArray(result)) {
54
+ // 非分页响应
55
+ data.value = result
56
+ if (pagination) {
57
+ pagination.setTotal(result.length)
58
+ }
59
+ } else {
60
+ // 分页响应
61
+ data.value = result.items
62
+ if (pagination) {
63
+ pagination.setTotal(result.total)
64
+ }
65
+ }
66
+ })
67
+ }
68
+
69
+ const refresh = async () => {
70
+ await fetchData()
71
+ }
72
+
73
+ const reset = async () => {
74
+ if (pagination) {
75
+ pagination.reset()
76
+ }
77
+ await fetchData()
78
+ }
79
+
80
+ // 监听分页变化
81
+ if (pagination) {
82
+ watch([pagination.page, pagination.pageSize], () => {
83
+ fetchData()
84
+ })
85
+ }
86
+
87
+ // 自动获取
88
+ if (immediate) {
89
+ onMounted(() => {
90
+ fetchData()
91
+ })
92
+ }
93
+
94
+ return {
95
+ data,
96
+ loading,
97
+ error,
98
+ pagination,
99
+ refresh,
100
+ reset
101
+ }
102
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * 对话框控制 composable
3
+ */
4
+ import { ref, Ref } from 'vue'
5
+
6
+ export interface UseDialogReturn<T = any> {
7
+ /** 对话框是否可见 */
8
+ visible: Ref<boolean>
9
+ /** 当前数据(编辑模式) */
10
+ data: Ref<T | null>
11
+ /** 是否为编辑模式 */
12
+ isEdit: Ref<boolean>
13
+ /** 打开对话框(新建模式) */
14
+ open: () => void
15
+ /** 打开对话框(编辑模式) */
16
+ openEdit: (item: T) => void
17
+ /** 关闭对话框 */
18
+ close: () => void
19
+ /** 重置数据 */
20
+ reset: () => void
21
+ }
22
+
23
+ export function useDialog<T = any>(defaultData?: T): UseDialogReturn<T> {
24
+ const visible = ref(false)
25
+ const data = ref<T | null>(null) as Ref<T | null>
26
+ const isEdit = ref(false)
27
+
28
+ const open = () => {
29
+ data.value = defaultData ? { ...defaultData } : null
30
+ isEdit.value = false
31
+ visible.value = true
32
+ }
33
+
34
+ const openEdit = (item: T) => {
35
+ data.value = { ...item }
36
+ isEdit.value = true
37
+ visible.value = true
38
+ }
39
+
40
+ const close = () => {
41
+ visible.value = false
42
+ }
43
+
44
+ const reset = () => {
45
+ data.value = null
46
+ isEdit.value = false
47
+ }
48
+
49
+ return {
50
+ visible,
51
+ data,
52
+ isEdit,
53
+ open,
54
+ openEdit,
55
+ close,
56
+ reset
57
+ }
58
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * 加载状态管理 composable
3
+ * 提供统一的 loading、error 状态管理
4
+ */
5
+ import { ref, Ref } from 'vue'
6
+ import { message } from '@koishijs/client'
7
+
8
+ export interface UseLoadingOptions {
9
+ /** 初始加载状态 */
10
+ initialLoading?: boolean
11
+ /** 错误时是否显示 toast */
12
+ showErrorToast?: boolean
13
+ /** 默认错误消息 */
14
+ defaultErrorMessage?: string
15
+ }
16
+
17
+ export interface UseLoadingReturn {
18
+ /** 是否正在加载 */
19
+ loading: Ref<boolean>
20
+ /** 错误信息 */
21
+ error: Ref<string | null>
22
+ /** 包装异步函数,自动处理 loading 和 error 状态 */
23
+ withLoading: <T>(fn: () => Promise<T>, errorMessage?: string) => Promise<T | undefined>
24
+ /** 手动设置加载状态 */
25
+ setLoading: (value: boolean) => void
26
+ /** 手动设置错误 */
27
+ setError: (message: string | null) => void
28
+ /** 清除错误 */
29
+ clearError: () => void
30
+ }
31
+
32
+ export function useLoading(options: UseLoadingOptions = {}): UseLoadingReturn {
33
+ const {
34
+ initialLoading = false,
35
+ showErrorToast = true,
36
+ defaultErrorMessage = '操作失败'
37
+ } = options
38
+
39
+ const loading = ref(initialLoading)
40
+ const error = ref<string | null>(null)
41
+
42
+ const setLoading = (value: boolean) => {
43
+ loading.value = value
44
+ }
45
+
46
+ const setError = (msg: string | null) => {
47
+ error.value = msg
48
+ if (msg && showErrorToast) {
49
+ message.error(msg)
50
+ }
51
+ }
52
+
53
+ const clearError = () => {
54
+ error.value = null
55
+ }
56
+
57
+ const withLoading = async <T>(
58
+ fn: () => Promise<T>,
59
+ errorMessage?: string
60
+ ): Promise<T | undefined> => {
61
+ loading.value = true
62
+ error.value = null
63
+
64
+ try {
65
+ const result = await fn()
66
+ return result
67
+ } catch (e) {
68
+ const msg = e instanceof Error ? e.message : (errorMessage || defaultErrorMessage)
69
+ setError(msg)
70
+ return undefined
71
+ } finally {
72
+ loading.value = false
73
+ }
74
+ }
75
+
76
+ return {
77
+ loading,
78
+ error,
79
+ withLoading,
80
+ setLoading,
81
+ setError,
82
+ clearError
83
+ }
84
+ }
@@ -0,0 +1,110 @@
1
+ /**
2
+ * 分页管理 composable
3
+ * 提供统一的分页状态和控制
4
+ */
5
+ import { ref, computed, Ref, ComputedRef } from 'vue'
6
+
7
+ export interface UsePaginationOptions {
8
+ /** 初始页码(从 1 开始) */
9
+ initialPage?: number
10
+ /** 每页数量 */
11
+ initialPageSize?: number
12
+ /** 可选的每页数量选项 */
13
+ pageSizeOptions?: number[]
14
+ }
15
+
16
+ export interface UsePaginationReturn {
17
+ /** 当前页码(从 1 开始) */
18
+ page: Ref<number>
19
+ /** 每页数量 */
20
+ pageSize: Ref<number>
21
+ /** 总数量 */
22
+ total: Ref<number>
23
+ /** 总页数 */
24
+ totalPages: ComputedRef<number>
25
+ /** 是否有下一页 */
26
+ hasNext: ComputedRef<boolean>
27
+ /** 是否有上一页 */
28
+ hasPrev: ComputedRef<boolean>
29
+ /** 当前页的 offset(用于 API 调用) */
30
+ offset: ComputedRef<number>
31
+ /** 每页数量选项 */
32
+ pageSizeOptions: number[]
33
+ /** 设置总数 */
34
+ setTotal: (value: number) => void
35
+ /** 下一页 */
36
+ nextPage: () => void
37
+ /** 上一页 */
38
+ prevPage: () => void
39
+ /** 跳转到指定页 */
40
+ goToPage: (pageNum: number) => void
41
+ /** 重置到第一页 */
42
+ reset: () => void
43
+ /** 更改每页数量(会重置到第一页) */
44
+ changePageSize: (size: number) => void
45
+ }
46
+
47
+ export function usePagination(options: UsePaginationOptions = {}): UsePaginationReturn {
48
+ const {
49
+ initialPage = 1,
50
+ initialPageSize = 20,
51
+ pageSizeOptions = [10, 20, 50, 100]
52
+ } = options
53
+
54
+ const page = ref(initialPage)
55
+ const pageSize = ref(initialPageSize)
56
+ const total = ref(0)
57
+
58
+ const totalPages = computed(() => Math.ceil(total.value / pageSize.value) || 1)
59
+ const hasNext = computed(() => page.value < totalPages.value)
60
+ const hasPrev = computed(() => page.value > 1)
61
+ const offset = computed(() => (page.value - 1) * pageSize.value)
62
+
63
+ const setTotal = (value: number) => {
64
+ total.value = value
65
+ }
66
+
67
+ const nextPage = () => {
68
+ if (hasNext.value) {
69
+ page.value++
70
+ }
71
+ }
72
+
73
+ const prevPage = () => {
74
+ if (hasPrev.value) {
75
+ page.value--
76
+ }
77
+ }
78
+
79
+ const goToPage = (pageNum: number) => {
80
+ if (pageNum >= 1 && pageNum <= totalPages.value) {
81
+ page.value = pageNum
82
+ }
83
+ }
84
+
85
+ const reset = () => {
86
+ page.value = 1
87
+ }
88
+
89
+ const changePageSize = (size: number) => {
90
+ pageSize.value = size
91
+ page.value = 1
92
+ }
93
+
94
+ return {
95
+ page,
96
+ pageSize,
97
+ total,
98
+ totalPages,
99
+ hasNext,
100
+ hasPrev,
101
+ offset,
102
+ pageSizeOptions,
103
+ setTotal,
104
+ nextPage,
105
+ prevPage,
106
+ goToPage,
107
+ reset,
108
+ changePageSize
109
+ }
110
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * 中间件分类定义
3
+ */
4
+
5
+ export interface CategoryDefinition {
6
+ id: string
7
+ label: string
8
+ icon?: string
9
+ order: number
10
+ }
11
+
12
+ /** 中间件分类列表 */
13
+ export const CATEGORIES: CategoryDefinition[] = [
14
+ { id: 'billing', label: '计费模块', icon: 'coins', order: 1 },
15
+ { id: 'transform', label: '转换处理', icon: 'edit', order: 2 },
16
+ { id: 'cache', label: '缓存模块', icon: 'folder', order: 3 },
17
+ { id: 'preset', label: '预设模块', icon: 'bookmark', order: 4 },
18
+ { id: 'request', label: '请求模块', icon: 'link', order: 5 },
19
+ { id: 'task', label: '任务模块', icon: 'clipboard', order: 6 },
20
+ { id: 'other', label: '其他', icon: 'more', order: 99 }
21
+ ]
22
+
23
+ /** 分类 ID 到标签的映射 */
24
+ export const CATEGORY_LABELS: Record<string, string> = Object.fromEntries(
25
+ CATEGORIES.map(c => [c.id, c.label])
26
+ )
27
+
28
+ /** 获取分类定义 */
29
+ export function getCategory(id: string): CategoryDefinition | undefined {
30
+ return CATEGORIES.find(c => c.id === id)
31
+ }
32
+
33
+ /** 获取分类标签 */
34
+ export function getCategoryLabel(id: string): string {
35
+ return CATEGORY_LABELS[id] || id
36
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Constants 统一导出
3
+ */
4
+ export * from './phases'
5
+ export * from './categories'
@@ -0,0 +1,44 @@
1
+ /**
2
+ * 中间件生命周期阶段定义
3
+ */
4
+
5
+ export interface PhaseDefinition {
6
+ id: string
7
+ label: string
8
+ color: string
9
+ order: number
10
+ }
11
+
12
+ /** 生命周期阶段列表 */
13
+ export const PHASES: PhaseDefinition[] = [
14
+ { id: 'lifecycle-prepare', label: '准备', color: '#909399', order: 1 },
15
+ { id: 'lifecycle-pre-request', label: '预处理', color: '#e6a23c', order: 2 },
16
+ { id: 'lifecycle-request', label: '请求', color: '#409eff', order: 3 },
17
+ { id: 'lifecycle-post-request', label: '后处理', color: '#67c23a', order: 4 },
18
+ { id: 'lifecycle-finalize', label: '完成', color: '#909399', order: 5 }
19
+ ]
20
+
21
+ /** 阶段 ID 到标签的映射 */
22
+ export const PHASE_LABELS: Record<string, string> = Object.fromEntries(
23
+ PHASES.map(p => [p.id, p.label])
24
+ )
25
+
26
+ /** 阶段 ID 到颜色的映射 */
27
+ export const PHASE_COLORS: Record<string, string> = Object.fromEntries(
28
+ PHASES.map(p => [p.id, p.color])
29
+ )
30
+
31
+ /** 获取阶段定义 */
32
+ export function getPhase(id: string): PhaseDefinition | undefined {
33
+ return PHASES.find(p => p.id === id)
34
+ }
35
+
36
+ /** 获取阶段标签 */
37
+ export function getPhaseLabel(id: string): string {
38
+ return PHASE_LABELS[id] || id
39
+ }
40
+
41
+ /** 获取阶段颜色 */
42
+ export function getPhaseColor(id: string): string {
43
+ return PHASE_COLORS[id] || '#909399'
44
+ }
@@ -256,3 +256,45 @@
256
256
  .ml-fade-in {
257
257
  animation: ml-fade-in 0.3s ease;
258
258
  }
259
+
260
+ /* ========== 隐藏式滚动条 ========== */
261
+ /* 通用隐藏式滚动条样式 - 默认隐藏,悬停时显示 */
262
+ .ml-scrollbar,
263
+ .ml-view-content {
264
+ scrollbar-width: thin;
265
+ scrollbar-color: transparent transparent;
266
+ }
267
+
268
+ .ml-scrollbar:hover,
269
+ .ml-view-content:hover {
270
+ scrollbar-color: var(--k-color-border) transparent;
271
+ }
272
+
273
+ /* Webkit 浏览器滚动条 */
274
+ .ml-scrollbar::-webkit-scrollbar,
275
+ .ml-view-content::-webkit-scrollbar {
276
+ width: 6px;
277
+ height: 6px;
278
+ }
279
+
280
+ .ml-scrollbar::-webkit-scrollbar-track,
281
+ .ml-view-content::-webkit-scrollbar-track {
282
+ background: transparent;
283
+ }
284
+
285
+ .ml-scrollbar::-webkit-scrollbar-thumb,
286
+ .ml-view-content::-webkit-scrollbar-thumb {
287
+ background-color: transparent;
288
+ border-radius: 3px;
289
+ transition: background-color 0.2s;
290
+ }
291
+
292
+ .ml-scrollbar:hover::-webkit-scrollbar-thumb,
293
+ .ml-view-content:hover::-webkit-scrollbar-thumb {
294
+ background-color: var(--k-color-border);
295
+ }
296
+
297
+ .ml-scrollbar::-webkit-scrollbar-thumb:hover,
298
+ .ml-view-content::-webkit-scrollbar-thumb:hover {
299
+ background-color: var(--k-color-text-description);
300
+ }
package/client/types.ts CHANGED
@@ -44,6 +44,11 @@ export type {
44
44
  CardFieldsResponse
45
45
  } from '../src/types'
46
46
 
47
+ // 从核心模块导入插件相关类型
48
+ export type {
49
+ PluginInfo
50
+ } from '../src/core/types'
51
+
47
52
  // 类型别名(保持向后兼容)
48
53
  import type { ConfigField, CardFieldsResponse as _CardFieldsResponse } from '../src/types'
49
54
  export type FieldDefinition = ConfigField
@@ -73,6 +78,74 @@ export interface RemotePresetConfig {
73
78
  deleteRemoved: boolean
74
79
  }
75
80
 
81
+ // ============ 画廊类型 ============
82
+
83
+ /** 画廊项目 */
84
+ export interface GalleryItem {
85
+ id: number
86
+ prompt: string
87
+ images: string[]
88
+ createdAt: Date
89
+ channelId: number
90
+ }
91
+
92
+ /** 画廊响应 */
93
+ export interface GalleryResponse {
94
+ items: GalleryItem[]
95
+ total: number
96
+ hasMore: boolean
97
+ }
98
+
99
+ /** 最近图片 */
100
+ export interface RecentImage {
101
+ taskId: number
102
+ url: string
103
+ prompt: string
104
+ createdAt: Date
105
+ }
106
+
107
+ // ============ 缓存类型 ============
108
+
109
+ /** 缓存文件信息 */
110
+ export interface CacheFileInfo {
111
+ id: string
112
+ url?: string
113
+ filename: string
114
+ mime: string
115
+ size: number
116
+ createdAt?: Date
117
+ accessedAt?: Date
118
+ }
119
+
120
+ /** 缓存统计 */
121
+ export interface CacheStats {
122
+ totalFiles: number
123
+ totalSizeMB: number
124
+ maxSizeMB: number
125
+ oldestAccess: Date | null
126
+ newestAccess: Date | null
127
+ }
128
+
129
+ // ============ 外部插件类型 ============
130
+
131
+ /** 外部插件信息 */
132
+ export interface ExternalPluginInfo {
133
+ id: string
134
+ moduleName: string
135
+ name: string
136
+ }
137
+
138
+ // ============ 认证类型 ============
139
+
140
+ /** 当前用户信息 */
141
+ export interface CurrentUser {
142
+ loggedIn: boolean
143
+ source?: string
144
+ uid?: number
145
+ name?: string
146
+ authority?: number
147
+ }
148
+
76
149
  declare module '@koishijs/client' {
77
150
  interface Events {
78
151
  // 渠道