@marktoflow/gui 2.0.0-alpha.1
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/.turbo/turbo-build.log +26 -0
- package/.turbo/turbo-test.log +22 -0
- package/README.md +179 -0
- package/dist/client/assets/index-DwTI8opO.js +608 -0
- package/dist/client/assets/index-DwTI8opO.js.map +1 -0
- package/dist/client/assets/index-RoEdL6gO.css +1 -0
- package/dist/client/index.html +20 -0
- package/dist/client/vite.svg +9 -0
- package/dist/server/index.d.ts +3 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +56 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/routes/ai.js +50 -0
- package/dist/server/routes/ai.js.map +1 -0
- package/dist/server/routes/execute.js +62 -0
- package/dist/server/routes/execute.js.map +1 -0
- package/dist/server/routes/workflows.js +99 -0
- package/dist/server/routes/workflows.js.map +1 -0
- package/dist/server/server/index.js +95 -0
- package/dist/server/server/index.js.map +1 -0
- package/dist/server/server/routes/ai.js +87 -0
- package/dist/server/server/routes/ai.js.map +1 -0
- package/dist/server/server/routes/execute.js +63 -0
- package/dist/server/server/routes/execute.js.map +1 -0
- package/dist/server/server/routes/tools.js +518 -0
- package/dist/server/server/routes/tools.js.map +1 -0
- package/dist/server/server/routes/workflows.js +99 -0
- package/dist/server/server/routes/workflows.js.map +1 -0
- package/dist/server/server/services/AIService.js +69 -0
- package/dist/server/server/services/AIService.js.map +1 -0
- package/dist/server/server/services/FileWatcher.js +60 -0
- package/dist/server/server/services/FileWatcher.js.map +1 -0
- package/dist/server/server/services/WorkflowService.js +363 -0
- package/dist/server/server/services/WorkflowService.js.map +1 -0
- package/dist/server/server/services/agents/claude-code-provider.js +250 -0
- package/dist/server/server/services/agents/claude-code-provider.js.map +1 -0
- package/dist/server/server/services/agents/claude-provider.js +204 -0
- package/dist/server/server/services/agents/claude-provider.js.map +1 -0
- package/dist/server/server/services/agents/copilot-provider.js +227 -0
- package/dist/server/server/services/agents/copilot-provider.js.map +1 -0
- package/dist/server/server/services/agents/demo-provider.js +167 -0
- package/dist/server/server/services/agents/demo-provider.js.map +1 -0
- package/dist/server/server/services/agents/index.js +31 -0
- package/dist/server/server/services/agents/index.js.map +1 -0
- package/dist/server/server/services/agents/ollama-provider.js +220 -0
- package/dist/server/server/services/agents/ollama-provider.js.map +1 -0
- package/dist/server/server/services/agents/prompts.js +436 -0
- package/dist/server/server/services/agents/prompts.js.map +1 -0
- package/dist/server/server/services/agents/registry.js +242 -0
- package/dist/server/server/services/agents/registry.js.map +1 -0
- package/dist/server/server/services/agents/types.js +6 -0
- package/dist/server/server/services/agents/types.js.map +1 -0
- package/dist/server/server/websocket/index.js +85 -0
- package/dist/server/server/websocket/index.js.map +1 -0
- package/dist/server/services/AIService.d.ts +30 -0
- package/dist/server/services/AIService.d.ts.map +1 -0
- package/dist/server/services/AIService.js +216 -0
- package/dist/server/services/AIService.js.map +1 -0
- package/dist/server/services/FileWatcher.d.ts +10 -0
- package/dist/server/services/FileWatcher.d.ts.map +1 -0
- package/dist/server/services/FileWatcher.js +62 -0
- package/dist/server/services/FileWatcher.js.map +1 -0
- package/dist/server/services/WorkflowService.d.ts +54 -0
- package/dist/server/services/WorkflowService.d.ts.map +1 -0
- package/dist/server/services/WorkflowService.js +323 -0
- package/dist/server/services/WorkflowService.js.map +1 -0
- package/dist/server/shared/constants.js +175 -0
- package/dist/server/shared/constants.js.map +1 -0
- package/dist/server/shared/types.js +3 -0
- package/dist/server/shared/types.js.map +1 -0
- package/dist/server/websocket/index.d.ts +10 -0
- package/dist/server/websocket/index.d.ts.map +1 -0
- package/dist/server/websocket/index.js +85 -0
- package/dist/server/websocket/index.js.map +1 -0
- package/index.html +19 -0
- package/package.json +96 -0
- package/playwright.config.ts +27 -0
- package/postcss.config.js +6 -0
- package/public/vite.svg +9 -0
- package/src/client/App.tsx +520 -0
- package/src/client/components/Canvas/Canvas.tsx +405 -0
- package/src/client/components/Canvas/ExecutionOverlay.tsx +847 -0
- package/src/client/components/Canvas/NodeContextMenu.tsx +188 -0
- package/src/client/components/Canvas/OutputNode.tsx +111 -0
- package/src/client/components/Canvas/StepNode.tsx +106 -0
- package/src/client/components/Canvas/SubWorkflowNode.tsx +141 -0
- package/src/client/components/Canvas/Toolbar.tsx +189 -0
- package/src/client/components/Canvas/TriggerNode.tsx +128 -0
- package/src/client/components/Editor/InputsEditor.tsx +458 -0
- package/src/client/components/Editor/NewStepWizard.tsx +344 -0
- package/src/client/components/Editor/StepEditor.tsx +532 -0
- package/src/client/components/Editor/YamlEditor.tsx +160 -0
- package/src/client/components/Panels/PropertiesPanel.tsx +589 -0
- package/src/client/components/Prompt/ChangePreview.tsx +281 -0
- package/src/client/components/Prompt/PromptHistoryPanel.tsx +209 -0
- package/src/client/components/Prompt/PromptInput.tsx +108 -0
- package/src/client/components/Sidebar/Sidebar.tsx +343 -0
- package/src/client/components/common/Breadcrumb.tsx +40 -0
- package/src/client/components/common/Button.tsx +68 -0
- package/src/client/components/common/ContextMenu.tsx +202 -0
- package/src/client/components/common/KeyboardShortcuts.tsx +143 -0
- package/src/client/components/common/Modal.tsx +93 -0
- package/src/client/components/common/Tabs.tsx +57 -0
- package/src/client/components/common/ThemeToggle.tsx +63 -0
- package/src/client/components/index.ts +32 -0
- package/src/client/hooks/index.ts +4 -0
- package/src/client/hooks/useAIPrompt.ts +108 -0
- package/src/client/hooks/useCanvas.ts +247 -0
- package/src/client/hooks/useWebSocket.ts +164 -0
- package/src/client/hooks/useWorkflow.ts +138 -0
- package/src/client/main.tsx +10 -0
- package/src/client/stores/canvasStore.ts +348 -0
- package/src/client/stores/editorStore.ts +133 -0
- package/src/client/stores/executionStore.ts +440 -0
- package/src/client/stores/index.ts +4 -0
- package/src/client/stores/layoutStore.ts +103 -0
- package/src/client/stores/navigationStore.ts +49 -0
- package/src/client/stores/promptStore.ts +113 -0
- package/src/client/stores/themeStore.ts +75 -0
- package/src/client/stores/workflowStore.ts +177 -0
- package/src/client/styles/globals.css +346 -0
- package/src/client/utils/cn.ts +9 -0
- package/src/client/utils/index.ts +4 -0
- package/src/client/utils/serviceIcons.tsx +64 -0
- package/src/client/utils/stepValidation.ts +155 -0
- package/src/client/utils/workflowToGraph.ts +299 -0
- package/src/server/index.ts +114 -0
- package/src/server/routes/ai.ts +91 -0
- package/src/server/routes/execute.ts +71 -0
- package/src/server/routes/tools.ts +564 -0
- package/src/server/routes/workflows.ts +106 -0
- package/src/server/services/AIService.ts +105 -0
- package/src/server/services/FileWatcher.ts +69 -0
- package/src/server/services/WorkflowService.ts +441 -0
- package/src/server/services/agents/claude-code-provider.ts +320 -0
- package/src/server/services/agents/claude-provider.ts +248 -0
- package/src/server/services/agents/copilot-provider.ts +311 -0
- package/src/server/services/agents/demo-provider.ts +184 -0
- package/src/server/services/agents/index.ts +31 -0
- package/src/server/services/agents/ollama-provider.ts +267 -0
- package/src/server/services/agents/prompts.ts +482 -0
- package/src/server/services/agents/registry.ts +289 -0
- package/src/server/services/agents/types.ts +146 -0
- package/src/server/websocket/index.ts +104 -0
- package/src/shared/constants.ts +180 -0
- package/src/shared/types.ts +179 -0
- package/tailwind.config.ts +73 -0
- package/tests/e2e/app.spec.ts +90 -0
- package/tests/e2e/canvas.spec.ts +128 -0
- package/tests/e2e/workflow.spec.ts +185 -0
- package/tests/integration/api.test.ts +250 -0
- package/tests/integration/testApp.ts +31 -0
- package/tests/setup.ts +37 -0
- package/tests/unit/canvasStore.test.ts +502 -0
- package/tests/unit/components.test.tsx +151 -0
- package/tests/unit/executionStore.test.ts +527 -0
- package/tests/unit/layoutStore.test.ts +194 -0
- package/tests/unit/navigationStore.test.ts +152 -0
- package/tests/unit/stepValidation.test.ts +226 -0
- package/tests/unit/themeStore.test.ts +141 -0
- package/tests/unit/workflowToGraph.test.ts +289 -0
- package/tsconfig.json +29 -0
- package/tsconfig.server.json +28 -0
- package/vite.config.ts +31 -0
- package/vitest.config.ts +26 -0
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub Copilot AI agent provider
|
|
3
|
+
* Uses the @github/copilot-sdk for AI capabilities
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { createRequire } from 'node:module';
|
|
7
|
+
import { parse as yamlParse } from 'yaml';
|
|
8
|
+
import type {
|
|
9
|
+
AgentProvider,
|
|
10
|
+
AgentCapabilities,
|
|
11
|
+
AgentConfig,
|
|
12
|
+
PromptResult,
|
|
13
|
+
Workflow,
|
|
14
|
+
} from './types.js';
|
|
15
|
+
import { buildPrompt, generateSuggestions } from './prompts.js';
|
|
16
|
+
|
|
17
|
+
// Polyfill require for ESM environments (needed by Copilot SDK dependencies)
|
|
18
|
+
if (typeof globalThis.require === 'undefined') {
|
|
19
|
+
(globalThis as unknown as { require: NodeRequire }).require = createRequire(import.meta.url);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Dynamic import types for Copilot SDK
|
|
23
|
+
// Using loose types to handle SDK version differences
|
|
24
|
+
interface CopilotClientConfig {
|
|
25
|
+
cliPath?: string;
|
|
26
|
+
cliUrl?: string;
|
|
27
|
+
autoStart?: boolean;
|
|
28
|
+
logLevel?: 'error' | 'info' | 'none' | 'warning' | 'debug' | 'all';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface CopilotSessionOptions {
|
|
32
|
+
model?: string;
|
|
33
|
+
streaming?: boolean;
|
|
34
|
+
systemMessage?: { content: string };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface CopilotSessionResponse {
|
|
38
|
+
data?: {
|
|
39
|
+
content?: string;
|
|
40
|
+
messageId?: string;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface SessionEvent {
|
|
45
|
+
type: string;
|
|
46
|
+
data: {
|
|
47
|
+
deltaContent?: string;
|
|
48
|
+
content?: string;
|
|
49
|
+
message?: string;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export class CopilotProvider implements AgentProvider {
|
|
54
|
+
readonly id = 'copilot';
|
|
55
|
+
readonly name = 'GitHub Copilot';
|
|
56
|
+
readonly capabilities: AgentCapabilities = {
|
|
57
|
+
streaming: true,
|
|
58
|
+
toolUse: true,
|
|
59
|
+
codeExecution: true,
|
|
60
|
+
systemPrompts: true,
|
|
61
|
+
models: [
|
|
62
|
+
'gpt-4.1',
|
|
63
|
+
'gpt-4o',
|
|
64
|
+
'gpt-4-turbo',
|
|
65
|
+
'claude-3.5-sonnet',
|
|
66
|
+
],
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// Using 'unknown' to handle SDK version differences
|
|
70
|
+
private client: unknown = null;
|
|
71
|
+
private model: string = 'gpt-4.1';
|
|
72
|
+
private ready: boolean = false;
|
|
73
|
+
private error: string | undefined;
|
|
74
|
+
private cliPath?: string;
|
|
75
|
+
private cliUrl?: string;
|
|
76
|
+
|
|
77
|
+
async initialize(config: AgentConfig): Promise<void> {
|
|
78
|
+
try {
|
|
79
|
+
// Try to import the Copilot SDK (dynamic import with webpackIgnore to avoid bundling issues)
|
|
80
|
+
const sdkModule = await import(/* webpackIgnore: true */ '@github/copilot-sdk').catch(
|
|
81
|
+
() => null
|
|
82
|
+
);
|
|
83
|
+
if (!sdkModule || !sdkModule.CopilotClient) {
|
|
84
|
+
this.ready = false;
|
|
85
|
+
this.error = 'GitHub Copilot SDK not installed. Run: npm install @github/copilot-sdk';
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const { CopilotClient } = sdkModule;
|
|
89
|
+
|
|
90
|
+
this.cliPath = config.options?.cliPath as string;
|
|
91
|
+
this.cliUrl = config.baseUrl || (config.options?.cliUrl as string);
|
|
92
|
+
|
|
93
|
+
const clientConfig: CopilotClientConfig = {
|
|
94
|
+
autoStart: true,
|
|
95
|
+
logLevel: 'error',
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
if (this.cliUrl) {
|
|
99
|
+
clientConfig.cliUrl = this.cliUrl;
|
|
100
|
+
} else {
|
|
101
|
+
clientConfig.cliPath = this.cliPath || 'copilot';
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
this.client = new CopilotClient(clientConfig);
|
|
105
|
+
|
|
106
|
+
if (config.model) {
|
|
107
|
+
this.model = config.model;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Start the client and test connectivity
|
|
111
|
+
try {
|
|
112
|
+
if (this.client) {
|
|
113
|
+
// Some SDK versions have start(), some don't (auto-start)
|
|
114
|
+
const client = this.client as { start?: () => Promise<void>; ping?: () => Promise<unknown> };
|
|
115
|
+
if (typeof client.start === 'function') {
|
|
116
|
+
await client.start();
|
|
117
|
+
}
|
|
118
|
+
if (typeof client.ping === 'function') {
|
|
119
|
+
await client.ping();
|
|
120
|
+
}
|
|
121
|
+
this.ready = true;
|
|
122
|
+
this.error = undefined;
|
|
123
|
+
}
|
|
124
|
+
} catch (pingError) {
|
|
125
|
+
this.ready = false;
|
|
126
|
+
this.error = `Cannot connect to GitHub Copilot CLI: ${pingError instanceof Error ? pingError.message : 'Unknown error'}`;
|
|
127
|
+
}
|
|
128
|
+
} catch (err) {
|
|
129
|
+
this.ready = false;
|
|
130
|
+
this.error = err instanceof Error ? err.message : 'Unknown error initializing Copilot';
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
isReady(): boolean {
|
|
135
|
+
return this.ready;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
getStatus(): { ready: boolean; model?: string; error?: string } {
|
|
139
|
+
return {
|
|
140
|
+
ready: this.ready,
|
|
141
|
+
model: this.model,
|
|
142
|
+
error: this.error,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async processPrompt(
|
|
147
|
+
prompt: string,
|
|
148
|
+
workflow: Workflow,
|
|
149
|
+
context?: { selectedStepId?: string; recentHistory?: string[] }
|
|
150
|
+
): Promise<PromptResult> {
|
|
151
|
+
if (!this.client || !this.ready) {
|
|
152
|
+
return {
|
|
153
|
+
explanation: 'GitHub Copilot provider not available.',
|
|
154
|
+
error: this.error || 'Provider not initialized',
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
// Build context-aware prompts
|
|
160
|
+
const { systemPrompt, userPrompt } = buildPrompt(prompt, workflow, context);
|
|
161
|
+
|
|
162
|
+
const client = this.client as {
|
|
163
|
+
createSession: (opts: CopilotSessionOptions) => Promise<{
|
|
164
|
+
sendAndWait: (opts: { prompt: string }) => Promise<CopilotSessionResponse | null>;
|
|
165
|
+
destroy: () => Promise<void>;
|
|
166
|
+
}>;
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const session = await client.createSession({
|
|
170
|
+
model: this.model,
|
|
171
|
+
systemMessage: { content: systemPrompt },
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
const response = await session.sendAndWait({ prompt: userPrompt });
|
|
176
|
+
const responseText = response?.data?.content || '';
|
|
177
|
+
return this.parseAIResponse(responseText, workflow);
|
|
178
|
+
} finally {
|
|
179
|
+
await session.destroy();
|
|
180
|
+
}
|
|
181
|
+
} catch (err) {
|
|
182
|
+
return {
|
|
183
|
+
explanation: '',
|
|
184
|
+
error: err instanceof Error ? err.message : 'Unknown error',
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async getSuggestions(workflow: Workflow, selectedStepId?: string): Promise<string[]> {
|
|
190
|
+
return generateSuggestions(workflow, selectedStepId);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async streamPrompt(
|
|
194
|
+
prompt: string,
|
|
195
|
+
workflow: Workflow,
|
|
196
|
+
onChunk: (chunk: string) => void,
|
|
197
|
+
context?: { selectedStepId?: string; recentHistory?: string[] }
|
|
198
|
+
): Promise<PromptResult> {
|
|
199
|
+
if (!this.client || !this.ready) {
|
|
200
|
+
return this.processPrompt(prompt, workflow, context);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const { systemPrompt, userPrompt } = buildPrompt(prompt, workflow, context);
|
|
204
|
+
let fullResponse = '';
|
|
205
|
+
|
|
206
|
+
try {
|
|
207
|
+
const client = this.client as {
|
|
208
|
+
createSession: (opts: CopilotSessionOptions) => Promise<{
|
|
209
|
+
send: (opts: { prompt: string }) => Promise<void>;
|
|
210
|
+
on: (callback: (event: SessionEvent) => void) => void;
|
|
211
|
+
destroy: () => Promise<void>;
|
|
212
|
+
}>;
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
const session = await client.createSession({
|
|
216
|
+
model: this.model,
|
|
217
|
+
streaming: true,
|
|
218
|
+
systemMessage: { content: systemPrompt },
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
return new Promise((resolve, reject) => {
|
|
222
|
+
session.on((event: SessionEvent) => {
|
|
223
|
+
if (event.type === 'assistant.message_delta') {
|
|
224
|
+
const chunk = event.data.deltaContent || '';
|
|
225
|
+
fullResponse += chunk;
|
|
226
|
+
onChunk(chunk);
|
|
227
|
+
} else if (event.type === 'assistant.message') {
|
|
228
|
+
fullResponse = event.data.content || fullResponse;
|
|
229
|
+
} else if (event.type === 'session.idle') {
|
|
230
|
+
session
|
|
231
|
+
.destroy()
|
|
232
|
+
.then(() => resolve(this.parseAIResponse(fullResponse, workflow)))
|
|
233
|
+
.catch(reject);
|
|
234
|
+
} else if (event.type === 'session.error') {
|
|
235
|
+
reject(new Error(event.data.message || 'Session error'));
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
session.send({ prompt: userPrompt }).catch(reject);
|
|
240
|
+
});
|
|
241
|
+
} catch (err) {
|
|
242
|
+
return {
|
|
243
|
+
explanation: '',
|
|
244
|
+
error: err instanceof Error ? err.message : 'Unknown error',
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async cancel(): Promise<void> {
|
|
250
|
+
if (this.client) {
|
|
251
|
+
const client = this.client as { stop?: () => Promise<unknown> };
|
|
252
|
+
if (typeof client.stop === 'function') {
|
|
253
|
+
await client.stop();
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
private parseAIResponse(responseText: string, originalWorkflow: Workflow): PromptResult {
|
|
259
|
+
const yamlMatch = responseText.match(/```yaml\n([\s\S]*?)\n```/);
|
|
260
|
+
let modifiedWorkflow: Workflow | undefined;
|
|
261
|
+
let explanation = responseText;
|
|
262
|
+
|
|
263
|
+
if (yamlMatch) {
|
|
264
|
+
try {
|
|
265
|
+
const parsedYaml = yamlParse(yamlMatch[1]);
|
|
266
|
+
if (parsedYaml && (parsedYaml.steps || parsedYaml.metadata)) {
|
|
267
|
+
modifiedWorkflow = parsedYaml as Workflow;
|
|
268
|
+
const explanationMatch = responseText.match(/^([\s\S]*?)```yaml/);
|
|
269
|
+
if (explanationMatch) {
|
|
270
|
+
explanation = explanationMatch[1].trim();
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
} catch {
|
|
274
|
+
// Failed to parse YAML
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
let diff: string | undefined;
|
|
279
|
+
if (modifiedWorkflow) {
|
|
280
|
+
diff = this.generateDiff(originalWorkflow, modifiedWorkflow);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return { explanation, workflow: modifiedWorkflow, diff };
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
private generateDiff(original: Workflow, modified: Workflow): string {
|
|
287
|
+
const originalStepIds = new Set(original.steps?.map((s) => s.id) || []);
|
|
288
|
+
const modifiedStepIds = new Set(modified.steps?.map((s) => s.id) || []);
|
|
289
|
+
|
|
290
|
+
const added = modified.steps?.filter((s) => !originalStepIds.has(s.id)) || [];
|
|
291
|
+
const removed = original.steps?.filter((s) => !modifiedStepIds.has(s.id)) || [];
|
|
292
|
+
|
|
293
|
+
let diff = '';
|
|
294
|
+
if (added.length > 0) {
|
|
295
|
+
diff += `+ Added ${added.length} step(s): ${added.map((s) => s.name || s.id).join(', ')}\n`;
|
|
296
|
+
}
|
|
297
|
+
if (removed.length > 0) {
|
|
298
|
+
diff += `- Removed ${removed.length} step(s): ${removed.map((s) => s.name || s.id).join(', ')}\n`;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return diff || 'No structural changes detected';
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export function createCopilotProvider(config?: AgentConfig): CopilotProvider {
|
|
306
|
+
const provider = new CopilotProvider();
|
|
307
|
+
if (config) {
|
|
308
|
+
provider.initialize(config);
|
|
309
|
+
}
|
|
310
|
+
return provider;
|
|
311
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Demo AI agent provider
|
|
3
|
+
* Provides simulated responses for testing without an API key
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
AgentProvider,
|
|
8
|
+
AgentCapabilities,
|
|
9
|
+
AgentConfig,
|
|
10
|
+
PromptResult,
|
|
11
|
+
Workflow,
|
|
12
|
+
} from './types.js';
|
|
13
|
+
import { generateSuggestions, AVAILABLE_SERVICES } from './prompts.js';
|
|
14
|
+
|
|
15
|
+
export class DemoProvider implements AgentProvider {
|
|
16
|
+
readonly id = 'demo';
|
|
17
|
+
readonly name = 'Demo Mode (No API)';
|
|
18
|
+
readonly capabilities: AgentCapabilities = {
|
|
19
|
+
streaming: false,
|
|
20
|
+
toolUse: false,
|
|
21
|
+
codeExecution: false,
|
|
22
|
+
systemPrompts: false,
|
|
23
|
+
models: ['demo'],
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
private ready: boolean = true;
|
|
27
|
+
|
|
28
|
+
async initialize(_config: AgentConfig): Promise<void> {
|
|
29
|
+
this.ready = true;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
isReady(): boolean {
|
|
33
|
+
return this.ready;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
getStatus(): { ready: boolean; model?: string; error?: string } {
|
|
37
|
+
return { ready: true, model: 'demo' };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async processPrompt(prompt: string, workflow: Workflow): Promise<PromptResult> {
|
|
41
|
+
const promptLower = prompt.toLowerCase();
|
|
42
|
+
let explanation = '';
|
|
43
|
+
const modifiedWorkflow = { ...workflow, steps: [...(workflow.steps || [])] };
|
|
44
|
+
|
|
45
|
+
// Detect which service is being requested
|
|
46
|
+
const serviceMatch = Object.keys(AVAILABLE_SERVICES).find((s) =>
|
|
47
|
+
promptLower.includes(s.toLowerCase())
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
// Simulate different responses based on prompt patterns
|
|
51
|
+
if (promptLower.includes('add') && serviceMatch) {
|
|
52
|
+
const service = AVAILABLE_SERVICES[serviceMatch as keyof typeof AVAILABLE_SERVICES];
|
|
53
|
+
const action = service.commonActions[0];
|
|
54
|
+
const stepId = `${serviceMatch}-${Date.now().toString(36)}`;
|
|
55
|
+
|
|
56
|
+
const newStep: any = {
|
|
57
|
+
id: stepId,
|
|
58
|
+
name: `${serviceMatch.charAt(0).toUpperCase() + serviceMatch.slice(1)} Action`,
|
|
59
|
+
action: `${serviceMatch}.${action}`,
|
|
60
|
+
inputs: this.generateDefaultInputs(serviceMatch, action),
|
|
61
|
+
outputVariable: `${serviceMatch}_result`,
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
modifiedWorkflow.steps.push(newStep);
|
|
65
|
+
explanation = `Added a ${serviceMatch} step using ${serviceMatch}.${action}. The step is configured with default inputs that you should customize.`;
|
|
66
|
+
} else if (promptLower.includes('error') || promptLower.includes('retry') || promptLower.includes('handling')) {
|
|
67
|
+
const maxRetries = promptLower.match(/(\d+)\s*(retry|retries|times)/)?.[1] || '3';
|
|
68
|
+
modifiedWorkflow.steps = modifiedWorkflow.steps.map((step) => ({
|
|
69
|
+
...step,
|
|
70
|
+
errorHandling: {
|
|
71
|
+
action: 'retry' as const,
|
|
72
|
+
maxRetries: parseInt(maxRetries),
|
|
73
|
+
retryDelay: 1000,
|
|
74
|
+
},
|
|
75
|
+
}));
|
|
76
|
+
explanation = `Added error handling with ${maxRetries} retries to all ${modifiedWorkflow.steps.length} steps.`;
|
|
77
|
+
} else if (promptLower.includes('condition') || promptLower.includes('if') || promptLower.includes('when')) {
|
|
78
|
+
if (modifiedWorkflow.steps.length > 0) {
|
|
79
|
+
const condition = this.extractCondition(promptLower);
|
|
80
|
+
modifiedWorkflow.steps[modifiedWorkflow.steps.length - 1] = {
|
|
81
|
+
...modifiedWorkflow.steps[modifiedWorkflow.steps.length - 1],
|
|
82
|
+
conditions: [condition],
|
|
83
|
+
};
|
|
84
|
+
explanation = `Added condition "${condition}" to the last step.`;
|
|
85
|
+
} else {
|
|
86
|
+
explanation = 'No steps to add conditions to. Please add some steps first.';
|
|
87
|
+
}
|
|
88
|
+
} else if (promptLower.includes('remove') || promptLower.includes('delete')) {
|
|
89
|
+
if (modifiedWorkflow.steps.length > 0) {
|
|
90
|
+
// Try to find a specific step to remove
|
|
91
|
+
const stepNameMatch = promptLower.match(/(?:remove|delete)\s+(?:the\s+)?["']?([^"']+)["']?\s+step/);
|
|
92
|
+
if (stepNameMatch) {
|
|
93
|
+
const targetName = stepNameMatch[1].toLowerCase();
|
|
94
|
+
const index = modifiedWorkflow.steps.findIndex(
|
|
95
|
+
(s) =>
|
|
96
|
+
s.id.toLowerCase().includes(targetName) ||
|
|
97
|
+
(s.name && s.name.toLowerCase().includes(targetName))
|
|
98
|
+
);
|
|
99
|
+
if (index >= 0) {
|
|
100
|
+
const removed = modifiedWorkflow.steps.splice(index, 1)[0];
|
|
101
|
+
explanation = `Removed step "${removed.name || removed.id}".`;
|
|
102
|
+
} else {
|
|
103
|
+
const removed = modifiedWorkflow.steps.pop();
|
|
104
|
+
explanation = `Could not find a step matching "${targetName}". Removed the last step "${removed?.name || removed?.id}" instead.`;
|
|
105
|
+
}
|
|
106
|
+
} else {
|
|
107
|
+
const removed = modifiedWorkflow.steps.pop();
|
|
108
|
+
explanation = `Removed the last step "${removed?.name || removed?.id}".`;
|
|
109
|
+
}
|
|
110
|
+
} else {
|
|
111
|
+
explanation = 'No steps to remove.';
|
|
112
|
+
}
|
|
113
|
+
} else if (promptLower.includes('notification') || promptLower.includes('notify')) {
|
|
114
|
+
modifiedWorkflow.steps.push({
|
|
115
|
+
id: `notify-${Date.now().toString(36)}`,
|
|
116
|
+
name: 'Send Notification',
|
|
117
|
+
action: 'slack.chat.postMessage',
|
|
118
|
+
inputs: {
|
|
119
|
+
channel: '#notifications',
|
|
120
|
+
text: 'Workflow "{{ workflow.name }}" completed successfully!',
|
|
121
|
+
},
|
|
122
|
+
outputVariable: 'notification_result',
|
|
123
|
+
});
|
|
124
|
+
explanation = 'Added a Slack notification step at the end of the workflow.';
|
|
125
|
+
} else if (promptLower.includes('http') || promptLower.includes('api') || promptLower.includes('request')) {
|
|
126
|
+
const method = promptLower.includes('post') ? 'POST' : promptLower.includes('put') ? 'PUT' : 'GET';
|
|
127
|
+
modifiedWorkflow.steps.push({
|
|
128
|
+
id: `http-${Date.now().toString(36)}`,
|
|
129
|
+
name: `HTTP ${method} Request`,
|
|
130
|
+
action: 'http.request',
|
|
131
|
+
inputs: {
|
|
132
|
+
method,
|
|
133
|
+
url: '{{ inputs.api_url }}',
|
|
134
|
+
headers: { 'Content-Type': 'application/json' },
|
|
135
|
+
},
|
|
136
|
+
outputVariable: 'api_response',
|
|
137
|
+
});
|
|
138
|
+
explanation = `Added an HTTP ${method} request step. Configure the URL and any required headers.`;
|
|
139
|
+
} else {
|
|
140
|
+
explanation = `Demo mode: I understood "${prompt}". In production mode with Claude or Ollama, this would intelligently analyze your request and modify the workflow accordingly. For now, try commands like:\n- "Add a Slack notification"\n- "Add error handling with 5 retries"\n- "Add a condition to run only on success"\n- "Remove the last step"`;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
explanation,
|
|
145
|
+
workflow: modifiedWorkflow,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async getSuggestions(workflow: Workflow, selectedStepId?: string): Promise<string[]> {
|
|
150
|
+
// Use the shared suggestions generator
|
|
151
|
+
return generateSuggestions(workflow, selectedStepId);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private generateDefaultInputs(service: string, _action: string): Record<string, unknown> {
|
|
155
|
+
const defaults: Record<string, Record<string, unknown>> = {
|
|
156
|
+
slack: { channel: '#general', text: 'Hello from Marktoflow!' },
|
|
157
|
+
github: { owner: '{{ inputs.owner }}', repo: '{{ inputs.repo }}' },
|
|
158
|
+
jira: { projectKey: '{{ inputs.project }}', summary: 'New Issue' },
|
|
159
|
+
gmail: { to: '{{ inputs.email }}', subject: 'Notification', body: 'Hello!' },
|
|
160
|
+
http: { method: 'GET', url: '{{ inputs.url }}' },
|
|
161
|
+
linear: { title: 'New Issue', teamId: '{{ inputs.team_id }}' },
|
|
162
|
+
notion: { parent: { database_id: '{{ inputs.database_id }}' } },
|
|
163
|
+
};
|
|
164
|
+
return defaults[service] || {};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
private extractCondition(prompt: string): string {
|
|
168
|
+
// Try to extract a meaningful condition from the prompt
|
|
169
|
+
if (prompt.includes('success')) {
|
|
170
|
+
return '{{ previous_step.success === true }}';
|
|
171
|
+
}
|
|
172
|
+
if (prompt.includes('fail')) {
|
|
173
|
+
return '{{ previous_step.success === false }}';
|
|
174
|
+
}
|
|
175
|
+
if (prompt.includes('production')) {
|
|
176
|
+
return '{{ inputs.environment === "production" }}';
|
|
177
|
+
}
|
|
178
|
+
return '{{ previous_step.success === true }}';
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function createDemoProvider(): DemoProvider {
|
|
183
|
+
return new DemoProvider();
|
|
184
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Providers Module
|
|
3
|
+
*
|
|
4
|
+
* This module provides a unified interface for AI agent providers,
|
|
5
|
+
* allowing the GUI to work with different backends:
|
|
6
|
+
*
|
|
7
|
+
* - Claude (Anthropic) - Full-featured AI with streaming support
|
|
8
|
+
* - Ollama (Local) - Local LLM support via Ollama
|
|
9
|
+
* - Demo Mode - Simulated responses for testing
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* ```typescript
|
|
13
|
+
* import { getAgentRegistry } from './agents';
|
|
14
|
+
*
|
|
15
|
+
* const registry = getAgentRegistry();
|
|
16
|
+
* await registry.autoDetectProvider();
|
|
17
|
+
*
|
|
18
|
+
* const result = await registry.processPrompt(
|
|
19
|
+
* "Add a Slack notification step",
|
|
20
|
+
* currentWorkflow
|
|
21
|
+
* );
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
export * from './types.js';
|
|
26
|
+
export * from './registry.js';
|
|
27
|
+
export * from './prompts.js';
|
|
28
|
+
export { ClaudeProvider, createClaudeProvider } from './claude-provider.js';
|
|
29
|
+
export { CopilotProvider, createCopilotProvider } from './copilot-provider.js';
|
|
30
|
+
export { DemoProvider, createDemoProvider } from './demo-provider.js';
|
|
31
|
+
export { OllamaProvider, createOllamaProvider } from './ollama-provider.js';
|