hello world
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
node_modules
|
||||
21
LICENSE
Normal file
@@ -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.
|
||||
149
README.md
Normal file
@@ -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
|
||||
49
TODO.md
Normal file
@@ -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
|
||||
8
apps/README.md
Normal file
@@ -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
|
||||
```
|
||||
34
cofounder/api/.env
Normal file
@@ -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"
|
||||
3
cofounder/api/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
node_modules/
|
||||
db/
|
||||
dump/
|
||||
3
cofounder/api/.prettierignore
Normal file
@@ -0,0 +1,3 @@
|
||||
db/
|
||||
dump/
|
||||
node_modules/
|
||||
4
cofounder/api/.prettierrc
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"tabWidth": 1,
|
||||
"useTabs": true
|
||||
}
|
||||
0
cofounder/api/README.md
Normal file
348
cofounder/api/build.js
Normal file
@@ -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,
|
||||
};
|
||||
42
cofounder/api/package.json
Normal file
@@ -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"
|
||||
}
|
||||
}
|
||||
338
cofounder/api/server.js
Normal file
@@ -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,
|
||||
},
|
||||
});
|
||||
}
|
||||
171
cofounder/api/system/functions/backend/asyncapi.js
Normal file
@@ -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,
|
||||
};
|
||||
176
cofounder/api/system/functions/backend/openapi.js
Normal file
@@ -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 <token>\` 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,
|
||||
};
|
||||
387
cofounder/api/system/functions/backend/server.js
Normal file
@@ -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,
|
||||
};
|
||||
1009
cofounder/api/system/functions/designer/layoutv1.js
Normal file
16
cofounder/api/system/functions/op/convert.js
Normal file
@@ -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,
|
||||
};
|
||||
81
cofounder/api/system/functions/op/indexdb.js
Normal file
@@ -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,
|
||||
};
|
||||
122
cofounder/api/system/functions/op/llm.js
Normal file
@@ -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,
|
||||
};
|
||||
739
cofounder/api/system/functions/op/project.js
Normal file
@@ -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<any> = (props) => {
|
||||
return (
|
||||
<div className="bg-[#eee] text-black text-lg p-4 m-2 rounded">
|
||||
<strong>{{ID}}</strong> placeholder
|
||||
<br /><div className="m-2 text-base p-4 bg-[#222] rounded text-white">
|
||||
To browse other versions<br/>
|
||||
Use ⌘+K / CMD+K and hover here
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
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,
|
||||
};
|
||||
18
cofounder/api/system/functions/op/render.js
Normal file
@@ -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,
|
||||
};
|
||||
345
cofounder/api/system/functions/pm/brd.js
Normal file
@@ -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,
|
||||
};
|
||||
139
cofounder/api/system/functions/pm/drd.js
Normal file
@@ -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,
|
||||
};
|
||||
149
cofounder/api/system/functions/pm/fjmd.js
Normal file
@@ -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,
|
||||
};
|
||||
117
cofounder/api/system/functions/pm/frd.js
Normal file
@@ -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,
|
||||
};
|
||||
127
cofounder/api/system/functions/pm/prd.js
Normal file
@@ -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,
|
||||
};
|
||||
221
cofounder/api/system/functions/pm/uxdmd.js
Normal file
@@ -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,
|
||||
};
|
||||
159
cofounder/api/system/functions/pm/uxsmd.js
Normal file
@@ -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,
|
||||
};
|
||||
365
cofounder/api/system/functions/swarm/augment.js
Normal file
@@ -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,
|
||||
};
|
||||
10
cofounder/api/system/functions/swarm/fix.js
Normal file
@@ -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,
|
||||
};
|
||||
20
cofounder/api/system/functions/swarm/review.js
Normal file
@@ -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,
|
||||
};
|
||||
493
cofounder/api/system/functions/ux/datamap.js
Normal file
@@ -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,
|
||||
};
|
||||
238
cofounder/api/system/functions/ux/sitemap.js
Normal file
@@ -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,
|
||||
};
|
||||
264
cofounder/api/system/functions/webapp/root.js
Normal file
@@ -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 (
|
||||
<>
|
||||
<GV_NavTop />
|
||||
|
||||
<Routes>
|
||||
<Route path="/" element={<UV_ExampleLanding />} />
|
||||
<Route path="/find/:slugexample" element={<UV_OtherViewExample/>} />
|
||||
</Routes>
|
||||
|
||||
<GV_Footer />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
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 : \`<Provider store={store}> <App /> </Provider>\` )
|
||||
> 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 \`<Provider store={store}> <App/> </Provider>\` , where the <App/> 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";
|
||||
[...]
|
||||
<Routes>
|
||||
<Route path="/" element={<UV_ExampleLanding />} />
|
||||
<Route path="/find/:slugexample" element={<UV_OtherViewExample/>} />
|
||||
</Routes>
|
||||
[...]
|
||||
\`\`\`
|
||||
---
|
||||
|
||||
> 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,
|
||||
};
|
||||
291
cofounder/api/system/functions/webapp/store.js
Normal file
@@ -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 \`<Provider store={store}> <App/> </Provider>\` , 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,
|
||||
};
|
||||
1477
cofounder/api/system/functions/webapp/view.js
Normal file
@@ -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
|
||||
0
cofounder/api/system/presets/ui/design/systems/protoboy-v1/primitives/.gitignore
vendored
Normal file
|
After Width: | Height: | Size: 227 KiB |
@@ -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
|
||||
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 67 KiB |
|
After Width: | Height: | Size: 8.0 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 9.4 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 801 B |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 264 KiB |
|
After Width: | Height: | Size: 7.2 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 89 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 889 B |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 45 KiB |
0
cofounder/api/system/presets/ui/design/systems/shadcn/primitives/.gitignore
vendored
Normal file
@@ -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
|
||||
<Accordion type="single" collapsible>
|
||||
<AccordionItem value="item-1">
|
||||
<AccordionTrigger>Is it accessible?</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Yes. It adheres to the WAI-ARIA design pattern.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### examples
|
||||
|
||||
```accordion-demo.tsx
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from "@/components/ui/accordion"
|
||||
|
||||
export default function AccordionDemo() {
|
||||
return (
|
||||
<Accordion type="single" collapsible className="w-full">
|
||||
<AccordionItem value="item-1">
|
||||
<AccordionTrigger>Is it accessible?</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Yes. It adheres to the WAI-ARIA design pattern.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="item-2">
|
||||
<AccordionTrigger>Is it styled?</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Yes. It comes with default styles that matches the other
|
||||
components' aesthetic.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="item-3">
|
||||
<AccordionTrigger>Is it animated?</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Yes. It's animated by default, but you can disable it if you
|
||||
prefer.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
)
|
||||
}
|
||||
```
|
||||
@@ -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
|
||||
<Avatar>
|
||||
<AvatarImage src="https://github.com/shadcn.png" />
|
||||
<AvatarFallback>CN</AvatarFallback>
|
||||
</Avatar>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### examples
|
||||
|
||||
```avatar-demo.tsx
|
||||
import {
|
||||
Avatar,
|
||||
AvatarFallback,
|
||||
AvatarImage,
|
||||
} from "@/components/ui/avatar"
|
||||
|
||||
export default function AvatarDemo() {
|
||||
return (
|
||||
<Avatar>
|
||||
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||
<AvatarFallback>CN</AvatarFallback>
|
||||
</Avatar>
|
||||
)
|
||||
}
|
||||
```
|
||||
@@ -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 variant="outline">Badge</Badge>
|
||||
```
|
||||
|
||||
```badge.mdx
|
||||
import { badgeVariants } from "@/components/ui/badge"
|
||||
<Link className={badgeVariants({ variant: "outline" })}>Badge</Link>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### examples
|
||||
|
||||
```badge-demo.tsx
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
|
||||
export default function BadgeDemo() {
|
||||
return <Badge>Badge</Badge>
|
||||
}
|
||||
```
|
||||
|
||||
```badge-destructive.tsx
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
|
||||
export default function BadgeDestructive() {
|
||||
return <Badge variant="destructive">Destructive</Badge>
|
||||
}
|
||||
```
|
||||
|
||||
```badge-outline.tsx
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
|
||||
export default function BadgeOutline() {
|
||||
return <Badge variant="outline">Outline</Badge>
|
||||
}
|
||||
```
|
||||
|
||||
```badge-secondary.tsx
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
|
||||
export default function BadgeSecondary() {
|
||||
return <Badge variant="secondary">Secondary</Badge>
|
||||
}
|
||||
```
|
||||
@@ -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
|
||||
<Progress value={33} />
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 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 <Progress value={progress} className="w-[60%]" />
|
||||
}
|
||||
```
|
||||
@@ -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
|
||||
<Slider defaultValue={[33]} max={100} step={1} />
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### examples
|
||||
|
||||
```slider-demo.tsx
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Slider } from "@/components/ui/slider"
|
||||
|
||||
type SliderProps = React.ComponentProps<typeof Slider>
|
||||
|
||||
export default function SliderDemo({ className, ...props }: SliderProps) {
|
||||
return (
|
||||
<Slider
|
||||
defaultValue={[50]}
|
||||
max={100}
|
||||
step={1}
|
||||
className={cn("w-[60%]", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
```
|
||||
@@ -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
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink href="/">Home</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink href="/components">Components</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage>Breadcrumb</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Custom separator
|
||||
|
||||
Use a custom component as `children` for `<BreadcrumbSeparator />` to create a custom separator.
|
||||
|
||||
```tsx
|
||||
import { Slash } from "lucide-react"
|
||||
|
||||
...
|
||||
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink href="/">Home</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator>
|
||||
<Slash />
|
||||
</BreadcrumbSeparator>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink href="/components">Components</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
```
|
||||
@@ -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 variant="outline">Button</Button>
|
||||
```
|
||||
|
||||
```button.mdx
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Button } from "@/components/ui/button"
|
||||
...
|
||||
<Link to="/">
|
||||
<Button variant="outline">Button</Button>
|
||||
</Link>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### examples
|
||||
|
||||
```button-demo.tsx
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
export default function ButtonDemo() {
|
||||
return <Button>Button</Button>
|
||||
}
|
||||
```
|
||||
|
||||
```button-destructive.tsx
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
export default function ButtonDestructive() {
|
||||
return <Button variant="destructive">Destructive</Button>
|
||||
}
|
||||
```
|
||||
|
||||
```button-ghost.tsx
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
export default function ButtonGhost() {
|
||||
return <Button variant="ghost">Ghost</Button>
|
||||
}
|
||||
```
|
||||
|
||||
```button-icon.tsx
|
||||
import { ChevronRight } from "lucide-react"
|
||||
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
export default function ButtonIcon() {
|
||||
return (
|
||||
<Button variant="outline" size="icon">
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```button-link.tsx
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
export default function ButtonLink() {
|
||||
return <Button variant="link">Link</Button>
|
||||
}
|
||||
```
|
||||
|
||||
```button-loading.tsx
|
||||
import { Loader2 } from "lucide-react"
|
||||
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
export default function ButtonLoading() {
|
||||
return (
|
||||
<Button disabled>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
Please wait
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```button-outline.tsx
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
export default function ButtonOutline() {
|
||||
return <Button variant="outline">Outline</Button>
|
||||
}
|
||||
```
|
||||
|
||||
```button-secondary.tsx
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
export default function ButtonSecondary() {
|
||||
return <Button variant="secondary">Secondary</Button>
|
||||
}
|
||||
```
|
||||
|
||||
```button-with-icon.tsx
|
||||
import { Mail } from "lucide-react"
|
||||
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
export default function ButtonWithIcon() {
|
||||
return (
|
||||
<Button>
|
||||
<Mail className="mr-2 h-4 w-4" /> Login with Email
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
```
|
||||
@@ -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 variant="outline">Button</Button>
|
||||
```
|
||||
|
||||
```button.mdx
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Button } from "@/components/ui/button"
|
||||
...
|
||||
<Link to="/">
|
||||
<Button variant="outline">Button</Button>
|
||||
</Link>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### examples
|
||||
|
||||
```button-demo.tsx
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
export default function ButtonDemo() {
|
||||
return <Button>Button</Button>
|
||||
}
|
||||
```
|
||||
|
||||
```button-destructive.tsx
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
export default function ButtonDestructive() {
|
||||
return <Button variant="destructive">Destructive</Button>
|
||||
}
|
||||
```
|
||||
|
||||
```button-ghost.tsx
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
export default function ButtonGhost() {
|
||||
return <Button variant="ghost">Ghost</Button>
|
||||
}
|
||||
```
|
||||
|
||||
```button-icon.tsx
|
||||
import { ChevronRight } from "lucide-react"
|
||||
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
export default function ButtonIcon() {
|
||||
return (
|
||||
<Button variant="outline" size="icon">
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```button-link.tsx
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
export default function ButtonLink() {
|
||||
return <Button variant="link">Link</Button>
|
||||
}
|
||||
```
|
||||
|
||||
```button-loading.tsx
|
||||
import { Loader2 } from "lucide-react"
|
||||
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
export default function ButtonLoading() {
|
||||
return (
|
||||
<Button disabled>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
Please wait
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```button-outline.tsx
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
export default function ButtonOutline() {
|
||||
return <Button variant="outline">Outline</Button>
|
||||
}
|
||||
```
|
||||
|
||||
```button-secondary.tsx
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
export default function ButtonSecondary() {
|
||||
return <Button variant="secondary">Secondary</Button>
|
||||
}
|
||||
```
|
||||
|
||||
```button-with-icon.tsx
|
||||
import { Mail } from "lucide-react"
|
||||
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
export default function ButtonWithIcon() {
|
||||
return (
|
||||
<Button>
|
||||
<Mail className="mr-2 h-4 w-4" /> Login with Email
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
```
|
||||
@@ -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 variant="outline">Button</Button>
|
||||
```
|
||||
|
||||
```button.mdx
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Button } from "@/components/ui/button"
|
||||
...
|
||||
<Link to="/">
|
||||
<Button variant="outline">Button</Button>
|
||||
</Link>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### examples
|
||||
|
||||
```button-demo.tsx
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
export default function ButtonDemo() {
|
||||
return <Button>Button</Button>
|
||||
}
|
||||
```
|
||||
|
||||
```button-destructive.tsx
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
export default function ButtonDestructive() {
|
||||
return <Button variant="destructive">Destructive</Button>
|
||||
}
|
||||
```
|
||||
|
||||
```button-ghost.tsx
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
export default function ButtonGhost() {
|
||||
return <Button variant="ghost">Ghost</Button>
|
||||
}
|
||||
```
|
||||
|
||||
```button-icon.tsx
|
||||
import { ChevronRight } from "lucide-react"
|
||||
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
export default function ButtonIcon() {
|
||||
return (
|
||||
<Button variant="outline" size="icon">
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```button-link.tsx
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
export default function ButtonLink() {
|
||||
return <Button variant="link">Link</Button>
|
||||
}
|
||||
```
|
||||
|
||||
```button-loading.tsx
|
||||
import { Loader2 } from "lucide-react"
|
||||
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
export default function ButtonLoading() {
|
||||
return (
|
||||
<Button disabled>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
Please wait
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```button-outline.tsx
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
export default function ButtonOutline() {
|
||||
return <Button variant="outline">Outline</Button>
|
||||
}
|
||||
```
|
||||
|
||||
```button-secondary.tsx
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
export default function ButtonSecondary() {
|
||||
return <Button variant="secondary">Secondary</Button>
|
||||
}
|
||||
```
|
||||
|
||||
```button-with-icon.tsx
|
||||
import { Mail } from "lucide-react"
|
||||
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
export default function ButtonWithIcon() {
|
||||
return (
|
||||
<Button>
|
||||
<Mail className="mr-2 h-4 w-4" /> Login with Email
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
```
|
||||
@@ -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 variant="outline">Button</Button>
|
||||
```
|
||||
|
||||
```button.mdx
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Button } from "@/components/ui/button"
|
||||
...
|
||||
<Link to="/">
|
||||
<Button variant="outline">Button</Button>
|
||||
</Link>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### examples
|
||||
|
||||
```button-demo.tsx
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
export default function ButtonDemo() {
|
||||
return <Button>Button</Button>
|
||||
}
|
||||
```
|
||||
|
||||
```button-destructive.tsx
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
export default function ButtonDestructive() {
|
||||
return <Button variant="destructive">Destructive</Button>
|
||||
}
|
||||
```
|
||||
|
||||
```button-ghost.tsx
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
export default function ButtonGhost() {
|
||||
return <Button variant="ghost">Ghost</Button>
|
||||
}
|
||||
```
|
||||
|
||||
```button-icon.tsx
|
||||
import { ChevronRight } from "lucide-react"
|
||||
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
export default function ButtonIcon() {
|
||||
return (
|
||||
<Button variant="outline" size="icon">
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```button-link.tsx
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
export default function ButtonLink() {
|
||||
return <Button variant="link">Link</Button>
|
||||
}
|
||||
```
|
||||
|
||||
```button-loading.tsx
|
||||
import { Loader2 } from "lucide-react"
|
||||
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
export default function ButtonLoading() {
|
||||
return (
|
||||
<Button disabled>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
Please wait
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```button-outline.tsx
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
export default function ButtonOutline() {
|
||||
return <Button variant="outline">Outline</Button>
|
||||
}
|
||||
```
|
||||
|
||||
```button-secondary.tsx
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
export default function ButtonSecondary() {
|
||||
return <Button variant="secondary">Secondary</Button>
|
||||
}
|
||||
```
|
||||
|
||||
```button-with-icon.tsx
|
||||
import { Mail } from "lucide-react"
|
||||
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
export default function ButtonWithIcon() {
|
||||
return (
|
||||
<Button>
|
||||
<Mail className="mr-2 h-4 w-4" /> Login with Email
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
```
|
||||
@@ -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
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={date}
|
||||
onSelect={setDate}
|
||||
className="rounded-md border"
|
||||
/>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 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<Date | undefined>(new Date())
|
||||
|
||||
return (
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={date}
|
||||
onSelect={setDate}
|
||||
className="rounded-md border"
|
||||
/>
|
||||
)
|
||||
}
|
||||
```
|
||||
@@ -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
|
||||
<Carousel>
|
||||
<CarouselContent>
|
||||
<CarouselItem>...</CarouselItem>
|
||||
<CarouselItem>...</CarouselItem>
|
||||
<CarouselItem>...</CarouselItem>
|
||||
</CarouselContent>
|
||||
<CarouselPrevious />
|
||||
<CarouselNext />
|
||||
</Carousel>
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Sizes
|
||||
|
||||
To set the size of the items, you can use the `basis` utility class on the `<CarouselItem />`.
|
||||
|
||||
```tsx title="Example" showLineNumbers {4-6}
|
||||
// 33% of the carousel width.
|
||||
<Carousel>
|
||||
<CarouselContent>
|
||||
<CarouselItem className="basis-1/3">...</CarouselItem>
|
||||
<CarouselItem className="basis-1/3">...</CarouselItem>
|
||||
<CarouselItem className="basis-1/3">...</CarouselItem>
|
||||
</CarouselContent>
|
||||
</Carousel>
|
||||
```
|
||||
|
||||
```tsx title="Responsive" showLineNumbers {4-6}
|
||||
// 50% on small screens and 33% on larger screens.
|
||||
<Carousel>
|
||||
<CarouselContent>
|
||||
<CarouselItem className="md:basis-1/2 lg:basis-1/3">...</CarouselItem>
|
||||
<CarouselItem className="md:basis-1/2 lg:basis-1/3">...</CarouselItem>
|
||||
<CarouselItem className="md:basis-1/2 lg:basis-1/3">...</CarouselItem>
|
||||
</CarouselContent>
|
||||
</Carousel>
|
||||
```
|
||||
|
||||
### Spacing
|
||||
|
||||
To set the spacing between the items, we use a `pl-[VALUE]` utility on the `<CarouselItem />` and a negative `-ml-[VALUE]` on the `<CarouselContent />`.
|
||||
|
||||
<Callout className="mt-6">
|
||||
**Why:** I tried to use the `gap` property or a `grid` layout on the `
|
||||
<CarouselContent />` 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.
|
||||
|
||||
</Callout>
|
||||
|
||||
<ComponentPreview
|
||||
name="carousel-spacing"
|
||||
title="Carousel"
|
||||
description="A carousel with 3 items with a spacing of 1rem."
|
||||
/>
|
||||
|
||||
```tsx title="Example" showLineNumbers /-ml-4/ /pl-4/
|
||||
<Carousel>
|
||||
<CarouselContent className="-ml-4">
|
||||
<CarouselItem className="pl-4">...</CarouselItem>
|
||||
<CarouselItem className="pl-4">...</CarouselItem>
|
||||
<CarouselItem className="pl-4">...</CarouselItem>
|
||||
</CarouselContent>
|
||||
</Carousel>
|
||||
```
|
||||
|
||||
```tsx title="Responsive" showLineNumbers /-ml-2/ /pl-2/ /md:-ml-4/ /md:pl-4/
|
||||
<Carousel>
|
||||
<CarouselContent className="-ml-2 md:-ml-4">
|
||||
<CarouselItem className="pl-2 md:pl-4">...</CarouselItem>
|
||||
<CarouselItem className="pl-2 md:pl-4">...</CarouselItem>
|
||||
<CarouselItem className="pl-2 md:pl-4">...</CarouselItem>
|
||||
</CarouselContent>
|
||||
</Carousel>
|
||||
```
|
||||
|
||||
### Orientation
|
||||
|
||||
Use the `orientation` prop to set the orientation of the carousel.
|
||||
|
||||
```tsx showLineNumbers /vertical | horizontal/
|
||||
<Carousel orientation="vertical | horizontal">
|
||||
<CarouselContent>
|
||||
<CarouselItem>...</CarouselItem>
|
||||
<CarouselItem>...</CarouselItem>
|
||||
<CarouselItem>...</CarouselItem>
|
||||
</CarouselContent>
|
||||
</Carousel>
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
You can pass options to the carousel using the `opts` prop.
|
||||
|
||||
```tsx showLineNumbers {2-5}
|
||||
<Carousel
|
||||
opts={{
|
||||
align: "start",
|
||||
loop: true,
|
||||
}}
|
||||
>
|
||||
<CarouselContent>
|
||||
<CarouselItem>...</CarouselItem>
|
||||
<CarouselItem>...</CarouselItem>
|
||||
<CarouselItem>...</CarouselItem>
|
||||
</CarouselContent>
|
||||
</Carousel>
|
||||
```
|
||||
|
||||
## 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<CarouselApi>();
|
||||
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 (
|
||||
<Carousel setApi={setApi}>
|
||||
<CarouselContent>
|
||||
<CarouselItem>...</CarouselItem>
|
||||
<CarouselItem>...</CarouselItem>
|
||||
<CarouselItem>...</CarouselItem>
|
||||
</CarouselContent>
|
||||
</Carousel>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## 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<CarouselApi>();
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!api) {
|
||||
return;
|
||||
}
|
||||
|
||||
api.on("select", () => {
|
||||
// Do something on select.
|
||||
});
|
||||
}, [api]);
|
||||
|
||||
return (
|
||||
<Carousel setApi={setApi}>
|
||||
<CarouselContent>
|
||||
<CarouselItem>...</CarouselItem>
|
||||
<CarouselItem>...</CarouselItem>
|
||||
<CarouselItem>...</CarouselItem>
|
||||
</CarouselContent>
|
||||
</Carousel>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## 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 (
|
||||
<Carousel
|
||||
plugins={[
|
||||
Autoplay({
|
||||
delay: 2000,
|
||||
}),
|
||||
]}
|
||||
>
|
||||
// ...
|
||||
</Carousel>
|
||||
)
|
||||
}
|
||||
```
|
||||
@@ -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
|
||||
<Carousel>
|
||||
<CarouselContent>
|
||||
<CarouselItem>...</CarouselItem>
|
||||
<CarouselItem>...</CarouselItem>
|
||||
<CarouselItem>...</CarouselItem>
|
||||
</CarouselContent>
|
||||
<CarouselPrevious />
|
||||
<CarouselNext />
|
||||
</Carousel>
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Sizes
|
||||
|
||||
To set the size of the items, you can use the `basis` utility class on the `<CarouselItem />`.
|
||||
|
||||
```tsx title="Example" showLineNumbers {4-6}
|
||||
// 33% of the carousel width.
|
||||
<Carousel>
|
||||
<CarouselContent>
|
||||
<CarouselItem className="basis-1/3">...</CarouselItem>
|
||||
<CarouselItem className="basis-1/3">...</CarouselItem>
|
||||
<CarouselItem className="basis-1/3">...</CarouselItem>
|
||||
</CarouselContent>
|
||||
</Carousel>
|
||||
```
|
||||
|
||||
```tsx title="Responsive" showLineNumbers {4-6}
|
||||
// 50% on small screens and 33% on larger screens.
|
||||
<Carousel>
|
||||
<CarouselContent>
|
||||
<CarouselItem className="md:basis-1/2 lg:basis-1/3">...</CarouselItem>
|
||||
<CarouselItem className="md:basis-1/2 lg:basis-1/3">...</CarouselItem>
|
||||
<CarouselItem className="md:basis-1/2 lg:basis-1/3">...</CarouselItem>
|
||||
</CarouselContent>
|
||||
</Carousel>
|
||||
```
|
||||
|
||||
### Spacing
|
||||
|
||||
To set the spacing between the items, we use a `pl-[VALUE]` utility on the `<CarouselItem />` and a negative `-ml-[VALUE]` on the `<CarouselContent />`.
|
||||
|
||||
<Callout className="mt-6">
|
||||
**Why:** I tried to use the `gap` property or a `grid` layout on the `
|
||||
<CarouselContent />` 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.
|
||||
|
||||
</Callout>
|
||||
|
||||
<ComponentPreview
|
||||
name="carousel-spacing"
|
||||
title="Carousel"
|
||||
description="A carousel with 3 items with a spacing of 1rem."
|
||||
/>
|
||||
|
||||
```tsx title="Example" showLineNumbers /-ml-4/ /pl-4/
|
||||
<Carousel>
|
||||
<CarouselContent className="-ml-4">
|
||||
<CarouselItem className="pl-4">...</CarouselItem>
|
||||
<CarouselItem className="pl-4">...</CarouselItem>
|
||||
<CarouselItem className="pl-4">...</CarouselItem>
|
||||
</CarouselContent>
|
||||
</Carousel>
|
||||
```
|
||||
|
||||
```tsx title="Responsive" showLineNumbers /-ml-2/ /pl-2/ /md:-ml-4/ /md:pl-4/
|
||||
<Carousel>
|
||||
<CarouselContent className="-ml-2 md:-ml-4">
|
||||
<CarouselItem className="pl-2 md:pl-4">...</CarouselItem>
|
||||
<CarouselItem className="pl-2 md:pl-4">...</CarouselItem>
|
||||
<CarouselItem className="pl-2 md:pl-4">...</CarouselItem>
|
||||
</CarouselContent>
|
||||
</Carousel>
|
||||
```
|
||||
|
||||
### Orientation
|
||||
|
||||
Use the `orientation` prop to set the orientation of the carousel.
|
||||
|
||||
```tsx showLineNumbers /vertical | horizontal/
|
||||
<Carousel orientation="vertical | horizontal">
|
||||
<CarouselContent>
|
||||
<CarouselItem>...</CarouselItem>
|
||||
<CarouselItem>...</CarouselItem>
|
||||
<CarouselItem>...</CarouselItem>
|
||||
</CarouselContent>
|
||||
</Carousel>
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
You can pass options to the carousel using the `opts` prop.
|
||||
|
||||
```tsx showLineNumbers {2-5}
|
||||
<Carousel
|
||||
opts={{
|
||||
align: "start",
|
||||
loop: true,
|
||||
}}
|
||||
>
|
||||
<CarouselContent>
|
||||
<CarouselItem>...</CarouselItem>
|
||||
<CarouselItem>...</CarouselItem>
|
||||
<CarouselItem>...</CarouselItem>
|
||||
</CarouselContent>
|
||||
</Carousel>
|
||||
```
|
||||
|
||||
## 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<CarouselApi>();
|
||||
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 (
|
||||
<Carousel setApi={setApi}>
|
||||
<CarouselContent>
|
||||
<CarouselItem>...</CarouselItem>
|
||||
<CarouselItem>...</CarouselItem>
|
||||
<CarouselItem>...</CarouselItem>
|
||||
</CarouselContent>
|
||||
</Carousel>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## 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<CarouselApi>();
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!api) {
|
||||
return;
|
||||
}
|
||||
|
||||
api.on("select", () => {
|
||||
// Do something on select.
|
||||
});
|
||||
}, [api]);
|
||||
|
||||
return (
|
||||
<Carousel setApi={setApi}>
|
||||
<CarouselContent>
|
||||
<CarouselItem>...</CarouselItem>
|
||||
<CarouselItem>...</CarouselItem>
|
||||
<CarouselItem>...</CarouselItem>
|
||||
</CarouselContent>
|
||||
</Carousel>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## 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 (
|
||||
<Carousel
|
||||
plugins={[
|
||||
Autoplay({
|
||||
delay: 2000,
|
||||
}),
|
||||
]}
|
||||
>
|
||||
// ...
|
||||
</Carousel>
|
||||
)
|
||||
}
|
||||
```
|
||||
@@ -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
|
||||
<Dialog>
|
||||
<DialogTrigger>Open</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Are you sure absolutely sure?</DialogTitle>
|
||||
<DialogDescription>
|
||||
This action cannot be undone. This will permanently delete your account
|
||||
and remove your data from our servers.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 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 (
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="outline">Edit Profile</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-[425px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Edit profile</DialogTitle>
|
||||
<DialogDescription>
|
||||
Make changes to your profile here. Click save when you're done.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="grid gap-4 py-4">
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label htmlFor="name" className="text-right">
|
||||
Name
|
||||
</Label>
|
||||
<Input id="name" value="Pedro Duarte" className="col-span-3" />
|
||||
</div>
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label htmlFor="username" className="text-right">
|
||||
Username
|
||||
</Label>
|
||||
<Input id="username" value="@peduarte" className="col-span-3" />
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button type="submit">Save changes</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
```
|
||||
@@ -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
|
||||
<Checkbox />
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### examples
|
||||
|
||||
```checkbox-demo.tsx
|
||||
"use client"
|
||||
|
||||
import { Checkbox } from "@/components/ui/checkbox"
|
||||
|
||||
export default function CheckboxDemo() {
|
||||
return (
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox id="terms" />
|
||||
<label
|
||||
htmlFor="terms"
|
||||
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
>
|
||||
Accept terms and conditions
|
||||
</label>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```checkbox-disabled.tsx
|
||||
import { Checkbox } from "@/components/ui/checkbox"
|
||||
|
||||
export default function CheckboxDisabled() {
|
||||
return (
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox id="terms2" disabled />
|
||||
<label
|
||||
htmlFor="terms2"
|
||||
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
>
|
||||
Accept terms and conditions
|
||||
</label>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```checkbox-with-text.tsx
|
||||
"use client"
|
||||
|
||||
import { Checkbox } from "@/components/ui/checkbox"
|
||||
|
||||
export default function CheckboxWithText() {
|
||||
return (
|
||||
<div className="items-top flex space-x-2">
|
||||
<Checkbox id="terms1" />
|
||||
<div className="grid gap-1.5 leading-none">
|
||||
<label
|
||||
htmlFor="terms1"
|
||||
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
>
|
||||
Accept terms and conditions
|
||||
</label>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
You agree to our Terms of Service and Privacy Policy.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
@@ -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 (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
aria-expanded={open}
|
||||
className="w-[200px] justify-between"
|
||||
>
|
||||
{value
|
||||
? frameworks.find((framework) => framework.value === value)?.label
|
||||
: "Select framework..."}
|
||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-[200px] p-0">
|
||||
<Command>
|
||||
<CommandInput placeholder="Search framework..." />
|
||||
<CommandEmpty>No framework found.</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{frameworks.map((framework) => (
|
||||
<CommandItem
|
||||
key={framework.value}
|
||||
onSelect={(currentValue) => {
|
||||
setValue(currentValue === value ? "" : currentValue)
|
||||
setOpen(false)
|
||||
}}
|
||||
>
|
||||
<Check
|
||||
className={cn(
|
||||
"mr-2 h-4 w-4",
|
||||
value === framework.value ? "opacity-100" : "opacity-0"
|
||||
)}
|
||||
/>
|
||||
{framework.label}
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 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 (
|
||||
<div className="flex w-full flex-col items-start justify-between rounded-md border px-4 py-3 sm:flex-row sm:items-center">
|
||||
<p className="text-sm font-medium leading-none">
|
||||
<span className="mr-2 rounded-lg bg-primary px-2 py-1 text-xs text-primary-foreground">
|
||||
{label}
|
||||
</span>
|
||||
<span className="text-muted-foreground">Create a new project</span>
|
||||
</p>
|
||||
<DropdownMenu open={open} onOpenChange={setOpen}>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="sm">
|
||||
<MoreHorizontal />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-[200px]">
|
||||
<DropdownMenuLabel>Actions</DropdownMenuLabel>
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem>
|
||||
<User className="mr-2 h-4 w-4" />
|
||||
Assign to...
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<Calendar className="mr-2 h-4 w-4" />
|
||||
Set due date...
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger>
|
||||
<Tags className="mr-2 h-4 w-4" />
|
||||
Apply label
|
||||
</DropdownMenuSubTrigger>
|
||||
<DropdownMenuSubContent className="p-0">
|
||||
<Command>
|
||||
<CommandInput
|
||||
placeholder="Filter label..."
|
||||
autoFocus={true}
|
||||
/>
|
||||
<CommandList>
|
||||
<CommandEmpty>No label found.</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{labels.map((label) => (
|
||||
<CommandItem
|
||||
key={label}
|
||||
onSelect={(value) => {
|
||||
setLabel(value)
|
||||
setOpen(false)
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuSub>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem className="text-red-600">
|
||||
<Trash className="mr-2 h-4 w-4" />
|
||||
Delete
|
||||
<DropdownMenuShortcut>⌘⌫</DropdownMenuShortcut>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```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<Status | null>(
|
||||
null
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="flex items-center space-x-4">
|
||||
<p className="text-sm text-muted-foreground">Status</p>
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="w-[150px] justify-start"
|
||||
>
|
||||
{selectedStatus ? (
|
||||
<>
|
||||
<selectedStatus.icon className="mr-2 h-4 w-4 shrink-0" />
|
||||
{selectedStatus.label}
|
||||
</>
|
||||
) : (
|
||||
<>+ Set status</>
|
||||
)}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="p-0" side="right" align="start">
|
||||
<Command>
|
||||
<CommandInput placeholder="Change status..." />
|
||||
<CommandList>
|
||||
<CommandEmpty>No results found.</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{statuses.map((status) => (
|
||||
<CommandItem
|
||||
key={status.value}
|
||||
onSelect={(value) => {
|
||||
setSelectedStatus(
|
||||
statuses.find((priority) => priority.value === value) ||
|
||||
null
|
||||
)
|
||||
setOpen(false)
|
||||
}}
|
||||
>
|
||||
<status.icon
|
||||
className={cn(
|
||||
"mr-2 h-4 w-4",
|
||||
status.value === selectedStatus?.value
|
||||
? "opacity-100"
|
||||
: "opacity-40"
|
||||
)}
|
||||
/>
|
||||
<span>{status.label}</span>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
@@ -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<Date>()
|
||||
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant={"outline"}
|
||||
className={cn(
|
||||
"w-[280px] justify-start text-left font-normal",
|
||||
!date && "text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
<CalendarIcon className="mr-2 h-4 w-4" />
|
||||
{date ? format(date, "PPP") : <span>Pick a date</span>}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0">
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={date}
|
||||
onSelect={setDate}
|
||||
initialFocus
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 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<Date>()
|
||||
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant={"outline"}
|
||||
className={cn(
|
||||
"w-[280px] justify-start text-left font-normal",
|
||||
!date && "text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
<CalendarIcon className="mr-2 h-4 w-4" />
|
||||
{date ? format(date, "PPP") : <span>Pick a date</span>}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0">
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={date}
|
||||
onSelect={setDate}
|
||||
initialFocus
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```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<Date>()
|
||||
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant={"outline"}
|
||||
className={cn(
|
||||
"w-[280px] justify-start text-left font-normal",
|
||||
!date && "text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
<CalendarIcon className="mr-2 h-4 w-4" />
|
||||
{date ? format(date, "PPP") : <span>Pick a date</span>}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="flex w-auto flex-col space-y-2 p-2">
|
||||
<Select
|
||||
onValueChange={(value) =>
|
||||
setDate(addDays(new Date(), parseInt(value)))
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select" />
|
||||
</SelectTrigger>
|
||||
<SelectContent position="popper">
|
||||
<SelectItem value="0">Today</SelectItem>
|
||||
<SelectItem value="1">Tomorrow</SelectItem>
|
||||
<SelectItem value="3">In 3 days</SelectItem>
|
||||
<SelectItem value="7">In a week</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<div className="rounded-md border">
|
||||
<Calendar mode="single" selected={date} onSelect={setDate} />
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```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<HTMLDivElement>) {
|
||||
const [date, setDate] = React.useState<DateRange | undefined>({
|
||||
from: new Date(2022, 0, 20),
|
||||
to: addDays(new Date(2022, 0, 20), 20),
|
||||
})
|
||||
|
||||
return (
|
||||
<div className={cn("grid gap-2", className)}>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
id="date"
|
||||
variant={"outline"}
|
||||
className={cn(
|
||||
"w-[300px] justify-start text-left font-normal",
|
||||
!date && "text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
<CalendarIcon className="mr-2 h-4 w-4" />
|
||||
{date?.from ? (
|
||||
date.to ? (
|
||||
<>
|
||||
{format(date.from, "LLL dd, y")} -{" "}
|
||||
{format(date.to, "LLL dd, y")}
|
||||
</>
|
||||
) : (
|
||||
format(date.from, "LLL dd, y")
|
||||
)
|
||||
) : (
|
||||
<span>Pick a date</span>
|
||||
)}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0" align="start">
|
||||
<Calendar
|
||||
initialFocus
|
||||
mode="range"
|
||||
defaultMonth={date?.from}
|
||||
selected={date}
|
||||
onSelect={setDate}
|
||||
numberOfMonths={2}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||