feat(vscode-ide-companion): split module & notes in english

This commit is contained in:
yiliang114
2025-11-25 00:32:51 +08:00
parent 3cf22c065f
commit f503eb2520
42 changed files with 4189 additions and 3063 deletions

View File

@@ -0,0 +1,134 @@
/**
* @license
* Copyright 2025 Qwen Team
* SPDX-License-Identifier: Apache-2.0
*/
import { useState, useCallback, useRef } from 'react';
import type { VSCodeAPI } from '../hooks/useVSCode.js';
/**
* File context management Hook
* Manages active file, selection content, and workspace file list
*/
export const useFileContext = (vscode: VSCodeAPI) => {
const [activeFileName, setActiveFileName] = useState<string | null>(null);
const [activeFilePath, setActiveFilePath] = useState<string | null>(null);
const [activeSelection, setActiveSelection] = useState<{
startLine: number;
endLine: number;
} | null>(null);
const [workspaceFiles, setWorkspaceFiles] = useState<
Array<{
id: string;
label: string;
description: string;
path: string;
}>
>([]);
// File reference mapping: @filename -> full path
const fileReferenceMap = useRef<Map<string, string>>(new Map());
// Whether workspace files have been requested
const hasRequestedFilesRef = useRef(false);
// Search debounce timer
const searchTimerRef = useRef<NodeJS.Timeout | null>(null);
/**
* Request workspace files
*/
const requestWorkspaceFiles = useCallback(
(query?: string) => {
if (!hasRequestedFilesRef.current && !query) {
hasRequestedFilesRef.current = true;
}
// If there's a query, clear previous timer and set up debounce
if (query && query.length >= 1) {
if (searchTimerRef.current) {
clearTimeout(searchTimerRef.current);
}
searchTimerRef.current = setTimeout(() => {
vscode.postMessage({
type: 'getWorkspaceFiles',
data: { query },
});
}, 300);
} else {
vscode.postMessage({
type: 'getWorkspaceFiles',
data: query ? { query } : {},
});
}
},
[vscode],
);
/**
* Add file reference
*/
const addFileReference = useCallback((fileName: string, filePath: string) => {
fileReferenceMap.current.set(fileName, filePath);
}, []);
/**
* Get file reference
*/
const getFileReference = useCallback(
(fileName: string) => fileReferenceMap.current.get(fileName),
[],
);
/**
* Clear file references
*/
const clearFileReferences = useCallback(() => {
fileReferenceMap.current.clear();
}, []);
/**
* Request active editor info
*/
const requestActiveEditor = useCallback(() => {
vscode.postMessage({ type: 'getActiveEditor', data: {} });
}, [vscode]);
/**
* Focus on active editor
*/
const focusActiveEditor = useCallback(() => {
vscode.postMessage({
type: 'focusActiveEditor',
data: {},
});
}, [vscode]);
return {
// State
activeFileName,
activeFilePath,
activeSelection,
workspaceFiles,
hasRequestedFiles: hasRequestedFilesRef.current,
// State setters
setActiveFileName,
setActiveFilePath,
setActiveSelection,
setWorkspaceFiles,
// File reference operations
addFileReference,
getFileReference,
clearFileReferences,
// Operations
requestWorkspaceFiles,
requestActiveEditor,
focusActiveEditor,
};
};

View File

