commit a906249f7b838f9529409b5477d3c57d7828a362 Author: raidendotai Date: Thu Sep 19 01:30:28 2024 +0100 hello world diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b512c09 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..63b4b68 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) [year] [fullname] + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..0e8359c --- /dev/null +++ b/README.md @@ -0,0 +1,149 @@ +[img] + +# cofounder : early alpha release + +* project - [cofounder.openinterface.ai](https://cofounder.openinterface.ai) +* 👋 [@n_raidenai](https://x.com/n_raidenai) + +**cofounder** +- full stack generative web apps ; backend + db + stateful web apps +- gen ui rooted in app architecture, with ai-guided mockup designer & modular design systems + +[demo] + +--- + +## Important + +**Early alpha release ; earlier than expected by 5/6 weeks** + +Still not merged with key target features of the project, notably : +- project iteration modules for all dimensions of generated projects +- admin interface for event streams and (deeper) project iterations +- integrate the full genUI plugin : + * generative design systems + * deploy finetuned models & serve from api.cofounder +- local, browser-based dev env for the entire project scope +- add { react-native , flutter , other web frameworks } +- validations & swarm code review and autofix +- code optimization +- [...] + +Be patient :) + +--- + +# Usage + +## Install & Init + +* Open your terminal and run + +```sh +npx @openinterface/cofounder -p "YourAppProjectName" -d "describe your app here" -a "(optional) design instructions" +``` + +Follow the instructions. The installer +- will ask you for your keys +- setup dirs & start installs +- will start the local `cofounder/api` builder and server +- will start generating your app 🎉 + +``` +note : +you will be asked for a cofounder.openinterface.ai key +it is recommended to use one as it enables the designer/layoutv1 and swarm/external-apis features +and can be used without limits during the current early alpha period + +the full index will be available for local download on v1 release +``` + +## Run + +Your backend & vite+react web app will incrementally generate inside `./apps/{YourApp}` +Open your terminal in `./apps/{YourApp}` and run + +```sh +npm i && npm run dev +``` + +It will start both the backend and vite+react, concurrently, after installing their dependencies +Go to `http://localhost:5173/` to open the web app 🎉 + +## Notes + +### Local API + +If you resume later and would like to iterate on your generated apps, +the local `./cofounder/api` server needs to be running to receive queries + +You can (re)start the `local cofounder API` running the following command from `./cofounder/api` + +```sh +npm run start +``` + +You can also generate new apps from the same env by running, from `./cofounder/api`, one of these command + +```sh +npm run start -- -p "ProjectName" -f "some app description" -a "minimalist and spacious , light theme" +npm run start -- -p "ProjectName" -f "./example_description.txt" -a "minimalist and spacious , light theme" +``` + +### Concurrency + +**[the architecture will be further detailed and documented later]** + +Every "node" in the `cofounder` architecture has a defined configuration under `./cofounder/api/system/structure/nodes/{category}/{name}.yaml` to handle things like concurrency, retries and limits per time interval + +For example, if you want multiple LLM generations to run in parallel (when possible - sequences and parallels are defined in DAGS under `./cofounder/api/system/structure/sequences/{definition}.yaml` ), +go to + +```yaml +#./cofounder/api/system/structure/nodes/op/llm.yaml +nodes: + op:LLM::GEN: + desc: "..." + in: [model, messages, preparser, parser, query, stream] + out: [generated, usage] + queue: + concurrency: 1 # <------------------------------- here + op:LLM::VECTORIZE: + desc: "{texts} -> {vectors}" + in: [texts] + out: [vectors, usage] + mapreduce: true + op:LLM::VECTORIZE:CHUNK: + desc: "{texts} -> {vectors}" + in: [texts] + out: [vectors, usage] + queue: + concurrency: 50 +``` + +and change the `op:LLM::GEN` parameter `concurrency` to a higher value + +The default LLM concurrency is set to `1` so you can see what's happening in your console streams step by step - but you can increment it to `5`-`8` + +--- + +# Docs, Design Systems, ... + +**[WIP]** + +--- + +# Architecture + +[img] + +--- + +# Some Credits + +- Cover art edited from image found in [patriciaklein.de](https://patriciaklein.de) +- Demo design systems built using Figma renders / UI kits from: + * blocks.pm by Hexa Plugin (see `cofounder/api/system/presets`) + * google material + * figma core + * shadcn \ No newline at end of file diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..0876c63 --- /dev/null +++ b/TODO.md @@ -0,0 +1,49 @@ +A non-ordered roadmap & todo dump +will update with proper map later, ignore for now + +--- + +## nearest +merge with browser-based local dev env using webcontainers ; console.cofounder.openinterface.ai + +## validation, errorfix +post-generation validation swarm modules +swarm autofix modules, merge +babel parse + +## build, deploy +vite plugin to generate web app without the cofounder modules +generate packed projects ready for deployment +automate deployments, integrate different services + +## design, layouts +plug in advanced designer + models +document how to custom design systems +add & index docs for shiny design systems +fonts / css modules +release extensive cofounder index on api access for layout designer to use +separate {desktop,mobile} in designer +RAG on icons via {text/clip} (like in openv0), maybe as a vite plugin + +## functional +deploy latest index checkpoint in api.cofounder + +## mgmt +more iteration modules, sequences to handle full lifecycle of generated projects +document everything + +## platforms +react-native / flutter +more frontend frameworks + +## project +technical articles +model train & serve from api +admin panel à la coolify + +## also +SEO stuff +analytics into the dev feedback +animation (ie. framer-motion) +functional, api, python support api-side +benchmarks \ No newline at end of file diff --git a/apps/README.md b/apps/README.md new file mode 100644 index 0000000..e19fff2 --- /dev/null +++ b/apps/README.md @@ -0,0 +1,8 @@ +## How to start apps + +Your backend & vite+react web app will incrementally generate inside `./apps/{YourApp}` +Open your terminal in `./apps/{YourApp}` and run + +```sh +npm i && npm run dev +``` diff --git a/cofounder/api/.env b/cofounder/api/.env new file mode 100644 index 0000000..c066c9f --- /dev/null +++ b/cofounder/api/.env @@ -0,0 +1,34 @@ +PORT = 667 + +OPENAI_API_KEY = "REPLACE_WITH_OPENAI_KEY" +ANTHROPIC_API_KEY = "REPLACE_WITH_ANTHROPIC_KEY" +COFOUNDER_API_KEY = "REPLACE_WITH_COFOUNDER.OPENINTERFACE.AI_KEY" + +# llm, can be 'ANTHROPIC' (for claude sonnet 3.5) or 'OPENAI' (uses diff. models for diff. passes) +# make sure there are matching api keys +LLM_PROVIDER = "ANTHROPIC" #"OPENAI" + +# should be kept to "text-embedding-3-small" to work with RAG using api.cofounder.openinterface.ai +EMBEDDING_MODEL = "text-embedding-3-small" + +# RAG from index (from api.cofounder.openinterface.ai ) +# enables features from { designer, swarm{externalapis} , ... } +# recommended to keep ; after alpha , in v1 release , big index will be release & downloadable to local +RAG_REMOTE_ENABLE = TRUE + +STATE_LOCAL = TRUE # persist locally +AUTOEXPORT_ENABLE = TRUE # writes generated app files on each increment ; keep , necessary now +AUTOINSTALL_ENABLE = TRUE # runs "npm i" on exported projects whenever dependencies from generated code change +EXPORT_APPS_ROOT = "../../apps" + +# these triggers the design system guided designer, generates a mockup layout image before implementing code +DESIGNER_ENABLE = TRUE +DESIGNER_DESIGN_SYSTEM = "presets/shadcn" #"presets/shadcn" + +# enables : code review after code generated , augment features like searching for external apis to implement in server , ... +SWARM_ENABLE = TRUE + +#STATE_CLOUD = TRUE # persist on cloud (firebase + cloudstorage) +#FIREBASE_SERVICE_KEY_PATH = "./firebase-service-key-p0dev.json" +#GOOGLECLOUDSTORAGE_SERVICE_KEY_PATH = "openv0-aa83086a03e1.json" +#GOOGLECLOUDSTORAGE_BUCKET = "uiray" \ No newline at end of file diff --git a/cofounder/api/.gitignore b/cofounder/api/.gitignore new file mode 100644 index 0000000..18a2a5b --- /dev/null +++ b/cofounder/api/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +db/ +dump/ \ No newline at end of file diff --git a/cofounder/api/.prettierignore b/cofounder/api/.prettierignore new file mode 100644 index 0000000..da4957d --- /dev/null +++ b/cofounder/api/.prettierignore @@ -0,0 +1,3 @@ +db/ +dump/ +node_modules/ \ No newline at end of file diff --git a/cofounder/api/.prettierrc b/cofounder/api/.prettierrc new file mode 100644 index 0000000..79a1682 --- /dev/null +++ b/cofounder/api/.prettierrc @@ -0,0 +1,4 @@ +{ + "tabWidth": 1, + "useTabs": true +} diff --git a/cofounder/api/README.md b/cofounder/api/README.md new file mode 100644 index 0000000..e69de29 diff --git a/cofounder/api/build.js b/cofounder/api/build.js new file mode 100644 index 0000000..c84f6e4 --- /dev/null +++ b/cofounder/api/build.js @@ -0,0 +1,348 @@ +import fs from "fs"; +import path from "path"; +import yaml from "yaml-js"; +import yml from "yaml"; +import { merge, fromPairs } from "lodash-es"; +import retry from "async-retry"; +import pqueue from "p-queue"; +import { EventEmitter } from "node:events"; +import { promisify } from "util"; +import { readdir } from "fs"; +import delay from "delay"; + +const functionsDir = `./system/functions`; +const unitsDir = `./system/structure`; +const LOGS_ENABLED = true; + +async function build({ system }) { + console.dir({ build: system.functions }); + + if (!system.nodes) system.nodes = {}; + if (!system.functions) system.functions = {}; + if (!system.sequences) system.sequences = {}; + + const queues = {}; + const events = { + main: new EventEmitter(), + log: { + node: new EventEmitter(), + sequence: new EventEmitter(), + }, + }; + + if (LOGS_ENABLED) { + events.log.node.on(`enqueue`, ({ id, context, data }) => { + console.log( + `\x1b[36mlog:enqueue: node:${id}\t${JSON.stringify({ context, data }).slice(0, 150)}\x1b[0m`, + ); + }); + events.log.node.on(`start`, ({ id, context, data }) => { + console.log( + `\x1b[33mlog:start: node:${id}\t${JSON.stringify({ context, data }).slice(0, 150)}\x1b[0m`, + ); + }); + events.log.node.on(`end`, ({ id, context, data, response }) => { + console.log( + `\x1b[32mlog:complete: node:${id}\t${JSON.stringify({ context, response, data }).slice(0, 150)}\x1b[0m`, + ); + }); + } + + system.run = async ({ id, context, data }) => { + // console.dir({ __debug__system__run : { input : { id, context, data }, system_nodes: system.nodes, } }) + try { + return await system.nodes[id].run({ context, data }); + } catch (err) { + console.dir({ SYSTEM_RUN_ERR: { err, id } }); + } + }; + + events.main.on(`run`, async ({ id, context, data }) => { + if (LOGS_ENABLED) { + console.log(`\x1b[31mevent:\`run\` →id:${id}\x1b[0m`); + } + await system.run({ id, context, data }); + }); + + system.nodes = fromPairs( + await Promise.all( + Object.keys(system.functions) + .filter((id) => Object.keys(system.nodes).includes(id)) + .map(async (id) => { + queues[id] = new pqueue({ + concurrency: parseInt(system.nodes[id].queue?.concurrency) || Infinity, + intervalCap: + parseInt(system.nodes[id].queue?.interval?.limit) || Infinity, + interval: parseInt(system.nodes[id].queue?.interval?.time) || 0, + timeout: parseInt(system.nodes[id].queue?.timeout) || undefined, + }); + // this is the function to be ran + const fn = async ({ context = {}, data = {} }) => { + events.log.node.emit(`enqueue`, { id, context, data }); + return await queues[id].add(async () => { + events.log.node.emit(`start`, { id, context, data }); + const response = await retry( + async (bail) => { + try { + const fnresponse = await system.functions[id]({ + context: { ...context, run: system.run }, + data: system.nodes[id].in?.length + ? system.nodes[id].in.reduce( + (acc, inp) => ({ ...acc, [inp]: data[inp] || null }), + {}, + ) // higher perf than fromPairs ? + : data, + }); + + return !fnresponse + ? { success: false } + : system.nodes[id].out?.length + ? system.nodes[id].out.reduce( + (acc, inp) => ({ ...acc, [inp]: fnresponse[inp] || null }), + {}, + ) + : fnresponse; + } catch (error) { + console.dir({ asyncretry_error: { id, error } }, { depth: null }); + throw new Error(error); + } + }, + { + retries: parseInt(system.nodes[id].queue?.retry) || 5, + }, + ); + events.log.node.emit(`end`, { id, context, data, response }); + return response; + }); + }; + + return [ + id, + { + type: `node`, + meta: system.nodes[id], + run: fn, + }, // to have same format as sequence : system.sequences[id].run and system.functions[id].run + ]; + }), + ), + ); + /* + make the DAG graph decomposition parallelizor from the system and relations + handle : seq , parallel , recursion too ! + */ + /* + event registration for system triggers (nodes are all registered for events node:{id} ) + */ + + if (LOGS_ENABLED) { + events.log.sequence.on(`sequence:start`, ({ id, context, data }) => { + console.log( + `\x1b[34mlog:start: sequence:${id}\t${JSON.stringify({ context, data }).slice(0, 150)}\x1b[0m`, + ); + }); + events.log.sequence.on( + `sequence:step:start`, + ({ id, index, over, context, data }) => { + console.log( + `\x1b[34mlog:start: sequence:${id}:step:${index}/${over - 1}\t${JSON.stringify({ context, data }).slice(0, 150)}\x1b[0m`, + ); + }, + ); + events.log.sequence.on( + `sequence:step:end`, + ({ id, index, over, context, data }) => { + console.log( + `\x1b[35mlog:done: sequence:${id}:step:${index}/${over - 1}\t${JSON.stringify({ context, data }).slice(0, 150)}\x1b[0m`, + ); + }, + ); + events.log.sequence.on(`sequence:end`, ({ id, context, data }) => { + console.log( + `\x1b[35mlog:done: sequence:${id}\t${JSON.stringify({ context, data }).slice(0, 150)}\x1b[0m`, + ); + }); + } + + async function makeDags() { + // need to implement recursion cases next ! + return fromPairs( + Object.keys(system.sequences).map((sequenceId) => { + const inDegree = {}, + adjList = {}; + const seq = system.sequences[sequenceId]; + const dag = fromPairs( + system.sequences[sequenceId].nodes.map((nodeId) => { + return [ + nodeId, + { + parents: !seq.relations?.parents + ? [] + : !seq.relations?.parents[nodeId]?.length + ? [] + : seq.relations.parents[nodeId], + }, + ]; + }), + ); + Object.keys(dag).forEach((node) => { + inDegree[node] = 0; + adjList[node] = []; + }); + Object.entries(dag).forEach(([node, { parents }]) => { + if (parents) { + parents.forEach((parent) => { + if (!adjList[parent]) { + console.error( + `build:DAG : parent node ${parent} of node ${node} not found in DAG - skipping dependency`, + ); + } else { + adjList[parent].push(node); + inDegree[node]++; + } + }); + } + }); + const queue = Object.keys(inDegree).filter((node) => inDegree[node] === 0); + const sequence = [], + visitedNodes = new Set(); + while (queue.length) { + const currentLevel = queue.splice(0, queue.length); + currentLevel.forEach((node) => { + visitedNodes.add(node); + adjList[node].forEach((neighbor) => { + if (--inDegree[neighbor] === 0) queue.push(neighbor); + }); + }); + sequence.push(currentLevel); + } + if (visitedNodes.size !== Object.keys(dag).length) { + console.dir({ dag, visitedNodes }, { depth: null }); + throw new Error("The provided DAG has cycles or unresolved dependencies"); + } + + // later ; update for logging etc + const run = async ({ context, data }) => { + events.log.sequence.emit(`sequence:start`, { + id: sequenceId, + context, + data, + }); + const sequenceLength = sequence.length; + for (const s of sequence.entries()) { + const [index, step] = s; + events.log.sequence.emit(`sequence:step:start`, { + id: sequenceId, + index, + over: sequenceLength, + context, + data, + }); + await Promise.all( + step.map(async (parallelfnId) => { + const response = await system.run({ + id: parallelfnId, + context: { ...context, run: system.run }, + data, + }); + data = merge(data, response); + }), + ); + events.log.sequence.emit(`sequence:step:end`, { + id: sequenceId, + index, + over: sequenceLength, + context, + data, + }); + } + events.log.sequence.emit(`sequence:end`, { + id: sequenceId, + context, + data, + }); + return data; + }; + if (system.sequences[sequenceId].triggers?.length) { + system.sequences[sequenceId].triggers.map((triggerevent) => { + events.main.on(triggerevent, async ({ context, data }) => { + if (LOGS_ENABLED) { + console.log( + `\x1b[31mevent:\`${triggerevent}\` →sequence:${sequenceId}\x1b[0m`, + ); + } + await run({ context, data }); + }); + }); + } + return [ + sequenceId, + { + type: `sequence`, + meta: { + ...system.sequences[sequenceId], + dag: sequence, + }, + run, + }, + ]; + }), + ); + } + system.nodes = { + ...system.nodes, + ...(await makeDags()), + }; + + system.queues = queues; + system.events = { + events, + new: async ({ event, context = {}, data = {} }) => { + events.main.emit(event, { context, data }); + }, // trigger events + run: async ({ id = false, context = {}, data = {} }) => { + events.main.emit(`run`, { id, context, data }); + }, // run node/seq events + }; + + return system; +} + +const readdirAsync = promisify(readdir); +async function getFilesRecursively(dir, ext) { + let results = []; + const list = await readdirAsync(dir, { withFileTypes: true }); + for (const file of list) { + const filePath = path.join(dir, file.name); + if (file.isDirectory()) { + results = results.concat(await getFilesRecursively(filePath, ext)); + } else if (file.name.endsWith(ext)) { + results.push(filePath); + } + } + return results; +} +const system = await build({ + system: { + functions: merge( + {}, + ...(await Promise.all( + (await getFilesRecursively(functionsDir, ".js")).map((file) => + import(`./${file}`).then((m) => m.default), + ), + )), + ), + ...merge( + {}, + ...(await Promise.all( + (await getFilesRecursively(unitsDir, ".yaml")).map((file) => + yaml.load(fs.readFileSync(`./${file}`, `utf-8`).toString()), + ), + )), + ), + }, +}); + +export default { + system, +}; diff --git a/cofounder/api/package.json b/cofounder/api/package.json new file mode 100644 index 0000000..e61f5a6 --- /dev/null +++ b/cofounder/api/package.json @@ -0,0 +1,42 @@ +{ + "type": "module", + "aliases": { + "@": "." + }, + "scripts": { + "start": "node --loader esm-module-alias/loader --no-warnings server", + "start:npx": "npm i && node --loader esm-module-alias/loader --no-warnings server", + "dev:build": "node --loader esm-module-alias/loader --no-warnings build" + }, + "dependencies": { + "@anthropic-ai/sdk": "^0.27.3", + "@google-cloud/storage": "^7.12.1", + "@resvg/resvg-js": "^2.6.2", + "async-retry": "^1.3.3", + "cloudconvert": "^2.3.7", + "colormap": "^2.3.2", + "cors": "^2.8.5", + "deepmerge": "^4.3.1", + "delay": "^6.0.0", + "dotenv": "^16.4.5", + "esm-module-alias": "^2.2.0", + "express": "^4.19.2", + "firebase-admin": "^12.4.0", + "firestore": "^1.1.6", + "fs-extra": "^11.2.0", + "js-yaml": "^4.1.0", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "module-alias": "^2.2.3", + "openai": "^4.55.4", + "p-all": "^5.0.0", + "p-queue": "^8.0.1", + "sharp": "^0.33.4", + "slugify": "^1.6.6", + "vectra": "^0.9.0", + "xml2js": "^0.6.2", + "yaml": "^2.5.0", + "yaml-js": "^0.3.1", + "yargs": "^17.7.2" + } +} diff --git a/cofounder/api/server.js b/cofounder/api/server.js new file mode 100644 index 0000000..eb4b18a --- /dev/null +++ b/cofounder/api/server.js @@ -0,0 +1,338 @@ +import express from "express"; +import cors from "cors"; +import dotenv from "dotenv"; +import yargs from "yargs"; +import fs from "fs"; +import { hideBin } from "yargs/helpers"; +import { merge } from "lodash-es"; +import cofounder from "./build.js"; +dotenv.config(); + +function _slugify(text) { + return text + .toString() + .toLowerCase() + .replace(/\s+/g, "-") // Replace spaces with - + .replace(/[^\w\-]+/g, "") // Remove all non-word chars + .replace(/\-\-+/g, "-") // Replace multiple - with single - + .replace(/^-+/, "") // Trim - from start + .replace(/-+$/, ""); // Trim - from end +} + +// init project from argv +// to be called like : npm run start -- --p "some-project-name" --d "app description right" +const timestamp = Date.now(); +const argv = yargs(hideBin(process.argv)).argv; +const newProject = { + project: + (!argv.p && !argv.project) || + _slugify(argv.p || argv.project).length === 0 || + !_slugify(argv.p || argv.project).match(/[a-z0-9]/) + ? `project-${timestamp}` + : _slugify(argv.p || argv.project), + description: argv.description || argv.d || argv.desc || false, + aesthetics: argv.aesthetics || argv.a || argv.aesthetic || false, +}; +if (argv.file || argv.f) { + newProject.description = fs.readFileSync(argv.file || argv.f, "utf-8"); +} +async function createNewProject() { + if (!newProject.description.length) { + console.error( + 'Error: -d "project description" is required and cannot be empty.', + ); + process.exit(1); + } + console.log( + `\x1b[31minitialized generating app : ${newProject.project}\x1b[0m`, + ); + console.log( + `\x1b[34m(see ${process.env.EXPORT_APPS_ROOT}/${newProject.project}/README.md for more details)\x1b[0m` + + `\n\x1b[38;5;37mto start app (api+frontend in parallel)` + + `\n\t> cd ${process.env.EXPORT_APPS_ROOT}/${newProject.project}` + + `\n\t> npm i && npm run dev\x1b[0m`, + ); + + const query = { + pm: { + details: { + text: `${newProject.project != `project-${timestamp}` ? "Project '" + newProject.project + "' :" : ""} ${newProject.description}`, + attachments: [], + design: { + aesthetics: { + text: newProject.aesthetics, + }, + }, + }, + }, + }; + console.dir({ query }, { depth: null }); + + /* + // debug : to resume ---------------------------------------------------------- + const data = await cofounder.system.run({ + id: "op:PROJECT::STATE:LOAD", + context: { + project: newProject.project, + }, + data: {}, + }); + await cofounder.system.run({ + id: `seq:project:init:v1:resume`, + context: { + project: newProject.project, + }, + data: merge(data, { + ...query, + debug: {}, + }), + }); + ---------------------------------------------------------- + */ + + await cofounder.system.run({ + id: `seq:project:init:v1`, + context: { + project: newProject.project, + }, + data: query, + }); +} + +// Call createNewProject if command args for init project are provided +if (newProject.project && newProject.description) { + createNewProject(); +} + + +const app = express(); +const PORT = process.env.PORT || 667; + +app.use(cors()); +app.use(express.json({ limit: "5mb" })); + +/* +app.post("/project/init", async (req, res) => { + try { + // see docs for steps + res.status(200).json({ end: true }); + } catch (error) { + console.error(error); + res.status(500).json({ error: "failed to init project" }); + } +}); +*/ + +const actions = { + // map action to function ; load means load project state before passing + "update:settings:preferences:versions": { + fn: _updateProjectPreferences, + load: false, + }, + "regenerate:ui": { fn: _regenerateUiComponent, load: true }, + "iterate:ui": { fn: _iterateUiComponent, load: true }, + /* + later, single universal interface approach, + > should go through an analysis sequence ; + ie. is is a new feature that needs db schemas & apis to be altered, or just at the layout level, etc + */ +}; +const actionsKeys = Object.keys(actions); + +app.post("/project/actions", async (req, res) => { + /* + in : { + project : `exampleproject`, + query : { + action : "example:action:whatever", + data : { + }, + }, + } + */ + console.dir( + { "cofounder:api:server:actions:debug": req.body }, + { depth: null }, + ); + try { + const { project, query } = req.body; + const { action } = query; + if (!actionsKeys.includes(action)) { + throw new Error(`action ${action} not recognized`); + } + const { fn, load } = actions[action]; + const data = await fn({ + request: { project, query }, + data: !load + ? {} + : await cofounder.system.run({ + id: "op:PROJECT::STATE:LOAD", + context: { + project, + }, + data: {}, + }), + }); + res.status(200).json({ end: true }); + } catch (error) { + console.error(error); + res.status(500).json({ error: "failed to process" }); + } +}); + +app.listen(PORT, () => { + console.log( + "\x1b[32m\ncofounder/api : server is running on port " + PORT + "\x1b[0m", + ); +}); + +// ------------ helpers -------------------------------------------------------- +async function _updateProjectPreferences({ request }) { + /* + in : { + project : `exampleproject`, + query : { + action : "example:action:whatever", + data : { + [views || sections] : { + [id] : {version} + } + }, + }, + } + */ + const { project, query } = request; + await cofounder.system.run({ + id: "op:PROJECT::STATE:UPDATE", + context: { project }, + data: { + operation: { + id: `settings:preferences:versions`, + }, + type: `end`, + content: { + key: `settings.preferences.versions`, + data: query.data, + }, + }, + }); +} +async function _regenerateUiComponent({ request, data }) { + const { project, query } = request; + /* + in : request: { + project : `exampleproject`, + query : { + action : "regenerate:ui" + data : { + [views || sections] : `{id}`, // <--- update : sections stuff removed, is views only (for now) + }, + }, + } + */ + + const type = Object.keys(query.data)[0]; + const id = query.data[type]; + + /* + need to make : + task { + type: "view", + view: { + type: unique || shared, + id, + }, + passes: { + functional: true, + redesign: process.env.DESIGNER_ENABLE + ? JSON.parse(process.env.DESIGNER_ENABLE.toLowerCase()) + : true, + } + } + */ + const task = { + type: "view", + view: { + type: id.startsWith(`UV_`) ? `unique` : `shared`, + id, + }, + passes: { + functional: true, + redesign: process.env.DESIGNER_ENABLE + ? JSON.parse(process.env.DESIGNER_ENABLE.toLowerCase()) + : false, + }, + }; + console.dir({ "debug:server:task:regen:ui": { request, task } }); + await cofounder.system.run({ + id: "WEBAPP:VIEW::GENERATE", + context: { project }, + data: { + ...data, + task, + }, + }); +} + +async function _iterateUiComponent({ request, data }) { + console.dir({ "cofounder:api:server:iterate:ui": "starts" }); + /* + designer/layoutv1 might be overkill, but its best way to have primitives to retrieve design system docs (if applies) + + */ + + /* + in : { + project: meta.project, + query: { + action: "iterate:ui", + data: { + views : { + [id] : { + [version] : { + user : { + text: editUserText, + attachments: [], // later, can attach image + }, + screenshot: { base64: image ? image : false}, + designer: bool + } + }, + } + }, + }, + }), + } + */ + const { project, query } = request; + const id = Object.keys(query.data.views)[0]; + const version = Object.keys(query.data.views[id])[0]; + const { notes, screenshot, designer } = query.data.views[id][version]; + + const task = { + type: "view", + view: { + type: id.startsWith(`UV_`) ? `unique` : `shared`, + id, + version, + }, + iteration: { + notes, // {text,attachements} + screenshot, // {base64 : "base64str" || false } + designer: process.env.DESIGNER_ENABLE + ? JSON.parse(process.env.DESIGNER_ENABLE.toLowerCase()) && designer + ? true + : false + : false, + }, + }; + console.dir({ "debug:server:task:regen:ui": { request, task } }); + await cofounder.system.run({ + id: "WEBAPP:VIEW::ITERATE", + context: { project }, + data: { + ...data, + task, + }, + }); +} \ No newline at end of file diff --git a/cofounder/api/system/functions/backend/asyncapi.js b/cofounder/api/system/functions/backend/asyncapi.js new file mode 100644 index 0000000..ea31a68 --- /dev/null +++ b/cofounder/api/system/functions/backend/asyncapi.js @@ -0,0 +1,171 @@ +import utils from "@/utils/index.js"; +import yaml from "yaml"; + +async function backendAsyncapiDefine({ context, data }) { + if (!data.backend.requirements?.realtimeWebsockets?.required) { + await context.run({ + id: "op:PROJECT::STATE:UPDATE", + context, + data: { + operation: { + id: "backend:specifications:asyncapi", + }, + type: `end`, + content: { + key: "backend.specifications.asyncapi", + data: {}, + }, + }, + }); + return { + backend: { + specifications: { + asyncapi: {}, + }, + }, + }; + } + + const { pm, db, backend } = data; + const { prd, frd, drd, brd } = pm; + const messages = [ + { + role: "system", + content: `- you are a genius Product Manager & Software Architect & Backend designer +- your role is to make the backend asyncAPI specs for the realtime features of the provided task + +- your asyncAPI specs should be comprehensive, and include schema object for each case, +which will be used as references to build the frontend app connected to the backend + +- cover all cases ; data-related tasks only (ie. you are making a mock backend for user-facing data operations) + +- do a thorough analysis of the provided task + +- think from perspectives of multiple personas, put yourself in situation, to make sure your asyncAPI definition is fully comprehensive and ready to be used in production exactly as is + +- ask yourself: + * what are all the events & schemas required by features expected to be seen by users in the frontend ? + +- ask yourself: + * what are all the events & schemas required by features expected to be seen by users in the app ? +- your aim is to cover all realtime use cases, as the expert product manager & architect you are + +--- + +the root dev url for the server is "http://localhost:1337" ; you can specify that in specs + +--- + +give a final, super comprehensive answer in strict, parseable asyncAPI YAML format +which will be perfectly ready to plug into the backend in development, +and pushed to staging directly and work flawlessly + +it should be comprehensive for the needs required by all the realtime events described in the provided docs +answer in strict parseable asyncAPI in YAML format, +with all schemas, for all scenarios ; - and specifying cases when a given schema field is required + +super important : +> methods, routes, operationIds, and components (parameters and components) only +> no input/output examples objects ! +> you are only to detail realtime events and their schemas for realtime features described in the provided documents ! + +--- + +important : +use snake_case for any naming you do + +--- + +your reply will be directly transferred as the final asyncAPI structure for the realtime events part of the backend, +so do not put anything else in your reply besides the asyncAPI structure that details the realtime events parts of the backend only ! +your reply should start with : "\`\`\`yaml" and end with "\`\`\`" + + +you will be tipped $99999 + major company shares for nailing it perfectly off the bat`, + }, + { + role: "user", + content: `\`\`\`PRD:product-requirements-document +${prd} +\`\`\``, + }, + { + role: "user", + content: `\`\`\`PRD:product-requirements-document +${frd} +\`\`\``, + }, + { + role: "user", + content: `\`\`\`DRD:database-requirements-document +${drd} +\`\`\` + +--- + +\`\`\`DB:schemas +${yaml.stringify({ schemas: db.schemas })} +\`\`\` + +`, + }, + { + role: "user", + content: `\`\`\`BRD:Backend-requirements-document +${brd} +\`\`\``, + }, + { + role: "user", + content: `implement the asyncAPI structure , for the realtime features specified in the provided documents +super important : +- your only focus is to make the asyncAPI for realtime events and their details , not anything else (such as a REST API ...) +- asyncAPI for realtime events and their details only ! + +it is expected to be very comprehensive and detailed ; in a VALID PARSEABLE YAML format + +you're a genius`, + }, + ]; + + const asyncapiStructure = ( + await context.run({ + id: "op:LLM::GEN", + context, + data: { + model: `chatgpt-4o-latest`, //`gpt-4o`, + messages, + preparser: `backticks`, + parser: `yaml`, + }, + }) + ).generated; + + await context.run({ + id: "op:PROJECT::STATE:UPDATE", + context, + data: { + operation: { + id: "backend:specifications:asyncapi", + }, + type: `end`, + content: { + key: "backend.specifications.asyncapi", + data: asyncapiStructure, + }, + }, + }); + + return { + backend: { + ...data.backend, + specifications: { + asyncapi: asyncapiStructure, + }, + }, + }; +} + +export default { + "BACKEND:ASYNCAPI::DEFINE": backendAsyncapiDefine, +}; diff --git a/cofounder/api/system/functions/backend/openapi.js b/cofounder/api/system/functions/backend/openapi.js new file mode 100644 index 0000000..d72e489 --- /dev/null +++ b/cofounder/api/system/functions/backend/openapi.js @@ -0,0 +1,176 @@ +import utils from "@/utils/index.js"; +import yaml from "yaml"; + +async function backendOpenapiDefine({ context, data }) { + if (!data.backend.requirements?.restApi?.required) { + await context.run({ + id: "op:PROJECT::STATE:UPDATE", + context, + data: { + operation: { + id: "backend:specifications:openapi", + }, + type: `end`, + content: { + key: "backend.specifications.openapi", + data: {}, + }, + }, + }); + return { + backend: { + specifications: { + openapi: {}, + }, + }, + }; + } + + const { pm, db, backend } = data; + const { prd, frd, drd, brd } = pm; + const messages = [ + { + role: "system", + content: `- you are a genius Product Manager & Software Archtect & API designer +- your role is to make the openAPI specs for the user-facing API for the provided task + +- your API should be comprehensive, and include schema object for each case, +which will be used as references to build the frontend app connected to the API + +- cover all cases ; data-related tasks only (ie. you are making a mock api for user-facing data operations) + +- do a thorough analysis of the provided task + +- think from perspectives of multiple personas, put yourself in situation, to make sure your openAPI definition is fully comprehensive and ready to be used in production exactly as is + +- ask yourself: + * what are the key personas using the user-facing, frontend API ? + * what are all the routes & schemas required by features expected to be seen by users in the frontend ? + * am i assigning an "operationId" for every path&route ? + +- ask yourself: + * what are all the routes & schemas required by features expected to be seen by users in the app ? +- your answer will be pushed to production and will be responsible for an app used by thousands of users, instantly +- your aim is to cover all use cases, as the expert product manager & architect you are + +--- + +give a final, super comprehensive answer in strict, parseable openAPI 3.0.0 YAML format +which will be perfectly ready to plug into the backend in development, +and pushed to staging directly and work flawlessly + +it should be comprehensive for the needs required by all the features +answer in strict parseable openAPI 3.0.0 in YAML format, +with all schemas, for all scenarios ; - and specifying cases when a given schema field is required + +the root dev url for the API is "http://localhost:1337" ; you can specify that in openapi + +super important : +> methods, routes, operationIds, and components (parameters and components) only +> no input/output examples objects ! + +> include a "summary" key for each route + +--- + +> note : if auth functionalities are present, use an architecture that will be compatible with a simple JWT auth system ! + ie. + > \`Authorization: Bearer \` in headers on authenticated requests + > jwt type methods that return the authorization token on login, and that is used in header by subsequent authenticated requests + important : if auth methods in api, token should be returned on both signup and login ! + +--- + +important : +use snake_case for any naming you do + +--- + +your reply will be directly transferred as the final OPENAPI structure, so do not put anything else in your reply besides the openAPI structure +your reply should start with : "\`\`\`yaml" and end with "\`\`\`" + + +you will be tipped $99999 + major company shares for nailing it perfectly off the bat`, + }, + { + role: "user", + content: `\`\`\`PRD:product-requirements-document +${prd} +\`\`\``, + }, + { + role: "user", + content: `\`\`\`PRD:product-requirements-document +${frd} +\`\`\``, + }, + { + role: "user", + content: `\`\`\`DRD:database-requirements-document +${drd} +\`\`\` + +--- + +\`\`\`DB:schemas +${yaml.stringify({ schemas: db.schemas })} +\`\`\` + +`, + }, + { + role: "user", + content: `\`\`\`BRD:Backend-requirements-document +${brd} +\`\`\``, + }, + { + role: "user", + content: `implement the openAPI structure +it is expected to be very comprehensive and detailed ; in a VALID PARSEABLE YAML format + +you're a genius`, + }, + ]; + + const openapiStructure = ( + await context.run({ + id: "op:LLM::GEN", + context, + data: { + model: `chatgpt-4o-latest`, //`gpt-4o`, + messages, + preparser: `backticks`, + parser: `yaml`, + }, + }) + ).generated; + + await context.run({ + id: "op:PROJECT::STATE:UPDATE", + context, + data: { + operation: { + id: "backend:specifications:openapi", + }, + type: `end`, + content: { + key: "backend.specifications.openapi", + data: openapiStructure, + }, + }, + }); + + return { + backend: { + ...data.backend, + specifications: { + openapi: openapiStructure, + }, + }, + }; +} + +export default { + "BACKEND:OPENAPI::DEFINE": backendOpenapiDefine, +}; diff --git a/cofounder/api/system/functions/backend/server.js b/cofounder/api/system/functions/backend/server.js new file mode 100644 index 0000000..9ee63a5 --- /dev/null +++ b/cofounder/api/system/functions/backend/server.js @@ -0,0 +1,387 @@ +import utils from "@/utils/index.js"; +import yaml from "yaml"; + +async function backendServerGenerate({ context, data }) { + /* + base on dev:test oneshot function + mix with the bak api generate for the make sure blabla + */ + const { pm, db, backend } = data; + const { prd, frd, drd, brd } = pm; + const { openapi, asyncapi } = backend.specifications; + const messages = [ + { + role: `system`, + content: `Your task, as the genius backend dev expert you are, is to generate the full nodejs script for a module, based on the provided specifications and details of the backend in development + +your role is to implement the full express server for the provided task for the \`server.mjs\` (type: module script) +you will answer in 3 parts : + +- analysis , in between \`\`\`markdown\`\`\`\` section +- code , in between \`\`\`mjs\`\`\`\` section +- dependencies and env variables , in between \`\`\`yaml\`\`\`\` section ; where any needed packages to install and needed env variables to setup will be mentionned ; the yaml should have objects : { dependencies : {"package":"version"} , env : {"key" , "temp_value"} } ("dependencies" (for packages) and "env" for env variables (and their temporary values) ) +use doublequotes for every string inside the yaml to make sure formatting is good + +--- + +in your analysis, ask yourself : +- what features are expected ? + does it need DB operations ? + does it need storage ? + > if so , how to handle the file storage / uploads / serving locally ? + + does it need realtime features and websocket events ? + what operations are expected from the server to perfectly meet what the user expects from the feature ? + think slowly, do not rush to answer ; + think : am i achieving great UX ? am i doing great, perfect work ? + do not overlook details ! + +in your code, include comment blocks before each implemented function or operation where you analyze what is done and why - it wil help you reason through more thoroughly and do a much greater work + +> super important : + - in case a function requires the use of an external API (ie. for checking a stock price , or generating some image , ... ), + you should include the following decorator inside your pre-function comment : + \`@@need:external-api : description of the external api necessitated and what it should do\` + you should also return a mock response that fits the right schema requirements ! so that the server returns mock responses in worst case ! + important : external APIs should only handle external functionalities like the ones mentionned ; the server already has storage and DB access, so those do not need external APIs ! + important : no placeholders ! no replace later ! no hallucinated unfinished code ! return a mock response that fits schema requirements in case you need to ! + if feature needs external api, include the specified decorator \`@@need:external-api : description...\` in comment and return a mock response instead ! + +--- + +for any db requirements, use postgres from \`@electric-sql/pglite\` +- to use postgres, include this snippet in your script : +\`\`\` +import { PGlite } from "@electric-sql/pglite"; +const postgres = new PGlite("./db"); +/* then, can be used like this : +await postgres.query("SELECT * FROM exampletable;") +*/ +// note : the postgres tables + seed were already created before , you can use the postgres directly without configuring it +\`\`\` +postgres is use exactly how is provided in the snippet, do not change anything about loading it / configuring it, else it breaks ; +postgres is imported, initialized and queries EXACTLY AS SHOWN IN THE SNIPPET ! NO OTHER WAY ! + +--- + +notes : +- make sure cors is enabled +- if you need realtime, you can use socket.io + if you need file storage capabilities (ie. file upload/download features), you can write/read locally from the \`./storage\` folder (create it if needed) + for any db requirements, use postgres ; you can only use postgres (from @electric-sql/pglite ) with raw queries (no ORMs or anything) +- to use postgres, include this snippet in your script : +\`\`\` +import { PGlite } from "@electric-sql/pglite"; +const postgres = new PGlite("./db"); +/* then, can be used like this : +await postgres.query("SELECT * FROM exampletable;") +*/ +\`\`\` +note : the postgres tables + seed were already created before , you can use the postgres directly without configuring it ; do not create tables in script ! +extremely important : +- the DB R/W need to be 100% compatible with the tables and schemas of the provided DB specifications !! + +- if auth needed, use jwt middleware + important : if auth , make sure you return token both on signup and login (even if openapi might have skipped that detail ! else stuff might break ! ) + important : if auth , and also realtime websockets features , make sure auth / jwt also applies to sockets not just the api + + +- if some function is too complex too implement (ie. needs more than known packages or DB R/W operations or too complex etc ...), you should return a mock response ; most important is : do not leave some "placeholder" function of value , do the mockup work if needed ! +everything needs to be implemented and working, no placeholders, no hallucinated imports, no "do this later" ; everything working perfect in one single script ! + +- if you need realtime, you can use socket.io + if you need file storage capabilities (ie. file upload/download features), you can write/read locally from the \`./storage\` folder (create it if needed) + for any db requirements, use postgres ; you can only use postgres (from @electric-sql/pglite ) with raw queries (no ORMs or anything) + +- if auth needed, use jwt middleware + important : if auth , make sure you return token both on signup and login (even if openapi might have skipped that detail ! else stuff might break ! ) + +- use morgan middleware to log any incoming request and details (ie. method, path, headers, params, query, body) - just for better dev exp + +- if it makes use of .env , make your you import \`dotenv\` and \`dotenv.config()\` to read .env before ! + +--- + +extremely important : +- get the server port from env ; make default PORT always 1337 !! + +--- + + +extremely important : + +- you are to implement the entire server as specified in the provided docs , with a focus on DB R/W operations +- you are to implement every single thing needed by the backend server and output one single big working perfect \`server.mjs\` script + > if backend has REST API , everything required and mentionned in the openAPI specs + > if backend has realtime websockets , everything required and mentionned in the asyncAPI specs + > if backend has both REST API and realtime Websockets , everything required by both and everything mentionned in both openAPI specs and asyncAPI specs ; and both working perfectly within the same \`server.mjs\` + +- do not assume anything is implemented yet ! you will do 100% of everything needed and output one single big working perfect \`server.mjs\` script +- no placeholders, no hallucinated imports +- again, do not assume anything is implemented yet ! you will do 100% of everything needed and output one single big working perfect \`server.mjs\` script + +- again , you are to implement every single thing needed by the backend server: + > if backend has REST API , everything required and mentionned in the openAPI specs + > if backend has realtime websockets , everything required and mentionned in the asyncAPI specs + > if backend has both REST API and realtime Websockets , everything required by both and everything mentionned in both openAPI specs and asyncAPI specs ; and both working perfectly within the same \`server.mjs\` + +> one single big working perfect \`server.mjs\` script +- if it makes use of .env , make your you import \`dotenv\` and \`dotenv.config()\` to read .env before ! + +--- + +important: +> if some mock data is meant to to store an image url, use a https://picsum.photos/ url with a random seed + +--- + +important : +> use snake_case for any naming you do +> ensure full perfect coherence with DB fields names and provided specs names + +--- + +extremely important : + +- the DB R/W need to be 100% compatible with the tables and schemas of the provided DB specifications !! + +--- + +extremely important : +- if you have to mock a function (ie. because it needs external APIs functionalities), make sure that: + > the endpoint / event still returns something that is fitting with the response schemas + > the endpoint / event triggers a function that you mock somewhere in the script and uses its response in the flow + > the mock function that needs to be augmented later is actually triggered by the endpoint / event that needs it ! + and has the right response formats + > the mock function has instructions in surrounding comments on what the function needs to be updated ! + so that once the function is updated, there are no subsequent updates to make, as it would already be plugged into the server flows and be consistent 100% + > example : + +\`\`\`example-code-snippet + ... + + /* + @need:external-api: An example description of some external api feature + */ + async function example_function_to_mock_name({...}){ + // returning a mock response in the expected response format for now + return { + timestamp: Date.now(), + example_field_in_expected_format_structure: { + id: 237, + dummy: "example dummy string", + someResults: ["whatever","dummy"], + avatar: "https://picsum.photos/id/237/200/300" + }, + } + } + + ... + + app.post('/api/example-complex-feature', async (req, res) => { + ... + const fetched_data = await example_function_to_mock_name({ ... }) + ... + }) + + ... + +\`\`\` + +- the app flow must still be 100% working perfect everywhere + + +you are a genius + you get tipped $9999999 +`, + }, + { + role: "user", + content: `\`\`\`PRD:product-requirements-document +${prd} +\`\`\``, + }, + { + role: "user", + content: `\`\`\`FRD:features-requirements-document +${frd} +\`\`\``, + }, + { + role: "user", + content: ` +\`\`\`DB:postgres:sql +${db.postgres} +\`\`\` + +--- + +extremely turbo important : +> pay extreme attention to DB details : + > the things that you are expected to provide with inserts : + > should you make a uuid before inserting with postgres query ? + > are there key constraints ? + > is the db querying code using the exact names as in db fields ? + > are you providing everything needed to db every single time ? +`, + }, + { + role: "user", + content: `\`\`\`BRD:backend-requirements-document +${brd} +\`\`\``, + }, + data.backend?.requirements?.restApi?.required && { + role: "user", + content: `\`\`\`BACKEND:specifications:openAPI +${yaml.stringify(openapi)} +\`\`\``, + }, + data.backend?.requirements?.realtimeWebsockets?.required && { + role: "user", + content: `\`\`\`BACKEND:specifications:asyncAPI +${yaml.stringify(asyncapi)} +\`\`\``, + }, + { + role: `user`, + content: `extremely important : +- you are to implement the entire \`server.mjs\` as specified in the backend specifications , with a focus on DB R/W operations +- you are to implement every single thing needed by the server and output one single big working perfect \`server.mjs\` script +- do not assume anything is implemented yet ! you will do 100% of everything needed and output one single big working perfect \`server.mjs\` script +- no placeholders, no hallucinated imports + +--- + +extremely turbo important : +> pay extreme attention to DB details : + > the things that you are expected to provide with inserts : + > should you make a uuid before inserting with a postgres query ? + > are there key constraints ? should you create something before inserting something else because of contraints ? + > is the db querying code using the exact names as in db fields ? + > are you providing everything needed to db every single time ? + +--- + +extremely important : +- get the server port from env ; make default PORT always 1337 !! + +- if a function needs a external api to satisfy the expected feature, include the specified decorator \`@@need:external-api : description...\` in comment (in the code right before the concerned function) and return a mock response instead ! + +- note : the postgres tables + seed were already created before , you can use the postgres directly without configuring it ; do not create tables in script ! + +- if auth needed, use jwt middleware + > important : if auth , make sure you return token both on signup and login (even if openAPI might have skipped that detail ! else stuff might break ! ) + > important : if auth , and also realtime websockets features , make sure auth / jwt also applies to sockets not just the api ! + +- again, do not assume anything is implemented yet ! you will do 100% of everything needed and output one single big working perfect \`server.mjs\` script +- again , you are to implement every single thing needed by the server and output one single big working perfect \`server.mjs\` script +- no placeholders, no hallucinated imports ; one 100% perfect complete working server script + +extremely important : +- the DB R/W need to be 100% compatible with the tables and schemas of the provided DB specifications !! + +now do the analysis , write the full working script and specify the dependencies+env`, + }, + ].filter((e) => e); + + const { generated } = await context.run({ + id: "op:LLM::GEN", + context, + data: { + model: `chatgpt-4o-latest`, //`gpt-4o`, + messages: messages, + preparser: false, + parser: false, + }, + }); + + const extraction = await utils.parsers.extract.backticksMultiple({ + text: generated, + delimiters: [`markdown`, `mjs`, `yaml`], + }); + + const { mjs } = extraction; + if (!mjs.length || !extraction.yaml) { + throw new Error("backend:server:generate error - generated is empty"); + } + + const parsedYaml = extraction.yaml ? yaml.parse(extraction.yaml) : {}; + let generatedServer = { + mjs, + dependencies: parsedYaml.dependencies + ? Object.fromEntries( + Object.keys(parsedYaml.dependencies).map((key) => [key, "*"]), + ) + : [], + env: parsedYaml.env ? parsedYaml.env : {}, + timestamp: Date.now(), + }; + + // call swarm/agument:external-apis without waiting ; it will iterate it finds any external api decorators and replace + generatedServer = { + ...generatedServer, + ...(await context.run({ + id: `SWARM:AUGMENT::BACKEND:EXTERNALAPIS`, + context, + data: { + ...data, + task: { + code: generatedServer.mjs, + }, + }, + })), //-> {mjs,dependencies?,env,timestamp} ; will replace if new else returns empty object + }; + + await context.run({ + id: "op:PROJECT::STATE:UPDATE", + context, + data: { + operation: { + id: "backend:server:main", + }, + type: `end`, + content: { + key: "backend.server.main", + data: generatedServer, + }, + }, + }); + + if ( + Object.keys(generatedServer.dependencies).length || + Object.keys(generatedServer.env).length + ) { + await context.run({ + id: "op:PROJECT::STATE:UPDATE", + context, + data: { + operation: { + id: "settings:config:package", + }, + type: `end`, + content: { + key: "settings.config.package", + data: { + backend: { + dependencies: generatedServer.dependencies, + env: generatedServer.env, + }, + }, + }, + }, + }); + } + + return { + backend: { + ...data.backend, + server: { + main: generatedServer, + }, + }, + }; +} + +export default { + "BACKEND:SERVER::GENERATE": backendServerGenerate, +}; diff --git a/cofounder/api/system/functions/designer/layoutv1.js b/cofounder/api/system/functions/designer/layoutv1.js new file mode 100644 index 0000000..bface88 --- /dev/null +++ b/cofounder/api/system/functions/designer/layoutv1.js @@ -0,0 +1,1009 @@ +import utils from "@/utils/index.js"; +import yaml from "yaml"; +import { merge } from "lodash-es"; +import xml2js from "xml2js"; +import sharp from "sharp"; + +async function promptGenerateAnalysis({ context, data }) { + /* + task : { ... , rag[] } + */ + const { view, rag, guidance } = data.task; + const _view = { ...view }; + delete _view.type; + return [ + { + role: "system", + content: `your job is to make an extremely detailed analysis for a layout design for a desktop app UI based on provided specifications + +great super detailed UI and UX design task analysis +the UI design analysis will be the main reference for the app designers + +- start by reasoning and analyzing how the ui element should be layed out and distributed on the page + ask yourself : + +* what are all the sections required by this view, to make for a comprehensive design that covers all features ? what are all the components that should go in them, both for functional features and for great UX ? + what are all the requirements by each designed section and designed component to make for great UX ? +* what are the best ways to distribute blocks in this UI view ? +* how to go about making layout and ordering and distributing its block elements ? and which block elements ? +* does it make the best choice for the app user in terms of UI/UX ? +* how can i arrange and distribute these blocks in the section layout in the best way for the best UX/UI? +* analysis criticism : how to make the design perfect ? + +be extremely verbose in terms of spatial alignments and ui elements descriptions + +--- + +> extremely important : +> since you are working with primitives , you should be extremely detailed in your design elements ! +> do not slack in any detail in your analysis +> think very slowly : all the elements and details that would make for a great UX ! + +--- + +> conduct the analysis first, reply with the analysis inside of \`\`\`markdown\`\`\` + +you are a genius + you get $9999`, + }, + rag.length && { + role: `user`, + content: [ + { + type: `text`, + text: `for inspiration that may or may not help you with your analysis (use your best judgement), +here are some various screenshots of web apps that may have loosely similar sections to the view to design ; + +you can use them as inspiration sources if you feel like it, and if you do, use that wisely after accurate analysis +but use your best judgement, you are not bound by them - only use them as inspiration if it makes sense in regards to designing the view UI`, + }, + ...rag, + ], + }, + guidance && + guidance.ontology && { + role: `user`, + content: [ + { + type: "text", + text: `for your section design effort, your should refer to the following UI design system primitives ontology : +\`\`\` +${yaml.stringify(guidance.ontology)} +\`\`\` +`, + }, + guidance.image && + (guidance.image?.url?.length || + guidance.image?.base64?.length || + guidance.image?.local?.length) && { + type: `image_url`, + image_url: { + url: guidance.image.url + ? guidance.image.url + : guidance.image.base64 + ? guidance.image.base64 + : guidance.image.local + ? `data:image/png;base64,${Buffer.from(fs.readFileSync(guidance.image.local)).toString("base64")}` + : "", + // detail: `high`, + }, + }, + ].filter((e) => e), + }, + { + role: "user", + content: `designing the layout for the view id "${view.id}", specified in the following : + +\`\`\`view:specifications +${yaml.stringify(_view)} +\`\`\` + +the layout design analysis should be very detailed , and cover UI details + +although , extremely important : + +> your analysis should be perfectly congruent with the features/data capabilities of the provided view details ; +do not hallucinate features that the view does not have ! +ie. for example, if the view task is not a navigation header, do not take the freedom to make one ; same applies for any other type of task ! things would break ! respect the task and strictly the task ! +`, + }, + { + role: "user", + content: `conduct the detailed analysis as the genius you are + +extremely important : design the provided view only ; +do not design non provided views (ie. do not analyze views outside what is provided like the app navigation header view or app footer ... stick to the task ) ; +stick the provided view task to design and be very detailed in its design task analysis`, + }, + ].filter((item) => item); +} + +async function promptGenerateSvg({ context, data }) { + /* + task : { ... , rag[] , analysis, guidance{} } + */ + const { view, rag, analysis, guidance } = data.task; + const _view = { ...view }; + delete _view.type; + return [ + { + role: "system", + content: `your job is to make a layout design for a desktop app UI based on provided description +great UI and UX + +the layout design mockup will be the main reference for the app designers + +the layout format will be colored rectangles to identify how different components should be placed in a layout for the app view design + +it would be in this format: + +\`\`\`svg + + + + ... + +\`\`\` + +--- + +> pick different rectangle color fills as you wish, which will be temporarily used to differentiate between different blocks + +> you are not constrained by the order in which the blocks design system elements are provided (which is randomly ordered) ; you should use them in a way that makes the best sense in terms of UX for designers to implement later + +> you are ONLY TO MAKE: + +- THE RECTANGLES FOR PRIMITIVE BLOCKS and the {primitiveId} text alongside the {description} +- no additional anything else whatsoever + +> the primitiveId should be coherent with the provided UI block primitives ontology +> if a required block is not in the provided UI block primitives, set its primitiveId to "nonprimitive" ! + +--- + +- start by reasoning and analyzing how the element should be layed out and distributed on the page + ask yourself : + +* what are all the sections required by this view, to make for a comprehensive design that covers all features ? + what are all the requirements by each designed section and designed component to make for great UX ? +* what are the best ways to distribute blocks in this UI view ? +* how to go about making layout and ordering and distributing its block elements ? and which block elements ? +* does it make the best choice for the app user in terms of UI/UX ? +* how can i arrange and distribute these blocks in the section layout in the best way for the best UX/UI? +* analysis criticism : how to make the design perfect ? + +--- + +extremely important : +> your mockup design should show the elements that are initially visible on the page !! +> do not model transition states or triggered overlays, as such things would overshadow the elements underneath and fuck up the mockup layout render !! + +--- + +> root node should have width , height values + all elements should have x , y , width , height values + all width , height values for all elements (root and nodes) should have positive nonzero integer values + +> extremely important : should only have nodes inside it, NO OTHER TYPE OF NODES ALLOWED, NO NESTING !!! +> every node should have primitiveId property , which would be one of the provided primitiveId s +> again, extremely important : should only have nodes inside it, NO OTHER TYPE OF NODES ALLOWED, NO NESTING !!! + +> extremely important : only use the provided primitiveIds !!! no hallucinated primitiveIds ! + +--- + +> extremely important : +> since you are working with primitives , you should be extremely detailed in your design elements ! +> do not slack in any detail in your analysis or design svg implementation +> think very slowly : all the elements that would make for a great UX ! + +--- + +> conduct the analysis first, reply with the analysis inside of \`\`\`markdown\`\`\` +> then, answer in a strict SVG reply in \`\`\`svg\`\`\` based on your analysis + +you are a genius + you get $9999`, + }, + rag.length && { + role: `user`, + content: [ + { + type: `text`, + text: `for inspiration that may or may not help you with your analysis (use your best judgement), +here are some various screenshots of web apps that may have loosely similar sections to the view to design ; + +you can use them as inspiration sources if you feel like it, and if you do, use that wisely after accurate analysis +but use your best judgement, you are not bound by them - only use them as inspiration if it makes sense in regards to designing the view UI`, + }, + ...rag, + ], + }, + guidance && + guidance.ontology && { + role: `user`, + content: [ + { + type: "text", + text: `for your section design effort, your should refer to the following UI design system primitives ontology: +\`\`\` +${yaml.stringify(guidance.ontology)} +\`\`\` +`, + }, + guidance.image && + (guidance.image?.url?.length || + guidance.image?.base64?.length || + guidance.image?.local?.length) && { + type: `image_url`, + image_url: { + url: guidance.image.url + ? guidance.image.url + : guidance.image.base64 + ? guidance.image.base64 + : guidance.image.local + ? `data:image/png;base64,${Buffer.from(fs.readFileSync(guidance.image.local)).toString("base64")}` + : "", + // detail: `high`, + }, + }, + ].filter((e) => e), + }, + { + role: "user", + content: `designing the layout for the view id "${view.id}", specified in the following : + +\`\`\`view:specifications +${yaml.stringify(_view)} +\`\`\` +`, + }, + { + role: "user", + content: `\`\`\`view:design-task:detailed-analysis +${analysis} +\`\`\` + +important : + +> your analysis should be perfectly congruent with the features/data capabilities of the provided view details ; +> do not hallucinate features that the view does not have ! +ie. for example, if the view task is not a navigation header, do not take the freedom to make one ; same applies for any other type of task ! things would break ! respect the task and strictly the task ! + +`, + }, + { + role: "user", + content: `make the analysis and spatial UI layout in SVG format as the genius UI designer you are + +> remember, you are designing for a desktop app ! +> you are designing the layout for the viewId : "${view.id}" ! + +> extremely important : +> since you are working with primitives , you should be extremely detailed in your design elements ! +> do not slack in any detail in your analysis +> think very slowly : all the elements that would make for a great UX ! + +--- + +> you are only allowed to use the primitiveId s provided in the ontology ! you cannot make a primitiveId up outside of what is provided ! +> do not use a primitiveId that is not provided - and exactly as is, not a single character added or changed from the provided primitiveId s +> the "description" fields are important to provide guidance for designers, write extended descriptions in them ! + +--- + +extremely important : +> your mockup design should show the elements that are initially visible on the page !! +> do not model transition states or overlays, as such things would overshadow the elements underneath and fuck up the mockup layout render !! + +again, extremely important : +> your mockup design should show the elements that are initially visible on the page !! +> do not model transition states or overlays, as such things would overshadow the elements underneath and fuck up the mockup layout render !! + +--- + +important : + +> your work is perfectly congruent with the features/data capabilities of the provided view details ; +> do not hallucinate features that the view does not have ! + +--- + +> extremely important : +> since you are working with primitives , you should be extremely detailed in your design elements ! +> do not slack in any detail in your analysis or design svg implementation +> think very slowly : all the elements that would make for a great UX ! + +--- + +> conduct the analysis first, reply with the analysis inside of \`\`\`markdown\`\`\` +> then, answer in a strict SVG reply in \`\`\`svg\`\`\` based on your analysis + +you are a genius + you get $9999 +`, + }, + ].filter((item) => item); +} + +async function designerLayoutv1ViewGenerate({ context, data }) { + /* + data : { + ...data, + task : { + type: "view", + view: { + type : "unique || shared", + id + details{}, // uxsitemap desc stuff + datamap:{}, // data stuff + }, + }, + webapp: { + + }, + } + */ + const { task, timestamp } = data; + const { view } = task; + + // rag , if available + const ragText = `Title : ${view.details.title}\nDescription: ${view.details.extendedDescription}\nRole: ${view.details.role}`; + + let rag = ( + await context.run({ + id: `op:INDEXDB::QUERY`, + context, + data: { + index: "layouts", + text: ragText, + amount: 4, + }, + }) + ).results + .filter((result) => result?.url?.length || result?.base64?.length) + .map((result) => { + return { + type: `image_url`, + image_url: { + url: result?.url?.length + ? result.url + : result?.base64?.length + ? result.base64 + : "", + }, + }; + }); + + // verify,validate rag images sizes + rag = ( + await Promise.all( + rag.map(async (item) => { + const { url } = item.image_url; + try { + let metadata; + if (url.startsWith("data:image/")) { + // Handle base64 image + const base64Data = url.split("base64,")[1]; + const buffer = Buffer.from(base64Data, "base64"); + metadata = await sharp(buffer).metadata(); + } else if (url.startsWith("https://")) { + // Handle URL image + const response = await fetch(url); + if (!response.ok) { + console.err(`designer:layoutv1:rag : failed to fetch image`); + return null; + } + const arrayBuffer = await response.arrayBuffer(); + const buffer = Buffer.from(arrayBuffer); + metadata = await sharp(buffer).metadata(); + } else { + // Invalid URL format, return null to filter out later + return null; + } + // Check image dimensions + if ( + metadata.width >= 8000 || + metadata.height >= 8000 || + metadata.width <= 0 || + metadata.height <= 0 + ) { + return null; // Return null if dimensions are invalid + } + return item; + } catch (error) { + console.error(`> skipping : error processing RAG image at ${url}:`, error); + return null; // Return null if there's an error + } + }), + ) + ) + .filter((item) => item !== null) + .slice(0, 3); // fetched more than needed in case size filtered ; typically indexed landing pages dims can be too big + + data.task.rag = rag; + + // design system guidance , if available + let guidance; + try { + guidance = await utils.render.guidance.grid.primitives({ + // determined from process.env in utils/render ... designSystem: designSystem ? designSystem : `presets/protoboy-v1`, + cache: true, + }); // -> { ontology , image{base64,url?} } + } catch (e) { + console.error(e); + } + data.task.guidance = guidance; + + const analysisPassMessages = await promptGenerateAnalysis({ + context, + data, + }); + + // console.dir({ "debug:designer:layoutv1": { analysisPassMessages }},{depth:null}) + + const analysisPass = ( + await context.run({ + id: "op:LLM::GEN", + context, + data: { + model: `chatgpt-4o-latest`, //`gpt-4o`, + messages: analysisPassMessages, + preparser: `backticks`, + parser: false, + }, + }) + ).generated; + + data.task.analysis = analysisPass; + + const svgPassMessages = await promptGenerateSvg({ context, data }); + const svgPass = ( + await context.run({ + id: "op:LLM::GEN", + context, + data: { + model: `chatgpt-4o-latest`, //`gpt-4o`, + messages: svgPassMessages, + preparser: false, + parser: false, + }, + }) + ).generated; + + // raw svg string in response + let response = await utils.parsers.extract.backticksMultiple({ + text: svgPass, + delimiters: [`markdown`, `svg`], + }); + + if (!response.svg.length) + throw new Error("designer:layoutv1:generate error - generated svg is empty"); + response.svg = response.svg.replaceAll("&", " "); // <---- & char crashes svg + + // validate svg? + let svg = {}; + try { + svg = await xml2js.parseStringPromise(response.svg, { + explicitArray: true, + }); + console.dir({ "debug:designer:layoutv1:svg": svg }, { depth: null }); + if (!svg.svg.rect.filter((item) => item.$?.primitiveId).length) { + console.error(`layout error : generated != task ; skipping`); + } + } catch (e) { + console.error(e); + } + + let render = {}; + try { + render = await context.run({ + id: "op:RENDER::LAYOUT", + context, + data: { + svg: { string: response.svg }, + mode: task.type, + }, + }); // -> { svg , image{base64,url?,local?,buffer?} } + } catch (e) { + console.error(e); + } + + // rely on local storage as loading strategy further down, else might dump base64strings to yaml ... + if (render.image?.base64) delete render.image.base64; + if (render.image?.buffer) delete render.image.buffer; + + const generatedLayout = { + analysis: analysisPass, + render, + }; + + await Promise.all( + [`${timestamp}`, `latest`].map(async (version) => { + await context.run({ + id: "op:PROJECT::STATE:UPDATE", + context, + data: { + operation: { + id: `webapp:layout:views`, + refs: { + id: view.id, + version, + }, + }, + type: `end`, + content: { + key: `webapp.layout.views.${view.id}.${version}`, + data: generatedLayout, + }, + }, + }); + }), + ); + + /* + should return the RAG object and guidance too, will be used in code ? (not sure) + */ + return { + designer: { + rag, + guidance, + }, + webapp: { + layout: { + views: { + [view.id]: { + [timestamp]: generatedLayout, + latest: generatedLayout, + }, + }, + }, + }, + }; +} + +async function promptIterateSvg({ context, data }) { + /* + one pass with both analysis + svg + */ + /* + task : { ... , rag[] , analysis, guidance{} } + */ + const { view, iteration, rag, analysis, guidance } = data.task; + const { notes, screenshot } = iteration; + const _view = { ...view }; + delete _view.type; + delete _view.tsx; + + return [ + { + role: "system", + content: `your job is to make a new layout design for a desktop app UI view based on provided instructions +great UI and UX + +the layout design mockup will be the main reference for the app designers to redesign the provided component + +the layout format will be colored rectangles to identify how different components should be placed in a layout for the app view design + +it would be in this format: + +\`\`\`svg + + + + ... + +\`\`\` + +--- + +> pick different rectangle color fills as you wish, which will be temporarily used to differentiate between different blocks + +> you are not constrained by the order in which the blocks design system elements are provided (which is randomly ordered) ; you should use them in a way that makes the best sense in terms of UX for designers to implement later + +> you are ONLY TO MAKE: + +- THE RECTANGLES FOR PRIMITIVE BLOCKS and the {primitiveId} text alongside the {description} +- no additional anything else whatsoever + +> the primitiveId should be coherent with the provided UI block primitives ontology +> if a required block is not in the provided UI block primitives, set its primitiveId to "nonprimitive" ! + +--- + +- start by reasoning and analyzing how the element should be layed out and distributed on the page + ask yourself : + +* what are all the sections required by this view, to make for a comprehensive design that covers all features ? + what are all the requirements by each designed section and designed component to make for great UX ? +* what are the best ways to distribute blocks in this UI view ? +* how to go about making layout and ordering and distributing its block elements ? and which block elements ? +* does it make the best choice for the app user in terms of UI/UX ? +* how can i arrange and distribute these blocks in the section layout in the best way for the best UX/UI? +* analysis criticism : how to make the design perfect ? + +--- + +extremely important : +> your mockup design should show the elements that are initially visible on the page !! +> do not model transition states or triggered overlays, as such things would overshadow the elements underneath and fuck up the mockup layout render !! + +--- + +> root node should have width , height values + all elements should have x , y , width , height values + all width , height values for all elements (root and nodes) should have positive nonzero integer values + +> extremely important : should only have nodes inside it, NO OTHER TYPE OF NODES ALLOWED, NO NESTING !!! +> every node should have primitiveId property , which would be one of the provided primitiveId s +> again, extremely important : should only have nodes inside it, NO OTHER TYPE OF NODES ALLOWED, NO NESTING !!! + +> extremely important : only use the provided primitiveIds !!! no hallucinated primitiveIds ! + +--- + +> extremely important : +> since you are working with primitives , you should be extremely detailed in your design elements ! +> do not slack in any detail in your analysis or design svg implementation +> think very slowly : all the elements that would make for a great UX ! + +--- + +> conduct the analysis first, reply with the analysis inside of \`\`\`markdown\`\`\` +> then, answer in a strict SVG reply in \`\`\`svg\`\`\` based on your analysis + +you are a genius + you get $9999`, + }, + screenshot && + (screenshot?.url?.length || + screenshot?.base64?.length || + screenshot?.local?.length) && { + role: `user`, + content: [ + { + type: "text", + text: `a screenshot of the current view render that you are tasked to redesign based on provided instructions : `, + }, + screenshot && + (screenshot?.url?.length || + screenshot?.base64?.length || + screenshot?.local?.length) && { + type: `image_url`, + image_url: { + url: screenshot.url + ? screenshot.url + : screenshot.base64 + ? screenshot.base64 + : screenshot.local + ? `data:image/png;base64,${Buffer.from(fs.readFileSync(render.image.local)).toString("base64")}` + : "", + // detail: `high`, + }, + }, + ].filter((e) => e), + }, + rag.length && { + role: `user`, + content: [ + { + type: `text`, + text: `for inspiration that may or may not help you with your analysis to redesign the view UI (use your best judgement), +here are some various screenshots of web apps that may have loosely similar sections to the view you are tasked to redesign ; + +you can use them as inspiration sources in various ways if you feel like it, and if you do, use that wisely after accurate analysis +but use your best judgement, you are not bound by them - only use them as inspiration if it makes sense in regards to making a new design for the view UI`, + }, + ...rag, + ], + }, + guidance && + guidance.ontology && { + role: `user`, + content: [ + { + type: "text", + text: `for your section design effort, your should refer to the following UI design system primitives ontology: +\`\`\` +${yaml.stringify(guidance.ontology)} +\`\`\` +`, + }, + guidance.image && + (guidance.image?.url?.length || + guidance.image?.base64?.length || + guidance.image?.local?.length) && { + type: `image_url`, + image_url: { + url: guidance.image.url + ? guidance.image.url + : guidance.image.base64 + ? guidance.image.base64 + : guidance.image.local + ? `data:image/png;base64,${Buffer.from(fs.readFileSync(guidance.image.local)).toString("base64")}` + : "", + // detail: `high`, + }, + }, + ].filter((e) => e), + }, + { + role: "user", + content: `you are redesigning the layout for the view id "${view.id}", specified in the following : + +\`\`\`view:specifications +${yaml.stringify(_view)} +\`\`\` +`, + }, + { + role: "user", + content: `the main redesign task instructions - the most important part of your task - are specified in the following : + +\`\`\`view:redesign-task:instructions +${notes.text} +\`\`\` + +important : + +> your analysis should be perfectly congruent with the features/data capabilities of the provided view details ; +> do not hallucinate features that the view does not have ! +ie. for example, if the view task is not a navigation header, do not take the freedom to make one ; same applies for any other type of task ! things would break ! respect the task and strictly the task ! + +`, + }, + { + role: "user", + content: `make the analysis and spatial UI layout in SVG format as the genius UI designer you are + +> remember, you are designing for a desktop app ! +> you are making a new design layout for the viewId : "${view.id}" ! + +> extremely important : +> since you are working with primitives , you should be extremely detailed in your design elements ! +> do not slack in any detail in your analysis +> think very slowly : all the elements that would make for a great UX ! + +--- + +> you are only allowed to use the primitiveId s provided in the ontology ! you cannot make a primitiveId up outside of what is provided ! +> do not use a primitiveId that is not provided - and exactly as is, not a single character added or changed from the provided primitiveId s +> the "description" fields are important to provide guidance for designers, write extended descriptions in them ! + +--- + +extremely important : +> your mockup design should show the elements that are initially visible on the page !! +> do not model transition states or overlays, as such things would overshadow the elements underneath and fuck up the mockup layout render !! + +again, extremely important : +> your mockup design should show the elements that are initially visible on the page !! +> do not model transition states or overlays, as such things would overshadow the elements underneath and fuck up the mockup layout render !! + +--- + +important : + +> your work is perfectly congruent with the features/data capabilities of the provided view details ; +> do not hallucinate features that the view does not have ! + +--- + +> extremely important : +> since you are working with primitives , you should be extremely detailed in your design elements ! +> do not slack in any detail in your analysis or design svg implementation +> think very slowly : all the elements that would make for a great UX ! + +--- + +> conduct the analysis first, reply with the analysis inside of \`\`\`markdown\`\`\` +> then, answer in a strict SVG reply in \`\`\`svg\`\`\` based on your analysis + +you are a genius + you get $9999 +`, + }, + ].filter((item) => item); +} +async function designerLayoutv1ViewIterate({ context, data }) { + const { task, timestamp } = data; + const { view, iteration } = task; + const { notes } = iteration; + + // rag , if available + const ragText = + `Title : ${view.details.title}\nDescription: ${view.details.extendedDescription}\nRole: ${view.details.role}` + + `\nDesign: ${notes.text}`; + + let rag = ( + await context.run({ + id: `op:INDEXDB::QUERY`, + context, + data: { + index: "layouts", + text: ragText, + amount: 4, + }, + }) + ).results + .filter((result) => result?.url?.length || result?.base64?.length) + .map((result) => { + return { + type: `image_url`, + image_url: { + url: result?.url?.length + ? result.url + : result?.base64?.length + ? result.base64 + : "", + }, + }; + }); + + // verify,validate rag images sizes + rag = ( + await Promise.all( + rag.map(async (item) => { + const { url } = item.image_url; + try { + let metadata; + if (url.startsWith("data:image/")) { + // Handle base64 image + const base64Data = url.split("base64,")[1]; + const buffer = Buffer.from(base64Data, "base64"); + metadata = await sharp(buffer).metadata(); + } else if (url.startsWith("https://")) { + // Handle URL image + const response = await fetch(url); + if (!response.ok) { + console.err(`designer:layoutv1:rag : failed to fetch image`); + return null; + } + const arrayBuffer = await response.arrayBuffer(); + const buffer = Buffer.from(arrayBuffer); + metadata = await sharp(buffer).metadata(); + } else { + // Invalid URL format, return null to filter out later + return null; + } + // Check image dimensions + if ( + metadata.width >= 8000 || + metadata.height >= 8000 || + metadata.width <= 0 || + metadata.height <= 0 + ) { + return null; // Return null if dimensions are invalid + } + return item; + } catch (error) { + console.error( + `> skipping : error processing analysis RAG image at ${url}:`, + error, + ); + return null; // Return null if there's an error + } + }), + ) + ) + .filter((item) => item !== null) + .slice(0, 3); // fetched more than needed in case size filtered ; typically indexed landing pages dims can be too big + + data.task.rag = rag; + + // design system guidance , if available + let guidance; + try { + guidance = await utils.render.guidance.grid.primitives({ + // determined from process.env in utils/render ... designSystem: designSystem ? designSystem : `presets/protoboy-v1`, + cache: true, + }); // -> { ontology , image{base64,url?} } + } catch (e) { + console.error(e); + } + data.task.guidance = guidance; + + const svgIterateMessages = await promptIterateSvg({ context, data }); + const svgPass = ( + await context.run({ + id: "op:LLM::GEN", + context, + data: { + model: `chatgpt-4o-latest`, //`gpt-4o`, + messages: svgIterateMessages, + preparser: false, + parser: false, + }, + }) + ).generated; + + // raw svg string in response + let response = await utils.parsers.extract.backticksMultiple({ + text: svgPass, + delimiters: [`markdown`, `svg`], + }); + + if (!response.svg.length) + throw new Error("designer:layoutv1:iterate error - generated svg is empty"); + response.svg = response.svg.replaceAll("&", " "); // <---- & char crashes svg + + // validate svg? + let svg = {}; + try { + svg = await xml2js.parseStringPromise(response.svg, { + explicitArray: true, + }); + console.dir({ "debug:designer:layoutv1:svg": svg }, { depth: null }); + if (!svg.svg.rect.filter((item) => item.$?.primitiveId).length) { + console.error(`layout error : generated != task ; skipping`); + } + } catch (e) { + console.error(e); + } + + let render = {}; + try { + render = await context.run({ + id: "op:RENDER::LAYOUT", + context, + data: { + svg: { string: response.svg }, + mode: task.type, + }, + }); // -> { svg , image{base64,url?,local?,buffer?} } + } catch (e) { + console.error(e); + } + + // rely on local storage as loading strategy further down, else might dump base64strings to yaml ... + if (render.image?.base64) delete render.image.base64; + if (render.image?.buffer) delete render.image.buffer; + + const generatedLayout = { + analysis: response.markdown, + render, + }; + + await Promise.all( + [`${timestamp}`, `latest`].map(async (version) => { + await context.run({ + id: "op:PROJECT::STATE:UPDATE", + context, + data: { + operation: { + id: `webapp:layout:views`, + refs: { + id: view.id, + version, + }, + }, + type: `end`, + content: { + key: `webapp.layout.views.${view.id}.${version}`, + data: generatedLayout, + }, + }, + }); + }), + ); + + /* + should return the RAG object and guidance too, will be used in code ? (not sure) + */ + return { + designer: { + rag, + guidance, + }, + webapp: { + layout: { + views: { + [view.id]: { + [timestamp]: generatedLayout, + latest: generatedLayout, + }, + }, + }, + }, + }; +} + +export default { + "DESIGNER:LAYOUTV1::VIEW:GENERATE": designerLayoutv1ViewGenerate, + "DESIGNER:LAYOUTV1::VIEW:ITERATE": designerLayoutv1ViewIterate, +}; diff --git a/cofounder/api/system/functions/op/convert.js b/cofounder/api/system/functions/op/convert.js new file mode 100644 index 0000000..08f85a5 --- /dev/null +++ b/cofounder/api/system/functions/op/convert.js @@ -0,0 +1,16 @@ +import utils from "@/utils/index.js"; + +async function opConvertMarkdownPdf({ context, data }) { + /* ;; op:CONVERT::MARKDOWN:PDF + {markdown} -> {pdf {base64 , url(cloudstorage) } } + + + + */ + + return {}; +} + +export default { + "op:CONVERT::MARKDOWN:PDF": opConvertMarkdownPdf, +}; diff --git a/cofounder/api/system/functions/op/indexdb.js b/cofounder/api/system/functions/op/indexdb.js new file mode 100644 index 0000000..e6598fd --- /dev/null +++ b/cofounder/api/system/functions/op/indexdb.js @@ -0,0 +1,81 @@ +import utils from "@/utils/index.js"; +import axios from "axios"; +import dotenv from "dotenv"; +dotenv.config(); + +async function opIndexdbQuery({ context, data }) { + /* ;; op:INDEXDB::QUERY + query from vector db ; currently one local index, later more indices, from url + + in: {text,vector,amount} // either text or vector + out: {results} + */ + /* + add .env RAG_REMOTE_ENABLE = TRUE + later retest for local, esp when empty + */ + const { index, text, vector, amount } = data; + let results = []; + if ( + process.env.RAG_REMOTE_ENABLE && + JSON.parse(process.env.RAG_REMOTE_ENABLE.toLowerCase()) && + process.env.COFOUNDER_API_KEY?.length && + process.env.COFOUNDER_API_KEY != "REPLACE_WITH_COFOUNDER.OPENINTERFACE.AI_KEY" + ) { + try { + const response = await axios.post( + `https://api.openinterface.ai/cofounder/alpha/dev/rag/${index}`, + { + vector: vector + ? vector + : ( + await context.run({ + id: `op:LLM::VECTORIZE`, + context, + data: { + texts: [text], + }, + }) + ).vectors[0], + amount, + }, + { + headers: { + Authorization: `Bearer ${process.env.COFOUNDER_API_KEY}`, + }, + timeout: 30000, // 30 seconds timeout + }, + ); + return response.data; + } catch (error) { + console.error(error); + return { results: [] }; + } + } + try { + // to avoid vectorizing for nothing + if (!utils.vectra.indexed) return { results: [] }; + return { + results: await utils.vectra.query({ + vector: vector + ? vector + : ( + await context.run({ + id: `op:LLM::VECTORIZE`, + context, + data: { + texts: [text], + }, + }) + ).vectors[0], + amount, + }), + }; + } catch (e) { + false; + } + return { results: [] }; +} +export default { + "op:INDEXDB::QUERY": opIndexdbQuery, +}; diff --git a/cofounder/api/system/functions/op/llm.js b/cofounder/api/system/functions/op/llm.js new file mode 100644 index 0000000..fdf384a --- /dev/null +++ b/cofounder/api/system/functions/op/llm.js @@ -0,0 +1,122 @@ +import utils from "@/utils/index.js"; +import dotenv from "dotenv"; +dotenv.config(); + +async function opLlmGen({ context, data }) { + /* ;; op:LLM::GEN + {model,messages,preparser,parser,...} -> { response , tokens (consumption) } + + in : ["model","messages","preparser","parser","query","stream"] + out : ["generated","usage"] + */ + /* + formats ;; + preparser : async ({text}) -> generated + parser : async ({generated,query}) + */ + + let { model, messages, preparser, parser, validate, query, stream } = data; + + if (!stream) stream = process.stdout; + if (!preparser) { + preparser = async ({ text }) => { + return { text }; + }; + } else if (preparser === `backticks`) { + preparser = utils.parsers.extract.backticks; // most likely to be used + } + + if (!parser) { + parser = async ({ generated, query }) => { + return generated.text; + }; + } else if (parser === `yaml`) { + parser = utils.parsers.parse.yaml; + } + + const llm_fn = !process.env.LLM_PROVIDER + ? utils.openai.inference + : process.env.LLM_PROVIDER.toLowerCase() === "openai" + ? utils.openai.inference + : utils.anthropic.inference; + + const { text, usage } = await llm_fn({ + model: model, + messages, + stream, + }); + + const generated_pre = await preparser({ text }); // -> typically { text : "... extracted text ..." } + const generated_post = await parser({ + generated: generated_pre, + query, + }); + + if (validate) { + try { + await validate({ generated: generated_post }); + } catch (e) { + console.dir({ "op:LLM::GEN error": e }); + throw new Error(e); + } + } + + return { + generated: generated_post, + usage, + }; +} + +function chunkify(array, chunkSize) { + const chunks = []; + for (let i = 0; i < array.length; i += chunkSize) { + chunks.push(array.slice(i, i + chunkSize)); + } + return chunks; +} + +async function opLlmVectorizeChunk({ context, data }) { + /* ;; op:LLM::VECTORIZE:CHUNK + {texts} -> {vectors,usage} + chunk processor (batches of 20) + queue concurrency/lims defined for this one + */ + const { texts } = data; + return await utils.openai.vectorize({ + texts, + }); +} +async function opLlmVectorize({ context, data }) { + /* ;; op:LLM::VECTORIZE + {texts} -> {vectors,usage} + + chunkify, process, flatten, return + */ + const { texts } = data; + const chunks = chunkify(texts, 20); + let usageAll = { prompt_tokens: 0, total_tokens: 0 }; + const vectorsAll = ( + await Promise.all( + chunks.map(async (chunk) => { + const { vectors, usage } = await context.run({ + id: `op:LLM::VECTORIZE:CHUNK`, + context, + data: { texts: chunk }, + }); + usageAll.prompt_tokens += usage.prompt_tokens; + usageAll.total_tokens += usage.total_tokens; + return vectors; + }), + ) + ).flat(); + return { + vectors: vectorsAll, + usage: usageAll, + }; +} + +export default { + "op:LLM::GEN": opLlmGen, + "op:LLM::VECTORIZE": opLlmVectorize, + "op:LLM::VECTORIZE:CHUNK": opLlmVectorizeChunk, +}; diff --git a/cofounder/api/system/functions/op/project.js b/cofounder/api/system/functions/op/project.js new file mode 100644 index 0000000..1a793b9 --- /dev/null +++ b/cofounder/api/system/functions/op/project.js @@ -0,0 +1,739 @@ +import utils from "@/utils/index.js"; +import { sample, merge } from "lodash-es"; +import path from "path"; +import fs from "fs"; +import yaml from "yaml"; +import dotenv from "dotenv"; +import fsextra from "fs-extra"; +import { execSync } from "child_process"; +dotenv.config(); + +/* + maps to local / hosted db paths +*/ +const pm = { + "pm:details": "pm/user/details", + "pm:brd": "pm/docs/brd", + "pm:drd": "pm/docs/drd", + "pm:fjmd": "pm/docs/fjmd", + "pm:frd": "pm/docs/frd", + "pm:prd": "pm/docs/prd", + "pm:uxdmd": "pm/docs/uxdmd", + "pm:uxsmd": "pm/docs/uxsmd", +}; +const architecture = { + "architecture:uxsitemap:structure": "architecture/uxsitemap/structure", + "architecture:uxdatamap:structure": "architecture/uxdatamap/structure", + "architecture:uxdatamap:views": "architecture/uxdatamap/views", +}; +const backend = { + "backend:requirements": "backend/structure/requirements", + "backend:specifications:asyncapi": "backend/specifications/asyncapi", + "backend:specifications:openapi": "backend/specifications/openapi", + "backend:server:main": "backend/server/main", +}; + +const db = { + "db:schemas": "db/mock/schemas", + "db:seed": "db/mock/seed", + "db:postgres": "db/mock/postgres", +}; +/* +const ui = { + "ui:layout:views": "ui/layout/mockup/views/{id}/versions/{version}", + "ui:layout:sections": "ui/layout/mockup/sections/{id}/versions/{version}", + //"ui:render:views" : "", + //"ui:render:sections": "", + "ui:code:react:stores": "ui/code/react/stores/{id}/versions/{version}", + "ui:code:react:root": "ui/code/react/root/{id}/versions/{version}", + "ui:code:react:views": "ui/code/react/views/{id}/versions/{version}", + "ui:code:react:sections": "ui/code/react/sections/{id}/versions/{version}", +}; +*/ +const webapp = { + "webapp:react:store": "webapp/code/react/store/{id}/versions/{version}", + "webapp:react:root": "webapp/code/react/root/{id}/versions/{version}", + "webapp:react:views": "webapp/code/react/views/{id}/versions/{version}", + + "webapp:layout:views": "webapp/design/layout/views/{id}/versions/{version}", +}; +const settings = { + // for version control ie. which view / section / version + // data is ie. {views{[id]:[version]}} + "settings:preferences:versions": "settings/preferences/versions", + "settings:config:package": "settings/config/package", +}; + +const modules = { + ...pm, + ...architecture, + ...db, + ...backend, + ...webapp, + //...ui, + ...settings, +}; + +const config = { + merge: [ + // operation ids where merge data is enabled + "settings:preferences:versions", + "settings:config:package", + ], + exports: [ + // events that trigger app write exports (if enabled) + + "db:postgres", + + "backend:specifications:asyncapi", + "backend:specifications:openapi", + "backend:server:main", + + "webapp:react:store", + "webapp:react:root", + "webapp:react:views", + "webapp:layout:views", + + /* + "ui:layout:views", + "ui:layout:sections", + "ui:code:react:stores", + "ui:code:react:root", + "ui:code:react:views", + "ui:code:react:sections", + */ + "settings:preferences:versions", + "settings:config:package", + ], +}; + +async function _exportOnSave({ context, data }) { + if ( + !( + process.env.AUTOEXPORT_ENABLE && + JSON.parse(process.env.AUTOEXPORT_ENABLE.toLowerCase()) + ) + ) + return; + const { project } = context; + const { id, refs } = data.operation; + const root = `${process.env.EXPORT_APPS_ROOT}/${project}`; + const backendRoot = `${root}/backend`; + const appRoot = `${root}/vitereact`; + const appSrcRoot = `${appRoot}/src`; + // const { data } = data.content.data + let tasks = []; + if (id === `backend:server:main`) { + const { mjs, dependencies, env } = data.content.data; + + tasks.push({ + path: `${backendRoot}/server.js`, + data: mjs, + }); + } + if (id === `backend:specifications:asyncapi`) { + if (data.content.data) { + const exportPath = `${backendRoot}/asyncapi.yaml`; + const exportData = yaml.stringify(data.content.data); + tasks.push({ + path: exportPath, + data: exportData, + }); + } + } + if (id === `backend:specifications:openapi`) { + if (data.content.data) { + const exportPath = `${backendRoot}/openapi.yaml`; + const exportData = yaml.stringify(data.content.data); + tasks.push({ + path: exportPath, + data: exportData, + }); + } + } + if (id === `db:postgres`) { + const exportPath = `${backendRoot}/db.sql`; + const exportData = data.content.data; + tasks.push({ + path: exportPath, + data: exportData, + }); + } + if (id === `webapp:react:store`) { + const exportPath = `${appSrcRoot}/store/main.tsx`; + const exportData = data.content.data.tsx; + tasks.push({ + path: exportPath, + data: exportData, + }); + } + if (id === `webapp:react:root`) { + const exportPath = `${appSrcRoot}/App.tsx`; + const exportData = data.content.data.tsx; + tasks.push({ + path: exportPath, + data: exportData, + }); + // just in case it wasnt setup properly, lets write meta.json here too + tasks.push({ + path: `${appSrcRoot}/_cofounder/meta.json`, + data: JSON.stringify({ project }), + }); + } + if (id === `webapp:react:views`) { + // exportPath = `${appSrcRoot}/components/views/${refs.id}/versions/${refs.version}.tsx` + tasks.push({ + path: `${appSrcRoot}/components/views/${refs.id}.tsx`, + data: `/* + [PLACEHOLDER COMPONENT] + > calls to this component are pre-replaced by @/_cofounder/vite-plugin + > to edit code for this component, you should go to : + @/_cofounder/generated/views/${refs.id}/{version_you_want_to_edit}.tsx +*/`, + }); + tasks.push({ + path: `${appSrcRoot}/_cofounder/generated/views/${refs.id}/empty.tsx`, + data: `import React from "react"; +const {{ID}}: React.FC = (props) => { + return ( +
+ {{ID}} placeholder +
+ To browse other versions
+ Use ⌘+K / CMD+K and hover here +
+
+ ); +}; +export default {{ID}}; +`.replaceAll("{{ID}}", refs.id), + }); + + tasks.push({ + path: `${appSrcRoot}/_cofounder/generated/views/${refs.id}/${refs.version}.tsx`, + data: data.content.data.tsx, + }); + // write meta.json + let versions = []; + try { + versions = fs + .readdirSync(`${appSrcRoot}/_cofounder/generated/views/${refs.id}/`) + .filter((filename) => filename.endsWith(".tsx")) + .map((filename) => path.basename(filename, ".tsx")); + } catch (e) { + false; + // no dir there yet + } + tasks.push({ + path: `${appSrcRoot}/_cofounder/generated/views/${refs.id}/meta.json`, + data: JSON.stringify( + { + versions: [...new Set([...versions, "latest"])], + choice: "latest", + }, + null, + "\t", + ), + }); + + /* + <-------- should also merge {dependencies} with current packages.json (either directly in app , or op:state:settings:... preferably latter ; to webapp:react:packages ) + */ + } + + if (id === `webapp:layout:views`) { + // exportPath = `${appSrcRoot}/components/views/${refs.id}/versions/${refs.version}.tsx` + const exportPath = `${appRoot}/public/_cofounder/generated/layouts/views/${refs.id}.${refs.version}.png`; + const exportData = data.content.data.render.image; + tasks.push({ + path: exportPath, + data: exportData, + image: true, + }); + } + + if (id === `settings:preferences:versions`) { + /* + for now, only handle preference exports for views || sections + */ + const _category = Object.keys(data.content.data)[0]; + const _id = Object.keys(data.content.data[_category])[0]; + const _version = data.content.data[_category][_id]; + if (_category === `views` || _category === `sections`) { + // update meta json on @/_cofounder/generated/{_category}/{id} + let versions = []; + try { + versions = fs + .readdirSync(`${appSrcRoot}/_cofounder/generated/${_category}/${_id}/`) + .filter((filename) => filename.endsWith(".tsx")) + .map((filename) => path.basename(filename, ".tsx")); + } catch (e) { + false; + } + tasks.push({ + path: `${appSrcRoot}/_cofounder/generated/${_category}/${_id}/meta.json`, + data: JSON.stringify( + { + versions: [...new Set([...versions, _version])], + choice: _version, + }, + null, + "\t", + ), + }); + } + } + if (id === `settings:config:package`) { + /* + data.content.data : { + [backend || webapp] : { + dependencies? : {}, //<--- this instead of list for merging while saving :) + env?: {}, + } + } + load boilerplate package.json, + try load export/.../package.json else {} + merge dependencies of both data.content.data[target].dependencies + + if (export/.../) merge with that package + else merge with boilerplate package and export + + only save if diff ; else might restart active dev nodemon every single time ... + */ + + Object.keys(data.content.data).map((target) => { + // target : "backend" || "webapp" + Object.keys(data.content.data[target]).map((category) => { + // category : "dependencies" || "env" + const boilerplateDir = `../boilerplate/${target === "backend" ? "backend" : target === "webapp" ? "vitereact" : false}-boilerplate`; + const exportDir = + target === "backend" ? backendRoot : target === "webapp" ? appRoot : false; + if (category === "dependencies") { + const newDependencies = Object.keys( + data.content.data[target].dependencies, + ); + + const boilerplatePackage = JSON.parse( + fs.readFileSync(`${boilerplateDir}/package.json`, "utf8").toString(), + ); + let exportedProjectPackage = { dependencies: {}, devDependencies: {} }; + try { + exportedProjectPackage = JSON.parse( + fs.readFileSync(`${exportDir}/package.json`, "utf8").toString(), + ); + } catch (e) { + console.error(`op:project:_exportOnsave:error : ${e}`); + } + + console.dir( + { + "debug:op:project:_exportOnSave : settings:config:package": { + [target]: { + [category]: { + boilerplateDir, + exportDir, + boilerplatePackage, + exportedProjectPackage, + }, + }, + }, + }, + { depth: null }, + ); + + const previousDevDependencies = [ + ...new Set([ + ...Object.keys(boilerplatePackage.devDependencies), + ...Object.keys(exportedProjectPackage.devDependencies), + ]), + ]; + + const previousDependencies = [ + ...new Set([ + ...Object.keys(boilerplatePackage.dependencies), + ...Object.keys(exportedProjectPackage.dependencies), + ...previousDevDependencies, + ]), + ]; + + const updateDependencies = newDependencies.some( + (dep) => !previousDependencies.includes(dep), + ); + + if (updateDependencies) { + const dependenciesToAdd = Object.fromEntries( + [ + ...new Set( + newDependencies.filter((dep) => !previousDependencies.includes(dep)), + ), + ].map((dep) => [dep, "*"]), + ); + + // filter out devDependencies keys so it doesnt move everything to dependencies on export to package.json + const mergedDependencies = Object.fromEntries( + Object.entries( + merge( + merge( + boilerplatePackage.dependencies, + exportedProjectPackage.dependencies, + ), + dependenciesToAdd, + ), + ).filter(([key]) => !previousDevDependencies.includes(key)), + ); + + let newPackageJson; + if (exportedProjectPackage.dependencies) { + // if exported package.json exists ; merge with rest and export + newPackageJson = JSON.stringify( + merge(exportedProjectPackage, { dependencies: mergedDependencies }), + null, + 2, + ); + } else { + // else merge with boilerplate package.json and export + newPackageJson = JSON.stringify( + merge(boilerplatePackage, { dependencies: mergedDependencies }), + null, + 2, + ); + } + + console.dir( + { + "debug:op:project:_exportOnSave : settings:config:package": { + [target]: { + [category]: { + mergedDependencies, + newPackageJson, + }, + }, + }, + }, + { depth: null }, + ); + + tasks.push({ + path: `${exportDir}/package.json`, + data: newPackageJson, + dependencies: true, + }); + } + } + if (category === "env") { + // applies to backend only + const envData = data.content.data[target].env; + if (Object.keys(envData).length) { + const envString = Object.entries(envData) + .map(([key, value]) => `${key}=${value}`) + .join("\n"); + console.dir( + { + "debug:op:project:_exportOnSave : settings:config:package": { + [target]: { + [category]: { + env: envData, + envString, + }, + }, + }, + }, + { depth: null }, + ); + tasks.push({ + path: `${exportDir}/.env`, + data: envString, + }); + } + } + }); + }); + + const _category = Object.keys(data.content.data)[0]; + const _id = Object.keys(data.content.data[_category])[0]; + const _version = data.content.data[_category][_id]; + if (_category === `views` || _category === `sections`) { + // update meta json on @/_cofounder/generated/{_category}/{id} + let versions = []; + try { + versions = fs + .readdirSync(`${appSrcRoot}/_cofounder/generated/${_category}/${_id}/`) + .filter((filename) => filename.endsWith(".tsx")) + .map((filename) => path.basename(filename, ".tsx")); + } catch (e) { + false; + } + tasks.push({ + path: `${appSrcRoot}/_cofounder/generated/${_category}/${_id}/meta.json`, + data: JSON.stringify( + { + versions: [...new Set([...versions, _version])], + choice: _version, + }, + null, + "\t", + ), + }); + } + } + + await Promise.all( + tasks.map(async (task) => { + const dir = path.dirname(task.path); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } + if (!task.image) { + fs.writeFileSync(task.path, task.data, "utf8"); + } else { + // case by case : + // local ? copy paste from local path + // url ? fetch and write + if (task.data?.local?.length) { + const sourcePath = task.data.local; + await fsextra.copyFile(sourcePath, task.path); + } else if (task.data?.url?.length) { + const response = await fetch(task.data.url); + if (!response.ok) { + throw new Error(`Failed to fetch image from ${task.data.url}`); + } + const buffer = await response.buffer(); + fs.writeFileSync(task.path, buffer); + } + } + + if ( + task.dependencies && + process.env.AUTOINSTALL_ENABLE && + JSON.parse(process.env.AUTOINSTALL_ENABLE.toLowerCase()) + ) { + const dependenciesRootPath = task.path.split("/").slice(0, -1).join("/"); + console.log( + `\x1b[33m> dependencies updated for : ${dependenciesRootPath}\n> now running 'npm i' inside that folder\x1b[0m`, + ); + execSync(`npm i`, { + stdio: "inherit", + cwd: dependenciesRootPath, // folder where package.json is + }); + } + }), + ); +} + +async function opProjectStateUpdate({ context, data }) { + // save, modular + /* + aim for stream structure + */ + /* + context : { project``, } + data: { + local: bool, + cloud: bool, + operation: { + id: "ui:code:lalala", + refs: { + [id] : "id value to replace etc", + [otherId] : "some value etc", + } + }, + type: enum start,stream,end + stream: "" || false, + content: { + // should have key here as would be in state :: edge case, how to deal with '.' paths in object ? + key : "" // state key ? ie. pm.prd ; uxsitemap.views.whatever + data : {} + } + } + */ + /* + add : + _created? + _updated + */ + /* + update stream only if cloud + */ + + const { project } = context; + const { operation, type, stream, content } = data; + // const [ local , cloud ] = [ process.env.STATE_LOCAL , process.env.STATE_CLOUD]; + const { id, refs } = operation; + /* + cases of start/stream/end + */ + const query = { + path: modules[id], + data: {}, + }; + const ogPath = `${query.path}`; + + if (refs) { + Object.keys(refs).map((ref) => { + query.path = query.path.replace(`{${ref}}`, refs[ref]); + }); + } + + if (type === `start`) { + query.data._created = Date.now(); + query.data._processing = true; + } + if (type === `end`) { + query.data._updated = Date.now(); + query.data._processing = false; + // query.data = { ...query.data, ...content } + } + if (content) query.data = { ...query.data, ...content }; + console.dir({ "debug:op:project:state:update": { query } }, { depth: null }); + if ( + process.env.STATE_LOCAL && + JSON.parse(process.env.STATE_LOCAL.toLowerCase()) + ) { + const localPath = `db/projects/${project}/state/${query.path}.yaml`; + const dir = path.dirname(localPath); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } + if (config.merge.includes(id)) { + try { + const previous = yaml.parse(fs.readFileSync(localPath, "utf8").toString()); + query.data = merge(previous, query.data); + } catch (e) { + console.dir({ + "op:project:update:error": `no previous state found for ${id}, will write new instead of merge`, + }); + } + } + fs.writeFileSync(localPath, yaml.stringify(query.data), "utf8"); + } + if ( + process.env.STATE_CLOUD && + JSON.parse(process.env.STATE_CLOUD.toLowerCase()) + ) { + if (refs) { + // need to write dummy timestamp in docs in case of firestore ; to be able to query subcollections + // query.path find index of "}" and split there+1, replace, log dummy timestamp + let subs = []; + for (let i = 0; i < ogPath.length; i++) { + if (ogPath[i] === "}") { + let sub = ogPath.slice(0, i + 1); + Object.keys(refs).map((ref) => { + sub = sub.replace(`{${ref}}`, refs[ref]); + }); + subs.push(sub); + } + } + await Promise.all( + subs.map(async (p) => { + await utils.firebase.doc.update({ + path: `/db/userdata/projects/${project}/state/${p}`, + data: { _created: Date.now() }, + }); + }), + ); + } + + query.path = `/db/userdata/projects/${project}/state/${query.path}`; + if (config.merge.includes(id)) { + query.merge = true; + } + await utils.firebase.doc.update(query); + } + + if ( + process.env.AUTOEXPORT_ENABLE && + JSON.parse(process.env.AUTOEXPORT_ENABLE.toLowerCase()) && + config.exports.includes(id) + ) + await _exportOnSave({ context, data }); +} + +async function opProjectStateLoad({ context, data }) { + // should have local || cloud strategies + const { project } = context; + // const [local, cloud] = [process.env.STATE_LOCAL, process.env.STATE_CLOUD]; + try { + if ( + process.env.STATE_LOCAL && + JSON.parse(process.env.STATE_LOCAL.toLowerCase()) + ) + return await utils.load.local({ project }); + if ( + process.env.STATE_CLOUD && + JSON.parse(process.env.STATE_CLOUD.toLowerCase()) + ) + return await utils.load.cloud({ project }); + } catch (e) { + console.error(`op:project:state:load:error : ${e}`); + } + console.log(`found no previous local / cloud state for project : ${project}`); + return {}; +} + +async function opProjectStateExport({ context, data }) { + // tons to update , just disregard this for now + + return; + // force export full project ; from {data} +} + +async function opProjectStateSetup({ context, data }) { + // if local export enabled, duplicate boilerplate + const { project } = context; + const dirs = [ + { + source: `../boilerplate/backend-boilerplate`, + target: `${process.env.EXPORT_APPS_ROOT}/${project}/backend`, + }, + { + source: `../boilerplate/vitereact-boilerplate`, + target: `${process.env.EXPORT_APPS_ROOT}/${project}/vitereact`, + }, + ]; + + for (const { source, target } of dirs) { + // Copy the directory from source to target while respecting .gitignore + await fsextra.copy(source, target, { + filter: (src) => { + // Respect .gitignore by checking if the file is not listed in .gitignore + const ignoreFile = `${source}/.gitignore`; + if (fs.existsSync(ignoreFile)) { + const ignoreList = fs + .readFileSync(ignoreFile, "utf-8") + .split("\n") + .map((line) => line.trim()) + .filter(Boolean); + return !ignoreList.some((ignorePattern) => src.includes(ignorePattern)); + } + return true; // If no .gitignore, copy everything + }, + recursive: true, // Ensure folders are created recursively + }); + } + + await fsextra.copyFile( + `../boilerplate/package.json`, + `${process.env.EXPORT_APPS_ROOT}/${project}/package.json`, + ); + await fsextra.copyFile( + `../boilerplate/README.md`, + `${process.env.EXPORT_APPS_ROOT}/${project}/README.md`, + ); + // write meta.json + fs.writeFileSync( + `${process.env.EXPORT_APPS_ROOT}/${project}/vitereact/src/_cofounder/meta.json`, + JSON.stringify({ project }), + ); +} + +async function opProjectStateSave({ context, data }) { + // save, full current state of project +} + +export default { + "op:PROJECT::STATE:UPDATE": opProjectStateUpdate, + "op:PROJECT::STATE:LOAD": opProjectStateLoad, + "op:PROJECT::STATE:SETUP": opProjectStateSetup, + "op:PROJECT::STATE:EXPORT": opProjectStateExport, + // "op:PROJECT::STATE:SAVE": opProjectStateSave, +}; diff --git a/cofounder/api/system/functions/op/render.js b/cofounder/api/system/functions/op/render.js new file mode 100644 index 0000000..beb76b7 --- /dev/null +++ b/cofounder/api/system/functions/op/render.js @@ -0,0 +1,18 @@ +import utils from "@/utils/index.js"; + +async function opRenderLayout({ context, data }) { + /* ;; op:RENDER::LAYOUT + render either { view , section , block } using utils.render and svg stuff + in : -> { svg{string``} , mode`view||...` , ...(designSystem,saveFilepath...) } + out : ["svg","image"] + */ + // const { svg , mode } = data // { svg{string``} , mode`view||...` } + return await utils.render.svg({ + ...data, + // saveFilepath: `./dump/renders/_opRenderLayoutDebug_${data.mode}_${Date.now()}.png`, + }); +} + +export default { + "op:RENDER::LAYOUT": opRenderLayout, +}; diff --git a/cofounder/api/system/functions/pm/brd.js b/cofounder/api/system/functions/pm/brd.js new file mode 100644 index 0000000..7777d95 --- /dev/null +++ b/cofounder/api/system/functions/pm/brd.js @@ -0,0 +1,345 @@ +import utils from "@/utils/index.js"; +import yaml from "yaml"; + +async function pmBrdAnalysis({ context, data }) { + const { pm, db } = data; + const { details, prd, frd, drd } = pm; + + /* + should be 2 (3?) steps : + determine if needs { rest api , realtime socket io api } + make structure + */ + const backendPrompt = [ + { + role: "system", + content: `you are an expert product manager and software architect and API designer ; +your role is to determine, based on the provided analysis documents for the app project in development, the specfications of the app backend + +your task is very straightforward : +- based strictly on provided docs and outlined features, determine whether, yes or no, for the core features of the app MVP to be implemented, the backend : + > requires a RESTful API ? + > requires realtime (ie. websockets) ? + +you will answer exactly in this format, delimited by \`\`\`yaml : + +\`\`\`yaml +backend: + requirements: + restApi: + justifyYourAnswer: "write your reasoning for your answer in case it is true" + required: boolean # whether the backend requires or no a REST API + realtimeWebsockets: + justifyYourAnswer: "write your reasoning for your answer in case it is true" + required: boolean # whether the backend requires or no a REST API +\`\`\` + +answer in strict parseable Yaml format, exactly in the provided format structure +your answer should start with : \`\`\`yaml + +you will be tipped $9999 +`, + }, + { + role: "user", + content: `\`\`\`app-project:description +${details.text} +\`\`\``, + }, + { + role: "user", + content: `\`\`\`PRD:product-requirements-document +${prd} +\`\`\``, + }, + { + role: "user", + content: `\`\`\`FRD:features-requirements-document +${frd} +\`\`\``, + }, + { + role: "user", + content: `determine the backend specifications in terms of whether the backend needs a REST API , and whether it needs realtime Websockets. +your answer should start with : \`\`\`yaml + +you are a genius +`, + }, + ]; + const backendStructureRequirements = ( + await context.run({ + id: "op:LLM::GEN", + context, + data: { + model: `gpt-4o-mini`, //`gpt-4o`, + messages: backendPrompt, + preparser: `backticks`, + parser: `yaml`, + }, + }) + ).generated; + + const messages = [ + { + role: "system", + content: `you are an expert product manager and software architect and backend and server and API designer + +your job is to consult the provided web app details & analysis documents +in order to create a comprehensive and full Backend Requirements Document (BRD) for it + +the emphasis are user-facing features, +based on the expected features and different journeys of different users in the web app + +- your role is to conduct the analysis required to design the user-facing server of the provided task +- do a thorough analysis of the provided task + +--- + +- think from all possibles perspectives, put yourself in situation, to make sure your server analysis is fully comprehensive and ready to be developed +- ask yourself: + * what are the features involved in the user-facing server and that is called by the frontend ? + * if a server API is required, what are all the routes required by features expected to be seen by users in the frontend ? what should go in their schemas ? (not technical, rather analytical description from a feature perspective) + * if realtime features are required, what are all the events required by features expected to be seen by users in the frontend ? what should go in their schemas ? (not technical, rather analytical description from a feature perspective) + +- your analysis will be used to make a prod-ready backend and will be responsible for an app used by thousands of users, instantly +- your aim is to cover all use cases, as the expert product manager & architect you are + +> analyze the task thoroughly, then reply with your analysis in markdown format, in a well-formatted document to give to backend devs + +--- + +> your role here is not the implementation itself, you are the product architect consultant +> your role is to analyze the requirements for all scenarios required by all features + ask yourself : + * am i covering all needed server features? + * am i covering all features that the user expects ? + * if a feature necessitates the use of an external API (ie. checking a stock price , generating an ai image, advanced features that need the use of an external API, etc ...) + important : the backend already has DB and storage capabilities , so DO NOT MENTION DB OR STORAGE AS EXTERNAL APIS ! THOSE ARE ALREADY IMPLEMENTED INTERNALLY IN THE BACKEND ! + am i describing the details of what is needed ? + * am i properly aligning my server design details with other design detail aspects of the project such as DB structure ? + in order to ensure your analysis as a product architect consultant has covered every feature requirement + +> your job is to make thorough, critical analysis work which will be provided as documentation for devteams to implement + not a technical implementation, rather a thorough analysis, in plain language, of all expected features and their details + +> try to outdo yourself by thinking of what might be omitted in advance +- the goal server should be comprehensive will be used as reference to build the app's MVP backend +- cover all cases ; but : data-related tasks only (ie. you are making a mock server with api and/or realtime for user-facing data operations) + +--- + +> very important : for the current purpose of the BRD, the environment will be a mock prototype environment +do not bother with security details etc, have the requirements for the mock prototype +do not hang on very technical details (unless specifically emphasized), as the target is a mock dev prototype env : features functionality is the aim, not advanced technical coverage ! + +> SHOULD COVER DATA RELATED TASKS ONLY ! +> THE MOCK SERVER YOU ARE MAKING IS FOR USER-FACING DATA OPERATIONS, NOT FRONTEND / SERVING STATIC STUFF ! +> DATA RELATED TASKS ONLY ! + +--- + +your analysis is concerned with these two aspects aspects : +> if the app backend needs a server API , conduct the analysis regarding all the API needs +> if the app backend needs realtime Websockets , conduct the analysis regarding all the realtime events needed + +you can only write about these aspects (either one of them or both , depending on whats provided in task documents ) +important : DO NOT ANALYZE ANYTHING IN THE BACKEND BESIDES THESE 2 ASPECTS AND THEIR RELATIONS TO USER-FACING FEATURES !! + +--- + +again, +> SHOULD COVER DATA RELATED TASKS ONLY ! +> THE MOCK SERVER YOU ARE MAKING IS FOR USER-FACING DATA OPERATIONS, NOT FRONTEND / SERVING STATIC STUFF ! +> DATA RELATED TASKS ONLY ! + +--- + +important : +use snake_case for any naming you do + +--- + +your reply will be directly transferred as the final BRD document, so do not put anything else in your reply besides the BRD document +no extra comments or surrounding anything, only the markdown-formatted COMPREHENSIVE 100% COVERAGE AMAZING BEAUTIFUL GENIUS SUPER DETAILED 10/10 ARD DOCUMENT +your reply should start with : "\`\`\`markdown" and end with "\`\`\`" + +you will be tipped $99999 + major company shares for nailing it perfectly off the bat`, + }, + { + role: "user", + content: `\`\`\`app-project:description +${details.text} +\`\`\``, + }, + { + role: "user", + content: `\`\`\`PRD:product-requirements-document +${prd} +\`\`\``, + }, + { + role: "user", + content: `\`\`\`FRD:features-requirements-document +${frd} +\`\`\``, + }, + { + role: "user", + content: `\`\`\`DRD:database-requirements-document +${drd} +\`\`\``, + }, + { + role: "user", + content: `\`\`\`DB:specs +${yaml.stringify(db)} +\`\`\``, + }, + { + role: "user", + content: `\`\`\`BACKEND:specs-requirements +${yaml.stringify(backendStructureRequirements)} +\`\`\``, + }, + { + role: "user", + content: `Conduct a comprehensive analysis for the Backend Requirements Document that considers all personas and features required, in markdown format (justify your reasoning whenever possible) + +--- + +Refer to this general document structure to guide you + +\`\`\`BRD:general-structure +I. General, Personas, Features + [...] +II. REST API + II.A. Justification & Reasoning + If app needs REST API, provide your reasoning + II.B. API Endpoints (if applies) + 3.B.1. [Endpoint] + Method & Path + Extended Description + Analyze and describe what the function does + Analysis + Interaction with <> DB + Analyze how does function interact with database based on provided DB details and schemas + ask yourself questions such as : + What fields does it need to insert / get / update / delete / ... for each operation ? + Based on provided DB details, does it need to create data on the fly such as ids / dates / ... ? + Does it need to insert data in multiple tables to not make DB conflicts ? + Be very specific & detailed into exactly how the relationships to <> DB tables work in this function + justify any answer by including snippets from the provided DB postgres code and elaborating + remember : the backend is tasked with creating any primitive required by db (ie. ids , ...), + as you can tell from the postgres code + make things 100% perfectly congruent in your analysis + Include any additional important analysis notes + Interaction with <> External APIs + Analyze if function needs to interact with external APIs for needed capabilities, and if so describe + Remember : App already has DB and storage , so external APIs would be external capabilities outside of these 2 + Add any important general analysis notes + Data Details + Auth + Does function requires the user to provided an auth token ? + Request + Body content type (json , form , ... ?) + Schema + Response + Content type + Schema + Additionals details / notes (if applies) + [...] +II. Realtime Websockets (if applies) + III.A. Justification & Reasoning + If app needs realtime events, provide your reasoning + III.B. Events (if applies) + 3.B.1. [Event] + Event name + Extended Description + Analyze and describe what the function does + Analysis + Interaction with <> DB + Analyze how does function interact with database based on provided DB details and schemas + ask yourself questions such as : + What fields does it need to insert / get / update / delete / ... for each operation ? + Based on provided DB details, does it need to create data on the fly such as ids / dates / ... ? + Does it need to insert data in multiple tables to not make DB conflicts ? + Be very specific & detailed into exactly how the relationships to <> DB tables work in this function + justify any answer by including snippets from the provided DB postgres code and elaborating + remember : the backend is tasked with creating any primitive required by db (ie. ids , ...), + as you can tell from the postgres code + make things 100% perfectly congruent in your analysis + Include any additional important analysis notes + Interaction with <> External APIs + Analyze if function needs to interact with external APIs for needed capabilities, and if so describe + Remember : App already has DB and storage , so external APIs would be external capabilities outside of these 2 + Add any important general analysis notes + Data Details + Auth + Does function requires the user to provided an auth token ? + Request payload + Schema + Response payload + Schema + Additional details / notes (if applies) + [...] +IV. Additional Notes + Any additional notes worth mentionning regarding the backend requirements +\`\`\` + +--- +you're a genius`, + }, + ]; + + const brd = ( + await context.run({ + id: "op:LLM::GEN", + context, + data: { + model: `chatgpt-4o-latest`, //`gpt-4o`, + messages, + preparser: `backticks`, + parser: false, + }, + }) + ).generated; + + await context.run({ + id: "op:PROJECT::STATE:UPDATE", + context, + data: { + operation: { + id: "pm:brd", + }, + type: `end`, + content: { + key: "pm.brd", + data: brd, + }, + }, + }); + + const backendRequirements = backendStructureRequirements.backend.requirements; + + await context.run({ + id: "op:PROJECT::STATE:UPDATE", + context, + data: { + operation: { + id: "backend:requirements", + }, + type: `end`, + content: { + key: "backend.requirements", + data: backendRequirements, + }, + }, + }); + + return { pm: { brd }, backend: { requirements: backendRequirements } }; +} + +export default { + "PM:BRD::ANALYSIS": pmBrdAnalysis, +}; diff --git a/cofounder/api/system/functions/pm/drd.js b/cofounder/api/system/functions/pm/drd.js new file mode 100644 index 0000000..0a4aebf --- /dev/null +++ b/cofounder/api/system/functions/pm/drd.js @@ -0,0 +1,139 @@ +import utils from "@/utils/index.js"; +import yaml from "yaml"; + +async function pmDrdAnalysis({ context, data }) { + /* ;; PM:DRD::ANALYSIS + make {userdetails,prd,frd,FJMD} -> DRD analysis + + + out : ["pm"] + */ + + const { pm } = data; + const { details, prd, frd, fjmd } = pm; + // const {text , attachments} = details + const messages = [ + { + role: "system", + content: `you are an expert product manager and database designer + +your job is to consult the provided web app details, Product Requirements Document, Features Requirements Documents & Features Journeys Map Document +in order to create a comprehensive and full Feature Database Requirements Document (DRD) for it + +--- + +the emphasis are user-facing features, +based on the expected features and different journeys of different users in the web app + +- your role is to conduct the analysis part for the provided app in development's DB part + DB schemas analysis should be comprehensive and cover EVERYTHING required by the app MVP, and nothing more - no shiny secondary features, but nothing less than 100% comprehensive for every single expected functionality in production + +- your current role is to do a thorough analysis of the provided task and answer with your analysis in markdown format + +- think from perspectives of multiple personas, put yourself in situation, to make sure your DB schemas reply is fully comprehensive and ready to be used in production exactly as is +- your answer will be pushed to dev teams directly, and will be responsible for an app used by thousands of users +- your aim is to cover all use cases, as the expert product manager you are + +- ask yourself: + * what are the key personas that use the app ? + * what are all the schemas required by features expected to be seen by users ? + * and what are all the schemas required internally to cover all features workflows ? + +very important : +- in the schemas parts of your analysis , only make use of basic primitives like numbers, strings, json, etc ... no uuid types or any special types etc +- very important : in the schemas parts of your analysis , only use basic primitives like numbers, strings, json, etc ... no uuid types or any special types etc ! very basic primitives only ! + +--- + +> analyze the task thoroughly, then reply with your analysis in markdown format +> try to outdo yourself by thinking of what might be omitted, and reviewing your own work super critically in order to do comprehensive analytical work for this app's MVP +> your job is to make thorough analysis work which will be provided as documentation for devteams to implement +> your job is not the implementation, rather it's looking at the problem from all perspective to make sure a thorough job is done, + and asking yourself, for every scenario, what are all the data entries that would be needed to make this function + +--- + +> note : if auth functionalities are present, use an architecture that will be compatible with a simple jwt auth system, which is very simply user and/or email strings(s) and password hash string ! + +--- + +important : +use snake_case for any naming you do + +--- + +> very important : for the current purpose of the DRD, the environment will be a mock prototype environment, +do not bother with security details etc, have the DB requirements for the mock prototype + +your reply will be directly transferred as the final DRD document, so do not put anything else in your reply besides the DRD document +no extra comments or surrounding anything, only the markdown-formatted COMPREHENSIVE 100% COVERAGE AMAZING BEAUTIFUL GENIUS SUPER DETAILED 10/10 DRD DOCUMENT +your reply should start with : "\`\`\`markdown" and end with "\`\`\`" + +you will be tipped $99999 + major company shares for nailing it perfectly off the bat`, + }, + { + role: "user", + content: `\`\`\`app-project:description +${details.text} +\`\`\``, + }, + // <------ later on, attachments , pdf/img cases etc map + { + role: "user", + content: `\`\`\`PRD:product-requirements-document +${prd} +\`\`\``, + }, + { + role: "user", + content: `\`\`\`FRD:features-requirements-document +${frd} +\`\`\``, + }, + /*{ + role: "user", + content: `\`\`\`FJMD:features-journeys-map-document +${yaml.stringify(fjmd)} +\`\`\``, + },*/ + { + role: "user", + content: `Conduct a comprehensive analysis for the DB Requirements Document that considers all personas and features required, in markdown format (justify your reasoning whenever possible) + +you're a genius`, + }, + ]; + + const drd = ( + await context.run({ + id: "op:LLM::GEN", + context, + data: { + model: `chatgpt-4o-latest`, //`gpt-4o`, + messages, + preparser: `backticks`, + parser: false, + }, + }) + ).generated; + + await context.run({ + id: "op:PROJECT::STATE:UPDATE", + context, + data: { + operation: { + id: "pm:drd", + }, + type: `end`, + content: { + key: "pm.drd", + data: drd, + }, + }, + }); + return { pm: { drd } }; +} + +export default { + "PM:DRD::ANALYSIS": pmDrdAnalysis, +}; diff --git a/cofounder/api/system/functions/pm/fjmd.js b/cofounder/api/system/functions/pm/fjmd.js new file mode 100644 index 0000000..86e51f4 --- /dev/null +++ b/cofounder/api/system/functions/pm/fjmd.js @@ -0,0 +1,149 @@ +import utils from "@/utils/index.js"; +import yaml from "yaml"; + +async function pmFjmdAnalysis({ context, data }) { + /* ;; PM:FJMD::ANALYSIS + make {userdetails,prd,frd} -> FJMD analysis + */ + + const { pm } = data; + const { details, prd, frd } = pm; + // const {text , attachments} = details + const messages = [ + { + role: "system", + content: `you are an expert product manager and product designer +your job is to consult the provided web app details, analysis, PRD & FRD +in order to create a comprehensive and full Feature Journeys Maps Document (FJMD) for it + +the emphasis are user-facing features, +based on the expected features and different journeys of different users in the web app + +your generated FJMD is very detailed, comprehensive and covers absolutely 100% of everything required for the web app + +you are not limited by provided example journeys +your analysis here should cover ALL journey cases (of the app MVP) + +while conducting your FJMD, ask yourself: +- am i covering all 100% the purpose and functions required for the app ? +- am i covering all 100% the expected features from all the users' perspectives? even the small details ? +- am i covering all 100% the user journeys ? +- am i covering all details that other product managers might have omitted from my analysis ? +- am i making sure what i am detailing in my FJMD is absolutely 100% comprehensive and ready to be put into development without any alteration ? + +conduct and reply with a generated comprehensive perfect FJMD document, yaml-formatted +the reply format should directly be a list of journeys items in valid yaml format, with this structure : + + +journeys: + - name : "..." + category : "..." + journeyId: "/*like JOUR-01 format*/" + description: "..." + participants: "..." + preconditions: "describe pre-existing conditions or assumptions..." + postconditions: "describe state expected outcomes after completing the journey" + steps : #list of journey steps, correlated with provided FRD (& PRD) + - intent : "..." + userInteraction : "describe how users will interact with the interface" + featuresIds : ["","",...] # list of featureIds involved in this step (featureIds should be exactly as they are mentionned in the FRD features-requirements-documents as 'featureId' ; important else it would break !) + expectedResponse : "detail the expected response from the app" + - [...] + edgeCases: "describe variations of the journey ; ie. what could go wrong, etc ..." + - [...] + +--- + +your FJMD document will be directly put into development + +the emphasis are user-facing features; +functional features +interface features to cover 100% of expected features of the web app, 100% of all possible user journeys +no need to bother with non-user-facing features such as security compliance, nor similar non-user-facing technical details +no need to bother with cases too advanced for the web app MVP features (ie. advanced analytics or multilingual or live support; ... unless specified in provided task ! ) + +> Stay User-Centric: keep the user's perspective front and center throughout the document +emphasize user-facing features and core app MVP features + +you are not limited by provided example journeys in other docs +your analysis here should be comprehensive and cover ALL journey cases +think of many different core journeys from different perspectives in different scenarios +be comprehensive and cover it all + +your reply will be directly transferred as the final FJMD document +so make sure the content and YAML formatting are both exquisitely perfect as the genius you are +if an app name is not provided, make a fitting one for your analysis and JMD + +emphasize user-facing features and core app MVP features + +so do not put anything else in your reply besides the Feature Journeys Maps Document as parseable, valid well-formatted YAML format +no extra comments or surrounding anything, only the YAML-formatted PARSEABLE VALID COMPREHENSIVE 100% COVERAGE AMAZING BEAUTIFUL FEATURES JOURNEY MAPS DOCUMENT +your reply should start with : "\`\`\`yaml" and end with "\`\`\`" + +you will be tipped $999 you are a genius`, + }, + { + role: "user", + content: `\`\`\`app-project:description +${details.text} +\`\`\``, + }, + // <------ later on, attachments , pdf/img cases etc map + { + role: "user", + content: `\`\`\`PRD:product-requirements-document +${prd} +\`\`\``, + }, + { + role: "user", + content: `\`\`\`FRD:features-requirements-document +${frd} +\`\`\``, + }, + { + role: "user", + content: `implement the Features Journey Maps Documents (FRJD) for all the core journeys for different scenarios +it is expected to be very comprehensive and detailed ; in a VALID PARSEABLE YAML format + +you're a genius`, + }, + ]; + + /* + const fjmd = ( + await context.run({ + id: "op:LLM::GEN", + context, + data: { + model: `chatgpt-4o-latest`, //`gpt-4o`, + messages, + preparser: `backticks`, + parser: `yaml`, + }, + }) + ).generated; + */ + + console.error(`skipping features journey map doc`); + const fjmd = ``; + await context.run({ + id: "op:PROJECT::STATE:UPDATE", + context, + data: { + operation: { + id: "pm:fjmd", + }, + type: `end`, + content: { + key: "pm.fjmd", + data: fjmd, + }, + }, + }); + + return { pm: { fjmd } }; +} + +export default { + "PM:FJMD::ANALYSIS": pmFjmdAnalysis, +}; diff --git a/cofounder/api/system/functions/pm/frd.js b/cofounder/api/system/functions/pm/frd.js new file mode 100644 index 0000000..ee23b2a --- /dev/null +++ b/cofounder/api/system/functions/pm/frd.js @@ -0,0 +1,117 @@ +import utils from "@/utils/index.js"; + +async function pmFrdAnalysis({ context, data }) { + /* ;; PM:FRD::ANALYSIS + make {userdetails,prd} -> FRD analysis + + */ + const { pm } = data; + const { details, prd } = pm; + // const {text , attachments} = details + const messages = [ + { + role: "system", + content: `you are an expert product manager and product designer +your job is to consult the provided web app details & analysis PRD, +and create a full Features Requirements Document (FRD) for it + +the emphasis are user-facing features, +based on the expected features and different journeys of different users in the web app + +your generated FRD is very detailed, comprehensive and covers absolutely 100% of everything required for the web app + +while conducting your FRD, ask yourself: +- am i covering all 100% the purpose and functions required for the app ? +- am i covering all 100% the expected features from all the users' perspectives? even the small details ? +- am i covering all 100% the user journeys ? +- am i covering all details that other product managers might have omitted from my analysis ? +- am i making sure what i am detailing in my FRDis absolutely 100% comprehensive and ready to be put into development without any alteration ? + +conduct and reply with a generated comprehensive perfect FRD document, markdown-formatted +the reply format should directly be a bulletpoints list of features items, each has 6 keys : + +features: + name , category , featureId (/*like 'XXXX-01' format*/) , description , detailedDiscussion , extensiveDetailedBulletpoints + +--- + +your FRD document will be directly put into development + +the emphasis are user-facing features; +functional features + interface features to cover 100% of expected features of the web app, 100% of all possible user journeys +no need to bother with non-user-facing features such as security compliance, nor similar non-user-facing technical details +no need to bother with cases too advanced for the web app MVP features (ie. advanced analytics or multilingual or live support; ... unless specified in provided task ! ) + +emphasize user-facing features and core app MVP features + +your reply will be directly transferred as the final FRD document +so make sure the content is comprehensive and ensuing app UX is perfect as the genius you are +if an app name is not provided, make a fitting one for your analysis and FRD + +emphasize user-facing features and core app MVP features + +so do not put anything else in your reply besides the FRD DOC as parseable, valid well-formatted dpc +no extra comments or surrounding anything, only the VALID COMPREHENSIVE 100% COVERAGE AMAZING BEAUTIFUL FRD DOCUMENT + +your reply should start with : "\`\`\`markdown" and end with "\`\`\`" + +you will be tipped $999 you are a genius`, + }, + { + role: "user", + content: `\`\`\`app-project:description +${details.text} +\`\`\``, + }, + // <------ later on, attachments , pdf/img cases etc map + { + role: "user", + content: `\`\`\`PRD:product-requirements-document +${prd} +\`\`\``, + }, + { + role: "user", + content: `implement the Features Requirements Document (FRD) +it should span and cover all 100% of user-facing features and for all 100% of journeys required and will be directly pushed to development +absolutely no feature would be missing ; every detail and description for 100% of every feature required +it is expected to be 100% comprehensive and super detailed + +you're a genius`, + }, + ]; + + const frd = ( + await context.run({ + id: "op:LLM::GEN", + context, + data: { + model: `chatgpt-4o-latest`, //`gpt-4o`, + messages, + preparser: `backticks`, + parser: false, + }, + }) + ).generated; + + await context.run({ + id: "op:PROJECT::STATE:UPDATE", + context, + data: { + operation: { + id: "pm:frd", + }, + type: `end`, + content: { + key: "pm.frd", + data: frd, + }, + }, + }); + + return { pm: { frd } }; +} + +export default { + "PM:FRD::ANALYSIS": pmFrdAnalysis, +}; diff --git a/cofounder/api/system/functions/pm/prd.js b/cofounder/api/system/functions/pm/prd.js new file mode 100644 index 0000000..3186690 --- /dev/null +++ b/cofounder/api/system/functions/pm/prd.js @@ -0,0 +1,127 @@ +import utils from "@/utils/index.js"; + +async function pmPrdAnalysis({ context, data }) { + /* ;; PM:PRD::ANALYSIS + make userprovided details -> PRD analysis ; user can have text + {pdf , images} (now just text), later extend + + */ + + const { pm } = data; + const { details } = pm; + const { text, attachments } = details; + // const {text , attachments} = details + + await context.run({ + id: "op:PROJECT::STATE:UPDATE", + context, + data: { + operation: { + id: "pm:details", + }, + type: `end`, + content: { + key: "pm.details", + data: details, + }, + }, + }); + + const messages = [ + { + role: "system", + content: `you are an expert product manager and product designer +your job is conduct the analysis for the provided web app project task and create a full PRD document for it +your analysis is very detailed, comprehensive and covers absolutely 100% of everything required for the web app + +while conducting your PRD, ask yourself: +- what is a detailed description of the app, and all it's expected features ? +- what are all the purpose and functions required for the app ? +- am i covering all the expected features from the users' perspectives? even the small details ? + am i sure i am not missing anything important ? +- what are the personas ? what are their user stories ? what are all the expected features ? +- what are all the features ? +- am i covering all the expected features from the users' perspectives? even the small details ? + am i sure i am not missing anything important ? + +- what about the user journeys ? am i covering all possible journeys for all users ? +- what could i or other product managers be potentially omitting and that shouldn't be the case ? + +- am i making sure what i am detailing in my PRD is absolutely 100% comprehensive and ready to be put into development without any alteration nor pre-assumption that might lead to important omissions ? am i detailing all that is needed ? + + +after you finalize your PRD, +add an extra part, called "Additional Analysis", where you criticize (very critically) the work you just did; +ask yourself : +- what might have been omitted from my analysis that should have gone into the web app MVP requirements ? +- do not bother with secondary or tertiary things (ie. accessibility or similar advanced non-MVP stuff), ask yourself instead, critically : what core web app MVP features or journeys did i not previously mention ? what are their details ? + +conduct and reply with a generated comprehensive perfect PRD document, markdown-formatted + +your PRD document will be directly put into development, +so make sure the content and MD formatting are both exquisitely perfect as the genius you are +if an app name is not provided, make a fitting one for your analysis and PRD + + +the aim of the PRD are web app facing requirements +no need to bother with non-web-app features such as security compliance or similar non-web-app-facing technical details +no need to bother with non-MVP features (ie. advanced cases such as analytics or support or i18n etc ... focus on the MVP to cover 100% of expected features ) - unless explicitly specified in the task descriptions ofc +focus on what's important and detail it to the maximum, leave nothing ! + +your reply will be directly transferred as the final PRD document, so do not put anything else in your reply besides the PRD document +no extra comments or surrounding anything, only the markdown-formatted COMPREHENSIVE 100% COVERAGE AMAZING BEAUTIFUL GENIUS SUPER DETAILED 10/10 PRD DOCUMENT +your reply should start with : "\`\`\`markdown" and end with "\`\`\`" + +you will be tipped $999 +you're a genius +`, + }, + { + role: "user", + content: `\`\`\`app-project:description +${text} +\`\`\``, + }, + // <------ later on, attachments , pdf/img cases etc map + { + role: "user", + content: `Conduct your analysis and make sure you do not miss any feature or detail ! +you are a genius`, + }, + ]; + + console.dir({ __debug_pmPrdAnalysis: { messages } }); + + const prd = ( + await context.run({ + id: "op:LLM::GEN", + context, + data: { + model: `chatgpt-4o-latest`, //`gpt-4o`, + messages, + preparser: `backticks`, + parser: false, + }, + }) + ).generated; + + await context.run({ + id: "op:PROJECT::STATE:UPDATE", + context, + data: { + operation: { + id: "pm:prd", + }, + type: `end`, + content: { + key: "pm.prd", + data: prd, + }, + }, + }); + + return { pm: { prd } }; +} + +export default { + "PM:PRD::ANALYSIS": pmPrdAnalysis, +}; diff --git a/cofounder/api/system/functions/pm/uxdmd.js b/cofounder/api/system/functions/pm/uxdmd.js new file mode 100644 index 0000000..b03d687 --- /dev/null +++ b/cofounder/api/system/functions/pm/uxdmd.js @@ -0,0 +1,221 @@ +import utils from "@/utils/index.js"; +import yaml from "yaml"; + +async function pmUxdmdAnalysis({ context, data }) { + /* ;; PM:UXDMD::ANALYSIS + {pm docs , db , openapi? , uxsitemap {analysis,struct,...}?} -> (<> crossanalysis) to make UX Datamap Doc + + */ + + const { pm, db, backend } = data; + const { details, prd, frd, drd, uxsmd, brd } = pm; + + const messages = [ + { + role: "system", + content: `- you are a genius Product Manager & Software Archtect + +- your role is to conduct the analysis required to design the frontend app architecture for the provided project in its details + +- think from perspectives of multiple personas, put yourself in situation, to make sure your app architecture analysis is fully comprehensive and ready to be developed +- ask yourself: + * what are the journeys involved in the app frontend ? + * what are all the routes , views , slugs , props , URL parameters , auth restrictions, required by features expected to be seen by users in the frontend ? + * what should go in their schemas ? (not technical, rather analytical description from a feature perspective) +- your analysis will be used to make a prod-ready app and will be responsible for an app used by thousands of users +- your aim is to cover all use cases, as the expert product manager & architect you are + +> analyze the task thoroughly, then reply with your analysis in markdown format, in a well-formatted document to give to app designers & devs + +> your role here is not the implementation itself, you are the product architect consultant +> your role is to analyze the requirements for all scenarios required by all features + ask yourself : + * am i covering all needed app features? + * am i covering all features that the user expects ? + in order to ensure your analysis as a product architect consultant has covered every feature requirement +> your job is to make thorough, critical analysis work which will be provided as documentation for designers & devteams to implement + not a technical implementation, rather a thorough analysis, of all expected architecture features and their details + +--- + +your aim is to determine, in extreme detail: + + I. the structure of the app: + * routes + * what views they link to + * slugs (ie. path /something/:example) if applies + * if route is restricted or not + * URL params (ie. /somepath?param_a=example¶m_b=example ) if applies + + > important : only refer to views ids specified in provided ux sitemap ! (UV_* and GV_* views) + + II. the relationships between views of the app (based on the provided UX sitemap ; (unique views UV_* and shared ui views GV_*) ) & app data (based on provided DB & backend docs & schemas ): + + conduct a cross analysis between UX sitemap views <> app data states in order to : + + 1. determine stateful variables , actions/dynamic functions , params : + 1A. if the view has state for dynamic data, describe + 1B. if the view should access slug passed into url (ie. /something/:example ), describe + 1C. if the view should access URL params and use them for a feature (ie. /somepath?param_a=X¶m_b=Y), describe + 1D. if the view has actions/dynamic functions like API calls or realtime events, describe in detail + 1E. revise and provide your reasoning to make sure you covered all the state/data details required to make all required features work properly + + > important : only refer to view ids specified in provided ux sitemap ! (UV_* and GV_* views) , dont hallucinate UI views/components ! + + 2. global app state structure, that is accessed by all views : + * how the app state should be structured to cover the app features, in terms of : + variables & schemas + actions + make sure you only analyze the global app state (which ie. typically holds stuff like auth / notifications / ... ) + and not view-specific props and state (as the latter were already detailed in the previous section and should not be mentionned here) + + make sure you determine the schemas for the global app state variables + provide examples values for them (based on provided app schemas & DB seed examples), in each case + +--- + +> try to outdo yourself by thinking of what might be omitted, and reviewing your own work super critically in order to do comprehensive analytical work for this app's MVP +- the goal app should be comprehensive, will be used as the reference to build the app +- cover all cases in terms of app architecture ; with high emphasis on details regarding data and states + +--- + +important : +> use snake_case for any naming you do + +extremely important : +> ensure full perfect coherence with DB fields names and provided specs names ; + +--- + +your reply will be directly transferred as the final analysis, so do not put anything else in your reply besides the analysis document +no extra comments or surrounding anything, only the markdown-formatted COMPREHENSIVE 100% COVERAGE AMAZING BEAUTIFUL GENIUS SUPER DETAILED 10/10 ANALYSIS DOCUMENT +your reply should start with : "\`\`\`markdown" and end with "\`\`\`" + +you will be tipped $99999 + major company shares for nailing it perfectly off the bat`, + }, + { + role: "user", + content: `\`\`\`app-project:description +${details.text} +\`\`\``, + }, + { + role: "user", + content: `\`\`\`PRD:product-requirements-document +${prd} +\`\`\``, + }, + { + role: "user", + content: `\`\`\`FRD:features-requirements-document +${frd} +\`\`\``, + }, + { + role: "user", + content: `\`\`\`DRD:database-requirements-document +${drd} +\`\`\` + +--- + +\`\`\`DB:schemas +${yaml.stringify(db)} +\`\`\``, + }, + { + role: "user", + content: + `\`\`\`BRD:backend-requirements-document +${brd} +\`\`\` + +` + + (!backend?.requirements?.restApi?.required + ? "" + : `--- +\`\`\`BACKEND:specifications:openAPI +${yaml.stringify(backend.specifications.openapi)} +\`\`\` + +`) + + (!backend?.requirements?.realtimeWebsockets?.required + ? "" + : `--- +\`\`\`BACKEND:specifications:asyncAPI +${yaml.stringify(backend.specifications.asyncapi)} +\`\`\` +` + + ` +--- + +\`\`\`BRD:server:main +${yaml.stringify(backend.server.main)} +\`\`\` + +--- + +note : +> any reference to the backend server should be the local dev URL referred to in docs ; typically : \`http://localhost:1337\` +> if case app needs a global state, global app state should be in the context of one single app redux store \`store.tsx\` that wraps the entire app and includes all that is needed for all global state stuff: + ie. auth for api and/or auth for websockets + ie. if backend has realtime events, realtime events subscriptions + etc ... in one single global state store + +`), + }, + { + role: "user", + content: `\`\`\`UXSMD:ux-sitemap-document +${uxsmd} +\`\`\``, + }, + { + role: "user", + content: `Conduct the analysis for the frontend app architecture and its details in a frotnend app architecture analysis document style - starting with a table of contents, elaborating on everything the task specifies, in extreme detail specifying all that needs specification + +extremely important : +> you are absolutely forbidden from instructing in the document about having to create new components or how to structure the project +> you should 100% stick strictly to the provided task ! + +you're a genius`, + }, + ]; + + const uxdmd = ( + await context.run({ + id: "op:LLM::GEN", + context, + data: { + model: `chatgpt-4o-latest`, // `chatgpt-4o-latest`,//`gpt-4o`, + messages, + preparser: `backticks`, + parser: false, + }, + }) + ).generated; + + await context.run({ + id: "op:PROJECT::STATE:UPDATE", + context, + data: { + operation: { + id: "pm:uxdmd", + }, + type: `end`, + content: { + key: "pm.uxdmd", + data: uxdmd, + }, + }, + }); + + return { + pm: { uxdmd }, + }; +} + +export default { + "PM:UXDMD::ANALYSIS": pmUxdmdAnalysis, +}; diff --git a/cofounder/api/system/functions/pm/uxsmd.js b/cofounder/api/system/functions/pm/uxsmd.js new file mode 100644 index 0000000..f1d6d38 --- /dev/null +++ b/cofounder/api/system/functions/pm/uxsmd.js @@ -0,0 +1,159 @@ +import utils from "@/utils/index.js"; +import yaml from "yaml"; + +async function pmUxsmdAnalysis({ context, data }) { + /* ;; PM:UXSMD::ANALYSIS + {pm docs , db , openapi?} -> (<> crossanalysis) to make UX Sitemap doc + + */ + const { pm } = data; + const { details, prd, frd } = pm; + const { text, attachments } = details; + const messages = [ + { + role: "system", + content: `you are an expert product manager and app designer + +your job is to consult the provided web app details and additional documents +in order to create a comprehensive and full UX Sitemap Document (UXSMD) for it + +- your current role is to do a thorough analysis of the provided web app requirements and answer with your analysis in markdown format + +- make sure your UX Sitemap Document is fully comprehensive and ready to be put in development exactly as is + your answer will be pushed to dev teams directly, and will be responsible for an app used by thousands of users + your aim is to cover all use cases, as the expert app designer you are + +--- + +ask yourself: + + I. + * am i covering shared global UI views in my analysis (ie. top navigation, footers, ...) in a separate section, + which also details the components that share them ? + am i assigning unique and expressive title-cased ids to them (in format "GV_{...}" ie. "GV_TopNav" ) ? + am i careful to consider cases of authenticated/unauthenticated + (whether conditionals regarding accessing the view itself or conditionals on its contained elements) to make sure my coverage is not missing things ? + + * am i covering all the needed unique UI views ; for all the required features ? + am i assigning unique and expressive title-cased ids to them (in format "UV_{...}" ie. "UV_Landing" ) ? + am i making sure unique views do not include duplicate shared global UI views which were already previously covered ? + am i careful to consider cases of authenticated/unauthenticated (whether conditionals regarding accessing the view itself or conditionals on its contained elements) to make sure my coverage is not missing things ? + + * am i extensively describing everything in details for the dev team to have 100% coverage of everything needed through my UX Sitemap Document analysis ? + + * am i covering EVERYTHING expected to be present in this web app: + every view (every unique view and every shared global view) expected to be in the app ? + every view's components expected to be in the app to cover all 100% of features and all their details ? + am i covering the views for all workflows, end to end ? + + * am i making sure i am covering the core and essential features / views , and not some optional secondary/tertiary not really required stuff ? + + II. + * am i describing the functional and features analysis of each view before further detailing it in order to have a cohesive and comprehensive analysis and not omit any details ? + * what are all the requirements needed by features expected to be seen by users in terms of UI views ( unique views and shared global views ) and contained views' components ? + * cross analysis between feature <> ui views required to create in ux sitemap ? + * what are ALL THE VIEWS required by ALL THE REQUIREMENTS required by the user ? + * am i covering all views (unique views and shared global views) ? + with all extensive details and descriptions ? + * am i making sure i am covering the core and essential features / views , and not some optional secondary/tertiary not really required stuff ? + + III. + can i make a table for all the cross links analysis between different views in order to establish inter-app navigation relationships ? + can i describe their intent in each case ? + can i also describe how the linking works (in terms of ui elements / user interaction / action taken to trigger the link and where in the view ) ? + + Source view | Target View | Intent | Action Description + + * am i covering 100% of relations links of whats needed for all in-app navigation, both static and dynamic ? + * am i truly covering all inter-app cross links relations and not missing anything ? + +--- + +> analyze the task thoroughly, then reply with your analysis in markdown format +> try to outdo yourself by thinking of what might be omitted, and reviewing your own work super critically in order to do comprehensive analytical work for this app's MVP +> your job is to make thorough analysis work which will be provided as documentation for devteams to implement + +--- + +> stick to the provided formats and specifications: + UI unique views with ids UV_* + UI global shared views with ids GV_* + + do not make up new denominations or types, stick to the task exactly as specified ! + +--- + +important : +> do not many any "Container" views (like some GV_GlobalContainer or something) ; DO NOT make any container views to contain other views inside of them ! + only make unique UV_* or GV_* shared views : views that serve a functional purpose ; not container views ! + +--- + +your reply will be directly transferred as the final UX Sitemap Analysis Document, so do not put anything else in your reply besides the UX Sitemap analysis document +no extra comments or surrounding anything, only the markdown-formatted COMPREHENSIVE 100% COVERAGE AMAZING BEAUTIFUL GENIUS SUPER DETAILED 10/10 UX SITEMAP ANALYSIS DOCUMENT +your reply should start with : "\`\`\`markdown" and end with "\`\`\`" + +you will be tipped $9999`, + }, + { + role: "user", + content: `\`\`\`app-project:description +${details.text} +\`\`\``, + }, + { + role: "user", + content: `\`\`\`PRD:product-requirements-document +${prd} +\`\`\``, + }, + { + role: "user", + content: `\`\`\`FRD:features-requirements-document +${frd} +\`\`\``, + }, + { + role: "user", + content: `Conduct a comprehensive and detailed analysis for the UX Sitemap Document for the app, in markdown format. elaborate and justify and detail to the greatest extent. make extensive descriptions. + +you're a genius`, + }, + ]; + + const uxsmd = ( + await context.run({ + id: "op:LLM::GEN", + context, + data: { + model: `chatgpt-4o-latest`, //`gpt-4o`, + messages, + preparser: `backticks`, + parser: false, + }, + }) + ).generated; + + await context.run({ + id: "op:PROJECT::STATE:UPDATE", + context, + data: { + operation: { + id: "pm:uxsmd", + }, + type: `end`, + content: { + key: "pm.uxsmd", + data: uxsmd, + }, + }, + }); + + return { + pm: { uxsmd }, + }; +} + +export default { + "PM:UXSMD::ANALYSIS": pmUxsmdAnalysis, +}; diff --git a/cofounder/api/system/functions/swarm/augment.js b/cofounder/api/system/functions/swarm/augment.js new file mode 100644 index 0000000..019b1eb --- /dev/null +++ b/cofounder/api/system/functions/swarm/augment.js @@ -0,0 +1,365 @@ +import utils from "@/utils/index.js"; +import yaml from "yaml"; + +async function promptAnalysis({ context, data }) { + const { task } = data; + const { code, decorators, apis } = task; + return [ + { + role: "system", + content: `you are an expert backend and node js dev +- your role is to generate an analysis for for functions to implement that may require the use of external APIs , either through API calls or npm sdks + +- you are provided with descriptions and contextual code snippets of desired functions, from a node server module + these function are tagged as needing the implementation of external APIs/SDKs for the tasks they are meant to accomplish + +- you are also provided with some search results for external APIs for each function from some external APIs that were indexed + > you are to determine whether external API search result(s) are relevant or no for the desired use cases descriptions + > if so , which ones to use and how ? what do they need to run ? how to use them ? how to format their expected response ? + > if no search result is relevant, do you know, about fitting nodejs/npm SDKs/packages or other APIs you are familiar with that are fit for the task ? + and if so, how to use them ? + > note : if a fitting external API is identified and also has SDKs you know about + its prefereable to call the API using the provided openapi / docs instead of the SDK you already know about + ( because SDKs might have been updated since your last knowledge base ) ; + use SDKs for when no APIs search results make sense for the analyzed implementation case + > note : if you are using references from provided docs, extracts and include snippets from them inside your analysis to further document your analysis properly + +conduct a detailed analysis for each of the ${apis.length} provided functions to implement + +your reply should start with : "\`\`\`markdown" and end with "\`\`\`" + +you will be tipped $999 +you're a genius +`, + }, + // each api entry in its own message + ...apis.map(({ id, description, snippet, rag }) => { + return { + role: "user", + content: `\`\`\`task:${id} +${yaml.stringify({ + functionDescription: description, + contextCodeSnippet: snippet, +})} +\`\`\` + +\`\`\`apis-search-results:${id} +${rag.length ? yaml.stringify(rag) : ""} +\`\`\` +`, + }; + }), + { + role: "user", + content: `Conduct your analysis each of the ${apis.length} provided functions to implement, with each function in its separate and very detailed section, and make sure you do not miss any useful detail ! + +be extremely detailed and include every single used reference and detail in your analysis for it to be fully comprehensive and complete + +you are a genius`, + }, + ]; +} + +async function promptImplementMerge({ context, data }) { + const { pm, db, backend, task } = data; + const { prd, frd, drd, brd } = pm; + const { code, apis, analysis } = task; + const { openapi, asyncapi } = backend.specifications; + /* + should also get BRD here ! important ! ie. so it doesnt do stupid placeholders ? + should provide full code too ? maybe 2 pass implement directly ; implement and revise ? + no need for rag here + */ + return [ + { + role: "system", + content: `Your task, as the genius backend dev expert you are, is to generate the full nodejs script for a module, based on the provided specifications and details of the backend in development + +- the current code of the server script is provided + the desired updates are provided +> your main task is to add the provided functions , and return a fully functional script that has both the original features and the newly added updates, with everything working perfectly and as expected + +--- + +your role is to implement the full express server for the provided task for the \`server.mjs\` (type: module script) +you will answer in 3 parts : + +- analysis , in between \`\`\`markdown\`\`\`\` section +- code , in between \`\`\`mjs\`\`\`\` section +- dependencies and env variables , in between \`\`\`yaml\`\`\`\` section ; where any needed packages to install and needed env variables to setup will be mentionned ; the yaml should have objects : { dependencies : {"package":"version"} , env : {"key" , "temp_value"} } ("dependencies" (for packages) and "env" for env variables (and their temporary values) ) +use doublequotes for every string inside the yaml to make sure formatting is good + +--- + +in your analysis, ask yourself : + > what are the added functions ? + > how do i merge all updates perfectly with the working code ? + > is the full flow covered ? + > are all the expected functions fullfilled ? + > am i covering all the parts for all the required updates ? + including imports, functions, db operations, ... ? + are all the new updates congruent with the original code structure, flow, db operations and all that is expected ? + +--- + +for any db requirements, use postgres from \`@electric-sql/pglite\` +- to use postgres, include this snippet in your script : +\`\`\` +import { PGlite } from "@electric-sql/pglite"; +const postgres = new PGlite("./db"); +/* then, can be used like this : +await postgres.query("SELECT * FROM exampletable;") +*/ +// note : the postgres tables + seed were already created before , you can use the postgres directly without configuring it +\`\`\` +postgres is use exactly how is provided in the snippet, do not change anything about loading it / configuring it, else it breaks ; +postgres is imported, initialized and queries EXACTLY AS SHOWN IN THE SNIPPET ! NO OTHER WAY ! + +--- + +note : the postgres tables + seed were already created before , you can use the postgres directly without configuring it ; do not create tables in script ! +extremely important : +- the DB R/W need to be 100% compatible with the tables and schemas of the provided DB specifications !! +- if it makes use of .env , make your you import \`dotenv\` and \`dotenv.config()\` to read .env before ! + +--- + +extremely important : + +- you are to implement the entire server as specified in the provided docs , with a focus on DB R/W operations +- you are to implement every single thing needed by the backend server and output one single big working perfect \`server.mjs\` script +- do not assume anything is implemented yet ! you will do 100% of everything needed and output one single big working perfect \`server.mjs\` script +- no placeholders, no hallucinated imports +- again, do not assume anything is implemented yet ! you will do 100% of everything needed and output one single big working perfect \`server.mjs\` script +--- + +note: +> if ie. some mock data is meant to to store an image url, use a https://picsum.photos/ url with a random seed + +super important : +> use snake_case for any new naming you do +> ensure full perfect coherence with DB fields names and all provided specs names + +--- + +extremely important : +- the DB R/W need to be 100% compatible with the tables and schemas of the provided DB specifications !! +- the app flow must be 100% working perfect everywhere + +you are a genius + you get tipped $9999999 +`, + }, + { + role: "user", + content: `\`\`\`PRD:product-requirements-document +${prd} +\`\`\` + +\`\`\`FRD:features-requirements-document +${frd} +\`\`\` +`, + }, + { + role: "user", + content: ` +\`\`\`DB:postgres:sql +${db.postgres} +\`\`\` + +--- + +extremely turbo important : +> pay extreme attention to DB details : + > the things that you are expected to provide with inserts : + > should you make a uuid before inserting with postgres query ? + > are there key constraints ? + > is the db querying code using the exact names as in db fields ? + > are you providing everything needed to db every single time ? +`, + }, + { + role: "user", + content: `\`\`\`BRD:backend-requirements-document +${brd} +\`\`\``, + }, + data.backend?.requirements?.restApi?.required && { + role: "user", + content: `\`\`\`BACKEND:specifications:openAPI +${yaml.stringify(openapi)} +\`\`\``, + }, + data.backend?.requirements?.realtimeWebsockets?.required && { + role: "user", + content: `\`\`\`BACKEND:specifications:asyncAPI +${yaml.stringify(asyncapi)} +\`\`\``, + }, + { + role: "user", + content: `The functions updates of the original code are the following : + +\`\`\`functions:update:tasks +${yaml.stringify({ + toUpdate: apis.map(({ description, snippet }) => { + return { + functionDescription: description, + contextCodeSnippet: snippet, + }; + }), +})} +\`\`\``, + }, + { + role: "user", + content: `The original full script code to update is : +\`\`\`mjs +${code} +\`\`\` +`, + }, + { + role: "user", + content: `The analysis of the new updates to make to the server code is in the following : + +\`\`\`functions:update:analysis +${analysis} +\`\`\``, + }, + { + role: "user", + content: `extremely important : +- you are to implement the entire \`server.mjs\` as specified in the backend specifications , with a focus on DB R/W operations +- you are to implement every single thing needed by the server and output one single big working perfect \`server.mjs\` script +- do not assume anything is implemented yet ! you will do 100% of everything needed and output one single big working perfect \`server.mjs\` script +- no placeholders, no hallucinated imports + + +--- + +extremely turbo important : +> pay extreme attention to DB details : + > the things that you are expected to provide with inserts : + > should you make a uuid before inserting with a postgres query ? + > are there key constraints ? should you create something before inserting something else because of contraints ? + > is the db querying code using the exact names as in db fields ? + > are you providing everything needed to db every single time ? + +--- + +- again, do not assume anything is implemented yet ! you will do 100% of everything needed and output one single big working perfect \`server.mjs\` script +- again , you are to implement every single thing needed by the server and output one single big working perfect \`server.mjs\` script +- no placeholders, no hallucinated imports ; one 100% perfect complete working server script + +extremely important : +- the DB R/W need to be 100% compatible with the tables and schemas of the provided DB specifications !! + +now do the analysis , write the full working script and specify the dependencies+env`, + }, + ].filter((e) => e); +} +async function promptImplementReview({ context, data }) { + const { task } = data; + const { brd } = pm; + const { code, decorators, apis, analysis, implementations } = task; + /* + maybe double check verify instead ? + */ + return []; +} + +async function swarmAugmentBackendExternalapis({ context, data }) { + /* + */ + const { task } = data; + const { code } = task; + const decorators = (await utils.parsers.extract.decorators({ code })).filter( + (item) => item.type === "external-api" || item.type === "external-apis", + ); + if (!decorators.length) return {}; + + // apis RAG + const apis = await Promise.all( + decorators.map(async (item, idx) => { + const { description, snippet } = item; + const ragText = `Description : ${description}\n\nCode Snippet :\n\`\`\`\n${snippet}\n\`\`\``; + return { + id: `fn:${idx + 1}/${decorators.length}`, + description, + snippet, + rag: ( + await context.run({ + id: `op:INDEXDB::QUERY`, + context, + data: { + index: "apis", + text: ragText, + amount: 5, + }, + }) + ).results, + }; + }), + ); + data.task.decorators = decorators; + data.task.apis = apis; + const messagesAnalysis = await promptAnalysis({ context, data }); + + const analysisPass = ( + await context.run({ + id: "op:LLM::GEN", + context, + data: { + model: `chatgpt-4o-latest`, //`gpt-4o`, + messages: messagesAnalysis, + preparser: `backticks`, + parser: false, + }, + }) + ).generated; + + data.task.analysis = analysisPass; + + const messagesImplementMerge = await promptImplementMerge({ context, data }); + const { generated } = await context.run({ + id: "op:LLM::GEN", + context, + data: { + model: `chatgpt-4o-latest`, //`gpt-4o`, + messages: messagesImplementMerge, + preparser: false, + parser: false, + }, + }); + + const extraction = await utils.parsers.extract.backticksMultiple({ + text: generated, + delimiters: [`markdown`, `mjs`, `yaml`], + }); + + const { mjs } = extraction; + if (!mjs.length || !extraction.yaml) { + throw new Error( + "swarm:augment:backend:externalApis:generate error - generated code is empty", + ); + } + + const parsedYaml = extraction.yaml ? yaml.parse(extraction.yaml) : {}; + const generatedServer = { + mjs, + dependencies: parsedYaml.dependencies + ? Object.fromEntries( + Object.keys(parsedYaml.dependencies).map((key) => [key, "*"]), + ) + : [], + env: parsedYaml.env ? parsedYaml.env : {}, + timestamp: Date.now(), + }; + + return generatedServer; +} + +export default { + "SWARM:AUGMENT::BACKEND:EXTERNALAPIS": swarmAugmentBackendExternalapis, +}; diff --git a/cofounder/api/system/functions/swarm/fix.js b/cofounder/api/system/functions/swarm/fix.js new file mode 100644 index 0000000..e1ace80 --- /dev/null +++ b/cofounder/api/system/functions/swarm/fix.js @@ -0,0 +1,10 @@ +import utils from "@/utils/index.js"; +import yaml from "yaml"; + +async function swarmFixBackend({ context, data }) {} +async function swarmFixWebapp({ context, data }) {} + +export default { + "SWARM:FIX::BACKEND": swarmFixBackend, + "SWARM:FIX::WEBAPP": swarmFixWebapp, +}; diff --git a/cofounder/api/system/functions/swarm/review.js b/cofounder/api/system/functions/swarm/review.js new file mode 100644 index 0000000..6f152c4 --- /dev/null +++ b/cofounder/api/system/functions/swarm/review.js @@ -0,0 +1,20 @@ +import utils from "@/utils/index.js"; +import yaml from "yaml"; +import dotenv from "dotenv"; +dotenv.config(); + +/* + should check process.env.SWARM_ENABLE +*/ + +async function swarmReviewServerMain({ context, data }) {} +async function swarmReviewWebappStore({ context, data }) {} +async function swarmReviewWebappRoot({ context, data }) {} +async function swarmReviewWebappView({ context, data }) {} + +export default { + "SWARM:REVIEW::SERVER:MAIN": swarmReviewServerMain, + "SWARM:REVIEW::WEBAPP:STORE": swarmReviewWebappStore, + "SWARM:REVIEW::WEBAPP:ROOT": swarmReviewWebappRoot, + "SWARM:REVIEW::WEBAPP:VIEW": swarmReviewWebappView, +}; diff --git a/cofounder/api/system/functions/ux/datamap.js b/cofounder/api/system/functions/ux/datamap.js new file mode 100644 index 0000000..6ca95a2 --- /dev/null +++ b/cofounder/api/system/functions/ux/datamap.js @@ -0,0 +1,493 @@ +import utils from "@/utils/index.js"; +import yaml from "yaml"; +import { merge, fromPairs } from "lodash-es"; + +function _chunkify(input_list, chunk_size) { + const chunks = []; + for (let i = 0; i < input_list.length; i += chunk_size) { + chunks.push(input_list.slice(i, i + chunk_size)); + } + return chunks; +} + +async function uxDatamapStructure({ context, data }) { + /* ;; UX:DATAMAP::ROOT:VIEWS + {...} -> app {routes, slugs, params , views } ; yaml + ;; preconsider layout stuff too, either here or in sitemap + + out : ["uxdatamap"] + */ + + const { pm, backend } = data; + const { prd, frd, brd, uxsmd, uxdmd } = pm; + const messages = [ + { + role: "system", + content: `- you are a genius Product Manager & Software Archtect + +- your role is to make the frontend app architecture for the provided project , based on the provided task and analysis documents +- your answer should be in the strict provided format that will be defined further + +your aim is to determine the: + * the structure of the app: + * global app state structure that is accessed by all views : how the app state should be structured to cover the app features + * routes, what views they link to + * route restrictions + * slugs if applies (ie. path /something/:example), describe + * URL params if applies (ie. /somepath?param_a=example¶m_b=example ), describe + + +- think from perspectives of multiple personas, put yourself in situation, to make sure your app architecture is fully comprehensive and ready to be developed +- ask yourself: + * what are the journeys involved in the app frontend ? + * what are all the routes , views , slugs , props , URL parameters , required by features expected to be seen by users in the frontend ? + * what should go in schemas ? + * am i covering all needed slugs ? + am i covering all URL parameters ? + +- your structure will be used to make a prod-ready app architecture and will be responsible for an app used by thousands of users +- your aim is to cover all use cases, as the expert product manager & architect you are + +> your answer should strictly be in this format : + +\`\`\`yaml +app: + root: + globalState: # global app state variables if applies ; name conventions should try match with dbschemas/openapi schemas for coherence + [name]: # global app state variable name + schema: # variable schema in JS-parseable interace format ; schema should be fully defined including nested fields (you are provided with all the documents and db schemas and openapi etc ... that you need to determine this) + default: # default value to assign to the variable ; should obviously be aligned with the defined schema + example: # example value to assign + ... + + routes: # list of app routes to cover all features and cases + - description: "..." # concise one sentence description of the route's role + path: "..." # path, including if any slugs (using the /:slug format ) + view: "" # view id to render on this route ; should match a provided id for a unique view from the ux sitemap (UV_*), based on the provided analysis + + # specify slugs / URL params if applies here + # note : remember there is a strong difference - slugs are (/examplepath/:slug_id) and urlParams are (/somepath?q=example&someparam=somevalue) do not confuse them ! + + slugs?: # if slugs in path, describe + - name: "..." # slug id as specified in path + intent: "..." # consise one sentence description of its role + ... + urlParams?: # does the view expect url params, if so describe each single URLparam individually in detail + - name: "..." # if the view will expect URL params, specify the URL param name + intent: "..." # consise description + example: "exampleValue" # an example of a value it would take - you are required to provide an example here + required?: boolean # if specifying the urlParam is required or optional for the view to function properly + ... +\`\`\` + +--- + +it should be comprehensive for the needs required by all the views + +> important : if some home or landing view, path should obviously be "/" ! + +your reply will be directly transferred as the final structure, so do not put anything else in your reply besides the final structure +your reply should start with : "\`\`\`yaml" and end with "\`\`\`" + +you will be tipped $99999 + major company shares for nailing it perfectly off the bat`, + }, + { + role: "user", + content: `\`\`\`PRD:app-product-requirements-document +${prd} +\`\`\``, + }, + { + role: "user", + content: `\`\`\`FRD:app-features-requirements-document +${yaml.stringify(frd)} +\`\`\``, + }, + { + role: "user", + content: + `\`\`\`BRD:backend-requirements-document +${brd} +\`\`\` + +` + + (!backend?.requirements?.restApi?.required + ? "" + : `--- +\`\`\`BACKEND:specifications:openAPI +${yaml.stringify(backend.specifications.openapi)} +\`\`\` + +`) + + (!backend?.requirements?.realtimeWebsockets?.required + ? "" + : `--- +\`\`\`BACKEND:specifications:asyncAPI +${yaml.stringify(backend.specifications.asyncapi)} +\`\`\` +` + + ` +--- + +\`\`\`BRD:server:main +${yaml.stringify(backend.server.main)} +\`\`\` + +`), + }, + { + role: "user", + content: `\`\`\`UXSMD:ux-sitemap-document +${uxsmd} +\`\`\``, + }, + { + role: "user", + content: `\`\`\`UXDMD:ux-sitemap-data-states-crossanalysis-document +${uxdmd} +\`\`\``, + }, + ]; + const uxdatamapStructure = ( + await context.run({ + id: "op:LLM::GEN", + context, + data: { + model: `chatgpt-4o-latest`, //`chatgpt-4o-latest`, // `chatgpt-4o-latest`,//`gpt-4o`, + messages, + preparser: `backticks`, + parser: `yaml`, + }, + }) + ).generated; + + await context.run({ + id: "op:PROJECT::STATE:UPDATE", + context, + data: { + operation: { + id: "architecture:uxdatamap:structure", + }, + type: `end`, + content: { + key: `uxdatamap.structure`, + data: uxdatamapStructure.app, + }, + }, + }); + + return { + uxdatamap: { + structure: uxdatamapStructure.app, // -> {root{},routes{}} + }, + }; +} + +async function uxDatamapViews({ context, data }) { + /* ;; UX:DATAMAP::VIEWS:SECTIONS + { ... } -> sections details with props & schemas ; yaml + ;; preconsider layout stuff too, either here or in sitemap + + out : ["uxdatamap"] + */ + + /* + - not sure if needs uxsitemap structure etc or just the uxdatamap structure (and other analysis docs) + - should also consider shared views and their distribution etc, unless that is done in uxsitemap + that and cross links ... unless that too in uxsitemap + - focus here is data not other stuff + */ + + const { uxdatamap, uxsitemap } = data; + /* + uxdatamap: { structure : { root{} , routes{} } } + uxsitemap: { structure : { views{ unique{}, shared{} } , crosslinks[{source,target,intent,action}] } } + + -> needs parallel chunking because high detail on each + -> parallel chunk uxsitemap views to detail data ops on them and their sections + -> all shared global views GV_* in same chunk (because diff approach?) , unique views uv_* in multi chunks because numerous + */ + + let tasks = []; + + const UVs = Object.keys(uxsitemap.structure.views.unique); + if (UVs.length) { + _chunkify(UVs, 5).map((uniqueViewsIdsChunk) => { + let filteredUxSitemap = { views: { unique: {} } }; + uniqueViewsIdsChunk.map((uv) => { + filteredUxSitemap.views.unique[uv] = uxsitemap.structure.views.unique[uv]; + }); + tasks.push({ + uxsitemap: filteredUxSitemap, // filtered ux sitemap with all chunk of unique views + crosslinks: uxsitemap.structure.crosslinks.filter((crosslink) => { + return uniqueViewsIdsChunk.includes(crosslink.source); + }), + ids: uniqueViewsIdsChunk, + type: `unique`, + }); + }); + } + const GVs = Object.keys(uxsitemap.structure.views.shared); + if (GVs.length) { + tasks.push({ + uxsitemap: { views: { shared: uxsitemap.structure.views.shared } }, // filtered ux sitemap with all shared views + crosslinks: uxsitemap.structure.crosslinks.filter((crosslink) => { + return GVs.includes(crosslink.source); + }), + ids: GVs, + type: `shared`, + }); + } + + let views = {}; + await Promise.all( + tasks.map(async (task) => { + const response = await context.run({ + id: `UX:DATAMAP::VIEWS:CHUNK`, + context, + data: { ...data, task }, + }); + views = merge(views, response.views); + }), + ); + + // console.dir({ __debug_uxDatamapViews: {views} } , {depth:null}) + await context.run({ + id: "op:PROJECT::STATE:UPDATE", + context, + data: { + operation: { + id: "architecture:uxdatamap:views", + }, + type: `end`, + content: { + key: `uxdatamap.views`, + data: views, + }, + }, + }); + + return { + uxdatamap: { + ...uxdatamap, + views, + }, + }; +} + +async function uxDatamapViewsChunk({ context, data }) { + /* ;; UX:DATAMAP::VIEWS:CHUNK + chunk processor for views+sections detailing + + out : ["views"] # make sure later, maybe there's more to it ? + */ + + /* + - not sure if needs uxsitemap structure etc or just the uxdatamap structure (and other analysis docs) + - should also consider shared views and their distribution etc, unless that is done in uxsitemap + that and cross links ... unless that too in uxsitemap + + - focus here is data not other stuff ! can link through filtering with uxsitemap later + */ + + const { pm, uxdatamap, backend, task } = data; + const { prd, frd, brd, uxsmd, uxdmd } = pm; + /* + --> task : { + uxsitemap{ + views{ + unique{ __chunk of views__ }, + shared{ __chunk of views__ }, + }, + } + crosslinks[], + ids[ ids of views__ ], + type: unique || shared + } + // returns {views{ unique{[id]:...} , shared{[id]:...} }} + */ + + const messages = [ + { + role: "system", + content: `- you are a genius Product Manager & App Architect + +- your role is to detail the frontend app architecture structure for the provided project, +for the specific views with ids : ${task.ids.join(",")}, +based on the provided task and analysis documents +- your answer should be in the strict provided format that will be defined further + + +your aim is to determine the: + * the structure of the specified frontend views (views ${task.ids.join(",")} ) , + for each view : + * state variables , schemas , dynamic data + * what it receives from the route (slugs) + * what it receives in URL params + * action / functions in this view and how they come into play to cover all intended features of the view +- for each view , ask yourself: + * what are the features involved in this view ; and how they come into play in app's features / journeys ? + * what data does this view receive as route slugs and url params ? + * what are all the state variables / actions / functions / ... required by features expected to be seen by users in the frontend ? + +- your detailed view structure for each view will be used to make a prod-ready app architecture and will be responsible for an app used by thousands of users +- your aim is to cover all use cases, as the expert product manager & architect you are + +> your answer is required to be in this strict defined format, in this strict order, in a strictly parseable YAML : + +\`\`\`yaml +# use view ids directly under the views object ; will either be UV_* (unique views) or GV_* (shared global views) ; +# for all provided views ids in task, conduct the structure detailing +views: + [view id]: # the view id that is detailed ; should be a value from : ${task.ids.join(" , ")} + slugs?: # if view takes dynamic slug variable (defined in route in app structure), describe + - name: "..." # slug id as defined in route + intent: "..." # consise one sentence description of the slug role + required: boolean # if required, set to true + default: "..." # default init value for the slug + ... + urlParams?: # if view takes URL params (defined in app structure), describe + - name: "..." # URL param key as defined in app structure + intent: "..." # consise one sentence description of the slug role + required: boolean # if required, set to true + ... + stateVariables?: # if view is stateful and needs view-level state variables, describe ; + - name: "..." # variable name + role: "..." # describe + schema: # variable schema in JS-parseable interface schema format ; schema should be fully defined including nested fields + default: # default init value for the view's state variables + mappingIfFromSource?: # if this state variable is set based on : received route slugs , or URL params , or app global state , specify + - source: # is either "slugs" || "urlParams" || "globalAppState" + name: # variable name as defined in slugs / urlParams / globalAppState to link it + ... + ... + actions?: # if view has dynamic actions/functions, describe + - name: "..." # action/function name + intent: "..." # describe + triggers: # list of ways the action/function is triggered; can be multiple ; ie. on page load, when some button is clicked inside view, when some state variable value changes, etc ... + interactionWithBackend?: # if action interacts with backend, describe + description: "if applies , describe the interaction with backend ; also mention if calls backend api or realtime, and describe how ; be detailed" + + ... + globalStateVariablesAccessed?: # if view needs to access global state variables (as defined in provided app structure under root.globalState), specify those in this object ; object should also contain description of relationship (ie. 'to get auth token to use in api calls authorization header' , 'to get current user profile to prefill [...] ' , etc ... ) + + [...] +\`\`\` + +--- +note : +- for any field that requires making example, you can draw from provided data examples in provided task docs + for any field such as image, media ... make sure you use urls rather than local references ; + if some entry requires an image url or media, use a valid "https://picsum.photos/..." url for it ! + +--- + +for yaml to be 100% valid, use quotes around string as much as possible +your reply should start with : "\`\`\`yaml" and end with "\`\`\`" + +you will be tipped $99999 + major company shares`, + }, + { + role: "user", + content: `\`\`\`PRD:app-product-requirements-document +${prd} +\`\`\``, + }, + { + role: "user", + content: `\`\`\`FRD:app-features-requirements-document +${frd} +\`\`\``, + }, + { + role: "user", + content: `\`\`\`UXSMD:ux-sitemap-document +${uxsmd} +\`\`\``, + }, + { + role: "user", + content: `\`\`\`UXDMD:ux-sitemap-data-crossanalysis-document +${uxdmd} +\`\`\``, + }, + { + role: "user", + content: + `\`\`\`BRD:backend-requirements-document +${brd} +\`\`\` + +` + + (!backend?.requirements?.restApi?.required + ? "" + : `--- +\`\`\`BACKEND:specifications:openAPI +${yaml.stringify(backend.specifications.openapi)} +\`\`\` + +`) + + (!backend?.requirements?.realtimeWebsockets?.required + ? "" + : `--- +\`\`\`BACKEND:specifications:asyncAPI +${yaml.stringify(backend.specifications.asyncapi)} +\`\`\` +` + + ` +--- + +\`\`\`BRD:server:main +${yaml.stringify(backend.server.main)} +\`\`\` + +`), + }, + { + role: "user", + content: `\`\`\`UX:ux-datamap +${yaml.stringify(uxdatamap.structure)} +\`\`\``, + }, + { + role: "user", + content: `\`\`\`UX:ux-sitemap-distilled +${yaml.stringify(task.uxsitemap)} +\`\`\``, + }, + { + role: "user", + content: `make the detailed architecture for views ids : ${task.ids.join(",")} and their components. +only make the detailed architecture for views ids : ${task.ids.join(",")} ; not any other views, +using the provided instructions and format + +make a coherent, cohesive, perfect, detailed structure +answer in parseable YAML format, strictly in the provided instructions format ; strictly parseable YAML ; +you're a genius`, + }, + ]; + const views = ( + await context.run({ + id: "op:LLM::GEN", + context, + data: { + model: `chatgpt-4o-latest`, //`chatgpt-4o-latest`, // `chatgpt-4o-latest`,//`gpt-4o`, + messages, + preparser: `backticks`, + parser: `yaml`, + }, + }) + ).generated.views; + + return { + views: { + [task.type]: views, + }, + }; // -> views : { unique{[ids...]} , shared{ids[...]} } +} + +export default { + "UX:DATAMAP::STRUCTURE": uxDatamapStructure, + "UX:DATAMAP::VIEWS": uxDatamapViews, + "UX:DATAMAP::VIEWS:CHUNK": uxDatamapViewsChunk, +}; diff --git a/cofounder/api/system/functions/ux/sitemap.js b/cofounder/api/system/functions/ux/sitemap.js new file mode 100644 index 0000000..feb4e20 --- /dev/null +++ b/cofounder/api/system/functions/ux/sitemap.js @@ -0,0 +1,238 @@ +import utils from "@/utils/index.js"; +import yaml from "yaml"; + +async function uxSitemapStructure({ context, data }) { + /* generate uxsitemap in strict format */ + + const { pm } = data; + const { prd, frd, uxsmd } = pm; + const messages = [ + { + role: "system", + content: `You are an extremely experienced UX expert and software product manager. +Your role is to create a comprehensive UX sitemap from the provided information. + +- Think very slowly and thoroughly. Take a deep breath. +- Provide a comprehensive, well-thought-out reply that covers all aspects of the analyzed problem. +- You are an expert at what you do. +- You are known to never forget a single angle. +- You will be tipped $999 for each perfect reply. + +Answer in a strict parseable YAML format, in this format: + +\`\`\`yaml + +# ux sitemap structure in great details +uxsitemap: + + views: + uniqueViews: # unique views with ids UV_* , as specified in provided docs + [unique view id UV_*]: + title: "" + extendedDescription: "describe the view in great extended detail that covers every single thing that should go in it without any omittance" # essential to detail specifics ! dont assume someone knows what you mean, detail it in details ! + notes: "important notes regarding this view component or its state(s) that were mentionned in provided docs & analysis and that should be mentionned" + role: "describe in detail role of this view in the app ; namely the features it aims to satisfy within the app features and UX" + globalSharedViews: # global shared views with ids GV_* such as nav etc, as specified in provided docs + [global shared view id GV_*]: + title: "" + extendedDescription: "describe the shared view in great extended detail that covers every single thing that should go in it without any omittance" # essential to detail specifics ! dont assume someone knows what you mean, detail it in details ! + notes: "important notes regarding view component or its state(s) that were mentionned in provided docs & analysis and that should be mentionned" + role: "describe in detail role of this view in the app ; namely the features it aims to satisfy within the app features and UX" + sharedByViews: [] # list of ids of all unique views UV_* that have this shared view displayed alongside with them + relativePosition: "" # describe the relative positioning of this GV_* shared view in relation to the app layout and unique views it is shared with; + [...] + + # cross inter-app links relationships between views + crossLinks: + - sourceViewId: "" # UV_* or GV_* + targetViewId: "" # UV_* + intent: "" + actionDescription: "" + [...] + +\`\`\` + +--- + +- every view id you refer to must exist in provided docs ! + +- As the expert, you should make a complete and comprehensive UX sitemap, + including all parts that might be unemphasized by a less experienced UX worker, if required by the app of course, such as auth flows, terms, etc. + +your reply should start with : "\`\`\`yaml" and end with "\`\`\`" +for yaml to be 100% valid, use quotes around string as much as possible + +A comprehensive UX sitemap in the provided yaml format +You are a genius at this task.`, + }, + { + role: "user", + content: `\`\`\`PRD:product-requirements-document +${prd} +\`\`\``, + }, + { + role: "user", + content: `\`\`\`FRD:features-requirements-document +${frd} +\`\`\``, + }, + { + role: "user", + content: `\`\`\`UXSMD:ux-sitemap-analysis-document +${uxsmd} +\`\`\``, + }, + { + role: "user", + content: `Make a full, comprehensive UX sitemap for it +You're a genius do a great job`, + }, + ]; + + const uxsitemapStructure = ( + await context.run({ + id: "op:LLM::GEN", + context, + data: { + model: `chatgpt-4o-latest`, // `chatgpt-4o-latest`,//`gpt-4o`, + messages, + preparser: `backticks`, + parser: `yaml`, + }, + }) + ).generated.uxsitemap; + + // <----- do post processing to format the response + const uxsitemap = { + structure: { + views: { + unique: uxsitemapStructure.views.uniqueViews, + shared: uxsitemapStructure.views.globalSharedViews, + }, + crosslinks: uxsitemapStructure.crossLinks.map((link) => { + return { + source: link.sourceViewId, + target: link.targetViewId, + intent: link.intent, + action: link.actionDescription, + }; + }), + }, + }; + + await context.run({ + id: "op:PROJECT::STATE:UPDATE", + context, + data: { + operation: { + id: "architecture:uxsitemap:structure", + }, + type: `end`, + content: { + key: "uxsitemap.structure", + data: uxsitemap.structure, + }, + }, + }); + + return { + uxsitemap, + }; +} + +// ____________________________________________________________________________________________________ + +async function uxSitemapViews({ context, data }) { + /* ;; UX:SITEMAP::VIEWS + {pm docs , db , openapi?, UXSMD, uxsitemap{structure} } -> uxsitemap{...,views} + + out : ["uxsitemap"] + */ + + // distribute processing of views to submodules , might typically replace ie. in case of specialized and whatnot + // but serves as a refiner , not as a detailer + // so can just pass for now + await Promise.all(); + + // postprocess to return single coherent object + + return {}; +} + +// ____________________________________________________________________________________________________ + +async function uxSitemapViewsNormal({ context, data }) { + /* ;; UX:SITEMAP::VIEWS:NORMAL + chunk processing from UX:SITEMAP::VIEWS ; for normal views + + out : ["views"] + */ + + const messages = [ + { + role: "system", + content: "[system+format]", + }, + ]; + await context.run({ + id: `op:LLM::GEN`, + context, + data: { messages, preparser: false, parser: false }, + }); + + return {}; +} +async function uxSitemapViewsSpecial({ context, data }) { + /* ;; UX:SITEMAP::VIEWS:SPECIAL + chunk processing from UX:SITEMAP::VIEWS; specialized processors for special sections ; ie. landing would be based on some highconversion process etc; + might override provided sections descriptions + + out : ["views"] + */ + + const messages = [ + { + role: "system", + content: "[system+format]", + }, + ]; + await context.run({ + id: `op:LLM::GEN`, + context, + data: { messages, preparser: false, parser: false }, + }); + + return {}; +} +async function uxSitemapViewsShared({ context, data }) { + /* ;; UX:SITEMAP::VIEWS:SHARED + chunk processing from UX:SITEMAP::VIEWS ; for shared views implementations + + + out : ["views"] + */ + + const messages = [ + { + role: "system", + content: "[system+format]", + }, + ]; + await context.run({ + id: `op:LLM::GEN`, + context, + data: { messages, preparser: false, parser: false }, + }); + + return {}; +} + +export default { + "UX:SITEMAP::STRUCTURE": uxSitemapStructure, + "UX:SITEMAP::VIEWS": uxSitemapViews, + + "UX:SITEMAP::VIEWS:NORMAL": uxSitemapViewsNormal, + "UX:SITEMAP::VIEWS:SPECIAL": uxSitemapViewsSpecial, + "UX:SITEMAP::VIEWS:SHARED": uxSitemapViewsShared, +}; diff --git a/cofounder/api/system/functions/webapp/root.js b/cofounder/api/system/functions/webapp/root.js new file mode 100644 index 0000000..3007f7b --- /dev/null +++ b/cofounder/api/system/functions/webapp/root.js @@ -0,0 +1,264 @@ +import utils from "@/utils/index.js"; +import yaml from "yaml"; + +async function promptRoot({ context, data }) { + // have a placeholder redux store module in case it'd wrap with non implemented store + /* + better implement using prompt, provide ux sitemap & datamap + provide react (redux store) too, in prompt also, for auth restricted stuff + */ + const { uxsitemap, uxdatamap, webapp } = data; + + const viewsImportHead = [ + ...Object.keys(uxsitemap.structure.views.shared), + ...Object.keys(uxsitemap.structure.views.unique), + ] + .map((viewId) => { + return `import ${viewId} from '@/components/views/${viewId}.tsx';`; + }) + .join("\n"); + + const boilerplate = `import React, { useState, useEffect } from "react"; +import "./App.css"; +import { + Route, + Routes, +} from "react-router-dom"; + +/* + import views : unique views (UV_*) and shared global views (GV_*) +*/ +import UV_ExampleLanding from '@/components/views/UV_ExampleLanding.tsx'; +import UV_OtherViewExample from '@/components/views/UV_OtherViewExample.tsx'; +import GV_NavTop from '@/components/views/GV_NavTop.tsx'; +import GV_Footer from '@/components/views/GV_Footer.tsx'; + +const App: React.FC = () => { + + return ( + <> + + + + } /> + } /> + + + + + ); +}; + +export default App; +`; + + /* + x emphasize redux / auth stuff + x provide example of reactrouterdom structure ; whole thing + x emphasize relative position of shared components ; thus emphasize tailwind usage and styling in rel to shared components + x emphasize React FC + x do not assume ! prompt + */ + return [ + { + role: `system`, + content: `your role as an expert web app and react senior dev and product manager is to write the code for the root react + tailwind app (App.tsx) component component based on the provided task + +> ask yourself what should be defined in the root App component in terms of: + > paths & unique views + > global shared views, and their relative position and conditionals + + > auth related restriction (if applies) in relation to the store provider that wraps the App component you are writing here ( it's used like this : \` \` ) + > very important : + do not auth restrict an entire view just because some sections of it are auth restricted while other elements are not auth restricted !! think sloowly ! + > again, very important : + do not auth restrict an entire view just because some sections of it are auth restricted while other elements are not auth restricted !! which would mess things up ! think sloowly ! + +> your answer should strictly be the code for the App.tsx component +your answer will be directly pasted into the component + +> it should encompasses everything required by the app's App, in one single script +> the store script you will write will wrap the root component of the app ; no need to write the wrapper part ; it will be included later as \` \` , where the is the actual script your will write and export here + +--- +your code should import the provided and described views, as follows : +\`\`\` +/* ... */ +${viewsImportHead} +/* ... */ +\`\`\` + +--- + +> conduct the analysis first, reply iwith the analysis inside of \`\`\`markdown\`\`\` +> then, answer with component code in \`\`\`tsx\`\`\` based on your analysis + +you are a genius + you get $9999`, + }, + { + role: "user", + content: `\`\`\`app:uxsitemap +${yaml.stringify({ + structure: { + views: uxsitemap.structure.views, + }, +})} +\`\`\``, + }, + { + role: "user", + content: `\`\`\`app:app-structure +${yaml.stringify({ + structure: uxdatamap.structure.routes, +})} +\`\`\``, + }, + { + role: `user`, + content: `an example of the overall root App structure is meant to be is as follows ; use it as a reference : +\`\`\`tsx +${boilerplate} +\`\`\` + +--- + +for additional reference if needed (ie. in case of auth conditionals) +the code for the global state store component that wraps the app (including this view you're working on) is defined in the following ; +you can import the store exports if needed by using : \`import {...} from '@/store/main'\` + +\`\`\`@/store/main.tsx +${webapp.react.store.redux.latest.tsx} +\`\`\` +`, + }, + { + role: "user", + content: `make the analysis and implement the tsx component; +> implement the react+tailwind component, fully and working from the get go; +> you are implementing the tsx code for the root App component + +--- +your code should import the provided and described views, as follows : +\`\`\` +/* ... */ +${viewsImportHead} +/* ... */ +\`\`\` + +--- + +> should be React.FC ! important ! +> you should respect the way to build Routes in the provided code snippet ! do not innovate in this regard ! +for reminder, this is the way : + +\`\`\` +import { + Route, + Routes, +} from "react-router-dom"; +[...] + + } /> + } /> + +[...] +\`\`\` +--- + +> do not hallucinate methods or component imports that do not exist ! + all that exists has been provided to you + any required additional actions should be implemented by you ; you are provided with all needed details to implement anything ! + > the global store and its methods is defined in @/store/main.tsx + > the views are defined in @/components/views/[sectionId].tsx + > that's all !! + DO NOT ASSUME OTHER STUFF IS IMPLEMENTED ! + IF YOU NEED TO CALL THE API OR SOMETHING SIMILAR, WRITE YOUR OWN FUNCTIONS INSIDE THIS VIEW !! + IMPLEMENT, DO NOT ASSUME ANYTHING ELSE IS IMPLEMENTED ! + +> conduct the analysis first, reply with the analysis inside of \`\`\`markdown\`\`\` +it should emphasize the full functionalities required and specified in the provided details + + +> then, answer in a react tsx code for the App root component reply in \`\`\`tsx\`\`\` based on your analysis +the code should be complete and fully functional ! + +you are a genius + you get $9999`, + }, + ]; +} + +async function webappRootGenerate({ context, data }) { + const timestamp = `${Date.now()}`; + + const messages = await promptRoot({ context, data }); + + const { generated } = await context.run({ + id: "op:LLM::GEN", + context, + data: { + model: `chatgpt-4o-latest`, //`gpt-4o`, + messages: messages, + preparser: false, + parser: false, + }, + }); + + const extraction = await utils.parsers.extract.backticksMultiple({ + text: generated, + delimiters: [`markdown`, `tsx`], + }); + + const { markdown, tsx } = extraction; + if (!tsx.length) { + throw new Error("webapp:root:generate error - generated tsx is empty"); + } + + const generatedRoot = { + analysis: markdown, + tsx, + timestamp, + }; + + await Promise.all( + [`${timestamp}`, `latest`].map(async (version) => { + await context.run({ + id: "op:PROJECT::STATE:UPDATE", + context, + data: { + operation: { + id: `webapp:react:root`, + refs: { + id: "app", + version, + }, + }, + type: `end`, + content: { + key: `webapp.react.root.app.${version}`, + data: generatedRoot, + }, + }, + }); + }), + ); + + return { + timestamp, + webapp: { + react: { + root: { + // ie. "views" , "sections" , "store" , "root" + app: { + // ie. {UV_* , SEC_* , redux} , "app" (in case of root) + [timestamp]: generatedRoot, + latest: generatedRoot, + }, + }, + }, + }, + }; +} + +export default { + "WEBAPP:ROOT::GENERATE": webappRootGenerate, +}; diff --git a/cofounder/api/system/functions/webapp/store.js b/cofounder/api/system/functions/webapp/store.js new file mode 100644 index 0000000..03cb597 --- /dev/null +++ b/cofounder/api/system/functions/webapp/store.js @@ -0,0 +1,291 @@ +import utils from "@/utils/index.js"; +import yaml from "yaml"; + +async function promptStore({ context, data }) { + const { pm, db, backend, uxdatamap } = data; + const { prd, uxdmd, brd } = pm; + + const store = { id: "redux" }; + /* + [system] + */ + + return [ + { + role: `system`, + content: `your role as an expert web app and react senior dev and product manager is to write the code for the ${store.id} store script component based on the provided task; which encompasses all required data states and methods for the app's global states and actions + +--- + +- analysis , in between \`\`\`markdown\`\`\`\` section +- full store component tsx code , in between \`\`\`tsx\`\`\`\` section +- dependencies, in between \`\`\`yaml\`\`\`\` section ; where any needed packages imported into the component code and need to be installed will be mentionned ; the yaml should have object : {dependencies : {"package":"version"} } ; (you can also just put "*" for version) +use doublequotes for every string inside the yaml to make sure formatting is good + +--- + +> ask yourself what should be defined in the store component that will be used by all views later , in terms of global state variables and actions + +> your answer should strictly be the code for the store tsx component +your answer will be directly pasted into the component + +> it should encompasses everything required by the app's global store states and actions, in one single script +> the store script you will write will wrap the root component of the app ; no need to write the wrapper part ; it will be included later as \` \` , where the store is the actual script your will write and export here + +--- + +try to use async/await when you can + +> extremely turbo important : +- you can only use the following packages : + - @reduxjs/toolkit + - react-redux + - redux-persist + - socket.io-client + - axios + +- you need to export default and make sure everything else that will be needed by views is exported too ! + +--- + +note : + > if app has auth capabilities, make sure you global state covers auth token + > if app has realtime capabilities, make sure global state covers realtime auth and subscriptions + feel free to consult the provided backend server code to help you figure out how those details should be implemented + +--- + +> important : + the store should strictly handle getting and setting the provided global state variables ! + it should not handle making the other api calls - those parts will be handled by concerned components ! +> again , very important : + the store should strictly handle getting and setting the provided global state variables ! + it should not handle making the other api calls - those parts will be handled by concerned components ! +> super important : + use localstorage to avoid things being lost on refresh ! + +--- +> conduct the analysis first, reply iwith the analysis inside of \`\`\`markdown\`\`\` +> make full complete store component code in \`\`\`tsx\`\`\` based on your analysis +> dependencies, in between \`\`\`yaml\`\`\`\` with object: {dependencies : {package:version}} + +you are a genius + you get $9999`, + }, + { + role: "user", + content: `\`\`\`PRD:product-requirements-document +${prd} +\`\`\``, + }, + { + role: "user", + content: `\`\`\`UX-analysis-document +${uxdmd} +\`\`\``, + }, + { + role: "user", + content: + `\`\`\`BRD:backend-requirements-document +${brd} +\`\`\` + +` + + (!backend?.requirements?.restApi?.required + ? "" + : `--- +\`\`\`BACKEND:specifications:openAPI +${yaml.stringify(backend.specifications.openapi)} +\`\`\` + +`) + + (!backend?.requirements?.realtimeWebsockets?.required + ? "" + : `--- +\`\`\`BACKEND:specifications:asyncAPI +${yaml.stringify(backend.specifications.asyncapi)} +\`\`\` +` + + ` +--- + +\`\`\`BRD:server:main +${yaml.stringify(backend.server.main)} +\`\`\` + +`), + }, + { + role: "user", + content: `\`\`\`app:architecture +${yaml.stringify(uxdatamap)} +\`\`\``, + }, + { + role: "user", + content: `make the analysis and implement the tsx component; +> implement the ${store.id} store component, fully and working from the get go; + +--- + +try to use async/await when you can + +> extremely turbo important !!! : +- you can only use the following packages : + - @reduxjs/toolkit + - react-redux + - redux-persist + - socket.io-client + - axios + +- you need to export default and make sure everything else that will be needed by views is exported too ! + +--- + +> make sure it has all the required imports !! no missing imports ! +> should export a default method too ! so that it can be imported later as \`import store from '@/store/main'\` ! + + +> important : + the store should strictly: + > handle getting and setting the provided global state variables ! + > if applies , handle realtime events subscriptions + it should not handle making view-specific api calls - those parts will be handled by concerned view components ! +> again, important : + the store should strictly: + > handle getting and setting the provided global state variables ! + > if applies , handle realtime events subscriptions + it should not handle making view-specific api calls - those parts will be handled by concerned view components ! + +> super important : + use localstorage to avoid things being lost on refresh ! + + +> do not assume that anything else is implemented ! +> do not make any assumptions that something else will be plugged here ! +> implement 100% of everything you need to implement ! do not hallucinate importing something that doesnt exist! + +> the store component should work 100% out the box without any further edits ! very important ! + +--- + +important : +> use snake_case for any naming you do + +extremely important : +> ensure full perfect coherence with: backend server methods / events / names, schemas +> field names and schemas epecially + +--- + +- analysis , in between \`\`\`markdown\`\`\`\` section +- full store component tsx code , in between \`\`\`tsx\`\`\`\` section +- dependencies, with object {dependencies:{package:version,...}}, in between \`\`\`yaml\`\`\`\` section + +you are a genius + you get $9999`, + }, + ]; +} + +async function webappStoreGenerate({ context, data }) { + const timestamp = `${Date.now()}`; + + const messages = await promptStore({ context, data }); + + const { generated } = await context.run({ + id: "op:LLM::GEN", + context, + data: { + model: `chatgpt-4o-latest`, //`gpt-4o`, + messages: messages, + preparser: false, + parser: false, + }, + }); + + const extraction = await utils.parsers.extract.backticksMultiple({ + text: generated, + delimiters: [`markdown`, `tsx`, `yaml`], + }); + + const { markdown, tsx } = extraction; + if (!tsx.length) { + throw new Error("webapp:store:generate error - generated tsx is empty"); + } + + const parsedYaml = extraction.yaml ? yaml.parse(extraction.yaml) : {}; + const generatedStore = { + analysis: markdown, + tsx, + dependencies: parsedYaml.dependencies + ? Object.fromEntries( + Object.keys(parsedYaml.dependencies).map((key) => [key, "*"]), + ) + : [], + timestamp, + }; + + await Promise.all( + [`${timestamp}`, `latest`].map(async (version) => { + await context.run({ + id: "op:PROJECT::STATE:UPDATE", + context, + data: { + operation: { + id: `webapp:react:store`, + refs: { + id: "redux", + version, + }, + }, + type: `end`, + content: { + key: `webapp.react.store.redux.${version}`, + data: generatedStore, + }, + }, + }); + }), + ); + + if (Object.keys(generatedStore.dependencies).length) { + await context.run({ + id: "op:PROJECT::STATE:UPDATE", + context, + data: { + operation: { + id: "settings:config:package", + }, + type: `end`, + content: { + key: "settings.config.package", + data: { + webapp: { + dependencies: generatedStore.dependencies, + }, + }, + }, + }, + }); + } + + return { + timestamp, + webapp: { + react: { + store: { + // ie. "views" , "sections" , "store" , "root" + redux: { + // ie. {UV_* , SEC_* , redux} , "app" (in case of root) + [timestamp]: generatedStore, + latest: generatedStore, + }, + }, + }, + }, + }; +} + +export default { + "WEBAPP:STORE::GENERATE": webappStoreGenerate, +}; diff --git a/cofounder/api/system/functions/webapp/view.js b/cofounder/api/system/functions/webapp/view.js new file mode 100644 index 0000000..91a484a --- /dev/null +++ b/cofounder/api/system/functions/webapp/view.js @@ -0,0 +1,1477 @@ +import utils from "@/utils/index.js"; +import yaml from "yaml"; +import fs from "fs"; +import { merge, sample } from "lodash-es"; + +async function webappViewGenerateMulti({ context, data }) { + /* get all view from uxsitemap , start gen */ + const { views } = data.uxsitemap.structure; + + // console.error("______DEBUG_____ : webapp:view:generate:multi : skipping functional pass, testing redesign only ! ! ! ! !",); + const passes = { + functional: true, + redesign: process.env.DESIGNER_ENABLE + ? JSON.parse(process.env.DESIGNER_ENABLE.toLowerCase()) + : false, + }; + + const tasks = [ + ...Object.keys(views.unique).map((viewId) => { + return { + task: { + type: "view", + view: { + type: "unique", + id: viewId, + }, + passes, + }, + }; + }), + ...Object.keys(views.shared).map((viewId) => { + return { + task: { + type: "view", + view: { + type: "shared", + id: viewId, + }, + passes, + }, + }; + }), + ]; + let response = {}; + + // console.error("__DEBUG________ : webapp:view:generate:multi : TASKS SLICED ! ! ! ! ! ! ! ! ! ! ! ! !"); + await Promise.all( + //[sample(tasks)] + // tasks.filter(e=>e.task.view.id.toLowerCase().includes('land')) + tasks.map(async (task) => { + response = merge( + response, + await context.run({ + id: `WEBAPP:VIEW::GENERATE`, + context, + data: { ...data, ...task }, + }), + ); + }), + ); + return response; +} + +async function promptViewFunctional({ context, data }) { + const { pm, backend, uxsitemap, uxdatamap, webapp, task } = data; + const { prd, frd, uxdmd } = pm; + const { view } = task; + /* + should return tsx , packages (if needed) + */ + return [ + { + role: `system`, + content: `your role as an expert react and tailwind senior dev and product manager is to write the code for the react view component based on the provided task; for view ${view.id} + +--- + +- analysis , in between \`\`\`markdown\`\`\`\` section +- full view component tsx code , in between \`\`\`tsx\`\`\`\` section +- dependencies, in between \`\`\`yaml\`\`\`\` section ; where any needed npm packages required code and need to be installed will be mentionned ; the yaml should have object : {dependencies : {"package":"version"} } ; (you can also just put "*" for version) +use doublequotes for every string inside the yaml to make sure formatting is good + +--- + +in your analysis , if functions makes use of global app state or makes calls to backend, include a snippet of how the app state store or backend code handles the functions (check the provided codes) before you get to implementing the function - this will help you reason better through it and provide good justification on how to structure your functions ; + +--- + +- in case you need temporary images or media, use a https://picsum.photos/ url with a random seed +no placeholder no hallucinated import of some local asset or image or component ... - do not make any assumptions about what is in the project other than strictly what it provided ! no hallucinations , no assumptions ! + +- in case you need to Link to other paths in the app (as described in the provided uxsitemaps), use (from : \`import { Link } from "react-router-dom"\` ) +- super important : + > if you link to other paths in the app, use \`import { Link } from "react-router-dom"\` !! + +--- + +- super important : +> render all the html nodes with one single big (<>...) that is returned by the default React.FC() view component +> do not split html nodes renders as functions ; use one very big (<>...) returned by the default component + use conditionals inside it when you need to, but no splitting render sections by functions - one big render block + +--- + +> any backend server calls should be to the dev url specified in the provided docs (typically http://localhost:1337/* unless specified otherwise) + +--- + +super important : +> use snake_case for any new naming you do + +extremely important : +> ensure full perfect coherence with: backend server and global app store : methods / events / names / schemas ... +> field names and schemas epecially +> everything should be perfectly coherent and functional + +--- + +> you are required to write the code for the full view, and for it to be fully functional +> no placeholders, no hallucinated imports, no assumptions that anything else has been implement , no missing imports ! + perfect working functional view component code with 100% of everything needed + +> very important : as long as every single requirements is in your generated code ! no hallucinated or assumed imports ! write and implement every single thing needed for this view ! + +you are a genius + you get $9999`, + }, + { + role: "user", + content: `\`\`\`PRD:product-requirements-document +${prd} +\`\`\` + +--- + +\`\`\`FRD:features-requirements-document +${frd} +\`\`\` +`, + }, + { + role: "user", + content: `\`\`\`UX:analysis-document +${uxdmd} +\`\`\``, + }, + { + role: `user`, + content: `writing code for the app view ${view.id} , defined in the following: +\`\`\` +${yaml.stringify(view)} +\`\`\``, + }, + { + role: "user", + content: + `` + + (!backend?.requirements?.restApi?.required + ? "" + : `--- +\`\`\`BACKEND:specifications:openAPI +${yaml.stringify(backend.specifications.openapi)} +\`\`\` + +`) + + (!backend?.requirements?.realtimeWebsockets?.required + ? "" + : `--- +\`\`\`BACKEND:specifications:asyncAPI +${yaml.stringify(backend.specifications.asyncapi)} +\`\`\` +` + + ` +--- + +\`\`\`BRD:server:main.js +${yaml.stringify(backend.server.main)} +\`\`\` + +`), + }, + { + role: `user`, + content: `the data states architecture for the ${view.id} alongside its relationships with the app architecture : +\`\`\`webapp:architecture +${yaml.stringify({ + architecture: { + ...uxdatamap.structure, + crosslinks: uxsitemap.structure.crosslinks, + }, + viewToImplement: view, +})} +\`\`\``, + }, + { + role: "user", + content: `for additional reference (if needed): +the root app component that wraps this view: + +\`\`\`@/App.tsx +${webapp.react.root.app.latest.tsx} +\`\`\` +--- + +the global state store component that wraps the app (including this view you're working on) is defined in the following ; +you can import the store exports if needed by using : \`import {...} from '@/store/main'\` + +\`\`\`@/store/main.tsx +${webapp.react.store.redux.latest.tsx} +\`\`\` +`, + }, + { + role: `user`, + content: `make the analysis and implement the tsx component; +> implement the react+tailwind component, fully and working from the get go; +> you are implementing the tsx code for view component : ${view.id} +> should be React.FC ! important ! + +- important : in case you need to link to other paths in the app (as described in the provided uxsitemaps), use (from : \`import { Link } from "react-router-dom"\` ) + +--- + +- super important : +> render all the html nodes with one single big (<>...) that is returned by the default React.FC() view component +> do not split html nodes renders as functions ; use one very big (<>...) returned by the default component + use conditionals inside it when you need to, but no splitting render sections by functions - one big render block + +--- + +> do not hallucinate methods or component imports that do not exist ! + all that exists has been provided to you + + any required additional actions should be implemented by you ; you are provided with all needed details to implement anything ! + > the global store and its methods is defined in @/store/main.tsx + > that's all !! + + DO NOT ASSUME OTHER STUFF IS IMPLEMENTED ! + + IF YOU NEED TO CALL THE BACKEND SERVER (whether for API / realtime websockets / ... ) OR SOMETHING SIMILAR, WRITE YOUR OWN COMPLETE FUNCTIONS INSIDE THIS VIEW !! + + do not write placeholders or imports from any "components to make" - all there is is the script you write so make it have 100% of everything needed + IMPLEMENT EVERYTHING NEEDED IN THIS SCRIPT, DO NOT ASSUME ANYTHING ELSE IS IMPLEMENTED OR WILL BE IMPLEMENTED BESIDES YOUR CODE ! + + do not hallucinate any imports - no hallucinated imports of local assets or images or components ... + no hallucinated imports ! no placeholders ! no assumptions that something exists ! + +--- + +- analysis , in between \`\`\`markdown\`\`\`\` section +- full view component tsx code , in between \`\`\`tsx\`\`\`\` section +- dependencies, for npm packages, with {dependencies:{package:version,...}}, in between \`\`\`yaml\`\`\`\` section + +you are a genius + you get $9999`, + }, + ].filter((e) => e); +} + +async function promptViewRedesign({ context, data }) { + /* + data : { + task : { + type , + view : { type , id , details, datamap , tsx }, + rag, + guidance { docs : { [usedPrimitiveId(s)] : "...mdx docs content..." } } || false, + layout : {analysis,render{svg,image{base64?,url?,local?}}}, + }, + } + */ + const { view, rag, guidance, layout } = data.task; + const { details } = data.pm; + const { tsx } = view; + const { render } = layout; + /* + DONT FORGET THE AESTHETICS OBJECT ! (see old code.js again) + */ + return [ + { + role: "system", + content: `your role as an expert react design engineer is to redesign and write the code for the react + tailwind view component based on the provided design task ; for view ${view.id} + +--- + +- analysis , in between \`\`\`markdown\`\`\`\` section +- full view component tsx code of redesigned view , in between \`\`\`tsx\`\`\`\` section +- dependencies, in between \`\`\`yaml\`\`\`\` section ; where any needed npm packages required code and need to be installed will be mentionned ; the yaml should have object : {dependencies : {"package":"version"} } ; (you can also just put "*" for version) +use doublequotes for every string inside the yaml to make sure formatting is good + +--- + +- in case you need temporary images or media, use a https://picsum.photos/ url with a random seed +no placeholder no hallucinated import of some local asset or image or component ... - do not make any assumptions about what is in the project other than strictly what it provided ! no hallucinations , no assumptions ! + +- in case you need to use icons, you can use icons from \`lucide-react\` ; but make sure they are icons you know 100% exist there ! no hallucinated icon names ! no assumptions ! + +- in case you need to Link to other paths in the app, use (from : \`import { Link } from "react-router-dom"\` ) +- super important : + > if you link to other paths in the app, use \`import { Link } from "react-router-dom"\` !! + +--- + + +- super important : +> render all the html nodes with one single big (<>...) that is returned by the default React.FC() view component +> do not split html nodes renders as functions ; use one very big (<>...) returned by the default component + use conditionals inside it when you need to, but no splitting render sections by functions - one big render block + +--- + +super important : +- your redesigned component should keep 100% of its functionalities from the previous view code version ; +- your role here is to redesign the component based on provided instructions +- keep in mind responsiveness + +--- + + +> you are required to write the code for the full view, and for it to be fully functional +> no placeholders, no hallucinated imports, no assumptions that anything else has been implement , no missing imports ! + perfect working functional view component code with 100% of everything needed + +> very important : + every single functionality is kept is in your redesigned view code ! + no hallucinated or assumed imports ! + write and implement every single thing needed for this view ! + +you are a genius +redesign the provided view component +you get $9999`, + }, + guidance?.docs && { + role: `user`, + content: `to help you in your task, you can refer to components code docs provided below : + +\`\`\` +${yaml.stringify({ docs: guidance.docs })} +\`\`\` + +------- + +super important + +you can use it docs reference when you judge it is good to do so ; +but use it as a reference when it makes sense to do so ! use your best judgement in all cases ! + +`, + }, + render?.image && + (render?.image?.url?.length || + render?.image?.base64?.length || + render?.image?.local?.length) && { + role: `user`, + content: [ + { + type: "text", + text: `the (desktop) design mockup of the view ${view.id} is as follows : + +you can use it as a reference when you judge it is good to do so ; +(you should also ensure mobile responsive while making it) + +important : use it as a reference when it makes sense to do so ! use your best judgement ! + +\`\`\`layout:design:mockup:figma-layers-export +${yaml.stringify({ + layers: render.svg.structure.svg.rect.map((item) => { + return { + primitiveType: item.$.primitiveId, + description: item.$.description, + _mockupCoords: { + x: item.$.x, + y: item.$.y, + w: item.$.width, + h: item.$.height, + }, + }; + }), +})} +\`\`\` +`, + }, + render?.image && + (render?.image?.url?.length || + render?.image?.base64?.length || + render?.image?.local?.length) && { + type: `image_url`, + image_url: { + url: render.image.url + ? render.image.url + : render.image.base64 + ? render.image.base64 + : render.image.local + ? `data:image/png;base64,${Buffer.from(fs.readFileSync(render.image.local)).toString("base64")}` + : "", + // detail: `high`, + }, + }, + ].filter((e) => e), + }, + { + role: "user", + content: `the code of the view ${view.id} that you are tasked to redesign is as follows : +\`\`\`${view.id}.tsx +${tsx} +\`\`\` + + +super important : +> your redesig should be perfectly congruent with the original view's features ; +> do not hallucinate features that the original view does not have ! do not take the freedom to add shit that isn't there ; things would break ! respect the task and strictly the task ! + +`, + }, + details?.design?.aesthetics?.text?.length && { + role: `user`, + content: `additionally - if it is any help, the design aesthetics instructions for the app are : + +${details.design.aesthetics.text}`, + }, + { + role: "user", + content: `make the analysis and implement the redesigned tsx component; +> redesign the react+tailwind component, fully and working from the get go; +> you are redesigning the tsx code for view component : ${view.id} +> should be React.FC ! important ! + +--- + + +- super important : +> render all the html nodes with one single big (<>...) that is returned by the default React.FC() view component +> do not split html nodes renders as functions ; use one very big (<>...) returned by the default component + use conditionals inside it when you need to, but no splitting render sections by functions - one big render block + +--- + +> do not hallucinate methods or component imports that do not exist ! + all that exists has been provided to you + DO NOT ASSUME OTHER STUFF IS IMPLEMENTED UNLESS IT WAS CLEARLY AND PRECISELY PROVIDED IN EXAMPLES OR DOCUMENTATION ! + NO ASSUMPTIONS ! + + do not write placeholders or imports from any "components to make" + all there is is the script you write so make it have 100% of everything needed + IMPLEMENT EVERYTHING NEEDED IN THIS SCRIPT, DO NOT ASSUME ANYTHING ELSE IS IMPLEMENTED OR WILL BE IMPLEMENTED BESIDES YOUR CODE AND EXACTLY WHAT WAS PROVIDED IN DOCS ! + + do not hallucinate any imports - no hallucinated imports of local assets or images or components ... + no hallucinated imports ! no placeholders ! no assumptions that something exists ! + +--- + +- analysis , in between \`\`\`markdown\`\`\`\` section +- full redesigned view component tsx code , in between \`\`\`tsx\`\`\`\` section +- dependencies, for npm packages, with {dependencies:{package:version,...}}, in between \`\`\`yaml\`\`\`\` section + +you are a genius +redesign the provided view and implement its full code +you get $9999`, + }, + ].filter((e) => e); +} + +async function webappViewGenerate({ context, data }) { + /* + data : { + ...data , + task : { + type: "view", + view: { type : "unique || shared" , id }, + passes: { + functional : true, + redesign : true, + } + } + } + */ + const timestamp = `${Date.now()}`; + + const { task } = data; + const { view, passes } = task; + + /* + passes : { functional:bool , redesign:bool } + if functional only, clear + if redesign only, svgPrompt -> recodePrompt + if both , parallel { functional , svg } -> recodePrompt + */ + + data.task.view = { + ...data.task.view, + details: data.uxsitemap.structure.views[task.view.type][task.view.id], + datamap: data.uxdatamap.views[task.view.type][task.view.id], + }; + + /* + should merge with data.webapp at every pass ! + */ + + if (passes.functional) { + const messagesFunctional = await promptViewFunctional({ + context, + data, + }); + + const { generated } = await context.run({ + id: "op:LLM::GEN", + context, + data: { + model: `chatgpt-4o-latest`, //`gpt-4o`, + messages: messagesFunctional, + preparser: false, + parser: false, + }, + }); + + const extraction = await utils.parsers.extract.backticksMultiple({ + text: generated, + delimiters: [`markdown`, `tsx`, `yaml`], + }); + + const { markdown, tsx } = extraction; + if (!tsx.length) { + throw new Error("webapp:view:generate error - generated tsx is empty"); + } + + const parsedYaml = extraction.yaml ? yaml.parse(extraction.yaml) : {}; + const generatedView = { + analysis: markdown, + tsx, + dependencies: parsedYaml.dependencies + ? Object.fromEntries( + Object.keys(parsedYaml.dependencies).map((key) => [key, "*"]), + ) + : [], + timestamp, + }; + + await Promise.all( + [`${timestamp}`, `latest`].map(async (version) => { + await context.run({ + id: "op:PROJECT::STATE:UPDATE", + context, + data: { + operation: { + id: `webapp:react:views`, + refs: { + id: view.id, + version, + }, + }, + type: `end`, + content: { + key: `webapp.react.views.${view.id}.${version}`, + data: generatedView, + }, + }, + }); + }), + ); + + if (Object.keys(generatedView.dependencies).length) { + await context.run({ + id: "op:PROJECT::STATE:UPDATE", + context, + data: { + operation: { + id: "settings:config:package", + }, + type: `end`, + content: { + key: "settings.config.package", + data: { + webapp: { + dependencies: generatedView.dependencies, + }, + }, + }, + }, + }); + } + + data.webapp = merge(data.webapp, { + react: { + views: { + // ie. "views" , "sections" , "store" , "root" + [view.id]: { + // ie. {UV_* , SEC_* , redux} , "app" (in case of root) + [timestamp]: generatedView, + latest: generatedView, + }, + }, + }, + }); + } + + if (passes.redesign) { + /* + svg pass (handled in DESIGNER:LAYOUTV1::VIEW:GENERATE) + */ + + const redesignTimestamp = `${Date.now()}`; + + const designerResponse = await context.run({ + id: "DESIGNER:LAYOUTV1::VIEW:GENERATE", + context, + data: { + ...data, + timestamp: redesignTimestamp, // to keep versions congruent + }, + }); // -> { designer {rag,guidance} , webapp { layout { views { [id] { [version] : { analysis , render{ svg,image } } } } } } } + + /* + merge (op:state:update already handled in designer:layoutv1) + */ + data.webapp = merge(data.webapp, designerResponse.webapp); + // console.dir({ designerResponse } , {depth:null }) ; process.exit(); + const { rag, guidance } = designerResponse.designer; + let primitivesIds = []; + try { + primitivesIds = [ + ...new Set( + designerResponse.webapp.layout.views[ + task.view.id + ].latest.render.svg.structure.svg.rect + .filter((item) => item.$?.primitiveId) + .map((item) => item.$.primitiveId), + ), + ]; + } catch (e) { + console.error(`webapp:view:generate:pass:redesign : ${e}`); + } + + data.task.view.tsx = data.webapp.react.views[task.view.id].latest.tsx; + const redesignTask = { + ...data.task, // type , view{type,id,details,datamap,tsx} + rag, + guidance: + guidance && guidance.docs?.primitives + ? { + docs: Object.fromEntries( + Object.entries( + Object.entries(guidance.docs.primitives) + .filter(([key]) => primitivesIds.includes(key)) + .reduce((acc, [key, value]) => { + if (!acc[value]) { + acc[value] = key; + } else { + acc[value] += ` , ${key}`; + } + return acc; + }, {}), + ).map(([value, keys]) => [keys, value]), + ), + } // filters out duplicates docs (ie. button , button_icon_only , button_secondary , ... share same docs) + : false, + layout: designerResponse.webapp.layout.views[task.view.id].latest, //{analysis,render{svg,image{base64?,url?,local?}}} + }; + + /* + recode pass (handled here with promptViewRedesign ) + make use of guidance (and rag?) ; since guidance will have design system docs that filter out whats needed ! + guidance { ontology{primitives{[ids]}} , image{base64,url} , docs{ primitives{ [id] : "... mdx content ..." } } } + */ + /* + in dependencies , filter out weird dependencies if exist like "@/components/..." + */ + + /* + use : redesignTimestamp !! + merge with data.webapp , {...react}: + op:state::update react + */ + + const messagesRedesign = await promptViewRedesign({ + context, + data: { + ...data, + task: redesignTask, + }, + }); + + const { generated } = await context.run({ + id: "op:LLM::GEN", + context, + data: { + model: `chatgpt-4o-latest`, //`gpt-4o`, + messages: messagesRedesign, + preparser: false, + parser: false, + }, + }); + + const extraction = await utils.parsers.extract.backticksMultiple({ + text: generated, + delimiters: [`markdown`, `tsx`, `yaml`], + }); + + const { markdown, tsx } = extraction; + if (!tsx.length) { + throw new Error("webapp:view:generate error - generated tsx is empty"); + } + + const parsedYaml = extraction.yaml ? yaml.parse(extraction.yaml) : {}; + const generatedView = { + analysis: markdown, + tsx, + dependencies: parsedYaml.dependencies + ? Object.fromEntries( + Object.keys(parsedYaml.dependencies).map((key) => [key, "*"]), + ) + : [], + timestamp: redesignTimestamp, + }; + + await Promise.all( + [`${redesignTimestamp}`, `latest`].map(async (version) => { + await context.run({ + id: "op:PROJECT::STATE:UPDATE", + context, + data: { + operation: { + id: `webapp:react:views`, + refs: { + id: view.id, + version, + }, + }, + type: `end`, + content: { + key: `webapp.react.views.${view.id}.${version}`, + data: generatedView, + }, + }, + }); + }), + ); + + if (Object.keys(generatedView.dependencies).length) { + await context.run({ + id: "op:PROJECT::STATE:UPDATE", + context, + data: { + operation: { + id: "settings:config:package", + }, + type: `end`, + content: { + key: "settings.config.package", + data: { + webapp: { + dependencies: generatedView.dependencies, + }, + }, + }, + }, + }); + } + + data.webapp = merge(data.webapp, { + react: { + views: { + // ie. "views" , "sections" , "store" , "root" + [view.id]: { + // ie. {UV_* , SEC_* , redux} , "app" (in case of root) + [redesignTimestamp]: generatedView, + latest: generatedView, + }, + }, + }, + }); + } + + return { webapp: data.webapp }; +} + +async function promptIterateNoDesigner({ context, data }) { + /* + prompt with : + current screenshot?.base64? + tsx + user notes text + */ + const { task } = data; + const { view, iteration } = task; + const { notes, screenshot } = iteration; + const { tsx } = view; + const { details } = data.pm; + + return [ + { + role: "system", + content: `your role as an expert react design engineer is to redesign and write the code for the react + tailwind view component based on the provided design task ; for view ${view.id} + +--- + +- analysis , in between \`\`\`markdown\`\`\`\` section +- full view component tsx code of redesigned view , in between \`\`\`tsx\`\`\`\` section +- dependencies, in between \`\`\`yaml\`\`\`\` section ; where any needed npm packages required code and need to be installed will be mentionned ; the yaml should have object : {dependencies : {"package":"version"} } ; (you can also just put "*" for version) +use doublequotes for every string inside the yaml to make sure formatting is good + +--- + +- in case you need temporary images or media, use a https://picsum.photos/ url with a random seed +no placeholder no hallucinated import of some local asset or image or component ... - do not make any assumptions about what is in the project other than strictly what it provided ! no hallucinations , no assumptions ! + +- in case you need to use icons, you can use icons from \`lucide-react\` ; but make sure they are icons you know 100% exist there ! no hallucinated icon names ! no assumptions ! + +- in case you need to Link to other paths in the app, use (from : \`import { Link } from "react-router-dom"\` ) +- super important : + > if you link to other paths in the app, use \`import { Link } from "react-router-dom"\` !! + +--- + + +- super important : +> render all the html nodes with one single big (<>...) that is returned by the default React.FC() view component +> do not split html nodes renders as functions ; use one very big (<>...) returned by the default component + use conditionals inside it when you need to, but no splitting render sections by functions - one big render block + +--- + +super important : +- your redesigned component should keep 100% of its functionalities from the previous view code version ; +- your role here is to redesign the component based on provided instructions +- keep in mind responsiveness + +--- + + +> you are required to write the code for the full view, and for it to be fully functional +> no placeholders, no hallucinated imports, no assumptions that anything else has been implement , no missing imports ! + perfect working functional view component code with 100% of everything needed + +> very important : + every single functionality is kept is in your redesigned view code ! + no hallucinated or assumed imports ! + write and implement every single thing needed for this view ! + +you are a genius +redesign the provided view component +you get $9999`, + }, + screenshot && + (screenshot?.url?.length || + screenshot?.base64?.length || + screenshot?.local?.length) && { + role: `user`, + content: [ + { + type: "text", + text: `a screenshot of the current view render : `, + }, + screenshot && + (screenshot?.url?.length || + screenshot?.base64?.length || + screenshot?.local?.length) && { + type: `image_url`, + image_url: { + url: screenshot.url + ? screenshot.url + : screenshot.base64 + ? screenshot.base64 + : screenshot.local + ? `data:image/png;base64,${Buffer.from(fs.readFileSync(render.image.local)).toString("base64")}` + : "", + // detail: `high`, + }, + }, + ].filter((e) => e), + }, + { + role: "user", + content: `the code of the view ${view.id} that you are tasked to redesign is as follows : +\`\`\`${view.id}.tsx +${tsx} +\`\`\` + +--- + +super important : +> your redesign should be perfectly congruent with the original view's features ; +> do not hallucinate features that the original view does not have ! do not take the freedom to add stuff that isn't there ; things would break ! respect the task and strictly the task ! + +`, + }, + details?.design?.aesthetics?.text?.length && { + role: `user`, + content: `additionally - if it is any help - the original design aesthetics instructions for the app were : + +\`\`\` +${details.design.aesthetics.text} +\`\`\` +`, + }, + notes?.text?.length && { + role: `user`, + content: `the new redesign instructions - the most important part of your task - are the following : + +\`\`\`view:redesign:instructions +${notes.text} +\`\`\` + +`, + }, + { + role: "user", + content: `make the analysis and implement the redesigned tsx component; +> redesign the react+tailwind component, fully and working from the get go; +> you are redesigning the tsx code for view component : ${view.id} +> should be React.FC ! important ! + +--- + + +- super important : +> render all the html nodes with one single big (<>...) that is returned by the default React.FC() view component +> do not split html nodes renders as functions ; use one very big (<>...) returned by the default component + use conditionals inside it when you need to, but no splitting render sections by functions - one big render block + +--- + +> do not hallucinate methods or component imports that do not exist ! + all that exists has been provided to you + DO NOT ASSUME OTHER STUFF IS IMPLEMENTED UNLESS IT WAS CLEARLY AND PRECISELY PROVIDED IN EXAMPLES OR DOCUMENTATION ! + NO ASSUMPTIONS ! + + do not write placeholders or imports from any "components to make" + all there is is the script you write so make it have 100% of everything needed + IMPLEMENT EVERYTHING NEEDED IN THIS SCRIPT, DO NOT ASSUME ANYTHING ELSE IS IMPLEMENTED OR WILL BE IMPLEMENTED BESIDES YOUR CODE AND EXACTLY WHAT WAS PROVIDED IN DOCS ! + + do not hallucinate any imports - no hallucinated imports of local assets or images or components ... + no hallucinated imports ! no placeholders ! no assumptions that something exists ! + +--- + +- analysis , in between \`\`\`markdown\`\`\`\` section +- full redesigned view component tsx code , in between \`\`\`tsx\`\`\`\` section +- dependencies, for npm packages, with {dependencies:{package:version,...}}, in between \`\`\`yaml\`\`\`\` section + +you are a genius +redesign the provided view and implement its full code +you get $9999`, + }, + ].filter((e) => e); +} + +async function promptIterateWithDesigner({ context, data }) { + /* + prompt with : + current screenshot?.base64? + tsx + suggested designer layout render + user notes text + */ + const { task } = data; + const { view, iteration, rag, guidance, layout } = task; + const { render } = layout; + const { notes, screenshot } = iteration; + const { tsx } = view; + const { details } = data.pm; + + return [ + { + role: "system", + content: `your role as an expert react design engineer is to redesign and write the code for the react + tailwind view component based on the provided design task ; for view ${view.id} + +--- + +- analysis , in between \`\`\`markdown\`\`\`\` section +- full view component tsx code of redesigned view , in between \`\`\`tsx\`\`\`\` section +- dependencies, in between \`\`\`yaml\`\`\`\` section ; where any needed packages imported into the component code and need to be installed will be mentionned ; the yaml should have object : {dependencies : {"package":"version"} } ; (you can also just put "*" for version) +use doublequotes for every string inside the yaml to make sure formatting is good + +--- + +- in case you need temporary images or media, use a https://picsum.photos/ url with a random seed +no placeholder no hallucinated import of some local asset or image or component ... - do not make any assumptions about what is in the project other than strictly what it provided ! no hallucinations , no assumptions ! + +- in case you need to use icons, you can use icons from \`lucide-react\` ; but make sure they are icons you know 100% exist there ! no hallucinated icon names ! no assumptions ! + +- in case you need to Link to other paths in the app, use (from : \`import { Link } from "react-router-dom"\` ) +- super important : + > if you link to other paths in the app, use \`import { Link } from "react-router-dom"\` !! + +--- + + +- super important : +> render all the html nodes with one single big (<>...) that is returned by the default React.FC() view component +> do not split html nodes renders as functions ; use one very big (<>...) returned by the default component + use conditionals inside it when you need to, but no splitting render sections by functions - one big render block + +--- + +super important : +- your redesigned component should keep 100% of its functionalities from the previous view code version ; +- your role here is to redesign the component based on provided instructions +- keep in mind responsiveness + +--- + + +> you are required to write the code for the full view, and for it to be fully functional +> no placeholders, no hallucinated imports, no assumptions that anything else has been implement , no missing imports ! + perfect working functional view component code with 100% of everything needed + +> very important : + every single functionality is kept is in your redesigned view code ! + no hallucinated or assumed imports ! + write and implement every single thing needed for this view ! + +you are a genius +redesign the provided view component +you get $9999`, + }, + screenshot && + (screenshot?.url?.length || + screenshot?.base64?.length || + screenshot?.local?.length) && { + role: `user`, + content: [ + { + type: "text", + text: `a screenshot of the current view render to redesign : `, + }, + screenshot && + (screenshot?.url?.length || + screenshot?.base64?.length || + screenshot?.local?.length) && { + type: `image_url`, + image_url: { + url: screenshot.url + ? screenshot.url + : screenshot.base64 + ? screenshot.base64 + : screenshot.local + ? `data:image/png;base64,${Buffer.from(fs.readFileSync(render.image.local)).toString("base64")}` + : "", + // detail: `high`, + }, + }, + ].filter((e) => e), + }, + guidance?.docs && { + role: `user`, + content: `to help you in your redesign task, you can refer to components code docs provided below : + +\`\`\` +${yaml.stringify({ docs: guidance.docs })} +\`\`\` + +------- + +super important + +you can use it docs reference when you judge it is good to do so ; +but use it as a reference when it makes sense to do so ! use your best judgement in all cases ! + +`, + }, + render?.image && + (render?.image?.url?.length || + render?.image?.base64?.length || + render?.image?.local?.length) && { + role: `user`, + content: [ + { + type: "text", + text: `the new suggested redesign mockup of the view ${view.id} is as follows : + +you can use it as a reference when you judge it is good to do so ; +(you should also ensure mobile responsive while making it) + +important : use it as a reference when it makes sense to do so ! use your best judgement ! + +\`\`\`layout:design:mockup:figma-layers-export +${yaml.stringify({ + layers: render.svg.structure.svg.rect.map((item) => { + return { + primitiveType: item.$.primitiveId, + description: item.$.description, + _mockupCoords: { + x: item.$.x, + y: item.$.y, + w: item.$.width, + h: item.$.height, + }, + }; + }), +})} +\`\`\` +`, + }, + render?.image && + (render?.image?.url?.length || + render?.image?.base64?.length || + render?.image?.local?.length) && { + type: `image_url`, + image_url: { + url: render.image.url + ? render.image.url + : render.image.base64 + ? render.image.base64 + : render.image.local + ? `data:image/png;base64,${Buffer.from(fs.readFileSync(render.image.local)).toString("base64")}` + : "", + // detail: `high`, + }, + }, + ].filter((e) => e), + }, + + { + role: "user", + content: `the code of the view ${view.id} that you are tasked to redesign is as follows : +\`\`\`${view.id}.tsx +${tsx} +\`\`\` + +--- + +super important : +> your redesign should be perfectly congruent with the original view's features ; +> do not hallucinate features that the original view does not have ! do not take the freedom to add stuff that isn't there ; things would break ! respect the task and strictly the task ! + +`, + }, + details?.design?.aesthetics?.text?.length && { + role: `user`, + content: `additionally - if it is any help - the original design aesthetics instructions for the app were : + +\`\`\` +${details.design.aesthetics.text} +\`\`\` +`, + }, + notes?.text?.length && { + role: `user`, + content: `the new redesign instructions ( on which the new redesign mockup are based ) are the following : + +\`\`\`view:redesign:instructions +${notes.text} +\`\`\` + +`, + }, + { + role: "user", + content: `make the analysis and implement the redesigned tsx component; +> redesign the react+tailwind component, fully and working from the get go; +> you are redesigning the tsx code for view component : ${view.id} +> should be React.FC ! important ! + +--- + + +- super important : +> render all the html nodes with one single big (<>...) that is returned by the default React.FC() view component +> do not split html nodes renders as functions ; use one very big (<>...) returned by the default component + use conditionals inside it when you need to, but no splitting render sections by functions - one big render block + +--- + +> do not hallucinate methods or component imports that do not exist ! + all that exists has been provided to you + DO NOT ASSUME OTHER STUFF IS IMPLEMENTED UNLESS IT WAS CLEARLY AND PRECISELY PROVIDED IN EXAMPLES OR DOCUMENTATION ! + NO ASSUMPTIONS ! + + do not write placeholders or imports from any "components to make" + all there is is the script you write so make it have 100% of everything needed + IMPLEMENT EVERYTHING NEEDED IN THIS SCRIPT, DO NOT ASSUME ANYTHING ELSE IS IMPLEMENTED OR WILL BE IMPLEMENTED BESIDES YOUR CODE AND EXACTLY WHAT WAS PROVIDED IN DOCS ! + + do not hallucinate any imports - no hallucinated imports of local assets or images or components ... + no hallucinated imports ! no placeholders ! no assumptions that something exists ! + +--- + +- analysis , in between \`\`\`markdown\`\`\`\` section +- full redesigned view component tsx code , in between \`\`\`tsx\`\`\`\` section +- dependencies, with {dependencies:{package:version,...}}, in between \`\`\`yaml\`\`\`\` section + +you are a genius +redesign the provided view and implement its full code +you get $9999`, + }, + ].filter((e) => e); +} + +//async function webappViewRedesign({ context, data }) {} +async function webappViewIterate({ context, data }) { + const timestamp = `${Date.now()}`; + + const { task } = data; + const { view, iteration } = task; + const { id, version } = view; + const { designer } = iteration; + + const tsx = data.webapp.react.views[task.view.id][version].tsx; + + // if designer , call designer/layoutv1/iterate + /* + if no designer, just have a prompt : promptIterateNoDesigner + if designer : + call designer/layoutv1/iterate , where it will do analysis+svg in one single pass + have ragText + user notes -> rag + have screenshot there too for ref + call promptIterateWithDesigner + */ + + // console.dir({"debug:webapp:view:iterate" : {task}},{depth:null}) + + data.task.view = { + ...data.task.view, + tsx, + details: data.uxsitemap.structure.views[task.view.type][task.view.id], + datamap: data.uxdatamap.views[task.view.type][task.view.id], + }; + + if (!designer) { + const promptMessagesNoDesigner = await promptIterateNoDesigner({ + context, + data, + }); + + const { generated } = await context.run({ + id: "op:LLM::GEN", + context, + data: { + model: `chatgpt-4o-latest`, //`gpt-4o`, + messages: promptMessagesNoDesigner, + preparser: false, + parser: false, + }, + }); + + const extraction = await utils.parsers.extract.backticksMultiple({ + text: generated, + delimiters: [`markdown`, `tsx`, `yaml`], + }); + + const { markdown, tsx } = extraction; + if (!tsx.length) { + throw new Error("webapp:view:generate error - generated tsx is empty"); + } + + const parsedYaml = extraction.yaml ? yaml.parse(extraction.yaml) : {}; + const generatedView = { + analysis: markdown, + tsx, + dependencies: parsedYaml.dependencies + ? Object.fromEntries( + Object.keys(parsedYaml.dependencies).map((key) => [key, "*"]), + ) + : [], + timestamp, + }; + + await Promise.all( + [`${timestamp}`, `latest`].map(async (version) => { + await context.run({ + id: "op:PROJECT::STATE:UPDATE", + context, + data: { + operation: { + id: `webapp:react:views`, + refs: { + id: view.id, + version, + }, + }, + type: `end`, + content: { + key: `webapp.react.views.${view.id}.${version}`, + data: generatedView, + }, + }, + }); + }), + ); + + if (Object.keys(generatedView.dependencies).length) { + await context.run({ + id: "op:PROJECT::STATE:UPDATE", + context, + data: { + operation: { + id: "settings:config:package", + }, + type: `end`, + content: { + key: "settings.config.package", + data: { + webapp: { + dependencies: generatedView.dependencies, + }, + }, + }, + }, + }); + } + + data.webapp = merge(data.webapp, { + react: { + views: { + // ie. "views" , "sections" , "store" , "root" + [view.id]: { + // ie. {UV_* , SEC_* , redux} , "app" (in case of root) + [timestamp]: generatedView, + latest: generatedView, + }, + }, + }, + }); + } else { + const redesignTimestamp = `${timestamp}`; + + const designerResponse = await context.run({ + id: "DESIGNER:LAYOUTV1::VIEW:ITERATE", + context, + data: { + ...data, + timestamp: redesignTimestamp, // to keep versions congruent + }, + }); // -> { designer {rag,guidance} , webapp { layout { views { [id] { [version] : { analysis , render{ svg,image } } } } } } } + + /* + merge (op:state:update already handled in designer:layoutv1) + */ + data.webapp = merge(data.webapp, designerResponse.webapp); + // console.dir({ designerResponse } , {depth:null }) ; process.exit(); + const { rag, guidance } = designerResponse.designer; + let primitivesIds = []; + try { + primitivesIds = [ + ...new Set( + designerResponse.webapp.layout.views[ + task.view.id + ].latest.render.svg.structure.svg.rect + .filter((item) => item.$?.primitiveId) + .map((item) => item.$.primitiveId), + ), + ]; + } catch (e) { + console.error(`webapp:view:generate:pass:redesign : ${e}`); + } + + const redesignTask = { + ...data.task, // type , view{type,id,details,datamap,tsx} , iteration{notes,screenshot} + rag, + guidance: + guidance && guidance.docs?.primitives + ? { + docs: Object.fromEntries( + Object.entries( + Object.entries(guidance.docs.primitives) + .filter(([key]) => primitivesIds.includes(key)) + .reduce((acc, [key, value]) => { + if (!acc[value]) { + acc[value] = key; + } else { + acc[value] += ` , ${key}`; + } + return acc; + }, {}), + ).map(([value, keys]) => [keys, value]), + ), + } // filters out duplicates docs (ie. button , button_icon_only , button_secondary , ... share same docs) + : false, + layout: designerResponse.webapp.layout.views[task.view.id].latest, //{analysis,render{svg,image{base64?,url?,local?}}} + }; + + const mesagesIterateWithDesigner = await promptIterateWithDesigner({ + context, + data: { + ...data, + task: redesignTask, + }, + }); + + const { generated } = await context.run({ + id: "op:LLM::GEN", + context, + data: { + model: `chatgpt-4o-latest`, //`gpt-4o`, + messages: mesagesIterateWithDesigner, + preparser: false, + parser: false, + }, + }); + + const extraction = await utils.parsers.extract.backticksMultiple({ + text: generated, + delimiters: [`markdown`, `tsx`, `yaml`], + }); + + const { markdown, tsx } = extraction; + if (!tsx.length) { + throw new Error("webapp:view:generate error - generated tsx is empty"); + } + + const parsedYaml = extraction.yaml ? yaml.parse(extraction.yaml) : {}; + const generatedView = { + analysis: markdown, + tsx, + dependencies: parsedYaml.dependencies + ? Object.fromEntries( + Object.keys(parsedYaml.dependencies).map((key) => [key, "*"]), + ) + : [], + timestamp: redesignTimestamp, + }; + + await Promise.all( + [`${redesignTimestamp}`, `latest`].map(async (version) => { + await context.run({ + id: "op:PROJECT::STATE:UPDATE", + context, + data: { + operation: { + id: `webapp:react:views`, + refs: { + id: view.id, + version, + }, + }, + type: `end`, + content: { + key: `webapp.react.views.${view.id}.${version}`, + data: generatedView, + }, + }, + }); + }), + ); + + if (Object.keys(generatedView.dependencies).length) { + await context.run({ + id: "op:PROJECT::STATE:UPDATE", + context, + data: { + operation: { + id: "settings:config:package", + }, + type: `end`, + content: { + key: "settings.config.package", + data: { + webapp: { + dependencies: generatedView.dependencies, + }, + }, + }, + }, + }); + } + + data.webapp = merge(data.webapp, { + react: { + views: { + // ie. "views" , "sections" , "store" , "root" + [view.id]: { + // ie. {UV_* , SEC_* , redux} , "app" (in case of root) + [redesignTimestamp]: generatedView, + latest: generatedView, + }, + }, + }, + }); + } + + return { webapp: data.webapp }; +} + +export default { + "WEBAPP:VIEW::GENERATE:MULTI": webappViewGenerateMulti, + "WEBAPP:VIEW::GENERATE": webappViewGenerate, + //"WEBAPP:VIEW::REDESIGN": webappViewRedesign, + "WEBAPP:VIEW::ITERATE": webappViewIterate, +}; diff --git a/cofounder/api/system/presets/ui/design/systems/protoboy-v1/README.md b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/README.md new file mode 100644 index 0000000..4af35cd --- /dev/null +++ b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/README.md @@ -0,0 +1,10 @@ +## Notes + +This is a demo design system and will be replaced on official post-alpha release + +## Credits + +Renders dumped from Figma Presets: + +- Blocks.pm by Hexa Plugin +- Google Material & Figma Core Design systems diff --git a/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/.gitignore b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/grid.png b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/grid.png new file mode 100644 index 0000000..6132bbe Binary files /dev/null and b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/grid.png differ diff --git a/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/ontology.yaml b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/ontology.yaml new file mode 100644 index 0000000..2bfe50d --- /dev/null +++ b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/ontology.yaml @@ -0,0 +1,73 @@ +primitives: + - id: heading + description: A primary heading for sectional content + - id: subheading + description: A secondary heading for a subsection of content + - id: text_line + description: A single line of text + - id: text_paragraph + description: A block of text longer than a line + - id: button + description: A clickable element used to perform actions + stretch: true + - id: link + description: A hyperlink pointing to another resource + - id: tag + description: An small capsule for categorizing or labeling - can also be used as a small status indicator or be clickable + - id: avatar + description: A graphical representation of either a user or brand, typically a photo or icon + - id: image + description: An element to display pictures or graphics + stretch: true + - id: icon + description: A graphical representation of an idea, action, or object + - id: input_datepicker + stretch: true + description: A control allowing the user to select a specific date + - id: input_description + stretch: true + description: An informational text describing an input field + - id: input_field + stretch: true + description: An input area where the user can enter text + - id: input_label + description: A label associated with an input field to describe its purpose + - id: input_searchfield + stretch: true + description: An input field specifically for search queries + - id: input_select + stretch: true + description: A dropdown menu allowing the user to choose from a list of options + - id: input_textarea + stretch: true + description: An input field for entering multi-line text + - id: radio_button + description: A control allowing the user to select one option from a group + - id: checkbox + description: A control that allows the user to select or deselect an option + - id: switch + description: A control allowing the user to toggle between two states + - id: slider + description: A control for selecting a value from a range + - id: media + description: An element for embedding video or audio + stretch: true + - id: map + description: A graphical representation of geographical information + stretch: true + - id: badge + description: A round indicator with a number inside it, used for example for counting or tracking + - id: calendar + description: A graphical representation of a calendar + stretch: true + - id: code_block + description: A block for displaying formatted code + stretch: true + - id: button_fab + description: A floating action round button that is sticky and fixed to the bottom right corner of the page + - id: progress_bar + description: A horizontal bar indicating progress toward completion + stretch: true + - id: nonprimitive + description: A placeholder for non-primitive components that should be designed based on description + stretch: true diff --git a/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/avatar.png b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/avatar.png new file mode 100644 index 0000000..1c7c931 Binary files /dev/null and b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/avatar.png differ diff --git a/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/avatar_block.png b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/avatar_block.png new file mode 100644 index 0000000..8443ad6 Binary files /dev/null and b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/avatar_block.png differ diff --git a/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/avatars_group.png b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/avatars_group.png new file mode 100644 index 0000000..95b539d Binary files /dev/null and b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/avatars_group.png differ diff --git a/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/badge.png b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/badge.png new file mode 100644 index 0000000..d1acf27 Binary files /dev/null and b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/badge.png differ diff --git a/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/breadcrumbs.png b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/breadcrumbs.png new file mode 100644 index 0000000..e3306f9 Binary files /dev/null and b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/breadcrumbs.png differ diff --git a/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/button.png b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/button.png new file mode 100644 index 0000000..d7c3693 Binary files /dev/null and b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/button.png differ diff --git a/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/button_fab.png b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/button_fab.png new file mode 100644 index 0000000..1f6e375 Binary files /dev/null and b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/button_fab.png differ diff --git a/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/calendar.png b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/calendar.png new file mode 100644 index 0000000..eeafb4c Binary files /dev/null and b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/calendar.png differ diff --git a/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/checkbox.png b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/checkbox.png new file mode 100644 index 0000000..a6b82c9 Binary files /dev/null and b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/checkbox.png differ diff --git a/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/code_block.png b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/code_block.png new file mode 100644 index 0000000..45a09bd Binary files /dev/null and b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/code_block.png differ diff --git a/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/heading.png b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/heading.png new file mode 100644 index 0000000..ae29bd3 Binary files /dev/null and b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/heading.png differ diff --git a/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/icon.png b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/icon.png new file mode 100644 index 0000000..a558cc6 Binary files /dev/null and b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/icon.png differ diff --git a/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/image.png b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/image.png new file mode 100644 index 0000000..5258bb2 Binary files /dev/null and b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/image.png differ diff --git a/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/input_datepicker.png b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/input_datepicker.png new file mode 100644 index 0000000..d252d66 Binary files /dev/null and b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/input_datepicker.png differ diff --git a/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/input_description.png b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/input_description.png new file mode 100644 index 0000000..665383d Binary files /dev/null and b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/input_description.png differ diff --git a/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/input_field.png b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/input_field.png new file mode 100644 index 0000000..bb317be Binary files /dev/null and b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/input_field.png differ diff --git a/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/input_label.png b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/input_label.png new file mode 100644 index 0000000..9bf0789 Binary files /dev/null and b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/input_label.png differ diff --git a/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/input_searchfield.png b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/input_searchfield.png new file mode 100644 index 0000000..b18a601 Binary files /dev/null and b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/input_searchfield.png differ diff --git a/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/input_select.png b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/input_select.png new file mode 100644 index 0000000..dde2e76 Binary files /dev/null and b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/input_select.png differ diff --git a/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/input_textarea.png b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/input_textarea.png new file mode 100644 index 0000000..5eee2be Binary files /dev/null and b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/input_textarea.png differ diff --git a/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/link.png b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/link.png new file mode 100644 index 0000000..9da7f3d Binary files /dev/null and b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/link.png differ diff --git a/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/logo.png b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/logo.png new file mode 100644 index 0000000..f44acc4 Binary files /dev/null and b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/logo.png differ diff --git a/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/map.png b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/map.png new file mode 100644 index 0000000..85ee5c3 Binary files /dev/null and b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/map.png differ diff --git a/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/media.png b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/media.png new file mode 100644 index 0000000..3707f1b Binary files /dev/null and b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/media.png differ diff --git a/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/menu_vertical.png b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/menu_vertical.png new file mode 100644 index 0000000..2a898aa Binary files /dev/null and b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/menu_vertical.png differ diff --git a/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/nonprimitive.png b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/nonprimitive.png new file mode 100644 index 0000000..a91334b Binary files /dev/null and b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/nonprimitive.png differ diff --git a/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/pagination.png b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/pagination.png new file mode 100644 index 0000000..1495c95 Binary files /dev/null and b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/pagination.png differ diff --git a/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/progress_bar.png b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/progress_bar.png new file mode 100644 index 0000000..22015c6 Binary files /dev/null and b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/progress_bar.png differ diff --git a/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/radio_button.png b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/radio_button.png new file mode 100644 index 0000000..310fa04 Binary files /dev/null and b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/radio_button.png differ diff --git a/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/rating_stars.png b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/rating_stars.png new file mode 100644 index 0000000..5f6ab80 Binary files /dev/null and b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/rating_stars.png differ diff --git a/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/search_bar.png b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/search_bar.png new file mode 100644 index 0000000..8fefdeb Binary files /dev/null and b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/search_bar.png differ diff --git a/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/slider.png b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/slider.png new file mode 100644 index 0000000..70f1c70 Binary files /dev/null and b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/slider.png differ diff --git a/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/subheading.png b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/subheading.png new file mode 100644 index 0000000..d740076 Binary files /dev/null and b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/subheading.png differ diff --git a/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/switch.png b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/switch.png new file mode 100644 index 0000000..2872beb Binary files /dev/null and b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/switch.png differ diff --git a/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/table.png b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/table.png new file mode 100644 index 0000000..da514c4 Binary files /dev/null and b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/table.png differ diff --git a/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/tabs_group.png b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/tabs_group.png new file mode 100644 index 0000000..f2a4edf Binary files /dev/null and b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/tabs_group.png differ diff --git a/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/tag.png b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/tag.png new file mode 100644 index 0000000..7eb74f8 Binary files /dev/null and b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/tag.png differ diff --git a/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/tags_toggle_group.png b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/tags_toggle_group.png new file mode 100644 index 0000000..a919e07 Binary files /dev/null and b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/tags_toggle_group.png differ diff --git a/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/text_line.png b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/text_line.png new file mode 100644 index 0000000..26856cd Binary files /dev/null and b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/text_line.png differ diff --git a/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/text_paragraph.png b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/text_paragraph.png new file mode 100644 index 0000000..1dd2937 Binary files /dev/null and b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/text_paragraph.png differ diff --git a/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/tooltip.png b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/tooltip.png new file mode 100644 index 0000000..dc1cdbd Binary files /dev/null and b/cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/renders/tooltip.png differ diff --git a/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/.gitignore b/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/accordion.mdx b/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/accordion.mdx new file mode 100644 index 0000000..3cf9e28 --- /dev/null +++ b/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/accordion.mdx @@ -0,0 +1,70 @@ +Name : Accordion +Description : A vertically stacked set of interactive headings that each reveal a section of content. + +--- + +### import + +``` +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "@/components/ui/accordion" +``` + +--- + +### use + +```accordion.mdx + + + Is it accessible? + + Yes. It adheres to the WAI-ARIA design pattern. + + + +``` + +--- + +### examples + +```accordion-demo.tsx +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "@/components/ui/accordion" + +export default function AccordionDemo() { + return ( + + + Is it accessible? + + Yes. It adheres to the WAI-ARIA design pattern. + + + + Is it styled? + + Yes. It comes with default styles that matches the other + components' aesthetic. + + + + Is it animated? + + Yes. It's animated by default, but you can disable it if you + prefer. + + + + ) +} +``` diff --git a/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/avatar.mdx b/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/avatar.mdx new file mode 100644 index 0000000..e8733ca --- /dev/null +++ b/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/avatar.mdx @@ -0,0 +1,42 @@ +Name : Avatar +Description : An image element with a fallback for representing the user. + +--- + +### import + +``` +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" +``` + +--- + +### use + +```avatar.mdx + + + CN + +``` + +--- + +### examples + +```avatar-demo.tsx +import { + Avatar, + AvatarFallback, + AvatarImage, +} from "@/components/ui/avatar" + +export default function AvatarDemo() { + return ( + + + CN + + ) +} +``` diff --git a/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/badge.mdx b/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/badge.mdx new file mode 100644 index 0000000..4923852 --- /dev/null +++ b/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/badge.mdx @@ -0,0 +1,59 @@ +Name : Badge +Description : Displays a badge or a component that looks like a badge. + +--- + +### import + +``` +import { Badge } from "@/components/ui/badge" +``` + +--- + +### use + +```badge.mdx +Badge +``` + +```badge.mdx +import { badgeVariants } from "@/components/ui/badge" +Badge +``` + +--- + +### examples + +```badge-demo.tsx +import { Badge } from "@/components/ui/badge" + +export default function BadgeDemo() { + return Badge +} +``` + +```badge-destructive.tsx +import { Badge } from "@/components/ui/badge" + +export default function BadgeDestructive() { + return Destructive +} +``` + +```badge-outline.tsx +import { Badge } from "@/components/ui/badge" + +export default function BadgeOutline() { + return Outline +} +``` + +```badge-secondary.tsx +import { Badge } from "@/components/ui/badge" + +export default function BadgeSecondary() { + return Secondary +} +``` diff --git a/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/bar_progress.mdx b/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/bar_progress.mdx new file mode 100644 index 0000000..ecb00b0 --- /dev/null +++ b/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/bar_progress.mdx @@ -0,0 +1,41 @@ +Name : Progress +Description : Displays an indicator showing the completion progress of a task, typically displayed as a progress bar. + +--- + +### import + +``` +import { Progress } from "@/components/ui/progress" +``` + +--- + +### use + +```progress.mdx + +``` + +--- + +### examples + +```progress-demo.tsx +"use client" + +import * as React from "react" + +import { Progress } from "@/components/ui/progress" + +export default function ProgressDemo() { + const [progress, setProgress] = React.useState(13) + + React.useEffect(() => { + const timer = setTimeout(() => setProgress(66), 500) + return () => clearTimeout(timer) + }, []) + + return +} +``` diff --git a/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/bar_slider.mdx b/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/bar_slider.mdx new file mode 100644 index 0000000..37bbbe4 --- /dev/null +++ b/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/bar_slider.mdx @@ -0,0 +1,41 @@ +Name : Slider +Description : An input where the user selects a value from within a given range. + +--- + +### import + +``` +import { Slider } from "@/components/ui/slider" +``` + +--- + +### use + +```slider.mdx + +``` + +--- + +### examples + +```slider-demo.tsx +import { cn } from "@/lib/utils" +import { Slider } from "@/components/ui/slider" + +type SliderProps = React.ComponentProps + +export default function SliderDemo({ className, ...props }: SliderProps) { + return ( + + ) +} +``` diff --git a/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/breadcrumbs.mdx b/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/breadcrumbs.mdx new file mode 100644 index 0000000..c0b1b93 --- /dev/null +++ b/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/breadcrumbs.mdx @@ -0,0 +1,62 @@ +--- +title: Breadcrumb +description: Displays the path to the current resource using a hierarchy of links. +component: true +--- + +## Usage + +```tsx +import { + Breadcrumb, + BreadcrumbItem, + BreadcrumbLink, + BreadcrumbList, + BreadcrumbPage, + BreadcrumbSeparator, +} from "@/components/ui/breadcrumb"; +``` + +```tsx + + + + Home + + + + Components + + + + Breadcrumb + + + +``` + +## Examples + +### Custom separator + +Use a custom component as `children` for `` to create a custom separator. + +```tsx +import { Slash } from "lucide-react" + +... + + + + + Home + + + + + + Components + + + +``` diff --git a/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/button.mdx b/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/button.mdx new file mode 100644 index 0000000..01e6247 --- /dev/null +++ b/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/button.mdx @@ -0,0 +1,124 @@ +Name : Button +Description : Displays a button or a component that looks like a button. + +--- + +### import + +``` +import { Button } from "@/components/ui/button" +``` + +--- + +### use + +```button.mdx +import { Button } from "@/components/ui/button" + + +``` + +```button.mdx +import { Link } from 'react-router-dom'; +import { Button } from "@/components/ui/button" +... + + + +``` + +--- + +### examples + +```button-demo.tsx +import { Button } from "@/components/ui/button" + +export default function ButtonDemo() { + return +} +``` + +```button-destructive.tsx +import { Button } from "@/components/ui/button" + +export default function ButtonDestructive() { + return +} +``` + +```button-ghost.tsx +import { Button } from "@/components/ui/button" + +export default function ButtonGhost() { + return +} +``` + +```button-icon.tsx +import { ChevronRight } from "lucide-react" + +import { Button } from "@/components/ui/button" + +export default function ButtonIcon() { + return ( + + ) +} +``` + +```button-link.tsx +import { Button } from "@/components/ui/button" + +export default function ButtonLink() { + return +} +``` + +```button-loading.tsx +import { Loader2 } from "lucide-react" + +import { Button } from "@/components/ui/button" + +export default function ButtonLoading() { + return ( + + ) +} +``` + +```button-outline.tsx +import { Button } from "@/components/ui/button" + +export default function ButtonOutline() { + return +} +``` + +```button-secondary.tsx +import { Button } from "@/components/ui/button" + +export default function ButtonSecondary() { + return +} +``` + +```button-with-icon.tsx +import { Mail } from "lucide-react" + +import { Button } from "@/components/ui/button" + +export default function ButtonWithIcon() { + return ( + + ) +} +``` diff --git a/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/button_icon_only.mdx b/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/button_icon_only.mdx new file mode 100644 index 0000000..01e6247 --- /dev/null +++ b/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/button_icon_only.mdx @@ -0,0 +1,124 @@ +Name : Button +Description : Displays a button or a component that looks like a button. + +--- + +### import + +``` +import { Button } from "@/components/ui/button" +``` + +--- + +### use + +```button.mdx +import { Button } from "@/components/ui/button" + + +``` + +```button.mdx +import { Link } from 'react-router-dom'; +import { Button } from "@/components/ui/button" +... + + + +``` + +--- + +### examples + +```button-demo.tsx +import { Button } from "@/components/ui/button" + +export default function ButtonDemo() { + return +} +``` + +```button-destructive.tsx +import { Button } from "@/components/ui/button" + +export default function ButtonDestructive() { + return +} +``` + +```button-ghost.tsx +import { Button } from "@/components/ui/button" + +export default function ButtonGhost() { + return +} +``` + +```button-icon.tsx +import { ChevronRight } from "lucide-react" + +import { Button } from "@/components/ui/button" + +export default function ButtonIcon() { + return ( + + ) +} +``` + +```button-link.tsx +import { Button } from "@/components/ui/button" + +export default function ButtonLink() { + return +} +``` + +```button-loading.tsx +import { Loader2 } from "lucide-react" + +import { Button } from "@/components/ui/button" + +export default function ButtonLoading() { + return ( + + ) +} +``` + +```button-outline.tsx +import { Button } from "@/components/ui/button" + +export default function ButtonOutline() { + return +} +``` + +```button-secondary.tsx +import { Button } from "@/components/ui/button" + +export default function ButtonSecondary() { + return +} +``` + +```button-with-icon.tsx +import { Mail } from "lucide-react" + +import { Button } from "@/components/ui/button" + +export default function ButtonWithIcon() { + return ( + + ) +} +``` diff --git a/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/button_secondary.mdx b/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/button_secondary.mdx new file mode 100644 index 0000000..01e6247 --- /dev/null +++ b/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/button_secondary.mdx @@ -0,0 +1,124 @@ +Name : Button +Description : Displays a button or a component that looks like a button. + +--- + +### import + +``` +import { Button } from "@/components/ui/button" +``` + +--- + +### use + +```button.mdx +import { Button } from "@/components/ui/button" + + +``` + +```button.mdx +import { Link } from 'react-router-dom'; +import { Button } from "@/components/ui/button" +... + + + +``` + +--- + +### examples + +```button-demo.tsx +import { Button } from "@/components/ui/button" + +export default function ButtonDemo() { + return +} +``` + +```button-destructive.tsx +import { Button } from "@/components/ui/button" + +export default function ButtonDestructive() { + return +} +``` + +```button-ghost.tsx +import { Button } from "@/components/ui/button" + +export default function ButtonGhost() { + return +} +``` + +```button-icon.tsx +import { ChevronRight } from "lucide-react" + +import { Button } from "@/components/ui/button" + +export default function ButtonIcon() { + return ( + + ) +} +``` + +```button-link.tsx +import { Button } from "@/components/ui/button" + +export default function ButtonLink() { + return +} +``` + +```button-loading.tsx +import { Loader2 } from "lucide-react" + +import { Button } from "@/components/ui/button" + +export default function ButtonLoading() { + return ( + + ) +} +``` + +```button-outline.tsx +import { Button } from "@/components/ui/button" + +export default function ButtonOutline() { + return +} +``` + +```button-secondary.tsx +import { Button } from "@/components/ui/button" + +export default function ButtonSecondary() { + return +} +``` + +```button-with-icon.tsx +import { Mail } from "lucide-react" + +import { Button } from "@/components/ui/button" + +export default function ButtonWithIcon() { + return ( + + ) +} +``` diff --git a/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/button_with_icon.mdx b/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/button_with_icon.mdx new file mode 100644 index 0000000..01e6247 --- /dev/null +++ b/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/button_with_icon.mdx @@ -0,0 +1,124 @@ +Name : Button +Description : Displays a button or a component that looks like a button. + +--- + +### import + +``` +import { Button } from "@/components/ui/button" +``` + +--- + +### use + +```button.mdx +import { Button } from "@/components/ui/button" + + +``` + +```button.mdx +import { Link } from 'react-router-dom'; +import { Button } from "@/components/ui/button" +... + + + +``` + +--- + +### examples + +```button-demo.tsx +import { Button } from "@/components/ui/button" + +export default function ButtonDemo() { + return +} +``` + +```button-destructive.tsx +import { Button } from "@/components/ui/button" + +export default function ButtonDestructive() { + return +} +``` + +```button-ghost.tsx +import { Button } from "@/components/ui/button" + +export default function ButtonGhost() { + return +} +``` + +```button-icon.tsx +import { ChevronRight } from "lucide-react" + +import { Button } from "@/components/ui/button" + +export default function ButtonIcon() { + return ( + + ) +} +``` + +```button-link.tsx +import { Button } from "@/components/ui/button" + +export default function ButtonLink() { + return +} +``` + +```button-loading.tsx +import { Loader2 } from "lucide-react" + +import { Button } from "@/components/ui/button" + +export default function ButtonLoading() { + return ( + + ) +} +``` + +```button-outline.tsx +import { Button } from "@/components/ui/button" + +export default function ButtonOutline() { + return +} +``` + +```button-secondary.tsx +import { Button } from "@/components/ui/button" + +export default function ButtonSecondary() { + return +} +``` + +```button-with-icon.tsx +import { Mail } from "lucide-react" + +import { Button } from "@/components/ui/button" + +export default function ButtonWithIcon() { + return ( + + ) +} +``` diff --git a/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/calendar_datepicker.mdx b/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/calendar_datepicker.mdx new file mode 100644 index 0000000..cedf89d --- /dev/null +++ b/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/calendar_datepicker.mdx @@ -0,0 +1,48 @@ +Name : Calendar +Description : A date field component that allows users to enter and edit date. + +--- + +### import + +``` +import { Calendar } from "@/components/ui/calendar" +``` + +--- + +### use + +```calendar.mdx + +``` + +--- + +### examples + +```calendar-demo.tsx +"use client" + +import * as React from "react" + +import { Calendar } from "@/components/ui/calendar" + +export default function CalendarDemo() { + const [date, setDate] = React.useState(new Date()) + + return ( + + ) +} +``` diff --git a/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/carousel_horizontal.mdx b/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/carousel_horizontal.mdx new file mode 100644 index 0000000..16387c1 --- /dev/null +++ b/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/carousel_horizontal.mdx @@ -0,0 +1,220 @@ +--- +title: Carousel +description: A carousel with motion and swipe built using Embla. +--- + +## Usage + +```tsx +import { + Carousel, + CarouselContent, + CarouselItem, + CarouselNext, + CarouselPrevious, +} from "@/components/ui/carousel"; +``` + +```tsx + + + ... + ... + ... + + + + +``` + +## Examples + +### Sizes + +To set the size of the items, you can use the `basis` utility class on the ``. + +```tsx title="Example" showLineNumbers {4-6} +// 33% of the carousel width. + + + ... + ... + ... + + +``` + +```tsx title="Responsive" showLineNumbers {4-6} +// 50% on small screens and 33% on larger screens. + + + ... + ... + ... + + +``` + +### Spacing + +To set the spacing between the items, we use a `pl-[VALUE]` utility on the `` and a negative `-ml-[VALUE]` on the ``. + + + **Why:** I tried to use the `gap` property or a `grid` layout on the ` + ` but it required a lot of math and mental effort to get the + spacing right. I found `pl-[VALUE]` and `-ml-[VALUE]` utilities much easier to + use. + +You can always adjust this in your own project if you need to. + + + + + +```tsx title="Example" showLineNumbers /-ml-4/ /pl-4/ + + + ... + ... + ... + + +``` + +```tsx title="Responsive" showLineNumbers /-ml-2/ /pl-2/ /md:-ml-4/ /md:pl-4/ + + + ... + ... + ... + + +``` + +### Orientation + +Use the `orientation` prop to set the orientation of the carousel. + +```tsx showLineNumbers /vertical | horizontal/ + + + ... + ... + ... + + +``` + +## Options + +You can pass options to the carousel using the `opts` prop. + +```tsx showLineNumbers {2-5} + + + ... + ... + ... + + +``` + +## API + +Use a state and the `setApi` props to get an instance of the carousel API. + +```tsx showLineNumbers {1,4,22} +import { type CarouselApi } from "@/components/ui/carousel"; + +export function Example() { + const [api, setApi] = React.useState(); + const [current, setCurrent] = React.useState(0); + const [count, setCount] = React.useState(0); + + React.useEffect(() => { + if (!api) { + return; + } + + setCount(api.scrollSnapList().length); + setCurrent(api.selectedScrollSnap() + 1); + + api.on("select", () => { + setCurrent(api.selectedScrollSnap() + 1); + }); + }, [api]); + + return ( + + + ... + ... + ... + + + ); +} +``` + +## Events + +You can listen to events using the api instance from `setApi`. + +```tsx showLineNumbers {1,4-14,16} +import { type CarouselApi } from "@/components/ui/carousel"; + +export function Example() { + const [api, setApi] = React.useState(); + + React.useEffect(() => { + if (!api) { + return; + } + + api.on("select", () => { + // Do something on select. + }); + }, [api]); + + return ( + + + ... + ... + ... + + + ); +} +``` + +## Plugins + +You can use the `plugins` prop to add plugins to the carousel. + +```ts showLineNumbers {1,6-10} +import Autoplay from "embla-carousel-autoplay" + +export function Example() { + return ( + + // ... + + ) +} +``` diff --git a/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/carousel_vertical.mdx b/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/carousel_vertical.mdx new file mode 100644 index 0000000..16387c1 --- /dev/null +++ b/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/carousel_vertical.mdx @@ -0,0 +1,220 @@ +--- +title: Carousel +description: A carousel with motion and swipe built using Embla. +--- + +## Usage + +```tsx +import { + Carousel, + CarouselContent, + CarouselItem, + CarouselNext, + CarouselPrevious, +} from "@/components/ui/carousel"; +``` + +```tsx + + + ... + ... + ... + + + + +``` + +## Examples + +### Sizes + +To set the size of the items, you can use the `basis` utility class on the ``. + +```tsx title="Example" showLineNumbers {4-6} +// 33% of the carousel width. + + + ... + ... + ... + + +``` + +```tsx title="Responsive" showLineNumbers {4-6} +// 50% on small screens and 33% on larger screens. + + + ... + ... + ... + + +``` + +### Spacing + +To set the spacing between the items, we use a `pl-[VALUE]` utility on the `` and a negative `-ml-[VALUE]` on the ``. + + + **Why:** I tried to use the `gap` property or a `grid` layout on the ` + ` but it required a lot of math and mental effort to get the + spacing right. I found `pl-[VALUE]` and `-ml-[VALUE]` utilities much easier to + use. + +You can always adjust this in your own project if you need to. + + + + + +```tsx title="Example" showLineNumbers /-ml-4/ /pl-4/ + + + ... + ... + ... + + +``` + +```tsx title="Responsive" showLineNumbers /-ml-2/ /pl-2/ /md:-ml-4/ /md:pl-4/ + + + ... + ... + ... + + +``` + +### Orientation + +Use the `orientation` prop to set the orientation of the carousel. + +```tsx showLineNumbers /vertical | horizontal/ + + + ... + ... + ... + + +``` + +## Options + +You can pass options to the carousel using the `opts` prop. + +```tsx showLineNumbers {2-5} + + + ... + ... + ... + + +``` + +## API + +Use a state and the `setApi` props to get an instance of the carousel API. + +```tsx showLineNumbers {1,4,22} +import { type CarouselApi } from "@/components/ui/carousel"; + +export function Example() { + const [api, setApi] = React.useState(); + const [current, setCurrent] = React.useState(0); + const [count, setCount] = React.useState(0); + + React.useEffect(() => { + if (!api) { + return; + } + + setCount(api.scrollSnapList().length); + setCurrent(api.selectedScrollSnap() + 1); + + api.on("select", () => { + setCurrent(api.selectedScrollSnap() + 1); + }); + }, [api]); + + return ( + + + ... + ... + ... + + + ); +} +``` + +## Events + +You can listen to events using the api instance from `setApi`. + +```tsx showLineNumbers {1,4-14,16} +import { type CarouselApi } from "@/components/ui/carousel"; + +export function Example() { + const [api, setApi] = React.useState(); + + React.useEffect(() => { + if (!api) { + return; + } + + api.on("select", () => { + // Do something on select. + }); + }, [api]); + + return ( + + + ... + ... + ... + + + ); +} +``` + +## Plugins + +You can use the `plugins` prop to add plugins to the carousel. + +```ts showLineNumbers {1,6-10} +import Autoplay from "embla-carousel-autoplay" + +export function Example() { + return ( + + // ... + + ) +} +``` diff --git a/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/dialog_overlay_trigger_button.mdx b/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/dialog_overlay_trigger_button.mdx new file mode 100644 index 0000000..429d579 --- /dev/null +++ b/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/dialog_overlay_trigger_button.mdx @@ -0,0 +1,90 @@ +Name : Dialog +Description : A window overlaid on either the primary window or another dialog window, rendering the content underneath inert. + +--- + +### import + +``` +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog" +``` + +--- + +### use + +```dialog.mdx + + Open + + + Are you sure absolutely sure? + + This action cannot be undone. This will permanently delete your account + and remove your data from our servers. + + + + +``` + +--- + +### examples + +```dialog-demo.tsx +import { Button } from "@/components/ui/button" +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog" +import { Input } from "@/components/ui/input" +import { Label } from "@/components/ui/label" + +export default function DialogDemo() { + return ( + + + + + + + Edit profile + + Make changes to your profile here. Click save when you're done. + + +
+
+ + +
+
+ + +
+
+ + + +
+
+ ) +} +``` diff --git a/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/input_checkbox.mdx b/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/input_checkbox.mdx new file mode 100644 index 0000000..f1237b8 --- /dev/null +++ b/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/input_checkbox.mdx @@ -0,0 +1,85 @@ +Name : Checkbox +Description : A control that allows the user to toggle between checked and not checked. + +--- + +### import + +``` +import { Checkbox } from "@/components/ui/checkbox" +``` + +--- + +### use + +```checkbox.mdx + +``` + +--- + +### examples + +```checkbox-demo.tsx +"use client" + +import { Checkbox } from "@/components/ui/checkbox" + +export default function CheckboxDemo() { + return ( +
+ + +
+ ) +} +``` + +```checkbox-disabled.tsx +import { Checkbox } from "@/components/ui/checkbox" + +export default function CheckboxDisabled() { + return ( +
+ + +
+ ) +} +``` + +```checkbox-with-text.tsx +"use client" + +import { Checkbox } from "@/components/ui/checkbox" + +export default function CheckboxWithText() { + return ( +
+ +
+ +

+ You agree to our Terms of Service and Privacy Policy. +

+
+
+ ) +} +``` diff --git a/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/input_combobox.mdx b/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/input_combobox.mdx new file mode 100644 index 0000000..13f95b8 --- /dev/null +++ b/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/input_combobox.mdx @@ -0,0 +1,347 @@ +Name : Combobox +Description : Autocomplete input and command palette with a list of suggestions. + +--- + +### import + +``` +"use client" + +import * as React from "react" +import { Check, ChevronsUpDown } from "lucide-react" + +import { cn } from "@/lib/utils" +import { Button } from "@/components/ui/button" +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, +} from "@/components/ui/command" +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover" + +const frameworks = [ + { + value: "next.js", + label: "Next.js", + }, + { + value: "sveltekit", + label: "SvelteKit", + }, + { + value: "nuxt.js", + label: "Nuxt.js", + }, + { + value: "remix", + label: "Remix", + }, + { + value: "astro", + label: "Astro", + }, +] + +export function ComboboxDemo() { + const [open, setOpen] = React.useState(false) + const [value, setValue] = React.useState("") + + return ( + + + + + + + + No framework found. + + {frameworks.map((framework) => ( + { + setValue(currentValue === value ? "" : currentValue) + setOpen(false) + }} + > + + {framework.label} + + ))} + + + + + ) +} +``` + +--- + +### use + +--- + +### examples + +```combobox-dropdown-menu.tsx +"use client" + +import * as React from "react" +import { Calendar, MoreHorizontal, Tags, Trash, User } from "lucide-react" + +import { Button } from "@/components/ui/button" +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from "@/components/ui/command" +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu" + +const labels = [ + "feature", + "bug", + "enhancement", + "documentation", + "design", + "question", + "maintenance", +] + +export default function ComboboxDropdownMenu() { + const [label, setLabel] = React.useState("feature") + const [open, setOpen] = React.useState(false) + + return ( +
+

+ + {label} + + Create a new project +

+ + + + + + Actions + + + + Assign to... + + + + Set due date... + + + + + + Apply label + + + + + + No label found. + + {labels.map((label) => ( + { + setLabel(value) + setOpen(false) + }} + > + {label} + + ))} + + + + + + + + + Delete + ⌘⌫ + + + + +
+ ) +} +``` + +```combobox-popover.tsx +"use client" + +import * as React from "react" +import { + ArrowUpCircle, + CheckCircle2, + Circle, + HelpCircle, + LucideIcon, + XCircle, +} from "lucide-react" + +import { cn } from "@/lib/utils" +import { Button } from "@/components/ui/button" +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from "@/components/ui/command" +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover" + +type Status = { + value: string + label: string + icon: LucideIcon +} + +const statuses: Status[] = [ + { + value: "backlog", + label: "Backlog", + icon: HelpCircle, + }, + { + value: "todo", + label: "Todo", + icon: Circle, + }, + { + value: "in progress", + label: "In Progress", + icon: ArrowUpCircle, + }, + { + value: "done", + label: "Done", + icon: CheckCircle2, + }, + { + value: "canceled", + label: "Canceled", + icon: XCircle, + }, +] + +export default function ComboboxPopover() { + const [open, setOpen] = React.useState(false) + const [selectedStatus, setSelectedStatus] = React.useState( + null + ) + + return ( +
+

