mirror of
https://github.com/tiddly-gittly/TidGi-Desktop.git
synced 2025-12-06 10:41:02 -08:00
feat: tool message & wiki tool schema
This commit is contained in:
parent
49515f74a1
commit
a398c600cb
25 changed files with 769 additions and 134 deletions
|
|
@ -541,6 +541,56 @@
|
|||
"WorkspaceName": "Workspace name",
|
||||
"WorkspaceNameTitle": ""
|
||||
},
|
||||
"WikiOperation": {
|
||||
"Title": "Wiki Operation",
|
||||
"Description": "Execute Tiddler operations (add, delete, or set text) in wiki workspaces",
|
||||
"ToolListPosition": {
|
||||
"TargetIdTitle": "Target ID",
|
||||
"TargetId": "ID of the target element where the tool list will be inserted",
|
||||
"PositionTitle": "Insert Position",
|
||||
"Position": "Position relative to target element (before/after)"
|
||||
},
|
||||
"ToolResultDurationTitle": "Tool Result Duration",
|
||||
"ToolResultDuration": "Number of rounds tool execution results remain visible in conversation, after which they become grayed out",
|
||||
"Tool": {
|
||||
"Name": "wiki-operation",
|
||||
"Caption": "Wiki Operation Tool",
|
||||
"Description": "Execute operations (add, delete, or set Tiddler text) in wiki workspaces",
|
||||
"ParametersTitle": "Parameters",
|
||||
"Parameters": {
|
||||
"workspaceName": {
|
||||
"Title": "Workspace Name",
|
||||
"Description": "Name or ID of the workspace to operate on"
|
||||
},
|
||||
"operation": {
|
||||
"Title": "Operation Type",
|
||||
"Description": "Type of operation to execute"
|
||||
},
|
||||
"title": {
|
||||
"Title": "Tiddler Title",
|
||||
"Description": "Title of the Tiddler"
|
||||
},
|
||||
"text": {
|
||||
"Title": "Tiddler Content",
|
||||
"Description": "Text content of the Tiddler"
|
||||
},
|
||||
"extraMeta": {
|
||||
"Title": "Extra Metadata",
|
||||
"Description": "JSON string of extra metadata such as tags and fields, defaults to \"{}\""
|
||||
},
|
||||
"options": {
|
||||
"Title": "Operation Options",
|
||||
"Description": "JSON string of operation options, defaults to \"{}\""
|
||||
}
|
||||
},
|
||||
"ExamplesTitle": "Usage Examples",
|
||||
"Examples": {
|
||||
"add": "Add note: <tool_use name=\"wiki-operation\">{\"workspaceName\": \"My Knowledge Base\", \"operation\": \"addTiddler\", \"title\": \"New Note\", \"text\": \"This is note content\", \"extraMeta\": \"{\\\"tags\\\":[\\\"tag1\\\",\\\"tag2\\\"]}\"}</tool_use>",
|
||||
"set": "Set text: <tool_use name=\"wiki-operation\">{\"workspaceName\": \"My Knowledge Base\", \"operation\": \"setTiddlerText\", \"title\": \"Existing Note\", \"text\": \"Updated content\"}</tool_use>",
|
||||
"delete": "Delete note: <tool_use name=\"wiki-operation\">{\"workspaceName\": \"My Knowledge Base\", \"operation\": \"deleteTiddler\", \"title\": \"Note to Delete\"}</tool_use>"
|
||||
}
|
||||
}
|
||||
},
|
||||
"WikiSearch": {
|
||||
"Description": "Search for content in TiddlyWiki workspaces using filter expressions",
|
||||
"Filter": "",
|
||||
|
|
@ -584,5 +634,36 @@
|
|||
"NewTab": "New Tab",
|
||||
"NewWeb": "Create a new webpage"
|
||||
}
|
||||
},
|
||||
"Tool": {
|
||||
"WikiOperation": {
|
||||
"Success": {
|
||||
"Added": "Successfully added tiddler \"{{title}}\" in wiki workspace \"{{workspaceName}}\"",
|
||||
"Deleted": "Successfully deleted tiddler \"{{title}}\" from wiki workspace \"{{workspaceName}}\"",
|
||||
"Updated": "Successfully set text for tiddler \"{{title}}\" in wiki workspace \"{{workspaceName}}\""
|
||||
},
|
||||
"Error": {
|
||||
"WorkspaceNotFound": "Workspace with name or ID \"{{workspaceName}}\" does not exist. Available workspaces: {{availableWorkspaces}}",
|
||||
"WorkspaceNotExist": "Workspace {{workspaceID}} does not exist"
|
||||
}
|
||||
},
|
||||
"WikiSearch": {
|
||||
"Success": {
|
||||
"NoResults": "No results found for filter \"{{filter}}\" in wiki workspace \"{{workspaceName}}\"",
|
||||
"Completed": "Wiki search completed successfully. Found {{totalResults}} total results, showing {{shownResults}}:\n\n"
|
||||
},
|
||||
"Error": {
|
||||
"WorkspaceNotFound": "Workspace with name or ID \"{{workspaceName}}\" does not exist. Available workspaces: {{availableWorkspaces}}",
|
||||
"WorkspaceNotExist": "Workspace {{workspaceID}} does not exist",
|
||||
"ExecutionFailed": "Tool execution failed: {{error}}"
|
||||
}
|
||||
},
|
||||
"Schema": {
|
||||
"Required": "Required",
|
||||
"Optional": "Optional",
|
||||
"Description": "Description",
|
||||
"Parameters": "Parameters",
|
||||
"Examples": "Usage Examples"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -192,6 +192,7 @@
|
|||
"Description": "此智能体的外部 API 调用调试日志。在偏好设置中启用 '外部 API 调试' 以开始记录。",
|
||||
"CurrentAgent": "显示智能体日志: {{agentId}}",
|
||||
"NoLogs": "未找到此智能体的 API 日志",
|
||||
"NoResponse": "无响应",
|
||||
"StatusStart": "已开始",
|
||||
"StatusUpdate": "处理中",
|
||||
"StatusDone": "已完成",
|
||||
|
|
@ -561,8 +562,70 @@
|
|||
"ToolListPositionTitle": "工具列表位置",
|
||||
"ToolResultDuration": "工具执行结果在对话中保持可见的轮数,超过此轮数后结果将变灰显示",
|
||||
"ToolResultDurationTitle": "工具结果持续轮数",
|
||||
"Tool": {
|
||||
"Parameters": {
|
||||
"workspaceName": {
|
||||
"Title": "工作空间名称",
|
||||
"Description": "要搜索的工作空间名称或ID"
|
||||
},
|
||||
"filter": {
|
||||
"Title": "过滤器",
|
||||
"Description": "TiddlyWiki 过滤器表达式"
|
||||
}
|
||||
}
|
||||
},
|
||||
"WorkspaceName": "要搜索的 TiddlyWiki 工作区名称",
|
||||
"WorkspaceNameTitle": "工作区名称"
|
||||
},
|
||||
"WikiOperation": {
|
||||
"Title": "Wiki 操作",
|
||||
"Description": "在 Wiki 工作空间中执行 Tiddler 操作(添加、删除或设置文本)",
|
||||
"ToolListPosition": {
|
||||
"TargetIdTitle": "目标ID",
|
||||
"TargetId": "要插入工具列表的目标元素的ID",
|
||||
"PositionTitle": "插入位置",
|
||||
"Position": "相对于目标元素的插入位置(before/after)"
|
||||
},
|
||||
"ToolResultDurationTitle": "工具结果持续轮数",
|
||||
"ToolResultDuration": "工具执行结果在对话中保持可见的轮数,超过此轮数后结果将变灰显示",
|
||||
"Tool": {
|
||||
"Name": "wiki-operation",
|
||||
"Caption": "Wiki 操作工具",
|
||||
"Description": "在Wiki工作空间中执行操作(添加、删除或设置Tiddler文本)",
|
||||
"ParametersTitle": "参数",
|
||||
"Parameters": {
|
||||
"workspaceName": {
|
||||
"Title": "工作空间名称",
|
||||
"Description": "要操作的工作空间名称或ID"
|
||||
},
|
||||
"operation": {
|
||||
"Title": "操作类型",
|
||||
"Description": "要执行的操作类型"
|
||||
},
|
||||
"title": {
|
||||
"Title": "Tiddler 标题",
|
||||
"Description": "Tiddler 的标题"
|
||||
},
|
||||
"text": {
|
||||
"Title": "Tiddler 内容",
|
||||
"Description": "Tiddler 的文本内容"
|
||||
},
|
||||
"extraMeta": {
|
||||
"Title": "额外元数据",
|
||||
"Description": "额外元数据的 JSON 字符串,如标签和字段,默认为 \"{}\""
|
||||
},
|
||||
"options": {
|
||||
"Title": "操作选项",
|
||||
"Description": "操作选项的 JSON 字符串,默认为 \"{}\""
|
||||
}
|
||||
},
|
||||
"ExamplesTitle": "使用示例",
|
||||
"Examples": {
|
||||
"add": "添加笔记: <tool_use name=\"wiki-operation\">{\"workspaceName\": \"我的知识库\", \"operation\": \"addTiddler\", \"title\": \"新笔记\", \"text\": \"这是笔记内容\", \"extraMeta\": \"{\\\"tags\\\":[\\\"标签1\\\",\\\"标签2\\\"]}\"}</tool_use>",
|
||||
"set": "设置文本: <tool_use name=\"wiki-operation\">{\"workspaceName\": \"我的知识库\", \"operation\": \"setTiddlerText\", \"title\": \"现有笔记\", \"text\": \"更新后的内容\"}</tool_use>",
|
||||
"delete": "删除笔记: <tool_use name=\"wiki-operation\">{\"workspaceName\": \"我的知识库\", \"operation\": \"deleteTiddler\", \"title\": \"要删除的笔记\"}</tool_use>"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Search": {
|
||||
|
|
@ -584,5 +647,36 @@
|
|||
"NewTab": "新建标签页",
|
||||
"NewWeb": "新建网页"
|
||||
}
|
||||
},
|
||||
"Tool": {
|
||||
"WikiOperation": {
|
||||
"Success": {
|
||||
"Added": "成功在Wiki工作空间\"{{workspaceName}}\"中添加了Tiddler\"{{title}}\"",
|
||||
"Deleted": "成功从Wiki工作空间\"{{workspaceName}}\"中删除了Tiddler\"{{title}}\"",
|
||||
"Updated": "成功在Wiki工作空间\"{{workspaceName}}\"中设置了Tiddler\"{{title}}\"的文本"
|
||||
},
|
||||
"Error": {
|
||||
"WorkspaceNotFound": "工作空间名称或ID\"{{workspaceName}}\"不存在。可用工作空间:{{availableWorkspaces}}",
|
||||
"WorkspaceNotExist": "工作空间{{workspaceID}}不存在"
|
||||
}
|
||||
},
|
||||
"WikiSearch": {
|
||||
"Success": {
|
||||
"NoResults": "在Wiki工作空间\"{{workspaceName}}\"中未找到过滤器\"{{filter}}\"的结果",
|
||||
"Completed": "Wiki搜索完成。找到{{totalResults}}个总结果,显示{{shownResults}}个:\n\n"
|
||||
},
|
||||
"Error": {
|
||||
"WorkspaceNotFound": "工作空间名称或ID\"{{workspaceName}}\"不存在。可用工作空间:{{availableWorkspaces}}",
|
||||
"WorkspaceNotExist": "工作空间{{workspaceID}}不存在",
|
||||
"ExecutionFailed": "工具执行失败:{{error}}"
|
||||
}
|
||||
},
|
||||
"Schema": {
|
||||
"Required": "必需",
|
||||
"Optional": "可选",
|
||||
"Description": "描述",
|
||||
"Parameters": "参数",
|
||||
"Examples": "使用示例"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ const LogHeader = styled(Box)`
|
|||
const LogContent = styled(Box)`
|
||||
font-family: 'Monaco', 'Consolas', 'Courier New', monospace;
|
||||
font-size: 12px;
|
||||
background-color: ${props => props.theme.palette.grey[100]};
|
||||
padding: 12px;
|
||||
border-radius: 4px;
|
||||
overflow-x: auto;
|
||||
|
|
@ -220,17 +219,17 @@ export const APILogsDialog: React.FC<APILogsDialogProps> = ({
|
|||
</LogContent>
|
||||
</Box>
|
||||
|
||||
{/* Response Content */}
|
||||
{log.responseContent && (
|
||||
{/* Response Content: always show block; display placeholder when missing */}
|
||||
<Box>
|
||||
<Typography variant='subtitle2' gutterBottom>
|
||||
{t('APILogs.ResponseContent')}
|
||||
</Typography>
|
||||
<LogContent>
|
||||
{log.responseContent}
|
||||
{log.responseContent && String(log.responseContent).length > 0
|
||||
? log.responseContent
|
||||
: t('APILogs.NoResponse')}
|
||||
</LogContent>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Response Metadata */}
|
||||
{log.responseMetadata && (
|
||||
|
|
|
|||
|
|
@ -10,28 +10,28 @@ import { useAgentChatStore } from '../../Agent/store/agentChatStore/index';
|
|||
import { MessageRenderer } from './MessageRenderer';
|
||||
|
||||
const BubbleContainer = styled(Box, {
|
||||
shouldForwardProp: (property) => property !== '$user' && property !== '$expired',
|
||||
})<{ $user: boolean; $expired?: boolean }>`
|
||||
shouldForwardProp: (property) => property !== '$user' && property !== '$tool' && property !== '$expired',
|
||||
})<{ $user: boolean; $tool: boolean; $expired?: boolean }>`
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
max-width: 80%;
|
||||
align-self: ${props => props.$user ? 'flex-end' : 'flex-start'};
|
||||
align-self: ${props => props.$tool ? 'center' : props.$user ? 'flex-end' : 'flex-start'};
|
||||
opacity: ${props => props.$expired ? 0.5 : 1};
|
||||
transition: opacity 0.3s ease-in-out;
|
||||
`;
|
||||
|
||||
const MessageAvatar = styled(Avatar, {
|
||||
shouldForwardProp: (property) => property !== '$user' && property !== '$expired',
|
||||
})<{ $user: boolean; $expired?: boolean }>`
|
||||
background-color: ${props => props.$user ? props.theme.palette.primary.main : props.theme.palette.secondary.main};
|
||||
color: ${props => props.$user ? props.theme.palette.primary.contrastText : props.theme.palette.secondary.contrastText};
|
||||
shouldForwardProp: (property) => property !== '$user' && property !== '$tool' && property !== '$expired',
|
||||
})<{ $user: boolean; $tool: boolean; $expired?: boolean }>`
|
||||
background-color: ${props => props.$tool ? props.theme.palette.info.main : props.$user ? props.theme.palette.primary.main : props.theme.palette.secondary.main};
|
||||
color: ${props => props.$tool ? props.theme.palette.info.contrastText : props.$user ? props.theme.palette.primary.contrastText : props.theme.palette.secondary.contrastText};
|
||||
opacity: ${props => props.$expired ? 0.7 : 1};
|
||||
transition: opacity 0.3s ease-in-out;
|
||||
`;
|
||||
|
||||
const MessageContent = styled(Box, {
|
||||
shouldForwardProp: (property) => property !== '$user' && property !== '$streaming' && property !== '$expired',
|
||||
})<{ $user: boolean; $streaming?: boolean; $expired?: boolean }>`
|
||||
shouldForwardProp: (property) => property !== '$user' && property !== '$tool' && property !== '$streaming' && property !== '$expired',
|
||||
})<{ $user: boolean; $tool: boolean; $streaming?: boolean; $expired?: boolean }>`
|
||||
background-color: ${props => props.$user ? props.theme.palette.primary.light : props.theme.palette.background.paper};
|
||||
color: ${props => props.$user ? props.theme.palette.primary.contrastText : props.theme.palette.text.primary};
|
||||
padding: 12px 16px;
|
||||
|
|
@ -94,6 +94,7 @@ export const MessageBubble: React.FC<MessageBubbleProps> = ({ messageId }) => {
|
|||
if (!message) return null;
|
||||
|
||||
const isUser = message.role === 'user';
|
||||
const isTool = message.role === 'tool';
|
||||
|
||||
// Calculate if message is expired for AI context
|
||||
const messageIndex = orderedMessageIds.indexOf(messageId);
|
||||
|
|
@ -101,15 +102,16 @@ export const MessageBubble: React.FC<MessageBubbleProps> = ({ messageId }) => {
|
|||
const isExpired = isMessageExpiredForAI(message, messageIndex, totalMessages);
|
||||
|
||||
return (
|
||||
<BubbleContainer $user={isUser} $expired={isExpired} data-testid='message-bubble'>
|
||||
{!isUser && (
|
||||
<MessageAvatar $user={isUser} $expired={isExpired}>
|
||||
<BubbleContainer $user={isUser} $tool={isTool} $expired={isExpired} data-testid='message-bubble'>
|
||||
{!isUser && !isTool && (
|
||||
<MessageAvatar $user={isUser} $tool={isTool} $expired={isExpired}>
|
||||
<SmartToyIcon />
|
||||
</MessageAvatar>
|
||||
)}
|
||||
|
||||
<MessageContent
|
||||
$user={isUser}
|
||||
$tool={isTool}
|
||||
$streaming={isStreaming}
|
||||
$expired={isExpired}
|
||||
data-testid={!isUser ? (isStreaming ? 'assistant-streaming-text' : 'assistant-message') : undefined}
|
||||
|
|
@ -118,7 +120,7 @@ export const MessageBubble: React.FC<MessageBubbleProps> = ({ messageId }) => {
|
|||
</MessageContent>
|
||||
|
||||
{isUser && (
|
||||
<MessageAvatar $user={isUser} $expired={isExpired}>
|
||||
<MessageAvatar $user={isUser} $tool={isTool} $expired={isExpired}>
|
||||
<PersonIcon />
|
||||
</MessageAvatar>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -287,8 +287,8 @@ describe('PromptPreviewDialog - Tool Information Rendering', () => {
|
|||
}
|
||||
expect(wikiSearchElement).toBeDefined();
|
||||
const wikiSearchText = `${wikiSearchElement?.caption ?? ''} ${wikiSearchElement?.text ?? ''}`;
|
||||
expect(wikiSearchText).toContain('Available Tools:');
|
||||
expect(wikiSearchText).toContain('Tool ID: wiki-search');
|
||||
expect(wikiSearchText).toContain('Wiki search tool');
|
||||
expect(wikiSearchText).toContain('## wiki-search');
|
||||
|
||||
// Verify the order: before-tool -> workspaces -> wiki-operation -> wiki-search -> post-tool
|
||||
const postToolElement: IPrompt | undefined = toolsSection?.children?.find((c: IPrompt) => c.id === 'default-post-tool');
|
||||
|
|
|
|||
|
|
@ -333,4 +333,92 @@ describe('MessageBubble - Duration-based Graying', () => {
|
|||
screen.getByText('Message 3 - duration 1').parentElement?.parentElement;
|
||||
expect(bubbleContainer).toHaveStyle({ opacity: '0.5' }); // Should be grayed out
|
||||
});
|
||||
|
||||
it('should not display avatar for tool role messages', () => {
|
||||
const toolMessage: AgentInstanceMessage = {
|
||||
id: 'tool-msg',
|
||||
role: 'tool',
|
||||
content: '<functions_result>Tool: wiki-search\nResult: Found some content</functions_result>',
|
||||
agentId: 'test-agent',
|
||||
contentType: 'text/plain',
|
||||
modified: new Date(),
|
||||
duration: undefined,
|
||||
metadata: {
|
||||
isToolResult: true,
|
||||
toolId: 'wiki-search',
|
||||
},
|
||||
};
|
||||
|
||||
// Setup store state
|
||||
mockMessages.set('tool-msg', toolMessage);
|
||||
mockOrderedMessageIds.push('tool-msg');
|
||||
|
||||
// Render the tool message
|
||||
render(
|
||||
<TestWrapper>
|
||||
<MessageBubble messageId='tool-msg' />
|
||||
</TestWrapper>,
|
||||
);
|
||||
|
||||
// Check that the message content is rendered
|
||||
expect(screen.getByText(/functions_result/)).toBeInTheDocument();
|
||||
|
||||
// Check that no avatar is displayed for tool messages
|
||||
const avatars = screen.queryAllByRole('img'); // Avatars are typically rendered as img elements
|
||||
expect(avatars.length).toBe(0); // Should have no avatars for tool messages
|
||||
});
|
||||
|
||||
it('should use same background color for tool and assistant messages', () => {
|
||||
const assistantMessage: AgentInstanceMessage = {
|
||||
id: 'assistant-msg',
|
||||
role: 'assistant',
|
||||
content: 'This is an assistant response',
|
||||
agentId: 'test-agent',
|
||||
contentType: 'text/plain',
|
||||
modified: new Date(),
|
||||
duration: undefined,
|
||||
};
|
||||
|
||||
const toolMessage: AgentInstanceMessage = {
|
||||
id: 'tool-msg',
|
||||
role: 'tool',
|
||||
content: '<functions_result>Tool result</functions_result>',
|
||||
agentId: 'test-agent',
|
||||
contentType: 'text/plain',
|
||||
modified: new Date(),
|
||||
duration: undefined,
|
||||
metadata: {
|
||||
isToolResult: true,
|
||||
toolId: 'test-tool',
|
||||
},
|
||||
};
|
||||
|
||||
// Setup store state
|
||||
mockMessages.set('assistant-msg', assistantMessage);
|
||||
mockMessages.set('tool-msg', toolMessage);
|
||||
mockOrderedMessageIds.push('assistant-msg', 'tool-msg');
|
||||
|
||||
// Render assistant message
|
||||
const { rerender } = render(
|
||||
<TestWrapper>
|
||||
<MessageBubble messageId='assistant-msg' />
|
||||
</TestWrapper>,
|
||||
);
|
||||
|
||||
const assistantContent = screen.getByText('This is an assistant response');
|
||||
const assistantBackgroundColor = window.getComputedStyle(assistantContent.parentElement!).backgroundColor;
|
||||
|
||||
// Render tool message
|
||||
rerender(
|
||||
<TestWrapper>
|
||||
<MessageBubble messageId='tool-msg' />
|
||||
</TestWrapper>,
|
||||
);
|
||||
|
||||
const toolContent = screen.getByText(/Tool result/);
|
||||
const toolBackgroundColor = window.getComputedStyle(toolContent.parentElement!).backgroundColor;
|
||||
|
||||
// Both should have the same background color
|
||||
expect(assistantBackgroundColor).toBe(toolBackgroundColor);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -13,11 +13,13 @@ export function useInitialPage() {
|
|||
hasInitialized.current = true;
|
||||
const activeWorkspace = workspacesList.find(workspace => workspace.active);
|
||||
if (!activeWorkspace) {
|
||||
setLocation(`/${PageType.guide}`);
|
||||
// If there's no active workspace, navigate to root instead of guide.
|
||||
// Root lets the UI stay neutral and prevents forcing the guide view.
|
||||
setLocation(`/`);
|
||||
} else if (activeWorkspace.pageType) {
|
||||
// Don't navigate to add page, fallback to guide instead
|
||||
if (activeWorkspace.pageType === PageType.add) {
|
||||
setLocation(`/${PageType.guide}`);
|
||||
setLocation(`/`);
|
||||
} else {
|
||||
setLocation(`/${activeWorkspace.pageType}`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,9 @@ import { AgentDefinitionServiceIPCDescriptor, IAgentDefinitionService } from '@s
|
|||
import { AgentInstanceServiceIPCDescriptor, IAgentInstanceService } from '@services/agentInstance/interface';
|
||||
import { AuthenticationServiceIPCDescriptor, IAuthenticationService } from '@services/auth/interface';
|
||||
import { ContextServiceIPCDescriptor, IContextService } from '@services/context/interface';
|
||||
import { DatabaseServiceIPCDescriptor, IDatabaseService } from '@services/database/interface';
|
||||
import { DeepLinkServiceIPCDescriptor, IDeepLinkService } from '@services/deepLink/interface';
|
||||
import { ExternalAPIServiceIPCDescriptor, IExternalAPIService } from '@services/externalAPI/interface';
|
||||
import { GitServiceIPCDescriptor, IGitService } from '@services/git/interface';
|
||||
import { IMenuService, MenuServiceIPCDescriptor } from '@services/menu/interface';
|
||||
import { INativeService, NativeServiceIPCDescriptor } from '@services/native/interface';
|
||||
|
|
@ -28,7 +30,6 @@ import { IWikiGitWorkspaceService, WikiGitWorkspaceServiceIPCDescriptor } from '
|
|||
import { IWindowService, WindowServiceIPCDescriptor } from '@services/windows/interface';
|
||||
import { IWorkspaceService, WorkspaceServiceIPCDescriptor } from '@services/workspaces/interface';
|
||||
import { IWorkspaceViewService, WorkspaceViewServiceIPCDescriptor } from '@services/workspacesView/interface';
|
||||
import { ExternalAPIServiceIPCDescriptor, IExternalAPIService } from '../../services/externalAPI/interface';
|
||||
|
||||
export const agentBrowser = createProxy<AsyncifyProxy<IAgentBrowserService>>(AgentBrowserServiceIPCDescriptor);
|
||||
export const agentDefinition = createProxy<AsyncifyProxy<IAgentDefinitionService>>(AgentDefinitionServiceIPCDescriptor);
|
||||
|
|
@ -37,6 +38,7 @@ export const auth = createProxy<IAuthenticationService>(AuthenticationServiceIPC
|
|||
export const context = createProxy<IContextService>(ContextServiceIPCDescriptor);
|
||||
export const deepLink = createProxy<IDeepLinkService>(DeepLinkServiceIPCDescriptor);
|
||||
export const externalAPI = createProxy<IExternalAPIService>(ExternalAPIServiceIPCDescriptor);
|
||||
export const database = createProxy<IDatabaseService>(DatabaseServiceIPCDescriptor);
|
||||
export const git = createProxy<IGitService>(GitServiceIPCDescriptor);
|
||||
export const menu = createProxy<IMenuService>(MenuServiceIPCDescriptor);
|
||||
export const native = createProxy<INativeService>(NativeServiceIPCDescriptor);
|
||||
|
|
@ -78,4 +80,5 @@ export const descriptors = {
|
|||
workspace: WorkspaceServiceIPCDescriptor,
|
||||
workspaceView: WorkspaceViewServiceIPCDescriptor,
|
||||
externalAPI: ExternalAPIServiceIPCDescriptor,
|
||||
database: DatabaseServiceIPCDescriptor,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -210,7 +210,7 @@ describe('WikiSearch Plugin Integration & YieldNextRound Mechanism', () => {
|
|||
// Verify tool result message was added to agent history
|
||||
expect(responseContext.handlerContext.agent.messages.length).toBeGreaterThan(0);
|
||||
const toolResultMessage = responseContext.handlerContext.agent.messages[responseContext.handlerContext.agent.messages.length - 1] as AgentInstanceMessage;
|
||||
expect(toolResultMessage.role).toBe('assistant'); // Changed from 'user' to 'assistant'
|
||||
expect(toolResultMessage.role).toBe('tool'); // Tool result message
|
||||
expect(toolResultMessage.content).toContain('<functions_result>');
|
||||
expect(toolResultMessage.content).toContain('Tool: wiki-search');
|
||||
expect(toolResultMessage.content).toContain('Important Note 1');
|
||||
|
|
@ -272,7 +272,7 @@ describe('WikiSearch Plugin Integration & YieldNextRound Mechanism', () => {
|
|||
// Verify error message was added to agent history
|
||||
expect(responseContext.handlerContext.agent.messages.length).toBeGreaterThan(0);
|
||||
const errorResultMessage = responseContext.handlerContext.agent.messages[responseContext.handlerContext.agent.messages.length - 1] as AgentInstanceMessage;
|
||||
expect(errorResultMessage.role).toBe('assistant'); // Changed from 'user' to 'assistant'
|
||||
expect(errorResultMessage.role).toBe('tool'); // Tool error message
|
||||
expect(errorResultMessage.content).toContain('Error:');
|
||||
expect(errorResultMessage.content).toContain('does not exist');
|
||||
});
|
||||
|
|
@ -352,7 +352,7 @@ describe('WikiSearch Plugin Integration & YieldNextRound Mechanism', () => {
|
|||
// Verify that tool result message was added
|
||||
const toolResultMessage = context.agent.messages.find(m => m.metadata?.isToolResult);
|
||||
expect(toolResultMessage).toBeTruthy();
|
||||
expect(toolResultMessage?.role).toBe('assistant');
|
||||
expect(toolResultMessage?.role).toBe('tool');
|
||||
expect(toolResultMessage?.content).toContain('<functions_result>');
|
||||
|
||||
// Verify that there are multiple responses (initial tool call + final explanation)
|
||||
|
|
|
|||
|
|
@ -22,6 +22,34 @@ import type { AIResponseContext, PluginActions, PromptConcatHookContext } from '
|
|||
import { wikiOperationPlugin } from '../wikiOperationPlugin';
|
||||
import { workspacesListPlugin } from '../workspacesListPlugin';
|
||||
|
||||
// Mock i18n
|
||||
vi.mock('@services/libs/i18n', () => ({
|
||||
i18n: {
|
||||
t: vi.fn((key: string, options?: Record<string, unknown>) => {
|
||||
const translations: Record<string, string> = {
|
||||
'Tool.WikiOperation.Success.Added': '成功在Wiki工作空间"{{workspaceName}}"中添加了Tiddler"{{title}}"',
|
||||
'Tool.WikiOperation.Success.Deleted': '成功从Wiki工作空间"{{workspaceName}}"中删除了Tiddler"{{title}}"',
|
||||
'Tool.WikiOperation.Success.Updated': '成功在Wiki工作空间"{{workspaceName}}"中设置了Tiddler"{{title}}"的文本',
|
||||
'Tool.WikiOperation.Error.WorkspaceNotFound': '工作空间名称或ID"{{workspaceName}}"不存在。可用工作空间:{{availableWorkspaces}}',
|
||||
'Tool.WikiOperation.Error.WorkspaceNotExist': '工作空间{{workspaceID}}不存在',
|
||||
};
|
||||
|
||||
let translation = translations[key] || key;
|
||||
|
||||
// Handle interpolation
|
||||
if (options && typeof options === 'object') {
|
||||
Object.keys(options).forEach(optionKey => {
|
||||
if (optionKey !== 'ns' && optionKey !== 'defaultValue') {
|
||||
translation = translation.replace(new RegExp(`{{${optionKey}}}`, 'g'), String(options[optionKey]));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return translation;
|
||||
}),
|
||||
},
|
||||
}));
|
||||
|
||||
// Helper to construct a complete AgentHandlerContext for tests
|
||||
const makeHandlerContext = (agentId = 'test-agent'): AgentHandlerContext => ({
|
||||
agent: {
|
||||
|
|
@ -203,7 +231,7 @@ describe('wikiOperationPlugin', () => {
|
|||
expect(toolResultMessage).toBeTruthy();
|
||||
expect(toolResultMessage?.content).toContain('<functions_result>');
|
||||
// Check for general success wording and tiddler title
|
||||
expect(toolResultMessage?.content).toContain('Successfully');
|
||||
expect(toolResultMessage?.content).toContain('成功在Wiki工作空间');
|
||||
expect(toolResultMessage?.content).toContain('Test Note');
|
||||
expect(toolResultMessage?.metadata?.isToolResult).toBe(true);
|
||||
expect(toolResultMessage?.metadata?.toolId).toBe('wiki-operation');
|
||||
|
|
@ -265,7 +293,7 @@ describe('wikiOperationPlugin', () => {
|
|||
// Check general update success wording and tiddler title
|
||||
const updateResult = handlerContext.agent.messages.find(m => m.metadata?.isToolResult);
|
||||
expect(updateResult).toBeTruthy();
|
||||
expect(updateResult?.content).toContain('Successfully');
|
||||
expect(updateResult?.content).toContain('成功在Wiki工作空间');
|
||||
expect(updateResult?.content).toContain('Existing Note');
|
||||
});
|
||||
|
||||
|
|
@ -323,7 +351,7 @@ describe('wikiOperationPlugin', () => {
|
|||
|
||||
const deleteResult = handlerContext.agent.messages.find(m => m.metadata?.isToolResult);
|
||||
expect(deleteResult).toBeTruthy();
|
||||
expect(deleteResult?.content).toContain('Successfully deleted tiddler "Note to Delete"');
|
||||
expect(deleteResult?.content).toContain('成功从Wiki工作空间');
|
||||
});
|
||||
|
||||
it('should handle workspace not found error', async () => {
|
||||
|
|
@ -374,7 +402,7 @@ describe('wikiOperationPlugin', () => {
|
|||
|
||||
const errResult = handlerContext.agent.messages.find(m => m.metadata?.isToolResult);
|
||||
expect(errResult).toBeTruthy();
|
||||
expect(errResult?.content).toContain('Workspace with name or ID "Non-existent Wiki" does not exist');
|
||||
expect(errResult?.content).toContain('工作空间名称或ID');
|
||||
// Ensure control is yielded to self on error so AI gets the next round
|
||||
expect(respCtx4.actions?.yieldNextRoundTo).toBe('self');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -23,6 +23,34 @@ import { createHandlerHooks, PromptConcatHookContext } from '../index';
|
|||
import { messageManagementPlugin } from '../messageManagementPlugin';
|
||||
import { wikiSearchPlugin } from '../wikiSearchPlugin';
|
||||
|
||||
// Mock i18n
|
||||
vi.mock('@services/libs/i18n', () => ({
|
||||
i18n: {
|
||||
t: vi.fn((key: string, options?: Record<string, unknown>) => {
|
||||
const translations: Record<string, string> = {
|
||||
'Tool.WikiSearch.Success.NoResults': '在Wiki工作空间"{{workspaceName}}"中未找到过滤器"{{filter}}"的结果',
|
||||
'Tool.WikiSearch.Success.Completed': 'Wiki搜索完成。找到{{totalResults}}个总结果,显示{{shownResults}}个:\n\n',
|
||||
'Tool.WikiSearch.Error.WorkspaceNotFound': '工作空间名称或ID"{{workspaceName}}"不存在。可用工作空间:{{availableWorkspaces}}',
|
||||
'Tool.WikiSearch.Error.WorkspaceNotExist': '工作空间{{workspaceID}}不存在',
|
||||
'Tool.WikiSearch.Error.ExecutionFailed': '工具执行失败:{{error}}',
|
||||
};
|
||||
|
||||
let translation = translations[key] || key;
|
||||
|
||||
// Handle interpolation
|
||||
if (options && typeof options === 'object') {
|
||||
Object.keys(options).forEach(optionKey => {
|
||||
if (optionKey !== 'ns' && optionKey !== 'defaultValue') {
|
||||
translation = translation.replace(new RegExp(`{{${optionKey}}}`, 'g'), String(options[optionKey]));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return translation;
|
||||
}),
|
||||
},
|
||||
}));
|
||||
|
||||
// Use the real agent config
|
||||
const exampleAgent = defaultAgents[0];
|
||||
const handlerConfig = exampleAgent.handlerConfig as AgentPromptDescription['handlerConfig'];
|
||||
|
|
@ -288,7 +316,7 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
|
|||
// Verify tool result message was added to agent history with correct settings
|
||||
expect(handlerContext.agent.messages.length).toBe(3); // user + ai + tool_result
|
||||
const toolResultMessage = handlerContext.agent.messages[2] as AgentInstanceMessage;
|
||||
expect(toolResultMessage.role).toBe('assistant'); // Changed from 'user' to 'assistant'
|
||||
expect(toolResultMessage.role).toBe('tool'); // Tool result message
|
||||
expect(toolResultMessage.content).toContain('<functions_result>');
|
||||
expect(toolResultMessage.content).toContain('Tool: wiki-search');
|
||||
expect(toolResultMessage.content).toContain('Important Note 1');
|
||||
|
|
@ -372,10 +400,10 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
|
|||
// Verify error message was added to agent history
|
||||
expect(handlerContext.agent.messages.length).toBe(2); // tool_call + error_result
|
||||
const errorResultMessage = handlerContext.agent.messages[1] as AgentInstanceMessage;
|
||||
expect(errorResultMessage.role).toBe('assistant'); // Changed from 'user' to 'assistant'
|
||||
expect(errorResultMessage.role).toBe('tool'); // Tool error message
|
||||
expect(errorResultMessage.content).toContain('<functions_result>');
|
||||
expect(errorResultMessage.content).toContain('Error:');
|
||||
expect(errorResultMessage.content).toContain('does not exist');
|
||||
expect(errorResultMessage.content).toContain('工作空间名称或ID');
|
||||
expect(errorResultMessage.metadata?.isToolResult).toBe(true);
|
||||
expect(errorResultMessage.metadata?.isError).toBe(true);
|
||||
expect(errorResultMessage.metadata?.isPersisted).toBe(false); // Should be false initially
|
||||
|
|
|
|||
|
|
@ -3,10 +3,20 @@
|
|||
* Handles wiki operation tool list injection, tool calling detection and response processing
|
||||
* Supports creating, updating, and deleting tiddlers in wiki workspaces
|
||||
*/
|
||||
import { identity } from 'lodash';
|
||||
import { WikiChannel } from '@/constants/channels';
|
||||
import { matchToolCalling } from '@services/agentDefinition/responsePatternUtility';
|
||||
import { container } from '@services/container';
|
||||
import { i18n } from '@services/libs/i18n';
|
||||
import { t } from '@services/libs/i18n/placeholder';
|
||||
import { logger } from '@services/libs/log';
|
||||
import serviceIdentifier from '@services/serviceIdentifier';
|
||||
import type { IWikiService } from '@services/wiki/interface';
|
||||
import { IWorkspaceService } from '@services/workspaces/interface';
|
||||
import { z } from 'zod/v4';
|
||||
|
||||
const t = identity;
|
||||
import type { AgentInstanceMessage } from '../interface';
|
||||
import { findPromptById } from '../promptConcat/promptConcat';
|
||||
import { schemaToToolContent } from '../utilities/schemaToToolContent';
|
||||
import type { PromptConcatPlugin } from './types';
|
||||
|
||||
/**
|
||||
* Wiki Operation Parameter Schema
|
||||
|
|
@ -48,29 +58,44 @@ export function getWikiOperationParameterSchema() {
|
|||
return WikiOperationParameterSchema;
|
||||
}
|
||||
|
||||
import { WikiChannel } from '@/constants/channels';
|
||||
import { matchToolCalling } from '@services/agentDefinition/responsePatternUtility';
|
||||
import { container } from '@services/container';
|
||||
import { logger } from '@services/libs/log';
|
||||
import serviceIdentifier from '@services/serviceIdentifier';
|
||||
import type { IWikiService } from '@services/wiki/interface';
|
||||
import { IWorkspaceService } from '@services/workspaces/interface';
|
||||
|
||||
import type { AgentInstanceMessage } from '../interface';
|
||||
import { findPromptById } from '../promptConcat/promptConcat';
|
||||
import type { PromptConcatPlugin } from './types';
|
||||
|
||||
/**
|
||||
* Parameter schema for Wiki operation tool
|
||||
*/
|
||||
const WikiOperationToolParameterSchema = z.object({
|
||||
workspaceName: z.string().describe('Name or ID of the workspace to operate on'),
|
||||
operation: z.enum([WikiChannel.addTiddler, WikiChannel.deleteTiddler, WikiChannel.setTiddlerText]).describe('Type of wiki operation to perform'),
|
||||
title: z.string().describe('Title of the tiddler'),
|
||||
text: z.string().optional().describe('Content/text of the tiddler (for addTiddler/setTiddlerText operations)'),
|
||||
extraMeta: z.string().optional().default('{}').describe('JSON string of additional metadata (tags, fields, etc.)'),
|
||||
options: z.string().optional().default('{}').describe('JSON string of operation options'),
|
||||
});
|
||||
workspaceName: z.string().meta({
|
||||
title: t('Schema.WikiOperation.Tool.Parameters.workspaceName.Title'),
|
||||
description: t('Schema.WikiOperation.Tool.Parameters.workspaceName.Description'),
|
||||
}),
|
||||
operation: z.enum([WikiChannel.addTiddler, WikiChannel.deleteTiddler, WikiChannel.setTiddlerText]).meta({
|
||||
title: t('Schema.WikiOperation.Tool.Parameters.operation.Title'),
|
||||
description: t('Schema.WikiOperation.Tool.Parameters.operation.Description'),
|
||||
}),
|
||||
title: z.string().meta({
|
||||
title: t('Schema.WikiOperation.Tool.Parameters.title.Title'),
|
||||
description: t('Schema.WikiOperation.Tool.Parameters.title.Description'),
|
||||
}),
|
||||
text: z.string().optional().meta({
|
||||
title: t('Schema.WikiOperation.Tool.Parameters.text.Title'),
|
||||
description: t('Schema.WikiOperation.Tool.Parameters.text.Description'),
|
||||
}),
|
||||
extraMeta: z.string().optional().default('{}').meta({
|
||||
title: t('Schema.WikiOperation.Tool.Parameters.extraMeta.Title'),
|
||||
description: t('Schema.WikiOperation.Tool.Parameters.extraMeta.Description'),
|
||||
}),
|
||||
options: z.string().optional().default('{}').meta({
|
||||
title: t('Schema.WikiOperation.Tool.Parameters.options.Title'),
|
||||
description: t('Schema.WikiOperation.Tool.Parameters.options.Description'),
|
||||
}),
|
||||
})
|
||||
.meta({
|
||||
title: 'wiki-operation',
|
||||
description: '在Wiki工作空间中执行操作(添加、删除或设置Tiddler文本)',
|
||||
examples: [
|
||||
{ workspaceName: '我的知识库', operation: WikiChannel.addTiddler, title: '示例笔记', text: '示例内容', extraMeta: '{}', options: '{}' },
|
||||
{ workspaceName: '我的知识库', operation: WikiChannel.setTiddlerText, title: '现有笔记', text: '更新后的内容', extraMeta: '{}', options: '{}' },
|
||||
{ workspaceName: '我的知识库', operation: WikiChannel.deleteTiddler, title: '要删除的笔记', extraMeta: '{}', options: '{}' },
|
||||
],
|
||||
});
|
||||
|
||||
/**
|
||||
* Wiki Operation plugin - Prompt processing
|
||||
|
|
@ -105,22 +130,8 @@ export const wikiOperationPlugin: PromptConcatPlugin = (hooks) => {
|
|||
// Get available wikis - now handled by workspacesListPlugin
|
||||
// The workspaces list will be injected separately by workspacesListPlugin
|
||||
|
||||
const wikiOperationToolContent = `
|
||||
## wiki-operation
|
||||
**描述**: 在Wiki工作空间中执行操作(添加、删除或设置Tiddler文本)
|
||||
**参数**:
|
||||
- workspaceName (string, 必需): 要操作的工作空间名称或ID
|
||||
- operation (string, 必需): 要执行的操作类型,可选值: "${WikiChannel.addTiddler}", "${WikiChannel.deleteTiddler}", "${WikiChannel.setTiddlerText}"
|
||||
- title (string, 必需): Tiddler的标题
|
||||
- text (string, 可选): Tiddler的内容/文本(用于 addTiddler / setTiddlerText 操作)
|
||||
- extraMeta (string, 可选): 额外元数据的JSON字符串,如标签和字段,默认为"{}"
|
||||
- options (string, 可选): 操作选项的JSON字符串,默认为"{}"
|
||||
|
||||
**使用示例**:
|
||||
- 添加笔记: <tool_use name="wiki-operation">{"workspaceName": "我的知识库", "operation": "${WikiChannel.addTiddler}", "title": "新笔记", "text": "这是笔记内容", "extraMeta": "{\\"tags\\":[\\"标签1\\",\\"标签2\\"]}"}</tool_use>
|
||||
- 设置文本: <tool_use name="wiki-operation">{"workspaceName": "我的知识库", "operation": "${WikiChannel.setTiddlerText}", "title": "现有笔记", "text": "更新后的内容"}</tool_use>
|
||||
- 删除笔记: <tool_use name="wiki-operation">{"workspaceName": "我的知识库", "operation": "${WikiChannel.deleteTiddler}", "title": "要删除的笔记"}</tool_use>
|
||||
`;
|
||||
// Build tool content using shared utility (schema contains title/examples meta)
|
||||
const wikiOperationToolContent = schemaToToolContent(WikiOperationToolParameterSchema);
|
||||
|
||||
// Insert the tool content based on position
|
||||
if (toolListPosition.position === 'after') {
|
||||
|
|
@ -224,12 +235,17 @@ export const wikiOperationPlugin: PromptConcatPlugin = (hooks) => {
|
|||
const workspaces = await workspaceService.getWorkspacesAsList();
|
||||
const targetWorkspace = workspaces.find(ws => ws.name === workspaceName || ws.id === workspaceName);
|
||||
if (!targetWorkspace) {
|
||||
throw new Error(`Workspace with name or ID "${workspaceName}" does not exist. Available workspaces: ${workspaces.map(w => `${w.name} (${w.id})`).join(', ')}`);
|
||||
throw new Error(
|
||||
i18n.t('Tool.WikiOperation.Error.WorkspaceNotFound', {
|
||||
workspaceName,
|
||||
availableWorkspaces: workspaces.map(w => `${w.name} (${w.id})`).join(', '),
|
||||
}) || `Workspace with name or ID "${workspaceName}" does not exist. Available workspaces: ${workspaces.map(w => `${w.name} (${w.id})`).join(', ')}`,
|
||||
);
|
||||
}
|
||||
const workspaceID = targetWorkspace.id;
|
||||
|
||||
if (!await workspaceService.exists(workspaceID)) {
|
||||
throw new Error(`Workspace ${workspaceID} does not exist`);
|
||||
throw new Error(i18n.t('Tool.WikiOperation.Error.WorkspaceNotExist', { workspaceID }));
|
||||
}
|
||||
|
||||
logger.debug('Executing wiki operation', {
|
||||
|
|
@ -251,19 +267,19 @@ export const wikiOperationPlugin: PromptConcatPlugin = (hooks) => {
|
|||
extraMeta || '{}',
|
||||
JSON.stringify({ withDate: true, ...options }),
|
||||
]);
|
||||
result = `Successfully added tiddler "${title}" in wiki workspace "${workspaceName}".`;
|
||||
result = i18n.t('Tool.WikiOperation.Success.Added', { title, workspaceName });
|
||||
break;
|
||||
}
|
||||
|
||||
case WikiChannel.deleteTiddler: {
|
||||
await wikiService.wikiOperationInServer(WikiChannel.deleteTiddler, workspaceID, [title]);
|
||||
result = `Successfully deleted tiddler "${title}" from wiki workspace "${workspaceName}".`;
|
||||
result = i18n.t('Tool.WikiOperation.Success.Deleted', { title, workspaceName });
|
||||
break;
|
||||
}
|
||||
|
||||
case WikiChannel.setTiddlerText: {
|
||||
await wikiService.wikiOperationInServer(WikiChannel.setTiddlerText, workspaceID, [title, text || '']);
|
||||
result = `Successfully set text for tiddler "${title}" in wiki workspace "${workspaceName}".`;
|
||||
result = i18n.t('Tool.WikiOperation.Success.Updated', { title, workspaceName });
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -302,7 +318,7 @@ export const wikiOperationPlugin: PromptConcatPlugin = (hooks) => {
|
|||
const toolResultMessage: AgentInstanceMessage = {
|
||||
id: `tool-result-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
|
||||
agentId: handlerContext.agent.id,
|
||||
role: 'assistant', // Changed from 'user' to 'assistant' to avoid user confusion
|
||||
role: 'tool', // Tool result message
|
||||
content: toolResultText,
|
||||
modified: toolResultTime,
|
||||
duration: toolResultDuration, // Use configurable duration - default 1 round for tool results
|
||||
|
|
@ -363,7 +379,7 @@ Error: ${error instanceof Error ? error.message : String(error)}
|
|||
const errorResultMessage: AgentInstanceMessage = {
|
||||
id: `tool-error-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
|
||||
agentId: handlerContext.agent.id,
|
||||
role: 'assistant', // Changed from 'user' to 'assistant' to avoid user confusion
|
||||
role: 'tool', // Tool error message
|
||||
content: errorMessage,
|
||||
modified: errorResultTime,
|
||||
duration: 2, // Error messages are visible to AI for 2 rounds: immediate + next round to allow explanation
|
||||
|
|
|
|||
|
|
@ -2,10 +2,22 @@
|
|||
* Wiki Search plugin
|
||||
* Handles wiki search tool list injection, tool calling detection and response processing
|
||||
*/
|
||||
import { identity } from 'lodash';
|
||||
import { WikiChannel } from '@/constants/channels';
|
||||
import { matchToolCalling } from '@services/agentDefinition/responsePatternUtility';
|
||||
import { container } from '@services/container';
|
||||
import { i18n } from '@services/libs/i18n';
|
||||
import { t } from '@services/libs/i18n/placeholder';
|
||||
import { logger } from '@services/libs/log';
|
||||
import serviceIdentifier from '@services/serviceIdentifier';
|
||||
import type { IWikiService } from '@services/wiki/interface';
|
||||
import { IWorkspaceService } from '@services/workspaces/interface';
|
||||
import type { ITiddlerFields } from 'tiddlywiki';
|
||||
import { z } from 'zod/v4';
|
||||
|
||||
const t = identity;
|
||||
import type { AgentInstanceMessage, IAgentInstanceService } from '../interface';
|
||||
import { findPromptById } from '../promptConcat/promptConcat';
|
||||
import type { IPrompt } from '../promptConcat/promptConcatSchema';
|
||||
import { schemaToToolContent } from '../utilities/schemaToToolContent';
|
||||
import type { AIResponseContext, PromptConcatPlugin } from './types';
|
||||
|
||||
/**
|
||||
* Wiki Search Parameter Schema
|
||||
|
|
@ -63,26 +75,24 @@ export function getWikiSearchParameterSchema() {
|
|||
return WikiSearchParameterSchema;
|
||||
}
|
||||
|
||||
import { WikiChannel } from '@/constants/channels';
|
||||
import { matchToolCalling } from '@services/agentDefinition/responsePatternUtility';
|
||||
import { container } from '@services/container';
|
||||
import { logger } from '@services/libs/log';
|
||||
import serviceIdentifier from '@services/serviceIdentifier';
|
||||
import type { IWikiService } from '@services/wiki/interface';
|
||||
import { IWorkspaceService } from '@services/workspaces/interface';
|
||||
import type { ITiddlerFields } from 'tiddlywiki';
|
||||
|
||||
import type { AgentInstanceMessage, IAgentInstanceService } from '../interface';
|
||||
import { findPromptById } from '../promptConcat/promptConcat';
|
||||
import type { IPrompt } from '../promptConcat/promptConcatSchema';
|
||||
import type { AIResponseContext, PromptConcatPlugin } from './types';
|
||||
|
||||
/**
|
||||
* Parameter schema for Wiki search tool
|
||||
*/
|
||||
const WikiSearchToolParameterSchema = z.object({
|
||||
workspaceName: z.string().describe('Name or ID of the workspace to search'),
|
||||
filter: z.string().describe('TiddlyWiki filter expression'),
|
||||
workspaceName: z.string().meta({
|
||||
title: t('Schema.WikiSearch.Tool.Parameters.workspaceName.Title'),
|
||||
description: t('Schema.WikiSearch.Tool.Parameters.workspaceName.Description'),
|
||||
}),
|
||||
filter: z.string().meta({
|
||||
title: t('Schema.WikiSearch.Tool.Parameters.filter.Title'),
|
||||
description: t('Schema.WikiSearch.Tool.Parameters.filter.Description'),
|
||||
}),
|
||||
}).meta({
|
||||
title: 'wiki-search',
|
||||
description: '在Wiki工作空间中搜索Tiddler内容',
|
||||
examples: [
|
||||
{ workspaceName: '我的知识库', filter: '[tag[示例]]' },
|
||||
],
|
||||
});
|
||||
|
||||
type WikiSearchToolParameter = z.infer<typeof WikiSearchToolParameterSchema>;
|
||||
|
|
@ -108,7 +118,10 @@ async function executeWikiSearchTool(
|
|||
if (!targetWorkspace) {
|
||||
return {
|
||||
success: false,
|
||||
error: `Workspace with name or ID "${workspaceName}" does not exist. Available workspaces: ${workspaces.map(w => `${w.name} (${w.id})`).join(', ')}`,
|
||||
error: i18n.t('Tool.WikiSearch.Error.WorkspaceNotFound', {
|
||||
workspaceName,
|
||||
availableWorkspaces: workspaces.map(w => `${w.name} (${w.id})`).join(', '),
|
||||
}) || `Workspace with name or ID "${workspaceName}" does not exist. Available workspaces: ${workspaces.map(w => `${w.name} (${w.id})`).join(', ')}`,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -117,7 +130,7 @@ async function executeWikiSearchTool(
|
|||
if (!await workspaceService.exists(workspaceID)) {
|
||||
return {
|
||||
success: false,
|
||||
error: `Workspace ${workspaceID} does not exist`,
|
||||
error: i18n.t('Tool.WikiSearch.Error.WorkspaceNotExist', { workspaceID }),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -134,7 +147,7 @@ async function executeWikiSearchTool(
|
|||
if (tiddlerTitles.length === 0) {
|
||||
return {
|
||||
success: true,
|
||||
data: `No results found for filter "${filter}" in wiki workspace "${workspaceName}".`,
|
||||
data: i18n.t('Tool.WikiSearch.Success.NoResults', { filter, workspaceName }),
|
||||
metadata: {
|
||||
filter,
|
||||
workspaceID,
|
||||
|
|
@ -144,7 +157,6 @@ async function executeWikiSearchTool(
|
|||
};
|
||||
}
|
||||
|
||||
// Retrieve full tiddler content if requested
|
||||
// Retrieve full tiddler content for each tiddler
|
||||
const results: Array<{ title: string; text?: string; fields?: ITiddlerFields }> = [];
|
||||
for (const title of tiddlerTitles) {
|
||||
|
|
@ -168,7 +180,10 @@ async function executeWikiSearchTool(
|
|||
}
|
||||
|
||||
// Format results as text with content
|
||||
let content = `Wiki search completed successfully. Found ${tiddlerTitles.length} total results, showing ${results.length}:\n\n`;
|
||||
let content = i18n.t('Tool.WikiSearch.Success.Completed', {
|
||||
totalResults: tiddlerTitles.length,
|
||||
shownResults: results.length,
|
||||
}) + '\n\n';
|
||||
|
||||
for (const result of results) {
|
||||
content += `**Tiddler: ${result.title}**\n\n`;
|
||||
|
|
@ -180,7 +195,6 @@ async function executeWikiSearchTool(
|
|||
content += '(Content not available)\n\n';
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: content,
|
||||
|
|
@ -200,7 +214,9 @@ async function executeWikiSearchTool(
|
|||
|
||||
return {
|
||||
success: false,
|
||||
error: `Tool execution failed: ${error instanceof Error ? error.message : String(error)}`,
|
||||
error: i18n.t('Tool.WikiSearch.Error.ExecutionFailed', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
}) || `Tool execution failed: ${error instanceof Error ? error.message : String(error)}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -238,8 +254,7 @@ export const wikiSearchPlugin: PromptConcatPlugin = (hooks) => {
|
|||
// Get available wikis - now handled by workspacesListPlugin
|
||||
// The workspaces list will be injected separately by workspacesListPlugin
|
||||
|
||||
const toolPromptContent =
|
||||
`Available Tools:\n- Tool ID: wiki-search\n- Tool Name: Wiki Search\n- Description: Search content in wiki workspaces\n- Parameters: {\n "workspaceName": "string (required) - The name or ID of the wiki workspace to search in",\n "filter": "string (required) - TiddlyWiki filter expression for searching, like [title[Index]]""\n}`;
|
||||
const toolPromptContent = schemaToToolContent(WikiSearchToolParameterSchema);
|
||||
|
||||
const toolPrompt: IPrompt = {
|
||||
id: `wiki-tool-list-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
|
||||
|
|
@ -392,7 +407,7 @@ export const wikiSearchPlugin: PromptConcatPlugin = (hooks) => {
|
|||
const toolResultMessage: AgentInstanceMessage = {
|
||||
id: `tool-result-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
|
||||
agentId: handlerContext.agent.id,
|
||||
role: 'assistant', // Changed from 'user' to 'assistant' to avoid user confusion
|
||||
role: 'tool', // Tool result message
|
||||
content: toolResultText,
|
||||
modified: toolResultTime,
|
||||
duration: toolResultDuration, // Use configurable duration - default 1 round for tool results
|
||||
|
|
@ -453,7 +468,7 @@ Error: ${error instanceof Error ? error.message : String(error)}
|
|||
const errorResultMessage: AgentInstanceMessage = {
|
||||
id: `tool-error-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
|
||||
agentId: handlerContext.agent.id,
|
||||
role: 'assistant', // Changed from 'user' to 'assistant' to avoid user confusion
|
||||
role: 'tool', // Tool error message
|
||||
content: errorMessage,
|
||||
modified: errorResultTime,
|
||||
duration: 2, // Error messages are visible to AI for 2 rounds: immediate + next round to allow explanation
|
||||
|
|
|
|||
|
|
@ -1,14 +1,11 @@
|
|||
import { createDynamicPromptConcatPluginSchema } from '@services/agentInstance/plugins/schemaRegistry';
|
||||
import { identity } from 'lodash';
|
||||
import { t } from '@services/libs/i18n/placeholder';
|
||||
import { z } from 'zod/v4';
|
||||
import { ModelParametersSchema, ProviderModelSchema } from './modelParameters';
|
||||
import { PromptSchema } from './prompts';
|
||||
import { ResponseSchema } from './response';
|
||||
import { HANDLER_CONFIG_UI_SCHEMA } from './uiSchema';
|
||||
|
||||
/** Placeholder to trigger VSCode i18nAlly extension to show translated text. */
|
||||
const t = identity;
|
||||
|
||||
/**
|
||||
* Base API configuration schema
|
||||
* Contains common fields shared between AIConfigSchema and AgentConfigSchema
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
import { identity } from 'lodash';
|
||||
import { t } from '@services/libs/i18n/placeholder';
|
||||
import { z } from 'zod/v4';
|
||||
|
||||
/** Placeholder to trigger VSCode i18nAlly extension to show translated text. */
|
||||
const t = identity;
|
||||
|
||||
/**
|
||||
* Provider and model selection schema
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
import { identity } from 'lodash';
|
||||
import { t } from '@services/libs/i18n/placeholder';
|
||||
import { z } from 'zod/v4';
|
||||
|
||||
/** Placeholder to trigger VSCode i18nAlly extension to show translated text. */
|
||||
const t = identity;
|
||||
|
||||
/**
|
||||
* Complete prompt configuration schema
|
||||
* Defines a prompt with its metadata and content structure
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
import { identity } from 'lodash';
|
||||
import { t } from '@services/libs/i18n/placeholder';
|
||||
import { z } from 'zod/v4';
|
||||
|
||||
/** Placeholder to trigger VSCode i18nAlly extension to show translated text. */
|
||||
const t = identity;
|
||||
|
||||
/**
|
||||
* Basic response configuration schema
|
||||
* Defines identifiers for AI responses that can be referenced by response modifications
|
||||
|
|
|
|||
|
|
@ -0,0 +1,126 @@
|
|||
/**
|
||||
* Tests for schemaToToolContent utility
|
||||
*/
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { z } from 'zod/v4';
|
||||
|
||||
import { schemaToToolContent } from '../schemaToToolContent';
|
||||
|
||||
// Mock i18n
|
||||
vi.mock('@services/libs/i18n', () => ({
|
||||
i18n: {
|
||||
t: vi.fn((key: string) => {
|
||||
const translations: Record<string, string> = {
|
||||
'Tool.Schema.Required': '必需',
|
||||
'Tool.Schema.Optional': '可选',
|
||||
'Tool.Schema.Description': '描述',
|
||||
'Tool.Schema.Parameters': '参数',
|
||||
'Tool.Schema.Examples': '使用示例',
|
||||
};
|
||||
return translations[key] || key;
|
||||
}),
|
||||
},
|
||||
}));
|
||||
|
||||
describe('schemaToToolContent', () => {
|
||||
it('should generate tool content from schema with title and description', () => {
|
||||
const testSchema = z.object({
|
||||
name: z.string().describe('The name parameter'),
|
||||
age: z.number().optional().describe('The age parameter'),
|
||||
}).meta({
|
||||
title: 'test-tool',
|
||||
description: 'A test tool for demonstration',
|
||||
examples: [
|
||||
{ name: 'John', age: 25 },
|
||||
{ name: 'Jane' },
|
||||
],
|
||||
});
|
||||
|
||||
const result = schemaToToolContent(testSchema);
|
||||
|
||||
expect(result).toContain('## test-tool');
|
||||
expect(result).toContain('**描述**: A test tool for demonstration');
|
||||
expect(result).toContain('- name (string, 必需): The name parameter');
|
||||
expect(result).toContain('- age (number, 可选): The age parameter');
|
||||
expect(result).toContain('<tool_use name="test-tool">{"name":"John","age":25}</tool_use>');
|
||||
expect(result).toContain('<tool_use name="test-tool">{"name":"Jane"}</tool_use>');
|
||||
});
|
||||
|
||||
it('should handle schema without description', () => {
|
||||
const testSchema = z.object({
|
||||
query: z.string().describe('Search query'),
|
||||
}).meta({
|
||||
title: 'search-tool',
|
||||
examples: [{ query: 'test search' }],
|
||||
});
|
||||
|
||||
const result = schemaToToolContent(testSchema);
|
||||
|
||||
expect(result).toContain('## search-tool');
|
||||
expect(result).toContain('**描述**: search-tool'); // fallback to title
|
||||
expect(result).toContain('- query (string, 必需): Search query');
|
||||
});
|
||||
|
||||
it('should handle schema without examples', () => {
|
||||
const testSchema = z.object({
|
||||
input: z.string().describe('Input text'),
|
||||
}).meta({
|
||||
title: 'input-tool',
|
||||
description: 'Processes input text',
|
||||
});
|
||||
|
||||
const result = schemaToToolContent(testSchema);
|
||||
|
||||
expect(result).toContain('## input-tool');
|
||||
expect(result).toContain('**描述**: Processes input text');
|
||||
expect(result).toContain('- input (string, 必需): Input text');
|
||||
expect(result).toContain('**使用示例**:\n'); // empty examples section
|
||||
});
|
||||
|
||||
it('should handle schema without meta', () => {
|
||||
const testSchema = z.object({
|
||||
value: z.string().describe('A value'),
|
||||
});
|
||||
|
||||
const result = schemaToToolContent(testSchema);
|
||||
|
||||
expect(result).toContain('## tool'); // default title
|
||||
expect(result).toContain('- value (string, 必需): A value');
|
||||
});
|
||||
|
||||
it('should handle different parameter types', () => {
|
||||
const testSchema = z.object({
|
||||
text: z.string().describe('Text input'),
|
||||
number: z.number().describe('Number input'),
|
||||
boolean: z.boolean().describe('Boolean input'),
|
||||
array: z.array(z.string()).describe('Array input'),
|
||||
object: z.object({ nested: z.string() }).describe('Object input'),
|
||||
}).meta({
|
||||
title: 'types-tool',
|
||||
description: 'Tool demonstrating different types',
|
||||
});
|
||||
|
||||
const result = schemaToToolContent(testSchema);
|
||||
|
||||
expect(result).toContain('- text (string, 必需): Text input');
|
||||
expect(result).toContain('- number (number, 必需): Number input');
|
||||
expect(result).toContain('- boolean (boolean, 必需): Boolean input');
|
||||
expect(result).toContain('- array (array, 必需): Array input');
|
||||
expect(result).toContain('- object (object, 必需): Object input');
|
||||
});
|
||||
|
||||
it('should handle enum parameters with options', () => {
|
||||
const testSchema = z.object({
|
||||
operation: z.enum(['add', 'delete', 'update']).describe('Type of operation to execute'),
|
||||
status: z.enum(['active', 'inactive']).describe('Current status'),
|
||||
}).meta({
|
||||
title: 'enum-tool',
|
||||
description: 'Tool demonstrating enum parameters',
|
||||
});
|
||||
|
||||
const result = schemaToToolContent(testSchema);
|
||||
|
||||
expect(result).toContain('- operation (enum, 必需): Type of operation to execute ("add", "delete", "update")');
|
||||
expect(result).toContain('- status (enum, 必需): Current status ("active", "inactive")');
|
||||
});
|
||||
});
|
||||
91
src/services/agentInstance/utilities/schemaToToolContent.ts
Normal file
91
src/services/agentInstance/utilities/schemaToToolContent.ts
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
import { i18n } from '@services/libs/i18n';
|
||||
import { z } from 'zod/v4';
|
||||
|
||||
/**
|
||||
* Build a tool content string from a Zod schema's JSON Schema meta and supplied examples.
|
||||
* Inputs:
|
||||
* - schema: Zod schema object
|
||||
* - toolId: string tool id (for header)
|
||||
* - examples: optional array of example strings (preformatted tool_use)
|
||||
*/
|
||||
export function schemaToToolContent(schema: z.ZodType) {
|
||||
const schemaUnknown: unknown = z.toJSONSchema(schema, { target: 'draft-7' });
|
||||
|
||||
let parameterLines = '';
|
||||
let schemaTitle = '';
|
||||
let schemaDescription = '';
|
||||
|
||||
if (schemaUnknown && typeof schemaUnknown === 'object' && schemaUnknown !== null) {
|
||||
const s = schemaUnknown as Record<string, unknown>;
|
||||
schemaTitle = s.title && typeof s.title === 'string'
|
||||
? s.title
|
||||
: '';
|
||||
schemaDescription = s.description && typeof s.description === 'string'
|
||||
? s.description
|
||||
: '';
|
||||
const props = s.properties as Record<string, unknown> | undefined;
|
||||
const requiredArray = Array.isArray(s.required) ? (s.required as string[]) : [];
|
||||
if (props) {
|
||||
parameterLines = Object.keys(props)
|
||||
.map((key) => {
|
||||
const property = props[key] as Record<string, unknown> | undefined;
|
||||
let type = property && typeof property.type === 'string' ? property.type : 'string';
|
||||
let desc = '';
|
||||
if (property) {
|
||||
if (typeof property.description === 'string') {
|
||||
// Try to translate the description if it looks like an i18n key
|
||||
desc = property.description.startsWith('Schema.')
|
||||
? i18n.t(property.description)
|
||||
: property.description;
|
||||
} else if (property.title && typeof property.title === 'string') {
|
||||
// Try to translate the title if it looks like an i18n key
|
||||
desc = property.title.startsWith('Schema.')
|
||||
? i18n.t(property.title)
|
||||
: property.title;
|
||||
}
|
||||
|
||||
// Handle enum values
|
||||
if (property.enum && Array.isArray(property.enum)) {
|
||||
const enumValues = property.enum.map(value => `"${String(value)}"`).join(', ');
|
||||
desc = desc ? `${desc} (${enumValues})` : `Options: ${enumValues}`;
|
||||
type = 'enum';
|
||||
}
|
||||
}
|
||||
const required = requiredArray.includes(key)
|
||||
? i18n.t('Tool.Schema.Required')
|
||||
: i18n.t('Tool.Schema.Optional');
|
||||
return `- ${key} (${type}, ${required}): ${desc}`;
|
||||
})
|
||||
.join('\n');
|
||||
}
|
||||
}
|
||||
|
||||
const toolId = (schemaUnknown && typeof schemaUnknown === 'object' && schemaUnknown !== null && (schemaUnknown as Record<string, unknown>).title)
|
||||
? String((schemaUnknown as Record<string, unknown>).title)
|
||||
: 'tool';
|
||||
|
||||
let exampleSection = '';
|
||||
if (schemaUnknown && typeof schemaUnknown === 'object' && schemaUnknown !== null) {
|
||||
const s = schemaUnknown as Record<string, unknown>;
|
||||
const ex = s.examples;
|
||||
if (Array.isArray(ex)) {
|
||||
exampleSection = ex
|
||||
.map(exampleItem => `- <tool_use name="${toolId}">${JSON.stringify(exampleItem)}</tool_use>`)
|
||||
.join('\n');
|
||||
}
|
||||
}
|
||||
|
||||
// Try to translate schema description if it looks like an i18n key
|
||||
const finalDescription = schemaDescription
|
||||
? (schemaDescription.startsWith('在Wiki')
|
||||
? schemaDescription // Already translated Chinese text
|
||||
: i18n.t(schemaDescription))
|
||||
: schemaTitle; // Fallback to title if no description
|
||||
|
||||
const descriptionLabel = i18n.t('Tool.Schema.Description');
|
||||
const parametersLabel = i18n.t('Tool.Schema.Parameters');
|
||||
const examplesLabel = i18n.t('Tool.Schema.Examples');
|
||||
|
||||
const content = `\n## ${toolId}\n**${descriptionLabel}**: ${finalDescription}\n**${parametersLabel}**:\n${parameterLines}\n\n**${examplesLabel}**:\n${exampleSection}\n`;
|
||||
return content;
|
||||
}
|
||||
|
|
@ -242,6 +242,52 @@ export class DatabaseService implements IDatabaseService {
|
|||
return this.dataSources.get(key)!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get database file information like whether it exists and its size in bytes.
|
||||
*/
|
||||
public async getDatabaseInfo(key: string): Promise<{ exists: boolean; size?: number }> {
|
||||
const databasePath = this.getDatabasePath(key);
|
||||
if (databasePath === ':memory:') {
|
||||
return { exists: true, size: undefined };
|
||||
}
|
||||
|
||||
try {
|
||||
const exists = await fs.pathExists(databasePath);
|
||||
if (!exists) return { exists: false };
|
||||
const stat = await fs.stat(databasePath);
|
||||
return { exists: true, size: stat.size };
|
||||
} catch (error) {
|
||||
logger.error(`getDatabaseInfo failed for key: ${key}`, { error: (error as Error).message });
|
||||
return { exists: false };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the database file for a given key and close any active connection.
|
||||
*/
|
||||
public async deleteDatabase(key: string): Promise<void> {
|
||||
try {
|
||||
// Close and remove from pool if exists
|
||||
if (this.dataSources.has(key)) {
|
||||
try {
|
||||
await this.dataSources.get(key)?.destroy();
|
||||
} catch (error) {
|
||||
logger.warn(`Failed to destroy datasource for key: ${key} before deletion`, { error: (error as Error).message });
|
||||
}
|
||||
this.dataSources.delete(key);
|
||||
}
|
||||
|
||||
const databasePath = this.getDatabasePath(key);
|
||||
if (databasePath !== ':memory:' && await fs.pathExists(databasePath)) {
|
||||
await fs.unlink(databasePath);
|
||||
logger.info(`Database file deleted for key: ${key}`);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`deleteDatabase failed for key: ${key}`, { error: (error as Error).message });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close database connection for a given key
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -61,6 +61,16 @@ export interface IDatabaseService {
|
|||
* Close database connection
|
||||
*/
|
||||
closeAppDatabase(key: string, drop?: boolean): void;
|
||||
|
||||
/**
|
||||
* Get database file information like whether it exists and its size in bytes.
|
||||
*/
|
||||
getDatabaseInfo(key: string): Promise<{ exists: boolean; size?: number }>;
|
||||
|
||||
/**
|
||||
* Delete the database file for a given key and close any active connection.
|
||||
*/
|
||||
deleteDatabase(key: string): Promise<void>;
|
||||
}
|
||||
|
||||
export const DatabaseServiceIPCDescriptor = {
|
||||
|
|
@ -70,5 +80,7 @@ export const DatabaseServiceIPCDescriptor = {
|
|||
initializeForApp: ProxyPropertyType.Function,
|
||||
getDatabase: ProxyPropertyType.Function,
|
||||
closeAppDatabase: ProxyPropertyType.Function,
|
||||
getDatabaseInfo: ProxyPropertyType.Function,
|
||||
deleteDatabase: ProxyPropertyType.Function,
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -77,7 +77,16 @@ export function streamFromProvider(
|
|||
return streamText({
|
||||
model: client(model),
|
||||
system: systemPrompt,
|
||||
messages,
|
||||
// Convert tool role messages to user role for API compatibility
|
||||
messages: messages.map(message => {
|
||||
if (message.role === 'tool') {
|
||||
return {
|
||||
...message,
|
||||
role: 'user' as const,
|
||||
};
|
||||
}
|
||||
return message;
|
||||
}) as typeof messages,
|
||||
temperature,
|
||||
abortSignal: signal,
|
||||
});
|
||||
|
|
|
|||
4
src/services/libs/i18n/placeholder.ts
Normal file
4
src/services/libs/i18n/placeholder.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
import { identity } from 'lodash';
|
||||
|
||||
/** Placeholder to trigger VSCode i18nAlly extension to show translated text. We translate it on needed (e.g. on frontend), because at this time, i18n service might not be fully initialized. */
|
||||
export const t = identity;
|
||||
|
|
@ -87,6 +87,11 @@ export function DeveloperTools(props: ISectionProps): React.JSX.Element {
|
|||
disabled={preference === undefined}
|
||||
onChange={async () => {
|
||||
await window.service.preference.set('externalAPIDebug', !preference?.externalAPIDebug);
|
||||
const info = await window.service.database.getDatabaseInfo('externalApi');
|
||||
if (!info?.exists) {
|
||||
// if database didn't exist before, enabling externalAPIDebug requires application restart to initialize the database table
|
||||
props.requestRestartCountDown?.();
|
||||
}
|
||||
}}
|
||||
name='externalAPIDebug'
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -138,8 +138,6 @@ export function Search(props: SearchProps): React.JSX.Element {
|
|||
// Get AI config from external API service
|
||||
const aiConfig = await window.service.externalAPI.getAIConfig();
|
||||
|
||||
// DEBUG: console aiConfig
|
||||
console.log(`aiConfig`, aiConfig);
|
||||
if (!aiConfig.api.provider) {
|
||||
showInfoSnackbar({
|
||||
message: t('Preference.SearchEmbeddingNoAIConfigError'),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue