create-prisma-php-app 1.28.8 → 2.0.0-alpha.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -5
- package/composer.json +8 -6
- package/dist/.htaccess +49 -4
- package/dist/bootstrap.php +652 -682
- package/dist/index.js +78 -58
- package/dist/postcss.config.js +4 -6
- package/dist/prisma-php.js +2 -42
- package/dist/settings/project-name.ts +26 -2
- package/dist/src/Lib/AI/ChatGPTClient.php +32 -103
- package/dist/src/Lib/CacheHandler.php +121 -0
- package/dist/src/Lib/ErrorHandler.php +123 -0
- package/dist/src/Lib/MainLayout.php +23 -22
- package/dist/src/Lib/PHPX/TemplateCompiler.php +47 -24
- package/dist/src/Lib/PHPX/TwMerge.php +7 -1
- package/dist/src/Lib/PrismaPHPSettings.php +44 -9
- package/dist/src/Lib/Request.php +33 -37
- package/dist/src/Lib/Validator.php +35 -7
- package/dist/src/app/css/tailwind.css +1 -3
- package/dist/src/app/index.php +2 -2
- package/dist/src/app/js/index.js +12 -1
- package/dist/src/app/layout.php +4 -7
- package/dist/src/app/not-found.php +1 -1
- package/package.json +1 -1
- package/tsconfig.json +5 -6
- package/dist/prisma/schema.prisma +0 -37
- package/dist/prisma/seed.ts +0 -74
- package/dist/prisma-client-php/index.enc +0 -1
- package/dist/prisma-client-php/index.js +0 -19
- package/dist/prisma-client-php/key.enc +0 -1
- package/dist/settings/prisma-schema.json +0 -103
- package/dist/settings/prisma-sdk.ts +0 -24
- package/dist/src/Lib/Prisma/Classes/PPHPUtility.php +0 -725
- package/dist/src/Lib/Prisma/Model/IModel.php +0 -20
- package/dist/tailwind.config.js +0 -8
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{execSync}from"child_process";import fs from"fs";import{fileURLToPath}from"url";import path from"path";import chalk from"chalk";import prompts from"prompts";import https from"https";const __filename=fileURLToPath(import.meta.url),__dirname=path.dirname(__filename);let updateAnswer=null;const nonBackendFiles=["favicon.ico","\\src\\app\\index.php","metadata.php","not-found.php"],dockerFiles=[".dockerignore","docker-compose.yml","Dockerfile","apache.conf"];function bsConfigUrls(e){const s=e.indexOf("\\htdocs\\");if(-1===s)return{bsTarget:"",bsPathRewrite:{}};const t=e.substring(0,s+"\\htdocs\\".length).replace(/\\/g,"\\\\"),n=e.replace(new RegExp(`^${t}`),"").replace(/\\/g,"/");let c=`http://localhost/${n}`;c=c.endsWith("/")?c.slice(0,-1):c;const i=c.replace(/(?<!:)(\/\/+)/g,"/"),o=n.replace(/\/\/+/g,"/");return{bsTarget:`${i}/`,bsPathRewrite:{"^/":`/${o.startsWith("/")?o.substring(1):o}/`}}}async function updatePackageJson(e,s){const t=path.join(e,"package.json");if(checkExcludeFiles(t))return;const n=JSON.parse(fs.readFileSync(t,"utf8"));n.scripts={...n.scripts,projectName:"tsx settings/project-name.ts"};let c=[];if(s.tailwindcss&&(n.scripts={...n.scripts,tailwind:"postcss src/app/css/tailwind.css -o src/app/css/styles.css --watch"},c.push("tailwind")),s.websocket&&(n.scripts={...n.scripts,websocket:"tsx settings/restart-websocket.ts"},c.push("websocket")),s.docker&&(n.scripts={...n.scripts,docker:"docker-compose up"},c.push("docker")),s.swaggerDocs){const e=s.prisma?"tsx settings/auto-swagger-docs.ts":"tsx settings/swagger-config.ts";n.scripts={...n.scripts,"create-swagger-docs":e}}let i={...n.scripts};i.browserSync="tsx settings/bs-config.ts",i.dev=`npm-run-all projectName -p browserSync ${c.join(" ")}`,n.scripts=i,n.type="module",s.prisma&&(n.prisma={seed:"tsx prisma/seed.ts"}),fs.writeFileSync(t,JSON.stringify(n,null,2))}async function updateComposerJson(e,s){const t=path.join(e,"composer.json");if(checkExcludeFiles(t))return;let n;if(fs.existsSync(t)){{const e=fs.readFileSync(t,"utf8");n=JSON.parse(e)}s.websocket&&(n.require={...n.require,"cboden/ratchet":"^0.4.4"}),s.prisma&&(n.require={...n.require,"ramsey/uuid":"5.x-dev","calicastle/cuid":"^2.0"}),fs.writeFileSync(t,JSON.stringify(n,null,2))}}async function updateIndexJsForWebSocket(e,s){if(!s.websocket)return;const t=path.join(e,"src","app","js","index.js");if(checkExcludeFiles(t))return;let n=fs.readFileSync(t,"utf8");n+='\n// WebSocket initialization\nvar ws = new WebSocket("ws://localhost:8080");\n',fs.writeFileSync(t,n,"utf8")}function copyRecursiveSync(e,s,t){const n=fs.existsSync(e),c=n&&fs.statSync(e);if(n&&c&&c.isDirectory()){const n=s.toLowerCase();if(!t.websocket&&n.includes("src\\lib\\websocket"))return;if(!t.prisma&&n.includes("src\\lib\\prisma"))return;if(t.backendOnly&&n.includes("src\\app\\js")||t.backendOnly&&n.includes("src\\app\\css")||t.backendOnly&&n.includes("src\\app\\assets"))return;if(!t.swaggerDocs&&n.includes("src\\app\\swagger-docs"))return;const c=s.replace(/\\/g,"/");if(updateAnswer?.excludeFilePath?.includes(c))return;fs.existsSync(s)||fs.mkdirSync(s,{recursive:!0}),fs.readdirSync(e).forEach((n=>{copyRecursiveSync(path.join(e,n),path.join(s,n),t)}))}else{if(checkExcludeFiles(s))return;if(!t.tailwindcss&&(s.includes("tailwind.css")||s.includes("styles.css")))return;if(!t.websocket&&(s.includes("restart-websocket.ts")||s.includes("restart-websocket.bat")))return;if(!t.docker&&dockerFiles.some((e=>s.includes(e))))return;if(t.backendOnly&&nonBackendFiles.some((e=>s.includes(e))))return;if(!t.backendOnly&&s.includes("route.php"))return;if(t.backendOnly&&!t.swaggerDocs&&s.includes("layout.php"))return;if(!t.swaggerDocs&&s.includes("swagger-config.ts"))return;if(t.tailwindcss&&s.includes("index.css"))return;if((!t.swaggerDocs||!t.prisma)&&(s.includes("auto-swagger-docs.ts")||s.includes("prisma-sdk.ts")||s.includes("prisma-schema-config.json")||s.includes("prisma-schema.json")))return;fs.copyFileSync(e,s,0)}}async function executeCopy(e,s,t){s.forEach((({src:s,dest:n})=>{copyRecursiveSync(path.join(__dirname,s),path.join(e,n),t)}))}function createOrUpdateTailwindConfig(e){const s=path.join(e,"tailwind.config.js");if(checkExcludeFiles(s))return;let t=fs.readFileSync(s,"utf8");const n=["./src/**/*.{html,js,php}"].map((e=>` "${e}"`)).join(",\n");t=t.replace(/content: \[\],/g,`content: [\n${n}\n],`),fs.writeFileSync(s,t,{flag:"w"})}function modifyPostcssConfig(e){const s=path.join(e,"postcss.config.js");if(checkExcludeFiles(s))return;fs.writeFileSync(s,"export default {\n plugins: {\n tailwindcss: {},\n autoprefixer: {},\n cssnano: {},\n },\n};",{flag:"w"})}function modifyLayoutPHP(e,s){const t=path.join(e,"src","app","layout.php");if(!checkExcludeFiles(t))try{let e=fs.readFileSync(t,"utf8"),n="";s.backendOnly||(s.tailwindcss||(n='\n <link href="<?= Request::baseUrl; ?>/css/index.css" rel="stylesheet">'),n+='\n <script src="<?= Request::baseUrl; ?>/js/json5.min.js"><\/script>\n <script src="<?= Request::baseUrl; ?>/js/index.js"><\/script>');let c="";s.backendOnly||(c=s.tailwindcss?` <link href="<?= Request::baseUrl; ?>/css/styles.css" rel="stylesheet"> ${n}`:n);const i=c.length>0?"\n":"";e=e.replace("</head>",`${c}${i} \x3c!-- Dynamic Head --\x3e\n <?= MainLayout::outputHeadScripts() ?>\n</head>`),fs.writeFileSync(t,e,{flag:"w"})}catch(e){}}async function createOrUpdateEnvFile(e,s){const t=path.join(e,".env");checkExcludeFiles(t)||fs.writeFileSync(t,s,{flag:"w"})}function checkExcludeFiles(e){return!!updateAnswer?.isUpdate&&(updateAnswer?.excludeFilePath?.includes(e.replace(/\\/g,"/"))??!1)}async function createDirectoryStructure(e,s){const t=[{src:"/bootstrap.php",dest:"/bootstrap.php"},{src:"/.htaccess",dest:"/.htaccess"},{src:"/../composer.json",dest:"/composer.json"},{src:"/tsconfig.json",dest:"/tsconfig.json"},{src:"/app-gitignore",dest:"/.gitignore"}];s.tailwindcss&&t.push({src:"/postcss.config.js",dest:"/postcss.config.js"},{src:"/tailwind.config.js",dest:"/tailwind.config.js"});const n=[{src:"/settings",dest:"/settings"},{src:"/src",dest:"/src"}];s.prisma&&n.push({src:"/prisma",dest:"/prisma"}),s.docker&&n.push({src:"/.dockerignore",dest:"/.dockerignore"},{src:"/docker-compose.yml",dest:"/docker-compose.yml"},{src:"/Dockerfile",dest:"/Dockerfile"},{src:"/apache.conf",dest:"/apache.conf"}),t.forEach((({src:s,dest:t})=>{const n=path.join(__dirname,s),c=path.join(e,t);if(checkExcludeFiles(c))return;const i=fs.readFileSync(n,"utf8");fs.writeFileSync(c,i,{flag:"w"})})),await executeCopy(e,n,s),await updatePackageJson(e,s),await updateComposerJson(e,s),s.backendOnly||await updateIndexJsForWebSocket(e,s),s.tailwindcss&&(createOrUpdateTailwindConfig(e),modifyPostcssConfig(e)),(s.tailwindcss||!s.backendOnly||s.swaggerDocs)&&modifyLayoutPHP(e,s);const c='# Prisma PHP Auth Secret Key For development only - Change this in production\nAUTH_SECRET=uxsjXVPHN038DEYls2Kw0QUgBcXKUyrjv416nIFWPY4= \n \n# PHPMailer\n# SMTP_HOST=smtp.gmail.com or your SMTP host\n# SMTP_USERNAME=john.doe@gmail.com or your SMTP username\n# SMTP_PASSWORD=123456\n# SMTP_PORT=587 for TLS, 465 for SSL or your SMTP port\n# SMTP_ENCRYPTION=ssl or tls\n# MAIL_FROM=john.doe@gmail.com\n# MAIL_FROM_NAME="John Doe"\n\n# SHOW ERRORS - Set to true to show errors in the browser for development only - Change this in production to false\nSHOW_ERRORS=true\n\n# APP TIMEZONE - Set your application timezone - Default is "UTC"\nAPP_TIMEZONE="UTC"\n\n# APP ENV - Set your application environment - Default is "development" - Change this in production to "production"\nAPP_ENV=development';if(s.prisma){const s=`${'# Environment variables declared in this file are automatically made available to Prisma.\n# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema\n\n# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.\n# See the documentation for all the connection string options: https://pris.ly/d/connection-strings\n\nDATABASE_URL="postgresql://johndoe:randompassword@localhost:5432/mydb?schema=public"'}\n\n${c}`;await createOrUpdateEnvFile(e,s)}else await createOrUpdateEnvFile(e,c)}async function getAnswer(e={}){const s=[];e.projectName||s.push({type:"text",name:"projectName",message:"What is your project named?",initial:"my-app"}),e.backendOnly||s.push({type:"toggle",name:"backendOnly",message:`Would you like to create a ${chalk.blue("backend-only project")}?`,initial:!1,active:"Yes",inactive:"No"});const t=()=>{process.exit(0)},n=await prompts(s,{onCancel:t}),c=[];n.backendOnly||e.backendOnly?(e.swaggerDocs||c.push({type:"toggle",name:"swaggerDocs",message:`Would you like to use ${chalk.blue("Swagger Docs")}?`,initial:!1,active:"Yes",inactive:"No"}),e.websocket||c.push({type:"toggle",name:"websocket",message:`Would you like to use ${chalk.blue("Websocket")}?`,initial:!0,active:"Yes",inactive:"No"}),e.prisma||c.push({type:"toggle",name:"prisma",message:`Would you like to use ${chalk.blue("Prisma PHP ORM")}?`,initial:!0,active:"Yes",inactive:"No"}),e.docker||c.push({type:"toggle",name:"docker",message:`Would you like to use ${chalk.blue("Docker")}?`,initial:!1,active:"Yes",inactive:"No"})):(e.swaggerDocs||c.push({type:"toggle",name:"swaggerDocs",message:`Would you like to use ${chalk.blue("Swagger Docs")}?`,initial:!1,active:"Yes",inactive:"No"}),e.tailwindcss||c.push({type:"toggle",name:"tailwindcss",message:`Would you like to use ${chalk.blue("Tailwind CSS")}?`,initial:!0,active:"Yes",inactive:"No"}),e.websocket||c.push({type:"toggle",name:"websocket",message:`Would you like to use ${chalk.blue("Websocket")}?`,initial:!0,active:"Yes",inactive:"No"}),e.prisma||c.push({type:"toggle",name:"prisma",message:`Would you like to use ${chalk.blue("Prisma PHP ORM")}?`,initial:!0,active:"Yes",inactive:"No"}),e.docker||c.push({type:"toggle",name:"docker",message:`Would you like to use ${chalk.blue("Docker")}?`,initial:!1,active:"Yes",inactive:"No"}));const i=await prompts(c,{onCancel:t});return{projectName:n.projectName?String(n.projectName).trim().replace(/ /g,"-"):e.projectName??"my-app",backendOnly:n.backendOnly??e.backendOnly??!1,swaggerDocs:i.swaggerDocs??e.swaggerDocs??!1,tailwindcss:i.tailwindcss??e.tailwindcss??!1,websocket:i.websocket??e.websocket??!1,prisma:i.prisma??e.prisma??!1,docker:i.docker??e.docker??!1}}async function uninstallDependencies(e,s,t=!1){s.forEach((e=>{}));const n=`npm uninstall ${t?"--save-dev":"--save"} ${s.join(" ")}`;execSync(n,{stdio:"inherit",cwd:e})}function fetchPackageVersion(e){return new Promise(((s,t)=>{https.get(`https://registry.npmjs.org/${e}`,(e=>{let n="";e.on("data",(e=>n+=e)),e.on("end",(()=>{try{const e=JSON.parse(n);s(e["dist-tags"].latest)}catch(e){t(new Error("Failed to parse JSON response"))}}))})).on("error",(e=>t(e)))}))}const readJsonFile=e=>{const s=fs.readFileSync(e,"utf8");return JSON.parse(s)};function compareVersions(e,s){const t=e.split(".").map(Number),n=s.split(".").map(Number);for(let e=0;e<t.length;e++){if(t[e]>n[e])return 1;if(t[e]<n[e])return-1}return 0}function getInstalledPackageVersion(e){try{const s=execSync(`npm list -g ${e} --depth=0`).toString().match(new RegExp(`${e}@(\\d+\\.\\d+\\.\\d+)`));return s?s[1]:null}catch(e){return null}}
|
|
2
|
+
import{execSync}from"child_process";import fs from"fs";import{fileURLToPath}from"url";import path from"path";import chalk from"chalk";import prompts from"prompts";import https from"https";import{randomBytes}from"crypto";const __filename=fileURLToPath(import.meta.url),__dirname=path.dirname(__filename);let updateAnswer=null;const nonBackendFiles=["favicon.ico","\\src\\app\\index.php","metadata.php","not-found.php"],dockerFiles=[".dockerignore","docker-compose.yml","Dockerfile","apache.conf"];function bsConfigUrls(e){const s=e.indexOf("\\htdocs\\");if(-1===s)return{bsTarget:"",bsPathRewrite:{}};const t=e.substring(0,s+"\\htdocs\\".length).replace(/\\/g,"\\\\"),n=e.replace(new RegExp(`^${t}`),"").replace(/\\/g,"/");let c=`http://localhost/${n}`;c=c.endsWith("/")?c.slice(0,-1):c;const i=c.replace(/(?<!:)(\/\/+)/g,"/"),o=n.replace(/\/\/+/g,"/");return{bsTarget:`${i}/`,bsPathRewrite:{"^/":`/${o.startsWith("/")?o.substring(1):o}/`}}}async function updatePackageJson(e,s){const t=path.join(e,"package.json");if(checkExcludeFiles(t))return;const n=JSON.parse(fs.readFileSync(t,"utf8"));n.scripts={...n.scripts,projectName:"tsx settings/project-name.ts"};let c=[];if(s.tailwindcss&&(n.scripts={...n.scripts,tailwind:"postcss src/app/css/tailwind.css -o src/app/css/styles.css --watch"},c.push("tailwind")),s.websocket&&(n.scripts={...n.scripts,websocket:"tsx settings/restart-websocket.ts"},c.push("websocket")),s.docker&&(n.scripts={...n.scripts,docker:"docker-compose up"},c.push("docker")),s.swaggerDocs){const e=s.prisma?"tsx settings/auto-swagger-docs.ts":"tsx settings/swagger-config.ts";n.scripts={...n.scripts,"create-swagger-docs":e}}let i={...n.scripts};i.browserSync="tsx settings/bs-config.ts",i.dev=`npm-run-all projectName -p browserSync ${c.join(" ")}`,n.scripts=i,n.type="module",s.prisma&&(n.prisma={seed:"tsx prisma/seed.ts"}),fs.writeFileSync(t,JSON.stringify(n,null,2))}async function updateComposerJson(e,s){const t=path.join(e,"composer.json");if(checkExcludeFiles(t))return;let n;if(fs.existsSync(t)){{const e=fs.readFileSync(t,"utf8");n=JSON.parse(e)}s.websocket&&(n.require={...n.require,"cboden/ratchet":"^0.4.4"}),s.prisma&&(n.require={...n.require,"calicastle/cuid":"^2.0.0"}),fs.writeFileSync(t,JSON.stringify(n,null,2))}}async function updateIndexJsForWebSocket(e,s){if(!s.websocket)return;const t=path.join(e,"src","app","js","index.js");if(checkExcludeFiles(t))return;let n=fs.readFileSync(t,"utf8");n+='\n// WebSocket initialization\nvar ws = new WebSocket("ws://localhost:8080");\n',fs.writeFileSync(t,n,"utf8")}function generateAuthSecret(){return randomBytes(33).toString("base64")}function generateLocalStoreKey(){return randomBytes(16).toString("hex")}function copyRecursiveSync(e,s,t){const n=fs.existsSync(e),c=n&&fs.statSync(e);if(n&&c&&c.isDirectory()){const n=s.toLowerCase();if(!t.websocket&&n.includes("src\\lib\\websocket"))return;if(t.backendOnly&&n.includes("src\\app\\js")||t.backendOnly&&n.includes("src\\app\\css")||t.backendOnly&&n.includes("src\\app\\assets"))return;if(!t.swaggerDocs&&n.includes("src\\app\\swagger-docs"))return;const c=s.replace(/\\/g,"/");if(updateAnswer?.excludeFilePath?.includes(c))return;fs.existsSync(s)||fs.mkdirSync(s,{recursive:!0}),fs.readdirSync(e).forEach((n=>{copyRecursiveSync(path.join(e,n),path.join(s,n),t)}))}else{if(checkExcludeFiles(s))return;if(!t.tailwindcss&&(s.includes("tailwind.css")||s.includes("styles.css")))return;if(!t.websocket&&(s.includes("restart-websocket.ts")||s.includes("restart-websocket.bat")))return;if(!t.docker&&dockerFiles.some((e=>s.includes(e))))return;if(t.backendOnly&&nonBackendFiles.some((e=>s.includes(e))))return;if(!t.backendOnly&&s.includes("route.php"))return;if(t.backendOnly&&!t.swaggerDocs&&s.includes("layout.php"))return;if(!t.swaggerDocs&&s.includes("swagger-config.ts"))return;if(t.tailwindcss&&s.includes("index.css"))return;if((!t.swaggerDocs||!t.prisma)&&(s.includes("auto-swagger-docs.ts")||s.includes("prisma-schema-config.json")))return;fs.copyFileSync(e,s,0)}}async function executeCopy(e,s,t){s.forEach((({src:s,dest:n})=>{copyRecursiveSync(path.join(__dirname,s),path.join(e,n),t)}))}function modifyPostcssConfig(e){const s=path.join(e,"postcss.config.js");if(checkExcludeFiles(s))return;fs.writeFileSync(s,'export default {\n plugins: {\n "@tailwindcss/postcss": {},\n },\n};',{flag:"w"})}function modifyLayoutPHP(e,s){const t=path.join(e,"src","app","layout.php");if(!checkExcludeFiles(t))try{let e=fs.readFileSync(t,"utf8"),n="";s.backendOnly||(s.tailwindcss||(n='\n <link href="<?= Request::baseUrl; ?>/css/index.css" rel="stylesheet" />'),n+='\n <script src="<?= Request::baseUrl; ?>/js/json5.min.js"><\/script>\n <script src="<?= Request::baseUrl; ?>/js/index.js"><\/script>');let c="";s.backendOnly||(c=s.tailwindcss?` <link href="<?= Request::baseUrl; ?>/css/styles.css" rel="stylesheet" /> ${n}`:n),e=e.replace("</head>",`${c}\n</head>`),fs.writeFileSync(t,e,{flag:"w"})}catch(e){}}async function createOrUpdateEnvFile(e,s){const t=path.join(e,".env");checkExcludeFiles(t)||fs.writeFileSync(t,s,{flag:"w"})}function checkExcludeFiles(e){return!!updateAnswer?.isUpdate&&(updateAnswer?.excludeFilePath?.includes(e.replace(/\\/g,"/"))??!1)}async function createDirectoryStructure(e,s){const t=[{src:"/bootstrap.php",dest:"/bootstrap.php"},{src:"/.htaccess",dest:"/.htaccess"},{src:"/../composer.json",dest:"/composer.json"},{src:"/tsconfig.json",dest:"/tsconfig.json"},{src:"/app-gitignore",dest:"/.gitignore"}];s.tailwindcss&&t.push({src:"/postcss.config.js",dest:"/postcss.config.js"});const n=[{src:"/settings",dest:"/settings"},{src:"/src",dest:"/src"}];s.docker&&n.push({src:"/.dockerignore",dest:"/.dockerignore"},{src:"/docker-compose.yml",dest:"/docker-compose.yml"},{src:"/Dockerfile",dest:"/Dockerfile"},{src:"/apache.conf",dest:"/apache.conf"}),t.forEach((({src:s,dest:t})=>{const n=path.join(__dirname,s),c=path.join(e,t);if(checkExcludeFiles(c))return;const i=fs.readFileSync(n,"utf8");fs.writeFileSync(c,i,{flag:"w"})})),await executeCopy(e,n,s),await updatePackageJson(e,s),await updateComposerJson(e,s),s.backendOnly||await updateIndexJsForWebSocket(e,s),s.tailwindcss&&modifyPostcssConfig(e),(s.tailwindcss||!s.backendOnly||s.swaggerDocs)&&modifyLayoutPHP(e,s);const c=`# Prisma PHP Auth Secret Key\nAUTH_SECRET="${generateAuthSecret()}"\n\n# PHPMailer\n# SMTP_HOST="smtp.gmail.com" or your SMTP host\n# SMTP_USERNAME="john.doe@gmail.com" or your SMTP username\n# SMTP_PASSWORD="123456"\n# SMTP_PORT="587" for TLS, 465 for SSL or your SMTP port\n# SMTP_ENCRYPTION="ssl" or tls\n# MAIL_FROM="john.doe@gmail.com"\n# MAIL_FROM_NAME="John Doe"\n\n# SHOW ERRORS - Set to true to show errors in the browser for development only - Change this in production to false\nSHOW_ERRORS="true"\n\n# APP TIMEZONE - Set your application timezone - Default is "UTC"\nAPP_TIMEZONE="UTC"\n\n# APP ENV - Set your application environment - Default is "development" - Change this in production to "production"\nAPP_ENV="development"\n\n# APP CACHE ENABLED - Set to true to enable caching - Default is false\nCACHE_ENABLED="false"\n# APP CACHE TTL - Set the cache time to live in seconds - Default is 600 seconds (10 minutes)\nCACHE_TTL="600"\n\n# LOCAL STORAGE KEY - Define a custom key for local storage.\n# If not set, it defaults to "pphp_local_store_59e13".\n# Spaces in the value will be replaced with underscores, and the key will be converted to lowercase automatically.\nLOCALSTORE_KEY="${generateLocalStoreKey()}"`;if(s.prisma){const s=`${'# Environment variables declared in this file are automatically made available to Prisma.\n# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema\n\n# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.\n# See the documentation for all the connection string options: https://pris.ly/d/connection-strings\n\nDATABASE_URL="postgresql://johndoe:randompassword@localhost:5432/mydb?schema=public"'}\n\n${c}`;await createOrUpdateEnvFile(e,s)}else await createOrUpdateEnvFile(e,c)}async function getAnswer(e={}){const s=[];e.projectName||s.push({type:"text",name:"projectName",message:"What is your project named?",initial:"my-app"}),e.backendOnly||s.push({type:"toggle",name:"backendOnly",message:`Would you like to create a ${chalk.blue("backend-only project")}?`,initial:!1,active:"Yes",inactive:"No"});const t=()=>{process.exit(0)},n=await prompts(s,{onCancel:t}),c=[];n.backendOnly||e.backendOnly?(e.swaggerDocs||c.push({type:"toggle",name:"swaggerDocs",message:`Would you like to use ${chalk.blue("Swagger Docs")}?`,initial:!1,active:"Yes",inactive:"No"}),e.websocket||c.push({type:"toggle",name:"websocket",message:`Would you like to use ${chalk.blue("Websocket")}?`,initial:!0,active:"Yes",inactive:"No"}),e.prisma||c.push({type:"toggle",name:"prisma",message:`Would you like to use ${chalk.blue("Prisma PHP ORM")}?`,initial:!0,active:"Yes",inactive:"No"}),e.docker||c.push({type:"toggle",name:"docker",message:`Would you like to use ${chalk.blue("Docker")}?`,initial:!1,active:"Yes",inactive:"No"})):(e.swaggerDocs||c.push({type:"toggle",name:"swaggerDocs",message:`Would you like to use ${chalk.blue("Swagger Docs")}?`,initial:!1,active:"Yes",inactive:"No"}),e.tailwindcss||c.push({type:"toggle",name:"tailwindcss",message:`Would you like to use ${chalk.blue("Tailwind CSS")}?`,initial:!1,active:"Yes",inactive:"No"}),e.websocket||c.push({type:"toggle",name:"websocket",message:`Would you like to use ${chalk.blue("Websocket")}?`,initial:!1,active:"Yes",inactive:"No"}),e.prisma||c.push({type:"toggle",name:"prisma",message:`Would you like to use ${chalk.blue("Prisma PHP ORM")}?`,initial:!1,active:"Yes",inactive:"No"}),e.docker||c.push({type:"toggle",name:"docker",message:`Would you like to use ${chalk.blue("Docker")}?`,initial:!1,active:"Yes",inactive:"No"}));const i=await prompts(c,{onCancel:t});return{projectName:n.projectName?String(n.projectName).trim().replace(/ /g,"-"):e.projectName??"my-app",backendOnly:n.backendOnly??e.backendOnly??!1,swaggerDocs:i.swaggerDocs??e.swaggerDocs??!1,tailwindcss:i.tailwindcss??e.tailwindcss??!1,websocket:i.websocket??e.websocket??!1,prisma:i.prisma??e.prisma??!1,docker:i.docker??e.docker??!1}}async function uninstallDependencies(e,s,t=!1){s.forEach((e=>{}));const n=`npm uninstall ${t?"--save-dev":"--save"} ${s.join(" ")}`;execSync(n,{stdio:"inherit",cwd:e})}function fetchPackageVersion(e){return new Promise(((s,t)=>{https.get(`https://registry.npmjs.org/${e}`,(e=>{let n="";e.on("data",(e=>n+=e)),e.on("end",(()=>{try{const e=JSON.parse(n);s(e["dist-tags"].latest)}catch(e){t(new Error("Failed to parse JSON response"))}}))})).on("error",(e=>t(e)))}))}const readJsonFile=e=>{const s=fs.readFileSync(e,"utf8");return JSON.parse(s)};function compareVersions(e,s){const t=e.split(".").map(Number),n=s.split(".").map(Number);for(let e=0;e<t.length;e++){if(t[e]>n[e])return 1;if(t[e]<n[e])return-1}return 0}function getInstalledPackageVersion(e){try{const s=execSync(`npm list -g ${e} --depth=0`).toString().match(new RegExp(`${e}@(\\d+\\.\\d+\\.\\d+)`));return s?s[1]:null}catch(e){return null}}
|
|
3
3
|
/**
|
|
4
4
|
* Install dependencies in the specified directory.
|
|
5
5
|
* @param {string} baseDir - The base directory where to install the dependencies.
|
|
@@ -31,6 +31,31 @@ async function installDependencies(baseDir, dependencies, isDev = false) {
|
|
|
31
31
|
cwd: baseDir,
|
|
32
32
|
});
|
|
33
33
|
}
|
|
34
|
+
const pinnedVersions = {
|
|
35
|
+
"@prisma/client": "^6.4.1",
|
|
36
|
+
"@prisma/internals": "^6.4.1",
|
|
37
|
+
"@tailwindcss/postcss": "^4.0.8",
|
|
38
|
+
"@types/browser-sync": "^2.29.0",
|
|
39
|
+
"@types/node": "^22.13.5",
|
|
40
|
+
"@types/prompts": "^2.4.9",
|
|
41
|
+
autoprefixer: "^10.4.20",
|
|
42
|
+
"browser-sync": "^3.0.3",
|
|
43
|
+
chalk: "^5.4.1",
|
|
44
|
+
"chokidar-cli": "^3.0.0",
|
|
45
|
+
"http-proxy-middleware": "^3.0.3",
|
|
46
|
+
"npm-run-all": "^4.1.5",
|
|
47
|
+
"php-parser": "^3.2.2",
|
|
48
|
+
postcss: "^8.5.3",
|
|
49
|
+
"postcss-cli": "^11.0.0",
|
|
50
|
+
prisma: "^6.4.1",
|
|
51
|
+
prompts: "^2.4.2",
|
|
52
|
+
tailwindcss: "^4.0.8",
|
|
53
|
+
tsx: "^4.19.3",
|
|
54
|
+
typescript: "^5.7.3",
|
|
55
|
+
};
|
|
56
|
+
function pkg(name) {
|
|
57
|
+
return pinnedVersions[name] ? `${name}@${pinnedVersions[name]}` : name;
|
|
58
|
+
}
|
|
34
59
|
async function main() {
|
|
35
60
|
try {
|
|
36
61
|
const args = process.argv.slice(2);
|
|
@@ -59,26 +84,28 @@ async function main() {
|
|
|
59
84
|
}
|
|
60
85
|
const currentDir = process.cwd();
|
|
61
86
|
const configPath = path.join(currentDir, "prisma-php.json");
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
87
|
+
if (fs.existsSync(configPath)) {
|
|
88
|
+
const localSettings = readJsonFile(configPath);
|
|
89
|
+
let excludeFiles = [];
|
|
90
|
+
localSettings.excludeFiles?.map((file) => {
|
|
91
|
+
const filePath = path.join(currentDir, file);
|
|
92
|
+
if (fs.existsSync(filePath))
|
|
93
|
+
excludeFiles.push(filePath.replace(/\\/g, "/"));
|
|
94
|
+
});
|
|
95
|
+
updateAnswer = {
|
|
96
|
+
projectName,
|
|
97
|
+
backendOnly: answer?.backendOnly ?? false,
|
|
98
|
+
swaggerDocs: answer?.swaggerDocs ?? false,
|
|
99
|
+
tailwindcss: answer?.tailwindcss ?? false,
|
|
100
|
+
websocket: answer?.websocket ?? false,
|
|
101
|
+
prisma: answer?.prisma ?? false,
|
|
102
|
+
docker: answer?.docker ?? false,
|
|
103
|
+
isUpdate: true,
|
|
104
|
+
excludeFiles: localSettings.excludeFiles ?? [],
|
|
105
|
+
excludeFilePath: excludeFiles ?? [],
|
|
106
|
+
filePath: currentDir,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
82
109
|
} else {
|
|
83
110
|
answer = await getAnswer();
|
|
84
111
|
}
|
|
@@ -99,10 +126,10 @@ async function main() {
|
|
|
99
126
|
latestVersionOfCreatePrismaPhpApp
|
|
100
127
|
) === -1
|
|
101
128
|
) {
|
|
102
|
-
execSync(
|
|
129
|
+
execSync("npm uninstall -g create-prisma-php-app", {
|
|
103
130
|
stdio: "inherit",
|
|
104
131
|
});
|
|
105
|
-
execSync(
|
|
132
|
+
execSync("npm install -g create-prisma-php-app", {
|
|
106
133
|
stdio: "inherit",
|
|
107
134
|
});
|
|
108
135
|
}
|
|
@@ -117,50 +144,44 @@ async function main() {
|
|
|
117
144
|
: path.join(currentDir, answer.projectName);
|
|
118
145
|
if (!projectName) process.chdir(answer.projectName);
|
|
119
146
|
const dependencies = [
|
|
120
|
-
"typescript",
|
|
121
|
-
"@types/node",
|
|
122
|
-
"tsx",
|
|
123
|
-
"
|
|
124
|
-
"
|
|
125
|
-
"
|
|
126
|
-
"
|
|
127
|
-
"browser-sync",
|
|
128
|
-
"
|
|
129
|
-
"php-parser",
|
|
147
|
+
pkg("typescript"),
|
|
148
|
+
pkg("@types/node"),
|
|
149
|
+
pkg("tsx"),
|
|
150
|
+
pkg("http-proxy-middleware"),
|
|
151
|
+
pkg("chalk"),
|
|
152
|
+
pkg("npm-run-all"),
|
|
153
|
+
pkg("browser-sync"),
|
|
154
|
+
pkg("@types/browser-sync"),
|
|
155
|
+
pkg("php-parser"),
|
|
130
156
|
];
|
|
131
157
|
if (answer.swaggerDocs) {
|
|
132
|
-
dependencies.push("swagger-jsdoc", "@types/swagger-jsdoc");
|
|
158
|
+
dependencies.push(pkg("swagger-jsdoc"), pkg("@types/swagger-jsdoc"));
|
|
133
159
|
}
|
|
134
160
|
if (answer.swaggerDocs && answer.prisma) {
|
|
135
|
-
dependencies.push("prompts", "@types/prompts"
|
|
161
|
+
dependencies.push(pkg("prompts"), pkg("@types/prompts"));
|
|
136
162
|
}
|
|
137
163
|
if (answer.tailwindcss) {
|
|
138
164
|
dependencies.push(
|
|
139
|
-
"tailwindcss
|
|
140
|
-
"
|
|
141
|
-
"postcss",
|
|
142
|
-
"postcss
|
|
143
|
-
"cssnano",
|
|
144
|
-
"@jridgewell/gen-mapping"
|
|
165
|
+
pkg("tailwindcss"),
|
|
166
|
+
pkg("postcss"),
|
|
167
|
+
pkg("postcss-cli"),
|
|
168
|
+
pkg("@tailwindcss/postcss")
|
|
145
169
|
);
|
|
146
170
|
}
|
|
147
171
|
if (answer.websocket) {
|
|
148
|
-
dependencies.push("chokidar-cli");
|
|
172
|
+
dependencies.push(pkg("chokidar-cli"));
|
|
149
173
|
}
|
|
150
174
|
if (answer.prisma) {
|
|
151
|
-
|
|
175
|
+
execSync("npm install -g prisma-client-php", { stdio: "inherit" });
|
|
152
176
|
}
|
|
153
177
|
await installDependencies(projectPath, dependencies, true);
|
|
154
178
|
if (!projectName) {
|
|
155
|
-
execSync(
|
|
179
|
+
execSync("npx tsc --init", { stdio: "inherit" });
|
|
156
180
|
}
|
|
157
|
-
|
|
158
|
-
execSync(`npx tailwindcss init -p`, { stdio: "inherit" });
|
|
181
|
+
await createDirectoryStructure(projectPath, answer);
|
|
159
182
|
if (answer.prisma) {
|
|
160
|
-
|
|
161
|
-
execSync(`npx prisma init`, { stdio: "inherit" });
|
|
183
|
+
execSync("npx ppo init --prisma-php", { stdio: "inherit" });
|
|
162
184
|
}
|
|
163
|
-
await createDirectoryStructure(projectPath, answer);
|
|
164
185
|
if (answer.swaggerDocs) {
|
|
165
186
|
const swaggerDocsPath = path.join(
|
|
166
187
|
projectPath,
|
|
@@ -235,12 +256,11 @@ async function main() {
|
|
|
235
256
|
"swagger-jsdoc",
|
|
236
257
|
"@types/swagger-jsdoc",
|
|
237
258
|
"prompts",
|
|
238
|
-
"@types/prompts"
|
|
239
|
-
"@prisma/internals"
|
|
259
|
+
"@types/prompts"
|
|
240
260
|
);
|
|
241
261
|
}
|
|
242
262
|
if (!updateAnswer.tailwindcss) {
|
|
243
|
-
const tailwindFiles = ["postcss.config.js"
|
|
263
|
+
const tailwindFiles = ["postcss.config.js"];
|
|
244
264
|
tailwindFiles.forEach((file) => {
|
|
245
265
|
const filePath = path.join(projectPath, file);
|
|
246
266
|
if (fs.existsSync(filePath)) {
|
|
@@ -252,11 +272,9 @@ async function main() {
|
|
|
252
272
|
});
|
|
253
273
|
updateUninstallDependencies.push(
|
|
254
274
|
"tailwindcss",
|
|
255
|
-
"autoprefixer",
|
|
256
275
|
"postcss",
|
|
257
276
|
"postcss-cli",
|
|
258
|
-
"
|
|
259
|
-
"@jridgewell/gen-mapping"
|
|
277
|
+
"@tailwindcss/postcss"
|
|
260
278
|
);
|
|
261
279
|
}
|
|
262
280
|
if (!updateAnswer.websocket) {
|
|
@@ -286,7 +304,11 @@ async function main() {
|
|
|
286
304
|
updateUninstallDependencies.push("chokidar-cli");
|
|
287
305
|
}
|
|
288
306
|
if (!updateAnswer.prisma) {
|
|
289
|
-
updateUninstallDependencies.push(
|
|
307
|
+
updateUninstallDependencies.push(
|
|
308
|
+
"prisma",
|
|
309
|
+
"@prisma/client",
|
|
310
|
+
"@prisma/internals"
|
|
311
|
+
);
|
|
290
312
|
}
|
|
291
313
|
if (!updateAnswer.docker) {
|
|
292
314
|
const dockerFiles = [
|
|
@@ -315,13 +337,11 @@ async function main() {
|
|
|
315
337
|
}
|
|
316
338
|
const projectPathModified = projectPath.replace(/\\/g, "\\");
|
|
317
339
|
const bsConfig = bsConfigUrls(projectPathModified);
|
|
318
|
-
const phpGenerateClassPath = answer.prisma ? "src/Lib/Prisma/Classes" : "";
|
|
319
340
|
const prismaPhpConfig = {
|
|
320
341
|
projectName: answer.projectName,
|
|
321
342
|
projectRootPath: projectPathModified,
|
|
322
343
|
phpEnvironment: "XAMPP",
|
|
323
344
|
phpRootPathExe: "C:\\xampp\\php\\php.exe",
|
|
324
|
-
phpGenerateClassPath,
|
|
325
345
|
bsTarget: bsConfig.bsTarget,
|
|
326
346
|
bsPathRewrite: bsConfig.bsPathRewrite,
|
|
327
347
|
backendOnly: answer.backendOnly,
|
package/dist/postcss.config.js
CHANGED
package/dist/prisma-php.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import chalk from"chalk";import{spawn}from"child_process";import fs from"fs";import path from"path";import prompts from"prompts";
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import chalk from"chalk";import{spawn}from"child_process";import fs from"fs";import path from"path";import prompts from"prompts";const args=process.argv.slice(2),readJsonFile=e=>{const o=fs.readFileSync(e,"utf8");return JSON.parse(o)},executeCommand=(e,o=[],t={})=>new Promise(((r,s)=>{const a=spawn(e,o,{stdio:"inherit",shell:!0,...t});a.on("error",(e=>{s(e)})),a.on("close",(e=>{0===e?r():s(new Error(`Process exited with code ${e}`))}))}));async function getAnswer(){const e=[{type:"toggle",name:"shouldProceed",message:`This command will update the ${chalk.blue("create-prisma-php-app")} package and overwrite all default files. ${chalk.blue("Do you want to proceed")}?`,initial:!1,active:"Yes",inactive:"No"}],o=await prompts(e,{onCancel:()=>{process.exit(0)}});return 0===Object.keys(o).length?null:o}const commandsToExecute={update:"npx php update project"};
|
|
3
3
|
const main = async () => {
|
|
4
4
|
if (args.length === 0) {
|
|
5
5
|
console.log("No command provided.");
|
|
@@ -55,46 +55,6 @@ const main = async () => {
|
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
|
-
if (formattedCommand === commandsToExecute.generateClass) {
|
|
59
|
-
try {
|
|
60
|
-
const currentDir = process.cwd();
|
|
61
|
-
const configPath = path.join(currentDir, "prisma-php.json");
|
|
62
|
-
if (!fs.existsSync(configPath)) {
|
|
63
|
-
console.error(
|
|
64
|
-
chalk.red(
|
|
65
|
-
"The configuration file 'prisma-php.json' was not found in the current directory."
|
|
66
|
-
)
|
|
67
|
-
);
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
const localSettings = readJsonFile(configPath);
|
|
71
|
-
if (!localSettings.prisma) {
|
|
72
|
-
console.error(
|
|
73
|
-
chalk.red(
|
|
74
|
-
"Install the 'Prisma PHP ORM' package by running the command 'npx php update project'."
|
|
75
|
-
)
|
|
76
|
-
);
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
const prismaClientPath = path.join(
|
|
80
|
-
__dirname,
|
|
81
|
-
"prisma-client-php",
|
|
82
|
-
"index.js"
|
|
83
|
-
);
|
|
84
|
-
if (!fs.existsSync(prismaClientPath)) {
|
|
85
|
-
console.error(
|
|
86
|
-
chalk.red(
|
|
87
|
-
"The 'prisma-client-php' package was not found in the current directory."
|
|
88
|
-
)
|
|
89
|
-
);
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
console.log("Executing command...\n");
|
|
93
|
-
await executeCommand("node", [prismaClientPath]);
|
|
94
|
-
} catch (error) {
|
|
95
|
-
console.error("Error in script execution:", error);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
58
|
};
|
|
99
59
|
main().catch((error) => {
|
|
100
60
|
console.error("Unhandled error in main function:", error);
|
|
@@ -102,15 +102,39 @@ const deleteFilesIfExist = async (filePaths: string[]): Promise<void> => {
|
|
|
102
102
|
console.error(`Error deleting ${filePath}:`, error);
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
|
+
|
|
106
|
+
// If the file is 'request-data.json', recreate it as an empty file
|
|
107
|
+
if (filePath.endsWith("request-data.json")) {
|
|
108
|
+
try {
|
|
109
|
+
await fsPromises.writeFile(filePath, ""); // Create an empty file
|
|
110
|
+
// console.log(`Created empty ${filePath}`);
|
|
111
|
+
} catch (error) {
|
|
112
|
+
console.error(`Error creating empty ${filePath}:`, error);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
105
115
|
}
|
|
106
116
|
};
|
|
107
117
|
|
|
108
|
-
|
|
118
|
+
async function deleteDirectoriesIfExist(dirPaths: string[]): Promise<void> {
|
|
119
|
+
for (const dirPath of dirPaths) {
|
|
120
|
+
try {
|
|
121
|
+
await fsPromises.rm(dirPath, { recursive: true, force: true });
|
|
122
|
+
console.log(`Deleted directory: ${dirPath}`);
|
|
123
|
+
} catch (error) {
|
|
124
|
+
console.error(`Error deleting directory (${dirPath}):`, error);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const filesToDelete = [
|
|
109
130
|
join(__dirname, "request-data.json"),
|
|
110
131
|
join(__dirname, "class-log.json"),
|
|
111
132
|
join(__dirname, "class-imports.json"),
|
|
112
133
|
];
|
|
113
134
|
|
|
114
|
-
|
|
135
|
+
const dirsToDelete = [join(__dirname, "..", "caches")];
|
|
136
|
+
|
|
137
|
+
await deleteFilesIfExist(filesToDelete);
|
|
138
|
+
await deleteDirectoriesIfExist(dirsToDelete);
|
|
115
139
|
await updateAllClassLogs();
|
|
116
140
|
await updateComponentImports();
|
|
@@ -7,10 +7,8 @@ namespace Lib\AI;
|
|
|
7
7
|
use GuzzleHttp\Client;
|
|
8
8
|
use GuzzleHttp\Exception\RequestException;
|
|
9
9
|
use Lib\Validator;
|
|
10
|
+
use RuntimeException;
|
|
10
11
|
|
|
11
|
-
/**
|
|
12
|
-
* ChatGPTClient handles communication with the OpenAI API for generating chat responses.
|
|
13
|
-
*/
|
|
14
12
|
class ChatGPTClient
|
|
15
13
|
{
|
|
16
14
|
private Client $client;
|
|
@@ -18,9 +16,6 @@ class ChatGPTClient
|
|
|
18
16
|
private string $apiKey = '';
|
|
19
17
|
private array $cache = [];
|
|
20
18
|
|
|
21
|
-
/**
|
|
22
|
-
* Constructor initializes the Guzzle HTTP client and sets up API configuration.
|
|
23
|
-
*/
|
|
24
19
|
public function __construct(Client $client = null)
|
|
25
20
|
{
|
|
26
21
|
// Initialize the Guzzle HTTP client, allowing for dependency injection
|
|
@@ -42,9 +37,11 @@ class ChatGPTClient
|
|
|
42
37
|
protected function determineModel(array $conversationHistory): string
|
|
43
38
|
{
|
|
44
39
|
$messageCount = count($conversationHistory);
|
|
45
|
-
$totalTokens = array_reduce(
|
|
46
|
-
|
|
47
|
-
|
|
40
|
+
$totalTokens = array_reduce(
|
|
41
|
+
$conversationHistory,
|
|
42
|
+
fn($carry, $item) => $carry + str_word_count($item['content'] ?? ''),
|
|
43
|
+
0
|
|
44
|
+
);
|
|
48
45
|
|
|
49
46
|
// If the conversation is long or complex, use a model with more tokens
|
|
50
47
|
if ($totalTokens > 4000 || $messageCount > 10) {
|
|
@@ -75,13 +72,14 @@ class ChatGPTClient
|
|
|
75
72
|
}
|
|
76
73
|
|
|
77
74
|
/**
|
|
78
|
-
* Sends a message to the OpenAI API and returns the AI's response.
|
|
75
|
+
* Sends a message to the OpenAI API and returns the AI's response as HTML.
|
|
76
|
+
*
|
|
77
|
+
* @param array $conversationHistory The conversation history array containing previous messages.
|
|
78
|
+
* @param string $userMessage The new user message to add to the conversation.
|
|
79
|
+
* @return string The AI-generated HTML response.
|
|
79
80
|
*
|
|
80
|
-
* @param array $conversationHistory The conversation history array containing previous messages.
|
|
81
|
-
* @param string $userMessage The new user message to add to the conversation.
|
|
82
|
-
* @return string The AI-generated response.
|
|
83
81
|
* @throws \InvalidArgumentException If a message in the conversation history is not valid.
|
|
84
|
-
* @throws
|
|
82
|
+
* @throws RuntimeException If the API request fails or returns an unexpected format.
|
|
85
83
|
*/
|
|
86
84
|
public function sendMessage(array $conversationHistory, string $userMessage): string
|
|
87
85
|
{
|
|
@@ -92,13 +90,20 @@ class ChatGPTClient
|
|
|
92
90
|
// Optional: Convert emojis or special patterns in the message
|
|
93
91
|
$userMessage = Validator::emojis($userMessage);
|
|
94
92
|
|
|
95
|
-
//
|
|
93
|
+
// Prepare the conversation, including a system-level instruction to return valid HTML
|
|
94
|
+
$systemInstruction = [
|
|
95
|
+
'role' => 'system',
|
|
96
|
+
'content' => 'You are ChatGPT. Please provide your response in valid HTML format.'
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
// Format existing history, then prepend the system message
|
|
96
100
|
$formattedHistory = $this->formatConversationHistory($conversationHistory);
|
|
101
|
+
array_unshift($formattedHistory, $systemInstruction);
|
|
97
102
|
|
|
98
|
-
//
|
|
103
|
+
// Append the new user message
|
|
99
104
|
$formattedHistory[] = ['role' => 'user', 'content' => $userMessage];
|
|
100
105
|
|
|
101
|
-
// Check cache
|
|
106
|
+
// Check cache
|
|
102
107
|
$cacheKey = md5(serialize($formattedHistory));
|
|
103
108
|
if (isset($this->cache[$cacheKey])) {
|
|
104
109
|
return $this->cache[$cacheKey];
|
|
@@ -112,107 +117,31 @@ class ChatGPTClient
|
|
|
112
117
|
$response = $this->client->request('POST', $this->apiUrl, [
|
|
113
118
|
'headers' => [
|
|
114
119
|
'Authorization' => 'Bearer ' . $this->apiKey,
|
|
115
|
-
'Content-Type'
|
|
120
|
+
'Content-Type' => 'application/json',
|
|
116
121
|
],
|
|
117
122
|
'json' => [
|
|
118
|
-
'model'
|
|
119
|
-
'messages'
|
|
120
|
-
'max_tokens'
|
|
123
|
+
'model' => $model,
|
|
124
|
+
'messages' => $formattedHistory,
|
|
125
|
+
'max_tokens' => 500,
|
|
121
126
|
],
|
|
122
127
|
]);
|
|
123
128
|
|
|
124
|
-
|
|
125
|
-
$
|
|
126
|
-
$responseContent = json_decode($responseBody, true);
|
|
129
|
+
$responseBody = $response->getBody();
|
|
130
|
+
$responseContent = json_decode((string) $responseBody, true);
|
|
127
131
|
|
|
128
132
|
// Check if response is in expected format
|
|
129
133
|
if (isset($responseContent['choices'][0]['message']['content'])) {
|
|
130
134
|
$aiMessage = $responseContent['choices'][0]['message']['content'];
|
|
135
|
+
|
|
131
136
|
// Cache the result
|
|
132
137
|
$this->cache[$cacheKey] = $aiMessage;
|
|
138
|
+
|
|
133
139
|
return $aiMessage;
|
|
134
140
|
}
|
|
135
141
|
|
|
136
|
-
throw new
|
|
142
|
+
throw new RuntimeException('Unexpected API response format.');
|
|
137
143
|
} catch (RequestException $e) {
|
|
138
|
-
|
|
139
|
-
throw new \RuntimeException("API request failed: " . $e->getMessage(), 0, $e);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Converts a GPT response to user-friendly HTML.
|
|
145
|
-
*
|
|
146
|
-
* @param string $gptResponse The raw response from GPT.
|
|
147
|
-
* @return string The formatted HTML.
|
|
148
|
-
*/
|
|
149
|
-
public function formatGPTResponseToHTML(string $gptResponse): string
|
|
150
|
-
{
|
|
151
|
-
try {
|
|
152
|
-
// Decode all HTML entities including numeric ones
|
|
153
|
-
$gptResponse = html_entity_decode($gptResponse, ENT_QUOTES | ENT_HTML401, 'UTF-8');
|
|
154
|
-
|
|
155
|
-
// Decode HTML special characters that might still be encoded
|
|
156
|
-
$gptResponse = htmlspecialchars_decode($gptResponse, ENT_QUOTES | ENT_HTML401);
|
|
157
|
-
|
|
158
|
-
// Handle code blocks with optional language identifiers (```csharp ... ``` or ``` ... ```)
|
|
159
|
-
$gptResponse = preg_replace_callback('/```(\w+)?\s*([\s\S]*?)\s*```/m', function ($matches) {
|
|
160
|
-
$languageClass = isset($matches[1]) ? ' class="language-' . htmlspecialchars($matches[1], ENT_QUOTES | ENT_HTML401) . '"' : '';
|
|
161
|
-
return '<pre><code' . $languageClass . '>' . htmlspecialchars($matches[2], ENT_QUOTES | ENT_HTML401) . '</code></pre>';
|
|
162
|
-
}, $gptResponse);
|
|
163
|
-
|
|
164
|
-
// Convert inline code (`code`) to <code></code> without double escaping
|
|
165
|
-
$gptResponse = preg_replace_callback('/`([^`]+)`/', function ($matches) {
|
|
166
|
-
return '<code>' . htmlspecialchars($matches[1], ENT_QUOTES | ENT_HTML401) . '</code>';
|
|
167
|
-
}, $gptResponse);
|
|
168
|
-
|
|
169
|
-
// Convert bold text (e.g., **text** or __text__ -> <strong>text</strong>)
|
|
170
|
-
$gptResponse = preg_replace('/(\*\*|__)(.*?)\1/', '<strong>$2</strong>', $gptResponse);
|
|
171
|
-
|
|
172
|
-
// Convert italic text (e.g., *text* or _text_ -> <em>text</em>)
|
|
173
|
-
$gptResponse = preg_replace('/(\*|_)(.*?)\1/', '<em>$2</em>', $gptResponse);
|
|
174
|
-
|
|
175
|
-
// Convert strikethrough text (e.g., ~~text~~ -> <del>text</del>)
|
|
176
|
-
$gptResponse = preg_replace('/~~(.*?)~~/s', '<del>$1</del>', $gptResponse);
|
|
177
|
-
|
|
178
|
-
// Convert Markdown links [text](url) to HTML links <a href="url">text</a>
|
|
179
|
-
$gptResponse = preg_replace_callback('/\[(.*?)\]\((https?:\/\/[^\s]+)\)/', function ($matches) {
|
|
180
|
-
return '<a href="' . htmlspecialchars($matches[2], ENT_QUOTES | ENT_HTML401) . '" target="_blank">' . htmlspecialchars($matches[1], ENT_QUOTES | ENT_HTML401) . '</a>';
|
|
181
|
-
}, $gptResponse);
|
|
182
|
-
|
|
183
|
-
// Auto-detect and convert raw URLs to clickable links
|
|
184
|
-
$gptResponse = preg_replace('/(?<!href="|">)(https?:\/\/[^\s]+)/', '<a href="$1" target="_blank">$1</a>', $gptResponse);
|
|
185
|
-
|
|
186
|
-
// Convert headers (e.g., # Header -> <h1>Header</h1>)
|
|
187
|
-
$gptResponse = preg_replace_callback('/^(#{1,6})\s*(.*?)$/m', function ($matches) {
|
|
188
|
-
$level = strlen($matches[1]);
|
|
189
|
-
return '<h' . $level . '>' . htmlspecialchars($matches[2], ENT_QUOTES | ENT_HTML401) . '</h' . $level . '>';
|
|
190
|
-
}, $gptResponse);
|
|
191
|
-
|
|
192
|
-
// Convert blockquotes (e.g., > quote -> <blockquote>quote</blockquote>)
|
|
193
|
-
$gptResponse = preg_replace('/^>\s*(.*?)$/m', '<blockquote>$1</blockquote>', $gptResponse);
|
|
194
|
-
|
|
195
|
-
// Convert unordered lists (e.g., - item or * item -> <ul><li>item</li></ul>)
|
|
196
|
-
$gptResponse = preg_replace_callback('/(?:^\s*[-*]\s+.*$(?:\n|$))+/m', function ($matches) {
|
|
197
|
-
return '<ul>' . preg_replace('/^\s*[-*]\s+(.*)$/m', '<li>$1</li>', $matches[0]) . '</ul>';
|
|
198
|
-
}, $gptResponse);
|
|
199
|
-
|
|
200
|
-
// Convert ordered lists (e.g., 1. item -> <ol><li>item</li></ol>)
|
|
201
|
-
$gptResponse = preg_replace_callback('/(?:^\d+\.\s+.*$(?:\n|$))+/m', function ($matches) {
|
|
202
|
-
return '<ol>' . preg_replace('/^\s*\d+\.\s+(.*)$/m', '<li>$1</li>', $matches[0]) . '</ol>';
|
|
203
|
-
}, $gptResponse);
|
|
204
|
-
|
|
205
|
-
// Convert newlines to <br> for better formatting in HTML, except within <pre> and <code> tags
|
|
206
|
-
$gptResponse = preg_replace_callback('/<(pre|code)>(.*?)<\/\1>/s', function ($matches) {
|
|
207
|
-
return $matches[0]; // Keep preformatted text as it is
|
|
208
|
-
}, $gptResponse);
|
|
209
|
-
|
|
210
|
-
// Convert remaining newlines to <br>
|
|
211
|
-
$gptResponse = nl2br($gptResponse);
|
|
212
|
-
|
|
213
|
-
return $gptResponse;
|
|
214
|
-
} catch (\Throwable) {
|
|
215
|
-
return $gptResponse;
|
|
144
|
+
throw new RuntimeException("API request failed: " . $e->getMessage(), 0, $e);
|
|
216
145
|
}
|
|
217
146
|
}
|
|
218
147
|
}
|