@@ -0,0 +1,123 @@
/**
* @license
* Copyright 2025 Qwen Team
* SPDX-License-Identifier: Apache-2.0
*/
import { useState, useRef, useCallback } from 'react';
export interface TextMessage {
role: 'user' | 'assistant' | 'thinking';
content: string;
timestamp: number;
fileContext?: {
fileName: string;
filePath: string;
startLine?: number;
endLine?: number;
};
}
/**
* Message handling Hook
* Manages message list, streaming responses, and loading state
*/
export const useMessageHandling = () => {
const [messages, setMessages] = useState<TextMessage[]>([]);
const [isStreaming, setIsStreaming] = useState(false);
const [isWaitingForResponse, setIsWaitingForResponse] = useState(false);
const [loadingMessage, setLoadingMessage] = useState('');
const [currentStreamContent, setCurrentStreamContent] = useState('');
// Use ref to store current stream content, avoiding useEffect dependency issues
const currentStreamContentRef = useRef<string>('');
/**
* Add message
*/
const addMessage = useCallback((message: TextMessage) => {
setMessages((prev) => [...prev, message]);
}, []);
/**
* Clear messages
*/
const clearMessages = useCallback(() => {
setMessages([]);
}, []);
/**
* Start streaming response
*/
const startStreaming = useCallback(() => {
setIsStreaming(true);
setCurrentStreamContent('');
currentStreamContentRef.current = '';
}, []);
/**
* Add stream chunk
*/
const appendStreamChunk = useCallback((chunk: string) => {
setCurrentStreamContent((prev) => {
const newContent = prev + chunk;
currentStreamContentRef.current = newContent;
return newContent;
});
}, []);
/**
* End streaming response
*/
const endStreaming = useCallback(() => {
// If there is streaming content, add it as complete assistant message
if (currentStreamContentRef.current) {
const assistantMessage: TextMessage = {
role: 'assistant',
content: currentStreamContentRef.current,
timestamp: Date.now(),
};
setMessages((prev) => [...prev, assistantMessage]);
}
setIsStreaming(false);
setIsWaitingForResponse(false);
setCurrentStreamContent('');
currentStreamContentRef.current = '';
}, []);
/**
* Set waiting for response state
*/
const setWaitingForResponse = useCallback((message: string) => {
setIsWaitingForResponse(true);
setLoadingMessage(message);
}, []);
/**
* Clear waiting for response state
*/
const clearWaitingForResponse = useCallback(() => {
setIsWaitingForResponse(false);
setLoadingMessage('');
}, []);
return {
// State
messages,
isStreaming,
isWaitingForResponse,
loadingMessage,
currentStreamContent,
// Operations
addMessage,
clearMessages,
startStreaming,
appendStreamChunk,
endStreaming,
setWaitingForResponse,
clearWaitingForResponse,
setMessages,
};
};

View File

@@ -0,0 +1,136 @@
/**
* @license
* Copyright 2025 Qwen Team
* SPDX-License-Identifier: Apache-2.0
*/
import { useState, useCallback, useMemo } from 'react';
import type { VSCodeAPI } from '../hooks/useVSCode.js';
/**
* Session management Hook
* Manages session list, current session, session switching, and search
*/
export const useSessionManagement = (vscode: VSCodeAPI) => {
const [qwenSessions, setQwenSessions] = useState<
Array<Record<string, unknown>>
>([]);
const [currentSessionId, setCurrentSessionId] = useState<string | null>(null);
const [currentSessionTitle, setCurrentSessionTitle] =
useState<string>('Past Conversations');
const [showSessionSelector, setShowSessionSelector] = useState(false);
const [sessionSearchQuery, setSessionSearchQuery] = useState('');
const [savedSessionTags, setSavedSessionTags] = useState<string[]>([]);
/**
* Filter session list
*/
const filteredSessions = useMemo(() => {
if (!sessionSearchQuery.trim()) {
return qwenSessions;
}
const query = sessionSearchQuery.toLowerCase();
return qwenSessions.filter((session) => {
const title = (
(session.title as string) ||
(session.name as string) ||
''
).toLowerCase();
return title.includes(query);
});
}, [qwenSessions, sessionSearchQuery]);
/**
* Load session list
*/
const handleLoadQwenSessions = useCallback(() => {
vscode.postMessage({ type: 'getQwenSessions', data: {} });
setShowSessionSelector(true);
}, [vscode]);
/**
* Create new session
*/
const handleNewQwenSession = useCallback(() => {
vscode.postMessage({ type: 'openNewChatTab', data: {} });
setShowSessionSelector(false);
}, [vscode]);
/**
* Switch session
*/
const handleSwitchSession = useCallback(
(sessionId: string) => {
if (sessionId === currentSessionId) {
console.log('[useSessionManagement] Already on this session, ignoring');
setShowSessionSelector(false);
return;
}
console.log('[useSessionManagement] Switching to session:', sessionId);
vscode.postMessage({
type: 'switchQwenSession',
data: { sessionId },
});
},
[currentSessionId, vscode],
);
/**
* Save session
*/
const handleSaveSession = useCallback(
(tag: string) => {
vscode.postMessage({
type: 'saveSession',
data: { tag },
});
},
[vscode],
);
/**
* 处理Save session响应
*/
const handleSaveSessionResponse = useCallback(
(response: { success: boolean; message?: string }) => {
if (response.success) {
if (response.message) {
const tagMatch = response.message.match(/tag: (.+)$/);
if (tagMatch) {
setSavedSessionTags((prev) => [...prev, tagMatch[1]]);
}
}
} else {
console.error('Failed to save session:', response.message);
}
},
[],
);
return {
// State
qwenSessions,
currentSessionId,
currentSessionTitle,
showSessionSelector,
sessionSearchQuery,
filteredSessions,
savedSessionTags,
// State setters
setQwenSessions,
setCurrentSessionId,
setCurrentSessionTitle,
setShowSessionSelector,
setSessionSearchQuery,
setSavedSessionTags,
// Operations
handleLoadQwenSessions,
handleNewQwenSession,
handleSwitchSession,
handleSaveSession,
handleSaveSessionResponse,
};
};

