diff --git a/.secrets.example b/.secrets.example index 220bfe52..28360234 100644 --- a/.secrets.example +++ b/.secrets.example @@ -32,5 +32,7 @@ S3_ACCESS_KEY = 'storage-access-key-never-use-this-value' S3_SECRET_KEY = 'storage-secret-key-never-use-this-value' ## AI +OPENAI_ORG_ID = '' +OPENAI_PROJECT_ID = '' OPENAI_API_KEY = '' GRAPHITE_WEBHOOK_SECRET = '' diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 73c8481b..5110f00f 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -8,14 +8,12 @@ "eamodio.gitlens", "github.github-vscode-theme", "github.vscode-pull-request-github", - "esbenp.prettier-vscode", "yzhang.markdown-all-in-one", "EditorConfig.EditorConfig", "humao.rest-client", "PKief.material-icon-theme", "hediet.vscode-drawio", "mikestead.dotenv", - "vivaxy.vscode-conventional-commits", "alberto-varela.monorepo-focus-workspace", "biomejs.biome", // inlang (i18n) @@ -38,11 +36,9 @@ "codezombiech.gitignore", "GitHub.vscode-github-actions", "csstools.postcss", - "dbaeumer.vscode-eslint", "ms-playwright.playwright", "ms-vsliveshare.vsliveshare", "streetsidesoftware.code-spell-checker", - "stylelint.vscode-stylelint", "svelte.svelte-vscode", "ardenivanov.svelte-intellisense", "JakobKruse.svelte-kit-snippets", diff --git a/apps/console/.secrets.example b/apps/console/.secrets.example index 458b6d11..5a0b7771 100644 --- a/apps/console/.secrets.example +++ b/apps/console/.secrets.example @@ -2,3 +2,7 @@ # https://generate-secret.vercel.app/64 to generate a secret. # or copy (openssl rand -hex 64) output here RATE_LIMIT_SECRET='fill-me-in' +## AI +OPENAI_ORG_ID = '' +OPENAI_PROJECT_ID = '' +OPENAI_API_KEY = '' diff --git a/apps/console/package.json b/apps/console/package.json index 8c264880..8dec72f3 100644 --- a/apps/console/package.json +++ b/apps/console/package.json @@ -29,6 +29,7 @@ "test:unit:ui": "dotenv-run -f .env -f .secrets -v -- vitest --ui" }, "devDependencies": { + "@ai-sdk/openai": "0.0.14", "@floating-ui/dom": "1.6.5", "@fontsource-variable/inter": "5.0.18", "@inlang/cli": "2.16.8", @@ -60,6 +61,7 @@ "@vincjo/datatables": "1.14.5", "@vitest/coverage-v8": "1.6.0", "@xyflow/svelte": "0.1.3", + "ai": "3.1.14", "autoprefixer": "10.4.19", "d3-array": "3.2.4", "d3-scale": "4.0.2", diff --git a/apps/console/src/lib/components/magic-spell-textarea/README.md b/apps/console/src/lib/components/magic-spell-textarea/README.md new file mode 100644 index 00000000..115d3f3d --- /dev/null +++ b/apps/console/src/lib/components/magic-spell-textarea/README.md @@ -0,0 +1,3 @@ +# Magic Spell + +Based on nextjs [magic-spell](https://github.com/ai-ng/magic-spell/tree/main) diff --git a/apps/console/src/lib/components/magic-spell-textarea/index.ts b/apps/console/src/lib/components/magic-spell-textarea/index.ts new file mode 100644 index 00000000..0997939d --- /dev/null +++ b/apps/console/src/lib/components/magic-spell-textarea/index.ts @@ -0,0 +1,2 @@ +// Reexport your entry components here +export { default as MagicSpellTextarea } from './magic-spell-textarea.svelte'; diff --git a/apps/console/src/lib/components/magic-spell-textarea/loader-icon.svelte b/apps/console/src/lib/components/magic-spell-textarea/loader-icon.svelte new file mode 100644 index 00000000..f8912a56 --- /dev/null +++ b/apps/console/src/lib/components/magic-spell-textarea/loader-icon.svelte @@ -0,0 +1,15 @@ + + + + diff --git a/apps/console/src/lib/components/magic-spell-textarea/magic-spell-textarea.svelte b/apps/console/src/lib/components/magic-spell-textarea/magic-spell-textarea.svelte new file mode 100644 index 00000000..c48752f0 --- /dev/null +++ b/apps/console/src/lib/components/magic-spell-textarea/magic-spell-textarea.svelte @@ -0,0 +1,65 @@ + + +
{ + handleSubmit(e); + input.set(''); + }} +> + 0 ? $completion.trim() : value} + on:change={(event) => { + if (!$isLoading) value = event.target?.value; + console.log(value); + }} + /> +
+
+ + +
+
+ + diff --git a/apps/console/src/lib/links.ts b/apps/console/src/lib/links.ts index 928f83e6..758dca95 100644 --- a/apps/console/src/lib/links.ts +++ b/apps/console/src/lib/links.ts @@ -26,9 +26,14 @@ export const menuNavLinks: MenuNavLinks = { }, { title: 'Experiments', + list: [{ href: '/customers', label: 'Customers', keywords: 'customers, users' }] + }, + { + title: 'AI', list: [ - { href: '/customers', label: 'Customers', keywords: 'customers, users' }, - { href: '/play', label: 'Play', keywords: 'play, users' } + { href: '/magic-spell', label: 'Magic Spell', keywords: 'magic-spell, ai, completion' }, + { href: '/assistants', label: 'Assistants', keywords: 'assistants, ai' }, + { href: '/chatbot', label: 'Chat Bot', keywords: 'chatbot, OpenAI' } ] } ], diff --git a/apps/console/src/routes/(app)/magic-spell/+page.server.ts b/apps/console/src/routes/(app)/magic-spell/+page.server.ts new file mode 100644 index 00000000..1af48f0a --- /dev/null +++ b/apps/console/src/routes/(app)/magic-spell/+page.server.ts @@ -0,0 +1,39 @@ +import { fail } from '@sveltejs/kit'; +import { message, superValidate } from 'sveltekit-superforms'; +import { zod } from 'sveltekit-superforms/adapters'; +import { z } from 'zod'; +import { Logger, sleep } from '@spectacular/utils'; + +const aiSchema = z.object({ + commentOne: z + .string({ required_error: 'First Comment is required' }) + .min(10, { message: 'First Comment must contain at least 10 character(s)' }) + .max(256) + .trim(), + commentTwo: z + .string({ required_error: 'Second Comment is required' }) + .min(10, { message: 'Second Name Comment contain at least 10 character(s)' }) + .max(256) + .trim() +}); + +const log = new Logger('server:ai:ms'); + +export const load = async () => { + const form = await superValidate(zod(aiSchema)); + return { form }; +}; + +export const actions = { + default: async ({ request }) => { + const form = await superValidate(request, zod(aiSchema)); + + await sleep(1000); + + if (!form.valid) return fail(400, { form }); + + const { commentOne, commentTwo } = form.data; + log.debug({ commentOne, commentTwo }); + return message(form, { type: 'success', message: 'AI sucessfull 😎' }); + } +}; diff --git a/apps/console/src/routes/(app)/magic-spell/+page.svelte b/apps/console/src/routes/(app)/magic-spell/+page.svelte new file mode 100644 index 00000000..c8b6d588 --- /dev/null +++ b/apps/console/src/routes/(app)/magic-spell/+page.svelte @@ -0,0 +1,134 @@ + + +
+
+
+

