@mseep/obsidian-agent-client 0.10.6
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.
- package/.claude/hooks/gh-setup.sh +49 -0
- package/.claude/settings.json +15 -0
- package/.claude/skills/release-notes/SKILL.md +331 -0
- package/.editorconfig +10 -0
- package/.github/FUNDING.yml +2 -0
- package/.github/ISSUE_TEMPLATE/bug_report.yml +90 -0
- package/.github/ISSUE_TEMPLATE/config.yml +11 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +59 -0
- package/.github/copilot-instructions.md +45 -0
- package/.github/pull_request_template.md +32 -0
- package/.github/workflows/ci.yaml +25 -0
- package/.github/workflows/docs.yml +58 -0
- package/.github/workflows/relay_to_openclaw.yml +59 -0
- package/.github/workflows/release.yaml +45 -0
- package/.prettierignore +10 -0
- package/.prettierrc +13 -0
- package/.vscode/extensions.json +7 -0
- package/.vscode/settings.json +37 -0
- package/.zed/settings.json +42 -0
- package/AGENTS.md +330 -0
- package/ARCHITECTURE.md +390 -0
- package/CONTRIBUTING.md +216 -0
- package/LICENSE +202 -0
- package/NOTICE +2 -0
- package/README.ja.md +121 -0
- package/README.md +125 -0
- package/docs/.vitepress/config.mts +124 -0
- package/docs/.vitepress/theme/custom.css +111 -0
- package/docs/.vitepress/theme/index.ts +4 -0
- package/docs/agent-setup/claude-code.md +84 -0
- package/docs/agent-setup/codex.md +76 -0
- package/docs/agent-setup/custom-agents.md +67 -0
- package/docs/agent-setup/gemini-cli.md +99 -0
- package/docs/agent-setup/index.md +34 -0
- package/docs/announcements/gemini-cli-deprecation.md +73 -0
- package/docs/getting-started/index.md +78 -0
- package/docs/getting-started/quick-start.md +38 -0
- package/docs/help/faq.md +181 -0
- package/docs/help/troubleshooting.md +221 -0
- package/docs/index.md +63 -0
- package/docs/public/apple-touch-icon.png +0 -0
- package/docs/public/demo.mp4 +0 -0
- package/docs/public/favicon-16x16.png +0 -0
- package/docs/public/favicon-32x32.png +0 -0
- package/docs/public/favicon.ico +0 -0
- package/docs/public/images/editing.webp +0 -0
- package/docs/public/images/export.webp +0 -0
- package/docs/public/images/floating-chat-button.webp +0 -0
- package/docs/public/images/floating-chat-instance-menu.webp +0 -0
- package/docs/public/images/floating-chat-view.webp +0 -0
- package/docs/public/images/mode-selection.webp +0 -0
- package/docs/public/images/model-selection.webp +0 -0
- package/docs/public/images/multi-session.webp +0 -0
- package/docs/public/images/remove-image.webp +0 -0
- package/docs/public/images/ribbon-icon.webp +0 -0
- package/docs/public/images/selection-context.gif +0 -0
- package/docs/public/images/sending-images.webp +0 -0
- package/docs/public/images/sending-messages.webp +0 -0
- package/docs/public/images/session-history-button.webp +0 -0
- package/docs/public/images/slash-commands-1.webp +0 -0
- package/docs/public/images/slash-commands-2.webp +0 -0
- package/docs/public/images/switch-agent.webp +0 -0
- package/docs/public/images/switch-default-agent.webp +0 -0
- package/docs/public/images/temporary-disable.gif +0 -0
- package/docs/reference/acp-support.md +110 -0
- package/docs/usage/chat-export.md +80 -0
- package/docs/usage/commands.md +51 -0
- package/docs/usage/context-files.md +57 -0
- package/docs/usage/editing.md +69 -0
- package/docs/usage/floating-chat.md +84 -0
- package/docs/usage/index.md +97 -0
- package/docs/usage/mcp-tools.md +33 -0
- package/docs/usage/mentions.md +70 -0
- package/docs/usage/mode-selection.md +28 -0
- package/docs/usage/model-selection.md +32 -0
- package/docs/usage/multi-session.md +68 -0
- package/docs/usage/sending-images.md +64 -0
- package/docs/usage/session-history.md +91 -0
- package/docs/usage/slash-commands.md +44 -0
- package/esbuild.config.mjs +49 -0
- package/eslint.config.mjs +25 -0
- package/main.js +228 -0
- package/manifest.json +11 -0
- package/package.json +52 -0
- package/src/acp/acp-client.ts +921 -0
- package/src/acp/acp-handler.ts +252 -0
- package/src/acp/permission-handler.ts +282 -0
- package/src/acp/terminal-handler.ts +264 -0
- package/src/acp/type-converter.ts +272 -0
- package/src/hooks/useAgent.ts +250 -0
- package/src/hooks/useAgentMessages.ts +470 -0
- package/src/hooks/useAgentSession.ts +544 -0
- package/src/hooks/useChatActions.ts +400 -0
- package/src/hooks/useHistoryModal.ts +219 -0
- package/src/hooks/useSessionHistory.ts +863 -0
- package/src/hooks/useSettings.ts +19 -0
- package/src/hooks/useSuggestions.ts +342 -0
- package/src/main.ts +9 -0
- package/src/plugin.ts +1126 -0
- package/src/services/chat-exporter.ts +552 -0
- package/src/services/message-sender.ts +755 -0
- package/src/services/message-state.ts +375 -0
- package/src/services/session-helpers.ts +211 -0
- package/src/services/session-state.ts +130 -0
- package/src/services/session-storage.ts +267 -0
- package/src/services/settings-normalizer.ts +255 -0
- package/src/services/settings-service.ts +285 -0
- package/src/services/update-checker.ts +128 -0
- package/src/services/vault-service.ts +558 -0
- package/src/services/view-registry.ts +345 -0
- package/src/types/agent.ts +92 -0
- package/src/types/chat.ts +351 -0
- package/src/types/errors.ts +136 -0
- package/src/types/obsidian-internals.d.ts +14 -0
- package/src/types/session.ts +731 -0
- package/src/ui/ChangeDirectoryModal.ts +137 -0
- package/src/ui/ChatContext.ts +25 -0
- package/src/ui/ChatHeader.tsx +295 -0
- package/src/ui/ChatPanel.tsx +1162 -0
- package/src/ui/ChatView.tsx +348 -0
- package/src/ui/ErrorBanner.tsx +104 -0
- package/src/ui/FloatingButton.tsx +351 -0
- package/src/ui/FloatingChatView.tsx +531 -0
- package/src/ui/InputArea.tsx +1107 -0
- package/src/ui/InputToolbar.tsx +371 -0
- package/src/ui/MessageBubble.tsx +442 -0
- package/src/ui/MessageList.tsx +265 -0
- package/src/ui/PermissionBanner.tsx +61 -0
- package/src/ui/SessionHistoryModal.tsx +821 -0
- package/src/ui/SettingsTab.ts +1337 -0
- package/src/ui/SuggestionPopup.tsx +138 -0
- package/src/ui/TerminalBlock.tsx +107 -0
- package/src/ui/ToolCallBlock.tsx +456 -0
- package/src/ui/shared/AttachmentStrip.tsx +57 -0
- package/src/ui/shared/IconButton.tsx +55 -0
- package/src/ui/shared/MarkdownRenderer.tsx +103 -0
- package/src/ui/view-host.ts +56 -0
- package/src/utils/error-utils.ts +274 -0
- package/src/utils/logger.ts +44 -0
- package/src/utils/mention-parser.ts +129 -0
- package/src/utils/paths.ts +246 -0
- package/src/utils/platform.ts +425 -0
- package/styles.css +2322 -0
- package/tsconfig.json +18 -0
- package/version-bump.mjs +18 -0
- package/versions.json +3 -0
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook for ChatPanel business callbacks.
|
|
3
|
+
*
|
|
4
|
+
* Encapsulates message sending, new chat, export, agent switching/restart,
|
|
5
|
+
* config changes, and related UI state (restoredMessage, agentUpdateNotification).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { useState, useCallback } from "react";
|
|
9
|
+
import { Notice, Platform } from "obsidian";
|
|
10
|
+
|
|
11
|
+
import type AgentClientPlugin from "../plugin";
|
|
12
|
+
import type { UseAgentReturn } from "./useAgent";
|
|
13
|
+
import type { UseSessionHistoryReturn } from "./useSessionHistory";
|
|
14
|
+
import type { UseSuggestionsReturn } from "./useSuggestions";
|
|
15
|
+
import type { ChatSession } from "../types/session";
|
|
16
|
+
import type {
|
|
17
|
+
ChatMessage,
|
|
18
|
+
AttachedFile,
|
|
19
|
+
ImagePromptContent,
|
|
20
|
+
ResourceLinkPromptContent,
|
|
21
|
+
} from "../types/chat";
|
|
22
|
+
import type { AgentClientPluginSettings } from "../plugin";
|
|
23
|
+
import type { AgentUpdateNotification } from "../services/update-checker";
|
|
24
|
+
import { ChatExporter } from "../services/chat-exporter";
|
|
25
|
+
import { getLogger } from "../utils/logger";
|
|
26
|
+
import { buildFileUri } from "../utils/paths";
|
|
27
|
+
import { convertWindowsPathToWsl } from "../utils/platform";
|
|
28
|
+
|
|
29
|
+
// ============================================================================
|
|
30
|
+
// Types
|
|
31
|
+
// ============================================================================
|
|
32
|
+
|
|
33
|
+
export interface UseChatActionsReturn {
|
|
34
|
+
// Message actions
|
|
35
|
+
handleSendMessage: (
|
|
36
|
+
content: string,
|
|
37
|
+
attachments?: AttachedFile[],
|
|
38
|
+
) => Promise<void>;
|
|
39
|
+
handleStopGeneration: () => Promise<void>;
|
|
40
|
+
handleNewChat: (requestedAgentId?: string) => Promise<void>;
|
|
41
|
+
handleExportChat: () => Promise<void>;
|
|
42
|
+
handleSwitchAgent: (agentId: string) => Promise<void>;
|
|
43
|
+
handleRestartAgent: () => Promise<void>;
|
|
44
|
+
|
|
45
|
+
// Config actions
|
|
46
|
+
handleSetMode: (modeId: string) => Promise<void>;
|
|
47
|
+
handleSetModel: (modelId: string) => Promise<void>;
|
|
48
|
+
handleSetConfigOption: (configId: string, value: string) => Promise<void>;
|
|
49
|
+
|
|
50
|
+
// UI state actions
|
|
51
|
+
handleClearError: () => void;
|
|
52
|
+
handleClearAgentUpdate: () => void;
|
|
53
|
+
handleRestoredMessageConsumed: () => void;
|
|
54
|
+
|
|
55
|
+
// State (moved from ChatPanel)
|
|
56
|
+
restoredMessage: string | null;
|
|
57
|
+
agentUpdateNotification: AgentUpdateNotification | null;
|
|
58
|
+
setAgentUpdateNotification: (n: AgentUpdateNotification | null) => void;
|
|
59
|
+
|
|
60
|
+
// Auto-export (needed by ChatPanel cleanup)
|
|
61
|
+
autoExportIfEnabled: (
|
|
62
|
+
trigger: "newChat" | "closeChat",
|
|
63
|
+
triggerMessages: ChatMessage[],
|
|
64
|
+
triggerSession: ChatSession,
|
|
65
|
+
) => Promise<void>;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ============================================================================
|
|
69
|
+
// Hook Implementation
|
|
70
|
+
// ============================================================================
|
|
71
|
+
|
|
72
|
+
export function useChatActions(
|
|
73
|
+
plugin: AgentClientPlugin,
|
|
74
|
+
agent: UseAgentReturn,
|
|
75
|
+
sessionHistory: UseSessionHistoryReturn,
|
|
76
|
+
suggestions: UseSuggestionsReturn,
|
|
77
|
+
session: ChatSession,
|
|
78
|
+
messages: ChatMessage[],
|
|
79
|
+
settings: AgentClientPluginSettings,
|
|
80
|
+
vaultPath: string,
|
|
81
|
+
): UseChatActionsReturn {
|
|
82
|
+
const logger = getLogger();
|
|
83
|
+
|
|
84
|
+
// ============================================================
|
|
85
|
+
// State (moved from ChatPanel)
|
|
86
|
+
// ============================================================
|
|
87
|
+
|
|
88
|
+
const [restoredMessage, setRestoredMessage] = useState<string | null>(null);
|
|
89
|
+
const [agentUpdateNotification, setAgentUpdateNotification] =
|
|
90
|
+
useState<AgentUpdateNotification | null>(null);
|
|
91
|
+
|
|
92
|
+
// ============================================================
|
|
93
|
+
// Auto-export
|
|
94
|
+
// ============================================================
|
|
95
|
+
|
|
96
|
+
const autoExportIfEnabled = useCallback(
|
|
97
|
+
async (
|
|
98
|
+
trigger: "newChat" | "closeChat",
|
|
99
|
+
triggerMessages: ChatMessage[],
|
|
100
|
+
triggerSession: ChatSession,
|
|
101
|
+
): Promise<void> => {
|
|
102
|
+
const isEnabled =
|
|
103
|
+
trigger === "newChat"
|
|
104
|
+
? plugin.settings.exportSettings.autoExportOnNewChat
|
|
105
|
+
: plugin.settings.exportSettings.autoExportOnCloseChat;
|
|
106
|
+
if (!isEnabled) return;
|
|
107
|
+
if (triggerMessages.length === 0) return;
|
|
108
|
+
if (!triggerSession.sessionId) return;
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
const exporter = new ChatExporter(plugin);
|
|
112
|
+
const openFile =
|
|
113
|
+
plugin.settings.exportSettings.openFileAfterExport;
|
|
114
|
+
const filePath = await exporter.exportToMarkdown(
|
|
115
|
+
triggerMessages,
|
|
116
|
+
triggerSession.agentDisplayName,
|
|
117
|
+
triggerSession.agentId,
|
|
118
|
+
triggerSession.sessionId,
|
|
119
|
+
triggerSession.createdAt,
|
|
120
|
+
openFile,
|
|
121
|
+
);
|
|
122
|
+
if (filePath) {
|
|
123
|
+
const context =
|
|
124
|
+
trigger === "newChat" ? "new session" : "closing chat";
|
|
125
|
+
new Notice(`[Agent Client] Chat exported to ${filePath}`);
|
|
126
|
+
logger.log(`Chat auto-exported before ${context}`);
|
|
127
|
+
}
|
|
128
|
+
} catch {
|
|
129
|
+
new Notice("[Agent Client] Failed to export chat");
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
[plugin, logger],
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
// ============================================================
|
|
136
|
+
// Message Actions
|
|
137
|
+
// ============================================================
|
|
138
|
+
|
|
139
|
+
const shouldConvertToWsl = Platform.isWin && settings.windowsWslMode;
|
|
140
|
+
|
|
141
|
+
const handleSendMessage = useCallback(
|
|
142
|
+
async (content: string, attachments?: AttachedFile[]) => {
|
|
143
|
+
// Dismiss overlays on send
|
|
144
|
+
agent.clearError();
|
|
145
|
+
setAgentUpdateNotification(null);
|
|
146
|
+
|
|
147
|
+
const isFirstMessage = messages.length === 0;
|
|
148
|
+
|
|
149
|
+
// Split attachments by kind
|
|
150
|
+
const images: ImagePromptContent[] = [];
|
|
151
|
+
const resourceLinks: ResourceLinkPromptContent[] = [];
|
|
152
|
+
|
|
153
|
+
if (attachments) {
|
|
154
|
+
for (const file of attachments) {
|
|
155
|
+
if (file.kind === "image" && file.data) {
|
|
156
|
+
images.push({
|
|
157
|
+
type: "image",
|
|
158
|
+
data: file.data,
|
|
159
|
+
mimeType: file.mimeType,
|
|
160
|
+
});
|
|
161
|
+
} else if (file.kind === "file" && file.path) {
|
|
162
|
+
let filePath = file.path;
|
|
163
|
+
if (shouldConvertToWsl) {
|
|
164
|
+
filePath = convertWindowsPathToWsl(filePath);
|
|
165
|
+
}
|
|
166
|
+
resourceLinks.push({
|
|
167
|
+
type: "resource_link",
|
|
168
|
+
uri: buildFileUri(filePath),
|
|
169
|
+
name:
|
|
170
|
+
file.name ??
|
|
171
|
+
file.path.split("/").pop() ??
|
|
172
|
+
"file",
|
|
173
|
+
mimeType: file.mimeType || undefined,
|
|
174
|
+
size: file.size,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
await agent.sendMessage(content, {
|
|
181
|
+
activeNote: settings.autoMentionActiveNote
|
|
182
|
+
? suggestions.mentions.activeNote
|
|
183
|
+
: null,
|
|
184
|
+
vaultBasePath: vaultPath,
|
|
185
|
+
isAutoMentionDisabled:
|
|
186
|
+
suggestions.mentions.isAutoMentionDisabled,
|
|
187
|
+
images: images.length > 0 ? images : undefined,
|
|
188
|
+
resourceLinks:
|
|
189
|
+
resourceLinks.length > 0 ? resourceLinks : undefined,
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// Save session metadata locally on first message
|
|
193
|
+
if (isFirstMessage && session.sessionId) {
|
|
194
|
+
await sessionHistory.saveSessionLocally(
|
|
195
|
+
session.sessionId,
|
|
196
|
+
content,
|
|
197
|
+
);
|
|
198
|
+
logger.log(
|
|
199
|
+
`[ChatPanel] Session saved locally: ${session.sessionId}`,
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
[
|
|
204
|
+
agent.clearError,
|
|
205
|
+
agent.sendMessage,
|
|
206
|
+
messages.length,
|
|
207
|
+
session.sessionId,
|
|
208
|
+
sessionHistory.saveSessionLocally,
|
|
209
|
+
logger,
|
|
210
|
+
settings.autoMentionActiveNote,
|
|
211
|
+
suggestions.mentions.activeNote,
|
|
212
|
+
suggestions.mentions.isAutoMentionDisabled,
|
|
213
|
+
shouldConvertToWsl,
|
|
214
|
+
vaultPath,
|
|
215
|
+
],
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
const handleStopGeneration = useCallback(async () => {
|
|
219
|
+
logger.log("Cancelling current operation...");
|
|
220
|
+
const lastMessage = agent.lastUserMessage;
|
|
221
|
+
await agent.cancelOperation();
|
|
222
|
+
if (lastMessage) {
|
|
223
|
+
setRestoredMessage(lastMessage);
|
|
224
|
+
}
|
|
225
|
+
}, [logger, agent.cancelOperation, agent.lastUserMessage]);
|
|
226
|
+
|
|
227
|
+
const handleNewChat = useCallback(
|
|
228
|
+
async (requestedAgentId?: string) => {
|
|
229
|
+
const isAgentSwitch =
|
|
230
|
+
requestedAgentId && requestedAgentId !== session.agentId;
|
|
231
|
+
|
|
232
|
+
// Skip if already empty AND not switching agents
|
|
233
|
+
if (messages.length === 0 && !isAgentSwitch) {
|
|
234
|
+
new Notice("[Agent Client] Already a new session");
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Cancel ongoing generation before starting new chat
|
|
239
|
+
if (agent.isSending) {
|
|
240
|
+
await agent.cancelOperation();
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
logger.log(
|
|
244
|
+
`[Debug] Creating new session${isAgentSwitch ? ` with agent: ${requestedAgentId}` : ""}...`,
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
// Auto-export current chat before starting new one (if has messages)
|
|
248
|
+
if (messages.length > 0) {
|
|
249
|
+
await autoExportIfEnabled("newChat", messages, session);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
suggestions.mentions.toggleAutoMention(false);
|
|
253
|
+
agent.clearMessages();
|
|
254
|
+
|
|
255
|
+
const newAgentId = isAgentSwitch
|
|
256
|
+
? requestedAgentId
|
|
257
|
+
: session.agentId;
|
|
258
|
+
await agent.restartSession(newAgentId);
|
|
259
|
+
|
|
260
|
+
// Invalidate session history cache when creating new session
|
|
261
|
+
sessionHistory.invalidateCache();
|
|
262
|
+
},
|
|
263
|
+
[
|
|
264
|
+
messages,
|
|
265
|
+
session,
|
|
266
|
+
logger,
|
|
267
|
+
autoExportIfEnabled,
|
|
268
|
+
agent.isSending,
|
|
269
|
+
agent.cancelOperation,
|
|
270
|
+
agent.clearMessages,
|
|
271
|
+
agent.restartSession,
|
|
272
|
+
suggestions.mentions.toggleAutoMention,
|
|
273
|
+
sessionHistory.invalidateCache,
|
|
274
|
+
],
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
const handleExportChat = useCallback(async () => {
|
|
278
|
+
if (messages.length === 0) {
|
|
279
|
+
new Notice("[Agent Client] No messages to export");
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
try {
|
|
284
|
+
const exporter = new ChatExporter(plugin);
|
|
285
|
+
const openFile = plugin.settings.exportSettings.openFileAfterExport;
|
|
286
|
+
const filePath = await exporter.exportToMarkdown(
|
|
287
|
+
messages,
|
|
288
|
+
session.agentDisplayName,
|
|
289
|
+
session.agentId,
|
|
290
|
+
session.sessionId || "unknown",
|
|
291
|
+
session.createdAt,
|
|
292
|
+
openFile,
|
|
293
|
+
);
|
|
294
|
+
new Notice(`[Agent Client] Chat exported to ${filePath}`);
|
|
295
|
+
} catch (error) {
|
|
296
|
+
new Notice("[Agent Client] Failed to export chat");
|
|
297
|
+
logger.error("Export error:", error);
|
|
298
|
+
}
|
|
299
|
+
}, [messages, session, plugin, logger]);
|
|
300
|
+
|
|
301
|
+
const handleSwitchAgent = useCallback(
|
|
302
|
+
async (agentId: string) => {
|
|
303
|
+
if (agentId !== session.agentId) {
|
|
304
|
+
await handleNewChat(agentId);
|
|
305
|
+
}
|
|
306
|
+
},
|
|
307
|
+
[session.agentId, handleNewChat],
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
const handleRestartAgent = useCallback(async () => {
|
|
311
|
+
logger.log("[ChatPanel] Restarting agent process...");
|
|
312
|
+
|
|
313
|
+
// Auto-export current chat before restart (if has messages)
|
|
314
|
+
if (messages.length > 0) {
|
|
315
|
+
await autoExportIfEnabled("newChat", messages, session);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Clear messages for fresh start
|
|
319
|
+
agent.clearMessages();
|
|
320
|
+
|
|
321
|
+
try {
|
|
322
|
+
await agent.forceRestartAgent();
|
|
323
|
+
new Notice("[Agent Client] Agent restarted");
|
|
324
|
+
} catch (error) {
|
|
325
|
+
new Notice("[Agent Client] Failed to restart agent");
|
|
326
|
+
logger.error("Restart error:", error);
|
|
327
|
+
}
|
|
328
|
+
}, [
|
|
329
|
+
logger,
|
|
330
|
+
messages,
|
|
331
|
+
session,
|
|
332
|
+
autoExportIfEnabled,
|
|
333
|
+
agent.clearMessages,
|
|
334
|
+
agent.forceRestartAgent,
|
|
335
|
+
]);
|
|
336
|
+
|
|
337
|
+
// ============================================================
|
|
338
|
+
// Config Actions
|
|
339
|
+
// ============================================================
|
|
340
|
+
|
|
341
|
+
const handleSetMode = useCallback(
|
|
342
|
+
async (modeId: string) => {
|
|
343
|
+
await agent.setMode(modeId);
|
|
344
|
+
},
|
|
345
|
+
[agent.setMode],
|
|
346
|
+
);
|
|
347
|
+
|
|
348
|
+
const handleSetModel = useCallback(
|
|
349
|
+
async (modelId: string) => {
|
|
350
|
+
await agent.setModel(modelId);
|
|
351
|
+
},
|
|
352
|
+
[agent.setModel],
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
const handleSetConfigOption = useCallback(
|
|
356
|
+
async (configId: string, value: string) => {
|
|
357
|
+
await agent.setConfigOption(configId, value);
|
|
358
|
+
},
|
|
359
|
+
[agent.setConfigOption],
|
|
360
|
+
);
|
|
361
|
+
|
|
362
|
+
// ============================================================
|
|
363
|
+
// UI State Actions
|
|
364
|
+
// ============================================================
|
|
365
|
+
|
|
366
|
+
const handleClearError = useCallback(() => {
|
|
367
|
+
agent.clearError();
|
|
368
|
+
}, [agent.clearError]);
|
|
369
|
+
|
|
370
|
+
const handleClearAgentUpdate = useCallback(() => {
|
|
371
|
+
setAgentUpdateNotification(null);
|
|
372
|
+
}, []);
|
|
373
|
+
|
|
374
|
+
const handleRestoredMessageConsumed = useCallback(() => {
|
|
375
|
+
setRestoredMessage(null);
|
|
376
|
+
}, []);
|
|
377
|
+
|
|
378
|
+
// ============================================================
|
|
379
|
+
// Return
|
|
380
|
+
// ============================================================
|
|
381
|
+
|
|
382
|
+
return {
|
|
383
|
+
handleSendMessage,
|
|
384
|
+
handleStopGeneration,
|
|
385
|
+
handleNewChat,
|
|
386
|
+
handleExportChat,
|
|
387
|
+
handleSwitchAgent,
|
|
388
|
+
handleRestartAgent,
|
|
389
|
+
handleSetMode,
|
|
390
|
+
handleSetModel,
|
|
391
|
+
handleSetConfigOption,
|
|
392
|
+
handleClearError,
|
|
393
|
+
handleClearAgentUpdate,
|
|
394
|
+
handleRestoredMessageConsumed,
|
|
395
|
+
restoredMessage,
|
|
396
|
+
agentUpdateNotification,
|
|
397
|
+
setAgentUpdateNotification,
|
|
398
|
+
autoExportIfEnabled,
|
|
399
|
+
};
|
|
400
|
+
}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { useRef, useCallback, useEffect } from "react";
|
|
2
|
+
import { Notice, Platform } from "obsidian";
|
|
3
|
+
import { SessionHistoryModal } from "../ui/SessionHistoryModal";
|
|
4
|
+
import { getLogger } from "../utils/logger";
|
|
5
|
+
import { convertWslPathToWindows } from "../utils/platform";
|
|
6
|
+
import type AgentClientPlugin from "../plugin";
|
|
7
|
+
import type { UseAgentReturn } from "./useAgent";
|
|
8
|
+
import type { UseSessionHistoryReturn } from "./useSessionHistory";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Hook for managing the session history modal lifecycle.
|
|
12
|
+
*
|
|
13
|
+
* Encapsulates modal creation, props synchronization, and
|
|
14
|
+
* session operation callbacks (restore, fork, delete).
|
|
15
|
+
*
|
|
16
|
+
* @param plugin - Plugin instance for app access
|
|
17
|
+
* @param agent - Agent hook for clearMessages
|
|
18
|
+
* @param sessionHistory - Session history hook for operations
|
|
19
|
+
* @param vaultPath - Current working directory
|
|
20
|
+
* @param isSessionReady - Whether the session is ready
|
|
21
|
+
* @param debugMode - Whether debug mode is enabled
|
|
22
|
+
*/
|
|
23
|
+
export function useHistoryModal(
|
|
24
|
+
plugin: AgentClientPlugin,
|
|
25
|
+
agent: UseAgentReturn,
|
|
26
|
+
sessionHistory: UseSessionHistoryReturn,
|
|
27
|
+
vaultPath: string,
|
|
28
|
+
isSessionReady: boolean,
|
|
29
|
+
debugMode: boolean,
|
|
30
|
+
onAgentCwdChange?: (cwd: string) => void,
|
|
31
|
+
): {
|
|
32
|
+
handleOpenHistory: () => void;
|
|
33
|
+
} {
|
|
34
|
+
const logger = getLogger();
|
|
35
|
+
const historyModalRef = useRef<SessionHistoryModal | null>(null);
|
|
36
|
+
|
|
37
|
+
const handleRestoreSession = useCallback(
|
|
38
|
+
async (sessionId: string, cwd: string) => {
|
|
39
|
+
try {
|
|
40
|
+
logger.log(`[ChatPanel] Restoring session: ${sessionId}`);
|
|
41
|
+
agent.clearMessages();
|
|
42
|
+
await sessionHistory.restoreSession(sessionId, cwd);
|
|
43
|
+
onAgentCwdChange?.(
|
|
44
|
+
Platform.isWin ? convertWslPathToWindows(cwd) : cwd,
|
|
45
|
+
);
|
|
46
|
+
new Notice("[Agent Client] Session restored");
|
|
47
|
+
} catch (error) {
|
|
48
|
+
new Notice("[Agent Client] Failed to restore session");
|
|
49
|
+
logger.error("Session restore error:", error);
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
[
|
|
53
|
+
logger,
|
|
54
|
+
agent.clearMessages,
|
|
55
|
+
sessionHistory.restoreSession,
|
|
56
|
+
onAgentCwdChange,
|
|
57
|
+
],
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
const handleForkSession = useCallback(
|
|
61
|
+
async (sessionId: string, cwd: string) => {
|
|
62
|
+
try {
|
|
63
|
+
logger.log(`[ChatPanel] Forking session: ${sessionId}`);
|
|
64
|
+
agent.clearMessages();
|
|
65
|
+
await sessionHistory.forkSession(sessionId, cwd);
|
|
66
|
+
onAgentCwdChange?.(
|
|
67
|
+
Platform.isWin ? convertWslPathToWindows(cwd) : cwd,
|
|
68
|
+
);
|
|
69
|
+
new Notice("[Agent Client] Session forked");
|
|
70
|
+
} catch (error) {
|
|
71
|
+
new Notice("[Agent Client] Failed to fork session");
|
|
72
|
+
logger.error("Session fork error:", error);
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
[
|
|
76
|
+
logger,
|
|
77
|
+
agent.clearMessages,
|
|
78
|
+
sessionHistory.forkSession,
|
|
79
|
+
onAgentCwdChange,
|
|
80
|
+
],
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
const handleDeleteSession = useCallback(
|
|
84
|
+
async (sessionId: string) => {
|
|
85
|
+
try {
|
|
86
|
+
logger.log(`[ChatPanel] Deleting session: ${sessionId}`);
|
|
87
|
+
await sessionHistory.deleteSession(sessionId);
|
|
88
|
+
new Notice("[Agent Client] Session deleted");
|
|
89
|
+
} catch (error) {
|
|
90
|
+
new Notice("[Agent Client] Failed to delete session");
|
|
91
|
+
logger.error("Session delete error:", error);
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
[sessionHistory.deleteSession, logger],
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
const handleEditTitle = useCallback(
|
|
98
|
+
async (sessionId: string, newTitle: string, sessionCwd: string) => {
|
|
99
|
+
try {
|
|
100
|
+
await sessionHistory.updateSessionTitle(
|
|
101
|
+
sessionId,
|
|
102
|
+
newTitle,
|
|
103
|
+
sessionCwd,
|
|
104
|
+
);
|
|
105
|
+
new Notice("[Agent Client] Title updated");
|
|
106
|
+
} catch (error) {
|
|
107
|
+
new Notice("[Agent Client] Failed to update title");
|
|
108
|
+
logger.error("Title update error:", error);
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
[sessionHistory.updateSessionTitle, logger],
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
const handleLoadMore = useCallback(() => {
|
|
115
|
+
void sessionHistory.loadMoreSessions();
|
|
116
|
+
}, [sessionHistory.loadMoreSessions]);
|
|
117
|
+
|
|
118
|
+
const handleFetchSessions = useCallback(
|
|
119
|
+
(cwd?: string) => {
|
|
120
|
+
void sessionHistory.fetchSessions(cwd);
|
|
121
|
+
},
|
|
122
|
+
[sessionHistory.fetchSessions],
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
const handleOpenHistory = useCallback(() => {
|
|
126
|
+
// Create modal if it doesn't exist
|
|
127
|
+
if (!historyModalRef.current) {
|
|
128
|
+
historyModalRef.current = new SessionHistoryModal(plugin.app, {
|
|
129
|
+
sessions: sessionHistory.sessions,
|
|
130
|
+
loading: sessionHistory.loading,
|
|
131
|
+
error: sessionHistory.error,
|
|
132
|
+
hasMore: sessionHistory.hasMore,
|
|
133
|
+
currentCwd: vaultPath,
|
|
134
|
+
canList: sessionHistory.canList,
|
|
135
|
+
canRestore: sessionHistory.canRestore,
|
|
136
|
+
canFork: sessionHistory.canFork,
|
|
137
|
+
isUsingLocalSessions: sessionHistory.isUsingLocalSessions,
|
|
138
|
+
localSessionIds: sessionHistory.localSessionIds,
|
|
139
|
+
isAgentReady: isSessionReady,
|
|
140
|
+
debugMode: debugMode,
|
|
141
|
+
onRestoreSession: handleRestoreSession,
|
|
142
|
+
onForkSession: handleForkSession,
|
|
143
|
+
onDeleteSession: handleDeleteSession,
|
|
144
|
+
onEditTitle: handleEditTitle,
|
|
145
|
+
onLoadMore: handleLoadMore,
|
|
146
|
+
onFetchSessions: handleFetchSessions,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
historyModalRef.current.open();
|
|
150
|
+
void sessionHistory.fetchSessions(vaultPath);
|
|
151
|
+
}, [
|
|
152
|
+
plugin.app,
|
|
153
|
+
sessionHistory.sessions,
|
|
154
|
+
sessionHistory.loading,
|
|
155
|
+
sessionHistory.error,
|
|
156
|
+
sessionHistory.hasMore,
|
|
157
|
+
sessionHistory.canList,
|
|
158
|
+
sessionHistory.canRestore,
|
|
159
|
+
sessionHistory.canFork,
|
|
160
|
+
sessionHistory.isUsingLocalSessions,
|
|
161
|
+
sessionHistory.localSessionIds,
|
|
162
|
+
sessionHistory.fetchSessions,
|
|
163
|
+
vaultPath,
|
|
164
|
+
isSessionReady,
|
|
165
|
+
debugMode,
|
|
166
|
+
handleRestoreSession,
|
|
167
|
+
handleForkSession,
|
|
168
|
+
handleDeleteSession,
|
|
169
|
+
handleEditTitle,
|
|
170
|
+
handleLoadMore,
|
|
171
|
+
handleFetchSessions,
|
|
172
|
+
]);
|
|
173
|
+
|
|
174
|
+
// Update modal props when session history state changes
|
|
175
|
+
useEffect(() => {
|
|
176
|
+
if (historyModalRef.current) {
|
|
177
|
+
historyModalRef.current.updateProps({
|
|
178
|
+
sessions: sessionHistory.sessions,
|
|
179
|
+
loading: sessionHistory.loading,
|
|
180
|
+
error: sessionHistory.error,
|
|
181
|
+
hasMore: sessionHistory.hasMore,
|
|
182
|
+
currentCwd: vaultPath,
|
|
183
|
+
canList: sessionHistory.canList,
|
|
184
|
+
canRestore: sessionHistory.canRestore,
|
|
185
|
+
canFork: sessionHistory.canFork,
|
|
186
|
+
isUsingLocalSessions: sessionHistory.isUsingLocalSessions,
|
|
187
|
+
localSessionIds: sessionHistory.localSessionIds,
|
|
188
|
+
isAgentReady: isSessionReady,
|
|
189
|
+
debugMode: debugMode,
|
|
190
|
+
onRestoreSession: handleRestoreSession,
|
|
191
|
+
onForkSession: handleForkSession,
|
|
192
|
+
onDeleteSession: handleDeleteSession,
|
|
193
|
+
onEditTitle: handleEditTitle,
|
|
194
|
+
onLoadMore: handleLoadMore,
|
|
195
|
+
onFetchSessions: handleFetchSessions,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}, [
|
|
199
|
+
sessionHistory.sessions,
|
|
200
|
+
sessionHistory.loading,
|
|
201
|
+
sessionHistory.error,
|
|
202
|
+
sessionHistory.hasMore,
|
|
203
|
+
sessionHistory.canList,
|
|
204
|
+
sessionHistory.canRestore,
|
|
205
|
+
sessionHistory.canFork,
|
|
206
|
+
sessionHistory.isUsingLocalSessions,
|
|
207
|
+
vaultPath,
|
|
208
|
+
isSessionReady,
|
|
209
|
+
debugMode,
|
|
210
|
+
handleRestoreSession,
|
|
211
|
+
handleForkSession,
|
|
212
|
+
handleDeleteSession,
|
|
213
|
+
handleEditTitle,
|
|
214
|
+
handleLoadMore,
|
|
215
|
+
handleFetchSessions,
|
|
216
|
+
]);
|
|
217
|
+
|
|
218
|
+
return { handleOpenHistory };
|
|
219
|
+
}
|