Status

+ + + + + + + + + No results found. + + {statuses.map((status) => ( + { + setSelectedStatus( + statuses.find((priority) => priority.value === value) || + null + ) + setOpen(false) + }} + > + + {status.label} + + ))} + + + + + +
+ ) +} +``` diff --git a/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/input_datepicker.mdx b/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/input_datepicker.mdx new file mode 100644 index 0000000..217e1af --- /dev/null +++ b/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/input_datepicker.mdx @@ -0,0 +1,239 @@ +Name : Date Picker +Description : A date picker component with range and presets. + +--- + +### import + +``` +"use client" + +import * as React from "react" +import { format } from "date-fns" +import { Calendar as CalendarIcon } from "lucide-react" + +import { cn } from "@/lib/utils" +import { Button } from "@/components/ui/button" +import { Calendar } from "@/components/ui/calendar" +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover" + +export function DatePickerDemo() { + const [date, setDate] = React.useState() + + return ( + + + + + + + + + ) +} +``` + +--- + +### use + +--- + +### examples + +```date-picker-demo.tsx +"use client" + +import * as React from "react" +import { format } from "date-fns" +import { Calendar as CalendarIcon } from "lucide-react" + +import { cn } from "@/lib/utils" +import { Button } from "@/components/ui/button" +import { Calendar } from "@/components/ui/calendar" +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover" + +export default function DatePickerDemo() { + const [date, setDate] = React.useState() + + return ( + + + + + + + + + ) +} +``` + +```date-picker-with-presets.tsx +"use client" + +import * as React from "react" +import { addDays, format } from "date-fns" +import { Calendar as CalendarIcon } from "lucide-react" + +import { cn } from "@/lib/utils" +import { Button } from "@/components/ui/button" +import { Calendar } from "@/components/ui/calendar" +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" + +export default function DatePickerWithPresets() { + const [date, setDate] = React.useState() + + return ( + + + + + + +
+ +
+
+
+ ) +} +``` + +```date-picker-with-range.tsx +"use client" + +import * as React from "react" +import { addDays, format } from "date-fns" +import { Calendar as CalendarIcon } from "lucide-react" +import { DateRange } from "react-day-picker" + +import { cn } from "@/lib/utils" +import { Button } from "@/components/ui/button" +import { Calendar } from "@/components/ui/calendar" +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover" + +export default function DatePickerWithRange({ + className, +}: React.HTMLAttributes) { + const [date, setDate] = React.useState({ + from: new Date(2022, 0, 20), + to: addDays(new Date(2022, 0, 20), 20), + }) + + return ( +
+ + + + + + + + +
+ ) +} +``` diff --git a/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/input_field.mdx b/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/input_field.mdx new file mode 100644 index 0000000..de99201 --- /dev/null +++ b/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/input_field.mdx @@ -0,0 +1,95 @@ +Name : Input +Description : Displays a form input field or a component that looks like an input field. + +--- + +### import + +``` +import { Input } from "@/components/ui/input" +``` + +--- + +### use + +```input.mdx + +``` + +--- + +### examples + +```input-demo.tsx +import { Input } from "@/components/ui/input" + +export default function InputDemo() { + return +} +``` + +```input-disabled.tsx +import { Input } from "@/components/ui/input" + +export default function InputDisabled() { + return +} +``` + +```input-file.tsx +import { Input } from "@/components/ui/input" +import { Label } from "@/components/ui/label" + +export default function InputFile() { + return ( +
+ + +
+ ) +} +``` + +```input-with-button.tsx +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" + +export default function InputWithButton() { + return ( +
+ + +
+ ) +} +``` + +```input-with-label.tsx +import { Input } from "@/components/ui/input" +import { Label } from "@/components/ui/label" + +export default function InputWithLabel() { + return ( +
+ + +
+ ) +} +``` + +```input-with-text.tsx +import { Input } from "@/components/ui/input" +import { Label } from "@/components/ui/label" + +export default function InputWithText() { + return ( +
+ + +

Enter your email address.

+
+ ) +} +``` diff --git a/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/input_label.mdx b/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/input_label.mdx new file mode 100644 index 0000000..f73e44a --- /dev/null +++ b/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/input_label.mdx @@ -0,0 +1,38 @@ +Name : Label +Description : Renders an accessible label associated with controls. + +--- + +### import + +``` +import { Label } from "@/components/ui/label" +``` + +--- + +### use + +```label.mdx + +``` + +--- + +### examples + +```label-demo.tsx +import { Checkbox } from "@/components/ui/checkbox" +import { Label } from "@/components/ui/label" + +export default function LabelDemo() { + return ( +
+
+ + +
+
+ ) +} +``` diff --git a/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/input_radio.mdx b/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/input_radio.mdx new file mode 100644 index 0000000..6e63d24 --- /dev/null +++ b/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/input_radio.mdx @@ -0,0 +1,56 @@ +Name : Radio Group +Description : A set of checkable buttons—known as radio buttons—where no more than one of the buttons can be checked at a time. + +--- + +### import + +``` +import { Label } from "@/components/ui/label" +import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group" +``` + +--- + +### use + +```radio-group.mdx + +
+ + +
+
+ + +
+
+``` + +--- + +### examples + +```radio-group-demo.tsx +import { Label } from "@/components/ui/label" +import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group" + +export default function RadioGroupDemo() { + return ( + +
+ + +
+
+ + +
+
+ + +
+
+ ) +} +``` diff --git a/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/input_searchfield.mdx b/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/input_searchfield.mdx new file mode 100644 index 0000000..de99201 --- /dev/null +++ b/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/input_searchfield.mdx @@ -0,0 +1,95 @@ +Name : Input +Description : Displays a form input field or a component that looks like an input field. + +--- + +### import + +``` +import { Input } from "@/components/ui/input" +``` + +--- + +### use + +```input.mdx + +``` + +--- + +### examples + +```input-demo.tsx +import { Input } from "@/components/ui/input" + +export default function InputDemo() { + return +} +``` + +```input-disabled.tsx +import { Input } from "@/components/ui/input" + +export default function InputDisabled() { + return +} +``` + +```input-file.tsx +import { Input } from "@/components/ui/input" +import { Label } from "@/components/ui/label" + +export default function InputFile() { + return ( +
+ + +
+ ) +} +``` + +```input-with-button.tsx +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" + +export default function InputWithButton() { + return ( +
+ + +
+ ) +} +``` + +```input-with-label.tsx +import { Input } from "@/components/ui/input" +import { Label } from "@/components/ui/label" + +export default function InputWithLabel() { + return ( +
+ + +
+ ) +} +``` + +```input-with-text.tsx +import { Input } from "@/components/ui/input" +import { Label } from "@/components/ui/label" + +export default function InputWithText() { + return ( +
+ + +

Enter your email address.

+
+ ) +} +``` diff --git a/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/input_select.mdx b/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/input_select.mdx new file mode 100644 index 0000000..96b8e7a --- /dev/null +++ b/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/input_select.mdx @@ -0,0 +1,71 @@ +Name : Select +Description : Displays a list of options for the user to pick from—triggered by a button. + +--- + +### import + +``` +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" +``` + +--- + +### use + +```select.mdx + +``` + +--- + +### examples + +```select-demo.tsx +import * as React from "react" + +import { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectLabel, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" + +export default function SelectDemo() { + return ( + + ) +} +``` diff --git a/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/input_switch.mdx b/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/input_switch.mdx new file mode 100644 index 0000000..39b9838 --- /dev/null +++ b/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/input_switch.mdx @@ -0,0 +1,36 @@ +Name : Switch +Description : A control that allows the user to toggle between checked and not checked. + +--- + +### import + +``` +import { Switch } from "@/components/ui/switch" +``` + +--- + +### use + +```switch.mdx + +``` + +--- + +### examples + +```switch-demo.tsx +import { Label } from "@/components/ui/label" +import { Switch } from "@/components/ui/switch" + +export default function SwitchDemo() { + return ( +
+ + +
+ ) +} +``` diff --git a/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/input_textarea.mdx b/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/input_textarea.mdx new file mode 100644 index 0000000..7e77073 --- /dev/null +++ b/cofounder/api/system/presets/ui/design/systems/shadcn/primitives/docs/input_textarea.mdx @@ -0,0 +1,83 @@ +Name : Textarea +Description : Displays a form textarea or a component that looks like a textarea. + +--- + +### import + +``` +import { Textarea } from "@/components/ui/textarea" +``` + +--- + +### use + +```textarea.mdx + + + {editUserText.length ? ( + <> +
+ +
+ + + ) : ( + <> + )} + + )} + + + ) : ( +
{`[...] processing [...]`}
+ )} + +