View File

@@ -0,0 +1,148 @@
/**
* @license
* Copyright 2025 Qwen Team
* SPDX-License-Identifier: Apache-2.0
*/
import { useCallback } from 'react';
import type { VSCodeAPI } from './useVSCode.js';
import { getRandomLoadingMessage } from '../constants/loadingMessages.js';
interface UseMessageSubmitProps {
vscode: VSCodeAPI;
inputText: string;
setInputText: (text: string) => void;
inputFieldRef: React.RefObject<HTMLDivElement>;
isStreaming: boolean;
fileContext: {
getFileReference: (fileName: string) => string | undefined;
activeFilePath: string | null;
activeFileName: string | null;
activeSelection: { startLine: number; endLine: number } | null;
clearFileReferences: () => void;
};
messageHandling: {
setWaitingForResponse: (message: string) => void;
};
}
/**
* Message submit Hook
* Handles message submission logic and context parsing
*/
export const useMessageSubmit = ({
vscode,
inputText,
setInputText,
inputFieldRef,
isStreaming,
fileContext,
messageHandling,
}: UseMessageSubmitProps) => {
const handleSubmit = useCallback(
(e: React.FormEvent) => {
e.preventDefault();
if (!inputText.trim() || isStreaming) {
return;
}
// Handle /login command
if (inputText.trim() === '/login') {
setInputText('');
if (inputFieldRef.current) {
inputFieldRef.current.textContent = '';
}
vscode.postMessage({
type: 'login',
data: {},
});
return;
}
messageHandling.setWaitingForResponse(getRandomLoadingMessage());
// Parse @file references from input text
const context: Array<{
type: string;
name: string;
value: string;
startLine?: number;
endLine?: number;
}> = [];
const fileRefPattern = /@([^\s]+)/g;
let match;
while ((match = fileRefPattern.exec(inputText)) !== null) {
const fileName = match[1];
const filePath = fileContext.getFileReference(fileName);
if (filePath) {
context.push({
type: 'file',
name: fileName,
value: filePath,
});
}
}
// Add active file selection context if present
if (fileContext.activeFilePath) {
const fileName = fileContext.activeFileName || 'current file';
context.push({
type: 'file',
name: fileName,
value: fileContext.activeFilePath,
startLine: fileContext.activeSelection?.startLine,
endLine: fileContext.activeSelection?.endLine,
});
}
let fileContextForMessage:
| {
fileName: string;
filePath: string;
startLine?: number;
endLine?: number;
}
| undefined;
if (fileContext.activeFilePath && fileContext.activeFileName) {
fileContextForMessage = {
fileName: fileContext.activeFileName,
filePath: fileContext.activeFilePath,
startLine: fileContext.activeSelection?.startLine,
endLine: fileContext.activeSelection?.endLine,
};
}
vscode.postMessage({
type: 'sendMessage',
data: {
text: inputText,
context: context.length > 0 ? context : undefined,
fileContext: fileContextForMessage,
},
});
setInputText('');
if (inputFieldRef.current) {
inputFieldRef.current.textContent = '';
}
fileContext.clearFileReferences();
},
[
inputText,
isStreaming,
setInputText,
inputFieldRef,
vscode,
fileContext,
messageHandling,
],
);
return { handleSubmit };
};

View File