Magic Spell

+
+ +
+
+ + + +
+
+
+
+ + + +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
diff --git a/apps/console/src/routes/(app)/play/+page.svelte b/apps/console/src/routes/(app)/play/+page.svelte deleted file mode 100644 index f4e5ebd8..00000000 --- a/apps/console/src/routes/(app)/play/+page.svelte +++ /dev/null @@ -1,75 +0,0 @@ - - -
-
-
- - -
- - - - First name - Last name - Email - - - - - - - - - {#each $rows as row} - - - - - - {/each} - -
{row.first_name}{row.last_name}{row.email}
-
- - -
-
-
- - - - - First Name - Last Name - Email - - - - - - - - - {#each $rows as row} - - - - - - {/each} - -
{row.first_name}{row.last_name}{row.email}
-
-
-
diff --git a/apps/console/src/routes/api/completion/+server.ts b/apps/console/src/routes/api/completion/+server.ts new file mode 100644 index 00000000..ada3ed14 --- /dev/null +++ b/apps/console/src/routes/api/completion/+server.ts @@ -0,0 +1,31 @@ +import { createOpenAI } from '@ai-sdk/openai'; +import { StreamingTextResponse, streamText } from 'ai'; + +import { Logger } from '@spectacular/utils'; +import { env } from '$env/dynamic/private'; +// import { limiter } from '$lib/server/limiter/limiter'; // TODO + +const log = new Logger('experiments:ai:completion:server'); + +// https://sdk.vercel.ai/docs/getting-started/svelte +const openai = createOpenAI({ + organization: env.OPENAI_ORG_ID, + project: env.OPENAI_PROJECT_ID, + apiKey: env.OPENAI_API_KEY +}); + +// free https://platform.openai.com/docs/guides/rate-limits/free-tier-rate-limits +const model = openai('gpt-3.5-turbo'); +// const model = openai("llama3-8b-8192") +const system = + 'You are a text editor. You will be given a prompt and a text to edit, which may be empty or incomplete. Edit the text to match the prompt, and only respond with the full edited version of the text - do not include any other information, context, or explanation. If you add on to the text, respond with the full version, not just the new portion. Do not include the prompt or otherwise preface your response. Do not enclose the response in quotes.'; + +export const POST = async ({ request }) => { + const { text, prompt } = await request.json(); + log.debug({ text, prompt }); + if (!prompt) return new Response('Prompt is required', { status: 400 }); + + const result = await streamText({ model, system, prompt: `Prompt: ${prompt}\nText: ${text}` }); + + return new StreamingTextResponse(result.toAIStream()); +}; diff --git a/apps/console/turbo.json b/apps/console/turbo.json index 7e18d3b7..28b1ef51 100644 --- a/apps/console/turbo.json +++ b/apps/console/turbo.json @@ -52,6 +52,9 @@ "BUN_ENV", "SW_DEV", "GCP_BUILDPACKS", + "OPENAI_ORG_ID", + "OPENAI_PROJECT_ID", + "OPENAI_API_KEY", "npm_package_version" ] }, diff --git a/packages/skeleton-ui/src/components/auto-resize-textarea/README.md b/packages/skeleton-ui/src/components/auto-resize-textarea/README.md new file mode 100644 index 00000000..2b57c382 --- /dev/null +++ b/packages/skeleton-ui/src/components/auto-resize-textarea/README.md @@ -0,0 +1,31 @@ +# Auto-resize Textarea for Svelte + +An in-built auto-resize functionality for a textarea element using the scroll height of a singleton proxy textarea element attached to the document body. This is the 100% accurate implementation and does not break irrespective of the borders, padding, or amount of text. + +## Install + +`npm install svelte-autoresize-textarea` + +## Usage + +```svelte + + + +``` + +## Props + +| prop | type | description | +| --------- | -------- | -------------------------------------------------------- | +| `maxRows` | `number` | Maximum number of rows up to which the textarea can grow | +| `minRows` | `number` | Minimum number of rows to show for textarea | + +Apart from these, the component accepts all props that are accepted by `