From 7a1078791c141e37ae8f176549172a03d380441e Mon Sep 17 00:00:00 2001 From: Laszlo Moczo Date: Wed, 7 May 2025 22:20:55 +0200 Subject: [PATCH 01/40] Hack --- package-lock.json | 51 +++---------- package.json | 1 + .../components/StepCodeEditor.tsx | 50 +++++++++---- .../StepConfigDrawer/components/StepMaker.tsx | 72 +++++++++++++++++++ .../StepConfigDrawer/hooks/useStepMakerAI.ts | 65 +++++++++++++++++ 5 files changed, 186 insertions(+), 53 deletions(-) create mode 100644 source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx create mode 100644 source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts diff --git a/package-lock.json b/package-lock.json index 6d7aa9edc..4aabfdeab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,6 +30,7 @@ "monaco-editor": "0.51.0", "monaco-yaml": "^5.2.3", "node-diff3": "^3.1.2", + "openai": "^4.97.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-hook-form": "^7.56.1", @@ -4570,7 +4571,6 @@ "version": "22.15.3", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.3.tgz", "integrity": "sha512-lX7HFZeHf4QG/J7tBZqrCAXwz9J5RD56Y6MpP0eJkka8p+K0RY/yBTW7CYFJ4VGCclxqOLKmiGP5juQc6MKgcw==", - "dev": true, "dependencies": { "undici-types": "~6.21.0" } @@ -4579,7 +4579,6 @@ "version": "2.6.12", "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz", "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", - "dev": true, "dependencies": { "@types/node": "*", "form-data": "^4.0.0" @@ -5463,7 +5462,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "dev": true, "dependencies": { "event-target-shim": "^5.0.0" }, @@ -5527,7 +5525,6 @@ "version": "4.6.0", "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", - "dev": true, "dependencies": { "humanize-ms": "^1.2.1" }, @@ -5984,8 +5981,7 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/available-typed-arrays": { "version": "1.0.7", @@ -6561,7 +6557,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" @@ -7172,7 +7167,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "dependencies": { "delayed-stream": "~1.0.0" }, @@ -8190,7 +8184,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, "engines": { "node": ">=0.4.0" } @@ -8418,7 +8411,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", @@ -8658,7 +8650,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, "engines": { "node": ">= 0.4" } @@ -8667,7 +8658,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "engines": { "node": ">= 0.4" } @@ -8709,7 +8699,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, "dependencies": { "es-errors": "^1.3.0" }, @@ -8721,7 +8710,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dev": true, "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", @@ -10017,7 +10005,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "dev": true, "engines": { "node": ">=6" } @@ -10617,7 +10604,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", - "dev": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -10631,14 +10617,12 @@ "node_modules/form-data-encoder": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", - "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==", - "dev": true + "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==" }, "node_modules/formdata-node": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", - "dev": true, "dependencies": { "node-domexception": "1.0.0", "web-streams-polyfill": "4.0.0-beta.3" @@ -10858,7 +10842,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", @@ -10899,7 +10882,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" @@ -11082,7 +11064,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -11222,7 +11203,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -11234,7 +11214,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, "dependencies": { "has-symbols": "^1.0.3" }, @@ -11644,7 +11623,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", - "dev": true, "dependencies": { "ms": "^2.0.0" } @@ -14743,7 +14721,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, "engines": { "node": ">= 0.4" } @@ -15412,7 +15389,6 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, "engines": { "node": ">= 0.6" } @@ -15421,7 +15397,6 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, "dependencies": { "mime-db": "1.52.0" }, @@ -15792,7 +15767,6 @@ "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", "deprecated": "Use your platform's native DOMException instead", - "dev": true, "funding": [ { "type": "github", @@ -16098,10 +16072,9 @@ } }, "node_modules/openai": { - "version": "4.96.2", - "resolved": "https://registry.npmjs.org/openai/-/openai-4.96.2.tgz", - "integrity": "sha512-R2XnxvMsizkROr7BV3uNp1q/3skwPZ7fmPjO1bXLnfB4Tu5xKxrT1EVwzjhxn0MZKBKAvOaGWS63jTMN6KrIXA==", - "dev": true, + "version": "4.97.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-4.97.0.tgz", + "integrity": "sha512-LRoiy0zvEf819ZUEJhgfV8PfsE8G5WpQi4AwA1uCV8SKvvtXQkoWUFkepD6plqyJQRghy2+AEPQ07FrJFKHZ9Q==", "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", @@ -16131,7 +16104,6 @@ "version": "18.19.87", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.87.tgz", "integrity": "sha512-OIAAu6ypnVZHmsHCeJ+7CCSub38QNBS9uceMQeg7K5Ur0Jr+wG9wEOEvvMbhp09pxD5czIUy/jND7s7Tb6Nw7A==", - "dev": true, "dependencies": { "undici-types": "~5.26.4" } @@ -16139,8 +16111,7 @@ "node_modules/openai/node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, "node_modules/optionator": { "version": "0.9.4", @@ -20340,8 +20311,7 @@ "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==" }, "node_modules/unfetch": { "version": "4.2.0", @@ -20820,7 +20790,6 @@ "version": "4.0.0-beta.3", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", - "dev": true, "engines": { "node": ">= 14" } @@ -21496,7 +21465,7 @@ "version": "8.18.1", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz", "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==", - "dev": true, + "devOptional": true, "engines": { "node": ">=10.0.0" }, @@ -21635,7 +21604,7 @@ "version": "3.24.3", "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.3.tgz", "integrity": "sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg==", - "dev": true, + "devOptional": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/package.json b/package.json index bb7740600..8b6ec1d7f 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,7 @@ "monaco-editor": "0.51.0", "monaco-yaml": "^5.2.3", "node-diff3": "^3.1.2", + "openai": "^4.97.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-hook-form": "^7.56.1", diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepCodeEditor.tsx b/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepCodeEditor.tsx index 01fe8568a..3382c35c2 100644 --- a/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepCodeEditor.tsx +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepCodeEditor.tsx @@ -1,10 +1,12 @@ -import { Box, Label } from '@bitrise/bitkit'; +import { Box, ButtonGroup, Label, ToggleButton } from '@bitrise/bitkit'; import { Editor } from '@monaco-editor/react'; import type { editor } from 'monaco-editor'; import { useCallback, useEffect, useState } from 'react'; import MonacoUtils from '@/core/utils/MonacoUtils'; +import StepMaker from './StepMaker'; + const EDITOR_OPTIONS = { fontSize: 13, fontFamily: 'mono', @@ -25,6 +27,7 @@ type Props = { const StepCodeEditor = ({ label, value, defaultValue, onChange }: Props) => { const [editorInstance, setEditor] = useState(); + const [state, setState] = useState<'script' | 'ai'>('script'); const updateEditorHeight = useCallback(() => { if (!editorInstance) { @@ -47,17 +50,40 @@ const StepCodeEditor = ({ label, value, defaultValue, onChange }: Props) => { }, [editorInstance, updateEditorHeight]); return ( - - {label && } - onChange(changedValue || null)} - beforeMount={MonacoUtils.configureEnvVarsCompletionProvider} - /> + + + {label && } + + setState('script')} + borderRightRadius={0} + /> + setState('ai')} + borderLeftRadius={0} + /> + + + + onChange(changedValue || null)} + beforeMount={MonacoUtils.configureEnvVarsCompletionProvider} + /> + + + + ); }; diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx b/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx new file mode 100644 index 000000000..f0935abb6 --- /dev/null +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx @@ -0,0 +1,72 @@ +/* eslint-disable react/no-array-index-key */ +import { Box, Button, Input } from '@bitrise/bitkit'; +import { useState } from 'react'; + +import { useSecrets } from '@/hooks/useSecrets'; + +import useStepMakerAI from '../hooks/useStepMakerAI'; +import { useStepDrawerContext } from '../StepConfigDrawer.context'; + +const StepMaker = () => { + const { workflowId } = useStepDrawerContext(); + + const [value, setValue] = useState(''); + + const { data } = useSecrets({ appSlug: '' }); + const token = data?.find(({ key }) => key === 'OPENAI_API_KEY')?.value || ''; + + const { isLoading, messages, sendMessage } = useStepMakerAI({ + bitriseYml: '', + selectedWorkflow: workflowId, + token, + }); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + sendMessage(value); + setValue(''); + }; + + if (!token) { + return 'No token provided'; + } + + return ( + <> + + {messages.map((message, index) => ( + + + {message.content} + + + ))} + + + setValue(e.currentTarget.value)} + size="md" + flex="1" + value={value} + /> + + + + ); +}; + +export default StepMaker; diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts b/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts new file mode 100644 index 000000000..26014f152 --- /dev/null +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts @@ -0,0 +1,65 @@ +import OpenAI from 'openai'; +import { useState } from 'react'; + +type Message = { + content: string; + sender: 'user' | 'ai'; + type: 'message' | 'plan' | 'content'; +}; + +type Props = { + bitriseYml: string; + selectedWorkflow: string; + token: string; +}; + +const useStepMakerAI = (props: Props) => { + const { bitriseYml, selectedWorkflow, token } = props; + const [isLoading, setIsLoading] = useState(false); + const [responseId, setResponseId] = useState(null); + + const [messages, setMessages] = useState([]); + + const client = new OpenAI({ + apiKey: token, + dangerouslyAllowBrowser: true, + }); + + const systemPrompt = ` +You are a DevOps engineer helping Bitrise CI/CD users with their workflows. You are given an existing (functioning) workflow and a new request to improve that workflow. +Your task is to understand the user's request and create a high-level plan to implement the requested changes. + +Technical considerations: +- DO NOT write any code or YAML, just a high-level plan. +- DO NOT assume the user uses any CI/CD tool other than Bitrise. + +Make sure to ask clarifying questions if the request is not clear. Think about various edge cases, not just the happy path. + +Selected workflow to edit: ${selectedWorkflow} + +bitrise.yml that implements the selected workflow: +\`\`\`yml +${bitriseYml} +\`\`\` +`; + + const sendMessage = async (input: string) => { + setIsLoading(true); + setMessages((prev) => [...prev, { content: input, sender: 'user', type: 'message' }]); + + const response = await client.responses.create({ + model: 'gpt-4o', + instructions: systemPrompt, + input, + previous_response_id: responseId, + }); + setIsLoading(false); + setResponseId(response.id); + + setMessages((prev) => [...prev, { content: response.output_text, sender: 'ai', type: 'message' }]); + }; + + return { isLoading, messages, sendMessage }; +}; + +export default useStepMakerAI; From 12b54409865bf38110e766b289bfb422efc8c3cd Mon Sep 17 00:00:00 2001 From: Tamas Papik Date: Thu, 8 May 2025 09:35:39 +0200 Subject: [PATCH 02/40] updated info --- .../unified-editor/StepConfigDrawer/components/StepMaker.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx b/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx index f0935abb6..985810d11 100644 --- a/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx @@ -28,7 +28,7 @@ const StepMaker = () => { }; if (!token) { - return 'No token provided'; + return 'No OPENAI_API_KEY token provided'; } return ( From 967f3283409a68840e15e45237ae383e000bc5b4 Mon Sep 17 00:00:00 2001 From: Tamas Papik Date: Thu, 8 May 2025 10:18:04 +0200 Subject: [PATCH 03/40] added process plan function call --- .../StepConfigDrawer/hooks/useStepMakerAI.ts | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts b/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts index 26014f152..779e916af 100644 --- a/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts @@ -26,7 +26,7 @@ const useStepMakerAI = (props: Props) => { }); const systemPrompt = ` -You are a DevOps engineer helping Bitrise CI/CD users with their workflows. You are given an existing (functioning) workflow and a new request to improve that workflow. +You are a DevOps engineer helping Bitrise CI/CD users with their bash script step. You are given an existing (functioning) workflow and editing a bash script step and a new request to improve that step. Your task is to understand the user's request and create a high-level plan to implement the requested changes. Technical considerations: @@ -52,10 +52,30 @@ ${bitriseYml} instructions: systemPrompt, input, previous_response_id: responseId, + tools: [ + { + name: 'process_plan', + strict: false, + parameters: { + type: 'object', + properties: { + plan: { + type: 'string', + description: 'The text of a plan.', + }, + }, + required: ['plan'], + additionalProperties: false, + }, + type: 'function', + description: + 'The provided plan gets processed and forwarded to help the customer proceed with their workflow/bash script setup.', + }, + ], }); setIsLoading(false); setResponseId(response.id); - + console.log('Response:', response); setMessages((prev) => [...prev, { content: response.output_text, sender: 'ai', type: 'message' }]); }; From 2ea5b5d7222201c5b8672710b53881bc7305d85d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliv=C3=A9r=20Falvai?= Date: Thu, 8 May 2025 10:25:59 +0200 Subject: [PATCH 04/40] System prompt states --- .../StepConfigDrawer/components/StepMaker.tsx | 4 +- .../StepConfigDrawer/hooks/useStepMakerAI.ts | 85 +++++++++++++++++-- spec/integration/test_bitrise.secrets.yml | 1 + 3 files changed, 81 insertions(+), 9 deletions(-) diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx b/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx index 985810d11..3588cf31c 100644 --- a/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx @@ -15,7 +15,7 @@ const StepMaker = () => { const { data } = useSecrets({ appSlug: '' }); const token = data?.find(({ key }) => key === 'OPENAI_API_KEY')?.value || ''; - const { isLoading, messages, sendMessage } = useStepMakerAI({ + const { isLoading, state, sendMessage } = useStepMakerAI({ bitriseYml: '', selectedWorkflow: workflowId, token, @@ -34,7 +34,7 @@ const StepMaker = () => { return ( <> - {messages.map((message, index) => ( + {state.messages.map((message, index) => ( ; +}; + +type CodeGeneration = { + kind: 'codeGeneration'; + messages: Message[]; + userQA: Map; +}; + +type WaitingForBuild = { + kind: 'waitingForBuild'; + buildSlug: string; + messages: Message[]; +}; + +type BuildLogEvaluation = { + kind: 'buildLogEvaluation'; + plan: string; + messages: Message[]; + buildLogSnippet: string; +}; + const useStepMakerAI = (props: Props) => { const { bitriseYml, selectedWorkflow, token } = props; const [isLoading, setIsLoading] = useState(false); const [responseId, setResponseId] = useState(null); - const [messages, setMessages] = useState([]); - const client = new OpenAI({ apiKey: token, dangerouslyAllowBrowser: true, }); - const systemPrompt = ` + const plannerPrompt = ` You are a DevOps engineer helping Bitrise CI/CD users with their bash script step. You are given an existing (functioning) workflow and editing a bash script step and a new request to improve that step. Your task is to understand the user's request and create a high-level plan to implement the requested changes. @@ -43,13 +74,29 @@ ${bitriseYml} \`\`\` `; + const coderSystemPrompt = ` +You are a DevOps engineer implementing Bitrise CI/CD workflows. You are given a high-level plan to implement a new feature in an existing workflow. Your task is to output the bitrise.yml file that implements the requested changes. Only output raw YML, no explanations or comments, no Markdown code blocks. +The selected workflow to improve: ${selectedWorkflow} + +This is the high-level plan you need to implement. It might contain unanswered questions. In this case, use your best judgment to fill in the gaps. +`; + + const [state, setState] = useState({ + kind: 'initial', + examplePrompts: [ + 'Add a step to send a Slack message when the build fails.', + 'Add a step to run unit tests before deploying to production.', + 'Add a step to send an email notification when the build succeeds.', + ], + messages: [], + }); + const sendMessage = async (input: string) => { setIsLoading(true); - setMessages((prev) => [...prev, { content: input, sender: 'user', type: 'message' }]); const response = await client.responses.create({ model: 'gpt-4o', - instructions: systemPrompt, + instructions: responseId ? coderSystemPrompt : plannerPrompt, input, previous_response_id: responseId, tools: [ @@ -76,10 +123,34 @@ ${bitriseYml} setIsLoading(false); setResponseId(response.id); console.log('Response:', response); - setMessages((prev) => [...prev, { content: response.output_text, sender: 'ai', type: 'message' }]); + + let nextState: StepMakerState; + switch (state.kind) { + case 'initial': + nextState = { + kind: 'planning', + messages: [...state.messages, { content: response.output_text, sender: 'ai', type: 'message' }], + userQA: new Map(), + }; + setState(nextState); + break; + case 'planning': + nextState = { + kind: 'planning', + userQA: new Map(), + messages: [...state.messages, { content: response.output_text, sender: 'ai', type: 'message' }], + }; + break; + case 'codeGeneration': + break; + case 'waitingForBuild': + break; + case 'buildLogEvaluation': + break; + } }; - return { isLoading, messages, sendMessage }; + return { isLoading, state, sendMessage }; }; export default useStepMakerAI; diff --git a/spec/integration/test_bitrise.secrets.yml b/spec/integration/test_bitrise.secrets.yml index 609276153..363f5503b 100644 --- a/spec/integration/test_bitrise.secrets.yml +++ b/spec/integration/test_bitrise.secrets.yml @@ -34,3 +34,4 @@ envs: is_expose: true is_protected: false test: elek + From eed60561f433317562694e766f4d841a2a07ce8f Mon Sep 17 00:00:00 2001 From: Laszlo Moczo Date: Thu, 8 May 2025 10:39:58 +0200 Subject: [PATCH 05/40] Hack --- .../components/StepCodeEditor.tsx | 22 ++------- .../StepConfigDrawer/components/StepMaker.tsx | 49 ++++++++++--------- .../StepConfigDrawer/hooks/useStepMakerAI.ts | 2 +- 3 files changed, 32 insertions(+), 41 deletions(-) diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepCodeEditor.tsx b/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepCodeEditor.tsx index 3382c35c2..60ad4eaec 100644 --- a/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepCodeEditor.tsx +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepCodeEditor.tsx @@ -1,4 +1,4 @@ -import { Box, ButtonGroup, Label, ToggleButton } from '@bitrise/bitkit'; +import { Box, FilterSwitch, FilterSwitchGroup, Label } from '@bitrise/bitkit'; import { Editor } from '@monaco-editor/react'; import type { editor } from 'monaco-editor'; import { useCallback, useEffect, useState } from 'react'; @@ -53,22 +53,10 @@ const StepCodeEditor = ({ label, value, defaultValue, onChange }: Props) => { {label && } - - setState('script')} - borderRightRadius={0} - /> - setState('ai')} - borderLeftRadius={0} - /> - + setState(v as 'script' | 'ai')} value={state} marginBlockStart="0"> + Script + AI input + { + const { message } = props; + return ( + + + + {message.content} + + + ); +}; + const StepMaker = () => { const { workflowId } = useStepDrawerContext(); @@ -27,30 +43,17 @@ const StepMaker = () => { setValue(''); }; - if (!token) { - return 'No OPENAI_API_KEY token provided'; - } - return ( <> - + {state.messages.map((message, index) => ( - - - {message.content} - - + ))} diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts b/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts index c1e24c9f6..46945433d 100644 --- a/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts @@ -1,7 +1,7 @@ import OpenAI from 'openai'; import { useState } from 'react'; -type Message = { +export type Message = { content: string; sender: 'user' | 'ai'; type: 'message' | 'plan' | 'content'; From 34d6a6266e27f549aab5c2e4688fc230d415810b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliv=C3=A9r=20Falvai?= Date: Thu, 8 May 2025 10:54:07 +0200 Subject: [PATCH 06/40] Fix missing user message in state --- .../unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts b/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts index 46945433d..4356fc6df 100644 --- a/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts @@ -93,10 +93,14 @@ This is the high-level plan you need to implement. It might contain unanswered q const sendMessage = async (input: string) => { setIsLoading(true); + setState((prev) => ({ + ...prev, + messages: [...prev.messages, { content: input, sender: 'user', type: 'message' }], + })); const response = await client.responses.create({ model: 'gpt-4o', - instructions: responseId ? coderSystemPrompt : plannerPrompt, + instructions: state.kind === 'planning' ? plannerPrompt : coderSystemPrompt, input, previous_response_id: responseId, tools: [ From 13831d7e8984da7e94cf27611f97f6f08191338b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliv=C3=A9r=20Falvai?= Date: Thu, 8 May 2025 11:13:20 +0200 Subject: [PATCH 07/40] Extract prompts --- .../StepConfigDrawer/hooks/prompts.ts | 90 +++++++++++++++++++ .../StepConfigDrawer/hooks/useStepMakerAI.ts | 36 ++------ 2 files changed, 95 insertions(+), 31 deletions(-) create mode 100644 source/javascripts/components/unified-editor/StepConfigDrawer/hooks/prompts.ts diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/prompts.ts b/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/prompts.ts new file mode 100644 index 000000000..e24836d30 --- /dev/null +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/prompts.ts @@ -0,0 +1,90 @@ +const bitriseInfo = ` +# Bitrise step constraints + +## Introduction + +Bitrise steps are the building blocks of CI/CD workflows. Steps are very similar to GitHub Actions' Actions. Any executable can be a step as long it has an entrypoint. + +Step parameters are called inputs. They are implemented as env vars, and a step's inputs are described in a metadata file called \`step.yml\`. Steps also have outputs (also env vars) that can be connected to (referenced in) other steps' inputs. + +## bitrise.yml + +A workflow is declared in a file called \`bitrise.yml\`. A workflow is essentially a sequence of step references with inputs. A step reference can be: + +1. A step ID and a version constraint. There is a central registry called the Step Library, which contains the mapping of step IDs to git repositories (where the step source code is). For example, \`git-clone@8\` is a step reference to the step with ID \`git-clone\`, selecting the latest \`8.x.x\` version. +2. A direct git repo reference. For example, \`git::github.com/user/repo\`. + +## Notable env vars, steps, step outputs + +There are some notable values that come in handy when implementing a new step. + +### Env vars + +The following env vars are exposed to the build environment and steps can read them: + +- \`$BITRISE_SOURCE_DIR\`: Local path of the working directory. Unless modified, this is where the repo is checked out. +- \`$BITRISE_DEPLOY_DIR\`: Local path of a special directory used for build artifacts. Steps can copy/write files to this dir if they want to create an artifact. +- \`$PR\`: \`true\` if the current build relates to a PR, \`false\` otherwise + +### The build environment + +You can find the following CLI tools pre-installed and ready for use: +- curl +- wget +- zip +- tar +- jq +- git +- coreutils (cat, chmod, cp, install, ln, etc.) +- node, npm, yarn, pnpm +- gh (note: some subcommands need authentication!) +- aws (version 2) +- gcloud +- pipx +- firebase + +Linux-only: +- docker + +macOS-only: +- brew +- xcodebuild +- xcodes +- swift +- tuist +- pod (Cocoapods) +`; + +export const plannerPrompt = (selectedWorkflow: string, bitriseYml: string) => ` +You are a DevOps engineer helping Bitrise CI/CD users with their bash script step. You are given an existing (functioning) workflow and editing a bash script step and a new request to improve that step. +Your task is to understand the user's request and create a high-level plan to implement the requested changes. + +Technical considerations: +- DO NOT write any code or YAML, just a high-level plan. +- DO NOT assume the user uses any CI/CD tool other than Bitrise. + +Make sure to ask clarifying questions if the request is not clear. Think about various edge cases, not just the happy path. + +Selected workflow to edit: ${selectedWorkflow} + +bitrise.yml that implements the selected workflow: +\`\`\`yml +${bitriseYml} +\`\`\` + +Below you will find some documentation about Bitrise workflows and implementation details you need to know: +${bitriseInfo} +`; + +export const coderSystemPrompt = (selectedWorkflow: string) => ` +You are a DevOps engineer implementing Bitrise CI/CD workflows. You are given a high-level plan to implement a new feature in an existing workflow. Your task is to output the bitrise.yml file that implements the requested changes. Only output raw YML, no explanations or comments, no Markdown code blocks. +The selected workflow to improve: ${selectedWorkflow} + +This is the high-level plan you need to implement. It might contain unanswered questions. In this case, use your best judgment to fill in the gaps. +`; + +export const examplePrompts = [ + 'Add a step to send a Slack message when the build fails.', + 'Add a step to run unit tests before deploying to production.', + 'Add a step to send an email notification when the build succeeds.', +]; diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts b/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts index 4356fc6df..a92738a70 100644 --- a/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts @@ -1,6 +1,8 @@ import OpenAI from 'openai'; import { useState } from 'react'; +import { coderSystemPrompt, examplePrompts, plannerPrompt } from './prompts'; + export type Message = { content: string; sender: 'user' | 'ai'; @@ -56,38 +58,9 @@ const useStepMakerAI = (props: Props) => { dangerouslyAllowBrowser: true, }); - const plannerPrompt = ` -You are a DevOps engineer helping Bitrise CI/CD users with their bash script step. You are given an existing (functioning) workflow and editing a bash script step and a new request to improve that step. -Your task is to understand the user's request and create a high-level plan to implement the requested changes. - -Technical considerations: -- DO NOT write any code or YAML, just a high-level plan. -- DO NOT assume the user uses any CI/CD tool other than Bitrise. - -Make sure to ask clarifying questions if the request is not clear. Think about various edge cases, not just the happy path. - -Selected workflow to edit: ${selectedWorkflow} - -bitrise.yml that implements the selected workflow: -\`\`\`yml -${bitriseYml} -\`\`\` -`; - - const coderSystemPrompt = ` -You are a DevOps engineer implementing Bitrise CI/CD workflows. You are given a high-level plan to implement a new feature in an existing workflow. Your task is to output the bitrise.yml file that implements the requested changes. Only output raw YML, no explanations or comments, no Markdown code blocks. -The selected workflow to improve: ${selectedWorkflow} - -This is the high-level plan you need to implement. It might contain unanswered questions. In this case, use your best judgment to fill in the gaps. -`; - const [state, setState] = useState({ kind: 'initial', - examplePrompts: [ - 'Add a step to send a Slack message when the build fails.', - 'Add a step to run unit tests before deploying to production.', - 'Add a step to send an email notification when the build succeeds.', - ], + examplePrompts, messages: [], }); @@ -100,7 +73,8 @@ This is the high-level plan you need to implement. It might contain unanswered q const response = await client.responses.create({ model: 'gpt-4o', - instructions: state.kind === 'planning' ? plannerPrompt : coderSystemPrompt, + instructions: + state.kind === 'planning' ? plannerPrompt(selectedWorkflow, bitriseYml) : coderSystemPrompt(selectedWorkflow), input, previous_response_id: responseId, tools: [ From c60fccec8daa1db752c7f68231af985391dc32b3 Mon Sep 17 00:00:00 2001 From: Tamas Papik Date: Thu, 8 May 2025 10:54:19 +0200 Subject: [PATCH 08/40] added script processing tool --- .../StepConfigDrawer/hooks/useStepMakerAI.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts b/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts index a92738a70..17f16f097 100644 --- a/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts @@ -96,6 +96,24 @@ const useStepMakerAI = (props: Props) => { description: 'The provided plan gets processed and forwarded to help the customer proceed with their workflow/bash script setup.', }, + { + name: 'store_bash_script', + strict: false, + parameters: { + type: 'object', + properties: { + script: { + type: 'string', + description: 'The text of the bash script.', + }, + }, + required: ['script'], + additionalProperties: false, + }, + type: 'function', + description: + 'The provided bash script gets stored in the selected Bitrise workflow script step. The script is a part of the workflow.', + }, ], }); setIsLoading(false); From 95b61c8aca34552e51efc01b5b3a44d52fd5bc02 Mon Sep 17 00:00:00 2001 From: Tamas Papik Date: Thu, 8 May 2025 11:13:29 +0200 Subject: [PATCH 09/40] added plan --- .../StepConfigDrawer/components/StepMaker.tsx | 4 +- .../StepConfigDrawer/hooks/useStepMakerAI.ts | 188 +++++++++++------- 2 files changed, 123 insertions(+), 69 deletions(-) diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx b/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx index e6d649f6e..da66deae3 100644 --- a/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx @@ -31,7 +31,7 @@ const StepMaker = () => { const { data } = useSecrets({ appSlug: '' }); const token = data?.find(({ key }) => key === 'OPENAI_API_KEY')?.value || ''; - const { isLoading, state, sendMessage } = useStepMakerAI({ + const { isLoading, messages, sendMessage } = useStepMakerAI({ bitriseYml: '', selectedWorkflow: workflowId, token, @@ -52,7 +52,7 @@ const StepMaker = () => { paddingBlockStart="8" marginBlockStart="16" > - {state.messages.map((message, index) => ( + {messages.map((message, index) => ( ))} diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts b/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts index 17f16f097..c7acb8386 100644 --- a/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts @@ -15,38 +15,38 @@ type Props = { token: string; }; -type StepMakerState = InitialState | Planning | CodeGeneration | WaitingForBuild | BuildLogEvaluation; - -type InitialState = { - kind: 'initial'; - messages: Message[]; - examplePrompts: string[]; -}; - -type Planning = { - kind: 'planning'; - messages: Message[]; - userQA: Map; -}; - -type CodeGeneration = { - kind: 'codeGeneration'; - messages: Message[]; - userQA: Map; -}; - -type WaitingForBuild = { - kind: 'waitingForBuild'; - buildSlug: string; - messages: Message[]; -}; - -type BuildLogEvaluation = { - kind: 'buildLogEvaluation'; - plan: string; - messages: Message[]; - buildLogSnippet: string; -}; +// type StepMakerState = InitialState | Planning | CodeGeneration | WaitingForBuild | BuildLogEvaluation; + +// type InitialState = { +// kind: 'initial'; +// messages: Message[]; +// examplePrompts: string[]; +// }; + +// type Planning = { +// kind: 'planning'; +// messages: Message[]; +// userQA: Map; +// }; + +// type CodeGeneration = { +// kind: 'codeGeneration'; +// messages: Message[]; +// userQA: Map; +// }; + +// type WaitingForBuild = { +// kind: 'waitingForBuild'; +// buildSlug: string; +// messages: Message[]; +// }; + +// type BuildLogEvaluation = { +// kind: 'buildLogEvaluation'; +// plan: string; +// messages: Message[]; +// buildLogSnippet: string; +// }; const useStepMakerAI = (props: Props) => { const { bitriseYml, selectedWorkflow, token } = props; @@ -58,23 +58,51 @@ const useStepMakerAI = (props: Props) => { dangerouslyAllowBrowser: true, }); - const [state, setState] = useState({ - kind: 'initial', - examplePrompts, - messages: [], - }); + const [messages, setMessages] = useState([]); + + const plannerPrompt = ` +You are a DevOps engineer helping Bitrise CI/CD users with their bash script step. You are given an existing (functioning) workflow and editing a bash script step and a new request to improve that step. +Your task is to understand the user's request and create a high-level plan to implement the requested changes. + +Technical considerations: +- DO NOT write any code or YAML, just a high-level plan. +- DO NOT assume the user uses any CI/CD tool other than Bitrise. + +Make sure to ask clarifying questions if the request is not clear. Think about various edge cases, not just the happy path. + +Selected workflow to edit: ${selectedWorkflow} + +bitrise.yml that implements the selected workflow: +\`\`\`yml +${bitriseYml} +\`\`\` +`; + + const coderSystemPrompt = ` +You are a DevOps engineer implementing Bitrise CI/CD workflows. You are given a high-level plan to implement a new feature in an existing workflow. Your task is to output the bitrise.yml file that implements the requested changes. Only output raw YML, no explanations or comments, no Markdown code blocks. +The selected workflow to improve: ${selectedWorkflow} + +This is the high-level plan you need to implement. It might contain unanswered questions. In this case, use your best judgment to fill in the gaps. +`; + + // const [state, setState] = useState({ + // kind: 'initial', + // examplePrompts: [ + // 'Add a step to send a Slack message when the build fails.', + // 'Add a step to run unit tests before deploying to production.', + // 'Add a step to send an email notification when the build succeeds.', + // ], + // messages: [], + // }); const sendMessage = async (input: string) => { setIsLoading(true); - setState((prev) => ({ - ...prev, - messages: [...prev.messages, { content: input, sender: 'user', type: 'message' }], - })); + console.log(coderSystemPrompt); + setMessages((prev) => [...prev, { content: input, sender: 'user', type: 'message' }]); const response = await client.responses.create({ model: 'gpt-4o', - instructions: - state.kind === 'planning' ? plannerPrompt(selectedWorkflow, bitriseYml) : coderSystemPrompt(selectedWorkflow), + instructions: plannerPrompt, input, previous_response_id: responseId, tools: [ @@ -118,35 +146,61 @@ const useStepMakerAI = (props: Props) => { }); setIsLoading(false); setResponseId(response.id); + console.log('Response:', response); - let nextState: StepMakerState; - switch (state.kind) { - case 'initial': - nextState = { - kind: 'planning', - messages: [...state.messages, { content: response.output_text, sender: 'ai', type: 'message' }], - userQA: new Map(), - }; - setState(nextState); - break; - case 'planning': - nextState = { - kind: 'planning', - userQA: new Map(), - messages: [...state.messages, { content: response.output_text, sender: 'ai', type: 'message' }], - }; - break; - case 'codeGeneration': - break; - case 'waitingForBuild': - break; - case 'buildLogEvaluation': - break; + // let type = 'message'; + // if(response.output[0].type === 'function_call' && + // response.output[0].name === 'process_plan' ) + // { + // type = 'plan'; + // } + // [ + // { + // "id": "fc_681c74a030b08191a5101aedcfebcef3050d103f60d8a0dc", + // "type": "function_call", + // "status": "completed", + // "arguments": "{\"plan\":\"1. Research Method to Fetch External IP:\\n - Use a reliable external service or command that returns the external IP address, such as `curl` with a service like `http://ipinfo.io/ip` or `http://ifconfig.me`.\\n\\n2. Error Handling:\\n - Ensure the command can handle potential errors gracefully. For example, handle situations where the external service is unreachable or returns an unexpected result.\\n\\n3. Security Considerations:\\n - Ensure that using an external service does not expose sensitive information about your environment.\\n\\n4. Integration with the Bitrise Workflow:\\n - Add the command to the existing bash script step within your Bitrise workflow.\\n - Use `echo` to print the result to the console.\\n\\n5. Testing:\\n - Test the script in a controlled environment to ensure it works as expected without throwing errors.\\n\\n6. Documentation:\\n - Optionally update any relevant documentation to ensure the process is clear for future reference.\"}", + // "call_id": "call_YRQHlxiphLgI5wY41QDNvGTl", + // "name": "process_plan" + // } + // ] + + if (response.output[0].type === 'message') { + setMessages((prev) => [...prev, { content: response.output_text, sender: 'ai', type: 'message' }]); + } else if (response.output[0].type === 'function_call' && response.output[0].name === 'process_plan') { + setMessages((prev) => [ + ...prev, + { content: JSON.parse((response.output[0] as any).arguments).plan, sender: 'ai', type: 'plan' }, + ]); } + // let nextState: StepMakerState; + // switch (state.kind) { + // case 'initial': + // nextState = { + // kind: 'planning', + // messages: [...state.messages, { content: response.output_text, sender: 'ai', type: 'message' }], + // userQA: new Map(), + // }; + // setState(nextState); + // break; + // case 'planning': + // nextState = { + // kind: 'planning', + // userQA: new Map(), + // messages: [...state.messages, { content: response.output_text, sender: 'ai', type: 'message' }], + // }; + // break; + // case 'codeGeneration': + // break; + // case 'waitingForBuild': + // break; + // case 'buildLogEvaluation': + // break; + // } }; - return { isLoading, state, sendMessage }; + return { isLoading, messages, sendMessage }; }; export default useStepMakerAI; From ea48bcbfac5a0b244d7cd4aa2f1cc3ef1abbd7b7 Mon Sep 17 00:00:00 2001 From: Tamas Papik Date: Thu, 8 May 2025 11:17:46 +0200 Subject: [PATCH 10/40] resolved conflict --- .../StepConfigDrawer/hooks/useStepMakerAI.ts | 22 ++----------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts b/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts index c7acb8386..c67282bad 100644 --- a/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts @@ -1,7 +1,7 @@ import OpenAI from 'openai'; import { useState } from 'react'; -import { coderSystemPrompt, examplePrompts, plannerPrompt } from './prompts'; +import { plannerPrompt } from './prompts'; export type Message = { content: string; @@ -60,24 +60,6 @@ const useStepMakerAI = (props: Props) => { const [messages, setMessages] = useState([]); - const plannerPrompt = ` -You are a DevOps engineer helping Bitrise CI/CD users with their bash script step. You are given an existing (functioning) workflow and editing a bash script step and a new request to improve that step. -Your task is to understand the user's request and create a high-level plan to implement the requested changes. - -Technical considerations: -- DO NOT write any code or YAML, just a high-level plan. -- DO NOT assume the user uses any CI/CD tool other than Bitrise. - -Make sure to ask clarifying questions if the request is not clear. Think about various edge cases, not just the happy path. - -Selected workflow to edit: ${selectedWorkflow} - -bitrise.yml that implements the selected workflow: -\`\`\`yml -${bitriseYml} -\`\`\` -`; - const coderSystemPrompt = ` You are a DevOps engineer implementing Bitrise CI/CD workflows. You are given a high-level plan to implement a new feature in an existing workflow. Your task is to output the bitrise.yml file that implements the requested changes. Only output raw YML, no explanations or comments, no Markdown code blocks. The selected workflow to improve: ${selectedWorkflow} @@ -102,7 +84,7 @@ This is the high-level plan you need to implement. It might contain unanswered q const response = await client.responses.create({ model: 'gpt-4o', - instructions: plannerPrompt, + instructions: plannerPrompt(selectedWorkflow, bitriseYml), input, previous_response_id: responseId, tools: [ From 0326c0f89ace08e0703617d25f5606a8aa2dad72 Mon Sep 17 00:00:00 2001 From: Tamas Papik Date: Thu, 8 May 2025 11:41:33 +0200 Subject: [PATCH 11/40] updated inputs model --- .../StepConfigDrawer/hooks/useStepMakerAI.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts b/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts index c67282bad..7d15c5e01 100644 --- a/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts @@ -1,4 +1,5 @@ import OpenAI from 'openai'; +import { ResponseInput } from 'openai/resources/responses/responses'; import { useState } from 'react'; import { plannerPrompt } from './prompts'; @@ -82,10 +83,18 @@ This is the high-level plan you need to implement. It might contain unanswered q console.log(coderSystemPrompt); setMessages((prev) => [...prev, { content: input, sender: 'user', type: 'message' }]); + const inputs: ResponseInput = [ + { + content: input, + role: 'user', // | 'assistant' | 'system' | 'developer'; + type: 'message', + }, + ]; + const response = await client.responses.create({ model: 'gpt-4o', instructions: plannerPrompt(selectedWorkflow, bitriseYml), - input, + input: inputs, previous_response_id: responseId, tools: [ { From 9224eea808bb019d2e42f4ad80e6b4da82b3d353 Mon Sep 17 00:00:00 2001 From: Laszlo Moczo Date: Thu, 8 May 2025 11:43:20 +0200 Subject: [PATCH 12/40] Hack --- .../components/ExpandableMessage.tsx | 33 ++++++++++++++++++ .../StepConfigDrawer/components/StepMaker.tsx | 17 +++++++-- .../StepConfigDrawer/components/purr.png | Bin 0 -> 12344 bytes 3 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 source/javascripts/components/unified-editor/StepConfigDrawer/components/ExpandableMessage.tsx create mode 100644 source/javascripts/components/unified-editor/StepConfigDrawer/components/purr.png diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/components/ExpandableMessage.tsx b/source/javascripts/components/unified-editor/StepConfigDrawer/components/ExpandableMessage.tsx new file mode 100644 index 000000000..6f694fa1e --- /dev/null +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/components/ExpandableMessage.tsx @@ -0,0 +1,33 @@ +import { Box, Button, Card, Collapse, forwardRef, Icon, MarkdownContent, useDisclosure } from '@bitrise/bitkit'; + +type ExpandableMessageProps = { + buttonLabel: string; + children: string; + isExpanded?: boolean; + title: string; +}; + +const ExpandableMessage = forwardRef((props, ref) => { + const { buttonLabel, children, isExpanded, title } = props; + + const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: isExpanded }); + + return ( + + onToggle()} padding="16" data-group role="button" display="flex" alignItems="center" gap="8"> + + {title} + + + + + + + + + ); +}); + +export default ExpandableMessage; diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx b/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx index da66deae3..94b9d9ba8 100644 --- a/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx @@ -6,6 +6,8 @@ import { useSecrets } from '@/hooks/useSecrets'; import useStepMakerAI, { Message } from '../hooks/useStepMakerAI'; import { useStepDrawerContext } from '../StepConfigDrawer.context'; +import ExpandableMessage from './ExpandableMessage'; +import aiAvatar from './purr.png'; interface MessageItemProps extends BoxProps { message: Message; @@ -15,9 +17,18 @@ const MessageItem = (props: MessageItemProps) => { const { message } = props; return ( - - - {message.content} + + + {message.type === 'plan' && ( + + {message.content} + + )} + {message.type === 'message' && message.content} ); diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/components/purr.png b/source/javascripts/components/unified-editor/StepConfigDrawer/components/purr.png new file mode 100644 index 0000000000000000000000000000000000000000..aa7b302edcf300d63beb442c7c932f80861fd3d0 GIT binary patch literal 12344 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

fa3_Rug_B= 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 e$i>a)cl-nJ5=uq7Yjsip0000 Date: Thu, 8 May 2025 11:47:52 +0200 Subject: [PATCH 13/40] Always call plan function --- .../StepConfigDrawer/hooks/prompts.ts | 2 +- .../StepConfigDrawer/hooks/useStepMakerAI.ts | 28 ++++++++++++++----- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/prompts.ts b/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/prompts.ts index e24836d30..92acb74b9 100644 --- a/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/prompts.ts +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/prompts.ts @@ -57,7 +57,7 @@ macOS-only: export const plannerPrompt = (selectedWorkflow: string, bitriseYml: string) => ` You are a DevOps engineer helping Bitrise CI/CD users with their bash script step. You are given an existing (functioning) workflow and editing a bash script step and a new request to improve that step. -Your task is to understand the user's request and create a high-level plan to implement the requested changes. +Your goal is to understand the user's request and create a high-level plan to implement the requested changes. Technical considerations: - DO NOT write any code or YAML, just a high-level plan. diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts b/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts index 7d15c5e01..bde291547 100644 --- a/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts @@ -16,6 +16,8 @@ type Props = { token: string; }; +const FUNCTION_CALL_PLAN = 'store_plan'; + // type StepMakerState = InitialState | Planning | CodeGeneration | WaitingForBuild | BuildLogEvaluation; // type InitialState = { @@ -92,28 +94,36 @@ This is the high-level plan you need to implement. It might contain unanswered q ]; const response = await client.responses.create({ - model: 'gpt-4o', + model: 'gpt-4o-mini', instructions: plannerPrompt(selectedWorkflow, bitriseYml), input: inputs, previous_response_id: responseId, tools: [ { - name: 'process_plan', + name: FUNCTION_CALL_PLAN, strict: false, parameters: { type: 'object', properties: { plan: { type: 'string', - description: 'The text of a plan.', + description: 'The plan you created in Markdown.', + }, + questions: { + description: 'The questions to ask the user.', + type: 'array', + items: { + type: 'string', + }, + minItems: 0, + maxItems: 3, }, }, - required: ['plan'], + required: ['plan', 'questions'], additionalProperties: false, }, type: 'function', - description: - 'The provided plan gets processed and forwarded to help the customer proceed with their workflow/bash script setup.', + description: 'Store the current high-level plan for the next phase (code generation).', }, { name: 'store_bash_script', @@ -134,6 +144,10 @@ This is the high-level plan you need to implement. It might contain unanswered q 'The provided bash script gets stored in the selected Bitrise workflow script step. The script is a part of the workflow.', }, ], + tool_choice: { + type: 'function', + name: FUNCTION_CALL_PLAN, + }, }); setIsLoading(false); setResponseId(response.id); @@ -159,7 +173,7 @@ This is the high-level plan you need to implement. It might contain unanswered q if (response.output[0].type === 'message') { setMessages((prev) => [...prev, { content: response.output_text, sender: 'ai', type: 'message' }]); - } else if (response.output[0].type === 'function_call' && response.output[0].name === 'process_plan') { + } else if (response.output[0].type === 'function_call' && response.output[0].name === FUNCTION_CALL_PLAN) { setMessages((prev) => [ ...prev, { content: JSON.parse((response.output[0] as any).arguments).plan, sender: 'ai', type: 'plan' }, From eb593ebc68128da23100604464b04a966f78d653 Mon Sep 17 00:00:00 2001 From: Tamas Papik Date: Thu, 8 May 2025 12:01:07 +0200 Subject: [PATCH 14/40] added action --- .../unified-editor/StepConfigDrawer/components/StepMaker.tsx | 2 +- .../unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx b/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx index 94b9d9ba8..f56c8e961 100644 --- a/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx @@ -50,7 +50,7 @@ const StepMaker = () => { const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); - sendMessage(value); + sendMessage('chat', value); setValue(''); }; diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts b/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts index bde291547..3fb72a6bf 100644 --- a/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts @@ -80,7 +80,7 @@ This is the high-level plan you need to implement. It might contain unanswered q // messages: [], // }); - const sendMessage = async (input: string) => { + const sendMessage = async (action: 'chat' | 'process_with_plan', input: string) => { setIsLoading(true); console.log(coderSystemPrompt); setMessages((prev) => [...prev, { content: input, sender: 'user', type: 'message' }]); From 2fb2cdbd95769be7b049a8e73aedddac7a8badf5 Mon Sep 17 00:00:00 2001 From: Tamas Papik Date: Thu, 8 May 2025 12:01:24 +0200 Subject: [PATCH 15/40] update action --- .../unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts b/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts index 3fb72a6bf..a7b47eb7a 100644 --- a/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts @@ -82,7 +82,7 @@ This is the high-level plan you need to implement. It might contain unanswered q const sendMessage = async (action: 'chat' | 'process_with_plan', input: string) => { setIsLoading(true); - console.log(coderSystemPrompt); + console.log(coderSystemPrompt, action); setMessages((prev) => [...prev, { content: input, sender: 'user', type: 'message' }]); const inputs: ResponseInput = [ From bb4f5a67cbc9e96ef8c425aed6aa1408a7d6d686 Mon Sep 17 00:00:00 2001 From: Tamas Papik Date: Thu, 8 May 2025 12:07:32 +0200 Subject: [PATCH 16/40] added tool result --- .../StepConfigDrawer/hooks/useStepMakerAI.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts b/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts index a7b47eb7a..48f7ed1ed 100644 --- a/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts @@ -93,6 +93,14 @@ This is the high-level plan you need to implement. It might contain unanswered q }, ]; + if (action === 'process_with_plan') { + inputs.push({ + output: 'plan ok', + call_id: 'planner_id', + type: 'function_call_output', + }); + } + const response = await client.responses.create({ model: 'gpt-4o-mini', instructions: plannerPrompt(selectedWorkflow, bitriseYml), From 00a801c552217d00696e583e87dd432827a59ec2 Mon Sep 17 00:00:00 2001 From: Laszlo Moczo Date: Thu, 8 May 2025 12:11:32 +0200 Subject: [PATCH 17/40] Hack --- .../components/ExpandableMessage.tsx | 5 ++-- .../StepConfigDrawer/components/StepMaker.tsx | 23 ++++++++++++------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/components/ExpandableMessage.tsx b/source/javascripts/components/unified-editor/StepConfigDrawer/components/ExpandableMessage.tsx index 6f694fa1e..2746f36b9 100644 --- a/source/javascripts/components/unified-editor/StepConfigDrawer/components/ExpandableMessage.tsx +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/components/ExpandableMessage.tsx @@ -4,11 +4,12 @@ type ExpandableMessageProps = { buttonLabel: string; children: string; isExpanded?: boolean; + onButtonClick?: VoidFunction; title: string; }; const ExpandableMessage = forwardRef((props, ref) => { - const { buttonLabel, children, isExpanded, title } = props; + const { buttonLabel, children, isExpanded, onButtonClick, title } = props; const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: isExpanded }); @@ -17,7 +18,7 @@ const ExpandableMessage = forwardRef((props, ref) onToggle()} padding="16" data-group role="button" display="flex" alignItems="center" gap="8"> {title} - diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx b/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx index f56c8e961..bf60e237c 100644 --- a/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx @@ -11,20 +11,23 @@ import aiAvatar from './purr.png'; interface MessageItemProps extends BoxProps { message: Message; + onPlanButtonClick?: VoidFunction; } const MessageItem = (props: MessageItemProps) => { - const { message } = props; + const { message, onPlanButtonClick } = props; return ( - + {message.sender === 'user' && ( + /* + + */ + + )} + {message.sender === 'ai' && } {message.type === 'plan' && ( - + {message.content} )} @@ -64,7 +67,11 @@ const StepMaker = () => { marginBlockStart="16" > {messages.map((message, index) => ( - + sendMessage('process_with_plan', 'Proceed with code generation')} + /> ))} From aeccb70a63a4af92db6bf9138cf0c9eb5d0e8049 Mon Sep 17 00:00:00 2001 From: Laszlo Moczo Date: Thu, 8 May 2025 12:21:36 +0200 Subject: [PATCH 18/40] Hack --- .../StepConfigDrawer/components/StepMaker.tsx | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx b/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx index bf60e237c..628f914d2 100644 --- a/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx @@ -1,5 +1,5 @@ /* eslint-disable react/no-array-index-key */ -import { Avatar, Box, BoxProps, Button, Input } from '@bitrise/bitkit'; +import { Avatar, Box, BoxProps, Button, EmptyState, Input, Text } from '@bitrise/bitkit'; import { useState } from 'react'; import { useSecrets } from '@/hooks/useSecrets'; @@ -19,10 +19,11 @@ const MessageItem = (props: MessageItemProps) => { return ( {message.sender === 'user' && ( - /* - - */ - + )} {message.sender === 'ai' && } @@ -66,6 +67,15 @@ const StepMaker = () => { paddingBlockStart="8" marginBlockStart="16" > + {messages.length === 0 && ( + + {!token && To get started, add your ChatGPT API key as a secret.} + + )} {messages.map((message, index) => ( { setValue(e.currentTarget.value)} size="md" flex="1" value={value} /> - From b1c870fc1b1455883b40f5a74379ced46426aa49 Mon Sep 17 00:00:00 2001 From: Tamas Papik Date: Thu, 8 May 2025 12:28:14 +0200 Subject: [PATCH 19/40] iterate on plan --- .../StepConfigDrawer/hooks/useStepMakerAI.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts b/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts index 48f7ed1ed..79db27c45 100644 --- a/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts @@ -80,6 +80,8 @@ This is the high-level plan you need to implement. It might contain unanswered q // messages: [], // }); + const [toolOutputId, setToolOutputId] = useState(''); + const sendMessage = async (action: 'chat' | 'process_with_plan', input: string) => { setIsLoading(true); console.log(coderSystemPrompt, action); @@ -93,12 +95,19 @@ This is the high-level plan you need to implement. It might contain unanswered q }, ]; - if (action === 'process_with_plan') { + if (toolOutputId.length > 0) { + let output = 'plan: ok!'; + if (action !== 'process_with_plan') { + output = 'plan: requires refinement'; + } + inputs.push({ - output: 'plan ok', - call_id: 'planner_id', + output, + call_id: toolOutputId, type: 'function_call_output', }); + + setToolOutputId(''); } const response = await client.responses.create({ @@ -182,6 +191,7 @@ This is the high-level plan you need to implement. It might contain unanswered q if (response.output[0].type === 'message') { setMessages((prev) => [...prev, { content: response.output_text, sender: 'ai', type: 'message' }]); } else if (response.output[0].type === 'function_call' && response.output[0].name === FUNCTION_CALL_PLAN) { + setToolOutputId(response.output[0].call_id); setMessages((prev) => [ ...prev, { content: JSON.parse((response.output[0] as any).arguments).plan, sender: 'ai', type: 'plan' }, From ccfa557ba9a74431236c121fe78d7330883f4fa8 Mon Sep 17 00:00:00 2001 From: Laszlo Moczo Date: Thu, 8 May 2025 12:29:28 +0200 Subject: [PATCH 20/40] Hack --- .../StepConfigDrawer/components/ExpandableMessage.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/components/ExpandableMessage.tsx b/source/javascripts/components/unified-editor/StepConfigDrawer/components/ExpandableMessage.tsx index 2746f36b9..f29ecb751 100644 --- a/source/javascripts/components/unified-editor/StepConfigDrawer/components/ExpandableMessage.tsx +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/components/ExpandableMessage.tsx @@ -13,18 +13,26 @@ const ExpandableMessage = forwardRef((props, ref) const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: isExpanded }); + const handleClick = (e: React.MouseEvent) => { + e.stopPropagation(); + onButtonClick?.(); + }; + return ( onToggle()} padding="16" data-group role="button" display="flex" alignItems="center" gap="8"> {title} - + From 48c064ff78a144f47ef191b75be4ceb105f16e07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliv=C3=A9r=20Falvai?= Date: Thu, 8 May 2025 12:32:12 +0200 Subject: [PATCH 21/40] New coder system prompt --- .../StepConfigDrawer/hooks/prompts.ts | 32 ++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/prompts.ts b/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/prompts.ts index 92acb74b9..4454caa32 100644 --- a/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/prompts.ts +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/prompts.ts @@ -63,7 +63,9 @@ Technical considerations: - DO NOT write any code or YAML, just a high-level plan. - DO NOT assume the user uses any CI/CD tool other than Bitrise. -Make sure to ask clarifying questions if the request is not clear. Think about various edge cases, not just the happy path. +Content considerations: +- Make sure to ask clarifying questions if the request is not clear. Think about various edge cases, not just the happy path. +- The plan shouldn't be too verbose, but it should be detailed enough to understand the implementation. Selected workflow to edit: ${selectedWorkflow} @@ -76,15 +78,29 @@ Below you will find some documentation about Bitrise workflows and implementatio ${bitriseInfo} `; -export const coderSystemPrompt = (selectedWorkflow: string) => ` -You are a DevOps engineer implementing Bitrise CI/CD workflows. You are given a high-level plan to implement a new feature in an existing workflow. Your task is to output the bitrise.yml file that implements the requested changes. Only output raw YML, no explanations or comments, no Markdown code blocks. -The selected workflow to improve: ${selectedWorkflow} - -This is the high-level plan you need to implement. It might contain unanswered questions. In this case, use your best judgment to fill in the gaps. -`; - export const examplePrompts = [ 'Add a step to send a Slack message when the build fails.', 'Add a step to run unit tests before deploying to production.', 'Add a step to send an email notification when the build succeeds.', ]; + +export const coderSystemPrompt = (selectedWorkflow: string, bitriseYml: string) => ` +You are an expert bash script developer. Your task is to generate a single, syntactically correct bash script based on the plan provided. + +## Instructions: +- Output ONLY valid bash script code with no additional text, explanations, or markdown formatting +- Include appropriate shebang (#!/bin/bash) at the beginning +- Ensure errors are handled and error messages are meaningful, and do parameter validation when appropriate +- Add helpful comments within the script to document functionality +- Follow best practices for bash scripting (variable naming, quoting, etc.) +- Implement all functionality described in the provided plan + +The bash script you provide will be parsed and executed directly as a bash script at the right place within the workflow. It's going to be inlined into a script step, you do not need to work on that. + +Workflow you are working on: ${selectedWorkflow} + +bitrise.yml that implements the selected workflow: +\`\`\`yml +${bitriseYml} +\`\`\` +`; From d69b655cc5cd0f7bd37e63140b7362aff1515bc1 Mon Sep 17 00:00:00 2001 From: Tamas Papik Date: Thu, 8 May 2025 12:35:11 +0200 Subject: [PATCH 22/40] added code gen --- .../StepConfigDrawer/hooks/useStepMakerAI.ts | 85 +++++-------------- 1 file changed, 21 insertions(+), 64 deletions(-) diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts b/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts index 79db27c45..e6b0f9c48 100644 --- a/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts @@ -2,7 +2,7 @@ import OpenAI from 'openai'; import { ResponseInput } from 'openai/resources/responses/responses'; import { useState } from 'react'; -import { plannerPrompt } from './prompts'; +import { coderSystemPrompt, plannerPrompt } from './prompts'; export type Message = { content: string; @@ -17,6 +17,7 @@ type Props = { }; const FUNCTION_CALL_PLAN = 'store_plan'; +const FUNCTION_CALL_STORE_BASH_SCRIPT = 'store_bash_script'; // type StepMakerState = InitialState | Planning | CodeGeneration | WaitingForBuild | BuildLogEvaluation; @@ -63,26 +64,9 @@ const useStepMakerAI = (props: Props) => { const [messages, setMessages] = useState([]); - const coderSystemPrompt = ` -You are a DevOps engineer implementing Bitrise CI/CD workflows. You are given a high-level plan to implement a new feature in an existing workflow. Your task is to output the bitrise.yml file that implements the requested changes. Only output raw YML, no explanations or comments, no Markdown code blocks. -The selected workflow to improve: ${selectedWorkflow} - -This is the high-level plan you need to implement. It might contain unanswered questions. In this case, use your best judgment to fill in the gaps. -`; - - // const [state, setState] = useState({ - // kind: 'initial', - // examplePrompts: [ - // 'Add a step to send a Slack message when the build fails.', - // 'Add a step to run unit tests before deploying to production.', - // 'Add a step to send an email notification when the build succeeds.', - // ], - // messages: [], - // }); - const [toolOutputId, setToolOutputId] = useState(''); - const sendMessage = async (action: 'chat' | 'process_with_plan', input: string) => { + const sendMessage = async (action: 'chat' | 'process_with_plan' | 'generate_code', input: string) => { setIsLoading(true); console.log(coderSystemPrompt, action); setMessages((prev) => [...prev, { content: input, sender: 'user', type: 'message' }]); @@ -96,9 +80,9 @@ This is the high-level plan you need to implement. It might contain unanswered q ]; if (toolOutputId.length > 0) { - let output = 'plan: ok!'; + let output = 'ok'; if (action !== 'process_with_plan') { - output = 'plan: requires refinement'; + output = 'requires refinement'; } inputs.push({ @@ -110,9 +94,14 @@ This is the high-level plan you need to implement. It might contain unanswered q setToolOutputId(''); } + let instructions = plannerPrompt(selectedWorkflow, bitriseYml); + if (action === 'generate_code') { + instructions = coderSystemPrompt(selectedWorkflow, bitriseYml); + } + const response = await client.responses.create({ model: 'gpt-4o-mini', - instructions: plannerPrompt(selectedWorkflow, bitriseYml), + instructions, input: inputs, previous_response_id: responseId, tools: [ @@ -143,7 +132,7 @@ This is the high-level plan you need to implement. It might contain unanswered q description: 'Store the current high-level plan for the next phase (code generation).', }, { - name: 'store_bash_script', + name: FUNCTION_CALL_STORE_BASH_SCRIPT, strict: false, parameters: { type: 'object', @@ -171,23 +160,6 @@ This is the high-level plan you need to implement. It might contain unanswered q console.log('Response:', response); - // let type = 'message'; - // if(response.output[0].type === 'function_call' && - // response.output[0].name === 'process_plan' ) - // { - // type = 'plan'; - // } - // [ - // { - // "id": "fc_681c74a030b08191a5101aedcfebcef3050d103f60d8a0dc", - // "type": "function_call", - // "status": "completed", - // "arguments": "{\"plan\":\"1. Research Method to Fetch External IP:\\n - Use a reliable external service or command that returns the external IP address, such as `curl` with a service like `http://ipinfo.io/ip` or `http://ifconfig.me`.\\n\\n2. Error Handling:\\n - Ensure the command can handle potential errors gracefully. For example, handle situations where the external service is unreachable or returns an unexpected result.\\n\\n3. Security Considerations:\\n - Ensure that using an external service does not expose sensitive information about your environment.\\n\\n4. Integration with the Bitrise Workflow:\\n - Add the command to the existing bash script step within your Bitrise workflow.\\n - Use `echo` to print the result to the console.\\n\\n5. Testing:\\n - Test the script in a controlled environment to ensure it works as expected without throwing errors.\\n\\n6. Documentation:\\n - Optionally update any relevant documentation to ensure the process is clear for future reference.\"}", - // "call_id": "call_YRQHlxiphLgI5wY41QDNvGTl", - // "name": "process_plan" - // } - // ] - if (response.output[0].type === 'message') { setMessages((prev) => [...prev, { content: response.output_text, sender: 'ai', type: 'message' }]); } else if (response.output[0].type === 'function_call' && response.output[0].name === FUNCTION_CALL_PLAN) { @@ -196,31 +168,16 @@ This is the high-level plan you need to implement. It might contain unanswered q ...prev, { content: JSON.parse((response.output[0] as any).arguments).plan, sender: 'ai', type: 'plan' }, ]); + } else if ( + response.output[0].type === 'function_call' && + response.output[0].name === FUNCTION_CALL_STORE_BASH_SCRIPT + ) { + setToolOutputId(response.output[0].call_id); + setMessages((prev) => [ + ...prev, + { content: JSON.parse((response.output[0] as any).arguments).script, sender: 'ai', type: 'content' }, + ]); } - // let nextState: StepMakerState; - // switch (state.kind) { - // case 'initial': - // nextState = { - // kind: 'planning', - // messages: [...state.messages, { content: response.output_text, sender: 'ai', type: 'message' }], - // userQA: new Map(), - // }; - // setState(nextState); - // break; - // case 'planning': - // nextState = { - // kind: 'planning', - // userQA: new Map(), - // messages: [...state.messages, { content: response.output_text, sender: 'ai', type: 'message' }], - // }; - // break; - // case 'codeGeneration': - // break; - // case 'waitingForBuild': - // break; - // case 'buildLogEvaluation': - // break; - // } }; return { isLoading, messages, sendMessage }; From b0acf03bead781b7b913d5956b5f7fa9c178c8ee Mon Sep 17 00:00:00 2001 From: Tamas Papik Date: Thu, 8 May 2025 12:40:30 +0200 Subject: [PATCH 23/40] added tool selection --- .../StepConfigDrawer/hooks/useStepMakerAI.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts b/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts index e6b0f9c48..79fb7f2c1 100644 --- a/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts @@ -95,10 +95,15 @@ const useStepMakerAI = (props: Props) => { } let instructions = plannerPrompt(selectedWorkflow, bitriseYml); - if (action === 'generate_code') { + if (action === 'process_with_plan') { instructions = coderSystemPrompt(selectedWorkflow, bitriseYml); } + let selectedTool = FUNCTION_CALL_PLAN; + if (action === 'process_with_plan') { + selectedTool = FUNCTION_CALL_STORE_BASH_SCRIPT; + } + const response = await client.responses.create({ model: 'gpt-4o-mini', instructions, @@ -152,7 +157,7 @@ const useStepMakerAI = (props: Props) => { ], tool_choice: { type: 'function', - name: FUNCTION_CALL_PLAN, + name: selectedTool, }, }); setIsLoading(false); From 9c2b692f024232dca0875760e2ea27268f521d6d Mon Sep 17 00:00:00 2001 From: Laszlo Moczo Date: Thu, 8 May 2025 14:00:17 +0200 Subject: [PATCH 24/40] Hack --- .../components/EditorWrapper.tsx | 60 +++++++++++++++++++ .../components/ExpandableMessage.tsx | 8 ++- .../components/StepCodeEditor.tsx | 49 +-------------- .../StepConfigDrawer/components/StepMaker.tsx | 41 ++++++++++--- 4 files changed, 103 insertions(+), 55 deletions(-) create mode 100644 source/javascripts/components/unified-editor/StepConfigDrawer/components/EditorWrapper.tsx diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/components/EditorWrapper.tsx b/source/javascripts/components/unified-editor/StepConfigDrawer/components/EditorWrapper.tsx new file mode 100644 index 000000000..3a5ee74cb --- /dev/null +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/components/EditorWrapper.tsx @@ -0,0 +1,60 @@ +import { Editor } from '@monaco-editor/react'; +import { editor } from 'monaco-editor'; +import { useCallback, useEffect, useState } from 'react'; + +import MonacoUtils from '@/core/utils/MonacoUtils'; + +const EDITOR_OPTIONS = { + fontSize: 13, + fontFamily: 'mono', + roundedSelection: false, + scrollBeyondLastLine: false, + stickyScroll: { enabled: true }, + contextmenu: false, + minimap: { enabled: false }, + padding: { top: 16, bottom: 16 }, +}; + +type EditorWrapperProps = { + value: string; + defaultValue?: string; + onChange: (value: string | null) => void; +}; + +const EditorWrapper = (props: EditorWrapperProps) => { + const { defaultValue, onChange, value } = props; + const [editorInstance, setEditor] = useState(); + + const updateEditorHeight = useCallback(() => { + if (!editorInstance) { + return; + } + + const contentHeight = Math.min(editorInstance?.getContentHeight() || 250, window.innerHeight * 0.5); + requestAnimationFrame(() => + editorInstance?.layout({ + height: contentHeight, + width: editorInstance?.getLayoutInfo().width, + }), + ); + }, [editorInstance]); + + useEffect(() => { + editorInstance?.onDidContentSizeChange(updateEditorHeight); + updateEditorHeight(); + return undefined; + }, [editorInstance, updateEditorHeight]); + + return ( + onChange(changedValue || null)} + beforeMount={MonacoUtils.configureEnvVarsCompletionProvider} + /> + ); +}; +export default EditorWrapper; diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/components/ExpandableMessage.tsx b/source/javascripts/components/unified-editor/StepConfigDrawer/components/ExpandableMessage.tsx index f29ecb751..1d9bdd275 100644 --- a/source/javascripts/components/unified-editor/StepConfigDrawer/components/ExpandableMessage.tsx +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/components/ExpandableMessage.tsx @@ -1,15 +1,18 @@ import { Box, Button, Card, Collapse, forwardRef, Icon, MarkdownContent, useDisclosure } from '@bitrise/bitkit'; +import EditorWrapper from './EditorWrapper'; + type ExpandableMessageProps = { buttonLabel: string; children: string; isExpanded?: boolean; onButtonClick?: VoidFunction; title: string; + type: 'plan' | 'content' | 'message'; }; const ExpandableMessage = forwardRef((props, ref) => { - const { buttonLabel, children, isExpanded, onButtonClick, title } = props; + const { buttonLabel, children, isExpanded, onButtonClick, title, type } = props; const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: isExpanded }); @@ -29,7 +32,8 @@ const ExpandableMessage = forwardRef((props, ref) - + {type === 'plan' && } + {type === 'content' && } diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepCodeEditor.tsx b/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepCodeEditor.tsx index 60ad4eaec..97bfb7142 100644 --- a/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepCodeEditor.tsx +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepCodeEditor.tsx @@ -1,23 +1,9 @@ import { Box, FilterSwitch, FilterSwitchGroup, Label } from '@bitrise/bitkit'; -import { Editor } from '@monaco-editor/react'; -import type { editor } from 'monaco-editor'; -import { useCallback, useEffect, useState } from 'react'; - -import MonacoUtils from '@/core/utils/MonacoUtils'; +import { useState } from 'react'; +import EditorWrapper from './EditorWrapper'; import StepMaker from './StepMaker'; -const EDITOR_OPTIONS = { - fontSize: 13, - fontFamily: 'mono', - roundedSelection: false, - scrollBeyondLastLine: false, - stickyScroll: { enabled: true }, - contextmenu: false, - minimap: { enabled: false }, - padding: { top: 16, bottom: 16 }, -}; - type Props = { label?: string; value: string; @@ -26,29 +12,8 @@ type Props = { }; const StepCodeEditor = ({ label, value, defaultValue, onChange }: Props) => { - const [editorInstance, setEditor] = useState(); const [state, setState] = useState<'script' | 'ai'>('script'); - const updateEditorHeight = useCallback(() => { - if (!editorInstance) { - return; - } - - const contentHeight = Math.min(editorInstance?.getContentHeight() || 250, window.innerHeight * 0.5); - requestAnimationFrame(() => - editorInstance?.layout({ - height: contentHeight, - width: editorInstance?.getLayoutInfo().width, - }), - ); - }, [editorInstance]); - - useEffect(() => { - editorInstance?.onDidContentSizeChange(updateEditorHeight); - updateEditorHeight(); - return undefined; - }, [editorInstance, updateEditorHeight]); - return ( @@ -59,15 +24,7 @@ const StepCodeEditor = ({ label, value, defaultValue, onChange }: Props) => { - onChange(changedValue || null)} - beforeMount={MonacoUtils.configureEnvVarsCompletionProvider} - /> + diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx b/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx index 628f914d2..7e14ffd75 100644 --- a/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx @@ -1,5 +1,5 @@ /* eslint-disable react/no-array-index-key */ -import { Avatar, Box, BoxProps, Button, EmptyState, Input, Text } from '@bitrise/bitkit'; +import { Avatar, Box, BoxProps, Button, Input, Text } from '@bitrise/bitkit'; import { useState } from 'react'; import { useSecrets } from '@/hooks/useSecrets'; @@ -28,7 +28,22 @@ const MessageItem = (props: MessageItemProps) => { {message.sender === 'ai' && } {message.type === 'plan' && ( - + + {message.content} + + )} + {message.type === 'content' && ( + {message.content} )} @@ -68,13 +83,25 @@ const StepMaker = () => { marginBlockStart="16" > {messages.length === 0 && ( - + + + Meowdy! I'm Purr Request, your paw-sonal Step-making assistant. + + + Just give me a whisker of a description about what you want your step to +
+ do, and I’ll pounce on it for you. +
{!token && To get started, add your ChatGPT API key as a secret.} -
+
)} {messages.map((message, index) => ( Date: Thu, 8 May 2025 14:21:53 +0200 Subject: [PATCH 25/40] Update example prompts --- .../unified-editor/StepConfigDrawer/hooks/prompts.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/prompts.ts b/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/prompts.ts index 4454caa32..250a88827 100644 --- a/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/prompts.ts +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/prompts.ts @@ -79,9 +79,10 @@ ${bitriseInfo} `; export const examplePrompts = [ - 'Add a step to send a Slack message when the build fails.', - 'Add a step to run unit tests before deploying to production.', - 'Add a step to send an email notification when the build succeeds.', + 'Look at the changed files of the PR that triggered this build. Add a label to the PR based on the size of the diff, such as `Size: M` or `Size: XXL`.', + "Fetch one random Chuck Norris joke from `https://api.chucknorris.io/jokes/random` so that I don't get bored waiting for the build.", + 'Print the IP address of the machine running the build.', + "Iterate the dependency tree, fetch their declared license metadata and abort the build if a dependency's license is in our forbidden license list file. Present results in a table format and post it as a PR comment. If the offending dependency is a transitive one, make sure to also report the direct dependency that introduced it.", ]; export const coderSystemPrompt = (selectedWorkflow: string, bitriseYml: string) => ` From 4f89b19041bacfddbc132f953a898b2c4ab9cd90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliv=C3=A9r=20Falvai?= Date: Thu, 8 May 2025 14:28:31 +0200 Subject: [PATCH 26/40] Purr-fect everything --- .../unified-editor/StepConfigDrawer/components/StepMaker.tsx | 4 ++-- .../unified-editor/StepConfigDrawer/hooks/prompts.ts | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx b/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx index 7e14ffd75..baa8aeae1 100644 --- a/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx @@ -31,7 +31,7 @@ const MessageItem = (props: MessageItemProps) => { {message.content} @@ -41,7 +41,7 @@ const MessageItem = (props: MessageItemProps) => { {message.content} diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/prompts.ts b/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/prompts.ts index 250a88827..dfa377587 100644 --- a/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/prompts.ts +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/prompts.ts @@ -66,6 +66,7 @@ Technical considerations: Content considerations: - Make sure to ask clarifying questions if the request is not clear. Think about various edge cases, not just the happy path. - The plan shouldn't be too verbose, but it should be detailed enough to understand the implementation. +- For every response in this conversation, add a 1-3 word cat-like comment to the end of the response. Selected workflow to edit: ${selectedWorkflow} From 0fe64bae98d53dd8c5bb9887a7692db772b24dc5 Mon Sep 17 00:00:00 2001 From: Laszlo Moczo Date: Thu, 8 May 2025 14:34:42 +0200 Subject: [PATCH 27/40] Hack --- .../components/ExpandableMessage.tsx | 16 +++-- .../components/StepCodeEditor.tsx | 2 +- .../StepConfigDrawer/components/StepMaker.tsx | 59 ++++++++++++++++--- 3 files changed, 65 insertions(+), 12 deletions(-) diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/components/ExpandableMessage.tsx b/source/javascripts/components/unified-editor/StepConfigDrawer/components/ExpandableMessage.tsx index 1d9bdd275..1ae4150ad 100644 --- a/source/javascripts/components/unified-editor/StepConfigDrawer/components/ExpandableMessage.tsx +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/components/ExpandableMessage.tsx @@ -6,15 +6,16 @@ type ExpandableMessageProps = { buttonLabel: string; children: string; isExpanded?: boolean; + isLoading?: boolean; onButtonClick?: VoidFunction; title: string; type: 'plan' | 'content' | 'message'; }; const ExpandableMessage = forwardRef((props, ref) => { - const { buttonLabel, children, isExpanded, onButtonClick, title, type } = props; + const { buttonLabel, children, isLoading, onButtonClick, title, type } = props; - const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: isExpanded }); + const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true }); const handleClick = (e: React.MouseEvent) => { e.stopPropagation(); @@ -26,7 +27,7 @@ const ExpandableMessage = forwardRef((props, ref) onToggle()} padding="16" data-group role="button" display="flex" alignItems="center" gap="8"> {title} - @@ -34,7 +35,14 @@ const ExpandableMessage = forwardRef((props, ref) {type === 'plan' && } {type === 'content' && } - diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepCodeEditor.tsx b/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepCodeEditor.tsx index 97bfb7142..c3b5a4039 100644 --- a/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepCodeEditor.tsx +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepCodeEditor.tsx @@ -27,7 +27,7 @@ const StepCodeEditor = ({ label, value, defaultValue, onChange }: Props) => {
- +
); diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx b/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx index baa8aeae1..cbabe06b6 100644 --- a/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx @@ -1,23 +1,26 @@ /* eslint-disable react/no-array-index-key */ -import { Avatar, Box, BoxProps, Button, Input, Text } from '@bitrise/bitkit'; +import { Avatar, Box, BoxProps, Button, Input, MarkdownContent, ProgressBitbot, Text } from '@bitrise/bitkit'; import { useState } from 'react'; import { useSecrets } from '@/hooks/useSecrets'; +import { examplePrompts } from '../hooks/prompts'; import useStepMakerAI, { Message } from '../hooks/useStepMakerAI'; import { useStepDrawerContext } from '../StepConfigDrawer.context'; import ExpandableMessage from './ExpandableMessage'; import aiAvatar from './purr.png'; interface MessageItemProps extends BoxProps { + isLoading: boolean; message: Message; onPlanButtonClick?: VoidFunction; + onSaveButtonClick?: (value: string | null) => void; } const MessageItem = (props: MessageItemProps) => { - const { message, onPlanButtonClick } = props; + const { isLoading, message, onPlanButtonClick, onSaveButtonClick } = props; return ( - + {message.sender === 'user' && ( { onButtonClick={onPlanButtonClick} title="The Purr-fect plan" type="plan" + isLoading={isLoading} > {message.content} @@ -40,20 +44,26 @@ const MessageItem = (props: MessageItemProps) => { {message.type === 'content' && ( onSaveButtonClick?.(message.content)} title="The Purr-fect code" type="content" + isLoading={isLoading} > {message.content} )} - {message.type === 'message' && message.content} + {message.type === 'message' && } ); }; -const StepMaker = () => { +type StepMakerProps = { + onChange: (value: string | null) => void; +}; + +const StepMaker = (props: StepMakerProps) => { + const { onChange } = props; const { workflowId } = useStepDrawerContext(); const [value, setValue] = useState(''); @@ -81,6 +91,7 @@ const StepMaker = () => { borderColor="border/minimal" paddingBlockStart="8" marginBlockStart="16" + minHeight="245px" > {messages.length === 0 && ( {
do, and I’ll pounce on it for you. - {!token && To get started, add your ChatGPT API key as a secret.} + {!token && ( + + To get started, add your ChatGPT API key as a secret. The key must +
+ be OPENAI_API_KEY e.g: OPENAI_API_KEY=’your_API_key’ +
+ )} + + {examplePrompts.map((p) => ( + { + sendMessage('chat', p); + }} + > + {p} + + ))} +
)} {messages.map((message, index) => ( @@ -108,8 +145,11 @@ const StepMaker = () => { key={index} message={message} onPlanButtonClick={() => sendMessage('process_with_plan', 'Proceed with code generation')} + onSaveButtonClick={onChange} + isLoading={isLoading} /> ))} + {isLoading && }
{ size="md" flex="1" value={value} + autoFocus + placeholder="What do you need?" /> + + Purr Request uses ChatGPT4 and may not be reliable. Always review and test code before deploying. + ); }; From 6f5cdbff2f1d52b176d04602eb252dd81489e6f8 Mon Sep 17 00:00:00 2001 From: Tamas Papik Date: Thu, 8 May 2025 14:36:43 +0200 Subject: [PATCH 28/40] updated prompt --- .../StepConfigDrawer/hooks/prompts.ts | 3 +++ .../StepConfigDrawer/hooks/useStepMakerAI.ts | 19 +++++++++---------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/prompts.ts b/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/prompts.ts index dfa377587..c90b17c06 100644 --- a/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/prompts.ts +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/prompts.ts @@ -62,6 +62,9 @@ Your goal is to understand the user's request and create a high-level plan to im Technical considerations: - DO NOT write any code or YAML, just a high-level plan. - DO NOT assume the user uses any CI/CD tool other than Bitrise. +- DO NOT add high level plan in responses. +- When there is a plan, call store_plan function instead of returning the plan. +- When you respond with code call store_bash_script function instead of returning the code. Content considerations: - Make sure to ask clarifying questions if the request is not clear. Think about various edge cases, not just the happy path. diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts b/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts index 79fb7f2c1..eb1ea8b40 100644 --- a/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts @@ -66,9 +66,8 @@ const useStepMakerAI = (props: Props) => { const [toolOutputId, setToolOutputId] = useState(''); - const sendMessage = async (action: 'chat' | 'process_with_plan' | 'generate_code', input: string) => { + const sendMessage = async (action: 'chat' | 'process_with_plan', input: string) => { setIsLoading(true); - console.log(coderSystemPrompt, action); setMessages((prev) => [...prev, { content: input, sender: 'user', type: 'message' }]); const inputs: ResponseInput = [ @@ -99,10 +98,10 @@ const useStepMakerAI = (props: Props) => { instructions = coderSystemPrompt(selectedWorkflow, bitriseYml); } - let selectedTool = FUNCTION_CALL_PLAN; - if (action === 'process_with_plan') { - selectedTool = FUNCTION_CALL_STORE_BASH_SCRIPT; - } + // let selectedTool = FUNCTION_CALL_PLAN; + // if (action === 'process_with_plan') { + // selectedTool = FUNCTION_CALL_STORE_BASH_SCRIPT; + // } const response = await client.responses.create({ model: 'gpt-4o-mini', @@ -155,10 +154,10 @@ const useStepMakerAI = (props: Props) => { 'The provided bash script gets stored in the selected Bitrise workflow script step. The script is a part of the workflow.', }, ], - tool_choice: { - type: 'function', - name: selectedTool, - }, + // tool_choice: { + // type: 'function', + // name: selectedTool, + // }, }); setIsLoading(false); setResponseId(response.id); From 370d2aa0af014f8bdfdca2a1c2df642ee9f04231 Mon Sep 17 00:00:00 2001 From: Tamas Papik Date: Thu, 8 May 2025 14:57:03 +0200 Subject: [PATCH 29/40] updated prompts --- .../unified-editor/StepConfigDrawer/hooks/prompts.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/prompts.ts b/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/prompts.ts index c90b17c06..9e5c2bfe4 100644 --- a/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/prompts.ts +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/prompts.ts @@ -62,9 +62,8 @@ Your goal is to understand the user's request and create a high-level plan to im Technical considerations: - DO NOT write any code or YAML, just a high-level plan. - DO NOT assume the user uses any CI/CD tool other than Bitrise. -- DO NOT add high level plan in responses. -- When there is a plan, call store_plan function instead of returning the plan. -- When you respond with code call store_bash_script function instead of returning the code. +- DO NOT add high level plan or code in responses. +- YOU MUST ALWAYS CALL call store_plan function instead of returning the plan. Content considerations: - Make sure to ask clarifying questions if the request is not clear. Think about various edge cases, not just the happy path. @@ -93,7 +92,7 @@ export const coderSystemPrompt = (selectedWorkflow: string, bitriseYml: string) You are an expert bash script developer. Your task is to generate a single, syntactically correct bash script based on the plan provided. ## Instructions: -- Output ONLY valid bash script code with no additional text, explanations, or markdown formatting +- YOU MUST ALWAYS CALL store_bash_script function instead of returning code in response. - Include appropriate shebang (#!/bin/bash) at the beginning - Ensure errors are handled and error messages are meaningful, and do parameter validation when appropriate - Add helpful comments within the script to document functionality From c701f9b87c770f2e9e90d540bcbfffdc2dddb0a5 Mon Sep 17 00:00:00 2001 From: Tamas Papik Date: Thu, 8 May 2025 15:01:27 +0200 Subject: [PATCH 30/40] added reset --- .../StepConfigDrawer/hooks/useStepMakerAI.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts b/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts index eb1ea8b40..c31ce9285 100644 --- a/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts @@ -66,6 +66,12 @@ const useStepMakerAI = (props: Props) => { const [toolOutputId, setToolOutputId] = useState(''); + const reset = () => { + setMessages([]); + setToolOutputId(''); + setResponseId(''); + }; + const sendMessage = async (action: 'chat' | 'process_with_plan', input: string) => { setIsLoading(true); setMessages((prev) => [...prev, { content: input, sender: 'user', type: 'message' }]); @@ -184,7 +190,7 @@ const useStepMakerAI = (props: Props) => { } }; - return { isLoading, messages, sendMessage }; + return { isLoading, messages, sendMessage, reset }; }; export default useStepMakerAI; From 7c7d6e0cf19e9919eb5ae74e201605e806a4f498 Mon Sep 17 00:00:00 2001 From: Tamas Papik Date: Thu, 8 May 2025 15:04:24 +0200 Subject: [PATCH 31/40] cleanup --- .../StepConfigDrawer/hooks/useStepMakerAI.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts b/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts index c31ce9285..c9b74902f 100644 --- a/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts @@ -56,20 +56,18 @@ const useStepMakerAI = (props: Props) => { const { bitriseYml, selectedWorkflow, token } = props; const [isLoading, setIsLoading] = useState(false); const [responseId, setResponseId] = useState(null); + const [messages, setMessages] = useState([]); + const [toolOutputId, setToolOutputId] = useState(null); const client = new OpenAI({ apiKey: token, dangerouslyAllowBrowser: true, }); - const [messages, setMessages] = useState([]); - - const [toolOutputId, setToolOutputId] = useState(''); - const reset = () => { setMessages([]); - setToolOutputId(''); - setResponseId(''); + setToolOutputId(null); + setResponseId(null); }; const sendMessage = async (action: 'chat' | 'process_with_plan', input: string) => { @@ -84,7 +82,7 @@ const useStepMakerAI = (props: Props) => { }, ]; - if (toolOutputId.length > 0) { + if (toolOutputId) { let output = 'ok'; if (action !== 'process_with_plan') { output = 'requires refinement'; From aceffce38846f2eae31fb2c87f84423762de0e31 Mon Sep 17 00:00:00 2001 From: Laszlo Moczo Date: Thu, 8 May 2025 15:05:51 +0200 Subject: [PATCH 32/40] Hack --- .../StepConfigDrawer/components/EditorWrapper.tsx | 5 +++-- .../StepConfigDrawer/components/ExpandableMessage.tsx | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/components/EditorWrapper.tsx b/source/javascripts/components/unified-editor/StepConfigDrawer/components/EditorWrapper.tsx index 3a5ee74cb..0023f9a82 100644 --- a/source/javascripts/components/unified-editor/StepConfigDrawer/components/EditorWrapper.tsx +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/components/EditorWrapper.tsx @@ -19,10 +19,11 @@ type EditorWrapperProps = { value: string; defaultValue?: string; onChange: (value: string | null) => void; + readOnly?: boolean; }; const EditorWrapper = (props: EditorWrapperProps) => { - const { defaultValue, onChange, value } = props; + const { defaultValue, onChange, readOnly, value } = props; const [editorInstance, setEditor] = useState(); const updateEditorHeight = useCallback(() => { @@ -50,7 +51,7 @@ const EditorWrapper = (props: EditorWrapperProps) => { theme="vs-dark" onMount={setEditor} defaultLanguage="shell" - options={EDITOR_OPTIONS} + options={{ ...EDITOR_OPTIONS, readOnly }} value={value || defaultValue} onChange={(changedValue) => onChange(changedValue || null)} beforeMount={MonacoUtils.configureEnvVarsCompletionProvider} diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/components/ExpandableMessage.tsx b/source/javascripts/components/unified-editor/StepConfigDrawer/components/ExpandableMessage.tsx index 1ae4150ad..08123865a 100644 --- a/source/javascripts/components/unified-editor/StepConfigDrawer/components/ExpandableMessage.tsx +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/components/ExpandableMessage.tsx @@ -34,7 +34,7 @@ const ExpandableMessage = forwardRef((props, ref) {type === 'plan' && } - {type === 'content' && } + {type === 'content' && } + + )}
{ autoFocus placeholder="What do you need?" /> - From 929520d42f0d4dd32641ab2efd2f7f283fae5be6 Mon Sep 17 00:00:00 2001 From: Laszlo Moczo Date: Thu, 8 May 2025 15:19:40 +0200 Subject: [PATCH 34/40] Hack --- .../StepConfigDrawer/components/StepMaker.tsx | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx b/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx index 469f13b8c..5ead6e97b 100644 --- a/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx @@ -43,9 +43,9 @@ const MessageItem = (props: MessageItemProps) => { )} {message.type === 'content' && ( onSaveButtonClick?.(message.content)} - title="The Purr-fect code" + title="Here is the purr-fect code. Apply to write it into the YML." type="content" isLoading={isLoading} > @@ -152,7 +152,14 @@ const StepMaker = (props: StepMakerProps) => { {isLoading && } {messages.length > 0 && ( - From 1815460a447a05238467161c5cb028f31ee6728d Mon Sep 17 00:00:00 2001 From: Laszlo Moczo Date: Thu, 8 May 2025 15:30:52 +0200 Subject: [PATCH 35/40] Hack --- .../StepConfigDrawer/components/StepMaker.tsx | 51 +++++++++---------- 1 file changed, 23 insertions(+), 28 deletions(-) diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx b/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx index 5ead6e97b..3f744a216 100644 --- a/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx @@ -85,14 +85,7 @@ const StepMaker = (props: StepMakerProps) => { return ( <> - + {messages.length === 0 && ( { )} - {examplePrompts.map((p) => ( - { - sendMessage('chat', p); - }} - > - {p} - - ))} + {!!token && + examplePrompts.map((p) => ( + { + sendMessage('chat', p); + }} + > + {p} + + ))} )} @@ -154,7 +149,7 @@ const StepMaker = (props: StepMakerProps) => { From 515680b29620057bf537cf1321c148654e872eb1 Mon Sep 17 00:00:00 2001 From: Tamas Papik Date: Thu, 8 May 2025 15:36:42 +0200 Subject: [PATCH 36/40] iteration on outputs --- .../StepConfigDrawer/hooks/useStepMakerAI.ts | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts b/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts index c9b74902f..d90e90242 100644 --- a/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts @@ -168,24 +168,23 @@ const useStepMakerAI = (props: Props) => { console.log('Response:', response); - if (response.output[0].type === 'message') { - setMessages((prev) => [...prev, { content: response.output_text, sender: 'ai', type: 'message' }]); - } else if (response.output[0].type === 'function_call' && response.output[0].name === FUNCTION_CALL_PLAN) { - setToolOutputId(response.output[0].call_id); - setMessages((prev) => [ - ...prev, - { content: JSON.parse((response.output[0] as any).arguments).plan, sender: 'ai', type: 'plan' }, - ]); - } else if ( - response.output[0].type === 'function_call' && - response.output[0].name === FUNCTION_CALL_STORE_BASH_SCRIPT - ) { - setToolOutputId(response.output[0].call_id); - setMessages((prev) => [ - ...prev, - { content: JSON.parse((response.output[0] as any).arguments).script, sender: 'ai', type: 'content' }, - ]); - } + response.output.forEach((outputItem) => { + if (outputItem.type === 'message') { + setMessages((prev) => [...prev, { content: response.output_text, sender: 'ai', type: 'message' }]); + } else if (outputItem.type === 'function_call' && outputItem.name === FUNCTION_CALL_PLAN) { + setToolOutputId(outputItem.call_id); + setMessages((prev) => [ + ...prev, + { content: JSON.parse((outputItem as any).arguments).plan, sender: 'ai', type: 'plan' }, + ]); + } else if (outputItem.type === 'function_call' && outputItem.name === FUNCTION_CALL_STORE_BASH_SCRIPT) { + setToolOutputId(outputItem.call_id); + setMessages((prev) => [ + ...prev, + { content: JSON.parse((outputItem as any).arguments).script, sender: 'ai', type: 'content' }, + ]); + } + }); }; return { isLoading, messages, sendMessage, reset }; From 35e94e4c35220532264b4971bb55379c40f0aca3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliv=C3=A9r=20Falvai?= Date: Thu, 8 May 2025 15:42:50 +0200 Subject: [PATCH 37/40] Everything --- .../components/StepCodeEditor.tsx | 2 +- .../StepConfigDrawer/components/StepMaker.tsx | 16 +++++++++-- .../StepConfigDrawer/hooks/prompts.ts | 28 +++++++++++++++++-- .../StepConfigDrawer/hooks/useStepMakerAI.ts | 6 ++-- 4 files changed, 43 insertions(+), 9 deletions(-) diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepCodeEditor.tsx b/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepCodeEditor.tsx index c3b5a4039..e5927a8f1 100644 --- a/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepCodeEditor.tsx +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepCodeEditor.tsx @@ -20,7 +20,7 @@ const StepCodeEditor = ({ label, value, defaultValue, onChange }: Props) => { {label && } setState(v as 'script' | 'ai')} value={state} marginBlockStart="0"> Script - AI input + Step Maker diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx b/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx index 3f744a216..24215b5f2 100644 --- a/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx @@ -2,6 +2,8 @@ import { Avatar, Box, BoxProps, Button, Input, MarkdownContent, ProgressBitbot, Text } from '@bitrise/bitkit'; import { useState } from 'react'; +import PageProps from '@/core/utils/PageProps'; +import useEnvVars from '@/hooks/useEnvVars'; import { useSecrets } from '@/hooks/useSecrets'; import { examplePrompts } from '../hooks/prompts'; @@ -68,11 +70,19 @@ const StepMaker = (props: StepMakerProps) => { const [value, setValue] = useState(''); - const { data } = useSecrets({ appSlug: '' }); - const token = data?.find(({ key }) => key === 'OPENAI_API_KEY')?.value || ''; + const appSlug = PageProps.appSlug(); + const { data: secretData } = useSecrets({ appSlug }); + const { envs } = useEnvVars({ + enabled: true, + stepBundleIds: [], + workflowIds: workflowId ? [workflowId] : [], + }); + const token = secretData?.find(({ key }) => key === 'OPENAI_API_KEY')?.value || ''; const { isLoading, messages, sendMessage, reset } = useStepMakerAI({ bitriseYml: '', + appSecretKeys: secretData?.map(({ key }) => key) || [], + appEnvKeys: envs.map(({ key }) => key) || [], selectedWorkflow: workflowId, token, }); @@ -175,7 +185,7 @@ const StepMaker = (props: StepMakerProps) => { - Purr Request uses ChatGPT4 and may not be reliable. Always review and test code before deploying. + Purr Request uses LLMs and may not always be reliable. Always review code before testing. ); diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/prompts.ts b/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/prompts.ts index 9e5c2bfe4..60d9323fc 100644 --- a/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/prompts.ts +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/prompts.ts @@ -55,7 +55,12 @@ macOS-only: - pod (Cocoapods) `; -export const plannerPrompt = (selectedWorkflow: string, bitriseYml: string) => ` +export const plannerPrompt = ( + selectedWorkflow: string, + bitriseYml: string, + appSecretKeys: string[], + appEnvKeys: string[], +) => ` You are a DevOps engineer helping Bitrise CI/CD users with their bash script step. You are given an existing (functioning) workflow and editing a bash script step and a new request to improve that step. Your goal is to understand the user's request and create a high-level plan to implement the requested changes. @@ -77,7 +82,13 @@ bitrise.yml that implements the selected workflow: ${bitriseYml} \`\`\` -Below you will find some documentation about Bitrise workflows and implementation details you need to know: +Available user-defined env vars: +${appEnvKeys.map((key) => `- $${key}`).join('\n')} + +Available user-defined secret env vars: +${appSecretKeys.map((key) => `- $${key}`).join('\n')} + +Below you will find some documentation about Bitrise workflows and implementation details you need to be aware of: ${bitriseInfo} `; @@ -88,7 +99,12 @@ export const examplePrompts = [ "Iterate the dependency tree, fetch their declared license metadata and abort the build if a dependency's license is in our forbidden license list file. Present results in a table format and post it as a PR comment. If the offending dependency is a transitive one, make sure to also report the direct dependency that introduced it.", ]; -export const coderSystemPrompt = (selectedWorkflow: string, bitriseYml: string) => ` +export const coderSystemPrompt = ( + selectedWorkflow: string, + bitriseYml: string, + appEnvKeys: string[], + appSecretKeys: string[], +) => ` You are an expert bash script developer. Your task is to generate a single, syntactically correct bash script based on the plan provided. ## Instructions: @@ -103,6 +119,12 @@ The bash script you provide will be parsed and executed directly as a bash scrip Workflow you are working on: ${selectedWorkflow} +Available user-defined env vars: +${appEnvKeys.map((key) => `- $${key}`).join('\n')} + +Available user-defined secret env vars: +${appSecretKeys.map((key) => `- $${key}`).join('\n')} + bitrise.yml that implements the selected workflow: \`\`\`yml ${bitriseYml} diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts b/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts index d90e90242..99221fcc5 100644 --- a/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts @@ -13,6 +13,8 @@ export type Message = { type Props = { bitriseYml: string; selectedWorkflow: string; + appSecretKeys: string[]; + appEnvKeys: string[]; token: string; }; @@ -97,9 +99,9 @@ const useStepMakerAI = (props: Props) => { setToolOutputId(''); } - let instructions = plannerPrompt(selectedWorkflow, bitriseYml); + let instructions = plannerPrompt(selectedWorkflow, bitriseYml, props.appSecretKeys, props.appEnvKeys); if (action === 'process_with_plan') { - instructions = coderSystemPrompt(selectedWorkflow, bitriseYml); + instructions = coderSystemPrompt(selectedWorkflow, bitriseYml, props.appSecretKeys, props.appEnvKeys); } // let selectedTool = FUNCTION_CALL_PLAN; From 67e3c5729829a1bc664ba98d71f8b595d312279d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliv=C3=A9r=20Falvai?= Date: Thu, 8 May 2025 15:43:35 +0200 Subject: [PATCH 38/40] Set temp --- .../unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts b/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts index 99221fcc5..297db3b4f 100644 --- a/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/hooks/useStepMakerAI.ts @@ -114,6 +114,7 @@ const useStepMakerAI = (props: Props) => { instructions, input: inputs, previous_response_id: responseId, + temperature: 0.5, tools: [ { name: FUNCTION_CALL_PLAN, From 6ddb378ccb754f2027bdfae6165b063ff7638e4a Mon Sep 17 00:00:00 2001 From: Laszlo Moczo Date: Thu, 8 May 2025 16:15:58 +0200 Subject: [PATCH 39/40] Hack --- .../unified-editor/StepConfigDrawer/components/StepMaker.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx b/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx index 24215b5f2..d316138d9 100644 --- a/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx @@ -79,6 +79,8 @@ const StepMaker = (props: StepMakerProps) => { }); const token = secretData?.find(({ key }) => key === 'OPENAI_API_KEY')?.value || ''; + console.log({ appSlug, secretData, token }); + const { isLoading, messages, sendMessage, reset } = useStepMakerAI({ bitriseYml: '', appSecretKeys: secretData?.map(({ key }) => key) || [], From 8d9711921cb74e0b7e1645c33f9dea72939ab5cb Mon Sep 17 00:00:00 2001 From: Laszlo Moczo Date: Fri, 9 May 2025 10:35:46 +0200 Subject: [PATCH 40/40] Hack --- .../StepConfigDrawer/components/StepMaker.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx b/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx index d316138d9..ad1da2a59 100644 --- a/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/components/StepMaker.tsx @@ -1,6 +1,6 @@ /* eslint-disable react/no-array-index-key */ import { Avatar, Box, BoxProps, Button, Input, MarkdownContent, ProgressBitbot, Text } from '@bitrise/bitkit'; -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import PageProps from '@/core/utils/PageProps'; import useEnvVars from '@/hooks/useEnvVars'; @@ -69,17 +69,21 @@ const StepMaker = (props: StepMakerProps) => { const { workflowId } = useStepDrawerContext(); const [value, setValue] = useState(''); + const [token, setToken] = useState(''); const appSlug = PageProps.appSlug(); - const { data: secretData } = useSecrets({ appSlug }); + const { data: secretData, isLoading: isSecretsLoading } = useSecrets({ appSlug }); const { envs } = useEnvVars({ enabled: true, stepBundleIds: [], workflowIds: workflowId ? [workflowId] : [], }); - const token = secretData?.find(({ key }) => key === 'OPENAI_API_KEY')?.value || ''; - console.log({ appSlug, secretData, token }); + useEffect(() => { + if (!isSecretsLoading) { + setToken(secretData?.find(({ key }) => key === 'OPENAI_API_KEY')?.value || ''); + } + }, [isSecretsLoading, secretData]); const { isLoading, messages, sendMessage, reset } = useStepMakerAI({ bitriseYml: '',