App Icon

AI Cloud Master

An AI-driven cloud assistant executes AWS-related tasks using Node.js


Try Now
MIT LicenseGitHub Repository
ScreenshotScreenshotScreenshotScreenshotScreenshotScreenshotScreenshot

Customization

1. Install OpenKBS CLI:
npm install -g openkbs
2. Create and enter project directory:
mkdir woo-agent && cd woo-agent
3. Clone your app locally:
openkbs login
openkbs ls
openkbs clone <id-from-ls-output>
4. Initialize Git repository:
git init && git stage . && git commit -m "First commit"
5. Add new features:
Examples:
openkbs modify "Implement new command getBitcoinPrice(days) that returns price history from CoinGecko API" src/Events/actions.js
openkbs modify "Add instructions for getBitcoinPrice" src/Events/actions.js app/instructions.txt
6. Review changes and deploy to OpenKBS:
git diff
openkbs push
7. Test your customization:
Go to chat and ask: "Analyze the price of Bitcoin"

Disclaimer

The applications provided through OpenKBS are developmental blueprints and are intended solely as starting points for software engineers and developers. These open-source templates are not production-ready solutions.

Before any production deployment, developers must:

  • Conduct comprehensive code reviews and security audits
  • Implement robust security measures and safeguards
  • Perform extensive testing procedures
  • Ensure full compliance with applicable regulations
  • Adapt and enhance the codebase for specific use cases

NO WARRANTY DISCLAIMER: These blueprints are provided "as-is" without any warranties, whether express or implied. By using these blueprints, you assume full responsibility for all aspects of development, implementation, and maintenance of any derived applications. OpenKBS shall not be liable for any damages or consequences arising from the use of these blueprints.

Instructions and Source Code

You are an AI-driven cloud assistant. Use Node.js to generate AWS code and perform tasks based on user requests using the provided Template Functions.

Guidelines:

- Output only one Template Function per message.
Functions always export the handler function at the end.
- Once you output a function, it will be executed (as it is) and you will get the response.
- Avoid writing to the file system; if necessary, use /tmp.
- Before performing any specific operations, first explore the available resources to understand their structure, name and contents. This could involve listing available items, such as tables in a database, files in a directory, or objects in a cloud storage bucket.
- Once the resources are identified, gather detailed information about their structure and attributes. This might include describing the schema of a database table, examining metadata of files, or reviewing properties of cloud resources.
- Avoid using placeholder variables in the code. Instead, request the necessary information from the user to generate fully functional and executable code.
- When requested to work with a database, first list the tables to get the exact table names and then describe the table before the query. If this is a DynamoDB table or similar database, this also includes selecting few records to check all item attributes

Template Functions:

```javascript
const AWS = require('aws-sdk'); // require example 1
const dns = require('dns'); // require example 2
const { promisify } = require('util'); // require example 3

AWS.config.update({ region: 'us-east-1' });

const handler = async () => {
    // Generate code here to perform the desired operations and get a response.


    // code Examples:
    // return await new AWS.S3().listBuckets().promise();
   // await promisify(dns.resolveAny)('example.net')

};

module.exports = { handler };
```


List of other APIs you can call to assist the user:

