Compare commits
1587 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 098d444b32 | |||
| d50dcd8fb6 | |||
| 1c46df505c | |||
| 80ac5d713d | |||
| 37343dcbeb | |||
| 8329dc8141 | |||
| 96f293d240 | |||
| 4a00af18fe | |||
| 4e1ffd3ec4 | |||
| 1f4e2975cf | |||
| 7fbd064fd2 | |||
| 7f2d921136 | |||
| 7799c1f0dc | |||
| 2a93382700 | |||
| 2f8a00e1a5 | |||
| 058d667ac5 | |||
| 4b7c63206a | |||
| e989f78434 | |||
| e51ea247a8 | |||
| 38a78fa0b8 | |||
| 88ecdf85f3 | |||
| 114c781083 | |||
| 1755d6dae9 | |||
| 76baa05539 | |||
| f4e3559036 | |||
| 637751cf7c | |||
| c14eb1724f | |||
| 10e2dbbf04 | |||
| 0c0cdd2110 | |||
| 36ca5ca4cd | |||
| d629c8fc14 | |||
| a129a614e1 | |||
| cbab243e32 | |||
| 812c3d368f | |||
| f0d8a6b987 | |||
| 9bb52366ff | |||
| 172976e65e | |||
| 2ab3f1d0c7 | |||
| 6cd1858708 | |||
| 9ad9a91d11 | |||
| 4230f1542d | |||
| 69c13e1442 | |||
| 097830c082 | |||
| 8f59af0c64 | |||
| f375374525 | |||
| e06e2c2dbe | |||
| ebd898902d | |||
| 417eb9500d | |||
| 7e2fa61037 | |||
| b1d19ca43a | |||
| 25f1a2044f | |||
| ad88aa7b52 | |||
| 61ecb939dc | |||
| 28888e1de0 | |||
| 3310b692dc | |||
| 83633bdae3 | |||
| a262d8efe3 | |||
| 5bb91b773d | |||
| eab26a90a7 | |||
| 3edac3d2d6 | |||
| 0436afeec4 | |||
| 98626359aa | |||
| f50253f09f | |||
| fd433a07d6 | |||
| 32d2297700 | |||
| 45b8457f85 | |||
| 53362903c2 | |||
| f7c3ed9610 | |||
| 18328537ef | |||
| 6c516172bf | |||
| 38a0f33200 | |||
| a8cbd40a78 | |||
| c0fcaec52b | |||
| 0825623e7a | |||
| f367b9c5d8 | |||
| ae33724440 | |||
| 034945e8ea | |||
| 8f1da46986 | |||
| 7a344581af | |||
| d110b5ecfc | |||
| 4acd114e31 | |||
| 6532e30b1b | |||
| 47409585d3 | |||
| ee0431487f | |||
| 4ff2f56ffe | |||
| c518ed857e | |||
| fe2983ccba | |||
| c2ef089c07 | |||
| 2bb4a81f1c | |||
| e7eaf3a4b2 | |||
| 496d254fcf | |||
| aa70c13985 | |||
| 675a760e44 | |||
| c095c39b1c | |||
| 6c0f9d6d8d | |||
| ae3397f9a9 | |||
| 502a38f770 | |||
| 3375bac60c | |||
| 3c495a12ee | |||
| 9ec0cb2be6 | |||
| f537e2e27a | |||
| 4df711c7d1 | |||
| 009b982060 | |||
| 45e818f691 | |||
| 1ef8f6dcbb | |||
| e7585e09ec | |||
| 01b019dc80 | |||
| 3fcde2c55e | |||
| a4f52b2064 | |||
| 129f9957e5 | |||
| 3428ff6f3d | |||
| 25a8c5476b | |||
| e76920fa6b | |||
| 4ed56ed38d | |||
| 81f972aefb | |||
| db25fa357b | |||
| 6034ce8e0c | |||
| 73aed95d87 | |||
| 2ac13ef9db | |||
| e6ba070e55 | |||
| 5eea3c10f7 | |||
| d346e72c98 | |||
| a4824d6973 | |||
| 205a2448f3 | |||
| 712c9a78ff | |||
| 56d92ccd67 | |||
| b039b38e61 | |||
| 8ea300b777 | |||
| 3a5b2d03ac | |||
| 485c6c9d90 | |||
| 1d20bf3e79 | |||
| 53af53a01c | |||
| 2adb88e9a1 | |||
| 869cfdffcd | |||
| 27a768e10d | |||
| 918fa71a1c | |||
| bf1de55c8d | |||
| fdf407a3e4 | |||
| e5b4b7296c | |||
| 268bd594eb | |||
| 0ad8f01404 | |||
| 378d5dc764 | |||
| 20a0ec1f0e | |||
| ad9b42df41 | |||
| 0130c4e456 | |||
| 05e7f12db1 | |||
| 3e9a209552 | |||
| 8d338ca2c6 | |||
| 37124ba29a | |||
| 4f4ca63454 | |||
| f0406d8181 | |||
| 0f981717dd | |||
| 9ecf416fb0 | |||
| 3c132527f9 | |||
| 54e5da26d1 | |||
| 0bdbced718 | |||
| 1571490408 | |||
| 366798154b | |||
| 7fd9087c56 | |||
| b0f98485ca | |||
| 61d836b01d | |||
| b3620c23fb | |||
| 29a32041a8 | |||
| 84f609dda4 | |||
| 0826f1a858 | |||
| 210e578157 | |||
| dd5055ea20 | |||
| c87a429b2a | |||
| 1a8659cdaa | |||
| 3faffa65ff | |||
| ae5b86a3af | |||
| 4a0cd0a61c | |||
| 8c26e456c6 | |||
| 647a91cea3 | |||
| 6dd7302e7f | |||
| 3af7049234 | |||
| 619c76c96d | |||
| b09b4dc0ae | |||
| e1811ae163 | |||
| 784bb22672 | |||
| ba370693eb | |||
| 7117c90cda | |||
| 53848b31bf | |||
| 206c4c8b84 | |||
| 33dd5ac6c8 | |||
| 8ff3d62b5e | |||
| 2ff7d190a7 | |||
| 2227078c9a | |||
| ccf44242c3 | |||
| 6933a8e0fc | |||
| 5009ba6862 | |||
| 0daf1b6aab | |||
| 1dcf5fba85 | |||
| 2608425343 | |||
| e5e7cc2c5b | |||
| f5f084f663 | |||
| 2107e0942c | |||
| ff25e6ec87 | |||
| da13c7e71e | |||
| 88f5bcd81f | |||
| 7b62aa5ff4 | |||
| f05abf53f3 | |||
| 9741c79b03 | |||
| 5f5d6c230b | |||
| f1d0f7cff2 | |||
| e9c959371b | |||
| 1533a04680 | |||
| b61234cea9 | |||
| 3e41df5c9e | |||
| 2d7ebe83a0 | |||
| f00537afae | |||
| 4870438dcd | |||
| 13c79a9814 | |||
| 843a7bfa98 | |||
| a106b45e0d | |||
| 333b2652f5 | |||
| 8a0e87fc46 | |||
| b1083f2bf5 | |||
| d8a84a02eb | |||
| a403e7afcc | |||
| 59e68349dc | |||
| 43bb3a1b35 | |||
| 4977ed62c9 | |||
| 6adaf05680 | |||
| a70d99a3a9 | |||
| db41db65fa | |||
| 3e40878a88 | |||
| e74d129e83 | |||
| 4708674033 | |||
| 54af72adf1 | |||
| 6fee4c32ba | |||
| f99fc3eb56 | |||
| cccd2b2480 | |||
| 83bdbd658b | |||
| 28f4c2235d | |||
| b3ae69df9d | |||
| e72312a403 | |||
| b2d51aa6ab | |||
| d0098ae093 | |||
| b378f2ff77 | |||
| 5ce0e14ea6 | |||
| 8698ab9431 | |||
| 900d046c24 | |||
| 66f882eac8 | |||
| 9a8333e537 | |||
| 237eaf85cb | |||
| 887b3f3f3a | |||
| fee294e35b | |||
| 9df9d81b64 | |||
| c37daf3b8d | |||
| ae63ebe2e8 | |||
| 38f1bc1546 | |||
| b9e4a8783a | |||
| 6045ea3988 | |||
| 373557030d | |||
| 1434338774 | |||
| ddf42cb705 | |||
| 0757c70445 | |||
| 59e0729ec1 | |||
| 564c906ebb | |||
| 757b593355 | |||
| 6ee24c0de2 | |||
| 7e22e06fc8 | |||
| 6629ee671f | |||
| ed3bcc3669 | |||
| c93bf66fcb | |||
| 5af4ea1a2f | |||
| 1bcf7c12ed | |||
| 43c8aec9bc | |||
| 54aac0b785 | |||
| 705231857c | |||
| 044837b871 | |||
| 0e0a88e516 | |||
| 5246860a12 | |||
| 5cc55ed0c0 | |||
| 52fb8a8d8f | |||
| 359e970083 | |||
| 82fa5fffda | |||
| 658c850a60 | |||
| 543447b31b | |||
| 2a2211d0c7 | |||
| 4ea20a0f43 | |||
| 133cf4f979 | |||
| a7920e0b27 | |||
| 80bf70a9d4 | |||
| 53c3ac1777 | |||
| 6eb47ebd6e | |||
| dc161feb81 | |||
| 82783a7cc6 | |||
| 628b90a284 | |||
| 7a07039ad7 | |||
| 35b0d90fd2 | |||
| f9cf8c533c | |||
| 5502d5a2e1 | |||
| f6b39f795a | |||
| 52302c1453 | |||
| c40d37ec52 | |||
| de9b06d9f3 | |||
| 6afd93b403 | |||
| 92c24d031a | |||
| 4a00f8a5c3 | |||
| daba7d782c | |||
| 69dd7918da | |||
| 0349f7d60f | |||
| b808c5d340 | |||
| 0b24961e14 | |||
| 9f33f30f6a | |||
| dbd2eed8bc | |||
| a4632f1a55 | |||
| adf0d3fda8 | |||
| 22987f8e75 | |||
| 4290885b2e | |||
| 8c523c4f45 | |||
| f6b861e5e7 | |||
| 5a747a2c5b | |||
| 908e06a043 | |||
| 75060011db | |||
| 4c66355607 | |||
| 898a55c4d8 | |||
| 2ca2eac30a | |||
| eff4dedc47 | |||
| bc28d61c9b | |||
| f9119a6fc6 | |||
| 077dc119b0 | |||
| 1cfbf7c301 | |||
| fe508cdc0d | |||
| 294da3e1bc | |||
| bf0c10fc79 | |||
| f1f2df98e2 | |||
| 4a34d2855a | |||
| 2d9b08d462 | |||
| b5c803d75f | |||
| c9e77d408a | |||
| 667c30fb3a | |||
| bf197fbd0f | |||
| a7cbdcc6bd | |||
| 29831a5a38 | |||
| 6b9cd41461 | |||
| 04c3852e15 | |||
| eadd1acd57 | |||
| 92ebea092f | |||
| 24d6a652b5 | |||
| 64c9feaf63 | |||
| f4ae4ee256 | |||
| a8d8a09660 | |||
| 4d5a2a1f11 | |||
| 6c42da91fc | |||
| 877f127b39 | |||
| 2ce1c95de0 | |||
| b0032caef4 | |||
| 15df4b5a89 | |||
| c3c6af9680 | |||
| e0df66f2d7 | |||
| 5b2ac47aa9 | |||
| 52f4e19cab | |||
| 7ebc0dd98e | |||
| f333176ea4 | |||
| bd69b8a0d4 | |||
| df145c3642 | |||
| 89f6383f46 | |||
| 7ca05c249c | |||
| ff3be1ccb8 | |||
| 3fd43091d7 | |||
| 6bf6ebd3c8 | |||
| 8cf0e58adb | |||
| bf33cd262b | |||
| 191973a988 | |||
| 5c5a812e6e | |||
| 97037d059d | |||
| 32eae5997d | |||
| c31cf70e60 | |||
| e4e39e99b3 | |||
| 8173153a96 | |||
| 496543eefd | |||
| 1b14efee19 | |||
| 0580f7e799 | |||
| 89f3171525 | |||
| b845777235 | |||
| ac10a7b084 | |||
| 40f3816d19 | |||
| 5aa8a804b7 | |||
| 590275cbc9 | |||
| 5c2fbbbe76 | |||
| 2cfd9b366b | |||
| 5f238a8d89 | |||
| d54c09f2a8 | |||
| 4d1b8aa407 | |||
| 100d21e7b3 | |||
| 9525632ada | |||
| d2d05531f8 | |||
| eab3a138af | |||
| 4407fa25f4 | |||
| db42383830 | |||
| 721729577a | |||
| d046b9d32f | |||
| 6c04cf4d2d | |||
| d2d46b27db | |||
| f8b71770a9 | |||
| 8cb0b44b94 | |||
| 4de3925110 | |||
| f55a499676 | |||
| 716a3e318d | |||
| d72fb97041 | |||
| 34553d8da3 | |||
| e5bd9b894d | |||
| db0aebe7ca | |||
| 522bb0f5e8 | |||
| 971758ccee | |||
| bc0edd3b8a | |||
| c47187c18e | |||
| 392086f671 | |||
| 244ad5568b | |||
| 41927597f0 | |||
| f6a56a61be | |||
| b533cd4266 | |||
| f8b9642e55 | |||
| 125b8220a4 | |||
| d4a57af24d | |||
| 9b42956b0f | |||
| d715f2616e | |||
| b14c80af1c | |||
| bc8e7abf26 | |||
| 8484e74dea | |||
| 63e9a34d73 | |||
| 20a0380e47 | |||
| ff2310a0de | |||
| 244ccd1b03 | |||
| 9d0650d555 | |||
| 3039834e85 | |||
| 5f7b91f233 | |||
| bb77b06047 | |||
| 33b252b533 | |||
| fb5336bf15 | |||
| c15340d5fd | |||
| 82515da893 | |||
| c2fa700dc1 | |||
| 9e9794ece3 | |||
| eb7167bb4f | |||
| 546af4b7c6 | |||
| 6df8c63144 | |||
| 445c968279 | |||
| d54d57ec63 | |||
| e79e8899a1 | |||
| 8c453beeca | |||
| d7c95f285e | |||
| 5f5df223e8 | |||
| 115a1a1c1f | |||
| 28ffd9510d | |||
| bc39bce60d | |||
| 080088e8eb | |||
| 47df8696a6 | |||
| f322f5e1d3 | |||
| 01c8e764b2 | |||
| 119927b565 | |||
| 5a31ae302f | |||
| b7f4a3a574 | |||
| 83792fd623 | |||
| 6993af2416 | |||
| 6385bc9efe | |||
| 551934cf6a | |||
| f03a255314 | |||
| 6d07da5661 | |||
| 5c51243f9c | |||
| 7df98ae282 | |||
| ab5f2336bc | |||
| a1a65012f2 | |||
| 43b01089cc | |||
| 2d2ae2f269 | |||
| 1473468b4c | |||
| e01a0b1b8b | |||
| d2602b765d | |||
| 885e136f16 | |||
| f241b7047c | |||
| 25a286b0b7 | |||
| b66ce33ced | |||
| 3083097885 | |||
| e9df744b5e | |||
| 62799acdb6 | |||
| c21c7917ce | |||
| 47bd0b446a | |||
| fc48e88628 | |||
| 9738ad3029 | |||
| 5e9a47ec2e | |||
| 97f3d1be35 | |||
| f76f989937 | |||
| 7f916a402f | |||
| f0a1faa3ab | |||
| 6616c8fc88 | |||
| d72e82e156 | |||
| 3eda5a6c5c | |||
| 85e989b024 | |||
| 26ea905cae | |||
| c8e5c25579 | |||
| b686783c84 | |||
| 337e8d6da9 | |||
| d079830824 | |||
| ef26867bce | |||
| fd5c81cb96 | |||
| f16c0cbb2c | |||
| 9e93f161df | |||
| df49ad34f7 | |||
| cf636d5b2d | |||
| 845075fb5b | |||
| c69ce3e1b9 | |||
| 784fd069b2 | |||
| 990830213d | |||
| f1dc6c9189 | |||
| e944bde4b9 | |||
| e05a450b1f | |||
| ad403cf0e1 | |||
| ffe11ff7d2 | |||
| 5695064fd6 | |||
| c8b88d36c3 | |||
| 7d123764be | |||
| 34f95c35cc | |||
| 875e3d75ff | |||
| 3b40564ebe | |||
| 2f9da25028 | |||
| 01535692cf | |||
| cb996aee38 | |||
| d755438db3 | |||
| d38bf8ac5f | |||
| 0fc15712ca | |||
| ef1b2eeb88 | |||
| 7adfba1d25 | |||
| 84ede7d286 | |||
| c0df4475f9 | |||
| 8cb38f19ee | |||
| f59be0bbfc | |||
| dfa9d65201 | |||
| 2960fececf | |||
| ccbc30f1b2 | |||
| a1c9221feb | |||
| a132207741 | |||
| b1b7c92177 | |||
| 87f3416ff6 | |||
| 1cdc12e5cd | |||
| 5f7f48a3d5 | |||
| 960c6f311d | |||
| f244d173da | |||
| e8b78194ab | |||
| b3343588a8 | |||
| 71a512717c | |||
| c7b99eb58d | |||
| a6b31edde9 | |||
| 22128ad609 | |||
| 2218679430 | |||
| 6720d8e98e | |||
| ffebf6f7aa | |||
| 8ab8f315e4 | |||
| 8d0cd68cf1 | |||
| 608370d289 | |||
| 3be780ed1b | |||
| 67f15ed17d | |||
| 31b32ada8e | |||
| 6c065a7250 | |||
| c04537b77a | |||
| 32853142de | |||
| a64c8168ac | |||
| 34d2b25be5 | |||
| 92d9d6cd0d | |||
| 3687e57cbd | |||
| 3253864d4c | |||
| 2cd9fd2406 | |||
| 73fd0fa17e | |||
| b6b1065517 | |||
| 39d32e52ae | |||
| d5ace59c9b | |||
| 290d512537 | |||
| cd00f2aa3e | |||
| f09d1e5f8f | |||
| df192b8e25 | |||
| 665d761265 | |||
| 812c71dc7a | |||
| 4f4428a647 | |||
| 3a106af89c | |||
| e755eacd46 | |||
| 785662faab | |||
| 02dc8dd179 | |||
| 308e9c4bb3 | |||
| d70c4c642a | |||
| 02f6b226fb | |||
| 9bc1a5ef37 | |||
| ef7491bd2d | |||
| 00ccf118be | |||
| 7b3ef0c5b0 | |||
| c8d6fdf321 | |||
| eb7812761b | |||
| 657a32f1e1 | |||
| 4ab7cf10ea | |||
| 141a5d41f7 | |||
| b4992b8186 | |||
| 8043b3fe53 | |||
| d210e4ed28 | |||
| 1aec69d936 | |||
| 8ce2f5c94c | |||
| 0800b99579 | |||
| 638390af46 | |||
| 32398dc581 | |||
| f0c6d6f163 | |||
| 9c83cbc0f0 | |||
| 872757a6ad | |||
| 6bdbde18b5 | |||
| c295e141e1 | |||
| 314507c5d1 | |||
| ac62f192d6 | |||
| 28a9c19435 | |||
| f3c1b13d38 | |||
| 8907a558c5 | |||
| ba4c9472c2 | |||
| 325717871f | |||
| 7119c0b95e | |||
| 41b3ef58de | |||
| 7d5f71d8b7 | |||
| e8cc186265 | |||
| d3a5df905e | |||
| e7212c8eb6 | |||
| 87df0b2865 | |||
| 6e52dc0cac | |||
| 49f51459c5 | |||
| d38bf619a5 | |||
| 7d8760ead1 | |||
| 1799582f1f | |||
| 1eb4046261 | |||
| 66ce235d30 | |||
| b897498356 | |||
| 7ce28bea47 | |||
| f46f69c0a1 | |||
| 0765240fb4 | |||
| 491facec2e | |||
| 8976ddab13 | |||
| 5db2734513 | |||
| 51ab7ebf4b | |||
| 8f120e826c | |||
| c10f7fed99 | |||
| 859d1879cf | |||
| f60c1e1326 | |||
| 1d754b82b9 | |||
| 1f176bd719 | |||
| cf58b4e07a | |||
| b41371f917 | |||
| 7f3298dc15 | |||
| 7f178ddc61 | |||
| 5611bdec4a | |||
| 5d1c9810e7 | |||
| d118835c46 | |||
| 56934ab064 | |||
| 6e443ce487 | |||
| ccb204aca8 | |||
| 0ab4405951 | |||
| 5d109caf19 | |||
| 1d1c3f1df4 | |||
| 9e9e90a9df | |||
| fa5f421521 | |||
| 7642974a3b | |||
| 571587ceac | |||
| a147e99933 | |||
| 956a3e9acb | |||
| 1bbac4f879 | |||
| b05b5dd788 | |||
| fc08dbdefe | |||
| a7177656a4 | |||
| 07a43c119e | |||
| eeed2ac7cd | |||
| 3b18df78b2 | |||
| 835fa2b9e7 | |||
| cff5e73e4b | |||
| 5087d4c985 | |||
| d023f46031 | |||
| ec37a8f49f | |||
| 94c22a6928 | |||
| 2c91ac1be1 | |||
| 9e56c46717 | |||
| 5ae825cec2 | |||
| f47813face | |||
| 5b5ea22964 | |||
| a89b2e1aa0 | |||
| 09aca8cf54 | |||
| 2672888031 | |||
| 9c714ffaed | |||
| 2161843876 | |||
| e4686cb9ee | |||
| a25886cc9f | |||
| 0ab2e14ba9 | |||
| e39b33de04 | |||
| a48aaafb92 | |||
| 68613b8e5b | |||
| ba061cf47a | |||
| 4d857b758a | |||
| 64f3d6b0ae | |||
| 8a7c529ede | |||
| 0e5691ef18 | |||
| c1f877932d | |||
| ae88a3004d | |||
| b7b4ffd2f0 | |||
| 4ff0b9405d | |||
| 22379515e3 | |||
| d7156fa614 | |||
| 59f5760708 | |||
| b44598b685 | |||
| 41e4a95556 | |||
| c8172b605c | |||
| 638ffee508 | |||
| 6982b40688 | |||
| 027585f737 | |||
| acb64b9066 | |||
| a857cc862e | |||
| 6821823fbd | |||
| 08fa2687e6 | |||
| 75ffed8918 | |||
| 379d0af081 | |||
| 3dc8efe5e1 | |||
| 35c4841d19 | |||
| 4f352a4dad | |||
| 873622e3e4 | |||
| 233858f447 | |||
| 7707781f0f | |||
| 4e6928edd4 | |||
| c755282408 | |||
| d28959a637 | |||
| 2b197551d6 | |||
| 01539ffdf4 | |||
| f00190aa20 | |||
| 1c585cd307 | |||
| bc67fa9e66 | |||
| a5d8c2bf14 | |||
| 5df85eeb64 | |||
| 43b22f36b9 | |||
| 33f150df11 | |||
| 4463fa3a94 | |||
| d5440e93f2 | |||
| 35d7cb1abc | |||
| de22a3fd1f | |||
| d4de119db4 | |||
| 7cf33cb8fb | |||
| 0ef72929f4 | |||
| adb9e6fcca | |||
| 0bf229c5ef | |||
| c589742201 | |||
| 3e4b8a43b8 | |||
| 0dd6521a9d | |||
| 05878ed7b3 | |||
| b9683050fb | |||
| b1e7d48714 | |||
| 57181398dc | |||
| d970f349c2 | |||
| e65790ed24 | |||
| b983d57a39 | |||
| d74f71c49e | |||
| 5fb7d29f78 | |||
| 3a17b27abf | |||
| fda723fdb7 | |||
| 4efa758797 | |||
| d138a2c01e | |||
| 3e652d8777 | |||
| 06d6005ffb | |||
| 3a2f0e7114 | |||
| 4d635b4933 | |||
| a7c233c0a8 | |||
| 29f11a6074 | |||
| b1ab51b68a | |||
| 9fa925fd16 | |||
| 8ce5bc60b7 | |||
| f9e6b5e3d2 | |||
| 3b8d7d3d06 | |||
| d887c49b53 | |||
| 20c6c26764 | |||
| 7b81836b21 | |||
| 289c52c2cb | |||
| 66de14d3cf | |||
| 0f8d4c381a | |||
| 6d0bf7fffa | |||
| 30a8f0470e | |||
| 01f0587b42 | |||
| 9fe76ea4eb | |||
| 810de4d160 | |||
| 61abe5d8bc | |||
| ae41315c8f | |||
| 4fb5d7787b | |||
| 888c5260eb | |||
| dbe1c98c13 | |||
| 927e31471c | |||
| 520ee79498 | |||
| 32f2bb98e1 | |||
| ebc1121c3f | |||
| d255cc342b | |||
| 73004f638b | |||
| a154ba9e0d | |||
| f2d6ae813c | |||
| 0690e61da1 | |||
| c13b3fb110 | |||
| b521c8bdd1 | |||
| 03cf85dd7a | |||
| 0bdba06181 | |||
| e6f6227e59 | |||
| f7d592aecc | |||
| 13876fbb69 | |||
| 14e3b5a933 | |||
| 5d7c79e168 | |||
| 08c7844047 | |||
| 75e7a4650d | |||
| 2f9d102d02 | |||
| 465cb9286f | |||
| cb0be62c40 | |||
| 30784e0740 | |||
| 283353a71a | |||
| a934d3ead7 | |||
| 1464475df2 | |||
| a4cfef1740 | |||
| ecee044716 | |||
| 0bd2b10706 | |||
| e0cf29a07a | |||
| feffd8a93c | |||
| 3c050db5c3 | |||
| 8c615d6233 | |||
| 194bbd5996 | |||
| 3002f6bb99 | |||
| 022c085241 | |||
| d71b78b20b | |||
| 6627d1020a | |||
| 9454df0585 | |||
| 3cd2facb7f | |||
| 1e2d460583 | |||
| 6bb35a2494 | |||
| 7ab16f4858 | |||
| f14bb05f1f | |||
| 0a1145fce7 | |||
| 749fd6caf5 | |||
| 8db94647df | |||
| 7bf5b07f60 | |||
| 31fefe12ac | |||
| 05d3ea95da | |||
| d7b1fb3e7a | |||
| eb97c5d393 | |||
| 39c2bab09f | |||
| 88c1115034 | |||
| ab6171d6c9 | |||
| 19f9f6ca81 | |||
| aecd86b56b | |||
| ed5ddb3d1f | |||
| f724bb7951 | |||
| 47f085b0ce | |||
| 024307a329 | |||
| d25abace00 | |||
| 84bc3cf9a9 | |||
| 900da199cc | |||
| 7ce8778c7d | |||
| 46abdcc528 | |||
| af75cf7116 | |||
| 46b3d86dbb | |||
| 0b61819b2d | |||
| 21eede5c27 | |||
| 5d284ddb55 | |||
| 9b3ea0bf7c | |||
| f89656b582 | |||
| 7691c30bd5 | |||
| 622b02bb80 | |||
| 9156cece88 | |||
| 256e4339f5 | |||
| 08e85a3827 | |||
| 85969a537b | |||
| 0e8a742dd9 | |||
| e9b8d6c6a5 | |||
| c103d1d708 | |||
| 279890f962 | |||
| f4b1be8330 | |||
| f0f05357ca | |||
| 2d6a628391 | |||
| cfef0bfc4b | |||
| 9943bbe4b7 | |||
| 4f26d270ed | |||
| 6c66be57a6 | |||
| 7bc643c889 | |||
| b77c8ed13d | |||
| 7e6d9ff4a2 | |||
| d237a6a7f3 | |||
| ec63c8e3ff | |||
| bc1960f1a5 | |||
| b3b2a4a09c | |||
| 7f02c0fa8a | |||
| 6d0b72ebd4 | |||
| 9972e8b2b4 | |||
| 22cef958c6 | |||
| c234ed3f38 | |||
| 1c810f3e28 | |||
| 6043696ca7 | |||
| e7f81e24dc | |||
| 28a2806d4b | |||
| 9683bef22e | |||
| 3e53eebcf4 | |||
| b406431755 | |||
| caf297283c | |||
| 9f7c200e69 | |||
| 4f26d0e120 | |||
| 039a4028a3 | |||
| e11a167a55 | |||
| 4639de7157 | |||
| 1462653763 | |||
| ebdabfefb4 | |||
| 560dc17883 | |||
| ea715bc2b8 | |||
| 1189277f46 | |||
| 73e017bb6f | |||
| 11e6c3f440 | |||
| 50a97c0729 | |||
| b991170aec | |||
| 6d1fbd3152 | |||
| 07005ec619 | |||
| 818ac2f32e | |||
| 246418367f | |||
| 2b5ff2c672 | |||
| 1e032ed819 | |||
| a7112fa127 | |||
| b529c1b275 | |||
| 345989463f | |||
| fbf2a69203 | |||
| b45918c4c3 | |||
| 60410ea046 | |||
| 9db18e74b7 | |||
| 954fbea6ba | |||
| 402016da36 | |||
| 2fac69e1f7 | |||
| 05c32f93ab | |||
| 0e8d8e2449 | |||
| d55748d291 | |||
| 612114beaf | |||
| 6a1d232347 | |||
| 437be3d61d | |||
| 008698943a | |||
| 62b5767bc9 | |||
| 8496265084 | |||
| a3ee86b65b | |||
| 2379887c93 | |||
| 1e029580c6 | |||
| e52826f731 | |||
| b428739a19 | |||
| 867147829b | |||
| 42c6125e94 | |||
| 63b0b66739 | |||
| cfdb8726d8 | |||
| b04295fa90 | |||
| 77799aadcc | |||
| f51e399dcb | |||
| 63b371b678 | |||
| d26692fbf2 | |||
| 0034dc39b5 | |||
| 36e73e4ad2 | |||
| 05ed853084 | |||
| 59bf6f13fe | |||
| dcc5c5cbbf | |||
| 1803c8e297 | |||
| 14f7cfadf8 | |||
| 1d93108df9 | |||
| cde0e0724c | |||
| ee712a0c8c | |||
| 721da5e226 | |||
| e3c658bd79 | |||
| 3b49211aab | |||
| b991f8fe10 | |||
| 9fbeddf8f4 | |||
| 252bcb5de3 | |||
| 4ef6246284 | |||
| a253e6524a | |||
| f34113302f | |||
| 1c287d3a0f | |||
| 29064d6ada | |||
| c95f80dd75 | |||
| 0e9258c300 | |||
| 08a9c26ed2 | |||
| 1dbdb88717 | |||
| cd1715e703 | |||
| d9fe66299a | |||
| e1741b528e | |||
| 68bf2e9be8 | |||
| 266ffe9b9e | |||
| a46ef57a60 | |||
| 6505f7601f | |||
| 9684a58dbc | |||
| f50c843cb7 | |||
| 7f34e89107 | |||
| 4315ccba98 | |||
| 6759e17f1c | |||
| b1d591c946 | |||
| fad9ea6a5d | |||
| 5e5d20b705 | |||
| fa5e180371 | |||
| ba3a120f6d | |||
| 8c54e612ac | |||
| 239119a12e | |||
| 34e529cc70 | |||
| e2a494e39d | |||
| cee1e2d521 | |||
| edf9cded4e | |||
| f6dbe4c44c | |||
| 0b207d2157 | |||
| 5686ff9e75 | |||
| ce9ff6fad6 | |||
| c3450135a5 | |||
| f96fce7ce0 | |||
| f572bbfaa9 | |||
| 921cdd51be | |||
| d34bf90637 | |||
| 53f07670d5 | |||
| bd8bd90ad9 | |||
| 4d1236eacb | |||
| be06ac6a14 | |||
| ce23b834d0 | |||
| 9b80161f41 | |||
| 98695ca54a | |||
| c2252e451b | |||
| a7b5a04a2f | |||
| 9eb8ee0d2b | |||
| 7362ae4f54 | |||
| 2bd699f3b5 | |||
| 9b6dc5b26e | |||
| a607103b44 | |||
| 8b3721ec12 | |||
| a2181dc7b2 | |||
| 8b579d0798 | |||
| 3d652b5b42 | |||
| 687404ce92 | |||
| dd65280f59 | |||
| 6f7a57bf15 | |||
| 99219d2ad7 | |||
| df3c01af28 | |||
| a3191046ef | |||
| 303be150f3 | |||
| 24fe537382 | |||
| b3bc24c5fc | |||
| c1a3064217 | |||
| bc98d1fba9 | |||
| 93616ad570 | |||
| 2d7f0382c0 | |||
| c9ba958e15 | |||
| 538ba7af05 | |||
| 0bb60d5303 | |||
| d88b587dd0 | |||
| 6dc0c2fb44 | |||
| e8aa9847f0 | |||
| a158f05ed7 | |||
| e2176adff2 | |||
| 58d3f3bd34 | |||
| 87d0b7b324 | |||
| a806ab5940 | |||
| fd7a2ff969 | |||
| d3bb2837a7 | |||
| 42a188df90 | |||
| ff93c8c2c9 | |||
| 9207e52a2c | |||
| e09b88f874 | |||
| ef02343133 | |||
| 1800467f8d | |||
| cc9dbb04c1 | |||
| adf7937b0a | |||
| 4167d6aa4a | |||
| c1880d2359 | |||
| d30d1ec778 | |||
| e1a8af3454 | |||
| 61958903af | |||
| a346c865cf | |||
| e29e9836aa | |||
| 9dc33ad5f9 | |||
| 535cb42f2b | |||
| aa0cc20aad | |||
| a519527ca6 | |||
| f6c968a628 | |||
| 1a874fd1d2 | |||
| fd21da7359 | |||
| 0fa3068410 | |||
| e44d497a4b | |||
| 0cd22e8c4a | |||
| 3f10abf677 | |||
| 639c6ac77a | |||
| 4dd52dde81 | |||
| 58877013e6 | |||
| f8c5678050 | |||
| b8a41af8b3 | |||
| 09ef132092 | |||
| 199fcab4d2 | |||
| 67dc4d5e67 | |||
| 46cd8043c2 | |||
| ce56f7bad5 | |||
| 6f1ea679be | |||
| 01a4d832f3 | |||
| af0ea6dbb8 | |||
| 31470fff77 | |||
| 00446604ce | |||
| 469d2a75c0 | |||
| 3cb4563243 | |||
| 1d5a58c476 | |||
| 172698cc3f | |||
| 4dcbb6689e | |||
| 75cc66e338 | |||
| 611d94e598 | |||
| 76aac6226f | |||
| 9623078e58 | |||
| cd00b2a83b | |||
| 5e4806c01f | |||
| 2e6803db1c | |||
| 7bc036428f | |||
| 7f0a696ac4 | |||
| 4bf2b9bef8 | |||
| 61681169fd | |||
| 2ca61fab49 | |||
| 5bac29e0d9 | |||
| 77408e73c6 | |||
| 3b5925a3a9 | |||
| 46c080da47 | |||
| 7807588440 | |||
| f2e87cbab6 | |||
| 32e9ef2f3b | |||
| b98575edc0 | |||
| 679bbdf338 | |||
| 9d6622adc8 | |||
| e092edf03d | |||
| 19e73716cc | |||
| 552072fffe | |||
| fff6f8859a | |||
| 84bde4f66d | |||
| 17674e5a4b | |||
| 139c6383d0 | |||
| 1487bf89b0 | |||
| 4ea7fa2e58 | |||
| 37a3dc575b | |||
| 4b0709d8bf | |||
| b3f747ad2d | |||
| 7946255374 | |||
| 458fb63d3d | |||
| 6fb35a7d1b | |||
| ca701ef708 | |||
| 02710fcdc1 | |||
| 1552d9eb80 | |||
| 85578ad42a | |||
| 73e0689c63 | |||
| 88a20c3ad0 | |||
| 9cf881bd24 | |||
| 1f92e7c571 | |||
| d39281a0ac | |||
| 15c7096efc | |||
| 612ef9a886 | |||
| 986de489de | |||
| 99a79bf0b8 | |||
| d13546b833 | |||
| e1445bf422 | |||
| f9da37428f | |||
| f941c403e0 | |||
| 9fa2953d37 | |||
| ba15c3df1a | |||
| b7422f2aaf | |||
| b86445d09b | |||
| 32cf591dda | |||
| e5060814fe | |||
| 30804794da | |||
| 23e6816cfa | |||
| a0cdd3c921 | |||
| d4261e65fc | |||
| 3d16c50e40 | |||
| bd7b06dbec | |||
| 4e5da50c40 | |||
| e16048f9d7 | |||
| f6d4efb12c | |||
| 1d7dd31453 | |||
| 89e77146fd | |||
| 7427b2b904 | |||
| 113713dadb | |||
| 702be607cf | |||
| 05455bed69 | |||
| a7f9128202 | |||
| 878fb4ae50 | |||
| a81d684f85 | |||
| 1128a3ea58 | |||
| 55449a076d | |||
| 1033ab76c3 | |||
| b62d50cada | |||
| f1c7d4e4a7 | |||
| 03e037284a | |||
| f14a145962 | |||
| 72fab6e18b | |||
| 1a969d90a1 | |||
| 5bdd9e8ade | |||
| 395f5e265e | |||
| ef1afd7a41 | |||
| de0f17ad5e | |||
| 8dfa6cee0b | |||
| 93b7b0c184 | |||
| 0f0da40d59 | |||
| dfac33c025 | |||
| 15d4b6499d | |||
| 4179032022 | |||
| 7060fbb326 | |||
| e8feb8393f | |||
| 87ed3117f8 | |||
| 8b6b810410 | |||
| ed94dbbc04 | |||
| 4bc002d6d0 | |||
| 27b84bc209 | |||
| 00e3b8829a | |||
| a781d9e869 | |||
| 7bfc87817c | |||
| a4faceb7b0 | |||
| fa48363af0 | |||
| 90df3f5d38 | |||
| 932471b809 | |||
| 00f26f0b08 | |||
| 90c29c355e | |||
| 1d4dc088a0 | |||
| c207c68915 | |||
| 2e81f5273e | |||
| 29d7de7381 | |||
| c4d4048f29 | |||
| 46711df18b | |||
| 069e0c8af8 | |||
| 6a19cf6d4a | |||
| 540f6445e4 | |||
| ef303144e2 | |||
| c7c9ef89ac | |||
| 5cd2a8444f | |||
| fd25f08b0f | |||
| b67e959e88 | |||
| 9f21b47e0b | |||
| 2cec655883 | |||
| 4ebc152a81 | |||
| 2816496eee | |||
| 2cf7f6a031 | |||
| 8b545d5a60 | |||
| 8b6b9e9832 | |||
| 3fce4de48e | |||
| 87b1639b15 | |||
| e59d9d11eb | |||
| c0e6404031 | |||
| 3e6411e162 | |||
| 85d0d67af1 | |||
| 50c4e9769b | |||
| 4ad84590c3 | |||
| 7764297ab1 | |||
| d743dec6a8 | |||
| baaabe8e2e | |||
| 95baacbdda | |||
| d05bd48185 | |||
| f4466bede9 | |||
| bee5dd3ace | |||
| 47607c1e16 | |||
| 27c84efb41 | |||
| 21f267b15f | |||
| be9f1ac817 | |||
| eca611dbba | |||
| 0ce102dfbe | |||
| 4ccbd1819a | |||
| 171fa5c90f | |||
| d4963680e6 | |||
| a13017af2d | |||
| 178a0265c0 | |||
| 7ea29292be | |||
| 08639a5f31 | |||
| 3105e262d2 | |||
| bfc61e7d3b | |||
| 1d9c5968db | |||
| ae32b0a295 | |||
| 1587b1c23c | |||
| 1002b78e16 | |||
| b56d3bbb07 | |||
| 3769b7f612 | |||
| 069958661a | |||
| 0061e3a008 | |||
| b4bf08a9d6 | |||
| 50985368c0 | |||
| d7ca59d4c4 | |||
| d0d05c9164 | |||
| 04f11f85ad | |||
| 04fa50120c | |||
| 5a3c35d6ca | |||
| b400d6d722 | |||
| 4f15076fbf | |||
| d73fba4c6f | |||
| 4304ea9ecd | |||
| 649609b87a | |||
| 9704afeab7 | |||
| 74eeb2e863 | |||
| 9363a761eb | |||
| c595103f6f | |||
| f4c2e3e6fa | |||
| 4a702f1a3b | |||
| 0f9308f8ad | |||
| a17b83da41 | |||
| e6f04feda4 | |||
| f531a5a289 | |||
| 73e7690e72 | |||
| 99de8b13de | |||
| 33237cd2f6 | |||
| 3d61781984 | |||
| 92dd4d0ce0 | |||
| b0a9ba2f74 | |||
| 3b8d6de5c9 | |||
| 7eabc91774 | |||
| 86b384f788 | |||
| 229515f4b3 | |||
| 5e6c170c98 | |||
| 5910c4f56d | |||
| 3cce076576 | |||
| 2a9af460d8 | |||
| 0201fc7d8c | |||
| 909f1ddf99 | |||
| 65f04e6e13 | |||
| 5cd2d8950e | |||
| f200ef6e36 | |||
| f12285cd67 | |||
| dfc36ba72b | |||
| 5c22be9147 | |||
| d625a12c82 | |||
| f14c179721 | |||
| 9e21715721 | |||
| 2fa5885b13 | |||
| da99eb8328 | |||
| 50e52ba09c | |||
| ff9d0721b9 | |||
| 5b1ce26aaf | |||
| 2cbf068b32 | |||
| 590749b92a | |||
| 983355aa01 | |||
| 4bc12b3fff | |||
| 20ba6eddcd | |||
| 48c644536f | |||
| 756c0a926f | |||
| 28462f5b2c | |||
| 4e091af8b2 | |||
| 441510ad8f | |||
| 486979e6ee | |||
| ff46dfa31c | |||
| 4cc553dc47 | |||
| 38d23693ce | |||
| e14cf82ac0 | |||
| 989a79be49 | |||
| cab8e9f74a | |||
| 69e0330dd5 | |||
| 31ca4fbb40 | |||
| 926b0aa1ef | |||
| 847200c747 | |||
| 0764270132 | |||
| ec34d2e8fb | |||
| 8328145eab | |||
| aedd5877f3 | |||
| 2d7535746b | |||
| 5fe75d83bf | |||
| ec424d88aa | |||
| 77c4987ea6 | |||
| 33355b1f9a | |||
| eebea5fa45 | |||
| 77834f8d7f | |||
| 4b88e3d154 | |||
| f23734a4e8 | |||
| 2ef27712d1 | |||
| 679d220dc3 | |||
| c0563c17e7 | |||
| 0219352944 | |||
| 702b3d838b | |||
| 1510f88074 | |||
| 7ab59e8377 | |||
| 432cad116d | |||
| 6033606a81 | |||
| a8af055348 | |||
| c5007df5ac | |||
| ed725b1bd3 | |||
| b000177ac3 | |||
| 8a5c09ccb1 | |||
| 49dbb007e7 | |||
| 4e771b3435 | |||
| fd88b44b65 | |||
| 548e730682 | |||
| 4f324cbbd5 | |||
| 38ee563b3e | |||
| b5e8d1b25b | |||
| ce59f4ee08 | |||
| a165c8b5c5 | |||
| cab7615463 | |||
| 0c33c795a5 | |||
| d7bf0e267f | |||
| 50c68b59b9 | |||
| 9775951756 | |||
| 8ba9225f61 | |||
| 5c5aed31b8 | |||
| 1f4816199d | |||
| 0764fef26b | |||
| 121e9fd43f | |||
| c68ec47adf | |||
| aa2fca3f57 | |||
| ab2bfa0b44 | |||
| 1dfe42ee1e | |||
| e1a8d1327b | |||
| fd3a9a6fd3 | |||
| 7c94573637 | |||
| 9bdbd59141 | |||
| 91b12651d0 | |||
| 31daedcd1a | |||
| 61ef6fd37e | |||
| 2ef2a5f69b | |||
| b9d9b87f2e | |||
| 1e775e1f62 | |||
| fe996ae9df | |||
| b20e7391d6 | |||
| 922095052b | |||
| 6378008a28 | |||
| 073257468d | |||
| 3ae8b37bfb | |||
| ad4db85334 | |||
| 78bbb9748c | |||
| fc51c22746 | |||
| 3f48b13247 | |||
| 1f31e54fe0 | |||
| 559c24d4dd | |||
| 8c1e5c0d90 | |||
| c16137892f | |||
| e1f24ab277 | |||
| 475ecfe2c9 | |||
| 2ed1cc3eaf | |||
| 90b707c6a2 | |||
| 8cb9b4f393 | |||
| 4fc960ba2c | |||
| 5ff274749e | |||
| 6bb408c4c8 | |||
| 1387fe2a58 | |||
| 4b8808040b | |||
| b292d42e1d | |||
| cf63845af0 | |||
| 0432f4daa1 | |||
| 3704078d23 | |||
| c5be3a8c60 | |||
| 456d73a479 | |||
| f712d04474 | |||
| f13af7f391 | |||
| 434d03ea8c | |||
| 98134a2364 | |||
| d70557d63c | |||
| d5aeb348db | |||
| 696ff1a8fe | |||
| eab4a39b26 | |||
| fd738df91b | |||
| 0b33b20929 | |||
| def18f875a | |||
| bdc56406d7 | |||
| d9370c8ad8 | |||
| 2f2d5616b0 | |||
| 88bf5663c7 | |||
| 7479ea3167 | |||
| c9910b69da | |||
| 4f4e7cef1d | |||
| 754a81ae3e | |||
| 7676cb3944 | |||
| 8cd3804ce7 | |||
| d5a86edd86 | |||
| 0bee937c22 | |||
| abaa6883c8 | |||
| 65c6e36f41 | |||
| 9e3b3d85dc | |||
| a2ea24c60d | |||
| 3e3f5f32f7 | |||
| 18319c6081 | |||
| c443744e62 | |||
| 8b71a036ba | |||
| cf2ecebf1a | |||
| 579a6ef194 | |||
| 4a344b6517 | |||
| f54ba7d072 | |||
| 81717d562d | |||
| f571494b1b | |||
| 740b718395 | |||
| 67aef205f5 | |||
| 974932a4c7 | |||
| c8e7772b79 | |||
| 531181794c | |||
| 6e71a28256 | |||
| 765c2c8819 | |||
| 7d085e2e83 | |||
| de30e336f2 | |||
| b5b617a984 | |||
| c197fda6ff | |||
| cbd00f11f7 | |||
| eee4c39f4f | |||
| 6df1cb7897 | |||
| 92da3cb9cc | |||
| 8fc501f7aa | |||
| 1cc7736010 | |||
| fc9edbc4ce | |||
| 171a410aee | |||
| dbdf5faaac | |||
| e8f46057e1 | |||
| e25a79f187 | |||
| ca88a4adbb | |||
| 1c01ceb802 | |||
| 664d2e6eac | |||
| 4b0b57c191 | |||
| 61e4bd28e2 | |||
| 2e10e22402 | |||
| e7a15f4b00 | |||
| f06192b9f5 | |||
| 20084d4f3f | |||
| 82fc002216 | |||
| 38765cba8a | |||
| 9bcb08bec6 | |||
| 7094755a10 | |||
| 68b977fd73 | |||
| 00462d9884 | |||
| 8f76de68c6 | |||
| 5972241146 | |||
| 102d745e4c | |||
| bc68e72324 | |||
| 73366697ac | |||
| f49802326b | |||
| 556d5323af | |||
| f29ead339a | |||
| 06546014d4 | |||
| a3ced3768e | |||
| d34df339b7 | |||
| 1f1ca56d1a | |||
| a882e5901e | |||
| 3b524f1933 | |||
| 27b1d25bbf | |||
| 9bc5b21f61 | |||
| aefeb0a394 | |||
| 0b5ba77a7e | |||
| 6a64c3f33b | |||
| 5edb843414 | |||
| 0b8cc4694a | |||
| af6a22542b | |||
| 39840ad801 | |||
| 706bf4f117 | |||
| ace927676d | |||
| ced3a17bbf | |||
| 565cce122a | |||
| c7a80269c0 | |||
| 3ef1b64124 | |||
| 008d5ac44b | |||
| 62b56009b6 | |||
| 62803dd5f2 | |||
| f4326065af | |||
| 2ff9bd7287 | |||
| 36bab8d6a6 | |||
| a08cbe0d77 | |||
| 6b4ac591b6 | |||
| 1e39ae3218 | |||
| 312f58751d | |||
| 7904db8d18 | |||
| 5d770e126c | |||
| 84e4a86997 | |||
| c54f594c69 | |||
| beb0a3350a | |||
| 8fea9a66e7 | |||
| 428b81ed26 | |||
| cab6eb5470 | |||
| 9adc118e4f | |||
| cda640d9e0 | |||
| 99c9b62ef0 | |||
| ec3b8be254 | |||
| b9e1459e7b | |||
| 85ee2b4fbb | |||
| 63ad71ad83 | |||
| 6ad1f19095 | |||
| 954156d363 | |||
| a347de4f61 | |||
| ab6ef7b866 | |||
| be8a7e16b7 | |||
| f4c5f83855 | |||
| 235efa420d | |||
| 10515a7485 | |||
| 51db367286 | |||
| e32fd6b0f0 | |||
| 03c0cb80b7 | |||
| d22995b975 | |||
| a7c8a739b3 | |||
| 17c544d740 | |||
| e0a1075e6e | |||
| c0fa93bb39 | |||
| d2b95a7043 | |||
| 9fd8b5da49 | |||
| 521c27a5f7 | |||
| d3133eb916 | |||
| 0154ea0be4 | |||
| a112164f7d | |||
| f257e32a26 | |||
| 07385ab6b0 | |||
| 04c7c8fb70 | |||
| 443f085f4e | |||
| c396555f5d | |||
| 51e257f082 | |||
| ef79aecefd | |||
| 426307c030 | |||
| e0b9fccdfd | |||
| 45a2f9845a |
@@ -1,84 +0,0 @@
|
||||
{
|
||||
"hooks": {
|
||||
"PostToolUse": [
|
||||
{
|
||||
"matcher": "Task",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "entire hooks claude-code post-task"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": "TodoWrite",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "entire hooks claude-code post-todo"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Task",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "entire hooks claude-code pre-task"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"SessionEnd": [
|
||||
{
|
||||
"matcher": "",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "entire hooks claude-code session-end"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"SessionStart": [
|
||||
{
|
||||
"matcher": "",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "entire hooks claude-code session-start"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"Stop": [
|
||||
{
|
||||
"matcher": "",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "entire hooks claude-code stop"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"UserPromptSubmit": [
|
||||
{
|
||||
"matcher": "",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "entire hooks claude-code user-prompt-submit"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"permissions": {
|
||||
"deny": [
|
||||
"Read(./.entire/metadata/**)"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
# Taste (Continuously Learned by [CommandCode][cmd])
|
||||
|
||||
[cmd]: https://commandcode.ai/
|
||||
|
||||
# github-actions
|
||||
- Use `actions/checkout@v6` and `actions/setup-node@v6` (not v4) in GitHub Actions workflows. Confidence: 0.65
|
||||
- Use Node.js version 24 in GitHub Actions workflows (not 20). Confidence: 0.65
|
||||
|
||||
# project
|
||||
- This project is **prompts.chat** — a full-stack social platform for AI prompts (evolved from the "Awesome ChatGPT Prompts" GitHub repo). Confidence: 0.95
|
||||
- Package manager is npm (not pnpm or yarn). Confidence: 0.95
|
||||
|
||||
# architecture
|
||||
- Use Next.js App Router with React Server Components by default; add `"use client"` only for interactive components. Confidence: 0.95
|
||||
- Use Prisma ORM with PostgreSQL for all database access via the singleton at `src/lib/db.ts`. Confidence: 0.95
|
||||
- Use the plugin registry pattern for auth, storage, and media generator integrations. Confidence: 0.90
|
||||
- Use `revalidateTag()` for cache invalidation after mutations. Confidence: 0.90
|
||||
|
||||
# typescript
|
||||
- Use TypeScript 5 in strict mode throughout the project. Confidence: 0.95
|
||||
|
||||
# styling
|
||||
- Use Tailwind CSS 4 + Radix UI + shadcn/ui for all UI components. Confidence: 0.95
|
||||
- Use the `cn()` utility for conditional/merged Tailwind class names. Confidence: 0.90
|
||||
|
||||
# api
|
||||
- Validate all API route inputs with Zod schemas. Confidence: 0.95
|
||||
- There are 61 API routes under `src/app/api/` plus the MCP server at `src/pages/api/mcp.ts`. Confidence: 0.90
|
||||
|
||||
# i18n
|
||||
- Use `useTranslations()` (client) and `getTranslations()` (server) from next-intl for all user-facing strings. Confidence: 0.95
|
||||
- Support 17 locales with RTL support for Arabic, Hebrew, and Farsi. Confidence: 0.90
|
||||
|
||||
# database
|
||||
- Use soft deletes (`deletedAt` field) on Prompt and Comment models — never hard-delete these records. Confidence: 0.95
|
||||
|
||||
+5
-1
@@ -10,6 +10,7 @@ dist
|
||||
# Git
|
||||
.git
|
||||
.gitignore
|
||||
.gitattributes
|
||||
|
||||
# IDE
|
||||
.vscode
|
||||
@@ -23,10 +24,11 @@ dist
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.sentry-build-plugin
|
||||
|
||||
# Docker
|
||||
compose.yml
|
||||
docker-compose*.yml
|
||||
Dockerfile*
|
||||
|
||||
# Development files
|
||||
.github
|
||||
@@ -38,6 +40,8 @@ packages
|
||||
# Test files
|
||||
coverage
|
||||
.nyc_output
|
||||
vitest.config.ts
|
||||
vitest.setup.ts
|
||||
|
||||
# OS files
|
||||
.DS_Store
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
tmp/
|
||||
settings.local.json
|
||||
metadata/
|
||||
logs/
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"strategy": "manual-commit",
|
||||
"enabled": true,
|
||||
"telemetry": true
|
||||
}
|
||||
+51
-1
@@ -63,4 +63,54 @@ CRON_SECRET="your-secret-key-here"
|
||||
# FAL_VIDEO_MODELS="fal-ai/veo3,fal-ai/kling-video/v2/master/text-to-video" # Comma-separated list of video models
|
||||
# FAL_IMAGE_MODELS="fal-ai/flux-pro/v1.1-ultra,fal-ai/flux/dev" # Comma-separated list of image models
|
||||
|
||||
# SENTRY_AUTH_TOKEN=sentry-auth-token
|
||||
# SENTRY_AUTH_TOKEN=sentry-auth-token
|
||||
|
||||
# GOOGLE_ADSENSE_ACCOUNT=ca-pub-xxxxxxxxxxxxxxxx
|
||||
|
||||
# ==============================================================================
|
||||
# SSO Authentication Configurations
|
||||
#
|
||||
# Why two generic providers (OIDC vs OAuth 2.0)?
|
||||
# - "OIDC" (OpenID Connect) performs strict cryptographic validation on the
|
||||
# ID Token (verifying 'aud' matching the client ID, 'iss' matching issuer, etc).
|
||||
# Use this for standard providers like Google, Auth0, Okta, Keycloak.
|
||||
# - "OAuth 2.0" bypasses strict OIDC validation. Use this for legacy or
|
||||
# enterprise systems that do not return OIDC ID Tokens
|
||||
# ==============================================================================
|
||||
|
||||
# Generic OIDC Config (Strict Validation)
|
||||
# ⚠️ Ensure you add "oidc" to the `providers` array in your prompts.config.ts file
|
||||
# Callback URL to whitelist: <your-domain>/api/auth/callback/oidc
|
||||
# AUTH_OIDC_ID="dummy-oidc-client-id"
|
||||
# AUTH_OIDC_SECRET="dummy-oidc-client-secret"
|
||||
# AUTH_OIDC_ISSUER="https://oidc.example.com"
|
||||
# AUTH_OIDC_WELLKNOWN="https://oidc.example.com/.well-known/openid-configuration"
|
||||
# AUTH_OIDC_SCOPE="openid email profile"
|
||||
# AUTH_OIDC_NAME="Company OIDC"
|
||||
# Optional overrides (uncomment to use):
|
||||
# AUTH_OIDC_LOGO="https://your-domain.com/oidc-logo.png" # Local path or full URL to button image
|
||||
# AUTH_OIDC_AUTHORIZATION_URL="https://oidc.example.com/authorize"
|
||||
# AUTH_OIDC_TOKEN_URL="https://oidc.example.com/token"
|
||||
# AUTH_OIDC_USERINFO_URL="https://oidc.example.com/userinfo"
|
||||
# AUTH_OIDC_JWKS_URL="https://oidc.example.com/jwks"
|
||||
# AUTH_OIDC_TOKEN_AUTH_METHOD="client_secret_post" # Allowed values: "client_secret_basic", "client_secret_post", "none"
|
||||
# AUTH_OIDC_ENABLE_PKCE="true" # PKCE is enabled by default. Set to "false" to disable.
|
||||
|
||||
# Generic OAuth 2.0 Config (Loose Validation)
|
||||
# ⚠️ Ensure you add "oauth" to the `providers` array in your prompts.config.ts file
|
||||
# Callback URL to whitelist: <your-domain>/api/auth/callback/oauth
|
||||
# AUTH_OAUTH_ID="dummy-oauth-client-id"
|
||||
# AUTH_OAUTH_SECRET="dummy-oauth-client-secret"
|
||||
# AUTH_OAUTH_ISSUER="https://sso.example.com"
|
||||
# AUTH_OAUTH_WELLKNOWN="https://sso.example.com/.well-known/openid-configuration"
|
||||
# AUTH_OAUTH_SCOPE="email profile"
|
||||
# AUTH_OAUTH_NAME="Company SSO"
|
||||
# Optional overrides (uncomment to use):
|
||||
# AUTH_OAUTH_LOGO="https://your-domain.com/sso-logo.png" # Local path or full URL to button image
|
||||
# AUTH_OAUTH_AUTHORIZATION_URL="https://sso.example.com/authorize"
|
||||
# AUTH_OAUTH_TOKEN_URL="https://sso.example.com/token"
|
||||
# AUTH_OAUTH_USERINFO_URL="https://sso.example.com/userinfo"
|
||||
# AUTH_OAUTH_JWKS_URL="https://sso.example.com/jwks"
|
||||
# AUTH_OAUTH_TOKEN_AUTH_METHOD="client_secret_basic" # Allowed values: "client_secret_basic", "client_secret_post", "none"
|
||||
# AUTH_OAUTH_ENABLE_PKCE="true" # PKCE is enabled by default. Set to "false" to disable.
|
||||
|
||||
|
||||
@@ -48,3 +48,4 @@ next-env.d.ts
|
||||
|
||||
# Sentry Config File
|
||||
.env.sentry-build-plugin
|
||||
.env.docker
|
||||
|
||||
@@ -1,45 +1,68 @@
|
||||
# Docker Deployment Guide
|
||||
|
||||
Run your own prompts.chat instance with a single command.
|
||||
Run your own prompts.chat instance using Docker Compose.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name prompts \
|
||||
-p 4444:3000 \
|
||||
-v prompts-data:/data \
|
||||
ghcr.io/f/prompts.chat
|
||||
git clone https://github.com/f/prompts.chat.git
|
||||
cd prompts.chat
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
**First run:** The container will clone the repository and build the app (~3-5 minutes).
|
||||
**Subsequent runs:** Starts immediately using the cached build.
|
||||
|
||||
Open http://localhost:4444 in your browser.
|
||||
|
||||
## Custom Branding
|
||||
## Using a Pre-built Image
|
||||
|
||||
Customize your instance with environment variables:
|
||||
Edit `compose.yml` and replace the `build` block with the published image:
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name my-prompts \
|
||||
-p 4444:3000 \
|
||||
-v prompts-data:/data \
|
||||
-e PCHAT_NAME="Acme Prompts" \
|
||||
-e PCHAT_DESCRIPTION="Our team's AI prompt library" \
|
||||
-e PCHAT_COLOR="#ff6600" \
|
||||
-e PCHAT_AUTH_PROVIDERS="github,google" \
|
||||
-e PCHAT_LOCALES="en,es,fr" \
|
||||
ghcr.io/f/prompts.chat
|
||||
```yaml
|
||||
services:
|
||||
app:
|
||||
# build:
|
||||
# context: .
|
||||
# dockerfile: docker/Dockerfile
|
||||
image: ghcr.io/f/prompts.chat:latest
|
||||
```
|
||||
|
||||
> **Note:** Branding is applied during the first build. To change branding later, delete the volume and re-run:
|
||||
> ```bash
|
||||
> docker rm -f my-prompts
|
||||
> docker volume rm prompts-data
|
||||
> docker run ... # with new env vars
|
||||
> ```
|
||||
Then run:
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
## Standalone (Bring Your Own Database)
|
||||
|
||||
If you already have a PostgreSQL instance, you can run just the app container:
|
||||
|
||||
```bash
|
||||
docker build -f docker/Dockerfile -t prompts.chat .
|
||||
docker run -d \
|
||||
--name prompts \
|
||||
-p 4444:3000 \
|
||||
-e DATABASE_URL="postgresql://user:pass@your-db-host:5432/prompts?schema=public" \
|
||||
-e AUTH_SECRET="$(openssl rand -base64 32)" \
|
||||
prompts.chat
|
||||
```
|
||||
|
||||
## Custom Branding
|
||||
|
||||
All branding is configured via `PCHAT_*` environment variables at runtime -- no rebuild needed.
|
||||
|
||||
```yaml
|
||||
# compose.yml
|
||||
services:
|
||||
app:
|
||||
environment:
|
||||
# ... existing vars ...
|
||||
PCHAT_NAME: "Acme Prompts"
|
||||
PCHAT_DESCRIPTION: "Our team's AI prompt library"
|
||||
PCHAT_COLOR: "#ff6600"
|
||||
PCHAT_AUTH_PROVIDERS: "github,google"
|
||||
PCHAT_LOCALES: "en,es,fr"
|
||||
```
|
||||
|
||||
Then restart: `docker compose up -d`
|
||||
|
||||
## Configuration Variables
|
||||
|
||||
@@ -95,103 +118,102 @@ All variables are prefixed with `PCHAT_` to avoid conflicts.
|
||||
|
||||
| Variable | Description | Default |
|
||||
|----------|-------------|---------|
|
||||
| `AUTH_SECRET` | Secret for authentication tokens | Auto-generated |
|
||||
| `PORT` | Internal container port | `3000` |
|
||||
| `DATABASE_URL` | PostgreSQL connection string | Internal DB |
|
||||
| `AUTH_SECRET` | Secret for authentication tokens | Auto-generated (set explicitly for production) |
|
||||
| `DATABASE_URL` | PostgreSQL connection string | Set in compose.yml |
|
||||
| `DIRECT_URL` | Direct PostgreSQL URL (bypasses poolers) | Same as DATABASE_URL |
|
||||
| `PORT` | Host port mapping | `4444` |
|
||||
|
||||
## Production Setup
|
||||
|
||||
For production, set `AUTH_SECRET` explicitly:
|
||||
For production, always set `AUTH_SECRET` explicitly:
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name prompts \
|
||||
-p 4444:3000 \
|
||||
-v prompts-data:/data \
|
||||
-e AUTH_SECRET="$(openssl rand -base64 32)" \
|
||||
-e PCHAT_NAME="My Company Prompts" \
|
||||
ghcr.io/f/prompts.chat
|
||||
# Generate a secret
|
||||
export AUTH_SECRET=$(openssl rand -base64 32)
|
||||
|
||||
# Start with explicit secret
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
Or add it to a `.env` file next to `compose.yml`:
|
||||
|
||||
```env
|
||||
AUTH_SECRET=your-secret-key-here
|
||||
```
|
||||
|
||||
### With OAuth Providers
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name prompts \
|
||||
-p 4444:3000 \
|
||||
-v prompts-data:/data \
|
||||
-e AUTH_SECRET="your-secret-key" \
|
||||
-e PCHAT_AUTH_PROVIDERS="github,google" \
|
||||
-e AUTH_GITHUB_ID="your-github-client-id" \
|
||||
-e AUTH_GITHUB_SECRET="your-github-client-secret" \
|
||||
-e AUTH_GOOGLE_ID="your-google-client-id" \
|
||||
-e AUTH_GOOGLE_SECRET="your-google-client-secret" \
|
||||
ghcr.io/f/prompts.chat
|
||||
```yaml
|
||||
# compose.yml
|
||||
services:
|
||||
app:
|
||||
environment:
|
||||
# ... existing vars ...
|
||||
PCHAT_AUTH_PROVIDERS: "github,google"
|
||||
AUTH_GITHUB_ID: "your-github-client-id"
|
||||
AUTH_GITHUB_SECRET: "your-github-client-secret"
|
||||
AUTH_GOOGLE_ID: "your-google-client-id"
|
||||
AUTH_GOOGLE_SECRET: "your-google-client-secret"
|
||||
```
|
||||
|
||||
### With AI Features (OpenAI)
|
||||
|
||||
```yaml
|
||||
# compose.yml
|
||||
services:
|
||||
app:
|
||||
environment:
|
||||
# ... existing vars ...
|
||||
PCHAT_FEATURE_AI_SEARCH: "true"
|
||||
OPENAI_API_KEY: "sk-..."
|
||||
```
|
||||
|
||||
## Database Seeding
|
||||
|
||||
Seed the database with example prompts:
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name prompts \
|
||||
-p 4444:3000 \
|
||||
-v prompts-data:/data \
|
||||
-e PCHAT_FEATURE_AI_SEARCH="true" \
|
||||
-e OPENAI_API_KEY="sk-..." \
|
||||
ghcr.io/f/prompts.chat
|
||||
docker compose exec app npx prisma db seed
|
||||
```
|
||||
|
||||
## Custom Logo
|
||||
|
||||
Mount your logo file:
|
||||
Mount your logo file into the app container:
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name prompts \
|
||||
-p 4444:3000 \
|
||||
-v prompts-data:/data \
|
||||
-v ./my-logo.svg:/data/app/public/logo.svg \
|
||||
-e PCHAT_NAME="My App" \
|
||||
ghcr.io/f/prompts.chat
|
||||
```yaml
|
||||
# compose.yml
|
||||
services:
|
||||
app:
|
||||
volumes:
|
||||
- ./my-logo.svg:/app/public/logo.svg
|
||||
environment:
|
||||
PCHAT_LOGO: "/logo.svg"
|
||||
```
|
||||
|
||||
## Data Persistence
|
||||
|
||||
### All-in-One Image
|
||||
|
||||
Data is stored in `/data` inside the container:
|
||||
- `/data/postgres` - PostgreSQL database files
|
||||
|
||||
Mount a volume to persist data:
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
-v prompts-data:/data \
|
||||
ghcr.io/f/prompts.chat
|
||||
```
|
||||
PostgreSQL data is stored in the `postgres_data` named volume and persists across container restarts, rebuilds, and image updates.
|
||||
|
||||
### Backup
|
||||
|
||||
```bash
|
||||
# Backup database
|
||||
docker exec prompts pg_dump -U prompts prompts > backup.sql
|
||||
docker compose exec db pg_dump -U prompts prompts > backup.sql
|
||||
|
||||
# Restore database
|
||||
docker exec -i prompts psql -U prompts prompts < backup.sql
|
||||
docker compose exec -T db psql -U prompts prompts < backup.sql
|
||||
```
|
||||
|
||||
## Building Locally
|
||||
|
||||
Build and run locally:
|
||||
|
||||
```bash
|
||||
docker build -f docker/Dockerfile -t prompts.chat .
|
||||
docker run -p 4444:3000 -v prompts-data:/data prompts.chat
|
||||
docker compose build
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
## Health Check
|
||||
|
||||
The container includes a health check endpoint:
|
||||
The app container includes a health check endpoint:
|
||||
|
||||
```bash
|
||||
curl http://localhost:4444/api/health
|
||||
@@ -211,88 +233,108 @@ Response:
|
||||
### View Logs
|
||||
|
||||
```bash
|
||||
# All logs
|
||||
docker logs prompts
|
||||
# All services
|
||||
docker compose logs
|
||||
|
||||
# Follow logs
|
||||
docker logs -f prompts
|
||||
docker compose logs -f
|
||||
|
||||
# PostgreSQL logs (inside container)
|
||||
docker exec prompts cat /var/log/supervisor/postgresql.log
|
||||
# App logs only
|
||||
docker compose logs app
|
||||
|
||||
# Next.js logs (inside container)
|
||||
docker exec prompts cat /var/log/supervisor/nextjs.log
|
||||
# Database logs only
|
||||
docker compose logs db
|
||||
```
|
||||
|
||||
### Database Access
|
||||
|
||||
```bash
|
||||
# Connect to PostgreSQL
|
||||
docker exec -it prompts psql -U prompts -d prompts
|
||||
docker compose exec db psql -U prompts -d prompts
|
||||
|
||||
# Run SQL query
|
||||
docker exec prompts psql -U prompts -d prompts -c "SELECT COUNT(*) FROM \"Prompt\""
|
||||
# Run a query
|
||||
docker compose exec db psql -U prompts -d prompts -c "SELECT COUNT(*) FROM \"Prompt\""
|
||||
```
|
||||
|
||||
### Container Shell
|
||||
|
||||
```bash
|
||||
docker exec -it prompts bash
|
||||
docker compose exec app sh
|
||||
docker compose exec db bash
|
||||
```
|
||||
|
||||
### Common Issues
|
||||
|
||||
**Container won't start:**
|
||||
- Check logs: `docker logs prompts`
|
||||
- Ensure port 4444 is available: `lsof -i :4444`
|
||||
**App container keeps restarting:**
|
||||
- Check logs: `docker compose logs app`
|
||||
- Database may not be ready yet -- the entrypoint retries for up to 60 seconds
|
||||
|
||||
**Database connection errors:**
|
||||
- Wait for PostgreSQL to initialize (can take 30-60 seconds on first run)
|
||||
- Check database logs: `docker exec prompts cat /var/log/supervisor/postgresql.log`
|
||||
- Verify the `db` service is healthy: `docker compose ps`
|
||||
- Check database logs: `docker compose logs db`
|
||||
|
||||
**Authentication issues:**
|
||||
- Ensure `AUTH_SECRET` is set for production
|
||||
- For OAuth, verify callback URLs are configured correctly
|
||||
|
||||
## Resource Requirements
|
||||
|
||||
Minimum:
|
||||
- 1 CPU core
|
||||
- 1GB RAM
|
||||
- 2GB disk space
|
||||
|
||||
Recommended:
|
||||
- 2 CPU cores
|
||||
- 2GB RAM
|
||||
- 10GB disk space
|
||||
- Set `AUTH_SECRET` explicitly for production
|
||||
- For OAuth, verify callback URLs match your domain
|
||||
|
||||
## Updating
|
||||
|
||||
```bash
|
||||
# Pull latest image
|
||||
docker pull ghcr.io/f/prompts.chat
|
||||
# If using pre-built images
|
||||
docker compose pull
|
||||
docker compose up -d
|
||||
|
||||
# Stop and remove old container
|
||||
docker stop prompts && docker rm prompts
|
||||
|
||||
# Start new container (data persists in volume)
|
||||
docker run -d \
|
||||
--name prompts \
|
||||
-p 4444:3000 \
|
||||
-v prompts-data:/data \
|
||||
-e AUTH_SECRET="your-secret-key" \
|
||||
ghcr.io/f/prompts.chat
|
||||
# If building locally
|
||||
git pull
|
||||
docker compose build
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
## Security Considerations
|
||||
Data persists in the `postgres_data` volume across updates.
|
||||
|
||||
1. **Always set AUTH_SECRET** in production
|
||||
2. **Use HTTPS** - put a reverse proxy (nginx, Caddy, Traefik) in front
|
||||
3. **Limit exposed ports** - only expose what's needed
|
||||
4. **Regular updates** - pull the latest image regularly
|
||||
5. **Backup data** - regularly backup the `/data` volume
|
||||
## Migrating from the Old Single-Image Setup
|
||||
|
||||
## Example: Running Behind Nginx
|
||||
If you were using the previous all-in-one Docker image:
|
||||
|
||||
```bash
|
||||
# 1. Export your database from the old container
|
||||
docker exec prompts pg_dump -U prompts prompts > backup.sql
|
||||
|
||||
# 2. Stop and remove the old container
|
||||
docker stop prompts && docker rm prompts
|
||||
|
||||
# 3. Start the new compose setup
|
||||
docker compose up -d
|
||||
|
||||
# 4. Import your data
|
||||
docker compose exec -T db psql -U prompts prompts < backup.sql
|
||||
```
|
||||
|
||||
## Resource Requirements
|
||||
|
||||
**Runtime** (after first build):
|
||||
- 1 CPU core
|
||||
- 1GB RAM
|
||||
- 2GB disk space
|
||||
|
||||
**First-run build** (Next.js compilation on startup):
|
||||
- 1 CPU core
|
||||
- Higher memory required (OOM may occur with low limits)
|
||||
- 2GB disk space
|
||||
|
||||
> ⚠️ If you see `Killed` followed by `exited with code 137` during first startup,
|
||||
> your Docker container likely ran out of memory during the build step.
|
||||
> Increasing Docker's memory allocation (e.g., ~4GB or more) can help resolve this.
|
||||
> On Docker Desktop: Settings → Resources → Memory.
|
||||
|
||||
**Recommended for production:**
|
||||
- 2 CPU cores
|
||||
- 2GB RAM (runtime)
|
||||
- 10GB disk space
|
||||
|
||||
## Running Behind a Reverse Proxy
|
||||
|
||||
### Nginx
|
||||
|
||||
```nginx
|
||||
server {
|
||||
@@ -316,6 +358,23 @@ server {
|
||||
}
|
||||
```
|
||||
|
||||
### Caddy
|
||||
|
||||
```caddyfile
|
||||
prompts.example.com {
|
||||
reverse_proxy localhost:4444
|
||||
}
|
||||
```
|
||||
|
||||
## Security Considerations
|
||||
|
||||
1. **Always set AUTH_SECRET** in production
|
||||
2. **Use HTTPS** -- put a reverse proxy (Nginx, Caddy, Traefik) in front
|
||||
3. **Change default database password** -- update `POSTGRES_PASSWORD` in compose.yml and the connection strings
|
||||
4. **Limit exposed ports** -- only expose what's needed
|
||||
5. **Regular updates** -- pull the latest image regularly
|
||||
6. **Backup data** -- regularly backup the database
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
# Governance
|
||||
|
||||
This document describes the governance model for prompts.chat.
|
||||
|
||||
## Overview
|
||||
|
||||
prompts.chat is an open-source project that welcomes contributions from anyone. The project operates with a small group of maintainers who are responsible for the overall direction, code review, and release process. We aim to grow this group over time and welcome new maintainers who demonstrate sustained, quality contributions.
|
||||
|
||||
## Roles
|
||||
|
||||
### Users
|
||||
|
||||
Anyone who uses prompts.chat, whether by browsing the website, self-hosting the platform, using the CLI, or integrating with the MCP server. Users are encouraged to file issues, suggest features, and contribute prompts.
|
||||
|
||||
### Contributors
|
||||
|
||||
Anyone who contributes to the project. This includes submitting prompts through the web platform, opening pull requests, reporting bugs, improving documentation, or helping other users. All contributors are listed at https://github.com/f/prompts.chat/graphs/contributors.
|
||||
|
||||
### Maintainers
|
||||
|
||||
People with commit access to the repository. Maintainers review and merge pull requests, triage issues, and participate in project decisions. The current list of maintainers is in [OWNERS.md](OWNERS.md).
|
||||
|
||||
Maintainers are expected to:
|
||||
- Review pull requests in a timely manner
|
||||
- Help triage and respond to issues
|
||||
- Follow the project's code of conduct
|
||||
- Participate in governance discussions when they arise
|
||||
|
||||
### Project Lead
|
||||
|
||||
The project lead has final decision-making authority on project direction, releases, and governance changes. The project lead is responsible for ensuring the project stays aligned with its mission and the broader community's needs.
|
||||
|
||||
## Becoming a Maintainer
|
||||
|
||||
New maintainers are added based on sustained, high-quality contributions and demonstrated understanding of the project. There is no fixed checklist, but the following signals are considered:
|
||||
|
||||
- A track record of accepted pull requests over several months
|
||||
- Thoughtful code reviews on other people's contributions
|
||||
- Constructive participation in issue discussions
|
||||
- Reliability and follow-through on commitments
|
||||
|
||||
Any existing maintainer can nominate a contributor for maintainer status. The nomination is discussed among the current maintainers, and the project lead makes the final decision. We are actively looking to grow the maintainer team, especially from contributors at different organizations.
|
||||
|
||||
## Decision Making
|
||||
|
||||
Most day-to-day decisions (bug fixes, small features, documentation updates) are made through the standard pull request review process. Any maintainer can approve and merge a PR.
|
||||
|
||||
For larger decisions (new features with broad impact, architectural changes, governance changes, dependency additions), we use a "lazy consensus" approach:
|
||||
|
||||
1. A proposal is opened as a GitHub issue or discussion.
|
||||
2. The proposal is left open for at least 7 days to allow feedback.
|
||||
3. If there are no objections from maintainers, the proposal is accepted.
|
||||
4. If there are objections, the maintainers discuss and try to reach agreement. The project lead has final say if consensus cannot be reached.
|
||||
|
||||
## Changing This Document
|
||||
|
||||
Changes to this governance document follow the same decision-making process described above. Any maintainer or contributor can propose changes by opening a pull request.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
All participants in the project are expected to be respectful, constructive, and welcoming. We do not tolerate harassment, discrimination, or abusive behavior in any form. Violations can be reported to the project lead or through the channels listed in [SECURITY.md](SECURITY.md).
|
||||
@@ -1,121 +1,14 @@
|
||||
Creative Commons Legal Code
|
||||
This project uses a dual-license model:
|
||||
|
||||
CC0 1.0 Universal
|
||||
- **Source code and site-authored content** (everything under src/,
|
||||
prisma/, scripts/, and all configuration/build files) is licensed under
|
||||
the MIT License. This includes the interactive book content in
|
||||
src/content/book/.
|
||||
See LICENSE-MIT for the full text.
|
||||
|
||||
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
|
||||
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
|
||||
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
|
||||
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
|
||||
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
|
||||
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
|
||||
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
|
||||
HEREUNDER.
|
||||
- **Prompt content and data** (prompts.csv, PROMPTS.md, and all user-submitted
|
||||
prompt text) is dedicated to the public domain under CC0 1.0 Universal.
|
||||
See LICENSE-CC0 for the full text.
|
||||
|
||||
Statement of Purpose
|
||||
|
||||
The laws of most jurisdictions throughout the world automatically confer
|
||||
exclusive Copyright and Related Rights (defined below) upon the creator
|
||||
and subsequent owner(s) (each and all, an "owner") of an original work of
|
||||
authorship and/or a database (each, a "Work").
|
||||
|
||||
Certain owners wish to permanently relinquish those rights to a Work for
|
||||
the purpose of contributing to a commons of creative, cultural and
|
||||
scientific works ("Commons") that the public can reliably and without fear
|
||||
of later claims of infringement build upon, modify, incorporate in other
|
||||
works, reuse and redistribute as freely as possible in any form whatsoever
|
||||
and for any purposes, including without limitation commercial purposes.
|
||||
These owners may contribute to the Commons to promote the ideal of a free
|
||||
culture and the further production of creative, cultural and scientific
|
||||
works, or to gain reputation or greater distribution for their Work in
|
||||
part through the use and efforts of others.
|
||||
|
||||
For these and/or other purposes and motivations, and without any
|
||||
expectation of additional consideration or compensation, the person
|
||||
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
|
||||
is an owner of Copyright and Related Rights in the Work, voluntarily
|
||||
elects to apply CC0 to the Work and publicly distribute the Work under its
|
||||
terms, with knowledge of his or her Copyright and Related Rights in the
|
||||
Work and the meaning and intended legal effect of CC0 on those rights.
|
||||
|
||||
1. Copyright and Related Rights. A Work made available under CC0 may be
|
||||
protected by copyright and related or neighboring rights ("Copyright and
|
||||
Related Rights"). Copyright and Related Rights include, but are not
|
||||
limited to, the following:
|
||||
|
||||
i. the right to reproduce, adapt, distribute, perform, display,
|
||||
communicate, and translate a Work;
|
||||
ii. moral rights retained by the original author(s) and/or performer(s);
|
||||
iii. publicity and privacy rights pertaining to a person's image or
|
||||
likeness depicted in a Work;
|
||||
iv. rights protecting against unfair competition in regards to a Work,
|
||||
subject to the limitations in paragraph 4(a), below;
|
||||
v. rights protecting the extraction, dissemination, use and reuse of data
|
||||
in a Work;
|
||||
vi. database rights (such as those arising under Directive 96/9/EC of the
|
||||
European Parliament and of the Council of 11 March 1996 on the legal
|
||||
protection of databases, and under any national implementation
|
||||
thereof, including any amended or successor version of such
|
||||
directive); and
|
||||
vii. other similar, equivalent or corresponding rights throughout the
|
||||
world based on applicable law or treaty, and any national
|
||||
implementations thereof.
|
||||
|
||||
2. Waiver. To the greatest extent permitted by, but not in contravention
|
||||
of, applicable law, Affirmer hereby overtly, fully, permanently,
|
||||
irrevocably and unconditionally waives, abandons, and surrenders all of
|
||||
Affirmer's Copyright and Related Rights and associated claims and causes
|
||||
of action, whether now known or unknown (including existing as well as
|
||||
future claims and causes of action), in the Work (i) in all territories
|
||||
worldwide, (ii) for the maximum duration provided by applicable law or
|
||||
treaty (including future time extensions), (iii) in any current or future
|
||||
medium and for any number of copies, and (iv) for any purpose whatsoever,
|
||||
including without limitation commercial, advertising or promotional
|
||||
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
|
||||
member of the public at large and to the detriment of Affirmer's heirs and
|
||||
successors, fully intending that such Waiver shall not be subject to
|
||||
revocation, rescission, cancellation, termination, or any other legal or
|
||||
equitable action to disrupt the quiet enjoyment of the Work by the public
|
||||
as contemplated by Affirmer's express Statement of Purpose.
|
||||
|
||||
3. Public License Fallback. Should any part of the Waiver for any reason
|
||||
be judged legally invalid or ineffective under applicable law, then the
|
||||
Waiver shall be preserved to the maximum extent permitted taking into
|
||||
account Affirmer's express Statement of Purpose. In addition, to the
|
||||
extent the Waiver is so judged Affirmer hereby grants to each affected
|
||||
person a royalty-free, non transferable, non sublicensable, non exclusive,
|
||||
irrevocable and unconditional license to exercise Affirmer's Copyright and
|
||||
Related Rights in the Work (i) in all territories worldwide, (ii) for the
|
||||
maximum duration provided by applicable law or treaty (including future
|
||||
time extensions), (iii) in any current or future medium and for any number
|
||||
of copies, and (iv) for any purpose whatsoever, including without
|
||||
limitation commercial, advertising or promotional purposes (the
|
||||
"License"). The License shall be deemed effective as of the date CC0 was
|
||||
applied by Affirmer to the Work. Should any part of the License for any
|
||||
reason be judged legally invalid or ineffective under applicable law, such
|
||||
partial invalidity or ineffectiveness shall not invalidate the remainder
|
||||
of the License, and in such case Affirmer hereby affirms that he or she
|
||||
will not (i) exercise any of his or her remaining Copyright and Related
|
||||
Rights in the Work or (ii) assert any associated claims and causes of
|
||||
action with respect to the Work, in either case contrary to Affirmer's
|
||||
express Statement of Purpose.
|
||||
|
||||
4. Limitations and Disclaimers.
|
||||
|
||||
a. No trademark or patent rights held by Affirmer are waived, abandoned,
|
||||
surrendered, licensed or otherwise affected by this document.
|
||||
b. Affirmer offers the Work as-is and makes no representations or
|
||||
warranties of any kind concerning the Work, express, implied,
|
||||
statutory or otherwise, including without limitation warranties of
|
||||
title, merchantability, fitness for a particular purpose, non
|
||||
infringement, or the absence of latent or other defects, accuracy, or
|
||||
the present or absence of errors, whether or not discoverable, all to
|
||||
the greatest extent permissible under applicable law.
|
||||
c. Affirmer disclaims responsibility for clearing rights of other persons
|
||||
that may apply to the Work or any use thereof, including without
|
||||
limitation any person's Copyright and Related Rights in the Work.
|
||||
Further, Affirmer disclaims responsibility for obtaining any necessary
|
||||
consents, permissions or other rights required for any use of the
|
||||
Work.
|
||||
d. Affirmer understands and acknowledges that Creative Commons is not a
|
||||
party to this document and has no duty or obligation with respect to
|
||||
this CC0 or use of the Work.
|
||||
If you are unsure which license applies to a particular file, the MIT License
|
||||
applies by default.
|
||||
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
Creative Commons Legal Code
|
||||
|
||||
CC0 1.0 Universal
|
||||
|
||||
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
|
||||
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
|
||||
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
|
||||
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
|
||||
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
|
||||
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
|
||||
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
|
||||
HEREUNDER.
|
||||
|
||||
Statement of Purpose
|
||||
|
||||
The laws of most jurisdictions throughout the world automatically confer
|
||||
exclusive Copyright and Related Rights (defined below) upon the creator
|
||||
and subsequent owner(s) (each and all, an "owner") of an original work of
|
||||
authorship and/or a database (each, a "Work").
|
||||
|
||||
Certain owners wish to permanently relinquish those rights to a Work for
|
||||
the purpose of contributing to a commons of creative, cultural and
|
||||
scientific works ("Commons") that the public can reliably and without fear
|
||||
of later claims of infringement build upon, modify, incorporate in other
|
||||
works, reuse and redistribute as freely as possible in any form whatsoever
|
||||
and for any purposes, including without limitation commercial purposes.
|
||||
These owners may contribute to the Commons to promote the ideal of a free
|
||||
culture and the further production of creative, cultural and scientific
|
||||
works, or to gain reputation or greater distribution for their Work in
|
||||
part through the use and efforts of others.
|
||||
|
||||
For these and/or other purposes and motivations, and without any
|
||||
expectation of additional consideration or compensation, the person
|
||||
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
|
||||
is an owner of Copyright and Related Rights in the Work, voluntarily
|
||||
elects to apply CC0 to the Work and publicly distribute the Work under its
|
||||
terms, with knowledge of his or her Copyright and Related Rights in the
|
||||
Work and the meaning and intended legal effect of CC0 on those rights.
|
||||
|
||||
1. Copyright and Related Rights. A Work made available under CC0 may be
|
||||
protected by copyright and related or neighboring rights ("Copyright and
|
||||
Related Rights"). Copyright and Related Rights include, but are not
|
||||
limited to, the following:
|
||||
|
||||
i. the right to reproduce, adapt, distribute, perform, display,
|
||||
communicate, and translate a Work;
|
||||
ii. moral rights retained by the original author(s) and/or performer(s);
|
||||
iii. publicity and privacy rights pertaining to a person's image or
|
||||
likeness depicted in a Work;
|
||||
iv. rights protecting against unfair competition in regards to a Work,
|
||||
subject to the limitations in paragraph 4(a), below;
|
||||
v. rights protecting the extraction, dissemination, use and reuse of data
|
||||
in a Work;
|
||||
vi. database rights (such as those arising under Directive 96/9/EC of the
|
||||
European Parliament and of the Council of 11 March 1996 on the legal
|
||||
protection of databases, and under any national implementation
|
||||
thereof, including any amended or successor version of such
|
||||
directive); and
|
||||
vii. other similar, equivalent or corresponding rights throughout the
|
||||
world based on applicable law or treaty, and any national
|
||||
implementations thereof.
|
||||
|
||||
2. Waiver. To the greatest extent permitted by, but not in contravention
|
||||
of, applicable law, Affirmer hereby overtly, fully, permanently,
|
||||
irrevocably and unconditionally waives, abandons, and surrenders all of
|
||||
Affirmer's Copyright and Related Rights and associated claims and causes
|
||||
of action, whether now known or unknown (including existing as well as
|
||||
future claims and causes of action), in the Work (i) in all territories
|
||||
worldwide, (ii) for the maximum duration provided by applicable law or
|
||||
treaty (including future time extensions), (iii) in any current or future
|
||||
medium and for any number of copies, and (iv) for any purpose whatsoever,
|
||||
including without limitation commercial, advertising or promotional
|
||||
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
|
||||
member of the public at large and to the detriment of Affirmer's heirs and
|
||||
successors, fully intending that such Waiver shall not be subject to
|
||||
revocation, rescission, cancellation, termination, or any other legal or
|
||||
equitable action to disrupt the quiet enjoyment of the Work by the public
|
||||
as contemplated by Affirmer's express Statement of Purpose.
|
||||
|
||||
3. Public License Fallback. Should any part of the Waiver for any reason
|
||||
be judged legally invalid or ineffective under applicable law, then the
|
||||
Waiver shall be preserved to the maximum extent permitted taking into
|
||||
account Affirmer's express Statement of Purpose. In addition, to the
|
||||
extent the Waiver is so judged Affirmer hereby grants to each affected
|
||||
person a royalty-free, non transferable, non sublicensable, non exclusive,
|
||||
irrevocable and unconditional license to exercise Affirmer's Copyright and
|
||||
Related Rights in the Work (i) in all territories worldwide, (ii) for the
|
||||
maximum duration provided by applicable law or treaty (including future
|
||||
time extensions), (iii) in any current or future medium and for any number
|
||||
of copies, and (iv) for any purpose whatsoever, including without
|
||||
limitation commercial, advertising or promotional purposes (the
|
||||
"License"). The License shall be deemed effective as of the date CC0 was
|
||||
applied by Affirmer to the Work. Should any part of the License for any
|
||||
reason be judged legally invalid or ineffective under applicable law, such
|
||||
partial invalidity or ineffectiveness shall not invalidate the remainder
|
||||
of the License, and in such case Affirmer hereby affirms that he or she
|
||||
will not (i) exercise any of his or her remaining Copyright and Related
|
||||
Rights in the Work or (ii) assert any associated claims and causes of
|
||||
action with respect to the Work, in either case contrary to Affirmer's
|
||||
express Statement of Purpose.
|
||||
|
||||
4. Limitations and Disclaimers.
|
||||
|
||||
a. No trademark or patent rights held by Affirmer are waived, abandoned,
|
||||
surrendered, licensed or otherwise affected by this document.
|
||||
b. Affirmer offers the Work as-is and makes no representations or
|
||||
warranties of any kind concerning the Work, express, implied,
|
||||
statutory or otherwise, including without limitation warranties of
|
||||
title, merchantability, fitness for a particular purpose, non
|
||||
infringement, or the absence of latent or other defects, accuracy, or
|
||||
the present or absence of errors, whether or not discoverable, all to
|
||||
the greatest extent permissible under applicable law.
|
||||
c. Affirmer disclaims responsibility for clearing rights of other persons
|
||||
that may apply to the Work or any use thereof, including without
|
||||
limitation any person's Copyright and Related Rights in the Work.
|
||||
Further, Affirmer disclaims responsibility for obtaining any necessary
|
||||
consents, permissions or other rights required for any use of the
|
||||
Work.
|
||||
d. Affirmer understands and acknowledges that Creative Commons is not a
|
||||
party to this document and has no duty or obligation with respect to
|
||||
this CC0 or use of the Work.
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022-present Fatih Kadir Akin and contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,23 @@
|
||||
# Owners
|
||||
|
||||
This file lists the current maintainers and emeritus maintainers of prompts.chat.
|
||||
|
||||
## Project Lead
|
||||
|
||||
| Name | GitHub | Affiliation |
|
||||
|------|--------|-------------|
|
||||
| Fatih Kadir Akin | [@f](https://github.com/f) | Independent |
|
||||
|
||||
## Maintainers
|
||||
|
||||
| Name | GitHub | Affiliation | Since |
|
||||
|------|--------|-------------|-------|
|
||||
| Fatih Kadir Akin | [@f](https://github.com/f) | Independent | Dec 2022 |
|
||||
|
||||
## Emeritus Maintainers
|
||||
|
||||
None yet.
|
||||
|
||||
---
|
||||
|
||||
We are actively looking to grow the maintainer team. If you are interested in becoming a maintainer, please see [GOVERNANCE.md](GOVERNANCE.md) for how the process works. Sustained contributions and involvement in code review are the best way to get started.
|
||||
+50687
-1952
File diff suppressed because it is too large
Load Diff
@@ -34,12 +34,12 @@
|
||||
|
||||
<p align="center">
|
||||
<sub>
|
||||
🏆 Featured in <a href="https://www.forbes.com/sites/tjmccue/2023/01/19/chatgpt-success-completely-depends-on-your-prompt/">Forbes</a> ·
|
||||
🎓 Referenced by <a href="https://www.huit.harvard.edu/news/ai-prompts">Harvard</a>, <a href="https://etc.cuit.columbia.edu/news/columbia-prompt-library-effective-academic-ai-use">Columbia</a> ·
|
||||
📄 <a href="https://scholar.google.com/citations?user=AZ0Dg8YAAAAJ&hl=en">40+ academic citations</a> ·
|
||||
🏆 Featured in <a href="https://www.forbes.com/sites/tjmccue/2023/01/19/chatgpt-success-completely-depends-on-your-prompt/">Forbes</a> ·
|
||||
🎓 Referenced by <a href="https://www.huit.harvard.edu/news/ai-prompts">Harvard</a>, <a href="https://etc.cuit.columbia.edu/news/columbia-prompt-library-effective-academic-ai-use">Columbia</a> ·
|
||||
📄 <a href="https://scholar.google.com/citations?user=AZ0Dg8YAAAAJ&hl=en">40+ academic citations</a> ·
|
||||
❤️ <a href="https://huggingface.co/datasets/fka/prompts.chat">Most liked dataset</a> on Hugging Face<br>
|
||||
⭐ 143k+ GitHub stars ·
|
||||
🏅 <a href="https://spotlights-feed.github.com/spotlights/prompts-chat/index/">GitHub Staff Pick</a> ·
|
||||
⭐ 143k+ GitHub stars ·
|
||||
🏅 <a href="https://spotlights-feed.github.com/spotlights/prompts-chat/index/">GitHub Staff Pick</a> ·
|
||||
🚀 First prompt library (Dec 2022)
|
||||
</sub>
|
||||
</p>
|
||||
@@ -47,9 +47,9 @@
|
||||
<p align="center">
|
||||
<sub><strong>Loved by AI pioneers:</strong></sub><br>
|
||||
<sub>
|
||||
<a href="https://x.com/gdb/status/1602072566671110144"><strong>Greg Brockman</strong></a> (OpenAI Co-Founder) ·
|
||||
<a href="https://x.com/woj_zaremba/status/1601362952841760769"><strong>Wojciech Zaremba</strong></a> (OpenAI Co-Founder) ·
|
||||
<a href="https://x.com/clementdelangue/status/1830976369389642059"><strong>Clement Delangue</strong></a> (Hugging Face CEO) ·
|
||||
<a href="https://x.com/gdb/status/1602072566671110144"><strong>Greg Brockman</strong></a> (OpenAI Co-Founder) ·
|
||||
<a href="https://x.com/woj_zaremba/status/1601362952841760769"><strong>Wojciech Zaremba</strong></a> (OpenAI Co-Founder) ·
|
||||
<a href="https://x.com/clementdelangue/status/1830976369389642059"><strong>Clement Delangue</strong></a> (Hugging Face CEO) ·
|
||||
<a href="https://x.com/ashtom/status/1887250944427237816"><strong>Thomas Dohmke</strong></a> (Former GitHub CEO)
|
||||
</sub>
|
||||
</p>
|
||||
@@ -73,7 +73,7 @@ A curated collection of **prompt examples** for AI chat models. Originally creat
|
||||
|
||||
Learn prompt engineering with our **free, interactive guide** — 25+ chapters covering everything from basics to advanced techniques like chain-of-thought reasoning, few-shot learning, and AI agents.
|
||||
|
||||
**[Start Reading →](https://fka.gumroad.com/l/art-of-chatgpt-prompting)**
|
||||
**[Start Reading →](https://fka.gumroad.com/l/art-of-chatgpt-prompting)** (Source: https://github.com/f/prompts.chat/tree/main/src/content/book)
|
||||
|
||||
---
|
||||
|
||||
@@ -203,14 +203,6 @@ Use prompts.chat as an MCP server in your AI tools.
|
||||
<img height="30" alt="Sentry" src="https://raw.githubusercontent.com/f/prompts.chat/main/public/sponsors/sentry.svg">
|
||||
</picture>
|
||||
</a>
|
||||
<!-- MitteAI -->
|
||||
<a href="https://mitte.ai/?utm_source=prompts.chat">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/f/prompts.chat/main/public/sponsors/mitte-dark.svg">
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/f/prompts.chat/main/public/sponsors/mitte.svg">
|
||||
<img height="35" alt="MitteAI" src="https://raw.githubusercontent.com/f/prompts.chat/main/public/sponsors/mitte.svg">
|
||||
</picture>
|
||||
</a>
|
||||
<!-- Each Labs (py-[6px]) -->
|
||||
<a href="https://www.eachlabs.ai/?utm_source=promptschat&utm_medium=referral">
|
||||
<picture>
|
||||
@@ -219,12 +211,12 @@ Use prompts.chat as an MCP server in your AI tools.
|
||||
<img height="28" alt="Each Labs" src="https://raw.githubusercontent.com/f/prompts.chat/main/public/sponsors/eachlabs.png">
|
||||
</picture>
|
||||
</a>
|
||||
<!-- Warp (py-2) -->
|
||||
<a href="https://warp.dev/?utm_source=prompts.chat">
|
||||
<!-- CommandCode (py-1) -->
|
||||
<a href="https://commandcode.ai/?utm_source=prompts.chat">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/f/prompts.chat/main/public/sponsors/warp-dark.svg">
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/f/prompts.chat/main/public/sponsors/warp.svg">
|
||||
<img height="25" alt="Warp" src="https://raw.githubusercontent.com/f/prompts.chat/main/public/sponsors/warp.svg">
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/f/prompts.chat/main/public/sponsors/commandcode-dark.svg">
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/f/prompts.chat/main/public/sponsors/commandcode.svg">
|
||||
<img height="30" alt="CommandCode" src="https://raw.githubusercontent.com/f/prompts.chat/main/public/sponsors/commandcode.svg">
|
||||
</picture>
|
||||
</a>
|
||||
</p>
|
||||
@@ -246,4 +238,10 @@ Use prompts.chat as an MCP server in your AI tools.
|
||||
|
||||
## 📜 License
|
||||
|
||||
**[CC0 1.0 Universal (Public Domain)](https://creativecommons.org/publicdomain/zero/1.0/)** — Copy, modify, distribute, and use freely. No attribution required.
|
||||
This project is dual-licensed:
|
||||
|
||||
- **Source code and site-authored content** is licensed under the [MIT License](LICENSE-MIT),
|
||||
including the interactive book content in [`src/content/book`](src/content/book).
|
||||
- **Prompt content and data** (prompts.csv, PROMPTS.md, user-submitted prompts) is dedicated to the public domain under [CC0 1.0 Universal](LICENSE-CC0).
|
||||
|
||||
See [LICENSE](LICENSE) for details.
|
||||
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
| Version | Supported |
|
||||
|---------|-----------|
|
||||
| Latest | Yes |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
If you discover a security vulnerability in prompts.chat, please report it responsibly.
|
||||
|
||||
**Do NOT open a public GitHub issue for security vulnerabilities.**
|
||||
|
||||
Instead, please report vulnerabilities by emailing **security@prompts.chat**.
|
||||
|
||||
Include the following in your report:
|
||||
|
||||
- Description of the vulnerability
|
||||
- Steps to reproduce
|
||||
- Potential impact
|
||||
- Suggested fix (if any)
|
||||
|
||||
## CVE Coordination
|
||||
|
||||
We coordinate the CVE identification and disclosure process with the GitHub Security team. Confirmed
|
||||
vulnerabilities will be tracked through GitHub Security Advisories, and CVE IDs will be requested
|
||||
and assigned as appropriate.
|
||||
|
||||
## Scope
|
||||
The following are **out of scope**:
|
||||
|
||||
- Denial of service attacks
|
||||
- Social engineering
|
||||
- Issues in third-party dependencies (report these upstream)
|
||||
- Attacks requiring physical access
|
||||
|
||||
## Disclosure Policy
|
||||
|
||||
We ask that you give us reasonable time to address the issue before any public disclosure. We are
|
||||
committed to working with security researchers and will credit reporters (unless anonymity is
|
||||
preferred) once the issue is resolved.
|
||||
|
||||
## Thank You
|
||||
|
||||
We appreciate the security research community's efforts in helping keep prompts.chat and its users
|
||||
safe.
|
||||
+2
-2
@@ -32,9 +32,9 @@ This guide explains how to deploy **prompts.chat** on your own private server fo
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **Node.js** 18+
|
||||
- **Node.js** 24.x
|
||||
- **PostgreSQL** database
|
||||
- **npm** or **yarn**
|
||||
- **npm**
|
||||
|
||||
## Environment Variables
|
||||
|
||||
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
services:
|
||||
db:
|
||||
image: postgres:17-bookworm
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_USER: prompts
|
||||
# Change this for production — see Security Considerations in DOCKER.md
|
||||
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD:-prompts}"
|
||||
POSTGRES_DB: prompts
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U prompts -d prompts"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
start_period: 10s
|
||||
|
||||
app:
|
||||
image: ghcr.io/f/prompts.chat:latest
|
||||
# To build locally instead of using the pre-built image, comment out
|
||||
# the 'image' line above and uncomment the build block below:
|
||||
# build:
|
||||
# context: .
|
||||
# dockerfile: docker/Dockerfile
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${PORT:-4444}:3000"
|
||||
environment:
|
||||
DATABASE_URL: "postgresql://prompts:${POSTGRES_PASSWORD:-prompts}@db:5432/prompts?schema=public"
|
||||
DIRECT_URL: "postgresql://prompts:${POSTGRES_PASSWORD:-prompts}@db:5432/prompts?schema=public"
|
||||
AUTH_SECRET: "${AUTH_SECRET:-}"
|
||||
NODE_ENV: production
|
||||
# Branding (all optional — set any PCHAT_* variable to customize at runtime)
|
||||
# PCHAT_NAME: "My Prompt Library"
|
||||
# PCHAT_DESCRIPTION: "Our team's AI prompt collection"
|
||||
# PCHAT_COLOR: "#6366f1"
|
||||
# PCHAT_AUTH_PROVIDERS: "credentials"
|
||||
# PCHAT_LOCALES: "en"
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
+73
-28
@@ -1,49 +1,94 @@
|
||||
# prompts.chat Bootstrap Docker Image
|
||||
# Lightweight image that clones and builds on first run
|
||||
# prompts.chat Docker Image
|
||||
# Multi-stage build for a production-ready Next.js application
|
||||
#
|
||||
# Usage:
|
||||
# docker run -p 4444:3000 -v prompts-data:/data ghcr.io/f/prompts.chat
|
||||
# docker run -p 4444:3000 -v prompts-data:/data -e PCHAT_NAME="My App" ghcr.io/f/prompts.chat
|
||||
# Usage with Docker Compose:
|
||||
# docker compose up -d
|
||||
#
|
||||
# Usage standalone (bring your own PostgreSQL):
|
||||
# docker build -f docker/Dockerfile -t prompts.chat .
|
||||
# docker run -p 4444:3000 -e DATABASE_URL="postgresql://..." prompts.chat
|
||||
|
||||
FROM node:24-bookworm-slim
|
||||
# ---- Stage 1: Base ----
|
||||
FROM node:24-bookworm-slim AS base
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
openssl \
|
||||
curl \
|
||||
ca-certificates \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# ---- Stage 2: Dependencies ----
|
||||
FROM base AS deps
|
||||
|
||||
# Copy dependency manifests and Prisma schema (postinstall runs prisma generate)
|
||||
COPY package.json package-lock.json ./
|
||||
COPY prisma/schema.prisma prisma/schema.prisma
|
||||
COPY prisma.config.ts tsconfig.json ./
|
||||
|
||||
# Dummy DATABASE_URL for prisma generate (no actual connection is made)
|
||||
ENV DATABASE_URL="postgresql://dummy:dummy@localhost:5432/dummy"
|
||||
|
||||
RUN npm ci
|
||||
|
||||
# ---- Stage 3: Builder ----
|
||||
FROM base AS builder
|
||||
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY . .
|
||||
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
ENV DATABASE_URL="postgresql://dummy:dummy@localhost:5432/dummy"
|
||||
|
||||
RUN npm run build
|
||||
|
||||
# ---- Stage 4: Runner ----
|
||||
FROM node:24-bookworm-slim AS runner
|
||||
|
||||
LABEL org.opencontainers.image.source="https://github.com/f/prompts.chat"
|
||||
LABEL org.opencontainers.image.description="prompts.chat - Self-hosted AI prompt library"
|
||||
LABEL org.opencontainers.image.licenses="MIT"
|
||||
|
||||
# Install PostgreSQL, git, and utilities
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
postgresql-15 \
|
||||
postgresql-contrib-15 \
|
||||
supervisor \
|
||||
git \
|
||||
curl \
|
||||
openssl \
|
||||
curl \
|
||||
ca-certificates \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& mkdir -p /var/run/postgresql /var/log/supervisor \
|
||||
&& chown -R postgres:postgres /var/run/postgresql
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Create directories
|
||||
WORKDIR /app
|
||||
RUN mkdir -p /data/postgres /data/app && chown -R postgres:postgres /data/postgres
|
||||
|
||||
# Copy bootstrap scripts
|
||||
COPY docker/bootstrap.sh /bootstrap.sh
|
||||
COPY docker/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
|
||||
RUN chmod +x /bootstrap.sh
|
||||
RUN groupadd --system --gid 1001 nodejs && \
|
||||
useradd --system --uid 1001 --gid nodejs nextjs
|
||||
|
||||
# Copy standalone build output
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/public ./public
|
||||
|
||||
# Copy Prisma schema, migrations, and config for runtime migrations
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/prisma ./prisma
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/prisma.config.ts ./prisma.config.ts
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/package.json ./package.json
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/tsconfig.json ./tsconfig.json
|
||||
|
||||
# Install only Prisma CLI and its dependencies for runtime migrations
|
||||
RUN npm install --no-save prisma@6.19 dotenv
|
||||
|
||||
# Copy entrypoint script
|
||||
COPY --chown=nextjs:nodejs docker/entrypoint.sh ./entrypoint.sh
|
||||
RUN chmod +x ./entrypoint.sh
|
||||
|
||||
# Environment defaults
|
||||
ENV NODE_ENV=production
|
||||
ENV PORT=3000
|
||||
ENV HOSTNAME="0.0.0.0"
|
||||
ENV DATABASE_URL="postgresql://prompts:prompts@localhost:5432/prompts?schema=public"
|
||||
ENV DIRECT_URL="postgresql://prompts:prompts@localhost:5432/prompts?schema=public"
|
||||
ENV REPO_URL="https://github.com/f/prompts.chat.git"
|
||||
ENV HOSTNAME=0.0.0.0
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=180s --retries=3 \
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \
|
||||
CMD curl -f http://localhost:3000/api/health || exit 1
|
||||
|
||||
ENTRYPOINT ["/bootstrap.sh"]
|
||||
USER nextjs
|
||||
|
||||
ENTRYPOINT ["./entrypoint.sh"]
|
||||
|
||||
@@ -1,158 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo ""
|
||||
echo "╔═══════════════════════════════════════════════════════════════╗"
|
||||
echo "║ ║"
|
||||
echo "║ 🚀 prompts.chat - AI Prompt Library ║"
|
||||
echo "║ ║"
|
||||
echo "╚═══════════════════════════════════════════════════════════════╝"
|
||||
echo ""
|
||||
|
||||
# Paths
|
||||
APP_DIR="/data/app"
|
||||
PGDATA="/data/postgres"
|
||||
PGBIN="/usr/lib/postgresql/15/bin"
|
||||
BUILD_MARKER="/data/.built"
|
||||
|
||||
# Generate AUTH_SECRET if not provided
|
||||
if [ -z "$AUTH_SECRET" ]; then
|
||||
if [ -f "/data/.auth_secret" ]; then
|
||||
export AUTH_SECRET=$(cat /data/.auth_secret)
|
||||
else
|
||||
export AUTH_SECRET=$(openssl rand -base64 32)
|
||||
echo "$AUTH_SECRET" > /data/.auth_secret
|
||||
echo "⚠ AUTH_SECRET generated and saved"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Initialize PostgreSQL if needed
|
||||
if [ ! -f "$PGDATA/PG_VERSION" ]; then
|
||||
echo "▶ Initializing PostgreSQL..."
|
||||
su postgres -c "$PGBIN/initdb -D $PGDATA"
|
||||
|
||||
cat >> "$PGDATA/postgresql.conf" << EOF
|
||||
listen_addresses = 'localhost'
|
||||
port = 5432
|
||||
max_connections = 100
|
||||
shared_buffers = 128MB
|
||||
EOF
|
||||
|
||||
cat > "$PGDATA/pg_hba.conf" << EOF
|
||||
local all all trust
|
||||
host all all 127.0.0.1/32 trust
|
||||
host all all ::1/128 trust
|
||||
EOF
|
||||
|
||||
su postgres -c "$PGBIN/pg_ctl -D $PGDATA -l /tmp/pg.log start"
|
||||
sleep 3
|
||||
su postgres -c "$PGBIN/createuser -s prompts 2>/dev/null" || true
|
||||
su postgres -c "$PGBIN/createdb -O prompts prompts 2>/dev/null" || true
|
||||
su postgres -c "$PGBIN/pg_ctl -D $PGDATA stop"
|
||||
sleep 2
|
||||
echo "✓ PostgreSQL initialized"
|
||||
fi
|
||||
|
||||
# Clone and build on first run
|
||||
if [ ! -f "$BUILD_MARKER" ]; then
|
||||
echo ""
|
||||
echo "▶ First run detected - building prompts.chat..."
|
||||
echo ""
|
||||
|
||||
# Clone repository
|
||||
if [ ! -d "$APP_DIR/.git" ]; then
|
||||
echo "▶ Cloning repository..."
|
||||
rm -rf "$APP_DIR"
|
||||
git clone --depth 1 "$REPO_URL" "$APP_DIR"
|
||||
echo "✓ Repository cloned"
|
||||
fi
|
||||
|
||||
cd "$APP_DIR"
|
||||
|
||||
# Clean up unnecessary files
|
||||
rm -rf .github .claude packages .git
|
||||
|
||||
# Install dependencies (including devDependencies needed for build)
|
||||
echo "▶ Installing dependencies..."
|
||||
NODE_ENV=development npm ci
|
||||
echo "✓ Dependencies installed"
|
||||
|
||||
# Run docker-setup.js to generate config with branding
|
||||
echo "▶ Generating configuration..."
|
||||
node scripts/docker-setup.js
|
||||
echo "✓ Configuration generated"
|
||||
|
||||
# Generate Prisma client
|
||||
echo "▶ Generating Prisma client..."
|
||||
npx prisma generate
|
||||
echo "✓ Prisma client generated"
|
||||
|
||||
# Build Next.js
|
||||
echo "▶ Building Next.js application (this may take a few minutes)..."
|
||||
npm run build
|
||||
echo "✓ Build complete"
|
||||
|
||||
# Copy static files for standalone mode
|
||||
echo "▶ Copying static assets..."
|
||||
cp -r .next/static .next/standalone/.next/
|
||||
cp -r public .next/standalone/
|
||||
echo "✓ Static assets copied"
|
||||
|
||||
# Mark as built
|
||||
touch "$BUILD_MARKER"
|
||||
|
||||
echo ""
|
||||
echo "✅ Build complete! Starting application..."
|
||||
echo ""
|
||||
else
|
||||
echo "✓ Using existing build"
|
||||
cd "$APP_DIR"
|
||||
fi
|
||||
|
||||
# Start supervisor (manages PostgreSQL and Next.js)
|
||||
echo "▶ Starting services..."
|
||||
/usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf &
|
||||
SUPERVISOR_PID=$!
|
||||
|
||||
# Wait for PostgreSQL
|
||||
echo "▶ Waiting for PostgreSQL..."
|
||||
for i in $(seq 1 30); do
|
||||
if $PGBIN/pg_isready -h localhost -p 5432 >/dev/null 2>&1; then
|
||||
echo "✓ PostgreSQL is ready"
|
||||
break
|
||||
fi
|
||||
if [ $i -eq 30 ]; then
|
||||
echo "✗ PostgreSQL failed to start"
|
||||
exit 1
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
# Run migrations
|
||||
echo "▶ Running database migrations..."
|
||||
npx prisma migrate deploy
|
||||
echo "✓ Migrations complete"
|
||||
|
||||
# Seed on first run only
|
||||
SEED_MARKER="/data/.seeded"
|
||||
if [ ! -f "$SEED_MARKER" ]; then
|
||||
echo "▶ Seeding database..."
|
||||
if npx tsx prisma/seed.ts 2>/dev/null; then
|
||||
touch "$SEED_MARKER"
|
||||
echo "✓ Database seeded"
|
||||
else
|
||||
echo "⚠ Seeding skipped"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "╔═══════════════════════════════════════════════════════════════╗"
|
||||
echo "║ ║"
|
||||
echo "║ ✅ prompts.chat is running! ║"
|
||||
echo "║ ║"
|
||||
echo "║ 🌐 Open http://localhost:${PORT:-80} in your browser ║"
|
||||
echo "║ ║"
|
||||
echo "╚═══════════════════════════════════════════════════════════════╝"
|
||||
echo ""
|
||||
|
||||
wait $SUPERVISOR_PID
|
||||
Executable
+41
@@ -0,0 +1,41 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
# Generate AUTH_SECRET if not provided
|
||||
if [ -z "$AUTH_SECRET" ]; then
|
||||
AUTH_SECRET=$(openssl rand -base64 32)
|
||||
export AUTH_SECRET
|
||||
echo "[prompts.chat] AUTH_SECRET not set -- generated a random value."
|
||||
echo "[prompts.chat] Set AUTH_SECRET explicitly for production to persist sessions across restarts."
|
||||
fi
|
||||
|
||||
# Wait for PostgreSQL to be ready
|
||||
echo "[prompts.chat] Waiting for database..."
|
||||
MAX_RETRIES=30
|
||||
RETRY_COUNT=0
|
||||
until node -e "
|
||||
const net = require('net');
|
||||
const url = new URL(process.env.DATABASE_URL);
|
||||
const sock = net.createConnection({ host: url.hostname, port: url.port || 5432 });
|
||||
sock.setTimeout(2000);
|
||||
sock.on('connect', () => { sock.setTimeout(0); sock.destroy(); process.exit(0); });
|
||||
sock.on('timeout', () => { sock.destroy(); process.exit(1); });
|
||||
sock.on('error', () => process.exit(1));
|
||||
" 2>/dev/null; do
|
||||
RETRY_COUNT=$((RETRY_COUNT + 1))
|
||||
if [ "$RETRY_COUNT" -ge "$MAX_RETRIES" ]; then
|
||||
echo "[prompts.chat] ERROR: Database not reachable after ${MAX_RETRIES} attempts."
|
||||
exit 1
|
||||
fi
|
||||
echo "[prompts.chat] Database not ready (attempt ${RETRY_COUNT}/${MAX_RETRIES}), retrying in 2s..."
|
||||
sleep 2
|
||||
done
|
||||
|
||||
# Run database migrations
|
||||
echo "[prompts.chat] Running database migrations..."
|
||||
npx prisma migrate deploy
|
||||
echo "[prompts.chat] Migrations applied successfully."
|
||||
|
||||
# Start the application
|
||||
echo "[prompts.chat] Starting application on port ${PORT:-3000}..."
|
||||
exec node server.js
|
||||
@@ -1,28 +0,0 @@
|
||||
[supervisord]
|
||||
nodaemon=true
|
||||
user=root
|
||||
logfile=/var/log/supervisor/supervisord.log
|
||||
pidfile=/var/run/supervisord.pid
|
||||
childlogdir=/var/log/supervisor
|
||||
|
||||
[program:postgresql]
|
||||
command=/usr/lib/postgresql/15/bin/postgres -D /data/postgres
|
||||
user=postgres
|
||||
autostart=true
|
||||
autorestart=true
|
||||
priority=10
|
||||
stdout_logfile=/var/log/supervisor/postgresql.log
|
||||
stderr_logfile=/var/log/supervisor/postgresql-error.log
|
||||
|
||||
[program:nextjs]
|
||||
command=node /data/app/.next/standalone/server.js
|
||||
directory=/data/app
|
||||
user=root
|
||||
autostart=true
|
||||
autorestart=true
|
||||
priority=20
|
||||
startsecs=10
|
||||
startretries=3
|
||||
environment=NODE_ENV="production",PORT="%(ENV_PORT)s",HOSTNAME="0.0.0.0",DATABASE_URL="%(ENV_DATABASE_URL)s",AUTH_SECRET="%(ENV_AUTH_SECRET)s"
|
||||
stdout_logfile=/var/log/supervisor/nextjs.log
|
||||
stderr_logfile=/var/log/supervisor/nextjs-error.log
|
||||
+17
-40
@@ -27,6 +27,7 @@
|
||||
"close": "إغلاق",
|
||||
"all": "الكل",
|
||||
"none": "لا شيء",
|
||||
"ad": "إعلان",
|
||||
"moreLines": "+{count} سطر إضافي",
|
||||
"codeView": "كود",
|
||||
"treeView": "شجرة",
|
||||
@@ -39,6 +40,7 @@
|
||||
"promptmasters": "Promptmasters",
|
||||
"prompts": "الأوامر",
|
||||
"skills": "المهارات",
|
||||
"taste": "Taste",
|
||||
"workflows": "سير العمل",
|
||||
"categories": "الفئات",
|
||||
"tags": "الوسوم",
|
||||
@@ -107,7 +109,9 @@
|
||||
"title": "الأوامر",
|
||||
"create": "إنشاء أمر",
|
||||
"createSkill": "إنشاء مهارة",
|
||||
"createTaste": "إنشاء Taste",
|
||||
"skillsDescription": "مهارات الوكيل هي أوامر متعددة الملفات تمنح وكلاء الذكاء الاصطناعي قدرات متخصصة. تتضمن تعليمات وإعدادات وملفات داعمة يمكن استخدامها مع Claude وCursor وWindsurf ومساعدي البرمجة الآخرين.",
|
||||
"tastesDescription": "Tastes هي ملفات markdown مفردة تحدد أسلوب البرمجة وتفضيلاتك. تساعد وكلاء البرمجة بالذكاء الاصطناعي على تعلم طريقة كتابتك للكود، حتى يتمكنوا من مطابقة اصطلاحاتك وأنماطك وذوقك.",
|
||||
"createInfo": "هذه المنصة لا تقوم بتشغيل أو تنفيذ الأوامر — إنها مكتبة مجتمعية لمشاركة واكتشاف أوامر الذكاء الاصطناعي. أنشئ أمرك هنا، ويمكن للآخرين نسخه واستخدامه في أدوات الذكاء الاصطناعي المفضلة لديهم مثل ChatGPT و Claude و Gemini أو أي نموذج لغوي آخر. يمكن للمجتمع أيضاً التعليق على أوامرك واقتراح تحسينات من خلال طلبات التغيير.",
|
||||
"hfDataStudio": {
|
||||
"button": "استوديو بيانات HF",
|
||||
@@ -189,7 +193,8 @@
|
||||
"audio": "صوت",
|
||||
"structured": "منظم",
|
||||
"document": "مستند",
|
||||
"skill": "مهارة"
|
||||
"skill": "مهارة",
|
||||
"taste": "Taste"
|
||||
},
|
||||
"inputType": "أمر المستخدم",
|
||||
"outputType": "استجابة الذكاء الاصطناعي",
|
||||
@@ -199,7 +204,8 @@
|
||||
"inputTypes": {
|
||||
"text": "أمر نصي",
|
||||
"structured": "منظم (JSON/YAML)",
|
||||
"skill": "مهارة (مهارات الوكيل)"
|
||||
"skill": "مهارة (مهارات الوكيل)",
|
||||
"taste": "Taste (Coding Taste)"
|
||||
},
|
||||
"outputTypes": {
|
||||
"text": "نص",
|
||||
@@ -1024,45 +1030,8 @@
|
||||
}
|
||||
},
|
||||
"heroIndustries": {
|
||||
"prefix": "أو استكشف حسب المجال",
|
||||
"clickToExplore": "انقر للاستكشاف",
|
||||
"searchPlaceholder": "ابحث عن برومبتات...",
|
||||
"teachers": "المعلمون",
|
||||
"developers": "المطورون",
|
||||
"marketers": "المسوقون",
|
||||
"designers": "المصممون",
|
||||
"writers": "الكتّاب",
|
||||
"analysts": "المحللون",
|
||||
"entrepreneurs": "رواد الأعمال",
|
||||
"researchers": "الباحثون",
|
||||
"students": "الطلاب",
|
||||
"consultants": "المستشارون",
|
||||
"engineers": "المهندسون",
|
||||
"creators": "المبدعون",
|
||||
"lawyers": "المحامون",
|
||||
"doctors": "الأطباء",
|
||||
"nurses": "الممرضون",
|
||||
"accountants": "المحاسبون",
|
||||
"salespeople": "المبيعات",
|
||||
"recruiters": "التوظيف",
|
||||
"managers": "المدراء",
|
||||
"executives": "التنفيذيون",
|
||||
"freelancers": "المستقلون",
|
||||
"photographers": "المصورون",
|
||||
"musicians": "الموسيقيون",
|
||||
"artists": "الفنانون",
|
||||
"architects": "المعماريون",
|
||||
"scientists": "العلماء",
|
||||
"journalists": "الصحفيون",
|
||||
"editors": "المحررون",
|
||||
"translators": "المترجمون",
|
||||
"coaches": "المدربون",
|
||||
"therapists": "المعالجون",
|
||||
"trainers": "المدربون",
|
||||
"chefs": "الطهاة",
|
||||
"realtors": "العقاريون",
|
||||
"investors": "المستثمرون",
|
||||
"traders": "المتداولون"
|
||||
"videoTitle": "GitHub Open Source Friday"
|
||||
},
|
||||
"notFound": {
|
||||
"title": "الصفحة غير موجودة",
|
||||
@@ -1483,6 +1452,7 @@
|
||||
"supportNow": "ادعم الآن",
|
||||
"techStackTitle": "تطور التقنية",
|
||||
"coreContributorsTitle": "المساهمون الأساسيون",
|
||||
"securityTitle": "الأمان",
|
||||
"designCreditsTitle": "التصميم",
|
||||
"ideationTitle": "الأفكار",
|
||||
"communityContributorsTitle": "مساهمو المجتمع",
|
||||
@@ -2257,5 +2227,12 @@
|
||||
"submit": "إرسال",
|
||||
"communityExamples": "أمثلة المجتمع",
|
||||
"userExample": "مثال المستخدم"
|
||||
},
|
||||
"selfHosting": {
|
||||
"prerequisites": {
|
||||
"nodeJs": "Node.js 24.x",
|
||||
"postgresql": "PostgreSQL database",
|
||||
"npm": "npm"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+17
-40
@@ -27,6 +27,7 @@
|
||||
"close": "Bağla",
|
||||
"all": "Hamısı",
|
||||
"none": "Heç biri",
|
||||
"ad": "Reklam",
|
||||
"moreLines": "+{count} daha çox sətir",
|
||||
"codeView": "Kod",
|
||||
"treeView": "Ağac",
|
||||
@@ -39,6 +40,7 @@
|
||||
"promptmasters": "Promptmasterlər",
|
||||
"prompts": "Promptlar",
|
||||
"skills": "Bacarıqlar",
|
||||
"taste": "Taste",
|
||||
"workflows": "İş axınları",
|
||||
"categories": "Kateqoriyalar",
|
||||
"tags": "Etiketlər",
|
||||
@@ -107,7 +109,9 @@
|
||||
"title": "Promptlar",
|
||||
"create": "Prompt Yarat",
|
||||
"createSkill": "Bacarıq Yarat",
|
||||
"createTaste": "Taste Yarat",
|
||||
"skillsDescription": "Agent Bacarıqları AI agentlərinə xüsusi qabiliyyətlər verən çox fayllı promptlardır. Claude, Cursor, Windsurf və digər AI kodlaşdırma köməkçiləri ilə istifadə edilə bilən təlimatlar, konfiqurasiyalar və dəstəkləyici fayllar daxildir.",
|
||||
"tastesDescription": "Tastes kodlaşdırma tərzinizi və üstünlüklərinizi müəyyən edən tək markdown fayllarıdır. AI kodlaşdırma agentlərinə kodunuzu necə yazdığınızı öyrənməyə kömək edir, beləliklə onlar sizin konvensiyalarınızı, nümunələrinizi və zövqünüzü uyğunlaşdıra bilərlər.",
|
||||
"createInfo": "Bu platforma promptları işlətmir — süni intellekt promptlarını paylaşmaq və kəşf etmək üçün icma tərəfindən idarə olunan bir kitabxanadır. Promptunuzu burada yaradın və başqaları onu ChatGPT, Claude, Gemini və ya hər hansı digər LLM kimi üstünlük verdikləri AI alətlərində kopyalayıb istifadə edə bilərlər.",
|
||||
"hfDataStudio": {
|
||||
"button": "HF Data Studio",
|
||||
@@ -189,7 +193,8 @@
|
||||
"audio": "Audio",
|
||||
"structured": "Strukturlaşdırılmış",
|
||||
"document": "Sənəd",
|
||||
"skill": "Bacarıq"
|
||||
"skill": "Bacarıq",
|
||||
"taste": "Taste"
|
||||
},
|
||||
"inputType": "İstifadəçi Promptu",
|
||||
"outputType": "AI Cavabı",
|
||||
@@ -199,7 +204,8 @@
|
||||
"inputTypes": {
|
||||
"text": "Mətn Promptu",
|
||||
"structured": "Strukturlaşdırılmış (JSON/YAML)",
|
||||
"skill": "Bacarıq (Agent Bacarıqları)"
|
||||
"skill": "Bacarıq (Agent Bacarıqları)",
|
||||
"taste": "Taste (Coding Taste)"
|
||||
},
|
||||
"outputTypes": {
|
||||
"text": "Mətn",
|
||||
@@ -1019,45 +1025,8 @@
|
||||
}
|
||||
},
|
||||
"heroIndustries": {
|
||||
"prefix": "və ya sahəyə görə kəşf edin",
|
||||
"clickToExplore": "Kəşf etmək üçün klikləyin",
|
||||
"searchPlaceholder": "Promptları axtar...",
|
||||
"teachers": "Müəllimlər",
|
||||
"developers": "Tərtibatçılar",
|
||||
"marketers": "Marketoloqlar",
|
||||
"designers": "Dizaynerlər",
|
||||
"writers": "Yazıçılar",
|
||||
"analysts": "Analitiklər",
|
||||
"entrepreneurs": "Sahibkarlar",
|
||||
"researchers": "Tədqiqatçılar",
|
||||
"students": "Tələbələr",
|
||||
"consultants": "Məsləhətçilər",
|
||||
"engineers": "Mühəndislər",
|
||||
"creators": "Yaradıcılar",
|
||||
"lawyers": "Vəkillər",
|
||||
"doctors": "Həkimlər",
|
||||
"nurses": "Tibb bacıları",
|
||||
"accountants": "Mühasiblər",
|
||||
"salespeople": "Satıcılar",
|
||||
"recruiters": "İşə götürənlər",
|
||||
"managers": "Menecerlər",
|
||||
"executives": "İcraçılar",
|
||||
"freelancers": "Frilanslər",
|
||||
"photographers": "Fotoqraflar",
|
||||
"musicians": "Musiqiçilər",
|
||||
"artists": "Rəssamlar",
|
||||
"architects": "Memarlar",
|
||||
"scientists": "Alimlər",
|
||||
"journalists": "Jurnalistlər",
|
||||
"editors": "Redaktorlar",
|
||||
"translators": "Tərcüməçilər",
|
||||
"coaches": "Məşqçilər",
|
||||
"therapists": "Terapevtlər",
|
||||
"trainers": "Təlimçilər",
|
||||
"chefs": "Aşpazlar",
|
||||
"realtors": "Rieltorlar",
|
||||
"investors": "İnvestorlar",
|
||||
"traders": "Treyderlər"
|
||||
"videoTitle": "GitHub Open Source Friday"
|
||||
},
|
||||
"promptBuilder": {
|
||||
"title": "Prompt Qurma Agenti",
|
||||
@@ -1483,6 +1452,7 @@
|
||||
"supportNow": "İndi Dəstəklə",
|
||||
"techStackTitle": "Texnoloji Təkamül",
|
||||
"coreContributorsTitle": "Əsas Töhfəçilər",
|
||||
"securityTitle": "Təhlükəsizlik",
|
||||
"designCreditsTitle": "Dizayn",
|
||||
"ideationTitle": "İdeya",
|
||||
"communityContributorsTitle": "İcma Töhfəçiləri",
|
||||
@@ -2271,5 +2241,12 @@
|
||||
"submit": "Göndər",
|
||||
"communityExamples": "İcma Nümunələri",
|
||||
"userExample": "İstifadəçi nümunəsi"
|
||||
},
|
||||
"selfHosting": {
|
||||
"prerequisites": {
|
||||
"nodeJs": "Node.js 24.x",
|
||||
"postgresql": "PostgreSQL database",
|
||||
"npm": "npm"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+17
-40
@@ -27,6 +27,7 @@
|
||||
"close": "Schließen",
|
||||
"all": "Alle",
|
||||
"none": "Keine",
|
||||
"ad": "Anzeige",
|
||||
"moreLines": "+{count} weitere Zeilen",
|
||||
"codeView": "Code",
|
||||
"treeView": "Baumansicht",
|
||||
@@ -39,6 +40,7 @@
|
||||
"promptmasters": "Promptmasters",
|
||||
"prompts": "Prompts",
|
||||
"skills": "Fähigkeiten",
|
||||
"taste": "Taste",
|
||||
"workflows": "Workflows",
|
||||
"categories": "Kategorien",
|
||||
"tags": "Tags",
|
||||
@@ -107,7 +109,9 @@
|
||||
"title": "Prompts",
|
||||
"create": "Prompt erstellen",
|
||||
"createSkill": "Skill erstellen",
|
||||
"createTaste": "Taste erstellen",
|
||||
"skillsDescription": "Agent Skills sind Multi-Datei-Prompts, die KI-Agenten spezialisierte Fähigkeiten verleihen. Sie enthalten Anweisungen, Konfigurationen und unterstützende Dateien, die mit Claude, Cursor, Windsurf und anderen KI-Coding-Assistenten verwendet werden können.",
|
||||
"tastesDescription": "Tastes sind einzelne Markdown-Dateien, die Ihren Codierstil und Ihre Vorlieben definieren. Sie helfen KI-Coding-Agenten zu lernen, wie Sie Code schreiben, damit sie Ihre Konventionen, Muster und Ihren Geschmack übernehmen können.",
|
||||
"createInfo": "Diese Plattform führt keine Prompts aus — sie ist eine community-gesteuerte Bibliothek zum Teilen und Entdecken von KI-Prompts. Erstellen Sie hier Ihren Prompt, und andere können ihn kopieren und in ihren bevorzugten KI-Tools wie ChatGPT, Claude, Gemini oder anderen LLMs verwenden. Die Community kann auch Ihre Prompts kommentieren und Verbesserungen durch Änderungsanfragen vorschlagen.",
|
||||
"hfDataStudio": {
|
||||
"button": "HF Data Studio",
|
||||
@@ -189,7 +193,8 @@
|
||||
"audio": "Audio",
|
||||
"structured": "Strukturiert",
|
||||
"document": "Dokument",
|
||||
"skill": "Skill"
|
||||
"skill": "Skill",
|
||||
"taste": "Taste"
|
||||
},
|
||||
"inputType": "Benutzer-Prompt",
|
||||
"outputType": "KI-Antwort",
|
||||
@@ -199,7 +204,8 @@
|
||||
"inputTypes": {
|
||||
"text": "Text-Prompt",
|
||||
"structured": "Strukturiert (JSON/YAML)",
|
||||
"skill": "Skill (Agent Skills)"
|
||||
"skill": "Skill (Agent Skills)",
|
||||
"taste": "Taste (Coding Taste)"
|
||||
},
|
||||
"outputTypes": {
|
||||
"text": "Text",
|
||||
@@ -1024,45 +1030,8 @@
|
||||
}
|
||||
},
|
||||
"heroIndustries": {
|
||||
"prefix": "oder nach Branche erkunden",
|
||||
"clickToExplore": "Klicke zum Erkunden",
|
||||
"searchPlaceholder": "Prompts suchen...",
|
||||
"teachers": "Lehrer",
|
||||
"developers": "Entwickler",
|
||||
"marketers": "Marketer",
|
||||
"designers": "Designer",
|
||||
"writers": "Autoren",
|
||||
"analysts": "Analysten",
|
||||
"entrepreneurs": "Unternehmer",
|
||||
"researchers": "Forscher",
|
||||
"students": "Studenten",
|
||||
"consultants": "Berater",
|
||||
"engineers": "Ingenieure",
|
||||
"creators": "Creator",
|
||||
"lawyers": "Anwälte",
|
||||
"doctors": "Ärzte",
|
||||
"nurses": "Krankenpfleger",
|
||||
"accountants": "Buchhalter",
|
||||
"salespeople": "Verkäufer",
|
||||
"recruiters": "Recruiter",
|
||||
"managers": "Manager",
|
||||
"executives": "Führungskräfte",
|
||||
"freelancers": "Freelancer",
|
||||
"photographers": "Fotografen",
|
||||
"musicians": "Musiker",
|
||||
"artists": "Künstler",
|
||||
"architects": "Architekten",
|
||||
"scientists": "Wissenschaftler",
|
||||
"journalists": "Journalisten",
|
||||
"editors": "Redakteure",
|
||||
"translators": "Übersetzer",
|
||||
"coaches": "Coaches",
|
||||
"therapists": "Therapeuten",
|
||||
"trainers": "Trainer",
|
||||
"chefs": "Köche",
|
||||
"realtors": "Makler",
|
||||
"investors": "Investoren",
|
||||
"traders": "Händler"
|
||||
"videoTitle": "GitHub Open Source Friday"
|
||||
},
|
||||
"notFound": {
|
||||
"title": "Seite nicht gefunden",
|
||||
@@ -1483,6 +1452,7 @@
|
||||
"supportNow": "Jetzt unterstützen",
|
||||
"techStackTitle": "Technologie-Evolution",
|
||||
"coreContributorsTitle": "Kern-Mitwirkende",
|
||||
"securityTitle": "Sicherheit",
|
||||
"designCreditsTitle": "Design",
|
||||
"ideationTitle": "Ideenfindung",
|
||||
"communityContributorsTitle": "Community-Mitwirkende",
|
||||
@@ -2271,5 +2241,12 @@
|
||||
"submit": "Absenden",
|
||||
"communityExamples": "Community-Beispiele",
|
||||
"userExample": "Benutzerbeispiel"
|
||||
},
|
||||
"selfHosting": {
|
||||
"prerequisites": {
|
||||
"nodeJs": "Node.js 24.x",
|
||||
"postgresql": "PostgreSQL database",
|
||||
"npm": "npm"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+17
-40
@@ -27,6 +27,7 @@
|
||||
"close": "Κλείσιμο",
|
||||
"all": "Όλα",
|
||||
"none": "Κανένα",
|
||||
"ad": "Διαφήμιση",
|
||||
"moreLines": "+{count} επιπλέον γραμμές",
|
||||
"codeView": "Κώδικας",
|
||||
"treeView": "Δέντρο",
|
||||
@@ -39,6 +40,7 @@
|
||||
"promptmasters": "Promptmasters",
|
||||
"prompts": "Prompts",
|
||||
"skills": "Δεξιότητες",
|
||||
"taste": "Taste",
|
||||
"workflows": "Ροές εργασίας",
|
||||
"categories": "Κατηγορίες",
|
||||
"tags": "Ετικέτες",
|
||||
@@ -107,7 +109,9 @@
|
||||
"title": "Prompts",
|
||||
"create": "Δημιουργία Prompt",
|
||||
"createSkill": "Δημιουργία Δεξιότητας",
|
||||
"createTaste": "Δημιουργία Taste",
|
||||
"skillsDescription": "Οι Δεξιότητες Πράκτορα είναι prompts πολλαπλών αρχείων που δίνουν στους AI πράκτορες εξειδικευμένες δυνατότητες. Περιλαμβάνουν οδηγίες, ρυθμίσεις και υποστηρικτικά αρχεία που μπορούν να χρησιμοποιηθούν με Claude, Cursor, Windsurf και άλλους βοηθούς προγραμματισμού AI.",
|
||||
"tastesDescription": "Τα Tastes είναι μεμονωμένα αρχεία markdown που ορίζουν το στυλ κωδικοποίησης και τις προτιμήσεις σας. Βοηθούν τους πράκτορες κωδικοποίησης AI να μάθουν πώς γράφετε κώδικα, ώστε να ταιριάζουν τις συμβάσεις, τα μοτίβα και το γούστο σας.",
|
||||
"createInfo": "Αυτή η πλατφόρμα δεν εκτελεί prompts — είναι μια κοινοτική βιβλιοθήκη για την κοινοποίηση και ανακάλυψη AI prompts. Δημιουργήστε το prompt σας εδώ και άλλοι μπορούν να το αντιγράψουν και να το χρησιμοποιήσουν στα αγαπημένα τους εργαλεία AI όπως ChatGPT, Claude, Gemini ή οποιοδήποτε άλλο LLM. Η κοινότητα μπορεί επίσης να σχολιάσει τα prompts σας και να προτείνει βελτιώσεις μέσω αιτημάτων αλλαγής.",
|
||||
"hfDataStudio": {
|
||||
"button": "HF Data Studio",
|
||||
@@ -189,7 +193,8 @@
|
||||
"audio": "Ήχος",
|
||||
"structured": "Δομημένο",
|
||||
"document": "Έγγραφο",
|
||||
"skill": "Δεξιότητα"
|
||||
"skill": "Δεξιότητα",
|
||||
"taste": "Taste"
|
||||
},
|
||||
"inputType": "Prompt χρήστη",
|
||||
"outputType": "Απάντηση AI",
|
||||
@@ -199,7 +204,8 @@
|
||||
"inputTypes": {
|
||||
"text": "Prompt κειμένου",
|
||||
"structured": "Δομημένο (JSON/YAML)",
|
||||
"skill": "Δεξιότητα (Agent Skills)"
|
||||
"skill": "Δεξιότητα (Agent Skills)",
|
||||
"taste": "Taste (Coding Taste)"
|
||||
},
|
||||
"outputTypes": {
|
||||
"text": "Κείμενο",
|
||||
@@ -1045,45 +1051,8 @@
|
||||
}
|
||||
},
|
||||
"heroIndustries": {
|
||||
"prefix": "ή εξερευνήστε ανά κλάδο",
|
||||
"clickToExplore": "Κάντε κλικ για εξερεύνηση",
|
||||
"searchPlaceholder": "Αναζήτηση prompts...",
|
||||
"teachers": "Εκπαιδευτικούς",
|
||||
"developers": "Προγραμματιστές",
|
||||
"marketers": "Μάρκετερ",
|
||||
"designers": "Σχεδιαστές",
|
||||
"writers": "Συγγραφείς",
|
||||
"analysts": "Αναλυτές",
|
||||
"entrepreneurs": "Επιχειρηματίες",
|
||||
"researchers": "Ερευνητές",
|
||||
"students": "Φοιτητές",
|
||||
"consultants": "Συμβούλους",
|
||||
"engineers": "Μηχανικούς",
|
||||
"creators": "Δημιουργούς",
|
||||
"lawyers": "Δικηγόρους",
|
||||
"doctors": "Γιατρούς",
|
||||
"nurses": "Νοσηλευτές",
|
||||
"accountants": "Λογιστές",
|
||||
"salespeople": "Πωλητές",
|
||||
"recruiters": "Recruiters",
|
||||
"managers": "Διευθυντές",
|
||||
"executives": "Στελέχη",
|
||||
"freelancers": "Ελεύθερους",
|
||||
"photographers": "Φωτογράφους",
|
||||
"musicians": "Μουσικούς",
|
||||
"artists": "Καλλιτέχνες",
|
||||
"architects": "Αρχιτέκτονες",
|
||||
"scientists": "Επιστήμονες",
|
||||
"journalists": "Δημοσιογράφους",
|
||||
"editors": "Συντάκτες",
|
||||
"translators": "Μεταφραστές",
|
||||
"coaches": "Προπονητές",
|
||||
"therapists": "Θεραπευτές",
|
||||
"trainers": "Εκπαιδευτές",
|
||||
"chefs": "Σεφ",
|
||||
"realtors": "Μεσίτες",
|
||||
"investors": "Επενδυτές",
|
||||
"traders": "Traders"
|
||||
"videoTitle": "GitHub Open Source Friday"
|
||||
},
|
||||
"promptBuilder": {
|
||||
"title": "Prompt Building Agent",
|
||||
@@ -1483,6 +1452,7 @@
|
||||
"supportNow": "Υποστηρίξτε Τώρα",
|
||||
"techStackTitle": "Εξέλιξη Τεχνολογίας",
|
||||
"coreContributorsTitle": "Κύριοι Συνεισφέροντες",
|
||||
"securityTitle": "Ασφάλεια",
|
||||
"designCreditsTitle": "Σχεδιασμός",
|
||||
"ideationTitle": "Ιδέες",
|
||||
"communityContributorsTitle": "Συνεισφέροντες Κοινότητας",
|
||||
@@ -2262,5 +2232,12 @@
|
||||
"submit": "Υποβολή",
|
||||
"communityExamples": "Παραδείγματα Κοινότητας",
|
||||
"userExample": "Παράδειγμα Χρήστη"
|
||||
},
|
||||
"selfHosting": {
|
||||
"prerequisites": {
|
||||
"nodeJs": "Node.js 24.x",
|
||||
"postgresql": "PostgreSQL database",
|
||||
"npm": "npm"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+17
-40
@@ -27,6 +27,7 @@
|
||||
"close": "Close",
|
||||
"all": "All",
|
||||
"none": "None",
|
||||
"ad": "Ad",
|
||||
"moreLines": "+{count} more lines",
|
||||
"codeView": "Code",
|
||||
"treeView": "Tree",
|
||||
@@ -39,6 +40,7 @@
|
||||
"promptmasters": "Promptmasters",
|
||||
"prompts": "Prompts",
|
||||
"skills": "Skills",
|
||||
"taste": "Taste",
|
||||
"workflows": "Workflows",
|
||||
"categories": "Categories",
|
||||
"tags": "Tags",
|
||||
@@ -107,7 +109,9 @@
|
||||
"title": "Prompts",
|
||||
"create": "Create Prompt",
|
||||
"createSkill": "Create Skill",
|
||||
"createTaste": "Create Taste",
|
||||
"skillsDescription": "Agent Skills are multi-file prompts that give AI agents specialized capabilities. They include instructions, configurations, and supporting files that can be used with Claude, Cursor, Windsurf, and other AI coding assistants.",
|
||||
"tastesDescription": "Tastes are single markdown files that define your coding style and preferences. They help AI coding agents learn how you write code, so they can match your conventions, patterns, and taste.",
|
||||
"createInfo": "This platform doesn't run or execute prompts — it's a community-driven library for sharing and discovering AI prompts. Create your prompt here, and others can copy and use it in their preferred AI tools like ChatGPT, Claude, Gemini, or any other LLM. The community can also comment on your prompts and suggest improvements through change requests.",
|
||||
"hfDataStudio": {
|
||||
"button": "HF Data Studio",
|
||||
@@ -189,7 +193,8 @@
|
||||
"audio": "Audio",
|
||||
"structured": "Structured",
|
||||
"document": "Document",
|
||||
"skill": "Skill"
|
||||
"skill": "Skill",
|
||||
"taste": "Taste"
|
||||
},
|
||||
"inputType": "User Prompt",
|
||||
"outputType": "AI Response",
|
||||
@@ -199,7 +204,8 @@
|
||||
"inputTypes": {
|
||||
"text": "Text Prompt",
|
||||
"structured": "Structured (JSON/YAML)",
|
||||
"skill": "Skill (Agent Skills)"
|
||||
"skill": "Skill (Agent Skills)",
|
||||
"taste": "Taste (Coding Taste)"
|
||||
},
|
||||
"outputTypes": {
|
||||
"text": "Text",
|
||||
@@ -1019,45 +1025,8 @@
|
||||
}
|
||||
},
|
||||
"heroIndustries": {
|
||||
"prefix": "or explore by industry",
|
||||
"clickToExplore": "Click to explore",
|
||||
"searchPlaceholder": "Search prompts...",
|
||||
"teachers": "Teachers",
|
||||
"developers": "Developers",
|
||||
"marketers": "Marketers",
|
||||
"designers": "Designers",
|
||||
"writers": "Writers",
|
||||
"analysts": "Analysts",
|
||||
"entrepreneurs": "Entrepreneurs",
|
||||
"researchers": "Researchers",
|
||||
"students": "Students",
|
||||
"consultants": "Consultants",
|
||||
"engineers": "Engineers",
|
||||
"creators": "Creators",
|
||||
"lawyers": "Lawyers",
|
||||
"doctors": "Doctors",
|
||||
"nurses": "Nurses",
|
||||
"accountants": "Accountants",
|
||||
"salespeople": "Salespeople",
|
||||
"recruiters": "Recruiters",
|
||||
"managers": "Managers",
|
||||
"executives": "Executives",
|
||||
"freelancers": "Freelancers",
|
||||
"photographers": "Photographers",
|
||||
"musicians": "Musicians",
|
||||
"artists": "Artists",
|
||||
"architects": "Architects",
|
||||
"scientists": "Scientists",
|
||||
"journalists": "Journalists",
|
||||
"editors": "Editors",
|
||||
"translators": "Translators",
|
||||
"coaches": "Coaches",
|
||||
"therapists": "Therapists",
|
||||
"trainers": "Trainers",
|
||||
"chefs": "Chefs",
|
||||
"realtors": "Realtors",
|
||||
"investors": "Investors",
|
||||
"traders": "Traders"
|
||||
"videoTitle": "GitHub Open Source Friday"
|
||||
},
|
||||
"promptBuilder": {
|
||||
"title": "Prompt Building Agent",
|
||||
@@ -1483,6 +1452,7 @@
|
||||
"supportNow": "Support Now",
|
||||
"techStackTitle": "Tech Evolution",
|
||||
"coreContributorsTitle": "Core Contributors",
|
||||
"securityTitle": "Security",
|
||||
"designCreditsTitle": "Design",
|
||||
"ideationTitle": "Ideation",
|
||||
"communityContributorsTitle": "Community Contributors",
|
||||
@@ -2275,5 +2245,12 @@
|
||||
"submit": "Submit",
|
||||
"communityExamples": "Community Examples",
|
||||
"userExample": "User example"
|
||||
},
|
||||
"selfHosting": {
|
||||
"prerequisites": {
|
||||
"nodeJs": "Node.js 24.x",
|
||||
"postgresql": "PostgreSQL database",
|
||||
"npm": "npm"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+17
-40
@@ -27,6 +27,7 @@
|
||||
"close": "Cerrar",
|
||||
"all": "Todos",
|
||||
"none": "Ninguno",
|
||||
"ad": "Anuncio",
|
||||
"moreLines": "+{count} líneas más",
|
||||
"codeView": "Código",
|
||||
"treeView": "Árbol",
|
||||
@@ -39,6 +40,7 @@
|
||||
"promptmasters": "Promptmasters",
|
||||
"prompts": "Prompts",
|
||||
"skills": "Habilidades",
|
||||
"taste": "Taste",
|
||||
"workflows": "Flujos de trabajo",
|
||||
"categories": "Categorías",
|
||||
"tags": "Etiquetas",
|
||||
@@ -107,7 +109,9 @@
|
||||
"title": "Prompts",
|
||||
"create": "Crear Prompt",
|
||||
"createSkill": "Crear Habilidad",
|
||||
"createTaste": "Crear Taste",
|
||||
"skillsDescription": "Las Habilidades de Agente son prompts de múltiples archivos que dan a los agentes de IA capacidades especializadas. Incluyen instrucciones, configuraciones y archivos de soporte que se pueden usar con Claude, Cursor, Windsurf y otros asistentes de codificación de IA.",
|
||||
"tastesDescription": "Los Tastes son archivos markdown individuales que definen tu estilo de codificación y preferencias. Ayudan a los agentes de codificación de IA a aprender cómo escribes código, para que puedan adaptarse a tus convenciones, patrones y gusto.",
|
||||
"createInfo": "Esta plataforma no ejecuta prompts — es una biblioteca comunitaria para compartir y descubrir prompts de IA. Crea tu prompt aquí, y otros podrán copiarlo y usarlo en sus herramientas de IA favoritas como ChatGPT, Claude, Gemini o cualquier otro LLM. La comunidad también puede comentar tus prompts y sugerir mejoras a través de solicitudes de cambio.",
|
||||
"hfDataStudio": {
|
||||
"button": "HF Data Studio",
|
||||
@@ -189,7 +193,8 @@
|
||||
"audio": "Audio",
|
||||
"structured": "Estructurado",
|
||||
"document": "Documento",
|
||||
"skill": "Habilidad"
|
||||
"skill": "Habilidad",
|
||||
"taste": "Taste"
|
||||
},
|
||||
"inputType": "Prompt del Usuario",
|
||||
"outputType": "Respuesta de IA",
|
||||
@@ -199,7 +204,8 @@
|
||||
"inputTypes": {
|
||||
"text": "Prompt de Texto",
|
||||
"structured": "Estructurado (JSON/YAML)",
|
||||
"skill": "Habilidad (Agent Skills)"
|
||||
"skill": "Habilidad (Agent Skills)",
|
||||
"taste": "Taste (Coding Taste)"
|
||||
},
|
||||
"outputTypes": {
|
||||
"text": "Texto",
|
||||
@@ -1024,45 +1030,8 @@
|
||||
}
|
||||
},
|
||||
"heroIndustries": {
|
||||
"prefix": "o explora por industria",
|
||||
"clickToExplore": "Haz clic para explorar",
|
||||
"searchPlaceholder": "Buscar prompts...",
|
||||
"teachers": "Profesores",
|
||||
"developers": "Desarrolladores",
|
||||
"marketers": "Marketers",
|
||||
"designers": "Diseñadores",
|
||||
"writers": "Escritores",
|
||||
"analysts": "Analistas",
|
||||
"entrepreneurs": "Emprendedores",
|
||||
"researchers": "Investigadores",
|
||||
"students": "Estudiantes",
|
||||
"consultants": "Consultores",
|
||||
"engineers": "Ingenieros",
|
||||
"creators": "Creadores",
|
||||
"lawyers": "Abogados",
|
||||
"doctors": "Médicos",
|
||||
"nurses": "Enfermeros",
|
||||
"accountants": "Contadores",
|
||||
"salespeople": "Vendedores",
|
||||
"recruiters": "Reclutadores",
|
||||
"managers": "Gerentes",
|
||||
"executives": "Ejecutivos",
|
||||
"freelancers": "Freelancers",
|
||||
"photographers": "Fotógrafos",
|
||||
"musicians": "Músicos",
|
||||
"artists": "Artistas",
|
||||
"architects": "Arquitectos",
|
||||
"scientists": "Científicos",
|
||||
"journalists": "Periodistas",
|
||||
"editors": "Editores",
|
||||
"translators": "Traductores",
|
||||
"coaches": "Coaches",
|
||||
"therapists": "Terapeutas",
|
||||
"trainers": "Entrenadores",
|
||||
"chefs": "Chefs",
|
||||
"realtors": "Agentes",
|
||||
"investors": "Inversores",
|
||||
"traders": "Traders"
|
||||
"videoTitle": "GitHub Open Source Friday"
|
||||
},
|
||||
"notFound": {
|
||||
"title": "Página No Encontrada",
|
||||
@@ -1483,6 +1452,7 @@
|
||||
"supportNow": "Apoyar Ahora",
|
||||
"techStackTitle": "Evolución Tecnológica",
|
||||
"coreContributorsTitle": "Contribuidores Principales",
|
||||
"securityTitle": "Seguridad",
|
||||
"designCreditsTitle": "Diseño",
|
||||
"ideationTitle": "Ideación",
|
||||
"communityContributorsTitle": "Contribuidores de la Comunidad",
|
||||
@@ -2274,5 +2244,12 @@
|
||||
"submit": "Enviar",
|
||||
"communityExamples": "Ejemplos de la Comunidad",
|
||||
"userExample": "Ejemplo de usuario"
|
||||
},
|
||||
"selfHosting": {
|
||||
"prerequisites": {
|
||||
"nodeJs": "Node.js 24.x",
|
||||
"postgresql": "PostgreSQL database",
|
||||
"npm": "npm"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+17
-40
@@ -27,6 +27,7 @@
|
||||
"close": "بستن",
|
||||
"all": "همه",
|
||||
"none": "هیچکدام",
|
||||
"ad": "تبلیغ",
|
||||
"moreLines": "+{count} خط دیگر",
|
||||
"codeView": "کد",
|
||||
"treeView": "درختی",
|
||||
@@ -39,6 +40,7 @@
|
||||
"promptmasters": "پرامپتمسترها",
|
||||
"prompts": "پرامپتها",
|
||||
"skills": "مهارتها",
|
||||
"taste": "Taste",
|
||||
"workflows": "گردش کار",
|
||||
"categories": "دستهبندیها",
|
||||
"tags": "برچسبها",
|
||||
@@ -107,7 +109,9 @@
|
||||
"title": "پرامپتها",
|
||||
"create": "ایجاد پرامپت",
|
||||
"createSkill": "ایجاد مهارت",
|
||||
"createTaste": "ایجاد Taste",
|
||||
"skillsDescription": "مهارتهای عامل، پرامپتهای چند فایلی هستند که به عاملهای هوش مصنوعی قابلیتهای تخصصی میدهند. شامل دستورالعملها، تنظیمات و فایلهای پشتیبانی هستند که میتوانند با Claude، Cursor، Windsurf و سایر دستیاران برنامهنویسی AI استفاده شوند.",
|
||||
"tastesDescription": "Tastes فایلهای تک markdown هستند که سبک و ترجیحات کدنویسی شما را تعریف میکنند. به عاملهای کدنویسی AI کمک میکنند تا نحوه نوشتن کد شما را یاد بگیرند، تا بتوانند قراردادها، الگوها و سلیقه شما را مطابقت دهند.",
|
||||
"createInfo": "این پلتفرم پرامپتها را اجرا نمیکند — این یک کتابخانه مبتنی بر جامعه برای اشتراکگذاری و کشف پرامپتهای هوش مصنوعی است. پرامپت خود را اینجا بسازید و دیگران میتوانند آن را در ابزارهای هوش مصنوعی مورد علاقه خود مانند ChatGPT، Claude، Gemini یا هر LLM دیگری کپی و استفاده کنند.",
|
||||
"hfDataStudio": {
|
||||
"button": "HF Data Studio",
|
||||
@@ -189,7 +193,8 @@
|
||||
"audio": "صدا",
|
||||
"structured": "ساختارمند",
|
||||
"document": "سند",
|
||||
"skill": "مهارت"
|
||||
"skill": "مهارت",
|
||||
"taste": "Taste"
|
||||
},
|
||||
"inputType": "پرامپت کاربر",
|
||||
"outputType": "پاسخ هوش مصنوعی",
|
||||
@@ -199,7 +204,8 @@
|
||||
"inputTypes": {
|
||||
"text": "پرامپت متنی",
|
||||
"structured": "ساختارمند (JSON/YAML)",
|
||||
"skill": "مهارت (مهارتهای عامل)"
|
||||
"skill": "مهارت (مهارتهای عامل)",
|
||||
"taste": "Taste (Coding Taste)"
|
||||
},
|
||||
"outputTypes": {
|
||||
"text": "متن",
|
||||
@@ -1019,45 +1025,8 @@
|
||||
}
|
||||
},
|
||||
"heroIndustries": {
|
||||
"prefix": "یا بر اساس صنعت کاوش کنید",
|
||||
"clickToExplore": "برای کاوش کلیک کنید",
|
||||
"searchPlaceholder": "جستجوی پرامپت...",
|
||||
"teachers": "معلمان",
|
||||
"developers": "توسعهدهندگان",
|
||||
"marketers": "بازاریابان",
|
||||
"designers": "طراحان",
|
||||
"writers": "نویسندگان",
|
||||
"analysts": "تحلیلگران",
|
||||
"entrepreneurs": "کارآفرینان",
|
||||
"researchers": "پژوهشگران",
|
||||
"students": "دانشجویان",
|
||||
"consultants": "مشاوران",
|
||||
"engineers": "مهندسان",
|
||||
"creators": "خالقان",
|
||||
"lawyers": "وکلا",
|
||||
"doctors": "پزشکان",
|
||||
"nurses": "پرستاران",
|
||||
"accountants": "حسابداران",
|
||||
"salespeople": "فروشندگان",
|
||||
"recruiters": "استخدامکنندگان",
|
||||
"managers": "مدیران",
|
||||
"executives": "مدیران ارشد",
|
||||
"freelancers": "فریلنسرها",
|
||||
"photographers": "عکاسان",
|
||||
"musicians": "موسیقیدانان",
|
||||
"artists": "هنرمندان",
|
||||
"architects": "معماران",
|
||||
"scientists": "دانشمندان",
|
||||
"journalists": "روزنامهنگاران",
|
||||
"editors": "ویراستاران",
|
||||
"translators": "مترجمان",
|
||||
"coaches": "مربیان",
|
||||
"therapists": "درمانگران",
|
||||
"trainers": "آموزشدهندگان",
|
||||
"chefs": "سرآشپزها",
|
||||
"realtors": "مشاوران املاک",
|
||||
"investors": "سرمایهگذاران",
|
||||
"traders": "معاملهگران"
|
||||
"videoTitle": "GitHub Open Source Friday"
|
||||
},
|
||||
"promptBuilder": {
|
||||
"title": "عامل ساخت پرامپت",
|
||||
@@ -1483,6 +1452,7 @@
|
||||
"supportNow": "همین الان حمایت کنید",
|
||||
"techStackTitle": "تکامل فناوری",
|
||||
"coreContributorsTitle": "مشارکتکنندگان اصلی",
|
||||
"securityTitle": "امنیت",
|
||||
"designCreditsTitle": "طراحی",
|
||||
"ideationTitle": "ایدهپردازی",
|
||||
"communityContributorsTitle": "مشارکتکنندگان جامعه",
|
||||
@@ -2255,5 +2225,12 @@
|
||||
"submit": "ارسال",
|
||||
"communityExamples": "مثالهای جامعه",
|
||||
"userExample": "مثال کاربر"
|
||||
},
|
||||
"selfHosting": {
|
||||
"prerequisites": {
|
||||
"nodeJs": "Node.js 24.x",
|
||||
"postgresql": "PostgreSQL database",
|
||||
"npm": "npm"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+17
-40
@@ -27,6 +27,7 @@
|
||||
"close": "Fermer",
|
||||
"all": "Tout",
|
||||
"none": "Aucun",
|
||||
"ad": "Publicité",
|
||||
"moreLines": "+{count} lignes supplémentaires",
|
||||
"codeView": "Code",
|
||||
"treeView": "Arborescence",
|
||||
@@ -39,6 +40,7 @@
|
||||
"promptmasters": "Promptmasters",
|
||||
"prompts": "Prompts",
|
||||
"skills": "Compétences",
|
||||
"taste": "Taste",
|
||||
"workflows": "Flux de travail",
|
||||
"categories": "Catégories",
|
||||
"tags": "Tags",
|
||||
@@ -107,7 +109,9 @@
|
||||
"title": "Prompts",
|
||||
"create": "Créer un Prompt",
|
||||
"createSkill": "Créer une Compétence",
|
||||
"createTaste": "Créer un Taste",
|
||||
"skillsDescription": "Les Compétences d'Agent sont des prompts multi-fichiers qui donnent aux agents IA des capacités spécialisées. Ils comprennent des instructions, des configurations et des fichiers de support qui peuvent être utilisés avec Claude, Cursor, Windsurf et d'autres assistants de codage IA.",
|
||||
"tastesDescription": "Les Tastes sont des fichiers markdown uniques qui définissent votre style de codage et vos préférences. Ils aident les agents de codage IA à apprendre comment vous écrivez du code, afin qu'ils puissent correspondre à vos conventions, modèles et goût.",
|
||||
"createInfo": "Cette plateforme n'exécute pas les prompts — c'est une bibliothèque communautaire pour partager et découvrir des prompts IA. Créez votre prompt ici, et d'autres pourront le copier et l'utiliser dans leurs outils IA préférés comme ChatGPT, Claude, Gemini ou tout autre LLM. La communauté peut également commenter vos prompts et suggérer des améliorations via des demandes de modification.",
|
||||
"hfDataStudio": {
|
||||
"button": "HF Data Studio",
|
||||
@@ -189,7 +193,8 @@
|
||||
"audio": "Audio",
|
||||
"structured": "Structuré",
|
||||
"document": "Document",
|
||||
"skill": "Compétence"
|
||||
"skill": "Compétence",
|
||||
"taste": "Taste"
|
||||
},
|
||||
"inputType": "Prompt Utilisateur",
|
||||
"outputType": "Réponse IA",
|
||||
@@ -199,7 +204,8 @@
|
||||
"inputTypes": {
|
||||
"text": "Prompt Texte",
|
||||
"structured": "Structuré (JSON/YAML)",
|
||||
"skill": "Compétence (Agent Skills)"
|
||||
"skill": "Compétence (Agent Skills)",
|
||||
"taste": "Taste (Coding Taste)"
|
||||
},
|
||||
"outputTypes": {
|
||||
"text": "Texte",
|
||||
@@ -1024,45 +1030,8 @@
|
||||
}
|
||||
},
|
||||
"heroIndustries": {
|
||||
"prefix": "ou explorez par secteur",
|
||||
"clickToExplore": "Cliquez pour explorer",
|
||||
"searchPlaceholder": "Rechercher des prompts...",
|
||||
"teachers": "Enseignants",
|
||||
"developers": "Développeurs",
|
||||
"marketers": "Marketeurs",
|
||||
"designers": "Designers",
|
||||
"writers": "Écrivains",
|
||||
"analysts": "Analystes",
|
||||
"entrepreneurs": "Entrepreneurs",
|
||||
"researchers": "Chercheurs",
|
||||
"students": "Étudiants",
|
||||
"consultants": "Consultants",
|
||||
"engineers": "Ingénieurs",
|
||||
"creators": "Créateurs",
|
||||
"lawyers": "Avocats",
|
||||
"doctors": "Médecins",
|
||||
"nurses": "Infirmiers",
|
||||
"accountants": "Comptables",
|
||||
"salespeople": "Commerciaux",
|
||||
"recruiters": "Recruteurs",
|
||||
"managers": "Managers",
|
||||
"executives": "Dirigeants",
|
||||
"freelancers": "Freelances",
|
||||
"photographers": "Photographes",
|
||||
"musicians": "Musiciens",
|
||||
"artists": "Artistes",
|
||||
"architects": "Architectes",
|
||||
"scientists": "Scientifiques",
|
||||
"journalists": "Journalistes",
|
||||
"editors": "Rédacteurs",
|
||||
"translators": "Traducteurs",
|
||||
"coaches": "Coachs",
|
||||
"therapists": "Thérapeutes",
|
||||
"trainers": "Formateurs",
|
||||
"chefs": "Chefs",
|
||||
"realtors": "Agents",
|
||||
"investors": "Investisseurs",
|
||||
"traders": "Traders"
|
||||
"videoTitle": "GitHub Open Source Friday"
|
||||
},
|
||||
"notFound": {
|
||||
"title": "Page Non Trouvée",
|
||||
@@ -1483,6 +1452,7 @@
|
||||
"supportNow": "Soutenir Maintenant",
|
||||
"techStackTitle": "Évolution Technologique",
|
||||
"coreContributorsTitle": "Contributeurs Principaux",
|
||||
"securityTitle": "Sécurité",
|
||||
"designCreditsTitle": "Design",
|
||||
"ideationTitle": "Idéation",
|
||||
"communityContributorsTitle": "Contributeurs de la Communauté",
|
||||
@@ -2274,5 +2244,12 @@
|
||||
"submit": "Soumettre",
|
||||
"communityExamples": "Exemples de la Communauté",
|
||||
"userExample": "Exemple utilisateur"
|
||||
},
|
||||
"selfHosting": {
|
||||
"prerequisites": {
|
||||
"nodeJs": "Node.js 24.x",
|
||||
"postgresql": "PostgreSQL database",
|
||||
"npm": "npm"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+17
-40
@@ -27,6 +27,7 @@
|
||||
"close": "סגור",
|
||||
"all": "הכל",
|
||||
"none": "ללא",
|
||||
"ad": "מודעה",
|
||||
"moreLines": "+{count} שורות נוספות",
|
||||
"codeView": "קוד",
|
||||
"treeView": "עץ",
|
||||
@@ -39,6 +40,7 @@
|
||||
"promptmasters": "מאסטרים",
|
||||
"prompts": "פרומפטים",
|
||||
"skills": "כישורים",
|
||||
"taste": "Taste",
|
||||
"workflows": "תהליכי עבודה",
|
||||
"categories": "קטגוריות",
|
||||
"tags": "תגיות",
|
||||
@@ -107,7 +109,9 @@
|
||||
"title": "פרומפטים",
|
||||
"create": "יצירת פרומפט",
|
||||
"createSkill": "יצירת מיומנות",
|
||||
"createTaste": "יצירת Taste",
|
||||
"skillsDescription": "מיומנויות סוכן הן פרומפטים מרובי קבצים שמעניקים לסוכני AI יכולות מתמחות. הן כוללות הוראות, הגדרות וקבצי תמיכה שניתן להשתמש בהם עם Claude, Cursor, Windsurf ועוזרי קידוד AI אחרים.",
|
||||
"tastesDescription": "Tastes הם קבצי markdown בודדים שמגדירים את סגנון הקידוד וההעדפות שלך. הם עוזרים לסוכני קידוד AI ללמוד איך אתה כותב קוד, כך שיוכלו להתאים למוסכמות, לדפוסים ולטעם שלך.",
|
||||
"createInfo": "פלטפורמה זו לא מריצה פרומפטים — היא ספרייה קהילתית לשיתוף וגילוי פרומפטים של AI. צור את הפרומפט שלך כאן, ואחרים יוכלו להעתיק ולהשתמש בו בכלי ה-AI המועדפים עליהם כמו ChatGPT, Claude, Gemini או כל LLM אחר. הקהילה יכולה גם להגיב על הפרומפטים שלך ולהציע שיפורים דרך בקשות שינוי.",
|
||||
"hfDataStudio": {
|
||||
"button": "HF Data Studio",
|
||||
@@ -189,7 +193,8 @@
|
||||
"audio": "אודיו",
|
||||
"structured": "מובנה",
|
||||
"document": "מסמך",
|
||||
"skill": "מיומנות"
|
||||
"skill": "מיומנות",
|
||||
"taste": "Taste"
|
||||
},
|
||||
"inputType": "פרומפט משתמש",
|
||||
"outputType": "תגובת AI",
|
||||
@@ -199,7 +204,8 @@
|
||||
"inputTypes": {
|
||||
"text": "פרומפט טקסט",
|
||||
"structured": "מובנה (JSON/YAML)",
|
||||
"skill": "מיומנות (Agent Skills)"
|
||||
"skill": "מיומנות (Agent Skills)",
|
||||
"taste": "Taste (Coding Taste)"
|
||||
},
|
||||
"outputTypes": {
|
||||
"text": "טקסט",
|
||||
@@ -1045,45 +1051,8 @@
|
||||
}
|
||||
},
|
||||
"heroIndustries": {
|
||||
"prefix": "או חקור לפי תעשייה",
|
||||
"clickToExplore": "לחץ לחקירה",
|
||||
"searchPlaceholder": "חפש פרומפטים...",
|
||||
"teachers": "מורים",
|
||||
"developers": "מפתחים",
|
||||
"marketers": "משווקים",
|
||||
"designers": "מעצבים",
|
||||
"writers": "סופרים",
|
||||
"analysts": "אנליסטים",
|
||||
"entrepreneurs": "יזמים",
|
||||
"researchers": "חוקרים",
|
||||
"students": "סטודנטים",
|
||||
"consultants": "יועצים",
|
||||
"engineers": "מהנדסים",
|
||||
"creators": "יוצרים",
|
||||
"lawyers": "עורכי דין",
|
||||
"doctors": "רופאים",
|
||||
"nurses": "אחיות",
|
||||
"accountants": "רואי חשבון",
|
||||
"salespeople": "אנשי מכירות",
|
||||
"recruiters": "מגייסים",
|
||||
"managers": "מנהלים",
|
||||
"executives": "בכירים",
|
||||
"freelancers": "פרילנסרים",
|
||||
"photographers": "צלמים",
|
||||
"musicians": "מוזיקאים",
|
||||
"artists": "אמנים",
|
||||
"architects": "אדריכלים",
|
||||
"scientists": "מדענים",
|
||||
"journalists": "עיתונאים",
|
||||
"editors": "עורכים",
|
||||
"translators": "מתרגמים",
|
||||
"coaches": "מאמנים",
|
||||
"therapists": "מטפלים",
|
||||
"trainers": "מדריכים",
|
||||
"chefs": "שפים",
|
||||
"realtors": "סוכני נדל״ן",
|
||||
"investors": "משקיעים",
|
||||
"traders": "סוחרים"
|
||||
"videoTitle": "GitHub Open Source Friday"
|
||||
},
|
||||
"promptBuilder": {
|
||||
"title": "סוכן בניית פרומפטים",
|
||||
@@ -1483,6 +1452,7 @@
|
||||
"supportNow": "תמכו עכשיו",
|
||||
"techStackTitle": "התפתחות טכנולוגית",
|
||||
"coreContributorsTitle": "תורמים מרכזיים",
|
||||
"securityTitle": "אבטחה",
|
||||
"designCreditsTitle": "עיצוב",
|
||||
"ideationTitle": "רעיונות",
|
||||
"communityContributorsTitle": "תורמי קהילה",
|
||||
@@ -2255,5 +2225,12 @@
|
||||
"submit": "שלח",
|
||||
"communityExamples": "דוגמאות קהילה",
|
||||
"userExample": "דוגמת משתמש"
|
||||
},
|
||||
"selfHosting": {
|
||||
"prerequisites": {
|
||||
"nodeJs": "Node.js 24.x",
|
||||
"postgresql": "PostgreSQL database",
|
||||
"npm": "npm"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+17
-40
@@ -27,6 +27,7 @@
|
||||
"close": "Chiudi",
|
||||
"all": "Tutti",
|
||||
"none": "Nessuno",
|
||||
"ad": "Annuncio",
|
||||
"moreLines": "+{count} altre righe",
|
||||
"codeView": "Codice",
|
||||
"treeView": "Albero",
|
||||
@@ -39,6 +40,7 @@
|
||||
"promptmasters": "Promptmaster",
|
||||
"prompts": "Prompt",
|
||||
"skills": "Competenze",
|
||||
"taste": "Taste",
|
||||
"workflows": "Flussi di lavoro",
|
||||
"categories": "Categorie",
|
||||
"tags": "Tag",
|
||||
@@ -107,7 +109,9 @@
|
||||
"title": "Prompt",
|
||||
"create": "Crea Prompt",
|
||||
"createSkill": "Crea Competenza",
|
||||
"createTaste": "Crea Taste",
|
||||
"skillsDescription": "Le Competenze dell'Agente sono prompt multi-file che danno agli agenti AI capacità specializzate. Includono istruzioni, configurazioni e file di supporto che possono essere utilizzati con Claude, Cursor, Windsurf e altri assistenti di codifica AI.",
|
||||
"tastesDescription": "I Tastes sono singoli file markdown che definiscono il tuo stile di codifica e le tue preferenze. Aiutano gli agenti di codifica AI a imparare come scrivi codice, così possono adattarsi alle tue convenzioni, pattern e gusto.",
|
||||
"createInfo": "Questa piattaforma non esegue i prompt — è una libreria comunitaria per condividere e scoprire prompt AI. Crea il tuo prompt qui, e altri potranno copiarlo e usarlo nei loro strumenti AI preferiti come ChatGPT, Claude, Gemini o qualsiasi altro LLM. La community può anche commentare i tuoi prompt e suggerire miglioramenti tramite richieste di modifica.",
|
||||
"hfDataStudio": {
|
||||
"button": "HF Data Studio",
|
||||
@@ -189,7 +193,8 @@
|
||||
"audio": "Audio",
|
||||
"structured": "Strutturato",
|
||||
"document": "Documento",
|
||||
"skill": "Abilità"
|
||||
"skill": "Abilità",
|
||||
"taste": "Taste"
|
||||
},
|
||||
"inputType": "Prompt Utente",
|
||||
"outputType": "Risposta AI",
|
||||
@@ -199,7 +204,8 @@
|
||||
"inputTypes": {
|
||||
"text": "Prompt Testuale",
|
||||
"structured": "Strutturato (JSON/YAML)",
|
||||
"skill": "Abilità (Agent Skills)"
|
||||
"skill": "Abilità (Agent Skills)",
|
||||
"taste": "Taste (Coding Taste)"
|
||||
},
|
||||
"outputTypes": {
|
||||
"text": "Testo",
|
||||
@@ -1024,45 +1030,8 @@
|
||||
}
|
||||
},
|
||||
"heroIndustries": {
|
||||
"prefix": "o esplora per settore",
|
||||
"clickToExplore": "Clicca per esplorare",
|
||||
"searchPlaceholder": "Cerca prompt...",
|
||||
"teachers": "Insegnanti",
|
||||
"developers": "Sviluppatori",
|
||||
"marketers": "Marketer",
|
||||
"designers": "Designer",
|
||||
"writers": "Scrittori",
|
||||
"analysts": "Analisti",
|
||||
"entrepreneurs": "Imprenditori",
|
||||
"researchers": "Ricercatori",
|
||||
"students": "Studenti",
|
||||
"consultants": "Consulenti",
|
||||
"engineers": "Ingegneri",
|
||||
"creators": "Creatori",
|
||||
"lawyers": "Avvocati",
|
||||
"doctors": "Medici",
|
||||
"nurses": "Infermieri",
|
||||
"accountants": "Contabili",
|
||||
"salespeople": "Venditori",
|
||||
"recruiters": "Recruiter",
|
||||
"managers": "Manager",
|
||||
"executives": "Dirigenti",
|
||||
"freelancers": "Freelancer",
|
||||
"photographers": "Fotografi",
|
||||
"musicians": "Musicisti",
|
||||
"artists": "Artisti",
|
||||
"architects": "Architetti",
|
||||
"scientists": "Scienziati",
|
||||
"journalists": "Giornalisti",
|
||||
"editors": "Redattori",
|
||||
"translators": "Traduttori",
|
||||
"coaches": "Coach",
|
||||
"therapists": "Terapeuti",
|
||||
"trainers": "Formatori",
|
||||
"chefs": "Chef",
|
||||
"realtors": "Agenti",
|
||||
"investors": "Investitori",
|
||||
"traders": "Trader"
|
||||
"videoTitle": "GitHub Open Source Friday"
|
||||
},
|
||||
"notFound": {
|
||||
"title": "Pagina Non Trovata",
|
||||
@@ -1483,6 +1452,7 @@
|
||||
"supportNow": "Supporta Ora",
|
||||
"techStackTitle": "Evoluzione Tecnologica",
|
||||
"coreContributorsTitle": "Contributori Principali",
|
||||
"securityTitle": "Sicurezza",
|
||||
"designCreditsTitle": "Design",
|
||||
"ideationTitle": "Ideazione",
|
||||
"communityContributorsTitle": "Contributori della Community",
|
||||
@@ -2277,5 +2247,12 @@
|
||||
"submit": "Invia",
|
||||
"communityExamples": "Esempi della Community",
|
||||
"userExample": "Esempio utente"
|
||||
},
|
||||
"selfHosting": {
|
||||
"prerequisites": {
|
||||
"nodeJs": "Node.js 24.x",
|
||||
"postgresql": "PostgreSQL database",
|
||||
"npm": "npm"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+17
-40
@@ -27,6 +27,7 @@
|
||||
"close": "閉じる",
|
||||
"all": "すべて",
|
||||
"none": "なし",
|
||||
"ad": "広告",
|
||||
"moreLines": "+{count}行",
|
||||
"codeView": "コード",
|
||||
"treeView": "ツリー",
|
||||
@@ -39,6 +40,7 @@
|
||||
"promptmasters": "Promptmasters",
|
||||
"prompts": "プロンプト",
|
||||
"skills": "スキル",
|
||||
"taste": "Taste",
|
||||
"workflows": "ワークフロー",
|
||||
"categories": "カテゴリー",
|
||||
"tags": "タグ",
|
||||
@@ -107,7 +109,9 @@
|
||||
"title": "プロンプト",
|
||||
"create": "プロンプトを作成",
|
||||
"createSkill": "スキルを作成",
|
||||
"createTaste": "Tasteを作成",
|
||||
"skillsDescription": "エージェントスキルは、AIエージェントに専門的な機能を与えるマルチファイルプロンプトです。Claude、Cursor、Windsurf、その他のAIコーディングアシスタントで使用できる指示、設定、サポートファイルが含まれています。",
|
||||
"tastesDescription": "Tastesは、コーディングスタイルと好みを定義する単一のマークダウンファイルです。AIコーディングエージェントがあなたのコードの書き方を学習し、規約、パターン、テイストに合わせることができます。",
|
||||
"createInfo": "このプラットフォームはプロンプトを実行しません — AIプロンプトを共有・発見するためのコミュニティライブラリです。ここでプロンプトを作成すると、他のユーザーがChatGPT、Claude、Geminiなどのお気に入りのAIツールでコピーして使用できます。コミュニティはあなたのプロンプトにコメントしたり、変更リクエストを通じて改善を提案することもできます。",
|
||||
"hfDataStudio": {
|
||||
"button": "HF Data Studio",
|
||||
@@ -189,7 +193,8 @@
|
||||
"audio": "音声",
|
||||
"structured": "構造化",
|
||||
"document": "ドキュメント",
|
||||
"skill": "スキル"
|
||||
"skill": "スキル",
|
||||
"taste": "Taste"
|
||||
},
|
||||
"inputType": "ユーザープロンプト",
|
||||
"outputType": "AI応答",
|
||||
@@ -199,7 +204,8 @@
|
||||
"inputTypes": {
|
||||
"text": "テキストプロンプト",
|
||||
"structured": "構造化 (JSON/YAML)",
|
||||
"skill": "スキル (Agent Skills)"
|
||||
"skill": "スキル (Agent Skills)",
|
||||
"taste": "Taste (Coding Taste)"
|
||||
},
|
||||
"outputTypes": {
|
||||
"text": "テキスト",
|
||||
@@ -1024,45 +1030,8 @@
|
||||
}
|
||||
},
|
||||
"heroIndustries": {
|
||||
"prefix": "または業種で探す",
|
||||
"clickToExplore": "クリックして探索",
|
||||
"searchPlaceholder": "プロンプトを検索...",
|
||||
"teachers": "教師",
|
||||
"developers": "開発者",
|
||||
"marketers": "マーケター",
|
||||
"designers": "デザイナー",
|
||||
"writers": "ライター",
|
||||
"analysts": "アナリスト",
|
||||
"entrepreneurs": "起業家",
|
||||
"researchers": "研究者",
|
||||
"students": "学生",
|
||||
"consultants": "コンサルタント",
|
||||
"engineers": "エンジニア",
|
||||
"creators": "クリエイター",
|
||||
"lawyers": "弁護士",
|
||||
"doctors": "医師",
|
||||
"nurses": "看護師",
|
||||
"accountants": "会計士",
|
||||
"salespeople": "営業",
|
||||
"recruiters": "採用担当",
|
||||
"managers": "マネージャー",
|
||||
"executives": "経営者",
|
||||
"freelancers": "フリーランス",
|
||||
"photographers": "写真家",
|
||||
"musicians": "ミュージシャン",
|
||||
"artists": "アーティスト",
|
||||
"architects": "建築家",
|
||||
"scientists": "科学者",
|
||||
"journalists": "ジャーナリスト",
|
||||
"editors": "編集者",
|
||||
"translators": "翻訳者",
|
||||
"coaches": "コーチ",
|
||||
"therapists": "セラピスト",
|
||||
"trainers": "トレーナー",
|
||||
"chefs": "シェフ",
|
||||
"realtors": "不動産業者",
|
||||
"investors": "投資家",
|
||||
"traders": "トレーダー"
|
||||
"videoTitle": "GitHub Open Source Friday"
|
||||
},
|
||||
"notFound": {
|
||||
"title": "ページが見つかりません",
|
||||
@@ -1483,6 +1452,7 @@
|
||||
"supportNow": "今すぐサポート",
|
||||
"techStackTitle": "技術スタックの進化",
|
||||
"coreContributorsTitle": "コアコントリビューター",
|
||||
"securityTitle": "セキュリティ",
|
||||
"designCreditsTitle": "デザイン",
|
||||
"ideationTitle": "アイデア",
|
||||
"communityContributorsTitle": "コミュニティコントリビューター",
|
||||
@@ -2259,5 +2229,12 @@
|
||||
"submit": "送信",
|
||||
"communityExamples": "コミュニティの例",
|
||||
"userExample": "ユーザーの例"
|
||||
},
|
||||
"selfHosting": {
|
||||
"prerequisites": {
|
||||
"nodeJs": "Node.js 24.x",
|
||||
"postgresql": "PostgreSQL database",
|
||||
"npm": "npm"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+17
-40
@@ -27,6 +27,7 @@
|
||||
"close": "닫기",
|
||||
"all": "전체",
|
||||
"none": "없음",
|
||||
"ad": "광고",
|
||||
"moreLines": "+{count}줄 더",
|
||||
"codeView": "코드",
|
||||
"treeView": "트리",
|
||||
@@ -39,6 +40,7 @@
|
||||
"promptmasters": "프롬프트마스터",
|
||||
"prompts": "프롬프트",
|
||||
"skills": "스킬",
|
||||
"taste": "Taste",
|
||||
"workflows": "워크플로우",
|
||||
"categories": "카테고리",
|
||||
"tags": "태그",
|
||||
@@ -107,7 +109,9 @@
|
||||
"title": "프롬프트",
|
||||
"create": "프롬프트 만들기",
|
||||
"createSkill": "스킬 만들기",
|
||||
"createTaste": "Taste 만들기",
|
||||
"skillsDescription": "에이전트 스킬은 AI 에이전트에게 전문 기능을 제공하는 멀티 파일 프롬프트입니다. Claude, Cursor, Windsurf 및 기타 AI 코딩 어시스턴트와 함께 사용할 수 있는 지침, 구성 및 지원 파일이 포함되어 있습니다.",
|
||||
"tastesDescription": "Tastes는 코딩 스타일과 선호도를 정의하는 단일 마크다운 파일입니다. AI 코딩 에이전트가 코드 작성 방식을 학습하여 규칙, 패턴 및 취향에 맞출 수 있도록 도와줍니다.",
|
||||
"createInfo": "이 플랫폼은 프롬프트를 실행하지 않습니다 — AI 프롬프트를 공유하고 발견하는 커뮤니티 라이브러리입니다. 여기서 프롬프트를 만들면 다른 사용자들이 ChatGPT, Claude, Gemini 등 선호하는 AI 도구에서 복사하여 사용할 수 있습니다. 커뮤니티는 프롬프트에 댓글을 달고 변경 요청을 통해 개선 사항을 제안할 수도 있습니다.",
|
||||
"hfDataStudio": {
|
||||
"button": "HF 데이터 스튜디오",
|
||||
@@ -189,7 +193,8 @@
|
||||
"audio": "오디오",
|
||||
"structured": "구조화",
|
||||
"document": "문서",
|
||||
"skill": "스킬"
|
||||
"skill": "스킬",
|
||||
"taste": "Taste"
|
||||
},
|
||||
"inputType": "사용자 프롬프트",
|
||||
"outputType": "AI 응답",
|
||||
@@ -199,7 +204,8 @@
|
||||
"inputTypes": {
|
||||
"text": "텍스트 프롬프트",
|
||||
"structured": "구조화 (JSON/YAML)",
|
||||
"skill": "스킬 (Agent Skills)"
|
||||
"skill": "스킬 (Agent Skills)",
|
||||
"taste": "Taste (Coding Taste)"
|
||||
},
|
||||
"outputTypes": {
|
||||
"text": "텍스트",
|
||||
@@ -1024,45 +1030,8 @@
|
||||
}
|
||||
},
|
||||
"heroIndustries": {
|
||||
"prefix": "또는 업종별 탐색",
|
||||
"clickToExplore": "클릭하여 탐색",
|
||||
"searchPlaceholder": "프롬프트 검색...",
|
||||
"teachers": "교사",
|
||||
"developers": "개발자",
|
||||
"marketers": "마케터",
|
||||
"designers": "디자이너",
|
||||
"writers": "작가",
|
||||
"analysts": "분석가",
|
||||
"entrepreneurs": "창업가",
|
||||
"researchers": "연구자",
|
||||
"students": "학생",
|
||||
"consultants": "컨설턴트",
|
||||
"engineers": "엔지니어",
|
||||
"creators": "크리에이터",
|
||||
"lawyers": "변호사",
|
||||
"doctors": "의사",
|
||||
"nurses": "간호사",
|
||||
"accountants": "회계사",
|
||||
"salespeople": "영업사원",
|
||||
"recruiters": "채용담당자",
|
||||
"managers": "매니저",
|
||||
"executives": "임원",
|
||||
"freelancers": "프리랜서",
|
||||
"photographers": "사진작가",
|
||||
"musicians": "음악가",
|
||||
"artists": "아티스트",
|
||||
"architects": "건축가",
|
||||
"scientists": "과학자",
|
||||
"journalists": "기자",
|
||||
"editors": "편집자",
|
||||
"translators": "번역가",
|
||||
"coaches": "코치",
|
||||
"therapists": "치료사",
|
||||
"trainers": "트레이너",
|
||||
"chefs": "셰프",
|
||||
"realtors": "부동산",
|
||||
"investors": "투자자",
|
||||
"traders": "트레이더"
|
||||
"videoTitle": "GitHub Open Source Friday"
|
||||
},
|
||||
"notFound": {
|
||||
"title": "페이지를 찾을 수 없습니다",
|
||||
@@ -1483,6 +1452,7 @@
|
||||
"supportNow": "지금 후원하기",
|
||||
"techStackTitle": "기술 스택 진화",
|
||||
"coreContributorsTitle": "핵심 기여자",
|
||||
"securityTitle": "보안",
|
||||
"designCreditsTitle": "디자인",
|
||||
"ideationTitle": "아이디어",
|
||||
"communityContributorsTitle": "커뮤니티 기여자",
|
||||
@@ -2259,5 +2229,12 @@
|
||||
"submit": "제출",
|
||||
"communityExamples": "커뮤니티 예제",
|
||||
"userExample": "사용자 예제"
|
||||
},
|
||||
"selfHosting": {
|
||||
"prerequisites": {
|
||||
"nodeJs": "Node.js 24.x",
|
||||
"postgresql": "PostgreSQL database",
|
||||
"npm": "npm"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+17
-40
@@ -27,6 +27,7 @@
|
||||
"close": "Sluiten",
|
||||
"all": "Alles",
|
||||
"none": "Geen",
|
||||
"ad": "Advertentie",
|
||||
"moreLines": "+{count} meer regels",
|
||||
"codeView": "Code",
|
||||
"treeView": "Boom",
|
||||
@@ -39,6 +40,7 @@
|
||||
"promptmasters": "Promptmasters",
|
||||
"prompts": "Prompts",
|
||||
"skills": "Vaardigheden",
|
||||
"taste": "Taste",
|
||||
"workflows": "Workflows",
|
||||
"categories": "Categorieën",
|
||||
"tags": "Tags",
|
||||
@@ -107,7 +109,9 @@
|
||||
"title": "Prompts",
|
||||
"create": "Prompt aanmaken",
|
||||
"createSkill": "Vaardigheid aanmaken",
|
||||
"createTaste": "Taste aanmaken",
|
||||
"skillsDescription": "Agent Vaardigheden zijn multi-bestand prompts die AI-agenten gespecialiseerde mogelijkheden geven. Ze bevatten instructies, configuraties en ondersteunende bestanden die kunnen worden gebruikt met Claude, Cursor, Windsurf en andere AI-coderingsassistenten.",
|
||||
"tastesDescription": "Tastes zijn enkele markdown-bestanden die je codeerstijl en voorkeuren definiëren. Ze helpen AI-coderingsagenten te leren hoe je code schrijft, zodat ze je conventies, patronen en smaak kunnen overnemen.",
|
||||
"createInfo": "Dit platform voert geen prompts uit — het is een door de community beheerde bibliotheek voor het delen en ontdekken van AI-prompts. Maak hier je prompt aan, en anderen kunnen het kopiëren en gebruiken in hun favoriete AI-tools zoals ChatGPT, Claude, Gemini of andere LLM's. De community kan ook reageren op je prompts en verbeteringen voorstellen via wijzigingsverzoeken.",
|
||||
"hfDataStudio": {
|
||||
"button": "HF Data Studio",
|
||||
@@ -189,7 +193,8 @@
|
||||
"audio": "Audio",
|
||||
"structured": "Gestructureerd",
|
||||
"document": "Document",
|
||||
"skill": "Vaardigheid"
|
||||
"skill": "Vaardigheid",
|
||||
"taste": "Taste"
|
||||
},
|
||||
"inputType": "Gebruikersprompt",
|
||||
"outputType": "AI-antwoord",
|
||||
@@ -199,7 +204,8 @@
|
||||
"inputTypes": {
|
||||
"text": "Tekstprompt",
|
||||
"structured": "Gestructureerd (JSON/YAML)",
|
||||
"skill": "Vaardigheid (Agent Skills)"
|
||||
"skill": "Vaardigheid (Agent Skills)",
|
||||
"taste": "Taste (Coding Taste)"
|
||||
},
|
||||
"outputTypes": {
|
||||
"text": "Tekst",
|
||||
@@ -1019,45 +1025,8 @@
|
||||
}
|
||||
},
|
||||
"heroIndustries": {
|
||||
"prefix": "of verken per industrie",
|
||||
"clickToExplore": "Klik om te verkennen",
|
||||
"searchPlaceholder": "Prompts zoeken...",
|
||||
"teachers": "Docenten",
|
||||
"developers": "Ontwikkelaars",
|
||||
"marketers": "Marketeers",
|
||||
"designers": "Ontwerpers",
|
||||
"writers": "Schrijvers",
|
||||
"analysts": "Analisten",
|
||||
"entrepreneurs": "Ondernemers",
|
||||
"researchers": "Onderzoekers",
|
||||
"students": "Studenten",
|
||||
"consultants": "Consultants",
|
||||
"engineers": "Ingenieurs",
|
||||
"creators": "Creators",
|
||||
"lawyers": "Advocaten",
|
||||
"doctors": "Artsen",
|
||||
"nurses": "Verpleegkundigen",
|
||||
"accountants": "Accountants",
|
||||
"salespeople": "Verkopers",
|
||||
"recruiters": "Recruiters",
|
||||
"managers": "Managers",
|
||||
"executives": "Directeuren",
|
||||
"freelancers": "Freelancers",
|
||||
"photographers": "Fotografen",
|
||||
"musicians": "Muzikanten",
|
||||
"artists": "Kunstenaars",
|
||||
"architects": "Architecten",
|
||||
"scientists": "Wetenschappers",
|
||||
"journalists": "Journalisten",
|
||||
"editors": "Redacteuren",
|
||||
"translators": "Vertalers",
|
||||
"coaches": "Coaches",
|
||||
"therapists": "Therapeuten",
|
||||
"trainers": "Trainers",
|
||||
"chefs": "Chefs",
|
||||
"realtors": "Makelaars",
|
||||
"investors": "Investeerders",
|
||||
"traders": "Handelaren"
|
||||
"videoTitle": "GitHub Open Source Friday"
|
||||
},
|
||||
"promptBuilder": {
|
||||
"title": "Prompt Building Agent",
|
||||
@@ -1483,6 +1452,7 @@
|
||||
"supportNow": "Steun nu",
|
||||
"techStackTitle": "Tech-evolutie",
|
||||
"coreContributorsTitle": "Kernbijdragers",
|
||||
"securityTitle": "Beveiliging",
|
||||
"designCreditsTitle": "Ontwerp",
|
||||
"ideationTitle": "Ideeën",
|
||||
"communityContributorsTitle": "Community-bijdragers",
|
||||
@@ -2262,5 +2232,12 @@
|
||||
"submit": "Versturen",
|
||||
"communityExamples": "Community Voorbeelden",
|
||||
"userExample": "Gebruikers Voorbeeld"
|
||||
},
|
||||
"selfHosting": {
|
||||
"prerequisites": {
|
||||
"nodeJs": "Node.js 24.x",
|
||||
"postgresql": "PostgreSQL database",
|
||||
"npm": "npm"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+17
-40
@@ -27,6 +27,7 @@
|
||||
"close": "Fechar",
|
||||
"all": "Todos",
|
||||
"none": "Nenhum",
|
||||
"ad": "Anúncio",
|
||||
"moreLines": "+{count} mais linhas",
|
||||
"codeView": "Código",
|
||||
"treeView": "Árvore",
|
||||
@@ -39,6 +40,7 @@
|
||||
"promptmasters": "Promptmasters",
|
||||
"prompts": "Prompts",
|
||||
"skills": "Habilidades",
|
||||
"taste": "Taste",
|
||||
"workflows": "Fluxos de trabalho",
|
||||
"categories": "Categorias",
|
||||
"tags": "Tags",
|
||||
@@ -107,7 +109,9 @@
|
||||
"title": "Prompts",
|
||||
"create": "Criar Prompt",
|
||||
"createSkill": "Criar Habilidade",
|
||||
"createTaste": "Criar Taste",
|
||||
"skillsDescription": "Habilidades de Agente são prompts multi-arquivo que dão aos agentes de IA capacidades especializadas. Incluem instruções, configurações e arquivos de suporte que podem ser usados com Claude, Cursor, Windsurf e outros assistentes de codificação de IA.",
|
||||
"tastesDescription": "Tastes são arquivos markdown individuais que definem seu estilo de codificação e preferências. Ajudam os agentes de codificação de IA a aprender como você escreve código, para que possam corresponder às suas convenções, padrões e gosto.",
|
||||
"createInfo": "Esta plataforma não executa prompts — é uma biblioteca comunitária para compartilhar e descobrir prompts de IA. Crie seu prompt aqui, e outros poderão copiá-lo e usá-lo em suas ferramentas de IA favoritas como ChatGPT, Claude, Gemini ou qualquer outro LLM. A comunidade também pode comentar seus prompts e sugerir melhorias através de solicitações de alteração.",
|
||||
"hfDataStudio": {
|
||||
"button": "HF Data Studio",
|
||||
@@ -189,7 +193,8 @@
|
||||
"audio": "Áudio",
|
||||
"structured": "Estruturado",
|
||||
"document": "Documento",
|
||||
"skill": "Habilidade"
|
||||
"skill": "Habilidade",
|
||||
"taste": "Taste"
|
||||
},
|
||||
"inputType": "Prompt do Usuário",
|
||||
"outputType": "Resposta da IA",
|
||||
@@ -199,7 +204,8 @@
|
||||
"inputTypes": {
|
||||
"text": "Prompt de Texto",
|
||||
"structured": "Estruturado (JSON/YAML)",
|
||||
"skill": "Habilidade (Agent Skills)"
|
||||
"skill": "Habilidade (Agent Skills)",
|
||||
"taste": "Taste (Coding Taste)"
|
||||
},
|
||||
"outputTypes": {
|
||||
"text": "Texto",
|
||||
@@ -1024,45 +1030,8 @@
|
||||
}
|
||||
},
|
||||
"heroIndustries": {
|
||||
"prefix": "ou explore por setor",
|
||||
"clickToExplore": "Clique para explorar",
|
||||
"searchPlaceholder": "Pesquisar prompts...",
|
||||
"teachers": "Professores",
|
||||
"developers": "Desenvolvedores",
|
||||
"marketers": "Marketers",
|
||||
"designers": "Designers",
|
||||
"writers": "Escritores",
|
||||
"analysts": "Analistas",
|
||||
"entrepreneurs": "Empreendedores",
|
||||
"researchers": "Pesquisadores",
|
||||
"students": "Estudantes",
|
||||
"consultants": "Consultores",
|
||||
"engineers": "Engenheiros",
|
||||
"creators": "Criadores",
|
||||
"lawyers": "Advogados",
|
||||
"doctors": "Médicos",
|
||||
"nurses": "Enfermeiros",
|
||||
"accountants": "Contadores",
|
||||
"salespeople": "Vendedores",
|
||||
"recruiters": "Recrutadores",
|
||||
"managers": "Gerentes",
|
||||
"executives": "Executivos",
|
||||
"freelancers": "Freelancers",
|
||||
"photographers": "Fotógrafos",
|
||||
"musicians": "Músicos",
|
||||
"artists": "Artistas",
|
||||
"architects": "Arquitetos",
|
||||
"scientists": "Cientistas",
|
||||
"journalists": "Jornalistas",
|
||||
"editors": "Editores",
|
||||
"translators": "Tradutores",
|
||||
"coaches": "Coaches",
|
||||
"therapists": "Terapeutas",
|
||||
"trainers": "Treinadores",
|
||||
"chefs": "Chefs",
|
||||
"realtors": "Corretores",
|
||||
"investors": "Investidores",
|
||||
"traders": "Traders"
|
||||
"videoTitle": "GitHub Open Source Friday"
|
||||
},
|
||||
"notFound": {
|
||||
"title": "Página Não Encontrada",
|
||||
@@ -1483,6 +1452,7 @@
|
||||
"supportNow": "Apoiar Agora",
|
||||
"techStackTitle": "Evolução Tecnológica",
|
||||
"coreContributorsTitle": "Contribuidores Principais",
|
||||
"securityTitle": "Segurança",
|
||||
"designCreditsTitle": "Design",
|
||||
"ideationTitle": "Ideação",
|
||||
"communityContributorsTitle": "Contribuidores da Comunidade",
|
||||
@@ -2273,5 +2243,12 @@
|
||||
"submit": "Enviar",
|
||||
"communityExamples": "Exemplos da Comunidade",
|
||||
"userExample": "Exemplo do usuário"
|
||||
},
|
||||
"selfHosting": {
|
||||
"prerequisites": {
|
||||
"nodeJs": "Node.js 24.x",
|
||||
"postgresql": "PostgreSQL database",
|
||||
"npm": "npm"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+17
-40
@@ -27,6 +27,7 @@
|
||||
"close": "Закрыть",
|
||||
"all": "Все",
|
||||
"none": "Нет",
|
||||
"ad": "Реклама",
|
||||
"moreLines": "+{count} строк",
|
||||
"codeView": "Код",
|
||||
"treeView": "Дерево",
|
||||
@@ -39,6 +40,7 @@
|
||||
"promptmasters": "Мастера промптов",
|
||||
"prompts": "Промпты",
|
||||
"skills": "Навыки",
|
||||
"taste": "Taste",
|
||||
"workflows": "Рабочие процессы",
|
||||
"categories": "Категории",
|
||||
"tags": "Теги",
|
||||
@@ -107,7 +109,9 @@
|
||||
"title": "Промпты",
|
||||
"create": "Создать промпт",
|
||||
"createSkill": "Создать навык",
|
||||
"createTaste": "Создать Taste",
|
||||
"skillsDescription": "Навыки агента — это многофайловые промпты, которые дают AI-агентам специализированные возможности. Они включают инструкции, конфигурации и вспомогательные файлы, которые можно использовать с Claude, Cursor, Windsurf и другими AI-помощниками для программирования.",
|
||||
"tastesDescription": "Tastes — это отдельные файлы markdown, которые определяют ваш стиль программирования и предпочтения. Они помогают AI-агентам для программирования изучить, как вы пишете код, чтобы они могли соответствовать вашим соглашениям, шаблонам и вкусу.",
|
||||
"createInfo": "Эта платформа не выполняет промпты — это библиотека сообщества для обмена и поиска AI-промптов. Создайте свой промпт здесь, и другие смогут скопировать и использовать его в своих любимых AI-инструментах, таких как ChatGPT, Claude, Gemini или любой другой LLM. Сообщество также может комментировать ваши промпты и предлагать улучшения через запросы на изменение.",
|
||||
"hfDataStudio": {
|
||||
"button": "HF Data Studio",
|
||||
@@ -189,7 +193,8 @@
|
||||
"audio": "Аудио",
|
||||
"structured": "Структурированный",
|
||||
"document": "Документ",
|
||||
"skill": "Навык"
|
||||
"skill": "Навык",
|
||||
"taste": "Taste"
|
||||
},
|
||||
"inputType": "Промпт пользователя",
|
||||
"outputType": "Ответ ИИ",
|
||||
@@ -199,7 +204,8 @@
|
||||
"inputTypes": {
|
||||
"text": "Текстовый промпт",
|
||||
"structured": "Структурированный (JSON/YAML)",
|
||||
"skill": "Навык (Agent Skills)"
|
||||
"skill": "Навык (Agent Skills)",
|
||||
"taste": "Taste (Coding Taste)"
|
||||
},
|
||||
"outputTypes": {
|
||||
"text": "Текст",
|
||||
@@ -1045,45 +1051,8 @@
|
||||
}
|
||||
},
|
||||
"heroIndustries": {
|
||||
"prefix": "или исследуйте по отрасли",
|
||||
"clickToExplore": "Нажмите для изучения",
|
||||
"searchPlaceholder": "Поиск промптов...",
|
||||
"teachers": "Учителей",
|
||||
"developers": "Разработчиков",
|
||||
"marketers": "Маркетологов",
|
||||
"designers": "Дизайнеров",
|
||||
"writers": "Писателей",
|
||||
"analysts": "Аналитиков",
|
||||
"entrepreneurs": "Предпринимателей",
|
||||
"researchers": "Исследователей",
|
||||
"students": "Студентов",
|
||||
"consultants": "Консультантов",
|
||||
"engineers": "Инженеров",
|
||||
"creators": "Создателей",
|
||||
"lawyers": "Юристов",
|
||||
"doctors": "Врачей",
|
||||
"nurses": "Медсестёр",
|
||||
"accountants": "Бухгалтеров",
|
||||
"salespeople": "Продавцов",
|
||||
"recruiters": "Рекрутеров",
|
||||
"managers": "Менеджеров",
|
||||
"executives": "Руководителей",
|
||||
"freelancers": "Фрилансеров",
|
||||
"photographers": "Фотографов",
|
||||
"musicians": "Музыкантов",
|
||||
"artists": "Художников",
|
||||
"architects": "Архитекторов",
|
||||
"scientists": "Учёных",
|
||||
"journalists": "Журналистов",
|
||||
"editors": "Редакторов",
|
||||
"translators": "Переводчиков",
|
||||
"coaches": "Коучей",
|
||||
"therapists": "Терапевтов",
|
||||
"trainers": "Тренеров",
|
||||
"chefs": "Поваров",
|
||||
"realtors": "Риелторов",
|
||||
"investors": "Инвесторов",
|
||||
"traders": "Трейдеров"
|
||||
"videoTitle": "GitHub Open Source Friday"
|
||||
},
|
||||
"promptBuilder": {
|
||||
"title": "Агент создания промптов",
|
||||
@@ -1483,6 +1452,7 @@
|
||||
"supportNow": "Поддержать сейчас",
|
||||
"techStackTitle": "Эволюция технологий",
|
||||
"coreContributorsTitle": "Основные участники",
|
||||
"securityTitle": "Безопасность",
|
||||
"designCreditsTitle": "Дизайн",
|
||||
"ideationTitle": "Идеи",
|
||||
"communityContributorsTitle": "Участники сообщества",
|
||||
@@ -2256,5 +2226,12 @@
|
||||
"submit": "Отправить",
|
||||
"communityExamples": "Примеры сообщества",
|
||||
"userExample": "Пример пользователя"
|
||||
},
|
||||
"selfHosting": {
|
||||
"prerequisites": {
|
||||
"nodeJs": "Node.js 24.x",
|
||||
"postgresql": "PostgreSQL database",
|
||||
"npm": "npm"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+17
-40
@@ -27,6 +27,7 @@
|
||||
"close": "Kapat",
|
||||
"all": "Tümü",
|
||||
"none": "Hiçbiri",
|
||||
"ad": "Reklam",
|
||||
"moreLines": "+{count} satır daha",
|
||||
"codeView": "Kod",
|
||||
"treeView": "Ağaç",
|
||||
@@ -39,6 +40,7 @@
|
||||
"promptmasters": "Promptmasters",
|
||||
"prompts": "Promptlar",
|
||||
"skills": "Yetenekler",
|
||||
"taste": "Taste",
|
||||
"workflows": "Akışlar",
|
||||
"categories": "Kategoriler",
|
||||
"tags": "Etiketler",
|
||||
@@ -107,7 +109,9 @@
|
||||
"title": "Promptlar",
|
||||
"create": "Prompt Oluştur",
|
||||
"createSkill": "Yetenek Oluştur",
|
||||
"createTaste": "Taste Oluştur",
|
||||
"skillsDescription": "Ajan Yetenekleri, AI ajanlara uzmanlaşmış yetenekler veren çok dosyalı promptlardır. Claude, Cursor, Windsurf ve diğer AI kodlama asistanlarıyla kullanılabilecek talimatlar, yapılandırmalar ve destek dosyaları içerirler.",
|
||||
"tastesDescription": "Tastes, kodlama tarzınızı ve tercihlerinizi tanımlayan tek markdown dosyalarıdır. AI kodlama ajanlarının kodunuzu nasıl yazdığınızı öğrenmesine yardımcı olur, böylece kurallarınıza, kalıplarınıza ve zevkinize uyum sağlayabilirler.",
|
||||
"createInfo": "Bu platform promptları çalıştırmaz — AI promptlarını paylaşmak ve keşfetmek için topluluk odaklı bir kütüphanedir. Promptunuzu burada oluşturun, diğerleri ChatGPT, Claude, Gemini veya başka herhangi bir LLM gibi tercih ettikleri AI araçlarında kopyalayıp kullanabilir. Topluluk ayrıca promptlarınıza yorum yapabilir ve değişiklik istekleri aracılığıyla iyileştirmeler önerebilir.",
|
||||
"hfDataStudio": {
|
||||
"button": "HF Veri Stüdyosu",
|
||||
@@ -189,7 +193,8 @@
|
||||
"audio": "Ses",
|
||||
"structured": "Yapılandırılmış",
|
||||
"document": "Doküman",
|
||||
"skill": "Yetenek"
|
||||
"skill": "Yetenek",
|
||||
"taste": "Taste"
|
||||
},
|
||||
"inputType": "Kullanıcı Promptu",
|
||||
"outputType": "AI Yanıtı",
|
||||
@@ -199,7 +204,8 @@
|
||||
"inputTypes": {
|
||||
"text": "Metin Promptu",
|
||||
"structured": "Yapılandırılmış (JSON/YAML)",
|
||||
"skill": "Yetenek (Agent Skills)"
|
||||
"skill": "Yetenek (Agent Skills)",
|
||||
"taste": "Taste (Coding Taste)"
|
||||
},
|
||||
"outputTypes": {
|
||||
"text": "Metin",
|
||||
@@ -1024,45 +1030,8 @@
|
||||
}
|
||||
},
|
||||
"heroIndustries": {
|
||||
"prefix": "veya sektöre göre keşfet",
|
||||
"clickToExplore": "Keşfetmek için tıkla",
|
||||
"searchPlaceholder": "Prompt ara...",
|
||||
"teachers": "Öğretmenler",
|
||||
"developers": "Geliştiriciler",
|
||||
"marketers": "Pazarlamacılar",
|
||||
"designers": "Tasarımcılar",
|
||||
"writers": "Yazarlar",
|
||||
"analysts": "Analistler",
|
||||
"entrepreneurs": "Girişimciler",
|
||||
"researchers": "Araştırmacılar",
|
||||
"students": "Öğrenciler",
|
||||
"consultants": "Danışmanlar",
|
||||
"engineers": "Mühendisler",
|
||||
"creators": "İçerik Üreticileri",
|
||||
"lawyers": "Avukatlar",
|
||||
"doctors": "Doktorlar",
|
||||
"nurses": "Hemşireler",
|
||||
"accountants": "Muhasebeciler",
|
||||
"salespeople": "Satışçılar",
|
||||
"recruiters": "İK Uzmanları",
|
||||
"managers": "Yöneticiler",
|
||||
"executives": "Üst Yöneticiler",
|
||||
"freelancers": "Serbest Çalışanlar",
|
||||
"photographers": "Fotoğrafçılar",
|
||||
"musicians": "Müzisyenler",
|
||||
"artists": "Sanatçılar",
|
||||
"architects": "Mimarlar",
|
||||
"scientists": "Bilim İnsanları",
|
||||
"journalists": "Gazeteciler",
|
||||
"editors": "Editörler",
|
||||
"translators": "Çevirmenler",
|
||||
"coaches": "Koçlar",
|
||||
"therapists": "Terapistler",
|
||||
"trainers": "Eğitmenler",
|
||||
"chefs": "Şefler",
|
||||
"realtors": "Emlakçılar",
|
||||
"investors": "Yatırımcılar",
|
||||
"traders": "Tüccarlar"
|
||||
"videoTitle": "GitHub Open Source Friday"
|
||||
},
|
||||
"notFound": {
|
||||
"title": "Sayfa Bulunamadı",
|
||||
@@ -1483,6 +1452,7 @@
|
||||
"supportNow": "Şimdi Destekle",
|
||||
"techStackTitle": "Teknoloji Evrimi",
|
||||
"coreContributorsTitle": "Çekirdek Katkıda Bulunanlar",
|
||||
"securityTitle": "Güvenlik",
|
||||
"designCreditsTitle": "Tasarım",
|
||||
"ideationTitle": "Fikir",
|
||||
"communityContributorsTitle": "Topluluk Katkıda Bulunanları",
|
||||
@@ -2270,5 +2240,12 @@
|
||||
"submit": "Gönder",
|
||||
"communityExamples": "Topluluk Örnekleri",
|
||||
"userExample": "Kullanıcı örneği"
|
||||
},
|
||||
"selfHosting": {
|
||||
"prerequisites": {
|
||||
"nodeJs": "Node.js 24.x",
|
||||
"postgresql": "PostgreSQL database",
|
||||
"npm": "npm"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+17
-40
@@ -27,6 +27,7 @@
|
||||
"close": "关闭",
|
||||
"all": "全部",
|
||||
"none": "无",
|
||||
"ad": "广告",
|
||||
"moreLines": "+{count}行",
|
||||
"codeView": "代码",
|
||||
"treeView": "树形视图",
|
||||
@@ -39,6 +40,7 @@
|
||||
"promptmasters": "Promptmasters",
|
||||
"prompts": "提示词",
|
||||
"skills": "技能",
|
||||
"taste": "Taste",
|
||||
"workflows": "工作流程",
|
||||
"categories": "分类",
|
||||
"tags": "标签",
|
||||
@@ -107,7 +109,9 @@
|
||||
"title": "提示词",
|
||||
"create": "创建提示词",
|
||||
"createSkill": "创建技能",
|
||||
"createTaste": "创建 Taste",
|
||||
"skillsDescription": "智能体技能是多文件提示词,可为AI智能体提供专业能力。它们包含指令、配置和支持文件,可与Claude、Cursor、Windsurf及其他AI编程助手配合使用。",
|
||||
"tastesDescription": "Tastes 是定义你编码风格和偏好的单个 markdown 文件。它们帮助 AI 编码代理学习你的代码编写方式,从而匹配你的惯例、模式和品味。",
|
||||
"createInfo": "本平台不运行或执行提示词——这是一个社区驱动的AI提示词分享和发现库。在这里创建您的提示词,其他人可以复制并在他们喜欢的AI工具中使用,如ChatGPT、Claude、Gemini或任何其他LLM。社区还可以评论您的提示词,并通过变更请求提出改进建议。",
|
||||
"hfDataStudio": {
|
||||
"button": "HF 数据工作室",
|
||||
@@ -189,7 +193,8 @@
|
||||
"audio": "音频",
|
||||
"structured": "结构化",
|
||||
"document": "文档",
|
||||
"skill": "技能"
|
||||
"skill": "技能",
|
||||
"taste": "Taste"
|
||||
},
|
||||
"inputType": "用户提示词",
|
||||
"outputType": "AI 响应",
|
||||
@@ -199,7 +204,8 @@
|
||||
"inputTypes": {
|
||||
"text": "文本提示词",
|
||||
"structured": "结构化 (JSON/YAML)",
|
||||
"skill": "技能 (Agent Skills)"
|
||||
"skill": "技能 (Agent Skills)",
|
||||
"taste": "Taste (Coding Taste)"
|
||||
},
|
||||
"outputTypes": {
|
||||
"text": "文本",
|
||||
@@ -1024,45 +1030,8 @@
|
||||
}
|
||||
},
|
||||
"heroIndustries": {
|
||||
"prefix": "或按行业探索",
|
||||
"clickToExplore": "点击探索",
|
||||
"searchPlaceholder": "搜索提示词...",
|
||||
"teachers": "教师",
|
||||
"developers": "开发者",
|
||||
"marketers": "营销人员",
|
||||
"designers": "设计师",
|
||||
"writers": "作家",
|
||||
"analysts": "分析师",
|
||||
"entrepreneurs": "创业者",
|
||||
"researchers": "研究员",
|
||||
"students": "学生",
|
||||
"consultants": "顾问",
|
||||
"engineers": "工程师",
|
||||
"creators": "创作者",
|
||||
"lawyers": "律师",
|
||||
"doctors": "医生",
|
||||
"nurses": "护士",
|
||||
"accountants": "会计",
|
||||
"salespeople": "销售",
|
||||
"recruiters": "招聘官",
|
||||
"managers": "经理",
|
||||
"executives": "高管",
|
||||
"freelancers": "自由职业者",
|
||||
"photographers": "摄影师",
|
||||
"musicians": "音乐家",
|
||||
"artists": "艺术家",
|
||||
"architects": "建筑师",
|
||||
"scientists": "科学家",
|
||||
"journalists": "记者",
|
||||
"editors": "编辑",
|
||||
"translators": "翻译",
|
||||
"coaches": "教练",
|
||||
"therapists": "治疗师",
|
||||
"trainers": "培训师",
|
||||
"chefs": "厨师",
|
||||
"realtors": "房产经纪",
|
||||
"investors": "投资者",
|
||||
"traders": "交易员"
|
||||
"videoTitle": "GitHub Open Source Friday"
|
||||
},
|
||||
"notFound": {
|
||||
"title": "页面未找到",
|
||||
@@ -1483,6 +1452,7 @@
|
||||
"supportNow": "立即支持",
|
||||
"techStackTitle": "技术演进",
|
||||
"coreContributorsTitle": "核心贡献者",
|
||||
"securityTitle": "安全",
|
||||
"designCreditsTitle": "设计",
|
||||
"ideationTitle": "创意",
|
||||
"communityContributorsTitle": "社区贡献者",
|
||||
@@ -2259,5 +2229,12 @@
|
||||
"submit": "提交",
|
||||
"communityExamples": "社区示例",
|
||||
"userExample": "用户示例"
|
||||
},
|
||||
"selfHosting": {
|
||||
"prerequisites": {
|
||||
"nodeJs": "Node.js 24.x",
|
||||
"postgresql": "PostgreSQL database",
|
||||
"npm": "npm"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4
-5
@@ -82,11 +82,10 @@ export default withSentryConfig(withMDX(withNextIntl(nextConfig)), {
|
||||
// Upload a larger set of source maps for prettier stack traces (increases build time)
|
||||
widenClientFileUpload: true,
|
||||
|
||||
// Route browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers.
|
||||
// This can increase your server load as well as your hosting bill.
|
||||
// Note: Check that the configured route will not match with your Next.js middleware, otherwise reporting of client-
|
||||
// side errors will fail.
|
||||
tunnelRoute: "/monitoring",
|
||||
// tunnelRoute removed — was proxying all browser Sentry events through Vercel edge,
|
||||
// generating unnecessary edge requests ($2.45/M) and function invocations ($0.60/M).
|
||||
// Estimated savings: $100-400/month. Trade-off: some ad-blockers may block direct Sentry calls.
|
||||
// See: https://github.com/f/prompts.chat/issues/1085
|
||||
|
||||
webpack: {
|
||||
// Enables automatic instrumentation of Vercel Cron Monitors. (Does not yet work with App Router route handlers.)
|
||||
|
||||
@@ -2375,7 +2375,7 @@ import type {
|
||||
|
||||
## Requirements
|
||||
|
||||
- **Node.js** 18+
|
||||
- **Node.js** 24.x
|
||||
- **TypeScript** 5+ (optional, for type checking)
|
||||
|
||||
---
|
||||
|
||||
@@ -2,7 +2,7 @@ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
||||
import { homedir } from 'os';
|
||||
import { join } from 'path';
|
||||
|
||||
const PROMPTS_URL = 'https://prompts.chat/prompts.json';
|
||||
const PROMPTS_URL = 'https://prompts.chat/prompts.json?full_content=true';
|
||||
const CACHE_DIR = join(homedir(), '.prompts-chat');
|
||||
const CACHE_FILE = join(CACHE_DIR, 'prompts.json');
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ export function PromptDetail({ prompt, onBack, onCopy }: PromptDetailProps) {
|
||||
const contentHeight = Math.max(terminalHeight - headerLines - footerLines, 5);
|
||||
|
||||
useEffect(() => {
|
||||
const vars = extractVariables(prompt.content);
|
||||
const vars = extractVariables(prompt.content ?? '');
|
||||
setVariables(vars);
|
||||
|
||||
const defaults: Record<string, string> = {};
|
||||
@@ -90,7 +90,7 @@ export function PromptDetail({ prompt, onBack, onCopy }: PromptDetailProps) {
|
||||
const contentLines = useMemo(() => {
|
||||
if (!prompt) return [];
|
||||
// Parse escape sequences like \n
|
||||
const parsedContent = prompt.content.replace(/\\n/g, '\n');
|
||||
const parsedContent = (prompt.content ?? '').replace(/\\n/g, '\n');
|
||||
return wrapText(parsedContent, terminalWidth - 6);
|
||||
}, [prompt, terminalWidth]);
|
||||
|
||||
@@ -200,12 +200,12 @@ export function PromptDetail({ prompt, onBack, onCopy }: PromptDetailProps) {
|
||||
setCurrentVarIndex(0);
|
||||
setCurrentInput(variableValues[variables[0].name] || '');
|
||||
} else {
|
||||
handleCopy(prompt.content);
|
||||
handleCopy(prompt.content ?? '');
|
||||
}
|
||||
}
|
||||
|
||||
if (input === 'C' && prompt) {
|
||||
handleCopy(prompt.content);
|
||||
handleCopy(prompt.content ?? '');
|
||||
}
|
||||
|
||||
if (input === 'o' && prompt) {
|
||||
|
||||
@@ -9,18 +9,18 @@ export interface Platform {
|
||||
|
||||
// Image generation platforms (Mitte.ai)
|
||||
export const imagePlatforms: Platform[] = [
|
||||
{ id: "mitte-nano-banana", name: "Mitte.ai - Nano Banana", baseUrl: "https://mitte.ai?model=nano-banana", sponsor: true },
|
||||
{ id: "mitte-nano-banana-pro", name: "Mitte.ai - Nano Banana Pro", baseUrl: "https://mitte.ai?model=nano-banana-pro", sponsor: true },
|
||||
{ id: "mitte-flux-2-flex", name: "Mitte.ai - Flux 2 Flex", baseUrl: "https://mitte.ai?model=flux-2-flex", sponsor: true },
|
||||
{ id: "mitte-flux-2", name: "Mitte.ai - Flux 2", baseUrl: "https://mitte.ai?model=flux-2", sponsor: true },
|
||||
{ id: "mitte-nano-banana", name: "Mitte.ai - Nano Banana", baseUrl: "https://mitte.ai?model=nano-banana", sponsor: false },
|
||||
{ id: "mitte-nano-banana-pro", name: "Mitte.ai - Nano Banana Pro", baseUrl: "https://mitte.ai?model=nano-banana-pro", sponsor: false },
|
||||
{ id: "mitte-flux-2-flex", name: "Mitte.ai - Flux 2 Flex", baseUrl: "https://mitte.ai?model=flux-2-flex", sponsor: false },
|
||||
{ id: "mitte-flux-2", name: "Mitte.ai - Flux 2", baseUrl: "https://mitte.ai?model=flux-2", sponsor: false },
|
||||
];
|
||||
|
||||
// Video generation platforms (Mitte.ai)
|
||||
export const videoPlatforms: Platform[] = [
|
||||
{ id: "mitte-veo-31", name: "Mitte.ai - Veo 3.1", baseUrl: "https://mitte.ai?model=veo-31", sponsor: true },
|
||||
{ id: "mitte-kling-26", name: "Mitte.ai - Kling 2.6", baseUrl: "https://mitte.ai?model=kling-26", sponsor: true },
|
||||
{ id: "mitte-sora-2", name: "Mitte.ai - Sora 2", baseUrl: "https://mitte.ai?model=sora-2", sponsor: true },
|
||||
{ id: "mitte-remotion", name: "Mitte.ai - Remotion", baseUrl: "https://mitte.ai?model=remotion", sponsor: true },
|
||||
{ id: "mitte-veo-31", name: "Mitte.ai - Veo 3.1", baseUrl: "https://mitte.ai?model=veo-31", sponsor: false },
|
||||
{ id: "mitte-kling-26", name: "Mitte.ai - Kling 2.6", baseUrl: "https://mitte.ai?model=kling-26", sponsor: false },
|
||||
{ id: "mitte-sora-2", name: "Mitte.ai - Sora 2", baseUrl: "https://mitte.ai?model=sora-2", sponsor: false },
|
||||
{ id: "mitte-remotion", name: "Mitte.ai - Remotion", baseUrl: "https://mitte.ai?model=remotion", sponsor: false },
|
||||
];
|
||||
|
||||
export const codePlatforms: Platform[] = [
|
||||
@@ -31,6 +31,7 @@ export const codePlatforms: Platform[] = [
|
||||
{ id: "goose", name: "Goose", baseUrl: "goose://recipe", isDeeplink: true },
|
||||
{ id: "github-copilot", name: "GitHub Copilot Chat", baseUrl: "https://github.com/copilot" },
|
||||
{ id: "github-copilot-agents", name: "GitHub Copilot Agents", baseUrl: "https://github.com/copilot/agents" },
|
||||
{ id: "netlify", name: "Netlify", baseUrl: "https://app.netlify.com/run" },
|
||||
{ id: "bolt", name: "Bolt", baseUrl: "https://bolt.new" },
|
||||
{ id: "lovable", name: "Lovable", baseUrl: "https://lovable.dev" },
|
||||
{ id: "v0", name: "v0", baseUrl: "https://v0.dev/chat" },
|
||||
@@ -103,6 +104,8 @@ export function buildUrl(
|
||||
return `${baseUrl}/?prompt=${encoded}`;
|
||||
case "lovable":
|
||||
return `${baseUrl}/?autosubmit=true#prompt=${encoded}`;
|
||||
case "netlify":
|
||||
return `${baseUrl}?prompt=${encoded}&ref=prompts-chat`;
|
||||
case "mistral":
|
||||
return `${baseUrl}?q=${encoded}`;
|
||||
case "perplexity":
|
||||
|
||||
@@ -192,6 +192,12 @@ export const codePlatforms: Platform[] = [
|
||||
baseUrl: "https://github.com/copilot",
|
||||
supportsQuerystring: true,
|
||||
},
|
||||
{
|
||||
id: "netlify",
|
||||
name: "Netlify",
|
||||
baseUrl: "https://app.netlify.com/run",
|
||||
supportsQuerystring: true,
|
||||
},
|
||||
{
|
||||
id: "bolt",
|
||||
name: "Bolt",
|
||||
@@ -219,24 +225,10 @@ export const codePlatforms: Platform[] = [
|
||||
];
|
||||
|
||||
// Image generation platforms
|
||||
export const imagePlatforms: Platform[] = [
|
||||
{
|
||||
id: "mitte-image",
|
||||
name: "Mitte.ai (Image)",
|
||||
baseUrl: "https://mitte.ai",
|
||||
supportsQuerystring: true,
|
||||
},
|
||||
];
|
||||
export const imagePlatforms: Platform[] = [];
|
||||
|
||||
// Video generation platforms
|
||||
export const videoPlatforms: Platform[] = [
|
||||
{
|
||||
id: "mitte-video",
|
||||
name: "Mitte.ai (Video)",
|
||||
baseUrl: "https://mitte.ai",
|
||||
supportsQuerystring: true,
|
||||
},
|
||||
];
|
||||
export const videoPlatforms: Platform[] = [];
|
||||
|
||||
export function buildUrl(
|
||||
platformId: string,
|
||||
@@ -292,6 +284,8 @@ export function buildUrl(
|
||||
return `${baseUrl}/?prompt=${encoded}`;
|
||||
case "lovable":
|
||||
return `${baseUrl}/?autosubmit=true#prompt=${encoded}`;
|
||||
case "netlify":
|
||||
return `${baseUrl}?prompt=${encoded}&ref=prompts-chat`;
|
||||
case "mistral":
|
||||
return `${baseUrl}?q=${encoded}`;
|
||||
case "perplexity":
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
-- AlterEnum
|
||||
ALTER TYPE "PromptType" ADD VALUE 'TASTE';
|
||||
@@ -0,0 +1,4 @@
|
||||
-- Add case-insensitive unique indexes to prevent TOCTOU race conditions
|
||||
-- These ensure the database enforces uniqueness regardless of casing
|
||||
CREATE UNIQUE INDEX "users_username_ci_unique" ON "users" (LOWER("username"));
|
||||
CREATE UNIQUE INDEX "users_email_ci_unique" ON "users" (LOWER("email"));
|
||||
@@ -139,6 +139,8 @@ model Prompt {
|
||||
@@index([isPrivate])
|
||||
@@index([isFeatured])
|
||||
@@index([isUnlisted])
|
||||
@@index([slug])
|
||||
@@index([isPrivate, isUnlisted, deletedAt, createdAt(sort: Desc)])
|
||||
@@map("prompts")
|
||||
}
|
||||
|
||||
@@ -409,6 +411,7 @@ enum PromptType {
|
||||
AUDIO
|
||||
STRUCTURED
|
||||
SKILL
|
||||
TASTE
|
||||
}
|
||||
|
||||
enum StructuredFormat {
|
||||
|
||||
+36
-8
@@ -3,14 +3,15 @@ import bcrypt from "bcryptjs";
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
const PROMPTS_JSON_URL = "https://prompts.chat/prompts.json";
|
||||
const PROMPTS_JSON_URL = "https://prompts.chat/prompts.json?full_content=true";
|
||||
|
||||
interface RemotePrompt {
|
||||
/** Raw shape returned by the remote API — content may be absent. */
|
||||
interface RemotePromptRaw {
|
||||
id: string;
|
||||
title: string;
|
||||
slug: string;
|
||||
description: string | null;
|
||||
content: string;
|
||||
content?: string;
|
||||
type: string;
|
||||
structuredFormat: string | null;
|
||||
mediaUrl: string | null;
|
||||
@@ -52,11 +53,20 @@ interface RemotePrompt {
|
||||
}>;
|
||||
}
|
||||
|
||||
interface RemotePromptsResponse {
|
||||
count: number;
|
||||
prompts: RemotePrompt[];
|
||||
/** Validated prompt with required content field. */
|
||||
type RemotePrompt = RemotePromptRaw & { content: string };
|
||||
|
||||
/** Type guard that narrows a raw API prompt to a validated RemotePrompt. */
|
||||
function hasContent(prompt: RemotePromptRaw): prompt is RemotePrompt {
|
||||
return typeof prompt.content === "string" && prompt.content.length > 0;
|
||||
}
|
||||
|
||||
interface RemotePromptsResponse {
|
||||
count: number;
|
||||
prompts: RemotePromptRaw[];
|
||||
}
|
||||
|
||||
/** Fetches all prompts from the remote prompts.chat API with full content. */
|
||||
async function fetchPrompts(): Promise<RemotePromptsResponse> {
|
||||
console.log(`📡 Fetching prompts from ${PROMPTS_JSON_URL}...`);
|
||||
const response = await fetch(PROMPTS_JSON_URL);
|
||||
@@ -68,12 +78,13 @@ async function fetchPrompts(): Promise<RemotePromptsResponse> {
|
||||
return data;
|
||||
}
|
||||
|
||||
/** Seeds the database with users, categories, tags, and prompts from prompts.chat. */
|
||||
async function main() {
|
||||
console.log("🌱 Seeding database from prompts.chat...");
|
||||
|
||||
// Create admin user for assigning prompts
|
||||
const password = await bcrypt.hash("password123", 12);
|
||||
|
||||
|
||||
const admin = await prisma.user.upsert({
|
||||
where: { email: "admin@prompts.chat" },
|
||||
update: {},
|
||||
@@ -174,7 +185,7 @@ async function main() {
|
||||
userIdMap.set(username, admin.id);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
const user = await prisma.user.upsert({
|
||||
where: { username },
|
||||
update: { name: author.name, avatar: author.avatar },
|
||||
@@ -225,6 +236,13 @@ async function main() {
|
||||
};
|
||||
const structuredFormat = remotePrompt.structuredFormat ? formatMap[remotePrompt.structuredFormat] : null;
|
||||
|
||||
// Skip prompts without content
|
||||
if (!hasContent(remotePrompt)) {
|
||||
console.warn(`⚠ Skipping prompt "${remotePrompt.title}" - no content`);
|
||||
promptsSkipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if prompt already exists
|
||||
const existingPrompt = await prisma.prompt.findFirst({
|
||||
where: { slug: remotePrompt.slug },
|
||||
@@ -280,6 +298,16 @@ async function main() {
|
||||
}
|
||||
|
||||
console.log(`✅ Created ${promptsCreated} prompts (${promptsSkipped} skipped)`);
|
||||
|
||||
// Fail-fast if no prompts were created — likely an upstream API regression
|
||||
const total = promptsCreated + promptsSkipped;
|
||||
if (total > 0 && promptsCreated === 0) {
|
||||
throw new Error(
|
||||
`Seeding failed: 0 prompts created out of ${total} (${promptsSkipped} skipped). ` +
|
||||
`This likely indicates an upstream API regression — check that ${PROMPTS_JSON_URL} returns content.`
|
||||
);
|
||||
}
|
||||
|
||||
console.log("\n🎉 Seeding complete!");
|
||||
console.log("\n📋 Test credentials (password: password123):");
|
||||
console.log(" Admin: admin@prompts.chat");
|
||||
|
||||
+3
-3
@@ -33,7 +33,7 @@ export default defineConfig({
|
||||
|
||||
// Authentication plugins
|
||||
auth: {
|
||||
// Available: "credentials" | "google" | "azure" | "github" | "apple" | custom
|
||||
// Available: "credentials" | "google" | "azure" | "github" | "apple" | "oidc" | "oauth" | custom
|
||||
// Use `providers` array to enable multiple auth providers
|
||||
providers: ["github", "google", "apple"],
|
||||
// Allow public registration (only applies to credentials provider)
|
||||
@@ -82,9 +82,9 @@ export default defineConfig({
|
||||
{ name: "Cognition", logo: "/sponsors/cognition.svg", url: "https://wind.surf/prompts-chat" },
|
||||
{ name: "CodeRabbit", className: 'py-1', logo: '/sponsors/coderabbit.svg', darkLogo: '/sponsors/coderabbit-dark.svg', url: "https://coderabbit.link/fatih" },
|
||||
{ name: "Sentry", className: 'py-1', logo: '/sponsors/sentry.svg', darkLogo: '/sponsors/sentry-dark.svg', url: "https://sentry.io/?utm_source=prompts.chat" },
|
||||
{ name: "MitteAI", logo: '/sponsors/mitte.svg', darkLogo: '/sponsors/mitte-dark.svg', url: "https://mitte.ai/?utm_source=prompts.chat" },
|
||||
|
||||
{ name: "eachlabs", className: 'py-[6px]', logo: '/sponsors/eachlabs.png', darkLogo: '/sponsors/eachlabs-dark.png', url: "https://www.eachlabs.ai/?utm_source=promptschat&utm_medium=referral" },
|
||||
{ name: "warp.dev", className: 'py-2', logo: '/sponsors/warp.svg', url: "https://warp.dev/?utm_source=prompts.chat" },
|
||||
{ name: "CommandCode", className: 'py-1', logo: '/sponsors/commandcode.svg', darkLogo: '/sponsors/commandcode-dark.svg', url: "https://commandcode.ai/?utm_source=prompts.chat" },
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
+44376
-1971
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,16 @@
|
||||
<svg width="1210" height="224" viewBox="0 0 1210 224" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 110C0 58.1455 0 32.2183 16.1091 16.1091C32.2183 0 58.1455 0 110 0H1099.25C1151.11 0 1177.03 0 1193.14 16.1091C1209.25 32.2183 1209.25 58.1455 1209.25 110V113.6C1209.25 165.455 1209.25 191.382 1193.14 207.491C1177.03 223.6 1151.11 223.6 1099.25 223.6H110C58.1454 223.6 32.2183 223.6 16.1091 207.491C0 191.382 0 165.455 0 113.6V110Z" fill="white"/>
|
||||
<path d="M40 110C40 77.0017 40 60.5025 50.2513 50.2513C60.5025 40 77.0017 40 110 40H113.5C146.498 40 162.997 40 173.249 50.2513C183.5 60.5025 183.5 77.0017 183.5 110V113.6C183.5 146.598 183.5 163.097 173.249 173.349C162.997 183.6 146.498 183.6 113.5 183.6H110C77.0017 183.6 60.5025 183.6 50.2513 173.349C40 163.097 40 146.598 40 113.6V110Z" fill="black"/>
|
||||
<path d="M138.806 67.6172C129.33 67.6172 121.622 75.3252 121.622 84.8007V92.165H101.984V84.8007C101.984 75.3252 94.2761 67.6172 84.8006 67.6172C75.3252 67.6172 67.6172 75.3252 67.6172 84.8007C67.6172 94.2761 75.3252 101.984 84.8006 101.984H92.165V121.622H84.8006C75.3252 121.622 67.6172 129.33 67.6172 138.806C67.6172 148.281 75.3252 155.989 84.8006 155.989C94.2761 155.989 101.984 148.281 101.984 138.806V131.441H121.622V138.806C121.622 148.281 129.33 155.989 138.806 155.989C148.281 155.989 155.989 148.281 155.989 138.806C155.989 129.33 148.281 121.622 138.806 121.622H131.441V101.984H138.806C148.281 101.984 155.989 94.2761 155.989 84.8007C155.989 75.3252 148.281 67.6172 138.806 67.6172ZM131.441 92.165V84.8007C131.441 80.7257 134.731 77.4363 138.806 77.4363C142.881 77.4363 146.17 80.7257 146.17 84.8007C146.17 88.8756 142.881 92.165 138.806 92.165H131.441ZM84.8006 92.165C80.7257 92.165 77.4363 88.8756 77.4363 84.8007C77.4363 80.7257 80.7257 77.4363 84.8006 77.4363C88.8756 77.4363 92.165 80.7257 92.165 84.8007V92.165H84.8006ZM101.984 121.622V101.984H121.622V121.622H101.984ZM138.806 146.17C134.731 146.17 131.441 142.881 131.441 138.806V131.441H138.806C142.881 131.441 146.17 134.731 146.17 138.806C146.17 142.881 142.881 146.17 138.806 146.17ZM84.8006 146.17C80.7257 146.17 77.4363 142.881 77.4363 138.806C77.4363 134.731 80.7257 131.441 84.8006 131.441H92.165V138.806C92.165 142.881 88.8756 146.17 84.8006 146.17Z" fill="white"/>
|
||||
<path d="M1133.6 163.923C1110.88 163.923 1096.39 148.3 1096.39 124.297C1096.39 100.437 1110.88 84.6719 1133.18 84.6719C1154.91 84.6719 1169.25 99.7267 1169.25 125.291V129.41H1115.28C1115.99 142.761 1122.95 149.436 1133.75 149.436C1141.84 149.436 1147.38 145.601 1149.51 138.926L1168.12 140.062C1164 154.833 1151.07 163.923 1133.6 163.923ZM1150.36 117.196C1149.51 104.84 1142.84 99.0166 1133.18 99.0166C1123.38 99.0166 1116.7 105.408 1115.28 117.196H1150.36Z" fill="black"/>
|
||||
<path d="M1048.92 163.922C1029.46 163.922 1017.39 148.583 1017.39 124.297C1017.39 100.01 1029.75 84.6713 1048.92 84.6713C1059.43 84.6713 1067.81 89.3581 1071.93 97.1696V61.3789H1090.11V162.218H1072.92L1072.5 150.998C1068.24 159.093 1059.43 163.922 1048.92 163.922ZM1054.32 149.151C1065.54 149.151 1071.93 140.346 1071.93 124.297C1071.93 108.106 1065.68 99.442 1054.32 99.442C1043.24 99.442 1036.28 108.816 1036.28 124.297C1036.28 139.494 1043.38 149.151 1054.32 149.151Z" fill="black"/>
|
||||
<path d="M977.746 163.923C955.163 163.923 940.393 148.3 940.393 124.297C940.393 100.295 955.163 84.6719 977.746 84.6719C1000.19 84.6719 1014.96 100.295 1014.96 124.297C1014.96 148.3 1000.19 163.923 977.746 163.923ZM977.746 149.152C989.392 149.152 996.209 140.062 996.209 124.297C996.209 108.674 989.392 99.4426 977.746 99.4426C965.957 99.4426 959.282 108.674 959.282 124.297C959.282 140.062 965.957 149.152 977.746 149.152Z" fill="black"/>
|
||||
<path d="M894.119 164.491C866.708 164.491 847.393 144.891 847.393 111.941C847.393 79.5592 865.998 59.1074 894.403 59.1074C920.11 59.1074 933.887 72.5999 938.148 95.4662L918.832 96.4604C916.418 83.2519 908.322 75.1564 894.403 75.1564C877.218 75.1564 866.566 89.5011 866.566 111.941C866.566 134.666 877.502 148.442 894.261 148.442C909.174 148.442 917.128 139.779 919.4 125.292L938.716 126.286C934.739 150.004 919.684 164.491 894.119 164.491Z" fill="black"/>
|
||||
<path d="M799.922 163.922C780.465 163.922 768.393 148.583 768.393 124.297C768.393 100.01 780.749 84.6713 799.922 84.6713C810.432 84.6713 818.812 89.3581 822.931 97.1696V61.3789H841.11V162.218H823.925L823.499 150.998C819.238 159.093 810.432 163.922 799.922 163.922ZM805.319 149.151C816.54 149.151 822.931 140.346 822.931 124.297C822.931 108.106 816.682 99.442 805.319 99.442C794.241 99.442 787.282 108.816 787.282 124.297C787.282 139.494 794.383 149.151 805.319 149.151Z" fill="black"/>
|
||||
<path d="M698.393 162.218V86.3762H714.868L715.294 99.4426C719.271 88.9327 727.934 84.6719 738.018 84.6719C754.777 84.6719 763.867 96.4601 763.867 113.503V162.218H745.687V119.326C745.687 106.544 742.279 99.4426 732.479 99.4426C722.679 99.4426 716.572 106.544 716.572 119.326V162.218H698.393Z" fill="black"/>
|
||||
<path d="M644.81 163.923C628.903 163.923 618.393 156.395 618.393 143.187C618.393 130.12 626.488 123.161 643.105 119.894L668.244 114.924C668.244 104.272 663.273 98.7325 653.757 98.7325C644.81 98.7325 639.981 102.851 638.276 110.521L619.671 109.669C622.653 93.7616 634.868 84.6719 653.757 84.6719C675.345 84.6719 686.423 96.034 686.423 116.202V143.755C686.423 147.874 687.844 149.01 690.684 149.01H693.099V162.218C691.962 162.502 688.98 162.786 686.565 162.786C678.328 162.786 672.221 159.804 670.658 150.43C666.966 158.526 657.308 163.923 644.81 163.923ZM648.502 150.714C660.574 150.714 668.244 143.045 668.244 131.541V127.28L648.644 131.257C640.549 132.819 637.282 136.228 637.282 141.483C637.282 147.448 641.259 150.714 648.502 150.714Z" fill="black"/>
|
||||
<path d="M506.393 162.218V86.3762H522.868L523.294 99.1586C526.986 89.9269 534.656 84.6719 544.172 84.6719C555.25 84.6719 562.919 90.495 566.186 100.295C569.594 90.2109 577.406 84.6719 587.916 84.6719C603.539 84.6719 613.197 94.7558 613.197 113.503V162.218H595.017V118.19C595.017 105.692 590.898 99.4426 582.093 99.4426C573.429 99.4426 568.174 106.544 568.174 118.758V162.218H551.273V118.758C551.273 106.402 547.864 99.4426 538.633 99.4426C529.969 99.4426 524.572 106.686 524.572 118.758V162.218H506.393Z" fill="black"/>
|
||||
<path d="M391.393 162.218V86.3762H407.868L408.294 99.1586C411.986 89.9269 419.656 84.6719 429.172 84.6719C440.25 84.6719 447.919 90.495 451.186 100.295C454.594 90.2109 462.406 84.6719 472.916 84.6719C488.539 84.6719 498.197 94.7558 498.197 113.503V162.218H480.017V118.19C480.017 105.692 475.898 99.4426 467.093 99.4426C458.429 99.4426 453.174 106.544 453.174 118.758V162.218H436.273V118.758C436.273 106.402 432.864 99.4426 423.633 99.4426C414.969 99.4426 409.572 106.686 409.572 118.758V162.218H391.393Z" fill="black"/>
|
||||
<path d="M347.746 163.923C325.163 163.923 310.393 148.3 310.393 124.297C310.393 100.295 325.163 84.6719 347.746 84.6719C370.186 84.6719 384.957 100.295 384.957 124.297C384.957 148.3 370.186 163.923 347.746 163.923ZM347.746 149.152C359.392 149.152 366.209 140.062 366.209 124.297C366.209 108.674 359.392 99.4426 347.746 99.4426C335.957 99.4426 329.282 108.674 329.282 124.297C329.282 140.062 335.957 149.152 347.746 149.152Z" fill="black"/>
|
||||
<path d="M264.227 164.491C236.816 164.491 217.5 144.891 217.5 111.941C217.5 79.5592 236.105 59.1074 264.511 59.1074C290.218 59.1074 303.994 72.5999 308.255 95.4662L288.939 96.4604C286.525 83.2519 278.429 75.1564 264.511 75.1564C247.326 75.1564 236.674 89.5011 236.674 111.941C236.674 134.666 247.61 148.442 264.369 148.442C279.282 148.442 287.235 139.779 289.507 125.292L308.823 126.286C304.846 150.004 289.792 164.491 264.227 164.491Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.4 KiB |
@@ -0,0 +1,16 @@
|
||||
<svg width="1210" height="224" viewBox="0 0 1210 224" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 110C0 58.1455 0 32.2183 16.1091 16.1091C32.2183 0 58.1455 0 110 0H1099.36C1151.21 0 1177.14 0 1193.25 16.1091C1209.36 32.2183 1209.36 58.1455 1209.36 110V113.605C1209.36 165.459 1209.36 191.386 1193.25 207.496C1177.14 223.605 1151.21 223.605 1099.36 223.605H110C58.1454 223.605 32.2183 223.605 16.1091 207.496C0 191.386 0 165.459 0 113.605V110Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M113.14 45.8721H110.465C93.6901 45.8721 81.6998 45.8845 72.5877 47.1096C63.6446 48.312 58.3583 50.5847 54.4715 54.4715C50.5847 58.3583 48.312 63.6446 47.1096 72.5877C45.8845 81.6998 45.8721 93.6901 45.8721 110.465V113.14C45.8721 129.915 45.8845 141.905 47.1096 151.017C48.312 159.96 50.5847 165.246 54.4715 169.133C58.3583 173.02 63.6446 175.293 72.5877 176.495C81.6998 177.72 93.6901 177.733 110.465 177.733H113.14C129.915 177.733 141.905 177.72 151.017 176.495C159.96 175.293 165.246 173.02 169.133 169.133C173.02 165.246 175.293 159.96 176.495 151.017C177.72 141.905 177.733 129.915 177.733 113.14V110.465C177.733 93.6901 177.72 81.6998 176.495 72.5877C175.293 63.6446 173.02 58.3583 169.133 54.4715C165.246 50.5847 159.96 48.312 151.017 47.1096C141.905 45.8845 129.915 45.8721 113.14 45.8721ZM50.3193 50.3193C40 60.6387 40 77.2474 40 110.465V113.14C40 146.357 40 162.966 50.3193 173.285C60.6387 183.605 77.2474 183.605 110.465 183.605H113.14C146.357 183.605 162.966 183.605 173.285 173.285C183.605 162.966 183.605 146.357 183.605 113.14V110.465C183.605 77.2474 183.605 60.6387 173.285 50.3193C162.966 40 146.357 40 113.14 40H110.465C77.2474 40 60.6387 40 50.3193 50.3193Z" fill="white"/>
|
||||
<path d="M138.805 67.6163C129.329 67.6163 121.621 75.3243 121.621 84.7997V92.1641H101.983V84.7997C101.983 75.3243 94.2752 67.6163 84.7997 67.6163C75.3243 67.6163 67.6163 75.3243 67.6163 84.7997C67.6163 94.2752 75.3243 101.983 84.7997 101.983H92.1641V121.621H84.7997C75.3243 121.621 67.6163 129.329 67.6163 138.805C67.6163 148.28 75.3243 155.988 84.7997 155.988C94.2752 155.988 101.983 148.28 101.983 138.805V131.441H121.621V138.805C121.621 148.28 129.329 155.988 138.805 155.988C148.28 155.988 155.988 148.28 155.988 138.805C155.988 129.329 148.28 121.621 138.805 121.621H131.441V101.983H138.805C148.28 101.983 155.988 94.2752 155.988 84.7997C155.988 75.3243 148.28 67.6163 138.805 67.6163ZM131.441 92.1641V84.7997C131.441 80.7248 134.73 77.4354 138.805 77.4354C142.88 77.4354 146.169 80.7248 146.169 84.7997C146.169 88.8747 142.88 92.1641 138.805 92.1641H131.441ZM84.7997 92.1641C80.7248 92.1641 77.4354 88.8747 77.4354 84.7997C77.4354 80.7248 80.7248 77.4354 84.7997 77.4354C88.8747 77.4354 92.1641 80.7248 92.1641 84.7997V92.1641H84.7997ZM101.983 121.621V101.983H121.621V121.621H101.983ZM138.805 146.169C134.73 146.169 131.441 142.88 131.441 138.805V131.441H138.805C142.88 131.441 146.169 134.73 146.169 138.805C146.169 142.88 142.88 146.169 138.805 146.169ZM84.7997 146.169C80.7248 146.169 77.4354 142.88 77.4354 138.805C77.4354 134.73 80.7248 131.441 84.7997 131.441H92.1641V138.805C92.1641 142.88 88.8747 146.169 84.7997 146.169Z" fill="white"/>
|
||||
<path d="M1133.71 163.927C1110.98 163.927 1096.5 148.304 1096.5 124.301C1096.5 100.441 1110.98 84.6758 1133.28 84.6758C1155.01 84.6758 1169.36 99.7306 1169.36 125.295V129.414H1115.39C1116.1 142.765 1123.06 149.44 1133.85 149.44C1141.95 149.44 1147.49 145.605 1149.62 138.93L1168.22 140.066C1164.1 154.837 1151.18 163.927 1133.71 163.927ZM1150.47 117.2C1149.62 104.844 1142.94 99.0205 1133.28 99.0205C1123.48 99.0205 1116.81 105.412 1115.39 117.2H1150.47Z" fill="white"/>
|
||||
<path d="M1049.03 163.926C1029.57 163.926 1017.5 148.587 1017.5 124.301C1017.5 100.014 1029.85 84.6752 1049.03 84.6752C1059.54 84.6752 1067.92 89.3621 1072.04 97.1735V61.3828H1090.22V162.222H1073.03L1072.6 151.002C1068.34 159.097 1059.54 163.926 1049.03 163.926ZM1054.42 149.155C1065.65 149.155 1072.04 140.35 1072.04 124.301C1072.04 108.11 1065.79 99.4459 1054.42 99.4459C1043.35 99.4459 1036.39 108.82 1036.39 124.301C1036.39 139.497 1043.49 149.155 1054.42 149.155Z" fill="white"/>
|
||||
<path d="M977.851 163.927C955.269 163.927 940.498 148.304 940.498 124.301C940.498 100.299 955.269 84.6758 977.851 84.6758C1000.29 84.6758 1015.06 100.299 1015.06 124.301C1015.06 148.304 1000.29 163.927 977.851 163.927ZM977.851 149.156C989.497 149.156 996.314 140.066 996.314 124.301C996.314 108.678 989.497 99.4465 977.851 99.4465C966.063 99.4465 959.388 108.678 959.388 124.301C959.388 140.066 966.063 149.156 977.851 149.156Z" fill="white"/>
|
||||
<path d="M894.225 164.495C866.814 164.495 847.498 144.895 847.498 111.945C847.498 79.5632 866.104 59.1113 894.509 59.1113C920.216 59.1113 933.992 72.6038 938.253 95.4701L918.937 96.4643C916.523 83.2558 908.427 75.1603 894.509 75.1603C877.324 75.1603 866.672 89.505 866.672 111.945C866.672 134.669 877.608 148.446 894.367 148.446C909.28 148.446 917.233 139.782 919.506 125.296L938.821 126.29C934.844 150.008 919.79 164.495 894.225 164.495Z" fill="white"/>
|
||||
<path d="M800.028 163.926C780.57 163.926 768.498 148.587 768.498 124.301C768.498 100.014 780.854 84.6752 800.028 84.6752C810.538 84.6752 818.917 89.3621 823.036 97.1735V61.3828H841.216V162.222H824.03L823.604 151.002C819.344 159.097 810.538 163.926 800.028 163.926ZM805.425 149.155C816.645 149.155 823.036 140.35 823.036 124.301C823.036 108.11 816.787 99.4459 805.425 99.4459C794.347 99.4459 787.388 108.82 787.388 124.301C787.388 139.497 794.489 149.155 805.425 149.155Z" fill="white"/>
|
||||
<path d="M698.498 162.222V86.3801H714.973L715.399 99.4465C719.376 88.9366 728.04 84.6758 738.123 84.6758C754.883 84.6758 763.972 96.464 763.972 113.507V162.222H745.793V119.33C745.793 106.548 742.384 99.4465 732.584 99.4465C722.785 99.4465 716.677 106.548 716.677 119.33V162.222H698.498Z" fill="white"/>
|
||||
<path d="M644.915 163.927C629.008 163.927 618.498 156.399 618.498 143.191C618.498 130.124 626.594 123.165 643.211 119.898L668.349 114.927C668.349 104.275 663.378 98.7364 653.863 98.7364C644.915 98.7364 640.086 102.855 638.382 110.525L619.776 109.672C622.759 93.7655 634.973 84.6758 653.863 84.6758C675.451 84.6758 686.529 96.0379 686.529 116.206V143.759C686.529 147.878 687.949 149.014 690.79 149.014H693.204V162.222C692.068 162.506 689.085 162.79 686.671 162.79C678.433 162.79 672.326 159.808 670.764 150.434C667.071 158.53 657.413 163.927 644.915 163.927ZM648.608 150.718C660.68 150.718 668.349 143.049 668.349 131.545V127.284L648.75 131.26C640.654 132.823 637.388 136.231 637.388 141.486C637.388 147.452 641.364 150.718 648.608 150.718Z" fill="white"/>
|
||||
<path d="M506.498 162.222V86.3801H522.973L523.399 99.1625C527.092 89.9308 534.761 84.6758 544.277 84.6758C555.355 84.6758 563.025 90.4989 566.291 100.299C569.7 90.2148 577.511 84.6758 588.021 84.6758C603.644 84.6758 613.302 94.7597 613.302 113.507V162.222H595.123V118.194C595.123 105.696 591.004 99.4465 582.198 99.4465C573.535 99.4465 568.28 106.548 568.28 118.762V162.222H551.378V118.762C551.378 106.406 547.97 99.4465 538.738 99.4465C530.074 99.4465 524.677 106.69 524.677 118.762V162.222H506.498Z" fill="white"/>
|
||||
<path d="M391.498 162.222V86.3801H407.973L408.399 99.1625C412.092 89.9308 419.761 84.6758 429.277 84.6758C440.355 84.6758 448.025 90.4989 451.291 100.299C454.7 90.2148 462.511 84.6758 473.021 84.6758C488.644 84.6758 498.302 94.7597 498.302 113.507V162.222H480.123V118.194C480.123 105.696 476.004 99.4465 467.198 99.4465C458.535 99.4465 453.28 106.548 453.28 118.762V162.222H436.378V118.762C436.378 106.406 432.97 99.4465 423.738 99.4465C415.074 99.4465 409.677 106.69 409.677 118.762V162.222H391.498Z" fill="white"/>
|
||||
<path d="M347.851 163.927C325.269 163.927 310.498 148.304 310.498 124.301C310.498 100.299 325.269 84.6758 347.851 84.6758C370.291 84.6758 385.062 100.299 385.062 124.301C385.062 148.304 370.291 163.927 347.851 163.927ZM347.851 149.156C359.497 149.156 366.314 140.066 366.314 124.301C366.314 108.678 359.497 99.4465 347.851 99.4465C336.063 99.4465 329.388 108.678 329.388 124.301C329.388 140.066 336.063 149.156 347.851 149.156Z" fill="white"/>
|
||||
<path d="M264.332 164.495C236.921 164.495 217.605 144.895 217.605 111.945C217.605 79.5632 236.211 59.1113 264.616 59.1113C290.323 59.1113 304.1 72.6038 308.36 95.4701L289.045 96.4643C286.63 83.2558 278.535 75.1603 264.616 75.1603C247.431 75.1603 236.779 89.505 236.779 111.945C236.779 134.669 247.715 148.446 264.474 148.446C279.387 148.446 287.341 139.782 289.613 125.296L308.929 126.29C304.952 150.008 289.897 164.495 264.332 164.495Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 8.3 KiB |
@@ -0,0 +1,44 @@
|
||||
<svg width="240" height="24" viewBox="0 0 240 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13.4283 0.610352L26.8565 23.8046H0L13.4283 0.610352Z" fill="#fff"/>
|
||||
<path d="M236.436 22.7547L235.081 17.1148V23.8045H233.849V15.1372H235.557L236.924 20.7405L238.291 15.1372H240V23.8045H238.767V17.1148L237.412 22.7547H236.436Z" fill="#fff"/>
|
||||
<path d="M226.237 23.8045L228.776 15.1372H230.436L232.974 23.8045H231.644L231.022 21.5462H228.19L227.568 23.8045H226.237ZM229.606 16.4678L228.52 20.362H230.692L229.606 16.4678Z" fill="#fff"/>
|
||||
<path d="M219.444 23.8045V15.1372H222.142C223.972 15.1372 225.095 16.126 225.095 17.7252C225.095 18.7628 224.436 19.5197 223.631 19.7761C224.485 19.9348 224.9 20.4109 224.973 21.3386L225.193 23.8045H223.911L223.716 21.5828C223.643 20.716 223.301 20.4231 222.239 20.4231H220.726V23.8045H219.444ZM220.726 19.2267H222.117C223.179 19.2267 223.777 18.7018 223.777 17.7862C223.777 16.8585 223.179 16.3458 222.117 16.3458H220.726V19.2267Z" fill="#fff"/>
|
||||
<path d="M214.957 23.9999C213.09 23.9999 211.748 22.4251 211.748 19.4831C211.748 16.6876 212.956 14.9419 214.994 14.9419C216.63 14.9419 217.704 16.0772 218.082 17.8107L216.752 17.8961C216.508 16.8341 215.934 16.1504 215.006 16.1504C213.774 16.1504 213.078 17.3468 213.078 19.4831C213.078 21.6072 213.798 22.7913 215.067 22.7913C216.178 22.7913 216.861 21.8758 216.91 20.4963H215.006V19.3854H218.094V23.8045H217.057L217.02 22.6937C216.642 23.4749 215.983 23.9999 214.957 23.9999Z" fill="#fff"/>
|
||||
<path d="M207.651 23.9999C205.589 23.9999 204.429 22.3763 204.429 19.4831C204.429 16.5655 205.589 14.9419 207.651 14.9419C209.714 14.9419 210.873 16.5655 210.873 19.4831C210.873 22.3763 209.714 23.9999 207.651 23.9999ZM207.651 22.8035C208.872 22.8035 209.543 21.6804 209.543 19.4831C209.543 17.2857 208.872 16.1382 207.651 16.1382C206.431 16.1382 205.76 17.2857 205.76 19.4831C205.76 21.6804 206.431 22.8035 207.651 22.8035Z" fill="#fff"/>
|
||||
<path d="M197.489 23.8045V15.1372H200.187C202.017 15.1372 203.14 16.126 203.14 17.7252C203.14 18.7628 202.481 19.5197 201.676 19.7761C202.53 19.9348 202.945 20.4109 203.018 21.3386L203.238 23.8045H201.956L201.761 21.5828C201.688 20.716 201.346 20.4231 200.284 20.4231H198.771V23.8045H197.489ZM198.771 19.2267H200.162C201.224 19.2267 201.822 18.7018 201.822 17.7862C201.822 16.8585 201.224 16.3458 200.162 16.3458H198.771V19.2267Z" fill="#fff"/>
|
||||
<path d="M190.403 23.8045V15.1372H193.088C194.894 15.1372 196.09 16.1626 196.09 17.8351C196.09 19.4953 194.894 20.5207 193.088 20.5207H191.684V23.8045H190.403ZM191.684 19.3122H192.954C194.186 19.3122 194.76 18.7995 194.76 17.8351C194.76 16.8707 194.186 16.3458 192.954 16.3458H191.684V19.3122Z" fill="#fff"/>
|
||||
<path d="M175.791 23.8045V15.1372H181.112V16.3458H177.073V18.8727H180.966V20.0446H177.073V22.596H181.21V23.8045H175.791Z" fill="#fff"/>
|
||||
<path d="M168.216 23.8045V15.1372H170.914C172.744 15.1372 173.867 16.126 173.867 17.7252C173.867 18.7628 173.208 19.5197 172.403 19.7761C173.257 19.9348 173.672 20.4109 173.745 21.3386L173.965 23.8045H172.683L172.488 21.5828C172.415 20.716 172.073 20.4231 171.011 20.4231H169.498V23.8045H168.216ZM169.498 19.2267H170.889C171.951 19.2267 172.549 18.7018 172.549 17.7862C172.549 16.8585 171.951 16.3458 170.889 16.3458H169.498V19.2267Z" fill="#fff"/>
|
||||
<path d="M160.374 23.8045L162.912 15.1372H164.572L167.111 23.8045H165.78L165.158 21.5462H162.326L161.704 23.8045H160.374ZM163.742 16.4678L162.656 20.362H164.828L163.742 16.4678Z" fill="#fff"/>
|
||||
<path d="M154.032 23.8045L153.104 15.1372H154.386L155.045 22.1565L155.862 16.1138H156.985L157.803 22.1565L158.462 15.1372H159.744L158.816 23.8045H157.144L156.424 18.5309L155.704 23.8045H154.032Z" fill="#fff"/>
|
||||
<path d="M148.471 23.8045V16.3458H145.932V15.1372H152.279V16.3458H149.74V23.8045H148.471Z" fill="#fff"/>
|
||||
<path d="M139.322 23.8045V15.1372H144.741V16.3213H140.604V18.9826H144.522V20.1545H140.604V23.8045H139.322Z" fill="#fff"/>
|
||||
<path d="M134.469 23.9999C132.407 23.9999 131.247 22.3763 131.247 19.4831C131.247 16.5655 132.407 14.9419 134.469 14.9419C136.532 14.9419 137.691 16.5655 137.691 19.4831C137.691 22.3763 136.532 23.9999 134.469 23.9999ZM134.469 22.8035C135.69 22.8035 136.361 21.6804 136.361 19.4831C136.361 17.2857 135.69 16.1382 134.469 16.1382C133.249 16.1382 132.577 17.2857 132.577 19.4831C132.577 21.6804 133.249 22.8035 134.469 22.8035Z" fill="#fff"/>
|
||||
<path d="M127.261 23.9999C125.381 23.9999 124.161 22.8035 124.014 20.9846L125.332 20.8992C125.503 22.1443 126.211 22.8035 127.31 22.8035C128.371 22.8035 128.969 22.3763 128.969 21.6072C128.969 20.8381 128.554 20.3987 126.736 19.8859C124.954 19.3976 124.209 18.7995 124.209 17.4566C124.209 15.9673 125.308 14.9419 127.126 14.9419C128.933 14.9419 129.97 16.0772 130.141 17.7496L128.823 17.8229C128.689 16.8219 128.127 16.1382 127.09 16.1382C126.138 16.1382 125.528 16.6387 125.528 17.3956C125.528 18.1525 125.955 18.4332 127.493 18.8605C129.567 19.4221 130.288 20.1911 130.288 21.6072C130.288 23.0355 129.079 23.9999 127.261 23.9999Z" fill="#fff"/>
|
||||
<path d="M109.927 23.8045V15.1372H115.248V16.3458H111.208V18.8727H115.102V20.0446H111.208V22.596H115.346V23.8045H109.927Z" fill="#fff"/>
|
||||
<path d="M105.232 23.9999C103.145 23.9999 101.974 22.2664 101.974 19.4831C101.974 16.6876 103.145 14.9419 105.232 14.9419C106.807 14.9419 107.942 16.0284 108.296 17.9083L106.953 17.9816C106.709 16.773 106.05 16.1382 105.232 16.1382C103.975 16.1382 103.304 17.3468 103.304 19.4831C103.304 21.6072 103.975 22.8035 105.232 22.8035C106.123 22.8035 106.795 22.1077 107.014 20.7893L108.345 20.8503C108.04 22.8279 106.892 23.9999 105.232 23.9999Z" fill="#fff"/>
|
||||
<path d="M95.0342 23.8045V15.1372H97.7315C99.5622 15.1372 100.685 16.126 100.685 17.7252C100.685 18.7628 100.026 19.5197 99.2205 19.7761C100.075 19.9348 100.49 20.4109 100.563 21.3386L100.783 23.8045H99.5012L99.3059 21.5828C99.2327 20.716 98.891 20.4231 97.8291 20.4231H96.3157V23.8045H95.0342ZM96.3157 19.2267H97.7071C98.7689 19.2267 99.367 18.7018 99.367 17.7862C99.367 16.8585 98.7689 16.3458 97.7071 16.3458H96.3157V19.2267Z" fill="#fff"/>
|
||||
<path d="M90.5596 23.9999C88.7166 23.9999 87.606 22.8035 87.606 20.7893V15.1372H88.8875V20.8381C88.8875 22.1077 89.4733 22.8035 90.5596 22.8035C91.6458 22.8035 92.2317 22.1077 92.2317 20.8381V15.1372H93.5132V20.7893C93.5132 22.8035 92.4025 23.9999 90.5596 23.9999Z" fill="#fff"/>
|
||||
<path d="M83.2417 23.9999C81.179 23.9999 80.0195 22.3763 80.0195 19.4831C80.0195 16.5655 81.179 14.9419 83.2417 14.9419C85.3043 14.9419 86.4638 16.5655 86.4638 19.4831C86.4638 22.3763 85.3043 23.9999 83.2417 23.9999ZM83.2417 22.8035C84.4622 22.8035 85.1334 21.6804 85.1334 19.4831C85.1334 17.2857 84.4622 16.1382 83.2417 16.1382C82.0212 16.1382 81.3499 17.2857 81.3499 19.4831C81.3499 21.6804 82.0212 22.8035 83.2417 22.8035Z" fill="#fff"/>
|
||||
<path d="M76.0332 23.9999C74.1536 23.9999 72.9331 22.8035 72.7866 20.9846L74.1048 20.8992C74.2756 22.1443 74.9835 22.8035 76.082 22.8035C77.1438 22.8035 77.7419 22.3763 77.7419 21.6072C77.7419 20.8381 77.3269 20.3987 75.5083 19.8859C73.7264 19.3976 72.9819 18.7995 72.9819 17.4566C72.9819 15.9673 74.0804 14.9419 75.8989 14.9419C77.7052 14.9419 78.7427 16.0772 78.9135 17.7496L77.5954 17.8229C77.4611 16.8219 76.8997 16.1382 75.8623 16.1382C74.9103 16.1382 74.3 16.6387 74.3 17.3956C74.3 18.1525 74.7272 18.4332 76.265 18.8605C78.3399 19.4221 79.06 20.1911 79.06 21.6072C79.06 23.0355 77.8517 23.9999 76.0332 23.9999Z" fill="#fff"/>
|
||||
<path d="M58.4556 23.8045V15.1372H60.0178L62.8616 21.9856V15.1372H64.1187V23.8045H62.5564L59.7127 16.9561V23.8045H58.4556Z" fill="#fff"/>
|
||||
<path d="M51.3813 23.8045V15.1372H56.7027V16.3458H52.6629V18.8727H56.5563V20.0446H52.6629V22.596H56.8004V23.8045H51.3813Z" fill="#fff"/>
|
||||
<path d="M44.0386 23.8045V15.1372H46.7237C48.53 15.1372 49.7261 16.1626 49.7261 17.8351C49.7261 19.4953 48.53 20.5207 46.7237 20.5207H45.3201V23.8045H44.0386ZM45.3201 19.3122H46.5894C47.8221 19.3122 48.3958 18.7995 48.3958 17.8351C48.3958 16.8707 47.8221 16.3458 46.5894 16.3458H45.3201V19.3122Z" fill="#fff"/>
|
||||
<path d="M39.332 23.9999C37.2693 23.9999 36.1099 22.3763 36.1099 19.4831C36.1099 16.5655 37.2693 14.9419 39.332 14.9419C41.3946 14.9419 42.5541 16.5655 42.5541 19.4831C42.5541 22.3763 41.3946 23.9999 39.332 23.9999ZM39.332 22.8035C40.5525 22.8035 41.2238 21.6804 41.2238 19.4831C41.2238 17.2857 40.5525 16.1382 39.332 16.1382C38.1115 16.1382 37.4402 17.2857 37.4402 19.4831C37.4402 21.6804 38.1115 22.8035 39.332 22.8035Z" fill="#fff"/>
|
||||
<path d="M171.085 9.35094C169.083 9.35094 167.96 7.95929 167.96 5.46895C167.96 2.60019 168.864 0.292969 171.414 0.292969C173.013 0.292969 173.819 1.1597 174.148 2.38045L172.83 2.49032C172.659 1.89215 172.269 1.48931 171.414 1.48931C170.243 1.48931 169.474 2.36825 169.303 4.2482C169.718 3.71107 170.426 3.32043 171.329 3.32043C173.05 3.32043 174.161 4.48015 174.161 6.25024C174.161 8.16681 172.916 9.35094 171.085 9.35094ZM171.085 8.1424C172.147 8.1424 172.83 7.45878 172.83 6.27465C172.83 5.17597 172.196 4.48015 171.183 4.48015C170.096 4.48015 169.315 5.17597 169.315 6.27465C169.315 7.34891 169.999 8.1424 171.085 8.1424Z" fill="#fff" fill-opacity="0.5"/>
|
||||
<path d="M160.667 9.15562C160.667 7.22684 161.387 6.01829 163.925 4.67547C165.011 4.10171 165.475 3.72328 165.475 2.88096C165.475 2.05085 164.95 1.48931 163.937 1.48931C162.827 1.48931 162.217 2.09968 162.046 3.18615L160.715 3.1007C160.898 1.37944 162.021 0.292969 163.937 0.292969C165.78 0.292969 166.806 1.3184 166.806 2.83213C166.806 4.17496 166.22 4.83416 164.45 5.78635C162.949 6.59205 162.241 7.36112 162.192 7.94708H166.818V9.15562H160.667Z" fill="#fff" fill-opacity="0.5"/>
|
||||
<path d="M156.424 9.35094C154.508 9.35094 153.348 7.61748 153.348 4.83416C153.348 2.03864 154.508 0.292969 156.424 0.292969C158.34 0.292969 159.499 2.03864 159.499 4.83416C159.499 7.61748 158.34 9.35094 156.424 9.35094ZM154.678 4.83416C154.678 5.55441 154.752 6.1892 154.91 6.68971L157.449 1.98981C157.181 1.66021 156.827 1.48931 156.424 1.48931C155.338 1.48931 154.678 2.72226 154.678 4.83416ZM156.424 8.15461C157.51 8.15461 158.169 6.92165 158.169 4.83416C158.169 4.10171 158.096 3.46692 157.937 2.96641L155.386 7.6541C155.667 7.9837 156.021 8.15461 156.424 8.15461Z" fill="#fff" fill-opacity="0.5"/>
|
||||
<path d="M146.03 9.15562C146.03 7.22684 146.75 6.01829 149.289 4.67547C150.375 4.10171 150.839 3.72328 150.839 2.88096C150.839 2.05085 150.314 1.48931 149.301 1.48931C148.19 1.48931 147.58 2.09968 147.409 3.18615L146.079 3.1007C146.262 1.37944 147.385 0.292969 149.301 0.292969C151.144 0.292969 152.169 1.3184 152.169 2.83213C152.169 4.17496 151.583 4.83416 149.813 5.78635C148.312 6.59205 147.604 7.36112 147.555 7.94708H152.181V9.15562H146.03Z" fill="#fff" fill-opacity="0.5"/>
|
||||
<path d="M132.041 10.4985L135.727 0H136.899L133.212 10.4985H132.041Z" fill="#fff" fill-opacity="0.5"/>
|
||||
<path d="M124.722 10.4985L128.409 0H129.581L125.894 10.4985H124.722Z" fill="#fff" fill-opacity="0.5"/>
|
||||
<path d="M111.684 9.15582V7.54443H113.344V9.15582H111.684Z" fill="#fff"/>
|
||||
<path d="M105.232 9.35094C103.145 9.35094 101.974 7.61748 101.974 4.83416C101.974 2.03864 103.145 0.292969 105.232 0.292969C106.807 0.292969 107.942 1.37944 108.296 3.25939L106.953 3.33264C106.709 2.1241 106.05 1.48931 105.232 1.48931C103.975 1.48931 103.304 2.69785 103.304 4.83416C103.304 6.95827 103.975 8.15461 105.232 8.15461C106.123 8.15461 106.795 7.45878 107.014 6.14037L108.345 6.20141C108.04 8.17902 106.892 9.35094 105.232 9.35094Z" fill="#fff"/>
|
||||
<path d="M95.0464 9.15562V0.488281H96.6086L99.4524 7.3367V0.488281H100.71V9.15562H99.1473L96.3035 2.3072V9.15562H95.0464Z" fill="#fff"/>
|
||||
<path d="M87.8501 9.15562V7.94707H89.9249V1.69683H87.8501V0.488281H93.2691V1.69683H91.1943V7.94707H93.2691V9.15562H87.8501Z" fill="#fff"/>
|
||||
<path d="M73.4575 9.15562V0.488281H74.739V7.94707H78.8765V9.15562H73.4575Z" fill="#fff"/>
|
||||
<path d="M66.0171 9.15562V0.488281H71.3385V1.69683H67.2986V4.22378H71.192V5.3957H67.2986V7.94707H71.4361V9.15562H66.0171Z" fill="#fff"/>
|
||||
<path d="M61.3232 9.35094C59.2361 9.35094 58.0645 7.61748 58.0645 4.83416C58.0645 2.03864 59.2361 0.292969 61.3232 0.292969C62.8976 0.292969 64.0327 1.37944 64.3866 3.25939L63.0441 3.33264C62.8 2.1241 62.1409 1.48931 61.3232 1.48931C60.0661 1.48931 59.3948 2.69785 59.3948 4.83416C59.3948 6.95827 60.0661 8.15461 61.3232 8.15461C62.2142 8.15461 62.8854 7.45878 63.1051 6.14037L64.4355 6.20141C64.1303 8.17902 62.9831 9.35094 61.3232 9.35094Z" fill="#fff"/>
|
||||
<path d="M51.125 9.15562V0.488281H53.8223C55.6531 0.488281 56.7759 1.47709 56.7759 3.07627C56.7759 4.11391 56.1168 4.87078 55.3113 5.12714C56.1657 5.28583 56.5806 5.76193 56.6539 6.6897L56.8736 9.15562H55.592L55.3968 6.93385C55.3235 6.06711 54.9818 5.77413 53.9199 5.77413H52.4065V9.15562H51.125ZM52.4065 4.5778H53.7979C54.8597 4.5778 55.4578 4.05288 55.4578 3.13731C55.4578 2.20954 54.8597 1.69683 53.7979 1.69683H52.4065V4.5778Z" fill="#fff"/>
|
||||
<path d="M44.063 9.15562V0.488281H49.3844V1.69683H45.3445V4.22378H49.2379V5.3957H45.3445V7.94707H49.482V9.15562H44.063Z" fill="#fff"/>
|
||||
<path d="M38.5753 9.15562L36 0.488281H37.3548L39.332 7.44656L41.3092 0.488281H42.6639L40.0887 9.15562H38.5753Z" fill="#fff"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 13 KiB |
@@ -0,0 +1,44 @@
|
||||
<svg width="240" height="24" viewBox="0 0 240 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13.4283 0.610352L26.8565 23.8046H0L13.4283 0.610352Z" fill="#000"/>
|
||||
<path d="M236.436 22.7547L235.081 17.1148V23.8045H233.849V15.1372H235.557L236.924 20.7405L238.291 15.1372H240V23.8045H238.767V17.1148L237.412 22.7547H236.436Z" fill="#000"/>
|
||||
<path d="M226.237 23.8045L228.776 15.1372H230.436L232.974 23.8045H231.644L231.022 21.5462H228.19L227.568 23.8045H226.237ZM229.606 16.4678L228.52 20.362H230.692L229.606 16.4678Z" fill="#000"/>
|
||||
<path d="M219.444 23.8045V15.1372H222.142C223.972 15.1372 225.095 16.126 225.095 17.7252C225.095 18.7628 224.436 19.5197 223.631 19.7761C224.485 19.9348 224.9 20.4109 224.973 21.3386L225.193 23.8045H223.911L223.716 21.5828C223.643 20.716 223.301 20.4231 222.239 20.4231H220.726V23.8045H219.444ZM220.726 19.2267H222.117C223.179 19.2267 223.777 18.7018 223.777 17.7862C223.777 16.8585 223.179 16.3458 222.117 16.3458H220.726V19.2267Z" fill="#000"/>
|
||||
<path d="M214.957 23.9999C213.09 23.9999 211.748 22.4251 211.748 19.4831C211.748 16.6876 212.956 14.9419 214.994 14.9419C216.63 14.9419 217.704 16.0772 218.082 17.8107L216.752 17.8961C216.508 16.8341 215.934 16.1504 215.006 16.1504C213.774 16.1504 213.078 17.3468 213.078 19.4831C213.078 21.6072 213.798 22.7913 215.067 22.7913C216.178 22.7913 216.861 21.8758 216.91 20.4963H215.006V19.3854H218.094V23.8045H217.057L217.02 22.6937C216.642 23.4749 215.983 23.9999 214.957 23.9999Z" fill="#000"/>
|
||||
<path d="M207.651 23.9999C205.589 23.9999 204.429 22.3763 204.429 19.4831C204.429 16.5655 205.589 14.9419 207.651 14.9419C209.714 14.9419 210.873 16.5655 210.873 19.4831C210.873 22.3763 209.714 23.9999 207.651 23.9999ZM207.651 22.8035C208.872 22.8035 209.543 21.6804 209.543 19.4831C209.543 17.2857 208.872 16.1382 207.651 16.1382C206.431 16.1382 205.76 17.2857 205.76 19.4831C205.76 21.6804 206.431 22.8035 207.651 22.8035Z" fill="#000"/>
|
||||
<path d="M197.489 23.8045V15.1372H200.187C202.017 15.1372 203.14 16.126 203.14 17.7252C203.14 18.7628 202.481 19.5197 201.676 19.7761C202.53 19.9348 202.945 20.4109 203.018 21.3386L203.238 23.8045H201.956L201.761 21.5828C201.688 20.716 201.346 20.4231 200.284 20.4231H198.771V23.8045H197.489ZM198.771 19.2267H200.162C201.224 19.2267 201.822 18.7018 201.822 17.7862C201.822 16.8585 201.224 16.3458 200.162 16.3458H198.771V19.2267Z" fill="#000"/>
|
||||
<path d="M190.403 23.8045V15.1372H193.088C194.894 15.1372 196.09 16.1626 196.09 17.8351C196.09 19.4953 194.894 20.5207 193.088 20.5207H191.684V23.8045H190.403ZM191.684 19.3122H192.954C194.186 19.3122 194.76 18.7995 194.76 17.8351C194.76 16.8707 194.186 16.3458 192.954 16.3458H191.684V19.3122Z" fill="#000"/>
|
||||
<path d="M175.791 23.8045V15.1372H181.112V16.3458H177.073V18.8727H180.966V20.0446H177.073V22.596H181.21V23.8045H175.791Z" fill="#000"/>
|
||||
<path d="M168.216 23.8045V15.1372H170.914C172.744 15.1372 173.867 16.126 173.867 17.7252C173.867 18.7628 173.208 19.5197 172.403 19.7761C173.257 19.9348 173.672 20.4109 173.745 21.3386L173.965 23.8045H172.683L172.488 21.5828C172.415 20.716 172.073 20.4231 171.011 20.4231H169.498V23.8045H168.216ZM169.498 19.2267H170.889C171.951 19.2267 172.549 18.7018 172.549 17.7862C172.549 16.8585 171.951 16.3458 170.889 16.3458H169.498V19.2267Z" fill="#000"/>
|
||||
<path d="M160.374 23.8045L162.912 15.1372H164.572L167.111 23.8045H165.78L165.158 21.5462H162.326L161.704 23.8045H160.374ZM163.742 16.4678L162.656 20.362H164.828L163.742 16.4678Z" fill="#000"/>
|
||||
<path d="M154.032 23.8045L153.104 15.1372H154.386L155.045 22.1565L155.862 16.1138H156.985L157.803 22.1565L158.462 15.1372H159.744L158.816 23.8045H157.144L156.424 18.5309L155.704 23.8045H154.032Z" fill="#000"/>
|
||||
<path d="M148.471 23.8045V16.3458H145.932V15.1372H152.279V16.3458H149.74V23.8045H148.471Z" fill="#000"/>
|
||||
<path d="M139.322 23.8045V15.1372H144.741V16.3213H140.604V18.9826H144.522V20.1545H140.604V23.8045H139.322Z" fill="#000"/>
|
||||
<path d="M134.469 23.9999C132.407 23.9999 131.247 22.3763 131.247 19.4831C131.247 16.5655 132.407 14.9419 134.469 14.9419C136.532 14.9419 137.691 16.5655 137.691 19.4831C137.691 22.3763 136.532 23.9999 134.469 23.9999ZM134.469 22.8035C135.69 22.8035 136.361 21.6804 136.361 19.4831C136.361 17.2857 135.69 16.1382 134.469 16.1382C133.249 16.1382 132.577 17.2857 132.577 19.4831C132.577 21.6804 133.249 22.8035 134.469 22.8035Z" fill="#000"/>
|
||||
<path d="M127.261 23.9999C125.381 23.9999 124.161 22.8035 124.014 20.9846L125.332 20.8992C125.503 22.1443 126.211 22.8035 127.31 22.8035C128.371 22.8035 128.969 22.3763 128.969 21.6072C128.969 20.8381 128.554 20.3987 126.736 19.8859C124.954 19.3976 124.209 18.7995 124.209 17.4566C124.209 15.9673 125.308 14.9419 127.126 14.9419C128.933 14.9419 129.97 16.0772 130.141 17.7496L128.823 17.8229C128.689 16.8219 128.127 16.1382 127.09 16.1382C126.138 16.1382 125.528 16.6387 125.528 17.3956C125.528 18.1525 125.955 18.4332 127.493 18.8605C129.567 19.4221 130.288 20.1911 130.288 21.6072C130.288 23.0355 129.079 23.9999 127.261 23.9999Z" fill="#000"/>
|
||||
<path d="M109.927 23.8045V15.1372H115.248V16.3458H111.208V18.8727H115.102V20.0446H111.208V22.596H115.346V23.8045H109.927Z" fill="#000"/>
|
||||
<path d="M105.232 23.9999C103.145 23.9999 101.974 22.2664 101.974 19.4831C101.974 16.6876 103.145 14.9419 105.232 14.9419C106.807 14.9419 107.942 16.0284 108.296 17.9083L106.953 17.9816C106.709 16.773 106.05 16.1382 105.232 16.1382C103.975 16.1382 103.304 17.3468 103.304 19.4831C103.304 21.6072 103.975 22.8035 105.232 22.8035C106.123 22.8035 106.795 22.1077 107.014 20.7893L108.345 20.8503C108.04 22.8279 106.892 23.9999 105.232 23.9999Z" fill="#000"/>
|
||||
<path d="M95.0342 23.8045V15.1372H97.7315C99.5622 15.1372 100.685 16.126 100.685 17.7252C100.685 18.7628 100.026 19.5197 99.2205 19.7761C100.075 19.9348 100.49 20.4109 100.563 21.3386L100.783 23.8045H99.5012L99.3059 21.5828C99.2327 20.716 98.891 20.4231 97.8291 20.4231H96.3157V23.8045H95.0342ZM96.3157 19.2267H97.7071C98.7689 19.2267 99.367 18.7018 99.367 17.7862C99.367 16.8585 98.7689 16.3458 97.7071 16.3458H96.3157V19.2267Z" fill="#000"/>
|
||||
<path d="M90.5596 23.9999C88.7166 23.9999 87.606 22.8035 87.606 20.7893V15.1372H88.8875V20.8381C88.8875 22.1077 89.4733 22.8035 90.5596 22.8035C91.6458 22.8035 92.2317 22.1077 92.2317 20.8381V15.1372H93.5132V20.7893C93.5132 22.8035 92.4025 23.9999 90.5596 23.9999Z" fill="#000"/>
|
||||
<path d="M83.2417 23.9999C81.179 23.9999 80.0195 22.3763 80.0195 19.4831C80.0195 16.5655 81.179 14.9419 83.2417 14.9419C85.3043 14.9419 86.4638 16.5655 86.4638 19.4831C86.4638 22.3763 85.3043 23.9999 83.2417 23.9999ZM83.2417 22.8035C84.4622 22.8035 85.1334 21.6804 85.1334 19.4831C85.1334 17.2857 84.4622 16.1382 83.2417 16.1382C82.0212 16.1382 81.3499 17.2857 81.3499 19.4831C81.3499 21.6804 82.0212 22.8035 83.2417 22.8035Z" fill="#000"/>
|
||||
<path d="M76.0332 23.9999C74.1536 23.9999 72.9331 22.8035 72.7866 20.9846L74.1048 20.8992C74.2756 22.1443 74.9835 22.8035 76.082 22.8035C77.1438 22.8035 77.7419 22.3763 77.7419 21.6072C77.7419 20.8381 77.3269 20.3987 75.5083 19.8859C73.7264 19.3976 72.9819 18.7995 72.9819 17.4566C72.9819 15.9673 74.0804 14.9419 75.8989 14.9419C77.7052 14.9419 78.7427 16.0772 78.9135 17.7496L77.5954 17.8229C77.4611 16.8219 76.8997 16.1382 75.8623 16.1382C74.9103 16.1382 74.3 16.6387 74.3 17.3956C74.3 18.1525 74.7272 18.4332 76.265 18.8605C78.3399 19.4221 79.06 20.1911 79.06 21.6072C79.06 23.0355 77.8517 23.9999 76.0332 23.9999Z" fill="#000"/>
|
||||
<path d="M58.4556 23.8045V15.1372H60.0178L62.8616 21.9856V15.1372H64.1187V23.8045H62.5564L59.7127 16.9561V23.8045H58.4556Z" fill="#000"/>
|
||||
<path d="M51.3813 23.8045V15.1372H56.7027V16.3458H52.6629V18.8727H56.5563V20.0446H52.6629V22.596H56.8004V23.8045H51.3813Z" fill="#000"/>
|
||||
<path d="M44.0386 23.8045V15.1372H46.7237C48.53 15.1372 49.7261 16.1626 49.7261 17.8351C49.7261 19.4953 48.53 20.5207 46.7237 20.5207H45.3201V23.8045H44.0386ZM45.3201 19.3122H46.5894C47.8221 19.3122 48.3958 18.7995 48.3958 17.8351C48.3958 16.8707 47.8221 16.3458 46.5894 16.3458H45.3201V19.3122Z" fill="#000"/>
|
||||
<path d="M39.332 23.9999C37.2693 23.9999 36.1099 22.3763 36.1099 19.4831C36.1099 16.5655 37.2693 14.9419 39.332 14.9419C41.3946 14.9419 42.5541 16.5655 42.5541 19.4831C42.5541 22.3763 41.3946 23.9999 39.332 23.9999ZM39.332 22.8035C40.5525 22.8035 41.2238 21.6804 41.2238 19.4831C41.2238 17.2857 40.5525 16.1382 39.332 16.1382C38.1115 16.1382 37.4402 17.2857 37.4402 19.4831C37.4402 21.6804 38.1115 22.8035 39.332 22.8035Z" fill="#000"/>
|
||||
<path d="M171.085 9.35094C169.083 9.35094 167.96 7.95929 167.96 5.46895C167.96 2.60019 168.864 0.292969 171.414 0.292969C173.013 0.292969 173.819 1.1597 174.148 2.38045L172.83 2.49032C172.659 1.89215 172.269 1.48931 171.414 1.48931C170.243 1.48931 169.474 2.36825 169.303 4.2482C169.718 3.71107 170.426 3.32043 171.329 3.32043C173.05 3.32043 174.161 4.48015 174.161 6.25024C174.161 8.16681 172.916 9.35094 171.085 9.35094ZM171.085 8.1424C172.147 8.1424 172.83 7.45878 172.83 6.27465C172.83 5.17597 172.196 4.48015 171.183 4.48015C170.096 4.48015 169.315 5.17597 169.315 6.27465C169.315 7.34891 169.999 8.1424 171.085 8.1424Z" fill="#000" fill-opacity="0.5"/>
|
||||
<path d="M160.667 9.15562C160.667 7.22684 161.387 6.01829 163.925 4.67547C165.011 4.10171 165.475 3.72328 165.475 2.88096C165.475 2.05085 164.95 1.48931 163.937 1.48931C162.827 1.48931 162.217 2.09968 162.046 3.18615L160.715 3.1007C160.898 1.37944 162.021 0.292969 163.937 0.292969C165.78 0.292969 166.806 1.3184 166.806 2.83213C166.806 4.17496 166.22 4.83416 164.45 5.78635C162.949 6.59205 162.241 7.36112 162.192 7.94708H166.818V9.15562H160.667Z" fill="#000" fill-opacity="0.5"/>
|
||||
<path d="M156.424 9.35094C154.508 9.35094 153.348 7.61748 153.348 4.83416C153.348 2.03864 154.508 0.292969 156.424 0.292969C158.34 0.292969 159.499 2.03864 159.499 4.83416C159.499 7.61748 158.34 9.35094 156.424 9.35094ZM154.678 4.83416C154.678 5.55441 154.752 6.1892 154.91 6.68971L157.449 1.98981C157.181 1.66021 156.827 1.48931 156.424 1.48931C155.338 1.48931 154.678 2.72226 154.678 4.83416ZM156.424 8.15461C157.51 8.15461 158.169 6.92165 158.169 4.83416C158.169 4.10171 158.096 3.46692 157.937 2.96641L155.386 7.6541C155.667 7.9837 156.021 8.15461 156.424 8.15461Z" fill="#000" fill-opacity="0.5"/>
|
||||
<path d="M146.03 9.15562C146.03 7.22684 146.75 6.01829 149.289 4.67547C150.375 4.10171 150.839 3.72328 150.839 2.88096C150.839 2.05085 150.314 1.48931 149.301 1.48931C148.19 1.48931 147.58 2.09968 147.409 3.18615L146.079 3.1007C146.262 1.37944 147.385 0.292969 149.301 0.292969C151.144 0.292969 152.169 1.3184 152.169 2.83213C152.169 4.17496 151.583 4.83416 149.813 5.78635C148.312 6.59205 147.604 7.36112 147.555 7.94708H152.181V9.15562H146.03Z" fill="#000" fill-opacity="0.5"/>
|
||||
<path d="M132.041 10.4985L135.727 0H136.899L133.212 10.4985H132.041Z" fill="#000" fill-opacity="0.5"/>
|
||||
<path d="M124.722 10.4985L128.409 0H129.581L125.894 10.4985H124.722Z" fill="#000" fill-opacity="0.5"/>
|
||||
<path d="M111.684 9.15582V7.54443H113.344V9.15582H111.684Z" fill="#000"/>
|
||||
<path d="M105.232 9.35094C103.145 9.35094 101.974 7.61748 101.974 4.83416C101.974 2.03864 103.145 0.292969 105.232 0.292969C106.807 0.292969 107.942 1.37944 108.296 3.25939L106.953 3.33264C106.709 2.1241 106.05 1.48931 105.232 1.48931C103.975 1.48931 103.304 2.69785 103.304 4.83416C103.304 6.95827 103.975 8.15461 105.232 8.15461C106.123 8.15461 106.795 7.45878 107.014 6.14037L108.345 6.20141C108.04 8.17902 106.892 9.35094 105.232 9.35094Z" fill="#000"/>
|
||||
<path d="M95.0464 9.15562V0.488281H96.6086L99.4524 7.3367V0.488281H100.71V9.15562H99.1473L96.3035 2.3072V9.15562H95.0464Z" fill="#000"/>
|
||||
<path d="M87.8501 9.15562V7.94707H89.9249V1.69683H87.8501V0.488281H93.2691V1.69683H91.1943V7.94707H93.2691V9.15562H87.8501Z" fill="#000"/>
|
||||
<path d="M73.4575 9.15562V0.488281H74.739V7.94707H78.8765V9.15562H73.4575Z" fill="#000"/>
|
||||
<path d="M66.0171 9.15562V0.488281H71.3385V1.69683H67.2986V4.22378H71.192V5.3957H67.2986V7.94707H71.4361V9.15562H66.0171Z" fill="#000"/>
|
||||
<path d="M61.3232 9.35094C59.2361 9.35094 58.0645 7.61748 58.0645 4.83416C58.0645 2.03864 59.2361 0.292969 61.3232 0.292969C62.8976 0.292969 64.0327 1.37944 64.3866 3.25939L63.0441 3.33264C62.8 2.1241 62.1409 1.48931 61.3232 1.48931C60.0661 1.48931 59.3948 2.69785 59.3948 4.83416C59.3948 6.95827 60.0661 8.15461 61.3232 8.15461C62.2142 8.15461 62.8854 7.45878 63.1051 6.14037L64.4355 6.20141C64.1303 8.17902 62.9831 9.35094 61.3232 9.35094Z" fill="#000"/>
|
||||
<path d="M51.125 9.15562V0.488281H53.8223C55.6531 0.488281 56.7759 1.47709 56.7759 3.07627C56.7759 4.11391 56.1168 4.87078 55.3113 5.12714C56.1657 5.28583 56.5806 5.76193 56.6539 6.6897L56.8736 9.15562H55.592L55.3968 6.93385C55.3235 6.06711 54.9818 5.77413 53.9199 5.77413H52.4065V9.15562H51.125ZM52.4065 4.5778H53.7979C54.8597 4.5778 55.4578 4.05288 55.4578 3.13731C55.4578 2.20954 54.8597 1.69683 53.7979 1.69683H52.4065V4.5778Z" fill="#000"/>
|
||||
<path d="M44.063 9.15562V0.488281H49.3844V1.69683H45.3445V4.22378H49.2379V5.3957H45.3445V7.94707H49.482V9.15562H44.063Z" fill="#000"/>
|
||||
<path d="M38.5753 9.15562L36 0.488281H37.3548L39.332 7.44656L41.3092 0.488281H42.6639L40.0887 9.15562H38.5753Z" fill="#000"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 13 KiB |
+218
-307
@@ -39,377 +39,288 @@ python3 << 'PYTHON_SCRIPT'
|
||||
import csv
|
||||
import subprocess
|
||||
import os
|
||||
import io
|
||||
import sys
|
||||
import re
|
||||
|
||||
# Increase CSV field size limit to handle large prompt content
|
||||
csv.field_size_limit(sys.maxsize)
|
||||
|
||||
project_dir = os.environ.get('PROJECT_DIR', '.')
|
||||
csv_file = os.path.join(project_dir, 'prompts.csv')
|
||||
remote_csv = os.path.join(project_dir, 'prompts.csv.remote')
|
||||
prompts_md_path = os.path.join(project_dir, 'PROMPTS.md')
|
||||
|
||||
# Read existing local prompts (by act title as key)
|
||||
local_prompts = {}
|
||||
fieldnames = None
|
||||
skipped_local = 0
|
||||
with open(csv_file, 'r', newline='', encoding='utf-8') as f:
|
||||
# Normalize CRLF to LF
|
||||
content = f.read().replace('\r\n', '\n').replace('\r', '\n')
|
||||
reader = csv.DictReader(io.StringIO(content))
|
||||
fieldnames = reader.fieldnames
|
||||
for row in reader:
|
||||
try:
|
||||
act = row.get('act', '').strip()
|
||||
if act:
|
||||
local_prompts[act] = row
|
||||
except csv.Error as e:
|
||||
skipped_local += 1
|
||||
print(f"Skipping local row due to CSV error: {e}")
|
||||
# --- CSV I/O (ordered, roundtrip-safe) ---
|
||||
|
||||
print(f"Found {len(local_prompts)} existing local prompts" + (f" (skipped {skipped_local})" if skipped_local else ""))
|
||||
def read_csv(path):
|
||||
"""Read CSV into an ordered list of rows and an act->index map."""
|
||||
rows = []
|
||||
index = {}
|
||||
skipped = 0
|
||||
with open(path, 'r', newline='', encoding='utf-8') as f:
|
||||
reader = csv.DictReader(f)
|
||||
fieldnames = list(reader.fieldnames) if reader.fieldnames else []
|
||||
while True:
|
||||
try:
|
||||
row = next(reader)
|
||||
act = (row.get('act') or '').strip()
|
||||
if act:
|
||||
index[act] = len(rows)
|
||||
rows.append(row)
|
||||
except csv.Error as e:
|
||||
skipped += 1
|
||||
print(f"Skipping row due to CSV error: {e}")
|
||||
except StopIteration:
|
||||
break
|
||||
return rows, index, fieldnames, skipped
|
||||
|
||||
# Read remote prompts (normalize CRLF to LF)
|
||||
remote_prompts = []
|
||||
skipped_remote = 0
|
||||
with open(remote_csv, 'r', newline='', encoding='utf-8') as f:
|
||||
content = f.read().replace('\r\n', '\n').replace('\r', '\n')
|
||||
reader = csv.DictReader(io.StringIO(content))
|
||||
remote_fieldnames = reader.fieldnames
|
||||
while True:
|
||||
try:
|
||||
row = next(reader)
|
||||
remote_prompts.append(row)
|
||||
except csv.Error as e:
|
||||
skipped_remote += 1
|
||||
print(f"Skipping remote row due to CSV error: {e}")
|
||||
except StopIteration:
|
||||
break
|
||||
def write_csv(rows, fieldnames, path):
|
||||
"""Write CSV deterministically — unchanged rows produce identical bytes."""
|
||||
with open(path, 'w', newline='', encoding='utf-8') as f:
|
||||
writer = csv.DictWriter(f, fieldnames=fieldnames)
|
||||
writer.writeheader()
|
||||
for row in rows:
|
||||
writer.writerow(row)
|
||||
|
||||
print(f"Found {len(remote_prompts)} remote prompts" + (f" (skipped {skipped_remote})" if skipped_remote else ""))
|
||||
def rebuild_index(rows):
|
||||
"""Rebuild the act->index map after mutations."""
|
||||
return {(r.get('act') or '').strip(): i for i, r in enumerate(rows)}
|
||||
|
||||
# Use remote fieldnames if local is empty
|
||||
if not fieldnames:
|
||||
fieldnames = remote_fieldnames
|
||||
def normalize(s):
|
||||
"""Normalize a string for comparison only."""
|
||||
return (s or '').replace('\r\n', '\n').replace('\r', '\n').strip()
|
||||
|
||||
# Build set of remote prompt acts for quick lookup
|
||||
remote_acts = set()
|
||||
for row in remote_prompts:
|
||||
act = row.get('act', '').strip()
|
||||
if act:
|
||||
remote_acts.add(act)
|
||||
# --- Contributor helpers ---
|
||||
|
||||
# Find new, updated, and deleted prompts
|
||||
new_prompts = []
|
||||
updated_prompts = []
|
||||
deleted_prompts = []
|
||||
|
||||
# Check for new and updated
|
||||
for row in remote_prompts:
|
||||
act = row.get('act', '').strip()
|
||||
if not act:
|
||||
continue
|
||||
|
||||
if act not in local_prompts:
|
||||
new_prompts.append(row)
|
||||
else:
|
||||
local_row = local_prompts[act]
|
||||
# Check if content OR contributors changed
|
||||
content_changed = row.get('prompt', '').strip() != local_row.get('prompt', '').strip()
|
||||
contributors_changed = row.get('contributor', '').strip() != local_row.get('contributor', '').strip()
|
||||
if content_changed or contributors_changed:
|
||||
updated_prompts.append((row, local_row))
|
||||
|
||||
# Check for deleted (in local but not in remote)
|
||||
for act, local_row in local_prompts.items():
|
||||
if act not in remote_acts:
|
||||
deleted_prompts.append(local_row)
|
||||
|
||||
print(f"Found {len(new_prompts)} new prompts to add")
|
||||
print(f"Found {len(updated_prompts)} updated prompts to modify")
|
||||
print(f"Found {len(deleted_prompts)} prompts to remove (unlisted/deleted)")
|
||||
|
||||
# Helper function to parse contributors (supports "user1,user2,user3" format)
|
||||
def parse_contributors(contributor_field):
|
||||
"""Parse contributor field, returns (primary_author, co_authors_list)"""
|
||||
if not contributor_field:
|
||||
def parse_contributors(field):
|
||||
if not field:
|
||||
return 'anonymous', []
|
||||
|
||||
# Split by comma and clean up
|
||||
contributors = [c.strip() for c in contributor_field.split(',') if c.strip()]
|
||||
|
||||
if not contributors:
|
||||
parts = [c.strip() for c in field.split(',') if c.strip()]
|
||||
if not parts:
|
||||
return 'anonymous', []
|
||||
|
||||
primary = contributors[0]
|
||||
co_authors = contributors[1:] if len(contributors) > 1 else []
|
||||
return primary, co_authors
|
||||
return parts[0], parts[1:]
|
||||
|
||||
def build_commit_message(action, act, co_authors):
|
||||
"""Build commit message with optional co-author trailers"""
|
||||
def make_email(name):
|
||||
"""Build an email — don't double-suffix if the contributor is already an email."""
|
||||
if '@' in name:
|
||||
return name
|
||||
return f'{name}@users.noreply.github.com'
|
||||
|
||||
def build_commit_msg(action, act, co_authors):
|
||||
msg = f'{action} prompt: {act}'
|
||||
|
||||
if co_authors:
|
||||
msg += '\n\n'
|
||||
for co_author in co_authors:
|
||||
co_email = f"{co_author}@users.noreply.github.com"
|
||||
msg += f'Co-authored-by: {co_author} <{co_email}>\n'
|
||||
|
||||
for ca in co_authors:
|
||||
msg += f'Co-authored-by: {ca} <{make_email(ca)}>\n'
|
||||
return msg
|
||||
|
||||
def git_commit(author_name, author_email, message):
|
||||
"""Stage CSV + PROMPTS.md and commit if there are real changes. Returns True if committed."""
|
||||
subprocess.run(['git', 'add', csv_file, prompts_md_path], check=True)
|
||||
if subprocess.run(['git', 'diff', '--cached', '--quiet'], capture_output=True).returncode == 0:
|
||||
return False
|
||||
env = os.environ.copy()
|
||||
env['GIT_AUTHOR_NAME'] = author_name
|
||||
env['GIT_AUTHOR_EMAIL'] = author_email
|
||||
env['GIT_COMMITTER_NAME'] = author_name
|
||||
env['GIT_COMMITTER_EMAIL'] = author_email
|
||||
subprocess.run([
|
||||
'git', 'commit', '-m', message,
|
||||
f'--author={author_name} <{author_email}>'
|
||||
], env=env, check=True)
|
||||
return True
|
||||
|
||||
# --- PROMPTS.md helpers ---
|
||||
|
||||
EMAIL_RE = re.compile(r'^[^@\s]+@[^@\s]+\.[^@\s]+$')
|
||||
GITHUB_USERNAME_RE = re.compile(r'^[A-Za-z0-9](?:[A-Za-z0-9]|-(?=[A-Za-z0-9])){0,38}$')
|
||||
|
||||
def format_contributor_links(contributor_field):
|
||||
"""Format contributors as GitHub profile links"""
|
||||
if not contributor_field:
|
||||
return '@anonymous'
|
||||
|
||||
contributors = [c.strip() for c in contributor_field.split(',') if c.strip()]
|
||||
if not contributors:
|
||||
return '@anonymous'
|
||||
|
||||
return ', '.join([f'[@{c}](https://github.com/{c})' for c in contributors])
|
||||
|
||||
links = []
|
||||
for contributor in contributors:
|
||||
normalized = contributor.lstrip('@')
|
||||
if EMAIL_RE.match(normalized):
|
||||
links.append('@anonymous')
|
||||
elif GITHUB_USERNAME_RE.match(normalized):
|
||||
links.append(f'[@{normalized}](https://github.com/{normalized})')
|
||||
elif normalized:
|
||||
links.append(f'@{normalized}')
|
||||
return ', '.join(links) if links else '@anonymous'
|
||||
|
||||
def generate_prompt_block(row):
|
||||
"""Generate a single prompt's <details> block"""
|
||||
act = row.get('act', 'Untitled')
|
||||
prompt = row.get('prompt', '')
|
||||
contributor = row.get('contributor', '')
|
||||
prompt_type = row.get('type', 'TEXT').upper()
|
||||
|
||||
# Determine code block language based on type
|
||||
if prompt_type == 'TEXT':
|
||||
lang = 'md'
|
||||
elif prompt_type == 'JSON':
|
||||
lang = 'json'
|
||||
elif prompt_type == 'YAML':
|
||||
lang = 'yaml'
|
||||
else:
|
||||
lang = 'md'
|
||||
|
||||
contributor_links = format_contributor_links(contributor)
|
||||
|
||||
block = f'<details>\n'
|
||||
block += f'<summary><strong>{act}</strong></summary>\n\n'
|
||||
block += f'## {act}\n\n'
|
||||
block += f'Contributed by {contributor_links}\n\n'
|
||||
block += f'```{lang}\n'
|
||||
block += f'{prompt}\n'
|
||||
block += f'```\n\n'
|
||||
block += f'</details>\n\n'
|
||||
return block
|
||||
prompt_type = (row.get('type') or 'TEXT').upper()
|
||||
|
||||
def init_prompts_md(prompts_md_path):
|
||||
"""Initialize PROMPTS.md with header if it doesn't exist"""
|
||||
lang = {'TEXT': 'md', 'JSON': 'json', 'YAML': 'yaml'}.get(prompt_type, 'md')
|
||||
contributor_links = format_contributor_links(contributor)
|
||||
|
||||
return (
|
||||
f'<details>\n'
|
||||
f'<summary><strong>{act}</strong></summary>\n\n'
|
||||
f'## {act}\n\n'
|
||||
f'Contributed by {contributor_links}\n\n'
|
||||
f'```{lang}\n'
|
||||
f'{prompt}\n'
|
||||
f'```\n\n'
|
||||
f'</details>\n\n'
|
||||
)
|
||||
|
||||
def init_prompts_md():
|
||||
if not os.path.exists(prompts_md_path):
|
||||
with open(prompts_md_path, 'w', encoding='utf-8') as f:
|
||||
f.write('# prompts.chat\n\n')
|
||||
f.write('> A curated list of prompts for ChatGPT and other AI models.\n\n')
|
||||
f.write('---\n\n')
|
||||
|
||||
def append_prompt_to_md(row, prompts_md_path):
|
||||
"""Append a new prompt block to PROMPTS.md"""
|
||||
init_prompts_md(prompts_md_path)
|
||||
block = generate_prompt_block(row)
|
||||
def append_prompt_to_md(row):
|
||||
init_prompts_md()
|
||||
with open(prompts_md_path, 'a', encoding='utf-8') as f:
|
||||
f.write(block)
|
||||
f.write(generate_prompt_block(row))
|
||||
|
||||
def update_prompt_in_md(row, prompts_md_path):
|
||||
"""Update an existing prompt's block in PROMPTS.md"""
|
||||
def update_prompt_in_md(row):
|
||||
act = row.get('act', '')
|
||||
if not os.path.exists(prompts_md_path):
|
||||
append_prompt_to_md(row, prompts_md_path)
|
||||
append_prompt_to_md(row)
|
||||
return
|
||||
|
||||
with open(prompts_md_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# Find and replace the specific prompt block using regex
|
||||
import re
|
||||
# Pattern to match the entire <details> block for this prompt
|
||||
pattern = rf'<details>\n<summary><strong>{re.escape(act)}</strong></summary>.*?</details>\n\n'
|
||||
new_block = generate_prompt_block(row)
|
||||
|
||||
new_content, count = re.subn(pattern, new_block, content, flags=re.DOTALL)
|
||||
|
||||
new_content, count = re.subn(pattern, generate_prompt_block(row), content, flags=re.DOTALL)
|
||||
if count > 0:
|
||||
with open(prompts_md_path, 'w', encoding='utf-8') as f:
|
||||
f.write(new_content)
|
||||
else:
|
||||
# Prompt not found, append it
|
||||
append_prompt_to_md(row, prompts_md_path)
|
||||
append_prompt_to_md(row)
|
||||
|
||||
def remove_prompt_from_md(act, prompts_md_path):
|
||||
"""Remove a prompt's block from PROMPTS.md"""
|
||||
def remove_prompt_from_md(act):
|
||||
if not os.path.exists(prompts_md_path):
|
||||
return
|
||||
|
||||
with open(prompts_md_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
import re
|
||||
pattern = rf'<details>\n<summary><strong>{re.escape(act)}</strong></summary>.*?</details>\n\n'
|
||||
new_content = re.sub(pattern, '', content, flags=re.DOTALL)
|
||||
|
||||
with open(prompts_md_path, 'w', encoding='utf-8') as f:
|
||||
f.write(new_content)
|
||||
|
||||
prompts_md_path = os.path.join(project_dir, 'PROMPTS.md')
|
||||
# --- Main ---
|
||||
|
||||
local_rows, local_index, fieldnames, skipped_l = read_csv(csv_file)
|
||||
remote_rows, remote_index, remote_fieldnames, skipped_r = read_csv(remote_csv)
|
||||
|
||||
if not fieldnames:
|
||||
fieldnames = remote_fieldnames
|
||||
|
||||
print(f"Local: {len(local_rows)} prompts" + (f" (skipped {skipped_l})" if skipped_l else ""))
|
||||
print(f"Remote: {len(remote_rows)} prompts" + (f" (skipped {skipped_r})" if skipped_r else ""))
|
||||
|
||||
# Compute diffs
|
||||
remote_acts = set(remote_index.keys())
|
||||
new_prompts = []
|
||||
updated_prompts = []
|
||||
deleted_prompts = []
|
||||
|
||||
for row in remote_rows:
|
||||
act = (row.get('act') or '').strip()
|
||||
if not act:
|
||||
continue
|
||||
if act not in local_index:
|
||||
new_prompts.append(row)
|
||||
else:
|
||||
local_row = local_rows[local_index[act]]
|
||||
if (normalize(row.get('prompt')) != normalize(local_row.get('prompt')) or
|
||||
normalize(row.get('contributor')) != normalize(local_row.get('contributor'))):
|
||||
updated_prompts.append(row)
|
||||
|
||||
for act in list(local_index):
|
||||
if act not in remote_acts:
|
||||
deleted_prompts.append(local_rows[local_index[act]])
|
||||
|
||||
print(f"\nNew: {len(new_prompts)}, Updated: {len(updated_prompts)}, Deleted: {len(deleted_prompts)}")
|
||||
|
||||
if not new_prompts and not updated_prompts and not deleted_prompts:
|
||||
print("\nNo CSV changes detected. Already up to date!")
|
||||
else:
|
||||
# Process updates one at a time (apply and commit each update separately)
|
||||
if updated_prompts:
|
||||
print("\nApplying updates to existing prompts...")
|
||||
|
||||
for i, (remote_row, local_row) in enumerate(updated_prompts, 1):
|
||||
act = remote_row.get('act', '').strip()
|
||||
contributor_field = remote_row.get('contributor', '').strip()
|
||||
|
||||
# Update this specific prompt in local_prompts
|
||||
local_prompts[act] = remote_row
|
||||
|
||||
# Rewrite the CSV with this update applied
|
||||
with open(csv_file, 'w', newline='') as f:
|
||||
writer = csv.DictWriter(f, fieldnames=fieldnames)
|
||||
writer.writeheader()
|
||||
# Write in order of remote (to maintain order)
|
||||
for row in remote_prompts:
|
||||
row_act = row.get('act', '').strip()
|
||||
if row_act in local_prompts:
|
||||
writer.writerow(local_prompts[row_act])
|
||||
|
||||
# Update only this prompt's block in PROMPTS.md
|
||||
update_prompt_in_md(remote_row, prompts_md_path)
|
||||
|
||||
primary_author, co_authors = parse_contributors(contributor_field)
|
||||
email = f"{primary_author}@users.noreply.github.com"
|
||||
|
||||
subprocess.run(['git', 'add', csv_file, prompts_md_path], check=True)
|
||||
|
||||
# Check if there are actual changes to commit
|
||||
diff_result = subprocess.run(['git', 'diff', '--cached', '--quiet'], capture_output=True)
|
||||
if diff_result.returncode == 0:
|
||||
# No changes staged, skip this commit
|
||||
print(f"[UPDATE {i}/{len(updated_prompts)}] {act} - no changes, skipping")
|
||||
continue
|
||||
|
||||
env = os.environ.copy()
|
||||
env['GIT_AUTHOR_NAME'] = primary_author
|
||||
env['GIT_AUTHOR_EMAIL'] = email
|
||||
env['GIT_COMMITTER_NAME'] = primary_author
|
||||
env['GIT_COMMITTER_EMAIL'] = email
|
||||
|
||||
commit_msg = build_commit_message('Update', act, co_authors)
|
||||
|
||||
subprocess.run([
|
||||
'git', 'commit',
|
||||
'-m', commit_msg,
|
||||
f'--author={primary_author} <{email}>'
|
||||
], env=env, check=True)
|
||||
|
||||
co_authors_str = f" (+ {', '.join(co_authors)})" if co_authors else ""
|
||||
print(f"[UPDATE {i}/{len(updated_prompts)}] {primary_author}{co_authors_str}: {act}")
|
||||
|
||||
# Process new prompts
|
||||
if new_prompts:
|
||||
print("\nCreating commits for new prompts...")
|
||||
|
||||
for i, row in enumerate(new_prompts, 1):
|
||||
contributor_field = row.get('contributor', '').strip()
|
||||
act = row.get('act', 'Unknown')
|
||||
|
||||
primary_author, co_authors = parse_contributors(contributor_field)
|
||||
email = f"{primary_author}@users.noreply.github.com"
|
||||
|
||||
# Append this row to the CSV
|
||||
with open(csv_file, 'a', newline='') as f:
|
||||
writer = csv.DictWriter(f, fieldnames=fieldnames)
|
||||
writer.writerow(row)
|
||||
|
||||
# Track in local_prompts
|
||||
local_prompts[act] = row
|
||||
|
||||
# Append only this prompt's block to PROMPTS.md
|
||||
append_prompt_to_md(row, prompts_md_path)
|
||||
|
||||
# Stage and commit
|
||||
subprocess.run(['git', 'add', csv_file, prompts_md_path], check=True)
|
||||
|
||||
# Check if there are actual changes to commit
|
||||
diff_result = subprocess.run(['git', 'diff', '--cached', '--quiet'], capture_output=True)
|
||||
if diff_result.returncode == 0:
|
||||
print(f"[NEW {i}/{len(new_prompts)}] {act} - no changes, skipping")
|
||||
continue
|
||||
|
||||
env = os.environ.copy()
|
||||
env['GIT_AUTHOR_NAME'] = primary_author
|
||||
env['GIT_AUTHOR_EMAIL'] = email
|
||||
env['GIT_COMMITTER_NAME'] = primary_author
|
||||
env['GIT_COMMITTER_EMAIL'] = email
|
||||
|
||||
commit_msg = build_commit_message('Add', act, co_authors)
|
||||
|
||||
subprocess.run([
|
||||
'git', 'commit',
|
||||
'-m', commit_msg,
|
||||
f'--author={primary_author} <{email}>'
|
||||
], env=env, check=True)
|
||||
|
||||
co_authors_str = f" (+ {', '.join(co_authors)})" if co_authors else ""
|
||||
print(f"[NEW {i}/{len(new_prompts)}] {primary_author}{co_authors_str}: {act}")
|
||||
|
||||
# Process deleted prompts (remove from CSV, commit with original author)
|
||||
if deleted_prompts:
|
||||
print("\nRemoving unlisted/deleted prompts...")
|
||||
|
||||
for i, row in enumerate(deleted_prompts, 1):
|
||||
contributor_field = row.get('contributor', '').strip()
|
||||
act = row.get('act', 'Unknown')
|
||||
|
||||
primary_author, co_authors = parse_contributors(contributor_field)
|
||||
email = f"{primary_author}@users.noreply.github.com"
|
||||
|
||||
# Remove this prompt from local_prompts
|
||||
if act in local_prompts:
|
||||
del local_prompts[act]
|
||||
|
||||
# Rewrite CSV without the deleted prompt
|
||||
with open(csv_file, 'w', newline='') as f:
|
||||
writer = csv.DictWriter(f, fieldnames=fieldnames)
|
||||
writer.writeheader()
|
||||
for remaining_act, remaining_row in local_prompts.items():
|
||||
writer.writerow(remaining_row)
|
||||
|
||||
# Remove only this prompt's block from PROMPTS.md
|
||||
remove_prompt_from_md(act, prompts_md_path)
|
||||
|
||||
# Stage and commit
|
||||
subprocess.run(['git', 'add', csv_file, prompts_md_path], check=True)
|
||||
|
||||
# Check if there are actual changes to commit
|
||||
diff_result = subprocess.run(['git', 'diff', '--cached', '--quiet'], capture_output=True)
|
||||
if diff_result.returncode == 0:
|
||||
print(f"[REMOVE {i}/{len(deleted_prompts)}] {act} - no changes, skipping")
|
||||
continue
|
||||
|
||||
env = os.environ.copy()
|
||||
env['GIT_AUTHOR_NAME'] = primary_author
|
||||
env['GIT_AUTHOR_EMAIL'] = email
|
||||
env['GIT_COMMITTER_NAME'] = primary_author
|
||||
env['GIT_COMMITTER_EMAIL'] = email
|
||||
|
||||
commit_msg = build_commit_message('Remove', act, co_authors)
|
||||
|
||||
subprocess.run([
|
||||
'git', 'commit',
|
||||
'-m', commit_msg,
|
||||
f'--author={primary_author} <{email}>'
|
||||
], env=env, check=True)
|
||||
|
||||
co_authors_str = f" (+ {', '.join(co_authors)})" if co_authors else ""
|
||||
print(f"[REMOVE {i}/{len(deleted_prompts)}] {primary_author}{co_authors_str}: {act}")
|
||||
|
||||
print(f"\nDone! Created {len(new_prompts)} new, {len(updated_prompts)} update, {len(deleted_prompts)} remove commits.")
|
||||
print("Already up to date!")
|
||||
sys.exit(0)
|
||||
|
||||
counts = {'add': 0, 'update': 0, 'remove': 0}
|
||||
|
||||
# 1) Deletes — remove one row at a time, keeping other rows untouched
|
||||
if deleted_prompts:
|
||||
print("\nRemoving unlisted/deleted prompts...")
|
||||
for i, row in enumerate(deleted_prompts, 1):
|
||||
act = (row.get('act') or '').strip()
|
||||
contributor = (row.get('contributor') or '').strip()
|
||||
primary, coauthors = parse_contributors(contributor)
|
||||
email = make_email(primary)
|
||||
|
||||
if act in local_index:
|
||||
local_rows.pop(local_index[act])
|
||||
local_index = rebuild_index(local_rows)
|
||||
|
||||
remove_prompt_from_md(act)
|
||||
write_csv(local_rows, fieldnames, csv_file)
|
||||
|
||||
msg = build_commit_msg('Remove', act, coauthors)
|
||||
if git_commit(primary, email, msg):
|
||||
ca = f" (+ {', '.join(coauthors)})" if coauthors else ""
|
||||
print(f" [{i}/{len(deleted_prompts)}] {primary}{ca}: {act}")
|
||||
counts['remove'] += 1
|
||||
else:
|
||||
print(f" [{i}/{len(deleted_prompts)}] {act} — no changes, skipping")
|
||||
|
||||
# 2) Updates — replace only the specific row in-place
|
||||
if updated_prompts:
|
||||
print("\nUpdating existing prompts...")
|
||||
for i, row in enumerate(updated_prompts, 1):
|
||||
act = (row.get('act') or '').strip()
|
||||
contributor = (row.get('contributor') or '').strip()
|
||||
primary, coauthors = parse_contributors(contributor)
|
||||
email = make_email(primary)
|
||||
|
||||
if act in local_index:
|
||||
local_rows[local_index[act]] = row
|
||||
|
||||
update_prompt_in_md(row)
|
||||
write_csv(local_rows, fieldnames, csv_file)
|
||||
|
||||
msg = build_commit_msg('Update', act, coauthors)
|
||||
if git_commit(primary, email, msg):
|
||||
ca = f" (+ {', '.join(coauthors)})" if coauthors else ""
|
||||
print(f" [{i}/{len(updated_prompts)}] {primary}{ca}: {act}")
|
||||
counts['update'] += 1
|
||||
else:
|
||||
print(f" [{i}/{len(updated_prompts)}] {act} — no changes, skipping")
|
||||
|
||||
# 3) New prompts — append one at a time
|
||||
if new_prompts:
|
||||
print("\nAdding new prompts...")
|
||||
for i, row in enumerate(new_prompts, 1):
|
||||
act = (row.get('act') or '').strip()
|
||||
contributor = (row.get('contributor') or '').strip()
|
||||
primary, coauthors = parse_contributors(contributor)
|
||||
email = make_email(primary)
|
||||
|
||||
local_rows.append(row)
|
||||
local_index[act] = len(local_rows) - 1
|
||||
|
||||
append_prompt_to_md(row)
|
||||
write_csv(local_rows, fieldnames, csv_file)
|
||||
|
||||
msg = build_commit_msg('Add', act, coauthors)
|
||||
if git_commit(primary, email, msg):
|
||||
ca = f" (+ {', '.join(coauthors)})" if coauthors else ""
|
||||
print(f" [{i}/{len(new_prompts)}] {primary}{ca}: {act}")
|
||||
counts['add'] += 1
|
||||
else:
|
||||
print(f" [{i}/{len(new_prompts)}] {act} — no changes, skipping")
|
||||
|
||||
print(f"\nDone! {counts['add']} added, {counts['update']} updated, {counts['remove']} removed.")
|
||||
|
||||
PYTHON_SCRIPT
|
||||
PYTHON_EXIT=$?
|
||||
|
||||
@@ -12,7 +12,7 @@ Sentry.init({
|
||||
enabled: process.env.NODE_ENV === "production",
|
||||
|
||||
// Define how likely traces are sampled. Adjust this value in production, or use tracesSampler for greater control.
|
||||
tracesSampleRate: 1,
|
||||
tracesSampleRate: 0.1,
|
||||
|
||||
// Enable logs to be sent to Sentry
|
||||
enableLogs: true,
|
||||
|
||||
@@ -11,7 +11,7 @@ Sentry.init({
|
||||
enabled: process.env.NODE_ENV === "production",
|
||||
|
||||
// Define how likely traces are sampled. Adjust this value in production, or use tracesSampler for greater control.
|
||||
tracesSampleRate: 1,
|
||||
tracesSampleRate: 0.1,
|
||||
|
||||
// Enable logs to be sent to Sentry
|
||||
enableLogs: true,
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
// Mock all heavy dependencies before importing handler
|
||||
vi.mock("@/lib/db", () => ({
|
||||
db: {
|
||||
user: { findUnique: vi.fn() },
|
||||
prompt: { findMany: vi.fn(), findFirst: vi.fn(), create: vi.fn() },
|
||||
tag: { findUnique: vi.fn(), create: vi.fn() },
|
||||
$queryRaw: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/rate-limit", () => ({
|
||||
mcpGeneralLimiter: { check: vi.fn().mockReturnValue({ allowed: true }) },
|
||||
mcpToolCallLimiter: { check: vi.fn().mockReturnValue({ allowed: true }) },
|
||||
mcpWriteToolLimiter: { check: vi.fn().mockReturnValue({ allowed: true }) },
|
||||
mcpAiToolLimiter: { check: vi.fn().mockReturnValue({ allowed: true }) },
|
||||
}));
|
||||
|
||||
vi.mock("@/../prompts.config", () => ({
|
||||
default: {
|
||||
features: { mcp: true },
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/api-key", () => ({
|
||||
isValidApiKeyFormat: vi.fn().mockReturnValue(false),
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/skill-files", () => ({
|
||||
parseSkillFiles: vi.fn(),
|
||||
serializeSkillFiles: vi.fn(),
|
||||
sanitizeFilename: vi.fn(),
|
||||
DEFAULT_SKILL_FILE: "main.md",
|
||||
}));
|
||||
|
||||
function createMockReq(method: string, overrides: Partial<NextApiRequest> = {}): NextApiRequest {
|
||||
return {
|
||||
method,
|
||||
headers: {},
|
||||
url: "/api/mcp",
|
||||
socket: { remoteAddress: "127.0.0.1" },
|
||||
...overrides,
|
||||
} as unknown as NextApiRequest;
|
||||
}
|
||||
|
||||
function createMockRes(): NextApiResponse & {
|
||||
_status: number;
|
||||
_json: unknown;
|
||||
_headers: Record<string, string>;
|
||||
_ended: boolean;
|
||||
} {
|
||||
const res = {
|
||||
_status: 200,
|
||||
_json: null,
|
||||
_headers: {} as Record<string, string>,
|
||||
_ended: false,
|
||||
status(code: number) {
|
||||
res._status = code;
|
||||
return res;
|
||||
},
|
||||
json(data: unknown) {
|
||||
res._json = data;
|
||||
return res;
|
||||
},
|
||||
end() {
|
||||
res._ended = true;
|
||||
return res;
|
||||
},
|
||||
setHeader(key: string, value: string) {
|
||||
res._headers[key] = value;
|
||||
return res;
|
||||
},
|
||||
getHeader(key: string) {
|
||||
return res._headers[key];
|
||||
},
|
||||
headersSent: false,
|
||||
on: vi.fn(),
|
||||
};
|
||||
return res as unknown as NextApiResponse & typeof res;
|
||||
}
|
||||
|
||||
describe("MCP API handler - HTTP method routing", () => {
|
||||
let handler: (req: NextApiRequest, res: NextApiResponse) => Promise<void>;
|
||||
|
||||
beforeEach(async () => {
|
||||
vi.clearAllMocks();
|
||||
// Dynamic import to pick up mocks
|
||||
const mod = await import("@/pages/api/mcp");
|
||||
handler = mod.default;
|
||||
});
|
||||
|
||||
describe("GET requests", () => {
|
||||
it("should return 405 Method Not Allowed per MCP Streamable HTTP spec", async () => {
|
||||
const req = createMockReq("GET");
|
||||
const res = createMockRes();
|
||||
|
||||
await handler(req, res);
|
||||
|
||||
expect(res._status).toBe(405);
|
||||
});
|
||||
|
||||
it("should return JSON-RPC error body", async () => {
|
||||
const req = createMockReq("GET");
|
||||
const res = createMockRes();
|
||||
|
||||
await handler(req, res);
|
||||
|
||||
expect(res._json).toEqual({
|
||||
jsonrpc: "2.0",
|
||||
error: expect.objectContaining({
|
||||
code: -32000,
|
||||
message: expect.stringContaining("Method not allowed"),
|
||||
}),
|
||||
id: null,
|
||||
});
|
||||
});
|
||||
|
||||
it("should set Cache-Control: no-store to prevent caching stale 405s", async () => {
|
||||
const req = createMockReq("GET");
|
||||
const res = createMockRes();
|
||||
|
||||
await handler(req, res);
|
||||
|
||||
expect(res._headers["Cache-Control"]).toBe("no-store");
|
||||
});
|
||||
});
|
||||
|
||||
describe("DELETE requests", () => {
|
||||
it("should return 204 No Content", async () => {
|
||||
const req = createMockReq("DELETE");
|
||||
const res = createMockRes();
|
||||
|
||||
await handler(req, res);
|
||||
|
||||
expect(res._status).toBe(204);
|
||||
expect(res._ended).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("unsupported methods", () => {
|
||||
it("should return 405 for PUT", async () => {
|
||||
const req = createMockReq("PUT");
|
||||
const res = createMockRes();
|
||||
|
||||
await handler(req, res);
|
||||
|
||||
expect(res._status).toBe(405);
|
||||
});
|
||||
|
||||
it("should return 405 for PATCH", async () => {
|
||||
const req = createMockReq("PATCH");
|
||||
const res = createMockRes();
|
||||
|
||||
await handler(req, res);
|
||||
|
||||
expect(res._status).toBe(405);
|
||||
});
|
||||
});
|
||||
|
||||
describe("MCP disabled", () => {
|
||||
it("should return 404 when MCP feature is disabled", async () => {
|
||||
// Re-mock config with mcp disabled
|
||||
vi.doMock("@/../prompts.config", () => ({
|
||||
default: { features: { mcp: false } },
|
||||
}));
|
||||
|
||||
// Re-import to get new mock
|
||||
vi.resetModules();
|
||||
const mod = await import("@/pages/api/mcp");
|
||||
const disabledHandler = mod.default;
|
||||
|
||||
const req = createMockReq("GET");
|
||||
const res = createMockRes();
|
||||
|
||||
await disabledHandler(req, res);
|
||||
|
||||
expect(res._status).toBe(404);
|
||||
|
||||
// Restore original mock
|
||||
vi.doMock("@/../prompts.config", () => ({
|
||||
default: { features: { mcp: true } },
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,5 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { POST } from "@/app/api/auth/register/route";
|
||||
import { db } from "@/lib/db";
|
||||
import { getConfig } from "@/lib/config";
|
||||
@@ -158,14 +159,15 @@ describe("POST /api/auth/register", () => {
|
||||
});
|
||||
|
||||
describe("duplicate checks", () => {
|
||||
it("should return 400 when email already exists", async () => {
|
||||
// Mock: email check finds existing user
|
||||
vi.mocked(db.user.findUnique).mockImplementation(async (args) => {
|
||||
if (args?.where?.email) {
|
||||
return { id: "1", email: "test@example.com" } as never;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
it("should return 409 when email already exists", async () => {
|
||||
// Mock: create throws P2002 unique violation on email
|
||||
vi.mocked(db.user.create).mockRejectedValue(
|
||||
new Prisma.PrismaClientKnownRequestError("Unique constraint failed", {
|
||||
code: "P2002",
|
||||
meta: { target: ["email"] },
|
||||
clientVersion: "5.0.0",
|
||||
})
|
||||
);
|
||||
|
||||
const request = createRequest({
|
||||
name: "Test User",
|
||||
@@ -177,14 +179,43 @@ describe("POST /api/auth/register", () => {
|
||||
const response = await POST(request);
|
||||
const data = await response.json();
|
||||
|
||||
expect(response.status).toBe(400);
|
||||
expect(response.status).toBe(409);
|
||||
expect(data.error).toBe("email_taken");
|
||||
});
|
||||
|
||||
it("should return 400 when username already exists", async () => {
|
||||
// Mock: email check passes, username check (case-insensitive via findFirst) finds existing user
|
||||
vi.mocked(db.user.findUnique).mockResolvedValue(null);
|
||||
vi.mocked(db.user.findFirst).mockResolvedValue({ id: "1", username: "testuser" } as never);
|
||||
it("should return 409 for case-insensitive email collision", async () => {
|
||||
// Mock: create throws P2002 unique violation on CI email index
|
||||
vi.mocked(db.user.create).mockRejectedValue(
|
||||
new Prisma.PrismaClientKnownRequestError("Unique constraint failed", {
|
||||
code: "P2002",
|
||||
meta: { target: ["users_email_ci_unique"] },
|
||||
clientVersion: "5.0.0",
|
||||
})
|
||||
);
|
||||
|
||||
const request = createRequest({
|
||||
name: "Test User",
|
||||
username: "testuser",
|
||||
email: "Test@Example.COM",
|
||||
password: "password123",
|
||||
});
|
||||
|
||||
const response = await POST(request);
|
||||
const data = await response.json();
|
||||
|
||||
expect(response.status).toBe(409);
|
||||
expect(data.error).toBe("email_taken");
|
||||
});
|
||||
|
||||
it("should return 409 when username already exists", async () => {
|
||||
// Mock: create throws P2002 unique violation on username
|
||||
vi.mocked(db.user.create).mockRejectedValue(
|
||||
new Prisma.PrismaClientKnownRequestError("Unique constraint failed", {
|
||||
code: "P2002",
|
||||
meta: { target: ["username"] },
|
||||
clientVersion: "5.0.0",
|
||||
})
|
||||
);
|
||||
|
||||
const request = createRequest({
|
||||
name: "Test User",
|
||||
@@ -196,15 +227,52 @@ describe("POST /api/auth/register", () => {
|
||||
const response = await POST(request);
|
||||
const data = await response.json();
|
||||
|
||||
expect(response.status).toBe(409);
|
||||
expect(data.error).toBe("username_taken");
|
||||
});
|
||||
|
||||
it("should return 400 for uppercase username", async () => {
|
||||
const request = createRequest({
|
||||
name: "Test User",
|
||||
username: "TestUser",
|
||||
email: "test@example.com",
|
||||
password: "password123",
|
||||
});
|
||||
|
||||
const response = await POST(request);
|
||||
const data = await response.json();
|
||||
|
||||
expect(response.status).toBe(400);
|
||||
expect(data.error).toBe("validation_error");
|
||||
});
|
||||
|
||||
it("should return 409 for case-insensitive username collision", async () => {
|
||||
// Mock: create throws P2002 unique violation on CI username index
|
||||
vi.mocked(db.user.create).mockRejectedValue(
|
||||
new Prisma.PrismaClientKnownRequestError("Unique constraint failed", {
|
||||
code: "P2002",
|
||||
meta: { target: ["users_username_ci_unique"] },
|
||||
clientVersion: "5.0.0",
|
||||
})
|
||||
);
|
||||
|
||||
const request = createRequest({
|
||||
name: "Test User",
|
||||
username: "testuser",
|
||||
email: "test@example.com",
|
||||
password: "password123",
|
||||
});
|
||||
|
||||
const response = await POST(request);
|
||||
const data = await response.json();
|
||||
|
||||
expect(response.status).toBe(409);
|
||||
expect(data.error).toBe("username_taken");
|
||||
});
|
||||
});
|
||||
|
||||
describe("successful registration", () => {
|
||||
it("should create user and return user data", async () => {
|
||||
vi.mocked(db.user.findUnique).mockResolvedValue(null);
|
||||
vi.mocked(db.user.findFirst).mockResolvedValue(null);
|
||||
vi.mocked(db.user.create).mockResolvedValue({
|
||||
id: "user-123",
|
||||
name: "Test User",
|
||||
@@ -238,9 +306,44 @@ describe("POST /api/auth/register", () => {
|
||||
expect(data.password).toBeUndefined(); // Password should not be returned
|
||||
});
|
||||
|
||||
it("should lowercase and trim email and username before saving", async () => {
|
||||
vi.mocked(db.user.create).mockResolvedValue({
|
||||
id: "user-123",
|
||||
name: "Test User",
|
||||
username: "testuser",
|
||||
email: "test@example.com",
|
||||
password: "hashed_password",
|
||||
emailVerified: null,
|
||||
image: null,
|
||||
role: "USER",
|
||||
bio: null,
|
||||
credits: 0,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
});
|
||||
|
||||
const request = createRequest({
|
||||
name: " Test User ",
|
||||
username: " testuser ",
|
||||
email: " Test@Example.COM ",
|
||||
password: "password123",
|
||||
});
|
||||
|
||||
const response = await POST(request);
|
||||
expect(response.status).toBe(200);
|
||||
|
||||
expect(db.user.create).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
data: expect.objectContaining({
|
||||
name: "Test User",
|
||||
username: "testuser",
|
||||
email: "test@example.com",
|
||||
}),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("should accept valid username with underscores", async () => {
|
||||
vi.mocked(db.user.findUnique).mockResolvedValue(null);
|
||||
vi.mocked(db.user.findFirst).mockResolvedValue(null);
|
||||
vi.mocked(db.user.create).mockResolvedValue({
|
||||
id: "user-123",
|
||||
name: "Test User",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { GET, PATCH } from "@/app/api/user/profile/route";
|
||||
import { db } from "@/lib/db";
|
||||
import { auth } from "@/lib/auth";
|
||||
@@ -8,6 +9,7 @@ vi.mock("@/lib/db", () => ({
|
||||
db: {
|
||||
user: {
|
||||
findUnique: vi.fn(),
|
||||
findFirst: vi.fn(),
|
||||
update: vi.fn(),
|
||||
},
|
||||
},
|
||||
@@ -160,9 +162,15 @@ describe("PATCH /api/user/profile", () => {
|
||||
expect(data.error).toBe("validation_error");
|
||||
});
|
||||
|
||||
it("should return 400 if username is taken", async () => {
|
||||
it("should return 409 if username is taken", async () => {
|
||||
vi.mocked(auth).mockResolvedValue({ user: { id: "user1", username: "olduser" } } as never);
|
||||
vi.mocked(db.user.findUnique).mockResolvedValue({ id: "other-user" } as never);
|
||||
vi.mocked(db.user.update).mockRejectedValue(
|
||||
new Prisma.PrismaClientKnownRequestError("Unique constraint failed", {
|
||||
code: "P2002",
|
||||
meta: { target: ["username"] },
|
||||
clientVersion: "5.0.0",
|
||||
})
|
||||
);
|
||||
|
||||
const request = new Request("http://localhost:3000/api/user/profile", {
|
||||
method: "PATCH",
|
||||
@@ -172,7 +180,29 @@ describe("PATCH /api/user/profile", () => {
|
||||
const response = await PATCH(request);
|
||||
const data = await response.json();
|
||||
|
||||
expect(response.status).toBe(400);
|
||||
expect(response.status).toBe(409);
|
||||
expect(data.error).toBe("username_taken");
|
||||
});
|
||||
|
||||
it("should return 409 for case-insensitive username collision", async () => {
|
||||
vi.mocked(auth).mockResolvedValue({ user: { id: "user1", username: "olduser" } } as never);
|
||||
vi.mocked(db.user.update).mockRejectedValue(
|
||||
new Prisma.PrismaClientKnownRequestError("Unique constraint failed", {
|
||||
code: "P2002",
|
||||
meta: { target: ["users_username_ci_unique"] },
|
||||
clientVersion: "5.0.0",
|
||||
})
|
||||
);
|
||||
|
||||
const request = new Request("http://localhost:3000/api/user/profile", {
|
||||
method: "PATCH",
|
||||
body: JSON.stringify({ name: "Test", username: "takenuser" }),
|
||||
});
|
||||
|
||||
const response = await PATCH(request);
|
||||
const data = await response.json();
|
||||
|
||||
expect(response.status).toBe(409);
|
||||
expect(data.error).toBe("username_taken");
|
||||
});
|
||||
|
||||
@@ -196,13 +226,12 @@ describe("PATCH /api/user/profile", () => {
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(data.name).toBe("Updated Name");
|
||||
// Should NOT check for existing username when keeping the same one
|
||||
expect(db.user.findUnique).not.toHaveBeenCalled();
|
||||
// No pre-check needed — uniqueness enforced at DB level
|
||||
expect(db.user.update).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should update profile successfully", async () => {
|
||||
vi.mocked(auth).mockResolvedValue({ user: { id: "user1", username: "olduser" } } as never);
|
||||
vi.mocked(db.user.findUnique).mockResolvedValue(null); // Username not taken
|
||||
vi.mocked(db.user.update).mockResolvedValue({
|
||||
id: "user1",
|
||||
name: "New Name",
|
||||
@@ -318,7 +347,7 @@ describe("PATCH /api/user/profile", () => {
|
||||
|
||||
it("should accept valid username with underscores", async () => {
|
||||
vi.mocked(auth).mockResolvedValue({ user: { id: "user1", username: "old" } } as never);
|
||||
vi.mocked(db.user.findUnique).mockResolvedValue(null);
|
||||
vi.mocked(db.user.findFirst).mockResolvedValue(null);
|
||||
vi.mocked(db.user.update).mockResolvedValue({
|
||||
id: "user1",
|
||||
name: "Test",
|
||||
|
||||
@@ -29,6 +29,10 @@ describe("GET /api/prompts/[id]/versions", () => {
|
||||
});
|
||||
|
||||
it("should return empty array for prompt with no versions", async () => {
|
||||
vi.mocked(db.prompt.findUnique).mockResolvedValue({
|
||||
isPrivate: false,
|
||||
authorId: "user1",
|
||||
} as never);
|
||||
vi.mocked(db.promptVersion.findMany).mockResolvedValue([]);
|
||||
|
||||
const request = new Request("http://localhost:3000/api/prompts/123/versions");
|
||||
@@ -42,6 +46,10 @@ describe("GET /api/prompts/[id]/versions", () => {
|
||||
});
|
||||
|
||||
it("should return versions ordered by version desc", async () => {
|
||||
vi.mocked(db.prompt.findUnique).mockResolvedValue({
|
||||
isPrivate: false,
|
||||
authorId: "user1",
|
||||
} as never);
|
||||
vi.mocked(db.promptVersion.findMany).mockResolvedValue([
|
||||
{
|
||||
id: "v3",
|
||||
@@ -83,6 +91,10 @@ describe("GET /api/prompts/[id]/versions", () => {
|
||||
});
|
||||
|
||||
it("should include author info in response", async () => {
|
||||
vi.mocked(db.prompt.findUnique).mockResolvedValue({
|
||||
isPrivate: false,
|
||||
authorId: "user1",
|
||||
} as never);
|
||||
vi.mocked(db.promptVersion.findMany).mockResolvedValue([
|
||||
{
|
||||
id: "v1",
|
||||
@@ -106,6 +118,10 @@ describe("GET /api/prompts/[id]/versions", () => {
|
||||
});
|
||||
|
||||
it("should call findMany with correct parameters", async () => {
|
||||
vi.mocked(db.prompt.findUnique).mockResolvedValue({
|
||||
isPrivate: false,
|
||||
authorId: "user1",
|
||||
} as never);
|
||||
vi.mocked(db.promptVersion.findMany).mockResolvedValue([]);
|
||||
|
||||
const request = new Request("http://localhost:3000/api/prompts/123/versions");
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
||||
import { getProviderMetadata, getConfiguredProviderIds } from "@/lib/auth/provider-metadata";
|
||||
|
||||
type ConfigParam = Parameters<typeof getConfiguredProviderIds>[0];
|
||||
|
||||
describe("Provider Metadata", () => {
|
||||
const originalEnv = process.env;
|
||||
|
||||
beforeEach(() => {
|
||||
process.env = { ...originalEnv };
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
process.env = originalEnv;
|
||||
});
|
||||
|
||||
describe("getProviderMetadata", () => {
|
||||
it("should return empty maps when no generic providers are requested", () => {
|
||||
const result = getProviderMetadata(["github", "google"]);
|
||||
expect(result.displayNames).toEqual({});
|
||||
expect(result.logos).toEqual({});
|
||||
});
|
||||
|
||||
it("should read OIDC display name and logo from env", () => {
|
||||
process.env.AUTH_OIDC_NAME = "Corporate SSO";
|
||||
process.env.AUTH_OIDC_LOGO = "https://example.com/logo.svg";
|
||||
|
||||
const result = getProviderMetadata(["github", "oidc"]);
|
||||
expect(result.displayNames).toEqual({ oidc: "Corporate SSO" });
|
||||
expect(result.logos).toEqual({ oidc: "https://example.com/logo.svg" });
|
||||
});
|
||||
|
||||
it("should read OAuth display name and logo from env", () => {
|
||||
process.env.AUTH_OAUTH_NAME = "Legacy IdP";
|
||||
process.env.AUTH_OAUTH_LOGO = "https://idp.example.com/icon.png";
|
||||
|
||||
const result = getProviderMetadata(["oauth"]);
|
||||
expect(result.displayNames).toEqual({ oauth: "Legacy IdP" });
|
||||
expect(result.logos).toEqual({ oauth: "https://idp.example.com/icon.png" });
|
||||
});
|
||||
|
||||
it("should return both OIDC and OAuth metadata when both are configured", () => {
|
||||
process.env.AUTH_OIDC_NAME = "OIDC Provider";
|
||||
process.env.AUTH_OIDC_LOGO = "https://oidc.example.com/logo.svg";
|
||||
process.env.AUTH_OAUTH_NAME = "OAuth Provider";
|
||||
process.env.AUTH_OAUTH_LOGO = "https://oauth.example.com/logo.svg";
|
||||
|
||||
const result = getProviderMetadata(["oidc", "oauth"]);
|
||||
expect(result.displayNames).toEqual({
|
||||
oidc: "OIDC Provider",
|
||||
oauth: "OAuth Provider",
|
||||
});
|
||||
expect(result.logos).toEqual({
|
||||
oidc: "https://oidc.example.com/logo.svg",
|
||||
oauth: "https://oauth.example.com/logo.svg",
|
||||
});
|
||||
});
|
||||
|
||||
it("should omit name when env var is not set", () => {
|
||||
process.env.AUTH_OIDC_LOGO = "https://example.com/logo.svg";
|
||||
// AUTH_OIDC_NAME not set
|
||||
|
||||
const result = getProviderMetadata(["oidc"]);
|
||||
expect(result.displayNames).toEqual({});
|
||||
expect(result.logos).toEqual({ oidc: "https://example.com/logo.svg" });
|
||||
});
|
||||
|
||||
it("should omit logo when env var is not set", () => {
|
||||
process.env.AUTH_OIDC_NAME = "My Provider";
|
||||
// AUTH_OIDC_LOGO not set
|
||||
|
||||
const result = getProviderMetadata(["oidc"]);
|
||||
expect(result.displayNames).toEqual({ oidc: "My Provider" });
|
||||
expect(result.logos).toEqual({});
|
||||
});
|
||||
|
||||
it("should reject logo URLs with javascript: protocol", () => {
|
||||
process.env.AUTH_OIDC_NAME = "Evil Provider";
|
||||
process.env.AUTH_OIDC_LOGO = "javascript:alert('xss')";
|
||||
|
||||
const result = getProviderMetadata(["oidc"]);
|
||||
expect(result.displayNames).toEqual({ oidc: "Evil Provider" });
|
||||
expect(result.logos).toEqual({});
|
||||
});
|
||||
|
||||
it("should reject logo URLs with data: protocol", () => {
|
||||
process.env.AUTH_OIDC_LOGO = "data:image/svg+xml,<svg></svg>";
|
||||
|
||||
const result = getProviderMetadata(["oidc"]);
|
||||
expect(result.logos).toEqual({});
|
||||
});
|
||||
|
||||
it("should accept http:// logo URLs", () => {
|
||||
process.env.AUTH_OIDC_LOGO = "http://internal.corp.com/logo.png";
|
||||
|
||||
const result = getProviderMetadata(["oidc"]);
|
||||
expect(result.logos).toEqual({ oidc: "http://internal.corp.com/logo.png" });
|
||||
});
|
||||
|
||||
it("should reject malformed URLs", () => {
|
||||
process.env.AUTH_OIDC_LOGO = "not-a-url";
|
||||
|
||||
const result = getProviderMetadata(["oidc"]);
|
||||
expect(result.logos).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
describe("getConfiguredProviderIds", () => {
|
||||
it("should return providers array when configured", () => {
|
||||
const config = {
|
||||
auth: { providers: ["github", "google", "oidc"], allowRegistration: false },
|
||||
} as unknown as ConfigParam;
|
||||
|
||||
expect(getConfiguredProviderIds(config)).toEqual(["github", "google", "oidc"]);
|
||||
});
|
||||
|
||||
it("should fallback to deprecated singular provider string if present", () => {
|
||||
const config = {
|
||||
auth: { provider: "github", allowRegistration: false },
|
||||
} as unknown as ConfigParam;
|
||||
|
||||
expect(getConfiguredProviderIds(config)).toEqual(["github"]);
|
||||
});
|
||||
|
||||
it("should default to credentials when providers array is empty", () => {
|
||||
const config = {
|
||||
auth: { providers: [], allowRegistration: false },
|
||||
} as unknown as ConfigParam;
|
||||
|
||||
expect(getConfiguredProviderIds(config)).toEqual(["credentials"]);
|
||||
});
|
||||
|
||||
it("should default to credentials when providers is not set", () => {
|
||||
const config = {
|
||||
auth: { allowRegistration: false },
|
||||
} as unknown as ConfigParam;
|
||||
|
||||
expect(getConfiguredProviderIds(config)).toEqual(["credentials"]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,132 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any -- tests inspect the dynamic NextAuth provider object */
|
||||
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
||||
import { oauthPlugin } from "@/lib/plugins/auth/oauth";
|
||||
|
||||
describe("OAuth 2.0 Auth Plugin", () => {
|
||||
const originalEnv = process.env;
|
||||
|
||||
beforeEach(() => {
|
||||
process.env = { ...originalEnv };
|
||||
process.env.AUTH_OAUTH_ID = "test-client-id";
|
||||
process.env.AUTH_OAUTH_SECRET = "test-client-secret";
|
||||
process.env.AUTH_OAUTH_ISSUER = "https://sso.test.com";
|
||||
process.env.AUTH_OAUTH_NAME = "Test OAuth";
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
process.env = originalEnv;
|
||||
});
|
||||
|
||||
it("should have correct plugin id and name", () => {
|
||||
expect(oauthPlugin.id).toBe("oauth");
|
||||
expect(oauthPlugin.name).toBe("Generic OAuth 2.0");
|
||||
});
|
||||
|
||||
it("should have PKCE enabled by default", () => {
|
||||
const provider: any = oauthPlugin.getProvider();
|
||||
expect(provider.checks).toBeUndefined(); // Enabled by default
|
||||
});
|
||||
|
||||
it("should allow disabling PKCE explicitly", () => {
|
||||
process.env.AUTH_OAUTH_ENABLE_PKCE = "false";
|
||||
const provider: any = oauthPlugin.getProvider();
|
||||
expect(provider.checks).toEqual(["state"]);
|
||||
});
|
||||
|
||||
it("should configure standard OAuth provider dynamically from env", () => {
|
||||
process.env.AUTH_OAUTH_ID = "test-client-id";
|
||||
process.env.AUTH_OAUTH_SECRET = "test-client-secret";
|
||||
process.env.AUTH_OAUTH_ISSUER = "https://sso.test.com";
|
||||
|
||||
const provider: any = oauthPlugin.getProvider();
|
||||
|
||||
expect(provider.id).toBe("oauth");
|
||||
expect(provider.type).toBe("oauth");
|
||||
expect(provider.clientId).toBe("test-client-id");
|
||||
expect(provider.clientSecret).toBe("test-client-secret");
|
||||
expect(provider.issuer).toBe("https://sso.test.com");
|
||||
|
||||
// Check fallback URLs derived from issuer
|
||||
expect(provider.authorization.url).toBe("https://sso.test.com/authorize");
|
||||
expect(provider.token).toBe("https://sso.test.com/token");
|
||||
expect(provider.userinfo).toBe("https://sso.test.com/userinfo");
|
||||
|
||||
// Check default scope (should NOT include openid for relaxed OAuth)
|
||||
expect(provider.authorization.params.scope).toBe("email profile");
|
||||
});
|
||||
|
||||
it("should use explicit URL overrides when provided", () => {
|
||||
process.env.AUTH_OAUTH_ID = "test-client-id";
|
||||
process.env.AUTH_OAUTH_SECRET = "test-client-secret";
|
||||
process.env.AUTH_OAUTH_ISSUER = "https://sso.test.com";
|
||||
|
||||
process.env.AUTH_OAUTH_AUTHORIZATION_URL = "https://custom.test.com/auth";
|
||||
process.env.AUTH_OAUTH_TOKEN_URL = "https://custom.test.com/token";
|
||||
process.env.AUTH_OAUTH_USERINFO_URL = "https://custom.test.com/me";
|
||||
|
||||
const provider: any = oauthPlugin.getProvider();
|
||||
|
||||
expect(provider.authorization.url).toBe("https://custom.test.com/auth");
|
||||
expect(provider.token).toBe("https://custom.test.com/token");
|
||||
expect(provider.userinfo).toBe("https://custom.test.com/me");
|
||||
});
|
||||
|
||||
it("should handle wellknown discovery correctly", () => {
|
||||
process.env.AUTH_OAUTH_ID = "test-client-id";
|
||||
process.env.AUTH_OAUTH_SECRET = "test-client-secret";
|
||||
process.env.AUTH_OAUTH_ISSUER = "https://sso.test.com";
|
||||
process.env.AUTH_OAUTH_WELLKNOWN = "https://sso.test.com/.well-known";
|
||||
|
||||
const provider: any = oauthPlugin.getProvider();
|
||||
|
||||
expect(provider.wellKnown).toBe("https://sso.test.com/.well-known");
|
||||
expect(provider.authorization.url).toBeUndefined(); // Let wellKnown handle it
|
||||
expect(provider.token).toBeUndefined();
|
||||
expect(provider.userinfo).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should handle custom style logo", () => {
|
||||
process.env.AUTH_OAUTH_LOGO = "https://logo.com/image.png";
|
||||
const provider: any = oauthPlugin.getProvider();
|
||||
|
||||
expect(provider.style?.logo).toBe("https://logo.com/image.png");
|
||||
});
|
||||
|
||||
it("should process user profile mappings correctly", () => {
|
||||
const provider: any = oauthPlugin.getProvider();
|
||||
|
||||
const mockProfile = {
|
||||
sub: "12345",
|
||||
name: "Test User",
|
||||
email: "test@example.com",
|
||||
picture: "https://avatar.com/me.png",
|
||||
preferred_username: "testuser"
|
||||
};
|
||||
|
||||
const parsedProfile = provider.profile(mockProfile);
|
||||
|
||||
expect(parsedProfile.id).toBe("12345");
|
||||
expect(parsedProfile.name).toBe("Test User");
|
||||
expect(parsedProfile.email).toBe("test@example.com");
|
||||
expect(parsedProfile.image).toBe("https://avatar.com/me.png");
|
||||
expect(parsedProfile.username).toBe("testuser");
|
||||
});
|
||||
|
||||
it("should fallback correctly if standard profile fields are missing", () => {
|
||||
const provider: any = oauthPlugin.getProvider();
|
||||
|
||||
const weirdProfile = {
|
||||
id: 999, // Testing numeric ID coercion
|
||||
email: "weird@example.com",
|
||||
avatar_url: "https://avatar.com/me2.png"
|
||||
};
|
||||
|
||||
const parsedProfile = provider.profile(weirdProfile);
|
||||
|
||||
expect(parsedProfile.id).toBe("999");
|
||||
expect(parsedProfile.name).toBe("weird@example.com"); // Fallback to email
|
||||
expect(parsedProfile.email).toBe("weird@example.com");
|
||||
expect(parsedProfile.image).toBe("https://avatar.com/me2.png");
|
||||
expect(parsedProfile.username).toBe("weird"); // Fallback to email prefix
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,96 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any -- tests inspect the dynamic NextAuth provider object */
|
||||
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
||||
import { oidcPlugin } from "@/lib/plugins/auth/oidc";
|
||||
|
||||
describe("OpenID Connect Auth Plugin", () => {
|
||||
const originalEnv = process.env;
|
||||
|
||||
beforeEach(() => {
|
||||
process.env = { ...originalEnv };
|
||||
process.env.AUTH_OIDC_ID = "test-client-id";
|
||||
process.env.AUTH_OIDC_SECRET = "test-client-secret";
|
||||
process.env.AUTH_OIDC_ISSUER = "https://sso.test.com";
|
||||
process.env.AUTH_OIDC_NAME = "Test OIDC";
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
process.env = originalEnv;
|
||||
});
|
||||
|
||||
it("should have correct plugin id and name", () => {
|
||||
expect(oidcPlugin.id).toBe("oidc");
|
||||
expect(oidcPlugin.name).toBe("Generic OIDC");
|
||||
});
|
||||
|
||||
it("should configure standard OIDC provider dynamically from env", () => {
|
||||
process.env.AUTH_OIDC_ID = "test-client-id";
|
||||
process.env.AUTH_OIDC_SECRET = "test-client-secret";
|
||||
process.env.AUTH_OIDC_ISSUER = "https://sso.test.com";
|
||||
|
||||
const provider: any = oidcPlugin.getProvider();
|
||||
|
||||
expect(provider.id).toBe("oidc");
|
||||
expect(provider.type).toBe("oidc");
|
||||
expect(provider.clientId).toBe("test-client-id");
|
||||
expect(provider.clientSecret).toBe("test-client-secret");
|
||||
expect(provider.issuer).toBe("https://sso.test.com");
|
||||
expect(provider.wellKnown).toBe("https://sso.test.com/.well-known/openid-configuration");
|
||||
});
|
||||
|
||||
it("should support well-known discovery override", () => {
|
||||
process.env.AUTH_OIDC_ISSUER = "https://sso.test.com";
|
||||
process.env.AUTH_OIDC_WELLKNOWN = "https://sso.test.com/custom-well-known";
|
||||
|
||||
const provider: any = oidcPlugin.getProvider();
|
||||
expect(provider.wellKnown).toBe("https://sso.test.com/custom-well-known");
|
||||
});
|
||||
|
||||
it("should have PKCE enabled by default", () => {
|
||||
delete process.env.AUTH_OIDC_ENABLE_PKCE;
|
||||
const provider: any = oidcPlugin.getProvider();
|
||||
expect(provider.checks).toBeUndefined(); // Enabled by default
|
||||
});
|
||||
|
||||
it("should apply PKCE bypass if explicitly disabled", () => {
|
||||
process.env.AUTH_OIDC_ENABLE_PKCE = "false";
|
||||
const provider: any = oidcPlugin.getProvider();
|
||||
expect(provider.checks).toEqual(["state"]);
|
||||
});
|
||||
|
||||
it("should keep PKCE enabled if explicitly turned on", () => {
|
||||
process.env.AUTH_OIDC_ENABLE_PKCE = "true";
|
||||
const provider: any = oidcPlugin.getProvider();
|
||||
expect(provider.checks).toBeUndefined(); // Auth.js will handle PKCE natively
|
||||
});
|
||||
|
||||
it("should handle custom style logo", () => {
|
||||
process.env.AUTH_OIDC_LOGO = "https://logo.com/image.png";
|
||||
const provider: any = oidcPlugin.getProvider();
|
||||
|
||||
expect(provider.style?.logo).toBe("https://logo.com/image.png");
|
||||
});
|
||||
|
||||
it("should fall back to standard UserInfo fields correctly", () => {
|
||||
process.env.AUTH_OIDC_ID = "test-client-id";
|
||||
process.env.AUTH_OIDC_SECRET = "test-client-secret";
|
||||
process.env.AUTH_OIDC_ISSUER = "https://sso.test.com";
|
||||
|
||||
const provider: any = oidcPlugin.getProvider();
|
||||
|
||||
const mockProfile = {
|
||||
sub: "12345",
|
||||
name: "Test User",
|
||||
email: "test@example.com",
|
||||
picture: "https://avatar.com/me.png",
|
||||
preferred_username: "testuser"
|
||||
};
|
||||
|
||||
const parsedProfile: any = provider.profile(mockProfile);
|
||||
|
||||
expect(parsedProfile.id).toBe("12345");
|
||||
expect(parsedProfile.name).toBe("Test User");
|
||||
expect(parsedProfile.email).toBe("test@example.com");
|
||||
expect(parsedProfile.image).toBe("https://avatar.com/me.png");
|
||||
expect(parsedProfile.username).toBe("testuser");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,108 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { canViewPrompt, checkPromptAccess } from "@/lib/prompt-access";
|
||||
import { auth } from "@/lib/auth";
|
||||
import type { Session } from "next-auth";
|
||||
|
||||
vi.mock("@/lib/auth", () => ({
|
||||
auth: vi.fn(),
|
||||
}));
|
||||
|
||||
describe("canViewPrompt", () => {
|
||||
it("should return false for null prompt", () => {
|
||||
expect(canViewPrompt(null, null)).toBe(false);
|
||||
});
|
||||
|
||||
it("should return true for public prompt with no session", () => {
|
||||
expect(
|
||||
canViewPrompt({ isPrivate: false, authorId: "user1" }, null)
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("should return true for public prompt with any session", () => {
|
||||
const session = { user: { id: "other" } } as Session;
|
||||
expect(
|
||||
canViewPrompt({ isPrivate: false, authorId: "user1" }, session)
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false for private prompt with no session", () => {
|
||||
expect(
|
||||
canViewPrompt({ isPrivate: true, authorId: "user1" }, null)
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false for private prompt when user is not owner", () => {
|
||||
const session = { user: { id: "other", role: "USER" } } as Session;
|
||||
expect(
|
||||
canViewPrompt({ isPrivate: true, authorId: "user1" }, session)
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("should return true for private prompt when user is the owner", () => {
|
||||
const session = { user: { id: "user1", role: "USER" } } as Session;
|
||||
expect(
|
||||
canViewPrompt({ isPrivate: true, authorId: "user1" }, session)
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("should return true for private prompt when user is admin", () => {
|
||||
const session = { user: { id: "admin1", role: "ADMIN" } } as Session;
|
||||
expect(
|
||||
canViewPrompt({ isPrivate: true, authorId: "user1" }, session)
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false for private prompt with session missing user", () => {
|
||||
const session = {} as Session;
|
||||
expect(
|
||||
canViewPrompt({ isPrivate: true, authorId: "user1" }, session)
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("checkPromptAccess", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("should return 404 for null prompt", async () => {
|
||||
const result = await checkPromptAccess(null);
|
||||
expect(result).not.toBeNull();
|
||||
expect(result!.status).toBe(404);
|
||||
const data = await result!.json();
|
||||
expect(data.error).toBe("not_found");
|
||||
});
|
||||
|
||||
it("should return null for public prompt (no auth call)", async () => {
|
||||
const result = await checkPromptAccess({ isPrivate: false, authorId: "user1" });
|
||||
expect(result).toBeNull();
|
||||
expect(auth).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should return 404 for private prompt with no session", async () => {
|
||||
vi.mocked(auth).mockResolvedValue(null);
|
||||
const result = await checkPromptAccess({ isPrivate: true, authorId: "user1" });
|
||||
expect(result).not.toBeNull();
|
||||
expect(result!.status).toBe(404);
|
||||
expect(auth).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("should return 404 for private prompt when user is not owner", async () => {
|
||||
vi.mocked(auth).mockResolvedValue({ user: { id: "other", role: "USER" } } as never);
|
||||
const result = await checkPromptAccess({ isPrivate: true, authorId: "user1" });
|
||||
expect(result).not.toBeNull();
|
||||
expect(result!.status).toBe(404);
|
||||
});
|
||||
|
||||
it("should return null for private prompt when user is the owner", async () => {
|
||||
vi.mocked(auth).mockResolvedValue({ user: { id: "user1", role: "USER" } } as never);
|
||||
const result = await checkPromptAccess({ isPrivate: true, authorId: "user1" });
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it("should return null for private prompt when user is admin", async () => {
|
||||
vi.mocked(auth).mockResolvedValue({ user: { id: "admin1", role: "ADMIN" } } as never);
|
||||
const result = await checkPromptAccess({ isPrivate: true, authorId: "user1" });
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -2,6 +2,7 @@ import { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { getConfig } from "@/lib/config";
|
||||
import { getConfiguredProviderIds, getProviderMetadata } from "@/lib/auth/provider-metadata";
|
||||
import { AuthContent } from "@/components/auth/auth-content";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
@@ -9,24 +10,14 @@ export const metadata: Metadata = {
|
||||
description: "Login to your account",
|
||||
};
|
||||
|
||||
// Helper to get providers from config (supports both old `provider` and new `providers` array)
|
||||
function getProviders(config: Awaited<ReturnType<typeof getConfig>>): string[] {
|
||||
if (config.auth.providers && config.auth.providers.length > 0) {
|
||||
return config.auth.providers;
|
||||
}
|
||||
if (config.auth.provider) {
|
||||
return [config.auth.provider];
|
||||
}
|
||||
return ["credentials"];
|
||||
}
|
||||
|
||||
export default async function LoginPage() {
|
||||
const t = await getTranslations("auth");
|
||||
const config = await getConfig();
|
||||
const providers = getProviders(config);
|
||||
const providers = getConfiguredProviderIds(config);
|
||||
const hasCredentials = providers.includes("credentials");
|
||||
const hasOnlyCredentials = providers.length === 1 && hasCredentials;
|
||||
const useCloneBranding = config.homepage?.useCloneBranding ?? false;
|
||||
const { displayNames, logos } = getProviderMetadata(providers);
|
||||
|
||||
return (
|
||||
<div className="container flex min-h-[calc(100vh-6rem)] flex-col items-center justify-center py-8">
|
||||
@@ -38,7 +29,7 @@ export default async function LoginPage() {
|
||||
</p>
|
||||
</div>
|
||||
<div className="border rounded-lg p-4">
|
||||
<AuthContent providers={providers} mode="login" useCloneBranding={useCloneBranding} />
|
||||
<AuthContent providers={providers} mode="login" useCloneBranding={useCloneBranding} providerDisplayNames={displayNames} providerLogos={logos} />
|
||||
</div>
|
||||
{hasCredentials && (
|
||||
<p className="text-center text-xs text-muted-foreground">
|
||||
|
||||
@@ -3,6 +3,7 @@ import Link from "next/link";
|
||||
import { redirect } from "next/navigation";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { getConfig } from "@/lib/config";
|
||||
import { getConfiguredProviderIds, getProviderMetadata } from "@/lib/auth/provider-metadata";
|
||||
import { AuthContent } from "@/components/auth/auth-content";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
@@ -10,21 +11,10 @@ export const metadata: Metadata = {
|
||||
description: "Create a new account",
|
||||
};
|
||||
|
||||
// Helper to get providers from config (supports both old `provider` and new `providers` array)
|
||||
function getProviders(config: Awaited<ReturnType<typeof getConfig>>): string[] {
|
||||
if (config.auth.providers && config.auth.providers.length > 0) {
|
||||
return config.auth.providers;
|
||||
}
|
||||
if (config.auth.provider) {
|
||||
return [config.auth.provider];
|
||||
}
|
||||
return ["credentials"];
|
||||
}
|
||||
|
||||
export default async function RegisterPage() {
|
||||
const t = await getTranslations("auth");
|
||||
const config = await getConfig();
|
||||
const providers = getProviders(config);
|
||||
const providers = getConfiguredProviderIds(config);
|
||||
const hasCredentials = providers.includes("credentials");
|
||||
|
||||
// Block registration if disabled or credentials not enabled
|
||||
@@ -32,6 +22,8 @@ export default async function RegisterPage() {
|
||||
redirect("/login");
|
||||
}
|
||||
|
||||
const { displayNames, logos } = getProviderMetadata(providers);
|
||||
|
||||
return (
|
||||
<div className="container flex min-h-[calc(100vh-6rem)] flex-col items-center justify-center py-8">
|
||||
<div className="w-full max-w-sm space-y-4">
|
||||
@@ -40,7 +32,7 @@ export default async function RegisterPage() {
|
||||
<p className="text-xs text-muted-foreground">{t("registerDescription")}</p>
|
||||
</div>
|
||||
<div className="border rounded-lg p-4">
|
||||
<AuthContent providers={providers} mode="register" />
|
||||
<AuthContent providers={providers} mode="register" providerDisplayNames={displayNames} providerLogos={logos} />
|
||||
</div>
|
||||
<p className="text-center text-xs text-muted-foreground">
|
||||
{t("hasAccount")}{" "}
|
||||
|
||||
@@ -8,6 +8,7 @@ export const size = {
|
||||
height: 630,
|
||||
};
|
||||
export const contentType = "image/png";
|
||||
export const revalidate = 3600;
|
||||
|
||||
const radiusMap: Record<string, number> = {
|
||||
none: 0,
|
||||
@@ -49,6 +50,7 @@ export default async function OGImage({ params }: { params: Promise<{ username:
|
||||
|
||||
const user = await db.user.findFirst({
|
||||
where: { username: { equals: username, mode: "insensitive" } },
|
||||
orderBy: { createdAt: "asc" },
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
|
||||
@@ -38,6 +38,7 @@ export async function generateMetadata({ params }: UserProfilePageProps): Promis
|
||||
|
||||
const user = await db.user.findFirst({
|
||||
where: { username: { equals: username, mode: "insensitive" } },
|
||||
orderBy: { createdAt: "asc" },
|
||||
select: { name: true, username: true },
|
||||
});
|
||||
|
||||
@@ -72,6 +73,7 @@ export default async function UserProfilePage({ params, searchParams }: UserProf
|
||||
|
||||
const user = await db.user.findFirst({
|
||||
where: { username: { equals: username, mode: "insensitive" } },
|
||||
orderBy: { createdAt: "asc" },
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
|
||||
@@ -124,6 +124,11 @@ const coreContributors = [
|
||||
role: "Chrome Extension Maintainer",
|
||||
x: "fatihsolhann",
|
||||
},
|
||||
{
|
||||
username: "JohnPhamous",
|
||||
role: "Performance (Vercel)",
|
||||
x: "JohnPhamous",
|
||||
},
|
||||
{
|
||||
username: "claude-opus-4.5",
|
||||
displayName: "Claude Opus 4.5",
|
||||
@@ -169,6 +174,15 @@ const designCredits = [
|
||||
},
|
||||
];
|
||||
|
||||
const securityCredits = [
|
||||
{
|
||||
username: "mdisec",
|
||||
displayName: "Mehmet Ince",
|
||||
role: "Security Fixes",
|
||||
x: "mdisec",
|
||||
},
|
||||
];
|
||||
|
||||
const ideationCredits = [
|
||||
{
|
||||
username: "semihkislar",
|
||||
@@ -566,6 +580,53 @@ export default async function AboutPage() {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Security */}
|
||||
<section className="mb-10">
|
||||
<h2 className="text-lg font-semibold mb-4">{t("securityTitle")}</h2>
|
||||
<div className="grid gap-3 sm:grid-cols-2">
|
||||
{securityCredits.map((contributor) => (
|
||||
<div
|
||||
key={contributor.username}
|
||||
className="flex items-center gap-3 p-3 border rounded-lg"
|
||||
>
|
||||
<Image
|
||||
src={`https://github.com/${contributor.username}.png`}
|
||||
alt=""
|
||||
width={40}
|
||||
height={40}
|
||||
className="rounded-full shrink-0"
|
||||
/>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="font-medium text-sm">
|
||||
{contributor.displayName ?? `@${contributor.username}`}
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">{contributor.role}</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Link
|
||||
href={`https://github.com/${contributor.username}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="p-1.5 rounded-md hover:bg-muted transition-colors"
|
||||
title="GitHub"
|
||||
>
|
||||
<BrandIcon name="github" />
|
||||
</Link>
|
||||
<Link
|
||||
href={`https://x.com/${contributor.x}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="p-1.5 rounded-md hover:bg-muted transition-colors"
|
||||
title="X"
|
||||
>
|
||||
<BrandIcon name="x" />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Ideation */}
|
||||
<section className="mb-10">
|
||||
<h2 className="text-lg font-semibold mb-4">{t("ideationTitle")}</h2>
|
||||
|
||||
@@ -2,12 +2,16 @@ import { NextResponse } from "next/server";
|
||||
import bcrypt from "bcryptjs";
|
||||
import { z } from "zod";
|
||||
import { db } from "@/lib/db";
|
||||
import { isUniqueConstraintViolation } from "@/lib/db-errors";
|
||||
import { getConfig } from "@/lib/config";
|
||||
|
||||
// Trim before validation to prevent unicode/whitespace tricks that bypass uniqueness checks (e.g. "admin@x.com\u200B" vs "admin@x.com") on certain DBMS
|
||||
const trimmed = z.preprocess((v) => (typeof v === "string" ? v.trim() : v), z.string());
|
||||
|
||||
const registerSchema = z.object({
|
||||
name: z.string().min(2),
|
||||
username: z.string().min(1).regex(/^[a-zA-Z0-9_]+$/),
|
||||
email: z.string().email(),
|
||||
name: trimmed.pipe(z.string().min(2)),
|
||||
username: trimmed.pipe(z.string().min(1).max(30).regex(/^[a-z0-9_]+$/)),
|
||||
email: trimmed.pipe(z.string().email()).transform((v) => v.toLowerCase()),
|
||||
password: z.string().min(6),
|
||||
});
|
||||
|
||||
@@ -34,49 +38,41 @@ export async function POST(request: Request) {
|
||||
|
||||
const { name, username, email, password } = parsed.data;
|
||||
|
||||
// Check if email already exists
|
||||
const existingEmail = await db.user.findUnique({
|
||||
where: { email },
|
||||
});
|
||||
|
||||
if (existingEmail) {
|
||||
return NextResponse.json(
|
||||
{ error: "email_taken", message: "Email is already taken" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Check if username already exists (case-insensitive)
|
||||
const existingUsername = await db.user.findFirst({
|
||||
where: { username: { equals: username, mode: "insensitive" } },
|
||||
});
|
||||
|
||||
if (existingUsername) {
|
||||
return NextResponse.json(
|
||||
{ error: "username_taken", message: "Username is already taken" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Hash password
|
||||
const hashedPassword = await bcrypt.hash(password, 12);
|
||||
|
||||
// Create user
|
||||
const user = await db.user.create({
|
||||
data: {
|
||||
name,
|
||||
username,
|
||||
email,
|
||||
password: hashedPassword,
|
||||
},
|
||||
});
|
||||
// Atomic create — DB unique constraints enforce email and CI username uniqueness
|
||||
try {
|
||||
const user = await db.user.create({
|
||||
data: {
|
||||
name,
|
||||
username,
|
||||
email,
|
||||
password: hashedPassword,
|
||||
},
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
id: user.id,
|
||||
name: user.name,
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
});
|
||||
return NextResponse.json({
|
||||
id: user.id,
|
||||
name: user.name,
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
});
|
||||
} catch (error) {
|
||||
if (isUniqueConstraintViolation(error, "email")) {
|
||||
return NextResponse.json(
|
||||
{ error: "email_taken", message: "Email is already taken" },
|
||||
{ status: 409 }
|
||||
);
|
||||
}
|
||||
if (isUniqueConstraintViolation(error, "username")) {
|
||||
return NextResponse.json(
|
||||
{ error: "username_taken", message: "Username is already taken" },
|
||||
{ status: 409 }
|
||||
);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Registration error:", error);
|
||||
return NextResponse.json(
|
||||
|
||||
@@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from "next/server";
|
||||
import { z } from "zod";
|
||||
import { auth } from "@/lib/auth";
|
||||
import { db } from "@/lib/db";
|
||||
import { checkPromptAccess } from "@/lib/prompt-access";
|
||||
|
||||
const updateChangeRequestSchema = z.object({
|
||||
status: z.enum(["APPROVED", "REJECTED", "PENDING"]),
|
||||
@@ -196,7 +197,8 @@ export async function GET(
|
||||
select: {
|
||||
id: true,
|
||||
title: true,
|
||||
content: true,
|
||||
isPrivate: true,
|
||||
authorId: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -209,6 +211,9 @@ export async function GET(
|
||||
);
|
||||
}
|
||||
|
||||
const denied = await checkPromptAccess(changeRequest.prompt);
|
||||
if (denied) return denied;
|
||||
|
||||
return NextResponse.json(changeRequest);
|
||||
} catch (error) {
|
||||
console.error("Get change request error:", error);
|
||||
|
||||
@@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from "next/server";
|
||||
import { z } from "zod";
|
||||
import { auth } from "@/lib/auth";
|
||||
import { db } from "@/lib/db";
|
||||
import { checkPromptAccess } from "@/lib/prompt-access";
|
||||
|
||||
const createChangeRequestSchema = z.object({
|
||||
proposedContent: z.string().min(1),
|
||||
@@ -94,6 +95,14 @@ export async function GET(
|
||||
try {
|
||||
const { id: promptId } = await params;
|
||||
|
||||
const prompt = await db.prompt.findUnique({
|
||||
where: { id: promptId },
|
||||
select: { isPrivate: true, authorId: true },
|
||||
});
|
||||
|
||||
const denied = await checkPromptAccess(prompt);
|
||||
if (denied) return denied;
|
||||
|
||||
const changeRequests = await db.changeRequest.findMany({
|
||||
where: { promptId },
|
||||
orderBy: { createdAt: "desc" },
|
||||
|
||||
@@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from "next/server";
|
||||
import { auth } from "@/lib/auth";
|
||||
import { db } from "@/lib/db";
|
||||
import { z } from "zod";
|
||||
import { checkPromptAccess } from "@/lib/prompt-access";
|
||||
|
||||
const addExampleSchema = z.object({
|
||||
mediaUrl: z.string().url(),
|
||||
@@ -16,12 +17,11 @@ export async function GET(
|
||||
|
||||
const prompt = await db.prompt.findUnique({
|
||||
where: { id: promptId },
|
||||
select: { id: true, type: true },
|
||||
select: { id: true, type: true, isPrivate: true, authorId: true },
|
||||
});
|
||||
|
||||
if (!prompt) {
|
||||
return NextResponse.json({ error: "Prompt not found" }, { status: 404 });
|
||||
}
|
||||
const denied = await checkPromptAccess(prompt);
|
||||
if (denied || !prompt) return denied!;
|
||||
|
||||
// Only allow examples for IMAGE and VIDEO prompts
|
||||
if (prompt.type !== "IMAGE" && prompt.type !== "VIDEO") {
|
||||
|
||||
@@ -11,7 +11,7 @@ const updatePromptSchema = z.object({
|
||||
title: z.string().min(1).max(200).optional(),
|
||||
description: z.string().max(500).optional(),
|
||||
content: z.string().min(1).optional(),
|
||||
type: z.enum(["TEXT", "IMAGE", "VIDEO", "AUDIO", "SKILL"]).optional(), // Output type or SKILL
|
||||
type: z.enum(["TEXT", "IMAGE", "VIDEO", "AUDIO", "SKILL", "TASTE"]).optional(), // Output type, SKILL, or TASTE
|
||||
structuredFormat: z.enum(["JSON", "YAML"]).optional().nullable(),
|
||||
categoryId: z.string().optional().nullable(),
|
||||
tagIds: z.array(z.string()).optional(),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { NextRequest } from "next/server";
|
||||
import { db } from "@/lib/db";
|
||||
import { parseSkillFiles } from "@/lib/skill-files";
|
||||
import { parseSkillFiles, sanitizeFilename } from "@/lib/skill-files";
|
||||
import JSZip from "jszip";
|
||||
|
||||
/**
|
||||
@@ -53,9 +53,18 @@ export async function GET(
|
||||
// Create a zip file
|
||||
const zip = new JSZip();
|
||||
|
||||
// Add each file to the zip
|
||||
// Add each file to the zip (sanitize filenames to prevent Zip Slip)
|
||||
for (const file of files) {
|
||||
zip.file(file.filename, file.content);
|
||||
const candidate = file.filename
|
||||
.replace(/\\/g, "/")
|
||||
.split("/")
|
||||
.filter((segment) => segment && segment !== "." && segment !== "..")
|
||||
.join("/");
|
||||
const safeName = sanitizeFilename(candidate);
|
||||
if (!safeName) {
|
||||
continue;
|
||||
}
|
||||
zip.file(safeName, file.content);
|
||||
}
|
||||
|
||||
// Generate the zip content as blob for Response compatibility
|
||||
|
||||
@@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from "next/server";
|
||||
import { z } from "zod";
|
||||
import { auth } from "@/lib/auth";
|
||||
import { db } from "@/lib/db";
|
||||
import { checkPromptAccess } from "@/lib/prompt-access";
|
||||
|
||||
const createVersionSchema = z.object({
|
||||
content: z.string().min(1, "Content is required"),
|
||||
@@ -116,6 +117,14 @@ export async function GET(
|
||||
try {
|
||||
const { id: promptId } = await params;
|
||||
|
||||
const prompt = await db.prompt.findUnique({
|
||||
where: { id: promptId },
|
||||
select: { isPrivate: true, authorId: true },
|
||||
});
|
||||
|
||||
const denied = await checkPromptAccess(prompt);
|
||||
if (denied) return denied;
|
||||
|
||||
const versions = await db.promptVersion.findMany({
|
||||
where: { promptId },
|
||||
orderBy: { version: "desc" },
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { auth } from "@/lib/auth";
|
||||
import { db } from "@/lib/db";
|
||||
import { checkPromptAccess } from "@/lib/prompt-access";
|
||||
|
||||
// POST - Upvote a prompt
|
||||
export async function POST(
|
||||
@@ -21,14 +22,11 @@ export async function POST(
|
||||
// Check if prompt exists
|
||||
const prompt = await db.prompt.findUnique({
|
||||
where: { id: promptId },
|
||||
select: { isPrivate: true, authorId: true },
|
||||
});
|
||||
|
||||
if (!prompt) {
|
||||
return NextResponse.json(
|
||||
{ error: "not_found", message: "Prompt not found" },
|
||||
{ status: 404 }
|
||||
);
|
||||
}
|
||||
const denied = await checkPromptAccess(prompt);
|
||||
if (denied) return denied;
|
||||
|
||||
// Check if already voted
|
||||
const existing = await db.promptVote.findUnique({
|
||||
@@ -86,6 +84,14 @@ export async function DELETE(
|
||||
|
||||
const { id: promptId } = await params;
|
||||
|
||||
const prompt = await db.prompt.findUnique({
|
||||
where: { id: promptId },
|
||||
select: { isPrivate: true, authorId: true },
|
||||
});
|
||||
|
||||
const denied = await checkPromptAccess(prompt);
|
||||
if (denied) return denied;
|
||||
|
||||
// Delete vote
|
||||
await db.promptVote.deleteMany({
|
||||
where: {
|
||||
|
||||
@@ -13,7 +13,7 @@ const promptSchema = z.object({
|
||||
title: z.string().min(1).max(200),
|
||||
description: z.string().max(500).optional(),
|
||||
content: z.string().min(1),
|
||||
type: z.enum(["TEXT", "IMAGE", "VIDEO", "AUDIO", "SKILL"]), // Output type or SKILL
|
||||
type: z.enum(["TEXT", "IMAGE", "VIDEO", "AUDIO", "SKILL", "TASTE"]), // Output type, SKILL, or TASTE
|
||||
structuredFormat: z.enum(["JSON", "YAML"]).nullish(), // Input type indicator
|
||||
categoryId: z.string().optional(),
|
||||
tagIds: z.array(z.string()),
|
||||
|
||||
@@ -3,6 +3,7 @@ import { z } from "zod";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { auth } from "@/lib/auth";
|
||||
import { db } from "@/lib/db";
|
||||
import { isUniqueConstraintViolation } from "@/lib/db-errors";
|
||||
|
||||
const customLinkSchema = z.object({
|
||||
type: z.enum(["website", "github", "twitter", "linkedin", "instagram", "youtube", "twitch", "discord", "mastodon", "bluesky", "sponsor"]),
|
||||
@@ -10,13 +11,12 @@ const customLinkSchema = z.object({
|
||||
label: z.string().max(30).optional(),
|
||||
});
|
||||
|
||||
// Trim before validation to prevent unicode/whitespace tricks that bypass uniqueness checks (e.g. "admin\u200B@x.com" vs "admin@x.com")
|
||||
const trimmed = z.preprocess((v) => (typeof v === "string" ? v.trim() : v), z.string());
|
||||
|
||||
const updateProfileSchema = z.object({
|
||||
name: z.string().min(1).max(100),
|
||||
username: z
|
||||
.string()
|
||||
.min(1)
|
||||
.max(30)
|
||||
.regex(/^[a-zA-Z0-9_]+$/),
|
||||
name: trimmed.pipe(z.string().min(1).max(100)),
|
||||
username: trimmed.pipe(z.string().min(1).max(30).regex(/^[a-z0-9_]+$/)),
|
||||
avatar: z.string().url().optional().or(z.literal("")),
|
||||
bio: z.string().max(250).optional().or(z.literal("")),
|
||||
customLinks: z.array(customLinkSchema).max(5).optional(),
|
||||
@@ -44,43 +44,38 @@ export async function PATCH(request: NextRequest) {
|
||||
|
||||
const { name, username, avatar, bio, customLinks } = parsed.data;
|
||||
|
||||
// Check if username is taken by another user
|
||||
if (username !== session.user.username) {
|
||||
const existingUser = await db.user.findUnique({
|
||||
where: { username },
|
||||
select: { id: true },
|
||||
// Atomic update — DB-level CI unique index prevents collisions
|
||||
try {
|
||||
const user = await db.user.update({
|
||||
where: { id: session.user.id },
|
||||
data: {
|
||||
name,
|
||||
username,
|
||||
avatar: avatar || null,
|
||||
bio: bio || null,
|
||||
customLinks: customLinks && customLinks.length > 0 ? customLinks : Prisma.DbNull,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
username: true,
|
||||
email: true,
|
||||
avatar: true,
|
||||
bio: true,
|
||||
customLinks: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (existingUser && existingUser.id !== session.user.id) {
|
||||
return NextResponse.json(user);
|
||||
} catch (error) {
|
||||
if (isUniqueConstraintViolation(error, "username")) {
|
||||
return NextResponse.json(
|
||||
{ error: "username_taken", message: "This username is already taken" },
|
||||
{ status: 400 }
|
||||
{ status: 409 }
|
||||
);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Update user
|
||||
const user = await db.user.update({
|
||||
where: { id: session.user.id },
|
||||
data: {
|
||||
name,
|
||||
username,
|
||||
avatar: avatar || null,
|
||||
bio: bio || null,
|
||||
customLinks: customLinks && customLinks.length > 0 ? customLinks : Prisma.DbNull,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
username: true,
|
||||
email: true,
|
||||
avatar: true,
|
||||
bio: true,
|
||||
customLinks: true,
|
||||
},
|
||||
});
|
||||
|
||||
return NextResponse.json(user);
|
||||
} catch (error) {
|
||||
console.error("Update profile error:", error);
|
||||
return NextResponse.json(
|
||||
|
||||
@@ -130,7 +130,7 @@ export default async function BookHomePage() {
|
||||
{/* JSON-LD structured data */}
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd).replace(/</g, '\\u003c') }}
|
||||
/>
|
||||
<div className="max-w-2xl">
|
||||
{/* Continue Reading Button */}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { Server, Database, Key, Palette, Globe, Settings, Cpu } from "lucide-react";
|
||||
import DeepWikiIcon from "@/../public/deepwiki.svg";
|
||||
import Context7Icon from "@/../public/context7.svg";
|
||||
@@ -17,7 +18,9 @@ export const metadata = {
|
||||
description: "Deploy your own prompts.chat instance with customizable branding, themes, and authentication",
|
||||
};
|
||||
|
||||
export default function SelfHostingPage() {
|
||||
export default async function SelfHostingPage() {
|
||||
const t = await getTranslations("selfHosting");
|
||||
|
||||
return (
|
||||
<div className="container max-w-4xl py-10">
|
||||
<h1 className="text-2xl font-bold mb-2">Self-Hosting Guide</h1>
|
||||
@@ -110,9 +113,9 @@ export default function SelfHostingPage() {
|
||||
Prerequisites
|
||||
</h3>
|
||||
<ul className="list-disc list-inside text-muted-foreground space-y-1">
|
||||
<li>Node.js 18+</li>
|
||||
<li>PostgreSQL database</li>
|
||||
<li>npm or yarn</li>
|
||||
<li>{t("prerequisites.nodeJs")}</li>
|
||||
<li>{t("prerequisites.postgresql")}</li>
|
||||
<li>{t("prerequisites.npm")}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
+7
-1
@@ -9,6 +9,7 @@ import { CookieConsentBanner } from "@/components/layout/cookie-consent";
|
||||
import { Analytics } from "@/components/layout/analytics";
|
||||
import { WebsiteStructuredData } from "@/components/seo/structured-data";
|
||||
import { AppBanner } from "@/components/layout/app-banner";
|
||||
import { AnnouncementBanner } from "@/components/layout/announcement-banner";
|
||||
import { LocaleDetector } from "@/components/providers/locale-detector";
|
||||
import { getConfig } from "@/lib/config";
|
||||
import { isRtlLocale } from "@/lib/i18n/config";
|
||||
@@ -152,6 +153,7 @@ export default async function RootLayout({
|
||||
const pathname = headersList.get("x-pathname") || headersList.get("x-invoke-path") || "";
|
||||
const isEmbedRoute = pathname.startsWith("/embed");
|
||||
const isKidsRoute = pathname.startsWith("/kids");
|
||||
const isPresentationRoute = pathname.startsWith("/presentation");
|
||||
|
||||
const locale = await getLocale();
|
||||
const messages = await getMessages();
|
||||
@@ -180,6 +182,9 @@ export default async function RootLayout({
|
||||
return (
|
||||
<html lang={locale} dir={isRtl ? "rtl" : "ltr"} suppressHydrationWarning className={themeClasses} style={themeStyles}>
|
||||
<head>
|
||||
{process.env.GOOGLE_ADSENSE_ACCOUNT && (
|
||||
<meta name="google-adsense-account" content={process.env.GOOGLE_ADSENSE_ACCOUNT} />
|
||||
)}
|
||||
<WebsiteStructuredData />
|
||||
</head>
|
||||
<body className={`${fontClasses} antialiased`}>
|
||||
@@ -187,12 +192,13 @@ export default async function RootLayout({
|
||||
<Analytics gaId={process.env.GOOGLE_ANALYTICS_ID} />
|
||||
)}
|
||||
<Providers locale={locale} messages={messages} theme={config.theme} branding={{ ...config.branding, useCloneBranding: config.homepage?.useCloneBranding }}>
|
||||
{isEmbedRoute || isKidsRoute ? (
|
||||
{isEmbedRoute || isKidsRoute || isPresentationRoute ? (
|
||||
children
|
||||
) : (
|
||||
<>
|
||||
<LocaleDetector />
|
||||
<div className="relative min-h-screen flex flex-col">
|
||||
<AnnouncementBanner />
|
||||
<Header authProvider={config.auth.provider} allowRegistration={config.auth.allowRegistration} />
|
||||
<main className="flex-1">{children}</main>
|
||||
<Footer />
|
||||
|
||||
+21
-3
@@ -72,7 +72,7 @@ export default async function HomePage() {
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="absolute top-0 end-0 bottom-0 w-1/3 2xl:w-1/2 hidden md:block pointer-events-none">
|
||||
<div className="absolute top-0 end-0 bottom-0 w-2/5 xl:w-[45%] 2xl:w-1/2 hidden md:block pointer-events-none overflow-hidden">
|
||||
{/* Video background */}
|
||||
<div className="absolute inset-0">
|
||||
<div className="absolute inset-0 bg-gradient-to-r rtl:bg-gradient-to-l from-background via-background/80 to-transparent z-10" />
|
||||
@@ -83,7 +83,7 @@ export default async function HomePage() {
|
||||
playsInline
|
||||
className="absolute top-1/2 -translate-y-1/2 end-0 w-full h-auto opacity-30 dark:opacity-15 dark:invert"
|
||||
>
|
||||
<source src="/animation_compressed.mp4" type="video/mp4" />
|
||||
<source src="https://raw.githubusercontent.com/f/awesome-chatgpt-prompts/main/public/animation_compressed.mp4" type="video/mp4" />
|
||||
</video>
|
||||
</div>
|
||||
{/* Animated input overlay and clients */}
|
||||
@@ -126,7 +126,7 @@ export default async function HomePage() {
|
||||
)}
|
||||
|
||||
<div className="container relative z-20">
|
||||
<div className="max-w-2xl">
|
||||
<div className="max-w-2xl lg:max-w-xl xl:max-w-2xl">
|
||||
{useCloneBranding ? (
|
||||
<>
|
||||
<h1 className="text-2xl font-bold tracking-tight sm:text-3xl md:text-4xl lg:text-5xl !text-2xl sm:!text-3xl md:!text-4xl lg:!text-5xl text-primary">
|
||||
@@ -313,6 +313,24 @@ export default async function HomePage() {
|
||||
/>
|
||||
</BuiltWithLink>
|
||||
<span>by Cognition</span>
|
||||
<span className="mx-0.5">·</span>
|
||||
<span>hosted on</span>
|
||||
<Link href="https://vercel.com/oss" target="_blank" rel="noopener noreferrer" className="inline-flex items-center opacity-60 hover:opacity-100 transition-opacity">
|
||||
<Image
|
||||
src="/sponsors/vercel-oss.svg"
|
||||
alt="Vercel OSS Program"
|
||||
width={240}
|
||||
height={24}
|
||||
className="h-4.5 w-auto dark:hidden"
|
||||
/>
|
||||
<Image
|
||||
src="/sponsors/vercel-oss-dark.svg"
|
||||
alt="Vercel OSS Program"
|
||||
width={240}
|
||||
height={24}
|
||||
className="h-4.5 w-auto hidden dark:block"
|
||||
/>
|
||||
</Link>
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
+505
-211
@@ -1,233 +1,527 @@
|
||||
import { SlideDeck, SlideTitle, SlideContent, SlideHighlight } from "@/components/presentation/SlideDeck";
|
||||
import { MoveRight, Star, GitMerge, FileCode2, Sparkles, MessageSquare, Globe, Users, Code, Zap, Lightbulb } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Blocks,
|
||||
Bot,
|
||||
BrainCircuit,
|
||||
CheckCircle2,
|
||||
CircleDotDashed,
|
||||
ClipboardCheck,
|
||||
Clock3,
|
||||
DatabaseZap,
|
||||
FileText,
|
||||
Filter,
|
||||
GitBranch,
|
||||
Layers3,
|
||||
Lightbulb,
|
||||
MessageSquareText,
|
||||
Network,
|
||||
PackageCheck,
|
||||
Puzzle,
|
||||
RefreshCw,
|
||||
Route,
|
||||
Scissors,
|
||||
ShieldCheck,
|
||||
Sparkles,
|
||||
Target,
|
||||
TimerReset,
|
||||
WandSparkles,
|
||||
} from "lucide-react";
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
import Image from "next/image";
|
||||
import type { ReactNode } from "react";
|
||||
import { SlideContent, SlideDeck, SlideHighlight, SlideTitle } from "@/components/presentation/SlideDeck";
|
||||
|
||||
export const metadata = {
|
||||
title: "Why prompts.chat? | Presentation",
|
||||
description: "Discover why prompts are more important than ever in the agentic era.",
|
||||
title: "Prompt Tasarımı ve Context Yönetimi | Presentation",
|
||||
description: "30 dakikalık Türkçe konuşma: prompt tasarımı, context yönetimi ve prompts.chat.",
|
||||
};
|
||||
|
||||
const speakerNotes = [
|
||||
"Açılışta sunumun amacını netleştir: Bu konuşma prompt ezberlemek değil, bağlamı bilinçli yönetmek üzerine. 30 dakika sonunda herkesin kendi işine uygulayabileceği bir kontrol listesi olacak.",
|
||||
"Konuşmacı slaytı kısa tutulmalı. Kişisel hikaye: Awesome ChatGPT Prompts ile başlayan topluluk deneyimi, prompts.chat ile promptların paylaşılabilir, sürümlenebilir ve geliştirilebilir varlıklara dönüşmesi.",
|
||||
"Akışı anlat: önce zihinsel model, sonra promptun anatomisi, sonra context yönetimi ve ölçüm. prompts.chat bölümünü ürün demosu gibi değil, bu prensiplerin pratik karşılığı olarak konumlandır.",
|
||||
"Ana mesaj: Modeli sadece cevap makinesi gibi değil, sınırlı dikkat alanına sahip bir ekip arkadaşı gibi düşünün. Prompt tasarımı görevi tanımlar; context yönetimi modelin neye bakacağını seçer.",
|
||||
"Prompt tasarımının yalnızca güzel cümle yazmak olmadığını vurgula. Rol, hedef, veri, sınır, örnek ve çıktı sözleşmesi bir araya geldiğinde modelin çalışma alanı oluşur.",
|
||||
"Bu slaytta kötü prompt / iyi prompt farkını canlı okuyarak göster. İyi örneğin daha uzun olduğu için değil, karar yükünü azalttığı için iyi olduğunu söyle.",
|
||||
"Context penceresini dosya dolabı değil, toplantı masası metaforu ile anlat. Masaya her şeyi koymak odak kaybettirir; doğru şeyleri doğru sırayla koymak performansı artırır.",
|
||||
"Bağlam katmanlarını sırayla açıkla. Sistem ve geliştirici talimatlarının politikayı, kullanıcı mesajının amacı, araç sonuçlarının kanıtı, geçmişin ise sürekliliği taşıdığını anlat.",
|
||||
"Önemli ayrım: Context çokluğu kalite demek değildir. Gürültü, eski kararlar ve çelişkili talimatlar modeli kararsızlaştırır. Context tasarrufu aynı zamanda kalite kontrolüdür.",
|
||||
"Kısa bir teknik çerçeve ver: seç, sırala, sıkıştır, doğrula. Bu dört fiil context yönetiminin günlük pratiği olabilir.",
|
||||
"Örnekler model davranışını güçlü biçimde kilitler. Ancak yanlış örnekler de yanlış davranışı kilitler. Örnekleri az ama temsil gücü yüksek seçmek gerektiğini vurgula.",
|
||||
"Çıktı sözleşmesi özellikle ekiplerde önemlidir. JSON, tablo, diff, adım adım plan gibi formatlar otomasyon ve tekrar üretilebilirlik sağlar.",
|
||||
"Araç kullanan ajanlarda prompt, sadece cevap formatını değil yetki sınırını da belirler. Ne zaman arama yapmalı, ne zaman sormalı, ne zaman durmalı gibi kararları açık yazın.",
|
||||
"Context yönetiminin operasyonel tarafı: uzun konuşmalarda özet, dosya tabanlı işlerde ilgili parçalar, arama tabanlı işlerde kaynak ve alıntı. Her şeyi tek mesaja doldurmak yerine akış tasarla.",
|
||||
"Hata modlarını örneklerle anlat: belirsiz hedef, eski bağlam, çelişki, görünmez varsayım, format eksikliği. Bunlar model hatası gibi görünür ama çoğu zaman tasarım hatasıdır.",
|
||||
"Ölçüm slaytında küçük bir test setinin değerini anlat. Aynı promptu birkaç gerçek senaryo ile denemek, tek seferlik iyi cevaptan daha güvenilirdir.",
|
||||
"prompts.chat'i bağlamla ilişkilendir: iyi promptlar tek kişide kalmamalı. Paylaşım, sürümleme ve geri bildirim prompt kalitesini artırır.",
|
||||
"Ekip pratiğine geç: promptlar kod gibi yaşamalı. Sahiplik, değişiklik geçmişi, review ve örnek çıktılar promptun güvenilirliğini artırır.",
|
||||
"Kapanış kontrol listesini yavaş oku. Dinleyicilerin kendi promptlarını bu beş soruyla gözden geçirmesini iste.",
|
||||
"Son slaytta tek cümlelik kapanış: İyi prompt, modele ne yapacağını söyler; iyi context yönetimi, neye dayanarak yapacağını belirler.",
|
||||
];
|
||||
|
||||
type CardProps = {
|
||||
icon: LucideIcon;
|
||||
title: string;
|
||||
children: ReactNode;
|
||||
accent?: string;
|
||||
};
|
||||
|
||||
function Card({ icon: Icon, title, children, accent = "text-primary" }: CardProps) {
|
||||
return (
|
||||
<div className="rounded-3xl border border-border/70 bg-background/70 p-6 shadow-sm backdrop-blur transition-transform duration-300 hover:-translate-y-1">
|
||||
<div className={`mb-4 flex h-12 w-12 items-center justify-center rounded-2xl bg-primary/10 ${accent}`}>
|
||||
<Icon className="h-6 w-6" />
|
||||
</div>
|
||||
<h3 className="mb-2 text-2xl font-bold text-foreground">{title}</h3>
|
||||
<p className="text-lg leading-relaxed text-muted-foreground">{children}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Pill({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<span className="inline-flex items-center rounded-full border border-primary/20 bg-primary/10 px-4 py-2 text-base font-semibold text-primary">
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
function CodePanel({ children, title = "prompt.md" }: { children: ReactNode; title?: string }) {
|
||||
return (
|
||||
<div className="overflow-hidden rounded-3xl border border-border bg-zinc-950 text-zinc-100 shadow-2xl">
|
||||
<div className="flex items-center justify-between border-b border-white/10 bg-white/5 px-5 py-3">
|
||||
<div className="flex gap-2">
|
||||
<span className="h-3 w-3 rounded-full bg-red-400" />
|
||||
<span className="h-3 w-3 rounded-full bg-yellow-400" />
|
||||
<span className="h-3 w-3 rounded-full bg-green-400" />
|
||||
</div>
|
||||
<span className="font-mono text-xs text-zinc-400">{title}</span>
|
||||
</div>
|
||||
<pre className="whitespace-pre-wrap p-6 font-mono text-sm leading-relaxed md:text-base">{children}</pre>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function FlowStep({ icon: Icon, title, text }: { icon: LucideIcon; title: string; text: string }) {
|
||||
return (
|
||||
<div className="relative rounded-3xl border border-border/70 bg-background/75 p-5 shadow-sm">
|
||||
<div className="mb-4 flex h-12 w-12 items-center justify-center rounded-2xl bg-primary/10 text-primary">
|
||||
<Icon className="h-6 w-6" />
|
||||
</div>
|
||||
<h3 className="text-2xl font-bold">{title}</h3>
|
||||
<p className="mt-2 text-lg leading-relaxed text-muted-foreground">{text}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ContextLayer({ label, description, tone }: { label: string; description: string; tone: string }) {
|
||||
return (
|
||||
<div className="flex items-center gap-4 rounded-2xl border border-border bg-background/75 p-4 shadow-sm">
|
||||
<div className={`h-14 w-2 rounded-full ${tone}`} />
|
||||
<div>
|
||||
<h3 className="text-xl font-bold">{label}</h3>
|
||||
<p className="text-base text-muted-foreground">{description}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function MiniChecklist({ items }: { items: string[] }) {
|
||||
return (
|
||||
<div className="grid gap-3">
|
||||
{items.map((item) => (
|
||||
<div key={item} className="flex items-start gap-3 rounded-2xl border border-border/70 bg-background/70 p-4 text-xl font-semibold">
|
||||
<CheckCircle2 className="mt-1 h-6 w-6 flex-none text-primary" />
|
||||
<span>{item}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function PresentationPage() {
|
||||
return (
|
||||
<SlideDeck>
|
||||
{/* 1. Title Slide */}
|
||||
<div className="flex flex-col items-center justify-center text-center h-full">
|
||||
<div className="w-24 h-24 bg-primary/10 rounded-3xl flex items-center justify-center mb-8 rotate-12 transition-transform hover:rotate-0 duration-500">
|
||||
<Sparkles className="w-12 h-12 text-primary" />
|
||||
<SlideDeck notes={speakerNotes}>
|
||||
<div className="flex min-h-[70vh] flex-col items-center justify-center text-center">
|
||||
<div className="mb-8 flex h-24 w-24 rotate-6 items-center justify-center rounded-[2rem] bg-primary/10 text-primary shadow-xl shadow-primary/10">
|
||||
<WandSparkles className="h-12 w-12" />
|
||||
</div>
|
||||
<SlideTitle className="mb-6">Why prompts.chat?</SlideTitle>
|
||||
<SlideContent className="max-w-3xl mb-12">Discover why prompts are more important than ever in the agentic era.</SlideContent>
|
||||
<p className="text-sm text-muted-foreground animate-pulse mt-8">
|
||||
Use arrow keys <MoveRight className="inline w-4 h-4 mx-1" /> or swipe to navigate
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* 2. The Genesis */}
|
||||
<div className="flex flex-col justify-center h-full">
|
||||
<Star className="w-12 h-12 text-yellow-500 mb-6" />
|
||||
<SlideTitle>The History of prompts.chat</SlideTitle>
|
||||
<SlideContent>
|
||||
From a simple repository to a full-fledged platform. We started as Awesome ChatGPT Prompts, and evolved to support the entire AI community with typed prompts, workflows, and skills.
|
||||
<SlideTitle className="max-w-5xl">Prompt Tasarımı ve Context Yönetimi</SlideTitle>
|
||||
<SlideContent className="mx-auto max-w-3xl">
|
||||
30 dakikalık pratik bir çerçeve: daha iyi talimat, daha temiz bağlam, daha güvenilir AI çıktısı.
|
||||
</SlideContent>
|
||||
<div className="mt-12 p-6 bg-muted/50 rounded-2xl border border-border max-w-2xl">
|
||||
<p className="text-lg text-foreground font-mono">
|
||||
Awesome ChatGPT Prompts → <SlideHighlight>prompts.chat</SlideHighlight>
|
||||
</p>
|
||||
<div className="mt-10 flex flex-wrap justify-center gap-3">
|
||||
<Pill>prompts.chat/presentation</Pill>
|
||||
<Pill>Ok tuşları, boşluk, F, N</Pill>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 3. The Paradigm Shift */}
|
||||
<div className="flex flex-col justify-center h-full">
|
||||
<Zap className="w-12 h-12 text-blue-500 mb-6" />
|
||||
<SlideTitle>The Agentic Era</SlideTitle>
|
||||
<SlideContent>
|
||||
Why are prompts still important? Because agents need instructions. As AI becomes more autonomous, the quality of instructions determines the quality of the outcome.
|
||||
</SlideContent>
|
||||
</div>
|
||||
|
||||
{/* 4. The Core Question */}
|
||||
<div className="flex flex-col items-center justify-center text-center h-full">
|
||||
<Lightbulb className="w-16 h-16 text-yellow-400 mb-8" />
|
||||
<SlideTitle className="text-5xl md:text-6xl">Wait, isn't AI getting smarter?</SlideTitle>
|
||||
<SlideContent className="max-w-4xl mt-8">
|
||||
Yes, but <SlideHighlight>smart AI still needs direction.</SlideHighlight>
|
||||
<br /><br />
|
||||
A genius without instructions is just a very capable idle machine.
|
||||
</SlideContent>
|
||||
</div>
|
||||
|
||||
{/* 5. The Agentic Need */}
|
||||
<div className="flex flex-col justify-center h-full">
|
||||
<SlideTitle>The Agentic Need</SlideTitle>
|
||||
<SlideContent>
|
||||
Agents require precise instructions, constraints, and goal definitions.
|
||||
<ul className="mt-8 space-y-6 list-disc list-inside ml-4 text-foreground/80 text-2xl md:text-3xl">
|
||||
<li>What tools can they use?</li>
|
||||
<li>What are their boundaries?</li>
|
||||
<li>How should they format the output?</li>
|
||||
<li>What tone should they adopt?</li>
|
||||
</ul>
|
||||
</SlideContent>
|
||||
</div>
|
||||
|
||||
{/* 6. The SLM Revolution */}
|
||||
<div className="flex flex-col justify-center h-full">
|
||||
<Globe className="w-12 h-12 text-green-500 mb-6" />
|
||||
<SlideTitle>Crucial for SLMs</SlideTitle>
|
||||
<SlideContent>
|
||||
Small Language Models (SLMs) are the future of edge computing. They require precise, well-crafted prompts to perform tasks accurately without the massive parameter count of frontier models.
|
||||
</SlideContent>
|
||||
</div>
|
||||
|
||||
{/* 7. SLM Constraints */}
|
||||
<div className="flex flex-col justify-center h-full">
|
||||
<SlideTitle>SLM Constraints</SlideTitle>
|
||||
<SlideContent>
|
||||
Smaller models lack the vast "common sense" of 1T+ parameter models.
|
||||
<br /><br />
|
||||
They require <SlideHighlight>highly optimized, battle-tested prompts</SlideHighlight> to punch above their weight class and run efficiently on local devices.
|
||||
</SlideContent>
|
||||
</div>
|
||||
|
||||
{/* 8. The New Primitive */}
|
||||
<div className="flex flex-col justify-center h-full">
|
||||
<FileCode2 className="w-12 h-12 text-purple-500 mb-6" />
|
||||
<SlideTitle>Prompts are New Code Snippets</SlideTitle>
|
||||
<SlideContent>
|
||||
Just as developers copy/paste code snippets from StackOverflow, AI practitioners now share prompt templates. Prompts are the new primitive of programming.
|
||||
</SlideContent>
|
||||
<div className="mt-12 flex items-center gap-4 text-xl md:text-2xl font-mono text-muted-foreground bg-muted p-6 rounded-xl border max-w-fit">
|
||||
<span>const codeSnippet = "..."</span>
|
||||
<MoveRight className="w-6 h-6 text-primary mx-2" />
|
||||
<span className="text-primary">const promptTemplate = "..."</span>
|
||||
<div className="grid min-h-[70vh] items-center gap-8 lg:grid-cols-[1fr_0.95fr]">
|
||||
<div className="relative order-2 lg:order-1">
|
||||
<div className="absolute -left-8 top-10 h-72 w-72 rounded-full bg-primary/20 blur-3xl" />
|
||||
<div className="relative mx-auto w-full max-w-[34rem]">
|
||||
<div className="absolute -inset-4 -rotate-3 rounded-[3rem] bg-gradient-to-br from-primary/30 via-primary/10 to-transparent" />
|
||||
<div className="relative overflow-hidden rounded-[3rem] border border-border bg-background shadow-2xl">
|
||||
<Image
|
||||
src="https://github.com/f.png"
|
||||
alt="Fatih Kadir Akın profil fotoğrafı"
|
||||
width={900}
|
||||
height={900}
|
||||
priority
|
||||
unoptimized
|
||||
className="aspect-square w-full object-cover"
|
||||
/>
|
||||
<div className="absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/80 via-black/45 to-transparent p-8 text-white">
|
||||
<p className="text-sm font-bold uppercase tracking-[0.35em] text-white/70">Konuşmacı</p>
|
||||
<h2 className="mt-2 text-5xl font-black tracking-tight">Fatih Kadir Akın</h2>
|
||||
<p className="mt-3 max-w-md text-lg font-semibold leading-snug text-white/85">
|
||||
WordPress Developer Advocate, Automattic - Creator of prompts.chat
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="order-1 lg:order-2">
|
||||
<SlideTitle className="mb-5">Merhaba, ben Fatih.</SlideTitle>
|
||||
<SlideContent className="max-w-2xl">
|
||||
Geliştiricilerin hayatını kolaylaştıran uygulamalar geliştiriyorum; bunlardan biri de{" "}
|
||||
<SlideHighlight>prompts.chat</SlideHighlight>.
|
||||
</SlideContent>
|
||||
<div className="mt-10 flex flex-wrap items-center gap-5">
|
||||
<div className="flex items-center gap-3 rounded-2xl border border-border/70 bg-background/75 px-5 py-4 shadow-sm backdrop-blur">
|
||||
<Image
|
||||
src="https://cdn.simpleicons.org/wordpress/21759B"
|
||||
alt="WordPress"
|
||||
width={40}
|
||||
height={40}
|
||||
unoptimized
|
||||
className="h-10 w-10"
|
||||
/>
|
||||
<span className="text-xl font-bold text-foreground">WordPress</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 rounded-2xl border border-border/70 bg-background/75 px-5 py-4 shadow-sm backdrop-blur">
|
||||
<Image
|
||||
src="https://cdn.simpleicons.org/automattic/000000"
|
||||
alt="Automattic"
|
||||
width={40}
|
||||
height={40}
|
||||
unoptimized
|
||||
className="h-10 w-10 dark:invert"
|
||||
/>
|
||||
<span className="text-xl font-bold text-foreground">Automattic</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 9. Open Source AI */}
|
||||
<div className="flex flex-col justify-center h-full">
|
||||
<Users className="w-12 h-12 text-indigo-500 mb-6" />
|
||||
<SlideTitle>Community Sharing</SlideTitle>
|
||||
<SlideContent>
|
||||
Nobody learns in isolation. By sharing prompts, we accelerate the collective understanding of how to communicate with AI. It's the open-source movement for human-AI interaction.
|
||||
</SlideContent>
|
||||
</div>
|
||||
|
||||
{/* 10. Collective Intelligence */}
|
||||
<div className="flex flex-col items-center justify-center text-center h-full">
|
||||
<SlideTitle className="text-5xl md:text-6xl">Collective Intelligence</SlideTitle>
|
||||
<SlideContent className="max-w-4xl mt-8">
|
||||
We are building the <SlideHighlight>largest open repository</SlideHighlight> of human-AI interaction patterns.
|
||||
<br /><br />
|
||||
Tested, versioned, and peer-reviewed.
|
||||
</SlideContent>
|
||||
</div>
|
||||
|
||||
{/* 11. Feature - Typed Prompts */}
|
||||
<div className="flex flex-col justify-center h-full">
|
||||
<Code className="w-12 h-12 text-red-500 mb-6" />
|
||||
<SlideTitle>Feature: Typed Prompts</SlideTitle>
|
||||
<SlideContent>
|
||||
Natural language is ambiguous. <SlideHighlight>Typed Prompts</SlideHighlight> bring structure.
|
||||
<br /><br />
|
||||
Define inputs, outputs, and variables systematically so prompts can be executed predictably via APIs.
|
||||
</SlideContent>
|
||||
</div>
|
||||
|
||||
{/* 12. Feature - Workflows */}
|
||||
<div className="flex flex-col justify-center h-full">
|
||||
<div className="flex gap-2 mb-6">
|
||||
<div className="w-8 h-8 rounded bg-primary"></div>
|
||||
<MoveRight className="w-8 h-8 text-muted-foreground" />
|
||||
<div className="w-8 h-8 rounded bg-primary/70"></div>
|
||||
<MoveRight className="w-8 h-8 text-muted-foreground" />
|
||||
<div className="w-8 h-8 rounded bg-primary/40"></div>
|
||||
<div className="flex min-h-[70vh] flex-col justify-center">
|
||||
<SlideTitle>Bugünkü rota</SlideTitle>
|
||||
<div className="grid gap-5 md:grid-cols-2">
|
||||
<FlowStep icon={BrainCircuit} title="Zihinsel model" text="Modelin neyi, ne sırayla ve hangi amaçla görmesi gerektiğini düşünmek." />
|
||||
<FlowStep icon={MessageSquareText} title="Prompt anatomisi" text="Rol, hedef, bağlam, kısıtlar, örnekler ve çıktı sözleşmesi." />
|
||||
<FlowStep icon={Layers3} title="Context yönetimi" text="Sınırlı dikkat alanını seçmek, sıralamak, sıkıştırmak ve temizlemek." />
|
||||
<FlowStep icon={ClipboardCheck} title="Ölçüm ve paylaşım" text="Promptu tek seferlik cümle değil, iyileştirilebilir artefact yapmak." />
|
||||
</div>
|
||||
<SlideTitle>Feature: Workflows</SlideTitle>
|
||||
<SlideContent>
|
||||
Some tasks are too complex for a single prompt.
|
||||
<br /><br />
|
||||
Chain multiple prompts together to create <SlideHighlight>Workflows</SlideHighlight> — dividing and conquering complex agentic tasks.
|
||||
</SlideContent>
|
||||
</div>
|
||||
|
||||
{/* 13. Feature - Agent Skills */}
|
||||
<div className="flex flex-col justify-center h-full">
|
||||
<SlideTitle>Feature: Agent Skills</SlideTitle>
|
||||
<SlideContent>
|
||||
Equip coding assistants (like Claude, Cursor, Windsurf) with specialized, multi-file capabilities.
|
||||
<br /><br />
|
||||
Give your AI the context and rules it needs to build within your specific tech stack.
|
||||
</SlideContent>
|
||||
</div>
|
||||
|
||||
{/* 14. Feature - Change Requests */}
|
||||
<div className="flex flex-col justify-center h-full">
|
||||
<GitMerge className="w-12 h-12 text-orange-500 mb-6" />
|
||||
<SlideTitle>Feature: Change Requests</SlideTitle>
|
||||
<SlideContent>
|
||||
Prompts evolve. We've introduced <SlideHighlight>Pull Requests for Prompts.</SlideHighlight>
|
||||
<br /><br />
|
||||
Collaborate, suggest improvements, and refine instructions together as a community.
|
||||
</SlideContent>
|
||||
</div>
|
||||
|
||||
{/* 15. For Developers */}
|
||||
<div className="flex flex-col justify-center h-full">
|
||||
<SlideTitle>For Developers</SlideTitle>
|
||||
<SlideContent>
|
||||
prompts.chat isn't just for end-users. With features like Agent Skills, Typed Prompts, and API integrations, it's a vital tool for developers building the next generation of AI apps.
|
||||
</SlideContent>
|
||||
</div>
|
||||
|
||||
{/* 16. For Everyone */}
|
||||
<div className="flex flex-col justify-center h-full">
|
||||
<MessageSquare className="w-12 h-12 text-pink-500 mb-6" />
|
||||
<SlideTitle>For Everyone</SlideTitle>
|
||||
<SlideContent>
|
||||
Discover, collect, and learn from <SlideHighlight>Promptmasters</SlideHighlight>.
|
||||
<br /><br />
|
||||
A beautifully designed, accessible platform to find exactly what you need to make AI work for you.
|
||||
</SlideContent>
|
||||
</div>
|
||||
|
||||
{/* 17. Self-Hosted */}
|
||||
<div className="flex flex-col justify-center h-full">
|
||||
<Globe className="w-12 h-12 text-teal-500 mb-6" />
|
||||
<SlideTitle>Fully Self-Hosted</SlideTitle>
|
||||
<SlideContent>
|
||||
Your data, your platform.
|
||||
<br /><br />
|
||||
prompts.chat is <SlideHighlight>100% open-source</SlideHighlight> and deployable to any server or cloud provider. Own your organization's prompt library securely.
|
||||
</SlideContent>
|
||||
</div>
|
||||
|
||||
{/* 18. The Vision */}
|
||||
<div className="flex flex-col items-center justify-center text-center h-full">
|
||||
<SlideTitle className="text-5xl md:text-6xl">The Vision</SlideTitle>
|
||||
<SlideContent className="max-w-4xl mt-8">
|
||||
To create the <SlideHighlight>standard protocol</SlideHighlight> for human-AI interaction.
|
||||
<br /><br />
|
||||
Where every great instruction is shared, improved, and accessible to all.
|
||||
</SlideContent>
|
||||
</div>
|
||||
|
||||
{/* 19. Call to Action */}
|
||||
<div className="flex flex-col items-center justify-center text-center h-full">
|
||||
<div className="w-24 h-24 bg-primary/10 rounded-full flex items-center justify-center mb-8">
|
||||
<Users className="w-12 h-12 text-primary" />
|
||||
<div className="flex min-h-[70vh] flex-col justify-center">
|
||||
<SlideTitle>AI ile çalışmak: çok zeki ama bağlama bağımlı bir ekip arkadaşı</SlideTitle>
|
||||
<div className="grid gap-6 md:grid-cols-3">
|
||||
<Card icon={Target} title="Amaç net olmalı">
|
||||
Modelin optimizasyon hedefi belirsizse cevap da belirsizleşir.
|
||||
</Card>
|
||||
<Card icon={ShieldCheck} title="Sınırlar görünür olmalı">
|
||||
Ne yapılmayacağı, ne yapılacağı kadar önemlidir.
|
||||
</Card>
|
||||
<Card icon={DatabaseZap} title="Kanıt seçilmeli">
|
||||
Model en son ve en belirgin bağlama fazla ağırlık verir.
|
||||
</Card>
|
||||
</div>
|
||||
<SlideTitle>Join the Community</SlideTitle>
|
||||
<SlideContent className="max-w-2xl mb-12">
|
||||
Start exploring, sharing, and building the future of AI interaction today.
|
||||
<SlideContent className="mt-10 max-w-4xl">
|
||||
İyi prompt <SlideHighlight>ne yapılacağını</SlideHighlight>, iyi context yönetimi ise{" "}
|
||||
<SlideHighlight>neye dayanarak yapılacağını</SlideHighlight> belirler.
|
||||
</SlideContent>
|
||||
<div className="flex flex-col sm:flex-row gap-4">
|
||||
<Link href="/discover">
|
||||
<Button size="lg" className="text-lg px-8 py-6 rounded-xl">Explore Prompts</Button>
|
||||
</Link>
|
||||
<Link href="https://github.com/fka/awesome-chatgpt-prompts" target="_blank">
|
||||
<Button size="lg" variant="outline" className="text-lg px-8 py-6 rounded-xl">Star on GitHub</Button>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="grid min-h-[70vh] items-center gap-8 lg:grid-cols-2">
|
||||
<div>
|
||||
<SlideTitle>Prompt tasarımı cümle süslemek değildir</SlideTitle>
|
||||
<SlideContent>
|
||||
Prompt, modelle aranızdaki <SlideHighlight>çalışma sözleşmesidir</SlideHighlight>.
|
||||
Cevabın amacı, kapsamı, kaynakları, formatı ve kalite barı bu sözleşmede yer alır.
|
||||
</SlideContent>
|
||||
</div>
|
||||
<div className="grid gap-4">
|
||||
{[
|
||||
["Rol", "Hangi perspektiften düşünecek?"],
|
||||
["Görev", "Tam olarak neyi üretecek?"],
|
||||
["Bağlam", "Hangi bilgiye dayanacak?"],
|
||||
["Kısıt", "Neyi yapmayacak veya aşmayacak?"],
|
||||
["Çıktı", "Nasıl teslim edecek?"],
|
||||
].map(([label, text]) => (
|
||||
<div key={label} className="flex items-center gap-4 rounded-2xl border border-border bg-background/75 p-4">
|
||||
<div className="flex h-12 w-12 items-center justify-center rounded-xl bg-primary/10 font-black text-primary">{label[0]}</div>
|
||||
<div>
|
||||
<h3 className="text-xl font-bold">{label}</h3>
|
||||
<p className="text-muted-foreground">{text}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid min-h-[70vh] items-center gap-8 lg:grid-cols-2">
|
||||
<div>
|
||||
<SlideTitle>Kötü prompt / iyi prompt</SlideTitle>
|
||||
<SlideContent>
|
||||
Uzun olan değil, <SlideHighlight>karar yükünü azaltan</SlideHighlight> prompt daha iyidir.
|
||||
</SlideContent>
|
||||
</div>
|
||||
<div className="space-y-5">
|
||||
<CodePanel title="belirsiz.prompt">
|
||||
{`Bana context management hakkında iyi bir yazı yaz.`}
|
||||
</CodePanel>
|
||||
<CodePanel title="tasarlanmis.prompt">
|
||||
{`Rol: Kıdemli AI ürün danışmanı.
|
||||
Amaç: Teknik olmayan ürün ekibine context management anlat.
|
||||
Bağlam: 30 dakikalık şirket içi eğitim.
|
||||
Kısıt: Jargon kullanma, 3 örnek ver, riskleri belirt.
|
||||
Çıktı: Başlık, 5 maddelik özet, uygulanabilir kontrol listesi.`}
|
||||
</CodePanel>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex min-h-[70vh] flex-col justify-center">
|
||||
<SlideTitle>Context penceresi dosya dolabı değil, toplantı masasıdır</SlideTitle>
|
||||
<SlideContent className="max-w-5xl">
|
||||
Masaya her şeyi koyarsanız ekip odaklanamaz. Doğru belgeleri, doğru sırayla, doğru ayrıntı seviyesinde getirirsiniz.
|
||||
</SlideContent>
|
||||
<div className="mt-10 grid gap-5 md:grid-cols-4">
|
||||
<FlowStep icon={Filter} title="Seç" text="İlgili olanı ayır." />
|
||||
<FlowStep icon={Route} title="Sırala" text="Önceliği görünür yap." />
|
||||
<FlowStep icon={Scissors} title="Sıkıştır" text="Gürültüyü azalt." />
|
||||
<FlowStep icon={CheckCircle2} title="Doğrula" text="Kaynak ve sonucu kontrol et." />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid min-h-[70vh] items-center gap-10 lg:grid-cols-[0.9fr_1.1fr]">
|
||||
<div>
|
||||
<SlideTitle>Bağlam katmanları</SlideTitle>
|
||||
<SlideContent>
|
||||
Model tek bir metni değil, üst üste binmiş talimat ve kanıt katmanlarını yorumlar.
|
||||
</SlideContent>
|
||||
</div>
|
||||
<div className="grid gap-3">
|
||||
<ContextLayer label="Sistem" description="Kimlik, güvenlik ve en üst kurallar." tone="bg-red-500" />
|
||||
<ContextLayer label="Geliştirici" description="Uygulama davranışı, araç kullanımı, format kuralları." tone="bg-orange-500" />
|
||||
<ContextLayer label="Kullanıcı" description="Anlık amaç, ihtiyaç ve başarı tanımı." tone="bg-primary" />
|
||||
<ContextLayer label="Araç sonuçları" description="Arama, dosya, API veya veritabanından gelen kanıt." tone="bg-emerald-500" />
|
||||
<ContextLayer label="Geçmiş" description="Konuşma sürekliliği ve önceki kararlar." tone="bg-sky-500" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex min-h-[70vh] flex-col justify-center">
|
||||
<SlideTitle>Daha fazla context her zaman daha iyi değildir</SlideTitle>
|
||||
<div className="grid gap-6 md:grid-cols-3">
|
||||
<Card icon={CircleDotDashed} title="Gürültü" accent="text-orange-500">
|
||||
Alakasız detaylar, önemli sinyali gömer.
|
||||
</Card>
|
||||
<Card icon={RefreshCw} title="Eski kararlar" accent="text-sky-500">
|
||||
Geçmişte doğru olan bilgi bugün yanlış olabilir.
|
||||
</Card>
|
||||
<Card icon={Puzzle} title="Çelişkiler" accent="text-red-500">
|
||||
Model hangi talimata uyacağını tahmin etmek zorunda kalır.
|
||||
</Card>
|
||||
</div>
|
||||
<SlideContent className="mt-10">
|
||||
Context yönetimi, token tasarrufu değil; <SlideHighlight>dikkat yönetimidir</SlideHighlight>.
|
||||
</SlideContent>
|
||||
</div>
|
||||
|
||||
<div className="flex min-h-[70vh] flex-col justify-center">
|
||||
<SlideTitle>Pratik yöntem: seç, sırala, sıkıştır, doğrula</SlideTitle>
|
||||
<div className="grid gap-5 lg:grid-cols-4">
|
||||
<FlowStep icon={Filter} title="Seç" text="Bu görev için gerçekten gerekli bilgi ne?" />
|
||||
<FlowStep icon={Layers3} title="Sırala" text="En önemli talimat ve kanıt en görünür yerde mi?" />
|
||||
<FlowStep icon={Scissors} title="Sıkıştır" text="Tekrar, sohbet gürültüsü ve eski kararlar temizlendi mi?" />
|
||||
<FlowStep icon={ClipboardCheck} title="Doğrula" text="Cevap kaynak, format ve başarı kriteriyle uyuşuyor mu?" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid min-h-[70vh] items-center gap-10 lg:grid-cols-2">
|
||||
<div>
|
||||
<SlideTitle>Örnekler davranışı kilitler</SlideTitle>
|
||||
<SlideContent>
|
||||
Few-shot örnekler modele sadece formatı değil, <SlideHighlight>neyi iyi saydığınızı</SlideHighlight> gösterir.
|
||||
</SlideContent>
|
||||
</div>
|
||||
<div className="rounded-3xl border border-border bg-background/75 p-6 shadow-sm">
|
||||
<div className="mb-6 flex items-center gap-3">
|
||||
<Lightbulb className="h-7 w-7 text-yellow-500" />
|
||||
<h3 className="text-2xl font-bold">Örnek seçerken</h3>
|
||||
</div>
|
||||
<MiniChecklist
|
||||
items={[
|
||||
"Gerçek kullanım senaryosuna benziyor mu?",
|
||||
"Başarılı ve başarısız örnek ayrımı var mı?",
|
||||
"Format, ton ve ayrıntı seviyesi temsil ediliyor mu?",
|
||||
"Yanlış genelleme yaratacak özel durum var mı?",
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid min-h-[70vh] items-center gap-8 lg:grid-cols-2">
|
||||
<div>
|
||||
<SlideTitle>Çıktı sözleşmesi otomasyonun kapısıdır</SlideTitle>
|
||||
<SlideContent>
|
||||
İnsan okuyacaksa okunabilirlik, sistem okuyacaksa <SlideHighlight>şema</SlideHighlight> önemlidir.
|
||||
</SlideContent>
|
||||
</div>
|
||||
<CodePanel title="output-contract.json">
|
||||
{`{
|
||||
"summary": "en fazla 3 cümle",
|
||||
"risks": ["kanıta dayalı risk listesi"],
|
||||
"next_actions": [
|
||||
{ "owner": "rol", "task": "yapılacak iş", "priority": "P0 | P1 | P2" }
|
||||
],
|
||||
"confidence": "low | medium | high"
|
||||
}`}
|
||||
</CodePanel>
|
||||
</div>
|
||||
|
||||
<div className="flex min-h-[70vh] flex-col justify-center">
|
||||
<SlideTitle>Ajanlarda prompt = yetki ve durma kuralları</SlideTitle>
|
||||
<div className="grid gap-6 md:grid-cols-3">
|
||||
<Card icon={Bot} title="Ne zaman araç?" accent="text-sky-500">
|
||||
Bilgi eksikse arama yap; tahminle doldurma.
|
||||
</Card>
|
||||
<Card icon={ShieldCheck} title="Ne zaman sormalı?" accent="text-emerald-500">
|
||||
Kapsam veya risk davranışı etkiliyorsa kullanıcıya dön.
|
||||
</Card>
|
||||
<Card icon={TimerReset} title="Ne zaman durmalı?" accent="text-purple-500">
|
||||
Başarı kriteri tamamlandıysa yeni iş icat etme.
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid min-h-[70vh] items-center gap-8 lg:grid-cols-[1fr_1.1fr]">
|
||||
<div>
|
||||
<SlideTitle>Uzun işlerde context akışı tasarla</SlideTitle>
|
||||
<SlideContent>
|
||||
Tek dev prompt yerine, işi aşamalara böl: keşif, plan, uygulama, doğrulama, özet.
|
||||
</SlideContent>
|
||||
</div>
|
||||
<div className="grid gap-4">
|
||||
{[
|
||||
{ icon: Network, title: "Keşif", text: "Dosya, kaynak ve kısıtları topla." },
|
||||
{ icon: Route, title: "Plan", text: "Kararları ve bağımlılıkları görünür yap." },
|
||||
{ icon: Blocks, title: "Uygulama", text: "Sadece gerekli bağlamı sonraki adıma taşı." },
|
||||
{ icon: ClipboardCheck, title: "Doğrulama", text: "Çıktıyı ölçüt ve kaynakla kontrol et." },
|
||||
].map(({ icon: Icon, title, text }) => (
|
||||
<div key={title} className="flex items-center gap-4 rounded-2xl border border-border bg-background/75 p-4 shadow-sm">
|
||||
<div className="flex h-12 w-12 items-center justify-center rounded-xl bg-primary/10 text-primary">
|
||||
<Icon className="h-6 w-6" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-xl font-bold">{title}</h3>
|
||||
<p className="text-muted-foreground">{text}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex min-h-[70vh] flex-col justify-center">
|
||||
<SlideTitle>En sık görülen hata modları</SlideTitle>
|
||||
<div className="grid gap-5 md:grid-cols-2">
|
||||
<FlowStep icon={Target} title="Belirsiz hedef" text="Başarı tanımı yoksa model kendi hedefini seçer." />
|
||||
<FlowStep icon={Clock3} title="Eski context" text="Önceki kararlar yeni talimatla çakışır." />
|
||||
<FlowStep icon={Puzzle} title="Görünmez varsayım" text="Sizde açık olan bilgi modelde yoktur." />
|
||||
<FlowStep icon={FileText} title="Format eksikliği" text="Doğru bilgi yanlış biçimde gelirse kullanılamaz." />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid min-h-[70vh] items-center gap-10 lg:grid-cols-2">
|
||||
<div>
|
||||
<SlideTitle>Promptları ölç: tek iyi cevap yetmez</SlideTitle>
|
||||
<SlideContent>
|
||||
Küçük bir test seti, promptun gerçek hayatta dayanıklı olup olmadığını gösterir.
|
||||
</SlideContent>
|
||||
</div>
|
||||
<div className="rounded-3xl border border-border bg-background/75 p-6 shadow-sm">
|
||||
<MiniChecklist
|
||||
items={[
|
||||
"3 normal senaryo",
|
||||
"2 sınır durum",
|
||||
"1 kasıtlı belirsiz istek",
|
||||
"Beklenen format kontrolü",
|
||||
"İyi cevap nasıl görünür? kısa kontrol listesi",
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid min-h-[70vh] items-center gap-10 lg:grid-cols-[1.05fr_0.95fr]">
|
||||
<div>
|
||||
<SlideTitle>prompts.chat: promptları paylaşılabilir hale getirmek</SlideTitle>
|
||||
<SlideContent>
|
||||
Promptlar kaybolan sohbet parçaları olmamalı. Keşfedilebilir, fork edilebilir, iyileştirilebilir ve tekrar kullanılabilir olmalı.
|
||||
</SlideContent>
|
||||
</div>
|
||||
<div className="grid gap-4">
|
||||
<Card icon={Sparkles} title="Keşif">
|
||||
İyi örneği arayıp bulmak, sıfırdan başlamaktan hızlıdır.
|
||||
</Card>
|
||||
<Card icon={GitBranch} title="Değişiklik">
|
||||
Promptlar review ve geri bildirimle olgunlaşır.
|
||||
</Card>
|
||||
<Card icon={PackageCheck} title="Yeniden kullanım">
|
||||
Aynı kaliteyi farklı ekip ve iş akışlarına taşırsınız.
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex min-h-[70vh] flex-col justify-center">
|
||||
<SlideTitle>Promptlar kod gibi yaşamalı</SlideTitle>
|
||||
<div className="grid gap-5 md:grid-cols-4">
|
||||
<FlowStep icon={FileText} title="Sahiplik" text="Kim güncelliyor?" />
|
||||
<FlowStep icon={GitBranch} title="Sürüm" text="Ne değişti?" />
|
||||
<FlowStep icon={ClipboardCheck} title="Review" text="Kalite barı ne?" />
|
||||
<FlowStep icon={DatabaseZap} title="Örnek" text="Hangi veriyle çalıştı?" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid min-h-[70vh] items-center gap-10 lg:grid-cols-[0.95fr_1.05fr]">
|
||||
<div>
|
||||
<SlideTitle>Kapanış kontrol listesi</SlideTitle>
|
||||
<SlideContent>
|
||||
Bir sonraki promptunuzu göndermeden önce bu beş soruyu sorun.
|
||||
</SlideContent>
|
||||
</div>
|
||||
<MiniChecklist
|
||||
items={[
|
||||
"Modelin rolü ve hedefi net mi?",
|
||||
"Gerekli context seçildi ve sıralandı mı?",
|
||||
"Kısıtlar ve durma kuralları yazıldı mı?",
|
||||
"Çıktı formatı sözleşmeye bağlandı mı?",
|
||||
"En az birkaç gerçek senaryoyla denendi mi?",
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex min-h-[70vh] flex-col items-center justify-center text-center">
|
||||
<div className="mb-8 flex h-24 w-24 items-center justify-center rounded-full bg-primary/10 text-primary">
|
||||
<Sparkles className="h-12 w-12" />
|
||||
</div>
|
||||
<SlideTitle>Teşekkürler</SlideTitle>
|
||||
<SlideContent className="mx-auto max-w-4xl">
|
||||
İyi prompt modele <SlideHighlight>ne yapacağını</SlideHighlight> söyler.
|
||||
<br />
|
||||
İyi context yönetimi <SlideHighlight>neye dayanarak yapacağını</SlideHighlight> belirler.
|
||||
</SlideContent>
|
||||
<div className="mt-10 flex flex-wrap justify-center gap-3">
|
||||
<Pill>prompts.chat</Pill>
|
||||
<Pill>Q&A</Pill>
|
||||
</div>
|
||||
</div>
|
||||
</SlideDeck>
|
||||
|
||||
@@ -23,6 +23,8 @@ function getUserIdentifier(user: { email: string; username: string; githubUserna
|
||||
return user.githubUsername || (isUnclaimedAccount ? user.username : user.email);
|
||||
}
|
||||
|
||||
export const revalidate = 3600;
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const prompts = await db.prompt.findMany({
|
||||
|
||||
+210
-121
@@ -1,5 +1,6 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { db } from "@/lib/db";
|
||||
import crypto from "crypto";
|
||||
|
||||
function getUserIdentifier(user: {
|
||||
username: string;
|
||||
@@ -8,139 +9,227 @@ function getUserIdentifier(user: {
|
||||
return user.githubUsername || user.username;
|
||||
}
|
||||
|
||||
export async function GET() {
|
||||
const CONTENT_PREVIEW_LENGTH = 500;
|
||||
const DEFAULT_LIMIT = 50;
|
||||
const MAX_LIMIT = 100;
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
export const revalidate = 3600;
|
||||
|
||||
function generateETag(count: number, latestUpdatedAt: Date | null): string {
|
||||
const raw = `${count}-${latestUpdatedAt?.toISOString() ?? "none"}`;
|
||||
const hash = crypto.createHash("md5").update(raw).digest("hex");
|
||||
return `"${hash}"`;
|
||||
}
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const prompts = await db.prompt.findMany({
|
||||
where: {
|
||||
isPrivate: false,
|
||||
isUnlisted: false,
|
||||
deletedAt: null,
|
||||
const searchParams = request.nextUrl.searchParams;
|
||||
const fullContent = searchParams.get("full_content") === "true";
|
||||
const pageParam = searchParams.get("page");
|
||||
const limitParam = searchParams.get("limit");
|
||||
|
||||
const isPaginated = pageParam !== null || limitParam !== null;
|
||||
|
||||
const page = Math.max(1, parseInt(pageParam ?? "1", 10) || 1);
|
||||
const limit = Math.min(
|
||||
MAX_LIMIT,
|
||||
Math.max(1, parseInt(limitParam ?? String(DEFAULT_LIMIT), 10) || DEFAULT_LIMIT)
|
||||
);
|
||||
|
||||
const whereClause = {
|
||||
isPrivate: false,
|
||||
isUnlisted: false,
|
||||
deletedAt: null,
|
||||
};
|
||||
|
||||
// Fetch total count and latest updatedAt for ETag generation
|
||||
const [totalCount, latestPrompt] = await Promise.all([
|
||||
db.prompt.count({ where: whereClause }),
|
||||
db.prompt.findFirst({
|
||||
where: whereClause,
|
||||
orderBy: { updatedAt: "desc" },
|
||||
select: { updatedAt: true },
|
||||
}),
|
||||
]);
|
||||
|
||||
// Generate and check ETag for conditional requests
|
||||
const etag = generateETag(totalCount, latestPrompt?.updatedAt ?? null);
|
||||
const ifNoneMatch = request.headers.get("If-None-Match");
|
||||
|
||||
if (ifNoneMatch && ifNoneMatch === etag) {
|
||||
return new NextResponse(null, {
|
||||
status: 304,
|
||||
headers: {
|
||||
ETag: etag,
|
||||
"Cache-Control":
|
||||
"public, max-age=3600, s-maxage=3600, stale-while-revalidate=86400",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const selectClause = {
|
||||
id: true,
|
||||
title: true,
|
||||
slug: true,
|
||||
description: true,
|
||||
content: true,
|
||||
type: true,
|
||||
mediaUrl: true,
|
||||
viewCount: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
structuredFormat: true,
|
||||
isFeatured: true,
|
||||
featuredAt: true,
|
||||
requiresMediaUpload: true,
|
||||
requiredMediaType: true,
|
||||
requiredMediaCount: true,
|
||||
category: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
slug: true,
|
||||
icon: true,
|
||||
},
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
title: true,
|
||||
slug: true,
|
||||
description: true,
|
||||
content: true,
|
||||
type: true,
|
||||
mediaUrl: true,
|
||||
viewCount: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
structuredFormat: true,
|
||||
isFeatured: true,
|
||||
featuredAt: true,
|
||||
requiresMediaUpload: true,
|
||||
requiredMediaType: true,
|
||||
requiredMediaCount: true,
|
||||
category: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
slug: true,
|
||||
icon: true,
|
||||
},
|
||||
author: {
|
||||
select: {
|
||||
username: true,
|
||||
name: true,
|
||||
avatar: true,
|
||||
githubUsername: true,
|
||||
verified: true,
|
||||
},
|
||||
author: {
|
||||
select: {
|
||||
username: true,
|
||||
name: true,
|
||||
avatar: true,
|
||||
githubUsername: true,
|
||||
verified: true,
|
||||
},
|
||||
},
|
||||
contributors: {
|
||||
select: {
|
||||
username: true,
|
||||
name: true,
|
||||
avatar: true,
|
||||
githubUsername: true,
|
||||
verified: true,
|
||||
},
|
||||
contributors: {
|
||||
select: {
|
||||
username: true,
|
||||
name: true,
|
||||
avatar: true,
|
||||
githubUsername: true,
|
||||
verified: true,
|
||||
},
|
||||
},
|
||||
tags: {
|
||||
select: {
|
||||
tag: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
slug: true,
|
||||
color: true,
|
||||
},
|
||||
},
|
||||
tags: {
|
||||
select: {
|
||||
tag: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
slug: true,
|
||||
color: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
_count: {
|
||||
select: {
|
||||
votes: true,
|
||||
comments: true,
|
||||
},
|
||||
},
|
||||
_count: {
|
||||
select: {
|
||||
votes: true,
|
||||
comments: true,
|
||||
},
|
||||
},
|
||||
orderBy: { createdAt: "desc" },
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const queryOptions: any = {
|
||||
where: whereClause,
|
||||
select: selectClause,
|
||||
orderBy: { createdAt: "desc" as const },
|
||||
};
|
||||
|
||||
if (isPaginated) {
|
||||
queryOptions.skip = (page - 1) * limit;
|
||||
queryOptions.take = limit;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const prompts: any[] = await db.prompt.findMany(queryOptions);
|
||||
|
||||
const includeFullContent = fullContent;
|
||||
|
||||
const formattedPrompts = prompts.map((prompt) => {
|
||||
const content = prompt.content ?? "";
|
||||
const contentPreview =
|
||||
content.length > CONTENT_PREVIEW_LENGTH
|
||||
? content.slice(0, CONTENT_PREVIEW_LENGTH) + "..."
|
||||
: content;
|
||||
|
||||
return {
|
||||
id: prompt.id,
|
||||
title: prompt.title,
|
||||
slug: prompt.slug,
|
||||
description: prompt.description,
|
||||
...(includeFullContent
|
||||
? { content, contentPreview }
|
||||
: { contentPreview }),
|
||||
type: prompt.type,
|
||||
structuredFormat: prompt.structuredFormat,
|
||||
mediaUrl: prompt.mediaUrl,
|
||||
viewCount: prompt.viewCount,
|
||||
voteCount: prompt._count.votes,
|
||||
commentCount: prompt._count.comments,
|
||||
isFeatured: prompt.isFeatured,
|
||||
featuredAt: prompt.featuredAt,
|
||||
requiresMediaUpload: prompt.requiresMediaUpload,
|
||||
requiredMediaType: prompt.requiredMediaType,
|
||||
requiredMediaCount: prompt.requiredMediaCount,
|
||||
createdAt: prompt.createdAt,
|
||||
updatedAt: prompt.updatedAt,
|
||||
category: prompt.category
|
||||
? {
|
||||
id: prompt.category.id,
|
||||
name: prompt.category.name,
|
||||
slug: prompt.category.slug,
|
||||
icon: prompt.category.icon,
|
||||
}
|
||||
: null,
|
||||
author: {
|
||||
username: prompt.author.username,
|
||||
name: prompt.author.name,
|
||||
avatar: prompt.author.avatar,
|
||||
identifier: getUserIdentifier(prompt.author),
|
||||
verified: prompt.author.verified,
|
||||
},
|
||||
contributors: prompt.contributors.map((c: { username: string; name: string | null; avatar: string | null; githubUsername: string | null; verified: boolean }) => ({
|
||||
username: c.username,
|
||||
name: c.name,
|
||||
avatar: c.avatar,
|
||||
identifier: getUserIdentifier(c),
|
||||
verified: c.verified,
|
||||
})),
|
||||
tags: prompt.tags.map((pt: { tag: { id: string; name: string; slug: string; color: string | null } }) => ({
|
||||
id: pt.tag.id,
|
||||
name: pt.tag.name,
|
||||
slug: pt.tag.slug,
|
||||
color: pt.tag.color,
|
||||
})),
|
||||
};
|
||||
});
|
||||
|
||||
const formattedPrompts = prompts.map((prompt) => ({
|
||||
id: prompt.id,
|
||||
title: prompt.title,
|
||||
slug: prompt.slug,
|
||||
description: prompt.description,
|
||||
content: prompt.content,
|
||||
type: prompt.type,
|
||||
structuredFormat: prompt.structuredFormat,
|
||||
mediaUrl: prompt.mediaUrl,
|
||||
viewCount: prompt.viewCount,
|
||||
voteCount: prompt._count.votes,
|
||||
commentCount: prompt._count.comments,
|
||||
isFeatured: prompt.isFeatured,
|
||||
featuredAt: prompt.featuredAt,
|
||||
requiresMediaUpload: prompt.requiresMediaUpload,
|
||||
requiredMediaType: prompt.requiredMediaType,
|
||||
requiredMediaCount: prompt.requiredMediaCount,
|
||||
createdAt: prompt.createdAt,
|
||||
updatedAt: prompt.updatedAt,
|
||||
category: prompt.category
|
||||
? {
|
||||
id: prompt.category.id,
|
||||
name: prompt.category.name,
|
||||
slug: prompt.category.slug,
|
||||
icon: prompt.category.icon,
|
||||
}
|
||||
: null,
|
||||
author: {
|
||||
username: prompt.author.username,
|
||||
name: prompt.author.name,
|
||||
avatar: prompt.author.avatar,
|
||||
identifier: getUserIdentifier(prompt.author),
|
||||
verified: prompt.author.verified,
|
||||
},
|
||||
contributors: prompt.contributors.map((c) => ({
|
||||
username: c.username,
|
||||
name: c.name,
|
||||
avatar: c.avatar,
|
||||
identifier: getUserIdentifier(c),
|
||||
verified: c.verified,
|
||||
})),
|
||||
tags: prompt.tags.map((pt) => ({
|
||||
id: pt.tag.id,
|
||||
name: pt.tag.name,
|
||||
slug: pt.tag.slug,
|
||||
color: pt.tag.color,
|
||||
})),
|
||||
}));
|
||||
const totalPages = isPaginated ? Math.ceil(totalCount / limit) : 1;
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
count: formattedPrompts.length,
|
||||
prompts: formattedPrompts,
|
||||
const responseBody = isPaginated
|
||||
? {
|
||||
count: totalCount,
|
||||
page,
|
||||
limit,
|
||||
totalPages,
|
||||
hasMore: page < totalPages,
|
||||
prompts: formattedPrompts,
|
||||
}
|
||||
: {
|
||||
count: formattedPrompts.length,
|
||||
prompts: formattedPrompts,
|
||||
};
|
||||
|
||||
return NextResponse.json(responseBody, {
|
||||
headers: {
|
||||
"Cache-Control":
|
||||
"public, max-age=3600, s-maxage=3600, stale-while-revalidate=86400",
|
||||
ETag: etag,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
"Cache-Control": "public, max-age=3600",
|
||||
},
|
||||
}
|
||||
);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("prompts.json error:", error);
|
||||
return NextResponse.json(
|
||||
|
||||
@@ -87,7 +87,7 @@ export default async function EditPromptPage({ params }: EditPromptPageProps) {
|
||||
title: prompt.title,
|
||||
description: prompt.description || "",
|
||||
content: prompt.content,
|
||||
type: ((prompt.type === "IMAGE" || prompt.type === "VIDEO" || prompt.type === "AUDIO" || prompt.type === "SKILL") ? prompt.type : "TEXT") as "TEXT" | "IMAGE" | "VIDEO" | "AUDIO" | "SKILL",
|
||||
type: ((prompt.type === "IMAGE" || prompt.type === "VIDEO" || prompt.type === "AUDIO" || prompt.type === "SKILL" || prompt.type === "TASTE") ? prompt.type : "TEXT") as "TEXT" | "IMAGE" | "VIDEO" | "AUDIO" | "SKILL" | "TASTE",
|
||||
structuredFormat: prompt.structuredFormat ? (prompt.structuredFormat as "JSON" | "YAML") : undefined,
|
||||
categoryId: prompt.categoryId || undefined,
|
||||
tagIds: prompt.tags.map((t) => t.tagId),
|
||||
|
||||
@@ -8,6 +8,7 @@ export const size = {
|
||||
height: 630,
|
||||
};
|
||||
export const contentType = "image/png";
|
||||
export const revalidate = 3600;
|
||||
|
||||
const typeLabels: Record<string, string> = {
|
||||
TEXT: "Text Prompt",
|
||||
|
||||
@@ -8,6 +8,7 @@ import { AnimatedDate } from "@/components/ui/animated-date";
|
||||
import { ShareDropdown } from "@/components/prompts/share-dropdown";
|
||||
import { auth } from "@/lib/auth";
|
||||
import { db } from "@/lib/db";
|
||||
import { canViewPrompt } from "@/lib/prompt-access";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
@@ -61,13 +62,20 @@ export async function generateMetadata({ params }: PromptPageProps): Promise<Met
|
||||
const id = extractPromptId(idParam);
|
||||
const prompt = await db.prompt.findUnique({
|
||||
where: { id },
|
||||
select: { title: true, description: true },
|
||||
select: { title: true, description: true, isPrivate: true, authorId: true },
|
||||
});
|
||||
|
||||
if (!prompt) {
|
||||
return { title: "Prompt Not Found" };
|
||||
}
|
||||
|
||||
if (prompt.isPrivate) {
|
||||
const session = await auth();
|
||||
if (!canViewPrompt(prompt, session)) {
|
||||
return { title: "Prompt Not Found" };
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
title: prompt.title,
|
||||
description: prompt.description || `View the prompt: ${prompt.title}`,
|
||||
@@ -223,7 +231,7 @@ export default async function PromptPage({ params }: PromptPageProps) {
|
||||
}
|
||||
|
||||
// Check if user can view private prompt
|
||||
if (prompt.isPrivate && prompt.authorId !== session?.user?.id) {
|
||||
if (!canViewPrompt(prompt, session)) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
@@ -570,6 +578,18 @@ export default async function PromptPage({ params }: PromptPageProps) {
|
||||
promptId={prompt.id}
|
||||
promptSlug={prompt.slug ?? undefined}
|
||||
/>
|
||||
) : prompt.type === "TASTE" ? (
|
||||
<InteractivePromptContent
|
||||
content={prompt.content}
|
||||
title="taste.md"
|
||||
isLoggedIn={!!session?.user}
|
||||
promptId={prompt.id}
|
||||
promptSlug={prompt.slug ?? undefined}
|
||||
promptType={prompt.type}
|
||||
shareTitle={prompt.title}
|
||||
promptTitle={prompt.title}
|
||||
promptDescription={prompt.description ?? undefined}
|
||||
/>
|
||||
) : prompt.structuredFormat ? (
|
||||
<InteractivePromptContent
|
||||
content={prompt.content}
|
||||
@@ -659,8 +679,8 @@ export default async function PromptPage({ params }: PromptPageProps) {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Report & Prompt Flow - hide for SKILL type */}
|
||||
{prompt.type !== "SKILL" && (
|
||||
{/* Report & Prompt Flow - hide for SKILL and TASTE types */}
|
||||
{prompt.type !== "SKILL" && prompt.type !== "TASTE" && (
|
||||
<PromptFlowSection
|
||||
promptId={prompt.id}
|
||||
promptTitle={prompt.title}
|
||||
|
||||
@@ -18,7 +18,7 @@ interface PageProps {
|
||||
prompt?: string;
|
||||
title?: string;
|
||||
content?: string;
|
||||
type?: "TEXT" | "IMAGE" | "VIDEO" | "AUDIO";
|
||||
type?: "TEXT" | "IMAGE" | "VIDEO" | "AUDIO" | "SKILL" | "TASTE";
|
||||
format?: "JSON" | "YAML";
|
||||
}>;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ import { isAISearchEnabled, semanticSearch } from "@/lib/ai/embeddings";
|
||||
import { isAIGenerationEnabled } from "@/lib/ai/generation";
|
||||
import config from "@/../prompts.config";
|
||||
|
||||
export const revalidate = 60;
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Prompts",
|
||||
description: "Browse and discover AI prompts",
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
import { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { unstable_cache } from "next/cache";
|
||||
import { Plus } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { InfinitePromptList } from "@/components/prompts/infinite-prompt-list";
|
||||
import { db } from "@/lib/db";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Taste",
|
||||
description: "Browse and discover coding taste profiles",
|
||||
};
|
||||
|
||||
// Query for tastes list (cached)
|
||||
function getCachedTastes(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
orderBy: any,
|
||||
perPage: number,
|
||||
searchQuery?: string
|
||||
) {
|
||||
const cacheKey = JSON.stringify({ orderBy, perPage, searchQuery });
|
||||
|
||||
return unstable_cache(
|
||||
async () => {
|
||||
const where: Record<string, unknown> = {
|
||||
type: "TASTE",
|
||||
isPrivate: false,
|
||||
isUnlisted: false,
|
||||
deletedAt: null,
|
||||
};
|
||||
|
||||
if (searchQuery) {
|
||||
where.OR = [
|
||||
{ title: { contains: searchQuery, mode: "insensitive" } },
|
||||
{ content: { contains: searchQuery, mode: "insensitive" } },
|
||||
{ description: { contains: searchQuery, mode: "insensitive" } },
|
||||
];
|
||||
}
|
||||
|
||||
const [tastesRaw, totalCount] = await Promise.all([
|
||||
db.prompt.findMany({
|
||||
where,
|
||||
orderBy,
|
||||
skip: 0,
|
||||
take: perPage,
|
||||
include: {
|
||||
author: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
username: true,
|
||||
avatar: true,
|
||||
verified: true,
|
||||
},
|
||||
},
|
||||
category: {
|
||||
include: {
|
||||
parent: {
|
||||
select: { id: true, name: true, slug: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
tags: {
|
||||
include: {
|
||||
tag: true,
|
||||
},
|
||||
},
|
||||
contributors: {
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
name: true,
|
||||
avatar: true,
|
||||
},
|
||||
},
|
||||
_count: {
|
||||
select: {
|
||||
votes: true,
|
||||
contributors: true,
|
||||
outgoingConnections: { where: { label: { not: "related" } } },
|
||||
incomingConnections: { where: { label: { not: "related" } } },
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
db.prompt.count({ where }),
|
||||
]);
|
||||
|
||||
return {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
tastes: tastesRaw.map((p: any) => ({
|
||||
...p,
|
||||
voteCount: p._count.votes,
|
||||
contributorCount: p._count.contributors,
|
||||
contributors: p.contributors,
|
||||
})),
|
||||
total: totalCount,
|
||||
};
|
||||
},
|
||||
["taste", cacheKey],
|
||||
{ tags: ["prompts"] }
|
||||
)();
|
||||
}
|
||||
|
||||
interface TastesPageProps {
|
||||
searchParams: Promise<{
|
||||
q?: string;
|
||||
sort?: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export default async function TastesPage({ searchParams }: TastesPageProps) {
|
||||
const t = await getTranslations("prompts");
|
||||
const tNav = await getTranslations("nav");
|
||||
const tSearch = await getTranslations("search");
|
||||
const params = await searchParams;
|
||||
|
||||
const perPage = 24;
|
||||
|
||||
// Build order by clause
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
let orderBy: any = { createdAt: "desc" };
|
||||
if (params.sort === "oldest") {
|
||||
orderBy = { createdAt: "asc" };
|
||||
} else if (params.sort === "upvotes") {
|
||||
orderBy = { votes: { _count: "desc" } };
|
||||
}
|
||||
|
||||
const result = await getCachedTastes(orderBy, perPage, params.q);
|
||||
const tastes = result.tastes;
|
||||
const total = result.total;
|
||||
|
||||
return (
|
||||
<div className="container py-6">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4 mb-6">
|
||||
<div className="flex items-baseline gap-2">
|
||||
<h1 className="text-lg font-semibold">{tNav("taste")}</h1>
|
||||
<span className="text-xs text-muted-foreground">{tSearch("found", { count: total })}</span>
|
||||
</div>
|
||||
<Button size="sm" className="h-8 text-xs w-full sm:w-auto" asChild>
|
||||
<Link href="/prompts/new?type=TASTE">
|
||||
<Plus className="h-3.5 w-3.5 mr-1" />
|
||||
{t("createTaste")}
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<p className="text-sm text-muted-foreground mb-6">
|
||||
{t("tastesDescription")}
|
||||
</p>
|
||||
|
||||
<InfinitePromptList
|
||||
initialPrompts={tastes}
|
||||
initialTotal={total}
|
||||
filters={{
|
||||
q: params.q,
|
||||
type: "TASTE",
|
||||
sort: params.sort,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -9,6 +9,8 @@ interface AuthContentProps {
|
||||
providers: string[];
|
||||
mode: "login" | "register";
|
||||
useCloneBranding?: boolean;
|
||||
providerDisplayNames?: Record<string, string>;
|
||||
providerLogos?: Record<string, string>;
|
||||
}
|
||||
|
||||
const providerNames: Record<string, string> = {
|
||||
@@ -19,7 +21,7 @@ const providerNames: Record<string, string> = {
|
||||
credentials: "Email",
|
||||
};
|
||||
|
||||
export function AuthContent({ providers, mode, useCloneBranding = false }: AuthContentProps) {
|
||||
export function AuthContent({ providers, mode, useCloneBranding = false, providerDisplayNames, providerLogos }: AuthContentProps) {
|
||||
const t = useTranslations("auth");
|
||||
const hasCredentials = providers.includes("credentials");
|
||||
const oauthProviders = providers.filter((p) => p !== "credentials");
|
||||
@@ -34,7 +36,8 @@ export function AuthContent({ providers, mode, useCloneBranding = false }: AuthC
|
||||
<OAuthButton
|
||||
key={provider}
|
||||
provider={provider}
|
||||
providerName={providerNames[provider] || provider}
|
||||
providerName={providerDisplayNames?.[provider] || providerNames[provider] || provider}
|
||||
iconUrl={providerLogos?.[provider]}
|
||||
/>
|
||||
))}
|
||||
{hasGitHub && !useCloneBranding && (
|
||||
|
||||
@@ -10,6 +10,7 @@ import { analyticsAuth } from "@/lib/analytics";
|
||||
interface OAuthButtonProps {
|
||||
provider: string;
|
||||
providerName: string;
|
||||
iconUrl?: string;
|
||||
}
|
||||
|
||||
const providerIcons: Record<string, React.ReactNode> = {
|
||||
@@ -38,9 +39,10 @@ const providerIcons: Record<string, React.ReactNode> = {
|
||||
),
|
||||
};
|
||||
|
||||
export function OAuthButton({ provider, providerName }: OAuthButtonProps) {
|
||||
export function OAuthButton({ provider, providerName, iconUrl }: OAuthButtonProps) {
|
||||
const t = useTranslations("auth");
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [imgError, setImgError] = useState(false);
|
||||
|
||||
const handleSignIn = async () => {
|
||||
setIsLoading(true);
|
||||
@@ -63,8 +65,15 @@ export function OAuthButton({ provider, providerName }: OAuthButtonProps) {
|
||||
>
|
||||
{isLoading ? (
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
) : iconUrl && !imgError ? (
|
||||
/* eslint-disable-next-line @next/next/no-img-element */
|
||||
<img src={iconUrl} alt={providerName} className="mr-2 h-4 w-4 object-contain" onError={() => setImgError(true)} />
|
||||
) : providerIcons[provider] ? (
|
||||
<span className="mr-2">{providerIcons[provider]}</span>
|
||||
) : (
|
||||
<span className="mr-2 flex h-4 w-4 items-center justify-center rounded bg-primary/10 text-[10px] font-bold text-primary">
|
||||
{providerName.charAt(0).toUpperCase()}
|
||||
</span>
|
||||
)}
|
||||
{t("signInWith", { provider: providerName })}
|
||||
</Button>
|
||||
|
||||
@@ -22,7 +22,7 @@ import { analyticsAuth } from "@/lib/analytics";
|
||||
|
||||
const registerSchema = z.object({
|
||||
name: z.string().min(2, "Name must be at least 2 characters"),
|
||||
username: z.string().min(1, "Username is required").regex(/^[a-zA-Z0-9_]+$/, "Username can only contain letters, numbers, and underscores"),
|
||||
username: z.string().min(1, "Username is required").max(30, "Username must be at most 30 characters").regex(/^[a-z0-9_]+$/, "Username can only contain lowercase letters, numbers, and underscores"),
|
||||
email: z.string().email("Invalid email address"),
|
||||
password: z.string().min(6, "Password must be at least 6 characters"),
|
||||
confirmPassword: z.string(),
|
||||
|
||||
@@ -73,7 +73,7 @@ export function MusicProvider({ children }: { children: React.ReactNode }) {
|
||||
// Create and manage audio element
|
||||
useEffect(() => {
|
||||
if (!audioRef.current) {
|
||||
const audio = new Audio("/kids-music.mp3");
|
||||
const audio = new Audio("https://raw.githubusercontent.com/f/awesome-chatgpt-prompts/main/public/kids-music.mp3");
|
||||
audio.loop = true;
|
||||
audio.volume = volume;
|
||||
audioRef.current = audio;
|
||||
@@ -153,7 +153,7 @@ export function MusicButton() {
|
||||
} else {
|
||||
// Fallback behavior
|
||||
if (!localAudioRef.current) {
|
||||
localAudioRef.current = new Audio("/kids-music.mp3");
|
||||
localAudioRef.current = new Audio("https://raw.githubusercontent.com/f/awesome-chatgpt-prompts/main/public/kids-music.mp3");
|
||||
localAudioRef.current.loop = true;
|
||||
localAudioRef.current.volume = 0.3;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { Sparkles, X } from "lucide-react";
|
||||
import { useBranding } from "@/components/providers/branding-provider";
|
||||
|
||||
export interface AnnouncementBannerConfig {
|
||||
// Unique id used for the localStorage dismissal key. Bump this to re-show
|
||||
// the banner after editing the message for a new announcement.
|
||||
id: string;
|
||||
// Optional href; if provided the banner becomes a link.
|
||||
href?: string;
|
||||
// Whether the link should open in a new tab. Defaults to true for external links.
|
||||
external?: boolean;
|
||||
// Main message; rendered on small+ screens.
|
||||
message: React.ReactNode;
|
||||
// Shorter message rendered on mobile (falls back to `message`).
|
||||
messageShort?: React.ReactNode;
|
||||
// Optional call-to-action label rendered after the message (e.g. "View session →").
|
||||
cta?: string;
|
||||
}
|
||||
|
||||
// Edit this to change/disable the active announcement.
|
||||
// Set to `null` to hide the banner entirely.
|
||||
export const ACTIVE_ANNOUNCEMENT: AnnouncementBannerConfig | null = {
|
||||
id: "ms-build-2026",
|
||||
href: "https://build.microsoft.com/en-US/sessions/LTG468",
|
||||
external: true,
|
||||
message: (
|
||||
<>
|
||||
<span className="font-semibold">Fatih Kadir Akın</span> is speaking about
|
||||
prompts.chat at <span className="font-semibold">Microsoft Build 2026</span>
|
||||
{" · June 2–3, San Francisco"}
|
||||
</>
|
||||
),
|
||||
messageShort: (
|
||||
<>
|
||||
<span className="font-semibold">prompts.chat</span> at Microsoft Build 2026
|
||||
</>
|
||||
),
|
||||
cta: "View session →",
|
||||
};
|
||||
|
||||
export function AnnouncementBanner({
|
||||
announcement = ACTIVE_ANNOUNCEMENT,
|
||||
}: {
|
||||
announcement?: AnnouncementBannerConfig | null;
|
||||
}) {
|
||||
const branding = useBranding();
|
||||
const searchParams = useSearchParams();
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
|
||||
const storageKey = announcement
|
||||
? `announcement-banner-dismissed:${announcement.id}`
|
||||
: null;
|
||||
const hideViaQuery = searchParams?.has("no-announcement");
|
||||
|
||||
useEffect(() => {
|
||||
if (!storageKey) return;
|
||||
const dismissed = localStorage.getItem(storageKey);
|
||||
if (!dismissed) setIsVisible(true);
|
||||
}, [storageKey]);
|
||||
|
||||
if (!announcement || !isVisible || branding.useCloneBranding || hideViaQuery) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const handleDismiss = () => {
|
||||
if (storageKey) localStorage.setItem(storageKey, "true");
|
||||
setIsVisible(false);
|
||||
};
|
||||
|
||||
const content = (
|
||||
<>
|
||||
<Sparkles className="h-3.5 w-3.5 shrink-0" aria-hidden="true" />
|
||||
<span className="truncate">
|
||||
<span className="hidden sm:inline">{announcement.message}</span>
|
||||
<span className="sm:hidden">
|
||||
{announcement.messageShort ?? announcement.message}
|
||||
</span>
|
||||
{announcement.cta && (
|
||||
<span className="ml-1 underline-offset-2 hover:underline">
|
||||
{announcement.cta}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="bg-gradient-to-r from-primary/90 to-primary text-primary-foreground">
|
||||
<div className="container flex items-center justify-between gap-3 py-1.5 text-xs sm:text-sm">
|
||||
{announcement.href ? (
|
||||
<Link
|
||||
href={announcement.href}
|
||||
target={announcement.external ? "_blank" : undefined}
|
||||
rel={announcement.external ? "noopener noreferrer" : undefined}
|
||||
className="flex min-w-0 flex-1 items-center gap-2 hover:underline"
|
||||
>
|
||||
{content}
|
||||
</Link>
|
||||
) : (
|
||||
<div className="flex min-w-0 flex-1 items-center gap-2">{content}</div>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleDismiss}
|
||||
className="-mr-1 flex h-6 w-6 shrink-0 items-center justify-center rounded-md text-primary-foreground/80 hover:bg-primary-foreground/20 hover:text-primary-foreground"
|
||||
aria-label="Dismiss announcement"
|
||||
>
|
||||
<X className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -213,6 +213,13 @@ export function Header({ authProvider = "credentials", allowRegistration = true
|
||||
>
|
||||
{t("nav.skills")}
|
||||
</Link>
|
||||
<Link
|
||||
href="/taste"
|
||||
onClick={() => setMobileMenuOpen(false)}
|
||||
className="flex items-center gap-3 px-3 py-2.5 rounded-md text-sm font-medium text-muted-foreground hover:text-foreground hover:bg-accent transition-colors"
|
||||
>
|
||||
{t("nav.taste")}
|
||||
</Link>
|
||||
<Link
|
||||
href="/workflows"
|
||||
onClick={() => setMobileMenuOpen(false)}
|
||||
@@ -370,13 +377,19 @@ export function Header({ authProvider = "credentials", allowRegistration = true
|
||||
</Link>
|
||||
<Link
|
||||
href="/skills"
|
||||
className="px-3 py-1.5 rounded-md text-muted-foreground transition-colors hover:text-foreground hover:bg-accent"
|
||||
className="hidden xl:block px-3 py-1.5 rounded-md text-muted-foreground transition-colors hover:text-foreground hover:bg-accent"
|
||||
>
|
||||
{t("nav.skills")}
|
||||
</Link>
|
||||
<Link
|
||||
href="/taste"
|
||||
className="hidden xl:block px-3 py-1.5 rounded-md text-muted-foreground transition-colors hover:text-foreground hover:bg-accent"
|
||||
>
|
||||
{t("nav.taste")}
|
||||
</Link>
|
||||
<Link
|
||||
href="/workflows"
|
||||
className="px-3 py-1.5 rounded-md text-muted-foreground transition-colors hover:text-foreground hover:bg-accent"
|
||||
className="hidden xl:block px-3 py-1.5 rounded-md text-muted-foreground transition-colors hover:text-foreground hover:bg-accent"
|
||||
>
|
||||
{t("nav.workflows")}
|
||||
</Link>
|
||||
@@ -399,7 +412,7 @@ export function Header({ authProvider = "credentials", allowRegistration = true
|
||||
>
|
||||
{t("nav.promptmasters")}
|
||||
</Link>
|
||||
{/* Three-dot dropdown for Categories, Tags, Promptmasters on md screens */}
|
||||
{/* Three-dot dropdown for collapsed nav items */}
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="icon" className="2xl:hidden h-8 w-8">
|
||||
@@ -408,6 +421,22 @@ export function Header({ authProvider = "credentials", allowRegistration = true
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start">
|
||||
<DropdownMenuItem asChild className="xl:hidden">
|
||||
<Link href="/skills">
|
||||
{t("nav.skills")}
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem asChild className="xl:hidden">
|
||||
<Link href="/taste">
|
||||
{t("nav.taste")}
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem asChild className="xl:hidden">
|
||||
<Link href="/workflows">
|
||||
{t("nav.workflows")}
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator className="xl:hidden" />
|
||||
<DropdownMenuItem asChild>
|
||||
<Link href="/categories">
|
||||
{t("nav.categories")}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user