+ {sectionId} versions +

+ + {versions.map((version) => ( +
handleVersionChange(version)} + onMouseEnter={() => { + setComponent(() => components[version]); + setLayoutPreviewUrl( + `/_cofounder/generated/layouts/sections/${sectionId}.${version}.png`, + ); + }} + onMouseLeave={() => { + setComponent(() => components[choice]); + setLayoutPreviewUrl( + `/_cofounder/generated/layouts/sections/${sectionId}.${choice}.png`, + ); + }} + className="cursor-pointer duration-100 hover:bg-gray-300 p-1 hover:p-2 rounded text-xs" + > +
+ {version === choice ? ( + {version} + ) : ( + {version} + )} +
+
+ ))} + + )} + + )) || <>} + + ); +}; + +export default GenUiSection; diff --git a/cofounder/boilerplate/vitereact-boilerplate/src/_cofounder/genui/genui-view.tsx b/cofounder/boilerplate/vitereact-boilerplate/src/_cofounder/genui/genui-view.tsx new file mode 100644 index 0000000..0f26615 --- /dev/null +++ b/cofounder/boilerplate/vitereact-boilerplate/src/_cofounder/genui/genui-view.tsx @@ -0,0 +1,644 @@ +// @ts-ignore + +import React, { useEffect, useRef, useState } from "react"; +import { ErrorBoundary } from "@/_cofounder/genui/error-boundary"; +import GenUiPlaceholder from "@/_cofounder/genui/genui-placeholder"; +import { useScreenshot } from "use-screenshot-hook"; +import { createFileName } from "use-react-screenshot"; +import { RefreshCcw, PencilRuler } from "lucide-react"; +import meta from "@/_cofounder/meta.json"; + +interface GenUiViewProps { + //component: { [key: string]: any }; + [key: string]: any; +} + +const GenUiView: React.FC = (query) => { + const COFOUNDER_LOCAL_API = `{COFOUNDER_LOCAL_API_BASE_URL}`; + + const viewId = query.viewId; + let _query = { ...query }; + delete _query.viewId; + + const [Component, setComponent] = useState(null); + const [components, setComponents] = useState<{ [key: string]: React.FC }>({}); + const [choice, setChoice] = useState(""); + const [versions, setVersions] = useState([]); + const [versionsWithImportProblems, setVersionsWithImportProblems] = useState< + string[] + >([]); + const [loaded, setLoaded] = useState(false); + const [ready, setReady] = useState(false); + const [newMenu, setNewMenu] = useState(false); + const [editUserText, setEditUserText] = useState(""); + const [editEnableDesigner, setEditEnableDesigner] = useState(true); + + const [inferenceStream, setInferenceStream] = useState(""); + const [processing, setProcessing] = useState(false); + + const [isOpenTooltip, setIsOpenTooltip] = useState(false); + const [isOpenTooltipTab, setIsOpenTooltipTab] = useState(false); + const [layoutPreviewUrl, setLayoutPreviewUrl] = useState(``); + const [layoutPreviewBlob, setLayoutPreviewBlob] = useState(``); + + // _____________________________________________________________________ + const [cmdk, setCmdk] = useState(() => { + // Retrieve the initial state from local storage or default to false + // const savedCmdk = localStorage.getItem("cmdkState"); + // return savedCmdk === "true"; // Convert string to boolean + const savedCmdk = false; + return savedCmdk; + }); + + useEffect(() => { + const down = (e: KeyboardEvent) => { + if (e.key === "k" && (e.metaKey || e.ctrlKey)) { + e.preventDefault(); + setCmdk((prev) => { + const newState = !prev; + // localStorage.setItem("cmdkState", newState.toString()); // Save the new state to local storage + return newState; + }); + } else if (e.key === "Escape") { + setCmdk(false); + // localStorage.setItem("cmdkState", "false"); // Reset state in local storage + } + }; + document.addEventListener("keydown", down); + return () => document.removeEventListener("keydown", down); + }, []); + + const ref = useRef(null); + const tooltipRef = useRef(null); + const tooltipTabRef = useRef(null); + + useEffect(() => { + if (Component) { + _delayed_screenshot(); + } + }, [Component]); + + // _____________________________________________________________________ + + const { image, takeScreenshot } = useScreenshot({ ref }); + const testScreenshot = () => { + takeScreenshot(); + }; + async function _delayed_screenshot() { + await new Promise((resolve) => setTimeout(resolve, 1e3)); + takeScreenshot(); + } + + const download = (image, { name = "img", extension = "png" } = {}) => { + const a = document.createElement("a"); + a.href = image; + a.download = createFileName(extension, name); + a.click(); + }; + useEffect(() => { + if (image) { + // download(image, { name: "lorem-ipsum", extension: "png" }); + } + }, [image]); + + useEffect(() => { + const loadLayoutPreview = async () => { + if (layoutPreviewUrl.length > 0) { + setLayoutPreviewBlob(""); + try { + const layoutPreviewModule = new URL(layoutPreviewUrl, import.meta.url) + .href; + setLayoutPreviewBlob(layoutPreviewModule); + // check after + const response = await fetch(`http://localhost:5173${layoutPreviewUrl}`, { + method: "HEAD", + }); + if ( + !response.ok || + !response.headers.get("content-type")?.includes("image") + ) { + setLayoutPreviewBlob(""); + // console.log("> fetched resource is not an image ; probably empty, skipping preview layout assignment"); + } + } catch (error) { + console.error("no layout preview", error); + } + } + }; + + loadLayoutPreview(); + }, [layoutPreviewUrl]); + + const api_updateVersionPreference = async ({ version }) => { + try { + await fetch(`${COFOUNDER_LOCAL_API}/project/actions`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + project: meta.project, + query: { + action: "update:settings:preferences:versions", + data: { + views: { + [viewId]: `${version}`, + }, + }, + }, + }), + }); + } catch (error) { + console.error({ "genui:callApi:error": error }); + } + }; + + const api_regenerateComponent = async () => { + if (processing) return; + setProcessing(true); + try { + await fetch(`${COFOUNDER_LOCAL_API}/project/actions`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + project: meta.project, + query: { + action: "regenerate:ui", + data: { + views: viewId, + }, + }, + }), + }); + } catch (error) { + console.error({ "genui:callApi:error": error }); + } + setProcessing(false); + }; + + const api_iterateComponent = async () => { + if (processing) return; + setProcessing(true); + try { + await fetch(`${COFOUNDER_LOCAL_API}/project/actions`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + project: meta.project, + query: { + action: "iterate:ui", + data: { + views: { + [viewId]: { + [choice]: { + notes: { + text: editUserText, + attachments: [], // later, can attach extra image dragged into dropzone + }, + screenshot: { + base64: image ? image : false, + }, + designer: editEnableDesigner, + }, + }, + }, + }, + }, + }), + }); + } catch (error) { + console.error({ "genui:callApi:error": error }); + } + setProcessing(false); + }; + + const api_updateComponent = async ({ operation }) => { + return; // <------- debug ; is old method ; update later + if (processing) return; + setProcessing(true); + let _query = { ...query }; + if (operation === `edit`) { + _query.edit = { + version: choice, + iteration: `${editUserText}`, + }; + } + setEditUserText(``); + try { + const response = await fetch(`http://localhost:1337/${operation}`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(_query), + }); + + const reader = response.body.getReader(); + const decoder = new TextDecoder(); + let completion = ``; + while (true) { + const { done, value } = await reader.read(); + if (done) { + break; + } + const chunk = decoder.decode(value); + completion += chunk; + setInferenceStream(completion); + } + } catch (error) { + console.error({ "genui:callApi:error": error }); + } + setProcessing(false); + }; + + const loadComponent = async () => { + if (loaded) return; + setLoaded(true); + + // reset everything + setComponent(null); + setComponents({}); + setChoice(""); + setVersions([]); + setLoaded(false); + setNewMenu(false); + setInferenceStream(""); + setProcessing(false); + + try { + const _meta = await import( + `@/_cofounder/generated/views/${viewId}/meta.json` + ); + const meta = _meta.default; // Access the default export + // console.log({ id, choice: meta.choice, versions: meta.versions }); + + let loadedComponents: { [key: string]: React.FC } = {}; + let problematicVersions: string[] = []; + await Promise.all( + meta.versions + .sort((a, b) => { + if (a === "empty") return -1; + if (b === "empty") return 1; + return a.localeCompare(b); + }) + .reverse() + .map(async (version: string) => { + try { + const { default: LoadedVersionComponent } = await import( + `@/_cofounder/generated/views/${viewId}/${version}.tsx` + ); + loadedComponents[version] = LoadedVersionComponent; + // console.log({ "genui:load:version:success": version }); + } catch (err) { + // console.log({ "genui:load:version:error": { version, err } }); + problematicVersions.push(version); + } + }), + ); + + const workingVersions = meta.versions.filter( + (v) => !problematicVersions.includes(v), + ); + setVersions(workingVersions); + if (!workingVersions.length) { + throw new Error("no working version found"); + } + problematicVersions.map((_v) => { + delete loadedComponents[_v]; + }); + + const _choice = problematicVersions.includes(meta.choice) + ? `${workingVersions[0]}` + : `${meta.choice}`; + + setChoice(_choice); + + setLayoutPreviewUrl( + `/_cofounder/generated/layouts/views/${viewId}.${_choice}.png`, + ); + + /* + console.log({ + viewId: query.viewId, + workingVersions, + problematicVersions, + choice: workingVersions[0], + loadedComponents, + }); + */ + + setComponents(loadedComponents); + setComponent(() => loadedComponents[_choice]); + } catch (e) { + // console.log({ "genui:error": e }); + // await callApi({ operation: `new` }); + /* + reload this current react component right here at this point in some way + */ + setLoaded(false); + } + setReady(true); + }; + + useEffect(() => { + if (loaded) return; + loadComponent(); + }, [viewId, loaded]); + + const _delayed_api_updateVersionPreference = async ({ version }) => { + await new Promise((resolve) => setTimeout(resolve, 500)); + await api_updateVersionPreference({ version }); + }; + useEffect(() => { + // should cascade alongside error-boundary to filter out bad components + if (versionsWithImportProblems.length) { + setVersions((prev) => { + const filteredVersions = prev.filter( + (version) => !versionsWithImportProblems.includes(version), + ); + if ( + filteredVersions.length && + versionsWithImportProblems.includes(choice) + ) { + setComponent(null); + setChoice(""); + const newChoice = filteredVersions[0]; + setChoice(newChoice); + setComponent(() => components[newChoice]); + + _delayed_api_updateVersionPreference({ version: newChoice }); + } else { + setComponent(null); + setChoice(""); + } + return filteredVersions; + }); + } + }, [versionsWithImportProblems]); + + const handleVersionChange = (version: string) => { + console.log(`handleVersionChange : ${viewId} : ${version}`); + if (version != choice) { + setChoice(version); + api_updateVersionPreference({ version }); + setLayoutPreviewUrl( + `/_cofounder/generated/layouts/views/${viewId}.${version}.png`, + ); + } + setComponent(() => components[version]); + }; + + return ( + <> + {processing && ( +
+
+ building {viewId} + ... +
+ { || ``} +
+ )} + {(loaded && ready && !processing && !versions.length && ( +
+
+ no working version for {viewId} ; Try to regenerate ? +
+ +
+ )) || + ""} + {(versions.length && Component && ( +
+ + {(Component && ( +
+
setIsOpenTooltip(true)} + onMouseOut={() => setIsOpenTooltip(false)} + > + +
+
+ )) || <>} +
+ {cmdk && ( +
+

+ {viewId} versions +

+ + {!processing ? ( + <> + + +
setNewMenu(true)} + onMouseLeave={() => setNewMenu(false)} + onMouseOver={() => setIsOpenTooltipTab(true)} + onMouseOut={() => setIsOpenTooltipTab(false)} + onClick={() => setNewMenu(true)} + > + + {newMenu && ( +
+ {image && ( + <> +
+ Attached current view screenshot +
+ Current screenshot of the view + + )} + + + {editUserText.length ? ( + <> +
+ +
+ + + ) : ( + <> + )} +
+ )} +
+ + ) : ( +
{`[...] processing [...]`}
+ )} + + {!processing && + versions.map((version) => ( +
handleVersionChange(version)} + onMouseEnter={() => { + setComponent(() => components[version]); + setLayoutPreviewUrl( + `/_cofounder/generated/layouts/views/${viewId}.${version}.png`, + ); + }} + onMouseLeave={() => { + setComponent(() => components[choice]); + setLayoutPreviewUrl( + `/_cofounder/generated/layouts/views/${viewId}.${choice}.png`, + ); + }} + className="cursor-pointer duration-100 hover:bg-gray-300 p-1 hover:p-2 rounded text-xs" + > +
+ {choice === version ? ( + {version} + ) : ( + {version} + )} +
+
+ ))} + + {(layoutPreviewUrl?.length && layoutPreviewBlob?.length && ( + Reference layout design generated by Cofounder for this ui component version + )) || ( +
+ No reference layout design made for this version +
+ )} +
+ )} +
+ )) || <>} + + ); +}; + +export default GenUiView; diff --git a/cofounder/boilerplate/vitereact-boilerplate/src/_cofounder/genui/genui-view.tsx.bak b/cofounder/boilerplate/vitereact-boilerplate/src/_cofounder/genui/genui-view.tsx.bak new file mode 100644 index 0000000..36e185b --- /dev/null +++ b/cofounder/boilerplate/vitereact-boilerplate/src/_cofounder/genui/genui-view.tsx.bak @@ -0,0 +1,286 @@ +// @ts-ignore + +import React, { useEffect, useRef, useState } from "react"; +import { ErrorBoundary } from "@/_cofounder/genui/error-boundary"; +import GenUiPlaceholder from "@/_cofounder/genui/genui-placeholder"; +import { useScreenshot } from "use-screenshot-hook"; +import { createFileName } from "use-react-screenshot"; +import { RefreshCcw, PencilRuler } from "lucide-react"; +import meta from "@/_cofounder/meta.json"; + +interface GenUiViewProps { + //component: { [key: string]: any }; + [key: string]: any; +} + +const GenUiView: React.FC = (query) => { + const COFOUNDER_LOCAL_API = `{COFOUNDER_LOCAL_API_BASE_URL}`; + + const viewId = query.viewId; + let _query = { ...query }; + delete _query.viewId; + // console.log({ "genui:section": query }); + + const [Component, setComponent] = useState(null); + const [components, setComponents] = useState<{ [key: string]: React.FC }>({}); + const [choice, setChoice] = useState(""); + const [versions, setVersions] = useState([]); + const [versionsWithImportProblems, setVersionsWithImportProblems] = useState< + string[] + >([]); + const [loaded, setLoaded] = useState(false); + const [ready, setReady] = useState(false); + const [newMenu, setNewMenu] = useState(false); + const [editUserText, setEditUserText] = useState(""); + + const [inferenceStream, setInferenceStream] = useState(""); + const [processing, setProcessing] = useState(false); + + const [isOpenTooltip, setIsOpenTooltip] = useState(false); + const [isOpenTooltipTab, setIsOpenTooltipTab] = useState(false); + + // _____________________________________________________________________ + const [cmdk, setCmdk] = useState(() => { + // Retrieve the initial state from local storage or default to false + const savedCmdk = localStorage.getItem("cmdkState"); + return savedCmdk === "true"; // Convert string to boolean + }); + + useEffect(() => { + const down = (e: KeyboardEvent) => { + if (e.key === "k" && (e.metaKey || e.ctrlKey)) { + e.preventDefault(); + setCmdk((prev) => { + const newState = !prev; + localStorage.setItem("cmdkState", newState.toString()); // Save the new state to local storage + return newState; + }); + } else if (e.key === "Escape") { + setCmdk(false); + localStorage.setItem("cmdkState", "false"); // Reset state in local storage + } + }; + document.addEventListener("keydown", down); + return () => document.removeEventListener("keydown", down); + }, []); + + const ref = useRef(null); + const { image, takeScreenshot } = useScreenshot({ ref }); + const testScreenshot = () => { + takeScreenshot(); + }; + + const download = (image, { name = "img", extension = "png" } = {}) => { + const a = document.createElement("a"); + a.href = image; + a.download = createFileName(extension, name); + a.click(); + }; + useEffect(() => { + if (image) { + // console.log({ image }) + download(image, { name: "lorem-ipsum", extension: "png" }); + } + }, [image]); + + const callApi = async ({ operation }) => { + return; // <------- debug ; is old method ; update later + if (processing) return; + setProcessing(true); + let _query = { ...query }; + if (operation === `edit`) { + _query.edit = { + version: choice, + iteration: `${editUserText}`, + }; + } + setEditUserText(``); + try { + const response = await fetch(`http://localhost:1337/${operation}`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(_query), + }); + + const reader = response.body.getReader(); + const decoder = new TextDecoder(); + let completion = ``; + while (true) { + const { done, value } = await reader.read(); + if (done) { + break; + } + const chunk = decoder.decode(value); + completion += chunk; + setInferenceStream(completion); + } + } catch (error) { + console.error({ "genui:callApi:error": error }); + } + setProcessing(false); + }; + + const loadComponent = async () => { + if (loaded) return; + setLoaded(true); + + // reset everything + setComponent(null); + setComponents({}); + setChoice(""); + setVersions([]); + setLoaded(false); + setNewMenu(false); + setInferenceStream(""); + setProcessing(false); + + try { + const _meta = await import( + `@/_cofounder/generated/views/${viewId}/meta.json` + ); + const meta = _meta.default; // Access the default export + // console.log({ id, choice: meta.choice, versions: meta.versions }); + + let loadedComponents: { [key: string]: React.FC } = {}; + let problematicVersions: string[] = []; + + await Promise.all( + meta.versions + .sort() + .reverse() + .map(async (version: string) => { + try { + // try fetch first + const response = await fetch( + `@/_cofounder/generated/views/${viewId}/${version}.tsx`, + ); + console.log({ "debug:genui:view:fetch:response": response }); + if (!response.ok) { + throw new Error(`genui:view:fetchversion:error: ${version}`); + } + const { default: LoadedVersionComponent } = await import( + `@/_cofounder/generated/views/${viewId}/${version}.tsx` + ); + loadedComponents[version] = LoadedVersionComponent; + // console.log({ "genui:load:version:success": version }); + } catch (err) { + console.log({ "genui:view:load:version:error": { version, err } }); + problematicVersions.push(version); + } + }), + ); + + const workingVersions = meta.versions.filter( + (v) => !problematicVersions.includes(v), + ); + setVersions(workingVersions); + + if (!workingVersions.length) { + throw new Error("no working version found"); + } + problematicVersions.map((_v) => { + delete loadedComponents[_v]; + }); + + const _choice = problematicVersions.includes(meta.choice) + ? `${workingVersions[0]}` + : `${meta.choice}`; + + setChoice(_choice); + setComponents(loadedComponents); + setComponent(() => loadedComponents[_choice]); + } catch (e) { + // console.log({ "genui:error": e }); + // await callApi({ operation: `new` }); + /* + reload this current react component right here at this point in some way + */ + setLoaded(false); + } + }; + + useEffect(() => { + if (loaded) return; + loadComponent(); + }, [viewId, loaded]); + + const _delayed_api_updateVersionPreference = async ({ version }) => { + await new Promise((resolve) => setTimeout(resolve, 500)); + await api_updateVersionPreference({ version }); + }; + useEffect(() => { + // should cascade alongside error-boundary to filter out bad components + if (versionsWithImportProblems.length) { + setVersions((prev) => { + const filteredVersions = prev.filter( + (version) => !versionsWithImportProblems.includes(version), + ); + if ( + filteredVersions.length && + versionsWithImportProblems.includes(choice) + ) { + setComponent(null); + setChoice(""); + const newChoice = filteredVersions[0]; + setChoice(newChoice); + setComponent(() => components[newChoice]); + _delayed_api_updateVersionPreference({ version: newChoice }); + } else { + setComponent(null); + setChoice(""); + } + return filteredVersions; + }); + } + }, [versionsWithImportProblems]); + + const api_updateVersionPreference = async ({ version }) => { + true; // do this next + }; + + const handleVersionChange = (version: string) => { + console.log(`handleVersionChange : ${viewId} : ${version}`); + setChoice(version); + setComponent(() => components[version]); + }; + + return ( + <> + {cmdk && ( +
+ {viewId} : {JSON.stringify({ versions })} :{" "} + {JSON.stringify({ versionsWithImportProblems })} :{" "} + {JSON.stringify({ choice })} +
+
+ )} + + {(versions.length && Component && !processing && ( + <> + + {Component && ( +
+ +
+ )} +
+ + )) || <>} + + {cmdk && !versions.length && !processing && ( +
+ no working version for {viewId} ; Try to regenerate ? +
+ )} + + ); +}; + +export default GenUiView; diff --git a/cofounder/boilerplate/vitereact-boilerplate/src/_cofounder/genui/tooltip.tsx b/cofounder/boilerplate/vitereact-boilerplate/src/_cofounder/genui/tooltip.tsx new file mode 100644 index 0000000..16523d0 --- /dev/null +++ b/cofounder/boilerplate/vitereact-boilerplate/src/_cofounder/genui/tooltip.tsx @@ -0,0 +1,73 @@ +import React, { useState, useRef, useEffect } from "react"; + +const Tooltip = () => { + const [isOpen, setIsOpen] = useState(false); + const tooltipRef = useRef(null); + const dropdownRef = useRef(null); + + const handleDropdownPosition = () => { + const screenPadding = 16; + const placeholderRect = tooltipRef.current?.getBoundingClientRect(); + const dropdownRect = dropdownRef.current?.getBoundingClientRect(); + + if (!placeholderRect || !dropdownRect) return; + + const dropdownRightX = dropdownRect.x + dropdownRect.width; + const placeholderRightX = placeholderRect.x + placeholderRect.width; + + if (dropdownRect.x < 0) { + dropdownRef.current.style.left = "0"; + dropdownRef.current.style.right = "auto"; + dropdownRef.current.style.transform = `translateX(${-placeholderRect.x + screenPadding}px)`; + } else if (dropdownRightX > window.outerWidth) { + dropdownRef.current.style.left = "auto"; + dropdownRef.current.style.right = "0"; + dropdownRef.current.style.transform = `translateX(${window.outerWidth - placeholderRightX - screenPadding}px)`; + } + }; + + const toggleTooltip = () => { + setIsOpen(!isOpen); + }; + + useEffect(() => { + if (isOpen) { + handleDropdownPosition(); + } + }, [isOpen]); + + return ( +
+
setIsOpen(true)} + onMouseOut={() => setIsOpen(false)} + onTouchStart={toggleTooltip} + > + Here is a tooltip label +
+ {isOpen && ( +
+ +
+ )} +
+ ); +}; + +export default Tooltip; diff --git a/cofounder/boilerplate/vitereact-boilerplate/src/_cofounder/meta.json b/cofounder/boilerplate/vitereact-boilerplate/src/_cofounder/meta.json new file mode 100644 index 0000000..6d076bf --- /dev/null +++ b/cofounder/boilerplate/vitereact-boilerplate/src/_cofounder/meta.json @@ -0,0 +1 @@ +{ "project": "foundermatch" } diff --git a/cofounder/boilerplate/vitereact-boilerplate/src/_cofounder/utils.js b/cofounder/boilerplate/vitereact-boilerplate/src/_cofounder/utils.js new file mode 100644 index 0000000..e69de29 diff --git a/cofounder/boilerplate/vitereact-boilerplate/src/_cofounder/vite-plugin/index.js b/cofounder/boilerplate/vitereact-boilerplate/src/_cofounder/vite-plugin/index.js new file mode 100644 index 0000000..d9dcedd --- /dev/null +++ b/cofounder/boilerplate/vitereact-boilerplate/src/_cofounder/vite-plugin/index.js @@ -0,0 +1,137 @@ +async function editSectionsAndViews({ path, code }) { + // console.dir({ "_confounder:vite-plugin:editSectionsAndViews": true }); + const genUi = { + sections: false, + views: false, + }; + let newTsx = code + .split(`\n`) + .filter((line) => { + if (line.includes(`@/components/sections/`)) { + if (!genUi.sections) genUi.sections = []; + const sectionId = line.split(` `)[1]; + genUi.sections = [...new Set([...genUi.sections, sectionId])]; + return false; + } + + if (line.includes(`@/components/views/`)) { + if (!genUi.views) genUi.views = []; + const viewId = line.split(` `)[1]; + genUi.views = [...new Set([...genUi.views, viewId])]; + return false; + } + + return true; + }) + .join(`\n`); + if (genUi.sections) { + newTsx = `import GenUiSection from '@/_cofounder/genui/genui-section';\n${newTsx}`; + for (let sectionId of genUi.sections) { + newTsx = newTsx.replaceAll( + `<${sectionId}`, + ` { + if (line.includes(`@/components/views/`)) { + if (!genUi.views) genUi.views = []; + const viewId = line.split(` `)[1]; + genUi.views = [...new Set([...genUi.views, viewId])]; + return false; + } + return true; + }) + .join(`\n`); + if (genUi.views) { + newTsx = `import GenUiView from '@/_cofounder/genui/genui-view';\n${newTsx}`; + for (let viewId of genUi.views) { + newTsx = newTsx.replaceAll(`<${viewId}`, ` { + if (line.includes(`@/components/sections/`)) { + if (!genUi.sections) genUi.sections = []; + const sectionId = line.split(` `)[1]; + genUi.sections = [...new Set([...genUi.sections, sectionId])]; + return false; + } + return true; + }) + .join(`\n`); + if (genUi.sections) { + newTsx = `import GenUiSection from '@/_cofounder/genui/genui-section';\n${newTsx}`; + for (let sectionId of genUi.sections) { + newTsx = newTsx.replaceAll( + `<${sectionId}`, + ` GenUiView will call : @/_cofounder/generated/views/ + > @/_cofounder/generated/views/* will call GenUiSection + > GenUiSection will call @/_cofounder/generated/sections/ + */ + code = code.replaceAll( + `{COFOUNDER_LOCAL_API_BASE_URL}`, + `http://localhost:667`, + ); + if (path.includes(`src/App.tsx`)) { + return await editSectionsAndViews({ path, code }); + } + if ( + path.includes(`_cofounder/generated/views`) || + path.includes(`_cofounder/generated/sections`) + ) { + return await editSectionsAndViews({ path, code }); + } + /* + if ( + path.includes(`src/App.tsx`) + || path.includes(`src/components/views/`) + || path.includes(`src/components/sections/`) + || path.includes(`@/_cofounder/generated/views/`) + || path.includes(`@/_cofounder/generated/sections/`) + ) { + return await editSectionsAndViews({ path , code }); + } + */ + return code; + }, +}; diff --git a/cofounder/boilerplate/vitereact-boilerplate/src/assets/cofounder.webp b/cofounder/boilerplate/vitereact-boilerplate/src/assets/cofounder.webp new file mode 100644 index 0000000..dbb11d4 Binary files /dev/null and b/cofounder/boilerplate/vitereact-boilerplate/src/assets/cofounder.webp differ diff --git a/cofounder/boilerplate/vitereact-boilerplate/src/components/ui/accordion.tsx b/cofounder/boilerplate/vitereact-boilerplate/src/components/ui/accordion.tsx new file mode 100644 index 0000000..e6cae02 --- /dev/null +++ b/cofounder/boilerplate/vitereact-boilerplate/src/components/ui/accordion.tsx @@ -0,0 +1,56 @@ +import * as React from "react"; +import * as AccordionPrimitive from "@radix-ui/react-accordion"; +import { ChevronDown } from "lucide-react"; + +import { cn } from "@/lib/utils"; + +const Accordion = AccordionPrimitive.Root; + +const AccordionItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AccordionItem.displayName = "AccordionItem"; + +const AccordionTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + svg]:rotate-180", + className, + )} + {...props} + > + {children} + + + +)); +AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName; + +const AccordionContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + +
{children}
+
+)); + +AccordionContent.displayName = AccordionPrimitive.Content.displayName; + +export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }; diff --git a/cofounder/boilerplate/vitereact-boilerplate/src/components/ui/alert-dialog.tsx b/cofounder/boilerplate/vitereact-boilerplate/src/components/ui/alert-dialog.tsx new file mode 100644 index 0000000..8715b9c --- /dev/null +++ b/cofounder/boilerplate/vitereact-boilerplate/src/components/ui/alert-dialog.tsx @@ -0,0 +1,136 @@ +import * as React from "react"; +import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"; + +import { cn } from "@/lib/utils"; +import { buttonVariants } from "@/components/ui/button"; + +const AlertDialog = AlertDialogPrimitive.Root; + +const AlertDialogTrigger = AlertDialogPrimitive.Trigger; + +const AlertDialogPortal = AlertDialogPrimitive.Portal; + +const AlertDialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName; + +const AlertDialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + +)); +AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName; + +const AlertDialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +AlertDialogHeader.displayName = "AlertDialogHeader"; + +const AlertDialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +AlertDialogFooter.displayName = "AlertDialogFooter"; + +const AlertDialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName; + +const AlertDialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogDescription.displayName = + AlertDialogPrimitive.Description.displayName; + +const AlertDialogAction = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName; + +const AlertDialogCancel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName; + +export { + AlertDialog, + AlertDialogPortal, + AlertDialogOverlay, + AlertDialogTrigger, + AlertDialogContent, + AlertDialogHeader, + AlertDialogFooter, + AlertDialogTitle, + AlertDialogDescription, + AlertDialogAction, + AlertDialogCancel, +}; diff --git a/cofounder/boilerplate/vitereact-boilerplate/src/components/ui/alert.tsx b/cofounder/boilerplate/vitereact-boilerplate/src/components/ui/alert.tsx new file mode 100644 index 0000000..a0bd5c0 --- /dev/null +++ b/cofounder/boilerplate/vitereact-boilerplate/src/components/ui/alert.tsx @@ -0,0 +1,60 @@ +import * as React from "react"; +import { cva, type VariantProps } from "class-variance-authority"; + +import { cn } from "@/lib/utils"; + +const alertVariants = cva( + "relative w-full rounded-lg border border-neutral-200 p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-neutral-950 dark:border-neutral-800 dark:[&>svg]:text-neutral-50", + { + variants: { + variant: { + default: + "bg-white text-neutral-950 dark:bg-neutral-950 dark:text-neutral-50", + destructive: + "border-red-500/50 text-red-500 dark:border-red-500 [&>svg]:text-red-500 dark:border-red-900/50 dark:text-red-900 dark:dark:border-red-900 dark:[&>svg]:text-red-900", + }, + }, + defaultVariants: { + variant: "default", + }, + }, +); + +const Alert = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes & VariantProps +>(({ className, variant, ...props }, ref) => ( +
+)); +Alert.displayName = "Alert"; + +const AlertTitle = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +AlertTitle.displayName = "AlertTitle"; + +const AlertDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +AlertDescription.displayName = "AlertDescription"; + +export { Alert, AlertTitle, AlertDescription }; diff --git a/cofounder/boilerplate/vitereact-boilerplate/src/components/ui/aspect-ratio.tsx b/cofounder/boilerplate/vitereact-boilerplate/src/components/ui/aspect-ratio.tsx new file mode 100644 index 0000000..c9e6f4b --- /dev/null +++ b/cofounder/boilerplate/vitereact-boilerplate/src/components/ui/aspect-ratio.tsx @@ -0,0 +1,5 @@ +import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"; + +const AspectRatio = AspectRatioPrimitive.Root; + +export { AspectRatio }; diff --git a/cofounder/boilerplate/vitereact-boilerplate/src/components/ui/avatar.tsx b/cofounder/boilerplate/vitereact-boilerplate/src/components/ui/avatar.tsx new file mode 100644 index 0000000..802d5bc --- /dev/null +++ b/cofounder/boilerplate/vitereact-boilerplate/src/components/ui/avatar.tsx @@ -0,0 +1,48 @@ +import * as React from "react"; +import * as AvatarPrimitive from "@radix-ui/react-avatar"; + +import { cn } from "@/lib/utils"; + +const Avatar = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +Avatar.displayName = AvatarPrimitive.Root.displayName; + +const AvatarImage = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AvatarImage.displayName = AvatarPrimitive.Image.displayName; + +const AvatarFallback = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName; + +export { Avatar, AvatarImage, AvatarFallback }; diff --git a/cofounder/boilerplate/vitereact-boilerplate/src/components/ui/badge.tsx b/cofounder/boilerplate/vitereact-boilerplate/src/components/ui/badge.tsx new file mode 100644 index 0000000..86576be --- /dev/null +++ b/cofounder/boilerplate/vitereact-boilerplate/src/components/ui/badge.tsx @@ -0,0 +1,36 @@ +import * as React from "react"; +import { cva, type VariantProps } from "class-variance-authority"; + +import { cn } from "@/lib/utils"; + +const badgeVariants = cva( + "inline-flex items-center rounded-full border border-neutral-200 px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-neutral-950 focus:ring-offset-2 dark:border-neutral-800 dark:focus:ring-neutral-300", + { + variants: { + variant: { + default: + "border-transparent bg-neutral-900 text-neutral-50 hover:bg-neutral-900/80 dark:bg-neutral-50 dark:text-neutral-900 dark:hover:bg-neutral-50/80", + secondary: + "border-transparent bg-neutral-100 text-neutral-900 hover:bg-neutral-100/80 dark:bg-neutral-800 dark:text-neutral-50 dark:hover:bg-neutral-800/80", + destructive: + "border-transparent bg-red-500 text-neutral-50 hover:bg-red-500/80 dark:bg-red-900 dark:text-neutral-50 dark:hover:bg-red-900/80", + outline: "text-neutral-950 dark:text-neutral-50", + }, + }, + defaultVariants: { + variant: "default", + }, + }, +); + +export interface BadgeProps + extends React.HTMLAttributes, + VariantProps {} + +function Badge({ className, variant, ...props }: BadgeProps) { + return ( +
+ ); +} + +export { Badge, badgeVariants }; diff --git a/cofounder/boilerplate/vitereact-boilerplate/src/components/ui/breadcrumb.tsx b/cofounder/boilerplate/vitereact-boilerplate/src/components/ui/breadcrumb.tsx new file mode 100644 index 0000000..763ec37 --- /dev/null +++ b/cofounder/boilerplate/vitereact-boilerplate/src/components/ui/breadcrumb.tsx @@ -0,0 +1,118 @@ +import * as React from "react"; +import { Slot } from "@radix-ui/react-slot"; +import { ChevronRight, MoreHorizontal } from "lucide-react"; + +import { cn } from "@/lib/utils"; + +const Breadcrumb = React.forwardRef< + HTMLElement, + React.ComponentPropsWithoutRef<"nav"> & { + separator?: React.ReactNode; + } +>(({ ...props }, ref) =>