/textToImage("image prompt")
Description: """
Generates  images by text prompt (English only supported).

 A good prompt needs to be detailed and specific.
 A good process is to look through a list of keyword categories and decide whether you want to use any of them.

/googleSearch("query")
Description: """
Get results from Google Search API.
"""


/webpageToText("URL")
Description: """
Use this API to extract a website to text.
"""


/documentToText("documentURL")
Description: """
Extracts text from document URL - csv, text, pdf, docx, doc, xls, xlsx, etc.
"""


/imageToText("imageURL")
Description: """
Extracts text from images and returns the result (OCR).
"""


/textToSpeech("en-US", "text to convert to speech")
Description: """
The first parameter is the language code of the voice, following the BCP-47 standard.
This function converts text to speech and plays it for the user.
"""

Events Files (Node.js Backend)

Events/actions.js


export const getActions = (meta) => {
    return [

        [/\/?textToImage\("(.*)"\)/, async (match) => {
            const response = await openkbs.textToImage(match[1], { serviceId: 'stability.sd3Medium' });
            const imageSrc = `data:${response.ContentType};base64,${response.base64Data}`;
            return { type: 'SAVED_CHAT_IMAGE', imageSrc, ...meta };
        }],


        [/\/?googleSearch\("(.*)"\)/, async (match) => {
            const q = match[1];
            const searchParams = match[2] && JSON.parse(match[2]) || {};
            try {
                const noSecretsProvided = '{{secrets.googlesearch_api_key}}'.includes('secrets.googlesearch_api_key');

                const params = {
                    q,
                    ...searchParams,
                    ...(noSecretsProvided ? {} : { key: '{{secrets.googlesearch_api_key}}', cx: '{{secrets.googlesearch_engine_id}}' }),
                };

                const response = noSecretsProvided
                    ? await openkbs.googleSearch(params.q, params)
                    : await axios.get('https://www.googleapis.com/customsearch/v1', { params });

                const data = response?.map(({ title, link, snippet, pagemap }) => ({
                    title,
                    link,
                    snippet,
                    image: pagemap?.metatags?.[0]?.["og:image"]
                }));

                return { data, ...meta };

            } catch (e) {
                return { error: e.response.data, ...meta };
            }
        }],



        [/\/?webpageToText\("(.*)"\)/, async (match) => {
            try {
                let response = await openkbs.webpageToText(match[1]);

                // limit output length
                if (response?.content?.length > 5000) {
                    response.content = response.content.substring(0, 5000);
                }

                return { data: response, ...meta };
            } catch (e) {
                return { error: e.response.data, ...meta };
            }
        }],



        [/\/?documentToText\("(.*)"\)/, async (match) => {
            try {
                let response = await openkbs.documentToText(match[1]);

                // limit output length
                if (response?.text?.length > 5000) {
                    response.text = response.text.substring(0, 5000);
                }

                return { data: response, ...meta };
            } catch (e) {
                return { error: e.response.data, ...meta };
            }
        }],



        [/\/?imageToText\("(.*)"\)/, async (match) => {
            try {
                let response = await openkbs.imageToText(match[1]);

                if (response?.detections?.[0]?.txt) {
                    response = { detections: response?.detections?.[0]?.txt };
                }

                return { data: response, ...meta };
            } catch (e) {
                return { error: e.response.data, ...meta };
            }
        }],



        [/\/?textToSpeech\("(.*)"\s*,\s*"(.*)"\)/, async (match) => {
            try {
                const response = await openkbs.textToSpeech(match[2], {
                    languageCode: match[1]
                });
                return { data: response, ...meta };
            } catch (e) {
                return { error: e.response.data, ...meta };
            }
        }],


    ];
}

Events/onRequest.js

import {getActions} from './actions.js';


export const handler = async (event) => {
    const actions = getActions({});

    for (let [regex, action] of actions) {
        const lastMessage = event.payload.messages[event.payload.messages.length - 1].content;        
        const match = lastMessage?.match(regex);        
        if (match) return await action(match);            
    }

    return { type: 'CONTINUE' }
};

Events/onResponse.js

import {getActions} from './actions.js';

export const handler = async (event) => {
    const actions = getActions({_meta_actions: ["REQUEST_CHAT_MODEL"]});

    for (let [regex, action] of actions) {
        const lastMessage = event.payload.messages[event.payload.messages.length - 1].content;        
        const match = lastMessage?.match(regex);        
        if (match) return await action(match);            
    }

    return { type: 'CONTINUE' }
};

Frontend Files (React Frontend)

Frontend/contentRender.js

import React, { useState, useEffect, useRef } from "react";
import { IconButton, Tooltip, LinearProgress } from '@mui/material';
import CloseIcon from "@mui/icons-material/Close";
import SendIcon from "@mui/icons-material/Send";
import { FileCopy, PlayArrow } from "@mui/icons-material";

// Inject custom styles
const style = document.createElement('style');
style.innerHTML = `
    .codeContainer, .codeContainer code {
        background-color: #0d0d0d !important;
        color: white !important;
        text-shadow: none !important;
        border-radius: 10px !important;
        font-size: 13px !important;
    }
    .codeContainer * {
        background-color: #0d0d0d !important;
    }
    @keyframes pulseButton {
        0% { transform: scale(1); }
        50% { transform: scale(1.5); }
        100% { transform: scale(1); }
    }
    .pulseButton {
        animation: 0.7s pulse 2;
    }
`;
document.head.appendChild(style);

// Utility function to parse JSON safely
function parseJSON(str) {
    try {
        if (str && typeof str === 'string' && (str[0] === '{' || str[0] === '[')) {
            return JSON.parse(str);
        }
    } catch {}
}

// Check if code contains ES6 syntax or JSX
export const containsES6SyntaxOrJSX = (code) => {
    const es6Patterns = [/\bimport\b/, /\bexport\b/];
    const jsxPattern = /<([A-Za-z][A-Za-z0-9]*)(\s+[A-Za-z0-9\-]+(\s*=\s*{[^}]*}|="[^"]*"|='[^']*'|=[^>\s]+)?)*\s*\/?>/;
    return es6Patterns.some(pattern => pattern.test(code)) || jsxPattern.test(code);
};

const isMobile = window.innerWidth < 960;

export function CodeViewer(props) {
    const [response, setResponse] = useState(null);
    const [codeRunLoading, setCodeRunLoading] = useState(false);
    const [isLongContent, setIsLongContent] = useState(false);
    const [prismHeight, setPrismHeight] = useState(0);
    const responseRef = useRef(null);
    const prismRef = useRef(null);
    const [tooltipOpen, setTooltipOpen] = useState(true);

    const handleTooltipClose = () => setTooltipOpen(false);
    const handleTooltipOpen = () => setTooltipOpen(true);

    const { source, onClose, styleCopy, noCopy, limitedWidth, styleClose, execute, ...restProps } = props;
    const { ReactPrismjs, APIResponseComponent, CopyToClipboard } = props.params;
    const { executeNodejs, RequestChatAPI, kbUserData, generateMsgId, msgIndex, messages, setSystemAlert, KB } = props.params;

    const isLastMessage = msgIndex >= (messages?.filter(msg => msg?.content)?.length - 1);
    const canExecute = execute && props?.language?.includes('javascript') && !containsES6SyntaxOrJSX(source);

    useEffect(() => {
        const lineCount = source ? source.split('\n').length : 0;
        setIsLongContent(lineCount > 25);
    }, [source]);

    useEffect(() => {
        if (response && responseRef.current) {
            responseRef.current.scrollIntoView({ behavior: 'smooth' });
        }
    }, [response]);

    useEffect(() => {
        if (prismRef.current) {
            setPrismHeight(prismRef.current.clientHeight);
        }
    }, [source]);

    const runCode = async () => {
        try {
            const code = source.includes('module.exports')
                ? source
                : `const handler = async (event) => { ${source} }; module.exports = { handler };`;
            setCodeRunLoading(true);
            const response = await executeNodejs(code, KB);
            setCodeRunLoading(false);
            setResponse(response?.data?.error ? JSON.stringify(response.data) : JSON.stringify(response.data));
        } catch (error) {
            setCodeRunLoading(false);
            setResponse(`Error: ${error?.response?.status || error.message}`);
        }
    };

    const preStyle = {
        whiteSpace: limitedWidth ? 'pre-wrap' : undefined,
        wordBreak: limitedWidth ? 'break-all' : undefined,
        ...restProps.style
    };
    const colorIcon = '#e7e7e7';
    const oneliner = !(source?.split?.('\n')?.length > 1);

    const handleOnCopy = () => {
        setSystemAlert && setSystemAlert({ msg: 'Copied to clipboard', type: 'success', duration: 1500 });
        props?.onCopy && props.onCopy();
    };

    const formattedResponse = parseJSON(response) || { response }

    return (
        <div style={{ paddingBottom: 2, position: 'relative', display: 'inline-block', maxWidth: '100%', minWidth: '350px', overflowX: 'auto' }}>
            {oneliner && <div style={{ height: '40px' }}></div>}
            {onClose && (
                <IconButton onClick={onClose} style={styleClose}>
                    <CloseIcon style={{ opacity: 0.7, fontSize: '1rem', color: colorIcon }} />
                </IconButton>
            )}
            <div ref={prismRef}>
                <ReactPrismjs {...{ source, style: preStyle, ...restProps }} />
            </div>
            {!codeRunLoading && (
                <>
                    <CopyToClipboard text={source || ''}>
                        <IconButton onClick={handleOnCopy} style={{ position: 'absolute', top: `${prismHeight - 30}px`, right: '0px', zIndex: 10, color: 'rgba(11, 11, 11, 0.54)' }}>
                            {!noCopy && <FileCopy style={{ opacity: 0.7, color: colorIcon }} />}
                        </IconButton>
                    </CopyToClipboard>
                    {canExecute && (
                        <Tooltip title="Execute" arrow onMouseEnter={handleTooltipClose} onMouseLeave={handleTooltipOpen} open={isLastMessage && !response && !codeRunLoading && tooltipOpen} placement={"top"}>
                            <IconButton onClick={runCode} style={{ position: 'absolute', top: `${prismHeight - 30}px`, right: '36px', animation: isLastMessage && !response && !codeRunLoading && 'pulseButton 1.5s infinite', ...styleCopy }}>
                                <PlayArrow style={{ color: '#e7e7e7' }} />
                            </IconButton>
                        </Tooltip>
                    )}
                </>
            )}
            {codeRunLoading ? (
                <LinearProgress />
            ) : (
                response && (
                    <div ref={responseRef} style={{ position: 'relative', paddingTop: '30px' }}>
                        <APIResponseComponent open={false} showResponseSize={true} JSONData={formattedResponse} enableClipboard={true} />
                        <>
                            <CopyToClipboard text={response || ''}>
                                <IconButton onClick={handleOnCopy} style={{ position: 'absolute', top: '35px', right: '0px' }}>
                                    <FileCopy style={{ opacity: 0.7, color: '#3D86C9' }} />
                                </IconButton>
                            </CopyToClipboard>
                            {isLastMessage && (
                                <Tooltip title="Send Response" arrow open={isLastMessage} placement={"top"}>
                                    <IconButton onClick={async () => {
                                        await RequestChatAPI([...messages, {
                                            role: 'user',
                                            content: typeof response === 'number' ? response.toString() : response,
                                            userId: kbUserData().chatUsername,
                                            msgId: generateMsgId()
                                        }]);
                                    }} style={{ position: 'absolute', top: '35px', right: '36px', animation: 'pulseButton 1.5s infinite' }}>
                                        <SendIcon style={{ opacity: 0.9, color: '#3D86C9' }} />
                                    </IconButton>
                                </Tooltip>
                            )}
                        </>
                    </div>
                )
            )}
        </div>
    );
}

const onRenderChatMessage = async (params) => {
    const { content } = params.messages[params.msgIndex];
    if (content.match(/```/)) {
        let language = null;
        const output = [];

        content.split('\n').forEach(line => {
            if (!language) {
                language = /```(?<language>\w+)/g.exec(line)?.groups?.language;
                output.push(language ? { language, code: '' } : line);
            } else if (line.match(/```/)) {
                language = null;
            } else {
                output[output.length - 1].code += line + '\n';
            }
        });

        return output.map((o, i) =>
            typeof o === 'string'
                ? <p key={`a${i}`} style={{ marginTop: '0px', marginBottom: '0px' }}>{o}</p>
                : <div key={`a${i}`}>
                    <CodeViewer
                        key={`ab${i}`}
                        params={{ ...params, i }}
                        limitedWidth={isMobile}
                        execute={true}
                        className="codeContainer"
                        language={o.language}
                        source={o.code}
                    />
                </div>
        );
    }
}

const exports = { onRenderChatMessage };
window.contentRender = exports;
export default exports;

Frontend/contentRender.json

{
  "dependencies": {
    "react": "^18.2.0 (fixed)",
    "react-dom": "^18.2.0 (fixed)",
    "@mui/material": "^5.16.1 (fixed)",
    "@mui/icons-material": "^5.16.1 (fixed)",
    "@emotion/react": "^11.10.6 (fixed)",
    "@emotion/styled": "^11.10.6 (fixed)"
  }
}