mirror of
https://github.com/tiddly-gittly/TidGi-Desktop.git
synced 2026-01-25 05:51:04 -08:00
* feat: new config for tidgi mini window * chore: upgrade electron-forge * fix: use 汉语 和 漢語 * feat: shortcut to open mini window * test: TidGiMenubarWindow * feat: allow updateWindowProperties on the fly * fix: wrong icon path * fix: log not showing error message and stack * refactor: directly log error when using logger.error * feat: shortcut to open window * fix: menubar not closed * test: e2e for menubar * test: keyboard shortcut * test: wiki web content, and refactor to files * test: update command * Update Testing.md * test: menubar settings about menubarSyncWorkspaceWithMainWindow, menubarFixedWorkspaceId * test: simplify menubar test and cleanup test config * fix: view missing when execute several test all together * refactor: use hook to cleanup menubar setting * refactor: I clear test ai settings to before hook * Add option to show title bar on menubar window Introduces a new preference 'showMenubarWindowTitleBar' allowing users to toggle the title bar visibility on the menubar window. Updates related services, interfaces, and UI components to support this feature, and adds corresponding localization strings for English and Chinese. * refactor: tidgiminiwindow * refactor: preference keys to right order * Refactor window dimension checks to use constants Replaces hardcoded window dimensions with values from windowDimension and WindowNames constants for improved maintainability and consistency in window identification and checks. * I cleanup test wiki * Update defaultPreferences.ts * test: mini window workspace switch * fix: image broken by ai, and lint * fix: can't switch to mini window * refactor: useless todo * Update index.ts * refactor: reuse serialize-error * Update index.ts * Update testKeyboardShortcuts.ts * refactor: dup logic * Update ui.ts * fix: electron-ipc-cat
269 lines
9.9 KiB
TypeScript
269 lines
9.9 KiB
TypeScript
/**
|
|
* Message management plugin
|
|
* Unified plugin for handling message persistence, streaming updates, and UI synchronization
|
|
* Combines functionality from persistencePlugin and aiResponseHistoryPlugin
|
|
*/
|
|
import { container } from '@services/container';
|
|
import { logger } from '@services/libs/log';
|
|
import serviceIdentifier from '@services/serviceIdentifier';
|
|
import type { IAgentInstanceService } from '../interface';
|
|
import { createAgentMessage } from '../utilities';
|
|
import type { AgentStatusContext, AIResponseContext, PromptConcatPlugin, ToolExecutionContext, UserMessageContext } from './types';
|
|
|
|
/**
|
|
* Message management plugin
|
|
* Handles all message-related operations: persistence, streaming, UI updates, and duration-based filtering
|
|
*/
|
|
export const messageManagementPlugin: PromptConcatPlugin = (hooks) => {
|
|
// Handle user message persistence
|
|
hooks.userMessageReceived.tapAsync('messageManagementPlugin', async (context: UserMessageContext, callback) => {
|
|
try {
|
|
const { handlerContext, content, messageId } = context;
|
|
|
|
// Create user message using the helper function
|
|
const userMessage = createAgentMessage(messageId, handlerContext.agent.id, {
|
|
role: 'user',
|
|
content: content.text,
|
|
contentType: 'text/plain',
|
|
metadata: content.file ? { file: content.file } : undefined,
|
|
duration: undefined, // User messages persist indefinitely by default
|
|
});
|
|
|
|
// Add message to the agent's message array for immediate use (do this before persistence so plugins see it)
|
|
handlerContext.agent.messages.push(userMessage);
|
|
|
|
// Get the agent instance service to access repositories
|
|
const agentInstanceService = container.get<IAgentInstanceService>(serviceIdentifier.AgentInstance);
|
|
|
|
// Save user message to database (if persistence fails, we still keep the in-memory message)
|
|
await agentInstanceService.saveUserMessage(userMessage);
|
|
|
|
logger.debug('User message persisted to database', {
|
|
messageId,
|
|
agentId: handlerContext.agent.id,
|
|
contentLength: content.text.length,
|
|
});
|
|
|
|
callback();
|
|
} catch (error) {
|
|
logger.error('Message management plugin error in userMessageReceived', {
|
|
error,
|
|
messageId: context.messageId,
|
|
agentId: context.handlerContext.agent.id,
|
|
});
|
|
callback();
|
|
}
|
|
});
|
|
|
|
// Handle agent status persistence
|
|
hooks.agentStatusChanged.tapAsync('messageManagementPlugin', async (context: AgentStatusContext, callback) => {
|
|
try {
|
|
const { handlerContext, status } = context;
|
|
|
|
// Get the agent instance service to update status
|
|
const agentInstanceService = container.get<IAgentInstanceService>(serviceIdentifier.AgentInstance);
|
|
|
|
// Update agent status in database
|
|
await agentInstanceService.updateAgent(handlerContext.agent.id, {
|
|
status,
|
|
});
|
|
|
|
// Update the agent object for immediate use
|
|
handlerContext.agent.status = status;
|
|
|
|
logger.debug('Agent status updated in database', {
|
|
agentId: handlerContext.agent.id,
|
|
state: status.state,
|
|
});
|
|
|
|
callback();
|
|
} catch (error) {
|
|
logger.error('Message management plugin error in agentStatusChanged', {
|
|
error,
|
|
agentId: context.handlerContext.agent.id,
|
|
status: context.status,
|
|
});
|
|
callback();
|
|
}
|
|
});
|
|
|
|
// Handle AI response updates during streaming
|
|
hooks.responseUpdate.tapAsync('messageManagementPlugin', async (context: AIResponseContext, callback) => {
|
|
try {
|
|
const { handlerContext, response } = context;
|
|
|
|
if (response.status === 'update' && response.content) {
|
|
// Find or create AI response message in agent's message array
|
|
let aiMessage = handlerContext.agent.messages.find(
|
|
(message) => message.role === 'assistant' && !message.metadata?.isComplete,
|
|
);
|
|
|
|
if (!aiMessage) {
|
|
// Create new AI message for streaming updates
|
|
const now = new Date();
|
|
aiMessage = {
|
|
id: `ai-response-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
|
|
agentId: handlerContext.agent.id,
|
|
role: 'assistant',
|
|
content: response.content,
|
|
created: now,
|
|
modified: now,
|
|
metadata: { isComplete: false },
|
|
duration: undefined, // AI responses persist indefinitely by default
|
|
};
|
|
handlerContext.agent.messages.push(aiMessage);
|
|
// Persist immediately so DB timestamp reflects conversation order
|
|
try {
|
|
const agentInstanceService = container.get<IAgentInstanceService>(serviceIdentifier.AgentInstance);
|
|
await agentInstanceService.saveUserMessage(aiMessage);
|
|
aiMessage.metadata = { ...aiMessage.metadata, isPersisted: true };
|
|
} catch (persistError) {
|
|
logger.warn('Failed to persist initial streaming AI message', {
|
|
error: persistError,
|
|
messageId: aiMessage.id,
|
|
});
|
|
}
|
|
} else {
|
|
// Update existing message content
|
|
aiMessage.content = response.content;
|
|
aiMessage.modified = new Date();
|
|
}
|
|
|
|
// Update UI using the agent instance service
|
|
try {
|
|
const agentInstanceService = container.get<IAgentInstanceService>(serviceIdentifier.AgentInstance);
|
|
agentInstanceService.debounceUpdateMessage(aiMessage, handlerContext.agent.id);
|
|
} catch (serviceError) {
|
|
logger.warn('Failed to update UI for streaming message', {
|
|
error: serviceError,
|
|
messageId: aiMessage.id,
|
|
});
|
|
}
|
|
}
|
|
} catch (error) {
|
|
logger.error('Message management plugin error in responseUpdate', {
|
|
error,
|
|
});
|
|
} finally {
|
|
callback();
|
|
}
|
|
});
|
|
|
|
// Handle AI response completion
|
|
hooks.responseComplete.tapAsync('messageManagementPlugin', async (context: AIResponseContext, callback) => {
|
|
try {
|
|
const { handlerContext, response } = context;
|
|
|
|
if (response.status === 'done' && response.content) {
|
|
// Find and finalize AI response message
|
|
let aiMessage = handlerContext.agent.messages.find(
|
|
(message) => message.role === 'assistant' && !message.metadata?.isComplete && !message.metadata?.isToolResult,
|
|
);
|
|
|
|
if (aiMessage) {
|
|
// Mark as complete and update final content
|
|
aiMessage.content = response.content;
|
|
aiMessage.modified = new Date();
|
|
aiMessage.metadata = { ...aiMessage.metadata, isComplete: true };
|
|
} else {
|
|
// Create final message if streaming message wasn't found
|
|
const nowFinal = new Date();
|
|
aiMessage = {
|
|
id: `ai-response-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
|
|
agentId: handlerContext.agent.id,
|
|
role: 'assistant',
|
|
content: response.content,
|
|
created: nowFinal,
|
|
modified: nowFinal,
|
|
metadata: {
|
|
isComplete: true,
|
|
},
|
|
duration: undefined, // Default duration for AI responses
|
|
};
|
|
handlerContext.agent.messages.push(aiMessage);
|
|
}
|
|
|
|
// Get the agent instance service for persistence and UI updates
|
|
const agentInstanceService = container.get<IAgentInstanceService>(serviceIdentifier.AgentInstance);
|
|
|
|
// Save final AI message to database using the same method as user messages
|
|
await agentInstanceService.saveUserMessage(aiMessage);
|
|
|
|
// Final UI update
|
|
try {
|
|
agentInstanceService.debounceUpdateMessage(aiMessage, handlerContext.agent.id);
|
|
} catch (serviceError) {
|
|
logger.warn('Failed to update UI for completed message', {
|
|
error: serviceError,
|
|
messageId: aiMessage.id,
|
|
});
|
|
}
|
|
|
|
logger.debug('AI response message completed and persisted', {
|
|
messageId: aiMessage.id,
|
|
finalContentLength: response.content.length,
|
|
});
|
|
}
|
|
|
|
callback();
|
|
} catch (error) {
|
|
logger.error('Message management plugin error in responseComplete', {
|
|
error,
|
|
});
|
|
callback();
|
|
}
|
|
});
|
|
|
|
// Handle tool result messages persistence and UI updates
|
|
hooks.toolExecuted.tapAsync('messageManagementPlugin', async (context: ToolExecutionContext, callback) => {
|
|
try {
|
|
const { handlerContext } = context;
|
|
|
|
// Find newly added tool result messages that need to be persisted
|
|
const newToolResultMessages = handlerContext.agent.messages.filter(
|
|
(message) => message.metadata?.isToolResult && !message.metadata.isPersisted,
|
|
);
|
|
|
|
const agentInstanceService = container.get<IAgentInstanceService>(serviceIdentifier.AgentInstance);
|
|
|
|
// Save tool result messages to database and update UI
|
|
for (const message of newToolResultMessages) {
|
|
try {
|
|
// Save to database using the same method as user messages
|
|
await agentInstanceService.saveUserMessage(message);
|
|
|
|
// Update UI
|
|
agentInstanceService.debounceUpdateMessage(message, handlerContext.agent.id);
|
|
|
|
// Mark as persisted to avoid duplicate saves
|
|
message.metadata = { ...message.metadata, isPersisted: true, uiUpdated: true };
|
|
|
|
logger.debug('Tool result message persisted to database', {
|
|
messageId: message.id,
|
|
toolId: message.metadata.toolId,
|
|
duration: message.duration,
|
|
});
|
|
} catch (serviceError) {
|
|
logger.error('Failed to persist tool result message', {
|
|
error: serviceError,
|
|
messageId: message.id,
|
|
});
|
|
}
|
|
}
|
|
|
|
if (newToolResultMessages.length > 0) {
|
|
logger.debug('Tool result messages processed', {
|
|
count: newToolResultMessages.length,
|
|
messageIds: newToolResultMessages.map(m => m.id),
|
|
});
|
|
}
|
|
|
|
callback();
|
|
} catch (error) {
|
|
logger.error('Message management plugin error in toolExecuted', {
|
|
error,
|
|
});
|
|
callback();
|
|
}
|
|
});
|
|
};
|