fix: removed duplicated type declarations
This commit is contained in:
@@ -1,8 +1,9 @@
|
|||||||
import { contextBridge, ipcRenderer } from 'electron';
|
import { contextBridge, ipcRenderer } from 'electron';
|
||||||
|
import type { ElectronAPI } from './shared/electronApi';
|
||||||
|
|
||||||
// Expose protected methods that allow the renderer process to use
|
// Expose protected methods that allow the renderer process to use
|
||||||
// ipcRenderer without exposing the entire object
|
// ipcRenderer without exposing the entire object
|
||||||
contextBridge.exposeInMainWorld('electronAPI', {
|
export const electronAPI: ElectronAPI = {
|
||||||
// Projects
|
// Projects
|
||||||
projects: {
|
projects: {
|
||||||
create: (data: { name: string; description?: string; slug?: string; dataPath?: string }) => ipcRenderer.invoke('projects:create', data),
|
create: (data: { name: string; description?: string; slug?: string; dataPath?: string }) => ipcRenderer.invoke('projects:create', data),
|
||||||
@@ -82,6 +83,17 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
|||||||
rebuild: () => ipcRenderer.invoke('postMedia:rebuild'),
|
rebuild: () => ipcRenderer.invoke('postMedia:rebuild'),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Sync
|
||||||
|
sync: {
|
||||||
|
configure: (config: unknown) => ipcRenderer.invoke('sync:configure', config),
|
||||||
|
start: (direction?: 'push' | 'pull' | 'bidirectional') => ipcRenderer.invoke('sync:start', direction),
|
||||||
|
getStatus: () => ipcRenderer.invoke('sync:getStatus'),
|
||||||
|
isConfigured: () => ipcRenderer.invoke('sync:isConfigured'),
|
||||||
|
getPendingCount: () => ipcRenderer.invoke('sync:getPendingCount'),
|
||||||
|
getLog: (limit?: number) => ipcRenderer.invoke('sync:getLog', limit),
|
||||||
|
stopAutoSync: () => ipcRenderer.invoke('sync:stopAutoSync'),
|
||||||
|
},
|
||||||
|
|
||||||
// Tasks
|
// Tasks
|
||||||
tasks: {
|
tasks: {
|
||||||
getAll: () => ipcRenderer.invoke('tasks:getAll'),
|
getAll: () => ipcRenderer.invoke('tasks:getAll'),
|
||||||
@@ -242,8 +254,8 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
|||||||
ipcRenderer.on('chat-stream-delta', subscription);
|
ipcRenderer.on('chat-stream-delta', subscription);
|
||||||
return () => ipcRenderer.removeListener('chat-stream-delta', subscription);
|
return () => ipcRenderer.removeListener('chat-stream-delta', subscription);
|
||||||
},
|
},
|
||||||
onToolCall: (callback: (data: { conversationId: string; toolCall: unknown }) => void) => {
|
onToolCall: (callback: (data: { conversationId: string; toolCall: { name: string; arguments: Record<string, unknown> } }) => void) => {
|
||||||
const subscription = (_event: Electron.IpcRendererEvent, data: { conversationId: string; toolCall: unknown }) => callback(data);
|
const subscription = (_event: Electron.IpcRendererEvent, data: { conversationId: string; toolCall: { name: string; arguments: Record<string, unknown> } }) => callback(data);
|
||||||
ipcRenderer.on('chat-tool-call', subscription);
|
ipcRenderer.on('chat-tool-call', subscription);
|
||||||
return () => ipcRenderer.removeListener('chat-tool-call', subscription);
|
return () => ipcRenderer.removeListener('chat-tool-call', subscription);
|
||||||
},
|
},
|
||||||
@@ -269,192 +281,6 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
|||||||
once: (channel: string, callback: (...args: unknown[]) => void) => {
|
once: (channel: string, callback: (...args: unknown[]) => void) => {
|
||||||
ipcRenderer.once(channel, (_event, ...args) => callback(...args));
|
ipcRenderer.once(channel, (_event, ...args) => callback(...args));
|
||||||
},
|
},
|
||||||
});
|
};
|
||||||
|
|
||||||
// Type definitions for the exposed API
|
contextBridge.exposeInMainWorld('electronAPI', electronAPI);
|
||||||
export interface ElectronAPI {
|
|
||||||
projects: {
|
|
||||||
create: (data: { name: string; description?: string; slug?: string; dataPath?: string }) => Promise<unknown>;
|
|
||||||
update: (id: string, data: unknown) => Promise<unknown>;
|
|
||||||
delete: (id: string) => Promise<boolean>;
|
|
||||||
get: (id: string) => Promise<unknown>;
|
|
||||||
getAll: () => Promise<unknown[]>;
|
|
||||||
getActive: () => Promise<unknown>;
|
|
||||||
setActive: (id: string) => Promise<unknown>;
|
|
||||||
};
|
|
||||||
posts: {
|
|
||||||
create: (data: unknown) => Promise<unknown>;
|
|
||||||
update: (id: string, data: unknown) => Promise<unknown>;
|
|
||||||
delete: (id: string) => Promise<boolean>;
|
|
||||||
get: (id: string) => Promise<unknown>;
|
|
||||||
getAll: () => Promise<unknown[]>;
|
|
||||||
getByStatus: (status: string) => Promise<unknown[]>;
|
|
||||||
publish: (id: string) => Promise<unknown>;
|
|
||||||
unpublish: (id: string) => Promise<unknown>;
|
|
||||||
rebuildFromFiles: () => Promise<void>;
|
|
||||||
search: (query: string) => Promise<unknown[]>;
|
|
||||||
filter: (filter: unknown) => Promise<unknown[]>;
|
|
||||||
getTags: () => Promise<string[]>;
|
|
||||||
getCategories: () => Promise<string[]>;
|
|
||||||
getByYearMonth: () => Promise<{ year: number; month: number; count: number }[]>;
|
|
||||||
getTagsWithCounts: () => Promise<{ tag: string; count: number }[]>;
|
|
||||||
getCategoriesWithCounts: () => Promise<{ category: string; count: number }[]>;
|
|
||||||
getDashboardStats: () => Promise<{ totalPosts: number; draftCount: number; publishedCount: number; archivedCount: number }>;
|
|
||||||
getLinksTo: (id: string) => Promise<{ id: string; title: string; slug: string }[]>;
|
|
||||||
getLinkedBy: (id: string) => Promise<{ id: string; title: string; slug: string }[]>;
|
|
||||||
rebuildLinks: () => Promise<void>;
|
|
||||||
};
|
|
||||||
media: {
|
|
||||||
import: (sourcePath: string, metadata?: unknown) => Promise<unknown>;
|
|
||||||
importDialog: () => Promise<unknown[]>;
|
|
||||||
update: (id: string, data: unknown) => Promise<unknown>;
|
|
||||||
delete: (id: string) => Promise<boolean>;
|
|
||||||
get: (id: string) => Promise<unknown>;
|
|
||||||
getAll: () => Promise<unknown[]>;
|
|
||||||
rebuildFromFiles: () => Promise<void>;
|
|
||||||
};
|
|
||||||
dropbox: {
|
|
||||||
configure: (config: unknown) => Promise<void>;
|
|
||||||
isConfigured: () => Promise<boolean>;
|
|
||||||
getStatus: () => Promise<string>;
|
|
||||||
syncAll: () => Promise<unknown>;
|
|
||||||
startWatching: () => Promise<void>;
|
|
||||||
stopWatching: () => Promise<void>;
|
|
||||||
startPolling: () => Promise<void>;
|
|
||||||
stopPolling: () => Promise<void>;
|
|
||||||
getConflicts: () => Promise<unknown[]>;
|
|
||||||
resolveConflict: (conflictId: string, resolution: string) => Promise<void>;
|
|
||||||
getLastSyncTime: () => Promise<string | null>;
|
|
||||||
};
|
|
||||||
tasks: {
|
|
||||||
getAll: () => Promise<unknown[]>;
|
|
||||||
getRunning: () => Promise<unknown[]>;
|
|
||||||
cancel: (taskId: string) => Promise<boolean>;
|
|
||||||
clearCompleted: () => Promise<void>;
|
|
||||||
};
|
|
||||||
app: {
|
|
||||||
getDataPaths: () => Promise<{ database: string; posts: string; media: string }>;
|
|
||||||
openFolder: (folderPath: string) => Promise<string>;
|
|
||||||
showItemInFolder: (itemPath: string) => Promise<void>;
|
|
||||||
selectFolder: (title?: string) => Promise<string | null>;
|
|
||||||
getDefaultProjectPath: (projectId: string) => Promise<string>;
|
|
||||||
};
|
|
||||||
meta: {
|
|
||||||
getTags: () => Promise<string[]>;
|
|
||||||
getCategories: () => Promise<string[]>;
|
|
||||||
addTag: (tag: string) => Promise<string[]>;
|
|
||||||
removeTag: (tag: string) => Promise<string[]>;
|
|
||||||
addCategory: (category: string) => Promise<string[]>;
|
|
||||||
removeCategory: (category: string) => Promise<string[]>;
|
|
||||||
syncOnStartup: () => Promise<{ tags: string[]; categories: string[] }>;
|
|
||||||
};
|
|
||||||
tags: {
|
|
||||||
getAll: () => Promise<unknown[]>;
|
|
||||||
getWithCounts: () => Promise<unknown[]>;
|
|
||||||
get: (id: string) => Promise<unknown>;
|
|
||||||
getByName: (name: string) => Promise<unknown>;
|
|
||||||
create: (data: { name: string; color?: string }) => Promise<unknown>;
|
|
||||||
update: (id: string, data: { name?: string; color?: string | null }) => Promise<unknown>;
|
|
||||||
delete: (id: string) => Promise<boolean>;
|
|
||||||
merge: (sourceTagIds: string[], targetTagId: string) => Promise<void>;
|
|
||||||
rename: (id: string, newName: string) => Promise<unknown>;
|
|
||||||
getPostsWithTag: (tagId: string) => Promise<unknown[]>;
|
|
||||||
syncFromPosts: () => Promise<void>;
|
|
||||||
};
|
|
||||||
import: {
|
|
||||||
selectAndAnalyze: (uploadsFolder?: string) => Promise<unknown>;
|
|
||||||
analyzeFile: (filePath: string, uploadsFolder?: string) => Promise<unknown>;
|
|
||||||
selectUploadsFolder: () => Promise<string | null>;
|
|
||||||
execute: (reportJson: string, uploadsFolder?: string) => Promise<{ taskId: string; totalItems: number }>;
|
|
||||||
onProgress: (callback: (data: { step: string; detail?: string }) => void) => () => void;
|
|
||||||
onExecutionProgress: (callback: (data: {
|
|
||||||
taskId: string;
|
|
||||||
phase: string;
|
|
||||||
current: number;
|
|
||||||
total: number;
|
|
||||||
detail?: string;
|
|
||||||
eta?: number;
|
|
||||||
}) => void) => () => void;
|
|
||||||
};
|
|
||||||
importDefinitions: {
|
|
||||||
create: (name?: string) => Promise<unknown>;
|
|
||||||
get: (id: string) => Promise<unknown>;
|
|
||||||
getAll: () => Promise<unknown[]>;
|
|
||||||
update: (id: string, updates: unknown) => Promise<unknown>;
|
|
||||||
delete: (id: string) => Promise<boolean>;
|
|
||||||
};
|
|
||||||
metadataDiff: {
|
|
||||||
getStats: () => Promise<{
|
|
||||||
totalPosts: number;
|
|
||||||
publishedPosts: number;
|
|
||||||
draftPosts: number;
|
|
||||||
totalMedia: number;
|
|
||||||
}>;
|
|
||||||
scan: () => Promise<{
|
|
||||||
totalScanned: number;
|
|
||||||
postsWithDifferences: number;
|
|
||||||
differences: Array<{
|
|
||||||
postId: string;
|
|
||||||
title: string;
|
|
||||||
slug: string;
|
|
||||||
filePath?: string;
|
|
||||||
hasDifferences: boolean;
|
|
||||||
differences: Record<string, { dbValue: unknown; fileValue: unknown }>;
|
|
||||||
}>;
|
|
||||||
groups: Array<{
|
|
||||||
field: string;
|
|
||||||
label: string;
|
|
||||||
posts: Array<{
|
|
||||||
postId: string;
|
|
||||||
title: string;
|
|
||||||
slug: string;
|
|
||||||
dbValue: unknown;
|
|
||||||
fileValue: unknown;
|
|
||||||
}>;
|
|
||||||
}>;
|
|
||||||
}>;
|
|
||||||
syncDbToFile: (postIds: string[], groupLabel: string) => Promise<{ success: number; failed: number }>;
|
|
||||||
syncFileToDb: (postIds: string[], field: string, groupLabel: string) => Promise<{ success: number; failed: number }>;
|
|
||||||
};
|
|
||||||
chat: {
|
|
||||||
// API Key Management
|
|
||||||
checkReady: () => Promise<{ ready: boolean; error?: string; backend?: string }>;
|
|
||||||
validateApiKey: (apiKey: string) => Promise<{ isValid: boolean; models: Array<{ id: string; name: string }> }>;
|
|
||||||
setApiKey: (apiKey: string) => Promise<{ success: boolean; error?: string }>;
|
|
||||||
getApiKey: () => Promise<{ hasKey: boolean; maskedKey: string }>;
|
|
||||||
|
|
||||||
// Settings
|
|
||||||
getAvailableModels: () => Promise<{ success: boolean; models?: Array<{ id: string; name: string }>; selectedModel?: string; error?: string }>;
|
|
||||||
setDefaultModel: (modelId: string) => Promise<{ success: boolean; error?: string }>;
|
|
||||||
getSystemPrompt: () => Promise<{ success: boolean; prompt?: string; error?: string }>;
|
|
||||||
setSystemPrompt: (prompt: string) => Promise<{ success: boolean; error?: string }>;
|
|
||||||
|
|
||||||
// Conversations
|
|
||||||
getConversations: () => Promise<unknown[]>;
|
|
||||||
createConversation: (title?: string, model?: string) => Promise<unknown>;
|
|
||||||
getConversation: (id: string) => Promise<unknown>;
|
|
||||||
updateConversation: (id: string, updates: { title?: string; model?: string }) => Promise<unknown>;
|
|
||||||
deleteConversation: (id: string) => Promise<boolean>;
|
|
||||||
|
|
||||||
// Messaging
|
|
||||||
sendMessage: (conversationId: string, message: string) => Promise<string>;
|
|
||||||
abortMessage: (conversationId: string) => Promise<void>;
|
|
||||||
getHistory: (conversationId: string) => Promise<unknown[]>;
|
|
||||||
clearMessages: (conversationId: string) => Promise<void>;
|
|
||||||
setConversationModel: (conversationId: string, modelId: string) => Promise<void>;
|
|
||||||
|
|
||||||
// Event listeners
|
|
||||||
onStreamDelta: (callback: (data: { conversationId: string; delta: string }) => void) => () => void;
|
|
||||||
onToolCall: (callback: (data: { conversationId: string; toolCall: unknown }) => void) => () => void;
|
|
||||||
onToolResult: (callback: (data: { conversationId: string; result: unknown }) => void) => () => void;
|
|
||||||
onTitleUpdated: (callback: (data: { conversationId: string; title: string }) => void) => () => void;
|
|
||||||
};
|
|
||||||
on: (channel: string, callback: (...args: unknown[]) => void) => () => void;
|
|
||||||
once: (channel: string, callback: (...args: unknown[]) => void) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface Window {
|
|
||||||
electronAPI: ElectronAPI;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
486
src/main/shared/electronApi.ts
Normal file
486
src/main/shared/electronApi.ts
Normal file
@@ -0,0 +1,486 @@
|
|||||||
|
// Type definitions for the Electron API exposed via preload
|
||||||
|
|
||||||
|
export interface ImportExecuteResult {
|
||||||
|
taskId: string;
|
||||||
|
totalItems: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ImportExecutionProgress {
|
||||||
|
taskId: string;
|
||||||
|
phase: string;
|
||||||
|
current: number;
|
||||||
|
total: number;
|
||||||
|
detail?: string;
|
||||||
|
eta?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ImportCompleteResult {
|
||||||
|
taskId: string;
|
||||||
|
success: boolean;
|
||||||
|
posts: { imported: number; skipped: number; errors: number };
|
||||||
|
media: { imported: number; skipped: number; errors: number };
|
||||||
|
pages: { imported: number; skipped: number; errors: number };
|
||||||
|
tags: { created: number; skipped: number };
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ImportDefinitionData {
|
||||||
|
id: string;
|
||||||
|
projectId: string;
|
||||||
|
name: string;
|
||||||
|
wxrFilePath: string | null;
|
||||||
|
uploadsFolderPath: string | null;
|
||||||
|
lastAnalysisResult: unknown | null;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProjectMetadata {
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
dataPath?: string;
|
||||||
|
mainLanguage?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProjectData {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
description?: string;
|
||||||
|
dataPath?: string;
|
||||||
|
isActive: boolean;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PostData {
|
||||||
|
id: string;
|
||||||
|
projectId: string;
|
||||||
|
title: string;
|
||||||
|
slug: string;
|
||||||
|
excerpt?: string;
|
||||||
|
content: string;
|
||||||
|
status: 'draft' | 'published' | 'archived';
|
||||||
|
author?: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
publishedAt?: string;
|
||||||
|
tags: string[];
|
||||||
|
categories: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PostFilter {
|
||||||
|
status?: 'draft' | 'published' | 'archived';
|
||||||
|
tags?: string[];
|
||||||
|
categories?: string[];
|
||||||
|
year?: number;
|
||||||
|
month?: number;
|
||||||
|
from?: string;
|
||||||
|
to?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SearchResult {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
slug: string;
|
||||||
|
excerpt?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MediaData {
|
||||||
|
id: string;
|
||||||
|
projectId: string;
|
||||||
|
filename: string;
|
||||||
|
originalName: string;
|
||||||
|
mimeType: string;
|
||||||
|
size: number;
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
|
title?: string;
|
||||||
|
alt?: string;
|
||||||
|
caption?: string;
|
||||||
|
author?: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
tags: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MediaFilter {
|
||||||
|
tags?: string[];
|
||||||
|
year?: number;
|
||||||
|
month?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MediaSearchResult {
|
||||||
|
id: string;
|
||||||
|
originalName: string;
|
||||||
|
title?: string;
|
||||||
|
mimeType: string;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TaskProgress {
|
||||||
|
taskId: string;
|
||||||
|
status: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled';
|
||||||
|
progress: number;
|
||||||
|
message: string;
|
||||||
|
startTime: string;
|
||||||
|
endTime?: string;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SyncConfig {
|
||||||
|
autoSync: boolean;
|
||||||
|
syncInterval: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SyncResult {
|
||||||
|
success: boolean;
|
||||||
|
pushed: number;
|
||||||
|
pulled: number;
|
||||||
|
conflicts: number;
|
||||||
|
errors: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PaginatedPostsResult {
|
||||||
|
items: PostData[];
|
||||||
|
hasMore: boolean;
|
||||||
|
total: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DashboardStats {
|
||||||
|
totalPosts: number;
|
||||||
|
draftCount: number;
|
||||||
|
publishedCount: number;
|
||||||
|
archivedCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TagCount {
|
||||||
|
tag: string;
|
||||||
|
count: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CategoryCount {
|
||||||
|
category: string;
|
||||||
|
count: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TagData {
|
||||||
|
id: string;
|
||||||
|
projectId: string;
|
||||||
|
name: string;
|
||||||
|
color?: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TagWithCount {
|
||||||
|
name: string;
|
||||||
|
color: string | null;
|
||||||
|
count: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DeleteTagResult {
|
||||||
|
success: boolean;
|
||||||
|
postsUpdated: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MergeTagsResult {
|
||||||
|
success: boolean;
|
||||||
|
postsUpdated: number;
|
||||||
|
tagsDeleted: number;
|
||||||
|
targetTag: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RenameTagResult {
|
||||||
|
success: boolean;
|
||||||
|
postsUpdated: number;
|
||||||
|
oldName: string;
|
||||||
|
newName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SyncTagsResult {
|
||||||
|
discovered: number;
|
||||||
|
added: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Post-Media Link types
|
||||||
|
export interface MediaLinkData {
|
||||||
|
id: string;
|
||||||
|
projectId: string;
|
||||||
|
postId: string;
|
||||||
|
mediaId: string;
|
||||||
|
sortOrder: number;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chat/AI types
|
||||||
|
export interface ChatConversation {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
model?: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChatMessage {
|
||||||
|
id: string;
|
||||||
|
conversationId: string;
|
||||||
|
role: 'user' | 'assistant' | 'system' | 'tool';
|
||||||
|
content: string;
|
||||||
|
toolCallId?: string;
|
||||||
|
toolCalls?: string;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChatModel {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
provider?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChatReadyStatus {
|
||||||
|
ready: boolean;
|
||||||
|
error?: string;
|
||||||
|
backend?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChatApiKeyStatus {
|
||||||
|
hasKey: boolean;
|
||||||
|
maskedKey: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChatStreamDelta {
|
||||||
|
conversationId: string;
|
||||||
|
delta: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChatToolCall {
|
||||||
|
conversationId: string;
|
||||||
|
toolCall: {
|
||||||
|
name: string;
|
||||||
|
arguments: Record<string, unknown>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChatToolResult {
|
||||||
|
conversationId: string;
|
||||||
|
result: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChatTitleUpdate {
|
||||||
|
conversationId: string;
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ElectronAPI {
|
||||||
|
projects: {
|
||||||
|
create: (data: { name: string; description?: string; slug?: string; dataPath?: string }) => Promise<ProjectData>;
|
||||||
|
update: (id: string, data: Partial<ProjectData>) => Promise<ProjectData | null>;
|
||||||
|
delete: (id: string) => Promise<boolean>;
|
||||||
|
deleteWithData: (id: string) => Promise<boolean>;
|
||||||
|
get: (id: string) => Promise<ProjectData | null>;
|
||||||
|
getAll: () => Promise<ProjectData[]>;
|
||||||
|
getActive: () => Promise<ProjectData | null>;
|
||||||
|
setActive: (id: string) => Promise<ProjectData | null>;
|
||||||
|
};
|
||||||
|
posts: {
|
||||||
|
create: (data: Partial<PostData>) => Promise<PostData>;
|
||||||
|
update: (id: string, data: Partial<PostData>) => Promise<PostData | null>;
|
||||||
|
delete: (id: string) => Promise<boolean>;
|
||||||
|
get: (id: string) => Promise<PostData | null>;
|
||||||
|
getAll: (options?: { limit?: number; offset?: number }) => Promise<PaginatedPostsResult>;
|
||||||
|
getByStatus: (status: string) => Promise<PostData[]>;
|
||||||
|
publish: (id: string) => Promise<PostData | null>;
|
||||||
|
discard: (id: string) => Promise<PostData | null>;
|
||||||
|
hasPublishedVersion: (id: string) => Promise<boolean>;
|
||||||
|
rebuildFromFiles: () => Promise<void>;
|
||||||
|
reindexText: () => Promise<void>;
|
||||||
|
search: (query: string) => Promise<SearchResult[]>;
|
||||||
|
filter: (filter: PostFilter) => Promise<PostData[]>;
|
||||||
|
getTags: () => Promise<string[]>;
|
||||||
|
getCategories: () => Promise<string[]>;
|
||||||
|
getByYearMonth: () => Promise<{ year: number; month: number; count: number }[]>;
|
||||||
|
getDashboardStats: () => Promise<DashboardStats>;
|
||||||
|
getTagsWithCounts: () => Promise<TagCount[]>;
|
||||||
|
getCategoriesWithCounts: () => Promise<CategoryCount[]>;
|
||||||
|
getLinksTo: (id: string) => Promise<PostData[]>;
|
||||||
|
getLinkedBy: (id: string) => Promise<PostData[]>;
|
||||||
|
rebuildLinks: () => Promise<void>;
|
||||||
|
isSlugAvailable: (slug: string, excludePostId?: string) => Promise<boolean>;
|
||||||
|
generateUniqueSlug: (title: string, excludePostId?: string) => Promise<string>;
|
||||||
|
};
|
||||||
|
media: {
|
||||||
|
import: (sourcePath: string, metadata?: Partial<MediaData>) => Promise<MediaData>;
|
||||||
|
importDialog: () => Promise<MediaData[]>;
|
||||||
|
update: (id: string, data: Partial<MediaData>) => Promise<MediaData | null>;
|
||||||
|
replaceFile: (id: string, newSourcePath: string) => Promise<MediaData | null>;
|
||||||
|
replaceFileDialog: (id: string) => Promise<MediaData | null>;
|
||||||
|
delete: (id: string) => Promise<boolean>;
|
||||||
|
get: (id: string) => Promise<MediaData | null>;
|
||||||
|
getUrl: (id: string) => Promise<string | null>;
|
||||||
|
getFilePath: (id: string) => Promise<string | null>;
|
||||||
|
getAll: () => Promise<MediaData[]>;
|
||||||
|
rebuildFromFiles: () => Promise<void>;
|
||||||
|
reindexText: () => Promise<void>;
|
||||||
|
getThumbnail: (id: string, size?: 'small' | 'medium' | 'large') => Promise<string | null>;
|
||||||
|
regenerateThumbnails: (id: string) => Promise<Record<string, string> | null>;
|
||||||
|
regenerateMissingThumbnails: () => Promise<{ processed: number; generated: number; failed: number }>;
|
||||||
|
filter: (filter: MediaFilter) => Promise<MediaData[]>;
|
||||||
|
search: (query: string) => Promise<MediaSearchResult[]>;
|
||||||
|
getByYearMonth: () => Promise<{ year: number; month: number; count: number }[]>;
|
||||||
|
getTags: () => Promise<string[]>;
|
||||||
|
getTagsWithCounts: () => Promise<TagCount[]>;
|
||||||
|
};
|
||||||
|
postMedia: {
|
||||||
|
link: (postId: string, mediaId: string) => Promise<MediaLinkData>;
|
||||||
|
unlink: (postId: string, mediaId: string) => Promise<void>;
|
||||||
|
linkMany: (postId: string, mediaIds: string[]) => Promise<{ linked: string[]; skipped: string[] }>;
|
||||||
|
unlinkMany: (postId: string, mediaIds: string[]) => Promise<{ unlinked: string[] }>;
|
||||||
|
getForPost: (postId: string) => Promise<MediaLinkData[]>;
|
||||||
|
getForMedia: (mediaId: string) => Promise<MediaLinkData[]>;
|
||||||
|
getMediaDataForPost: (postId: string) => Promise<Array<MediaLinkData & { media: MediaData }>>;
|
||||||
|
reorder: (postId: string, mediaIds: string[]) => Promise<void>;
|
||||||
|
isLinked: (postId: string, mediaId: string) => Promise<boolean>;
|
||||||
|
import: (postId: string, filePath: string) => Promise<MediaLinkData>;
|
||||||
|
rebuild: () => Promise<void>;
|
||||||
|
};
|
||||||
|
sync: {
|
||||||
|
configure: (config: SyncConfig) => Promise<void>;
|
||||||
|
start: (direction?: 'push' | 'pull' | 'bidirectional') => Promise<SyncResult>;
|
||||||
|
getStatus: () => Promise<'idle' | 'syncing' | 'error'>;
|
||||||
|
isConfigured: () => Promise<boolean>;
|
||||||
|
getPendingCount: () => Promise<{ posts: number; media: number }>;
|
||||||
|
getLog: (limit?: number) => Promise<unknown[]>;
|
||||||
|
stopAutoSync: () => Promise<void>;
|
||||||
|
};
|
||||||
|
tasks: {
|
||||||
|
getAll: () => Promise<TaskProgress[]>;
|
||||||
|
getRunning: () => Promise<TaskProgress[]>;
|
||||||
|
cancel: (taskId: string) => Promise<boolean>;
|
||||||
|
clearCompleted: () => Promise<void>;
|
||||||
|
};
|
||||||
|
app: {
|
||||||
|
getDataPaths: () => Promise<{ database: string; posts: string; media: string }>;
|
||||||
|
openFolder: (folderPath: string) => Promise<string>;
|
||||||
|
showItemInFolder: (itemPath: string) => Promise<void>;
|
||||||
|
selectFolder: (title?: string) => Promise<string | null>;
|
||||||
|
getDefaultProjectPath: (projectId: string) => Promise<string>;
|
||||||
|
readProjectMetadata: (folderPath: string) => Promise<{ name?: string; description?: string; mainLanguage?: string } | null>;
|
||||||
|
};
|
||||||
|
meta: {
|
||||||
|
getTags: () => Promise<string[]>;
|
||||||
|
getCategories: () => Promise<string[]>;
|
||||||
|
addTag: (tag: string) => Promise<string[]>;
|
||||||
|
removeTag: (tag: string) => Promise<string[]>;
|
||||||
|
addCategory: (category: string) => Promise<string[]>;
|
||||||
|
removeCategory: (category: string) => Promise<string[]>;
|
||||||
|
syncOnStartup: () => Promise<{ tags: string[]; categories: string[]; projectMetadata: ProjectMetadata | null }>;
|
||||||
|
getProjectMetadata: () => Promise<ProjectMetadata | null>;
|
||||||
|
setProjectMetadata: (metadata: { name: string; description?: string }) => Promise<ProjectMetadata | null>;
|
||||||
|
updateProjectMetadata: (updates: { name?: string; description?: string; dataPath?: string; mainLanguage?: string }) => Promise<ProjectMetadata | null>;
|
||||||
|
};
|
||||||
|
tags: {
|
||||||
|
getAll: () => Promise<TagData[]>;
|
||||||
|
getWithCounts: () => Promise<TagWithCount[]>;
|
||||||
|
get: (id: string) => Promise<TagData | null>;
|
||||||
|
getByName: (name: string) => Promise<TagData | null>;
|
||||||
|
create: (data: { name: string; color?: string }) => Promise<TagData>;
|
||||||
|
update: (id: string, data: { name?: string; color?: string | null }) => Promise<TagData | null>;
|
||||||
|
delete: (id: string) => Promise<DeleteTagResult>;
|
||||||
|
merge: (sourceTagIds: string[], targetTagId: string) => Promise<MergeTagsResult>;
|
||||||
|
rename: (id: string, newName: string) => Promise<RenameTagResult>;
|
||||||
|
getPostsWithTag: (tagId: string) => Promise<string[]>;
|
||||||
|
syncFromPosts: () => Promise<SyncTagsResult>;
|
||||||
|
};
|
||||||
|
import: {
|
||||||
|
selectAndAnalyze: (uploadsFolder?: string) => Promise<unknown>;
|
||||||
|
analyzeFile: (filePath: string, uploadsFolder?: string) => Promise<unknown>;
|
||||||
|
selectUploadsFolder: () => Promise<string | null>;
|
||||||
|
execute: (reportJson: string, uploadsFolder?: string) => Promise<ImportExecuteResult>;
|
||||||
|
onProgress: (callback: (data: { step: string; detail?: string }) => void) => () => void;
|
||||||
|
onExecutionProgress: (callback: (data: ImportExecutionProgress) => void) => () => void;
|
||||||
|
onComplete: (callback: (data: ImportCompleteResult) => void) => () => void;
|
||||||
|
};
|
||||||
|
importDefinitions: {
|
||||||
|
create: (name?: string) => Promise<ImportDefinitionData>;
|
||||||
|
get: (id: string) => Promise<ImportDefinitionData | null>;
|
||||||
|
getAll: () => Promise<ImportDefinitionData[]>;
|
||||||
|
update: (id: string, updates: Partial<Pick<ImportDefinitionData, 'name' | 'wxrFilePath' | 'uploadsFolderPath' | 'lastAnalysisResult'>>) => Promise<ImportDefinitionData | null>;
|
||||||
|
delete: (id: string) => Promise<boolean>;
|
||||||
|
onNameUpdated: (callback: (data: { definitionId: string; name: string }) => void) => () => void;
|
||||||
|
};
|
||||||
|
metadataDiff: {
|
||||||
|
getStats: () => Promise<{
|
||||||
|
totalPosts: number;
|
||||||
|
publishedPosts: number;
|
||||||
|
draftPosts: number;
|
||||||
|
totalMedia: number;
|
||||||
|
}>;
|
||||||
|
scan: () => Promise<{
|
||||||
|
totalScanned: number;
|
||||||
|
postsWithDifferences: number;
|
||||||
|
differences: Array<{
|
||||||
|
postId: string;
|
||||||
|
title: string;
|
||||||
|
slug: string;
|
||||||
|
filePath?: string;
|
||||||
|
hasDifferences: boolean;
|
||||||
|
differences: Record<string, { dbValue: unknown; fileValue: unknown }>;
|
||||||
|
}>;
|
||||||
|
groups: Array<{
|
||||||
|
field: string;
|
||||||
|
label: string;
|
||||||
|
posts: Array<{
|
||||||
|
postId: string;
|
||||||
|
title: string;
|
||||||
|
slug: string;
|
||||||
|
dbValue: unknown;
|
||||||
|
fileValue: unknown;
|
||||||
|
}>;
|
||||||
|
}>;
|
||||||
|
}>;
|
||||||
|
syncDbToFile: (postIds: string[], groupLabel: string) => Promise<{ success: number; failed: number }>;
|
||||||
|
syncFileToDb: (postIds: string[], field: string, groupLabel: string) => Promise<{ success: number; failed: number }>;
|
||||||
|
};
|
||||||
|
chat: {
|
||||||
|
// API Key Management
|
||||||
|
checkReady: () => Promise<ChatReadyStatus>;
|
||||||
|
validateApiKey: (apiKey: string) => Promise<{ isValid: boolean; models: ChatModel[] }>;
|
||||||
|
setApiKey: (apiKey: string) => Promise<{ success: boolean; error?: string }>;
|
||||||
|
getApiKey: () => Promise<ChatApiKeyStatus>;
|
||||||
|
|
||||||
|
// Settings
|
||||||
|
getAvailableModels: () => Promise<{ success: boolean; models?: ChatModel[]; selectedModel?: string; error?: string }>;
|
||||||
|
setDefaultModel: (modelId: string) => Promise<{ success: boolean; error?: string }>;
|
||||||
|
getSystemPrompt: () => Promise<{ success: boolean; prompt?: string; error?: string }>;
|
||||||
|
setSystemPrompt: (prompt: string) => Promise<{ success: boolean; error?: string }>;
|
||||||
|
|
||||||
|
// Conversations
|
||||||
|
getConversations: () => Promise<ChatConversation[]>;
|
||||||
|
createConversation: (title?: string, model?: string) => Promise<ChatConversation>;
|
||||||
|
getConversation: (id: string) => Promise<ChatConversation | null>;
|
||||||
|
updateConversation: (id: string, updates: { title?: string; model?: string }) => Promise<ChatConversation | null>;
|
||||||
|
deleteConversation: (id: string) => Promise<boolean>;
|
||||||
|
|
||||||
|
// Messaging
|
||||||
|
sendMessage: (conversationId: string, message: string) => Promise<{ success: boolean; message?: string; error?: string }>;
|
||||||
|
abortMessage: (conversationId: string) => Promise<void>;
|
||||||
|
getHistory: (conversationId: string) => Promise<ChatMessage[]>;
|
||||||
|
clearMessages: (conversationId: string) => Promise<void>;
|
||||||
|
setConversationModel: (conversationId: string, modelId: string) => Promise<void>;
|
||||||
|
|
||||||
|
// Taxonomy Analysis
|
||||||
|
analyzeTaxonomy: (categories: Array<{ name: string; slug: string; existsInProject: boolean }>, tags: Array<{ name: string; slug: string; existsInProject: boolean }>, modelId: string) => Promise<{ success: boolean; categoryMappings?: Record<string, string>; tagMappings?: Record<string, string>; error?: string }>;
|
||||||
|
|
||||||
|
// Media Analysis
|
||||||
|
analyzeMediaImage: (mediaId: string, language?: string) => Promise<{ success: boolean; title?: string; alt?: string; caption?: string; error?: string }>;
|
||||||
|
|
||||||
|
// Event listeners for streaming/progress
|
||||||
|
onStreamDelta: (callback: (data: ChatStreamDelta) => void) => () => void;
|
||||||
|
onToolCall: (callback: (data: ChatToolCall) => void) => () => void;
|
||||||
|
onToolResult: (callback: (data: ChatToolResult) => void) => () => void;
|
||||||
|
onTitleUpdated: (callback: (data: ChatTitleUpdate) => void) => () => void;
|
||||||
|
};
|
||||||
|
on: (channel: string, callback: (...args: unknown[]) => void) => () => void;
|
||||||
|
once: (channel: string, callback: (...args: unknown[]) => void) => void;
|
||||||
|
}
|
||||||
|
|
||||||
453
src/renderer/types/electron.d.ts
vendored
453
src/renderer/types/electron.d.ts
vendored
@@ -1,455 +1,6 @@
|
|||||||
// Type definitions for the Electron API exposed via preload
|
export * from '../../main/shared/electronApi';
|
||||||
|
|
||||||
export interface ImportExecuteResult {
|
import type { ElectronAPI } from '../../main/shared/electronApi';
|
||||||
taskId: string;
|
|
||||||
totalItems: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ImportExecutionProgress {
|
|
||||||
taskId: string;
|
|
||||||
phase: string;
|
|
||||||
current: number;
|
|
||||||
total: number;
|
|
||||||
detail?: string;
|
|
||||||
eta?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ImportCompleteResult {
|
|
||||||
taskId: string;
|
|
||||||
success: boolean;
|
|
||||||
posts: { imported: number; skipped: number; errors: number };
|
|
||||||
media: { imported: number; skipped: number; errors: number };
|
|
||||||
pages: { imported: number; skipped: number; errors: number };
|
|
||||||
tags: { created: number; skipped: number };
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ImportDefinitionData {
|
|
||||||
id: string;
|
|
||||||
projectId: string;
|
|
||||||
name: string;
|
|
||||||
wxrFilePath: string | null;
|
|
||||||
uploadsFolderPath: string | null;
|
|
||||||
lastAnalysisResult: unknown | null;
|
|
||||||
createdAt: string;
|
|
||||||
updatedAt: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ProjectMetadata {
|
|
||||||
name: string;
|
|
||||||
description?: string;
|
|
||||||
dataPath?: string;
|
|
||||||
mainLanguage?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ProjectData {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
slug: string;
|
|
||||||
description?: string;
|
|
||||||
dataPath?: string;
|
|
||||||
isActive: boolean;
|
|
||||||
createdAt: string;
|
|
||||||
updatedAt: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PostData {
|
|
||||||
id: string;
|
|
||||||
projectId: string;
|
|
||||||
title: string;
|
|
||||||
slug: string;
|
|
||||||
excerpt?: string;
|
|
||||||
content: string;
|
|
||||||
status: 'draft' | 'published' | 'archived';
|
|
||||||
author?: string;
|
|
||||||
createdAt: string;
|
|
||||||
updatedAt: string;
|
|
||||||
publishedAt?: string;
|
|
||||||
tags: string[];
|
|
||||||
categories: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PostFilter {
|
|
||||||
status?: 'draft' | 'published' | 'archived';
|
|
||||||
tags?: string[];
|
|
||||||
categories?: string[];
|
|
||||||
year?: number;
|
|
||||||
month?: number;
|
|
||||||
from?: string;
|
|
||||||
to?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SearchResult {
|
|
||||||
id: string;
|
|
||||||
title: string;
|
|
||||||
slug: string;
|
|
||||||
excerpt?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MediaData {
|
|
||||||
id: string;
|
|
||||||
projectId: string;
|
|
||||||
filename: string;
|
|
||||||
originalName: string;
|
|
||||||
mimeType: string;
|
|
||||||
size: number;
|
|
||||||
width?: number;
|
|
||||||
height?: number;
|
|
||||||
title?: string;
|
|
||||||
alt?: string;
|
|
||||||
caption?: string;
|
|
||||||
author?: string;
|
|
||||||
createdAt: string;
|
|
||||||
updatedAt: string;
|
|
||||||
tags: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MediaFilter {
|
|
||||||
tags?: string[];
|
|
||||||
year?: number;
|
|
||||||
month?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MediaSearchResult {
|
|
||||||
id: string;
|
|
||||||
originalName: string;
|
|
||||||
title?: string;
|
|
||||||
mimeType: string;
|
|
||||||
createdAt: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TaskProgress {
|
|
||||||
taskId: string;
|
|
||||||
status: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled';
|
|
||||||
progress: number;
|
|
||||||
message: string;
|
|
||||||
startTime: string;
|
|
||||||
endTime?: string;
|
|
||||||
error?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SyncConfig {
|
|
||||||
autoSync: boolean;
|
|
||||||
syncInterval: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SyncResult {
|
|
||||||
success: boolean;
|
|
||||||
pushed: number;
|
|
||||||
pulled: number;
|
|
||||||
conflicts: number;
|
|
||||||
errors: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PaginatedPostsResult {
|
|
||||||
items: PostData[];
|
|
||||||
hasMore: boolean;
|
|
||||||
total: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DashboardStats {
|
|
||||||
totalPosts: number;
|
|
||||||
draftCount: number;
|
|
||||||
publishedCount: number;
|
|
||||||
archivedCount: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TagCount {
|
|
||||||
tag: string;
|
|
||||||
count: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CategoryCount {
|
|
||||||
category: string;
|
|
||||||
count: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TagData {
|
|
||||||
id: string;
|
|
||||||
projectId: string;
|
|
||||||
name: string;
|
|
||||||
color?: string;
|
|
||||||
createdAt: string;
|
|
||||||
updatedAt: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TagWithCount {
|
|
||||||
name: string;
|
|
||||||
color: string | null;
|
|
||||||
count: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DeleteTagResult {
|
|
||||||
success: boolean;
|
|
||||||
postsUpdated: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MergeTagsResult {
|
|
||||||
success: boolean;
|
|
||||||
postsUpdated: number;
|
|
||||||
tagsDeleted: number;
|
|
||||||
targetTag: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RenameTagResult {
|
|
||||||
success: boolean;
|
|
||||||
postsUpdated: number;
|
|
||||||
oldName: string;
|
|
||||||
newName: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SyncTagsResult {
|
|
||||||
discovered: number;
|
|
||||||
added: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Post-Media Link types
|
|
||||||
export interface MediaLinkData {
|
|
||||||
id: string;
|
|
||||||
projectId: string;
|
|
||||||
postId: string;
|
|
||||||
mediaId: string;
|
|
||||||
sortOrder: number;
|
|
||||||
createdAt: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Chat/AI types
|
|
||||||
export interface ChatConversation {
|
|
||||||
id: string;
|
|
||||||
title: string;
|
|
||||||
model?: string;
|
|
||||||
createdAt: string;
|
|
||||||
updatedAt: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ChatMessage {
|
|
||||||
id: string;
|
|
||||||
conversationId: string;
|
|
||||||
role: 'user' | 'assistant' | 'system' | 'tool';
|
|
||||||
content: string;
|
|
||||||
toolCallId?: string;
|
|
||||||
toolCalls?: string;
|
|
||||||
createdAt: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ChatModel {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
provider?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ChatReadyStatus {
|
|
||||||
ready: boolean;
|
|
||||||
error?: string;
|
|
||||||
backend?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ChatApiKeyStatus {
|
|
||||||
hasKey: boolean;
|
|
||||||
maskedKey: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ChatStreamDelta {
|
|
||||||
conversationId: string;
|
|
||||||
delta: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ChatToolCall {
|
|
||||||
conversationId: string;
|
|
||||||
toolCall: {
|
|
||||||
name: string;
|
|
||||||
arguments: Record<string, unknown>;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ChatToolResult {
|
|
||||||
conversationId: string;
|
|
||||||
result: unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ChatTitleUpdate {
|
|
||||||
conversationId: string;
|
|
||||||
title: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ElectronAPI {
|
|
||||||
projects: {
|
|
||||||
create: (data: { name: string; description?: string; slug?: string; dataPath?: string }) => Promise<ProjectData>;
|
|
||||||
update: (id: string, data: Partial<ProjectData>) => Promise<ProjectData | null>;
|
|
||||||
delete: (id: string) => Promise<boolean>;
|
|
||||||
deleteWithData: (id: string) => Promise<boolean>;
|
|
||||||
get: (id: string) => Promise<ProjectData | null>;
|
|
||||||
getAll: () => Promise<ProjectData[]>;
|
|
||||||
getActive: () => Promise<ProjectData | null>;
|
|
||||||
setActive: (id: string) => Promise<ProjectData | null>;
|
|
||||||
};
|
|
||||||
posts: {
|
|
||||||
create: (data: Partial<PostData>) => Promise<PostData>;
|
|
||||||
update: (id: string, data: Partial<PostData>) => Promise<PostData | null>;
|
|
||||||
delete: (id: string) => Promise<boolean>;
|
|
||||||
get: (id: string) => Promise<PostData | null>;
|
|
||||||
getAll: (options?: { limit?: number; offset?: number }) => Promise<PaginatedPostsResult>;
|
|
||||||
getByStatus: (status: string) => Promise<PostData[]>;
|
|
||||||
publish: (id: string) => Promise<PostData | null>;
|
|
||||||
discard: (id: string) => Promise<PostData | null>;
|
|
||||||
hasPublishedVersion: (id: string) => Promise<boolean>;
|
|
||||||
rebuildFromFiles: () => Promise<void>;
|
|
||||||
reindexText: () => Promise<void>;
|
|
||||||
search: (query: string) => Promise<SearchResult[]>;
|
|
||||||
filter: (filter: PostFilter) => Promise<PostData[]>;
|
|
||||||
getTags: () => Promise<string[]>;
|
|
||||||
getCategories: () => Promise<string[]>;
|
|
||||||
getByYearMonth: () => Promise<{ year: number; month: number; count: number }[]>;
|
|
||||||
getDashboardStats: () => Promise<DashboardStats>;
|
|
||||||
getTagsWithCounts: () => Promise<TagCount[]>;
|
|
||||||
getCategoriesWithCounts: () => Promise<CategoryCount[]>;
|
|
||||||
getLinksTo: (id: string) => Promise<PostData[]>;
|
|
||||||
getLinkedBy: (id: string) => Promise<PostData[]>;
|
|
||||||
rebuildLinks: () => Promise<void>;
|
|
||||||
isSlugAvailable: (slug: string, excludePostId?: string) => Promise<boolean>;
|
|
||||||
generateUniqueSlug: (title: string, excludePostId?: string) => Promise<string>;
|
|
||||||
};
|
|
||||||
media: {
|
|
||||||
import: (sourcePath: string, metadata?: Partial<MediaData>) => Promise<MediaData>;
|
|
||||||
importDialog: () => Promise<MediaData[]>;
|
|
||||||
update: (id: string, data: Partial<MediaData>) => Promise<MediaData | null>;
|
|
||||||
replaceFile: (id: string, newSourcePath: string) => Promise<MediaData | null>;
|
|
||||||
replaceFileDialog: (id: string) => Promise<MediaData | null>;
|
|
||||||
delete: (id: string) => Promise<boolean>;
|
|
||||||
get: (id: string) => Promise<MediaData | null>;
|
|
||||||
getUrl: (id: string) => Promise<string | null>;
|
|
||||||
getFilePath: (id: string) => Promise<string | null>;
|
|
||||||
getAll: () => Promise<MediaData[]>;
|
|
||||||
rebuildFromFiles: () => Promise<void>;
|
|
||||||
reindexText: () => Promise<void>;
|
|
||||||
getThumbnail: (id: string, size?: 'small' | 'medium' | 'large') => Promise<string | null>;
|
|
||||||
regenerateThumbnails: (id: string) => Promise<Record<string, string> | null>;
|
|
||||||
regenerateMissingThumbnails: () => Promise<{ processed: number; generated: number; failed: number }>;
|
|
||||||
filter: (filter: MediaFilter) => Promise<MediaData[]>;
|
|
||||||
search: (query: string) => Promise<MediaSearchResult[]>;
|
|
||||||
getByYearMonth: () => Promise<{ year: number; month: number; count: number }[]>;
|
|
||||||
getTags: () => Promise<string[]>;
|
|
||||||
getTagsWithCounts: () => Promise<TagCount[]>;
|
|
||||||
};
|
|
||||||
postMedia: {
|
|
||||||
link: (postId: string, mediaId: string) => Promise<MediaLinkData>;
|
|
||||||
unlink: (postId: string, mediaId: string) => Promise<void>;
|
|
||||||
linkMany: (postId: string, mediaIds: string[]) => Promise<{ linked: string[]; skipped: string[] }>;
|
|
||||||
unlinkMany: (postId: string, mediaIds: string[]) => Promise<{ unlinked: string[] }>;
|
|
||||||
getForPost: (postId: string) => Promise<MediaLinkData[]>;
|
|
||||||
getForMedia: (mediaId: string) => Promise<MediaLinkData[]>;
|
|
||||||
getMediaDataForPost: (postId: string) => Promise<Array<MediaLinkData & { media: MediaData }>>;
|
|
||||||
reorder: (postId: string, mediaIds: string[]) => Promise<void>;
|
|
||||||
isLinked: (postId: string, mediaId: string) => Promise<boolean>;
|
|
||||||
import: (postId: string, filePath: string) => Promise<MediaLinkData>;
|
|
||||||
rebuild: () => Promise<void>;
|
|
||||||
};
|
|
||||||
sync: {
|
|
||||||
configure: (config: SyncConfig) => Promise<void>;
|
|
||||||
start: (direction?: 'push' | 'pull' | 'bidirectional') => Promise<SyncResult>;
|
|
||||||
getStatus: () => Promise<'idle' | 'syncing' | 'error'>;
|
|
||||||
isConfigured: () => Promise<boolean>;
|
|
||||||
getPendingCount: () => Promise<{ posts: number; media: number }>;
|
|
||||||
getLog: (limit?: number) => Promise<unknown[]>;
|
|
||||||
stopAutoSync: () => Promise<void>;
|
|
||||||
};
|
|
||||||
tasks: {
|
|
||||||
getAll: () => Promise<TaskProgress[]>;
|
|
||||||
getRunning: () => Promise<TaskProgress[]>;
|
|
||||||
cancel: (taskId: string) => Promise<boolean>;
|
|
||||||
clearCompleted: () => Promise<void>;
|
|
||||||
};
|
|
||||||
app: {
|
|
||||||
getDataPaths: () => Promise<{ database: string; posts: string; media: string }>;
|
|
||||||
openFolder: (folderPath: string) => Promise<string>;
|
|
||||||
showItemInFolder: (itemPath: string) => Promise<void>;
|
|
||||||
selectFolder: (title?: string) => Promise<string | null>;
|
|
||||||
getDefaultProjectPath: (projectId: string) => Promise<string>;
|
|
||||||
readProjectMetadata: (folderPath: string) => Promise<{ name?: string; description?: string; mainLanguage?: string } | null>;
|
|
||||||
};
|
|
||||||
meta: {
|
|
||||||
getTags: () => Promise<string[]>;
|
|
||||||
getCategories: () => Promise<string[]>;
|
|
||||||
addTag: (tag: string) => Promise<string[]>;
|
|
||||||
removeTag: (tag: string) => Promise<string[]>;
|
|
||||||
addCategory: (category: string) => Promise<string[]>;
|
|
||||||
removeCategory: (category: string) => Promise<string[]>;
|
|
||||||
syncOnStartup: () => Promise<{ tags: string[]; categories: string[]; projectMetadata: ProjectMetadata | null }>;
|
|
||||||
getProjectMetadata: () => Promise<ProjectMetadata | null>;
|
|
||||||
setProjectMetadata: (metadata: { name: string; description?: string }) => Promise<ProjectMetadata | null>;
|
|
||||||
updateProjectMetadata: (updates: { name?: string; description?: string; dataPath?: string; mainLanguage?: string }) => Promise<ProjectMetadata | null>;
|
|
||||||
};
|
|
||||||
tags: {
|
|
||||||
getAll: () => Promise<TagData[]>;
|
|
||||||
getWithCounts: () => Promise<TagWithCount[]>;
|
|
||||||
get: (id: string) => Promise<TagData | null>;
|
|
||||||
getByName: (name: string) => Promise<TagData | null>;
|
|
||||||
create: (data: { name: string; color?: string }) => Promise<TagData>;
|
|
||||||
update: (id: string, data: { name?: string; color?: string | null }) => Promise<TagData | null>;
|
|
||||||
delete: (id: string) => Promise<DeleteTagResult>;
|
|
||||||
merge: (sourceTagIds: string[], targetTagId: string) => Promise<MergeTagsResult>;
|
|
||||||
rename: (id: string, newName: string) => Promise<RenameTagResult>;
|
|
||||||
getPostsWithTag: (tagId: string) => Promise<string[]>;
|
|
||||||
syncFromPosts: () => Promise<SyncTagsResult>;
|
|
||||||
};
|
|
||||||
import: {
|
|
||||||
selectAndAnalyze: (uploadsFolder?: string) => Promise<unknown>;
|
|
||||||
analyzeFile: (filePath: string, uploadsFolder?: string) => Promise<unknown>;
|
|
||||||
selectUploadsFolder: () => Promise<string | null>;
|
|
||||||
execute: (reportJson: string, uploadsFolder?: string) => Promise<ImportExecuteResult>;
|
|
||||||
onProgress: (callback: (data: { step: string; detail?: string }) => void) => () => void;
|
|
||||||
onExecutionProgress: (callback: (data: ImportExecutionProgress) => void) => () => void;
|
|
||||||
onComplete: (callback: (data: ImportCompleteResult) => void) => () => void;
|
|
||||||
};
|
|
||||||
importDefinitions: {
|
|
||||||
create: (name?: string) => Promise<ImportDefinitionData>;
|
|
||||||
get: (id: string) => Promise<ImportDefinitionData | null>;
|
|
||||||
getAll: () => Promise<ImportDefinitionData[]>;
|
|
||||||
update: (id: string, updates: Partial<Pick<ImportDefinitionData, 'name' | 'wxrFilePath' | 'uploadsFolderPath' | 'lastAnalysisResult'>>) => Promise<ImportDefinitionData | null>;
|
|
||||||
delete: (id: string) => Promise<boolean>;
|
|
||||||
onNameUpdated: (callback: (data: { definitionId: string; name: string }) => void) => () => void;
|
|
||||||
};
|
|
||||||
chat: {
|
|
||||||
// API Key Management
|
|
||||||
checkReady: () => Promise<ChatReadyStatus>;
|
|
||||||
validateApiKey: (apiKey: string) => Promise<{ isValid: boolean; models: ChatModel[] }>;
|
|
||||||
setApiKey: (apiKey: string) => Promise<{ success: boolean; error?: string }>;
|
|
||||||
getApiKey: () => Promise<ChatApiKeyStatus>;
|
|
||||||
|
|
||||||
// Settings
|
|
||||||
getAvailableModels: () => Promise<{ success: boolean; models?: ChatModel[]; selectedModel?: string; error?: string }>;
|
|
||||||
setDefaultModel: (modelId: string) => Promise<{ success: boolean; error?: string }>;
|
|
||||||
getSystemPrompt: () => Promise<{ success: boolean; prompt?: string; error?: string }>;
|
|
||||||
setSystemPrompt: (prompt: string) => Promise<{ success: boolean; error?: string }>;
|
|
||||||
|
|
||||||
// Conversations
|
|
||||||
getConversations: () => Promise<ChatConversation[]>;
|
|
||||||
createConversation: (title?: string, model?: string) => Promise<ChatConversation>;
|
|
||||||
getConversation: (id: string) => Promise<ChatConversation | null>;
|
|
||||||
updateConversation: (id: string, updates: { title?: string; model?: string }) => Promise<ChatConversation | null>;
|
|
||||||
deleteConversation: (id: string) => Promise<boolean>;
|
|
||||||
|
|
||||||
// Messaging
|
|
||||||
sendMessage: (conversationId: string, message: string) => Promise<{ success: boolean; message?: string; error?: string }>;
|
|
||||||
abortMessage: (conversationId: string) => Promise<void>;
|
|
||||||
getHistory: (conversationId: string) => Promise<ChatMessage[]>;
|
|
||||||
clearMessages: (conversationId: string) => Promise<void>;
|
|
||||||
setConversationModel: (conversationId: string, modelId: string) => Promise<void>;
|
|
||||||
|
|
||||||
// Taxonomy Analysis
|
|
||||||
analyzeTaxonomy: (categories: Array<{ name: string; slug: string; existsInProject: boolean }>, tags: Array<{ name: string; slug: string; existsInProject: boolean }>, modelId: string) => Promise<{ success: boolean; categoryMappings?: Record<string, string>; tagMappings?: Record<string, string>; error?: string }>;
|
|
||||||
|
|
||||||
// Media Analysis
|
|
||||||
analyzeMediaImage: (mediaId: string, language?: string) => Promise<{ success: boolean; title?: string; alt?: string; caption?: string; error?: string }>;
|
|
||||||
|
|
||||||
// Event listeners for streaming/progress
|
|
||||||
onStreamDelta: (callback: (data: ChatStreamDelta) => void) => () => void;
|
|
||||||
onToolCall: (callback: (data: ChatToolCall) => void) => () => void;
|
|
||||||
onToolResult: (callback: (data: ChatToolResult) => void) => () => void;
|
|
||||||
onTitleUpdated: (callback: (data: ChatTitleUpdate) => void) => () => void;
|
|
||||||
};
|
|
||||||
on: (channel: string, callback: (...args: unknown[]) => void) => () => void;
|
|
||||||
once: (channel: string, callback: (...args: unknown[]) => void) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
|
|||||||
28
tests/engine/PreloadContract.test.ts
Normal file
28
tests/engine/PreloadContract.test.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||||
|
|
||||||
|
const exposeInMainWorld = vi.fn();
|
||||||
|
|
||||||
|
vi.mock('electron', () => ({
|
||||||
|
contextBridge: {
|
||||||
|
exposeInMainWorld,
|
||||||
|
},
|
||||||
|
ipcRenderer: {
|
||||||
|
invoke: vi.fn(),
|
||||||
|
on: vi.fn(),
|
||||||
|
removeListener: vi.fn(),
|
||||||
|
once: vi.fn(),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('preload contract', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('exports the electronAPI contract object and exposes it via contextBridge', async () => {
|
||||||
|
const preloadModule = await import('../../src/main/preload');
|
||||||
|
|
||||||
|
expect(preloadModule).toHaveProperty('electronAPI');
|
||||||
|
expect(exposeInMainWorld).toHaveBeenCalledWith('electronAPI', (preloadModule as { electronAPI: unknown }).electronAPI);
|
||||||
|
});
|
||||||
|
});
|
||||||
9
tests/renderer/types/electronApiContract.test.ts
Normal file
9
tests/renderer/types/electronApiContract.test.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { describe, it, expectTypeOf } from 'vitest';
|
||||||
|
import type { ElectronAPI as SharedElectronAPI } from '../../../src/main/shared/electronApi';
|
||||||
|
import type { ElectronAPI as RendererElectronAPI } from '../../../src/renderer/types/electron';
|
||||||
|
|
||||||
|
describe('Electron API type contract', () => {
|
||||||
|
it('keeps renderer and shared ElectronAPI contracts in sync', () => {
|
||||||
|
expectTypeOf<RendererElectronAPI>().toEqualTypeOf<SharedElectronAPI>();
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user