@@ -0,0 +1,127 @@
/**
* @license
* Copyright 2025 Qwen Team
* SPDX-License-Identifier: Apache-2.0
*/
import { useState, useCallback } from 'react';
import type { ToolCallData } from '../components/ToolCall.js';
import type { ToolCallUpdate } from '../types/toolCall.js';
/**
* Tool call management Hook
* Manages tool call states and updates
*/
export const useToolCalls = () => {
const [toolCalls, setToolCalls] = useState<Map<string, ToolCallData>>(
new Map(),
);
/**
* Handle tool call update
*/
const handleToolCallUpdate = useCallback((update: ToolCallUpdate) => {
setToolCalls((prevToolCalls) => {
const newMap = new Map(prevToolCalls);
const existing = newMap.get(update.toolCallId);
const safeTitle = (title: unknown): string => {
if (typeof title === 'string') {
return title;
}
if (title && typeof title === 'object') {
return JSON.stringify(title);
}
return 'Tool Call';
};
if (update.type === 'tool_call') {
const content = update.content?.map((item) => ({
type: item.type as 'content' | 'diff',
content: item.content,
path: item.path,
oldText: item.oldText,
newText: item.newText,
}));
newMap.set(update.toolCallId, {
toolCallId: update.toolCallId,
kind: update.kind || 'other',
title: safeTitle(update.title),
status: update.status || 'pending',
rawInput: update.rawInput as string | object | undefined,
content,
locations: update.locations,
});
} else if (update.type === 'tool_call_update') {
const updatedContent = update.content
? update.content.map((item) => ({
type: item.type as 'content' | 'diff',
content: item.content,
path: item.path,
oldText: item.oldText,
newText: item.newText,
}))
: undefined;
if (existing) {
const mergedContent = updatedContent
? [...(existing.content || []), ...updatedContent]
: existing.content;
newMap.set(update.toolCallId, {
...existing,
...(update.kind && { kind: update.kind }),
...(update.title && { title: safeTitle(update.title) }),
...(update.status && { status: update.status }),
content: mergedContent,
...(update.locations && { locations: update.locations }),
});
} else {
newMap.set(update.toolCallId, {
toolCallId: update.toolCallId,
kind: update.kind || 'other',
title: update.title ? safeTitle(update.title) : '',
status: update.status || 'pending',
rawInput: update.rawInput as string | object | undefined,
content: updatedContent,
locations: update.locations,
});
}
}
return newMap;
});
}, []);
/**
* Clear all tool calls
*/
const clearToolCalls = useCallback(() => {
setToolCalls(new Map());
}, []);
/**
* Get in-progress tool calls
*/
const inProgressToolCalls = Array.from(toolCalls.values()).filter(
(toolCall) =>
toolCall.status === 'pending' || toolCall.status === 'in_progress',
);
/**
* Get completed tool calls
*/
const completedToolCalls = Array.from(toolCalls.values()).filter(
(toolCall) =>
toolCall.status === 'completed' || toolCall.status === 'failed',
);
return {
toolCalls,
inProgressToolCalls,
completedToolCalls,
handleToolCallUpdate,
clearToolCalls,
};
};

View File

