cowork-os 0.3.21 → 0.3.25
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/README.md +372 -10
- package/connectors/README.md +20 -0
- package/connectors/asana-mcp/README.md +24 -0
- package/connectors/asana-mcp/dist/index.js +427 -0
- package/connectors/asana-mcp/package.json +15 -0
- package/connectors/asana-mcp/src/index.ts +553 -0
- package/connectors/asana-mcp/tsconfig.json +13 -0
- package/connectors/hubspot-mcp/README.md +35 -0
- package/connectors/hubspot-mcp/dist/index.js +454 -0
- package/connectors/hubspot-mcp/package.json +15 -0
- package/connectors/hubspot-mcp/src/index.ts +562 -0
- package/connectors/hubspot-mcp/tsconfig.json +13 -0
- package/connectors/jira-mcp/README.md +49 -0
- package/connectors/jira-mcp/dist/index.js +588 -0
- package/connectors/jira-mcp/package.json +15 -0
- package/connectors/jira-mcp/src/index.ts +711 -0
- package/connectors/jira-mcp/tsconfig.json +13 -0
- package/connectors/linear-mcp/README.md +22 -0
- package/connectors/linear-mcp/dist/index.js +402 -0
- package/connectors/linear-mcp/package.json +15 -0
- package/connectors/linear-mcp/src/index.ts +522 -0
- package/connectors/linear-mcp/tsconfig.json +13 -0
- package/connectors/okta-mcp/README.md +24 -0
- package/connectors/okta-mcp/dist/index.js +411 -0
- package/connectors/okta-mcp/package.json +15 -0
- package/connectors/okta-mcp/src/index.ts +520 -0
- package/connectors/okta-mcp/tsconfig.json +13 -0
- package/connectors/salesforce-mcp/README.md +47 -0
- package/connectors/salesforce-mcp/dist/index.js +584 -0
- package/connectors/salesforce-mcp/package.json +15 -0
- package/connectors/salesforce-mcp/src/index.ts +722 -0
- package/connectors/salesforce-mcp/tsconfig.json +13 -0
- package/connectors/servicenow-mcp/README.md +26 -0
- package/connectors/servicenow-mcp/dist/index.js +400 -0
- package/connectors/servicenow-mcp/package.json +15 -0
- package/connectors/servicenow-mcp/src/index.ts +500 -0
- package/connectors/servicenow-mcp/tsconfig.json +13 -0
- package/connectors/templates/mcp-connector/README.md +31 -0
- package/connectors/templates/mcp-connector/package.json +15 -0
- package/connectors/templates/mcp-connector/src/index.ts +330 -0
- package/connectors/templates/mcp-connector/tsconfig.json +13 -0
- package/connectors/zendesk-mcp/README.md +40 -0
- package/connectors/zendesk-mcp/dist/index.js +431 -0
- package/connectors/zendesk-mcp/package.json +15 -0
- package/connectors/zendesk-mcp/src/index.ts +543 -0
- package/connectors/zendesk-mcp/tsconfig.json +13 -0
- package/dist/electron/electron/agent/custom-skill-loader.js +31 -1
- package/dist/electron/electron/agent/daemon.js +189 -13
- package/dist/electron/electron/agent/executor.js +895 -78
- package/dist/electron/electron/agent/llm/anthropic-compatible-provider.js +177 -0
- package/dist/electron/electron/agent/llm/azure-openai-provider.js +328 -0
- package/dist/electron/electron/agent/llm/bedrock-provider.js +49 -9
- package/dist/electron/electron/agent/llm/github-copilot-provider.js +97 -0
- package/dist/electron/electron/agent/llm/groq-provider.js +33 -0
- package/dist/electron/electron/agent/llm/index.js +13 -1
- package/dist/electron/electron/agent/llm/kimi-provider.js +33 -0
- package/dist/electron/electron/agent/llm/openai-compatible-provider.js +116 -0
- package/dist/electron/electron/agent/llm/openai-compatible.js +111 -0
- package/dist/electron/electron/agent/llm/openai-oauth.js +2 -1
- package/dist/electron/electron/agent/llm/openrouter-provider.js +1 -1
- package/dist/electron/electron/agent/llm/provider-factory.js +350 -4
- package/dist/electron/electron/agent/llm/types.js +66 -1
- package/dist/electron/electron/agent/llm/xai-provider.js +33 -0
- package/dist/electron/electron/agent/search/provider-factory.js +38 -2
- package/dist/electron/electron/agent/tools/box-tools.js +231 -0
- package/dist/electron/electron/agent/tools/builtin-settings.js +28 -0
- package/dist/electron/electron/agent/tools/dropbox-tools.js +237 -0
- package/dist/electron/electron/agent/tools/file-tools.js +66 -3
- package/dist/electron/electron/agent/tools/google-drive-tools.js +227 -0
- package/dist/electron/electron/agent/tools/grep-tools.js +90 -10
- package/dist/electron/electron/agent/tools/image-tools.js +11 -1
- package/dist/electron/electron/agent/tools/notion-tools.js +312 -0
- package/dist/electron/electron/agent/tools/onedrive-tools.js +217 -0
- package/dist/electron/electron/agent/tools/registry.js +548 -10
- package/dist/electron/electron/agent/tools/search-tools.js +28 -10
- package/dist/electron/electron/agent/tools/sharepoint-tools.js +243 -0
- package/dist/electron/electron/agent/tools/shell-tools.js +12 -3
- package/dist/electron/electron/agent/tools/x-tools.js +1 -1
- package/dist/electron/electron/agents/agent-dispatch.js +63 -0
- package/dist/electron/electron/database/repositories.js +19 -5
- package/dist/electron/electron/database/schema.js +8 -0
- package/dist/electron/electron/gateway/channels/whatsapp.js +55 -0
- package/dist/electron/electron/gateway/index.js +75 -1
- package/dist/electron/electron/gateway/router.js +209 -154
- package/dist/electron/electron/ipc/canvas-handlers.js +5 -0
- package/dist/electron/electron/ipc/handlers.js +763 -267
- package/dist/electron/electron/main.js +63 -0
- package/dist/electron/electron/mcp/oauth/connector-oauth.js +333 -0
- package/dist/electron/electron/mcp/registry/MCPRegistryManager.js +503 -154
- package/dist/electron/electron/memory/MemoryService.js +2 -1
- package/dist/electron/electron/preload.js +78 -1
- package/dist/electron/electron/settings/appearance-manager.js +18 -1
- package/dist/electron/electron/settings/box-manager.js +54 -0
- package/dist/electron/electron/settings/dropbox-manager.js +54 -0
- package/dist/electron/electron/settings/google-drive-manager.js +54 -0
- package/dist/electron/electron/settings/notion-manager.js +56 -0
- package/dist/electron/electron/settings/onedrive-manager.js +54 -0
- package/dist/electron/electron/settings/sharepoint-manager.js +54 -0
- package/dist/electron/electron/utils/box-api.js +153 -0
- package/dist/electron/electron/utils/dropbox-api.js +144 -0
- package/dist/electron/electron/utils/env-migration.js +19 -0
- package/dist/electron/electron/utils/google-drive-api.js +152 -0
- package/dist/electron/electron/utils/notion-api.js +103 -0
- package/dist/electron/electron/utils/onedrive-api.js +113 -0
- package/dist/electron/electron/utils/sharepoint-api.js +109 -0
- package/dist/electron/electron/utils/validation.js +98 -3
- package/dist/electron/electron/utils/x-cli.js +1 -1
- package/dist/electron/shared/channelMessages.js +284 -3
- package/dist/electron/shared/llm-provider-catalog.js +198 -0
- package/dist/electron/shared/types.js +90 -1
- package/package.json +14 -3
- package/resources/skills/nano-banana-pro.json +4 -4
- package/resources/skills/openai-image-gen.json +3 -3
- package/resources/skills/scripts/gen.py +163 -0
- package/resources/skills/scripts/generate_image.py +91 -0
- package/src/electron/agent/custom-skill-loader.ts +34 -1
- package/src/electron/agent/daemon.ts +210 -14
- package/src/electron/agent/executor.ts +1124 -85
- package/src/electron/agent/llm/anthropic-compatible-provider.ts +214 -0
- package/src/electron/agent/llm/azure-openai-provider.ts +388 -0
- package/src/electron/agent/llm/bedrock-provider.ts +62 -9
- package/src/electron/agent/llm/github-copilot-provider.ts +117 -0
- package/src/electron/agent/llm/groq-provider.ts +39 -0
- package/src/electron/agent/llm/index.ts +6 -0
- package/src/electron/agent/llm/kimi-provider.ts +39 -0
- package/src/electron/agent/llm/openai-compatible-provider.ts +153 -0
- package/src/electron/agent/llm/openai-compatible.ts +133 -0
- package/src/electron/agent/llm/openai-oauth.ts +2 -1
- package/src/electron/agent/llm/openrouter-provider.ts +2 -1
- package/src/electron/agent/llm/provider-factory.ts +459 -6
- package/src/electron/agent/llm/types.ts +95 -1
- package/src/electron/agent/llm/xai-provider.ts +39 -0
- package/src/electron/agent/search/provider-factory.ts +43 -2
- package/src/electron/agent/tools/box-tools.ts +239 -0
- package/src/electron/agent/tools/builtin-settings.ts +36 -0
- package/src/electron/agent/tools/dropbox-tools.ts +237 -0
- package/src/electron/agent/tools/file-tools.ts +66 -3
- package/src/electron/agent/tools/gmail-tools.ts +240 -0
- package/src/electron/agent/tools/google-calendar-tools.ts +258 -0
- package/src/electron/agent/tools/google-drive-tools.ts +228 -0
- package/src/electron/agent/tools/grep-tools.ts +97 -12
- package/src/electron/agent/tools/image-tools.ts +11 -1
- package/src/electron/agent/tools/notion-tools.ts +330 -0
- package/src/electron/agent/tools/onedrive-tools.ts +217 -0
- package/src/electron/agent/tools/registry.ts +794 -10
- package/src/electron/agent/tools/search-tools.ts +29 -11
- package/src/electron/agent/tools/sharepoint-tools.ts +247 -0
- package/src/electron/agent/tools/shell-tools.ts +11 -3
- package/src/electron/agent/tools/x-tools.ts +1 -1
- package/src/electron/agents/agent-dispatch.ts +79 -0
- package/src/electron/database/SecureSettingsRepository.ts +7 -1
- package/src/electron/database/repositories.ts +58 -6
- package/src/electron/database/schema.ts +8 -0
- package/src/electron/gateway/channels/discord.ts +4 -0
- package/src/electron/gateway/channels/google-chat.ts +3 -0
- package/src/electron/gateway/channels/line.ts +3 -0
- package/src/electron/gateway/channels/matrix-client.ts +15 -0
- package/src/electron/gateway/channels/matrix.ts +31 -0
- package/src/electron/gateway/channels/mattermost.ts +3 -0
- package/src/electron/gateway/channels/signal.ts +3 -0
- package/src/electron/gateway/channels/slack.ts +9 -4
- package/src/electron/gateway/channels/teams.ts +4 -0
- package/src/electron/gateway/channels/telegram.ts +2 -0
- package/src/electron/gateway/channels/twitch.ts +2 -0
- package/src/electron/gateway/channels/types.ts +8 -0
- package/src/electron/gateway/channels/whatsapp.ts +66 -0
- package/src/electron/gateway/index.ts +95 -2
- package/src/electron/gateway/router.ts +231 -161
- package/src/electron/gateway/security.ts +21 -9
- package/src/electron/ipc/canvas-handlers.ts +10 -0
- package/src/electron/ipc/handlers.ts +848 -292
- package/src/electron/main.ts +35 -0
- package/src/electron/mcp/oauth/connector-oauth.ts +448 -0
- package/src/electron/mcp/registry/MCPRegistryManager.ts +343 -12
- package/src/electron/memory/MemoryService.ts +7 -1
- package/src/electron/preload.ts +200 -5
- package/src/electron/settings/appearance-manager.ts +20 -2
- package/src/electron/settings/box-manager.ts +58 -0
- package/src/electron/settings/dropbox-manager.ts +58 -0
- package/src/electron/settings/google-workspace-manager.ts +59 -0
- package/src/electron/settings/notion-manager.ts +60 -0
- package/src/electron/settings/onedrive-manager.ts +58 -0
- package/src/electron/settings/sharepoint-manager.ts +58 -0
- package/src/electron/utils/box-api.ts +184 -0
- package/src/electron/utils/dropbox-api.ts +171 -0
- package/src/electron/utils/env-migration.ts +22 -0
- package/src/electron/utils/gmail-api.ts +121 -0
- package/src/electron/utils/google-calendar-api.ts +115 -0
- package/src/electron/utils/google-workspace-api.ts +228 -0
- package/src/electron/utils/google-workspace-auth.ts +109 -0
- package/src/electron/utils/google-workspace-oauth.ts +232 -0
- package/src/electron/utils/notion-api.ts +126 -0
- package/src/electron/utils/onedrive-api.ts +137 -0
- package/src/electron/utils/sharepoint-api.ts +132 -0
- package/src/electron/utils/validation.ts +128 -1
- package/src/electron/utils/x-cli.ts +1 -1
- package/src/renderer/App.tsx +119 -8
- package/src/renderer/components/ActivityFeedItem.tsx +34 -17
- package/src/renderer/components/AgentWorkingStatePanel.tsx +7 -5
- package/src/renderer/components/AppearanceSettings.tsx +37 -2
- package/src/renderer/components/BlueBubblesSettings.tsx +18 -7
- package/src/renderer/components/BoxSettings.tsx +203 -0
- package/src/renderer/components/BrowserView.tsx +101 -0
- package/src/renderer/components/BuiltinToolsSettings.tsx +105 -0
- package/src/renderer/components/CanvasPreview.tsx +68 -1
- package/src/renderer/components/ConnectorEnvModal.tsx +116 -0
- package/src/renderer/components/ConnectorSetupModal.tsx +566 -0
- package/src/renderer/components/ConnectorsSettings.tsx +397 -0
- package/src/renderer/components/ControlPlaneSettings.tsx +2 -0
- package/src/renderer/components/DiscordSettings.tsx +18 -7
- package/src/renderer/components/DropboxSettings.tsx +202 -0
- package/src/renderer/components/EmailSettings.tsx +18 -7
- package/src/renderer/components/FileViewer.tsx +21 -13
- package/src/renderer/components/GoogleChatSettings.tsx +17 -7
- package/src/renderer/components/GoogleWorkspaceSettings.tsx +332 -0
- package/src/renderer/components/ImessageSettings.tsx +22 -11
- package/src/renderer/components/LineIcons.tsx +376 -0
- package/src/renderer/components/LineSettings.tsx +18 -7
- package/src/renderer/components/MCPSettings.tsx +56 -0
- package/src/renderer/components/MainContent.tsx +740 -76
- package/src/renderer/components/MatrixSettings.tsx +18 -7
- package/src/renderer/components/MattermostSettings.tsx +18 -7
- package/src/renderer/components/NodesSettings.tsx +58 -99
- package/src/renderer/components/NotificationPanel.tsx +25 -11
- package/src/renderer/components/NotionSettings.tsx +231 -0
- package/src/renderer/components/Onboarding/Onboarding.tsx +13 -1
- package/src/renderer/components/OnboardingModal.tsx +70 -1
- package/src/renderer/components/OneDriveSettings.tsx +212 -0
- package/src/renderer/components/RightPanel.tsx +141 -28
- package/src/renderer/components/ScheduledTasksSettings.tsx +10 -62
- package/src/renderer/components/SearchSettings.tsx +118 -114
- package/src/renderer/components/Settings.tsx +1425 -651
- package/src/renderer/components/SharePointSettings.tsx +224 -0
- package/src/renderer/components/Sidebar.tsx +94 -19
- package/src/renderer/components/SignalSettings.tsx +18 -7
- package/src/renderer/components/SkillHubBrowser.tsx +144 -185
- package/src/renderer/components/SlackSettings.tsx +18 -7
- package/src/renderer/components/TaskQuickActions.tsx +11 -6
- package/src/renderer/components/TaskTimeline.tsx +58 -26
- package/src/renderer/components/TeamsSettings.tsx +18 -7
- package/src/renderer/components/TelegramSettings.tsx +18 -7
- package/src/renderer/components/ThemeIcon.tsx +16 -0
- package/src/renderer/components/TwitchSettings.tsx +18 -7
- package/src/renderer/components/VoiceSettings.tsx +30 -74
- package/src/renderer/components/WhatsAppSettings.tsx +48 -37
- package/src/renderer/components/WorkingStateHistory.tsx +7 -5
- package/src/renderer/components/WorkspaceSelector.tsx +42 -13
- package/src/renderer/hooks/useOnboardingFlow.ts +21 -0
- package/src/renderer/styles/index.css +2333 -209
- package/src/shared/channelMessages.ts +367 -4
- package/src/shared/llm-provider-catalog.ts +217 -0
- package/src/shared/types.ts +251 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState, useEffect } from 'react';
|
|
1
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
2
2
|
import { ChannelData, ChannelUserData, SecurityMode, ContextType, ContextPolicy } from '../../shared/types';
|
|
3
3
|
import { PairingCodeDisplay } from './PairingCodeDisplay';
|
|
4
4
|
import { ContextPolicySettings } from './ContextPolicySettings';
|
|
@@ -33,11 +33,7 @@ export function MatrixSettings({ onStatusChange }: MatrixSettingsProps) {
|
|
|
33
33
|
const [contextPolicies, setContextPolicies] = useState<Record<ContextType, ContextPolicy>>({} as Record<ContextType, ContextPolicy>);
|
|
34
34
|
const [savingPolicy, setSavingPolicy] = useState(false);
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
loadChannel();
|
|
38
|
-
}, []);
|
|
39
|
-
|
|
40
|
-
const loadChannel = async () => {
|
|
36
|
+
const loadChannel = useCallback(async () => {
|
|
41
37
|
try {
|
|
42
38
|
setLoading(true);
|
|
43
39
|
const channels = await window.electronAPI.getGatewayChannels();
|
|
@@ -76,7 +72,22 @@ export function MatrixSettings({ onStatusChange }: MatrixSettingsProps) {
|
|
|
76
72
|
} finally {
|
|
77
73
|
setLoading(false);
|
|
78
74
|
}
|
|
79
|
-
};
|
|
75
|
+
}, [onStatusChange]);
|
|
76
|
+
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
loadChannel();
|
|
79
|
+
}, [loadChannel]);
|
|
80
|
+
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
const unsubscribe = window.electronAPI?.onGatewayUsersUpdated?.((data) => {
|
|
83
|
+
if (data?.channelType !== 'matrix') return;
|
|
84
|
+
if (channel && data?.channelId && data.channelId !== channel.id) return;
|
|
85
|
+
loadChannel();
|
|
86
|
+
});
|
|
87
|
+
return () => {
|
|
88
|
+
if (unsubscribe) unsubscribe();
|
|
89
|
+
};
|
|
90
|
+
}, [channel?.id, loadChannel]);
|
|
80
91
|
|
|
81
92
|
const handleAddChannel = async () => {
|
|
82
93
|
if (!homeserver.trim() || !userId.trim() || !accessToken.trim()) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState, useEffect } from 'react';
|
|
1
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
2
2
|
import { ChannelData, ChannelUserData, SecurityMode, ContextType, ContextPolicy } from '../../shared/types';
|
|
3
3
|
import { PairingCodeDisplay } from './PairingCodeDisplay';
|
|
4
4
|
import { ContextPolicySettings } from './ContextPolicySettings';
|
|
@@ -31,11 +31,7 @@ export function MattermostSettings({ onStatusChange }: MattermostSettingsProps)
|
|
|
31
31
|
const [contextPolicies, setContextPolicies] = useState<Record<ContextType, ContextPolicy>>({} as Record<ContextType, ContextPolicy>);
|
|
32
32
|
const [savingPolicy, setSavingPolicy] = useState(false);
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
loadChannel();
|
|
36
|
-
}, []);
|
|
37
|
-
|
|
38
|
-
const loadChannel = async () => {
|
|
34
|
+
const loadChannel = useCallback(async () => {
|
|
39
35
|
try {
|
|
40
36
|
setLoading(true);
|
|
41
37
|
const channels = await window.electronAPI.getGatewayChannels();
|
|
@@ -71,7 +67,22 @@ export function MattermostSettings({ onStatusChange }: MattermostSettingsProps)
|
|
|
71
67
|
} finally {
|
|
72
68
|
setLoading(false);
|
|
73
69
|
}
|
|
74
|
-
};
|
|
70
|
+
}, [onStatusChange]);
|
|
71
|
+
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
loadChannel();
|
|
74
|
+
}, [loadChannel]);
|
|
75
|
+
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
const unsubscribe = window.electronAPI?.onGatewayUsersUpdated?.((data) => {
|
|
78
|
+
if (data?.channelType !== 'mattermost') return;
|
|
79
|
+
if (channel && data?.channelId && data.channelId !== channel.id) return;
|
|
80
|
+
loadChannel();
|
|
81
|
+
});
|
|
82
|
+
return () => {
|
|
83
|
+
if (unsubscribe) unsubscribe();
|
|
84
|
+
};
|
|
85
|
+
}, [channel?.id, loadChannel]);
|
|
75
86
|
|
|
76
87
|
const handleAddChannel = async () => {
|
|
77
88
|
if (!serverUrl.trim() || !token.trim()) {
|
|
@@ -118,101 +118,75 @@ export function NodesSettings({ compact = false }: NodesSettingsProps) {
|
|
|
118
118
|
};
|
|
119
119
|
|
|
120
120
|
if (loading) {
|
|
121
|
-
return
|
|
122
|
-
<div className="flex items-center justify-center py-8">
|
|
123
|
-
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-accent-500" />
|
|
124
|
-
</div>
|
|
125
|
-
);
|
|
121
|
+
return <div className="settings-loading">Loading companions...</div>;
|
|
126
122
|
}
|
|
127
123
|
|
|
128
124
|
return (
|
|
129
|
-
<div className="
|
|
130
|
-
{/* Header */}
|
|
125
|
+
<div className="nodes-settings">
|
|
131
126
|
{!compact && (
|
|
132
|
-
<div className="
|
|
133
|
-
<div>
|
|
134
|
-
<
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
127
|
+
<div className="settings-section nodes-header-section">
|
|
128
|
+
<div className="settings-section-header">
|
|
129
|
+
<div>
|
|
130
|
+
<h3>Mobile Companions</h3>
|
|
131
|
+
<p className="settings-description">
|
|
132
|
+
Connect iOS or Android devices as mobile companions for device-specific actions.
|
|
133
|
+
</p>
|
|
134
|
+
</div>
|
|
135
|
+
<button className="button-secondary button-small" onClick={loadNodes}>
|
|
136
|
+
Refresh
|
|
137
|
+
</button>
|
|
138
138
|
</div>
|
|
139
|
-
<button
|
|
140
|
-
onClick={loadNodes}
|
|
141
|
-
className="px-3 py-1.5 text-sm text-neutral-300 hover:text-neutral-100 bg-neutral-800 hover:bg-neutral-700 rounded-md transition-colors"
|
|
142
|
-
>
|
|
143
|
-
Refresh
|
|
144
|
-
</button>
|
|
145
139
|
</div>
|
|
146
140
|
)}
|
|
147
141
|
|
|
148
|
-
{/* Connection Instructions */}
|
|
149
142
|
{nodes.length === 0 && (
|
|
150
|
-
<div className="
|
|
151
|
-
<h4
|
|
152
|
-
<ol
|
|
143
|
+
<div className="nodes-empty-card">
|
|
144
|
+
<h4>How to Connect</h4>
|
|
145
|
+
<ol>
|
|
153
146
|
<li>Make sure the Control Plane is enabled and running</li>
|
|
154
147
|
<li>Install the companion app on your iOS or Android device</li>
|
|
155
148
|
<li>Enter the gateway URL and authentication token in the app</li>
|
|
156
149
|
<li>The device will appear here once connected</li>
|
|
157
150
|
</ol>
|
|
158
|
-
<div className="
|
|
159
|
-
<p
|
|
160
|
-
For local network: ws://{'<your-mac-ip>'}:18789
|
|
151
|
+
<div className="nodes-connection-note">
|
|
152
|
+
<p>
|
|
153
|
+
<code>For local network: ws://{'<your-mac-ip>'}:18789</code>
|
|
161
154
|
</p>
|
|
162
|
-
<p
|
|
155
|
+
<p>
|
|
163
156
|
For remote access: Enable Tailscale or SSH tunnel in Control Plane settings
|
|
164
157
|
</p>
|
|
165
158
|
</div>
|
|
166
159
|
</div>
|
|
167
160
|
)}
|
|
168
161
|
|
|
169
|
-
{/* Node List */}
|
|
170
162
|
{nodes.length > 0 && (
|
|
171
|
-
<div className="
|
|
163
|
+
<div className="nodes-list">
|
|
172
164
|
{nodes.map((node) => (
|
|
173
165
|
<div
|
|
174
166
|
key={node.id}
|
|
175
|
-
className={`
|
|
176
|
-
selectedNode === node.id
|
|
177
|
-
? 'border-accent-500'
|
|
178
|
-
: 'border-neutral-700 hover:border-neutral-600'
|
|
179
|
-
}`}
|
|
167
|
+
className={`nodes-card ${selectedNode === node.id ? 'is-active' : ''}`}
|
|
180
168
|
onClick={() => setSelectedNode(selectedNode === node.id ? null : node.id)}
|
|
181
169
|
>
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
<span className="text-2xl">{getPlatformIcon(node.platform)}</span>
|
|
170
|
+
<div className="nodes-card-header">
|
|
171
|
+
<div className="nodes-card-info">
|
|
172
|
+
<span className="nodes-platform-icon">{getPlatformIcon(node.platform)}</span>
|
|
186
173
|
<div>
|
|
187
|
-
<h4 className="
|
|
188
|
-
<p className="
|
|
174
|
+
<h4 className="nodes-card-title">{node.displayName}</h4>
|
|
175
|
+
<p className="nodes-card-subtitle">
|
|
189
176
|
{node.platform.toUpperCase()} · v{node.version}
|
|
190
177
|
</p>
|
|
191
178
|
</div>
|
|
192
179
|
</div>
|
|
193
|
-
<
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
node.isForeground
|
|
197
|
-
? 'bg-green-500/20 text-green-400'
|
|
198
|
-
: 'bg-yellow-500/20 text-yellow-400'
|
|
199
|
-
}`}
|
|
200
|
-
>
|
|
201
|
-
{node.isForeground ? 'Foreground' : 'Background'}
|
|
202
|
-
</span>
|
|
203
|
-
</div>
|
|
180
|
+
<span className={`nodes-status ${node.isForeground ? 'foreground' : 'background'}`}>
|
|
181
|
+
{node.isForeground ? 'Foreground' : 'Background'}
|
|
182
|
+
</span>
|
|
204
183
|
</div>
|
|
205
184
|
|
|
206
|
-
|
|
207
|
-
<div className="mt-3 flex flex-wrap gap-2">
|
|
185
|
+
<div className="nodes-capabilities">
|
|
208
186
|
{node.capabilities.map((cap) => (
|
|
209
187
|
<span
|
|
210
188
|
key={cap}
|
|
211
|
-
className={`
|
|
212
|
-
node.permissions?.[cap]
|
|
213
|
-
? 'bg-neutral-700 text-neutral-300'
|
|
214
|
-
: 'bg-neutral-800 text-neutral-500'
|
|
215
|
-
}`}
|
|
189
|
+
className={`nodes-capability ${node.permissions?.[cap] ? '' : 'denied'}`}
|
|
216
190
|
title={node.permissions?.[cap] ? 'Permission granted' : 'Permission not granted'}
|
|
217
191
|
>
|
|
218
192
|
{getCapabilityIcon(cap)} {cap}
|
|
@@ -221,33 +195,30 @@ export function NodesSettings({ compact = false }: NodesSettingsProps) {
|
|
|
221
195
|
))}
|
|
222
196
|
</div>
|
|
223
197
|
|
|
224
|
-
{/* Expanded Details */}
|
|
225
198
|
{selectedNode === node.id && (
|
|
226
|
-
<div className="
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
<span className="
|
|
231
|
-
<p className="text-neutral-300 font-mono mt-0.5 truncate">{node.id}</p>
|
|
199
|
+
<div className="nodes-details">
|
|
200
|
+
<div className="nodes-detail-grid">
|
|
201
|
+
<div className="nodes-detail">
|
|
202
|
+
<span className="nodes-detail-label">Node ID</span>
|
|
203
|
+
<span className="nodes-detail-value nodes-detail-mono">{node.id}</span>
|
|
232
204
|
</div>
|
|
233
|
-
<div>
|
|
234
|
-
<span className="
|
|
235
|
-
<
|
|
205
|
+
<div className="nodes-detail">
|
|
206
|
+
<span className="nodes-detail-label">Device ID</span>
|
|
207
|
+
<span className="nodes-detail-value nodes-detail-mono">{node.deviceId || 'N/A'}</span>
|
|
236
208
|
</div>
|
|
237
|
-
<div>
|
|
238
|
-
<span className="
|
|
239
|
-
<
|
|
209
|
+
<div className="nodes-detail">
|
|
210
|
+
<span className="nodes-detail-label">Connected</span>
|
|
211
|
+
<span className="nodes-detail-value">{formatTimestamp(node.connectedAt)}</span>
|
|
240
212
|
</div>
|
|
241
|
-
<div>
|
|
242
|
-
<span className="
|
|
243
|
-
<
|
|
213
|
+
<div className="nodes-detail">
|
|
214
|
+
<span className="nodes-detail-label">Last Activity</span>
|
|
215
|
+
<span className="nodes-detail-value">{formatTimestamp(node.lastActivityAt)}</span>
|
|
244
216
|
</div>
|
|
245
217
|
</div>
|
|
246
218
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
<
|
|
250
|
-
<div className="flex flex-wrap gap-2">
|
|
219
|
+
<div className="nodes-test">
|
|
220
|
+
<h5>Test Commands</h5>
|
|
221
|
+
<div className="nodes-test-actions">
|
|
251
222
|
{node.commands.slice(0, 6).map((cmd) => (
|
|
252
223
|
<button
|
|
253
224
|
key={cmd}
|
|
@@ -256,7 +227,7 @@ export function NodesSettings({ compact = false }: NodesSettingsProps) {
|
|
|
256
227
|
handleTestCommand(node.id, cmd);
|
|
257
228
|
}}
|
|
258
229
|
disabled={testingCommand === `${node.id}:${cmd}`}
|
|
259
|
-
className="
|
|
230
|
+
className="nodes-command-btn"
|
|
260
231
|
>
|
|
261
232
|
{testingCommand === `${node.id}:${cmd}` ? 'Testing...' : cmd}
|
|
262
233
|
</button>
|
|
@@ -264,25 +235,16 @@ export function NodesSettings({ compact = false }: NodesSettingsProps) {
|
|
|
264
235
|
</div>
|
|
265
236
|
</div>
|
|
266
237
|
|
|
267
|
-
{/* Test Result */}
|
|
268
238
|
{testResult && testResult.nodeId === node.id && (
|
|
269
|
-
<div
|
|
270
|
-
className=
|
|
271
|
-
testResult.result.ok
|
|
272
|
-
? 'bg-green-500/10 border border-green-500/30 text-green-400'
|
|
273
|
-
: 'bg-red-500/10 border border-red-500/30 text-red-400'
|
|
274
|
-
}`}
|
|
275
|
-
>
|
|
276
|
-
<p className="font-medium">
|
|
239
|
+
<div className={`nodes-result ${testResult.result.ok ? 'success' : 'error'}`}>
|
|
240
|
+
<p className="nodes-result-title">
|
|
277
241
|
{testResult.command}: {testResult.result.ok ? 'Success' : 'Failed'}
|
|
278
242
|
</p>
|
|
279
243
|
{testResult.result.error && (
|
|
280
|
-
<p className="
|
|
281
|
-
{testResult.result.error.message}
|
|
282
|
-
</p>
|
|
244
|
+
<p className="nodes-result-error">{testResult.result.error.message}</p>
|
|
283
245
|
)}
|
|
284
246
|
{testResult.result.ok && testResult.result.payload != null && (
|
|
285
|
-
<pre className="
|
|
247
|
+
<pre className="nodes-result-pre">
|
|
286
248
|
{(() => {
|
|
287
249
|
try {
|
|
288
250
|
return JSON.stringify(testResult.result.payload, null, 2).slice(0, 500);
|
|
@@ -301,14 +263,11 @@ export function NodesSettings({ compact = false }: NodesSettingsProps) {
|
|
|
301
263
|
</div>
|
|
302
264
|
)}
|
|
303
265
|
|
|
304
|
-
{/* Status Summary */}
|
|
305
266
|
{nodes.length > 0 && !compact && (
|
|
306
|
-
<div className="
|
|
307
|
-
<
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
<span className="text-green-400">{nodes.filter(n => n.isForeground).length}</span> in foreground
|
|
311
|
-
</p>
|
|
267
|
+
<div className="nodes-summary">
|
|
268
|
+
<span className="nodes-summary-count">{nodes.length}</span> companion{nodes.length !== 1 ? 's' : ''} connected
|
|
269
|
+
{' · '}
|
|
270
|
+
<span className="nodes-summary-highlight">{nodes.filter(n => n.isForeground).length}</span> in foreground
|
|
312
271
|
</div>
|
|
313
272
|
)}
|
|
314
273
|
</div>
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { useState, useEffect, useRef } from 'react';
|
|
2
|
+
import { ThemeIcon } from './ThemeIcon';
|
|
3
|
+
import { AlertTriangleIcon, CheckIcon, ClockIcon, InfoIcon, XIcon } from './LineIcons';
|
|
2
4
|
|
|
3
5
|
// Define types inline for the renderer
|
|
4
6
|
interface AppNotification {
|
|
5
7
|
id: string;
|
|
6
|
-
type: 'task_completed' | 'task_failed' | 'scheduled_task' | 'info' | 'warning' | 'error';
|
|
8
|
+
type: 'task_completed' | 'task_failed' | 'scheduled_task' | 'input_required' | 'info' | 'warning' | 'error';
|
|
7
9
|
title: string;
|
|
8
10
|
message: string;
|
|
9
11
|
read: boolean;
|
|
@@ -236,13 +238,14 @@ const Icons = {
|
|
|
236
238
|
),
|
|
237
239
|
};
|
|
238
240
|
|
|
239
|
-
const typeIcons: Record<string, { icon:
|
|
240
|
-
task_completed: { icon:
|
|
241
|
-
task_failed: { icon:
|
|
242
|
-
scheduled_task: { icon:
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
241
|
+
const typeIcons: Record<string, { icon: React.ReactNode; bg: string; color: string }> = {
|
|
242
|
+
task_completed: { icon: <ThemeIcon emoji="✅" icon={<CheckIcon size={14} />} />, bg: 'rgba(34, 197, 94, 0.15)', color: 'rgb(34, 197, 94)' },
|
|
243
|
+
task_failed: { icon: <ThemeIcon emoji="❌" icon={<XIcon size={14} />} />, bg: 'rgba(239, 68, 68, 0.15)', color: 'rgb(239, 68, 68)' },
|
|
244
|
+
scheduled_task: { icon: <ThemeIcon emoji="⏰" icon={<ClockIcon size={14} />} />, bg: 'var(--color-accent-glass)', color: 'var(--color-accent)' },
|
|
245
|
+
input_required: { icon: <ThemeIcon emoji="📝" icon={<InfoIcon size={14} />} />, bg: 'rgba(245, 158, 11, 0.15)', color: 'rgb(245, 158, 11)' },
|
|
246
|
+
info: { icon: <ThemeIcon emoji="ℹ️" icon={<InfoIcon size={14} />} />, bg: 'rgba(59, 130, 246, 0.15)', color: 'rgb(59, 130, 246)' },
|
|
247
|
+
warning: { icon: <ThemeIcon emoji="⚠️" icon={<AlertTriangleIcon size={14} />} />, bg: 'rgba(245, 158, 11, 0.15)', color: 'rgb(245, 158, 11)' },
|
|
248
|
+
error: { icon: <ThemeIcon emoji="🚨" icon={<AlertTriangleIcon size={14} />} />, bg: 'rgba(239, 68, 68, 0.15)', color: 'rgb(239, 68, 68)' },
|
|
246
249
|
};
|
|
247
250
|
|
|
248
251
|
function formatRelativeTime(timestamp: number): string {
|
|
@@ -260,6 +263,10 @@ function formatRelativeTime(timestamp: number): string {
|
|
|
260
263
|
return new Date(timestamp).toLocaleDateString();
|
|
261
264
|
}
|
|
262
265
|
|
|
266
|
+
function stripLeadingEmoji(text: string): string {
|
|
267
|
+
return text.replace(/^[\u{1F300}-\u{1FAFF}\u{2600}-\u{27BF}][\uFE0F\uFE0E]?\s*/u, '');
|
|
268
|
+
}
|
|
269
|
+
|
|
263
270
|
export function NotificationPanel({ onNotificationClick }: NotificationPanelProps) {
|
|
264
271
|
const [isOpen, setIsOpen] = useState(false);
|
|
265
272
|
const [notifications, setNotifications] = useState<AppNotification[]>([]);
|
|
@@ -340,8 +347,14 @@ export function NotificationPanel({ onNotificationClick }: NotificationPanelProp
|
|
|
340
347
|
};
|
|
341
348
|
|
|
342
349
|
const handleNotificationClick = async (notification: AppNotification) => {
|
|
343
|
-
|
|
344
|
-
if (
|
|
350
|
+
const shouldAutoClear = notification.type === 'input_required';
|
|
351
|
+
if (shouldAutoClear) {
|
|
352
|
+
try {
|
|
353
|
+
await window.electronAPI.deleteNotification(notification.id);
|
|
354
|
+
} catch (error) {
|
|
355
|
+
console.error('Failed to delete notification:', error);
|
|
356
|
+
}
|
|
357
|
+
} else if (!notification.read) {
|
|
345
358
|
try {
|
|
346
359
|
await window.electronAPI.markNotificationRead(notification.id);
|
|
347
360
|
} catch (error) {
|
|
@@ -424,6 +437,7 @@ export function NotificationPanel({ onNotificationClick }: NotificationPanelProp
|
|
|
424
437
|
notifications.map((notification) => {
|
|
425
438
|
const typeConfig = typeIcons[notification.type] || typeIcons.info;
|
|
426
439
|
const isHovered = hoveredId === notification.id;
|
|
440
|
+
const modernTitle = stripLeadingEmoji(notification.title);
|
|
427
441
|
|
|
428
442
|
return (
|
|
429
443
|
<div
|
|
@@ -451,7 +465,7 @@ export function NotificationPanel({ onNotificationClick }: NotificationPanelProp
|
|
|
451
465
|
{typeConfig.icon}
|
|
452
466
|
</div>
|
|
453
467
|
<div style={styles.notificationContent}>
|
|
454
|
-
<p style={styles.notificationTitle}>{
|
|
468
|
+
<p style={styles.notificationTitle}>{modernTitle}</p>
|
|
455
469
|
<p style={styles.notificationMessage}>{notification.message}</p>
|
|
456
470
|
<span style={styles.notificationTime}>
|
|
457
471
|
{formatRelativeTime(notification.createdAt)}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { NotionSettingsData } from '../../shared/types';
|
|
3
|
+
|
|
4
|
+
export function NotionSettings() {
|
|
5
|
+
const [settings, setSettings] = useState<NotionSettingsData | null>(null);
|
|
6
|
+
const [saving, setSaving] = useState(false);
|
|
7
|
+
const [testing, setTesting] = useState(false);
|
|
8
|
+
const [testResult, setTestResult] = useState<{ success: boolean; error?: string; name?: string; userId?: string } | null>(null);
|
|
9
|
+
const [status, setStatus] = useState<{ configured: boolean; connected: boolean; name?: string; error?: string } | null>(null);
|
|
10
|
+
const [statusLoading, setStatusLoading] = useState(false);
|
|
11
|
+
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
loadSettings();
|
|
14
|
+
refreshStatus();
|
|
15
|
+
}, []);
|
|
16
|
+
|
|
17
|
+
const loadSettings = async () => {
|
|
18
|
+
try {
|
|
19
|
+
const loaded = await window.electronAPI.getNotionSettings();
|
|
20
|
+
setSettings(loaded);
|
|
21
|
+
} catch (error) {
|
|
22
|
+
console.error('Failed to load Notion settings:', error);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const updateSettings = (updates: Partial<NotionSettingsData>) => {
|
|
27
|
+
if (!settings) return;
|
|
28
|
+
setSettings({ ...settings, ...updates });
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const handleSave = async () => {
|
|
32
|
+
if (!settings) return;
|
|
33
|
+
setSaving(true);
|
|
34
|
+
setTestResult(null);
|
|
35
|
+
try {
|
|
36
|
+
const payload: NotionSettingsData = { ...settings };
|
|
37
|
+
await window.electronAPI.saveNotionSettings(payload);
|
|
38
|
+
setSettings(payload);
|
|
39
|
+
await refreshStatus();
|
|
40
|
+
} catch (error) {
|
|
41
|
+
console.error('Failed to save Notion settings:', error);
|
|
42
|
+
} finally {
|
|
43
|
+
setSaving(false);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const refreshStatus = async () => {
|
|
48
|
+
try {
|
|
49
|
+
setStatusLoading(true);
|
|
50
|
+
const result = await window.electronAPI.getNotionStatus();
|
|
51
|
+
setStatus(result);
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.error('Failed to load Notion status:', error);
|
|
54
|
+
} finally {
|
|
55
|
+
setStatusLoading(false);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const handleTestConnection = async () => {
|
|
60
|
+
setTesting(true);
|
|
61
|
+
setTestResult(null);
|
|
62
|
+
try {
|
|
63
|
+
const result = await window.electronAPI.testNotionConnection();
|
|
64
|
+
setTestResult(result);
|
|
65
|
+
await refreshStatus();
|
|
66
|
+
} catch (error: any) {
|
|
67
|
+
setTestResult({ success: false, error: error.message || 'Failed to test connection' });
|
|
68
|
+
} finally {
|
|
69
|
+
setTesting(false);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
if (!settings) {
|
|
74
|
+
return <div className="settings-loading">Loading Notion settings...</div>;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const statusLabel = !status?.configured
|
|
78
|
+
? 'Missing Key'
|
|
79
|
+
: status.connected
|
|
80
|
+
? 'Connected'
|
|
81
|
+
: 'Configured';
|
|
82
|
+
|
|
83
|
+
const statusClass = !status?.configured
|
|
84
|
+
? 'missing'
|
|
85
|
+
: status.connected
|
|
86
|
+
? 'connected'
|
|
87
|
+
: 'configured';
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<div className="notion-settings">
|
|
91
|
+
<div className="settings-section">
|
|
92
|
+
<div className="settings-section-header">
|
|
93
|
+
<div className="settings-title-with-badge">
|
|
94
|
+
<h3>Connect Notion</h3>
|
|
95
|
+
{status && (
|
|
96
|
+
<span
|
|
97
|
+
className={`notion-status-badge ${statusClass}`}
|
|
98
|
+
title={!status.configured ? 'API key not configured' : status.connected ? 'Connected to Notion' : 'Configured'}
|
|
99
|
+
>
|
|
100
|
+
{statusLabel}
|
|
101
|
+
</span>
|
|
102
|
+
)}
|
|
103
|
+
{statusLoading && !status && (
|
|
104
|
+
<span className="notion-status-badge configured">Checking…</span>
|
|
105
|
+
)}
|
|
106
|
+
</div>
|
|
107
|
+
<button className="btn-secondary btn-sm" onClick={refreshStatus} disabled={statusLoading}>
|
|
108
|
+
{statusLoading ? 'Checking...' : 'Refresh Status'}
|
|
109
|
+
</button>
|
|
110
|
+
</div>
|
|
111
|
+
<p className="settings-description">
|
|
112
|
+
Connect the agent to Notion using an integration API key, then use the built-in `notion_action` tool to search,
|
|
113
|
+
read, and update pages or data sources.
|
|
114
|
+
</p>
|
|
115
|
+
{status?.error && (
|
|
116
|
+
<p className="settings-hint">Status check: {status.error}</p>
|
|
117
|
+
)}
|
|
118
|
+
<div className="settings-actions">
|
|
119
|
+
<button
|
|
120
|
+
className="btn-secondary btn-sm"
|
|
121
|
+
onClick={() => window.electronAPI.openExternal('https://notion.so/my-integrations')}
|
|
122
|
+
>
|
|
123
|
+
Open Integrations
|
|
124
|
+
</button>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
|
|
128
|
+
<div className="settings-section">
|
|
129
|
+
<div className="settings-field">
|
|
130
|
+
<label>Enable Integration</label>
|
|
131
|
+
<label className="settings-toggle">
|
|
132
|
+
<input
|
|
133
|
+
type="checkbox"
|
|
134
|
+
checked={settings.enabled}
|
|
135
|
+
onChange={(e) => updateSettings({ enabled: e.target.checked })}
|
|
136
|
+
/>
|
|
137
|
+
<span className="toggle-slider" />
|
|
138
|
+
</label>
|
|
139
|
+
</div>
|
|
140
|
+
|
|
141
|
+
<div className="settings-field">
|
|
142
|
+
<label>API Key</label>
|
|
143
|
+
<input
|
|
144
|
+
type="password"
|
|
145
|
+
className="settings-input"
|
|
146
|
+
placeholder="ntn_..."
|
|
147
|
+
value={settings.apiKey || ''}
|
|
148
|
+
onChange={(e) => updateSettings({ apiKey: e.target.value || undefined })}
|
|
149
|
+
/>
|
|
150
|
+
<p className="settings-hint">Create an integration and copy the key that starts with <code>ntn_</code> or <code>secret_</code>.</p>
|
|
151
|
+
</div>
|
|
152
|
+
|
|
153
|
+
<div className="settings-field">
|
|
154
|
+
<label>Notion Version</label>
|
|
155
|
+
<input
|
|
156
|
+
type="text"
|
|
157
|
+
className="settings-input"
|
|
158
|
+
placeholder="2025-09-03"
|
|
159
|
+
value={settings.notionVersion || ''}
|
|
160
|
+
onChange={(e) => updateSettings({ notionVersion: e.target.value || undefined })}
|
|
161
|
+
/>
|
|
162
|
+
<p className="settings-hint">Use the latest API version unless support requests a pin.</p>
|
|
163
|
+
</div>
|
|
164
|
+
|
|
165
|
+
<div className="settings-field">
|
|
166
|
+
<label>Timeout (ms)</label>
|
|
167
|
+
<input
|
|
168
|
+
type="number"
|
|
169
|
+
className="settings-input"
|
|
170
|
+
min={1000}
|
|
171
|
+
max={120000}
|
|
172
|
+
value={settings.timeoutMs ?? 20000}
|
|
173
|
+
onChange={(e) => updateSettings({ timeoutMs: Number(e.target.value) })}
|
|
174
|
+
/>
|
|
175
|
+
</div>
|
|
176
|
+
|
|
177
|
+
<div className="settings-actions">
|
|
178
|
+
<button className="btn-secondary btn-sm" onClick={handleTestConnection} disabled={testing}>
|
|
179
|
+
{testing ? 'Testing...' : 'Test Connection'}
|
|
180
|
+
</button>
|
|
181
|
+
<button className="btn-primary btn-sm" onClick={handleSave} disabled={saving}>
|
|
182
|
+
{saving ? 'Saving...' : 'Save Settings'}
|
|
183
|
+
</button>
|
|
184
|
+
</div>
|
|
185
|
+
|
|
186
|
+
{testResult && (
|
|
187
|
+
<div className={`test-result ${testResult.success ? 'success' : 'error'}`}>
|
|
188
|
+
{testResult.success ? (
|
|
189
|
+
<span>Connected{testResult.name ? ` as ${testResult.name}` : ''}</span>
|
|
190
|
+
) : (
|
|
191
|
+
<span>Connection failed: {testResult.error}</span>
|
|
192
|
+
)}
|
|
193
|
+
</div>
|
|
194
|
+
)}
|
|
195
|
+
</div>
|
|
196
|
+
|
|
197
|
+
<div className="settings-section">
|
|
198
|
+
<h4>Setup Tips</h4>
|
|
199
|
+
<ol className="settings-hint">
|
|
200
|
+
<li>Create an integration and copy its API key.</li>
|
|
201
|
+
<li>Share the target pages or databases with the integration.</li>
|
|
202
|
+
<li>Save settings, then click “Test Connection”.</li>
|
|
203
|
+
</ol>
|
|
204
|
+
</div>
|
|
205
|
+
|
|
206
|
+
<div className="settings-section">
|
|
207
|
+
<h4>Quick Usage</h4>
|
|
208
|
+
<pre className="settings-info-box">{`// Search for pages or data sources
|
|
209
|
+
notion_action({
|
|
210
|
+
action: "search",
|
|
211
|
+
query: "Roadmap"
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
// Read a page
|
|
215
|
+
notion_action({
|
|
216
|
+
action: "get_page",
|
|
217
|
+
page_id: "YOUR_PAGE_ID"
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// Create a page in a database
|
|
221
|
+
notion_action({
|
|
222
|
+
action: "create_page",
|
|
223
|
+
database_id: "YOUR_DATABASE_ID",
|
|
224
|
+
properties: {
|
|
225
|
+
Name: { title: [{ text: { content: "New item" } }] }
|
|
226
|
+
}
|
|
227
|
+
});`}</pre>
|
|
228
|
+
</div>
|
|
229
|
+
</div>
|
|
230
|
+
);
|
|
231
|
+
}
|