diff --git a/.vscode/hejny.code-snippets b/.vscode/hejny.code-snippets index da5d89d8f..ea2098107 100644 --- a/.vscode/hejny.code-snippets +++ b/.vscode/hejny.code-snippets @@ -25,8 +25,8 @@ }, "test": { "scope": "typescript", - "prefix": "AI+TDD", - "description": "Test+Implementation ready to AI development", + "prefix": "Function with unit test", + "description": "Test+Implementation of one function ready to AI development", "body": [ "import { describe, expect, it } from '@jest/globals';", "import spaceTrim from 'spacetrim';", @@ -53,7 +53,10 @@ " });", "});", "", - "function $1(value: string): boolean {", + "/**", + "* Function $1 will @@@", + "*/" + "export function $1(value: string): boolean {", " return value === 'Foo';", "}", "" diff --git a/TODO.md b/TODO.md index 3a2cc1fe4..9d32de34c 100644 --- a/TODO.md +++ b/TODO.md @@ -10,14 +10,10 @@ - [ ] When importing type USE always `import type { ... } from '...'` automatically Search & replace `import\s+(\{.*\/interfaces\/)` -> `import type $1` works - [ ] Script for auto-generating fresh unused tags with emojis [ðŸ‘Đ‍ðŸĶą][ðŸ‘Đ‍ðŸĶē][ðŸ‘ģ‍♂ïļ] - - [ ] Go through all .push(...) and decide to change to [...x,y] where better - - [ ] Do not use useEffect sooo often https://youtu.be/bGzanfKVFeU?si=CSebLURTV3RKqOsn - - - -p +- [ ] Mark all non-pure functions/classes with $ prefix _(half done but need to go through code and do it for all)_ + [ ] Explain all the point why the function is not pure ## Misc diff --git a/config.ts b/config.ts index ce2d4cb33..0c37c99ba 100644 --- a/config.ts +++ b/config.ts @@ -3,28 +3,17 @@ import spaceTrim from 'spacetrim'; import { Vector } from 'xyzt'; import packageJson from './package.json'; import type { DallePrompt } from './src/ai/text-to-image/dalle/interfaces/DallePrompt'; +import { maxdown } from './src/components/Content/Maxdown/maxdown'; import { FULLHD } from './src/constants'; import type { AspectRatioRange } from './src/utils/aspect-ratio/AspectRatioRange'; import { expectAspectRatioInRange } from './src/utils/aspect-ratio/expectAspectRatioInRange'; import { DigitalOceanSpaces } from './src/utils/cdn/classes/DigitalOceanSpaces'; +import { validateClientId } from './src/utils/client/validateClientId'; import { createColorfulComputeImageColorStats15 } from './src/utils/image/palette/15/createColorfulComputeImageColorStats15'; import type { IComputeImageColorStats } from './src/utils/image/utils/IImageColorStats'; import { isRunningInBrowser } from './src/utils/isRunningInWhatever'; -import { string_email, string_font_family } from './src/utils/typeAliases'; +import type { string_email, string_font_family, string_maxdown } from './src/utils/typeAliases'; import { isUrlOnPrivateNetwork } from './src/utils/validators/isUrlOnPrivateNetwork'; -import { validateUuid } from './src/utils/validators/validateUuid'; - -export const APP_VERSION = packageJson.version; -export const APP_NAME = 'WebGPT'; -export const ADMIN_EMAIL: string_email = 'pavol@webgpt.cz'; - -export const USE_DALLE_VERSION: 2 | 3 = 3; - -export const USE_DALLE_MODEL_SETTINGS: DallePrompt['modelSettings'] = { - style: 'vivid', - quality: `standard`, - // <- TODO: !! Play with theeese to achieve best results -}; const config = ConfigChecker.from({ ...process.env, @@ -40,6 +29,20 @@ const config = ConfigChecker.from({ }); export const NEXT_PUBLIC_URL = config.get('NEXT_PUBLIC_URL').url().required().value; + +export const APP_VERSION = packageJson.version; +export const APP_NAME = 'WebGPT'; +export const ADMIN_EMAIL: string_email = 'pavol@webgpt.cz'; +export const APP_SIGNATURE: string_maxdown = maxdown`[âĢ ${APP_NAME}](${NEXT_PUBLIC_URL.href})`; + +export const USE_DALLE_VERSION: 2 | 3 = 3; + +export const USE_DALLE_MODEL_SETTINGS: DallePrompt['modelSettings'] = { + style: 'vivid', + quality: `standard`, + // <- TODO: !! Play with theeese to achieve best results +}; + export const NEXT_PUBLIC_PROMPTBOOK_SERVER_URL = config.get('NEXT_PUBLIC_PROMPTBOOK_SERVER_URL').url().required().value; export const NEXT_PUBLIC_IMAGE_SERVER_URL = config.get('NEXT_PUBLIC_IMAGE_SERVER_URL').url().required().value; @@ -123,6 +126,8 @@ export const LIMIT_WALLPAPERS_EXCLUDE = config.get('LIMIT_WALLPAPERS_EXCLUDE').l export const OPENAI_API_KEY = config.get('OPENAI_API_KEY').value; +export const SENDGRID_API_KEY = config.get('SENDGRID_API_KEY').value; + export const AZURE_COMPUTER_VISION_ENDPOINT = config.get('AZURE_COMPUTER_VISION_ENDPOINT').url().value; export const AZURE_COMPUTER_VISION_KEY = config.get('AZURE_COMPUTER_VISION_KEY').value; @@ -1046,7 +1051,7 @@ export const TEXT_BACKGROUND_COLOR_DISTANCE_THEASHOLD_RATIO = 0.5; /* <- As a ra */ export const PRIMARY_TO_AVERAGE_MAX_COLOR_DISTANCE_THEASHOLD_RATIO = 0.1; /* <- As a ratio of distance between white and black */ -export const SYSTEM_AUTHOR_ID = validateUuid('000d2940-4a35-4859-83a8-3c754ea5df51'); +export const SYSTEM_AUTHOR_ID = validateClientId('000d2940-4a35-4859-83a8-3c754ea5df51'); // TODO: [🧠] How to do required only on server export const CDN_BUCKET = config.get('CDN_BUCKET') /*.required([📐])*/.value; diff --git a/database/dumps/structure.dump.pgsql b/database/dumps/structure.dump.pgsql index 5968818a6..3a9b83b16 100644 --- a/database/dumps/structure.dump.pgsql +++ b/database/dumps/structure.dump.pgsql @@ -1316,6 +1316,92 @@ ALTER TABLE public."Client" OWNER TO postgres; COMMENT ON TABLE public."Client" IS 'Client is one browser marked with unique id'; +-- +-- Name: ClientEmailVerification; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public."ClientEmailVerification" ( + id bigint NOT NULL, + "verificationRequestId" bigint NOT NULL, + "createdAt" timestamp with time zone DEFAULT now() NOT NULL +); + + +ALTER TABLE public."ClientEmailVerification" OWNER TO postgres; + +-- +-- Name: TABLE "ClientEmailVerification"; Type: COMMENT; Schema: public; Owner: postgres +-- + +COMMENT ON TABLE public."ClientEmailVerification" IS 'Verification of the client+email. Earch row means successfuly verified client.'; + + +-- +-- Name: ClientEmailVerificationRequest; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public."ClientEmailVerificationRequest" ( + id bigint NOT NULL, + "createdAt" timestamp with time zone DEFAULT now() NOT NULL, + "clientId" uuid DEFAULT gen_random_uuid() NOT NULL, + email text NOT NULL, + code text NOT NULL +); + + +ALTER TABLE public."ClientEmailVerificationRequest" OWNER TO postgres; + +-- +-- Name: TABLE "ClientEmailVerificationRequest"; Type: COMMENT; Schema: public; Owner: postgres +-- + +COMMENT ON TABLE public."ClientEmailVerificationRequest" IS 'Requests for email verification of the client'; + + +-- +-- Name: ClientEmailVerificationRequest_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +ALTER TABLE public."ClientEmailVerificationRequest" ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public."ClientEmailVerificationRequest_id_seq" + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: ClientEmailVerification_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +ALTER TABLE public."ClientEmailVerification" ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public."ClientEmailVerification_id_seq" + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: ClientEmailVerification_withRequests; Type: VIEW; Schema: public; Owner: postgres +-- + +CREATE VIEW public."ClientEmailVerification_withRequests" AS + SELECT "ClientEmailVerification"."createdAt", + "ClientEmailVerificationRequest"."clientId", + "ClientEmailVerificationRequest".email, + "ClientEmailVerification"."verificationRequestId", + "ClientEmailVerificationRequest".code + FROM (public."ClientEmailVerification" + LEFT JOIN public."ClientEmailVerificationRequest" ON (("ClientEmailVerification"."verificationRequestId" = "ClientEmailVerificationRequest".id))); + + +ALTER TABLE public."ClientEmailVerification_withRequests" OWNER TO postgres; + -- -- Name: PromptbookFeedback; Type: TABLE; Schema: public; Owner: postgres -- @@ -1956,6 +2042,22 @@ ALTER TABLE ONLY auth.users ADD CONSTRAINT users_pkey PRIMARY KEY (id); +-- +-- Name: ClientEmailVerificationRequest ClientEmailVerificationRequest_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public."ClientEmailVerificationRequest" + ADD CONSTRAINT "ClientEmailVerificationRequest_pkey" PRIMARY KEY (id); + + +-- +-- Name: ClientEmailVerification ClientEmailVerification_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public."ClientEmailVerification" + ADD CONSTRAINT "ClientEmailVerification_pkey" PRIMARY KEY (id); + + -- -- Name: Client Client_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres -- @@ -2429,6 +2531,14 @@ ALTER TABLE ONLY auth.sso_domains ADD CONSTRAINT sso_domains_sso_provider_id_fkey FOREIGN KEY (sso_provider_id) REFERENCES auth.sso_providers(id) ON DELETE CASCADE; +-- +-- Name: ClientEmailVerification ClientEmailVerification_verificationRequestId_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public."ClientEmailVerification" + ADD CONSTRAINT "ClientEmailVerification_verificationRequestId_fkey" FOREIGN KEY ("verificationRequestId") REFERENCES public."ClientEmailVerificationRequest"(id) ON UPDATE CASCADE ON DELETE CASCADE; + + -- -- Name: WallpaperFeedback WallpaperFeedback_wallpaperId_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres -- @@ -2493,6 +2603,18 @@ CREATE POLICY "Anyone can insert a row" ON public."WallpaperFeedback" FOR INSERT ALTER TABLE public."Client" ENABLE ROW LEVEL SECURITY; +-- +-- Name: ClientEmailVerification; Type: ROW SECURITY; Schema: public; Owner: postgres +-- + +ALTER TABLE public."ClientEmailVerification" ENABLE ROW LEVEL SECURITY; + +-- +-- Name: ClientEmailVerificationRequest; Type: ROW SECURITY; Schema: public; Owner: postgres +-- + +ALTER TABLE public."ClientEmailVerificationRequest" ENABLE ROW LEVEL SECURITY; + -- -- Name: ImagePromptExecution; Type: ROW SECURITY; Schema: public; Owner: postgres -- @@ -3503,6 +3625,51 @@ GRANT ALL ON TABLE public."Client" TO authenticated; GRANT ALL ON TABLE public."Client" TO service_role; +-- +-- Name: TABLE "ClientEmailVerification"; Type: ACL; Schema: public; Owner: postgres +-- + +GRANT ALL ON TABLE public."ClientEmailVerification" TO anon; +GRANT ALL ON TABLE public."ClientEmailVerification" TO authenticated; +GRANT ALL ON TABLE public."ClientEmailVerification" TO service_role; + + +-- +-- Name: TABLE "ClientEmailVerificationRequest"; Type: ACL; Schema: public; Owner: postgres +-- + +GRANT ALL ON TABLE public."ClientEmailVerificationRequest" TO anon; +GRANT ALL ON TABLE public."ClientEmailVerificationRequest" TO authenticated; +GRANT ALL ON TABLE public."ClientEmailVerificationRequest" TO service_role; + + +-- +-- Name: SEQUENCE "ClientEmailVerificationRequest_id_seq"; Type: ACL; Schema: public; Owner: postgres +-- + +GRANT ALL ON SEQUENCE public."ClientEmailVerificationRequest_id_seq" TO anon; +GRANT ALL ON SEQUENCE public."ClientEmailVerificationRequest_id_seq" TO authenticated; +GRANT ALL ON SEQUENCE public."ClientEmailVerificationRequest_id_seq" TO service_role; + + +-- +-- Name: SEQUENCE "ClientEmailVerification_id_seq"; Type: ACL; Schema: public; Owner: postgres +-- + +GRANT ALL ON SEQUENCE public."ClientEmailVerification_id_seq" TO anon; +GRANT ALL ON SEQUENCE public."ClientEmailVerification_id_seq" TO authenticated; +GRANT ALL ON SEQUENCE public."ClientEmailVerification_id_seq" TO service_role; + + +-- +-- Name: TABLE "ClientEmailVerification_withRequests"; Type: ACL; Schema: public; Owner: postgres +-- + +GRANT ALL ON TABLE public."ClientEmailVerification_withRequests" TO anon; +GRANT ALL ON TABLE public."ClientEmailVerification_withRequests" TO authenticated; +GRANT ALL ON TABLE public."ClientEmailVerification_withRequests" TO service_role; + + -- -- Name: TABLE "PromptbookFeedback"; Type: ACL; Schema: public; Owner: postgres -- diff --git a/package-lock.json b/package-lock.json index 0ac284ff5..e3015d7e2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,13 +16,14 @@ "@monaco-editor/react": "4.5.1", "@octokit/rest": "18.12.0", "@preact/signals-react": "1.3.6", - "@promptbook/core": "0.18.0", - "@promptbook/execute-javascript": "0.18.0", - "@promptbook/openai": "0.18.0", - "@promptbook/remote-client": "0.18.0", - "@promptbook/remote-server": "0.18.0", - "@promptbook/types": "0.18.0", - "@promptbook/utils": "0.18.0", + "@promptbook/core": "0.19.0", + "@promptbook/execute-javascript": "0.19.0", + "@promptbook/openai": "0.19.0", + "@promptbook/remote-client": "0.19.0", + "@promptbook/remote-server": "0.19.0", + "@promptbook/types": "0.19.0", + "@promptbook/utils": "0.19.0", + "@sendgrid/mail": "^8.1.0", "@supabase/supabase-js": "2.26.0", "@types/file-saver": "2.0.5", "@vercel/og": "0.5.8", @@ -3058,9 +3059,9 @@ } }, "node_modules/@promptbook/core": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@promptbook/core/-/core-0.18.0.tgz", - "integrity": "sha512-My8CdfZ9q0S8fG4zptyoEv1ifOZ7skUh/SNDv5U+Gj77cbVzOD07umIQ6CSg2sLlIBHlgKAEa0PvjAALXQO5yg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@promptbook/core/-/core-0.19.0.tgz", + "integrity": "sha512-PEsD6TAXXj9flpcb8geRYktIPwyswm/4IAMK5kdjGtf5+VLr6l5IbuFizxAWNpVdpOdN/qsr4fgwog4V8NhElA==", "dependencies": { "n12": "1.6.0", "spacetrim": "0.9.2" @@ -3072,16 +3073,16 @@ "integrity": "sha512-1yc++1RbULLNqyxxqOKfrJEdjWGAG4tyQgJKi9QRjYPmyiAOqpsnphJuDOGdZWtsLg3DH2roAYjz7S74CP6nYw==" }, "node_modules/@promptbook/execute-javascript": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@promptbook/execute-javascript/-/execute-javascript-0.18.0.tgz", - "integrity": "sha512-txoytirdeTwXQurE/Za32+r489hpTmjQ89x6SXhhldPussh2uip1WajPdXxTZXctsdvk5pb29fVkBlWSdihxLQ==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@promptbook/execute-javascript/-/execute-javascript-0.19.0.tgz", + "integrity": "sha512-gu5bvMmtCwx9YbOAyFzv+Ew78w78UdymKqPY9Zx9IlOD8mUWurs1I3J9cRCNIQHEOebuZJQA07yubiSwJ/suQQ==", "dependencies": { "n12": "1.6.0", "spacetrim": "0.9.2", "waitasecond": "1.11.1" }, "peerDependencies": { - "@promptbook/core": "0.18.0" + "@promptbook/core": "0.19.0" } }, "node_modules/@promptbook/execute-javascript/node_modules/n12": { @@ -3090,14 +3091,14 @@ "integrity": "sha512-1yc++1RbULLNqyxxqOKfrJEdjWGAG4tyQgJKi9QRjYPmyiAOqpsnphJuDOGdZWtsLg3DH2roAYjz7S74CP6nYw==" }, "node_modules/@promptbook/openai": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@promptbook/openai/-/openai-0.18.0.tgz", - "integrity": "sha512-R6uP1Ay1oF1AHMbnV4M1xPC4IcsBP96iC5ySMhy8Eps5Z2y72UACsMWealEMGvyrATeZnBN56824B0qvdLI6FA==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@promptbook/openai/-/openai-0.19.0.tgz", + "integrity": "sha512-NVWCeNc0lSpSPF6H6k+pH0tbMA7FU3s2jZA0aXQcze33guzg2NrU7MF2Bc3nz9wpDpvIdDvyYkMrc6LZxMnoLw==", "dependencies": { "openai": "4.2.0" }, "peerDependencies": { - "@promptbook/core": "0.18.0" + "@promptbook/core": "0.19.0" } }, "node_modules/@promptbook/openai/node_modules/openai": { @@ -3119,45 +3120,45 @@ } }, "node_modules/@promptbook/remote-client": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@promptbook/remote-client/-/remote-client-0.18.0.tgz", - "integrity": "sha512-9fFT2eYiKgpanbRCV9CPz6kZ3SK84PHcCDDM8Z8ARkD/C7L5ulVEvKaX2nN4oxvvs8oUpKP2f1reayX9Zt/Vqg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@promptbook/remote-client/-/remote-client-0.19.0.tgz", + "integrity": "sha512-NcBURR0qYoAOAACIGHD/HP+GaJ5vnpTbtSVpj1wJgro9lIuY9xcckEz2jMkptW3b1tunYSPTtQsPx8tQuMEkTQ==", "dependencies": { "socket.io-client": "4.7.2" }, "peerDependencies": { - "@promptbook/core": "0.18.0" + "@promptbook/core": "0.19.0" } }, "node_modules/@promptbook/remote-server": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@promptbook/remote-server/-/remote-server-0.18.0.tgz", - "integrity": "sha512-CBbi/8JX2gK7CNYDOZV56UgGyBgXnSFJ3J8WF01YFBoKiKtzcEuHbKiK/d8un8cVsF+xBimOZ/xDzHHvxOsjAw==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@promptbook/remote-server/-/remote-server-0.19.0.tgz", + "integrity": "sha512-Y/Nm45q18rJAP0tAJPSk530cFhpAVGV/5rDq031TTdV4lNn4/p/MpWRjP0wMJeyomGJVs8POy9KxtPRmDP1Nnw==", "dependencies": { "socket.io": "4.7.2", "spacetrim": "0.9.2" }, "peerDependencies": { - "@promptbook/core": "0.18.0" + "@promptbook/core": "0.19.0" } }, "node_modules/@promptbook/types": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@promptbook/types/-/types-0.18.0.tgz", - "integrity": "sha512-Ij8asg9y909jlaffXoa0ir3oI0JTNjwNJftqK5ZiB06G7Hu8N1YpJpvZX+FNDQ0IwrsQzWDCPifpuCcdF2dPsw==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@promptbook/types/-/types-0.19.0.tgz", + "integrity": "sha512-R0FyDWfRtObc47oyC+jVXfcgsdY9USnonPDxauDgHYNoqzVT3O578h92+CYprMr8itfMaftAJY8WqiA4xMtUSA==", "peerDependencies": { - "@promptbook/core": "0.18.0" + "@promptbook/core": "0.19.0" } }, "node_modules/@promptbook/utils": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@promptbook/utils/-/utils-0.18.0.tgz", - "integrity": "sha512-ATuzOBLOZ67JB775WWyN52EKhmvz+6n54YQHenjK0uzMMNYf2O+2V8NxYlvXYxeH/uxbrOoKSJAK1NouoSKLnA==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@promptbook/utils/-/utils-0.19.0.tgz", + "integrity": "sha512-M46wano6loWD9mqk8XTXynTSUmxlX5teh8BR418r+k0fhldX23vdJH/cjS9ECUQVzsThY+aDZNhEkEtMFS7KXg==", "dependencies": { "spacetrim": "0.9.2" }, "peerDependencies": { - "@promptbook/core": "0.18.0" + "@promptbook/core": "0.19.0" } }, "node_modules/@puppeteer/browsers": { @@ -3283,6 +3284,69 @@ "integrity": "sha512-6i/8UoL0P5y4leBIGzvkZdS85RDMG9y1ihZzmTZQ5LdHUYmZ7pKFoj8X0236s3lusPs1Fa5HTQUpwI+UfTcmeA==", "dev": true }, + "node_modules/@sendgrid/client": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@sendgrid/client/-/client-8.1.0.tgz", + "integrity": "sha512-Kp2kKLr307v/HnR3uGuySt0AbCkeG7naDVOzfPOtWvKHVZIEHmKidQjJjzytVZNYWtoRdYgNfBw6GyUznGqa6w==", + "dependencies": { + "@sendgrid/helpers": "^8.0.0", + "axios": "^1.6.0" + }, + "engines": { + "node": ">=12.*" + } + }, + "node_modules/@sendgrid/client/node_modules/axios": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz", + "integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==", + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/@sendgrid/client/node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@sendgrid/client/node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/@sendgrid/helpers": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@sendgrid/helpers/-/helpers-8.0.0.tgz", + "integrity": "sha512-Ze7WuW2Xzy5GT5WRx+yEv89fsg/pgy3T1E3FS0QEx0/VvRmigMZ5qyVGhJz4SxomegDkzXv/i0aFPpHKN8qdAA==", + "dependencies": { + "deepmerge": "^4.2.2" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/@sendgrid/mail": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@sendgrid/mail/-/mail-8.1.0.tgz", + "integrity": "sha512-WkE0qwOrJMX9oQ+Xvtl3CdmucD6/iKw6go0VPoPieVlfXc43rbIf91wvtO6m7sKPnzxw3G+8rekBgXibmP4S8Q==", + "dependencies": { + "@sendgrid/client": "^8.1.0", + "@sendgrid/helpers": "^8.0.0" + }, + "engines": { + "node": ">=12.*" + } + }, "node_modules/@shuding/opentype.js": { "version": "1.4.0-beta.0", "resolved": "https://registry.npmjs.org/@shuding/opentype.js/-/opentype.js-1.4.0-beta.0.tgz", @@ -7145,7 +7209,6 @@ "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -19433,9 +19496,9 @@ } }, "@promptbook/core": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@promptbook/core/-/core-0.18.0.tgz", - "integrity": "sha512-My8CdfZ9q0S8fG4zptyoEv1ifOZ7skUh/SNDv5U+Gj77cbVzOD07umIQ6CSg2sLlIBHlgKAEa0PvjAALXQO5yg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@promptbook/core/-/core-0.19.0.tgz", + "integrity": "sha512-PEsD6TAXXj9flpcb8geRYktIPwyswm/4IAMK5kdjGtf5+VLr6l5IbuFizxAWNpVdpOdN/qsr4fgwog4V8NhElA==", "requires": { "n12": "1.6.0", "spacetrim": "0.9.2" @@ -19449,9 +19512,9 @@ } }, "@promptbook/execute-javascript": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@promptbook/execute-javascript/-/execute-javascript-0.18.0.tgz", - "integrity": "sha512-txoytirdeTwXQurE/Za32+r489hpTmjQ89x6SXhhldPussh2uip1WajPdXxTZXctsdvk5pb29fVkBlWSdihxLQ==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@promptbook/execute-javascript/-/execute-javascript-0.19.0.tgz", + "integrity": "sha512-gu5bvMmtCwx9YbOAyFzv+Ew78w78UdymKqPY9Zx9IlOD8mUWurs1I3J9cRCNIQHEOebuZJQA07yubiSwJ/suQQ==", "requires": { "n12": "1.6.0", "spacetrim": "0.9.2", @@ -19466,9 +19529,9 @@ } }, "@promptbook/openai": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@promptbook/openai/-/openai-0.18.0.tgz", - "integrity": "sha512-R6uP1Ay1oF1AHMbnV4M1xPC4IcsBP96iC5ySMhy8Eps5Z2y72UACsMWealEMGvyrATeZnBN56824B0qvdLI6FA==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@promptbook/openai/-/openai-0.19.0.tgz", + "integrity": "sha512-NVWCeNc0lSpSPF6H6k+pH0tbMA7FU3s2jZA0aXQcze33guzg2NrU7MF2Bc3nz9wpDpvIdDvyYkMrc6LZxMnoLw==", "requires": { "openai": "4.2.0" }, @@ -19491,32 +19554,32 @@ } }, "@promptbook/remote-client": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@promptbook/remote-client/-/remote-client-0.18.0.tgz", - "integrity": "sha512-9fFT2eYiKgpanbRCV9CPz6kZ3SK84PHcCDDM8Z8ARkD/C7L5ulVEvKaX2nN4oxvvs8oUpKP2f1reayX9Zt/Vqg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@promptbook/remote-client/-/remote-client-0.19.0.tgz", + "integrity": "sha512-NcBURR0qYoAOAACIGHD/HP+GaJ5vnpTbtSVpj1wJgro9lIuY9xcckEz2jMkptW3b1tunYSPTtQsPx8tQuMEkTQ==", "requires": { "socket.io-client": "4.7.2" } }, "@promptbook/remote-server": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@promptbook/remote-server/-/remote-server-0.18.0.tgz", - "integrity": "sha512-CBbi/8JX2gK7CNYDOZV56UgGyBgXnSFJ3J8WF01YFBoKiKtzcEuHbKiK/d8un8cVsF+xBimOZ/xDzHHvxOsjAw==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@promptbook/remote-server/-/remote-server-0.19.0.tgz", + "integrity": "sha512-Y/Nm45q18rJAP0tAJPSk530cFhpAVGV/5rDq031TTdV4lNn4/p/MpWRjP0wMJeyomGJVs8POy9KxtPRmDP1Nnw==", "requires": { "socket.io": "4.7.2", "spacetrim": "0.9.2" } }, "@promptbook/types": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@promptbook/types/-/types-0.18.0.tgz", - "integrity": "sha512-Ij8asg9y909jlaffXoa0ir3oI0JTNjwNJftqK5ZiB06G7Hu8N1YpJpvZX+FNDQ0IwrsQzWDCPifpuCcdF2dPsw==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@promptbook/types/-/types-0.19.0.tgz", + "integrity": "sha512-R0FyDWfRtObc47oyC+jVXfcgsdY9USnonPDxauDgHYNoqzVT3O578h92+CYprMr8itfMaftAJY8WqiA4xMtUSA==", "requires": {} }, "@promptbook/utils": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@promptbook/utils/-/utils-0.18.0.tgz", - "integrity": "sha512-ATuzOBLOZ67JB775WWyN52EKhmvz+6n54YQHenjK0uzMMNYf2O+2V8NxYlvXYxeH/uxbrOoKSJAK1NouoSKLnA==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@promptbook/utils/-/utils-0.19.0.tgz", + "integrity": "sha512-M46wano6loWD9mqk8XTXynTSUmxlX5teh8BR418r+k0fhldX23vdJH/cjS9ECUQVzsThY+aDZNhEkEtMFS7KXg==", "requires": { "spacetrim": "0.9.2" } @@ -19611,6 +19674,59 @@ "integrity": "sha512-6i/8UoL0P5y4leBIGzvkZdS85RDMG9y1ihZzmTZQ5LdHUYmZ7pKFoj8X0236s3lusPs1Fa5HTQUpwI+UfTcmeA==", "dev": true }, + "@sendgrid/client": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@sendgrid/client/-/client-8.1.0.tgz", + "integrity": "sha512-Kp2kKLr307v/HnR3uGuySt0AbCkeG7naDVOzfPOtWvKHVZIEHmKidQjJjzytVZNYWtoRdYgNfBw6GyUznGqa6w==", + "requires": { + "@sendgrid/helpers": "^8.0.0", + "axios": "^1.6.0" + }, + "dependencies": { + "axios": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz", + "integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==", + "requires": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + } + } + }, + "@sendgrid/helpers": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@sendgrid/helpers/-/helpers-8.0.0.tgz", + "integrity": "sha512-Ze7WuW2Xzy5GT5WRx+yEv89fsg/pgy3T1E3FS0QEx0/VvRmigMZ5qyVGhJz4SxomegDkzXv/i0aFPpHKN8qdAA==", + "requires": { + "deepmerge": "^4.2.2" + } + }, + "@sendgrid/mail": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@sendgrid/mail/-/mail-8.1.0.tgz", + "integrity": "sha512-WkE0qwOrJMX9oQ+Xvtl3CdmucD6/iKw6go0VPoPieVlfXc43rbIf91wvtO6m7sKPnzxw3G+8rekBgXibmP4S8Q==", + "requires": { + "@sendgrid/client": "^8.1.0", + "@sendgrid/helpers": "^8.0.0" + } + }, "@shuding/opentype.js": { "version": "1.4.0-beta.0", "resolved": "https://registry.npmjs.org/@shuding/opentype.js/-/opentype.js-1.4.0-beta.0.tgz", @@ -22682,8 +22798,7 @@ "deepmerge": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==" }, "define-data-property": { "version": "1.1.1", diff --git a/package.json b/package.json index 51a9cd58b..9f0f02c65 100644 --- a/package.json +++ b/package.json @@ -64,13 +64,14 @@ "@monaco-editor/react": "4.5.1", "@octokit/rest": "18.12.0", "@preact/signals-react": "1.3.6", - "@promptbook/core": "0.18.0", - "@promptbook/execute-javascript": "0.18.0", - "@promptbook/openai": "0.18.0", - "@promptbook/remote-client": "0.18.0", - "@promptbook/remote-server": "0.18.0", - "@promptbook/types": "0.18.0", - "@promptbook/utils": "0.18.0", + "@promptbook/core": "0.19.0", + "@promptbook/execute-javascript": "0.19.0", + "@promptbook/openai": "0.19.0", + "@promptbook/remote-client": "0.19.0", + "@promptbook/remote-server": "0.19.0", + "@promptbook/types": "0.19.0", + "@promptbook/utils": "0.19.0", + "@sendgrid/mail": "^8.1.0", "@supabase/supabase-js": "2.26.0", "@types/file-saver": "2.0.5", "@vercel/og": "0.5.8", diff --git a/promptbook-server/server.ts b/promptbook-server/server.ts index b0d7b6340..295e89457 100644 --- a/promptbook-server/server.ts +++ b/promptbook-server/server.ts @@ -7,6 +7,7 @@ import { OpenAiExecutionTools } from '@promptbook/openai'; import { runRemoteServer } from '@promptbook/remote-server'; import { IS_DEVELOPMENT, OPENAI_API_KEY } from '../config'; import { SupabaseLoggerWrapperOfNaturalExecutionTools } from '../src/ai/prompt-templates/logger/SupabaseLoggerWrapperOfNaturalExecutionTools'; +import { client_id } from '../src/utils/typeAliases'; // [🎛] import { webgptPtpLibrary } from '../src/ai/prompt-templates/webgptPtpLibrary'; runRemoteServer({ @@ -17,7 +18,7 @@ runRemoteServer({ /* <- TODO: [🎛] Use here real webgptPtpLibrary */ }), - createNaturalExecutionTools(clientId) { + createNaturalExecutionTools(clientId: client_id) { return new SupabaseLoggerWrapperOfNaturalExecutionTools({ isVerbose: false /* <- Note: [3] */, clientId, diff --git a/scripts/generate-wallpapers-content/generate-wallpapers-content.ts b/scripts/generate-wallpapers-content/generate-wallpapers-content.ts index abe36c35c..fa636ee7d 100644 --- a/scripts/generate-wallpapers-content/generate-wallpapers-content.ts +++ b/scripts/generate-wallpapers-content/generate-wallpapers-content.ts @@ -12,7 +12,7 @@ import { forTime } from 'waitasecond'; import { FONTS, OPENAI_API_KEY } from '../../config'; import { extractTitleFromContent } from '../../src/utils/content/extractTitleFromContent'; import { IWallpaperMetadata } from '../../src/utils/IWallpaper'; -import { randomItem } from '../../src/utils/randomItem'; +import { $randomItem } from '../../src/utils/randomItem'; import { commit } from '../utils/autocommit/commit'; import { isWorkingTreeClean } from '../utils/autocommit/isWorkingTreeClean'; import { forEachHardcodedWallpaper } from '../utils/hardcoded-wallpaper/forEachHardcodedWallpaper'; @@ -142,7 +142,7 @@ async function generateWallpapersContent({ isCommited, parallel }: { isCommited: /**/ /**/ - const font = randomItem(...FONTS.filter(({ isSpecial }) => !isSpecial)).fontFamily; + const font = $randomItem(...FONTS.filter(({ isSpecial }) => !isSpecial)).fontFamily; /**/ await writeFile( @@ -198,7 +198,7 @@ async function generateWallpapersContent({ isCommited, parallel }: { isCommited: * @deprecated [ðŸ‘ļ] use exported version in src/ai/text-to-text/prompt-templates/createContentPromptTemplate.ts */ function createContentPromptTemplate() { - return randomItem( + return $randomItem( ` Write me content for website with wallpaper which alt text is: diff --git a/src/ai/prompt-templates/getExecutionTools.ts b/src/ai/prompt-templates/getExecutionTools.ts index 3281b0c7d..cbb4abe2d 100644 --- a/src/ai/prompt-templates/getExecutionTools.ts +++ b/src/ai/prompt-templates/getExecutionTools.ts @@ -7,7 +7,7 @@ import { IS_DEVELOPMENT, NEXT_PUBLIC_PROMPTBOOK_SERVER_URL } from '../../../conf import { isRunningInBrowser, isRunningInWebWorker } from '../../utils/isRunningInWhatever'; import { getSupabaseForWorker } from '../../utils/supabase/getSupabaseForWorker'; import { Database } from '../../utils/supabase/types'; -import { uuid } from '../../utils/typeAliases'; +import { client_id } from '../../utils/typeAliases'; import { simpleTextDialogue } from '../../workers/dialogues/simple-text/simpleTextDialogue'; /** @@ -27,7 +27,7 @@ let executionTools: ExecutionTools; * * @returns ExecutionTools */ -export function getExecutionTools(clientId: uuid): ExecutionTools { +export function getExecutionTools(clientId: client_id): ExecutionTools { if (!isRunningInWebWorker() && !isRunningInBrowser()) { throw new Error('This function is available ONLY in browser or worker'); } diff --git a/src/ai/prompt-templates/logger/SupabaseLoggerWrapperOfNaturalExecutionToolsOptions.ts b/src/ai/prompt-templates/logger/SupabaseLoggerWrapperOfNaturalExecutionToolsOptions.ts index f0957217e..b8de8596f 100644 --- a/src/ai/prompt-templates/logger/SupabaseLoggerWrapperOfNaturalExecutionToolsOptions.ts +++ b/src/ai/prompt-templates/logger/SupabaseLoggerWrapperOfNaturalExecutionToolsOptions.ts @@ -1,5 +1,5 @@ import type { CommonExecutionToolsOptions, NaturalExecutionTools } from '@promptbook/types'; -import type { uuid } from '../../../utils/typeAliases'; +import { client_id } from '../../../utils/typeAliases'; /** * Options for SupabaseLoggerWrapperOfNaturalExecutionTools @@ -14,5 +14,5 @@ export interface SupabaseLoggerWrapperOfNaturalExecutionToolsOptions extends Com /** * Client responsible for the requests */ - readonly clientId: uuid; + readonly clientId: client_id; } diff --git a/src/ai/text-to-image/dalle/DalleImageGenerator.ts b/src/ai/text-to-image/dalle/DalleImageGenerator.ts index 025d1137c..0648457cd 100644 --- a/src/ai/text-to-image/dalle/DalleImageGenerator.ts +++ b/src/ai/text-to-image/dalle/DalleImageGenerator.ts @@ -21,7 +21,7 @@ export class DalleImageGenerator implements ImageGenerator { public constructor(private readonly options: DalleImageGeneratorOptions) { if (!isRunningInNode()) { - throw new Error('DalleImageGenerator is available only in server/node, use RemoteImageGenerator instead'); + throw new Error('DalleImageGenerator is available only on server/node, use RemoteImageGenerator instead'); } this.openai = new OpenAI({ diff --git a/src/ai/text-to-image/getImageGenerator.ts b/src/ai/text-to-image/getImageGenerator.ts index 4736c7d88..9491824d8 100644 --- a/src/ai/text-to-image/getImageGenerator.ts +++ b/src/ai/text-to-image/getImageGenerator.ts @@ -1,6 +1,6 @@ import { IS_DEVELOPMENT, NEXT_PUBLIC_IMAGE_SERVER_URL } from '../../../config'; import { isRunningInBrowser, isRunningInWebWorker } from '../../utils/isRunningInWhatever'; -import { uuid } from '../../utils/typeAliases'; +import { client_id } from '../../utils/typeAliases'; import { ImageGenerator } from './0-interfaces/ImageGenerator'; import { RemoteImageGenerator } from './remote/RemoteImageGenerator'; @@ -21,7 +21,7 @@ let imageGenerator: ImageGenerator; * * @returns ImageGenerator based on OpenAI Dalle */ -export function getImageGenerator(clientId: uuid): ImageGenerator { +export function getImageGenerator(clientId: client_id): ImageGenerator { if (!isRunningInWebWorker() && !isRunningInBrowser()) { throw new Error('This function is available ONLY in browser or worker'); } diff --git a/src/ai/text-to-image/getPhotobank.ts b/src/ai/text-to-image/getPhotobank.ts index b8bf3dd1a..9a2d9d392 100644 --- a/src/ai/text-to-image/getPhotobank.ts +++ b/src/ai/text-to-image/getPhotobank.ts @@ -1,6 +1,6 @@ -import { uuid } from '@promptbook/types'; import { IS_DEVELOPMENT } from '../../../config'; import { isRunningInBrowser, isRunningInWebWorker } from '../../utils/isRunningInWhatever'; +import { client_id } from '../../utils/typeAliases'; import { ImageGenerator } from './0-interfaces/ImageGenerator'; import { PregeneratedPhotobank } from './photobank/PregeneratedPhotobank'; @@ -21,7 +21,7 @@ let photobank: PregeneratedPhotobank; * * @returns ImageGenerator based on pregenerated MidJourney images */ -export function getPhotobank(clientId: uuid): ImageGenerator { +export function getPhotobank(clientId: client_id): ImageGenerator { if (!isRunningInWebWorker() && !isRunningInBrowser()) { throw new Error('This function is available ONLY in browser or worker'); } diff --git a/src/ai/text-to-image/logger/SupabaseLoggerWrapperOfImageGeneratorOptions.ts b/src/ai/text-to-image/logger/SupabaseLoggerWrapperOfImageGeneratorOptions.ts index bd9af18b8..82d68d923 100644 --- a/src/ai/text-to-image/logger/SupabaseLoggerWrapperOfImageGeneratorOptions.ts +++ b/src/ai/text-to-image/logger/SupabaseLoggerWrapperOfImageGeneratorOptions.ts @@ -1,5 +1,5 @@ import type { CommonExecutionToolsOptions } from '@promptbook/types'; -import type { uuid } from '../../../utils/typeAliases'; +import type { client_id } from '../../../utils/typeAliases'; import { ImageGenerator } from '../0-interfaces/ImageGenerator'; /** @@ -15,5 +15,5 @@ export interface SupabaseLoggerWrapperOfImageGeneratorOptions extends CommonExec /** * Client responsible for the requests */ - readonly clientId: uuid; + readonly clientId: client_id; } diff --git a/src/ai/text-to-image/photobank/interfaces/PregeneratedPhotobankOptions.ts b/src/ai/text-to-image/photobank/interfaces/PregeneratedPhotobankOptions.ts index 6d1ac90c4..a1befc1b0 100644 --- a/src/ai/text-to-image/photobank/interfaces/PregeneratedPhotobankOptions.ts +++ b/src/ai/text-to-image/photobank/interfaces/PregeneratedPhotobankOptions.ts @@ -1,5 +1,5 @@ import { CommonExecutionToolsOptions } from '@promptbook/types'; -import { uuid } from '../../../../utils/typeAliases'; +import { client_id } from '../../../../utils/typeAliases'; /** * Search options for the pregenerated photobank @@ -8,5 +8,5 @@ export interface PregeneratedPhotobankOptions extends CommonExecutionToolsOption /** * Client ID for the GPT usage */ - clientId: uuid; + clientId: client_id; } diff --git a/src/ai/text-to-image/remote/delete/RemoteImageGenerator.ts.delete b/src/ai/text-to-image/remote/delete/RemoteImageGenerator.ts.delete index 6a8a9b6ab..3f687ad6e 100644 --- a/src/ai/text-to-image/remote/delete/RemoteImageGenerator.ts.delete +++ b/src/ai/text-to-image/remote/delete/RemoteImageGenerator.ts.delete @@ -1,4 +1,4 @@ -import { uuid } from '@promptbook/types'; +import { client_id } from '@promptbook/types'; import { ImageGenerator } from '../../0-interfaces/ImageGenerator'; import { ImagePrompt } from '../../0-interfaces/ImagePrompt'; import { ImagePromptResult } from '../../0-interfaces/ImagePromptResult'; @@ -8,7 +8,7 @@ import { RemoteImageGeneratorResponse } from './createRemoteImageGeneratorRouteH * Image generator called remotely */ export class RemoteImageGenerator implements ImageGenerator { - public constructor(private readonly clientId: uuid, private readonly subroute: string) {} + public constructor(private readonly clientId: client_id, private readonly subroute: string) {} public async generate(prompt: ImagePrompt): Promise> { const response = await fetch( diff --git a/src/ai/text-to-image/remote/delete/createRemoteImageGeneratorRouteHandler.ts.delete b/src/ai/text-to-image/remote/delete/createRemoteImageGeneratorRouteHandler.ts.delete index 001de3783..51da59f84 100644 --- a/src/ai/text-to-image/remote/delete/createRemoteImageGeneratorRouteHandler.ts.delete +++ b/src/ai/text-to-image/remote/delete/createRemoteImageGeneratorRouteHandler.ts.delete @@ -1,5 +1,5 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import type { uuid } from '../../../utils/typeAliases'; +import type { client_id } from '../../../utils/typeAliases'; import { isValidClientId } from '../../../utils/validators/isValidClientId'; import type { ImageGenerator } from '../0-interfaces/ImageGenerator'; import type { ImagePrompt } from '../0-interfaces/ImagePrompt'; @@ -14,7 +14,7 @@ interface CreateRemoteImageGeneratorRouteHandlerOptions { /** * Provides an image generator for a given client */ - createImageGenerator(clientId: uuid): ImageGenerator; + createImageGenerator(clientId: client_id): ImageGenerator; } /** diff --git a/src/ai/text-to-image/remote/interfaces/CreateRemoteImageGeneratorServerOptions.ts b/src/ai/text-to-image/remote/interfaces/CreateRemoteImageGeneratorServerOptions.ts index 712014580..059ae72a4 100644 --- a/src/ai/text-to-image/remote/interfaces/CreateRemoteImageGeneratorServerOptions.ts +++ b/src/ai/text-to-image/remote/interfaces/CreateRemoteImageGeneratorServerOptions.ts @@ -1,4 +1,5 @@ -import type { CommonExecutionToolsOptions, string_uri, uuid } from '@promptbook/types'; +import type { CommonExecutionToolsOptions, string_uri } from '@promptbook/types'; +import { client_id } from '../../../../utils/typeAliases'; import type { ImageGenerator } from '../../0-interfaces/ImageGenerator'; export interface CreateRemoteImageGeneratorServerOptions extends CommonExecutionToolsOptions { @@ -18,5 +19,5 @@ export interface CreateRemoteImageGeneratorServerOptions extends CommonExecution /** * Provides an image generator for a given client */ - createImageGenerator(clientId: uuid): ImageGenerator; + createImageGenerator(clientId: client_id): ImageGenerator; } diff --git a/src/ai/text-to-image/remote/interfaces/Imgs_Request.ts b/src/ai/text-to-image/remote/interfaces/Imgs_Request.ts index f40e7cf99..d5dd3a731 100644 --- a/src/ai/text-to-image/remote/interfaces/Imgs_Request.ts +++ b/src/ai/text-to-image/remote/interfaces/Imgs_Request.ts @@ -1,16 +1,16 @@ -import type { uuid } from '../../../../utils/typeAliases'; +import { client_id } from '../../../../utils/typeAliases'; import type { ImagePrompt } from '../../0-interfaces/ImagePrompt'; /** * Socket.io progress for remote image generation - * + * * This is a request from client to server */ export interface Imgs_Request { /** * Client responsible for the requests */ - readonly clientId: uuid; + readonly clientId: client_id; /** * The Prompt to execute diff --git a/src/ai/text-to-image/remote/interfaces/RemoteNaturalExecutionToolsOptions.ts b/src/ai/text-to-image/remote/interfaces/RemoteNaturalExecutionToolsOptions.ts index 2e9c8a1cf..738122e0e 100644 --- a/src/ai/text-to-image/remote/interfaces/RemoteNaturalExecutionToolsOptions.ts +++ b/src/ai/text-to-image/remote/interfaces/RemoteNaturalExecutionToolsOptions.ts @@ -1,5 +1,5 @@ import type { CommonExecutionToolsOptions } from '@promptbook/types'; -import type { uuid,string_uri } from '../../../../utils/typeAliases'; +import type { client_id, string_uri } from '../../../../utils/typeAliases'; /** * Options for RemoteNaturalExecutionTools @@ -22,5 +22,5 @@ export interface RemoteNaturalExecutionToolsOptions extends CommonExecutionTools /** * Your client ID */ - readonly clientId: uuid; + readonly clientId: client_id; } diff --git a/src/components/Aigen/Aigen.tsx b/src/components/Aigen/Aigen.tsx index 04f8e4be8..1551a74e3 100644 --- a/src/components/Aigen/Aigen.tsx +++ b/src/components/Aigen/Aigen.tsx @@ -1,11 +1,10 @@ -import { useRouter } from 'next/router'; import { Fragment } from 'react'; import { Color } from '../../utils/color/Color'; import { textColor } from '../../utils/color/operators/furthest'; import { colorLuminance } from '../../utils/color/utils/colorLuminance'; import { useCurrentWallpaper } from '../../utils/hooks/useCurrentWallpaper'; import { useSsrDetection } from '../../utils/hooks/useSsrDetection'; -import { randomItem } from '../../utils/randomItem'; +import { $randomItem } from '../../utils/randomItem'; import { WallpaperLink } from '../WallpaperLink/WallpaperLink'; /** @@ -38,7 +37,7 @@ export function Aigen() { colors = [...colors, ...colors, ...colors, ...colors, ...colors, ...colors]; } - const letters = randomItem( + const letters = $randomItem( [ { letter: 'A', color: colors[0] }, { letter: 'I', color: colors[1] }, @@ -70,10 +69,10 @@ export function Aigen() { ], ); - const y = randomItem('50%', '60%', '65%'); - const fontSize = randomItem('13', '14', '15', '16'); - const fontWeight = randomItem('normal', 'bold'); - const fontFamily = randomItem('monospace', 'sans-serif', 'serif', /*'cursive',*/ 'fantasy'); + const y = $randomItem('50%', '60%', '65%'); + const fontSize = $randomItem('13', '14', '15', '16'); + const fontWeight = $randomItem('normal', 'bold'); + const fontFamily = $randomItem('monospace', 'sans-serif', 'serif', /*'cursive',*/ 'fantasy'); const height = 10 + 30 * Math.random(); const width = height * letters.length; @@ -82,16 +81,16 @@ export function Aigen() {
- {randomItem( + {$randomItem(
- {randomItem('← ', '<-', 'â†ķ', '') + - randomItem( + {$randomItem('← ', '<-', 'â†ķ', '') + + $randomItem( 'Generated by AI', 'Created by AI', 'Made by AI', diff --git a/src/components/ClientVerificationComponent/AutomaticVerification.ts b/src/components/ClientVerificationComponent/AutomaticVerification.ts new file mode 100644 index 000000000..fb963a90c --- /dev/null +++ b/src/components/ClientVerificationComponent/AutomaticVerification.ts @@ -0,0 +1,23 @@ +import { client_id, string_email, string_token } from '../../utils/typeAliases'; + +/** + * Data for automatic verification, this data will be in the link in verification email + */ +export type AutomaticVerification = { + /** + * Verification code + */ + code: string_token; + + /** + * Client email + * Note: This is double check + */ + email: string_email; + + /** + * Client id + * Note: This is double check, because user can open the link on different device + */ + clientId: client_id; +}; diff --git a/src/components/ClientVerificationComponent/ClientVerificationComponent.module.css b/src/components/ClientVerificationComponent/ClientVerificationComponent.module.css new file mode 100644 index 000000000..7b2dc16ba --- /dev/null +++ b/src/components/ClientVerificationComponent/ClientVerificationComponent.module.css @@ -0,0 +1,7 @@ +.ClientVerificationComponent { + /*/ + outline: 1px dotted rgb(255, 38, 38); + /**/ + + /* TODO: Use or remove empty css */ +} diff --git a/src/components/ClientVerificationComponent/ClientVerificationComponent.tsx b/src/components/ClientVerificationComponent/ClientVerificationComponent.tsx new file mode 100644 index 000000000..82a0a0aae --- /dev/null +++ b/src/components/ClientVerificationComponent/ClientVerificationComponent.tsx @@ -0,0 +1,238 @@ +import { useCallback, useRef, useState } from 'react'; +import spaceTrim from 'spacetrim'; +import { classNames } from '../../utils/classNames'; +import { $backupClientEmail } from '../../utils/client/backupClientEmail'; +import { ClientEmailVerification } from '../../utils/client/ClientVerification'; +import { $provideClientEmail } from '../../utils/client/provideClientEmail'; +import { $provideClientIdWithoutVerification } from '../../utils/client/provideClientIdWithoutVerification'; +import { $sendEmailToVerifyClientForBrowser } from '../../utils/client/sendEmailToVerifyClientForBrowser'; +import { $verifyEmailCodeForBrowser } from '../../utils/client/verifyEmailCodeForBrowser'; +import { useInitialDelayedAction } from '../../utils/hooks/useInitialDelayedAction'; +import { useStyleModule } from '../../utils/hooks/useStyleModule'; +import type { string_css_class, string_token } from '../../utils/typeAliases'; +import type { AutomaticVerification } from './AutomaticVerification'; +import { VerificationCodeInput } from './VerificationCodeInput/VerificationCodeInput'; + +interface ClientVerificationComponentProps { + /** + * Optional CSS class name which will be added to root element + */ + readonly className?: string_css_class; + + /** + * If provided, the verification will be automatic + */ + readonly automaticVerification?: AutomaticVerification; + + /** + * Called when user successfully verifies his email + */ + onVerificationSuccess(verification: ClientEmailVerification): void; +} + +/** + * Renders a @@ + */ +export function ClientVerificationComponent(props: ClientVerificationComponentProps) { + const { onVerificationSuccess, automaticVerification, className } = props; + + const styles = useStyleModule(import('./ClientVerificationComponent.module.css')); + + const emailInputRef = useRef(null); + const [status, setStatus] = useState< + | 'BEFORE' + | 'PENDING_EMAIL_SENDING' + | 'EMAIL_SENT' + | 'ALREADY_EMAIL_SENT' + | 'PENDING_CODE_SUBMITTING' + | 'VERIFIED' + >('BEFORE'); + + const handleSuccess = useCallback( + (verification: ClientEmailVerification) => { + $backupClientEmail(verification.email); + onVerificationSuccess(verification); + }, + [onVerificationSuccess], + ); + + const submitEmail = useCallback(async () => { + console.info(`Submitting email`); + + if (status !== 'BEFORE') { + // TODO: Better then alert + alert( + { + PENDING_EMAIL_SENDING: `The email is now sending`, + EMAIL_SENT: `Email was sent`, + ALREADY_EMAIL_SENT: `Email was sent`, + PENDING_CODE_SUBMITTING: `The code is now submitting`, + VERIFIED: `You are already verified`, + }[status], + ); + return; + } + + setStatus('PENDING_EMAIL_SENDING'); + + const clientId = $provideClientIdWithoutVerification(); + const sendEmailResult = await $sendEmailToVerifyClientForBrowser({ + clientId, + email: emailInputRef.current!.value!, + }); + + if (sendEmailResult.status === 'EMAIL_SENT') { + setStatus('EMAIL_SENT'); + } else if (sendEmailResult.status === 'ALREADY_EMAIL_SENT') { + setStatus('ALREADY_EMAIL_SENT'); + } else if (sendEmailResult.status === 'ERROR') { + // TODO: Better then alert + alert(sendEmailResult.message); + setStatus('BEFORE'); + } else if (sendEmailResult.status === 'ALREADY_VERIFIED') { + setStatus('VERIFIED'); + handleSuccess({ + clientId, + email: emailInputRef.current!.value!, + isEmailVerified: true, + }); + } else if (sendEmailResult.status === 'LIMIT_REACHED') { + // TODO: [ðŸ“Ū] Lock for some time + // TODO: Better then alert + alert('Limit reached'); + setStatus('BEFORE'); + } + }, [status, emailInputRef, handleSuccess]); + + const submitCode = useCallback( + async (code: string_token, isStatusBypassed = false) => { + console.info(`Submitting code`); + + if (!['EMAIL_SENT', 'ALREADY_EMAIL_SENT'].includes(status) && !isStatusBypassed) { + throw new Error(`Code can be submitted only when status is "EMAIL_SENT" but it is "${status}"`); + // <- TODO: ShouldNeverHappenError + } + + setStatus('PENDING_CODE_SUBMITTING'); + + const clientId = $provideClientIdWithoutVerification(); + const codeVerifyResult = await $verifyEmailCodeForBrowser({ + clientId, + email: emailInputRef.current!.value!, + code, + }); + + if (codeVerifyResult.status === 'VERIFIED') { + setStatus('VERIFIED'); + handleSuccess({ + email: emailInputRef.current!.value!, + clientId, + isEmailVerified: true, + }); + } else if (codeVerifyResult.status === 'ERROR') { + // TODO: Better then alert + setStatus('EMAIL_SENT'); + alert(codeVerifyResult.message); + } + }, + [status, handleSuccess], + ); + + useInitialDelayedAction(async () => { + const email = $provideClientEmail() || automaticVerification?.email || null; + + if (email === null) { + return; + } + + emailInputRef.current!.value = email; + + await submitEmail(); + + if (!automaticVerification) { + return; + } + + const clientId = $provideClientIdWithoutVerification(); + if (automaticVerification.clientId !== clientId) { + console.error( + spaceTrim(` + Can not automatically verify email because clientId is not the same + + Local storage: ${clientId} + Automatic verification: ${automaticVerification.clientId} + `), + ); + return; + } + + await submitCode(automaticVerification.code, true); + }); + + return ( +
+

+ { + { + BEFORE: <>Verify your email, + PENDING_EMAIL_SENDING: <>Sending the email, + EMAIL_SENT: ( + <> + Verify code received in your email +
+ (Look in spam folder if not in inbox) + + ), + ALREADY_EMAIL_SENT: ( + <> + Verify code was sent into your email +
+ (Look in spam folder if not in inbox) + + ), + PENDING_CODE_SUBMITTING: <>Verifying the code, + VERIFIED: <>You are verified!, + }[status] + } +

+ +
+ + +
+ + {['EMAIL_SENT', 'ALREADY_EMAIL_SENT', 'PENDING_CODE_SUBMITTING', 'VERIFIED'].includes(status) && ( + + )} +
+ ); +} + +/** + * TODO: !!! Design + * TODO: [🧠] Show animations for'PENDING_EMAIL_SENDING', 'EMAIL_SENT' and 'PENDING_CODE_SUBMITTING' + * TODO: [🧠] What if user clicks on email link and veryfy on background with opened ? + */ diff --git a/src/components/ClientVerificationComponent/VerificationCodeInput/VerificationCodeInput.module.css b/src/components/ClientVerificationComponent/VerificationCodeInput/VerificationCodeInput.module.css new file mode 100644 index 000000000..747a9e888 --- /dev/null +++ b/src/components/ClientVerificationComponent/VerificationCodeInput/VerificationCodeInput.module.css @@ -0,0 +1,7 @@ +.VerificationCodeInput { + /*/ + outline: 1px dotted rgb(255, 38, 38); + /**/ + + /* TODO: Use or remove empty css */ +} diff --git a/src/components/ClientVerificationComponent/VerificationCodeInput/VerificationCodeInput.tsx b/src/components/ClientVerificationComponent/VerificationCodeInput/VerificationCodeInput.tsx new file mode 100644 index 000000000..467e392a6 --- /dev/null +++ b/src/components/ClientVerificationComponent/VerificationCodeInput/VerificationCodeInput.tsx @@ -0,0 +1,71 @@ +import { string_token } from '@promptbook/types'; +import { useCallback, useRef } from 'react'; +import { classNames } from '../../../utils/classNames'; +import { useStyleModule } from '../../../utils/hooks/useStyleModule'; +import type { string_css_class } from '../../../utils/typeAliases'; + +interface VerificationCodeInputProps { + /** + * Callback when code is entered and submitted + */ + onSubmit(code: string_token): void; + + /** + * If true, the input is disabled + */ + readonly isDisabled?: boolean; + + /** + * Optional CSS class name which will be added to root element + */ + readonly className?: string_css_class; +} + +/** + * Renders a verification code input + */ +export function VerificationCodeInput(props: VerificationCodeInputProps) { + const { onSubmit, isDisabled, className } = props; + + const styles = useStyleModule(import('./VerificationCodeInput.module.css')); + const codeInputRef = useRef(null); + + const submitCode = useCallback(() => { + if (codeInputRef.current === null) { + return; + } + + const code = codeInputRef.current.value; + if (code === '') { + return; + } + + onSubmit(code); + }, [onSubmit, codeInputRef]); + + return ( +
+ { + if (!(event.key === 'Enter' && event.shiftKey === false && event.ctrlKey === false)) { + return; + } + + submitCode(); + }} + /> + +
+ ); +} + +/** + * TODO: !!! Design + */ diff --git a/src/components/Content/Maxdown/maxdown.test.ts b/src/components/Content/Maxdown/maxdown.test.ts new file mode 100644 index 000000000..4c6a1f3d9 --- /dev/null +++ b/src/components/Content/Maxdown/maxdown.test.ts @@ -0,0 +1,38 @@ +import { describe, expect, it } from '@jest/globals'; +import { maxdown } from './maxdown'; + +describe(`maxdown string template literal tag function`, () => { + it(`should work with simple maxdown`, () => { + expect( + maxdown` + + # Hello world + + `, + ).toBe('# Hello world'); + }); + + it(`should work with multiline maxdown`, () => { + expect( + maxdown` + + # Hello world + + I am multiline **maxdown** + + `, + ).toBe('# Hello world\n\nI am multiline **maxdown**'); + }); + + it(`should work with templated maxdown`, () => { + expect( + maxdown` + + # Hello world + + I am multiline **${'maxdown'}** + + `, + ).toBe('# Hello world\n\nI am multiline **maxdown**'); + }); +}); diff --git a/src/components/Content/Maxdown/maxdown.ts b/src/components/Content/Maxdown/maxdown.ts new file mode 100644 index 000000000..7750ac528 --- /dev/null +++ b/src/components/Content/Maxdown/maxdown.ts @@ -0,0 +1,17 @@ +import spaceTrim from 'spacetrim'; +import { validateMaxdown } from './validateMaxdown'; + +/** + * Use maxdown as a template literal tag function + * + * If content is not a valid maxdown, throws an error + * If content is a valid maxdown: + * - It will be spaceTrimmed + */ +export function maxdown(strings: TemplateStringsArray, ...values: any[]) { + const content = strings.reduce((result, string, index) => { + return result + string + (values[index] ?? ''); + }, ''); + + return validateMaxdown(spaceTrim(content)); +} diff --git a/src/components/Content/Maxdown/validateMaxdown.ts b/src/components/Content/Maxdown/validateMaxdown.ts index 3135b7c1f..336b748eb 100644 --- a/src/components/Content/Maxdown/validateMaxdown.ts +++ b/src/components/Content/Maxdown/validateMaxdown.ts @@ -4,7 +4,7 @@ import { string_maxdown } from '../../../utils/typeAliases'; * Validate maxdown content * * If content is not a valid maxdown, throws an error - * If content is a valid maxdown, + * If content is a valid maxdown, returnes it */ export function validateMaxdown(content: unknown): string_maxdown { if (typeof content !== 'string') { @@ -14,6 +14,9 @@ export function validateMaxdown(content: unknown): string_maxdown { return content as string_maxdown; } + + + /** * TODO: !! Do here a real validation / sanitization * TODO: [🧠][🚓] Is/which combination it better to use asserts/check, validate or is utility function? diff --git a/src/components/ControlPanel/ControlPanel.tsx b/src/components/ControlPanel/ControlPanel.tsx index b3b8d49de..758ccc179 100644 --- a/src/components/ControlPanel/ControlPanel.tsx +++ b/src/components/ControlPanel/ControlPanel.tsx @@ -3,11 +3,11 @@ import { useRouter } from 'next/router'; import { IS_VERIFIED_EMAIL_REQUIRED } from '../../../config'; import type { LikedStatus } from '../../ai/recommendation/LikedStatus'; import { classNames } from '../../utils/classNames'; +import { $provideClientId } from '../../utils/client/provideClientId'; import { computeWallpaperUriid } from '../../utils/computeWallpaperUriid'; import { useCurrentWallpaper } from '../../utils/hooks/useCurrentWallpaper'; import { serializeWallpaper } from '../../utils/hydrateWallpaper'; import { getSupabaseForBrowser } from '../../utils/supabase/getSupabaseForBrowser'; -import { provideClientId } from '../../utils/supabase/provideClientId'; import { parseKeywordsFromWallpaper } from '../Gallery/GalleryFilter/utils/parseKeywordsFromWallpaper'; import { Hint } from '../Hint/Hint'; import { WallpaperLink } from '../WallpaperLink/WallpaperLink'; @@ -34,7 +34,7 @@ export function ControlPanel() {