Skip to content

Commit 6b61392

Browse files
committed
Add logic for flowtest ai creation for backend
1 parent bd15547 commit 6b61392

File tree

6 files changed

+129
-27
lines changed

6 files changed

+129
-27
lines changed

packages/flowtest-electron/src/ipc/collection.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -250,11 +250,11 @@ const registerRendererEventHandlers = (mainWindow, watcher) => {
250250
}
251251
});
252252

253-
ipcMain.handle('renderer:create-flowtest-ai', async (event, instruction, collectionId) => {
253+
ipcMain.handle('renderer:generate-nodes-ai', async (event, instruction, collectionId, model) => {
254254
try {
255255
const collection = collectionStore.getAll().find((c) => c.id === collectionId);
256256
if (collection) {
257-
return await flowTestAI.generate(collection.openapi_spec, instruction);
257+
return await flowTestAI.generate(collection.openapi_spec, instruction, model);
258258
} else {
259259
return Promise.reject(new Error('Collection not found'));
260260
}

packages/flowtest-electron/src/utils/flowtestai.js

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,7 @@ const dotenv = require('dotenv');
33
const { MemoryVectorStore } = require('langchain/vectorstores/memory');
44
const { OpenAIEmbeddings } = require('@langchain/openai');
55

6-
dotenv.config();
7-
8-
const openai = new OpenAI({
9-
apiKey: process.env.OPENAI_API_KEY,
10-
});
6+
//dotenv.config();
117

128
const SYSTEM_MESSAGE = `You are a helpful assistant. \
139
Respond to the following prompt by using function_call and then summarize actions. \
@@ -17,10 +13,14 @@ const SYSTEM_MESSAGE = `You are a helpful assistant. \
1713
const MAX_CALLS = 10;
1814

1915
class FlowtestAI {
20-
async generate(collection, user_instruction) {
21-
const available_functions = await this.get_available_functions(collection);
22-
const functions = await this.filter_functions(available_functions, user_instruction);
23-
return await this.process_user_instruction(functions, user_instruction);
16+
async generate(collection, user_instruction, model) {
17+
if (model.name === 'OPENAI') {
18+
const available_functions = await this.get_available_functions(collection);
19+
const functions = await this.filter_functions(available_functions, user_instruction, model.apiKey);
20+
return await this.process_user_instruction(functions, user_instruction, model.apiKey);
21+
} else {
22+
throw Error(`Model ${model.name} not supported`);
23+
}
2424
}
2525

2626
async get_available_functions(collection) {
@@ -69,7 +69,7 @@ class FlowtestAI {
6969
return functions;
7070
}
7171

72-
async filter_functions(functions, instruction) {
72+
async filter_functions(functions, instruction, apiKey) {
7373
const chunkSize = 32;
7474
const chunks = [];
7575

@@ -84,7 +84,7 @@ class FlowtestAI {
8484
documents,
8585
[],
8686
new OpenAIEmbeddings({
87-
openAIApiKey: process.env.OPENAI_API_KEY,
87+
openAIApiKey: apiKey,
8888
}),
8989
);
9090

@@ -98,7 +98,11 @@ class FlowtestAI {
9898
return selectedFunctions;
9999
}
100100

101-
async get_openai_response(functions, messages) {
101+
async get_openai_response(functions, messages, apiKey) {
102+
const openai = new OpenAI({
103+
apiKey,
104+
});
105+
102106
return await openai.chat.completions.create({
103107
model: 'gpt-3.5-turbo-16k-0613',
104108
functions: functions,
@@ -108,7 +112,7 @@ class FlowtestAI {
108112
});
109113
}
110114

111-
async process_user_instruction(functions, instruction) {
115+
async process_user_instruction(functions, instruction, apiKey) {
112116
let result = [];
113117
let num_calls = 0;
114118
const messages = [
@@ -117,7 +121,7 @@ class FlowtestAI {
117121
];
118122

119123
while (num_calls < MAX_CALLS) {
120-
const response = await this.get_openai_response(functions, messages);
124+
const response = await this.get_openai_response(functions, messages, apiKey);
121125
// console.log(response)
122126
const message = response['choices'][0]['message'];
123127

packages/flowtest-electron/tests/utils/flowtest-ai.test.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ describe('generate', () => {
1515
console.log('API name: %s, Version: %s', api.info.title, api.info.version);
1616
const resolvedSpec = (await JsonRefs.resolveRefs(api)).resolved;
1717

18-
let result = await f.generate(resolvedSpec, USER_INSTRUCTION);
18+
let result = await f.generate(resolvedSpec, USER_INSTRUCTION, {
19+
name: 'OPENAI',
20+
apiKey: '',
21+
});
1922
const nodeNames = result.map((node) => node.name);
2023
expect(nodeNames).toEqual(['addPet', 'getPetById', 'findPetsByStatus']);
2124
}, 60000);
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { GENAI_MODELS } from 'constants/Common';
2+
import useCollectionStore from 'stores/CollectionStore';
3+
4+
const translateGeneratedNodesToOpenApiNodes = (generatedNodes, openApiNodes) => {
5+
let outputNodes = [];
6+
generatedNodes.forEach((gnode, _) => {
7+
const node = openApiNodes.find((node) => node.operationId === gnode.name);
8+
if (node !== undefined) {
9+
const outputNode = { ...node };
10+
const node_arguments = JSON.parse(gnode.arguments);
11+
if (node_arguments.requestBody) {
12+
outputNode['requestBody'] = {};
13+
outputNode['requestBody']['type'] = 'raw-json';
14+
outputNode['requestBody']['body'] = JSON.stringify(node_arguments.requestBody);
15+
}
16+
if (node_arguments.parameters) {
17+
outputNode.variables = {};
18+
Object.entries(node_arguments.parameters).forEach(([paramName, paramValue], _) => {
19+
outputNode.variables[paramName] = {
20+
type: typeof paramValue,
21+
value: paramValue,
22+
};
23+
});
24+
}
25+
outputNodes.push({
26+
...outputNode,
27+
type: 'requestNode',
28+
});
29+
} else {
30+
throw Error(`Cannot find node: ${node.name} in openApi spec`);
31+
}
32+
});
33+
34+
return outputNodes;
35+
};
36+
37+
export const generateFlowData = async (instruction, modelName, collectionId) => {
38+
try {
39+
const { ipcRenderer } = window;
40+
41+
const collection = useCollectionStore.getState().collections.find((c) => c.id === collectionId);
42+
if (collection) {
43+
if (modelName === GENAI_MODELS.openai) {
44+
const apiKey = collection.dotEnvVariables['OPENAI_APIKEY'];
45+
if (apiKey) {
46+
const generatedNodes = await ipcRenderer.invoke('renderer:generate-nodes-ai', instruction, collectionId, {
47+
name: GENAI_MODELS.openai,
48+
apiKey,
49+
});
50+
const flowData = {
51+
nodes: translateGeneratedNodesToOpenApiNodes(generatedNodes, collection.nodes),
52+
};
53+
return flowData;
54+
} else {
55+
// prompt the user to add openai api key
56+
return Promise.reject(new Error(`OpenAI api key not added`));
57+
}
58+
} else {
59+
return Promise.reject(new Error(`model: ${modelName} not supported`));
60+
}
61+
} else {
62+
return Promise.reject(new Error('A flowtest with this path does not exist'));
63+
}
64+
} catch (error) {
65+
console.log(`Error generating flowData: ${error}`);
66+
// TODO: show error in UI
67+
return Promise.reject(error);
68+
}
69+
};

src/components/molecules/flow/index.js

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ import DelayNode from './nodes/DelayNode';
2323
import AuthNode from './nodes/AuthNode';
2424
import { useTabStore } from 'stores/TabStore';
2525
import FlowNode from 'components/atoms/flow/FlowNode';
26+
import { Popover } from '@headlessui/react';
27+
import { generateFlowData } from './flowtestai';
28+
import { GENAI_MODELS } from 'constants/Common';
2629

2730
const StartNode = () => (
2831
<FlowNode title='Start' handleLeft={false} handleRight={true} handleRightData={{ type: 'source' }}></FlowNode>
@@ -51,25 +54,25 @@ const init = (flowData) => {
5154
type: 'buttonedge',
5255
},
5356
];
54-
for (let i = 2; i <= flowData.nodes.length; i++) {
57+
for (let i = 0; i < flowData.nodes.length; i++) {
5558
nodes.push({
56-
id: `${i}`,
57-
type: flowData.nodes[i - 1].type,
58-
position: { x: 150 + i * 500, y: 50 },
59-
data: flowData.nodes[i - 1],
59+
id: `${i + 2}`,
60+
type: flowData.nodes[i].type,
61+
position: { x: 150 + (i + 1) * 500, y: 50 },
62+
data: flowData.nodes[i],
6063
});
6164
edges.push({
62-
id: `reactflow__edge-${i - 1}-${i}`,
63-
source: `${i - 1}`,
65+
id: `reactflow__edge-${i + 1}-${i + 2}`,
66+
source: `${i + 1}`,
6467
sourceHandle: null,
65-
target: `${i}`,
68+
target: `${i + 2}`,
6669
targetHandle: null,
6770
type: 'buttonedge',
6871
});
6972
}
7073
return {
71-
nodes: flowData.nodes,
72-
edges: flowData.edges,
74+
nodes,
75+
edges,
7376
};
7477
} else {
7578
return {
@@ -295,6 +298,25 @@ const Flow = ({ tabId, collectionId, flowData }) => {
295298
</ControlButton>
296299
</Controls>
297300
<Background variant='dots' gap={12} size={1} />
301+
<div className='absolute right-4 z-[2000] max-w-sm px-4 '>
302+
<button
303+
type='button'
304+
onClick={() => {
305+
generateFlowData('Add a pet then get all pets with status available', GENAI_MODELS.openai, collectionId)
306+
.then((flowData) => {
307+
const result = init(flowData);
308+
console.log(result);
309+
setNodes(result.nodes);
310+
setEdges(result.edges);
311+
})
312+
.catch((error) => {
313+
console.log(error);
314+
});
315+
}}
316+
>
317+
FlowTestAI
318+
</button>
319+
</div>
298320
<AddNodes collectionId={collectionId} />
299321
</ReactFlow>
300322
</div>

src/constants/Common.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,7 @@ export const OBJ_TYPES = {
2323
folder: 'folder',
2424
environment: 'environment',
2525
};
26+
27+
export const GENAI_MODELS = {
28+
openai: 'OPENAI',
29+
};

0 commit comments

Comments
 (0)