@@ -13,14 +13,14 @@ export interface VSCodeAPI {
declare const acquireVsCodeApi: () => VSCodeAPI;
/**
* 模块级别的 VS Code API 实例缓存
* acquireVsCodeApi() 只能调用一次,必须在模块级别缓存
* Module-level VS Code API instance cache
* acquireVsCodeApi() can only be called once, must be cached at module level
*/
let vscodeApiInstance: VSCodeAPI | null = null;
/**
* 获取 VS Code API 实例
* 使用模块级别缓存确保 acquireVsCodeApi() 只被调用一次
* Get VS Code API instance
* Uses module-level cache to ensure acquireVsCodeApi() is only called once
*/
function getVSCodeAPI(): VSCodeAPI {
if (vscodeApiInstance) {
@@ -47,7 +47,7 @@ function getVSCodeAPI(): VSCodeAPI {
/**
* Hook to get VS Code API
* 多个组件可以安全地调用此 hookAPI 实例会被复用
* Multiple components can safely call this hook, API instance will be reused
*/
export function useVSCode(): VSCodeAPI {
return getVSCodeAPI();

View File

@@ -0,0 +1,380 @@
/**
* @license
* Copyright 2025 Qwen Team
* SPDX-License-Identifier: Apache-2.0
*/
import { useEffect, useRef, useCallback } from 'react';
import type { Conversation } from '../../storage/conversationStore.js';
import type {
PermissionOption,
ToolCall as PermissionToolCall,
} from '../components/PermissionRequest.js';
import type { PlanEntry } from '../components/PlanDisplay.js';
import type { ToolCallUpdate } from '../types/toolCall.js';
interface UseWebViewMessagesProps {
// Session management
sessionManagement: {
currentSessionId: string | null;
setQwenSessions: (sessions: Array<Record<string, unknown>>) => void;
setCurrentSessionId: (id: string | null) => void;
setCurrentSessionTitle: (title: string) => void;
setShowSessionSelector: (show: boolean) => void;
handleSaveSessionResponse: (response: {
success: boolean;
message?: string;
}) => void;
};
// File context
fileContext: {
setActiveFileName: (name: string | null) => void;
setActiveFilePath: (path: string | null) => void;
setActiveSelection: (
selection: { startLine: number; endLine: number } | null,
) => void;
setWorkspaceFiles: (
files: Array<{
id: string;
label: string;
description: string;
path: string;
}>,
) => void;
addFileReference: (name: string, path: string) => void;
};
// Message handling
messageHandling: {
setMessages: (
messages: Array<{
role: 'user' | 'assistant' | 'thinking';
content: string;
timestamp: number;
fileContext?: {
fileName: string;
filePath: string;
startLine?: number;
endLine?: number;
};
}>,
) => void;
addMessage: (message: {
role: 'user' | 'assistant' | 'thinking';
content: string;
timestamp: number;
}) => void;
clearMessages: () => void;
startStreaming: () => void;
appendStreamChunk: (chunk: string) => void;
endStreaming: () => void;
clearWaitingForResponse: () => void;
};
// Tool calls
handleToolCallUpdate: (update: ToolCallUpdate) => void;
clearToolCalls: () => void;
// Plan
setPlanEntries: (entries: PlanEntry[]) => void;
// Permission
handlePermissionRequest: (request: {
options: PermissionOption[];
toolCall: PermissionToolCall;
}) => void;
// Input
inputFieldRef: React.RefObject<HTMLDivElement>;
setInputText: (text: string) => void;
}
/**
* WebView message handling Hook
* Handles all messages from VSCode Extension uniformly
*/
export const useWebViewMessages = ({
sessionManagement,
fileContext,
messageHandling,
handleToolCallUpdate,
clearToolCalls,
setPlanEntries,
handlePermissionRequest,
inputFieldRef,
setInputText,
}: UseWebViewMessagesProps) => {
// Use ref to store callbacks to avoid useEffect dependency issues
const handlersRef = useRef({
sessionManagement,
fileContext,
messageHandling,
handleToolCallUpdate,
clearToolCalls,
setPlanEntries,
handlePermissionRequest,
});
// Update refs
useEffect(() => {
handlersRef.current = {
sessionManagement,
fileContext,
messageHandling,
handleToolCallUpdate,
clearToolCalls,
setPlanEntries,
handlePermissionRequest,
};
});
const handleMessage = useCallback(
(event: MessageEvent) => {
const message = event.data;
const handlers = handlersRef.current;
switch (message.type) {
case 'conversationLoaded': {
const conversation = message.data as Conversation;
handlers.messageHandling.setMessages(conversation.messages);
break;
}
case 'message': {
handlers.messageHandling.addMessage(message.data);
break;
}
case 'streamStart':
handlers.messageHandling.startStreaming();
break;
case 'streamChunk': {
handlers.messageHandling.appendStreamChunk(message.data.chunk);
break;
}
case 'thoughtChunk': {
const thinkingMessage = {
role: 'thinking' as const,
content: message.data.content || message.data.chunk || '',
timestamp: Date.now(),
};
handlers.messageHandling.addMessage(thinkingMessage);
break;
}
case 'streamEnd':
handlers.messageHandling.endStreaming();
break;
case 'error':
handlers.messageHandling.clearWaitingForResponse();
break;
case 'permissionRequest': {
handlers.handlePermissionRequest(message.data);
const permToolCall = message.data?.toolCall as {
toolCallId?: string;
kind?: string;
title?: string;
status?: string;
content?: unknown[];
locations?: Array<{ path: string; line?: number | null }>;
};
if (permToolCall?.toolCallId) {
let kind = permToolCall.kind || 'execute';
if (permToolCall.title) {
const title = permToolCall.title.toLowerCase();
if (title.includes('touch') || title.includes('echo')) {
kind = 'execute';
} else if (title.includes('read') || title.includes('cat')) {
kind = 'read';
} else if (title.includes('write') || title.includes('edit')) {
kind = 'edit';
}
}
const normalizedStatus = (
permToolCall.status === 'pending' ||
permToolCall.status === 'in_progress' ||
permToolCall.status === 'completed' ||
permToolCall.status === 'failed'
? permToolCall.status
: 'pending'
) as ToolCallUpdate['status'];
handlers.handleToolCallUpdate({
type: 'tool_call',
toolCallId: permToolCall.toolCallId,
kind,
title: permToolCall.title,
status: normalizedStatus,
content: permToolCall.content as ToolCallUpdate['content'],
locations: permToolCall.locations,
});
}
break;
}
case 'plan':
if (message.data.entries && Array.isArray(message.data.entries)) {
handlers.setPlanEntries(message.data.entries as PlanEntry[]);
}
break;
case 'toolCall':
case 'toolCallUpdate': {
const toolCallData = message.data;
if (toolCallData.sessionUpdate && !toolCallData.type) {
toolCallData.type = toolCallData.sessionUpdate;
}
handlers.handleToolCallUpdate(toolCallData);
break;
}
case 'qwenSessionList': {
const sessions = message.data.sessions || [];
handlers.sessionManagement.setQwenSessions(sessions);
if (
handlers.sessionManagement.currentSessionId &&
sessions.length > 0
) {
const currentSession = sessions.find(
(s: Record<string, unknown>) =>
(s.id as string) ===
handlers.sessionManagement.currentSessionId ||
(s.sessionId as string) ===
handlers.sessionManagement.currentSessionId,
);
if (currentSession) {
const title =
(currentSession.title as string) ||
(currentSession.name as string) ||
'Past Conversations';
handlers.sessionManagement.setCurrentSessionTitle(title);
}
}
break;
}
case 'qwenSessionSwitched':
handlers.sessionManagement.setShowSessionSelector(false);
if (message.data.sessionId) {
handlers.sessionManagement.setCurrentSessionId(
message.data.sessionId as string,
);
}
if (message.data.session) {
const session = message.data.session as Record<string, unknown>;
const title =
(session.title as string) ||
(session.name as string) ||
'Past Conversations';
handlers.sessionManagement.setCurrentSessionTitle(title);
}
if (message.data.messages) {
handlers.messageHandling.setMessages(message.data.messages);
} else {
handlers.messageHandling.clearMessages();
}
handlers.clearToolCalls();
handlers.setPlanEntries([]);
break;
case 'conversationCleared':
handlers.messageHandling.clearMessages();
handlers.clearToolCalls();
handlers.sessionManagement.setCurrentSessionId(null);
handlers.sessionManagement.setCurrentSessionTitle(
'Past Conversations',
);
break;
case 'sessionTitleUpdated': {
const sessionId = message.data?.sessionId as string;
const title = message.data?.title as string;
if (sessionId && title) {
handlers.sessionManagement.setCurrentSessionId(sessionId);
handlers.sessionManagement.setCurrentSessionTitle(title);
}
break;
}
case 'activeEditorChanged': {
const fileName = message.data?.fileName as string | null;
const filePath = message.data?.filePath as string | null;
const selection = message.data?.selection as {
startLine: number;
endLine: number;
} | null;
handlers.fileContext.setActiveFileName(fileName);
handlers.fileContext.setActiveFilePath(filePath);
handlers.fileContext.setActiveSelection(selection);
break;
}
case 'fileAttached': {
const attachment = message.data as {
id: string;
type: string;
name: string;
value: string;
};
handlers.fileContext.addFileReference(
attachment.name,
attachment.value,
);
if (inputFieldRef.current) {
const currentText = inputFieldRef.current.textContent || '';
const newText = currentText
? `${currentText} @${attachment.name} `
: `@${attachment.name} `;
inputFieldRef.current.textContent = newText;
setInputText(newText);
const range = document.createRange();
const sel = window.getSelection();
range.selectNodeContents(inputFieldRef.current);
range.collapse(false);
sel?.removeAllRanges();
sel?.addRange(range);
}
break;
}
case 'workspaceFiles': {
const files = message.data?.files as Array<{
id: string;
label: string;
description: string;
path: string;
}>;
if (files) {
handlers.fileContext.setWorkspaceFiles(files);
}
break;
}
case 'saveSessionResponse': {
handlers.sessionManagement.handleSaveSessionResponse(message.data);
break;
}
default:
break;
}
},
[inputFieldRef, setInputText],
);
useEffect(() => {
window.addEventListener('message', handleMessage);
return () => window.removeEventListener('message', handleMessage);
}, [handleMessage]);
};