mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-09 23:17:59 -05:00
Compare commits
1211 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f415e5edc4 | ||
|
|
13981549d1 | ||
|
|
554dcdf062 | ||
|
|
6b28742b68 | ||
|
|
e5c95093f6 | ||
|
|
b87af80bff | ||
|
|
c2180bf8a0 | ||
|
|
fdac4314d2 | ||
|
|
a54fcbc094 | ||
|
|
05904a73b2 | ||
|
|
1b22d2ce81 | ||
|
|
26dff7cffe | ||
|
|
020037728d | ||
|
|
13a6e6c3fa | ||
|
|
cb12ceb82c | ||
|
|
0f32310ba6 | ||
|
|
730ddf5a66 | ||
|
|
ef4bec2c37 | ||
|
|
2bd27f9a4d | ||
|
|
3b4f7d6adb | ||
|
|
142c9a0428 | ||
|
|
9dc02f3728 | ||
|
|
833825f04a | ||
|
|
261becd129 | ||
|
|
3ecf7a15eb | ||
|
|
1420bfb73c | ||
|
|
f5ab7f21ae | ||
|
|
02229f0cb2 | ||
|
|
a2451ef3d3 | ||
|
|
6a262f3988 | ||
|
|
5145ce1684 | ||
|
|
e5bd5e4474 | ||
|
|
e9aede087d | ||
|
|
bfb6fffe38 | ||
|
|
ba2377f83b | ||
|
|
f502f984f3 | ||
|
|
74f371cc79 | ||
|
|
4fbec0a43f | ||
|
|
d248557042 | ||
|
|
8215a819e5 | ||
|
|
155f544ce8 | ||
|
|
22f949a41c | ||
|
|
f9aef6ae22 | ||
|
|
46b04a964d | ||
|
|
964b40de45 | ||
|
|
75aca00b6e | ||
|
|
d25084e05d | ||
|
|
445932c1c8 | ||
|
|
cc3f565d5e | ||
|
|
585f5e365b | ||
|
|
0977ed228f | ||
|
|
ed6b9c0c4a | ||
|
|
86bcdcf0d3 | ||
|
|
ac942416de | ||
|
|
195e0e8e3f | ||
|
|
1673ef98ac | ||
|
|
356b473dc3 | ||
|
|
3792bdd252 | ||
|
|
8d15219c12 | ||
|
|
c3adcf315b | ||
|
|
4df5d56ac5 | ||
|
|
7515809df0 | ||
|
|
385e93f4bb | ||
|
|
096af4fdfa | ||
|
|
dc3de95c39 | ||
|
|
79be435918 | ||
|
|
852562cfdd | ||
|
|
eb5d1f3e5b | ||
|
|
4da128d77c | ||
|
|
0c8d05fc98 | ||
|
|
4301342ffb | ||
|
|
56e485d13b | ||
|
|
1ed746bacf | ||
|
|
bf5d0a5573 | ||
|
|
fb148c6203 | ||
|
|
54ab82c8dd | ||
|
|
b90cc5b874 | ||
|
|
4787909851 | ||
|
|
776f82c0a6 | ||
|
|
2cfd75a422 | ||
|
|
c77268c13d | ||
|
|
34bc115468 | ||
|
|
2697da5d9c | ||
|
|
eca91232bf | ||
|
|
7356edccbb | ||
|
|
9208375523 | ||
|
|
df099e9485 | ||
|
|
f8b1880575 | ||
|
|
a7a7c8601c | ||
|
|
97a9295230 | ||
|
|
e9e5721610 | ||
|
|
a08d86d42b | ||
|
|
400178a3b0 | ||
|
|
da1f668272 | ||
|
|
f895bf469b | ||
|
|
88065088bf | ||
|
|
1c626dfcae | ||
|
|
132aae1615 | ||
|
|
f44fc18041 | ||
|
|
7761b16b87 | ||
|
|
71130c8b0a | ||
|
|
dd3209af06 | ||
|
|
7c0a3c15ac | ||
|
|
cdc1a832d7 | ||
|
|
aa9cc5604a | ||
|
|
fdba1cfac2 | ||
|
|
2e1ccb16f5 | ||
|
|
b6ba3b50a7 | ||
|
|
8651896277 | ||
|
|
3054d6c1ed | ||
|
|
3d75445459 | ||
|
|
5add2613ff | ||
|
|
bd0eca04d7 | ||
|
|
a60a1fc49a | ||
|
|
298546daf1 | ||
|
|
b60b98e42c | ||
|
|
7793a6d597 | ||
|
|
66b8434861 | ||
|
|
27ec4120bc | ||
|
|
1f0e3f2be6 | ||
|
|
88cda3a9ce | ||
|
|
d707d18ee6 | ||
|
|
b7f6bab282 | ||
|
|
61e7213425 | ||
|
|
3201abab56 | ||
|
|
d79696beae | ||
|
|
f604ca39a5 | ||
|
|
26ec12599f | ||
|
|
97372533ec | ||
|
|
66766a9d81 | ||
|
|
47a259b428 | ||
|
|
40a6bf5c8c | ||
|
|
da7eca9590 | ||
|
|
92b2e34d25 | ||
|
|
77521a3a57 | ||
|
|
cb8b9c547a | ||
|
|
b1cd8d151d | ||
|
|
1145f5c043 | ||
|
|
b304233062 | ||
|
|
3a50ce4d99 | ||
|
|
810d2089cf | ||
|
|
8c89507247 | ||
|
|
169dd4a503 | ||
|
|
dc4e5d3bdc | ||
|
|
31de55cbdf | ||
|
|
eaca49037d | ||
|
|
2d26c0cb32 | ||
|
|
cdf3d759b9 | ||
|
|
bf8fbebe22 | ||
|
|
b23299dae4 | ||
|
|
6c8f1a81c1 | ||
|
|
2c36926a4e | ||
|
|
89c1085950 | ||
|
|
4e09c389e8 | ||
|
|
641ac58017 | ||
|
|
6c1e4ff7d6 | ||
|
|
40e30a11e9 | ||
|
|
57e4b49bd6 | ||
|
|
d1ebad912e | ||
|
|
e12dd204ed | ||
|
|
621f9a40c7 | ||
|
|
3100daa346 | ||
|
|
c252e885af | ||
|
|
b0748c82f9 | ||
|
|
f5245f3eca | ||
|
|
f2ef5f0811 | ||
|
|
37443a7b77 | ||
|
|
e0d96e2126 | ||
|
|
827dd0466f | ||
|
|
ccd92b9054 | ||
|
|
6af291ca9f | ||
|
|
be9ab4c833 | ||
|
|
ab3a3d12fe | ||
|
|
e01d4cb990 | ||
|
|
8c2c49eb14 | ||
|
|
086982c7a3 | ||
|
|
2b7807a7de | ||
|
|
2d4a660246 | ||
|
|
e981b1dc1b | ||
|
|
3d9d9cbc54 | ||
|
|
58fcb4ed80 | ||
|
|
e4d211c2f0 | ||
|
|
0f4ec962ad | ||
|
|
f21eaf1f10 | ||
|
|
942da8815d | ||
|
|
4827866f9a | ||
|
|
214632604d | ||
|
|
1ddbac1d2e | ||
|
|
35a57bfad4 | ||
|
|
f8678b179a | ||
|
|
0ebb45b2db | ||
|
|
6247f421bc | ||
|
|
3e697d9ed9 | ||
|
|
6385d82b85 | ||
|
|
f91beb324e | ||
|
|
4f69b171f2 | ||
|
|
a1a189f328 | ||
|
|
7dc48510dc | ||
|
|
4431a1a484 | ||
|
|
93fe68785e | ||
|
|
50c1c6775b | ||
|
|
df5f823d1c | ||
|
|
094f87fa1f | ||
|
|
65efa039da | ||
|
|
6b15a50311 | ||
|
|
65787d7cc3 | ||
|
|
4d1a9a3f22 | ||
|
|
656a6b8abd | ||
|
|
889b44c90a | ||
|
|
3a33ec929f | ||
|
|
24356d99ec | ||
|
|
6de1c04517 | ||
|
|
38be2b76c4 | ||
|
|
a2f14cab54 | ||
|
|
474762d6fb | ||
|
|
0005c3e465 | ||
|
|
fc40b4f7af | ||
|
|
eb07a080fb | ||
|
|
2a7f51a2f6 | ||
|
|
90c3c43607 | ||
|
|
83d813a7cc | ||
|
|
811c736705 | ||
|
|
c6757311af | ||
|
|
b5b12ba2d1 | ||
|
|
0d30676e34 | ||
|
|
36bdccb449 | ||
|
|
f45730a89e | ||
|
|
04cd837e9c | ||
|
|
c23130a26e | ||
|
|
7575cd6f27 | ||
|
|
fbde64f0b0 | ||
|
|
25f7ed20f6 | ||
|
|
261aa3d72d | ||
|
|
9da19e84b7 | ||
|
|
e83afc0a62 | ||
|
|
1720fa8749 | ||
|
|
f3ad7750af | ||
|
|
78b7643e65 | ||
|
|
7ef1150383 | ||
|
|
67cfb21d08 | ||
|
|
a337af92bc | ||
|
|
b4a99779eb | ||
|
|
471cb4747c | ||
|
|
491bd783b5 | ||
|
|
5516fa39c3 | ||
|
|
21fa92bc41 | ||
|
|
26ca37328a | ||
|
|
731997f768 | ||
|
|
1d6975db49 | ||
|
|
c4a6d11cc0 | ||
|
|
7b5405e968 | ||
|
|
1ae3b47f5c | ||
|
|
3120a785df | ||
|
|
8775e76c32 | ||
|
|
9a6c68789d | ||
|
|
08bc1125bd | ||
|
|
f4f74da1dc | ||
|
|
de330d80f5 | ||
|
|
b7228d57f7 | ||
|
|
dcbeca1abe | ||
|
|
27ea333974 | ||
|
|
9861d3a0ac | ||
|
|
fdbf8be79b | ||
|
|
6f4f4e22f0 | ||
|
|
837aabca5e | ||
|
|
f7d2c9667f | ||
|
|
29befbc5f6 | ||
|
|
f9cfca92bf | ||
|
|
9cf8aaee1b | ||
|
|
25afacb25e | ||
|
|
0e6a1315d0 | ||
|
|
f0dc8e81d9 | ||
|
|
ab30d37020 | ||
|
|
12d42e29ac | ||
|
|
a5b7148375 | ||
|
|
57e6a0b621 | ||
|
|
b72e111e22 | ||
|
|
300aaa5368 | ||
|
|
bdcc42e566 | ||
|
|
a45bb1bf3b | ||
|
|
3a5363ac54 | ||
|
|
17b2e58c32 | ||
|
|
8d38c2f15e | ||
|
|
9762bbc451 | ||
|
|
e43afc8b6c | ||
|
|
fcf52ac4d5 | ||
|
|
842200bcf2 | ||
|
|
6009a7359f | ||
|
|
0acd86023c | ||
|
|
a0fb889644 | ||
|
|
431f206930 | ||
|
|
7443e28054 | ||
|
|
c962e3b398 | ||
|
|
d5b95cbd33 | ||
|
|
0fb084b9e4 | ||
|
|
95b9ca4670 | ||
|
|
746ff68a2e | ||
|
|
75a5b43252 | ||
|
|
8d0e50fd0d | ||
|
|
f7d1b06d75 | ||
|
|
73940ab390 | ||
|
|
f111dac020 | ||
|
|
690be530c8 | ||
|
|
4edb3be52d | ||
|
|
f526c36fc0 | ||
|
|
9c8d845ba4 | ||
|
|
65ac64c2cf | ||
|
|
9e3e18601c | ||
|
|
821d127c00 | ||
|
|
ecf5209e6f | ||
|
|
132f4bca38 | ||
|
|
d27f7c232b | ||
|
|
d06d2b01e3 | ||
|
|
92db054c53 | ||
|
|
cd7fa688c9 | ||
|
|
e359dc2946 | ||
|
|
0415eb47fe | ||
|
|
49d31c80f5 | ||
|
|
0ea600d0e8 | ||
|
|
deb085881f | ||
|
|
dda44f7382 | ||
|
|
668118b19b | ||
|
|
323e03520d | ||
|
|
ffcaa65590 | ||
|
|
cd66fa84d1 | ||
|
|
045475b9fe | ||
|
|
3334dfeefa | ||
|
|
cb9b88127f | ||
|
|
3c8478405c | ||
|
|
c4fd39df54 | ||
|
|
06d2db78ff | ||
|
|
516dee14b7 | ||
|
|
030ae5cc0a | ||
|
|
855e2c418c | ||
|
|
3bde9e853f | ||
|
|
31b795f8b1 | ||
|
|
57b6bc3684 | ||
|
|
8d4f2e0233 | ||
|
|
4da5dd7f74 | ||
|
|
e24f31cbce | ||
|
|
3b9cbeaa49 | ||
|
|
c592e54118 | ||
|
|
d02499a915 | ||
|
|
c0bb85479d | ||
|
|
b595273c3f | ||
|
|
193a15aca4 | ||
|
|
39d5d797ec | ||
|
|
3fbd57caf1 | ||
|
|
3db8f82449 | ||
|
|
207a14970b | ||
|
|
0997989f36 | ||
|
|
7fd912d8a9 | ||
|
|
c550e4591c | ||
|
|
a881dc1877 | ||
|
|
da36c453b5 | ||
|
|
635823664c | ||
|
|
14846ab05c | ||
|
|
6c99c841f4 | ||
|
|
37d7902fcd | ||
|
|
1e563b1e0a | ||
|
|
b5da61377c | ||
|
|
1d62ece915 | ||
|
|
bbab2ff732 | ||
|
|
18b7032494 | ||
|
|
76bc2fae83 | ||
|
|
1cfe229056 | ||
|
|
6791d968b8 | ||
|
|
163db5cf50 | ||
|
|
bbbb13af7e | ||
|
|
b7bbef8620 | ||
|
|
6fd4087a79 | ||
|
|
cb6e763714 | ||
|
|
d06b360b1d | ||
|
|
0713580862 | ||
|
|
f421f27d3f | ||
|
|
0083c89fa5 | ||
|
|
3cec449402 | ||
|
|
c5b3fcb181 | ||
|
|
dd7db6e144 | ||
|
|
306043eedb | ||
|
|
569598b39e | ||
|
|
cc66aa5a3e | ||
|
|
aea32d423f | ||
|
|
22abf98835 | ||
|
|
52edbea659 | ||
|
|
aa1d896b38 | ||
|
|
2fcd07e82d | ||
|
|
0db5ba1b27 | ||
|
|
e390ba0491 | ||
|
|
2f0509adaf | ||
|
|
9f0584a818 | ||
|
|
6b4d76298f | ||
|
|
b7a1e8f5cf | ||
|
|
3ce2788562 | ||
|
|
17a084cd61 | ||
|
|
dafd2f5ce8 | ||
|
|
5af67d08ba | ||
|
|
209b0f1906 | ||
|
|
e067b584ee | ||
|
|
87084edbe6 | ||
|
|
99e0b81233 | ||
|
|
d480057fd3 | ||
|
|
c197b04bcc | ||
|
|
c27c233da0 | ||
|
|
4fb039f3e8 | ||
|
|
feb591867e | ||
|
|
598c3bc684 | ||
|
|
01b2f809d1 | ||
|
|
d200f664e8 | ||
|
|
e2b077f582 | ||
|
|
d09fd6cf92 | ||
|
|
434d12956e | ||
|
|
b49b8eeb9d | ||
|
|
5b9f3d3d02 | ||
|
|
05022e3468 | ||
|
|
9f884c151c | ||
|
|
92c03b825b | ||
|
|
e9d53042f6 | ||
|
|
22c9384f19 | ||
|
|
6e02a73259 | ||
|
|
683b4476fa | ||
|
|
ae7937280e | ||
|
|
ebef5f3a27 | ||
|
|
7bfc6ebe48 | ||
|
|
23ef39de72 | ||
|
|
507fc112be | ||
|
|
26670e289d | ||
|
|
e52bd575e7 | ||
|
|
0b28128f25 | ||
|
|
a50edf8131 | ||
|
|
12c4c2d44f | ||
|
|
656dfafb8f | ||
|
|
9f604f379e | ||
|
|
75da06adf8 | ||
|
|
929a352edb | ||
|
|
c7b473f55f | ||
|
|
6cd078b0fe | ||
|
|
fb4c9827f8 | ||
|
|
4fd5f0051f | ||
|
|
002713ec4b | ||
|
|
5d6c1f7b88 | ||
|
|
7752beac01 | ||
|
|
7101dc58d4 | ||
|
|
58251e28e6 | ||
|
|
8ef9a45125 | ||
|
|
ca818a6503 | ||
|
|
1b903f2db5 | ||
|
|
414a54c358 | ||
|
|
3b9f0f9ce2 | ||
|
|
dcbdcb43aa | ||
|
|
1642ed754b | ||
|
|
d22b5783be | ||
|
|
8e7d8c93e3 | ||
|
|
ca3eb5b5a5 | ||
|
|
dc5a2b1ad1 | ||
|
|
3f84ed9b72 | ||
|
|
31874939ee | ||
|
|
b54ca604b6 | ||
|
|
e581608472 | ||
|
|
a4d4dfaa32 | ||
|
|
099e86e3c1 | ||
|
|
042de6a944 | ||
|
|
f44e7e34ec | ||
|
|
6bfb643ef8 | ||
|
|
cd5d1c0958 | ||
|
|
9df87d9b88 | ||
|
|
c864d17991 | ||
|
|
36c91f4ca9 | ||
|
|
6d4ba6d5cf | ||
|
|
5d791cd55f | ||
|
|
5630e133fd | ||
|
|
08a11935af | ||
|
|
3158b62da8 | ||
|
|
e157ce5fbc | ||
|
|
7de721e090 | ||
|
|
3e83fb398c | ||
|
|
a6e3c92c10 | ||
|
|
9670d96eca | ||
|
|
eb0d4cbd57 | ||
|
|
ffd12e1da4 | ||
|
|
84c2335a37 | ||
|
|
2ab8cec8c3 | ||
|
|
bf54c88ae4 | ||
|
|
41c068c023 | ||
|
|
6fda9bd72e | ||
|
|
3b4f227e43 | ||
|
|
0ae7eb197a | ||
|
|
304b5d9c7e | ||
|
|
ee3e1e8cf9 | ||
|
|
774e5d585c | ||
|
|
ede41af674 | ||
|
|
cb0c55c6f6 | ||
|
|
54cc93743f | ||
|
|
d22b21c8d1 | ||
|
|
8c32ad4c0d | ||
|
|
1e080e98e8 | ||
|
|
b069034d9c | ||
|
|
3a4f130f69 | ||
|
|
e80feee51f | ||
|
|
7f62467f84 | ||
|
|
7b2792122d | ||
|
|
4e9cab39c3 | ||
|
|
e3a57d30e8 | ||
|
|
f25db707d7 | ||
|
|
08d57b4f8b | ||
|
|
f56d85bda6 | ||
|
|
d7a650a355 | ||
|
|
a8f87f7e3a | ||
|
|
9330940658 | ||
|
|
fc5f815c7a | ||
|
|
7bf9251db1 | ||
|
|
1d08796853 | ||
|
|
21a640af50 | ||
|
|
a10e1a63af | ||
|
|
be91cd3794 | ||
|
|
1ee49ae611 | ||
|
|
aab1b3f259 | ||
|
|
8000394e98 | ||
|
|
0830490d32 | ||
|
|
93e1c513b2 | ||
|
|
ff79b78b5f | ||
|
|
3a3c946607 | ||
|
|
7b7586d093 | ||
|
|
ebcd243942 | ||
|
|
d413bcdfb0 | ||
|
|
ff768ca410 | ||
|
|
bbaf7e90f8 | ||
|
|
c80827f21b | ||
|
|
a5b7897b34 | ||
|
|
c6482f2997 | ||
|
|
bf1719a294 | ||
|
|
619cab162d | ||
|
|
72776f4402 | ||
|
|
6114c213d2 | ||
|
|
d1f0d21e7f | ||
|
|
33ac828d3d | ||
|
|
29156e3b61 | ||
|
|
fa4b34fc46 | ||
|
|
9fad5860bc | ||
|
|
0a4244bcef | ||
|
|
b7e814b721 | ||
|
|
3be57aff8f | ||
|
|
ddd3219126 | ||
|
|
67bd5bd8fa | ||
|
|
c52501616c | ||
|
|
3dbf0f5679 | ||
|
|
6187561219 | ||
|
|
022b4f64a7 | ||
|
|
768cdec6ce | ||
|
|
75f55c894a | ||
|
|
4a0450d1fc | ||
|
|
00ae718692 | ||
|
|
d7586cdd9f | ||
|
|
842ef27ed9 | ||
|
|
f208ff9356 | ||
|
|
31c34b2ea3 | ||
|
|
304cafe698 | ||
|
|
472aff5dd7 | ||
|
|
4d5c574363 | ||
|
|
e7d4afa46b | ||
|
|
f570592ad7 | ||
|
|
e64b1c9fcd | ||
|
|
7c5d625ca5 | ||
|
|
e5cb6e3d0f | ||
|
|
e4ccedc4ff | ||
|
|
d9cb63ce5f | ||
|
|
3468593f84 | ||
|
|
95d5fd9c35 | ||
|
|
becd19bc50 | ||
|
|
c93f6620f6 | ||
|
|
3647a3e38c | ||
|
|
f609b6ea4a | ||
|
|
2be3007d69 | ||
|
|
570b8d61f0 | ||
|
|
7045c4a47b | ||
|
|
5e11e5df91 | ||
|
|
2608f2f12c | ||
|
|
96207d85a7 | ||
|
|
a7fe1d3aea | ||
|
|
3faab2cb01 | ||
|
|
02d9fedf0c | ||
|
|
a8a693f1ff | ||
|
|
e0aade85a6 | ||
|
|
8f0ef58056 | ||
|
|
33ca1483aa | ||
|
|
620ce97056 | ||
|
|
25ac91779b | ||
|
|
d51a756c1b | ||
|
|
3d1feab507 | ||
|
|
98908dbfb9 | ||
|
|
00d9b45a22 | ||
|
|
b5b2855b40 | ||
|
|
a81f3847df | ||
|
|
3058e35edf | ||
|
|
6f3dee867c | ||
|
|
bfa7c919d8 | ||
|
|
e37b01b92c | ||
|
|
7e3e38a6f2 | ||
|
|
1c85fe9a51 | ||
|
|
5f446ad756 | ||
|
|
d99d5fe39c | ||
|
|
949f9287cf | ||
|
|
fca92a7499 | ||
|
|
c25ea5c677 | ||
|
|
dccd9e9ce5 | ||
|
|
b5d9964c48 | ||
|
|
4bd0f31f36 | ||
|
|
f8070f9029 | ||
|
|
bc8947caa6 | ||
|
|
f1111ec16f | ||
|
|
d0767507b2 | ||
|
|
8bd75debc1 | ||
|
|
ad2a375358 | ||
|
|
de91dc97a9 | ||
|
|
31ed712378 | ||
|
|
aca4d2fc15 | ||
|
|
96958104c0 | ||
|
|
6f29e2413c | ||
|
|
c2ccd51b3e | ||
|
|
ec430abca2 | ||
|
|
4b4060f63f | ||
|
|
72a048f37d | ||
|
|
fa18bef65b | ||
|
|
a9eb91aed1 | ||
|
|
16a4f37dac | ||
|
|
6c56d48e73 | ||
|
|
1082e55871 | ||
|
|
948b6575dc | ||
|
|
3ba33791f7 | ||
|
|
1e915d5427 | ||
|
|
d86198ad5d | ||
|
|
785f847c48 | ||
|
|
dab70a8f1d | ||
|
|
d2c0147822 | ||
|
|
67d9343022 | ||
|
|
b3caef1f31 | ||
|
|
5457d4bc7b | ||
|
|
32a2e09a14 | ||
|
|
80eaeb00c2 | ||
|
|
b67b4ff8fb | ||
|
|
6f4f8cfad2 | ||
|
|
3a8f01f3e4 | ||
|
|
383b6f05a6 | ||
|
|
a70f2a6690 | ||
|
|
53150021e0 | ||
|
|
4e5b834433 | ||
|
|
304fb28baf | ||
|
|
07e803cfdd | ||
|
|
d50aefcc68 | ||
|
|
6513cbb7c1 | ||
|
|
f9e822f6c8 | ||
|
|
74202ce1fc | ||
|
|
66c8fa2a77 | ||
|
|
766c7fbfbb | ||
|
|
675c42188a | ||
|
|
f414ae1936 | ||
|
|
ead0db9d2a | ||
|
|
10288111a8 | ||
|
|
01183f1771 | ||
|
|
ff081714e4 | ||
|
|
36bcd75832 | ||
|
|
9db969b1e0 | ||
|
|
2fbe0de5d3 | ||
|
|
6315cc105b | ||
|
|
61404d48a3 | ||
|
|
dbf9097a5b | ||
|
|
79b318fd9c | ||
|
|
cb39e697e2 | ||
|
|
e1a46c90c6 | ||
|
|
c7560be282 | ||
|
|
63f18995da | ||
|
|
af501347bb | ||
|
|
6747d89865 | ||
|
|
1201327adc | ||
|
|
f4434e302c | ||
|
|
42e2769526 | ||
|
|
a0c4bce56e | ||
|
|
64c5f2c473 | ||
|
|
413c460ae8 | ||
|
|
fa42ea44c1 | ||
|
|
1bd18ee85b | ||
|
|
e3e951832b | ||
|
|
56835b94d8 | ||
|
|
9a6a6fdacb | ||
|
|
41066b84d2 | ||
|
|
5cea73fe91 | ||
|
|
cbc98ab4f5 | ||
|
|
81411c73b7 | ||
|
|
d11ee04432 | ||
|
|
1d58fdf234 | ||
|
|
96f134b127 | ||
|
|
769555676e | ||
|
|
679c3418d6 | ||
|
|
7b48d6ed53 | ||
|
|
b093550656 | ||
|
|
9b702c4793 | ||
|
|
77ba4d106f | ||
|
|
77d897da85 | ||
|
|
f00d5516df | ||
|
|
831ce91577 | ||
|
|
1cce486442 | ||
|
|
7c398e64dc | ||
|
|
a6a9962cb3 | ||
|
|
16bd54c05a | ||
|
|
1130befb17 | ||
|
|
b6139d6f6e | ||
|
|
c86f2a0537 | ||
|
|
41f3d506da | ||
|
|
fc162ab236 | ||
|
|
bc25ea28e0 | ||
|
|
68aaedebbf | ||
|
|
ec70f6fafe | ||
|
|
37fd21e0aa | ||
|
|
82731850b2 | ||
|
|
991b0e31ad | ||
|
|
cd48cd4de4 | ||
|
|
4b37f92f3a | ||
|
|
698717d044 | ||
|
|
170eb36ebf | ||
|
|
118c477d97 | ||
|
|
2f9224c166 | ||
|
|
0ed0a26b3b | ||
|
|
0d4d953169 | ||
|
|
81a12e721e | ||
|
|
b03f9702d2 | ||
|
|
997c4639ed | ||
|
|
1e8b4769aa | ||
|
|
28b416078c | ||
|
|
d0720b85bc | ||
|
|
75ce8882c8 | ||
|
|
142d3aadb8 | ||
|
|
7c6e6d1603 | ||
|
|
e186ea630a | ||
|
|
b3490e9127 | ||
|
|
eed2072723 | ||
|
|
945405c461 | ||
|
|
c397f5acee | ||
|
|
3f435ce55e | ||
|
|
7a8d47a72e | ||
|
|
e91a8af7cd | ||
|
|
d17c627064 | ||
|
|
c8ea08e130 | ||
|
|
f62568efc7 | ||
|
|
a73e2aaa8b | ||
|
|
6cdee5351c | ||
|
|
d3e81e97d5 | ||
|
|
bb7016a99f | ||
|
|
c4278266ef | ||
|
|
b6c2c7456e | ||
|
|
9fd2156e9e | ||
|
|
c9a8c7e392 | ||
|
|
eb459d0ab9 | ||
|
|
a31dc733cf | ||
|
|
c3436e998f | ||
|
|
742d59f54d | ||
|
|
f9ce65eddf | ||
|
|
8c9ed34d99 | ||
|
|
541bdd3772 | ||
|
|
60d53ba14a | ||
|
|
5c611c6d65 | ||
|
|
2b78b5ea73 | ||
|
|
cf023e4d22 | ||
|
|
21774de275 | ||
|
|
2056e8ae5f | ||
|
|
11fd1544bb | ||
|
|
0260dbc3f2 | ||
|
|
68056b72c2 | ||
|
|
fa323e2e51 | ||
|
|
b0fa3e8a26 | ||
|
|
f65d62ea3d | ||
|
|
670e63c108 | ||
|
|
e62a635757 | ||
|
|
a29f9fd55f | ||
|
|
e9ff94f06f | ||
|
|
56f645890a | ||
|
|
1ce834f2fe | ||
|
|
0363f8a33d | ||
|
|
d1fcade5ab | ||
|
|
3af7d136c6 | ||
|
|
2eea3caccd | ||
|
|
fb445b166d | ||
|
|
3bf00cbd2a | ||
|
|
7d67ae397d | ||
|
|
f9980868a4 | ||
|
|
e4d21568e3 | ||
|
|
5479461617 | ||
|
|
86b3570252 | ||
|
|
44271cd101 | ||
|
|
f6a5c5c829 | ||
|
|
e64129c1ad | ||
|
|
70ff5394a4 | ||
|
|
6cd82f07ed | ||
|
|
4a3c6d17e3 | ||
|
|
0b16fa4dd0 | ||
|
|
eac358bc7c | ||
|
|
a072e6d1d8 | ||
|
|
3b901b33d1 | ||
|
|
fe9ebbf81b | ||
|
|
c99bb0aaa2 | ||
|
|
61725c2d15 | ||
|
|
8b0079b834 | ||
|
|
fdefb14e6b | ||
|
|
47913f87de | ||
|
|
48f520b3c7 | ||
|
|
da30c25efa | ||
|
|
b95ea9148f | ||
|
|
fcf947df22 | ||
|
|
7be9941bc9 | ||
|
|
807014a5d2 | ||
|
|
aef1f18c1e | ||
|
|
368576b082 | ||
|
|
aace3066aa | ||
|
|
ef5b6999ab | ||
|
|
9df886d1e9 | ||
|
|
9991796661 | ||
|
|
095a15d7b5 | ||
|
|
8620ab255a | ||
|
|
47ddfb639e | ||
|
|
5d48c2780c | ||
|
|
38614fad79 | ||
|
|
6f32aea96b | ||
|
|
98e98496e8 | ||
|
|
659b46fa2f | ||
|
|
fb3d6d4c88 | ||
|
|
ec2cc82b72 | ||
|
|
274d5e3afc | ||
|
|
c552bb9c5f | ||
|
|
ad7b791242 | ||
|
|
ce4893a53c | ||
|
|
517f1a91b6 | ||
|
|
dba7514350 | ||
|
|
e94de1dd26 | ||
|
|
a4e874b266 | ||
|
|
ec034f3fc7 | ||
|
|
e425d064c0 | ||
|
|
bcd1a2faf6 | ||
|
|
989a77261c | ||
|
|
5ab482127d | ||
|
|
b8bc632baa | ||
|
|
4d7ebd8bcb | ||
|
|
ca1156a6c2 | ||
|
|
89eb1849d0 | ||
|
|
1d4833f485 | ||
|
|
d5902e91da | ||
|
|
e6ba323de4 | ||
|
|
859711991f | ||
|
|
c178a90f02 | ||
|
|
eb8995ee7c | ||
|
|
b269447539 | ||
|
|
ce660e2df9 | ||
|
|
063bd610b1 | ||
|
|
9132cd224d | ||
|
|
c70c32a3bd | ||
|
|
cc0ace7de6 | ||
|
|
de1ac9a704 | ||
|
|
728a4c82c6 | ||
|
|
37f293a761 | ||
|
|
d1c08daaf4 | ||
|
|
90c34b2c46 | ||
|
|
3a0019bd13 | ||
|
|
a6122f2bbc | ||
|
|
9bf5f6e1fc | ||
|
|
22b3dde155 | ||
|
|
c1725c1c4b | ||
|
|
64eee587cd | ||
|
|
35c551984f | ||
|
|
d92d9a02cd | ||
|
|
64ede7f038 | ||
|
|
0fbbbe02c7 | ||
|
|
29c7827d6f | ||
|
|
22f9d6e2df | ||
|
|
0cb615428d | ||
|
|
74576ec921 | ||
|
|
67e681dd7c | ||
|
|
4b05da31e0 | ||
|
|
82fa4e8bbb | ||
|
|
4cd790b200 | ||
|
|
b7e0b42d48 | ||
|
|
c6ef5785c8 | ||
|
|
48eab7e744 | ||
|
|
701bf2b510 | ||
|
|
ba8acbba07 | ||
|
|
56d04a9558 | ||
|
|
2ca9044bc6 | ||
|
|
b2009fe467 | ||
|
|
eb4821ff30 | ||
|
|
4cceb22f21 | ||
|
|
fd67fd220c | ||
|
|
061c1dff4e | ||
|
|
1a05ef97d6 | ||
|
|
7595e54dfb | ||
|
|
6c9fce5da4 | ||
|
|
b296323716 | ||
|
|
d325fdde6c | ||
|
|
36f2a62f3f | ||
|
|
e83d3a6b9f | ||
|
|
6723adf3c1 | ||
|
|
9efc08a832 | ||
|
|
f345c4d1d8 | ||
|
|
f147eaee1c | ||
|
|
6f3df271fd | ||
|
|
3dd36a8a35 | ||
|
|
09cccd5487 | ||
|
|
1773530325 | ||
|
|
2da7a6755c | ||
|
|
1e81cd6850 | ||
|
|
ec73e2e9ce | ||
|
|
4937d72d70 | ||
|
|
8f06aec68b | ||
|
|
1de6f09069 | ||
|
|
b10b2461a5 | ||
|
|
34fc8f84f5 | ||
|
|
ee77dea2d6 | ||
|
|
bba407b507 | ||
|
|
ab63978ce8 | ||
|
|
e697e50d4e | ||
|
|
41ec229431 | ||
|
|
c0f5ba75f1 | ||
|
|
5a943bca32 | ||
|
|
923595f57e | ||
|
|
241d9fd12d | ||
|
|
97a8778449 | ||
|
|
833e700b58 | ||
|
|
2d49892aaa | ||
|
|
8ce5a1b7c0 | ||
|
|
88d2e7b97b | ||
|
|
c04eb01aed | ||
|
|
5d887fdca7 | ||
|
|
1a0fdb32fe | ||
|
|
9d45b8df1e | ||
|
|
ae3a7f0865 | ||
|
|
25f5e31378 | ||
|
|
7bdf0e94d7 | ||
|
|
8e43774b5e | ||
|
|
715f42c1a6 | ||
|
|
8200e9a88f | ||
|
|
c6f6c9e2a5 | ||
|
|
2d7ba91c0e | ||
|
|
872e034312 | ||
|
|
a63a7b0262 | ||
|
|
991a020917 | ||
|
|
f03f395225 | ||
|
|
174f6a48a6 | ||
|
|
c2f0a95802 | ||
|
|
4dc4073452 | ||
|
|
d9b70087c4 | ||
|
|
07fd9c3a4a | ||
|
|
377b84e18c | ||
|
|
223ecda80e | ||
|
|
7dde01e74b | ||
|
|
b768ca845e | ||
|
|
86ed32ea10 | ||
|
|
0e838940f1 | ||
|
|
7cc9a23f99 | ||
|
|
c42d2a32f3 | ||
|
|
4da355d269 | ||
|
|
2175fd1106 | ||
|
|
10692b5e5a | ||
|
|
62298bf094 | ||
|
|
5f1518ffd9 | ||
|
|
cae0e85826 | ||
|
|
fa9c97816b | ||
|
|
4bc37db547 | ||
|
|
15138629cb | ||
|
|
ace83ebcae | ||
|
|
b33ae5bff9 | ||
|
|
dc6052578d | ||
|
|
4adbae03e7 | ||
|
|
3509ce8ce4 | ||
|
|
7aae108b87 | ||
|
|
980a6d8347 | ||
|
|
745eaff622 | ||
|
|
35d857ef2e | ||
|
|
6e63eafb79 | ||
|
|
896f7bb0a0 | ||
|
|
97f69a24e1 | ||
|
|
1a2c4040aa | ||
|
|
4ad9be0836 | ||
|
|
0bf2bce368 | ||
|
|
0d881ecc00 | ||
|
|
7e6a5dc7e2 | ||
|
|
c1a3500bde | ||
|
|
561b6f2778 | ||
|
|
cdfee16b8a | ||
|
|
9f6cb1becf | ||
|
|
dca8745c44 | ||
|
|
c35c8d1f31 | ||
|
|
87c00cec6d | ||
|
|
17edf0405b | ||
|
|
79461840c3 | ||
|
|
e76fc8c2da | ||
|
|
e9150a53e3 | ||
|
|
f9f84111cb | ||
|
|
01ffee8e7c | ||
|
|
367189fe15 | ||
|
|
7de9e5fb19 | ||
|
|
10652eb9b8 | ||
|
|
010753d170 | ||
|
|
bf1c178282 | ||
|
|
6a664663cf | ||
|
|
333442909b | ||
|
|
67c993d743 | ||
|
|
38dd2d0f23 | ||
|
|
5ee6345120 | ||
|
|
dd1985c99c | ||
|
|
39356f3d74 | ||
|
|
1ec5b1bdad | ||
|
|
bcc75376e5 | ||
|
|
448e9ea835 | ||
|
|
a63f3a3d8d | ||
|
|
3ff6509028 | ||
|
|
2ae30f1399 | ||
|
|
0e65a8a31d | ||
|
|
d1f5c69e6b | ||
|
|
c468ecbc72 | ||
|
|
97257ca49f | ||
|
|
6daeb77740 | ||
|
|
f79e87e4c2 | ||
|
|
18599ac3c3 | ||
|
|
e49cde7766 | ||
|
|
7146ce512d | ||
|
|
1b7e883b10 | ||
|
|
e03c036a10 | ||
|
|
7e8ac5c27f | ||
|
|
c12b3d2550 | ||
|
|
d381a69c9f | ||
|
|
928581f387 | ||
|
|
2c7c8d582e | ||
|
|
426873245f | ||
|
|
ca6ff7edb6 | ||
|
|
094dae3d3f | ||
|
|
2ee27f972e | ||
|
|
0b2ef5e3a6 | ||
|
|
67ac3ff84b | ||
|
|
8ed2c5ec53 | ||
|
|
2ace06f239 | ||
|
|
20a272c8c3 | ||
|
|
0d86eaa3a1 | ||
|
|
aa0a33e60f | ||
|
|
603b5471a3 | ||
|
|
ab8d822edf | ||
|
|
5bb9b46554 | ||
|
|
994eb8db2a | ||
|
|
fbb164db98 | ||
|
|
dd8f633f9f | ||
|
|
6d1b172a3e | ||
|
|
b7876ca466 | ||
|
|
68df95906f | ||
|
|
760219dcce | ||
|
|
f1b83c1988 | ||
|
|
2f97782df0 | ||
|
|
7cb303e713 | ||
|
|
8eaa83fe21 | ||
|
|
5c92d5d456 | ||
|
|
aa01e7e58a | ||
|
|
a3c76da636 | ||
|
|
cf19c895bb | ||
|
|
c017027000 | ||
|
|
73d779ad79 | ||
|
|
25781279e2 | ||
|
|
6fada45cd8 | ||
|
|
e640102797 | ||
|
|
16f5819941 | ||
|
|
d83865c635 | ||
|
|
6b4ebbac6e | ||
|
|
708321d0bf | ||
|
|
e4d35afe1f | ||
|
|
1d74ccfeda | ||
|
|
545e590ce5 | ||
|
|
93f9293f2c | ||
|
|
b5570c1c0e | ||
|
|
225571c49a | ||
|
|
a1c518e4e1 | ||
|
|
04922fe5c9 | ||
|
|
8e70a61ba9 | ||
|
|
abae6b96b5 | ||
|
|
bff1852a85 | ||
|
|
7327b448e5 | ||
|
|
eb1e90bb7f | ||
|
|
3905d1cb81 | ||
|
|
cd084e8236 | ||
|
|
5d96484501 | ||
|
|
6747a497fc | ||
|
|
2df65527d3 | ||
|
|
d0b69455e2 | ||
|
|
6028b1f5c0 | ||
|
|
658cf11299 | ||
|
|
6312df3a07 | ||
|
|
9de7a00373 | ||
|
|
325a666a8b | ||
|
|
2149f5e36d | ||
|
|
009e1da5f1 | ||
|
|
6101493f12 | ||
|
|
4b5c2b43e9 | ||
|
|
bd402cdda5 | ||
|
|
0c30646a2d | ||
|
|
53792b9a1d | ||
|
|
48f86e66f4 | ||
|
|
fd422b5d0d | ||
|
|
17cf72834d | ||
|
|
3122b506fd | ||
|
|
a31305b7ee | ||
|
|
4f26a7aa73 | ||
|
|
a06ae0d2e7 | ||
|
|
ab97ac5a77 | ||
|
|
ac8bf96eee | ||
|
|
d4165f5be6 | ||
|
|
994c35f62c | ||
|
|
4ce6bc94c3 | ||
|
|
ba21d274ec | ||
|
|
d73a97ffa2 | ||
|
|
f2ec43e4f9 | ||
|
|
3e5d3735dc | ||
|
|
172d51e061 | ||
|
|
1ad31c92ac | ||
|
|
065fc5b87b | ||
|
|
3798c56e8c | ||
|
|
c591433248 | ||
|
|
f0186593a1 | ||
|
|
6cf02b9b5a | ||
|
|
2dc75b1ac1 | ||
|
|
d3f7ef4ac4 | ||
|
|
9a6cc78332 | ||
|
|
ea8762e99b | ||
|
|
cff0a8718e | ||
|
|
abca73106d | ||
|
|
afb99fbaf1 | ||
|
|
4d973ffb01 | ||
|
|
8841e9bd6b | ||
|
|
3d4b9f0665 | ||
|
|
c48039f97f | ||
|
|
8f7b11f089 | ||
|
|
ae670a7819 | ||
|
|
a5c224e4b0 | ||
|
|
0785f6e920 | ||
|
|
cf4a935575 | ||
|
|
521316bb8c | ||
|
|
d357280003 | ||
|
|
adf8c2244c | ||
|
|
ebfdb9ce3b | ||
|
|
784992f347 | ||
|
|
5218dd41b9 | ||
|
|
07e70409c7 | ||
|
|
07ba17422b | ||
|
|
d45324bb83 | ||
|
|
ced64129da | ||
|
|
1e14743391 | ||
|
|
a0bb754c8c | ||
|
|
851031239d | ||
|
|
3811b509ef | ||
|
|
abb835d22d | ||
|
|
f2a046ff24 | ||
|
|
bd6d4a91a3 | ||
|
|
21beca8fd5 | ||
|
|
0a86eda853 | ||
|
|
60a061e38a | ||
|
|
ab71fcfc49 | ||
|
|
864622c1dc | ||
|
|
8668622d66 | ||
|
|
53dd277cfe | ||
|
|
0e8e8c7a47 | ||
|
|
47da5eb6e8 | ||
|
|
37dcde2afc | ||
|
|
e31627c7c2 | ||
|
|
57c98d86ba | ||
|
|
0f7dfe084a | ||
|
|
afc1632830 | ||
|
|
56eee2c2d2 | ||
|
|
fc558a8eef | ||
|
|
c68cadfb84 | ||
|
|
95d93a2532 | ||
|
|
59b2023124 | ||
|
|
a672f17136 | ||
|
|
1de59668e4 | ||
|
|
26243b99e8 | ||
|
|
fce1423d05 | ||
|
|
3656d3d7ad | ||
|
|
581929bc01 | ||
|
|
11d8188415 | ||
|
|
36c98d18e9 | ||
|
|
0cf87e650d | ||
|
|
baef8d77f9 | ||
|
|
b74ab46820 | ||
|
|
533b4c53e0 | ||
|
|
c2d668c3eb | ||
|
|
1a5d5ddffa | ||
|
|
9de0d91f9a | ||
|
|
3db73ff721 | ||
|
|
9ffb48ee02 | ||
|
|
1f2a317ac2 | ||
|
|
a618d289d8 | ||
|
|
461d7b2342 | ||
|
|
4273161c0f | ||
|
|
54d42b33eb | ||
|
|
2c2c32c64b | ||
|
|
65e861822c | ||
|
|
12135d2aa8 | ||
|
|
f75c807580 | ||
|
|
9ea7ea79e9 | ||
|
|
5bbb349d8a | ||
|
|
ea09fcecb7 | ||
|
|
9ccb7600f9 | ||
|
|
ee17cf461a | ||
|
|
43cb124d97 | ||
|
|
76889fde26 | ||
|
|
7780d9b32b | ||
|
|
4a703a02cb | ||
|
|
a969d09782 | ||
|
|
0bc778130f | ||
|
|
df3d532495 | ||
|
|
f4f8fc051e |
35
.cursor/rules/emcn-components.mdc
Normal file
35
.cursor/rules/emcn-components.mdc
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
---
|
||||||
|
description: EMCN component library patterns
|
||||||
|
globs: ["apps/sim/components/emcn/**"]
|
||||||
|
---
|
||||||
|
|
||||||
|
# EMCN Components
|
||||||
|
|
||||||
|
Import from `@/components/emcn`, never from subpaths (except CSS files).
|
||||||
|
|
||||||
|
## CVA vs Direct Styles
|
||||||
|
|
||||||
|
**Use CVA when:** 2+ variants (primary/secondary, sm/md/lg)
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
const buttonVariants = cva('base-classes', {
|
||||||
|
variants: { variant: { default: '...', primary: '...' } }
|
||||||
|
})
|
||||||
|
export { Button, buttonVariants }
|
||||||
|
```
|
||||||
|
|
||||||
|
**Use direct className when:** Single consistent style, no variations
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
function Label({ className, ...props }) {
|
||||||
|
return <Primitive className={cn('style-classes', className)} {...props} />
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
|
||||||
|
- Use Radix UI primitives for accessibility
|
||||||
|
- Export component and variants (if using CVA)
|
||||||
|
- TSDoc with usage examples
|
||||||
|
- Consistent tokens: `font-medium`, `text-[12px]`, `rounded-[4px]`
|
||||||
|
- `transition-colors` for hover states
|
||||||
20
.cursor/rules/global.mdc
Normal file
20
.cursor/rules/global.mdc
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
description: Global coding standards that apply to all files
|
||||||
|
alwaysApply: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# Global Standards
|
||||||
|
|
||||||
|
You are a professional software engineer. All code must follow best practices: accurate, readable, clean, and efficient.
|
||||||
|
|
||||||
|
## Logging
|
||||||
|
Import `createLogger` from `sim/logger`. Use `logger.info`, `logger.warn`, `logger.error` instead of `console.log`.
|
||||||
|
|
||||||
|
## Comments
|
||||||
|
Use TSDoc for documentation. No `====` separators. No non-TSDoc comments.
|
||||||
|
|
||||||
|
## Styling
|
||||||
|
Never update global styles. Keep all styling local to components.
|
||||||
|
|
||||||
|
## Package Manager
|
||||||
|
Use `bun` and `bunx`, not `npm` and `npx`.
|
||||||
56
.cursor/rules/sim-architecture.mdc
Normal file
56
.cursor/rules/sim-architecture.mdc
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
---
|
||||||
|
description: Core architecture principles for the Sim app
|
||||||
|
globs: ["apps/sim/**"]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Sim App Architecture
|
||||||
|
|
||||||
|
## Core Principles
|
||||||
|
1. **Single Responsibility**: Each component, hook, store has one clear purpose
|
||||||
|
2. **Composition Over Complexity**: Break down complex logic into smaller pieces
|
||||||
|
3. **Type Safety First**: TypeScript interfaces for all props, state, return types
|
||||||
|
4. **Predictable State**: Zustand for global state, useState for UI-only concerns
|
||||||
|
|
||||||
|
## Root-Level Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
apps/sim/
|
||||||
|
├── app/ # Next.js app router (pages, API routes)
|
||||||
|
├── blocks/ # Block definitions and registry
|
||||||
|
├── components/ # Shared UI (emcn/, ui/)
|
||||||
|
├── executor/ # Workflow execution engine
|
||||||
|
├── hooks/ # Shared hooks (queries/, selectors/)
|
||||||
|
├── lib/ # App-wide utilities
|
||||||
|
├── providers/ # LLM provider integrations
|
||||||
|
├── stores/ # Zustand stores
|
||||||
|
├── tools/ # Tool definitions
|
||||||
|
└── triggers/ # Trigger definitions
|
||||||
|
```
|
||||||
|
|
||||||
|
## Feature Organization
|
||||||
|
|
||||||
|
Features live under `app/workspace/[workspaceId]/`:
|
||||||
|
|
||||||
|
```
|
||||||
|
feature/
|
||||||
|
├── components/ # Feature components
|
||||||
|
├── hooks/ # Feature-scoped hooks
|
||||||
|
├── utils/ # Feature-scoped utilities (2+ consumers)
|
||||||
|
├── feature.tsx # Main component
|
||||||
|
└── page.tsx # Next.js page entry
|
||||||
|
```
|
||||||
|
|
||||||
|
## Naming Conventions
|
||||||
|
- **Components**: PascalCase (`WorkflowList`)
|
||||||
|
- **Hooks**: `use` prefix (`useWorkflowOperations`)
|
||||||
|
- **Files**: kebab-case (`workflow-list.tsx`)
|
||||||
|
- **Stores**: `stores/feature/store.ts`
|
||||||
|
- **Constants**: SCREAMING_SNAKE_CASE
|
||||||
|
- **Interfaces**: PascalCase with suffix (`WorkflowListProps`)
|
||||||
|
|
||||||
|
## Utils Rules
|
||||||
|
|
||||||
|
- **Never create `utils.ts` for single consumer** - inline it
|
||||||
|
- **Create `utils.ts` when** 2+ files need the same helper
|
||||||
|
- **Check existing sources** before duplicating (`lib/` has many utilities)
|
||||||
|
- **Location**: `lib/` (app-wide) → `feature/utils/` (feature-scoped) → inline (single-use)
|
||||||
48
.cursor/rules/sim-components.mdc
Normal file
48
.cursor/rules/sim-components.mdc
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
---
|
||||||
|
description: Component patterns and structure for React components
|
||||||
|
globs: ["apps/sim/**/*.tsx"]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Component Patterns
|
||||||
|
|
||||||
|
## Structure Order
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
'use client' // Only if using hooks
|
||||||
|
|
||||||
|
// Imports (external → internal)
|
||||||
|
// Constants at module level
|
||||||
|
const CONFIG = { SPACING: 8 } as const
|
||||||
|
|
||||||
|
// Props interface
|
||||||
|
interface ComponentProps {
|
||||||
|
requiredProp: string
|
||||||
|
optionalProp?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Component({ requiredProp, optionalProp = false }: ComponentProps) {
|
||||||
|
// a. Refs
|
||||||
|
// b. External hooks (useParams, useRouter)
|
||||||
|
// c. Store hooks
|
||||||
|
// d. Custom hooks
|
||||||
|
// e. Local state
|
||||||
|
// f. useMemo
|
||||||
|
// g. useCallback
|
||||||
|
// h. useEffect
|
||||||
|
// i. Return JSX
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
|
||||||
|
1. `'use client'` only when using React hooks
|
||||||
|
2. Always define props interface
|
||||||
|
3. Extract constants with `as const`
|
||||||
|
4. Semantic HTML (`aside`, `nav`, `article`)
|
||||||
|
5. Optional chain callbacks: `onAction?.(id)`
|
||||||
|
|
||||||
|
## Component Extraction
|
||||||
|
|
||||||
|
**Extract when:** 50+ lines, used in 2+ files, or has own state/logic
|
||||||
|
|
||||||
|
**Keep inline when:** < 10 lines, single use, purely presentational
|
||||||
54
.cursor/rules/sim-hooks.mdc
Normal file
54
.cursor/rules/sim-hooks.mdc
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
---
|
||||||
|
description: Custom hook patterns and best practices
|
||||||
|
globs: ["apps/sim/**/use-*.ts", "apps/sim/**/hooks/**/*.ts"]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Hook Patterns
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface UseFeatureProps {
|
||||||
|
id: string
|
||||||
|
onSuccess?: (result: Result) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useFeature({ id, onSuccess }: UseFeatureProps) {
|
||||||
|
// 1. Refs for stable dependencies
|
||||||
|
const idRef = useRef(id)
|
||||||
|
const onSuccessRef = useRef(onSuccess)
|
||||||
|
|
||||||
|
// 2. State
|
||||||
|
const [data, setData] = useState<Data | null>(null)
|
||||||
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
|
|
||||||
|
// 3. Sync refs
|
||||||
|
useEffect(() => {
|
||||||
|
idRef.current = id
|
||||||
|
onSuccessRef.current = onSuccess
|
||||||
|
}, [id, onSuccess])
|
||||||
|
|
||||||
|
// 4. Operations (useCallback with empty deps when using refs)
|
||||||
|
const fetchData = useCallback(async () => {
|
||||||
|
setIsLoading(true)
|
||||||
|
try {
|
||||||
|
const result = await fetch(`/api/${idRef.current}`).then(r => r.json())
|
||||||
|
setData(result)
|
||||||
|
onSuccessRef.current?.(result)
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return { data, isLoading, fetchData }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
|
||||||
|
1. Single responsibility per hook
|
||||||
|
2. Props interface required
|
||||||
|
3. Refs for stable callback dependencies
|
||||||
|
4. Wrap returned functions in useCallback
|
||||||
|
5. Always try/catch async operations
|
||||||
|
6. Track loading/error states
|
||||||
61
.cursor/rules/sim-imports.mdc
Normal file
61
.cursor/rules/sim-imports.mdc
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
---
|
||||||
|
description: Import patterns for the Sim application
|
||||||
|
globs: ["apps/sim/**/*.ts", "apps/sim/**/*.tsx"]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Import Patterns
|
||||||
|
|
||||||
|
## Absolute Imports
|
||||||
|
|
||||||
|
**Always use absolute imports.** Never use relative imports.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ✓ Good
|
||||||
|
import { useWorkflowStore } from '@/stores/workflows/store'
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
|
||||||
|
// ✗ Bad
|
||||||
|
import { useWorkflowStore } from '../../../stores/workflows/store'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Barrel Exports
|
||||||
|
|
||||||
|
Use barrel exports (`index.ts`) when a folder has 3+ exports. Import from barrel, not individual files.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ✓ Good
|
||||||
|
import { Dashboard, Sidebar } from '@/app/workspace/[workspaceId]/logs/components'
|
||||||
|
|
||||||
|
// ✗ Bad
|
||||||
|
import { Dashboard } from '@/app/workspace/[workspaceId]/logs/components/dashboard/dashboard'
|
||||||
|
```
|
||||||
|
|
||||||
|
## No Re-exports
|
||||||
|
|
||||||
|
Do not re-export from non-barrel files. Import directly from the source.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ✓ Good - import from where it's declared
|
||||||
|
import { CORE_TRIGGER_TYPES } from '@/stores/logs/filters/types'
|
||||||
|
|
||||||
|
// ✗ Bad - re-exporting in utils.ts then importing from there
|
||||||
|
import { CORE_TRIGGER_TYPES } from '@/app/workspace/.../utils'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Import Order
|
||||||
|
|
||||||
|
1. React/core libraries
|
||||||
|
2. External libraries
|
||||||
|
3. UI components (`@/components/emcn`, `@/components/ui`)
|
||||||
|
4. Utilities (`@/lib/...`)
|
||||||
|
5. Stores (`@/stores/...`)
|
||||||
|
6. Feature imports
|
||||||
|
7. CSS imports
|
||||||
|
|
||||||
|
## Type Imports
|
||||||
|
|
||||||
|
Use `type` keyword for type-only imports:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import type { WorkflowLog } from '@/stores/logs/types'
|
||||||
|
```
|
||||||
207
.cursor/rules/sim-integrations.mdc
Normal file
207
.cursor/rules/sim-integrations.mdc
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
---
|
||||||
|
description: Adding new integrations (tools, blocks, triggers)
|
||||||
|
globs: ["apps/sim/tools/**", "apps/sim/blocks/**", "apps/sim/triggers/**"]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Adding Integrations
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Adding a new integration typically requires:
|
||||||
|
1. **Tools** - API operations (`tools/{service}/`)
|
||||||
|
2. **Block** - UI component (`blocks/blocks/{service}.ts`)
|
||||||
|
3. **Icon** - SVG icon (`components/icons.tsx`)
|
||||||
|
4. **Trigger** (optional) - Webhooks/polling (`triggers/{service}/`)
|
||||||
|
|
||||||
|
Always look up the service's API docs first.
|
||||||
|
|
||||||
|
## 1. Tools (`tools/{service}/`)
|
||||||
|
|
||||||
|
```
|
||||||
|
tools/{service}/
|
||||||
|
├── index.ts # Export all tools
|
||||||
|
├── types.ts # Params/response types
|
||||||
|
├── {action}.ts # Individual tool (e.g., send_message.ts)
|
||||||
|
└── ...
|
||||||
|
```
|
||||||
|
|
||||||
|
**Tool file structure:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// tools/{service}/{action}.ts
|
||||||
|
import type { {Service}Params, {Service}Response } from '@/tools/{service}/types'
|
||||||
|
import type { ToolConfig } from '@/tools/types'
|
||||||
|
|
||||||
|
export const {service}{Action}Tool: ToolConfig<{Service}Params, {Service}Response> = {
|
||||||
|
id: '{service}_{action}',
|
||||||
|
name: '{Service} {Action}',
|
||||||
|
description: 'What this tool does',
|
||||||
|
version: '1.0.0',
|
||||||
|
oauth: { required: true, provider: '{service}' }, // if OAuth
|
||||||
|
params: { /* param definitions */ },
|
||||||
|
request: {
|
||||||
|
url: '/api/tools/{service}/{action}',
|
||||||
|
method: 'POST',
|
||||||
|
headers: () => ({ 'Content-Type': 'application/json' }),
|
||||||
|
body: (params) => ({ ...params }),
|
||||||
|
},
|
||||||
|
transformResponse: async (response) => {
|
||||||
|
const data = await response.json()
|
||||||
|
if (!data.success) throw new Error(data.error)
|
||||||
|
return { success: true, output: data.output }
|
||||||
|
},
|
||||||
|
outputs: { /* output definitions */ },
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Register in `tools/registry.ts`:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { {service}{Action}Tool } from '@/tools/{service}'
|
||||||
|
// Add to registry object
|
||||||
|
{service}_{action}: {service}{Action}Tool,
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Block (`blocks/blocks/{service}.ts`)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { {Service}Icon } from '@/components/icons'
|
||||||
|
import type { BlockConfig } from '@/blocks/types'
|
||||||
|
import type { {Service}Response } from '@/tools/{service}/types'
|
||||||
|
|
||||||
|
export const {Service}Block: BlockConfig<{Service}Response> = {
|
||||||
|
type: '{service}',
|
||||||
|
name: '{Service}',
|
||||||
|
description: 'Short description',
|
||||||
|
longDescription: 'Detailed description',
|
||||||
|
category: 'tools',
|
||||||
|
bgColor: '#hexcolor',
|
||||||
|
icon: {Service}Icon,
|
||||||
|
subBlocks: [ /* see SubBlock Properties below */ ],
|
||||||
|
tools: {
|
||||||
|
access: ['{service}_{action}', ...],
|
||||||
|
config: {
|
||||||
|
tool: (params) => `{service}_${params.operation}`,
|
||||||
|
params: (params) => ({ ...params }),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
inputs: { /* input definitions */ },
|
||||||
|
outputs: { /* output definitions */ },
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### SubBlock Properties
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
{
|
||||||
|
id: 'fieldName', // Unique identifier
|
||||||
|
title: 'Field Label', // UI label
|
||||||
|
type: 'short-input', // See SubBlock Types below
|
||||||
|
placeholder: 'Hint text',
|
||||||
|
required: true, // See Required below
|
||||||
|
condition: { ... }, // See Condition below
|
||||||
|
dependsOn: ['otherField'], // See DependsOn below
|
||||||
|
mode: 'basic', // 'basic' | 'advanced' | 'both' | 'trigger'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**SubBlock Types:** `short-input`, `long-input`, `dropdown`, `code`, `switch`, `slider`, `oauth-input`, `channel-selector`, `user-selector`, `file-upload`, etc.
|
||||||
|
|
||||||
|
### `condition` - Show/hide based on another field
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Show when operation === 'send'
|
||||||
|
condition: { field: 'operation', value: 'send' }
|
||||||
|
|
||||||
|
// Show when operation is 'send' OR 'read'
|
||||||
|
condition: { field: 'operation', value: ['send', 'read'] }
|
||||||
|
|
||||||
|
// Show when operation !== 'send'
|
||||||
|
condition: { field: 'operation', value: 'send', not: true }
|
||||||
|
|
||||||
|
// Complex: NOT in list AND another condition
|
||||||
|
condition: {
|
||||||
|
field: 'operation',
|
||||||
|
value: ['list_channels', 'list_users'],
|
||||||
|
not: true,
|
||||||
|
and: { field: 'destinationType', value: 'dm', not: true }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `required` - Field validation
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Always required
|
||||||
|
required: true
|
||||||
|
|
||||||
|
// Conditionally required (same syntax as condition)
|
||||||
|
required: { field: 'operation', value: 'send' }
|
||||||
|
```
|
||||||
|
|
||||||
|
### `dependsOn` - Clear field when dependencies change
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Clear when credential changes
|
||||||
|
dependsOn: ['credential']
|
||||||
|
|
||||||
|
// Clear when authMethod changes AND (credential OR botToken) changes
|
||||||
|
dependsOn: { all: ['authMethod'], any: ['credential', 'botToken'] }
|
||||||
|
```
|
||||||
|
|
||||||
|
### `mode` - When to show field
|
||||||
|
|
||||||
|
- `'basic'` - Only in basic mode (default UI)
|
||||||
|
- `'advanced'` - Only in advanced mode (manual input)
|
||||||
|
- `'both'` - Show in both modes (default)
|
||||||
|
- `'trigger'` - Only when block is used as trigger
|
||||||
|
|
||||||
|
**Register in `blocks/registry.ts`:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { {Service}Block } from '@/blocks/blocks/{service}'
|
||||||
|
// Add to registry object (alphabetically)
|
||||||
|
{service}: {Service}Block,
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. Icon (`components/icons.tsx`)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export function {Service}Icon(props: SVGProps<SVGSVGElement>) {
|
||||||
|
return (
|
||||||
|
<svg {...props} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
{/* SVG path from service's brand assets */}
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. Trigger (`triggers/{service}/`) - Optional
|
||||||
|
|
||||||
|
```
|
||||||
|
triggers/{service}/
|
||||||
|
├── index.ts # Export all triggers
|
||||||
|
├── webhook.ts # Webhook handler
|
||||||
|
├── utils.ts # Shared utilities
|
||||||
|
└── {event}.ts # Specific event handlers
|
||||||
|
```
|
||||||
|
|
||||||
|
**Register in `triggers/registry.ts`:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { {service}WebhookTrigger } from '@/triggers/{service}'
|
||||||
|
// Add to TRIGGER_REGISTRY
|
||||||
|
{service}_webhook: {service}WebhookTrigger,
|
||||||
|
```
|
||||||
|
|
||||||
|
## Checklist
|
||||||
|
|
||||||
|
- [ ] Look up API docs for the service
|
||||||
|
- [ ] Create `tools/{service}/types.ts` with proper types
|
||||||
|
- [ ] Create tool files for each operation
|
||||||
|
- [ ] Create `tools/{service}/index.ts` barrel export
|
||||||
|
- [ ] Register tools in `tools/registry.ts`
|
||||||
|
- [ ] Add icon to `components/icons.tsx`
|
||||||
|
- [ ] Create block in `blocks/blocks/{service}.ts`
|
||||||
|
- [ ] Register block in `blocks/registry.ts`
|
||||||
|
- [ ] (Optional) Create triggers in `triggers/{service}/`
|
||||||
|
- [ ] (Optional) Register triggers in `triggers/registry.ts`
|
||||||
66
.cursor/rules/sim-queries.mdc
Normal file
66
.cursor/rules/sim-queries.mdc
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
---
|
||||||
|
description: React Query patterns for the Sim application
|
||||||
|
globs: ["apps/sim/hooks/queries/**/*.ts"]
|
||||||
|
---
|
||||||
|
|
||||||
|
# React Query Patterns
|
||||||
|
|
||||||
|
All React Query hooks live in `hooks/queries/`.
|
||||||
|
|
||||||
|
## Query Key Factory
|
||||||
|
|
||||||
|
Every query file defines a keys factory:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export const entityKeys = {
|
||||||
|
all: ['entity'] as const,
|
||||||
|
list: (workspaceId?: string) => [...entityKeys.all, 'list', workspaceId ?? ''] as const,
|
||||||
|
detail: (id?: string) => [...entityKeys.all, 'detail', id ?? ''] as const,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 1. Query keys factory
|
||||||
|
// 2. Types (if needed)
|
||||||
|
// 3. Private fetch functions
|
||||||
|
// 4. Exported hooks
|
||||||
|
```
|
||||||
|
|
||||||
|
## Query Hook
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export function useEntityList(workspaceId?: string, options?: { enabled?: boolean }) {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: entityKeys.list(workspaceId),
|
||||||
|
queryFn: () => fetchEntities(workspaceId as string),
|
||||||
|
enabled: Boolean(workspaceId) && (options?.enabled ?? true),
|
||||||
|
staleTime: 60 * 1000,
|
||||||
|
placeholderData: keepPreviousData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Mutation Hook
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export function useCreateEntity() {
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async (variables) => { /* fetch POST */ },
|
||||||
|
onSuccess: () => queryClient.invalidateQueries({ queryKey: entityKeys.all }),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Optimistic Updates
|
||||||
|
|
||||||
|
For optimistic mutations syncing with Zustand, use `createOptimisticMutationHandlers` from `@/hooks/queries/utils/optimistic-mutation`.
|
||||||
|
|
||||||
|
## Naming
|
||||||
|
|
||||||
|
- **Keys**: `entityKeys`
|
||||||
|
- **Query hooks**: `useEntity`, `useEntityList`
|
||||||
|
- **Mutation hooks**: `useCreateEntity`, `useUpdateEntity`
|
||||||
|
- **Fetch functions**: `fetchEntity` (private)
|
||||||
70
.cursor/rules/sim-stores.mdc
Normal file
70
.cursor/rules/sim-stores.mdc
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
---
|
||||||
|
description: Zustand store patterns
|
||||||
|
globs: ["apps/sim/**/store.ts", "apps/sim/**/stores/**/*.ts"]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Zustand Store Patterns
|
||||||
|
|
||||||
|
Stores live in `stores/`. Complex stores split into `store.ts` + `types.ts`.
|
||||||
|
|
||||||
|
## Basic Store
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { create } from 'zustand'
|
||||||
|
import { devtools } from 'zustand/middleware'
|
||||||
|
import type { FeatureState } from '@/stores/feature/types'
|
||||||
|
|
||||||
|
const initialState = { items: [] as Item[], activeId: null as string | null }
|
||||||
|
|
||||||
|
export const useFeatureStore = create<FeatureState>()(
|
||||||
|
devtools(
|
||||||
|
(set, get) => ({
|
||||||
|
...initialState,
|
||||||
|
setItems: (items) => set({ items }),
|
||||||
|
addItem: (item) => set((state) => ({ items: [...state.items, item] })),
|
||||||
|
reset: () => set(initialState),
|
||||||
|
}),
|
||||||
|
{ name: 'feature-store' }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Persisted Store
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { create } from 'zustand'
|
||||||
|
import { persist } from 'zustand/middleware'
|
||||||
|
|
||||||
|
export const useFeatureStore = create<FeatureState>()(
|
||||||
|
persist(
|
||||||
|
(set) => ({
|
||||||
|
width: 300,
|
||||||
|
setWidth: (width) => set({ width }),
|
||||||
|
_hasHydrated: false,
|
||||||
|
setHasHydrated: (v) => set({ _hasHydrated: v }),
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
name: 'feature-state',
|
||||||
|
partialize: (state) => ({ width: state.width }),
|
||||||
|
onRehydrateStorage: () => (state) => state?.setHasHydrated(true),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
|
||||||
|
1. Use `devtools` middleware (named stores)
|
||||||
|
2. Use `persist` only when data should survive reload
|
||||||
|
3. `partialize` to persist only necessary state
|
||||||
|
4. `_hasHydrated` pattern for persisted stores needing hydration tracking
|
||||||
|
5. Immutable updates only
|
||||||
|
6. `set((state) => ...)` when depending on previous state
|
||||||
|
7. Provide `reset()` action
|
||||||
|
|
||||||
|
## Outside React
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const items = useFeatureStore.getState().items
|
||||||
|
useFeatureStore.setState({ items: newItems })
|
||||||
|
```
|
||||||
40
.cursor/rules/sim-styling.mdc
Normal file
40
.cursor/rules/sim-styling.mdc
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
---
|
||||||
|
description: Tailwind CSS and styling conventions
|
||||||
|
globs: ["apps/sim/**/*.tsx", "apps/sim/**/*.css"]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Styling Rules
|
||||||
|
|
||||||
|
## Tailwind
|
||||||
|
|
||||||
|
1. **No inline styles** - Use Tailwind classes
|
||||||
|
2. **No duplicate dark classes** - Skip `dark:` when value matches light mode
|
||||||
|
3. **Exact values** - `text-[14px]`, `h-[26px]`
|
||||||
|
4. **Transitions** - `transition-colors` for interactive states
|
||||||
|
|
||||||
|
## Conditional Classes
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
<div className={cn(
|
||||||
|
'base-classes',
|
||||||
|
isActive && 'active-classes',
|
||||||
|
disabled ? 'opacity-60' : 'hover:bg-accent'
|
||||||
|
)} />
|
||||||
|
```
|
||||||
|
|
||||||
|
## CSS Variables
|
||||||
|
|
||||||
|
For dynamic values (widths, heights) synced with stores:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// In store
|
||||||
|
setWidth: (width) => {
|
||||||
|
set({ width })
|
||||||
|
document.documentElement.style.setProperty('--sidebar-width', `${width}px`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// In component
|
||||||
|
<aside style={{ width: 'var(--sidebar-width)' }} />
|
||||||
|
```
|
||||||
60
.cursor/rules/sim-testing.mdc
Normal file
60
.cursor/rules/sim-testing.mdc
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
---
|
||||||
|
description: Testing patterns with Vitest
|
||||||
|
globs: ["apps/sim/**/*.test.ts", "apps/sim/**/*.test.tsx"]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Testing Patterns
|
||||||
|
|
||||||
|
Use Vitest. Test files live next to source: `feature.ts` → `feature.test.ts`
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
/**
|
||||||
|
* Tests for [feature name]
|
||||||
|
*
|
||||||
|
* @vitest-environment node
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 1. Mocks BEFORE imports
|
||||||
|
vi.mock('@sim/db', () => ({ db: { select: vi.fn() } }))
|
||||||
|
vi.mock('@sim/logger', () => loggerMock)
|
||||||
|
|
||||||
|
// 2. Imports AFTER mocks
|
||||||
|
import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest'
|
||||||
|
import { createSession, loggerMock } from '@sim/testing'
|
||||||
|
import { myFunction } from '@/lib/feature'
|
||||||
|
|
||||||
|
describe('myFunction', () => {
|
||||||
|
beforeEach(() => vi.clearAllMocks())
|
||||||
|
|
||||||
|
it('should do something', () => {
|
||||||
|
expect(myFunction()).toBe(expected)
|
||||||
|
})
|
||||||
|
|
||||||
|
it.concurrent('runs in parallel', () => { ... })
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## @sim/testing Package
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Factories - create test data
|
||||||
|
import { createBlock, createWorkflow, createSession } from '@sim/testing'
|
||||||
|
|
||||||
|
// Mocks - pre-configured mocks
|
||||||
|
import { loggerMock, databaseMock, fetchMock } from '@sim/testing'
|
||||||
|
|
||||||
|
// Builders - fluent API for complex objects
|
||||||
|
import { ExecutionBuilder, WorkflowBuilder } from '@sim/testing'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
|
||||||
|
1. `@vitest-environment node` directive at file top
|
||||||
|
2. **Mocks before imports** - `vi.mock()` calls must come first
|
||||||
|
3. Use `@sim/testing` factories over manual test data
|
||||||
|
4. `it.concurrent` for independent tests (faster)
|
||||||
|
5. `beforeEach(() => vi.clearAllMocks())` to reset state
|
||||||
|
6. Group related tests with nested `describe` blocks
|
||||||
|
7. Test file naming: `*.test.ts` (not `*.spec.ts`)
|
||||||
20
.cursor/rules/sim-typescript.mdc
Normal file
20
.cursor/rules/sim-typescript.mdc
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
description: TypeScript conventions and type safety
|
||||||
|
globs: ["apps/sim/**/*.ts", "apps/sim/**/*.tsx"]
|
||||||
|
---
|
||||||
|
|
||||||
|
# TypeScript Rules
|
||||||
|
|
||||||
|
1. **No `any`** - Use proper types or `unknown` with type guards
|
||||||
|
2. **Props interface** - Always define for components
|
||||||
|
3. **Const assertions** - `as const` for constant objects/arrays
|
||||||
|
4. **Ref types** - Explicit: `useRef<HTMLDivElement>(null)`
|
||||||
|
5. **Type imports** - `import type { X }` for type-only imports
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ✗ Bad
|
||||||
|
const handleClick = (e: any) => {}
|
||||||
|
|
||||||
|
// ✓ Good
|
||||||
|
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {}
|
||||||
|
```
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
# Sim Development Environment Bashrc
|
|
||||||
# This gets sourced by post-create.sh
|
|
||||||
|
|
||||||
# Enhanced prompt with git branch info
|
|
||||||
parse_git_branch() {
|
|
||||||
git branch 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/ (\1)/'
|
|
||||||
}
|
|
||||||
|
|
||||||
export PS1="\[\033[01;32m\]\u@simstudio\[\033[00m\]:\[\033[01;34m\]\w\[\033[33m\]\$(parse_git_branch)\[\033[00m\]\$ "
|
|
||||||
|
|
||||||
# Helpful aliases
|
|
||||||
alias ll="ls -la"
|
|
||||||
alias ..="cd .."
|
|
||||||
alias ...="cd ../.."
|
|
||||||
|
|
||||||
# Database aliases
|
|
||||||
alias pgc="PGPASSWORD=postgres psql -h db -U postgres -d simstudio"
|
|
||||||
alias check-db="PGPASSWORD=postgres psql -h db -U postgres -c '\l'"
|
|
||||||
|
|
||||||
# Sim specific aliases
|
|
||||||
alias logs="cd /workspace/apps/sim && tail -f logs/*.log 2>/dev/null || echo 'No log files found'"
|
|
||||||
alias sim-start="cd /workspace && bun run dev"
|
|
||||||
alias sim-migrate="cd /workspace/apps/sim && bunx drizzle-kit push"
|
|
||||||
alias sim-generate="cd /workspace/apps/sim && bunx drizzle-kit generate"
|
|
||||||
alias sim-rebuild="cd /workspace && bun run build && bun run start"
|
|
||||||
alias docs-dev="cd /workspace/apps/docs && bun run dev"
|
|
||||||
|
|
||||||
# Turbo related commands
|
|
||||||
alias turbo-build="cd /workspace && bunx turbo run build"
|
|
||||||
alias turbo-dev="cd /workspace && bunx turbo run dev"
|
|
||||||
alias turbo-test="cd /workspace && bunx turbo run test"
|
|
||||||
|
|
||||||
# Bun specific commands
|
|
||||||
alias bun-update="cd /workspace && bun update"
|
|
||||||
alias bun-add="cd /workspace && bun add"
|
|
||||||
alias bun-pm="cd /workspace && bun pm"
|
|
||||||
alias bun-canary="bun upgrade --canary"
|
|
||||||
|
|
||||||
# Default to workspace directory
|
|
||||||
cd /workspace 2>/dev/null || true
|
|
||||||
|
|
||||||
# Welcome message - only show once per session
|
|
||||||
if [ -z "$SIM_WELCOME_SHOWN" ]; then
|
|
||||||
export SIM_WELCOME_SHOWN=1
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
||||||
echo "🚀 Welcome to Sim development environment!"
|
|
||||||
echo ""
|
|
||||||
echo "Available commands:"
|
|
||||||
echo " sim-start - Start all apps in development mode"
|
|
||||||
echo " sim-migrate - Push schema changes to the database for sim app"
|
|
||||||
echo " sim-generate - Generate new migrations for sim app"
|
|
||||||
echo " sim-rebuild - Build and start all apps"
|
|
||||||
echo " docs-dev - Start only the docs app in development mode"
|
|
||||||
echo ""
|
|
||||||
echo "Turbo commands:"
|
|
||||||
echo " turbo-build - Build all apps using Turborepo"
|
|
||||||
echo " turbo-dev - Start development mode for all apps"
|
|
||||||
echo " turbo-test - Run tests for all packages"
|
|
||||||
echo ""
|
|
||||||
echo "Bun commands:"
|
|
||||||
echo " bun-update - Update dependencies"
|
|
||||||
echo " bun-add - Add a new dependency"
|
|
||||||
echo " bun-pm - Manage dependencies"
|
|
||||||
echo " bun-canary - Upgrade to the latest canary version of Bun"
|
|
||||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
||||||
echo ""
|
|
||||||
fi
|
|
||||||
@@ -1,38 +1,43 @@
|
|||||||
# Use the latest Bun canary image for development
|
FROM oven/bun:1.3.3-alpine
|
||||||
FROM oven/bun:canary
|
|
||||||
|
|
||||||
# Avoid warnings by switching to noninteractive
|
|
||||||
ENV DEBIAN_FRONTEND=noninteractive
|
|
||||||
|
|
||||||
# Install necessary packages for development
|
# Install necessary packages for development
|
||||||
RUN apt-get update \
|
RUN apk add --no-cache \
|
||||||
&& apt-get -y install --no-install-recommends \
|
git \
|
||||||
git curl wget jq sudo postgresql-client vim nano \
|
curl \
|
||||||
bash-completion ca-certificates lsb-release gnupg \
|
wget \
|
||||||
&& apt-get clean -y \
|
jq \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
sudo \
|
||||||
|
postgresql-client \
|
||||||
|
vim \
|
||||||
|
nano \
|
||||||
|
bash \
|
||||||
|
bash-completion \
|
||||||
|
zsh \
|
||||||
|
zsh-vcs \
|
||||||
|
ca-certificates \
|
||||||
|
shadow
|
||||||
|
|
||||||
# Create a non-root user
|
# Create a non-root user with matching UID/GID
|
||||||
ARG USERNAME=bun
|
ARG USERNAME=bun
|
||||||
ARG USER_UID=1000
|
ARG USER_UID=1000
|
||||||
ARG USER_GID=$USER_UID
|
ARG USER_GID=$USER_UID
|
||||||
|
|
||||||
|
# Create user group if it doesn't exist
|
||||||
|
RUN if ! getent group $USER_GID >/dev/null; then \
|
||||||
|
addgroup -g $USER_GID $USERNAME; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create user if it doesn't exist
|
||||||
|
RUN if ! getent passwd $USER_UID >/dev/null; then \
|
||||||
|
adduser -D -u $USER_UID -G $(getent group $USER_GID | cut -d: -f1) $USERNAME; \
|
||||||
|
fi
|
||||||
|
|
||||||
# Add sudo support
|
# Add sudo support
|
||||||
RUN echo "$USERNAME ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/$USERNAME \
|
RUN echo "$USERNAME ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/$USERNAME \
|
||||||
&& chmod 0440 /etc/sudoers.d/$USERNAME
|
&& chmod 0440 /etc/sudoers.d/$USERNAME
|
||||||
|
|
||||||
# Install global packages for development
|
|
||||||
RUN bun install -g turbo drizzle-kit typescript @types/node
|
|
||||||
|
|
||||||
# Install bun completions
|
|
||||||
RUN bun completions > /etc/bash_completion.d/bun
|
|
||||||
|
|
||||||
# Set up shell environment
|
# Set up shell environment
|
||||||
RUN echo "export PATH=$PATH:/home/$USERNAME/.bun/bin" >> /etc/profile
|
RUN echo "export PATH=\$PATH:/home/$USERNAME/.bun/bin" >> /etc/profile
|
||||||
RUN echo "source /etc/profile" >> /etc/bash.bashrc
|
|
||||||
|
|
||||||
# Switch back to dialog for any ad-hoc use of apt-get
|
|
||||||
ENV DEBIAN_FRONTEND=dialog
|
|
||||||
|
|
||||||
WORKDIR /workspace
|
WORKDIR /workspace
|
||||||
|
|
||||||
|
|||||||
@@ -1,78 +1,75 @@
|
|||||||
# Sim Development Container
|
# Sim Development Container
|
||||||
|
|
||||||
This directory contains configuration files for Visual Studio Code Dev Containers / GitHub Codespaces. Dev containers provide a consistent, isolated development environment for this project.
|
Development container configuration for VS Code Dev Containers and GitHub Codespaces.
|
||||||
|
|
||||||
## Contents
|
## Prerequisites
|
||||||
|
|
||||||
- `devcontainer.json` - The main configuration file that defines the development container settings
|
|
||||||
- `Dockerfile` - Defines the container image and development environment
|
|
||||||
- `docker-compose.yml` - Sets up the application and database containers
|
|
||||||
- `post-create.sh` - Script that runs when the container is created
|
|
||||||
- `.bashrc` - Custom shell configuration with helpful aliases
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
### Prerequisites
|
|
||||||
|
|
||||||
- Visual Studio Code
|
- Visual Studio Code
|
||||||
- Docker installation:
|
- Docker Desktop or Podman Desktop
|
||||||
- Docker Desktop (Windows/macOS)
|
- VS Code Dev Containers extension
|
||||||
- Docker Engine (Linux)
|
|
||||||
- VS Code Remote - Containers extension
|
|
||||||
|
|
||||||
### Getting Started
|
## Getting Started
|
||||||
|
|
||||||
1. Open this project in Visual Studio Code
|
1. Open this project in VS Code
|
||||||
2. When prompted, click "Reopen in Container"
|
2. Click "Reopen in Container" when prompted (or press `F1` → "Dev Containers: Reopen in Container")
|
||||||
- Alternatively, press `F1` and select "Remote-Containers: Reopen in Container"
|
|
||||||
3. Wait for the container to build and initialize
|
3. Wait for the container to build and initialize
|
||||||
4. The post-creation script will automatically:
|
4. Start developing with `sim-start`
|
||||||
|
|
||||||
- Install dependencies
|
The setup script will automatically install dependencies and run migrations.
|
||||||
- Set up environment variables
|
|
||||||
- Run database migrations
|
|
||||||
- Configure helpful aliases
|
|
||||||
|
|
||||||
5. Start the application with `sim-start` (alias for `bun run dev`)
|
## Development Commands
|
||||||
|
|
||||||
### Development Commands
|
### Running Services
|
||||||
|
|
||||||
The development environment includes these helpful aliases:
|
You have two options for running the development environment:
|
||||||
|
|
||||||
|
**Option 1: Run everything together (recommended for most development)**
|
||||||
|
```bash
|
||||||
|
sim-start # Runs both app and socket server using concurrently
|
||||||
|
```
|
||||||
|
|
||||||
|
**Option 2: Run services separately (useful for debugging individual services)**
|
||||||
|
- In the **app** container terminal: `sim-app` (starts Next.js app on port 3000)
|
||||||
|
- In the **realtime** container terminal: `sim-sockets` (starts socket server on port 3002)
|
||||||
|
|
||||||
|
### Other Commands
|
||||||
|
|
||||||
- `sim-start` - Start the development server
|
|
||||||
- `sim-migrate` - Push schema changes to the database
|
- `sim-migrate` - Push schema changes to the database
|
||||||
- `sim-generate` - Generate new migrations
|
- `sim-generate` - Generate new migrations
|
||||||
- `sim-rebuild` - Build and start the production version
|
- `build` - Build the application
|
||||||
- `pgc` - Connect to the PostgreSQL database
|
- `pgc` - Connect to PostgreSQL database
|
||||||
- `check-db` - List all databases
|
|
||||||
|
|
||||||
### Using GitHub Codespaces
|
|
||||||
|
|
||||||
This project is also configured for GitHub Codespaces. To use it:
|
|
||||||
|
|
||||||
1. Go to the GitHub repository
|
|
||||||
2. Click the "Code" button
|
|
||||||
3. Select the "Codespaces" tab
|
|
||||||
4. Click "Create codespace on main"
|
|
||||||
|
|
||||||
This will start a new Codespace with the development environment already set up.
|
|
||||||
|
|
||||||
## Customization
|
|
||||||
|
|
||||||
You can customize the development environment by:
|
|
||||||
|
|
||||||
- Modifying `devcontainer.json` to add VS Code extensions or settings
|
|
||||||
- Updating the `Dockerfile` to install additional packages
|
|
||||||
- Editing `docker-compose.yml` to add services or change configuration
|
|
||||||
- Modifying `.bashrc` to add custom aliases or configurations
|
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
If you encounter issues:
|
**Build errors**: Rebuild the container with `F1` → "Dev Containers: Rebuild Container"
|
||||||
|
|
||||||
1. Rebuild the container: `F1` → "Remote-Containers: Rebuild Container"
|
**Port conflicts**: Ensure ports 3000, 3002, and 5432 are available
|
||||||
2. Check Docker logs for build errors
|
|
||||||
3. Verify Docker Desktop is running
|
|
||||||
4. Ensure all prerequisites are installed
|
|
||||||
|
|
||||||
For more information, see the [VS Code Remote Development documentation](https://code.visualstudio.com/docs/remote/containers).
|
**Container runtime issues**: Verify Docker Desktop or Podman Desktop is running
|
||||||
|
|
||||||
|
## Technical Details
|
||||||
|
|
||||||
|
Services:
|
||||||
|
- **App container** (8GB memory limit) - Main Next.js application
|
||||||
|
- **Realtime container** (4GB memory limit) - Socket.io server for real-time features
|
||||||
|
- **Database** - PostgreSQL with pgvector extension
|
||||||
|
- **Migrations** - Runs automatically on container creation
|
||||||
|
|
||||||
|
You can develop with services running together or independently.
|
||||||
|
|
||||||
|
### Personalization
|
||||||
|
|
||||||
|
**Project commands** (`sim-start`, `sim-app`, etc.) are automatically available via `/workspace/.devcontainer/sim-commands.sh`.
|
||||||
|
|
||||||
|
**Personal shell customization** (aliases, prompts, etc.) should use VS Code's dotfiles feature:
|
||||||
|
1. Create a dotfiles repository (e.g., `github.com/youruser/dotfiles`)
|
||||||
|
2. Add your `.bashrc`, `.zshrc`, or other configs
|
||||||
|
3. Configure in VS Code Settings:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"dotfiles.repository": "youruser/dotfiles",
|
||||||
|
"dotfiles.installCommand": "install.sh"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This separates project-specific commands from personal preferences, following VS Code best practices.
|
||||||
|
|||||||
@@ -13,13 +13,6 @@
|
|||||||
"source.fixAll.biome": "explicit",
|
"source.fixAll.biome": "explicit",
|
||||||
"source.organizeImports.biome": "explicit"
|
"source.organizeImports.biome": "explicit"
|
||||||
},
|
},
|
||||||
"terminal.integrated.defaultProfile.linux": "bash",
|
|
||||||
"terminal.integrated.profiles.linux": {
|
|
||||||
"bash": {
|
|
||||||
"path": "/bin/bash",
|
|
||||||
"args": ["--login"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"terminal.integrated.shellIntegration.enabled": true
|
"terminal.integrated.shellIntegration.enabled": true
|
||||||
},
|
},
|
||||||
"extensions": [
|
"extensions": [
|
||||||
@@ -36,18 +29,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
"forwardPorts": [3000, 5432],
|
"forwardPorts": [3000, 3002, 5432],
|
||||||
|
|
||||||
"postCreateCommand": "bash -c 'bash .devcontainer/post-create.sh || true'",
|
"postCreateCommand": "bash -c 'bash .devcontainer/post-create.sh || true'",
|
||||||
|
|
||||||
"postStartCommand": "bash -c 'if [ ! -f ~/.bashrc ] || ! grep -q \"sim-start\" ~/.bashrc; then cp .devcontainer/.bashrc ~/.bashrc; fi'",
|
"remoteUser": "bun"
|
||||||
|
|
||||||
"remoteUser": "bun",
|
|
||||||
|
|
||||||
"features": {
|
|
||||||
"ghcr.io/devcontainers/features/git:1": {},
|
|
||||||
"ghcr.io/prulloac/devcontainer-features/bun:1": {
|
|
||||||
"version": "latest"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,53 +7,56 @@ services:
|
|||||||
- ..:/workspace:cached
|
- ..:/workspace:cached
|
||||||
- bun-cache:/home/bun/.bun/cache:delegated
|
- bun-cache:/home/bun/.bun/cache:delegated
|
||||||
command: sleep infinity
|
command: sleep infinity
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: 8G
|
||||||
environment:
|
environment:
|
||||||
- NODE_ENV=development
|
- NODE_ENV=development
|
||||||
- DATABASE_URL=postgresql://postgres:postgres@db:5432/simstudio
|
- DATABASE_URL=postgresql://postgres:postgres@db:5432/simstudio
|
||||||
- POSTGRES_URL=postgresql://postgres:postgres@db:5432/simstudio
|
|
||||||
- BETTER_AUTH_URL=http://localhost:3000
|
- BETTER_AUTH_URL=http://localhost:3000
|
||||||
- NEXT_PUBLIC_APP_URL=http://localhost:3000
|
- NEXT_PUBLIC_APP_URL=http://localhost:3000
|
||||||
|
- BETTER_AUTH_SECRET=${BETTER_AUTH_SECRET:-your_auth_secret_here}
|
||||||
|
- ENCRYPTION_KEY=${ENCRYPTION_KEY:-your_encryption_key_here}
|
||||||
|
- COPILOT_API_KEY=${COPILOT_API_KEY}
|
||||||
|
- SIM_AGENT_API_URL=${SIM_AGENT_API_URL}
|
||||||
|
- OLLAMA_URL=${OLLAMA_URL:-http://localhost:11434}
|
||||||
|
- NEXT_PUBLIC_SOCKET_URL=${NEXT_PUBLIC_SOCKET_URL:-http://localhost:3002}
|
||||||
- BUN_INSTALL_CACHE_DIR=/home/bun/.bun/cache
|
- BUN_INSTALL_CACHE_DIR=/home/bun/.bun/cache
|
||||||
depends_on:
|
depends_on:
|
||||||
db:
|
db:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
realtime:
|
|
||||||
condition: service_healthy
|
|
||||||
migrations:
|
migrations:
|
||||||
condition: service_completed_successfully
|
condition: service_completed_successfully
|
||||||
ports:
|
ports:
|
||||||
- "3000:3000"
|
- "3000:3000"
|
||||||
- "3001:3001"
|
- "3001:3001"
|
||||||
working_dir: /workspace
|
working_dir: /workspace
|
||||||
healthcheck:
|
|
||||||
test: ['CMD', 'wget', '--spider', '--quiet', 'http://127.0.0.1:3000']
|
|
||||||
interval: 90s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 3
|
|
||||||
start_period: 10s
|
|
||||||
|
|
||||||
realtime:
|
realtime:
|
||||||
build:
|
build:
|
||||||
context: ..
|
context: ..
|
||||||
dockerfile: .devcontainer/Dockerfile
|
dockerfile: .devcontainer/Dockerfile
|
||||||
|
volumes:
|
||||||
|
- ..:/workspace:cached
|
||||||
|
- bun-cache:/home/bun/.bun/cache:delegated
|
||||||
command: sleep infinity
|
command: sleep infinity
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: 4G
|
||||||
environment:
|
environment:
|
||||||
- NODE_ENV=development
|
- NODE_ENV=development
|
||||||
- DATABASE_URL=postgresql://postgres:postgres@db:5432/simstudio
|
- DATABASE_URL=postgresql://postgres:postgres@db:5432/simstudio
|
||||||
- BETTER_AUTH_URL=http://localhost:3000
|
- BETTER_AUTH_URL=http://localhost:3000
|
||||||
- NEXT_PUBLIC_APP_URL=http://localhost:3000
|
- NEXT_PUBLIC_APP_URL=http://localhost:3000
|
||||||
|
- BETTER_AUTH_SECRET=${BETTER_AUTH_SECRET:-your_auth_secret_here}
|
||||||
depends_on:
|
depends_on:
|
||||||
db:
|
db:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
ports:
|
ports:
|
||||||
- "3002:3002"
|
- "3002:3002"
|
||||||
working_dir: /workspace
|
working_dir: /workspace
|
||||||
healthcheck:
|
|
||||||
test: ['CMD', 'wget', '--spider', '--quiet', 'http://127.0.0.1:3002']
|
|
||||||
interval: 90s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 3
|
|
||||||
start_period: 10s
|
|
||||||
|
|
||||||
migrations:
|
migrations:
|
||||||
build:
|
build:
|
||||||
|
|||||||
@@ -8,11 +8,43 @@ echo "🔧 Setting up Sim development environment..."
|
|||||||
# Change to the workspace root directory
|
# Change to the workspace root directory
|
||||||
cd /workspace
|
cd /workspace
|
||||||
|
|
||||||
# Setup .bashrc
|
# Install global packages for development (done at runtime, not build time)
|
||||||
echo "📄 Setting up .bashrc with aliases..."
|
echo "📦 Installing global development tools..."
|
||||||
cp /workspace/.devcontainer/.bashrc ~/.bashrc
|
bun install -g turbo drizzle-kit typescript @types/node 2>/dev/null || {
|
||||||
# Add to .profile to ensure .bashrc is sourced in non-interactive shells
|
echo "⚠️ Some global packages may already be installed, continuing..."
|
||||||
echo 'if [ -f ~/.bashrc ]; then . ~/.bashrc; fi' >> ~/.profile
|
}
|
||||||
|
|
||||||
|
# Set up bun completions (with proper shell detection)
|
||||||
|
echo "🔧 Setting up shell completions..."
|
||||||
|
if [ -n "$SHELL" ] && [ -f "$SHELL" ]; then
|
||||||
|
SHELL=/bin/bash bun completions 2>/dev/null | sudo tee /etc/bash_completion.d/bun > /dev/null || {
|
||||||
|
echo "⚠️ Could not install bun completions, but continuing..."
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Add project commands to shell profile
|
||||||
|
echo "📄 Setting up project commands..."
|
||||||
|
# Add sourcing of sim-commands.sh to user's shell config files if they exist
|
||||||
|
for rcfile in ~/.bashrc ~/.zshrc; do
|
||||||
|
if [ -f "$rcfile" ]; then
|
||||||
|
# Check if already added
|
||||||
|
if ! grep -q "sim-commands.sh" "$rcfile"; then
|
||||||
|
echo "" >> "$rcfile"
|
||||||
|
echo "# Sim project commands" >> "$rcfile"
|
||||||
|
echo "if [ -f /workspace/.devcontainer/sim-commands.sh ]; then" >> "$rcfile"
|
||||||
|
echo " source /workspace/.devcontainer/sim-commands.sh" >> "$rcfile"
|
||||||
|
echo "fi" >> "$rcfile"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# If no rc files exist yet, create a minimal one
|
||||||
|
if [ ! -f ~/.bashrc ] && [ ! -f ~/.zshrc ]; then
|
||||||
|
echo "# Source Sim project commands" > ~/.bashrc
|
||||||
|
echo "if [ -f /workspace/.devcontainer/sim-commands.sh ]; then" >> ~/.bashrc
|
||||||
|
echo " source /workspace/.devcontainer/sim-commands.sh" >> ~/.bashrc
|
||||||
|
echo "fi" >> ~/.bashrc
|
||||||
|
fi
|
||||||
|
|
||||||
# Clean and reinstall dependencies to ensure platform compatibility
|
# Clean and reinstall dependencies to ensure platform compatibility
|
||||||
echo "📦 Cleaning and reinstalling dependencies..."
|
echo "📦 Cleaning and reinstalling dependencies..."
|
||||||
@@ -29,18 +61,12 @@ chmod 700 ~/.bun ~/.bun/cache
|
|||||||
|
|
||||||
# Install dependencies with platform-specific binaries
|
# Install dependencies with platform-specific binaries
|
||||||
echo "Installing dependencies with Bun..."
|
echo "Installing dependencies with Bun..."
|
||||||
bun install || {
|
bun install
|
||||||
echo "⚠️ bun install had issues but continuing setup..."
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check for native dependencies
|
# Check for native dependencies
|
||||||
echo "Checking for native dependencies compatibility..."
|
echo "Checking for native dependencies compatibility..."
|
||||||
NATIVE_DEPS=$(grep '"trustedDependencies"' apps/sim/package.json || echo "")
|
if grep -q '"trustedDependencies"' apps/sim/package.json 2>/dev/null; then
|
||||||
if [ ! -z "$NATIVE_DEPS" ]; then
|
echo "⚠️ Native dependencies detected. Bun will handle compatibility during install."
|
||||||
echo "⚠️ Native dependencies detected. Ensuring compatibility with Bun..."
|
|
||||||
for pkg in $(echo $NATIVE_DEPS | grep -oP '"[^"]*"' | tr -d '"' | grep -v "trustedDependencies"); do
|
|
||||||
echo "Checking compatibility for $pkg..."
|
|
||||||
done
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Set up environment variables if .env doesn't exist for the sim app
|
# Set up environment variables if .env doesn't exist for the sim app
|
||||||
@@ -82,23 +108,6 @@ echo "Waiting for database to be ready..."
|
|||||||
fi
|
fi
|
||||||
) || echo "⚠️ Database setup had issues but continuing..."
|
) || echo "⚠️ Database setup had issues but continuing..."
|
||||||
|
|
||||||
# Add additional helpful aliases to .bashrc
|
|
||||||
cat << EOF >> ~/.bashrc
|
|
||||||
|
|
||||||
# Additional Sim Development Aliases
|
|
||||||
alias migrate="cd /workspace/apps/sim && DATABASE_URL=postgresql://postgres:postgres@db:5432/simstudio bunx drizzle-kit push"
|
|
||||||
alias generate="cd /workspace/apps/sim && bunx drizzle-kit generate"
|
|
||||||
alias dev="cd /workspace && bun run dev"
|
|
||||||
alias build="cd /workspace && bun run build"
|
|
||||||
alias start="cd /workspace && bun run dev"
|
|
||||||
alias lint="cd /workspace/apps/sim && bun run lint"
|
|
||||||
alias test="cd /workspace && bun run test"
|
|
||||||
alias bun-update="cd /workspace && bun update"
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# Source the .bashrc to make aliases available immediately
|
|
||||||
. ~/.bashrc
|
|
||||||
|
|
||||||
# Clear the welcome message flag to ensure it shows after setup
|
# Clear the welcome message flag to ensure it shows after setup
|
||||||
unset SIM_WELCOME_SHOWN
|
unset SIM_WELCOME_SHOWN
|
||||||
|
|
||||||
|
|||||||
42
.devcontainer/sim-commands.sh
Executable file
42
.devcontainer/sim-commands.sh
Executable file
@@ -0,0 +1,42 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Sim Project Commands
|
||||||
|
# Source this file to add project-specific commands to your shell
|
||||||
|
# Add to your ~/.bashrc or ~/.zshrc: source /workspace/.devcontainer/sim-commands.sh
|
||||||
|
|
||||||
|
# Project-specific aliases for Sim development
|
||||||
|
alias sim-start="cd /workspace && bun run dev:full"
|
||||||
|
alias sim-app="cd /workspace && bun run dev"
|
||||||
|
alias sim-sockets="cd /workspace && bun run dev:sockets"
|
||||||
|
alias sim-migrate="cd /workspace/apps/sim && bunx drizzle-kit push"
|
||||||
|
alias sim-generate="cd /workspace/apps/sim && bunx drizzle-kit generate"
|
||||||
|
alias sim-rebuild="cd /workspace && bun run build && bun run start"
|
||||||
|
alias docs-dev="cd /workspace/apps/docs && bun run dev"
|
||||||
|
|
||||||
|
# Database connection helpers
|
||||||
|
alias pgc="PGPASSWORD=postgres psql -h db -U postgres -d simstudio"
|
||||||
|
alias check-db="PGPASSWORD=postgres psql -h db -U postgres -c '\l'"
|
||||||
|
|
||||||
|
# Default to workspace directory
|
||||||
|
cd /workspace 2>/dev/null || true
|
||||||
|
|
||||||
|
# Welcome message - show once per session
|
||||||
|
if [ -z "$SIM_WELCOME_SHOWN" ]; then
|
||||||
|
export SIM_WELCOME_SHOWN=1
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo "🚀 Sim Development Environment"
|
||||||
|
echo ""
|
||||||
|
echo "Project commands:"
|
||||||
|
echo " sim-start - Start app + socket server"
|
||||||
|
echo " sim-app - Start only main app"
|
||||||
|
echo " sim-sockets - Start only socket server"
|
||||||
|
echo " sim-migrate - Push schema changes"
|
||||||
|
echo " sim-generate - Generate migrations"
|
||||||
|
echo ""
|
||||||
|
echo "Database:"
|
||||||
|
echo " pgc - Connect to PostgreSQL"
|
||||||
|
echo " check-db - List databases"
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
@@ -1,11 +1,67 @@
|
|||||||
|
# Git
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
|
||||||
|
# Documentation
|
||||||
LICENSE
|
LICENSE
|
||||||
NOTICE
|
NOTICE
|
||||||
|
README.md
|
||||||
|
*.md
|
||||||
|
docs/
|
||||||
|
|
||||||
|
# IDE and editor
|
||||||
|
.vscode
|
||||||
|
.idea
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# Environment and config
|
||||||
|
.env*
|
||||||
|
!.env.example
|
||||||
.prettierrc
|
.prettierrc
|
||||||
.prettierignore
|
.prettierignore
|
||||||
README.md
|
.eslintrc*
|
||||||
.gitignore
|
.eslintignore
|
||||||
.husky
|
|
||||||
|
# CI/CD and DevOps
|
||||||
.github
|
.github
|
||||||
.devcontainer
|
.devcontainer
|
||||||
.env.example
|
.husky
|
||||||
node_modules
|
docker-compose*.yml
|
||||||
|
Dockerfile*
|
||||||
|
|
||||||
|
# Build artifacts and caches
|
||||||
|
.next
|
||||||
|
.turbo
|
||||||
|
.cache
|
||||||
|
dist
|
||||||
|
build
|
||||||
|
out
|
||||||
|
coverage
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Dependencies (will be installed fresh in container)
|
||||||
|
node_modules
|
||||||
|
.bun
|
||||||
|
|
||||||
|
# Test files
|
||||||
|
**/*.test.ts
|
||||||
|
**/*.test.tsx
|
||||||
|
**/*.spec.ts
|
||||||
|
**/*.spec.tsx
|
||||||
|
__tests__
|
||||||
|
__mocks__
|
||||||
|
jest.config.*
|
||||||
|
vitest.config.*
|
||||||
|
|
||||||
|
# TypeScript build info
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# OS files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Temporary files
|
||||||
|
tmp
|
||||||
|
temp
|
||||||
|
*.tmp
|
||||||
|
|||||||
34
.gitattributes
vendored
Normal file
34
.gitattributes
vendored
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# Set default behavior to automatically normalize line endings
|
||||||
|
* text=auto eol=lf
|
||||||
|
|
||||||
|
# Explicitly declare text files you want to always be normalized and converted
|
||||||
|
# to native line endings on checkout
|
||||||
|
*.ts text eol=lf
|
||||||
|
*.tsx text eol=lf
|
||||||
|
*.js text eol=lf
|
||||||
|
*.jsx text eol=lf
|
||||||
|
*.json text eol=lf
|
||||||
|
*.md text eol=lf
|
||||||
|
*.yml text eol=lf
|
||||||
|
*.yaml text eol=lf
|
||||||
|
*.toml text eol=lf
|
||||||
|
*.css text eol=lf
|
||||||
|
*.scss text eol=lf
|
||||||
|
*.sh text eol=lf
|
||||||
|
*.bash text eol=lf
|
||||||
|
Dockerfile* text eol=lf
|
||||||
|
.dockerignore text eol=lf
|
||||||
|
.gitignore text eol=lf
|
||||||
|
.gitattributes text eol=lf
|
||||||
|
|
||||||
|
# Denote all files that are truly binary and should not be modified
|
||||||
|
*.png binary
|
||||||
|
*.jpg binary
|
||||||
|
*.jpeg binary
|
||||||
|
*.gif binary
|
||||||
|
*.ico binary
|
||||||
|
*.woff binary
|
||||||
|
*.woff2 binary
|
||||||
|
*.ttf binary
|
||||||
|
*.eot binary
|
||||||
|
*.pdf binary
|
||||||
8
.github/CONTRIBUTING.md
vendored
8
.github/CONTRIBUTING.md
vendored
@@ -321,8 +321,7 @@ In addition, you will need to update the registries:
|
|||||||
{
|
{
|
||||||
id: 'operation',
|
id: 'operation',
|
||||||
title: 'Operation',
|
title: 'Operation',
|
||||||
type: 'dropdown',
|
type: 'dropdown'
|
||||||
layout: 'full',
|
|
||||||
required: true,
|
required: true,
|
||||||
options: [
|
options: [
|
||||||
{ label: 'Generate Embeddings', id: 'generate' },
|
{ label: 'Generate Embeddings', id: 'generate' },
|
||||||
@@ -333,8 +332,7 @@ In addition, you will need to update the registries:
|
|||||||
{
|
{
|
||||||
id: 'apiKey',
|
id: 'apiKey',
|
||||||
title: 'API Key',
|
title: 'API Key',
|
||||||
type: 'short-input',
|
type: 'short-input'
|
||||||
layout: 'full',
|
|
||||||
placeholder: 'Your Pinecone API key',
|
placeholder: 'Your Pinecone API key',
|
||||||
password: true,
|
password: true,
|
||||||
required: true,
|
required: true,
|
||||||
@@ -376,7 +374,7 @@ In addition, you will need to update the registries:
|
|||||||
Add your block to the blocks registry (`/apps/sim/blocks/registry.ts`):
|
Add your block to the blocks registry (`/apps/sim/blocks/registry.ts`):
|
||||||
|
|
||||||
```typescript:/apps/sim/blocks/registry.ts
|
```typescript:/apps/sim/blocks/registry.ts
|
||||||
import { PineconeBlock } from './blocks/pinecone'
|
import { PineconeBlock } from '@/blocks/blocks/pinecone'
|
||||||
|
|
||||||
// Registry of all available blocks
|
// Registry of all available blocks
|
||||||
export const registry: Record<string, BlockConfig> = {
|
export const registry: Record<string, BlockConfig> = {
|
||||||
|
|||||||
159
.github/workflows/build.yml
vendored
159
.github/workflows/build.yml
vendored
@@ -1,159 +0,0 @@
|
|||||||
name: Build and Publish Docker Image
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [main]
|
|
||||||
tags: ['v*']
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-and-push:
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
# AMD64 builds on x86 runners
|
|
||||||
- dockerfile: ./docker/app.Dockerfile
|
|
||||||
image: ghcr.io/simstudioai/simstudio
|
|
||||||
platform: linux/amd64
|
|
||||||
arch: amd64
|
|
||||||
runner: linux-x64-8-core
|
|
||||||
- dockerfile: ./docker/db.Dockerfile
|
|
||||||
image: ghcr.io/simstudioai/migrations
|
|
||||||
platform: linux/amd64
|
|
||||||
arch: amd64
|
|
||||||
runner: linux-x64-8-core
|
|
||||||
- dockerfile: ./docker/realtime.Dockerfile
|
|
||||||
image: ghcr.io/simstudioai/realtime
|
|
||||||
platform: linux/amd64
|
|
||||||
arch: amd64
|
|
||||||
runner: linux-x64-8-core
|
|
||||||
# ARM64 builds on native ARM64 runners
|
|
||||||
- dockerfile: ./docker/app.Dockerfile
|
|
||||||
image: ghcr.io/simstudioai/simstudio
|
|
||||||
platform: linux/arm64
|
|
||||||
arch: arm64
|
|
||||||
runner: linux-arm64-8-core
|
|
||||||
- dockerfile: ./docker/db.Dockerfile
|
|
||||||
image: ghcr.io/simstudioai/migrations
|
|
||||||
platform: linux/arm64
|
|
||||||
arch: arm64
|
|
||||||
runner: linux-arm64-8-core
|
|
||||||
- dockerfile: ./docker/realtime.Dockerfile
|
|
||||||
image: ghcr.io/simstudioai/realtime
|
|
||||||
platform: linux/arm64
|
|
||||||
arch: arm64
|
|
||||||
runner: linux-arm64-8-core
|
|
||||||
runs-on: ${{ matrix.runner }}
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
packages: write
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
|
|
||||||
- name: Log in to the Container registry
|
|
||||||
if: github.event_name != 'pull_request'
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ github.repository_owner }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Extract metadata (tags, labels) for Docker
|
|
||||||
id: meta
|
|
||||||
uses: docker/metadata-action@v5
|
|
||||||
with:
|
|
||||||
images: ${{ matrix.image }}
|
|
||||||
tags: |
|
|
||||||
type=raw,value=latest-${{ matrix.arch }},enable=${{ github.ref == 'refs/heads/main' }}
|
|
||||||
type=ref,event=pr,suffix=-${{ matrix.arch }}
|
|
||||||
type=semver,pattern={{version}},suffix=-${{ matrix.arch }}
|
|
||||||
type=semver,pattern={{major}}.{{minor}},suffix=-${{ matrix.arch }}
|
|
||||||
type=semver,pattern={{major}}.{{minor}}.{{patch}},suffix=-${{ matrix.arch }}
|
|
||||||
type=sha,format=long,suffix=-${{ matrix.arch }}
|
|
||||||
|
|
||||||
- name: Build and push Docker image
|
|
||||||
uses: docker/build-push-action@v6
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
file: ${{ matrix.dockerfile }}
|
|
||||||
platforms: ${{ matrix.platform }}
|
|
||||||
push: ${{ github.event_name != 'pull_request' }}
|
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
|
||||||
cache-from: type=gha,scope=build-v3
|
|
||||||
cache-to: type=gha,mode=max,scope=build-v3
|
|
||||||
provenance: false
|
|
||||||
sbom: false
|
|
||||||
|
|
||||||
create-manifests:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: build-and-push
|
|
||||||
if: github.event_name != 'pull_request'
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- image: ghcr.io/simstudioai/simstudio
|
|
||||||
- image: ghcr.io/simstudioai/migrations
|
|
||||||
- image: ghcr.io/simstudioai/realtime
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
packages: write
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Log in to the Container registry
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ github.repository_owner }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Extract metadata for manifest
|
|
||||||
id: meta
|
|
||||||
uses: docker/metadata-action@v5
|
|
||||||
with:
|
|
||||||
images: ${{ matrix.image }}
|
|
||||||
tags: |
|
|
||||||
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
|
|
||||||
type=ref,event=pr
|
|
||||||
type=semver,pattern={{version}}
|
|
||||||
type=semver,pattern={{major}}.{{minor}}
|
|
||||||
type=semver,pattern={{major}}.{{minor}}.{{patch}}
|
|
||||||
type=sha,format=long
|
|
||||||
|
|
||||||
- name: Create and push manifest
|
|
||||||
run: |
|
|
||||||
# Extract the tags from metadata (these are the final manifest tags we want)
|
|
||||||
MANIFEST_TAGS="${{ steps.meta.outputs.tags }}"
|
|
||||||
|
|
||||||
# Create manifest for each tag
|
|
||||||
for manifest_tag in $MANIFEST_TAGS; do
|
|
||||||
echo "Creating manifest for $manifest_tag"
|
|
||||||
|
|
||||||
# The architecture-specific images have -amd64 and -arm64 suffixes
|
|
||||||
amd64_image="${manifest_tag}-amd64"
|
|
||||||
arm64_image="${manifest_tag}-arm64"
|
|
||||||
|
|
||||||
echo "Looking for images: $amd64_image and $arm64_image"
|
|
||||||
|
|
||||||
# Check if both architecture images exist
|
|
||||||
if docker manifest inspect "$amd64_image" >/dev/null 2>&1 && docker manifest inspect "$arm64_image" >/dev/null 2>&1; then
|
|
||||||
echo "Both images found, creating manifest..."
|
|
||||||
docker manifest create "$manifest_tag" \
|
|
||||||
"$amd64_image" \
|
|
||||||
"$arm64_image"
|
|
||||||
docker manifest push "$manifest_tag"
|
|
||||||
echo "Successfully created and pushed manifest for $manifest_tag"
|
|
||||||
else
|
|
||||||
echo "Error: One or both architecture images not found"
|
|
||||||
echo "Checking AMD64 image: $amd64_image"
|
|
||||||
docker manifest inspect "$amd64_image" || echo "AMD64 image not found"
|
|
||||||
echo "Checking ARM64 image: $arm64_image"
|
|
||||||
docker manifest inspect "$arm64_image" || echo "ARM64 image not found"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
318
.github/workflows/ci.yml
vendored
318
.github/workflows/ci.yml
vendored
@@ -6,72 +6,274 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
branches: [main, staging]
|
branches: [main, staging]
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ci-${{ github.ref }}
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test-build:
|
||||||
name: Test and Build
|
name: Test and Build
|
||||||
runs-on: ubuntu-latest
|
uses: ./.github/workflows/test-build.yml
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
steps:
|
# Detect if this is a version release commit (e.g., "v0.5.24: ...")
|
||||||
- name: Checkout code
|
detect-version:
|
||||||
uses: actions/checkout@v4
|
name: Detect Version
|
||||||
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||||
- name: Setup Bun
|
|
||||||
uses: oven-sh/setup-bun@v2
|
|
||||||
with:
|
|
||||||
bun-version: latest
|
|
||||||
|
|
||||||
- name: Setup Node
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: latest
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: bun install --frozen-lockfile
|
|
||||||
|
|
||||||
- name: Run tests with coverage
|
|
||||||
env:
|
|
||||||
NODE_OPTIONS: '--no-warnings'
|
|
||||||
NEXT_PUBLIC_APP_URL: 'https://www.sim.ai'
|
|
||||||
ENCRYPTION_KEY: '7cf672e460e430c1fba707575c2b0e2ad5a99dddf9b7b7e3b5646e630861db1c' # dummy key for CI only
|
|
||||||
run: bun run test
|
|
||||||
|
|
||||||
- name: Build application
|
|
||||||
env:
|
|
||||||
NODE_OPTIONS: '--no-warnings'
|
|
||||||
NEXT_PUBLIC_APP_URL: 'https://www.sim.ai'
|
|
||||||
STRIPE_SECRET_KEY: 'dummy_key_for_ci_only'
|
|
||||||
STRIPE_WEBHOOK_SECRET: 'dummy_secret_for_ci_only'
|
|
||||||
RESEND_API_KEY: 'dummy_key_for_ci_only'
|
|
||||||
AWS_REGION: 'us-west-2'
|
|
||||||
ENCRYPTION_KEY: '7cf672e460e430c1fba707575c2b0e2ad5a99dddf9b7b7e3b5646e630861db1c' # dummy key for CI only
|
|
||||||
run: bun run build
|
|
||||||
|
|
||||||
- name: Upload coverage to Codecov
|
|
||||||
uses: codecov/codecov-action@v5
|
|
||||||
with:
|
|
||||||
directory: ./apps/sim/coverage
|
|
||||||
fail_ci_if_error: false
|
|
||||||
verbose: true
|
|
||||||
|
|
||||||
migrations:
|
|
||||||
name: Apply Database Migrations
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/staging')
|
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/staging')
|
||||||
needs: test
|
outputs:
|
||||||
|
version: ${{ steps.extract.outputs.version }}
|
||||||
|
is_release: ${{ steps.extract.outputs.is_release }}
|
||||||
|
steps:
|
||||||
|
- name: Extract version from commit message
|
||||||
|
id: extract
|
||||||
|
run: |
|
||||||
|
COMMIT_MSG="${{ github.event.head_commit.message }}"
|
||||||
|
# Only tag versions on main branch
|
||||||
|
if [ "${{ github.ref }}" = "refs/heads/main" ] && [[ "$COMMIT_MSG" =~ ^(v[0-9]+\.[0-9]+\.[0-9]+): ]]; then
|
||||||
|
VERSION="${BASH_REMATCH[1]}"
|
||||||
|
echo "version=${VERSION}" >> $GITHUB_OUTPUT
|
||||||
|
echo "is_release=true" >> $GITHUB_OUTPUT
|
||||||
|
echo "✅ Detected release commit: ${VERSION}"
|
||||||
|
else
|
||||||
|
echo "version=" >> $GITHUB_OUTPUT
|
||||||
|
echo "is_release=false" >> $GITHUB_OUTPUT
|
||||||
|
echo "ℹ️ Not a release commit"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Build AMD64 images and push to ECR immediately (+ GHCR for main)
|
||||||
|
build-amd64:
|
||||||
|
name: Build AMD64
|
||||||
|
needs: [test-build, detect-version]
|
||||||
|
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/staging')
|
||||||
|
runs-on: blacksmith-8vcpu-ubuntu-2404
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
id-token: write
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- dockerfile: ./docker/app.Dockerfile
|
||||||
|
ghcr_image: ghcr.io/simstudioai/simstudio
|
||||||
|
ecr_repo_secret: ECR_APP
|
||||||
|
- dockerfile: ./docker/db.Dockerfile
|
||||||
|
ghcr_image: ghcr.io/simstudioai/migrations
|
||||||
|
ecr_repo_secret: ECR_MIGRATIONS
|
||||||
|
- dockerfile: ./docker/realtime.Dockerfile
|
||||||
|
ghcr_image: ghcr.io/simstudioai/realtime
|
||||||
|
ecr_repo_secret: ECR_REALTIME
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Setup Bun
|
- name: Configure AWS credentials
|
||||||
uses: oven-sh/setup-bun@v2
|
uses: aws-actions/configure-aws-credentials@v4
|
||||||
with:
|
with:
|
||||||
bun-version: latest
|
role-to-assume: ${{ github.ref == 'refs/heads/main' && secrets.AWS_ROLE_TO_ASSUME || secrets.STAGING_AWS_ROLE_TO_ASSUME }}
|
||||||
|
aws-region: ${{ github.ref == 'refs/heads/main' && secrets.AWS_REGION || secrets.STAGING_AWS_REGION }}
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Login to Amazon ECR
|
||||||
run: bun install
|
id: login-ecr
|
||||||
|
uses: aws-actions/amazon-ecr-login@v2
|
||||||
|
|
||||||
- name: Apply migrations
|
- name: Login to Docker Hub
|
||||||
working-directory: ./apps/sim
|
uses: docker/login-action@v3
|
||||||
env:
|
with:
|
||||||
DATABASE_URL: ${{ github.ref == 'refs/heads/main' && secrets.DATABASE_URL || secrets.STAGING_DATABASE_URL }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
run: bunx drizzle-kit migrate
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Login to GHCR
|
||||||
|
if: github.ref == 'refs/heads/main'
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: useblacksmith/setup-docker-builder@v1
|
||||||
|
|
||||||
|
- name: Generate tags
|
||||||
|
id: meta
|
||||||
|
run: |
|
||||||
|
ECR_REGISTRY="${{ steps.login-ecr.outputs.registry }}"
|
||||||
|
ECR_REPO="${{ secrets[matrix.ecr_repo_secret] }}"
|
||||||
|
GHCR_IMAGE="${{ matrix.ghcr_image }}"
|
||||||
|
|
||||||
|
# ECR tags (always build for ECR)
|
||||||
|
if [ "${{ github.ref }}" = "refs/heads/main" ]; then
|
||||||
|
ECR_TAG="latest"
|
||||||
|
else
|
||||||
|
ECR_TAG="staging"
|
||||||
|
fi
|
||||||
|
ECR_IMAGE="${ECR_REGISTRY}/${ECR_REPO}:${ECR_TAG}"
|
||||||
|
|
||||||
|
# Build tags list
|
||||||
|
TAGS="${ECR_IMAGE}"
|
||||||
|
|
||||||
|
# Add GHCR tags only for main branch
|
||||||
|
if [ "${{ github.ref }}" = "refs/heads/main" ]; then
|
||||||
|
GHCR_AMD64="${GHCR_IMAGE}:latest-amd64"
|
||||||
|
GHCR_SHA="${GHCR_IMAGE}:${{ github.sha }}-amd64"
|
||||||
|
TAGS="${TAGS},$GHCR_AMD64,$GHCR_SHA"
|
||||||
|
|
||||||
|
# Add version tag if this is a release commit
|
||||||
|
if [ "${{ needs.detect-version.outputs.is_release }}" = "true" ]; then
|
||||||
|
VERSION="${{ needs.detect-version.outputs.version }}"
|
||||||
|
GHCR_VERSION="${GHCR_IMAGE}:${VERSION}-amd64"
|
||||||
|
TAGS="${TAGS},$GHCR_VERSION"
|
||||||
|
echo "📦 Adding version tag: ${VERSION}-amd64"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "tags=${TAGS}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Build and push images
|
||||||
|
uses: useblacksmith/build-push-action@v2
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ${{ matrix.dockerfile }}
|
||||||
|
platforms: linux/amd64
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
provenance: false
|
||||||
|
sbom: false
|
||||||
|
|
||||||
|
# Build ARM64 images for GHCR (main branch only, runs in parallel)
|
||||||
|
build-ghcr-arm64:
|
||||||
|
name: Build ARM64 (GHCR Only)
|
||||||
|
needs: [test-build, detect-version]
|
||||||
|
runs-on: blacksmith-8vcpu-ubuntu-2404-arm
|
||||||
|
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- dockerfile: ./docker/app.Dockerfile
|
||||||
|
image: ghcr.io/simstudioai/simstudio
|
||||||
|
- dockerfile: ./docker/db.Dockerfile
|
||||||
|
image: ghcr.io/simstudioai/migrations
|
||||||
|
- dockerfile: ./docker/realtime.Dockerfile
|
||||||
|
image: ghcr.io/simstudioai/realtime
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Login to GHCR
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: useblacksmith/setup-docker-builder@v1
|
||||||
|
|
||||||
|
- name: Generate ARM64 tags
|
||||||
|
id: meta
|
||||||
|
run: |
|
||||||
|
IMAGE="${{ matrix.image }}"
|
||||||
|
TAGS="${IMAGE}:latest-arm64,${IMAGE}:${{ github.sha }}-arm64"
|
||||||
|
|
||||||
|
# Add version tag if this is a release commit
|
||||||
|
if [ "${{ needs.detect-version.outputs.is_release }}" = "true" ]; then
|
||||||
|
VERSION="${{ needs.detect-version.outputs.version }}"
|
||||||
|
TAGS="${TAGS},${IMAGE}:${VERSION}-arm64"
|
||||||
|
echo "📦 Adding version tag: ${VERSION}-arm64"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "tags=${TAGS}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Build and push ARM64 to GHCR
|
||||||
|
uses: useblacksmith/build-push-action@v2
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ${{ matrix.dockerfile }}
|
||||||
|
platforms: linux/arm64
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
provenance: false
|
||||||
|
sbom: false
|
||||||
|
|
||||||
|
# Create GHCR multi-arch manifests (only for main, after both builds)
|
||||||
|
create-ghcr-manifests:
|
||||||
|
name: Create GHCR Manifests
|
||||||
|
runs-on: blacksmith-2vcpu-ubuntu-2404
|
||||||
|
needs: [build-amd64, build-ghcr-arm64, detect-version]
|
||||||
|
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||||
|
permissions:
|
||||||
|
packages: write
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- image: ghcr.io/simstudioai/simstudio
|
||||||
|
- image: ghcr.io/simstudioai/migrations
|
||||||
|
- image: ghcr.io/simstudioai/realtime
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Login to GHCR
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Create and push manifests
|
||||||
|
run: |
|
||||||
|
IMAGE_BASE="${{ matrix.image }}"
|
||||||
|
|
||||||
|
# Create latest manifest
|
||||||
|
docker manifest create "${IMAGE_BASE}:latest" \
|
||||||
|
"${IMAGE_BASE}:latest-amd64" \
|
||||||
|
"${IMAGE_BASE}:latest-arm64"
|
||||||
|
docker manifest push "${IMAGE_BASE}:latest"
|
||||||
|
|
||||||
|
# Create SHA manifest
|
||||||
|
docker manifest create "${IMAGE_BASE}:${{ github.sha }}" \
|
||||||
|
"${IMAGE_BASE}:${{ github.sha }}-amd64" \
|
||||||
|
"${IMAGE_BASE}:${{ github.sha }}-arm64"
|
||||||
|
docker manifest push "${IMAGE_BASE}:${{ github.sha }}"
|
||||||
|
|
||||||
|
# Create version manifest if this is a release commit
|
||||||
|
if [ "${{ needs.detect-version.outputs.is_release }}" = "true" ]; then
|
||||||
|
VERSION="${{ needs.detect-version.outputs.version }}"
|
||||||
|
echo "📦 Creating version manifest: ${VERSION}"
|
||||||
|
docker manifest create "${IMAGE_BASE}:${VERSION}" \
|
||||||
|
"${IMAGE_BASE}:${VERSION}-amd64" \
|
||||||
|
"${IMAGE_BASE}:${VERSION}-arm64"
|
||||||
|
docker manifest push "${IMAGE_BASE}:${VERSION}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if docs changed
|
||||||
|
check-docs-changes:
|
||||||
|
name: Check Docs Changes
|
||||||
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||||
|
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||||
|
outputs:
|
||||||
|
docs_changed: ${{ steps.filter.outputs.docs }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 2 # Need at least 2 commits to detect changes
|
||||||
|
- uses: dorny/paths-filter@v3
|
||||||
|
id: filter
|
||||||
|
with:
|
||||||
|
filters: |
|
||||||
|
docs:
|
||||||
|
- 'apps/docs/content/docs/en/**'
|
||||||
|
- 'apps/sim/scripts/process-docs.ts'
|
||||||
|
- 'apps/sim/lib/chunkers/**'
|
||||||
|
|
||||||
|
# Process docs embeddings (only when docs change, after ECR images are pushed)
|
||||||
|
process-docs:
|
||||||
|
name: Process Docs
|
||||||
|
needs: [build-amd64, check-docs-changes]
|
||||||
|
if: needs.check-docs-changes.outputs.docs_changed == 'true'
|
||||||
|
uses: ./.github/workflows/docs-embeddings.yml
|
||||||
|
secrets: inherit
|
||||||
|
|||||||
28
.github/workflows/docs-embeddings.yml
vendored
28
.github/workflows/docs-embeddings.yml
vendored
@@ -1,17 +1,14 @@
|
|||||||
name: Process Docs Embeddings
|
name: Process Docs Embeddings
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
workflow_call:
|
||||||
branches: [main, staging]
|
|
||||||
paths:
|
|
||||||
- 'apps/docs/**'
|
|
||||||
workflow_dispatch: # Allow manual triggering
|
workflow_dispatch: # Allow manual triggering
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
process-docs-embeddings:
|
process-docs-embeddings:
|
||||||
name: Process Documentation Embeddings
|
name: Process Documentation Embeddings
|
||||||
runs-on: ubuntu-latest
|
runs-on: blacksmith-8vcpu-ubuntu-2404
|
||||||
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/staging'
|
if: github.ref == 'refs/heads/main'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
@@ -20,19 +17,30 @@ jobs:
|
|||||||
- name: Setup Bun
|
- name: Setup Bun
|
||||||
uses: oven-sh/setup-bun@v2
|
uses: oven-sh/setup-bun@v2
|
||||||
with:
|
with:
|
||||||
bun-version: latest
|
bun-version: 1.3.3
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: latest
|
node-version: latest
|
||||||
|
|
||||||
|
- name: Cache Bun dependencies
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.bun/install/cache
|
||||||
|
node_modules
|
||||||
|
**/node_modules
|
||||||
|
key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-bun-
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: bun install
|
run: bun install --frozen-lockfile
|
||||||
|
|
||||||
- name: Process docs embeddings
|
- name: Process docs embeddings
|
||||||
working-directory: ./apps/sim
|
working-directory: ./apps/sim
|
||||||
env:
|
env:
|
||||||
DATABASE_URL: ${{ github.ref == 'refs/heads/main' && secrets.DATABASE_URL || secrets.STAGING_DATABASE_URL }}
|
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
||||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||||
run: bun run scripts/process-docs-embeddings.ts --clear
|
run: bun run scripts/process-docs.ts --clear
|
||||||
|
|||||||
180
.github/workflows/i18n.yml
vendored
Normal file
180
.github/workflows/i18n.yml
vendored
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
name: 'Auto-translate Documentation'
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ staging ]
|
||||||
|
paths:
|
||||||
|
- 'apps/docs/content/docs/en/**'
|
||||||
|
- 'apps/docs/i18n.json'
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
translate:
|
||||||
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||||
|
if: github.actor != 'github-actions[bot]' # Prevent infinite loops
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GH_PAT }}
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Setup Bun
|
||||||
|
uses: oven-sh/setup-bun@v2
|
||||||
|
with:
|
||||||
|
bun-version: 1.3.3
|
||||||
|
|
||||||
|
- name: Cache Bun dependencies
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.bun/install/cache
|
||||||
|
node_modules
|
||||||
|
**/node_modules
|
||||||
|
key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-bun-
|
||||||
|
|
||||||
|
- name: Run Lingo.dev translations
|
||||||
|
env:
|
||||||
|
LINGODOTDEV_API_KEY: ${{ secrets.LINGODOTDEV_API_KEY }}
|
||||||
|
run: |
|
||||||
|
cd apps/docs
|
||||||
|
bunx lingo.dev@latest i18n
|
||||||
|
|
||||||
|
- name: Check for translation changes
|
||||||
|
id: changes
|
||||||
|
run: |
|
||||||
|
cd apps/docs
|
||||||
|
git config --local user.email "action@github.com"
|
||||||
|
git config --local user.name "GitHub Action"
|
||||||
|
|
||||||
|
if [ -n "$(git status --porcelain content/docs)" ]; then
|
||||||
|
echo "changes=true" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "changes=false" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Create Pull Request with translations
|
||||||
|
if: steps.changes.outputs.changes == 'true'
|
||||||
|
uses: peter-evans/create-pull-request@v5
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GH_PAT }}
|
||||||
|
commit-message: "feat(i18n): update translations"
|
||||||
|
title: "feat(i18n): update translations"
|
||||||
|
body: |
|
||||||
|
## Summary
|
||||||
|
Automated translation updates triggered by changes to documentation.
|
||||||
|
|
||||||
|
This PR was automatically created after content changes were made, updating translations for all supported languages using Lingo.dev AI translation engine.
|
||||||
|
|
||||||
|
**Original trigger**: ${{ github.event.head_commit.message }}
|
||||||
|
**Commit**: ${{ github.sha }}
|
||||||
|
**Workflow**: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||||
|
|
||||||
|
## Type of Change
|
||||||
|
- [ ] Bug fix
|
||||||
|
- [ ] New feature
|
||||||
|
- [ ] Breaking change
|
||||||
|
- [x] Documentation
|
||||||
|
- [ ] Other: ___________
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
This PR includes automated translations for modified English documentation content:
|
||||||
|
- 🇪🇸 Spanish (es) translations
|
||||||
|
- 🇫🇷 French (fr) translations
|
||||||
|
- 🇨🇳 Chinese (zh) translations
|
||||||
|
- 🇯🇵 Japanese (ja) translations
|
||||||
|
- 🇩🇪 German (de) translations
|
||||||
|
|
||||||
|
**What reviewers should focus on:**
|
||||||
|
- Verify translated content accuracy and context
|
||||||
|
- Check that all links and references work correctly in translated versions
|
||||||
|
- Ensure formatting, code blocks, and structure are preserved
|
||||||
|
- Validate that technical terms are appropriately translated
|
||||||
|
|
||||||
|
## Checklist
|
||||||
|
- [x] Code follows project style guidelines (automated translation)
|
||||||
|
- [x] Self-reviewed my changes (automated process)
|
||||||
|
- [ ] Tests added/updated and passing
|
||||||
|
- [x] No new warnings introduced
|
||||||
|
- [x] I confirm that I have read and agree to the terms outlined in the [Contributor License Agreement (CLA)](./CONTRIBUTING.md#contributor-license-agreement-cla)
|
||||||
|
|
||||||
|
## Screenshots/Videos
|
||||||
|
<!-- Translation changes are text-based - no visual changes expected -->
|
||||||
|
<!-- Reviewers should check the documentation site renders correctly for all languages -->
|
||||||
|
branch: auto-translate/staging-merge-${{ github.run_id }}
|
||||||
|
base: staging
|
||||||
|
labels: |
|
||||||
|
i18n
|
||||||
|
|
||||||
|
verify-translations:
|
||||||
|
needs: translate
|
||||||
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||||
|
if: always() # Run even if translation fails
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: staging
|
||||||
|
|
||||||
|
- name: Setup Bun
|
||||||
|
uses: oven-sh/setup-bun@v2
|
||||||
|
with:
|
||||||
|
bun-version: 1.3.3
|
||||||
|
|
||||||
|
- name: Cache Bun dependencies
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.bun/install/cache
|
||||||
|
node_modules
|
||||||
|
**/node_modules
|
||||||
|
key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-bun-
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
cd apps/docs
|
||||||
|
bun install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Build documentation to verify translations
|
||||||
|
run: |
|
||||||
|
cd apps/docs
|
||||||
|
bun run build
|
||||||
|
|
||||||
|
- name: Report translation status
|
||||||
|
run: |
|
||||||
|
cd apps/docs
|
||||||
|
echo "## Translation Status Report" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "**Triggered by merge to staging branch**" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
|
en_count=$(find content/docs/en -name "*.mdx" | wc -l)
|
||||||
|
es_count=$(find content/docs/es -name "*.mdx" 2>/dev/null | wc -l || echo 0)
|
||||||
|
fr_count=$(find content/docs/fr -name "*.mdx" 2>/dev/null | wc -l || echo 0)
|
||||||
|
zh_count=$(find content/docs/zh -name "*.mdx" 2>/dev/null | wc -l || echo 0)
|
||||||
|
ja_count=$(find content/docs/ja -name "*.mdx" 2>/dev/null | wc -l || echo 0)
|
||||||
|
de_count=$(find content/docs/de -name "*.mdx" 2>/dev/null | wc -l || echo 0)
|
||||||
|
|
||||||
|
es_percentage=$((es_count * 100 / en_count))
|
||||||
|
fr_percentage=$((fr_count * 100 / en_count))
|
||||||
|
zh_percentage=$((zh_count * 100 / en_count))
|
||||||
|
ja_percentage=$((ja_count * 100 / en_count))
|
||||||
|
de_percentage=$((de_count * 100 / en_count))
|
||||||
|
|
||||||
|
echo "### Coverage Statistics" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "- **🇬🇧 English**: $en_count files (source)" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "- **🇪🇸 Spanish**: $es_count/$en_count files ($es_percentage%)" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "- **🇫🇷 French**: $fr_count/$en_count files ($fr_percentage%)" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "- **🇨🇳 Chinese**: $zh_count/$en_count files ($zh_percentage%)" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "- **🇯🇵 Japanese**: $ja_count/$en_count files ($ja_percentage%)" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "- **🇩🇪 German**: $de_count/$en_count files ($de_percentage%)" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "🔄 **Auto-translation PR**: Check for new pull request with updated translations" >> $GITHUB_STEP_SUMMARY
|
||||||
181
.github/workflows/images.yml
vendored
Normal file
181
.github/workflows/images.yml
vendored
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
name: Build and Push Images
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-amd64:
|
||||||
|
name: Build AMD64
|
||||||
|
runs-on: blacksmith-8vcpu-ubuntu-2404
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- dockerfile: ./docker/app.Dockerfile
|
||||||
|
ghcr_image: ghcr.io/simstudioai/simstudio
|
||||||
|
ecr_repo_secret: ECR_APP
|
||||||
|
- dockerfile: ./docker/db.Dockerfile
|
||||||
|
ghcr_image: ghcr.io/simstudioai/migrations
|
||||||
|
ecr_repo_secret: ECR_MIGRATIONS
|
||||||
|
- dockerfile: ./docker/realtime.Dockerfile
|
||||||
|
ghcr_image: ghcr.io/simstudioai/realtime
|
||||||
|
ecr_repo_secret: ECR_REALTIME
|
||||||
|
outputs:
|
||||||
|
registry: ${{ steps.login-ecr.outputs.registry }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Configure AWS credentials
|
||||||
|
uses: aws-actions/configure-aws-credentials@v4
|
||||||
|
with:
|
||||||
|
role-to-assume: ${{ github.ref == 'refs/heads/main' && secrets.AWS_ROLE_TO_ASSUME || secrets.STAGING_AWS_ROLE_TO_ASSUME }}
|
||||||
|
aws-region: ${{ github.ref == 'refs/heads/main' && secrets.AWS_REGION || secrets.STAGING_AWS_REGION }}
|
||||||
|
|
||||||
|
- name: Login to Amazon ECR
|
||||||
|
id: login-ecr
|
||||||
|
uses: aws-actions/amazon-ecr-login@v2
|
||||||
|
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Login to GHCR
|
||||||
|
if: github.ref == 'refs/heads/main'
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: useblacksmith/setup-docker-builder@v1
|
||||||
|
|
||||||
|
- name: Generate tags
|
||||||
|
id: meta
|
||||||
|
run: |
|
||||||
|
ECR_REGISTRY="${{ steps.login-ecr.outputs.registry }}"
|
||||||
|
ECR_REPO="${{ secrets[matrix.ecr_repo_secret] }}"
|
||||||
|
GHCR_IMAGE="${{ matrix.ghcr_image }}"
|
||||||
|
|
||||||
|
# ECR tags (always build for ECR)
|
||||||
|
if [ "${{ github.ref }}" = "refs/heads/main" ]; then
|
||||||
|
ECR_TAG="latest"
|
||||||
|
else
|
||||||
|
ECR_TAG="staging"
|
||||||
|
fi
|
||||||
|
ECR_IMAGE="${ECR_REGISTRY}/${ECR_REPO}:${ECR_TAG}"
|
||||||
|
|
||||||
|
# Build tags list
|
||||||
|
TAGS="${ECR_IMAGE}"
|
||||||
|
|
||||||
|
# Add GHCR tags only for main branch
|
||||||
|
if [ "${{ github.ref }}" = "refs/heads/main" ]; then
|
||||||
|
GHCR_AMD64="${GHCR_IMAGE}:latest-amd64"
|
||||||
|
GHCR_SHA="${GHCR_IMAGE}:${{ github.sha }}-amd64"
|
||||||
|
TAGS="${TAGS},$GHCR_AMD64,$GHCR_SHA"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "tags=${TAGS}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Build and push images
|
||||||
|
uses: useblacksmith/build-push-action@v2
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ${{ matrix.dockerfile }}
|
||||||
|
platforms: linux/amd64
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
provenance: false
|
||||||
|
sbom: false
|
||||||
|
|
||||||
|
build-ghcr-arm64:
|
||||||
|
name: Build ARM64 (GHCR Only)
|
||||||
|
runs-on: blacksmith-8vcpu-ubuntu-2404-arm
|
||||||
|
if: github.ref == 'refs/heads/main'
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- dockerfile: ./docker/app.Dockerfile
|
||||||
|
image: ghcr.io/simstudioai/simstudio
|
||||||
|
- dockerfile: ./docker/db.Dockerfile
|
||||||
|
image: ghcr.io/simstudioai/migrations
|
||||||
|
- dockerfile: ./docker/realtime.Dockerfile
|
||||||
|
image: ghcr.io/simstudioai/realtime
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Login to GHCR
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: useblacksmith/setup-docker-builder@v1
|
||||||
|
|
||||||
|
- name: Generate ARM64 tags
|
||||||
|
id: meta
|
||||||
|
run: |
|
||||||
|
IMAGE="${{ matrix.image }}"
|
||||||
|
echo "tags=${IMAGE}:latest-arm64,${IMAGE}:${{ github.sha }}-arm64" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Build and push ARM64 to GHCR
|
||||||
|
uses: useblacksmith/build-push-action@v2
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ${{ matrix.dockerfile }}
|
||||||
|
platforms: linux/arm64
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
provenance: false
|
||||||
|
sbom: false
|
||||||
|
|
||||||
|
create-ghcr-manifests:
|
||||||
|
name: Create GHCR Manifests
|
||||||
|
runs-on: blacksmith-8vcpu-ubuntu-2404
|
||||||
|
needs: [build-amd64, build-ghcr-arm64]
|
||||||
|
if: github.ref == 'refs/heads/main'
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- image: ghcr.io/simstudioai/simstudio
|
||||||
|
- image: ghcr.io/simstudioai/migrations
|
||||||
|
- image: ghcr.io/simstudioai/realtime
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Login to GHCR
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Create and push manifests
|
||||||
|
run: |
|
||||||
|
IMAGE_BASE="${{ matrix.image }}"
|
||||||
|
|
||||||
|
# Create latest manifest
|
||||||
|
docker manifest create "${IMAGE_BASE}:latest" \
|
||||||
|
"${IMAGE_BASE}:latest-amd64" \
|
||||||
|
"${IMAGE_BASE}:latest-arm64"
|
||||||
|
docker manifest push "${IMAGE_BASE}:latest"
|
||||||
|
|
||||||
|
# Create SHA manifest
|
||||||
|
docker manifest create "${IMAGE_BASE}:${{ github.sha }}" \
|
||||||
|
"${IMAGE_BASE}:${{ github.sha }}-amd64" \
|
||||||
|
"${IMAGE_BASE}:${{ github.sha }}-arm64"
|
||||||
|
docker manifest push "${IMAGE_BASE}:${{ github.sha }}"
|
||||||
39
.github/workflows/migrations.yml
vendored
Normal file
39
.github/workflows/migrations.yml
vendored
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
name: Database Migrations
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
migrate:
|
||||||
|
name: Apply Database Migrations
|
||||||
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Bun
|
||||||
|
uses: oven-sh/setup-bun@v2
|
||||||
|
with:
|
||||||
|
bun-version: 1.3.3
|
||||||
|
|
||||||
|
- name: Cache Bun dependencies
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.bun/install/cache
|
||||||
|
node_modules
|
||||||
|
**/node_modules
|
||||||
|
key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-bun-
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: bun install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Apply migrations
|
||||||
|
working-directory: ./packages/db
|
||||||
|
env:
|
||||||
|
DATABASE_URL: ${{ github.ref == 'refs/heads/main' && secrets.DATABASE_URL || secrets.STAGING_DATABASE_URL }}
|
||||||
|
run: bunx drizzle-kit migrate --config=./drizzle.config.ts
|
||||||
17
.github/workflows/publish-cli.yml
vendored
17
.github/workflows/publish-cli.yml
vendored
@@ -8,7 +8,7 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
publish-npm:
|
publish-npm:
|
||||||
runs-on: ubuntu-latest
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -16,7 +16,7 @@ jobs:
|
|||||||
- name: Setup Bun
|
- name: Setup Bun
|
||||||
uses: oven-sh/setup-bun@v2
|
uses: oven-sh/setup-bun@v2
|
||||||
with:
|
with:
|
||||||
bun-version: latest
|
bun-version: 1.3.3
|
||||||
|
|
||||||
- name: Setup Node.js for npm publishing
|
- name: Setup Node.js for npm publishing
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
@@ -24,9 +24,20 @@ jobs:
|
|||||||
node-version: '18'
|
node-version: '18'
|
||||||
registry-url: 'https://registry.npmjs.org/'
|
registry-url: 'https://registry.npmjs.org/'
|
||||||
|
|
||||||
|
- name: Cache Bun dependencies
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.bun/install/cache
|
||||||
|
node_modules
|
||||||
|
**/node_modules
|
||||||
|
key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-bun-
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
working-directory: packages/cli
|
working-directory: packages/cli
|
||||||
run: bun install
|
run: bun install --frozen-lockfile
|
||||||
|
|
||||||
- name: Build package
|
- name: Build package
|
||||||
working-directory: packages/cli
|
working-directory: packages/cli
|
||||||
|
|||||||
4
.github/workflows/publish-python-sdk.yml
vendored
4
.github/workflows/publish-python-sdk.yml
vendored
@@ -8,7 +8,7 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
publish-pypi:
|
publish-pypi:
|
||||||
runs-on: ubuntu-latest
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -84,6 +84,6 @@ jobs:
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Documentation
|
### Documentation
|
||||||
See the [README](https://github.com/simstudio/sim/tree/main/packages/python-sdk) for usage instructions.
|
See the [README](https://github.com/simstudioai/sim/tree/main/packages/python-sdk) or the [docs](https://docs.sim.ai/sdks/python) for more information.
|
||||||
draft: false
|
draft: false
|
||||||
prerelease: false
|
prerelease: false
|
||||||
22
.github/workflows/publish-ts-sdk.yml
vendored
22
.github/workflows/publish-ts-sdk.yml
vendored
@@ -8,7 +8,7 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
publish-npm:
|
publish-npm:
|
||||||
runs-on: ubuntu-latest
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -16,17 +16,27 @@ jobs:
|
|||||||
- name: Setup Bun
|
- name: Setup Bun
|
||||||
uses: oven-sh/setup-bun@v2
|
uses: oven-sh/setup-bun@v2
|
||||||
with:
|
with:
|
||||||
bun-version: latest
|
bun-version: 1.3.3
|
||||||
|
|
||||||
- name: Setup Node.js for npm publishing
|
- name: Setup Node.js for npm publishing
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: '18'
|
node-version: '22'
|
||||||
registry-url: 'https://registry.npmjs.org/'
|
registry-url: 'https://registry.npmjs.org/'
|
||||||
|
|
||||||
|
- name: Cache Bun dependencies
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.bun/install/cache
|
||||||
|
node_modules
|
||||||
|
**/node_modules
|
||||||
|
key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-bun-
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
working-directory: packages/ts-sdk
|
run: bun install --frozen-lockfile
|
||||||
run: bun install
|
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
working-directory: packages/ts-sdk
|
working-directory: packages/ts-sdk
|
||||||
@@ -80,6 +90,6 @@ jobs:
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Documentation
|
### Documentation
|
||||||
See the [README](https://github.com/simstudio/sim/tree/main/packages/ts-sdk) for usage instructions.
|
See the [README](https://github.com/simstudioai/sim/tree/main/packages/ts-sdk) or the [docs](https://docs.sim.ai/sdks/typescript) for more information.
|
||||||
draft: false
|
draft: false
|
||||||
prerelease: false
|
prerelease: false
|
||||||
82
.github/workflows/test-build.yml
vendored
Normal file
82
.github/workflows/test-build.yml
vendored
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
name: Test and Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test-build:
|
||||||
|
name: Test and Build
|
||||||
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Bun
|
||||||
|
uses: oven-sh/setup-bun@v2
|
||||||
|
with:
|
||||||
|
bun-version: 1.3.3
|
||||||
|
|
||||||
|
- name: Setup Node
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: latest
|
||||||
|
|
||||||
|
- name: Mount Bun cache (Sticky Disk)
|
||||||
|
uses: useblacksmith/stickydisk@v1
|
||||||
|
with:
|
||||||
|
key: ${{ github.repository }}-bun-cache
|
||||||
|
path: ~/.bun/install/cache
|
||||||
|
|
||||||
|
- name: Mount node_modules (Sticky Disk)
|
||||||
|
uses: useblacksmith/stickydisk@v1
|
||||||
|
with:
|
||||||
|
key: ${{ github.repository }}-node-modules
|
||||||
|
path: ./node_modules
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: bun install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Lint code
|
||||||
|
run: bun run lint:check
|
||||||
|
|
||||||
|
- name: Run tests with coverage
|
||||||
|
env:
|
||||||
|
NODE_OPTIONS: '--no-warnings'
|
||||||
|
NEXT_PUBLIC_APP_URL: 'https://www.sim.ai'
|
||||||
|
DATABASE_URL: 'postgresql://postgres:postgres@localhost:5432/simstudio'
|
||||||
|
ENCRYPTION_KEY: '7cf672e460e430c1fba707575c2b0e2ad5a99dddf9b7b7e3b5646e630861db1c' # dummy key for CI only
|
||||||
|
run: bun run test
|
||||||
|
|
||||||
|
- name: Check schema and migrations are in sync
|
||||||
|
working-directory: packages/db
|
||||||
|
run: |
|
||||||
|
bunx drizzle-kit generate --config=./drizzle.config.ts
|
||||||
|
if [ -n "$(git status --porcelain ./migrations)" ]; then
|
||||||
|
echo "❌ Schema and migrations are out of sync!"
|
||||||
|
echo "Run 'cd packages/db && bunx drizzle-kit generate' and commit the new migrations."
|
||||||
|
git status --porcelain ./migrations
|
||||||
|
git diff ./migrations
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "✅ Schema and migrations are in sync"
|
||||||
|
|
||||||
|
- name: Build application
|
||||||
|
env:
|
||||||
|
NODE_OPTIONS: '--no-warnings'
|
||||||
|
NEXT_PUBLIC_APP_URL: 'https://www.sim.ai'
|
||||||
|
DATABASE_URL: 'postgresql://postgres:postgres@localhost:5432/simstudio'
|
||||||
|
STRIPE_SECRET_KEY: 'dummy_key_for_ci_only'
|
||||||
|
STRIPE_WEBHOOK_SECRET: 'dummy_secret_for_ci_only'
|
||||||
|
RESEND_API_KEY: 'dummy_key_for_ci_only'
|
||||||
|
AWS_REGION: 'us-west-2'
|
||||||
|
ENCRYPTION_KEY: '7cf672e460e430c1fba707575c2b0e2ad5a99dddf9b7b7e3b5646e630861db1c' # dummy key for CI only
|
||||||
|
run: bun run build
|
||||||
|
|
||||||
|
- name: Upload coverage to Codecov
|
||||||
|
uses: codecov/codecov-action@v5
|
||||||
|
with:
|
||||||
|
directory: ./apps/sim/coverage
|
||||||
|
fail_ci_if_error: false
|
||||||
|
verbose: true
|
||||||
44
.github/workflows/trigger-deploy.yml
vendored
44
.github/workflows/trigger-deploy.yml
vendored
@@ -1,44 +0,0 @@
|
|||||||
name: Trigger.dev Deploy
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
- staging
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
deploy:
|
|
||||||
name: Trigger.dev Deploy
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
concurrency:
|
|
||||||
group: trigger-deploy-${{ github.ref }}
|
|
||||||
cancel-in-progress: false
|
|
||||||
env:
|
|
||||||
TRIGGER_ACCESS_TOKEN: ${{ secrets.TRIGGER_ACCESS_TOKEN }}
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Setup Node.js
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: 'lts/*'
|
|
||||||
|
|
||||||
- name: Setup Bun
|
|
||||||
uses: oven-sh/setup-bun@v2
|
|
||||||
with:
|
|
||||||
bun-version: latest
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: bun install
|
|
||||||
|
|
||||||
- name: Deploy to Staging
|
|
||||||
if: github.ref == 'refs/heads/staging'
|
|
||||||
working-directory: ./apps/sim
|
|
||||||
run: npx --yes trigger.dev@4.0.1 deploy -e staging
|
|
||||||
|
|
||||||
- name: Deploy to Production
|
|
||||||
if: github.ref == 'refs/heads/main'
|
|
||||||
working-directory: ./apps/sim
|
|
||||||
run: npx --yes trigger.dev@4.0.1 deploy
|
|
||||||
|
|
||||||
8
.gitignore
vendored
8
.gitignore
vendored
@@ -46,7 +46,7 @@ sim-standalone.tar.gz
|
|||||||
next-env.d.ts
|
next-env.d.ts
|
||||||
|
|
||||||
# cursorrules
|
# cursorrules
|
||||||
.cursorrules
|
# .cursorrules
|
||||||
|
|
||||||
# docs
|
# docs
|
||||||
/apps/docs/.source
|
/apps/docs/.source
|
||||||
@@ -67,5 +67,9 @@ start-collector.sh
|
|||||||
# VSCode
|
# VSCode
|
||||||
.vscode
|
.vscode
|
||||||
|
|
||||||
|
# IntelliJ
|
||||||
|
.idea
|
||||||
|
|
||||||
## Helm Chart Tests
|
## Helm Chart Tests
|
||||||
helm/sim/test
|
helm/sim/test
|
||||||
|
i18n.cache
|
||||||
|
|||||||
295
CLAUDE.md
Normal file
295
CLAUDE.md
Normal file
@@ -0,0 +1,295 @@
|
|||||||
|
# Sim Studio Development Guidelines
|
||||||
|
|
||||||
|
You are a professional software engineer. All code must follow best practices: accurate, readable, clean, and efficient.
|
||||||
|
|
||||||
|
## Global Standards
|
||||||
|
|
||||||
|
- **Logging**: Import `createLogger` from `@sim/logger`. Use `logger.info`, `logger.warn`, `logger.error` instead of `console.log`
|
||||||
|
- **Comments**: Use TSDoc for documentation. No `====` separators. No non-TSDoc comments
|
||||||
|
- **Styling**: Never update global styles. Keep all styling local to components
|
||||||
|
- **Package Manager**: Use `bun` and `bunx`, not `npm` and `npx`
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Core Principles
|
||||||
|
1. Single Responsibility: Each component, hook, store has one clear purpose
|
||||||
|
2. Composition Over Complexity: Break down complex logic into smaller pieces
|
||||||
|
3. Type Safety First: TypeScript interfaces for all props, state, return types
|
||||||
|
4. Predictable State: Zustand for global state, useState for UI-only concerns
|
||||||
|
|
||||||
|
### Root Structure
|
||||||
|
```
|
||||||
|
apps/sim/
|
||||||
|
├── app/ # Next.js app router (pages, API routes)
|
||||||
|
├── blocks/ # Block definitions and registry
|
||||||
|
├── components/ # Shared UI (emcn/, ui/)
|
||||||
|
├── executor/ # Workflow execution engine
|
||||||
|
├── hooks/ # Shared hooks (queries/, selectors/)
|
||||||
|
├── lib/ # App-wide utilities
|
||||||
|
├── providers/ # LLM provider integrations
|
||||||
|
├── stores/ # Zustand stores
|
||||||
|
├── tools/ # Tool definitions
|
||||||
|
└── triggers/ # Trigger definitions
|
||||||
|
```
|
||||||
|
|
||||||
|
### Naming Conventions
|
||||||
|
- Components: PascalCase (`WorkflowList`)
|
||||||
|
- Hooks: `use` prefix (`useWorkflowOperations`)
|
||||||
|
- Files: kebab-case (`workflow-list.tsx`)
|
||||||
|
- Stores: `stores/feature/store.ts`
|
||||||
|
- Constants: SCREAMING_SNAKE_CASE
|
||||||
|
- Interfaces: PascalCase with suffix (`WorkflowListProps`)
|
||||||
|
|
||||||
|
## Imports
|
||||||
|
|
||||||
|
**Always use absolute imports.** Never use relative imports.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ✓ Good
|
||||||
|
import { useWorkflowStore } from '@/stores/workflows/store'
|
||||||
|
|
||||||
|
// ✗ Bad
|
||||||
|
import { useWorkflowStore } from '../../../stores/workflows/store'
|
||||||
|
```
|
||||||
|
|
||||||
|
Use barrel exports (`index.ts`) when a folder has 3+ exports. Do not re-export from non-barrel files; import directly from the source.
|
||||||
|
|
||||||
|
### Import Order
|
||||||
|
1. React/core libraries
|
||||||
|
2. External libraries
|
||||||
|
3. UI components (`@/components/emcn`, `@/components/ui`)
|
||||||
|
4. Utilities (`@/lib/...`)
|
||||||
|
5. Stores (`@/stores/...`)
|
||||||
|
6. Feature imports
|
||||||
|
7. CSS imports
|
||||||
|
|
||||||
|
Use `import type { X }` for type-only imports.
|
||||||
|
|
||||||
|
## TypeScript
|
||||||
|
|
||||||
|
1. No `any` - Use proper types or `unknown` with type guards
|
||||||
|
2. Always define props interface for components
|
||||||
|
3. `as const` for constant objects/arrays
|
||||||
|
4. Explicit ref types: `useRef<HTMLDivElement>(null)`
|
||||||
|
|
||||||
|
## Components
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
'use client' // Only if using hooks
|
||||||
|
|
||||||
|
const CONFIG = { SPACING: 8 } as const
|
||||||
|
|
||||||
|
interface ComponentProps {
|
||||||
|
requiredProp: string
|
||||||
|
optionalProp?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Component({ requiredProp, optionalProp = false }: ComponentProps) {
|
||||||
|
// Order: refs → external hooks → store hooks → custom hooks → state → useMemo → useCallback → useEffect → return
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Extract when: 50+ lines, used in 2+ files, or has own state/logic. Keep inline when: < 10 lines, single use, purely presentational.
|
||||||
|
|
||||||
|
## Hooks
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface UseFeatureProps { id: string }
|
||||||
|
|
||||||
|
export function useFeature({ id }: UseFeatureProps) {
|
||||||
|
const idRef = useRef(id)
|
||||||
|
const [data, setData] = useState<Data | null>(null)
|
||||||
|
|
||||||
|
useEffect(() => { idRef.current = id }, [id])
|
||||||
|
|
||||||
|
const fetchData = useCallback(async () => { ... }, []) // Empty deps when using refs
|
||||||
|
|
||||||
|
return { data, fetchData }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Zustand Stores
|
||||||
|
|
||||||
|
Stores live in `stores/`. Complex stores split into `store.ts` + `types.ts`.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { create } from 'zustand'
|
||||||
|
import { devtools } from 'zustand/middleware'
|
||||||
|
|
||||||
|
const initialState = { items: [] as Item[] }
|
||||||
|
|
||||||
|
export const useFeatureStore = create<FeatureState>()(
|
||||||
|
devtools(
|
||||||
|
(set, get) => ({
|
||||||
|
...initialState,
|
||||||
|
setItems: (items) => set({ items }),
|
||||||
|
reset: () => set(initialState),
|
||||||
|
}),
|
||||||
|
{ name: 'feature-store' }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Use `devtools` middleware. Use `persist` only when data should survive reload with `partialize` to persist only necessary state.
|
||||||
|
|
||||||
|
## React Query
|
||||||
|
|
||||||
|
All React Query hooks live in `hooks/queries/`.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export const entityKeys = {
|
||||||
|
all: ['entity'] as const,
|
||||||
|
list: (workspaceId?: string) => [...entityKeys.all, 'list', workspaceId ?? ''] as const,
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useEntityList(workspaceId?: string) {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: entityKeys.list(workspaceId),
|
||||||
|
queryFn: () => fetchEntities(workspaceId as string),
|
||||||
|
enabled: Boolean(workspaceId),
|
||||||
|
staleTime: 60 * 1000,
|
||||||
|
placeholderData: keepPreviousData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Styling
|
||||||
|
|
||||||
|
Use Tailwind only, no inline styles. Use `cn()` from `@/lib/utils` for conditional classes.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
<div className={cn('base-classes', isActive && 'active-classes')} />
|
||||||
|
```
|
||||||
|
|
||||||
|
## EMCN Components
|
||||||
|
|
||||||
|
Import from `@/components/emcn`, never from subpaths (except CSS files). Use CVA when 2+ variants exist.
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
Use Vitest. Test files: `feature.ts` → `feature.test.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
/**
|
||||||
|
* @vitest-environment node
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Mocks BEFORE imports
|
||||||
|
vi.mock('@sim/db', () => ({ db: { select: vi.fn() } }))
|
||||||
|
|
||||||
|
// Imports AFTER mocks
|
||||||
|
import { describe, expect, it, vi } from 'vitest'
|
||||||
|
import { createSession, loggerMock } from '@sim/testing'
|
||||||
|
|
||||||
|
describe('feature', () => {
|
||||||
|
beforeEach(() => vi.clearAllMocks())
|
||||||
|
it.concurrent('runs in parallel', () => { ... })
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Use `@sim/testing` factories over manual test data.
|
||||||
|
|
||||||
|
## Utils Rules
|
||||||
|
|
||||||
|
- Never create `utils.ts` for single consumer - inline it
|
||||||
|
- Create `utils.ts` when 2+ files need the same helper
|
||||||
|
- Check existing sources in `lib/` before duplicating
|
||||||
|
|
||||||
|
## Adding Integrations
|
||||||
|
|
||||||
|
New integrations require: **Tools** → **Block** → **Icon** → (optional) **Trigger**
|
||||||
|
|
||||||
|
Always look up the service's API docs first.
|
||||||
|
|
||||||
|
### 1. Tools (`tools/{service}/`)
|
||||||
|
|
||||||
|
```
|
||||||
|
tools/{service}/
|
||||||
|
├── index.ts # Barrel export
|
||||||
|
├── types.ts # Params/response types
|
||||||
|
└── {action}.ts # Tool implementation
|
||||||
|
```
|
||||||
|
|
||||||
|
**Tool structure:**
|
||||||
|
```typescript
|
||||||
|
export const serviceTool: ToolConfig<Params, Response> = {
|
||||||
|
id: 'service_action',
|
||||||
|
name: 'Service Action',
|
||||||
|
description: '...',
|
||||||
|
version: '1.0.0',
|
||||||
|
oauth: { required: true, provider: 'service' },
|
||||||
|
params: { /* ... */ },
|
||||||
|
request: { url: '/api/tools/service/action', method: 'POST', ... },
|
||||||
|
transformResponse: async (response) => { /* ... */ },
|
||||||
|
outputs: { /* ... */ },
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Register in `tools/registry.ts`.
|
||||||
|
|
||||||
|
### 2. Block (`blocks/blocks/{service}.ts`)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export const ServiceBlock: BlockConfig = {
|
||||||
|
type: 'service',
|
||||||
|
name: 'Service',
|
||||||
|
description: '...',
|
||||||
|
category: 'tools',
|
||||||
|
bgColor: '#hexcolor',
|
||||||
|
icon: ServiceIcon,
|
||||||
|
subBlocks: [ /* see SubBlock Properties */ ],
|
||||||
|
tools: { access: ['service_action'], config: { tool: (p) => `service_${p.operation}` } },
|
||||||
|
inputs: { /* ... */ },
|
||||||
|
outputs: { /* ... */ },
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Register in `blocks/registry.ts` (alphabetically).
|
||||||
|
|
||||||
|
**SubBlock Properties:**
|
||||||
|
```typescript
|
||||||
|
{
|
||||||
|
id: 'field', title: 'Label', type: 'short-input', placeholder: '...',
|
||||||
|
required: true, // or condition object
|
||||||
|
condition: { field: 'op', value: 'send' }, // show/hide
|
||||||
|
dependsOn: ['credential'], // clear when dep changes
|
||||||
|
mode: 'basic', // 'basic' | 'advanced' | 'both' | 'trigger'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**condition examples:**
|
||||||
|
- `{ field: 'op', value: 'send' }` - show when op === 'send'
|
||||||
|
- `{ field: 'op', value: ['a','b'] }` - show when op is 'a' OR 'b'
|
||||||
|
- `{ field: 'op', value: 'x', not: true }` - show when op !== 'x'
|
||||||
|
- `{ field: 'op', value: 'x', not: true, and: { field: 'type', value: 'dm', not: true } }` - complex
|
||||||
|
|
||||||
|
**dependsOn:** `['field']` or `{ all: ['a'], any: ['b', 'c'] }`
|
||||||
|
|
||||||
|
### 3. Icon (`components/icons.tsx`)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export function ServiceIcon(props: SVGProps<SVGSVGElement>) {
|
||||||
|
return <svg {...props}>/* SVG from brand assets */</svg>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Trigger (`triggers/{service}/`) - Optional
|
||||||
|
|
||||||
|
```
|
||||||
|
triggers/{service}/
|
||||||
|
├── index.ts # Barrel export
|
||||||
|
├── webhook.ts # Webhook handler
|
||||||
|
└── {event}.ts # Event-specific handlers
|
||||||
|
```
|
||||||
|
|
||||||
|
Register in `triggers/registry.ts`.
|
||||||
|
|
||||||
|
### Integration Checklist
|
||||||
|
|
||||||
|
- [ ] Look up API docs
|
||||||
|
- [ ] Create `tools/{service}/` with types and tools
|
||||||
|
- [ ] Register tools in `tools/registry.ts`
|
||||||
|
- [ ] Add icon to `components/icons.tsx`
|
||||||
|
- [ ] Create block in `blocks/blocks/{service}.ts`
|
||||||
|
- [ ] Register block in `blocks/registry.ts`
|
||||||
|
- [ ] (Optional) Create and register triggers
|
||||||
2
LICENSE
2
LICENSE
@@ -187,7 +187,7 @@
|
|||||||
same "printed page" as the copyright notice for easier
|
same "printed page" as the copyright notice for easier
|
||||||
identification within third-party archives.
|
identification within third-party archives.
|
||||||
|
|
||||||
Copyright 2025 Sim Studio, Inc.
|
Copyright 2026 Sim Studio, Inc.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
|||||||
2
NOTICE
2
NOTICE
@@ -1,4 +1,4 @@
|
|||||||
Sim Studio
|
Sim Studio
|
||||||
Copyright 2025 Sim Studio
|
Copyright 2026 Sim Studio
|
||||||
|
|
||||||
This product includes software developed for the Sim project.
|
This product includes software developed for the Sim project.
|
||||||
108
README.md
108
README.md
@@ -13,8 +13,25 @@
|
|||||||
<a href="https://docs.sim.ai" target="_blank" rel="noopener noreferrer"><img src="https://img.shields.io/badge/Docs-6F3DFA.svg" alt="Documentation"></a>
|
<a href="https://docs.sim.ai" target="_blank" rel="noopener noreferrer"><img src="https://img.shields.io/badge/Docs-6F3DFA.svg" alt="Documentation"></a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
### Build Workflows with Ease
|
||||||
|
Design agent workflows visually on a canvas—connect agents, tools, and blocks, then run them instantly.
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="apps/sim/public/static/demo.gif" alt="Sim Demo" width="800"/>
|
<img src="apps/sim/public/static/workflow.gif" alt="Workflow Builder Demo" width="800"/>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
### Supercharge with Copilot
|
||||||
|
Leverage Copilot to generate nodes, fix errors, and iterate on flows directly from natural language.
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img src="apps/sim/public/static/copilot.gif" alt="Copilot Demo" width="800"/>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
### Integrate Vector Databases
|
||||||
|
Upload documents to a vector store and let agents answer questions grounded in your specific content.
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img src="apps/sim/public/static/knowledge.gif" alt="Knowledge Uploads and Retrieval Demo" width="800"/>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
## Quickstart
|
## Quickstart
|
||||||
@@ -72,6 +89,36 @@ Wait for the model to download, then visit [http://localhost:3000](http://localh
|
|||||||
docker compose -f docker-compose.ollama.yml exec ollama ollama pull llama3.1:8b
|
docker compose -f docker-compose.ollama.yml exec ollama ollama pull llama3.1:8b
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Using an External Ollama Instance
|
||||||
|
|
||||||
|
If you already have Ollama running on your host machine (outside Docker), you need to configure the `OLLAMA_URL` to use `host.docker.internal` instead of `localhost`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Docker Desktop (macOS/Windows)
|
||||||
|
OLLAMA_URL=http://host.docker.internal:11434 docker compose -f docker-compose.prod.yml up -d
|
||||||
|
|
||||||
|
# Linux (add extra_hosts or use host IP)
|
||||||
|
docker compose -f docker-compose.prod.yml up -d # Then set OLLAMA_URL to your host's IP
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why?** When running inside Docker, `localhost` refers to the container itself, not your host machine. `host.docker.internal` is a special DNS name that resolves to the host.
|
||||||
|
|
||||||
|
For Linux users, you can either:
|
||||||
|
- Use your host machine's actual IP address (e.g., `http://192.168.1.100:11434`)
|
||||||
|
- Add `extra_hosts: ["host.docker.internal:host-gateway"]` to the simstudio service in your compose file
|
||||||
|
|
||||||
|
#### Using vLLM
|
||||||
|
|
||||||
|
Sim also supports [vLLM](https://docs.vllm.ai/) for self-hosted models with OpenAI-compatible API:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Set these environment variables
|
||||||
|
VLLM_BASE_URL=http://your-vllm-server:8000
|
||||||
|
VLLM_API_KEY=your_optional_api_key # Only if your vLLM instance requires auth
|
||||||
|
```
|
||||||
|
|
||||||
|
When running with Docker, use `host.docker.internal` if vLLM is on your host machine (same as Ollama above).
|
||||||
|
|
||||||
### Self-hosted: Dev Containers
|
### Self-hosted: Dev Containers
|
||||||
|
|
||||||
1. Open VS Code with the [Remote - Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers)
|
1. Open VS Code with the [Remote - Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers)
|
||||||
@@ -83,6 +130,7 @@ docker compose -f docker-compose.ollama.yml exec ollama ollama pull llama3.1:8b
|
|||||||
|
|
||||||
**Requirements:**
|
**Requirements:**
|
||||||
- [Bun](https://bun.sh/) runtime
|
- [Bun](https://bun.sh/) runtime
|
||||||
|
- [Node.js](https://nodejs.org/) v20+ (required for sandboxed code execution)
|
||||||
- PostgreSQL 12+ with [pgvector extension](https://github.com/pgvector/pgvector) (required for AI embeddings)
|
- PostgreSQL 12+ with [pgvector extension](https://github.com/pgvector/pgvector) (required for AI embeddings)
|
||||||
|
|
||||||
**Note:** Sim uses vector embeddings for AI features like knowledge bases and semantic search, which requires the `pgvector` PostgreSQL extension.
|
**Note:** Sim uses vector embeddings for AI features like knowledge bases and semantic search, which requires the `pgvector` PostgreSQL extension.
|
||||||
@@ -127,8 +175,21 @@ DATABASE_URL="postgresql://postgres:your_password@localhost:5432/simstudio"
|
|||||||
|
|
||||||
4. Set up the database:
|
4. Set up the database:
|
||||||
|
|
||||||
|
First, configure the database package environment:
|
||||||
```bash
|
```bash
|
||||||
bunx drizzle-kit migrate
|
cd packages/db
|
||||||
|
cp .env.example .env
|
||||||
|
```
|
||||||
|
|
||||||
|
Update your `packages/db/.env` file with the database URL:
|
||||||
|
```bash
|
||||||
|
DATABASE_URL="postgresql://postgres:your_password@localhost:5432/simstudio"
|
||||||
|
```
|
||||||
|
|
||||||
|
Then run the migrations:
|
||||||
|
```bash
|
||||||
|
cd packages/db # Required so drizzle picks correct .env file
|
||||||
|
bunx drizzle-kit migrate --config=./drizzle.config.ts
|
||||||
```
|
```
|
||||||
|
|
||||||
5. Start the development servers:
|
5. Start the development servers:
|
||||||
@@ -159,7 +220,47 @@ bun run dev:sockets
|
|||||||
Copilot is a Sim-managed service. To use Copilot on a self-hosted instance:
|
Copilot is a Sim-managed service. To use Copilot on a self-hosted instance:
|
||||||
|
|
||||||
- Go to https://sim.ai → Settings → Copilot and generate a Copilot API key
|
- Go to https://sim.ai → Settings → Copilot and generate a Copilot API key
|
||||||
- Set `COPILOT_API_KEY` in your self-hosted environment to that value
|
- Set `COPILOT_API_KEY` environment variable in your self-hosted apps/sim/.env file to that value
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
Key environment variables for self-hosted deployments (see `apps/sim/.env.example` for full list):
|
||||||
|
|
||||||
|
| Variable | Required | Description |
|
||||||
|
|----------|----------|-------------|
|
||||||
|
| `DATABASE_URL` | Yes | PostgreSQL connection string with pgvector |
|
||||||
|
| `BETTER_AUTH_SECRET` | Yes | Auth secret (`openssl rand -hex 32`) |
|
||||||
|
| `BETTER_AUTH_URL` | Yes | Your app URL (e.g., `http://localhost:3000`) |
|
||||||
|
| `NEXT_PUBLIC_APP_URL` | Yes | Public app URL (same as above) |
|
||||||
|
| `ENCRYPTION_KEY` | Yes | Encryption key (`openssl rand -hex 32`) |
|
||||||
|
| `OLLAMA_URL` | No | Ollama server URL (default: `http://localhost:11434`) |
|
||||||
|
| `VLLM_BASE_URL` | No | vLLM server URL for self-hosted models |
|
||||||
|
| `COPILOT_API_KEY` | No | API key from sim.ai for Copilot features |
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Ollama models not showing in dropdown (Docker)
|
||||||
|
|
||||||
|
If you're running Ollama on your host machine and Sim in Docker, change `OLLAMA_URL` from `localhost` to `host.docker.internal`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
OLLAMA_URL=http://host.docker.internal:11434 docker compose -f docker-compose.prod.yml up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
See [Using an External Ollama Instance](#using-an-external-ollama-instance) for details.
|
||||||
|
|
||||||
|
### Database connection issues
|
||||||
|
|
||||||
|
Ensure PostgreSQL has the pgvector extension installed. When using Docker, wait for the database to be healthy before running migrations.
|
||||||
|
|
||||||
|
### Port conflicts
|
||||||
|
|
||||||
|
If ports 3000, 3002, or 5432 are in use, configure alternatives:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Custom ports
|
||||||
|
NEXT_PUBLIC_APP_URL=http://localhost:3100 POSTGRES_PORT=5433 docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
## Tech Stack
|
## Tech Stack
|
||||||
|
|
||||||
@@ -174,6 +275,7 @@ Copilot is a Sim-managed service. To use Copilot on a self-hosted instance:
|
|||||||
- **Monorepo**: [Turborepo](https://turborepo.org/)
|
- **Monorepo**: [Turborepo](https://turborepo.org/)
|
||||||
- **Realtime**: [Socket.io](https://socket.io/)
|
- **Realtime**: [Socket.io](https://socket.io/)
|
||||||
- **Background Jobs**: [Trigger.dev](https://trigger.dev/)
|
- **Background Jobs**: [Trigger.dev](https://trigger.dev/)
|
||||||
|
- **Remote Code Execution**: [E2B](https://www.e2b.dev/)
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
import type { ReactNode } from 'react'
|
|
||||||
|
|
||||||
export default function SlugLayout({ children }: { children: ReactNode }) {
|
|
||||||
return children
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
import { DocsBody, DocsDescription, DocsPage, DocsTitle } from 'fumadocs-ui/page'
|
|
||||||
import { notFound } from 'next/navigation'
|
|
||||||
import mdxComponents from '@/components/mdx-components'
|
|
||||||
import { source } from '@/lib/source'
|
|
||||||
|
|
||||||
export const dynamic = 'force-dynamic'
|
|
||||||
|
|
||||||
export default async function Page(props: { params: Promise<{ slug?: string[] }> }) {
|
|
||||||
const params = await props.params
|
|
||||||
const page = source.getPage(params.slug)
|
|
||||||
if (!page) notFound()
|
|
||||||
|
|
||||||
const MDX = page.data.body
|
|
||||||
|
|
||||||
return (
|
|
||||||
<DocsPage
|
|
||||||
toc={page.data.toc}
|
|
||||||
full={page.data.full}
|
|
||||||
tableOfContent={{
|
|
||||||
style: 'clerk',
|
|
||||||
enabled: true,
|
|
||||||
header: <div className='mb-2 font-medium text-sm'>On this page</div>,
|
|
||||||
single: false,
|
|
||||||
}}
|
|
||||||
article={{
|
|
||||||
className: 'scroll-smooth max-sm:pb-16',
|
|
||||||
}}
|
|
||||||
tableOfContentPopover={{
|
|
||||||
style: 'clerk',
|
|
||||||
enabled: true,
|
|
||||||
}}
|
|
||||||
footer={{
|
|
||||||
enabled: false,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<DocsTitle>{page.data.title}</DocsTitle>
|
|
||||||
<DocsDescription>{page.data.description}</DocsDescription>
|
|
||||||
<DocsBody>
|
|
||||||
<MDX components={mdxComponents} />
|
|
||||||
</DocsBody>
|
|
||||||
</DocsPage>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function generateStaticParams() {
|
|
||||||
return source.generateParams()
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function generateMetadata(props: { params: Promise<{ slug?: string[] }> }) {
|
|
||||||
const params = await props.params
|
|
||||||
const page = source.getPage(params.slug)
|
|
||||||
if (!page) notFound()
|
|
||||||
|
|
||||||
return {
|
|
||||||
title: page.data.title,
|
|
||||||
description: page.data.description,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
import type { ReactNode } from 'react'
|
|
||||||
import { DocsLayout } from 'fumadocs-ui/layouts/docs'
|
|
||||||
import { ExternalLink, GithubIcon } from 'lucide-react'
|
|
||||||
import Link from 'next/link'
|
|
||||||
import { source } from '@/lib/source'
|
|
||||||
|
|
||||||
const GitHubLink = () => (
|
|
||||||
<div className='fixed right-4 bottom-4 z-50'>
|
|
||||||
<Link
|
|
||||||
href='https://github.com/simstudioai/sim'
|
|
||||||
target='_blank'
|
|
||||||
rel='noopener noreferrer'
|
|
||||||
className='flex h-8 w-8 items-center justify-center rounded-full border border-border bg-background transition-colors hover:bg-muted'
|
|
||||||
>
|
|
||||||
<GithubIcon className='h-4 w-4' />
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
|
|
||||||
export default function Layout({ children }: { children: ReactNode }) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<DocsLayout
|
|
||||||
tree={source.pageTree}
|
|
||||||
nav={{
|
|
||||||
title: <div className='flex items-center font-medium'>Sim</div>,
|
|
||||||
}}
|
|
||||||
links={[
|
|
||||||
{
|
|
||||||
text: 'Visit Sim',
|
|
||||||
url: 'https://sim.ai',
|
|
||||||
icon: <ExternalLink className='h-4 w-4' />,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
sidebar={{
|
|
||||||
defaultOpenLevel: 1,
|
|
||||||
collapsible: true,
|
|
||||||
footer: null,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</DocsLayout>
|
|
||||||
<GitHubLink />
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
335
apps/docs/app/[lang]/[[...slug]]/page.tsx
Normal file
335
apps/docs/app/[lang]/[[...slug]]/page.tsx
Normal file
@@ -0,0 +1,335 @@
|
|||||||
|
import type React from 'react'
|
||||||
|
import { findNeighbour } from 'fumadocs-core/page-tree'
|
||||||
|
import defaultMdxComponents from 'fumadocs-ui/mdx'
|
||||||
|
import { DocsBody, DocsDescription, DocsPage, DocsTitle } from 'fumadocs-ui/page'
|
||||||
|
import { ChevronLeft, ChevronRight } from 'lucide-react'
|
||||||
|
import Link from 'next/link'
|
||||||
|
import { notFound } from 'next/navigation'
|
||||||
|
import { PageNavigationArrows } from '@/components/docs-layout/page-navigation-arrows'
|
||||||
|
import { TOCFooter } from '@/components/docs-layout/toc-footer'
|
||||||
|
import { LLMCopyButton } from '@/components/page-actions'
|
||||||
|
import { StructuredData } from '@/components/structured-data'
|
||||||
|
import { CodeBlock } from '@/components/ui/code-block'
|
||||||
|
import { Heading } from '@/components/ui/heading'
|
||||||
|
import { type PageData, source } from '@/lib/source'
|
||||||
|
|
||||||
|
export default async function Page(props: { params: Promise<{ slug?: string[]; lang: string }> }) {
|
||||||
|
const params = await props.params
|
||||||
|
const page = source.getPage(params.slug, params.lang)
|
||||||
|
if (!page) notFound()
|
||||||
|
|
||||||
|
const data = page.data as PageData
|
||||||
|
const MDX = data.body
|
||||||
|
const baseUrl = 'https://docs.sim.ai'
|
||||||
|
|
||||||
|
const pageTreeRecord = source.pageTree as Record<string, any>
|
||||||
|
const pageTree =
|
||||||
|
pageTreeRecord[params.lang] ?? pageTreeRecord.en ?? Object.values(pageTreeRecord)[0]
|
||||||
|
const neighbours = pageTree ? findNeighbour(pageTree, page.url) : null
|
||||||
|
|
||||||
|
const generateBreadcrumbs = () => {
|
||||||
|
const breadcrumbs: Array<{ name: string; url: string }> = [
|
||||||
|
{
|
||||||
|
name: 'Home',
|
||||||
|
url: baseUrl,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const urlParts = page.url.split('/').filter(Boolean)
|
||||||
|
let currentPath = ''
|
||||||
|
|
||||||
|
urlParts.forEach((part, index) => {
|
||||||
|
if (index === 0 && ['en', 'es', 'fr', 'de', 'ja', 'zh'].includes(part)) {
|
||||||
|
currentPath = `/${part}`
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
currentPath += `/${part}`
|
||||||
|
|
||||||
|
const name = part
|
||||||
|
.split('-')
|
||||||
|
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
||||||
|
.join(' ')
|
||||||
|
|
||||||
|
if (index === urlParts.length - 1) {
|
||||||
|
breadcrumbs.push({
|
||||||
|
name: data.title,
|
||||||
|
url: `${baseUrl}${page.url}`,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
breadcrumbs.push({
|
||||||
|
name: name,
|
||||||
|
url: `${baseUrl}${currentPath}`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return breadcrumbs
|
||||||
|
}
|
||||||
|
|
||||||
|
const breadcrumbs = generateBreadcrumbs()
|
||||||
|
|
||||||
|
const CustomFooter = () => (
|
||||||
|
<div className='mt-12'>
|
||||||
|
{/* Navigation links */}
|
||||||
|
<div className='flex items-center justify-between py-8'>
|
||||||
|
{neighbours?.previous ? (
|
||||||
|
<Link
|
||||||
|
href={neighbours.previous.url}
|
||||||
|
className='group flex items-center gap-2 text-muted-foreground transition-colors hover:text-foreground'
|
||||||
|
>
|
||||||
|
<ChevronLeft className='group-hover:-translate-x-1 h-4 w-4 transition-transform' />
|
||||||
|
<span className='font-medium'>{neighbours.previous.name}</span>
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
<div />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{neighbours?.next ? (
|
||||||
|
<Link
|
||||||
|
href={neighbours.next.url}
|
||||||
|
className='group flex items-center gap-2 text-muted-foreground transition-colors hover:text-foreground'
|
||||||
|
>
|
||||||
|
<span className='font-medium'>{neighbours.next.name}</span>
|
||||||
|
<ChevronRight className='h-4 w-4 transition-transform group-hover:translate-x-1' />
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
<div />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Divider line */}
|
||||||
|
<div className='border-border border-t' />
|
||||||
|
|
||||||
|
{/* Social icons */}
|
||||||
|
<div className='flex items-center gap-4 py-6'>
|
||||||
|
<Link
|
||||||
|
href='https://x.com/simdotai'
|
||||||
|
target='_blank'
|
||||||
|
rel='noopener noreferrer'
|
||||||
|
aria-label='X (Twitter)'
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className='h-5 w-5 bg-gray-400 transition-colors hover:bg-gray-500 dark:bg-gray-500 dark:hover:bg-gray-400'
|
||||||
|
style={{
|
||||||
|
maskImage:
|
||||||
|
"url('data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 24 24%22%3E%3Cpath d=%22M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z%22/%3E%3C/svg%3E')",
|
||||||
|
maskRepeat: 'no-repeat',
|
||||||
|
maskPosition: 'center center',
|
||||||
|
WebkitMaskImage:
|
||||||
|
"url('data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 24 24%22%3E%3Cpath d=%22M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z%22/%3E%3C/svg%3E')",
|
||||||
|
WebkitMaskRepeat: 'no-repeat',
|
||||||
|
WebkitMaskPosition: 'center center',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
href='https://github.com/simstudioai/sim'
|
||||||
|
target='_blank'
|
||||||
|
rel='noopener noreferrer'
|
||||||
|
aria-label='GitHub'
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className='h-5 w-5 bg-gray-400 transition-colors hover:bg-gray-500 dark:bg-gray-500 dark:hover:bg-gray-400'
|
||||||
|
style={{
|
||||||
|
maskImage:
|
||||||
|
"url('data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 24 24%22%3E%3Cpath d=%22M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z%22/%3E%3C/svg%3E')",
|
||||||
|
maskRepeat: 'no-repeat',
|
||||||
|
maskPosition: 'center center',
|
||||||
|
WebkitMaskImage:
|
||||||
|
"url('data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 24 24%22%3E%3Cpath d=%22M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z%22/%3E%3C/svg%3E')",
|
||||||
|
WebkitMaskRepeat: 'no-repeat',
|
||||||
|
WebkitMaskPosition: 'center center',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
href='https://discord.gg/Hr4UWYEcTT'
|
||||||
|
target='_blank'
|
||||||
|
rel='noopener noreferrer'
|
||||||
|
aria-label='Discord'
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className='h-5 w-5 bg-gray-400 transition-colors hover:bg-gray-500 dark:bg-gray-500 dark:hover:bg-gray-400'
|
||||||
|
style={{
|
||||||
|
maskImage:
|
||||||
|
"url('data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 24 24%22%3E%3Cpath d=%22M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515a.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0a12.64 12.64 0 0 0-.617-1.25a.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057a19.9 19.9 0 0 0 5.993 3.03a.078.078 0 0 0 .084-.028a14.09 14.09 0 0 0 1.226-1.994a.076.076 0 0 0-.041-.106a13.107 13.107 0 0 1-1.872-.892a.077.077 0 0 1-.008-.128a10.2 10.2 0 0 0 .372-.292a.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127a12.299 12.299 0 0 1-1.873.892a.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028a19.839 19.839 0 0 0 6.002-3.03a.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.956-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.955-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.946 2.418-2.157 2.418z%22/%3E%3C/svg%3E')",
|
||||||
|
maskRepeat: 'no-repeat',
|
||||||
|
maskPosition: 'center center',
|
||||||
|
WebkitMaskImage:
|
||||||
|
"url('data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 24 24%22%3E%3Cpath d=%22M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515a.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0a12.64 12.64 0 0 0-.617-1.25a.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057a19.9 19.9 0 0 0 5.993 3.03a.078.078 0 0 0 .084-.028a14.09 14.09 0 0 0 1.226-1.994a.076.076 0 0 0-.041-.106a13.107 13.107 0 0 1-1.872-.892a.077.077 0 0 1-.008-.128a10.2 10.2 0 0 0 .372-.292a.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127a12.299 12.299 0 0 1-1.873.892a.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028a19.839 19.839 0 0 0 6.002-3.03a.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.956-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.955-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.946 2.418-2.157 2.418z%22/%3E%3C/svg%3E')",
|
||||||
|
WebkitMaskRepeat: 'no-repeat',
|
||||||
|
WebkitMaskPosition: 'center center',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<StructuredData
|
||||||
|
title={data.title}
|
||||||
|
description={data.description || ''}
|
||||||
|
url={`${baseUrl}${page.url}`}
|
||||||
|
lang={params.lang}
|
||||||
|
breadcrumb={breadcrumbs}
|
||||||
|
/>
|
||||||
|
<DocsPage
|
||||||
|
toc={data.toc}
|
||||||
|
full={data.full}
|
||||||
|
breadcrumb={{
|
||||||
|
enabled: false,
|
||||||
|
}}
|
||||||
|
tableOfContent={{
|
||||||
|
style: 'clerk',
|
||||||
|
enabled: true,
|
||||||
|
header: (
|
||||||
|
<div key='toc-header' className='mb-2 font-medium text-sm'>
|
||||||
|
On this page
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
footer: <TOCFooter />,
|
||||||
|
single: false,
|
||||||
|
}}
|
||||||
|
tableOfContentPopover={{
|
||||||
|
style: 'clerk',
|
||||||
|
enabled: true,
|
||||||
|
}}
|
||||||
|
footer={{
|
||||||
|
enabled: true,
|
||||||
|
component: <CustomFooter />,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className='relative mt-6 sm:mt-0'>
|
||||||
|
<div className='absolute top-1 right-0 flex items-center gap-2'>
|
||||||
|
<div className='hidden sm:flex'>
|
||||||
|
<LLMCopyButton markdownUrl={`${page.url}.mdx`} />
|
||||||
|
</div>
|
||||||
|
<PageNavigationArrows previous={neighbours?.previous} next={neighbours?.next} />
|
||||||
|
</div>
|
||||||
|
<DocsTitle>{data.title}</DocsTitle>
|
||||||
|
<DocsDescription>{data.description}</DocsDescription>
|
||||||
|
</div>
|
||||||
|
<DocsBody>
|
||||||
|
<MDX
|
||||||
|
components={{
|
||||||
|
...defaultMdxComponents,
|
||||||
|
CodeBlock,
|
||||||
|
h1: (props: React.HTMLAttributes<HTMLHeadingElement>) => (
|
||||||
|
<Heading as='h1' {...props} />
|
||||||
|
),
|
||||||
|
h2: (props: React.HTMLAttributes<HTMLHeadingElement>) => (
|
||||||
|
<Heading as='h2' {...props} />
|
||||||
|
),
|
||||||
|
h3: (props: React.HTMLAttributes<HTMLHeadingElement>) => (
|
||||||
|
<Heading as='h3' {...props} />
|
||||||
|
),
|
||||||
|
h4: (props: React.HTMLAttributes<HTMLHeadingElement>) => (
|
||||||
|
<Heading as='h4' {...props} />
|
||||||
|
),
|
||||||
|
h5: (props: React.HTMLAttributes<HTMLHeadingElement>) => (
|
||||||
|
<Heading as='h5' {...props} />
|
||||||
|
),
|
||||||
|
h6: (props: React.HTMLAttributes<HTMLHeadingElement>) => (
|
||||||
|
<Heading as='h6' {...props} />
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</DocsBody>
|
||||||
|
</DocsPage>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function generateStaticParams() {
|
||||||
|
return source.generateParams()
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function generateMetadata(props: {
|
||||||
|
params: Promise<{ slug?: string[]; lang: string }>
|
||||||
|
}) {
|
||||||
|
const params = await props.params
|
||||||
|
const page = source.getPage(params.slug, params.lang)
|
||||||
|
if (!page) notFound()
|
||||||
|
|
||||||
|
const data = page.data as PageData
|
||||||
|
const baseUrl = 'https://docs.sim.ai'
|
||||||
|
const fullUrl = `${baseUrl}${page.url}`
|
||||||
|
|
||||||
|
const ogImageUrl = `${baseUrl}/api/og?title=${encodeURIComponent(data.title)}`
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: data.title,
|
||||||
|
description:
|
||||||
|
data.description || 'Sim visual workflow builder for AI applications documentation',
|
||||||
|
keywords: [
|
||||||
|
'AI workflow builder',
|
||||||
|
'visual workflow editor',
|
||||||
|
'AI automation',
|
||||||
|
'workflow automation',
|
||||||
|
'AI agents',
|
||||||
|
'no-code AI',
|
||||||
|
'drag and drop workflows',
|
||||||
|
data.title?.toLowerCase().split(' '),
|
||||||
|
]
|
||||||
|
.flat()
|
||||||
|
.filter(Boolean),
|
||||||
|
authors: [{ name: 'Sim Team' }],
|
||||||
|
category: 'Developer Tools',
|
||||||
|
openGraph: {
|
||||||
|
title: data.title,
|
||||||
|
description:
|
||||||
|
data.description || 'Sim visual workflow builder for AI applications documentation',
|
||||||
|
url: fullUrl,
|
||||||
|
siteName: 'Sim Documentation',
|
||||||
|
type: 'article',
|
||||||
|
locale: params.lang === 'en' ? 'en_US' : `${params.lang}_${params.lang.toUpperCase()}`,
|
||||||
|
alternateLocale: ['en', 'es', 'fr', 'de', 'ja', 'zh']
|
||||||
|
.filter((lang) => lang !== params.lang)
|
||||||
|
.map((lang) => (lang === 'en' ? 'en_US' : `${lang}_${lang.toUpperCase()}`)),
|
||||||
|
images: [
|
||||||
|
{
|
||||||
|
url: ogImageUrl,
|
||||||
|
width: 1200,
|
||||||
|
height: 630,
|
||||||
|
alt: data.title,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
twitter: {
|
||||||
|
card: 'summary_large_image',
|
||||||
|
title: data.title,
|
||||||
|
description:
|
||||||
|
data.description || 'Sim visual workflow builder for AI applications documentation',
|
||||||
|
images: [ogImageUrl],
|
||||||
|
creator: '@simdotai',
|
||||||
|
site: '@simdotai',
|
||||||
|
},
|
||||||
|
robots: {
|
||||||
|
index: true,
|
||||||
|
follow: true,
|
||||||
|
googleBot: {
|
||||||
|
index: true,
|
||||||
|
follow: true,
|
||||||
|
'max-video-preview': -1,
|
||||||
|
'max-image-preview': 'large',
|
||||||
|
'max-snippet': -1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
canonical: fullUrl,
|
||||||
|
alternates: {
|
||||||
|
canonical: fullUrl,
|
||||||
|
languages: {
|
||||||
|
'x-default': `${baseUrl}${page.url.replace(`/${params.lang}`, '')}`,
|
||||||
|
en: `${baseUrl}${page.url.replace(`/${params.lang}`, '')}`,
|
||||||
|
es: `${baseUrl}/es${page.url.replace(`/${params.lang}`, '')}`,
|
||||||
|
fr: `${baseUrl}/fr${page.url.replace(`/${params.lang}`, '')}`,
|
||||||
|
de: `${baseUrl}/de${page.url.replace(`/${params.lang}`, '')}`,
|
||||||
|
ja: `${baseUrl}/ja${page.url.replace(`/${params.lang}`, '')}`,
|
||||||
|
zh: `${baseUrl}/zh${page.url.replace(`/${params.lang}`, '')}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
137
apps/docs/app/[lang]/layout.tsx
Normal file
137
apps/docs/app/[lang]/layout.tsx
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
import type { ReactNode } from 'react'
|
||||||
|
import { defineI18nUI } from 'fumadocs-ui/i18n'
|
||||||
|
import { DocsLayout } from 'fumadocs-ui/layouts/docs'
|
||||||
|
import { RootProvider } from 'fumadocs-ui/provider/next'
|
||||||
|
import { Geist_Mono, Inter } from 'next/font/google'
|
||||||
|
import Image from 'next/image'
|
||||||
|
import {
|
||||||
|
SidebarFolder,
|
||||||
|
SidebarItem,
|
||||||
|
SidebarSeparator,
|
||||||
|
} from '@/components/docs-layout/sidebar-components'
|
||||||
|
import { Navbar } from '@/components/navbar/navbar'
|
||||||
|
import { i18n } from '@/lib/i18n'
|
||||||
|
import { source } from '@/lib/source'
|
||||||
|
import '../global.css'
|
||||||
|
|
||||||
|
const inter = Inter({
|
||||||
|
subsets: ['latin'],
|
||||||
|
variable: '--font-geist-sans',
|
||||||
|
})
|
||||||
|
|
||||||
|
const geistMono = Geist_Mono({
|
||||||
|
subsets: ['latin'],
|
||||||
|
variable: '--font-geist-mono',
|
||||||
|
})
|
||||||
|
|
||||||
|
const { provider } = defineI18nUI(i18n, {
|
||||||
|
translations: {
|
||||||
|
en: {
|
||||||
|
displayName: 'English',
|
||||||
|
},
|
||||||
|
es: {
|
||||||
|
displayName: 'Español',
|
||||||
|
},
|
||||||
|
fr: {
|
||||||
|
displayName: 'Français',
|
||||||
|
},
|
||||||
|
de: {
|
||||||
|
displayName: 'Deutsch',
|
||||||
|
},
|
||||||
|
ja: {
|
||||||
|
displayName: '日本語',
|
||||||
|
},
|
||||||
|
zh: {
|
||||||
|
displayName: '简体中文',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
type LayoutProps = {
|
||||||
|
children: ReactNode
|
||||||
|
params: Promise<{ lang: string }>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function Layout({ children, params }: LayoutProps) {
|
||||||
|
const { lang } = await params
|
||||||
|
|
||||||
|
const structuredData = {
|
||||||
|
'@context': 'https://schema.org',
|
||||||
|
'@type': 'WebSite',
|
||||||
|
name: 'Sim Documentation',
|
||||||
|
description:
|
||||||
|
'Comprehensive documentation for Sim - the visual workflow builder for AI Agent Workflows.',
|
||||||
|
url: 'https://docs.sim.ai',
|
||||||
|
publisher: {
|
||||||
|
'@type': 'Organization',
|
||||||
|
name: 'Sim',
|
||||||
|
url: 'https://sim.ai',
|
||||||
|
logo: {
|
||||||
|
'@type': 'ImageObject',
|
||||||
|
url: 'https://docs.sim.ai/static/logo.png',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
inLanguage: lang,
|
||||||
|
potentialAction: {
|
||||||
|
'@type': 'SearchAction',
|
||||||
|
target: {
|
||||||
|
'@type': 'EntryPoint',
|
||||||
|
urlTemplate: 'https://docs.sim.ai/api/search?q={search_term_string}',
|
||||||
|
},
|
||||||
|
'query-input': 'required name=search_term_string',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<html
|
||||||
|
lang={lang}
|
||||||
|
className={`${inter.variable} ${geistMono.variable}`}
|
||||||
|
suppressHydrationWarning
|
||||||
|
>
|
||||||
|
<head>
|
||||||
|
<script
|
||||||
|
type='application/ld+json'
|
||||||
|
dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }}
|
||||||
|
/>
|
||||||
|
{/* OneDollarStats Analytics - CDN script handles everything automatically */}
|
||||||
|
<script defer src='https://assets.onedollarstats.com/stonks.js' />
|
||||||
|
</head>
|
||||||
|
<body className='flex min-h-screen flex-col font-sans'>
|
||||||
|
<RootProvider i18n={provider(lang)}>
|
||||||
|
<Navbar />
|
||||||
|
<DocsLayout
|
||||||
|
tree={source.pageTree[lang]}
|
||||||
|
nav={{
|
||||||
|
title: (
|
||||||
|
<Image
|
||||||
|
src='/static/logo.png'
|
||||||
|
alt='Sim'
|
||||||
|
width={72}
|
||||||
|
height={28}
|
||||||
|
className='h-7 w-auto'
|
||||||
|
priority
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
sidebar={{
|
||||||
|
defaultOpenLevel: 0,
|
||||||
|
collapsible: false,
|
||||||
|
footer: null,
|
||||||
|
banner: null,
|
||||||
|
components: {
|
||||||
|
Item: SidebarItem,
|
||||||
|
Folder: SidebarFolder,
|
||||||
|
Separator: SidebarSeparator,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
containerProps={{
|
||||||
|
className: '!pt-0',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</DocsLayout>
|
||||||
|
</RootProvider>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
)
|
||||||
|
}
|
||||||
23
apps/docs/app/[lang]/not-found.tsx
Normal file
23
apps/docs/app/[lang]/not-found.tsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { DocsBody, DocsPage } from 'fumadocs-ui/page'
|
||||||
|
|
||||||
|
export const metadata = {
|
||||||
|
title: 'Page Not Found',
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function NotFound() {
|
||||||
|
return (
|
||||||
|
<DocsPage>
|
||||||
|
<DocsBody>
|
||||||
|
<div className='flex min-h-[60vh] flex-col items-center justify-center text-center'>
|
||||||
|
<h1 className='mb-4 bg-gradient-to-b from-[#8357FF] to-[#6F3DFA] bg-clip-text font-bold text-8xl text-transparent'>
|
||||||
|
404
|
||||||
|
</h1>
|
||||||
|
<h2 className='mb-2 font-semibold text-2xl text-foreground'>Page Not Found</h2>
|
||||||
|
<p className='text-muted-foreground'>
|
||||||
|
The page you're looking for doesn't exist or has been moved.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</DocsBody>
|
||||||
|
</DocsPage>
|
||||||
|
)
|
||||||
|
}
|
||||||
152
apps/docs/app/api/og/route.tsx
Normal file
152
apps/docs/app/api/og/route.tsx
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
import { ImageResponse } from 'next/og'
|
||||||
|
import type { NextRequest } from 'next/server'
|
||||||
|
|
||||||
|
export const runtime = 'edge'
|
||||||
|
|
||||||
|
const TITLE_FONT_SIZE = {
|
||||||
|
large: 64,
|
||||||
|
medium: 56,
|
||||||
|
small: 48,
|
||||||
|
} as const
|
||||||
|
|
||||||
|
function getTitleFontSize(title: string): number {
|
||||||
|
if (title.length > 45) return TITLE_FONT_SIZE.small
|
||||||
|
if (title.length > 30) return TITLE_FONT_SIZE.medium
|
||||||
|
return TITLE_FONT_SIZE.large
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a Google Font dynamically by fetching the CSS and extracting the font URL.
|
||||||
|
*/
|
||||||
|
async function loadGoogleFont(font: string, weights: string, text: string): Promise<ArrayBuffer> {
|
||||||
|
const url = `https://fonts.googleapis.com/css2?family=${font}:wght@${weights}&text=${encodeURIComponent(text)}`
|
||||||
|
const css = await (await fetch(url)).text()
|
||||||
|
const resource = css.match(/src: url\((.+)\) format\('(opentype|truetype)'\)/)
|
||||||
|
|
||||||
|
if (resource) {
|
||||||
|
const response = await fetch(resource[1])
|
||||||
|
if (response.status === 200) {
|
||||||
|
return await response.arrayBuffer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('Failed to load font data')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates dynamic Open Graph images for documentation pages.
|
||||||
|
*/
|
||||||
|
export async function GET(request: NextRequest) {
|
||||||
|
const { searchParams } = new URL(request.url)
|
||||||
|
const title = searchParams.get('title') || 'Documentation'
|
||||||
|
|
||||||
|
const baseUrl = new URL(request.url).origin
|
||||||
|
|
||||||
|
const allText = `${title}docs.sim.ai`
|
||||||
|
const fontData = await loadGoogleFont('Geist', '400;500;600', allText)
|
||||||
|
|
||||||
|
return new ImageResponse(
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
height: '100%',
|
||||||
|
width: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
background: '#0c0c0c',
|
||||||
|
position: 'relative',
|
||||||
|
fontFamily: 'Geist',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Base gradient layer - subtle purple tint across the entire image */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
background:
|
||||||
|
'radial-gradient(ellipse 150% 100% at 50% 100%, rgba(88, 28, 135, 0.15) 0%, rgba(88, 28, 135, 0.08) 25%, rgba(88, 28, 135, 0.03) 50%, transparent 80%)',
|
||||||
|
display: 'flex',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Secondary glow - adds depth without harsh edges */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
background:
|
||||||
|
'radial-gradient(ellipse 100% 80% at 80% 90%, rgba(112, 31, 252, 0.12) 0%, rgba(112, 31, 252, 0.04) 40%, transparent 70%)',
|
||||||
|
display: 'flex',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Top darkening - creates natural vignette */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
background:
|
||||||
|
'linear-gradient(180deg, rgba(0, 0, 0, 0.3) 0%, transparent 40%, transparent 100%)',
|
||||||
|
display: 'flex',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Content */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
padding: '56px 72px',
|
||||||
|
height: '100%',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Logo */}
|
||||||
|
<img src={`${baseUrl}/static/logo.png`} alt='sim' height={32} />
|
||||||
|
|
||||||
|
{/* Title */}
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: getTitleFontSize(title),
|
||||||
|
fontWeight: 600,
|
||||||
|
color: '#ffffff',
|
||||||
|
lineHeight: 1.1,
|
||||||
|
letterSpacing: '-0.02em',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{/* Footer */}
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: 500,
|
||||||
|
color: '#71717a',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
docs.sim.ai
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>,
|
||||||
|
{
|
||||||
|
width: 1200,
|
||||||
|
height: 630,
|
||||||
|
fonts: [
|
||||||
|
{
|
||||||
|
name: 'Geist',
|
||||||
|
data: fontData,
|
||||||
|
style: 'normal',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,4 +1,126 @@
|
|||||||
import { createFromSource } from 'fumadocs-core/search/server'
|
import { sql } from 'drizzle-orm'
|
||||||
import { source } from '@/lib/source'
|
import { type NextRequest, NextResponse } from 'next/server'
|
||||||
|
import { db, docsEmbeddings } from '@/lib/db'
|
||||||
|
import { generateSearchEmbedding } from '@/lib/embeddings'
|
||||||
|
|
||||||
export const { GET } = createFromSource(source)
|
export const runtime = 'nodejs'
|
||||||
|
export const revalidate = 0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hybrid search API endpoint
|
||||||
|
* - English: Vector embeddings + keyword search
|
||||||
|
* - Other languages: Keyword search only
|
||||||
|
*/
|
||||||
|
export async function GET(request: NextRequest) {
|
||||||
|
try {
|
||||||
|
const searchParams = request.nextUrl.searchParams
|
||||||
|
const query = searchParams.get('query') || searchParams.get('q') || ''
|
||||||
|
const locale = searchParams.get('locale') || 'en'
|
||||||
|
const limit = Number.parseInt(searchParams.get('limit') || '10', 10)
|
||||||
|
|
||||||
|
if (!query || query.trim().length === 0) {
|
||||||
|
return NextResponse.json([])
|
||||||
|
}
|
||||||
|
|
||||||
|
const candidateLimit = limit * 3
|
||||||
|
const similarityThreshold = 0.6
|
||||||
|
|
||||||
|
const localeMap: Record<string, string> = {
|
||||||
|
en: 'english',
|
||||||
|
es: 'spanish',
|
||||||
|
fr: 'french',
|
||||||
|
de: 'german',
|
||||||
|
ja: 'simple', // PostgreSQL doesn't have Japanese support, use simple
|
||||||
|
zh: 'simple', // PostgreSQL doesn't have Chinese support, use simple
|
||||||
|
}
|
||||||
|
const tsConfig = localeMap[locale] || 'simple'
|
||||||
|
|
||||||
|
const useVectorSearch = locale === 'en'
|
||||||
|
let vectorResults: Array<{
|
||||||
|
chunkId: string
|
||||||
|
chunkText: string
|
||||||
|
sourceDocument: string
|
||||||
|
sourceLink: string
|
||||||
|
headerText: string
|
||||||
|
headerLevel: number
|
||||||
|
similarity: number
|
||||||
|
searchType: string
|
||||||
|
}> = []
|
||||||
|
|
||||||
|
if (useVectorSearch) {
|
||||||
|
const queryEmbedding = await generateSearchEmbedding(query)
|
||||||
|
vectorResults = await db
|
||||||
|
.select({
|
||||||
|
chunkId: docsEmbeddings.chunkId,
|
||||||
|
chunkText: docsEmbeddings.chunkText,
|
||||||
|
sourceDocument: docsEmbeddings.sourceDocument,
|
||||||
|
sourceLink: docsEmbeddings.sourceLink,
|
||||||
|
headerText: docsEmbeddings.headerText,
|
||||||
|
headerLevel: docsEmbeddings.headerLevel,
|
||||||
|
similarity: sql<number>`1 - (${docsEmbeddings.embedding} <=> ${JSON.stringify(queryEmbedding)}::vector)`,
|
||||||
|
searchType: sql<string>`'vector'`,
|
||||||
|
})
|
||||||
|
.from(docsEmbeddings)
|
||||||
|
.where(
|
||||||
|
sql`1 - (${docsEmbeddings.embedding} <=> ${JSON.stringify(queryEmbedding)}::vector) >= ${similarityThreshold}`
|
||||||
|
)
|
||||||
|
.orderBy(sql`${docsEmbeddings.embedding} <=> ${JSON.stringify(queryEmbedding)}::vector`)
|
||||||
|
.limit(candidateLimit)
|
||||||
|
}
|
||||||
|
|
||||||
|
const keywordResults = await db
|
||||||
|
.select({
|
||||||
|
chunkId: docsEmbeddings.chunkId,
|
||||||
|
chunkText: docsEmbeddings.chunkText,
|
||||||
|
sourceDocument: docsEmbeddings.sourceDocument,
|
||||||
|
sourceLink: docsEmbeddings.sourceLink,
|
||||||
|
headerText: docsEmbeddings.headerText,
|
||||||
|
headerLevel: docsEmbeddings.headerLevel,
|
||||||
|
similarity: sql<number>`ts_rank(${docsEmbeddings.chunkTextTsv}, plainto_tsquery(${tsConfig}, ${query}))`,
|
||||||
|
searchType: sql<string>`'keyword'`,
|
||||||
|
})
|
||||||
|
.from(docsEmbeddings)
|
||||||
|
.where(sql`${docsEmbeddings.chunkTextTsv} @@ plainto_tsquery(${tsConfig}, ${query})`)
|
||||||
|
.orderBy(
|
||||||
|
sql`ts_rank(${docsEmbeddings.chunkTextTsv}, plainto_tsquery(${tsConfig}, ${query})) DESC`
|
||||||
|
)
|
||||||
|
.limit(candidateLimit)
|
||||||
|
|
||||||
|
const seenIds = new Set<string>()
|
||||||
|
const mergedResults = []
|
||||||
|
|
||||||
|
for (let i = 0; i < Math.max(vectorResults.length, keywordResults.length); i++) {
|
||||||
|
if (i < vectorResults.length && !seenIds.has(vectorResults[i].chunkId)) {
|
||||||
|
mergedResults.push(vectorResults[i])
|
||||||
|
seenIds.add(vectorResults[i].chunkId)
|
||||||
|
}
|
||||||
|
if (i < keywordResults.length && !seenIds.has(keywordResults[i].chunkId)) {
|
||||||
|
mergedResults.push(keywordResults[i])
|
||||||
|
seenIds.add(keywordResults[i].chunkId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const filteredResults = mergedResults.slice(0, limit)
|
||||||
|
const searchResults = filteredResults.map((result) => {
|
||||||
|
const title = result.headerText || result.sourceDocument.replace('.mdx', '')
|
||||||
|
const pathParts = result.sourceDocument
|
||||||
|
.replace('.mdx', '')
|
||||||
|
.split('/')
|
||||||
|
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: result.chunkId,
|
||||||
|
type: 'page' as const,
|
||||||
|
url: result.sourceLink,
|
||||||
|
content: title,
|
||||||
|
breadcrumbs: pathParts,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return NextResponse.json(searchResults)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Semantic search error:', error)
|
||||||
|
|
||||||
|
return NextResponse.json([])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,8 +2,494 @@
|
|||||||
@import "fumadocs-ui/css/neutral.css";
|
@import "fumadocs-ui/css/neutral.css";
|
||||||
@import "fumadocs-ui/css/preset.css";
|
@import "fumadocs-ui/css/preset.css";
|
||||||
|
|
||||||
|
/* Prevent overscroll bounce effect on the page */
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
overscroll-behavior: none;
|
||||||
|
}
|
||||||
|
|
||||||
@theme {
|
@theme {
|
||||||
--color-fd-primary: #802fff; /* Purple from control-bar component */
|
--color-fd-primary: #802fff; /* Purple from control-bar component */
|
||||||
|
--font-geist-sans: var(--font-geist-sans);
|
||||||
|
--font-geist-mono: var(--font-geist-mono);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Font family utilities */
|
||||||
|
.font-sans {
|
||||||
|
font-family: var(--font-geist-sans), ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
|
||||||
|
"Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.font-mono {
|
||||||
|
font-family: var(--font-geist-mono), ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
|
||||||
|
"Liberation Mono", "Courier New", monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Target any potential border classes */
|
||||||
|
* {
|
||||||
|
--fd-border-sidebar: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Override any CSS custom properties for borders */
|
||||||
|
:root {
|
||||||
|
--fd-border: transparent !important;
|
||||||
|
--fd-border-sidebar: transparent !important;
|
||||||
|
--fd-nav-height: 64px; /* Custom navbar height (h-16 = 4rem = 64px) */
|
||||||
|
/* Content container width used to center main content */
|
||||||
|
--spacing-fd-container: 1400px;
|
||||||
|
/* Edge gutter = leftover space on each side of centered container */
|
||||||
|
--edge-gutter: max(1rem, calc((100vw - var(--spacing-fd-container)) / 2));
|
||||||
|
/* Shift the sidebar slightly left from the content edge for extra breathing room */
|
||||||
|
--sidebar-shift: 90px;
|
||||||
|
--sidebar-offset: max(0px, calc(var(--edge-gutter) - var(--sidebar-shift)));
|
||||||
|
/* Shift TOC slightly right to match sidebar spacing for symmetry */
|
||||||
|
--toc-shift: 90px;
|
||||||
|
--toc-offset: max(0px, calc(var(--edge-gutter) - var(--toc-shift)));
|
||||||
|
/* Sidebar and TOC have 20px internal padding - navbar accounts for this directly */
|
||||||
|
/* Extra gap between sidebar/TOC and the main text content */
|
||||||
|
--content-gap: 1.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Remove custom layout variable overrides to fallback to fumadocs defaults */
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
Navbar Light Mode Styling
|
||||||
|
============================================ */
|
||||||
|
|
||||||
|
/* Light mode navbar and search styling */
|
||||||
|
:root:not(.dark) nav {
|
||||||
|
background-color: hsla(0, 0%, 96%, 0.85) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root:not(.dark) nav button[type="button"] {
|
||||||
|
background-color: hsla(0, 0%, 93%, 0.85) !important;
|
||||||
|
backdrop-filter: blur(33px) saturate(180%) !important;
|
||||||
|
-webkit-backdrop-filter: blur(33px) saturate(180%) !important;
|
||||||
|
color: rgba(0, 0, 0, 0.6) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root:not(.dark) nav button[type="button"] kbd {
|
||||||
|
color: rgba(0, 0, 0, 0.6) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dark mode navbar and search styling */
|
||||||
|
:root.dark nav {
|
||||||
|
background-color: hsla(0, 0%, 7.04%, 0.92) !important;
|
||||||
|
backdrop-filter: blur(25px) saturate(180%) brightness(0.6) !important;
|
||||||
|
-webkit-backdrop-filter: blur(25px) saturate(180%) brightness(0.6) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
Custom Sidebar Styling (Turborepo-inspired)
|
||||||
|
============================================ */
|
||||||
|
|
||||||
|
/* Floating sidebar appearance - remove background */
|
||||||
|
[data-sidebar-container],
|
||||||
|
#nd-sidebar {
|
||||||
|
background: transparent !important;
|
||||||
|
background-color: transparent !important;
|
||||||
|
border: none !important;
|
||||||
|
--color-fd-muted: transparent !important;
|
||||||
|
--color-fd-card: transparent !important;
|
||||||
|
--color-fd-secondary: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
aside[data-sidebar],
|
||||||
|
aside#nd-sidebar {
|
||||||
|
background: transparent !important;
|
||||||
|
background-color: transparent !important;
|
||||||
|
border: none !important;
|
||||||
|
border-right: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fumadocs v16: Add sidebar placeholder styling for grid area */
|
||||||
|
[data-sidebar-placeholder] {
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fumadocs v16: Hide sidebar panel (floating collapse button) */
|
||||||
|
[data-sidebar-panel] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile only: Reduce gap between navbar and content */
|
||||||
|
@media (max-width: 1023px) {
|
||||||
|
#nd-docs-layout {
|
||||||
|
margin-top: -25px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Desktop only: Apply custom navbar offset, sidebar width and margin offsets */
|
||||||
|
/* On mobile, let fumadocs handle the layout natively */
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
:root {
|
||||||
|
--fd-banner-height: 64px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#nd-docs-layout {
|
||||||
|
--fd-docs-height: calc(100dvh - 64px) !important;
|
||||||
|
--fd-sidebar-width: 300px !important;
|
||||||
|
margin-left: var(--sidebar-offset) !important;
|
||||||
|
margin-right: var(--toc-offset) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide fumadocs nav on desktop - we use custom navbar there */
|
||||||
|
#nd-docs-layout > header {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sidebar spacing - compact like turborepo */
|
||||||
|
/* Fumadocs v16: [data-sidebar-viewport] doesn't exist, target #nd-sidebar > div instead */
|
||||||
|
[data-sidebar-viewport],
|
||||||
|
#nd-sidebar > div {
|
||||||
|
padding: 0.5rem 12px 12px;
|
||||||
|
background: transparent !important;
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Override sidebar item styling to match Raindrop */
|
||||||
|
/* Target Link and button elements in sidebar - override Fumadocs itemVariants */
|
||||||
|
/* Exclude the small chevron-only toggle buttons */
|
||||||
|
/* Using html prefix for higher specificity over Tailwind v4 utilities */
|
||||||
|
html #nd-sidebar a,
|
||||||
|
html #nd-sidebar button:not([aria-label*="ollapse"]):not([aria-label*="xpand"]) {
|
||||||
|
font-size: 0.9375rem !important; /* 15px to match Raindrop */
|
||||||
|
line-height: 1.4 !important;
|
||||||
|
padding: 0.5rem 0.75rem !important; /* More compact like Raindrop */
|
||||||
|
font-weight: 400 !important;
|
||||||
|
border-radius: 0.75rem !important; /* More rounded like Raindrop */
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial,
|
||||||
|
sans-serif !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dark mode sidebar text */
|
||||||
|
html.dark #nd-sidebar a,
|
||||||
|
html.dark #nd-sidebar button:not([aria-label*="ollapse"]):not([aria-label*="xpand"]) {
|
||||||
|
color: rgba(255, 255, 255, 0.6) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Light mode sidebar text */
|
||||||
|
html:not(.dark) #nd-sidebar a,
|
||||||
|
html:not(.dark) #nd-sidebar button:not([aria-label*="ollapse"]):not([aria-label*="xpand"]) {
|
||||||
|
color: rgba(0, 0, 0, 0.6) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make sure chevron icons are visible and properly styled */
|
||||||
|
#nd-sidebar svg {
|
||||||
|
display: inline-block !important;
|
||||||
|
opacity: 0.6 !important;
|
||||||
|
flex-shrink: 0 !important;
|
||||||
|
width: 0.75rem !important;
|
||||||
|
height: 0.75rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure the small chevron toggle buttons are visible */
|
||||||
|
#nd-sidebar button[aria-label*="ollapse"],
|
||||||
|
#nd-sidebar button[aria-label*="xpand"] {
|
||||||
|
display: flex !important;
|
||||||
|
opacity: 1 !important;
|
||||||
|
padding: 0.25rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Root-level spacing now handled by [data-sidebar-viewport] > * rule below */
|
||||||
|
|
||||||
|
/* Add tiny gap between nested items */
|
||||||
|
#nd-sidebar ul li {
|
||||||
|
margin-bottom: 0.0625rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#nd-sidebar ul li:last-child {
|
||||||
|
margin-bottom: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Section headers should be slightly larger */
|
||||||
|
/* Fumadocs v16: Also target #nd-sidebar for compatibility */
|
||||||
|
[data-sidebar-viewport] [data-separator],
|
||||||
|
#nd-sidebar [data-separator],
|
||||||
|
#nd-sidebar p {
|
||||||
|
font-size: 0.75rem !important;
|
||||||
|
font-weight: 600 !important;
|
||||||
|
text-transform: uppercase !important;
|
||||||
|
letter-spacing: 0.05em !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Override active state (NO PURPLE) */
|
||||||
|
#nd-sidebar a[data-active="true"],
|
||||||
|
#nd-sidebar button[data-active="true"],
|
||||||
|
#nd-sidebar a.bg-fd-primary\/10,
|
||||||
|
#nd-sidebar a.text-fd-primary,
|
||||||
|
#nd-sidebar a[class*="bg-fd-primary"],
|
||||||
|
#nd-sidebar a[class*="text-fd-primary"],
|
||||||
|
/* Override custom sidebar purple classes */
|
||||||
|
#nd-sidebar
|
||||||
|
a.bg-purple-50\/80,
|
||||||
|
#nd-sidebar a.text-purple-600,
|
||||||
|
#nd-sidebar a[class*="bg-purple"],
|
||||||
|
#nd-sidebar a[class*="text-purple"] {
|
||||||
|
background-image: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dark mode active state */
|
||||||
|
html.dark #nd-sidebar a[data-active="true"],
|
||||||
|
html.dark #nd-sidebar button[data-active="true"],
|
||||||
|
html.dark #nd-sidebar a.bg-fd-primary\/10,
|
||||||
|
html.dark #nd-sidebar a.text-fd-primary,
|
||||||
|
html.dark #nd-sidebar a[class*="bg-fd-primary"],
|
||||||
|
html.dark #nd-sidebar a[class*="text-fd-primary"],
|
||||||
|
html.dark #nd-sidebar a.bg-purple-50\/80,
|
||||||
|
html.dark #nd-sidebar a.text-purple-600,
|
||||||
|
html.dark #nd-sidebar a[class*="bg-purple"],
|
||||||
|
html.dark #nd-sidebar a[class*="text-purple"] {
|
||||||
|
background-color: rgba(255, 255, 255, 0.15) !important;
|
||||||
|
color: rgba(255, 255, 255, 1) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Light mode active state */
|
||||||
|
html:not(.dark) #nd-sidebar a[data-active="true"],
|
||||||
|
html:not(.dark) #nd-sidebar button[data-active="true"],
|
||||||
|
html:not(.dark) #nd-sidebar a.bg-fd-primary\/10,
|
||||||
|
html:not(.dark) #nd-sidebar a.text-fd-primary,
|
||||||
|
html:not(.dark) #nd-sidebar a[class*="bg-fd-primary"],
|
||||||
|
html:not(.dark) #nd-sidebar a[class*="text-fd-primary"],
|
||||||
|
html:not(.dark) #nd-sidebar a.bg-purple-50\/80,
|
||||||
|
html:not(.dark) #nd-sidebar a.text-purple-600,
|
||||||
|
html:not(.dark) #nd-sidebar a[class*="bg-purple"],
|
||||||
|
html:not(.dark) #nd-sidebar a[class*="text-purple"] {
|
||||||
|
background-color: rgba(0, 0, 0, 0.07) !important;
|
||||||
|
color: rgba(0, 0, 0, 0.9) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dark mode hover state */
|
||||||
|
html.dark #nd-sidebar a:hover:not([data-active="true"]),
|
||||||
|
html.dark #nd-sidebar button:hover:not([data-active="true"]) {
|
||||||
|
background-color: rgba(255, 255, 255, 0.08) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Light mode hover state */
|
||||||
|
html:not(.dark) #nd-sidebar a:hover:not([data-active="true"]),
|
||||||
|
html:not(.dark) #nd-sidebar button:hover:not([data-active="true"]) {
|
||||||
|
background-color: rgba(0, 0, 0, 0.03) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dark mode - ensure active/selected items don't change on hover */
|
||||||
|
html.dark #nd-sidebar a.bg-purple-50\/80:hover,
|
||||||
|
html.dark #nd-sidebar a[class*="bg-purple"]:hover,
|
||||||
|
html.dark #nd-sidebar a[data-active="true"]:hover,
|
||||||
|
html.dark #nd-sidebar button[data-active="true"]:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 0.15) !important;
|
||||||
|
color: rgba(255, 255, 255, 1) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Light mode - ensure active/selected items don't change on hover */
|
||||||
|
html:not(.dark) #nd-sidebar a.bg-purple-50\/80:hover,
|
||||||
|
html:not(.dark) #nd-sidebar a[class*="bg-purple"]:hover,
|
||||||
|
html:not(.dark) #nd-sidebar a[data-active="true"]:hover,
|
||||||
|
html:not(.dark) #nd-sidebar button[data-active="true"]:hover {
|
||||||
|
background-color: rgba(0, 0, 0, 0.07) !important;
|
||||||
|
color: rgba(0, 0, 0, 0.9) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide search, platform, and collapse button from sidebar completely */
|
||||||
|
[data-sidebar] [data-search],
|
||||||
|
[data-sidebar] .search-toggle,
|
||||||
|
#nd-sidebar [data-search],
|
||||||
|
#nd-sidebar .search-toggle,
|
||||||
|
[data-sidebar-viewport] [data-search],
|
||||||
|
[data-sidebar-viewport] button[data-search],
|
||||||
|
aside[data-sidebar] [role="button"]:has([data-search]),
|
||||||
|
aside[data-sidebar] > div > button:first-child,
|
||||||
|
#nd-sidebar > div > button:first-child,
|
||||||
|
[data-sidebar] a[href*="sim.ai"],
|
||||||
|
#nd-sidebar a[href*="sim.ai"],
|
||||||
|
[data-sidebar-viewport] a[href*="sim.ai"],
|
||||||
|
/* Hide search buttons (but NOT folder chevron buttons) */
|
||||||
|
aside[data-sidebar] > div:first-child
|
||||||
|
> button:not([aria-label="Collapse"]):not([aria-label="Expand"]),
|
||||||
|
#nd-sidebar > div:first-child > button:not([aria-label="Collapse"]):not([aria-label="Expand"]),
|
||||||
|
/* Hide sidebar collapse button (panel icon) - direct children only */
|
||||||
|
aside[data-sidebar] > button:first-of-type:not([aria-label="Collapse"]):not([aria-label="Expand"]),
|
||||||
|
[data-sidebar]
|
||||||
|
> button[type="button"]:first-of-type:not([aria-label="Collapse"]):not([aria-label="Expand"]),
|
||||||
|
button[data-collapse]:not([aria-label="Collapse"]):not([aria-label="Expand"]),
|
||||||
|
[data-sidebar-header] button,
|
||||||
|
/* Hide theme toggle from sidebar footer */
|
||||||
|
aside[data-sidebar] [data-theme-toggle],
|
||||||
|
[data-sidebar-footer],
|
||||||
|
[data-sidebar] footer,
|
||||||
|
footer button[aria-label*="heme"],
|
||||||
|
aside[data-sidebar] > div:last-child:has(button[aria-label*="heme"]),
|
||||||
|
aside[data-sidebar] button[aria-label*="heme"],
|
||||||
|
[data-sidebar] button[aria-label*="Theme"],
|
||||||
|
/* Additional theme toggle selectors */
|
||||||
|
aside[data-sidebar] > *:last-child
|
||||||
|
button,
|
||||||
|
[data-sidebar-viewport] ~ *,
|
||||||
|
aside[data-sidebar] > div:not([data-sidebar-viewport]),
|
||||||
|
/* Aggressive theme toggle hiding */
|
||||||
|
aside[data-sidebar] svg[class*="sun"],
|
||||||
|
aside[data-sidebar] svg[class*="moon"],
|
||||||
|
aside[data-sidebar] button[type="button"]:last-child,
|
||||||
|
aside button:has(svg:only-child),
|
||||||
|
[data-sidebar] div:has(> button[type="button"]:only-child:last-child),
|
||||||
|
/* Hide theme toggle and other non-content elements */
|
||||||
|
aside[data-sidebar] > *:not([data-sidebar-viewport]) {
|
||||||
|
display: none !important;
|
||||||
|
visibility: hidden !important;
|
||||||
|
opacity: 0 !important;
|
||||||
|
height: 0 !important;
|
||||||
|
max-height: 0 !important;
|
||||||
|
overflow: hidden !important;
|
||||||
|
pointer-events: none !important;
|
||||||
|
position: absolute !important;
|
||||||
|
left: -9999px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Desktop only: Hide sidebar toggle buttons and nav title/logo (keep visible on mobile) */
|
||||||
|
@media (min-width: 1025px) {
|
||||||
|
[data-sidebar-container] > button,
|
||||||
|
[data-sidebar-container] [data-toggle],
|
||||||
|
aside[data-sidebar] [data-sidebar-toggle],
|
||||||
|
button[data-sidebar-toggle],
|
||||||
|
nav button[data-sidebar-toggle],
|
||||||
|
button[aria-label="Toggle Sidebar"],
|
||||||
|
button[aria-label="Collapse Sidebar"],
|
||||||
|
/* Hide nav title/logo in sidebar on desktop - target all possible locations */
|
||||||
|
aside[data-sidebar] a[href="/"],
|
||||||
|
aside[data-sidebar] a[href="/"] img,
|
||||||
|
aside[data-sidebar] > a:first-child,
|
||||||
|
aside[data-sidebar] > div > a:first-child,
|
||||||
|
aside[data-sidebar] img[alt="Sim"],
|
||||||
|
[data-sidebar-header],
|
||||||
|
[data-sidebar] [data-title],
|
||||||
|
#nd-sidebar > a:first-child,
|
||||||
|
#nd-sidebar > div:first-child > a:first-child,
|
||||||
|
#nd-sidebar img[alt="Sim"],
|
||||||
|
/* Hide theme toggle at bottom of sidebar on desktop */
|
||||||
|
#nd-sidebar
|
||||||
|
> footer,
|
||||||
|
#nd-sidebar footer,
|
||||||
|
aside#nd-sidebar > *:last-child:not(div),
|
||||||
|
#nd-sidebar > button:last-child,
|
||||||
|
#nd-sidebar button[aria-label*="theme" i],
|
||||||
|
#nd-sidebar button[aria-label*="Theme"],
|
||||||
|
#nd-sidebar > div:last-child > button {
|
||||||
|
display: none !important;
|
||||||
|
visibility: hidden !important;
|
||||||
|
height: 0 !important;
|
||||||
|
max-height: 0 !important;
|
||||||
|
overflow: hidden !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Extra aggressive - hide everything after the viewport */
|
||||||
|
aside[data-sidebar] [data-sidebar-viewport] ~ * {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tighter spacing for sidebar content */
|
||||||
|
[data-sidebar-viewport] > * {
|
||||||
|
margin-bottom: 0.0625rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-sidebar-viewport] > *:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-sidebar-viewport] ul {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure sidebar starts with content immediately */
|
||||||
|
aside[data-sidebar] > div:first-child {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Remove all sidebar borders and backgrounds */
|
||||||
|
[data-sidebar-container],
|
||||||
|
aside[data-sidebar],
|
||||||
|
[data-sidebar],
|
||||||
|
[data-sidebar] *,
|
||||||
|
#nd-sidebar,
|
||||||
|
#nd-sidebar * {
|
||||||
|
border: none !important;
|
||||||
|
border-right: none !important;
|
||||||
|
border-left: none !important;
|
||||||
|
border-top: none !important;
|
||||||
|
border-bottom: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Override fumadocs background colors for sidebar */
|
||||||
|
.dark #nd-sidebar,
|
||||||
|
.dark [data-sidebar-container],
|
||||||
|
.dark aside[data-sidebar] {
|
||||||
|
--color-fd-muted: transparent !important;
|
||||||
|
--color-fd-secondary: transparent !important;
|
||||||
|
background: transparent !important;
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Force normal text flow in sidebar */
|
||||||
|
[data-sidebar],
|
||||||
|
[data-sidebar] *,
|
||||||
|
[data-sidebar-viewport],
|
||||||
|
[data-sidebar-viewport] * {
|
||||||
|
writing-mode: horizontal-tb !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
Code Block Styling (Improved)
|
||||||
|
============================================ */
|
||||||
|
|
||||||
|
/* Apply Geist Mono to code elements */
|
||||||
|
code,
|
||||||
|
pre,
|
||||||
|
pre code {
|
||||||
|
font-family: var(--font-geist-mono), ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
|
||||||
|
"Liberation Mono", "Courier New", monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Inline code */
|
||||||
|
:not(pre) > code {
|
||||||
|
padding: 0.2em 0.4em;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
font-size: 0.875em;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Light mode inline code */
|
||||||
|
:root:not(.dark) :not(pre) > code {
|
||||||
|
background-color: rgb(243 244 246);
|
||||||
|
color: rgb(220 38 38);
|
||||||
|
border: 1px solid rgb(229 231 235);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dark mode inline code */
|
||||||
|
.dark :not(pre) > code {
|
||||||
|
background-color: rgb(31 41 55);
|
||||||
|
color: rgb(248 113 113);
|
||||||
|
border: 1px solid rgb(55 65 81);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Code block container improvements */
|
||||||
|
pre {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.7;
|
||||||
|
tab-size: 2;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre code {
|
||||||
|
display: block;
|
||||||
|
width: fit-content;
|
||||||
|
min-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Syntax highlighting adjustments for better readability */
|
||||||
|
pre code .line {
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Custom text highlighting styles */
|
/* Custom text highlighting styles */
|
||||||
@@ -16,6 +502,63 @@
|
|||||||
color: var(--color-fd-primary);
|
color: var(--color-fd-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Add bottom spacing to prevent abrupt page endings */
|
||||||
|
[data-content] {
|
||||||
|
padding-top: 1.5rem !important;
|
||||||
|
padding-bottom: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Alternative fallback for different Fumadocs versions */
|
||||||
|
main article,
|
||||||
|
.docs-page main {
|
||||||
|
padding-top: 1.5rem !important;
|
||||||
|
padding-bottom: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
Center and Constrain Main Content Width
|
||||||
|
============================================ */
|
||||||
|
|
||||||
|
/* Main content area - center and constrain like turborepo/raindrop */
|
||||||
|
/* Note: --sidebar-offset and --toc-offset are now applied at #nd-docs-layout level */
|
||||||
|
main[data-main] {
|
||||||
|
max-width: var(--spacing-fd-container, 1400px);
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
padding-top: 1rem;
|
||||||
|
padding-left: var(--content-gap);
|
||||||
|
padding-right: var(--content-gap);
|
||||||
|
order: 1 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Adjust for smaller screens */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
main[data-main] {
|
||||||
|
padding-left: 1rem;
|
||||||
|
padding-right: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure docs page content is properly constrained */
|
||||||
|
[data-docs-page] {
|
||||||
|
max-width: 1400px;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
padding-top: 1.5rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Override Fumadocs default content padding */
|
||||||
|
article[data-content],
|
||||||
|
div[data-content] {
|
||||||
|
padding-top: 1.5rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Remove any unwanted borders/outlines from video elements */
|
||||||
|
video {
|
||||||
|
outline: none !important;
|
||||||
|
border-style: solid !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* Tailwind v4 content sources */
|
/* Tailwind v4 content sources */
|
||||||
@source '../app/**/*.{js,ts,jsx,tsx,mdx}';
|
@source '../app/**/*.{js,ts,jsx,tsx,mdx}';
|
||||||
@source '../components/**/*.{js,ts,jsx,tsx,mdx}';
|
@source '../components/**/*.{js,ts,jsx,tsx,mdx}';
|
||||||
|
|||||||
@@ -1,30 +1,38 @@
|
|||||||
import type { ReactNode } from 'react'
|
import type { ReactNode } from 'react'
|
||||||
import { RootProvider } from 'fumadocs-ui/provider'
|
|
||||||
import { Inter } from 'next/font/google'
|
|
||||||
import './global.css'
|
|
||||||
import { Analytics } from '@vercel/analytics/next'
|
|
||||||
|
|
||||||
const inter = Inter({
|
export default function RootLayout({ children }: { children: ReactNode }) {
|
||||||
subsets: ['latin'],
|
return children
|
||||||
})
|
|
||||||
|
|
||||||
export default function Layout({ children }: { children: ReactNode }) {
|
|
||||||
return (
|
|
||||||
<html lang='en' className={inter.className} suppressHydrationWarning>
|
|
||||||
<body className='flex min-h-screen flex-col'>
|
|
||||||
<RootProvider>
|
|
||||||
{children}
|
|
||||||
<Analytics />
|
|
||||||
</RootProvider>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const metadata = {
|
export const metadata = {
|
||||||
title: 'Sim',
|
metadataBase: new URL('https://docs.sim.ai'),
|
||||||
|
title: {
|
||||||
|
default: 'Sim Documentation - Visual Workflow Builder for AI Applications',
|
||||||
|
template: '%s',
|
||||||
|
},
|
||||||
description:
|
description:
|
||||||
'Build agents in seconds with a drag and drop workflow builder. Access comprehensive documentation to help you create efficient workflows and maximize your automation capabilities.',
|
'Comprehensive documentation for Sim - the visual workflow builder for AI applications. Create powerful AI agents, automation workflows, and data processing pipelines by connecting blocks on a canvas—no coding required.',
|
||||||
|
keywords: [
|
||||||
|
'AI workflow builder',
|
||||||
|
'visual workflow editor',
|
||||||
|
'AI automation',
|
||||||
|
'workflow automation',
|
||||||
|
'AI agents',
|
||||||
|
'no-code AI',
|
||||||
|
'drag and drop workflows',
|
||||||
|
'AI integrations',
|
||||||
|
'workflow canvas',
|
||||||
|
'AI Agent Workflow Builder',
|
||||||
|
'workflow orchestration',
|
||||||
|
'agent builder',
|
||||||
|
'AI workflow automation',
|
||||||
|
'visual programming',
|
||||||
|
],
|
||||||
|
authors: [{ name: 'Sim Team', url: 'https://sim.ai' }],
|
||||||
|
creator: 'Sim',
|
||||||
|
publisher: 'Sim',
|
||||||
|
category: 'Developer Tools',
|
||||||
|
classification: 'Developer Documentation',
|
||||||
manifest: '/favicon/site.webmanifest',
|
manifest: '/favicon/site.webmanifest',
|
||||||
icons: {
|
icons: {
|
||||||
icon: [
|
icon: [
|
||||||
@@ -39,4 +47,54 @@ export const metadata = {
|
|||||||
statusBarStyle: 'default',
|
statusBarStyle: 'default',
|
||||||
title: 'Sim Docs',
|
title: 'Sim Docs',
|
||||||
},
|
},
|
||||||
|
openGraph: {
|
||||||
|
type: 'website',
|
||||||
|
locale: 'en_US',
|
||||||
|
alternateLocale: ['es_ES', 'fr_FR', 'de_DE', 'ja_JP', 'zh_CN'],
|
||||||
|
url: 'https://docs.sim.ai',
|
||||||
|
siteName: 'Sim Documentation',
|
||||||
|
title: 'Sim Documentation - Visual Workflow Builder for AI Applications',
|
||||||
|
description:
|
||||||
|
'Comprehensive documentation for Sim - the visual workflow builder for AI applications. Create powerful AI agents, automation workflows, and data processing pipelines.',
|
||||||
|
images: [
|
||||||
|
{
|
||||||
|
url: 'https://docs.sim.ai/api/og?title=Sim%20Documentation',
|
||||||
|
width: 1200,
|
||||||
|
height: 630,
|
||||||
|
alt: 'Sim Documentation',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
twitter: {
|
||||||
|
card: 'summary_large_image',
|
||||||
|
title: 'Sim Documentation - Visual Workflow Builder for AI Applications',
|
||||||
|
description:
|
||||||
|
'Comprehensive documentation for Sim - the visual workflow builder for AI applications.',
|
||||||
|
creator: '@simdotai',
|
||||||
|
site: '@simdotai',
|
||||||
|
images: ['https://docs.sim.ai/api/og?title=Sim%20Documentation'],
|
||||||
|
},
|
||||||
|
robots: {
|
||||||
|
index: true,
|
||||||
|
follow: true,
|
||||||
|
googleBot: {
|
||||||
|
index: true,
|
||||||
|
follow: true,
|
||||||
|
'max-video-preview': -1,
|
||||||
|
'max-image-preview': 'large',
|
||||||
|
'max-snippet': -1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
alternates: {
|
||||||
|
canonical: 'https://docs.sim.ai',
|
||||||
|
languages: {
|
||||||
|
'x-default': 'https://docs.sim.ai',
|
||||||
|
en: 'https://docs.sim.ai',
|
||||||
|
es: 'https://docs.sim.ai/es',
|
||||||
|
fr: 'https://docs.sim.ai/fr',
|
||||||
|
de: 'https://docs.sim.ai/de',
|
||||||
|
ja: 'https://docs.sim.ai/ja',
|
||||||
|
zh: 'https://docs.sim.ai/zh',
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
31
apps/docs/app/llms-full.txt/route.ts
Normal file
31
apps/docs/app/llms-full.txt/route.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { getLLMText } from '@/lib/llms'
|
||||||
|
import { source } from '@/lib/source'
|
||||||
|
|
||||||
|
export const revalidate = false
|
||||||
|
|
||||||
|
export async function GET() {
|
||||||
|
try {
|
||||||
|
const pages = source.getPages().filter((page) => {
|
||||||
|
if (!page || !page.data || !page.url) return false
|
||||||
|
|
||||||
|
const pathParts = page.url.split('/').filter(Boolean)
|
||||||
|
const hasLangPrefix = pathParts[0] && ['es', 'fr', 'de', 'ja', 'zh'].includes(pathParts[0])
|
||||||
|
|
||||||
|
return !hasLangPrefix
|
||||||
|
})
|
||||||
|
|
||||||
|
const scan = pages.map((page) => getLLMText(page))
|
||||||
|
const scanned = await Promise.all(scan)
|
||||||
|
|
||||||
|
const filtered = scanned.filter((text) => text && text.length > 0)
|
||||||
|
|
||||||
|
return new Response(filtered.join('\n\n---\n\n'), {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'text/plain; charset=utf-8',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error generating LLM full text:', error)
|
||||||
|
return new Response('Error generating full documentation text', { status: 500 })
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +1,33 @@
|
|||||||
import { notFound } from 'next/navigation'
|
import { notFound } from 'next/navigation'
|
||||||
import { type NextRequest, NextResponse } from 'next/server'
|
import { type NextRequest, NextResponse } from 'next/server'
|
||||||
|
import { i18n } from '@/lib/i18n'
|
||||||
import { getLLMText } from '@/lib/llms'
|
import { getLLMText } from '@/lib/llms'
|
||||||
import { source } from '@/lib/source'
|
import { source } from '@/lib/source'
|
||||||
|
|
||||||
export const revalidate = false
|
export const revalidate = false
|
||||||
|
|
||||||
export async function GET(_req: NextRequest, { params }: { params: Promise<{ slug?: string[] }> }) {
|
export async function GET(
|
||||||
|
_request: NextRequest,
|
||||||
|
{ params }: { params: Promise<{ slug?: string[] }> }
|
||||||
|
) {
|
||||||
const { slug } = await params
|
const { slug } = await params
|
||||||
const page = source.getPage(slug)
|
|
||||||
|
let lang: (typeof i18n.languages)[number] = i18n.defaultLanguage
|
||||||
|
let pageSlug = slug
|
||||||
|
|
||||||
|
if (slug && slug.length > 0 && i18n.languages.includes(slug[0] as typeof lang)) {
|
||||||
|
lang = slug[0] as typeof lang
|
||||||
|
pageSlug = slug.slice(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const page = source.getPage(pageSlug, lang)
|
||||||
if (!page) notFound()
|
if (!page) notFound()
|
||||||
|
|
||||||
return new NextResponse(await getLLMText(page))
|
return new NextResponse(await getLLMText(page), {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'text/markdown',
|
||||||
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function generateStaticParams() {
|
export function generateStaticParams() {
|
||||||
|
|||||||
@@ -1,12 +1,87 @@
|
|||||||
import { getLLMText } from '@/lib/llms'
|
|
||||||
import { source } from '@/lib/source'
|
import { source } from '@/lib/source'
|
||||||
|
|
||||||
// cached forever
|
|
||||||
export const revalidate = false
|
export const revalidate = false
|
||||||
|
|
||||||
export async function GET() {
|
export async function GET() {
|
||||||
const scan = source.getPages().map(getLLMText)
|
const baseUrl = 'https://docs.sim.ai'
|
||||||
const scanned = await Promise.all(scan)
|
|
||||||
|
|
||||||
return new Response(scanned.join('\n\n'))
|
try {
|
||||||
|
const pages = source.getPages().filter((page) => {
|
||||||
|
if (!page || !page.data || !page.url) return false
|
||||||
|
|
||||||
|
const pathParts = page.url.split('/').filter(Boolean)
|
||||||
|
const hasLangPrefix = pathParts[0] && ['es', 'fr', 'de', 'ja', 'zh'].includes(pathParts[0])
|
||||||
|
|
||||||
|
return !hasLangPrefix
|
||||||
|
})
|
||||||
|
|
||||||
|
const sections: Record<string, Array<{ title: string; url: string; description?: string }>> = {}
|
||||||
|
|
||||||
|
pages.forEach((page) => {
|
||||||
|
const pathParts = page.url.split('/').filter(Boolean)
|
||||||
|
const section =
|
||||||
|
pathParts[0] && ['en', 'es', 'fr', 'de', 'ja', 'zh'].includes(pathParts[0])
|
||||||
|
? pathParts[1] || 'root'
|
||||||
|
: pathParts[0] || 'root'
|
||||||
|
|
||||||
|
if (!sections[section]) {
|
||||||
|
sections[section] = []
|
||||||
|
}
|
||||||
|
|
||||||
|
sections[section].push({
|
||||||
|
title: page.data.title || 'Untitled',
|
||||||
|
url: `${baseUrl}${page.url}`,
|
||||||
|
description: page.data.description,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const manifest = `# Sim Documentation
|
||||||
|
|
||||||
|
> Visual Workflow Builder for AI Applications
|
||||||
|
|
||||||
|
Sim is a visual workflow builder for AI applications that lets you build AI agent workflows visually. Create powerful AI agents, automation workflows, and data processing pipelines by connecting blocks on a canvas—no coding required.
|
||||||
|
|
||||||
|
## Documentation Overview
|
||||||
|
|
||||||
|
This file provides an overview of our documentation. For full content of all pages, see ${baseUrl}/llms-full.txt
|
||||||
|
|
||||||
|
## Main Sections
|
||||||
|
|
||||||
|
${Object.entries(sections)
|
||||||
|
.map(([section, items]) => {
|
||||||
|
const sectionTitle = section
|
||||||
|
.split('-')
|
||||||
|
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
||||||
|
.join(' ')
|
||||||
|
return `### ${sectionTitle}\n\n${items.map((item) => `- ${item.title}: ${item.url}${item.description ? `\n ${item.description}` : ''}`).join('\n')}`
|
||||||
|
})
|
||||||
|
.join('\n\n')}
|
||||||
|
|
||||||
|
## Additional Resources
|
||||||
|
|
||||||
|
- Full documentation content: ${baseUrl}/llms-full.txt
|
||||||
|
- Individual page content: ${baseUrl}/llms.mdx/[page-path]
|
||||||
|
- API documentation: ${baseUrl}/sdks/
|
||||||
|
- Tool integrations: ${baseUrl}/tools/
|
||||||
|
|
||||||
|
## Statistics
|
||||||
|
|
||||||
|
- Total pages: ${pages.length} (English only)
|
||||||
|
- Other languages available at: ${baseUrl}/[lang]/ (es, fr, de, ja, zh)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Generated: ${new Date().toISOString()}
|
||||||
|
Format: llms.txt v0.1.0
|
||||||
|
See: https://llmstxt.org for specification`
|
||||||
|
|
||||||
|
return new Response(manifest, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'text/plain; charset=utf-8',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error generating LLM manifest:', error)
|
||||||
|
return new Response('Error generating documentation manifest', { status: 500 })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
100
apps/docs/app/robots.txt/route.ts
Normal file
100
apps/docs/app/robots.txt/route.ts
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
export const revalidate = false
|
||||||
|
|
||||||
|
export async function GET() {
|
||||||
|
const baseUrl = 'https://docs.sim.ai'
|
||||||
|
|
||||||
|
const robotsTxt = `# Robots.txt for Sim Documentation
|
||||||
|
# Generated on ${new Date().toISOString()}
|
||||||
|
|
||||||
|
User-agent: *
|
||||||
|
Allow: /
|
||||||
|
|
||||||
|
# Search engine crawlers
|
||||||
|
User-agent: Googlebot
|
||||||
|
Allow: /
|
||||||
|
|
||||||
|
User-agent: Bingbot
|
||||||
|
Allow: /
|
||||||
|
|
||||||
|
User-agent: Slurp
|
||||||
|
Allow: /
|
||||||
|
|
||||||
|
User-agent: DuckDuckBot
|
||||||
|
Allow: /
|
||||||
|
|
||||||
|
User-agent: Baiduspider
|
||||||
|
Allow: /
|
||||||
|
|
||||||
|
User-agent: YandexBot
|
||||||
|
Allow: /
|
||||||
|
|
||||||
|
# AI and LLM crawlers - explicitly allowed for documentation indexing
|
||||||
|
User-agent: GPTBot
|
||||||
|
Allow: /
|
||||||
|
|
||||||
|
User-agent: ChatGPT-User
|
||||||
|
Allow: /
|
||||||
|
|
||||||
|
User-agent: CCBot
|
||||||
|
Allow: /
|
||||||
|
|
||||||
|
User-agent: anthropic-ai
|
||||||
|
Allow: /
|
||||||
|
|
||||||
|
User-agent: Claude-Web
|
||||||
|
Allow: /
|
||||||
|
|
||||||
|
User-agent: Applebot
|
||||||
|
Allow: /
|
||||||
|
|
||||||
|
User-agent: PerplexityBot
|
||||||
|
Allow: /
|
||||||
|
|
||||||
|
User-agent: Diffbot
|
||||||
|
Allow: /
|
||||||
|
|
||||||
|
User-agent: FacebookBot
|
||||||
|
Allow: /
|
||||||
|
|
||||||
|
User-agent: cohere-ai
|
||||||
|
Allow: /
|
||||||
|
|
||||||
|
# Disallow admin and internal paths (if any exist)
|
||||||
|
Disallow: /.next/
|
||||||
|
Disallow: /api/internal/
|
||||||
|
Disallow: /_next/static/
|
||||||
|
Disallow: /admin/
|
||||||
|
|
||||||
|
# Allow but don't prioritize these
|
||||||
|
Allow: /api/search
|
||||||
|
Allow: /llms.txt
|
||||||
|
Allow: /llms-full.txt
|
||||||
|
Allow: /llms.mdx/
|
||||||
|
|
||||||
|
# Sitemaps
|
||||||
|
Sitemap: ${baseUrl}/sitemap.xml
|
||||||
|
|
||||||
|
# Crawl delay for aggressive bots (optional)
|
||||||
|
# Crawl-delay: 1
|
||||||
|
|
||||||
|
# Additional resources for AI indexing
|
||||||
|
# See https://github.com/AnswerDotAI/llms-txt for more info
|
||||||
|
# LLM-friendly content:
|
||||||
|
# Manifest: ${baseUrl}/llms.txt
|
||||||
|
# Full content: ${baseUrl}/llms-full.txt
|
||||||
|
# Individual pages: ${baseUrl}/llms.mdx/[page-path]
|
||||||
|
|
||||||
|
# Multi-language documentation available at:
|
||||||
|
# ${baseUrl}/en - English
|
||||||
|
# ${baseUrl}/es - Español
|
||||||
|
# ${baseUrl}/fr - Français
|
||||||
|
# ${baseUrl}/de - Deutsch
|
||||||
|
# ${baseUrl}/ja - 日本語
|
||||||
|
# ${baseUrl}/zh - 简体中文`
|
||||||
|
|
||||||
|
return new Response(robotsTxt, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'text/plain',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
63
apps/docs/app/sitemap.xml/route.ts
Normal file
63
apps/docs/app/sitemap.xml/route.ts
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import { i18n } from '@/lib/i18n'
|
||||||
|
import { source } from '@/lib/source'
|
||||||
|
|
||||||
|
export const revalidate = false
|
||||||
|
|
||||||
|
export async function GET() {
|
||||||
|
const baseUrl = 'https://docs.sim.ai'
|
||||||
|
|
||||||
|
const allPages = source.getPages()
|
||||||
|
|
||||||
|
const getPriority = (url: string): string => {
|
||||||
|
if (url === '/introduction' || url === '/') return '1.0'
|
||||||
|
if (url === '/getting-started') return '0.9'
|
||||||
|
if (url.match(/^\/[^/]+$/)) return '0.8'
|
||||||
|
if (url.includes('/sdks/') || url.includes('/tools/')) return '0.7'
|
||||||
|
return '0.6'
|
||||||
|
}
|
||||||
|
|
||||||
|
const urls = allPages
|
||||||
|
.flatMap((page) => {
|
||||||
|
const urlWithoutLang = page.url.replace(/^\/[a-z]{2}\//, '/')
|
||||||
|
|
||||||
|
return i18n.languages.map((lang) => {
|
||||||
|
const url =
|
||||||
|
lang === i18n.defaultLanguage
|
||||||
|
? `${baseUrl}${urlWithoutLang}`
|
||||||
|
: `${baseUrl}/${lang}${urlWithoutLang}`
|
||||||
|
|
||||||
|
return ` <url>
|
||||||
|
<loc>${url}</loc>
|
||||||
|
<lastmod>${new Date().toISOString().split('T')[0]}</lastmod>
|
||||||
|
<changefreq>weekly</changefreq>
|
||||||
|
<priority>${getPriority(urlWithoutLang)}</priority>
|
||||||
|
${i18n.languages.length > 1 ? generateAlternateLinks(baseUrl, urlWithoutLang) : ''}
|
||||||
|
</url>`
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.join('\n')
|
||||||
|
|
||||||
|
const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml">
|
||||||
|
${urls}
|
||||||
|
</urlset>`
|
||||||
|
|
||||||
|
return new Response(sitemap, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/xml',
|
||||||
|
'Cache-Control': 'public, max-age=3600, s-maxage=3600',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateAlternateLinks(baseUrl: string, urlWithoutLang: string): string {
|
||||||
|
return i18n.languages
|
||||||
|
.map((lang) => {
|
||||||
|
const url =
|
||||||
|
lang === i18n.defaultLanguage
|
||||||
|
? `${baseUrl}${urlWithoutLang}`
|
||||||
|
: `${baseUrl}/${lang}${urlWithoutLang}`
|
||||||
|
return ` <xhtml:link rel="alternate" hreflang="${lang}" href="${url}" />`
|
||||||
|
})
|
||||||
|
.join('\n')
|
||||||
|
}
|
||||||
11
apps/docs/cli.json
Normal file
11
apps/docs/cli.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"aliases": {
|
||||||
|
"uiDir": "./components/ui",
|
||||||
|
"componentsDir": "./components",
|
||||||
|
"blockDir": "./components",
|
||||||
|
"cssDir": "./styles",
|
||||||
|
"libDir": "./lib"
|
||||||
|
},
|
||||||
|
"baseDir": "",
|
||||||
|
"commands": {}
|
||||||
|
}
|
||||||
42
apps/docs/components/docs-layout/page-navigation-arrows.tsx
Normal file
42
apps/docs/components/docs-layout/page-navigation-arrows.tsx
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { ChevronLeft, ChevronRight } from 'lucide-react'
|
||||||
|
import Link from 'next/link'
|
||||||
|
|
||||||
|
interface PageNavigationArrowsProps {
|
||||||
|
previous?: {
|
||||||
|
url: string
|
||||||
|
}
|
||||||
|
next?: {
|
||||||
|
url: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PageNavigationArrows({ previous, next }: PageNavigationArrowsProps) {
|
||||||
|
if (!previous && !next) return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='flex items-center gap-2'>
|
||||||
|
{previous && (
|
||||||
|
<Link
|
||||||
|
href={previous.url}
|
||||||
|
className='inline-flex items-center justify-center gap-1.5 rounded-lg border border-border/40 bg-background px-2.5 py-1.5 text-muted-foreground/60 text-sm transition-all hover:border-border hover:bg-accent/50 hover:text-muted-foreground'
|
||||||
|
aria-label='Previous page'
|
||||||
|
title='Previous page'
|
||||||
|
>
|
||||||
|
<ChevronLeft className='h-4 w-4' />
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
{next && (
|
||||||
|
<Link
|
||||||
|
href={next.url}
|
||||||
|
className='inline-flex items-center justify-center gap-1.5 rounded-lg border border-border/40 bg-background px-2.5 py-1.5 text-muted-foreground/60 text-sm transition-all hover:border-border hover:bg-accent/50 hover:text-muted-foreground'
|
||||||
|
aria-label='Next page'
|
||||||
|
title='Next page'
|
||||||
|
>
|
||||||
|
<ChevronRight className='h-4 w-4' />
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
206
apps/docs/components/docs-layout/sidebar-components.tsx
Normal file
206
apps/docs/components/docs-layout/sidebar-components.tsx
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { type ReactNode, useEffect, useState } from 'react'
|
||||||
|
import type { Folder, Item, Separator } from 'fumadocs-core/page-tree'
|
||||||
|
import { ChevronRight } from 'lucide-react'
|
||||||
|
import Link from 'next/link'
|
||||||
|
import { usePathname } from 'next/navigation'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const LANG_PREFIXES = ['/en', '/es', '/fr', '/de', '/ja', '/zh']
|
||||||
|
|
||||||
|
function stripLangPrefix(path: string): string {
|
||||||
|
for (const prefix of LANG_PREFIXES) {
|
||||||
|
if (path === prefix) return '/'
|
||||||
|
if (path.startsWith(`${prefix}/`)) return path.slice(prefix.length)
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
function isActive(url: string, pathname: string, nested = true): boolean {
|
||||||
|
const normalizedPathname = stripLangPrefix(pathname)
|
||||||
|
const normalizedUrl = stripLangPrefix(url)
|
||||||
|
return (
|
||||||
|
normalizedUrl === normalizedPathname ||
|
||||||
|
(nested && normalizedPathname.startsWith(`${normalizedUrl}/`))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SidebarItem({ item }: { item: Item }) {
|
||||||
|
const pathname = usePathname()
|
||||||
|
const active = isActive(item.url, pathname, false)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
href={item.url}
|
||||||
|
data-active={active}
|
||||||
|
className={cn(
|
||||||
|
// Mobile styles (default)
|
||||||
|
'flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors',
|
||||||
|
'text-fd-muted-foreground hover:bg-fd-accent/50 hover:text-fd-accent-foreground',
|
||||||
|
active && 'bg-fd-primary/10 font-medium text-fd-primary',
|
||||||
|
// Desktop styles (lg+)
|
||||||
|
'lg:mb-[0.0625rem] lg:block lg:rounded-md lg:px-2.5 lg:py-1.5 lg:font-normal lg:text-[13px] lg:leading-tight',
|
||||||
|
'lg:text-gray-600 lg:dark:text-gray-400',
|
||||||
|
!active && 'lg:hover:bg-gray-100/60 lg:dark:hover:bg-gray-800/40',
|
||||||
|
active &&
|
||||||
|
'lg:bg-purple-50/80 lg:font-normal lg:text-purple-600 lg:dark:bg-purple-900/15 lg:dark:text-purple-400'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{item.name}
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SidebarFolder({ item, children }: { item: Folder; children: ReactNode }) {
|
||||||
|
const pathname = usePathname()
|
||||||
|
const hasActiveChild = checkHasActiveChild(item, pathname)
|
||||||
|
const hasChildren = item.children.length > 0
|
||||||
|
const [open, setOpen] = useState(hasActiveChild)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setOpen(hasActiveChild)
|
||||||
|
}, [hasActiveChild])
|
||||||
|
|
||||||
|
const active = item.index ? isActive(item.index.url, pathname, false) : false
|
||||||
|
|
||||||
|
if (item.index && !hasChildren) {
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
href={item.index.url}
|
||||||
|
data-active={active}
|
||||||
|
className={cn(
|
||||||
|
// Mobile styles (default)
|
||||||
|
'flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors',
|
||||||
|
'text-fd-muted-foreground hover:bg-fd-accent/50 hover:text-fd-accent-foreground',
|
||||||
|
active && 'bg-fd-primary/10 font-medium text-fd-primary',
|
||||||
|
// Desktop styles (lg+)
|
||||||
|
'lg:mb-[0.0625rem] lg:block lg:rounded-md lg:px-2.5 lg:py-1.5 lg:font-normal lg:text-[13px] lg:leading-tight',
|
||||||
|
'lg:text-gray-600 lg:dark:text-gray-400',
|
||||||
|
!active && 'lg:hover:bg-gray-100/60 lg:dark:hover:bg-gray-800/40',
|
||||||
|
active &&
|
||||||
|
'lg:bg-purple-50/80 lg:font-normal lg:text-purple-600 lg:dark:bg-purple-900/15 lg:dark:text-purple-400'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{item.name}
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='flex flex-col lg:mb-[0.0625rem]'>
|
||||||
|
<div className='flex w-full items-center lg:gap-0.5'>
|
||||||
|
{item.index ? (
|
||||||
|
<Link
|
||||||
|
href={item.index.url}
|
||||||
|
data-active={active}
|
||||||
|
className={cn(
|
||||||
|
// Mobile styles (default)
|
||||||
|
'flex flex-1 items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors',
|
||||||
|
'text-fd-muted-foreground hover:bg-fd-accent/50 hover:text-fd-accent-foreground',
|
||||||
|
active && 'bg-fd-primary/10 font-medium text-fd-primary',
|
||||||
|
// Desktop styles (lg+)
|
||||||
|
'lg:block lg:flex-1 lg:rounded-md lg:px-2.5 lg:py-1.5 lg:font-medium lg:text-[13px] lg:leading-tight',
|
||||||
|
'lg:text-gray-800 lg:dark:text-gray-200',
|
||||||
|
!active && 'lg:hover:bg-gray-100/60 lg:dark:hover:bg-gray-800/40',
|
||||||
|
active &&
|
||||||
|
'lg:bg-purple-50/80 lg:text-purple-600 lg:dark:bg-purple-900/15 lg:dark:text-purple-400'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{item.name}
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
<button
|
||||||
|
onClick={() => setOpen(!open)}
|
||||||
|
className={cn(
|
||||||
|
// Mobile styles (default)
|
||||||
|
'flex flex-1 items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors',
|
||||||
|
'text-fd-muted-foreground hover:bg-fd-accent/50',
|
||||||
|
// Desktop styles (lg+)
|
||||||
|
'lg:flex lg:w-full lg:cursor-pointer lg:items-center lg:justify-between lg:rounded-md lg:px-2.5 lg:py-1.5 lg:text-left lg:font-medium lg:text-[13px] lg:leading-tight',
|
||||||
|
'lg:text-gray-800 lg:hover:bg-gray-100/60 lg:dark:text-gray-200 lg:dark:hover:bg-gray-800/40'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<span>{item.name}</span>
|
||||||
|
{/* Desktop-only chevron for non-index folders */}
|
||||||
|
<ChevronRight
|
||||||
|
className={cn(
|
||||||
|
'ml-auto hidden h-3 w-3 flex-shrink-0 text-gray-400 transition-transform duration-200 ease-in-out lg:block dark:text-gray-500',
|
||||||
|
open && 'rotate-90'
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{hasChildren && (
|
||||||
|
<button
|
||||||
|
onClick={() => setOpen(!open)}
|
||||||
|
className={cn(
|
||||||
|
// Mobile styles
|
||||||
|
'rounded p-1 hover:bg-fd-accent/50',
|
||||||
|
// Desktop styles
|
||||||
|
'lg:cursor-pointer lg:rounded lg:p-1 lg:transition-colors lg:hover:bg-gray-100/60 lg:dark:hover:bg-gray-800/40'
|
||||||
|
)}
|
||||||
|
aria-label={open ? 'Collapse' : 'Expand'}
|
||||||
|
>
|
||||||
|
<ChevronRight
|
||||||
|
className={cn(
|
||||||
|
// Mobile styles
|
||||||
|
'h-4 w-4 transition-transform',
|
||||||
|
// Desktop styles
|
||||||
|
'lg:h-3 lg:w-3 lg:text-gray-400 lg:duration-200 lg:ease-in-out lg:dark:text-gray-500',
|
||||||
|
open && 'rotate-90'
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{hasChildren && (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'overflow-hidden transition-all duration-200 ease-in-out',
|
||||||
|
open ? 'max-h-[10000px] opacity-100' : 'max-h-0 opacity-0'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{/* Mobile: simple indent */}
|
||||||
|
<div className='ml-4 flex flex-col gap-0.5 lg:hidden'>{children}</div>
|
||||||
|
{/* Desktop: styled with border */}
|
||||||
|
<ul className='mt-0.5 ml-2 hidden space-y-[0.0625rem] border-gray-200/60 border-l pl-2.5 lg:block dark:border-gray-700/60'>
|
||||||
|
{children}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SidebarSeparator({ item }: { item: Separator }) {
|
||||||
|
return (
|
||||||
|
<p
|
||||||
|
className={cn(
|
||||||
|
// Mobile styles
|
||||||
|
'mt-4 mb-2 px-2 font-medium text-fd-muted-foreground text-xs',
|
||||||
|
// Desktop styles
|
||||||
|
'lg:mt-4 lg:mb-1.5 lg:px-2.5 lg:font-semibold lg:text-[10px] lg:text-gray-500/80 lg:uppercase lg:tracking-wide lg:dark:text-gray-500'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{item.name}
|
||||||
|
</p>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkHasActiveChild(node: Folder, pathname: string): boolean {
|
||||||
|
if (node.index && isActive(node.index.url, pathname)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const child of node.children) {
|
||||||
|
if (child.type === 'page' && isActive(child.url, pathname)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (child.type === 'folder' && checkHasActiveChild(child, pathname)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
41
apps/docs/components/docs-layout/toc-footer.tsx
Normal file
41
apps/docs/components/docs-layout/toc-footer.tsx
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { ArrowRight, ChevronRight } from 'lucide-react'
|
||||||
|
import Link from 'next/link'
|
||||||
|
|
||||||
|
export function TOCFooter() {
|
||||||
|
const [isHovered, setIsHovered] = useState(false)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='sticky bottom-0 mt-6'>
|
||||||
|
<div className='flex flex-col gap-2 rounded-lg border border-border bg-secondary p-6 text-sm'>
|
||||||
|
<div className='text-balance font-semibold text-base leading-tight'>
|
||||||
|
Start building today
|
||||||
|
</div>
|
||||||
|
<div className='text-muted-foreground'>Trusted by over 60,000 builders.</div>
|
||||||
|
<div className='text-muted-foreground'>
|
||||||
|
Build Agentic workflows visually on a drag-and-drop canvas or with natural language.
|
||||||
|
</div>
|
||||||
|
<Link
|
||||||
|
href='https://sim.ai/signup'
|
||||||
|
target='_blank'
|
||||||
|
rel='noopener noreferrer'
|
||||||
|
onMouseEnter={() => setIsHovered(true)}
|
||||||
|
onMouseLeave={() => setIsHovered(false)}
|
||||||
|
className='group mt-2 inline-flex h-8 w-fit items-center justify-center gap-1 whitespace-nowrap rounded-[10px] border border-[#6F3DFA] bg-gradient-to-b from-[#8357FF] to-[#6F3DFA] px-3 pr-[10px] pl-[12px] font-medium text-sm text-white shadow-[inset_0_2px_4px_0_#9B77FF] outline-none transition-all hover:shadow-lg focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50'
|
||||||
|
aria-label='Get started with Sim - Sign up for free'
|
||||||
|
>
|
||||||
|
<span>Get started</span>
|
||||||
|
<span className='inline-flex transition-transform duration-200 group-hover:translate-x-0.5'>
|
||||||
|
{isHovered ? (
|
||||||
|
<ArrowRight className='h-4 w-4' aria-hidden='true' />
|
||||||
|
) : (
|
||||||
|
<ChevronRight className='h-4 w-4' aria-hidden='true' />
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
@@ -1,10 +0,0 @@
|
|||||||
import defaultMdxComponents from 'fumadocs-ui/mdx'
|
|
||||||
import { ThemeImage } from './ui/theme-image'
|
|
||||||
|
|
||||||
// Extend the default MDX components with our custom components
|
|
||||||
const mdxComponents = {
|
|
||||||
...defaultMdxComponents,
|
|
||||||
ThemeImage,
|
|
||||||
}
|
|
||||||
|
|
||||||
export default mdxComponents
|
|
||||||
66
apps/docs/components/navbar/navbar.tsx
Normal file
66
apps/docs/components/navbar/navbar.tsx
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import Image from 'next/image'
|
||||||
|
import Link from 'next/link'
|
||||||
|
import { LanguageDropdown } from '@/components/ui/language-dropdown'
|
||||||
|
import { SearchTrigger } from '@/components/ui/search-trigger'
|
||||||
|
import { ThemeToggle } from '@/components/ui/theme-toggle'
|
||||||
|
|
||||||
|
export function Navbar() {
|
||||||
|
return (
|
||||||
|
<nav
|
||||||
|
className='sticky top-0 z-50 border-border/50 border-b'
|
||||||
|
style={{
|
||||||
|
backdropFilter: 'blur(25px) saturate(180%)',
|
||||||
|
WebkitBackdropFilter: 'blur(25px) saturate(180%)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Desktop: Single row layout */}
|
||||||
|
<div className='hidden h-16 w-full items-center lg:flex'>
|
||||||
|
<div
|
||||||
|
className='relative flex w-full items-center justify-between'
|
||||||
|
style={{
|
||||||
|
paddingLeft: 'calc(var(--sidebar-offset) + 32px)',
|
||||||
|
paddingRight: 'calc(var(--toc-offset) + 60px)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Left cluster: logo */}
|
||||||
|
<div className='flex items-center'>
|
||||||
|
<Link href='/' className='flex min-w-[100px] items-center'>
|
||||||
|
<Image
|
||||||
|
src='/static/logo.png'
|
||||||
|
alt='Sim'
|
||||||
|
width={72}
|
||||||
|
height={28}
|
||||||
|
className='h-7 w-auto'
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Center cluster: search - absolutely positioned to center */}
|
||||||
|
<div className='-translate-x-1/2 absolute left-1/2 flex items-center justify-center'>
|
||||||
|
<SearchTrigger />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Right cluster aligns with TOC edge */}
|
||||||
|
<div className='flex items-center gap-4'>
|
||||||
|
<Link
|
||||||
|
href='https://sim.ai'
|
||||||
|
target='_blank'
|
||||||
|
rel='noopener noreferrer'
|
||||||
|
className='rounded-xl px-3 py-2 font-normal text-[0.9375rem] text-foreground/60 leading-[1.4] transition-colors hover:bg-foreground/8 hover:text-foreground'
|
||||||
|
style={{
|
||||||
|
fontFamily:
|
||||||
|
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Platform
|
||||||
|
</Link>
|
||||||
|
<LanguageDropdown />
|
||||||
|
<ThemeToggle />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
)
|
||||||
|
}
|
||||||
60
apps/docs/components/page-actions.tsx
Normal file
60
apps/docs/components/page-actions.tsx
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { useCopyButton } from 'fumadocs-ui/utils/use-copy-button'
|
||||||
|
import { Check, Copy } from 'lucide-react'
|
||||||
|
|
||||||
|
const cache = new Map<string, string>()
|
||||||
|
|
||||||
|
export function LLMCopyButton({
|
||||||
|
markdownUrl,
|
||||||
|
}: {
|
||||||
|
/**
|
||||||
|
* A URL to fetch the raw Markdown/MDX content of page
|
||||||
|
*/
|
||||||
|
markdownUrl: string
|
||||||
|
}) {
|
||||||
|
const [isLoading, setLoading] = useState(false)
|
||||||
|
const [checked, onClick] = useCopyButton(async () => {
|
||||||
|
const cached = cache.get(markdownUrl)
|
||||||
|
if (cached) return navigator.clipboard.writeText(cached)
|
||||||
|
|
||||||
|
setLoading(true)
|
||||||
|
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.write([
|
||||||
|
new ClipboardItem({
|
||||||
|
'text/plain': fetch(markdownUrl).then(async (res) => {
|
||||||
|
const content = await res.text()
|
||||||
|
cache.set(markdownUrl, content)
|
||||||
|
|
||||||
|
return content
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
} finally {
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
disabled={isLoading}
|
||||||
|
onClick={onClick}
|
||||||
|
className='flex cursor-pointer items-center gap-1.5 rounded-lg border border-border/40 bg-background px-2.5 py-2 text-muted-foreground/60 text-sm leading-none transition-all hover:border-border hover:bg-accent/50 hover:text-muted-foreground'
|
||||||
|
aria-label={checked ? 'Copied to clipboard' : 'Copy page content'}
|
||||||
|
>
|
||||||
|
{checked ? (
|
||||||
|
<>
|
||||||
|
<Check className='h-3.5 w-3.5' />
|
||||||
|
<span>Copied</span>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Copy className='h-3.5 w-3.5' />
|
||||||
|
<span>Copy page</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
174
apps/docs/components/structured-data.tsx
Normal file
174
apps/docs/components/structured-data.tsx
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
import Script from 'next/script'
|
||||||
|
|
||||||
|
interface StructuredDataProps {
|
||||||
|
title: string
|
||||||
|
description: string
|
||||||
|
url: string
|
||||||
|
lang: string
|
||||||
|
dateModified?: string
|
||||||
|
breadcrumb?: Array<{ name: string; url: string }>
|
||||||
|
}
|
||||||
|
|
||||||
|
export function StructuredData({
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
url,
|
||||||
|
lang,
|
||||||
|
dateModified,
|
||||||
|
breadcrumb,
|
||||||
|
}: StructuredDataProps) {
|
||||||
|
const baseUrl = 'https://docs.sim.ai'
|
||||||
|
|
||||||
|
const articleStructuredData = {
|
||||||
|
'@context': 'https://schema.org',
|
||||||
|
'@type': 'TechArticle',
|
||||||
|
headline: title,
|
||||||
|
description: description,
|
||||||
|
url: url,
|
||||||
|
datePublished: dateModified || new Date().toISOString(),
|
||||||
|
dateModified: dateModified || new Date().toISOString(),
|
||||||
|
author: {
|
||||||
|
'@type': 'Organization',
|
||||||
|
name: 'Sim Team',
|
||||||
|
url: baseUrl,
|
||||||
|
},
|
||||||
|
publisher: {
|
||||||
|
'@type': 'Organization',
|
||||||
|
name: 'Sim',
|
||||||
|
url: baseUrl,
|
||||||
|
logo: {
|
||||||
|
'@type': 'ImageObject',
|
||||||
|
url: `${baseUrl}/static/logo.png`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mainEntityOfPage: {
|
||||||
|
'@type': 'WebPage',
|
||||||
|
'@id': url,
|
||||||
|
},
|
||||||
|
inLanguage: lang,
|
||||||
|
isPartOf: {
|
||||||
|
'@type': 'WebSite',
|
||||||
|
name: 'Sim Documentation',
|
||||||
|
url: baseUrl,
|
||||||
|
},
|
||||||
|
potentialAction: {
|
||||||
|
'@type': 'ReadAction',
|
||||||
|
target: url,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const breadcrumbStructuredData = breadcrumb && {
|
||||||
|
'@context': 'https://schema.org',
|
||||||
|
'@type': 'BreadcrumbList',
|
||||||
|
itemListElement: breadcrumb.map((item, index) => ({
|
||||||
|
'@type': 'ListItem',
|
||||||
|
position: index + 1,
|
||||||
|
name: item.name,
|
||||||
|
item: item.url,
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
|
||||||
|
const websiteStructuredData = url === baseUrl && {
|
||||||
|
'@context': 'https://schema.org',
|
||||||
|
'@type': 'WebSite',
|
||||||
|
name: 'Sim Documentation',
|
||||||
|
url: baseUrl,
|
||||||
|
description:
|
||||||
|
'Comprehensive documentation for Sim visual workflow builder for AI applications. Create powerful AI agents, automation workflows, and data processing pipelines.',
|
||||||
|
publisher: {
|
||||||
|
'@type': 'Organization',
|
||||||
|
name: 'Sim',
|
||||||
|
url: baseUrl,
|
||||||
|
},
|
||||||
|
potentialAction: {
|
||||||
|
'@type': 'SearchAction',
|
||||||
|
target: {
|
||||||
|
'@type': 'EntryPoint',
|
||||||
|
urlTemplate: `${baseUrl}/search?q={search_term_string}`,
|
||||||
|
},
|
||||||
|
'query-input': 'required name=search_term_string',
|
||||||
|
},
|
||||||
|
inLanguage: ['en', 'es', 'fr', 'de', 'ja', 'zh'],
|
||||||
|
}
|
||||||
|
|
||||||
|
const faqStructuredData = title.toLowerCase().includes('faq') && {
|
||||||
|
'@context': 'https://schema.org',
|
||||||
|
'@type': 'FAQPage',
|
||||||
|
mainEntity: [],
|
||||||
|
}
|
||||||
|
|
||||||
|
const softwareStructuredData = {
|
||||||
|
'@context': 'https://schema.org',
|
||||||
|
'@type': 'SoftwareApplication',
|
||||||
|
name: 'Sim',
|
||||||
|
applicationCategory: 'DeveloperApplication',
|
||||||
|
operatingSystem: 'Any',
|
||||||
|
description:
|
||||||
|
'Visual workflow builder for AI applications. Create powerful AI agents, automation workflows, and data processing pipelines by connecting blocks on a canvas—no coding required.',
|
||||||
|
url: baseUrl,
|
||||||
|
author: {
|
||||||
|
'@type': 'Organization',
|
||||||
|
name: 'Sim Team',
|
||||||
|
},
|
||||||
|
offers: {
|
||||||
|
'@type': 'Offer',
|
||||||
|
category: 'Developer Tools',
|
||||||
|
},
|
||||||
|
featureList: [
|
||||||
|
'Visual workflow builder with drag-and-drop interface',
|
||||||
|
'AI agent creation and automation',
|
||||||
|
'80+ built-in integrations',
|
||||||
|
'Real-time team collaboration',
|
||||||
|
'Multiple deployment options',
|
||||||
|
'Custom integrations via MCP protocol',
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Script
|
||||||
|
id='article-structured-data'
|
||||||
|
type='application/ld+json'
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: JSON.stringify(articleStructuredData),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{breadcrumbStructuredData && (
|
||||||
|
<Script
|
||||||
|
id='breadcrumb-structured-data'
|
||||||
|
type='application/ld+json'
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: JSON.stringify(breadcrumbStructuredData),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{websiteStructuredData && (
|
||||||
|
<Script
|
||||||
|
id='website-structured-data'
|
||||||
|
type='application/ld+json'
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: JSON.stringify(websiteStructuredData),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{faqStructuredData && (
|
||||||
|
<Script
|
||||||
|
id='faq-structured-data'
|
||||||
|
type='application/ld+json'
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: JSON.stringify(faqStructuredData),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{url === baseUrl && (
|
||||||
|
<Script
|
||||||
|
id='software-structured-data'
|
||||||
|
type='application/ld+json'
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: JSON.stringify(softwareStructuredData),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,44 +1,40 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import type * as React from 'react'
|
import type * as React from 'react'
|
||||||
|
import { blockTypeToIconMap } from '@/components/ui/icon-mapping'
|
||||||
|
|
||||||
interface BlockInfoCardProps {
|
interface BlockInfoCardProps {
|
||||||
type: string
|
type: string
|
||||||
color: string
|
color: string
|
||||||
icon?: boolean
|
icon?: React.ComponentType<{ className?: string }>
|
||||||
iconSvg?: string
|
iconSvg?: string // Deprecated: Use automatic icon resolution instead
|
||||||
}
|
}
|
||||||
|
|
||||||
export function BlockInfoCard({
|
export function BlockInfoCard({
|
||||||
type,
|
type,
|
||||||
color,
|
color,
|
||||||
icon = false,
|
icon: IconComponent,
|
||||||
iconSvg,
|
iconSvg,
|
||||||
}: BlockInfoCardProps): React.ReactNode {
|
}: BlockInfoCardProps): React.ReactNode {
|
||||||
|
// Auto-resolve icon component from block type if not explicitly provided
|
||||||
|
const ResolvedIcon = IconComponent || blockTypeToIconMap[type] || null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='mb-6 overflow-hidden rounded-lg border border-border'>
|
<div className='mb-6 overflow-hidden rounded-lg border border-border'>
|
||||||
<div className='flex items-center justify-center p-6'>
|
<div className='flex items-center justify-center p-6'>
|
||||||
<div
|
<div
|
||||||
className='flex h-20 w-20 items-center justify-center rounded-lg'
|
className='flex h-20 w-20 items-center justify-center rounded-lg'
|
||||||
style={{ backgroundColor: color }}
|
style={{ background: color }}
|
||||||
>
|
>
|
||||||
{iconSvg ? (
|
{ResolvedIcon ? (
|
||||||
|
<ResolvedIcon className='h-10 w-10 text-white' />
|
||||||
|
) : iconSvg ? (
|
||||||
<div className='h-10 w-10 text-white' dangerouslySetInnerHTML={{ __html: iconSvg }} />
|
<div className='h-10 w-10 text-white' dangerouslySetInnerHTML={{ __html: iconSvg }} />
|
||||||
) : (
|
) : (
|
||||||
<div className='font-mono text-xl opacity-70'>{type.substring(0, 2)}</div>
|
<div className='font-mono text-xl opacity-70'>{type.substring(0, 2)}</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{icon && (
|
|
||||||
<style jsx global>{`
|
|
||||||
.block-icon {
|
|
||||||
width: 80px;
|
|
||||||
height: 80px;
|
|
||||||
margin: 1rem auto;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
`}</style>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,163 +0,0 @@
|
|||||||
import {
|
|
||||||
AgentIcon,
|
|
||||||
ApiIcon,
|
|
||||||
ChartBarIcon,
|
|
||||||
CodeIcon,
|
|
||||||
ConditionalIcon,
|
|
||||||
ConnectIcon,
|
|
||||||
ResponseIcon,
|
|
||||||
} from '@/components/icons'
|
|
||||||
import { cn } from '@/lib/utils'
|
|
||||||
|
|
||||||
// Custom Feature component specifically for BlockTypes to handle the 6-item layout
|
|
||||||
const BlockFeature = ({
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
icon,
|
|
||||||
href,
|
|
||||||
index,
|
|
||||||
totalItems,
|
|
||||||
itemsPerRow,
|
|
||||||
}: {
|
|
||||||
title: string
|
|
||||||
description: string
|
|
||||||
icon: React.ReactNode
|
|
||||||
href?: string
|
|
||||||
index: number
|
|
||||||
totalItems: number
|
|
||||||
itemsPerRow: number
|
|
||||||
}) => {
|
|
||||||
const blockColor = {
|
|
||||||
'--block-color':
|
|
||||||
title === 'Agent'
|
|
||||||
? '#8b5cf6'
|
|
||||||
: title === 'API'
|
|
||||||
? '#3b82f6'
|
|
||||||
: title === 'Condition'
|
|
||||||
? '#f59e0b'
|
|
||||||
: title === 'Function'
|
|
||||||
? '#10b981'
|
|
||||||
: title === 'Router'
|
|
||||||
? '#6366f1'
|
|
||||||
: title === 'Evaluator'
|
|
||||||
? '#ef4444'
|
|
||||||
: '#8b5cf6',
|
|
||||||
} as React.CSSProperties
|
|
||||||
|
|
||||||
const content = (
|
|
||||||
<>
|
|
||||||
{index < itemsPerRow && (
|
|
||||||
<div className='pointer-events-none absolute inset-0 h-full w-full bg-gradient-to-t from-neutral-100 to-transparent opacity-0 transition duration-200 group-hover/feature:opacity-100 dark:from-neutral-800' />
|
|
||||||
)}
|
|
||||||
{index >= itemsPerRow && (
|
|
||||||
<div className='pointer-events-none absolute inset-0 h-full w-full bg-gradient-to-b from-neutral-100 to-transparent opacity-0 transition duration-200 group-hover/feature:opacity-100 dark:from-neutral-800' />
|
|
||||||
)}
|
|
||||||
<div
|
|
||||||
className='relative z-10 mb-4 px-10 text-neutral-500 transition-colors duration-200 group-hover/feature:text-[color:var(--block-color,#8b5cf6)] dark:text-neutral-400 dark:group-hover/feature:text-[color:var(--block-color,#a78bfa)]'
|
|
||||||
style={blockColor}
|
|
||||||
>
|
|
||||||
{icon}
|
|
||||||
</div>
|
|
||||||
<div className='relative z-10 mb-2 px-10 font-bold text-lg'>
|
|
||||||
<div
|
|
||||||
className='absolute inset-y-0 left-0 h-6 w-1 origin-center rounded-tr-full rounded-br-full bg-neutral-300 transition-all duration-200 group-hover/feature:h-8 group-hover/feature:bg-[color:var(--block-color,#8b5cf6)] dark:bg-neutral-700'
|
|
||||||
style={blockColor}
|
|
||||||
/>
|
|
||||||
<span className='inline-block text-neutral-800 transition duration-200 group-hover/feature:translate-x-2 dark:text-neutral-100'>
|
|
||||||
{title}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<p className='relative z-10 max-w-xs px-10 text-neutral-600 text-sm dark:text-neutral-300'>
|
|
||||||
{description}
|
|
||||||
</p>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
|
|
||||||
const containerClasses = cn(
|
|
||||||
'flex flex-col lg:border-r py-5 relative group/feature dark:border-neutral-800',
|
|
||||||
(index === 0 || index === itemsPerRow) && 'lg:border-l dark:border-neutral-800',
|
|
||||||
index < itemsPerRow && 'lg:border-b dark:border-neutral-800',
|
|
||||||
href && 'cursor-pointer hover:bg-neutral-50 dark:hover:bg-neutral-900/50 transition-colors'
|
|
||||||
)
|
|
||||||
|
|
||||||
if (href) {
|
|
||||||
return (
|
|
||||||
<a href={href} className={containerClasses} style={{ textDecoration: 'none' }}>
|
|
||||||
{content}
|
|
||||||
</a>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return <div className={containerClasses}>{content}</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
export function BlockTypes() {
|
|
||||||
const features = [
|
|
||||||
{
|
|
||||||
title: 'Agent',
|
|
||||||
description:
|
|
||||||
'Create powerful AI agents using any LLM provider with customizable system prompts and tool integrations.',
|
|
||||||
icon: <AgentIcon className='h-6 w-6' />,
|
|
||||||
href: '/blocks/agent',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'API',
|
|
||||||
description:
|
|
||||||
'Connect to any external API with support for all standard HTTP methods and customizable request parameters.',
|
|
||||||
icon: <ApiIcon className='h-6 w-6' />,
|
|
||||||
href: '/blocks/api',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Condition',
|
|
||||||
description:
|
|
||||||
'Add a condition to the workflow to branch the execution path based on a boolean expression.',
|
|
||||||
icon: <ConditionalIcon className='h-6 w-6' />,
|
|
||||||
href: '/blocks/condition',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Function',
|
|
||||||
description:
|
|
||||||
'Execute custom JavaScript or TypeScript code within your workflow to transform data or implement complex logic.',
|
|
||||||
icon: <CodeIcon className='h-6 w-6' />,
|
|
||||||
href: '/blocks/function',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Router',
|
|
||||||
description:
|
|
||||||
'Intelligently direct workflow execution to different paths based on input analysis.',
|
|
||||||
icon: <ConnectIcon className='h-6 w-6' />,
|
|
||||||
href: '/blocks/router',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Evaluator',
|
|
||||||
description:
|
|
||||||
'Assess content using customizable evaluation metrics and scoring criteria across multiple dimensions.',
|
|
||||||
icon: <ChartBarIcon className='h-6 w-6' />,
|
|
||||||
href: '/blocks/evaluator',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Response',
|
|
||||||
description:
|
|
||||||
'Send a response back to the caller with customizable data, status, and headers.',
|
|
||||||
icon: <ResponseIcon className='h-6 w-6' />,
|
|
||||||
href: '/blocks/response',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
const totalItems = features.length
|
|
||||||
const itemsPerRow = 3 // For large screens
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='relative z-10 mx-auto grid max-w-7xl grid-cols-1 py-10 md:grid-cols-2 lg:grid-cols-3'>
|
|
||||||
{features.map((feature, index) => (
|
|
||||||
<BlockFeature
|
|
||||||
key={feature.title}
|
|
||||||
{...feature}
|
|
||||||
index={index}
|
|
||||||
totalItems={totalItems}
|
|
||||||
itemsPerRow={itemsPerRow}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
27
apps/docs/components/ui/button.tsx
Normal file
27
apps/docs/components/ui/button.tsx
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { cva, type VariantProps } from 'class-variance-authority'
|
||||||
|
|
||||||
|
const variants = {
|
||||||
|
primary: 'bg-fd-primary text-fd-primary-foreground hover:bg-fd-primary/80',
|
||||||
|
outline: 'border hover:bg-fd-accent hover:text-fd-accent-foreground',
|
||||||
|
ghost: 'hover:bg-fd-accent hover:text-fd-accent-foreground',
|
||||||
|
secondary:
|
||||||
|
'border bg-fd-secondary text-fd-secondary-foreground hover:bg-fd-accent hover:text-fd-accent-foreground',
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export const buttonVariants = cva(
|
||||||
|
'inline-flex items-center justify-center rounded-md p-2 text-sm font-medium transition-colors duration-100 disabled:pointer-events-none disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fd-ring',
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: variants,
|
||||||
|
color: variants,
|
||||||
|
size: {
|
||||||
|
sm: 'gap-1 px-2 py-1.5 text-xs',
|
||||||
|
icon: 'p-1.5 [&_svg]:size-5',
|
||||||
|
'icon-sm': 'p-1.5 [&_svg]:size-4.5',
|
||||||
|
'icon-xs': 'p-1 [&_svg]:size-4',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export type ButtonProps = VariantProps<typeof buttonVariants>
|
||||||
50
apps/docs/components/ui/code-block.tsx
Normal file
50
apps/docs/components/ui/code-block.tsx
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { CodeBlock as FumadocsCodeBlock } from 'fumadocs-ui/components/codeblock'
|
||||||
|
import { Check, Copy } from 'lucide-react'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
export function CodeBlock(props: React.ComponentProps<typeof FumadocsCodeBlock>) {
|
||||||
|
const [copied, setCopied] = useState(false)
|
||||||
|
|
||||||
|
const handleCopy = async (text: string) => {
|
||||||
|
await navigator.clipboard.writeText(text)
|
||||||
|
setCopied(true)
|
||||||
|
setTimeout(() => setCopied(false), 2000)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FumadocsCodeBlock
|
||||||
|
{...props}
|
||||||
|
Actions={({ children, className }) => (
|
||||||
|
<div className={cn('empty:hidden', className)}>
|
||||||
|
{/* Custom copy button */}
|
||||||
|
<button
|
||||||
|
type='button'
|
||||||
|
aria-label={copied ? 'Copied Text' : 'Copy Text'}
|
||||||
|
onClick={(e) => {
|
||||||
|
const pre = (e.currentTarget as HTMLElement)
|
||||||
|
.closest('.nd-codeblock')
|
||||||
|
?.querySelector('pre')
|
||||||
|
if (pre) handleCopy(pre.textContent || '')
|
||||||
|
}}
|
||||||
|
className={cn(
|
||||||
|
'cursor-pointer rounded-md p-2 transition-all',
|
||||||
|
'border border-border bg-background/80 hover:bg-muted',
|
||||||
|
'backdrop-blur-sm'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<span className='flex items-center justify-center'>
|
||||||
|
{copied ? (
|
||||||
|
<Check size={16} className='text-green-600 dark:text-green-400' />
|
||||||
|
) : (
|
||||||
|
<Copy size={16} className='text-muted-foreground' />
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
import {
|
|
||||||
IconAdjustmentsBolt,
|
|
||||||
IconCloud,
|
|
||||||
IconEaseInOut,
|
|
||||||
IconHeart,
|
|
||||||
IconHelp,
|
|
||||||
IconHistory,
|
|
||||||
IconRouteAltLeft,
|
|
||||||
IconTerminal2,
|
|
||||||
} from '@tabler/icons-react'
|
|
||||||
import { cn } from '@/lib/utils'
|
|
||||||
|
|
||||||
export function Features() {
|
|
||||||
const features = [
|
|
||||||
{
|
|
||||||
title: 'Multi-LLM Support',
|
|
||||||
description: 'Connect to any LLM provider including OpenAI, Anthropic, and more',
|
|
||||||
icon: <IconCloud />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'API Deployment',
|
|
||||||
description: 'Deploy your workflows as secure, scalable APIs',
|
|
||||||
icon: <IconTerminal2 />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Webhook Integration',
|
|
||||||
description: 'Trigger workflows via webhooks from external services',
|
|
||||||
icon: <IconRouteAltLeft />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Scheduled Execution',
|
|
||||||
description: 'Schedule workflows to run at specific times or intervals',
|
|
||||||
icon: <IconEaseInOut />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '40+ Integrations',
|
|
||||||
description: 'Connect to hundreds of external services and data sources',
|
|
||||||
icon: <IconAdjustmentsBolt />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Visual Debugging',
|
|
||||||
description: 'Debug workflows visually with detailed execution logs',
|
|
||||||
icon: <IconHelp />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Version Control',
|
|
||||||
description: 'Track changes and roll back to previous versions',
|
|
||||||
icon: <IconHistory />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Team Collaboration',
|
|
||||||
description: 'Collaborate with team members on workflow development',
|
|
||||||
icon: <IconHeart />,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
return (
|
|
||||||
<div className='relative z-20 mx-auto grid max-w-7xl grid-cols-1 py-10 md:grid-cols-2 lg:grid-cols-4'>
|
|
||||||
{features.map((feature, index) => (
|
|
||||||
<Feature key={feature.title} {...feature} index={index} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Feature = ({
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
icon,
|
|
||||||
index,
|
|
||||||
}: {
|
|
||||||
title: string
|
|
||||||
description: string
|
|
||||||
icon: React.ReactNode
|
|
||||||
index: number
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
'group/feature relative flex flex-col py-5 lg:border-r dark:border-neutral-800',
|
|
||||||
(index === 0 || index === 4) && 'lg:border-l dark:border-neutral-800',
|
|
||||||
index < 4 && 'lg:border-b dark:border-neutral-800'
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{index < 4 && (
|
|
||||||
<div className='pointer-events-none absolute inset-0 h-full w-full bg-gradient-to-t from-neutral-100 to-transparent opacity-0 transition duration-200 group-hover/feature:opacity-100 dark:from-neutral-800' />
|
|
||||||
)}
|
|
||||||
{index >= 4 && (
|
|
||||||
<div className='pointer-events-none absolute inset-0 h-full w-full bg-gradient-to-b from-neutral-100 to-transparent opacity-0 transition duration-200 group-hover/feature:opacity-100 dark:from-neutral-800' />
|
|
||||||
)}
|
|
||||||
<div className='relative z-10 mb-4 px-10 text-neutral-600 dark:text-neutral-400'>{icon}</div>
|
|
||||||
<div className='relative z-10 mb-2 px-10 font-bold text-lg'>
|
|
||||||
<div className='absolute inset-y-0 left-0 h-6 w-1 origin-center rounded-tr-full rounded-br-full bg-neutral-300 transition-all duration-200 group-hover/feature:h-8 group-hover/feature:bg-purple-500 dark:bg-neutral-700' />
|
|
||||||
<span className='inline-block text-neutral-800 transition duration-200 group-hover/feature:translate-x-2 dark:text-neutral-100'>
|
|
||||||
{title}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<p className='relative z-10 max-w-xs px-10 text-neutral-600 text-sm dark:text-neutral-300'>
|
|
||||||
{description}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
58
apps/docs/components/ui/heading.tsx
Normal file
58
apps/docs/components/ui/heading.tsx
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { type ComponentPropsWithoutRef, useState } from 'react'
|
||||||
|
import { Check, Link } from 'lucide-react'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
type HeadingTag = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'
|
||||||
|
|
||||||
|
interface HeadingProps extends ComponentPropsWithoutRef<'h1'> {
|
||||||
|
as?: HeadingTag
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Heading({ as, className, ...props }: HeadingProps) {
|
||||||
|
const [copied, setCopied] = useState(false)
|
||||||
|
const As = as ?? 'h1'
|
||||||
|
|
||||||
|
if (!props.id) {
|
||||||
|
return <As className={className} {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleClick = async (e: React.MouseEvent) => {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
const url = `${window.location.origin}${window.location.pathname}#${props.id}`
|
||||||
|
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(url)
|
||||||
|
setCopied(true)
|
||||||
|
|
||||||
|
// Update URL hash without scrolling
|
||||||
|
window.history.pushState(null, '', `#${props.id}`)
|
||||||
|
|
||||||
|
setTimeout(() => setCopied(false), 2000)
|
||||||
|
} catch {
|
||||||
|
// Fallback: just navigate to the anchor
|
||||||
|
window.location.hash = props.id as string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<As className={cn('group flex scroll-m-28 flex-row items-center gap-2', className)} {...props}>
|
||||||
|
<a data-card='' href={`#${props.id}`} className='peer' onClick={handleClick}>
|
||||||
|
{props.children}
|
||||||
|
</a>
|
||||||
|
{copied ? (
|
||||||
|
<Check
|
||||||
|
aria-hidden
|
||||||
|
className='size-3.5 shrink-0 text-green-500 opacity-100 transition-opacity'
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Link
|
||||||
|
aria-hidden
|
||||||
|
className='size-3.5 shrink-0 text-fd-muted-foreground opacity-0 transition-opacity group-hover:opacity-100 peer-hover:opacity-100'
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</As>
|
||||||
|
)
|
||||||
|
}
|
||||||
248
apps/docs/components/ui/icon-mapping.ts
Normal file
248
apps/docs/components/ui/icon-mapping.ts
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
// Auto-generated file - do not edit manually
|
||||||
|
// Generated by scripts/generate-docs.ts
|
||||||
|
// Maps block types to their icon component references
|
||||||
|
|
||||||
|
import type { ComponentType, SVGProps } from 'react'
|
||||||
|
import {
|
||||||
|
AhrefsIcon,
|
||||||
|
AirtableIcon,
|
||||||
|
ApifyIcon,
|
||||||
|
ApolloIcon,
|
||||||
|
ArxivIcon,
|
||||||
|
AsanaIcon,
|
||||||
|
BrainIcon,
|
||||||
|
BrowserUseIcon,
|
||||||
|
CalendlyIcon,
|
||||||
|
CirclebackIcon,
|
||||||
|
ClayIcon,
|
||||||
|
ConfluenceIcon,
|
||||||
|
CursorIcon,
|
||||||
|
DatadogIcon,
|
||||||
|
DiscordIcon,
|
||||||
|
DocumentIcon,
|
||||||
|
DropboxIcon,
|
||||||
|
DuckDuckGoIcon,
|
||||||
|
DynamoDBIcon,
|
||||||
|
ElasticsearchIcon,
|
||||||
|
ElevenLabsIcon,
|
||||||
|
ExaAIIcon,
|
||||||
|
EyeIcon,
|
||||||
|
FirecrawlIcon,
|
||||||
|
FirefliesIcon,
|
||||||
|
GithubIcon,
|
||||||
|
GitLabIcon,
|
||||||
|
GmailIcon,
|
||||||
|
GoogleCalendarIcon,
|
||||||
|
GoogleDocsIcon,
|
||||||
|
GoogleDriveIcon,
|
||||||
|
GoogleFormsIcon,
|
||||||
|
GoogleGroupsIcon,
|
||||||
|
GoogleIcon,
|
||||||
|
GoogleSheetsIcon,
|
||||||
|
GoogleSlidesIcon,
|
||||||
|
GoogleVaultIcon,
|
||||||
|
GrafanaIcon,
|
||||||
|
GrainIcon,
|
||||||
|
GreptileIcon,
|
||||||
|
HubspotIcon,
|
||||||
|
HuggingFaceIcon,
|
||||||
|
HunterIOIcon,
|
||||||
|
ImageIcon,
|
||||||
|
IncidentioIcon,
|
||||||
|
IntercomIcon,
|
||||||
|
JinaAIIcon,
|
||||||
|
JiraIcon,
|
||||||
|
JiraServiceManagementIcon,
|
||||||
|
KalshiIcon,
|
||||||
|
LinearIcon,
|
||||||
|
LinkedInIcon,
|
||||||
|
LinkupIcon,
|
||||||
|
MailchimpIcon,
|
||||||
|
MailgunIcon,
|
||||||
|
MailServerIcon,
|
||||||
|
Mem0Icon,
|
||||||
|
MicrosoftExcelIcon,
|
||||||
|
MicrosoftOneDriveIcon,
|
||||||
|
MicrosoftPlannerIcon,
|
||||||
|
MicrosoftSharepointIcon,
|
||||||
|
MicrosoftTeamsIcon,
|
||||||
|
MistralIcon,
|
||||||
|
MongoDBIcon,
|
||||||
|
MySQLIcon,
|
||||||
|
Neo4jIcon,
|
||||||
|
NotionIcon,
|
||||||
|
OpenAIIcon,
|
||||||
|
OutlookIcon,
|
||||||
|
PackageSearchIcon,
|
||||||
|
ParallelIcon,
|
||||||
|
PerplexityIcon,
|
||||||
|
PineconeIcon,
|
||||||
|
PipedriveIcon,
|
||||||
|
PolymarketIcon,
|
||||||
|
PostgresIcon,
|
||||||
|
PosthogIcon,
|
||||||
|
QdrantIcon,
|
||||||
|
RDSIcon,
|
||||||
|
RedditIcon,
|
||||||
|
ResendIcon,
|
||||||
|
S3Icon,
|
||||||
|
SalesforceIcon,
|
||||||
|
SearchIcon,
|
||||||
|
SendgridIcon,
|
||||||
|
SentryIcon,
|
||||||
|
SerperIcon,
|
||||||
|
ServiceNowIcon,
|
||||||
|
SftpIcon,
|
||||||
|
ShopifyIcon,
|
||||||
|
SlackIcon,
|
||||||
|
SmtpIcon,
|
||||||
|
SpotifyIcon,
|
||||||
|
SQSIcon,
|
||||||
|
SshIcon,
|
||||||
|
STTIcon,
|
||||||
|
StagehandIcon,
|
||||||
|
StripeIcon,
|
||||||
|
SupabaseIcon,
|
||||||
|
TavilyIcon,
|
||||||
|
TelegramIcon,
|
||||||
|
TranslateIcon,
|
||||||
|
TrelloIcon,
|
||||||
|
TTSIcon,
|
||||||
|
TwilioIcon,
|
||||||
|
TypeformIcon,
|
||||||
|
VideoIcon,
|
||||||
|
WealthboxIcon,
|
||||||
|
WebflowIcon,
|
||||||
|
WhatsAppIcon,
|
||||||
|
WikipediaIcon,
|
||||||
|
WordpressIcon,
|
||||||
|
xIcon,
|
||||||
|
YouTubeIcon,
|
||||||
|
ZendeskIcon,
|
||||||
|
ZepIcon,
|
||||||
|
ZoomIcon,
|
||||||
|
} from '@/components/icons'
|
||||||
|
|
||||||
|
type IconComponent = ComponentType<SVGProps<SVGSVGElement>>
|
||||||
|
|
||||||
|
export const blockTypeToIconMap: Record<string, IconComponent> = {
|
||||||
|
ahrefs: AhrefsIcon,
|
||||||
|
airtable: AirtableIcon,
|
||||||
|
apify: ApifyIcon,
|
||||||
|
apollo: ApolloIcon,
|
||||||
|
arxiv: ArxivIcon,
|
||||||
|
asana: AsanaIcon,
|
||||||
|
browser_use: BrowserUseIcon,
|
||||||
|
calendly: CalendlyIcon,
|
||||||
|
circleback: CirclebackIcon,
|
||||||
|
clay: ClayIcon,
|
||||||
|
confluence: ConfluenceIcon,
|
||||||
|
cursor: CursorIcon,
|
||||||
|
datadog: DatadogIcon,
|
||||||
|
discord: DiscordIcon,
|
||||||
|
dropbox: DropboxIcon,
|
||||||
|
duckduckgo: DuckDuckGoIcon,
|
||||||
|
dynamodb: DynamoDBIcon,
|
||||||
|
elasticsearch: ElasticsearchIcon,
|
||||||
|
elevenlabs: ElevenLabsIcon,
|
||||||
|
exa: ExaAIIcon,
|
||||||
|
file: DocumentIcon,
|
||||||
|
firecrawl: FirecrawlIcon,
|
||||||
|
fireflies: FirefliesIcon,
|
||||||
|
github: GithubIcon,
|
||||||
|
gitlab: GitLabIcon,
|
||||||
|
gmail: GmailIcon,
|
||||||
|
google_calendar: GoogleCalendarIcon,
|
||||||
|
google_docs: GoogleDocsIcon,
|
||||||
|
google_drive: GoogleDriveIcon,
|
||||||
|
google_forms: GoogleFormsIcon,
|
||||||
|
google_groups: GoogleGroupsIcon,
|
||||||
|
google_search: GoogleIcon,
|
||||||
|
google_sheets: GoogleSheetsIcon,
|
||||||
|
google_slides: GoogleSlidesIcon,
|
||||||
|
google_vault: GoogleVaultIcon,
|
||||||
|
grafana: GrafanaIcon,
|
||||||
|
grain: GrainIcon,
|
||||||
|
greptile: GreptileIcon,
|
||||||
|
hubspot: HubspotIcon,
|
||||||
|
huggingface: HuggingFaceIcon,
|
||||||
|
hunter: HunterIOIcon,
|
||||||
|
image_generator: ImageIcon,
|
||||||
|
imap: MailServerIcon,
|
||||||
|
incidentio: IncidentioIcon,
|
||||||
|
intercom: IntercomIcon,
|
||||||
|
jina: JinaAIIcon,
|
||||||
|
jira: JiraIcon,
|
||||||
|
jira_service_management: JiraServiceManagementIcon,
|
||||||
|
kalshi: KalshiIcon,
|
||||||
|
knowledge: PackageSearchIcon,
|
||||||
|
linear: LinearIcon,
|
||||||
|
linkedin: LinkedInIcon,
|
||||||
|
linkup: LinkupIcon,
|
||||||
|
mailchimp: MailchimpIcon,
|
||||||
|
mailgun: MailgunIcon,
|
||||||
|
mem0: Mem0Icon,
|
||||||
|
memory: BrainIcon,
|
||||||
|
microsoft_excel: MicrosoftExcelIcon,
|
||||||
|
microsoft_planner: MicrosoftPlannerIcon,
|
||||||
|
microsoft_teams: MicrosoftTeamsIcon,
|
||||||
|
mistral_parse: MistralIcon,
|
||||||
|
mongodb: MongoDBIcon,
|
||||||
|
mysql: MySQLIcon,
|
||||||
|
neo4j: Neo4jIcon,
|
||||||
|
notion: NotionIcon,
|
||||||
|
onedrive: MicrosoftOneDriveIcon,
|
||||||
|
openai: OpenAIIcon,
|
||||||
|
outlook: OutlookIcon,
|
||||||
|
parallel_ai: ParallelIcon,
|
||||||
|
perplexity: PerplexityIcon,
|
||||||
|
pinecone: PineconeIcon,
|
||||||
|
pipedrive: PipedriveIcon,
|
||||||
|
polymarket: PolymarketIcon,
|
||||||
|
postgresql: PostgresIcon,
|
||||||
|
posthog: PosthogIcon,
|
||||||
|
qdrant: QdrantIcon,
|
||||||
|
rds: RDSIcon,
|
||||||
|
reddit: RedditIcon,
|
||||||
|
resend: ResendIcon,
|
||||||
|
s3: S3Icon,
|
||||||
|
salesforce: SalesforceIcon,
|
||||||
|
search: SearchIcon,
|
||||||
|
sendgrid: SendgridIcon,
|
||||||
|
sentry: SentryIcon,
|
||||||
|
serper: SerperIcon,
|
||||||
|
servicenow: ServiceNowIcon,
|
||||||
|
sftp: SftpIcon,
|
||||||
|
sharepoint: MicrosoftSharepointIcon,
|
||||||
|
shopify: ShopifyIcon,
|
||||||
|
slack: SlackIcon,
|
||||||
|
smtp: SmtpIcon,
|
||||||
|
spotify: SpotifyIcon,
|
||||||
|
sqs: SQSIcon,
|
||||||
|
ssh: SshIcon,
|
||||||
|
stagehand: StagehandIcon,
|
||||||
|
stripe: StripeIcon,
|
||||||
|
stt: STTIcon,
|
||||||
|
supabase: SupabaseIcon,
|
||||||
|
tavily: TavilyIcon,
|
||||||
|
telegram: TelegramIcon,
|
||||||
|
thinking: BrainIcon,
|
||||||
|
translate: TranslateIcon,
|
||||||
|
trello: TrelloIcon,
|
||||||
|
tts: TTSIcon,
|
||||||
|
twilio_sms: TwilioIcon,
|
||||||
|
twilio_voice: TwilioIcon,
|
||||||
|
typeform: TypeformIcon,
|
||||||
|
video_generator: VideoIcon,
|
||||||
|
vision: EyeIcon,
|
||||||
|
wealthbox: WealthboxIcon,
|
||||||
|
webflow: WebflowIcon,
|
||||||
|
whatsapp: WhatsAppIcon,
|
||||||
|
wikipedia: WikipediaIcon,
|
||||||
|
wordpress: WordpressIcon,
|
||||||
|
x: xIcon,
|
||||||
|
youtube: YouTubeIcon,
|
||||||
|
zendesk: ZendeskIcon,
|
||||||
|
zep: ZepIcon,
|
||||||
|
zoom: ZoomIcon,
|
||||||
|
}
|
||||||
53
apps/docs/components/ui/image.tsx
Normal file
53
apps/docs/components/ui/image.tsx
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useState } from 'react'
|
||||||
|
import NextImage, { type ImageProps as NextImageProps } from 'next/image'
|
||||||
|
import { Lightbox } from '@/components/ui/lightbox'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
interface ImageProps extends Omit<NextImageProps, 'className'> {
|
||||||
|
className?: string
|
||||||
|
enableLightbox?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Image({
|
||||||
|
className = 'w-full',
|
||||||
|
enableLightbox = true,
|
||||||
|
alt = '',
|
||||||
|
src,
|
||||||
|
...props
|
||||||
|
}: ImageProps) {
|
||||||
|
const [isLightboxOpen, setIsLightboxOpen] = useState(false)
|
||||||
|
|
||||||
|
const handleImageClick = () => {
|
||||||
|
if (enableLightbox) {
|
||||||
|
setIsLightboxOpen(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<NextImage
|
||||||
|
className={cn(
|
||||||
|
'overflow-hidden rounded-xl border border-border object-cover shadow-sm',
|
||||||
|
enableLightbox && 'cursor-pointer transition-opacity hover:opacity-90',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
alt={alt}
|
||||||
|
src={src}
|
||||||
|
onClick={handleImageClick}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{enableLightbox && (
|
||||||
|
<Lightbox
|
||||||
|
isOpen={isLightboxOpen}
|
||||||
|
onClose={() => setIsLightboxOpen(false)}
|
||||||
|
src={typeof src === 'string' ? src : String(src)}
|
||||||
|
alt={alt}
|
||||||
|
type='image'
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
129
apps/docs/components/ui/language-dropdown.tsx
Normal file
129
apps/docs/components/ui/language-dropdown.tsx
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { Check, ChevronRight } from 'lucide-react'
|
||||||
|
import { useParams, usePathname, useRouter } from 'next/navigation'
|
||||||
|
|
||||||
|
const languages = {
|
||||||
|
en: { name: 'English', flag: '🇺🇸' },
|
||||||
|
es: { name: 'Español', flag: '🇪🇸' },
|
||||||
|
fr: { name: 'Français', flag: '🇫🇷' },
|
||||||
|
de: { name: 'Deutsch', flag: '🇩🇪' },
|
||||||
|
ja: { name: '日本語', flag: '🇯🇵' },
|
||||||
|
zh: { name: '简体中文', flag: '🇨🇳' },
|
||||||
|
}
|
||||||
|
|
||||||
|
export function LanguageDropdown() {
|
||||||
|
const [isOpen, setIsOpen] = useState(false)
|
||||||
|
const pathname = usePathname()
|
||||||
|
const params = useParams()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const [currentLang, setCurrentLang] = useState(() => {
|
||||||
|
const langFromParams = params?.lang as string
|
||||||
|
return langFromParams && Object.keys(languages).includes(langFromParams) ? langFromParams : 'en'
|
||||||
|
})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const langFromParams = params?.lang as string
|
||||||
|
|
||||||
|
if (langFromParams && Object.keys(languages).includes(langFromParams)) {
|
||||||
|
if (langFromParams !== currentLang) {
|
||||||
|
setCurrentLang(langFromParams)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (currentLang !== 'en') {
|
||||||
|
setCurrentLang('en')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [params, currentLang])
|
||||||
|
|
||||||
|
const handleLanguageChange = (locale: string) => {
|
||||||
|
if (locale === currentLang) {
|
||||||
|
setIsOpen(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsOpen(false)
|
||||||
|
|
||||||
|
const segments = pathname.split('/').filter(Boolean)
|
||||||
|
|
||||||
|
if (segments[0] && Object.keys(languages).includes(segments[0])) {
|
||||||
|
segments.shift()
|
||||||
|
}
|
||||||
|
|
||||||
|
let newPath = ''
|
||||||
|
if (locale === 'en') {
|
||||||
|
newPath = segments.length > 0 ? `/${segments.join('/')}` : '/introduction'
|
||||||
|
} else {
|
||||||
|
newPath = `/${locale}${segments.length > 0 ? `/${segments.join('/')}` : '/introduction'}`
|
||||||
|
}
|
||||||
|
|
||||||
|
router.push(newPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isOpen) return
|
||||||
|
const onKey = (e: KeyboardEvent) => {
|
||||||
|
if (e.key === 'Escape') setIsOpen(false)
|
||||||
|
}
|
||||||
|
window.addEventListener('keydown', onKey)
|
||||||
|
return () => window.removeEventListener('keydown', onKey)
|
||||||
|
}, [isOpen])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='relative'>
|
||||||
|
<button
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
setIsOpen(!isOpen)
|
||||||
|
}}
|
||||||
|
aria-haspopup='listbox'
|
||||||
|
aria-expanded={isOpen}
|
||||||
|
aria-controls='language-menu'
|
||||||
|
className='flex cursor-pointer items-center gap-1.5 rounded-xl px-3 py-2 font-normal text-[0.9375rem] text-foreground/60 leading-[1.4] transition-colors hover:bg-foreground/8 hover:text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring'
|
||||||
|
style={{
|
||||||
|
fontFamily:
|
||||||
|
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span>{languages[currentLang as keyof typeof languages]?.name}</span>
|
||||||
|
<ChevronRight className='h-3.5 w-3.5' />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{isOpen && (
|
||||||
|
<>
|
||||||
|
<div className='fixed inset-0 z-[1000]' aria-hidden onClick={() => setIsOpen(false)} />
|
||||||
|
<div
|
||||||
|
id='language-menu'
|
||||||
|
role='listbox'
|
||||||
|
className='absolute top-full right-0 z-[1001] mt-1 max-h-[75vh] w-56 overflow-auto rounded-xl border border-border/50 bg-white shadow-2xl md:w-44 md:bg-background/95 md:backdrop-blur-md dark:bg-neutral-950 md:dark:bg-background/95'
|
||||||
|
>
|
||||||
|
{Object.entries(languages).map(([code, lang]) => (
|
||||||
|
<button
|
||||||
|
key={code}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
handleLanguageChange(code)
|
||||||
|
}}
|
||||||
|
role='option'
|
||||||
|
aria-selected={currentLang === code}
|
||||||
|
className={`flex w-full cursor-pointer items-center gap-3 px-3 py-3 text-base transition-colors first:rounded-t-xl last:rounded-b-xl hover:bg-muted/80 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring md:gap-2 md:px-2.5 md:py-2 md:text-sm ${
|
||||||
|
currentLang === code ? 'bg-muted/60 font-medium text-primary' : 'text-foreground'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<span className='text-base md:text-sm'>{lang.flag}</span>
|
||||||
|
<span className='leading-none'>{lang.name}</span>
|
||||||
|
{currentLang === code && (
|
||||||
|
<Check className='ml-auto h-4 w-4 text-primary md:h-3.5 md:w-3.5' />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
74
apps/docs/components/ui/lightbox.tsx
Normal file
74
apps/docs/components/ui/lightbox.tsx
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useEffect, useRef } from 'react'
|
||||||
|
import { getAssetUrl } from '@/lib/utils'
|
||||||
|
|
||||||
|
interface LightboxProps {
|
||||||
|
isOpen: boolean
|
||||||
|
onClose: () => void
|
||||||
|
src: string
|
||||||
|
alt: string
|
||||||
|
type: 'image' | 'video'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Lightbox({ isOpen, onClose, src, alt, type }: LightboxProps) {
|
||||||
|
const overlayRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleKeyDown = (event: KeyboardEvent) => {
|
||||||
|
if (event.key === 'Escape') {
|
||||||
|
onClose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleClickOutside = (event: MouseEvent) => {
|
||||||
|
if (overlayRef.current && event.target === overlayRef.current) {
|
||||||
|
onClose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isOpen) {
|
||||||
|
document.addEventListener('keydown', handleKeyDown)
|
||||||
|
document.addEventListener('click', handleClickOutside)
|
||||||
|
document.body.style.overflow = 'hidden'
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('keydown', handleKeyDown)
|
||||||
|
document.removeEventListener('click', handleClickOutside)
|
||||||
|
document.body.style.overflow = 'unset'
|
||||||
|
}
|
||||||
|
}, [isOpen, onClose])
|
||||||
|
|
||||||
|
if (!isOpen) return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={overlayRef}
|
||||||
|
className='fixed inset-0 z-50 flex items-center justify-center bg-black/80 p-12 backdrop-blur-sm'
|
||||||
|
role='dialog'
|
||||||
|
aria-modal='true'
|
||||||
|
aria-label='Media viewer'
|
||||||
|
>
|
||||||
|
<div className='relative max-h-full max-w-full overflow-hidden rounded-xl shadow-2xl'>
|
||||||
|
{type === 'image' ? (
|
||||||
|
<img
|
||||||
|
src={src}
|
||||||
|
alt={alt}
|
||||||
|
className='max-h-[calc(100vh-6rem)] max-w-[calc(100vw-6rem)] rounded-xl object-contain'
|
||||||
|
loading='lazy'
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<video
|
||||||
|
src={getAssetUrl(src)}
|
||||||
|
autoPlay
|
||||||
|
loop
|
||||||
|
muted
|
||||||
|
playsInline
|
||||||
|
className='max-h-[calc(100vh-6rem)] max-w-[calc(100vw-6rem)] rounded-xl outline-none focus:outline-none'
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
38
apps/docs/components/ui/search-trigger.tsx
Normal file
38
apps/docs/components/ui/search-trigger.tsx
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { Search } from 'lucide-react'
|
||||||
|
|
||||||
|
export function SearchTrigger() {
|
||||||
|
const handleClick = () => {
|
||||||
|
const event = new KeyboardEvent('keydown', {
|
||||||
|
key: 'k',
|
||||||
|
metaKey: true,
|
||||||
|
bubbles: true,
|
||||||
|
})
|
||||||
|
document.dispatchEvent(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type='button'
|
||||||
|
className='flex h-10 w-[460px] cursor-pointer items-center gap-2 rounded-xl border border-border/50 px-3 py-2 text-sm backdrop-blur-xl transition-colors hover:border-border'
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'hsla(0, 0%, 5%, 0.85)',
|
||||||
|
backdropFilter: 'blur(33px) saturate(180%)',
|
||||||
|
WebkitBackdropFilter: 'blur(33px) saturate(180%)',
|
||||||
|
color: 'rgba(255, 255, 255, 0.6)',
|
||||||
|
}}
|
||||||
|
onClick={handleClick}
|
||||||
|
>
|
||||||
|
<Search className='h-4 w-4' />
|
||||||
|
<span>Search...</span>
|
||||||
|
<kbd
|
||||||
|
className='ml-auto flex items-center gap-0.5 font-medium'
|
||||||
|
style={{ color: 'rgba(255, 255, 255, 0.6)' }}
|
||||||
|
>
|
||||||
|
<span style={{ fontSize: '15px', lineHeight: '1' }}>⌘</span>
|
||||||
|
<span style={{ fontSize: '13px', lineHeight: '1' }}>K</span>
|
||||||
|
</kbd>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
'use client'
|
|
||||||
|
|
||||||
import { useEffect, useState } from 'react'
|
|
||||||
import Image from 'next/image'
|
|
||||||
import { useTheme } from 'next-themes'
|
|
||||||
|
|
||||||
interface ThemeImageProps {
|
|
||||||
lightSrc: string
|
|
||||||
darkSrc: string
|
|
||||||
alt: string
|
|
||||||
width?: number
|
|
||||||
height?: number
|
|
||||||
className?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ThemeImage({
|
|
||||||
lightSrc,
|
|
||||||
darkSrc,
|
|
||||||
alt,
|
|
||||||
width = 600,
|
|
||||||
height = 400,
|
|
||||||
className = 'rounded-lg border border-border my-6',
|
|
||||||
}: ThemeImageProps) {
|
|
||||||
const { resolvedTheme } = useTheme()
|
|
||||||
const [imageSrc, setImageSrc] = useState(lightSrc)
|
|
||||||
const [mounted, setMounted] = useState(false)
|
|
||||||
|
|
||||||
// Wait until component is mounted to avoid hydration mismatch
|
|
||||||
useEffect(() => {
|
|
||||||
setMounted(true)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (mounted) {
|
|
||||||
setImageSrc(resolvedTheme === 'dark' ? darkSrc : lightSrc)
|
|
||||||
}
|
|
||||||
}, [resolvedTheme, lightSrc, darkSrc, mounted])
|
|
||||||
|
|
||||||
if (!mounted) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='flex justify-center'>
|
|
||||||
<Image src={imageSrc} alt={alt} width={width} height={height} className={className} />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
32
apps/docs/components/ui/theme-toggle.tsx
Normal file
32
apps/docs/components/ui/theme-toggle.tsx
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { Moon, Sun } from 'lucide-react'
|
||||||
|
import { useTheme } from 'next-themes'
|
||||||
|
|
||||||
|
export function ThemeToggle() {
|
||||||
|
const { theme, setTheme } = useTheme()
|
||||||
|
const [mounted, setMounted] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setMounted(true)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
if (!mounted) {
|
||||||
|
return (
|
||||||
|
<button className='flex cursor-pointer items-center justify-center rounded-md p-1 text-muted-foreground'>
|
||||||
|
<Moon className='h-4 w-4' />
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
|
||||||
|
className='flex cursor-pointer items-center justify-center rounded-md p-1 text-muted-foreground transition-colors hover:text-foreground'
|
||||||
|
aria-label='Toggle theme'
|
||||||
|
>
|
||||||
|
{theme === 'dark' ? <Moon className='h-4 w-4' /> : <Sun className='h-4 w-4' />}
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,4 +1,8 @@
|
|||||||
import { getVideoUrl } from '@/lib/utils'
|
'use client'
|
||||||
|
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { getAssetUrl } from '@/lib/utils'
|
||||||
|
import { Lightbox } from './lightbox'
|
||||||
|
|
||||||
interface VideoProps {
|
interface VideoProps {
|
||||||
src: string
|
src: string
|
||||||
@@ -7,24 +11,47 @@ interface VideoProps {
|
|||||||
loop?: boolean
|
loop?: boolean
|
||||||
muted?: boolean
|
muted?: boolean
|
||||||
playsInline?: boolean
|
playsInline?: boolean
|
||||||
|
enableLightbox?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Video({
|
export function Video({
|
||||||
src,
|
src,
|
||||||
className = 'w-full -mb-2 rounded-lg',
|
className = 'w-full rounded-xl border border-border shadow-sm overflow-hidden outline-none focus:outline-none',
|
||||||
autoPlay = true,
|
autoPlay = true,
|
||||||
loop = true,
|
loop = true,
|
||||||
muted = true,
|
muted = true,
|
||||||
playsInline = true,
|
playsInline = true,
|
||||||
|
enableLightbox = true,
|
||||||
}: VideoProps) {
|
}: VideoProps) {
|
||||||
|
const [isLightboxOpen, setIsLightboxOpen] = useState(false)
|
||||||
|
|
||||||
|
const handleVideoClick = () => {
|
||||||
|
if (enableLightbox) {
|
||||||
|
setIsLightboxOpen(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<video
|
<>
|
||||||
autoPlay={autoPlay}
|
<video
|
||||||
loop={loop}
|
autoPlay={autoPlay}
|
||||||
muted={muted}
|
loop={loop}
|
||||||
playsInline={playsInline}
|
muted={muted}
|
||||||
className={className}
|
playsInline={playsInline}
|
||||||
src={getVideoUrl(src)}
|
className={`${className} ${enableLightbox ? 'cursor-pointer transition-opacity hover:opacity-90' : ''}`}
|
||||||
/>
|
src={getAssetUrl(src)}
|
||||||
|
onClick={handleVideoClick}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{enableLightbox && (
|
||||||
|
<Lightbox
|
||||||
|
isOpen={isLightboxOpen}
|
||||||
|
onClose={() => setIsLightboxOpen(false)}
|
||||||
|
src={src}
|
||||||
|
alt={`Video: ${src}`}
|
||||||
|
type='video'
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,303 +0,0 @@
|
|||||||
---
|
|
||||||
title: Agent
|
|
||||||
description: Create powerful AI agents using any LLM provider
|
|
||||||
---
|
|
||||||
|
|
||||||
import { Callout } from 'fumadocs-ui/components/callout'
|
|
||||||
import { Step, Steps } from 'fumadocs-ui/components/steps'
|
|
||||||
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
|
|
||||||
import { ThemeImage } from '@/components/ui/theme-image'
|
|
||||||
|
|
||||||
The Agent block serves as the interface between your workflow and Large Language Models (LLMs). It executes inference requests against various AI providers, processes natural language inputs according to defined instructions, and generates structured or unstructured outputs for downstream consumption.
|
|
||||||
|
|
||||||
<ThemeImage
|
|
||||||
lightSrc="/static/light/agent-light.png"
|
|
||||||
darkSrc="/static/dark/agent-dark.png"
|
|
||||||
alt="Agent Block Configuration"
|
|
||||||
width={350}
|
|
||||||
height={175}
|
|
||||||
/>
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
The Agent block enables you to:
|
|
||||||
|
|
||||||
<Steps>
|
|
||||||
<Step>
|
|
||||||
<strong>Process natural language</strong>: Analyze user input and generate contextual responses
|
|
||||||
</Step>
|
|
||||||
<Step>
|
|
||||||
<strong>Execute AI-powered tasks</strong>: Perform content analysis, generation, and decision-making
|
|
||||||
</Step>
|
|
||||||
<Step>
|
|
||||||
<strong>Call external tools</strong>: Access APIs, databases, and services during processing
|
|
||||||
</Step>
|
|
||||||
<Step>
|
|
||||||
<strong>Generate structured output</strong>: Return JSON data that matches your schema requirements
|
|
||||||
</Step>
|
|
||||||
</Steps>
|
|
||||||
|
|
||||||
## Configuration Options
|
|
||||||
|
|
||||||
### System Prompt
|
|
||||||
|
|
||||||
The system prompt establishes the agent's operational parameters and behavioral constraints. This configuration defines the agent's role, response methodology, and processing boundaries for all incoming requests.
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
You are a helpful assistant that specializes in financial analysis.
|
|
||||||
Always provide clear explanations and cite sources when possible.
|
|
||||||
When responding to questions about investments, include risk disclaimers.
|
|
||||||
```
|
|
||||||
|
|
||||||
### User Prompt
|
|
||||||
|
|
||||||
The user prompt represents the primary input data for inference processing. This parameter accepts natural language text or structured data that the agent will analyze and respond to. Input sources include:
|
|
||||||
|
|
||||||
- **Static Configuration**: Direct text input specified in the block configuration
|
|
||||||
- **Dynamic Input**: Data passed from upstream blocks through connection interfaces
|
|
||||||
- **Runtime Generation**: Programmatically generated content during workflow execution
|
|
||||||
|
|
||||||
### Model Selection
|
|
||||||
|
|
||||||
The Agent block supports multiple LLM providers through a unified inference interface. Available models include:
|
|
||||||
|
|
||||||
**OpenAI Models**: GPT-5, GPT-4o, o1, o3, o4-mini, gpt-4.1 (API-based inference)
|
|
||||||
**Anthropic Models**: Claude 3.7 Sonnet (API-based inference)
|
|
||||||
**Google Models**: Gemini 2.5 Pro, Gemini 2.0 Flash (API-based inference)
|
|
||||||
**Alternative Providers**: Groq, Cerebras, xAI, DeepSeek (API-based inference)
|
|
||||||
**Local Deployment**: Ollama-compatible models (self-hosted inference)
|
|
||||||
|
|
||||||
<div className="mx-auto w-3/5 overflow-hidden rounded-lg">
|
|
||||||
<video autoPlay loop muted playsInline className="w-full -mb-2 rounded-lg" src="/models.mp4"></video>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
### Temperature
|
|
||||||
|
|
||||||
Control the creativity and randomness of responses:
|
|
||||||
|
|
||||||
<Tabs items={['Low (0-0.3)', 'Medium (0.3-0.7)', 'High (0.7-2.0)']}>
|
|
||||||
<Tab>
|
|
||||||
More deterministic, focused responses. Best for factual tasks, customer support, and
|
|
||||||
situations where accuracy is critical.
|
|
||||||
</Tab>
|
|
||||||
<Tab>
|
|
||||||
Balanced creativity and focus. Suitable for general purpose applications that require both
|
|
||||||
accuracy and some creativity.
|
|
||||||
</Tab>
|
|
||||||
<Tab>
|
|
||||||
More creative, varied responses. Ideal for creative writing, brainstorming, and generating
|
|
||||||
diverse ideas.
|
|
||||||
</Tab>
|
|
||||||
</Tabs>
|
|
||||||
|
|
||||||
<p className="mt-4 text-sm text-gray-600 dark:text-gray-400">
|
|
||||||
The temperature range (0-1 or 0-2) varies depending on the selected model.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
### API Key
|
|
||||||
|
|
||||||
Your API key for the selected LLM provider. This is securely stored and used for authentication.
|
|
||||||
|
|
||||||
### Tools
|
|
||||||
|
|
||||||
Tools extend the agent's capabilities through external API integrations and service connections. The tool system enables function calling, allowing the agent to execute operations beyond text generation.
|
|
||||||
|
|
||||||
**Tool Integration Process**:
|
|
||||||
1. Access the Tools configuration section within the Agent block
|
|
||||||
2. Select from 60+ pre-built integrations or define custom functions
|
|
||||||
3. Configure authentication parameters and operational constraints
|
|
||||||
|
|
||||||
<div className="mx-auto w-3/5 overflow-hidden rounded-lg">
|
|
||||||
<video autoPlay loop muted playsInline className="w-full -mb-2 rounded-lg" src="/tools.mp4"></video>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
**Available Tool Categories**:
|
|
||||||
- **Communication**: Gmail, Slack, Telegram, WhatsApp, Microsoft Teams
|
|
||||||
- **Data Sources**: Notion, Google Sheets, Airtable, Supabase, Pinecone
|
|
||||||
- **Web Services**: Firecrawl, Google Search, Exa AI, browser automation
|
|
||||||
- **Development**: GitHub, Jira, Linear repository and issue management
|
|
||||||
- **AI Services**: OpenAI, Perplexity, Hugging Face, ElevenLabs
|
|
||||||
|
|
||||||
**Tool Execution Control**:
|
|
||||||
- **Auto**: Model determines tool invocation based on context and necessity
|
|
||||||
- **Required**: Tool must be called during every inference request
|
|
||||||
- **None**: Tool definition available but excluded from model context
|
|
||||||
|
|
||||||
<div className="mx-auto w-3/5 overflow-hidden rounded-lg">
|
|
||||||
<video autoPlay loop muted playsInline className="w-full -mb-2 rounded-lg" src="/granular-tool-control.mp4"></video>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
### Response Format
|
|
||||||
|
|
||||||
The Response Format parameter enforces structured output generation through JSON Schema validation. This ensures consistent, machine-readable responses that conform to predefined data structures:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"name": "user_analysis",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"sentiment": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": ["positive", "negative", "neutral"]
|
|
||||||
},
|
|
||||||
"confidence": {
|
|
||||||
"type": "number",
|
|
||||||
"minimum": 0,
|
|
||||||
"maximum": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": ["sentiment", "confidence"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This configuration constrains the model's output to comply with the specified schema, preventing free-form text responses and ensuring structured data generation.
|
|
||||||
|
|
||||||
### Accessing Results
|
|
||||||
|
|
||||||
After an agent completes, you can access its outputs:
|
|
||||||
|
|
||||||
- **`<agent.content>`**: The agent's response text or structured data
|
|
||||||
- **`<agent.tokens>`**: Token usage statistics (prompt, completion, total)
|
|
||||||
- **`<agent.tool_calls>`**: Details of any tools the agent used during execution
|
|
||||||
- **`<agent.cost>`**: Estimated cost of the API call (if available)
|
|
||||||
|
|
||||||
## Advanced Features
|
|
||||||
|
|
||||||
### Memory Integration
|
|
||||||
|
|
||||||
Agents can maintain context across interactions using the memory system:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// In a Function block before the agent
|
|
||||||
const memory = {
|
|
||||||
conversation_history: previousMessages,
|
|
||||||
user_preferences: userProfile,
|
|
||||||
session_data: currentSession
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### Structured Output Validation
|
|
||||||
|
|
||||||
Use JSON Schema to ensure consistent, machine-readable responses:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"analysis": {"type": "string"},
|
|
||||||
"confidence": {"type": "number", "minimum": 0, "maximum": 1},
|
|
||||||
"categories": {"type": "array", "items": {"type": "string"}}
|
|
||||||
},
|
|
||||||
"required": ["analysis", "confidence"]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Error Handling
|
|
||||||
|
|
||||||
Agents automatically handle common errors:
|
|
||||||
- API rate limits with exponential backoff
|
|
||||||
- Invalid tool calls with retry logic
|
|
||||||
- Network failures with connection recovery
|
|
||||||
- Schema validation errors with fallback responses
|
|
||||||
|
|
||||||
## Inputs and Outputs
|
|
||||||
|
|
||||||
<Tabs items={['Configuration', 'Variables', 'Results']}>
|
|
||||||
<Tab>
|
|
||||||
<ul className="list-disc space-y-2 pl-6">
|
|
||||||
<li>
|
|
||||||
<strong>System Prompt</strong>: Instructions defining agent behavior and role
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>User Prompt</strong>: Input text or data to process
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Model</strong>: AI model selection (OpenAI, Anthropic, Google, etc.)
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Temperature</strong>: Response randomness control (0-2)
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Tools</strong>: Array of available tools for function calling
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Response Format</strong>: JSON Schema for structured output
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</Tab>
|
|
||||||
<Tab>
|
|
||||||
<ul className="list-disc space-y-2 pl-6">
|
|
||||||
<li>
|
|
||||||
<strong>agent.content</strong>: Agent's response text or structured data
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>agent.tokens</strong>: Token usage statistics object
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>agent.tool_calls</strong>: Array of tool execution details
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>agent.cost</strong>: Estimated API call cost (if available)
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</Tab>
|
|
||||||
<Tab>
|
|
||||||
<ul className="list-disc space-y-2 pl-6">
|
|
||||||
<li>
|
|
||||||
<strong>Content</strong>: Primary response output from the agent
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Metadata</strong>: Usage statistics and execution details
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Access</strong>: Available in blocks after the agent
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</Tab>
|
|
||||||
</Tabs>
|
|
||||||
|
|
||||||
## Example Use Cases
|
|
||||||
|
|
||||||
### Customer Support Automation
|
|
||||||
|
|
||||||
<div className="mb-4 rounded-md border p-4">
|
|
||||||
<h4 className="font-medium">Scenario: Handle customer inquiries with database access</h4>
|
|
||||||
<ol className="list-decimal pl-5 text-sm">
|
|
||||||
<li>User submits support ticket via API block</li>
|
|
||||||
<li>Agent processes inquiry with product database tools</li>
|
|
||||||
<li>Agent generates response and creates follow-up ticket</li>
|
|
||||||
<li>Response block sends reply to customer</li>
|
|
||||||
</ol>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
### Multi-Model Content Analysis
|
|
||||||
|
|
||||||
<div className="mb-4 rounded-md border p-4">
|
|
||||||
<h4 className="font-medium">Scenario: Analyze content with different AI models</h4>
|
|
||||||
<ol className="list-decimal pl-5 text-sm">
|
|
||||||
<li>Function block processes uploaded document</li>
|
|
||||||
<li>Agent with GPT-4o performs technical analysis</li>
|
|
||||||
<li>Agent with Claude analyzes sentiment and tone</li>
|
|
||||||
<li>Function block combines results for final report</li>
|
|
||||||
</ol>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
### Tool-Powered Research Assistant
|
|
||||||
|
|
||||||
<div className="mb-4 rounded-md border p-4">
|
|
||||||
<h4 className="font-medium">Scenario: Research assistant with web search and document access</h4>
|
|
||||||
<ol className="list-decimal pl-5 text-sm">
|
|
||||||
<li>User query received via input</li>
|
|
||||||
<li>Agent searches web using Google Search tool</li>
|
|
||||||
<li>Agent accesses Notion database for internal docs</li>
|
|
||||||
<li>Agent compiles comprehensive research report</li>
|
|
||||||
</ol>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
## Best Practices
|
|
||||||
|
|
||||||
- **Be specific in system prompts**: Clearly define the agent's role, tone, and limitations. The more specific your instructions are, the better the agent will be able to fulfill its intended purpose.
|
|
||||||
- **Choose the right temperature setting**: Use lower temperature settings (0-0.3) when accuracy is important, or increase temperature (0.7-2.0) for more creative or varied responses
|
|
||||||
- **Leverage tools effectively**: Integrate tools that complement the agent's purpose and enhance its capabilities. Be selective about which tools you provide to avoid overwhelming the agent. For tasks with little overlap, use another Agent block for the best results.
|
|
||||||
@@ -1,234 +0,0 @@
|
|||||||
---
|
|
||||||
title: API
|
|
||||||
description: Connect to external services through API endpoints
|
|
||||||
---
|
|
||||||
|
|
||||||
import { Callout } from 'fumadocs-ui/components/callout'
|
|
||||||
import { Step, Steps } from 'fumadocs-ui/components/steps'
|
|
||||||
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
|
|
||||||
import { ThemeImage } from '@/components/ui/theme-image'
|
|
||||||
|
|
||||||
The API block enables you to connect your workflow to external services through HTTP requests. It supports various methods like GET, POST, PUT, DELETE, and PATCH, allowing you to interact with virtually any API endpoint.
|
|
||||||
|
|
||||||
<ThemeImage
|
|
||||||
lightSrc="/static/light/api-light.png"
|
|
||||||
darkSrc="/static/dark/api-dark.png"
|
|
||||||
alt="API Block"
|
|
||||||
width={350}
|
|
||||||
height={175}
|
|
||||||
/>
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
The API block enables you to:
|
|
||||||
|
|
||||||
<Steps>
|
|
||||||
<Step>
|
|
||||||
<strong>Connect to external services</strong>: Make HTTP requests to REST APIs and web services
|
|
||||||
</Step>
|
|
||||||
<Step>
|
|
||||||
<strong>Send and receive data</strong>: Process responses and transform data from external sources
|
|
||||||
</Step>
|
|
||||||
<Step>
|
|
||||||
<strong>Integrate third-party platforms</strong>: Connect with services like Stripe, Slack, or custom APIs
|
|
||||||
</Step>
|
|
||||||
<Step>
|
|
||||||
<strong>Handle authentication</strong>: Support various auth methods including Bearer tokens and API keys
|
|
||||||
</Step>
|
|
||||||
</Steps>
|
|
||||||
|
|
||||||
## Configuration Options
|
|
||||||
|
|
||||||
### URL
|
|
||||||
|
|
||||||
The endpoint URL for the API request. This can be:
|
|
||||||
|
|
||||||
- A static URL entered directly in the block
|
|
||||||
- A dynamic URL connected from another block's output
|
|
||||||
- A URL with path parameters
|
|
||||||
|
|
||||||
### Method
|
|
||||||
|
|
||||||
Select the HTTP method for your request:
|
|
||||||
|
|
||||||
- **GET**: Retrieve data from the server
|
|
||||||
- **POST**: Send data to the server to create a resource
|
|
||||||
- **PUT**: Update an existing resource on the server
|
|
||||||
- **DELETE**: Remove a resource from the server
|
|
||||||
- **PATCH**: Partially update an existing resource
|
|
||||||
|
|
||||||
### Query Parameters
|
|
||||||
|
|
||||||
Define key-value pairs that will be appended to the URL as query parameters. For example:
|
|
||||||
|
|
||||||
```
|
|
||||||
Key: apiKey
|
|
||||||
Value: your_api_key_here
|
|
||||||
|
|
||||||
Key: limit
|
|
||||||
Value: 10
|
|
||||||
```
|
|
||||||
|
|
||||||
These would be added to the URL as `?apiKey=your_api_key_here&limit=10`.
|
|
||||||
|
|
||||||
### Headers
|
|
||||||
|
|
||||||
Configure HTTP headers for your request. Common headers include:
|
|
||||||
|
|
||||||
```
|
|
||||||
Key: Content-Type
|
|
||||||
Value: application/json
|
|
||||||
|
|
||||||
Key: Authorization
|
|
||||||
Value: Bearer your_token_here
|
|
||||||
```
|
|
||||||
|
|
||||||
### Request Body
|
|
||||||
|
|
||||||
For methods that support a request body (POST, PUT, PATCH), you can define the data to send. The body can be:
|
|
||||||
|
|
||||||
- JSON data entered directly in the block
|
|
||||||
- Data connected from another block's output
|
|
||||||
- Dynamically generated during workflow execution
|
|
||||||
|
|
||||||
### Accessing Results
|
|
||||||
|
|
||||||
After an API request completes, you can access its outputs:
|
|
||||||
|
|
||||||
- **`<api.data>`**: The response body data from the API
|
|
||||||
- **`<api.status>`**: HTTP status code (200, 404, 500, etc.)
|
|
||||||
- **`<api.headers>`**: Response headers from the server
|
|
||||||
- **`<api.error>`**: Error details if the request failed
|
|
||||||
|
|
||||||
## Advanced Features
|
|
||||||
|
|
||||||
### Dynamic URL Construction
|
|
||||||
|
|
||||||
Build URLs dynamically using variables from previous blocks:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// In a Function block before the API
|
|
||||||
const userId = <start.userId>;
|
|
||||||
const apiUrl = `https://api.example.com/users/${userId}/profile`;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Request Retries
|
|
||||||
|
|
||||||
The API block automatically handles:
|
|
||||||
- Network timeouts with exponential backoff
|
|
||||||
- Rate limit responses (429 status codes)
|
|
||||||
- Server errors (5xx status codes) with retry logic
|
|
||||||
- Connection failures with reconnection attempts
|
|
||||||
|
|
||||||
### Response Validation
|
|
||||||
|
|
||||||
Validate API responses before processing:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// In a Function block after the API
|
|
||||||
if (<api.status> === 200) {
|
|
||||||
const data = <api.data>;
|
|
||||||
// Process successful response
|
|
||||||
} else {
|
|
||||||
// Handle error response
|
|
||||||
console.error(`API Error: ${<api.status>}`);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Inputs and Outputs
|
|
||||||
|
|
||||||
<Tabs items={['Configuration', 'Variables', 'Results']}>
|
|
||||||
<Tab>
|
|
||||||
<ul className="list-disc space-y-2 pl-6">
|
|
||||||
<li>
|
|
||||||
<strong>URL</strong>: The endpoint to send the request to
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Method</strong>: HTTP method (GET, POST, PUT, DELETE, PATCH)
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Query Parameters</strong>: Key-value pairs for URL parameters
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Headers</strong>: HTTP headers for authentication and content type
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Body</strong>: Request payload for POST/PUT/PATCH methods
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</Tab>
|
|
||||||
<Tab>
|
|
||||||
<ul className="list-disc space-y-2 pl-6">
|
|
||||||
<li>
|
|
||||||
<strong>api.data</strong>: Response body data from the API call
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>api.status</strong>: HTTP status code returned by server
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>api.headers</strong>: Response headers from the server
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>api.error</strong>: Error details if request failed
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</Tab>
|
|
||||||
<Tab>
|
|
||||||
<ul className="list-disc space-y-2 pl-6">
|
|
||||||
<li>
|
|
||||||
<strong>Response Data</strong>: Primary API response content
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Status Information</strong>: HTTP status and error details
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Access</strong>: Available in blocks after the API call
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</Tab>
|
|
||||||
</Tabs>
|
|
||||||
|
|
||||||
## Example Use Cases
|
|
||||||
|
|
||||||
### Fetch User Profile Data
|
|
||||||
|
|
||||||
<div className="mb-4 rounded-md border p-4">
|
|
||||||
<h4 className="font-medium">Scenario: Retrieve user information from external service</h4>
|
|
||||||
<ol className="list-decimal pl-5 text-sm">
|
|
||||||
<li>Function block constructs user ID from input</li>
|
|
||||||
<li>API block calls GET /users/{id} endpoint</li>
|
|
||||||
<li>Function block processes and formats user data</li>
|
|
||||||
<li>Response block returns formatted profile</li>
|
|
||||||
</ol>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
### Create Support Ticket
|
|
||||||
|
|
||||||
<div className="mb-4 rounded-md border p-4">
|
|
||||||
<h4 className="font-medium">Scenario: Submit support request to ticketing system</h4>
|
|
||||||
<ol className="list-decimal pl-5 text-sm">
|
|
||||||
<li>Agent analyzes user issue and generates ticket data</li>
|
|
||||||
<li>API block POSTs ticket to support system</li>
|
|
||||||
<li>Condition block checks if ticket was created successfully</li>
|
|
||||||
<li>Response block confirms ticket creation with ID</li>
|
|
||||||
</ol>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
### Payment Processing
|
|
||||||
|
|
||||||
<div className="mb-4 rounded-md border p-4">
|
|
||||||
<h4 className="font-medium">Scenario: Process payment through Stripe API</h4>
|
|
||||||
<ol className="list-decimal pl-5 text-sm">
|
|
||||||
<li>Function block validates payment data</li>
|
|
||||||
<li>API block creates payment intent via Stripe</li>
|
|
||||||
<li>Condition block handles payment success/failure</li>
|
|
||||||
<li>Function block updates order status in database</li>
|
|
||||||
</ol>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
## Best Practices
|
|
||||||
|
|
||||||
- **Use environment variables for sensitive data**: Don't hardcode API keys or credentials
|
|
||||||
- **Handle errors gracefully**: Connect error handling logic for failed requests
|
|
||||||
- **Validate responses**: Check status codes and response formats before processing data
|
|
||||||
- **Respect rate limits**: Be mindful of API rate limits and implement appropriate throttling
|
|
||||||
@@ -1,236 +0,0 @@
|
|||||||
---
|
|
||||||
title: Condition
|
|
||||||
description: Create conditional logic and branching in your workflows
|
|
||||||
---
|
|
||||||
|
|
||||||
import { Callout } from 'fumadocs-ui/components/callout'
|
|
||||||
import { Step, Steps } from 'fumadocs-ui/components/steps'
|
|
||||||
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
|
|
||||||
import { Accordion, Accordions } from 'fumadocs-ui/components/accordion'
|
|
||||||
import { ThemeImage } from '@/components/ui/theme-image'
|
|
||||||
|
|
||||||
The Condition block allows you to branch your workflow execution path based on boolean expressions. It evaluates conditions and routes the workflow accordingly, enabling you to create dynamic, responsive workflows with different execution paths.
|
|
||||||
|
|
||||||
<ThemeImage
|
|
||||||
lightSrc="/static/light/condition-light.png"
|
|
||||||
darkSrc="/static/dark/condition-dark.png"
|
|
||||||
alt="Condition Block"
|
|
||||||
width={350}
|
|
||||||
height={175}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Callout>
|
|
||||||
Condition blocks enable deterministic decision-making without requiring an LLM, making them ideal
|
|
||||||
for straightforward branching logic.
|
|
||||||
</Callout>
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
The Condition block enables you to:
|
|
||||||
|
|
||||||
<Steps>
|
|
||||||
<Step>
|
|
||||||
<strong>Create branching logic</strong>: Route workflows based on boolean expressions
|
|
||||||
</Step>
|
|
||||||
<Step>
|
|
||||||
<strong>Make data-driven decisions</strong>: Evaluate conditions using previous block outputs
|
|
||||||
</Step>
|
|
||||||
<Step>
|
|
||||||
<strong>Handle multiple scenarios</strong>: Define multiple conditions with different paths
|
|
||||||
</Step>
|
|
||||||
<Step>
|
|
||||||
<strong>Provide deterministic routing</strong>: Make decisions without requiring an LLM
|
|
||||||
</Step>
|
|
||||||
</Steps>
|
|
||||||
|
|
||||||
## How It Works
|
|
||||||
|
|
||||||
The Condition block operates through a sequential evaluation process:
|
|
||||||
|
|
||||||
1. **Evaluate Expression** - Processes the JavaScript/TypeScript boolean expression using current workflow data
|
|
||||||
2. **Determine Result** - Returns true or false based on the expression evaluation
|
|
||||||
3. **Route Workflow** - Directs execution to the appropriate destination block based on the result
|
|
||||||
4. **Provide Context** - Generates metadata about the decision for debugging and monitoring
|
|
||||||
|
|
||||||
## Configuration Options
|
|
||||||
|
|
||||||
### Conditions
|
|
||||||
|
|
||||||
Define one or more conditions that will be evaluated. Each condition includes:
|
|
||||||
|
|
||||||
- **Expression**: A JavaScript/TypeScript expression that evaluates to true or false
|
|
||||||
- **Path**: The destination block to route to if the condition is true
|
|
||||||
- **Description**: Optional explanation of what the condition checks
|
|
||||||
|
|
||||||
You can create multiple conditions that are evaluated in order, with the first matching condition determining the execution path.
|
|
||||||
|
|
||||||
### Condition Expression Format
|
|
||||||
|
|
||||||
Conditions use JavaScript syntax and can reference input values from previous blocks.
|
|
||||||
|
|
||||||
<Tabs items={['Score Threshold', 'Text Analysis', 'Multiple Conditions']}>
|
|
||||||
<Tab>
|
|
||||||
```javascript
|
|
||||||
// Check if a score is above a threshold
|
|
||||||
<agent.score> > 75
|
|
||||||
```
|
|
||||||
</Tab>
|
|
||||||
<Tab>
|
|
||||||
```javascript
|
|
||||||
// Check if a text contains specific keywords
|
|
||||||
<agent.text>.includes('urgent') || <agent.text>.includes('emergency')
|
|
||||||
```
|
|
||||||
</Tab>
|
|
||||||
<Tab>
|
|
||||||
```javascript
|
|
||||||
// Check multiple conditions
|
|
||||||
<agent.age> >= 18 && <agent.country> === 'US'
|
|
||||||
```
|
|
||||||
</Tab>
|
|
||||||
</Tabs>
|
|
||||||
|
|
||||||
|
|
||||||
### Accessing Results
|
|
||||||
|
|
||||||
After a condition evaluates, you can access its outputs:
|
|
||||||
|
|
||||||
- **`<condition.result>`**: Boolean result of the condition evaluation
|
|
||||||
- **`<condition.matched_condition>`**: ID of the condition that was matched
|
|
||||||
- **`<condition.content>`**: Description of the evaluation result
|
|
||||||
- **`<condition.path>`**: Details of the chosen routing destination
|
|
||||||
|
|
||||||
## Advanced Features
|
|
||||||
|
|
||||||
### Complex Expressions
|
|
||||||
|
|
||||||
Use JavaScript operators and functions in conditions:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// String operations
|
|
||||||
<user.email>.endsWith('@company.com')
|
|
||||||
|
|
||||||
// Array operations
|
|
||||||
<api.tags>.includes('urgent')
|
|
||||||
|
|
||||||
// Mathematical operations
|
|
||||||
<agent.confidence> * 100 > 85
|
|
||||||
|
|
||||||
// Date comparisons
|
|
||||||
new Date(<api.created_at>) > new Date('2024-01-01')
|
|
||||||
```
|
|
||||||
|
|
||||||
### Multiple Condition Evaluation
|
|
||||||
|
|
||||||
Conditions are evaluated in order until one matches:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Condition 1: Check for high priority
|
|
||||||
<ticket.priority> === 'high'
|
|
||||||
|
|
||||||
// Condition 2: Check for urgent keywords
|
|
||||||
<ticket.subject>.toLowerCase().includes('urgent')
|
|
||||||
|
|
||||||
// Condition 3: Default fallback
|
|
||||||
true
|
|
||||||
```
|
|
||||||
|
|
||||||
### Error Handling
|
|
||||||
|
|
||||||
Conditions automatically handle:
|
|
||||||
- Undefined or null values with safe evaluation
|
|
||||||
- Type mismatches with appropriate fallbacks
|
|
||||||
- Invalid expressions with error logging
|
|
||||||
- Missing variables with default values
|
|
||||||
|
|
||||||
## Inputs and Outputs
|
|
||||||
|
|
||||||
<Tabs items={['Configuration', 'Variables', 'Results']}>
|
|
||||||
<Tab>
|
|
||||||
<ul className="list-disc space-y-2 pl-6">
|
|
||||||
<li>
|
|
||||||
<strong>Conditions</strong>: Array of boolean expressions to evaluate
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Expressions</strong>: JavaScript/TypeScript conditions using block outputs
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Routing Paths</strong>: Destination blocks for each condition result
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</Tab>
|
|
||||||
<Tab>
|
|
||||||
<ul className="list-disc space-y-2 pl-6">
|
|
||||||
<li>
|
|
||||||
<strong>condition.result</strong>: Boolean result of condition evaluation
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>condition.matched_condition</strong>: ID of the matched condition
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>condition.content</strong>: Description of evaluation result
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>condition.path</strong>: Details of chosen routing destination
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</Tab>
|
|
||||||
<Tab>
|
|
||||||
<ul className="list-disc space-y-2 pl-6">
|
|
||||||
<li>
|
|
||||||
<strong>Boolean Result</strong>: Primary condition evaluation outcome
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Routing Information</strong>: Path selection and condition details
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Access</strong>: Available in blocks after the condition
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</Tab>
|
|
||||||
</Tabs>
|
|
||||||
|
|
||||||
## Example Use Cases
|
|
||||||
|
|
||||||
### Customer Support Routing
|
|
||||||
|
|
||||||
<div className="mb-4 rounded-md border p-4">
|
|
||||||
<h4 className="font-medium">Scenario: Route support tickets based on priority</h4>
|
|
||||||
<ol className="list-decimal pl-5 text-sm">
|
|
||||||
<li>API block fetches support ticket data</li>
|
|
||||||
<li>Condition checks if `<api.priority>` equals 'high'</li>
|
|
||||||
<li>High priority tickets → Agent with escalation tools</li>
|
|
||||||
<li>Normal priority tickets → Standard support agent</li>
|
|
||||||
</ol>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
### Content Moderation
|
|
||||||
|
|
||||||
<div className="mb-4 rounded-md border p-4">
|
|
||||||
<h4 className="font-medium">Scenario: Filter content based on analysis results</h4>
|
|
||||||
<ol className="list-decimal pl-5 text-sm">
|
|
||||||
<li>Agent analyzes user-generated content</li>
|
|
||||||
<li>Condition checks if `<agent.toxicity_score>` > 0.7</li>
|
|
||||||
<li>Toxic content → Moderation workflow</li>
|
|
||||||
<li>Clean content → Publishing workflow</li>
|
|
||||||
</ol>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
### User Onboarding Flow
|
|
||||||
|
|
||||||
<div className="mb-4 rounded-md border p-4">
|
|
||||||
<h4 className="font-medium">Scenario: Personalize onboarding based on user type</h4>
|
|
||||||
<ol className="list-decimal pl-5 text-sm">
|
|
||||||
<li>Function block processes user registration data</li>
|
|
||||||
<li>Condition checks if `<user.account_type>` === 'enterprise'</li>
|
|
||||||
<li>Enterprise users → Advanced setup workflow</li>
|
|
||||||
<li>Individual users → Simple onboarding workflow</li>
|
|
||||||
</ol>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
## Best Practices
|
|
||||||
|
|
||||||
- **Order conditions correctly**: Place more specific conditions before general ones to ensure specific logic takes precedence over fallbacks
|
|
||||||
- **Include a default condition**: Add a catch-all condition (`true`) as the last condition to handle unmatched cases and prevent workflow execution from getting stuck
|
|
||||||
- **Keep expressions simple**: Use clear, straightforward boolean expressions for better readability and easier debugging
|
|
||||||
- **Document your conditions**: Add descriptions to explain the purpose of each condition for better team collaboration and maintenance
|
|
||||||
- **Test edge cases**: Verify conditions handle boundary values correctly by testing with values at the edges of your condition ranges
|
|
||||||
@@ -1,131 +0,0 @@
|
|||||||
---
|
|
||||||
title: Evaluator
|
|
||||||
description: Assess content quality using customizable evaluation metrics
|
|
||||||
---
|
|
||||||
|
|
||||||
import { Callout } from 'fumadocs-ui/components/callout'
|
|
||||||
import { Step, Steps } from 'fumadocs-ui/components/steps'
|
|
||||||
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
|
|
||||||
import { ThemeImage } from '@/components/ui/theme-image'
|
|
||||||
import { Video } from '@/components/ui/video'
|
|
||||||
|
|
||||||
The Evaluator block uses AI to score and assess content quality based on metrics you define. Perfect for quality control, A/B testing, and ensuring your AI outputs meet specific standards.
|
|
||||||
|
|
||||||
<ThemeImage
|
|
||||||
lightSrc="/static/light/evaluator-light.png"
|
|
||||||
darkSrc="/static/dark/evaluator-dark.png"
|
|
||||||
alt="Evaluator Block Configuration"
|
|
||||||
width={350}
|
|
||||||
height={175}
|
|
||||||
/>
|
|
||||||
|
|
||||||
## What You Can Evaluate
|
|
||||||
|
|
||||||
**AI-Generated Content**: Score chatbot responses, generated articles, or marketing copy
|
|
||||||
**User Input**: Evaluate customer feedback, survey responses, or form submissions
|
|
||||||
**Content Quality**: Assess clarity, accuracy, relevance, and tone
|
|
||||||
**Performance Metrics**: Track improvements over time with consistent scoring
|
|
||||||
**A/B Testing**: Compare different approaches with objective metrics
|
|
||||||
|
|
||||||
## Configuration Options
|
|
||||||
|
|
||||||
### Evaluation Metrics
|
|
||||||
|
|
||||||
Define custom metrics to evaluate content against. Each metric includes:
|
|
||||||
|
|
||||||
- **Name**: A short identifier for the metric
|
|
||||||
- **Description**: A detailed explanation of what the metric measures
|
|
||||||
- **Range**: The numeric range for scoring (e.g., 1-5, 0-10)
|
|
||||||
|
|
||||||
Example metrics:
|
|
||||||
|
|
||||||
```
|
|
||||||
Accuracy (1-5): How factually accurate is the content?
|
|
||||||
Clarity (1-5): How clear and understandable is the content?
|
|
||||||
Relevance (1-5): How relevant is the content to the original query?
|
|
||||||
```
|
|
||||||
|
|
||||||
### Content
|
|
||||||
|
|
||||||
The content to be evaluated. This can be:
|
|
||||||
|
|
||||||
- Directly provided in the block configuration
|
|
||||||
- Connected from another block's output (typically an Agent block)
|
|
||||||
- Dynamically generated during workflow execution
|
|
||||||
|
|
||||||
### Model Selection
|
|
||||||
|
|
||||||
Choose an AI model to perform the evaluation:
|
|
||||||
|
|
||||||
**OpenAI**: GPT-4o, o1, o3, o4-mini, gpt-4.1
|
|
||||||
**Anthropic**: Claude 3.7 Sonnet
|
|
||||||
**Google**: Gemini 2.5 Pro, Gemini 2.0 Flash
|
|
||||||
**Other Providers**: Groq, Cerebras, xAI, DeepSeek
|
|
||||||
**Local Models**: Any model running on Ollama
|
|
||||||
|
|
||||||
<div className="w-full max-w-2xl mx-auto overflow-hidden rounded-lg">
|
|
||||||
<Video src="models.mp4" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
**Recommendation**: Use models with strong reasoning capabilities like GPT-4o or Claude 3.7 Sonnet for more accurate evaluations.
|
|
||||||
|
|
||||||
### API Key
|
|
||||||
|
|
||||||
Your API key for the selected LLM provider. This is securely stored and used for authentication.
|
|
||||||
|
|
||||||
## How It Works
|
|
||||||
|
|
||||||
1. The Evaluator block takes the provided content and your custom metrics
|
|
||||||
2. It generates a specialized prompt that instructs the LLM to evaluate the content
|
|
||||||
3. The prompt includes clear guidelines on how to score each metric
|
|
||||||
4. The LLM evaluates the content and returns numeric scores for each metric
|
|
||||||
5. The Evaluator block formats these scores as structured output for use in your workflow
|
|
||||||
|
|
||||||
## Inputs and Outputs
|
|
||||||
|
|
||||||
### Inputs
|
|
||||||
|
|
||||||
- **Content**: The text or structured data to evaluate
|
|
||||||
- **Metrics**: Custom evaluation criteria with scoring ranges
|
|
||||||
- **Model Settings**: LLM provider and parameters
|
|
||||||
|
|
||||||
### Outputs
|
|
||||||
|
|
||||||
- **Content**: A summary of the evaluation
|
|
||||||
- **Model**: The model used for evaluation
|
|
||||||
- **Tokens**: Usage statistics
|
|
||||||
- **Metric Scores**: Numeric scores for each defined metric
|
|
||||||
|
|
||||||
## Example Usage
|
|
||||||
|
|
||||||
Here's an example of how an Evaluator block might be configured for assessing customer service responses:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
# Example Evaluator Configuration
|
|
||||||
metrics:
|
|
||||||
- name: Empathy
|
|
||||||
description: How well does the response acknowledge and address the customer's emotional state?
|
|
||||||
range:
|
|
||||||
min: 1
|
|
||||||
max: 5
|
|
||||||
- name: Solution
|
|
||||||
description: How effectively does the response solve the customer's problem?
|
|
||||||
range:
|
|
||||||
min: 1
|
|
||||||
max: 5
|
|
||||||
- name: Clarity
|
|
||||||
description: How clear and easy to understand is the response?
|
|
||||||
range:
|
|
||||||
min: 1
|
|
||||||
max: 5
|
|
||||||
|
|
||||||
model: Anthropic/claude-3-opus
|
|
||||||
```
|
|
||||||
|
|
||||||
## Best Practices
|
|
||||||
|
|
||||||
- **Use specific metric descriptions**: Clearly define what each metric measures to get more accurate evaluations
|
|
||||||
- **Choose appropriate ranges**: Select scoring ranges that provide enough granularity without being overly complex
|
|
||||||
- **Connect with Agent blocks**: Use Evaluator blocks to assess Agent block outputs and create feedback loops
|
|
||||||
- **Use consistent metrics**: For comparative analysis, maintain consistent metrics across similar evaluations
|
|
||||||
- **Combine multiple metrics**: Use several metrics to get a comprehensive evaluation
|
|
||||||
@@ -1,313 +0,0 @@
|
|||||||
---
|
|
||||||
title: Function
|
|
||||||
description: Execute custom JavaScript or TypeScript code in your workflows
|
|
||||||
---
|
|
||||||
|
|
||||||
import { Callout } from 'fumadocs-ui/components/callout'
|
|
||||||
import { Step, Steps } from 'fumadocs-ui/components/steps'
|
|
||||||
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
|
|
||||||
import { ThemeImage } from '@/components/ui/theme-image'
|
|
||||||
|
|
||||||
The Function block lets you run custom JavaScript or TypeScript code in your workflow. Use it to transform data, perform calculations, or implement custom logic that isn't available in other blocks.
|
|
||||||
|
|
||||||
<ThemeImage
|
|
||||||
lightSrc="/static/light/function-light.png"
|
|
||||||
darkSrc="/static/dark/function-dark.png"
|
|
||||||
alt="Function Block with Code Editor"
|
|
||||||
width={350}
|
|
||||||
height={175}
|
|
||||||
/>
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
The Function block enables you to:
|
|
||||||
|
|
||||||
<Steps>
|
|
||||||
<Step>
|
|
||||||
<strong>Transform data</strong>: Convert formats, parse text, manipulate arrays and objects
|
|
||||||
</Step>
|
|
||||||
<Step>
|
|
||||||
<strong>Perform calculations</strong>: Math operations, statistics, financial calculations
|
|
||||||
</Step>
|
|
||||||
<Step>
|
|
||||||
<strong>Implement custom logic</strong>: Complex conditionals, loops, and algorithms
|
|
||||||
</Step>
|
|
||||||
<Step>
|
|
||||||
<strong>Process external data</strong>: Parse responses, format requests, handle authentication
|
|
||||||
</Step>
|
|
||||||
</Steps>
|
|
||||||
|
|
||||||
## How It Works
|
|
||||||
|
|
||||||
The Function block runs your code in a secure, isolated environment:
|
|
||||||
|
|
||||||
1. **Receive Input**: Access data from previous blocks via the `input` object
|
|
||||||
2. **Execute Code**: Run your JavaScript/TypeScript code
|
|
||||||
3. **Return Results**: Use `return` to pass data to the next block
|
|
||||||
4. **Handle Errors**: Built-in error handling and logging
|
|
||||||
|
|
||||||
## Configuration Options
|
|
||||||
|
|
||||||
### Code Editor
|
|
||||||
|
|
||||||
Write your JavaScript/TypeScript code in a full-featured editor with:
|
|
||||||
- Syntax highlighting and error checking
|
|
||||||
- Line numbers and bracket matching
|
|
||||||
- Support for modern JavaScript features
|
|
||||||
- Native support for `fetch`
|
|
||||||
|
|
||||||
### Accessing Input Data
|
|
||||||
|
|
||||||
Use the `input` object to access data from previous blocks:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Access data from connected blocks
|
|
||||||
const userData = <agent.userData>;
|
|
||||||
const orderData = <agent.orderData>;
|
|
||||||
|
|
||||||
// Access specific fields
|
|
||||||
const customerName = <agent.customer.name>;
|
|
||||||
const total = <agent.order.total>;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Common Examples
|
|
||||||
|
|
||||||
**Data Transformation**:
|
|
||||||
```javascript
|
|
||||||
// Convert and format data
|
|
||||||
const formatted = {
|
|
||||||
name: <agent.user.firstName> + ' ' + <agent.user.lastName>,
|
|
||||||
email: <agent.user.email>.toLowerCase(),
|
|
||||||
joinDate: new Date(<agent.user.created>).toLocaleDateString()
|
|
||||||
};
|
|
||||||
return formatted;
|
|
||||||
```
|
|
||||||
|
|
||||||
**Calculations**:
|
|
||||||
```javascript
|
|
||||||
// Calculate discounts and totals
|
|
||||||
const subtotal = <agent.items>.reduce((sum, item) => sum + item.price, 0);
|
|
||||||
const discount = subtotal > 100 ? 0.1 : 0;
|
|
||||||
const total = subtotal * (1 - discount);
|
|
||||||
|
|
||||||
return { subtotal, discount, total };
|
|
||||||
```
|
|
||||||
|
|
||||||
**Data Validation**:
|
|
||||||
```javascript
|
|
||||||
// Validate email format
|
|
||||||
const email = <agent.email>;
|
|
||||||
const isValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
|
|
||||||
|
|
||||||
if (!isValid) {
|
|
||||||
throw new Error('Invalid email format');
|
|
||||||
}
|
|
||||||
return { email, isValid };
|
|
||||||
```
|
|
||||||
|
|
||||||
### Accessing Results
|
|
||||||
|
|
||||||
After a function executes, you can access its outputs:
|
|
||||||
|
|
||||||
- **`<function.result>`**: The value returned from your function
|
|
||||||
- **`<function.stdout>`**: Any console.log() output from your code
|
|
||||||
|
|
||||||
## Advanced Features
|
|
||||||
|
|
||||||
### Async/Await Support
|
|
||||||
|
|
||||||
Use async functions for complex operations:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Async function example
|
|
||||||
const processData = async () => {
|
|
||||||
const data = <api.response>;
|
|
||||||
|
|
||||||
// Process data with async operations
|
|
||||||
const processed = await Promise.all(
|
|
||||||
data.map(async (item) => {
|
|
||||||
return {
|
|
||||||
id: item.id,
|
|
||||||
processed: true,
|
|
||||||
timestamp: new Date().toISOString()
|
|
||||||
};
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
return processed;
|
|
||||||
};
|
|
||||||
|
|
||||||
return await processData();
|
|
||||||
```
|
|
||||||
|
|
||||||
### Error Handling
|
|
||||||
|
|
||||||
Implement robust error handling:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
try {
|
|
||||||
const result = <api.data>;
|
|
||||||
|
|
||||||
if (!result || !result.length) {
|
|
||||||
throw new Error('No data received');
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.map(item => ({
|
|
||||||
id: item.id,
|
|
||||||
name: item.name.trim(),
|
|
||||||
valid: true
|
|
||||||
}));
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Processing failed:', error.message);
|
|
||||||
return { error: error.message, valid: false };
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Performance Optimization
|
|
||||||
|
|
||||||
Optimize for large datasets:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Efficient data processing
|
|
||||||
const data = <api.large_dataset>;
|
|
||||||
|
|
||||||
// Use efficient array methods
|
|
||||||
const processed = data
|
|
||||||
.filter(item => item.status === 'active')
|
|
||||||
.map(item => ({
|
|
||||||
id: item.id,
|
|
||||||
summary: item.description.substring(0, 100)
|
|
||||||
}))
|
|
||||||
.slice(0, 1000); // Limit results
|
|
||||||
|
|
||||||
return processed;
|
|
||||||
```
|
|
||||||
|
|
||||||
## Security and Limitations
|
|
||||||
|
|
||||||
<Callout type="warning">
|
|
||||||
Functions run in a secure environment with these restrictions:
|
|
||||||
- **Execution timeout**: 30 seconds maximum to prevent infinite loops
|
|
||||||
- **Memory limits**: Limited memory to prevent resource exhaustion
|
|
||||||
- **No network access**: Cannot make HTTP requests (use API blocks instead)
|
|
||||||
- **Limited APIs**: Only safe JavaScript APIs are available
|
|
||||||
</Callout>
|
|
||||||
|
|
||||||
## Inputs and Outputs
|
|
||||||
|
|
||||||
<Tabs items={['Configuration', 'Variables', 'Results']}>
|
|
||||||
<Tab>
|
|
||||||
<ul className="list-disc space-y-2 pl-6">
|
|
||||||
<li>
|
|
||||||
<strong>Code</strong>: Your JavaScript/TypeScript code to execute
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Timeout</strong>: Maximum execution time (defaults to 30 seconds)
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Input Data</strong>: All connected block outputs available via variables
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</Tab>
|
|
||||||
<Tab>
|
|
||||||
<ul className="list-disc space-y-2 pl-6">
|
|
||||||
<li>
|
|
||||||
<strong>function.result</strong>: The value returned from your function
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>function.stdout</strong>: Console.log() output from your code
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>function.error</strong>: Error details if function failed
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>function.execution_time</strong>: Time taken to execute
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</Tab>
|
|
||||||
<Tab>
|
|
||||||
<ul className="list-disc space-y-2 pl-6">
|
|
||||||
<li>
|
|
||||||
<strong>Function Result</strong>: Primary output from your code
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Debug Information</strong>: Logs and execution details
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Access</strong>: Available in blocks after the function
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</Tab>
|
|
||||||
</Tabs>
|
|
||||||
|
|
||||||
## Example Use Cases
|
|
||||||
|
|
||||||
### Data Processing Pipeline
|
|
||||||
|
|
||||||
<div className="mb-4 rounded-md border p-4">
|
|
||||||
<h4 className="font-medium">Scenario: Transform API response into structured data</h4>
|
|
||||||
<ol className="list-decimal pl-5 text-sm">
|
|
||||||
<li>API block fetches raw customer data</li>
|
|
||||||
<li>Function block processes and validates data</li>
|
|
||||||
<li>Function block calculates derived metrics</li>
|
|
||||||
<li>Response block returns formatted results</li>
|
|
||||||
</ol>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
### Business Logic Implementation
|
|
||||||
|
|
||||||
<div className="mb-4 rounded-md border p-4">
|
|
||||||
<h4 className="font-medium">Scenario: Calculate loyalty scores and tiers</h4>
|
|
||||||
<ol className="list-decimal pl-5 text-sm">
|
|
||||||
<li>Agent retrieves customer purchase history</li>
|
|
||||||
<li>Function block calculates loyalty metrics</li>
|
|
||||||
<li>Function block determines customer tier</li>
|
|
||||||
<li>Condition block routes based on tier level</li>
|
|
||||||
</ol>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
### Data Validation and Sanitization
|
|
||||||
|
|
||||||
<div className="mb-4 rounded-md border p-4">
|
|
||||||
<h4 className="font-medium">Scenario: Validate and clean user input</h4>
|
|
||||||
<ol className="list-decimal pl-5 text-sm">
|
|
||||||
<li>User input received from form submission</li>
|
|
||||||
<li>Function block validates email format and phone numbers</li>
|
|
||||||
<li>Function block sanitizes and normalizes data</li>
|
|
||||||
<li>API block saves validated data to database</li>
|
|
||||||
</ol>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
### Example: Loyalty Score Calculator
|
|
||||||
|
|
||||||
```javascript title="loyalty-calculator.js"
|
|
||||||
// Process customer data and calculate loyalty score
|
|
||||||
const { purchaseHistory, accountAge, supportTickets } = <agent>;
|
|
||||||
|
|
||||||
// Calculate metrics
|
|
||||||
const totalSpent = purchaseHistory.reduce((sum, purchase) => sum + purchase.amount, 0);
|
|
||||||
const purchaseFrequency = purchaseHistory.length / (accountAge / 365);
|
|
||||||
const ticketRatio = supportTickets.resolved / supportTickets.total;
|
|
||||||
|
|
||||||
// Calculate loyalty score (0-100)
|
|
||||||
const spendScore = Math.min(totalSpent / 1000 * 30, 30);
|
|
||||||
const frequencyScore = Math.min(purchaseFrequency * 20, 40);
|
|
||||||
const supportScore = ticketRatio * 30;
|
|
||||||
|
|
||||||
const loyaltyScore = Math.round(spendScore + frequencyScore + supportScore);
|
|
||||||
|
|
||||||
return {
|
|
||||||
customer: <agent.name>,
|
|
||||||
loyaltyScore,
|
|
||||||
loyaltyTier: loyaltyScore >= 80 ? "Platinum" : loyaltyScore >= 60 ? "Gold" : "Silver",
|
|
||||||
metrics: { spendScore, frequencyScore, supportScore }
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
## Best Practices
|
|
||||||
|
|
||||||
- **Keep functions focused**: Write functions that do one thing well to improve maintainability and debugging
|
|
||||||
- **Handle errors gracefully**: Use try/catch blocks to handle potential errors and provide meaningful error messages
|
|
||||||
- **Test edge cases**: Ensure your code handles unusual inputs, null values, and boundary conditions correctly
|
|
||||||
- **Optimize for performance**: Be mindful of computational complexity and memory usage for large datasets
|
|
||||||
- **Use console.log() for debugging**: Leverage stdout output to debug and monitor function execution
|
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
---
|
|
||||||
title: Blocks
|
|
||||||
description: The building components of your AI workflows
|
|
||||||
---
|
|
||||||
|
|
||||||
import { Card, Cards } from 'fumadocs-ui/components/card'
|
|
||||||
import { Step, Steps } from 'fumadocs-ui/components/steps'
|
|
||||||
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
|
|
||||||
import { BlockTypes } from '@/components/ui/block-types'
|
|
||||||
import { Video } from '@/components/ui/video'
|
|
||||||
|
|
||||||
Blocks are the building components you connect together to create AI workflows. Think of them as specialized modules that each handle a specific task—from chatting with AI models to making API calls or processing data.
|
|
||||||
|
|
||||||
<div className="w-full max-w-2xl mx-auto overflow-hidden rounded-lg">
|
|
||||||
<Video src="connections.mp4" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
## Core Block Types
|
|
||||||
|
|
||||||
Sim provides seven core block types that handle the essential functions of AI workflows:
|
|
||||||
|
|
||||||
### Processing Blocks
|
|
||||||
- **[Agent](/blocks/agent)** - Chat with AI models (OpenAI, Anthropic, Google, local models)
|
|
||||||
- **[Function](/blocks/function)** - Run custom JavaScript/TypeScript code
|
|
||||||
- **[API](/blocks/api)** - Connect to external services via HTTP requests
|
|
||||||
|
|
||||||
### Logic Blocks
|
|
||||||
- **[Condition](/blocks/condition)** - Branch workflow paths based on boolean expressions
|
|
||||||
- **[Router](/blocks/router)** - Use AI to intelligently route requests to different paths
|
|
||||||
- **[Evaluator](/blocks/evaluator)** - Score and assess content quality using AI
|
|
||||||
|
|
||||||
### Output Blocks
|
|
||||||
- **[Response](/blocks/response)** - Format and return final results from your workflow
|
|
||||||
|
|
||||||
<BlockTypes />
|
|
||||||
|
|
||||||
## How Blocks Work
|
|
||||||
|
|
||||||
Each block has three main components:
|
|
||||||
|
|
||||||
**Inputs**: Data coming into the block from other blocks or user input
|
|
||||||
**Configuration**: Settings that control how the block behaves
|
|
||||||
**Outputs**: Data the block produces for other blocks to use
|
|
||||||
|
|
||||||
<Steps>
|
|
||||||
<Step>
|
|
||||||
<strong>Receive Input</strong>: Block receives data from connected blocks or user input
|
|
||||||
</Step>
|
|
||||||
<Step>
|
|
||||||
<strong>Process</strong>: Block processes the input according to its configuration
|
|
||||||
</Step>
|
|
||||||
<Step>
|
|
||||||
<strong>Output Results</strong>: Block produces output data for the next blocks in the workflow
|
|
||||||
</Step>
|
|
||||||
</Steps>
|
|
||||||
|
|
||||||
## Connecting Blocks
|
|
||||||
|
|
||||||
You create workflows by connecting blocks together. The output of one block becomes the input of another:
|
|
||||||
|
|
||||||
- **Drag to connect**: Drag from an output port to an input port
|
|
||||||
- **Multiple connections**: One output can connect to multiple inputs
|
|
||||||
- **Branching paths**: Some blocks can route to different paths based on conditions
|
|
||||||
|
|
||||||
<div className="w-full max-w-2xl mx-auto overflow-hidden rounded-lg">
|
|
||||||
<Video src="connections.mp4" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
## Common Patterns
|
|
||||||
|
|
||||||
### Sequential Processing
|
|
||||||
Connect blocks in a chain where each block processes the output of the previous one:
|
|
||||||
```
|
|
||||||
User Input → Agent → Function → Response
|
|
||||||
```
|
|
||||||
|
|
||||||
### Conditional Branching
|
|
||||||
Use Condition or Router blocks to create different paths:
|
|
||||||
```
|
|
||||||
User Input → Router → Agent A (for questions)
|
|
||||||
→ Agent B (for commands)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Quality Control
|
|
||||||
Use Evaluator blocks to assess and filter outputs:
|
|
||||||
```
|
|
||||||
Agent → Evaluator → Condition → Response (if good)
|
|
||||||
→ Agent (retry if bad)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Block Configuration
|
|
||||||
|
|
||||||
Each block type has specific configuration options:
|
|
||||||
|
|
||||||
**All Blocks**:
|
|
||||||
- Input/output connections
|
|
||||||
- Error handling behavior
|
|
||||||
- Execution timeout settings
|
|
||||||
|
|
||||||
**AI Blocks** (Agent, Router, Evaluator):
|
|
||||||
- Model selection (OpenAI, Anthropic, Google, local)
|
|
||||||
- API keys and authentication
|
|
||||||
- Temperature and other model parameters
|
|
||||||
- System prompts and instructions
|
|
||||||
|
|
||||||
**Logic Blocks** (Condition, Function):
|
|
||||||
- Custom expressions or code
|
|
||||||
- Variable references
|
|
||||||
- Execution environment settings
|
|
||||||
|
|
||||||
**Integration Blocks** (API, Response):
|
|
||||||
- Endpoint configuration
|
|
||||||
- Headers and authentication
|
|
||||||
- Request/response formatting
|
|
||||||
|
|
||||||
<Cards>
|
|
||||||
<Card title="Agent Block" href="/blocks/agent">
|
|
||||||
Connect to AI models and create intelligent responses
|
|
||||||
</Card>
|
|
||||||
<Card title="Function Block" href="/blocks/function">
|
|
||||||
Run custom code to process and transform data
|
|
||||||
</Card>
|
|
||||||
<Card title="API Block" href="/blocks/api">
|
|
||||||
Integrate with external services and APIs
|
|
||||||
</Card>
|
|
||||||
<Card title="Condition Block" href="/blocks/condition">
|
|
||||||
Create branching logic based on data evaluation
|
|
||||||
</Card>
|
|
||||||
</Cards>
|
|
||||||
@@ -1,175 +0,0 @@
|
|||||||
---
|
|
||||||
title: Loop
|
|
||||||
description: Create iterative workflows with loops that execute blocks repeatedly
|
|
||||||
---
|
|
||||||
|
|
||||||
import { Callout } from 'fumadocs-ui/components/callout'
|
|
||||||
import { Step, Steps } from 'fumadocs-ui/components/steps'
|
|
||||||
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
|
|
||||||
import { ThemeImage } from '@/components/ui/theme-image'
|
|
||||||
|
|
||||||
The Loop block is a container block in Sim that allows you to execute a group of blocks repeatedly. Loops enable iterative processing in your workflows.
|
|
||||||
|
|
||||||
<ThemeImage
|
|
||||||
lightSrc="/static/light/loop-light.png"
|
|
||||||
darkSrc="/static/dark/loop-dark.png"
|
|
||||||
alt="Loop Block"
|
|
||||||
width={500}
|
|
||||||
height={300}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Callout type="info">
|
|
||||||
Loop blocks are container nodes that can hold other blocks inside them. The blocks inside a loop will execute multiple times based on your configuration.
|
|
||||||
</Callout>
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
The Loop block enables you to:
|
|
||||||
|
|
||||||
<Steps>
|
|
||||||
<Step>
|
|
||||||
<strong>Iterate over collections</strong>: Process arrays or objects one item at a time
|
|
||||||
</Step>
|
|
||||||
<Step>
|
|
||||||
<strong>Repeat operations</strong>: Execute blocks a fixed number of times
|
|
||||||
</Step>
|
|
||||||
</Steps>
|
|
||||||
|
|
||||||
## Configuration Options
|
|
||||||
|
|
||||||
### Loop Type
|
|
||||||
|
|
||||||
Choose between two types of loops:
|
|
||||||
|
|
||||||
<Tabs items={['For Loop', 'ForEach Loop']}>
|
|
||||||
<Tab>
|
|
||||||
A numeric loop that executes a fixed number of times. Use this when you need to repeat an operation a specific number of times.
|
|
||||||
|
|
||||||
```
|
|
||||||
Example: Run 5 times
|
|
||||||
- Iteration 1
|
|
||||||
- Iteration 2
|
|
||||||
- Iteration 3
|
|
||||||
- Iteration 4
|
|
||||||
- Iteration 5
|
|
||||||
```
|
|
||||||
</Tab>
|
|
||||||
<Tab>
|
|
||||||
A collection-based loop that iterates over each item in an array or object. Use this when you need to process a collection of items.
|
|
||||||
|
|
||||||
```
|
|
||||||
Example: Process ["apple", "banana", "orange"]
|
|
||||||
- Iteration 1: Process "apple"
|
|
||||||
- Iteration 2: Process "banana"
|
|
||||||
- Iteration 3: Process "orange"
|
|
||||||
```
|
|
||||||
</Tab>
|
|
||||||
</Tabs>
|
|
||||||
|
|
||||||
## How to Use Loops
|
|
||||||
|
|
||||||
### Creating a Loop
|
|
||||||
|
|
||||||
1. Drag a Loop block from the toolbar onto your canvas
|
|
||||||
2. Configure the loop type and parameters
|
|
||||||
3. Drag other blocks inside the loop container
|
|
||||||
4. Connect the blocks as needed
|
|
||||||
|
|
||||||
### Accessing Results
|
|
||||||
|
|
||||||
After a loop completes, you can access aggregated results:
|
|
||||||
|
|
||||||
- **`<loop.results>`**: Array of results from all loop iterations
|
|
||||||
|
|
||||||
## Example Use Cases
|
|
||||||
|
|
||||||
### Processing API Results
|
|
||||||
|
|
||||||
<div className="mb-4 rounded-md border p-4">
|
|
||||||
<h4 className="font-medium">Scenario: Process multiple customer records</h4>
|
|
||||||
<ol className="list-decimal pl-5 text-sm">
|
|
||||||
<li>API block fetches customer list</li>
|
|
||||||
<li>ForEach loop iterates over each customer</li>
|
|
||||||
<li>Inside loop: Agent analyzes customer data</li>
|
|
||||||
<li>Inside loop: Function stores analysis results</li>
|
|
||||||
</ol>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
### Iterative Content Generation
|
|
||||||
|
|
||||||
<div className="mb-4 rounded-md border p-4">
|
|
||||||
<h4 className="font-medium">Scenario: Generate multiple variations</h4>
|
|
||||||
<ol className="list-decimal pl-5 text-sm">
|
|
||||||
<li>Set For loop to 5 iterations</li>
|
|
||||||
<li>Inside loop: Agent generates content variation</li>
|
|
||||||
<li>Inside loop: Evaluator scores the content</li>
|
|
||||||
<li>After loop: Function selects best variation</li>
|
|
||||||
</ol>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
## Advanced Features
|
|
||||||
|
|
||||||
### Limitations
|
|
||||||
|
|
||||||
<Callout type="warning">
|
|
||||||
Container blocks (Loops and Parallels) cannot be nested inside each other. This means:
|
|
||||||
- You cannot place a Loop block inside another Loop block
|
|
||||||
- You cannot place a Parallel block inside a Loop block
|
|
||||||
- You cannot place any container block inside another container block
|
|
||||||
|
|
||||||
If you need multi-dimensional iteration, consider restructuring your workflow to use sequential loops or process data in stages.
|
|
||||||
</Callout>
|
|
||||||
|
|
||||||
<Callout type="info">
|
|
||||||
Loops execute sequentially, not in parallel. If you need concurrent execution, use the Parallel block instead.
|
|
||||||
</Callout>
|
|
||||||
|
|
||||||
## Inputs and Outputs
|
|
||||||
|
|
||||||
<Tabs items={['Configuration', 'Variables', 'Results']}>
|
|
||||||
<Tab>
|
|
||||||
<ul className="list-disc space-y-2 pl-6">
|
|
||||||
<li>
|
|
||||||
<strong>Loop Type</strong>: Choose between 'for' or 'forEach'
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Iterations</strong>: Number of times to execute (for loops)
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Collection</strong>: Array or object to iterate over (forEach loops)
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</Tab>
|
|
||||||
<Tab>
|
|
||||||
<ul className="list-disc space-y-2 pl-6">
|
|
||||||
<li>
|
|
||||||
<strong>loop.currentItem</strong>: Current item being processed
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>loop.index</strong>: Current iteration number (0-based)
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>loop.items</strong>: Full collection (forEach loops)
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</Tab>
|
|
||||||
<Tab>
|
|
||||||
<ul className="list-disc space-y-2 pl-6">
|
|
||||||
<li>
|
|
||||||
<strong>loop.results</strong>: Array of all iteration results
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Structure</strong>: Results maintain iteration order
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Access</strong>: Available in blocks after the loop
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</Tab>
|
|
||||||
</Tabs>
|
|
||||||
|
|
||||||
## Best Practices
|
|
||||||
|
|
||||||
- **Set reasonable limits**: Keep iteration counts reasonable to avoid long execution times
|
|
||||||
- **Use ForEach for collections**: When processing arrays or objects, use ForEach instead of For loops
|
|
||||||
- **Handle errors gracefully**: Consider adding error handling inside loops for robust workflows
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"title": "Blocks",
|
|
||||||
"pages": [
|
|
||||||
"agent",
|
|
||||||
"api",
|
|
||||||
"condition",
|
|
||||||
"evaluator",
|
|
||||||
"function",
|
|
||||||
"loop",
|
|
||||||
"parallel",
|
|
||||||
"response",
|
|
||||||
"router",
|
|
||||||
"workflow"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,210 +0,0 @@
|
|||||||
---
|
|
||||||
title: Parallel
|
|
||||||
description: Execute multiple blocks concurrently for faster workflow processing
|
|
||||||
---
|
|
||||||
|
|
||||||
import { Callout } from 'fumadocs-ui/components/callout'
|
|
||||||
import { Step, Steps } from 'fumadocs-ui/components/steps'
|
|
||||||
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
|
|
||||||
import { ThemeImage } from '@/components/ui/theme-image'
|
|
||||||
|
|
||||||
The Parallel block is a container block in Sim that allows you to execute multiple instances of blocks concurrently.
|
|
||||||
|
|
||||||
<ThemeImage
|
|
||||||
lightSrc="/static/light/parallel-light.png"
|
|
||||||
darkSrc="/static/dark/parallel-dark.png"
|
|
||||||
alt="Parallel Block"
|
|
||||||
width={500}
|
|
||||||
height={300}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Callout type="info">
|
|
||||||
Parallel blocks are container nodes that execute their contents multiple times simultaneously, unlike loops which execute sequentially.
|
|
||||||
</Callout>
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
The Parallel block enables you to:
|
|
||||||
|
|
||||||
<Steps>
|
|
||||||
<Step>
|
|
||||||
<strong>Distribute work</strong>: Process multiple items concurrently
|
|
||||||
</Step>
|
|
||||||
<Step>
|
|
||||||
<strong>Speed up execution</strong>: Run independent operations simultaneously
|
|
||||||
</Step>
|
|
||||||
<Step>
|
|
||||||
<strong>Handle bulk operations</strong>: Process large datasets efficiently
|
|
||||||
</Step>
|
|
||||||
<Step>
|
|
||||||
<strong>Aggregate results</strong>: Collect outputs from all parallel executions
|
|
||||||
</Step>
|
|
||||||
</Steps>
|
|
||||||
|
|
||||||
## Configuration Options
|
|
||||||
|
|
||||||
### Parallel Type
|
|
||||||
|
|
||||||
Choose between two types of parallel execution:
|
|
||||||
|
|
||||||
<Tabs items={['Count-based', 'Collection-based']}>
|
|
||||||
<Tab>
|
|
||||||
Execute a fixed number of parallel instances. Use this when you need to run the same operation multiple times concurrently.
|
|
||||||
|
|
||||||
```
|
|
||||||
Example: Run 5 parallel instances
|
|
||||||
- Instance 1 ┐
|
|
||||||
- Instance 2 ├─ All execute simultaneously
|
|
||||||
- Instance 3 │
|
|
||||||
- Instance 4 │
|
|
||||||
- Instance 5 ┘
|
|
||||||
```
|
|
||||||
</Tab>
|
|
||||||
<Tab>
|
|
||||||
Distribute a collection across parallel instances. Each instance processes one item from the collection simultaneously.
|
|
||||||
|
|
||||||
```
|
|
||||||
Example: Process ["task1", "task2", "task3"] in parallel
|
|
||||||
- Instance 1: Process "task1" ┐
|
|
||||||
- Instance 2: Process "task2" ├─ All execute simultaneously
|
|
||||||
- Instance 3: Process "task3" ┘
|
|
||||||
```
|
|
||||||
</Tab>
|
|
||||||
</Tabs>
|
|
||||||
|
|
||||||
## How to Use Parallel Blocks
|
|
||||||
|
|
||||||
### Creating a Parallel Block
|
|
||||||
|
|
||||||
1. Drag a Parallel block from the toolbar onto your canvas
|
|
||||||
2. Configure the parallel type and parameters
|
|
||||||
3. Drag a single block inside the parallel container
|
|
||||||
4. Connect the block as needed
|
|
||||||
|
|
||||||
### Accessing Results
|
|
||||||
|
|
||||||
After a parallel block completes, you can access aggregated results:
|
|
||||||
|
|
||||||
- **`<parallel.results>`**: Array of results from all parallel instances
|
|
||||||
|
|
||||||
## Example Use Cases
|
|
||||||
|
|
||||||
### Batch API Processing
|
|
||||||
|
|
||||||
<div className="mb-4 rounded-md border p-4">
|
|
||||||
<h4 className="font-medium">Scenario: Process multiple API calls simultaneously</h4>
|
|
||||||
<ol className="list-decimal pl-5 text-sm">
|
|
||||||
<li>Parallel block with collection of API endpoints</li>
|
|
||||||
<li>Inside parallel: API block calls each endpoint</li>
|
|
||||||
<li>After parallel: Process all responses together</li>
|
|
||||||
</ol>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
### Multi-Model AI Processing
|
|
||||||
|
|
||||||
<div className="mb-4 rounded-md border p-4">
|
|
||||||
<h4 className="font-medium">Scenario: Get responses from multiple AI models</h4>
|
|
||||||
<ol className="list-decimal pl-5 text-sm">
|
|
||||||
<li>Count-based parallel set to 3 instances</li>
|
|
||||||
<li>Inside parallel: Agent configured with different model per instance</li>
|
|
||||||
<li>After parallel: Compare and select best response</li>
|
|
||||||
</ol>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
## Advanced Features
|
|
||||||
|
|
||||||
### Result Aggregation
|
|
||||||
|
|
||||||
Results from all parallel instances are automatically collected:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// In a Function block after the parallel
|
|
||||||
const allResults = input.parallel.results;
|
|
||||||
// Returns: [result1, result2, result3, ...]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Instance Isolation
|
|
||||||
|
|
||||||
Each parallel instance runs independently:
|
|
||||||
- Separate variable scopes
|
|
||||||
- No shared state between instances
|
|
||||||
- Failures in one instance don't affect others
|
|
||||||
|
|
||||||
### Limitations
|
|
||||||
|
|
||||||
<Callout type="warning">
|
|
||||||
Container blocks (Loops and Parallels) cannot be nested inside each other. This means:
|
|
||||||
- You cannot place a Loop block inside a Parallel block
|
|
||||||
- You cannot place another Parallel block inside a Parallel block
|
|
||||||
- You cannot place any container block inside another container block
|
|
||||||
</Callout>
|
|
||||||
|
|
||||||
<Callout type="warning">
|
|
||||||
Parallel blocks can only contain a single block. You cannot have multiple blocks connected to each other inside a parallel - only the first block would execute in that case.
|
|
||||||
</Callout>
|
|
||||||
|
|
||||||
<Callout type="info">
|
|
||||||
While parallel execution is faster, be mindful of:
|
|
||||||
- API rate limits when making concurrent requests
|
|
||||||
- Memory usage with large datasets
|
|
||||||
- Maximum of 20 concurrent instances to prevent resource exhaustion
|
|
||||||
</Callout>
|
|
||||||
|
|
||||||
## Parallel vs Loop
|
|
||||||
|
|
||||||
Understanding when to use each:
|
|
||||||
|
|
||||||
| Feature | Parallel | Loop |
|
|
||||||
|---------|----------|------|
|
|
||||||
| Execution | Concurrent | Sequential |
|
|
||||||
| Speed | Faster for independent operations | Slower but ordered |
|
|
||||||
| Order | No guaranteed order | Maintains order |
|
|
||||||
| Use case | Independent operations | Dependent operations |
|
|
||||||
| Resource usage | Higher | Lower |
|
|
||||||
|
|
||||||
## Inputs and Outputs
|
|
||||||
|
|
||||||
<Tabs items={['Configuration', 'Variables', 'Results']}>
|
|
||||||
<Tab>
|
|
||||||
<ul className="list-disc space-y-2 pl-6">
|
|
||||||
<li>
|
|
||||||
<strong>Parallel Type</strong>: Choose between 'count' or 'collection'
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Count</strong>: Number of instances to run (count-based)
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Collection</strong>: Array or object to distribute (collection-based)
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</Tab>
|
|
||||||
<Tab>
|
|
||||||
<ul className="list-disc space-y-2 pl-6">
|
|
||||||
<li>
|
|
||||||
<strong>parallel.currentItem</strong>: Item for this instance
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>parallel.index</strong>: Instance number (0-based)
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>parallel.items</strong>: Full collection (collection-based)
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</Tab>
|
|
||||||
<Tab>
|
|
||||||
<ul className="list-disc space-y-2 pl-6">
|
|
||||||
<li>
|
|
||||||
<strong>parallel.results</strong>: Array of all instance results
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Access</strong>: Available in blocks after the parallel
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</Tab>
|
|
||||||
</Tabs>
|
|
||||||
|
|
||||||
## Best Practices
|
|
||||||
|
|
||||||
- **Independent operations only**: Ensure operations don't depend on each other
|
|
||||||
- **Handle rate limits**: Add delays or throttling for API-heavy workflows
|
|
||||||
- **Error handling**: Each instance should handle its own errors gracefully
|
|
||||||
@@ -1,186 +0,0 @@
|
|||||||
---
|
|
||||||
title: Response
|
|
||||||
description: Send a structured response back to API calls
|
|
||||||
---
|
|
||||||
|
|
||||||
import { Callout } from 'fumadocs-ui/components/callout'
|
|
||||||
import { Step, Steps } from 'fumadocs-ui/components/steps'
|
|
||||||
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
|
|
||||||
import { ThemeImage } from '@/components/ui/theme-image'
|
|
||||||
|
|
||||||
The Response block is the final step in your workflow that formats and returns data to whoever called your workflow. It's like the "return" statement for your entire workflow—it packages up results and sends them back.
|
|
||||||
|
|
||||||
<ThemeImage
|
|
||||||
lightSrc="/static/light/response-light.png"
|
|
||||||
darkSrc="/static/dark/response-dark.png"
|
|
||||||
alt="Response Block Configuration"
|
|
||||||
width={350}
|
|
||||||
height={175}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Callout type="info">
|
|
||||||
Response blocks are terminal blocks - they end the workflow execution and cannot connect to other blocks.
|
|
||||||
</Callout>
|
|
||||||
|
|
||||||
## When You Need Response Blocks
|
|
||||||
|
|
||||||
**API Endpoints**: When your workflow is called via API, Response blocks format the return data
|
|
||||||
**Webhooks**: Return confirmation or data back to the calling system
|
|
||||||
**Testing**: See formatted results when testing your workflow
|
|
||||||
**Data Export**: Structure data for external systems or reports
|
|
||||||
|
|
||||||
## Two Ways to Build Responses
|
|
||||||
|
|
||||||
### Builder Mode (Recommended)
|
|
||||||
Visual interface for building response structure:
|
|
||||||
- Drag and drop fields
|
|
||||||
- Reference workflow variables easily
|
|
||||||
- Visual preview of response structure
|
|
||||||
|
|
||||||
### Editor Mode (Advanced)
|
|
||||||
Write JSON directly:
|
|
||||||
- Full control over response format
|
|
||||||
- Support for complex nested structures
|
|
||||||
- Use `<variable.name>` syntax for dynamic values
|
|
||||||
|
|
||||||
## Configuration Options
|
|
||||||
|
|
||||||
### Response Data
|
|
||||||
|
|
||||||
The response data is the main content that will be sent back to the API caller. This should be formatted as JSON and can include:
|
|
||||||
|
|
||||||
- Static values
|
|
||||||
- Dynamic references to workflow variables using the `<variable.name>` syntax
|
|
||||||
- Nested objects and arrays
|
|
||||||
- Any valid JSON structure
|
|
||||||
|
|
||||||
### Status Code
|
|
||||||
|
|
||||||
Set the HTTP status code for the response. Common status codes include:
|
|
||||||
|
|
||||||
<Tabs items={['Success (2xx)', 'Client Error (4xx)', 'Server Error (5xx)']}>
|
|
||||||
<Tab>
|
|
||||||
<ul className="list-disc space-y-2 pl-6">
|
|
||||||
<li><strong>200</strong>: OK - Standard success response</li>
|
|
||||||
<li><strong>201</strong>: Created - Resource successfully created</li>
|
|
||||||
<li><strong>204</strong>: No Content - Success with no response body</li>
|
|
||||||
</ul>
|
|
||||||
</Tab>
|
|
||||||
<Tab>
|
|
||||||
<ul className="list-disc space-y-2 pl-6">
|
|
||||||
<li><strong>400</strong>: Bad Request - Invalid request parameters</li>
|
|
||||||
<li><strong>401</strong>: Unauthorized - Authentication required</li>
|
|
||||||
<li><strong>404</strong>: Not Found - Resource doesn't exist</li>
|
|
||||||
<li><strong>422</strong>: Unprocessable Entity - Validation errors</li>
|
|
||||||
</ul>
|
|
||||||
</Tab>
|
|
||||||
<Tab>
|
|
||||||
<ul className="list-disc space-y-2 pl-6">
|
|
||||||
<li><strong>500</strong>: Internal Server Error - Server-side error</li>
|
|
||||||
<li><strong>502</strong>: Bad Gateway - External service error</li>
|
|
||||||
<li><strong>503</strong>: Service Unavailable - Service temporarily down</li>
|
|
||||||
</ul>
|
|
||||||
</Tab>
|
|
||||||
</Tabs>
|
|
||||||
|
|
||||||
<p className="mt-4 text-sm text-gray-600 dark:text-gray-400">
|
|
||||||
Default status code is 200 if not specified.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
### Response Headers
|
|
||||||
|
|
||||||
Configure additional HTTP headers to include in the response.
|
|
||||||
|
|
||||||
Headers are configured as key-value pairs:
|
|
||||||
|
|
||||||
| Key | Value |
|
|
||||||
|-----|-------|
|
|
||||||
| Content-Type | application/json |
|
|
||||||
| Cache-Control | no-cache |
|
|
||||||
| X-API-Version | 1.0 |
|
|
||||||
|
|
||||||
## Inputs and Outputs
|
|
||||||
|
|
||||||
<Tabs items={['Inputs', 'Outputs']}>
|
|
||||||
<Tab>
|
|
||||||
<ul className="list-disc space-y-2 pl-6">
|
|
||||||
<li>
|
|
||||||
<strong>data</strong> (JSON, optional): The JSON data to send in the response body
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>status</strong> (number, optional): HTTP status code (default: 200)
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>headers</strong> (JSON, optional): Additional response headers
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</Tab>
|
|
||||||
<Tab>
|
|
||||||
<ul className="list-disc space-y-2 pl-6">
|
|
||||||
<li><strong>data</strong>: The response body data</li>
|
|
||||||
<li><strong>status</strong>: HTTP status code</li>
|
|
||||||
<li><strong>headers</strong>: Response headers</li>
|
|
||||||
</ul>
|
|
||||||
</Tab>
|
|
||||||
</Tabs>
|
|
||||||
|
|
||||||
## Variable References
|
|
||||||
|
|
||||||
Use the `<variable.name>` syntax to dynamically insert workflow variables into your response:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"user": {
|
|
||||||
"id": "<variable.userId>",
|
|
||||||
"name": "<variable.userName>",
|
|
||||||
"email": "<variable.userEmail>"
|
|
||||||
},
|
|
||||||
"query": "<variable.searchQuery>",
|
|
||||||
"results": "<variable.searchResults>",
|
|
||||||
"totalFound": "<variable.resultCount>",
|
|
||||||
"processingTime": "<variable.executionTime>ms"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
<Callout type="warning">
|
|
||||||
Variable names are case-sensitive and must match exactly with the variables available in your workflow.
|
|
||||||
</Callout>
|
|
||||||
|
|
||||||
## Example Usage
|
|
||||||
|
|
||||||
Here's an example of how a Response block might be configured for a user search API:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
data: |
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"data": {
|
|
||||||
"users": "<variable.searchResults>",
|
|
||||||
"pagination": {
|
|
||||||
"page": "<variable.currentPage>",
|
|
||||||
"limit": "<variable.pageSize>",
|
|
||||||
"total": "<variable.totalUsers>"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"query": {
|
|
||||||
"searchTerm": "<variable.searchTerm>",
|
|
||||||
"filters": "<variable.appliedFilters>"
|
|
||||||
},
|
|
||||||
"timestamp": "<variable.timestamp>"
|
|
||||||
}
|
|
||||||
status: 200
|
|
||||||
headers:
|
|
||||||
- key: X-Total-Count
|
|
||||||
value: <variable.totalUsers>
|
|
||||||
- key: Cache-Control
|
|
||||||
value: public, max-age=300
|
|
||||||
```
|
|
||||||
|
|
||||||
## Best Practices
|
|
||||||
|
|
||||||
- **Use meaningful status codes**: Choose appropriate HTTP status codes that accurately reflect the outcome of the workflow
|
|
||||||
- **Structure your responses consistently**: Maintain a consistent JSON structure across all your API endpoints for better developer experience
|
|
||||||
- **Include relevant metadata**: Add timestamps and version information to help with debugging and monitoring
|
|
||||||
- **Handle errors gracefully**: Use conditional logic in your workflow to set appropriate error responses with descriptive messages
|
|
||||||
- **Validate variable references**: Ensure all referenced variables exist and contain the expected data types before the Response block executes
|
|
||||||
|
|
||||||
@@ -1,259 +0,0 @@
|
|||||||
---
|
|
||||||
title: Router
|
|
||||||
description: Route workflow execution based on specific conditions or logic
|
|
||||||
---
|
|
||||||
|
|
||||||
import { Callout } from 'fumadocs-ui/components/callout'
|
|
||||||
import { Step, Steps } from 'fumadocs-ui/components/steps'
|
|
||||||
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
|
|
||||||
import { Accordion, Accordions } from 'fumadocs-ui/components/accordion'
|
|
||||||
import { ThemeImage } from '@/components/ui/theme-image'
|
|
||||||
import { Video } from '@/components/ui/video'
|
|
||||||
|
|
||||||
The Router block uses AI to intelligently decide which path your workflow should take next. Unlike Condition blocks that use simple rules, Router blocks can understand context and make smart routing decisions based on content analysis.
|
|
||||||
|
|
||||||
<ThemeImage
|
|
||||||
lightSrc="/static/light/router-light.png"
|
|
||||||
darkSrc="/static/dark/router-dark.png"
|
|
||||||
alt="Router Block with Multiple Paths"
|
|
||||||
width={350}
|
|
||||||
height={175}
|
|
||||||
/>
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
The Router block enables you to:
|
|
||||||
|
|
||||||
<Steps>
|
|
||||||
<Step>
|
|
||||||
<strong>Intelligent content routing</strong>: Use AI to understand intent and context
|
|
||||||
</Step>
|
|
||||||
<Step>
|
|
||||||
<strong>Dynamic path selection</strong>: Route workflows based on unstructured content analysis
|
|
||||||
</Step>
|
|
||||||
<Step>
|
|
||||||
<strong>Context-aware decisions</strong>: Make smart routing choices beyond simple rules
|
|
||||||
</Step>
|
|
||||||
<Step>
|
|
||||||
<strong>Multi-path management</strong>: Handle complex workflows with multiple potential destinations
|
|
||||||
</Step>
|
|
||||||
</Steps>
|
|
||||||
|
|
||||||
## Router vs Condition Blocks
|
|
||||||
|
|
||||||
<Accordions>
|
|
||||||
<Accordion title="When to Use Router">
|
|
||||||
- AI-powered content analysis needed
|
|
||||||
- Unstructured or varying content types
|
|
||||||
- Intent-based routing (e.g., "route support tickets to departments")
|
|
||||||
- Context-aware decision making required
|
|
||||||
</Accordion>
|
|
||||||
<Accordion title="When to Use Condition">
|
|
||||||
- Simple, rule-based decisions
|
|
||||||
- Structured data or numeric comparisons
|
|
||||||
- Fast, deterministic routing needed
|
|
||||||
- Boolean logic sufficient
|
|
||||||
</Accordion>
|
|
||||||
</Accordions>
|
|
||||||
|
|
||||||
## How It Works
|
|
||||||
|
|
||||||
The Router block:
|
|
||||||
|
|
||||||
<Steps>
|
|
||||||
<Step>
|
|
||||||
<strong>Analyze content</strong>: Uses an LLM to understand input content and context
|
|
||||||
</Step>
|
|
||||||
<Step>
|
|
||||||
<strong>Evaluate targets</strong>: Compares content against available destination blocks
|
|
||||||
</Step>
|
|
||||||
<Step>
|
|
||||||
<strong>Select destination</strong>: Identifies the most appropriate path based on intent
|
|
||||||
</Step>
|
|
||||||
<Step>
|
|
||||||
<strong>Route execution</strong>: Directs workflow to the selected block
|
|
||||||
</Step>
|
|
||||||
</Steps>
|
|
||||||
|
|
||||||
## Configuration Options
|
|
||||||
|
|
||||||
### Content/Prompt
|
|
||||||
|
|
||||||
The content or prompt that the Router will analyze to make routing decisions. This can be:
|
|
||||||
|
|
||||||
- A direct user query or input
|
|
||||||
- Output from a previous block
|
|
||||||
- A system-generated message
|
|
||||||
|
|
||||||
### Target Blocks
|
|
||||||
|
|
||||||
The possible destination blocks that the Router can select from. The Router will automatically detect connected blocks, but you can also:
|
|
||||||
|
|
||||||
- Customize the descriptions of target blocks to improve routing accuracy
|
|
||||||
- Specify routing criteria for each target block
|
|
||||||
- Exclude certain blocks from being considered as routing targets
|
|
||||||
|
|
||||||
### Model Selection
|
|
||||||
|
|
||||||
Choose an AI model to power the routing decision:
|
|
||||||
|
|
||||||
**OpenAI**: GPT-4o, o1, o3, o4-mini, gpt-4.1 \
|
|
||||||
**Anthropic**: Claude 3.7 Sonnet \
|
|
||||||
**Google**: Gemini 2.5 Pro, Gemini 2.0 Flash \
|
|
||||||
**Other Providers**: Groq, Cerebras, xAI, DeepSeek \
|
|
||||||
**Local Models**: Any model running on Ollama
|
|
||||||
|
|
||||||
<div className="w-full max-w-2xl mx-auto overflow-hidden rounded-lg">
|
|
||||||
<Video src="router-model-dropdown.mp4" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
**Recommendation**: Use models with strong reasoning capabilities like GPT-4o or Claude 3.7 Sonnet for more accurate routing decisions.
|
|
||||||
|
|
||||||
### API Key
|
|
||||||
|
|
||||||
Your API key for the selected LLM provider. This is securely stored and used for authentication.
|
|
||||||
|
|
||||||
### Accessing Results
|
|
||||||
|
|
||||||
After a router makes a decision, you can access its outputs:
|
|
||||||
|
|
||||||
- **`<router.content>`**: Summary of the routing decision made
|
|
||||||
- **`<router.selected_path>`**: Details of the chosen destination block
|
|
||||||
- **`<router.tokens>`**: Token usage statistics from the LLM
|
|
||||||
- **`<router.model>`**: The model used for decision-making
|
|
||||||
|
|
||||||
## Advanced Features
|
|
||||||
|
|
||||||
### Custom Routing Criteria
|
|
||||||
|
|
||||||
Define specific criteria for each target block:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Example routing descriptions
|
|
||||||
Target Block 1: "Technical support issues, API problems, integration questions"
|
|
||||||
Target Block 2: "Billing inquiries, subscription changes, payment issues"
|
|
||||||
Target Block 3: "General questions, feedback, feature requests"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Multi-Model Routing
|
|
||||||
|
|
||||||
Use different models for different routing scenarios:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Fast routing for simple cases
|
|
||||||
Model: GPT-4o-mini
|
|
||||||
Criteria: Simple, common routing patterns
|
|
||||||
|
|
||||||
// Complex routing for nuanced decisions
|
|
||||||
Model: Claude 3.7 Sonnet
|
|
||||||
Criteria: Complex content analysis required
|
|
||||||
```
|
|
||||||
|
|
||||||
### Fallback Handling
|
|
||||||
|
|
||||||
Implement robust fallback mechanisms:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Router configuration
|
|
||||||
Primary Targets: ["Support", "Sales", "Technical"]
|
|
||||||
Fallback Target: "General" // Default when no specific match
|
|
||||||
Confidence Threshold: 0.7 // Minimum confidence for routing
|
|
||||||
```
|
|
||||||
|
|
||||||
## Inputs and Outputs
|
|
||||||
|
|
||||||
<Tabs items={['Configuration', 'Variables', 'Results']}>
|
|
||||||
<Tab>
|
|
||||||
<ul className="list-disc space-y-2 pl-6">
|
|
||||||
<li>
|
|
||||||
<strong>Content/Prompt</strong>: Text to analyze for routing decisions
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Target Blocks</strong>: Connected blocks as potential destinations
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Model</strong>: AI model for routing analysis
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>API Key</strong>: Authentication for selected LLM provider
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</Tab>
|
|
||||||
<Tab>
|
|
||||||
<ul className="list-disc space-y-2 pl-6">
|
|
||||||
<li>
|
|
||||||
<strong>router.content</strong>: Summary of routing decision
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>router.selected_path</strong>: Details of chosen destination
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>router.tokens</strong>: Token usage statistics
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>router.model</strong>: Model used for decision-making
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</Tab>
|
|
||||||
<Tab>
|
|
||||||
<ul className="list-disc space-y-2 pl-6">
|
|
||||||
<li>
|
|
||||||
<strong>Routing Decision</strong>: Primary path selection result
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Decision Context</strong>: Analysis summary and reasoning
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Access</strong>: Available in blocks after the router
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</Tab>
|
|
||||||
</Tabs>
|
|
||||||
|
|
||||||
## Example Use Cases
|
|
||||||
|
|
||||||
### Customer Support Triage
|
|
||||||
|
|
||||||
<div className="mb-4 rounded-md border p-4">
|
|
||||||
<h4 className="font-medium">Scenario: Route support tickets to specialized departments</h4>
|
|
||||||
<ol className="list-decimal pl-5 text-sm">
|
|
||||||
<li>User submits support request via form</li>
|
|
||||||
<li>Router analyzes ticket content and context</li>
|
|
||||||
<li>Technical issues → Engineering support agent</li>
|
|
||||||
<li>Billing questions → Finance support agent</li>
|
|
||||||
</ol>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
### Content Classification
|
|
||||||
|
|
||||||
<div className="mb-4 rounded-md border p-4">
|
|
||||||
<h4 className="font-medium">Scenario: Classify and route user-generated content</h4>
|
|
||||||
<ol className="list-decimal pl-5 text-sm">
|
|
||||||
<li>User submits content or feedback</li>
|
|
||||||
<li>Router analyzes content type and sentiment</li>
|
|
||||||
<li>Feature requests → Product team workflow</li>
|
|
||||||
<li>Bug reports → Technical support workflow</li>
|
|
||||||
</ol>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
### Lead Qualification
|
|
||||||
|
|
||||||
<div className="mb-4 rounded-md border p-4">
|
|
||||||
<h4 className="font-medium">Scenario: Route leads based on qualification criteria</h4>
|
|
||||||
<ol className="list-decimal pl-5 text-sm">
|
|
||||||
<li>Lead information captured from form</li>
|
|
||||||
<li>Router analyzes company size, industry, and needs</li>
|
|
||||||
<li>Enterprise leads → Sales team with custom pricing</li>
|
|
||||||
<li>SMB leads → Self-service onboarding flow</li>
|
|
||||||
</ol>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
## Best Practices
|
|
||||||
|
|
||||||
- **Provide clear target descriptions**: Help the Router understand when to select each destination with specific, detailed descriptions
|
|
||||||
- **Use specific routing criteria**: Define clear conditions and examples for each path to improve accuracy
|
|
||||||
- **Implement fallback paths**: Connect a default destination for when no specific path is appropriate
|
|
||||||
- **Test with diverse inputs**: Ensure the Router handles various input types, edge cases, and unexpected content
|
|
||||||
- **Monitor routing performance**: Review routing decisions regularly and refine criteria based on actual usage patterns
|
|
||||||
- **Choose appropriate models**: Use models with strong reasoning capabilities for complex routing decisions
|
|
||||||
@@ -1,259 +0,0 @@
|
|||||||
---
|
|
||||||
title: Workflow
|
|
||||||
description: Execute other workflows as reusable components within your current workflow
|
|
||||||
---
|
|
||||||
|
|
||||||
import { Callout } from 'fumadocs-ui/components/callout'
|
|
||||||
import { Step, Steps } from 'fumadocs-ui/components/steps'
|
|
||||||
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
|
|
||||||
import { ThemeImage } from '@/components/ui/theme-image'
|
|
||||||
|
|
||||||
The Workflow block allows you to execute other workflows as reusable components within your current workflow. This powerful feature enables modular design, code reuse, and the creation of complex nested workflows that can be composed from smaller, focused workflows.
|
|
||||||
|
|
||||||
<ThemeImage
|
|
||||||
lightSrc="/static/light/workflow-light.png"
|
|
||||||
darkSrc="/static/dark/workflow-dark.png"
|
|
||||||
alt="Workflow Block"
|
|
||||||
width={300}
|
|
||||||
height={175}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Callout type="info">
|
|
||||||
Workflow blocks enable modular design by allowing you to compose complex workflows from smaller, reusable components.
|
|
||||||
</Callout>
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
The Workflow block serves as a bridge between workflows, enabling you to:
|
|
||||||
|
|
||||||
<Steps>
|
|
||||||
<Step>
|
|
||||||
<strong>Reuse existing workflows</strong>: Execute previously created workflows as components within new workflows
|
|
||||||
</Step>
|
|
||||||
<Step>
|
|
||||||
<strong>Create modular designs</strong>: Break down complex processes into smaller, manageable workflows
|
|
||||||
</Step>
|
|
||||||
<Step>
|
|
||||||
<strong>Maintain separation of concerns</strong>: Keep different business logic isolated in separate workflows
|
|
||||||
</Step>
|
|
||||||
<Step>
|
|
||||||
<strong>Enable team collaboration</strong>: Share and reuse workflows across different projects and team members
|
|
||||||
</Step>
|
|
||||||
</Steps>
|
|
||||||
|
|
||||||
## How It Works
|
|
||||||
|
|
||||||
The Workflow block:
|
|
||||||
|
|
||||||
1. Takes a reference to another workflow in your workspace
|
|
||||||
2. Passes input data from the current workflow to the child workflow
|
|
||||||
3. Executes the child workflow in an isolated context
|
|
||||||
4. Returns the results back to the parent workflow for further processing
|
|
||||||
|
|
||||||
## Configuration Options
|
|
||||||
|
|
||||||
### Workflow Selection
|
|
||||||
|
|
||||||
Choose which workflow to execute from a dropdown list of available workflows in your workspace. The list includes:
|
|
||||||
|
|
||||||
- All workflows you have access to in the current workspace
|
|
||||||
- Workflows shared with you by other team members
|
|
||||||
- Both enabled and disabled workflows (though only enabled workflows can be executed)
|
|
||||||
|
|
||||||
### Input Data
|
|
||||||
|
|
||||||
Define the data to pass to the child workflow:
|
|
||||||
|
|
||||||
- **Single Variable Input**: Select a variable or block output to pass to the child workflow
|
|
||||||
- **Variable References**: Use `<variable.name>` to reference workflow variables
|
|
||||||
- **Block References**: Use `<blockName.field>` to reference outputs from previous blocks
|
|
||||||
- **Automatic Mapping**: The selected data is automatically available as `start.input` in the child workflow
|
|
||||||
- **Optional**: The input field is optional - child workflows can run without input data
|
|
||||||
- **Type Preservation**: Variable types (strings, numbers, objects, etc.) are preserved when passed to the child workflow
|
|
||||||
|
|
||||||
### Accessing Results
|
|
||||||
|
|
||||||
After a workflow executes, you can access its outputs:
|
|
||||||
|
|
||||||
- **`<workflow.response>`**: The complete output from the child workflow
|
|
||||||
- **`<workflow.name>`**: The name of the executed child workflow
|
|
||||||
- **`<workflow.success>`**: Boolean indicating successful completion
|
|
||||||
- **`<workflow.error>`**: Error details if the workflow failed
|
|
||||||
- **`<workflow.execution_time>`**: Time taken to execute the workflow
|
|
||||||
|
|
||||||
### Execution Context
|
|
||||||
|
|
||||||
The child workflow executes with:
|
|
||||||
|
|
||||||
- Its own isolated execution context
|
|
||||||
- Access to the same workspace resources (API keys, environment variables)
|
|
||||||
- Proper workspace membership and permission checks
|
|
||||||
- Independent logging and monitoring
|
|
||||||
|
|
||||||
## Safety and Limitations
|
|
||||||
|
|
||||||
To prevent infinite recursion and ensure system stability, the Workflow block includes several safety mechanisms:
|
|
||||||
|
|
||||||
<Callout type="warning">
|
|
||||||
**Cycle Detection**: The system automatically detects and prevents circular dependencies between workflows to avoid infinite loops.
|
|
||||||
</Callout>
|
|
||||||
|
|
||||||
- **Maximum Depth Limit**: Nested workflows are limited to a maximum depth of 10 levels
|
|
||||||
- **Cycle Detection**: Automatic detection and prevention of circular workflow dependencies
|
|
||||||
- **Timeout Protection**: Child workflows inherit timeout settings to prevent indefinite execution
|
|
||||||
- **Resource Limits**: Memory and execution time limits apply to prevent resource exhaustion
|
|
||||||
|
|
||||||
## Advanced Features
|
|
||||||
|
|
||||||
### Dynamic Workflow Selection
|
|
||||||
|
|
||||||
Select workflows dynamically based on runtime conditions:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// In a Function block before the Workflow block
|
|
||||||
const workflowId = <condition.result> ? 'premium-workflow' : 'standard-workflow';
|
|
||||||
return { selectedWorkflow: workflowId };
|
|
||||||
```
|
|
||||||
|
|
||||||
### Error Handling and Fallbacks
|
|
||||||
|
|
||||||
Implement robust error handling for child workflows:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// In a Function block after the Workflow block
|
|
||||||
if (!<workflow.success>) {
|
|
||||||
console.error('Child workflow failed:', <workflow.error>);
|
|
||||||
// Implement fallback logic
|
|
||||||
return { fallback: true, error: <workflow.error> };
|
|
||||||
}
|
|
||||||
return <workflow.response>;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Workflow Chaining
|
|
||||||
|
|
||||||
Chain multiple workflows together:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Pass output from one workflow to another
|
|
||||||
Workflow 1 Input: <start.input>
|
|
||||||
Workflow 2 Input: <workflow1.response>
|
|
||||||
Workflow 3 Input: <workflow2.response>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Inputs and Outputs
|
|
||||||
|
|
||||||
<Tabs items={['Configuration', 'Variables', 'Results']}>
|
|
||||||
<Tab>
|
|
||||||
<ul className="list-disc space-y-2 pl-6">
|
|
||||||
<li>
|
|
||||||
<strong>Workflow Selection</strong>: Choose which workflow to execute
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Input Data</strong>: Variable or block reference to pass to child workflow
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Execution Context</strong>: Isolated environment with workspace resources
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</Tab>
|
|
||||||
<Tab>
|
|
||||||
<ul className="list-disc space-y-2 pl-6">
|
|
||||||
<li>
|
|
||||||
<strong>workflow.response</strong>: Complete output from child workflow
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>workflow.name</strong>: Name of executed child workflow
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>workflow.success</strong>: Boolean indicating completion status
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>workflow.error</strong>: Error details if workflow failed
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>workflow.execution_time</strong>: Time taken to execute
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</Tab>
|
|
||||||
<Tab>
|
|
||||||
<ul className="list-disc space-y-2 pl-6">
|
|
||||||
<li>
|
|
||||||
<strong>Workflow Response</strong>: Primary output from child workflow
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Execution Status</strong>: Success status and error information
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Access</strong>: Available in blocks after the workflow
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</Tab>
|
|
||||||
</Tabs>
|
|
||||||
|
|
||||||
## Example Use Cases
|
|
||||||
|
|
||||||
### Modular Customer Onboarding
|
|
||||||
|
|
||||||
<div className="mb-4 rounded-md border p-4">
|
|
||||||
<h4 className="font-medium">Scenario: Break down complex onboarding into reusable components</h4>
|
|
||||||
<ol className="list-decimal pl-5 text-sm">
|
|
||||||
<li>Main workflow receives customer data</li>
|
|
||||||
<li>Workflow block executes validation workflow</li>
|
|
||||||
<li>Workflow block executes account setup workflow</li>
|
|
||||||
<li>Workflow block executes welcome email workflow</li>
|
|
||||||
</ol>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
### Microservice Architecture
|
|
||||||
|
|
||||||
<div className="mb-4 rounded-md border p-4">
|
|
||||||
<h4 className="font-medium">Scenario: Create independent service workflows</h4>
|
|
||||||
<ol className="list-decimal pl-5 text-sm">
|
|
||||||
<li>Payment processing workflow handles transactions</li>
|
|
||||||
<li>Inventory management workflow updates stock</li>
|
|
||||||
<li>Notification workflow sends confirmations</li>
|
|
||||||
<li>Main workflow orchestrates all services</li>
|
|
||||||
</ol>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
### Conditional Processing
|
|
||||||
|
|
||||||
<div className="mb-4 rounded-md border p-4">
|
|
||||||
<h4 className="font-medium">Scenario: Execute different workflows based on conditions</h4>
|
|
||||||
<ol className="list-decimal pl-5 text-sm">
|
|
||||||
<li>Condition block evaluates user type</li>
|
|
||||||
<li>Enterprise users → Complex approval workflow</li>
|
|
||||||
<li>Standard users → Simple approval workflow</li>
|
|
||||||
<li>Free users → Basic processing workflow</li>
|
|
||||||
</ol>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
### Example: Customer Validation Workflow
|
|
||||||
|
|
||||||
```javascript title="validation-workflow.js"
|
|
||||||
// Main workflow passes customer data to validation workflow
|
|
||||||
const customerData = <start.input>;
|
|
||||||
|
|
||||||
// Validation workflow processes the data
|
|
||||||
const emailValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(customerData.email);
|
|
||||||
const phoneValid = /^\+?[1-9]\d{1,14}$/.test(customerData.phone);
|
|
||||||
|
|
||||||
return {
|
|
||||||
customer: customerData,
|
|
||||||
validation: {
|
|
||||||
email: emailValid,
|
|
||||||
phone: phoneValid,
|
|
||||||
overall: emailValid && phoneValid
|
|
||||||
}
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
## Best Practices
|
|
||||||
|
|
||||||
- **Keep workflows focused**: Design child workflows to handle specific, well-defined tasks with clear inputs and outputs
|
|
||||||
- **Minimize nesting depth**: Avoid deeply nested workflow hierarchies for better maintainability and performance
|
|
||||||
- **Handle errors gracefully**: Implement proper error handling for child workflow failures and provide fallback mechanisms
|
|
||||||
- **Document dependencies**: Clearly document which workflows depend on others and maintain dependency maps
|
|
||||||
- **Test independently**: Ensure child workflows can be tested and validated independently from parent workflows
|
|
||||||
- **Monitor performance**: Be aware that nested workflows can impact overall execution time and resource usage
|
|
||||||
- **Use semantic naming**: Give workflows descriptive names that clearly indicate their purpose and functionality
|
|
||||||
@@ -1,190 +0,0 @@
|
|||||||
---
|
|
||||||
title: Accessing Connected Data
|
|
||||||
description: Techniques for accessing and manipulating data from connected blocks
|
|
||||||
---
|
|
||||||
|
|
||||||
import { Callout } from 'fumadocs-ui/components/callout'
|
|
||||||
import { File, Files, Folder } from 'fumadocs-ui/components/files'
|
|
||||||
|
|
||||||
Once blocks are connected, you can access data from source blocks in destination blocks using connection tags and various data access techniques.
|
|
||||||
|
|
||||||
## Basic Data Access
|
|
||||||
|
|
||||||
The simplest way to access data is through direct references using connection tags:
|
|
||||||
|
|
||||||
<Files>
|
|
||||||
<File name="Simple Property" annotation="<block.content>" />
|
|
||||||
<File name="Nested Property" annotation="<block.tokens.total>" />
|
|
||||||
<File name="Array Element" annotation="<block.items[0].name>" />
|
|
||||||
<File name="Complex Path" annotation="<block.data.users[2].profile.email>" />
|
|
||||||
</Files>
|
|
||||||
|
|
||||||
## Advanced Data Access Techniques
|
|
||||||
|
|
||||||
### Array Access
|
|
||||||
|
|
||||||
You can access array elements using square bracket notation:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Access the first item in an array
|
|
||||||
<block.items[0]>
|
|
||||||
|
|
||||||
// Access a specific property of an array item
|
|
||||||
<block.items[2].name>
|
|
||||||
|
|
||||||
// Access the last item in an array (in Function blocks)
|
|
||||||
const items = input.block.items;
|
|
||||||
const lastItem = items[items.length - 1];
|
|
||||||
```
|
|
||||||
|
|
||||||
### Object Property Access
|
|
||||||
|
|
||||||
Access object properties using dot notation:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Access a simple property
|
|
||||||
<block.content>
|
|
||||||
|
|
||||||
// Access a nested property
|
|
||||||
<block.data.user.profile.name>
|
|
||||||
|
|
||||||
// Access a property with special characters (in Function blocks)
|
|
||||||
const data = input.block.data;
|
|
||||||
const specialProp = data['property-with-dashes'];
|
|
||||||
```
|
|
||||||
|
|
||||||
### Dynamic References
|
|
||||||
|
|
||||||
Connection references are evaluated at runtime, allowing for dynamic data flow through your workflow:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// In a Function block, you can access connected data
|
|
||||||
const userName = input.userBlock.name;
|
|
||||||
const orderTotal = input.apiBlock.body.order.total;
|
|
||||||
|
|
||||||
// Process the data
|
|
||||||
const discount = orderTotal > 100 ? 0.1 : 0;
|
|
||||||
const finalPrice = orderTotal * (1 - discount);
|
|
||||||
|
|
||||||
// Return the result
|
|
||||||
return {
|
|
||||||
userName,
|
|
||||||
originalTotal: orderTotal,
|
|
||||||
discount: discount * 100 + '%',
|
|
||||||
finalPrice
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
## Data Transformation
|
|
||||||
|
|
||||||
### Using Function Blocks
|
|
||||||
|
|
||||||
Function blocks are the most powerful way to transform data between connections:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Example: Transform API response data
|
|
||||||
const apiResponse = input.apiBlock.data;
|
|
||||||
const transformedData = {
|
|
||||||
users: apiResponse.results.map(user => ({
|
|
||||||
id: user.id,
|
|
||||||
fullName: `${user.firstName} ${user.lastName}`,
|
|
||||||
email: user.email.toLowerCase(),
|
|
||||||
isActive: user.status === 'active'
|
|
||||||
})),
|
|
||||||
totalCount: apiResponse.count,
|
|
||||||
timestamp: new Date().toISOString()
|
|
||||||
};
|
|
||||||
|
|
||||||
return transformedData;
|
|
||||||
```
|
|
||||||
|
|
||||||
### String Interpolation
|
|
||||||
|
|
||||||
You can combine connection tags with static text:
|
|
||||||
|
|
||||||
```
|
|
||||||
Hello, <userBlock.name>! Your order #<orderBlock.id> has been processed.
|
|
||||||
```
|
|
||||||
|
|
||||||
### Conditional Content
|
|
||||||
|
|
||||||
In Function blocks, you can create conditional content based on connected data:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const user = input.userBlock;
|
|
||||||
const orderTotal = input.orderBlock.total;
|
|
||||||
|
|
||||||
let message = `Thank you for your order, ${user.name}!`;
|
|
||||||
|
|
||||||
if (orderTotal > 100) {
|
|
||||||
message += " You've qualified for free shipping!";
|
|
||||||
} else {
|
|
||||||
message += ` Add $${(100 - orderTotal).toFixed(2)} more to qualify for free shipping.`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { message };
|
|
||||||
```
|
|
||||||
|
|
||||||
## Handling Missing Data
|
|
||||||
|
|
||||||
It's important to handle cases where connected data might be missing or null:
|
|
||||||
|
|
||||||
<Callout type="warning">
|
|
||||||
Always validate connected data before using it, especially when accessing nested properties or
|
|
||||||
array elements.
|
|
||||||
</Callout>
|
|
||||||
|
|
||||||
### Default Values
|
|
||||||
|
|
||||||
In Function blocks, you can provide default values for missing data:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const userName = input.userBlock?.name || 'Guest'
|
|
||||||
const items = input.orderBlock?.items || []
|
|
||||||
const total = input.orderBlock?.total ?? 0
|
|
||||||
```
|
|
||||||
|
|
||||||
### Conditional Checks
|
|
||||||
|
|
||||||
Check if data exists before accessing nested properties:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
let userEmail = 'No email provided'
|
|
||||||
if (input.userBlock && input.userBlock.contact && input.userBlock.contact.email) {
|
|
||||||
userEmail = input.userBlock.contact.email
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Optional Chaining
|
|
||||||
|
|
||||||
In Function blocks, use optional chaining to safely access nested properties:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const userCity = input.userBlock?.address?.city
|
|
||||||
const firstItemName = input.orderBlock?.items?.[0]?.name
|
|
||||||
```
|
|
||||||
|
|
||||||
## Debugging Connection Data
|
|
||||||
|
|
||||||
When troubleshooting connection issues, these techniques can help:
|
|
||||||
|
|
||||||
1. **Log Data**: In Function blocks, use `console.log()` to inspect connected data
|
|
||||||
2. **Return Full Objects**: Return the full input object to see all available data
|
|
||||||
3. **Check Types**: Verify the data types of connected values
|
|
||||||
4. **Validate Paths**: Ensure you're using the correct path to access nested data
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Example debugging function
|
|
||||||
function debugConnections() {
|
|
||||||
console.log('All inputs:', input)
|
|
||||||
console.log('User data type:', typeof input.userBlock)
|
|
||||||
console.log('Order items:', input.orderBlock?.items)
|
|
||||||
|
|
||||||
return {
|
|
||||||
debug: true,
|
|
||||||
allInputs: input,
|
|
||||||
userExists: !!input.userBlock,
|
|
||||||
orderItemCount: input.orderBlock?.items?.length || 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
---
|
|
||||||
title: Connection Basics
|
|
||||||
description: Learn how connections work in Sim
|
|
||||||
---
|
|
||||||
|
|
||||||
import { Callout } from 'fumadocs-ui/components/callout'
|
|
||||||
import { Step, Steps } from 'fumadocs-ui/components/steps'
|
|
||||||
|
|
||||||
## How Connections Work
|
|
||||||
|
|
||||||
Connections are the pathways that allow data to flow between blocks in your workflow. When you connect two blocks in Sim, you're establishing a data flow relationship that defines how information passes from one block to another.
|
|
||||||
|
|
||||||
<Callout type="info">
|
|
||||||
Each connection represents a directed relationship where data flows from a source block's output
|
|
||||||
to a destination block's input.
|
|
||||||
</Callout>
|
|
||||||
|
|
||||||
### Creating Connections
|
|
||||||
|
|
||||||
<Steps>
|
|
||||||
<Step>
|
|
||||||
<strong>Select Source Block</strong>: Click on the output port of the block you want to connect
|
|
||||||
from
|
|
||||||
</Step>
|
|
||||||
<Step>
|
|
||||||
<strong>Draw Connection</strong>: Drag to the input port of the destination block
|
|
||||||
</Step>
|
|
||||||
<Step>
|
|
||||||
<strong>Confirm Connection</strong>: Release to create the connection
|
|
||||||
</Step>
|
|
||||||
<Step>
|
|
||||||
<strong>Configure (Optional)</strong>: Some connections may require additional configuration
|
|
||||||
</Step>
|
|
||||||
</Steps>
|
|
||||||
|
|
||||||
### Connection Flow
|
|
||||||
|
|
||||||
The flow of data through connections follows these principles:
|
|
||||||
|
|
||||||
1. **Directional Flow**: Data always flows from outputs to inputs
|
|
||||||
2. **Execution Order**: Blocks execute in order based on their connections
|
|
||||||
3. **Data Transformation**: Data may be transformed as it passes between blocks
|
|
||||||
4. **Conditional Paths**: Some blocks (like Router and Condition) can direct flow to different paths
|
|
||||||
|
|
||||||
### Connection Visualization
|
|
||||||
|
|
||||||
Connections are visually represented in the workflow editor:
|
|
||||||
|
|
||||||
- **Solid Lines**: Active connections that will pass data
|
|
||||||
- **Animated Flow**: During execution, data flow is visualized along connections
|
|
||||||
- **Color Coding**: Different connection types may have different colors
|
|
||||||
- **Connection Tags**: Visual indicators showing what data is available
|
|
||||||
|
|
||||||
### Managing Connections
|
|
||||||
|
|
||||||
You can manage your connections in several ways:
|
|
||||||
|
|
||||||
- **Delete**: Click on a connection and press Delete or use the context menu
|
|
||||||
- **Reroute**: Drag a connection to change its path
|
|
||||||
- **Inspect**: Click on a connection to see details about the data being passed
|
|
||||||
- **Disable**: Temporarily disable a connection without deleting it
|
|
||||||
|
|
||||||
<Callout type="warning">
|
|
||||||
Deleting a connection will immediately stop data flow between the blocks. Make sure this is
|
|
||||||
intended before removing connections.
|
|
||||||
</Callout>
|
|
||||||
|
|
||||||
## Connection Compatibility
|
|
||||||
|
|
||||||
Not all blocks can be connected to each other. Compatibility depends on:
|
|
||||||
|
|
||||||
1. **Data Type Compatibility**: The output type must be compatible with the input type
|
|
||||||
2. **Block Restrictions**: Some blocks may have restrictions on what they can connect to
|
|
||||||
3. **Workflow Logic**: Connections must make logical sense in the context of your workflow
|
|
||||||
|
|
||||||
The editor will indicate when connections are invalid or incompatible.
|
|
||||||
@@ -1,208 +0,0 @@
|
|||||||
---
|
|
||||||
title: Connection Best Practices
|
|
||||||
description: Recommended patterns for effective connection management
|
|
||||||
---
|
|
||||||
|
|
||||||
import { Callout } from 'fumadocs-ui/components/callout'
|
|
||||||
import { Step, Steps } from 'fumadocs-ui/components/steps'
|
|
||||||
|
|
||||||
## Workflow Organization
|
|
||||||
|
|
||||||
### Organize Your Connections
|
|
||||||
|
|
||||||
Keep your workflow clean and understandable by organizing connections logically:
|
|
||||||
|
|
||||||
- **Minimize crossing connections** when possible to reduce visual complexity
|
|
||||||
- **Group related blocks together** to make data flow more intuitive
|
|
||||||
- **Use consistent flow direction** (typically left-to-right or top-to-bottom)
|
|
||||||
- **Label complex connections** with descriptive names
|
|
||||||
|
|
||||||
<Callout type="info">
|
|
||||||
A well-organized workflow is easier to understand, debug, and maintain. Take time to arrange your
|
|
||||||
blocks and connections in a logical manner.
|
|
||||||
</Callout>
|
|
||||||
|
|
||||||
### Connection Naming Conventions
|
|
||||||
|
|
||||||
When working with multiple connections, consistent naming helps maintain clarity:
|
|
||||||
|
|
||||||
<Steps>
|
|
||||||
<Step>
|
|
||||||
<strong>Use descriptive block names</strong>: Name blocks based on their function (e.g.,
|
|
||||||
"UserDataFetcher", "ResponseGenerator")
|
|
||||||
</Step>
|
|
||||||
<Step>
|
|
||||||
<strong>Be specific with connection references</strong>: Use clear variable names when
|
|
||||||
referencing connections in code
|
|
||||||
</Step>
|
|
||||||
<Step>
|
|
||||||
<strong>Document complex connections</strong>: Add comments explaining non-obvious data
|
|
||||||
transformations
|
|
||||||
</Step>
|
|
||||||
</Steps>
|
|
||||||
|
|
||||||
## Data Validation
|
|
||||||
|
|
||||||
### Validate Data Flow
|
|
||||||
|
|
||||||
Ensure that the data being passed between blocks is compatible:
|
|
||||||
|
|
||||||
- **Check that required fields are available** in the source block
|
|
||||||
- **Verify data types match expectations** before using them
|
|
||||||
- **Use Function blocks to transform data** when necessary
|
|
||||||
- **Handle missing or null values** with default values or conditional logic
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Example: Validating and transforming data in a Function block
|
|
||||||
function processUserData() {
|
|
||||||
// Validate required fields
|
|
||||||
if (!input.userBlock || !input.userBlock.id) {
|
|
||||||
return { error: 'Missing user data', valid: false }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transform and validate data types
|
|
||||||
const userId = String(input.userBlock.id)
|
|
||||||
const userName = input.userBlock.name || 'Unknown User'
|
|
||||||
const userScore = Number(input.userBlock.score) || 0
|
|
||||||
|
|
||||||
return {
|
|
||||||
valid: true,
|
|
||||||
user: {
|
|
||||||
id: userId,
|
|
||||||
name: userName,
|
|
||||||
score: userScore,
|
|
||||||
isHighScore: userScore > 100,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Documentation
|
|
||||||
|
|
||||||
### Document Connection Purpose
|
|
||||||
|
|
||||||
Add comments or descriptions to clarify the purpose of connections, especially in complex workflows:
|
|
||||||
|
|
||||||
- **What data is being passed**: Document the key fields and their purpose
|
|
||||||
- **Why this connection exists**: Explain the relationship between blocks
|
|
||||||
- **Any transformations or conditions applied**: Note any data processing that occurs
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Example: Documenting connection purpose in a Function block
|
|
||||||
/*
|
|
||||||
* This function processes user data from the UserFetcher block
|
|
||||||
* and order history from the OrderHistory block to generate
|
|
||||||
* personalized product recommendations.
|
|
||||||
*
|
|
||||||
* Input:
|
|
||||||
* - userBlock: User profile data (id, preferences, history)
|
|
||||||
* - orderBlock: Recent order history (items, dates, amounts)
|
|
||||||
*
|
|
||||||
* Output:
|
|
||||||
* - recommendations: Array of recommended product IDs
|
|
||||||
* - userSegment: Calculated user segment for marketing
|
|
||||||
* - conversionProbability: Estimated likelihood of purchase
|
|
||||||
*/
|
|
||||||
function generateRecommendations() {
|
|
||||||
// Implementation...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Testing and Debugging
|
|
||||||
|
|
||||||
### Test Connection References
|
|
||||||
|
|
||||||
Verify that connection references work as expected:
|
|
||||||
|
|
||||||
- **Test with different input values** to ensure robustness
|
|
||||||
- **Check edge cases** (empty values, large datasets, special characters)
|
|
||||||
- **Ensure error handling for missing or invalid data**
|
|
||||||
- **Use console logging in Function blocks** to debug connection issues
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Example: Testing connection references with edge cases
|
|
||||||
function testConnections() {
|
|
||||||
console.log('Testing connections...')
|
|
||||||
|
|
||||||
// Log all inputs for debugging
|
|
||||||
console.log('All inputs:', JSON.stringify(input, null, 2))
|
|
||||||
|
|
||||||
// Test for missing data
|
|
||||||
const hasUserData = !!input.userBlock
|
|
||||||
console.log('Has user data:', hasUserData)
|
|
||||||
|
|
||||||
// Test edge cases
|
|
||||||
const items = input.orderBlock?.items || []
|
|
||||||
console.log('Item count:', items.length)
|
|
||||||
console.log('Empty items test:', items.length === 0 ? 'Passed' : 'Failed')
|
|
||||||
|
|
||||||
// Return test results
|
|
||||||
return {
|
|
||||||
tests: {
|
|
||||||
hasUserData,
|
|
||||||
hasItems: items.length > 0,
|
|
||||||
hasLargeOrder: items.length > 10,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Performance Considerations
|
|
||||||
|
|
||||||
### Optimize Data Flow
|
|
||||||
|
|
||||||
Keep your workflows efficient by optimizing how data flows through connections:
|
|
||||||
|
|
||||||
- **Pass only necessary data** between blocks to reduce memory usage
|
|
||||||
- **Use Function blocks to filter large datasets** before passing them on
|
|
||||||
- **Consider caching results** for expensive operations
|
|
||||||
- **Break complex workflows into smaller, reusable components**
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Example: Optimizing data flow by filtering
|
|
||||||
function optimizeUserData() {
|
|
||||||
const userData = input.userBlock
|
|
||||||
|
|
||||||
// Only pass necessary fields to downstream blocks
|
|
||||||
return {
|
|
||||||
id: userData.id,
|
|
||||||
name: userData.name,
|
|
||||||
email: userData.email,
|
|
||||||
// Filter out unnecessary profile data, history, etc.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Security Best Practices
|
|
||||||
|
|
||||||
### Secure Sensitive Data
|
|
||||||
|
|
||||||
Protect sensitive information when using connections:
|
|
||||||
|
|
||||||
- **Never expose API keys or credentials** in connection data
|
|
||||||
- **Sanitize user input** before processing it
|
|
||||||
- **Redact sensitive information** when logging connection data
|
|
||||||
- **Use secure connections** for external API calls
|
|
||||||
|
|
||||||
<Callout type="warning">
|
|
||||||
Be careful when logging connection data that might contain sensitive information. Always redact or
|
|
||||||
mask sensitive fields like passwords, API keys, or personal information.
|
|
||||||
</Callout>
|
|
||||||
|
|
||||||
## Advanced Patterns
|
|
||||||
|
|
||||||
### Conditional Connections
|
|
||||||
|
|
||||||
Use Condition blocks to create dynamic workflows:
|
|
||||||
|
|
||||||
- **Route data based on content** to different processing paths
|
|
||||||
- **Implement fallback paths** for error handling
|
|
||||||
- **Create decision trees** for complex business logic
|
|
||||||
|
|
||||||
### Feedback Loops
|
|
||||||
|
|
||||||
Create more sophisticated workflows with feedback connections:
|
|
||||||
|
|
||||||
- **Implement iterative processing** by connecting later blocks back to earlier ones
|
|
||||||
- **Use Memory blocks** to store state between iterations
|
|
||||||
- **Set termination conditions** to prevent infinite loops
|
|
||||||
@@ -1,190 +0,0 @@
|
|||||||
---
|
|
||||||
title: Connection Data Structure
|
|
||||||
description: Understanding the data structure of different block outputs
|
|
||||||
---
|
|
||||||
|
|
||||||
import { Callout } from 'fumadocs-ui/components/callout'
|
|
||||||
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
|
|
||||||
|
|
||||||
When you connect blocks, the output data structure from the source block determines what values are available in the destination block. Each block type produces a specific output structure that you can reference in downstream blocks.
|
|
||||||
|
|
||||||
<Callout type="info">
|
|
||||||
Understanding these data structures is essential for effectively using connection tags and
|
|
||||||
accessing the right data in your workflows.
|
|
||||||
</Callout>
|
|
||||||
|
|
||||||
## Block Output Structures
|
|
||||||
|
|
||||||
Different block types produce different output structures. Here's what you can expect from each block type:
|
|
||||||
|
|
||||||
<Tabs items={['Agent Output', 'API Output', 'Function Output', 'Evaluator Output', 'Condition Output', 'Router Output']}>
|
|
||||||
<Tab>
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"content": "The generated text response",
|
|
||||||
"model": "gpt-4o",
|
|
||||||
"tokens": {
|
|
||||||
"prompt": 120,
|
|
||||||
"completion": 85,
|
|
||||||
"total": 205
|
|
||||||
},
|
|
||||||
"toolCalls": [...],
|
|
||||||
"cost": [...],
|
|
||||||
"usage": [...]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Agent Block Output Fields
|
|
||||||
|
|
||||||
- **content**: The main text response generated by the agent
|
|
||||||
- **model**: The AI model used (e.g., "gpt-4o", "claude-3-opus")
|
|
||||||
- **tokens**: Token usage statistics
|
|
||||||
- **prompt**: Number of tokens in the prompt
|
|
||||||
- **completion**: Number of tokens in the completion
|
|
||||||
- **total**: Total tokens used
|
|
||||||
- **toolCalls**: Array of tool calls made by the agent (if any)
|
|
||||||
- **cost**: Array of cost objects for each tool call (if any)
|
|
||||||
- **usage**: Token usage statistics for the entire response
|
|
||||||
|
|
||||||
</Tab>
|
|
||||||
<Tab>
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"data": "Response data",
|
|
||||||
"status": 200,
|
|
||||||
"headers": {
|
|
||||||
"content-type": "application/json",
|
|
||||||
"cache-control": "no-cache"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### API Block Output Fields
|
|
||||||
|
|
||||||
- **data**: The response data from the API (can be any type)
|
|
||||||
- **status**: HTTP status code of the response
|
|
||||||
- **headers**: HTTP headers returned by the API
|
|
||||||
|
|
||||||
</Tab>
|
|
||||||
<Tab>
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"result": "Function return value",
|
|
||||||
"stdout": "Console output",
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Function Block Output Fields
|
|
||||||
|
|
||||||
- **result**: The return value of the function (can be any type)
|
|
||||||
- **stdout**: Console output captured during function execution
|
|
||||||
|
|
||||||
</Tab>
|
|
||||||
<Tab>
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"content": "Evaluation summary",
|
|
||||||
"model": "gpt-5",
|
|
||||||
"tokens": {
|
|
||||||
"prompt": 120,
|
|
||||||
"completion": 85,
|
|
||||||
"total": 205
|
|
||||||
},
|
|
||||||
"metric1": 8.5,
|
|
||||||
"metric2": 7.2,
|
|
||||||
"metric3": 9.0
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Evaluator Block Output Fields
|
|
||||||
|
|
||||||
- **content**: Summary of the evaluation
|
|
||||||
- **model**: The AI model used for evaluation
|
|
||||||
- **tokens**: Token usage statistics
|
|
||||||
- **[metricName]**: Score for each metric defined in the evaluator (dynamic fields)
|
|
||||||
|
|
||||||
</Tab>
|
|
||||||
<Tab>
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"content": "Original content passed through",
|
|
||||||
"conditionResult": true,
|
|
||||||
"selectedPath": {
|
|
||||||
"blockId": "2acd9007-27e8-4510-a487-73d3b825e7c1",
|
|
||||||
"blockType": "agent",
|
|
||||||
"blockTitle": "Follow-up Agent"
|
|
||||||
},
|
|
||||||
"selectedConditionId": "condition-1"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Condition Block Output Fields
|
|
||||||
|
|
||||||
- **content**: The original content passed through
|
|
||||||
- **conditionResult**: Boolean result of the condition evaluation
|
|
||||||
- **selectedPath**: Information about the selected path
|
|
||||||
- **blockId**: ID of the next block in the selected path
|
|
||||||
- **blockType**: Type of the next block
|
|
||||||
- **blockTitle**: Title of the next block
|
|
||||||
- **selectedConditionId**: ID of the selected condition
|
|
||||||
|
|
||||||
</Tab>
|
|
||||||
<Tab>
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"content": "Routing decision",
|
|
||||||
"model": "gpt-4o",
|
|
||||||
"tokens": {
|
|
||||||
"prompt": 120,
|
|
||||||
"completion": 85,
|
|
||||||
"total": 205
|
|
||||||
},
|
|
||||||
"selectedPath": {
|
|
||||||
"blockId": "2acd9007-27e8-4510-a487-73d3b825e7c1",
|
|
||||||
"blockType": "agent",
|
|
||||||
"blockTitle": "Customer Service Agent"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Router Block Output Fields
|
|
||||||
|
|
||||||
- **content**: The routing decision text
|
|
||||||
- **model**: The AI model used for routing
|
|
||||||
- **tokens**: Token usage statistics
|
|
||||||
- **selectedPath**: Information about the selected path
|
|
||||||
- **blockId**: ID of the selected destination block
|
|
||||||
- **blockType**: Type of the selected block
|
|
||||||
- **blockTitle**: Title of the selected block
|
|
||||||
|
|
||||||
</Tab>
|
|
||||||
</Tabs>
|
|
||||||
|
|
||||||
## Custom Output Structures
|
|
||||||
|
|
||||||
Some blocks may produce custom output structures based on their configuration:
|
|
||||||
|
|
||||||
1. **Agent Blocks with Response Format**: When using a response format in an Agent block, the output structure will match the defined schema instead of the standard structure.
|
|
||||||
|
|
||||||
2. **Function Blocks**: The `result` field can contain any data structure returned by your function code.
|
|
||||||
|
|
||||||
3. **API Blocks**: The `data` field will contain whatever the API returns, which could be any valid JSON structure.
|
|
||||||
|
|
||||||
<Callout type="warning">
|
|
||||||
Always check the actual output structure of your blocks during development to ensure you're
|
|
||||||
referencing the correct fields in your connections.
|
|
||||||
</Callout>
|
|
||||||
|
|
||||||
## Nested Data Structures
|
|
||||||
|
|
||||||
Many block outputs contain nested data structures. You can access these using dot notation in connection tags:
|
|
||||||
|
|
||||||
```
|
|
||||||
<blockId.path.to.nested.data>
|
|
||||||
```
|
|
||||||
|
|
||||||
For example:
|
|
||||||
|
|
||||||
- `<agent1.tokens.total>` - Access the total tokens from an Agent block
|
|
||||||
- `<api1.data.results[0].id>` - Access the ID of the first result from an API response
|
|
||||||
- `<function1.result.calculations.total>` - Access a nested field in a Function block's result
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
---
|
|
||||||
title: Connections
|
|
||||||
description: Connect your blocks to one another.
|
|
||||||
---
|
|
||||||
|
|
||||||
import { Callout } from 'fumadocs-ui/components/callout'
|
|
||||||
import { Card, Cards } from 'fumadocs-ui/components/card'
|
|
||||||
import { ConnectIcon } from '@/components/icons'
|
|
||||||
import { Video } from '@/components/ui/video'
|
|
||||||
|
|
||||||
Connections are the pathways that allow data to flow between blocks in your workflow. They define how information is passed from one block to another, enabling you to create sophisticated, multi-step processes.
|
|
||||||
|
|
||||||
<Callout type="info">
|
|
||||||
Properly configured connections are essential for creating effective workflows. They determine how
|
|
||||||
data moves through your system and how blocks interact with each other.
|
|
||||||
</Callout>
|
|
||||||
|
|
||||||
<div className="mx-auto w-full overflow-hidden rounded-lg">
|
|
||||||
<Video src="connections.mp4" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
## Connection Types
|
|
||||||
|
|
||||||
Sim supports different types of connections that enable various workflow patterns:
|
|
||||||
|
|
||||||
<Cards>
|
|
||||||
<Card title="Connection Basics" href="/connections/basics">
|
|
||||||
Learn how connections work and how to create them in your workflows
|
|
||||||
</Card>
|
|
||||||
<Card title="Connection Tags" href="/connections/tags">
|
|
||||||
Understand how to use connection tags to reference data between blocks
|
|
||||||
</Card>
|
|
||||||
<Card title="Data Structure" href="/connections/data-structure">
|
|
||||||
Explore the output data structures of different block types
|
|
||||||
</Card>
|
|
||||||
<Card title="Accessing Data" href="/connections/accessing-data">
|
|
||||||
Learn techniques for accessing and manipulating connected data
|
|
||||||
</Card>
|
|
||||||
<Card title="Best Practices" href="/connections/best-practices">
|
|
||||||
Follow recommended patterns for effective connection management
|
|
||||||
</Card>
|
|
||||||
</Cards>
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"title": "Connections",
|
|
||||||
"pages": ["basics", "tags", "data-structure", "accessing-data", "best-practices"]
|
|
||||||
}
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
---
|
|
||||||
title: Connection Tags
|
|
||||||
description: Using connection tags to reference data between blocks
|
|
||||||
---
|
|
||||||
|
|
||||||
import { Callout } from 'fumadocs-ui/components/callout'
|
|
||||||
import { Video } from '@/components/ui/video'
|
|
||||||
|
|
||||||
Connection tags are visual representations of the data available from connected blocks. They provide an easy way to reference outputs from previous blocks in your workflow.
|
|
||||||
|
|
||||||
<div className="mx-auto w-full overflow-hidden rounded-lg">
|
|
||||||
<Video src="connections.mp4" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
### What Are Connection Tags?
|
|
||||||
|
|
||||||
Connection tags are interactive elements that appear when blocks are connected. They represent the data that can flow from one block to another and allow you to:
|
|
||||||
|
|
||||||
- Visualize available data from source blocks
|
|
||||||
- Reference specific data fields in destination blocks
|
|
||||||
- Create dynamic data flows between blocks
|
|
||||||
|
|
||||||
<Callout type="info">
|
|
||||||
Connection tags make it easy to see what data is available from previous blocks and use it in your
|
|
||||||
current block without having to remember complex data structures.
|
|
||||||
</Callout>
|
|
||||||
|
|
||||||
## Using Connection Tags
|
|
||||||
|
|
||||||
There are two primary ways to use connection tags in your workflows:
|
|
||||||
|
|
||||||
<div className="my-6 grid grid-cols-1 gap-4 md:grid-cols-2">
|
|
||||||
<div className="rounded-lg border border-gray-200 p-4 dark:border-gray-800">
|
|
||||||
<h3 className="mb-2 text-lg font-medium">Drag and Drop</h3>
|
|
||||||
<div className="text-sm text-gray-600 dark:text-gray-400">
|
|
||||||
Click on a connection tag and drag it into input fields of destination blocks. A dropdown will
|
|
||||||
appear showing available values.
|
|
||||||
</div>
|
|
||||||
<ol className="mt-2 list-decimal pl-5 text-sm text-gray-600 dark:text-gray-400">
|
|
||||||
<li>Hover over a connection tag to see available data</li>
|
|
||||||
<li>Click and drag the tag to an input field</li>
|
|
||||||
<li>Select the specific data field from the dropdown</li>
|
|
||||||
<li>The reference is inserted automatically</li>
|
|
||||||
</ol>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="rounded-lg border border-gray-200 p-4 dark:border-gray-800">
|
|
||||||
<h3 className="mb-2 text-lg font-medium">Angle Bracket Syntax</h3>
|
|
||||||
<div className="text-sm text-gray-600 dark:text-gray-400">
|
|
||||||
Type <code><></code> in input fields to see a dropdown of available connection values
|
|
||||||
from previous blocks.
|
|
||||||
</div>
|
|
||||||
<ol className="mt-2 list-decimal pl-5 text-sm text-gray-600 dark:text-gray-400">
|
|
||||||
<li>Click in any input field where you want to use connected data</li>
|
|
||||||
<li>
|
|
||||||
Type <code><></code> to trigger the connection dropdown
|
|
||||||
</li>
|
|
||||||
<li>Browse and select the data you want to reference</li>
|
|
||||||
<li>Continue typing or select from the dropdown to complete the reference</li>
|
|
||||||
</ol>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
## Tag Syntax
|
|
||||||
|
|
||||||
Connection tags use a simple syntax to reference data:
|
|
||||||
|
|
||||||
```
|
|
||||||
<blockId.path.to.data>
|
|
||||||
```
|
|
||||||
|
|
||||||
Where:
|
|
||||||
|
|
||||||
- `blockId` is the identifier of the source block
|
|
||||||
- `path.to.data` is the path to the specific data field
|
|
||||||
|
|
||||||
For example:
|
|
||||||
|
|
||||||
- `<agent1.content>` - References the content field from a block with ID "agent1"
|
|
||||||
- `<api2.data.users[0].name>` - References the name of the first user in the users array from the data field of a block with ID "api2"
|
|
||||||
|
|
||||||
## Dynamic Tag References
|
|
||||||
|
|
||||||
Connection tags are evaluated at runtime, which means:
|
|
||||||
|
|
||||||
1. They always reference the most current data
|
|
||||||
2. They can be used in expressions and combined with static text
|
|
||||||
3. They can be nested within other data structures
|
|
||||||
|
|
||||||
### Examples
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Reference in text
|
|
||||||
"The user's name is <userBlock.name>"
|
|
||||||
|
|
||||||
// Reference in JSON
|
|
||||||
{
|
|
||||||
"userName": "<userBlock.name>",
|
|
||||||
"orderTotal": <apiBlock.data.total>
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reference in code
|
|
||||||
const greeting = "Hello, <userBlock.name>!";
|
|
||||||
const total = <apiBlock.data.total> * 1.1; // Add 10% tax
|
|
||||||
```
|
|
||||||
|
|
||||||
<Callout type="warning">
|
|
||||||
When using connection tags in numeric contexts, make sure the referenced data is actually a number
|
|
||||||
to avoid type conversion issues.
|
|
||||||
</Callout>
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
---
|
|
||||||
title: Copilot
|
|
||||||
description: Build and edit workflows with Sim Copilot
|
|
||||||
---
|
|
||||||
|
|
||||||
import { Callout } from 'fumadocs-ui/components/callout'
|
|
||||||
import { Card, Cards } from 'fumadocs-ui/components/card'
|
|
||||||
import { MessageCircle, Package, Zap, Infinity as InfinityIcon, Brain, BrainCircuit } from 'lucide-react'
|
|
||||||
|
|
||||||
Copilot is your in-editor assistant that helps you build, understand, and improve workflows. It can:
|
|
||||||
|
|
||||||
- **Explain**: Answer questions about Sim and your current workflow
|
|
||||||
- **Guide**: Suggest edits and best practices
|
|
||||||
- **Edit**: Make changes to blocks, connections, and settings when you approve
|
|
||||||
|
|
||||||
<Callout type="info">
|
|
||||||
Copilot is a Sim-managed service. For self-hosted deployments, generate a Copilot API key in the hosted app (sim.ai → Settings → Copilot)
|
|
||||||
1. Go to [sim.ai](https://sim.ai) → Settings → Copilot and generate a Copilot API key
|
|
||||||
2. Set `COPILOT_API_KEY` in your self-hosted environment to that value
|
|
||||||
</Callout>
|
|
||||||
|
|
||||||
## Modes
|
|
||||||
|
|
||||||
<Cards>
|
|
||||||
<Card
|
|
||||||
title={
|
|
||||||
<span className="inline-flex items-center gap-2">
|
|
||||||
<MessageCircle className="h-4 w-4 text-muted-foreground" />
|
|
||||||
Ask
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div className="m-0 text-sm">
|
|
||||||
Q&A mode for explanations, guidance, and suggestions without making changes to your workflow.
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
<Card
|
|
||||||
title={
|
|
||||||
<span className="inline-flex items-center gap-2">
|
|
||||||
<Package className="h-4 w-4 text-muted-foreground" />
|
|
||||||
Agent
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div className="m-0 text-sm">
|
|
||||||
Build-and-edit mode. Copilot proposes specific edits (add blocks, wire variables, tweak settings) and applies them when you approve.
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
</Cards>
|
|
||||||
|
|
||||||
## Depth Levels
|
|
||||||
|
|
||||||
<Cards>
|
|
||||||
<Card
|
|
||||||
title={
|
|
||||||
<span className="inline-flex items-center gap-2">
|
|
||||||
<Zap className="h-4 w-4 text-muted-foreground" />
|
|
||||||
Fast
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div className="m-0 text-sm">Quickest and cheapest. Best for small edits, simple workflows, and minor tweaks.</div>
|
|
||||||
</Card>
|
|
||||||
<Card
|
|
||||||
title={
|
|
||||||
<span className="inline-flex items-center gap-2">
|
|
||||||
<InfinityIcon className="h-4 w-4 text-muted-foreground" />
|
|
||||||
Auto
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div className="m-0 text-sm">Balanced speed and reasoning. Recommended default for most tasks.</div>
|
|
||||||
</Card>
|
|
||||||
<Card
|
|
||||||
title={
|
|
||||||
<span className="inline-flex items-center gap-2">
|
|
||||||
<Brain className="h-4 w-4 text-muted-foreground" />
|
|
||||||
Advanced
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div className="m-0 text-sm">More reasoning for larger workflows and complex edits while staying performant.</div>
|
|
||||||
</Card>
|
|
||||||
<Card
|
|
||||||
title={
|
|
||||||
<span className="inline-flex items-center gap-2">
|
|
||||||
<BrainCircuit className="h-4 w-4 text-muted-foreground" />
|
|
||||||
Behemoth
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div className="m-0 text-sm">Maximum reasoning for deep planning, debugging, and complex architectural changes.</div>
|
|
||||||
</Card>
|
|
||||||
</Cards>
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"title": "Copilot",
|
|
||||||
"pages": ["index"]
|
|
||||||
}
|
|
||||||
154
apps/docs/content/docs/de/blocks/agent.mdx
Normal file
154
apps/docs/content/docs/de/blocks/agent.mdx
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
---
|
||||||
|
title: Agent
|
||||||
|
---
|
||||||
|
|
||||||
|
import { Callout } from 'fumadocs-ui/components/callout'
|
||||||
|
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
|
||||||
|
import { Image } from '@/components/ui/image'
|
||||||
|
|
||||||
|
Der Agent-Block verbindet deinen Workflow mit Large Language Models (LLMs). Er verarbeitet natürlichsprachliche Eingaben, ruft externe Tools auf und generiert strukturierte oder unstrukturierte Ausgaben.
|
||||||
|
|
||||||
|
<div className="flex justify-center">
|
||||||
|
<Image
|
||||||
|
src="/static/blocks/agent.png"
|
||||||
|
alt="Agent-Block-Konfiguration"
|
||||||
|
width={500}
|
||||||
|
height={400}
|
||||||
|
className="my-6"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
## Konfigurationsoptionen
|
||||||
|
|
||||||
|
### System-Prompt
|
||||||
|
|
||||||
|
Der System-Prompt legt die Betriebsparameter und Verhaltenseinschränkungen des Agenten fest. Diese Konfiguration definiert die Rolle des Agenten, die Antwortmethodik und die Verarbeitungsgrenzen für alle eingehenden Anfragen.
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
You are a helpful assistant that specializes in financial analysis.
|
||||||
|
Always provide clear explanations and cite sources when possible.
|
||||||
|
When responding to questions about investments, include risk disclaimers.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Benutzer-Prompt
|
||||||
|
|
||||||
|
Der Benutzer-Prompt stellt die primären Eingabedaten für die Inferenzverarbeitung dar. Dieser Parameter akzeptiert natürlichsprachlichen Text oder strukturierte Daten, die der Agent analysieren und auf die er reagieren wird. Zu den Eingabequellen gehören:
|
||||||
|
|
||||||
|
- **Statische Konfiguration**: Direkte Texteingabe, die in der Block-Konfiguration angegeben ist
|
||||||
|
- **Dynamische Eingabe**: Daten, die von vorgelagerten Blöcken über Verbindungsschnittstellen übergeben werden
|
||||||
|
- **Laufzeitgenerierung**: Programmatisch generierte Inhalte während der Workflow-Ausführung
|
||||||
|
|
||||||
|
### Modellauswahl
|
||||||
|
|
||||||
|
Der Agent-Block unterstützt mehrere LLM-Anbieter über eine einheitliche Inferenzschnittstelle. Verfügbare Modelle umfassen:
|
||||||
|
|
||||||
|
- **OpenAI**: GPT-5.1, GPT-5, GPT-4o, o1, o3, o4-mini, gpt-4.1
|
||||||
|
- **Anthropic**: Claude 4.5 Sonnet, Claude Opus 4.1
|
||||||
|
- **Google**: Gemini 2.5 Pro, Gemini 2.0 Flash
|
||||||
|
- **Andere Anbieter**: Groq, Cerebras, xAI, Azure OpenAI, OpenRouter
|
||||||
|
- **Lokale Modelle**: Ollama oder VLLM-kompatible Modelle
|
||||||
|
|
||||||
|
### Temperatur
|
||||||
|
|
||||||
|
Steuert die Zufälligkeit und Kreativität der Antworten:
|
||||||
|
|
||||||
|
- **Niedrig (0-0,3)**: Deterministisch und fokussiert. Am besten für faktische Aufgaben und Genauigkeit.
|
||||||
|
- **Mittel (0,3-0,7)**: Ausgewogene Kreativität und Fokus. Gut für allgemeine Verwendung.
|
||||||
|
- **Hoch (0,7-2,0)**: Kreativ und abwechslungsreich. Ideal für Brainstorming und Content-Generierung.
|
||||||
|
|
||||||
|
### API-Schlüssel
|
||||||
|
|
||||||
|
Ihr API-Schlüssel für den ausgewählten LLM-Anbieter. Dieser wird sicher gespeichert und für die Authentifizierung verwendet.
|
||||||
|
|
||||||
|
### Tools
|
||||||
|
|
||||||
|
Erweitern Sie die Fähigkeiten des Agenten mit externen Integrationen. Wählen Sie aus über 60 vorgefertigten Tools oder definieren Sie benutzerdefinierte Funktionen.
|
||||||
|
|
||||||
|
**Verfügbare Kategorien:**
|
||||||
|
- **Kommunikation**: Gmail, Slack, Telegram, WhatsApp, Microsoft Teams
|
||||||
|
- **Datenquellen**: Notion, Google Sheets, Airtable, Supabase, Pinecone
|
||||||
|
- **Webdienste**: Firecrawl, Google Search, Exa AI, Browser-Automatisierung
|
||||||
|
- **Entwicklung**: GitHub, Jira, Linear
|
||||||
|
- **KI-Dienste**: OpenAI, Perplexity, Hugging Face, ElevenLabs
|
||||||
|
|
||||||
|
**Ausführungsmodi:**
|
||||||
|
- **Auto**: Modell entscheidet kontextbasiert, wann Tools verwendet werden
|
||||||
|
- **Erforderlich**: Tool muss bei jeder Anfrage aufgerufen werden
|
||||||
|
- **Keine**: Tool verfügbar, aber dem Modell nicht vorgeschlagen
|
||||||
|
|
||||||
|
### Antwortformat
|
||||||
|
|
||||||
|
Der Parameter für das Antwortformat erzwingt die Generierung strukturierter Ausgaben durch JSON-Schema-Validierung. Dies gewährleistet konsistente, maschinenlesbare Antworten, die vordefinierten Datenstrukturen entsprechen:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"sentiment": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["positive", "neutral", "negative"]
|
||||||
|
},
|
||||||
|
"summary": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Brief summary of the content"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["sentiment", "summary"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Diese Konfiguration beschränkt die Ausgabe des Modells auf die Einhaltung des angegebenen Schemas, verhindert Freitextantworten und stellt die Generierung strukturierter Daten sicher.
|
||||||
|
|
||||||
|
### Zugriff auf Ergebnisse
|
||||||
|
|
||||||
|
Nach Abschluss eines Agenten können Sie auf seine Ausgaben zugreifen:
|
||||||
|
|
||||||
|
- **response**: Der Antworttext oder die strukturierten Daten des Agenten
|
||||||
|
- **usage**: Token-Nutzungsstatistiken (Prompt, Completion, Gesamt)
|
||||||
|
- **toolExecutions**: Details zu allen Tools, die der Agent während der Ausführung verwendet hat
|
||||||
|
- **estimatedCost**: Geschätzte Kosten des API-Aufrufs (falls verfügbar)
|
||||||
|
|
||||||
|
## Erweiterte Funktionen
|
||||||
|
|
||||||
|
### Memory + Agent: Gesprächsverlauf
|
||||||
|
|
||||||
|
Verwenden Sie einen memory Block mit einer konsistenten memoryId (zum Beispiel, conversationHistory), um Nachrichten zwischen Durchläufen zu speichern und diesen Verlauf in den Prompt des Agenten einzubeziehen.
|
||||||
|
|
||||||
|
- Fügen Sie die Nachricht des Benutzers vor dem Agenten hinzu
|
||||||
|
- Lesen Sie den Gesprächsverlauf für den Kontext
|
||||||
|
- Hängen Sie die Antwort des Agenten nach dessen Ausführung an
|
||||||
|
|
||||||
|
Siehe den [`Memory`](/tools/memory) Blockverweis für Details.
|
||||||
|
|
||||||
|
## Ausgaben
|
||||||
|
|
||||||
|
- **`<agent.content>`**: Antworttext des Agenten
|
||||||
|
- **`<agent.tokens>`**: Token-Nutzungsstatistiken
|
||||||
|
- **`<agent.tool_calls>`**: Details zur Tool-Ausführung
|
||||||
|
- **`<agent.cost>`**: Geschätzte Kosten des API-Aufrufs
|
||||||
|
|
||||||
|
## Beispielanwendungsfälle
|
||||||
|
|
||||||
|
**Automatisierung des Kundenservice** - Bearbeitung von Anfragen mit Datenbank- und Tool-Zugriff
|
||||||
|
|
||||||
|
```
|
||||||
|
API (Ticket) → Agent (Postgres, KB, Linear) → Gmail (Reply) → Memory (Save)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Multi-Modell-Inhaltsanalyse** - Analyse von Inhalten mit verschiedenen KI-Modellen
|
||||||
|
|
||||||
|
```
|
||||||
|
Function (Process) → Agent (GPT-4o Technical) → Agent (Claude Sentiment) → Function (Report)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Tool-gestützter Rechercheassistent** - Recherche mit Websuche und Dokumentenzugriff
|
||||||
|
|
||||||
|
```
|
||||||
|
Input → Agent (Google Search, Notion) → Function (Compile Report)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Bewährte Praktiken
|
||||||
|
|
||||||
|
- **Sei spezifisch in System-Prompts**: Definiere die Rolle, den Ton und die Einschränkungen des Agenten klar. Je spezifischer deine Anweisungen sind, desto besser kann der Agent seinen vorgesehenen Zweck erfüllen.
|
||||||
|
- **Wähle die richtige Temperatureinstellung**: Verwende niedrigere Temperatureinstellungen (0-0,3), wenn Genauigkeit wichtig ist, oder erhöhe die Temperatur (0,7-2,0) für kreativere oder vielfältigere Antworten
|
||||||
|
- **Nutze Tools effektiv**: Integriere Tools, die den Zweck des Agenten ergänzen und seine Fähigkeiten erweitern. Sei selektiv bei der Auswahl der Tools, um den Agenten nicht zu überfordern. Für Aufgaben mit wenig Überschneidung verwende einen anderen Agent-Block für die besten Ergebnisse.
|
||||||
145
apps/docs/content/docs/de/blocks/api.mdx
Normal file
145
apps/docs/content/docs/de/blocks/api.mdx
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
---
|
||||||
|
title: API
|
||||||
|
---
|
||||||
|
|
||||||
|
import { Callout } from 'fumadocs-ui/components/callout'
|
||||||
|
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
|
||||||
|
import { Image } from '@/components/ui/image'
|
||||||
|
|
||||||
|
Der API-Block verbindet Ihren Workflow mit externen Diensten durch HTTP-Anfragen. Unterstützt GET, POST, PUT, DELETE und PATCH Methoden für die Interaktion mit REST-APIs.
|
||||||
|
|
||||||
|
<div className="flex justify-center">
|
||||||
|
<Image
|
||||||
|
src="/static/blocks/api.png"
|
||||||
|
alt="API-Block"
|
||||||
|
width={500}
|
||||||
|
height={400}
|
||||||
|
className="my-6"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
## Konfigurationsoptionen
|
||||||
|
|
||||||
|
### URL
|
||||||
|
|
||||||
|
Die Endpunkt-URL für die API-Anfrage. Diese kann sein:
|
||||||
|
|
||||||
|
- Eine statische URL, die direkt im Block eingegeben wird
|
||||||
|
- Eine dynamische URL, die mit der Ausgabe eines anderen Blocks verbunden ist
|
||||||
|
- Eine URL mit Pfadparametern
|
||||||
|
|
||||||
|
### Methode
|
||||||
|
|
||||||
|
Wählen Sie die HTTP-Methode für Ihre Anfrage:
|
||||||
|
|
||||||
|
- **GET**: Daten vom Server abrufen
|
||||||
|
- **POST**: Daten an den Server senden, um eine Ressource zu erstellen
|
||||||
|
- **PUT**: Eine bestehende Ressource auf dem Server aktualisieren
|
||||||
|
- **DELETE**: Eine Ressource vom Server entfernen
|
||||||
|
- **PATCH**: Eine bestehende Ressource teilweise aktualisieren
|
||||||
|
|
||||||
|
### Abfrageparameter
|
||||||
|
|
||||||
|
Definieren Sie Schlüssel-Wert-Paare, die als Abfrageparameter an die URL angehängt werden. Zum Beispiel:
|
||||||
|
|
||||||
|
```
|
||||||
|
Key: apiKey
|
||||||
|
Value: your_api_key_here
|
||||||
|
|
||||||
|
Key: limit
|
||||||
|
Value: 10
|
||||||
|
```
|
||||||
|
|
||||||
|
Diese würden der URL als `?apiKey=your_api_key_here&limit=10` hinzugefügt.
|
||||||
|
|
||||||
|
### Header
|
||||||
|
|
||||||
|
Konfigurieren Sie HTTP-Header für Ihre Anfrage. Häufige Header sind:
|
||||||
|
|
||||||
|
```
|
||||||
|
Key: Content-Type
|
||||||
|
Value: application/json
|
||||||
|
|
||||||
|
Key: Authorization
|
||||||
|
Value: Bearer your_token_here
|
||||||
|
```
|
||||||
|
|
||||||
|
### Anfragekörper
|
||||||
|
|
||||||
|
Für Methoden, die einen Anfragekörper unterstützen (POST, PUT, PATCH), können Sie die zu sendenden Daten definieren. Der Körper kann sein:
|
||||||
|
|
||||||
|
- JSON-Daten, die direkt im Block eingegeben werden
|
||||||
|
- Daten, die mit der Ausgabe eines anderen Blocks verbunden sind
|
||||||
|
- Dynamisch während der Workflow-Ausführung generiert
|
||||||
|
|
||||||
|
### Zugriff auf Ergebnisse
|
||||||
|
|
||||||
|
Nach Abschluss einer API-Anfrage können Sie auf folgende Ausgaben zugreifen:
|
||||||
|
|
||||||
|
- **`<api.data>`**: Die Antwortdaten vom API
|
||||||
|
- **`<api.status>`**: HTTP-Statuscode (200, 404, 500, usw.)
|
||||||
|
- **`<api.headers>`**: Antwort-Header vom Server
|
||||||
|
- **`<api.error>`**: Fehlerdetails, falls die Anfrage fehlgeschlagen ist
|
||||||
|
|
||||||
|
## Erweiterte Funktionen
|
||||||
|
|
||||||
|
### Dynamische URL-Konstruktion
|
||||||
|
|
||||||
|
Erstellen Sie URLs dynamisch mit Variablen aus vorherigen Blöcken:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// In a Function block before the API
|
||||||
|
const userId = <start.userId>;
|
||||||
|
const apiUrl = `https://api.example.com/users/${userId}/profile`;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Anfrage-Wiederholungen
|
||||||
|
|
||||||
|
Der API-Block verarbeitet automatisch:
|
||||||
|
- Netzwerk-Timeouts mit exponentiellem Backoff
|
||||||
|
- Antworten bei Ratenbegrenzung (429-Statuscodes)
|
||||||
|
- Serverfehler (5xx-Statuscodes) mit Wiederholungslogik
|
||||||
|
- Verbindungsfehler mit Wiederverbindungsversuchen
|
||||||
|
|
||||||
|
### Antwortvalidierung
|
||||||
|
|
||||||
|
Validieren Sie API-Antworten vor der Verarbeitung:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// In a Function block after the API
|
||||||
|
if (<api.status> === 200) {
|
||||||
|
const data = <api.data>;
|
||||||
|
// Process successful response
|
||||||
|
} else {
|
||||||
|
// Handle error response
|
||||||
|
console.error(`API Error: ${<api.status>}`);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ausgaben
|
||||||
|
|
||||||
|
- **`<api.data>`**: Antwortdaten vom API
|
||||||
|
- **`<api.status>`**: HTTP-Statuscode
|
||||||
|
- **`<api.headers>`**: Antwort-Header
|
||||||
|
- **`<api.error>`**: Fehlerdetails bei fehlgeschlagener Anfrage
|
||||||
|
|
||||||
|
## Anwendungsbeispiele
|
||||||
|
|
||||||
|
**Benutzerprofildaten abrufen** - Benutzerinformationen von externem Dienst abrufen
|
||||||
|
|
||||||
|
```
|
||||||
|
Function (Build ID) → API (GET /users/{id}) → Function (Format) → Response
|
||||||
|
```
|
||||||
|
|
||||||
|
**Zahlungsabwicklung** - Zahlung über die Stripe-API verarbeiten
|
||||||
|
|
||||||
|
```
|
||||||
|
Function (Validate) → API (Stripe) → Condition (Success) → Supabase (Update)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Bewährte Praktiken
|
||||||
|
|
||||||
|
- **Umgebungsvariablen für sensible Daten verwenden**: Keine API-Schlüssel oder Anmeldedaten im Code festlegen
|
||||||
|
- **Fehler elegant behandeln**: Fehlerbehandlungslogik für fehlgeschlagene Anfragen einbinden
|
||||||
|
- **Antworten validieren**: Statuscode und Antwortformate vor der Datenverarbeitung prüfen
|
||||||
|
- **Ratenbegrenzungen respektieren**: Achten Sie auf API-Ratenbegrenzungen und implementieren Sie angemessene Drosselung
|
||||||
149
apps/docs/content/docs/de/blocks/condition.mdx
Normal file
149
apps/docs/content/docs/de/blocks/condition.mdx
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
---
|
||||||
|
title: Bedingung
|
||||||
|
---
|
||||||
|
|
||||||
|
import { Callout } from 'fumadocs-ui/components/callout'
|
||||||
|
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
|
||||||
|
import { Image } from '@/components/ui/image'
|
||||||
|
|
||||||
|
Der Bedingungsblock verzweigt die Workflow-Ausführung basierend auf booleschen Ausdrücken. Bewerten Sie Bedingungen anhand vorheriger Block-Ausgaben und leiten Sie zu verschiedenen Pfaden weiter, ohne dass ein LLM erforderlich ist.
|
||||||
|
|
||||||
|
<div className="flex justify-center">
|
||||||
|
<Image
|
||||||
|
src="/static/blocks/condition.png"
|
||||||
|
alt="Bedingungsblock"
|
||||||
|
width={500}
|
||||||
|
height={400}
|
||||||
|
className="my-6"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
## Konfigurationsoptionen
|
||||||
|
|
||||||
|
### Bedingungen
|
||||||
|
|
||||||
|
Definieren Sie eine oder mehrere Bedingungen, die ausgewertet werden. Jede Bedingung umfasst:
|
||||||
|
|
||||||
|
- **Ausdruck**: Ein JavaScript/TypeScript-Ausdruck, der zu wahr oder falsch ausgewertet wird
|
||||||
|
- **Pfad**: Der Zielblock, zu dem weitergeleitet werden soll, wenn die Bedingung wahr ist
|
||||||
|
- **Beschreibung**: Optionale Erklärung, was die Bedingung prüft
|
||||||
|
|
||||||
|
Sie können mehrere Bedingungen erstellen, die der Reihe nach ausgewertet werden, wobei die erste übereinstimmende Bedingung den Ausführungspfad bestimmt.
|
||||||
|
|
||||||
|
### Format für Bedingungsausdrücke
|
||||||
|
|
||||||
|
Bedingungen verwenden JavaScript-Syntax und können auf Eingabewerte aus vorherigen Blöcken verweisen.
|
||||||
|
|
||||||
|
<Tabs items={['Schwellenwert', 'Textanalyse', 'Mehrere Bedingungen']}>
|
||||||
|
<Tab>
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Check if a score is above a threshold
|
||||||
|
<agent.score> > 75
|
||||||
|
```
|
||||||
|
|
||||||
|
</Tab>
|
||||||
|
<Tab>
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Check if a text contains specific keywords
|
||||||
|
<agent.text>.includes('urgent') || <agent.text>.includes('emergency')
|
||||||
|
```
|
||||||
|
|
||||||
|
</Tab>
|
||||||
|
<Tab>
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Check multiple conditions
|
||||||
|
<agent.age> >= 18 && <agent.country> === 'US'
|
||||||
|
```
|
||||||
|
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
### Zugriff auf Ergebnisse
|
||||||
|
|
||||||
|
Nach der Auswertung einer Bedingung können Sie auf folgende Ausgaben zugreifen:
|
||||||
|
|
||||||
|
- **condition.result**: Boolesches Ergebnis der Bedingungsauswertung
|
||||||
|
- **condition.matched_condition**: ID der übereinstimmenden Bedingung
|
||||||
|
- **condition.content**: Beschreibung des Auswertungsergebnisses
|
||||||
|
- **condition.path**: Details zum gewählten Routing-Ziel
|
||||||
|
|
||||||
|
## Erweiterte Funktionen
|
||||||
|
|
||||||
|
### Komplexe Ausdrücke
|
||||||
|
|
||||||
|
Verwenden Sie JavaScript-Operatoren und -Funktionen in Bedingungen:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// String operations
|
||||||
|
<user.email>.endsWith('@company.com')
|
||||||
|
|
||||||
|
// Array operations
|
||||||
|
<api.tags>.includes('urgent')
|
||||||
|
|
||||||
|
// Mathematical operations
|
||||||
|
<agent.confidence> * 100 > 85
|
||||||
|
|
||||||
|
// Date comparisons
|
||||||
|
new Date(<api.created_at>) > new Date('2024-01-01')
|
||||||
|
```
|
||||||
|
|
||||||
|
### Auswertung mehrerer Bedingungen
|
||||||
|
|
||||||
|
Bedingungen werden der Reihe nach ausgewertet, bis eine übereinstimmt:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Condition 1: Check for high priority
|
||||||
|
<ticket.priority> === 'high'
|
||||||
|
|
||||||
|
// Condition 2: Check for urgent keywords
|
||||||
|
<ticket.subject>.toLowerCase().includes('urgent')
|
||||||
|
|
||||||
|
// Condition 3: Default fallback
|
||||||
|
true
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fehlerbehandlung
|
||||||
|
|
||||||
|
Bedingungen behandeln automatisch:
|
||||||
|
- Undefinierte oder Null-Werte mit sicherer Auswertung
|
||||||
|
- Typabweichungen mit geeigneten Fallbacks
|
||||||
|
- Ungültige Ausdrücke mit Fehlerprotokollierung
|
||||||
|
- Fehlende Variablen mit Standardwerten
|
||||||
|
|
||||||
|
## Ausgaben
|
||||||
|
|
||||||
|
- **`<condition.result>`**: Boolesches Ergebnis der Auswertung
|
||||||
|
- **`<condition.matched_condition>`**: ID der übereinstimmenden Bedingung
|
||||||
|
- **`<condition.content>`**: Beschreibung des Auswertungsergebnisses
|
||||||
|
- **`<condition.path>`**: Details zum gewählten Routing-Ziel
|
||||||
|
|
||||||
|
## Beispielanwendungsfälle
|
||||||
|
|
||||||
|
**Kundenservice-Routing** - Tickets basierend auf Priorität weiterleiten
|
||||||
|
|
||||||
|
```
|
||||||
|
API (Ticket) → Condition (priority === 'high') → Agent (Escalation) or Agent (Standard)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Inhaltsmoderation** - Inhalte basierend auf Analysen filtern
|
||||||
|
|
||||||
|
```
|
||||||
|
Agent (Analyze) → Condition (toxicity > 0.7) → Moderation or Publish
|
||||||
|
```
|
||||||
|
|
||||||
|
**Benutzer-Onboarding-Ablauf** - Onboarding basierend auf Benutzertyp personalisieren
|
||||||
|
|
||||||
|
```
|
||||||
|
Function (Process) → Condition (account_type === 'enterprise') → Advanced or Simple
|
||||||
|
```
|
||||||
|
|
||||||
|
## Bewährte Praktiken
|
||||||
|
|
||||||
|
- **Bedingungen korrekt anordnen**: Platzieren Sie spezifischere Bedingungen vor allgemeinen, um sicherzustellen, dass spezifische Logik Vorrang vor Fallbacks hat
|
||||||
|
- **Verwenden Sie den Else-Zweig bei Bedarf**: Wenn keine Bedingungen übereinstimmen und der Else-Zweig nicht verbunden ist, endet der Workflow-Zweig ordnungsgemäß. Verbinden Sie den Else-Zweig, wenn Sie einen Fallback-Pfad für nicht übereinstimmende Fälle benötigen
|
||||||
|
- **Halten Sie Ausdrücke einfach**: Verwenden Sie klare, unkomplizierte boolesche Ausdrücke für bessere Lesbarkeit und einfachere Fehlersuche
|
||||||
|
- **Dokumentieren Sie Ihre Bedingungen**: Fügen Sie Beschreibungen hinzu, um den Zweck jeder Bedingung für bessere Teamzusammenarbeit und Wartung zu erklären
|
||||||
|
- **Testen Sie Grenzfälle**: Überprüfen Sie, ob Bedingungen Grenzwerte korrekt behandeln, indem Sie mit Werten an den Grenzen Ihrer Bedingungsbereiche testen
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user