diff --git a/package.json b/package.json index 4de741f..4ec6178 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "dependencies": { "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-icons": "1.3.0", + "openai": "^4.52.7", "partykit": "0.0.27", "rbush": "^4.0.0", "react": "^18.2.0", diff --git a/src/App.tsx b/src/App.tsx index 5a3a3f0..e6fee55 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -7,9 +7,11 @@ import { SocialShapeTool } from "./SocialShapeTool"; import { CustomToolbar, overrides } from "./ui"; // import { getDocumentMeta, getUserId, getUsersInRoom, setDocumentMeta } from "./storeUtils"; import { registerDefaultPropagators } from "./propagators/ScopedPropagators"; +import { PromptShape } from "./PromptShape"; +import { PromptShapeTool } from "./PromptTool"; -const shapeUtils = [SocialShapeUtil]; -const tools = [SocialShapeTool]; +const shapeUtils = [SocialShapeUtil, PromptShape]; +const tools = [SocialShapeTool, PromptShapeTool]; const HOST_URL = import.meta.env.DEV diff --git a/src/PromptShape.tsx b/src/PromptShape.tsx new file mode 100644 index 0000000..e15b674 --- /dev/null +++ b/src/PromptShape.tsx @@ -0,0 +1,169 @@ +import { + BaseBoxShapeUtil, + HTMLContainer, + // TLArrowShape, + TLBaseShape, + TLGeoShape, + TLOnResizeHandler, + TLShape, +} from "tldraw" +import { getEdge } from "./propagators/tlgraph" +import { llm } from "./llm" +import { isShapeOfType } from "./propagators/utils" +import { ISocialShape } from "./SocialShapeUtil" +// import TextInput from "react-autocomplete-input" +// import "react-autocomplete-input/dist/bundle.css" + +type IPrompt = TLBaseShape< + "prompt", + { + w: number + h: number + prompt: string + output: string + agentBinding: string | null + } +> + +export class PromptShape extends BaseBoxShapeUtil { + static override type = "prompt" as const + + FIXED_HEIGHT = 50 as const + MIN_WIDTH = 150 as const + PADDING = 4 as const + + getDefaultProps(): IPrompt["props"] { + return { + w: 300, + h: 50, + prompt: "", + output: "", + agentBinding: null, + } + } + + override onResize: TLOnResizeHandler = ( + shape, + { scaleX, initialShape }, + ) => { + const { x, y } = shape + const w = initialShape.props.w * scaleX + return { + x, + y, + props: { + ...shape.props, + w: Math.max(Math.abs(w), this.MIN_WIDTH), + h: this.FIXED_HEIGHT, + }, + } + } + + component(shape: IPrompt) { + const arrowBindings = this.editor.getBindingsInvolvingShape( + shape.id, + "arrow", + ) + const arrows = arrowBindings + .map((binding) => this.editor.getShape(binding.fromId)) + + const inputMap = arrows.reduce((acc, arrow) => { + const edge = getEdge(arrow, this.editor); + if (edge) { + const sourceShape = this.editor.getShape(edge.from); + if (sourceShape && edge.text) { + acc[edge.text] = sourceShape; + } + } + return acc; + }, {} as Record); + + const generateText = async (prompt: string) => { + await llm('', prompt, (partial: string, done: boolean) => { + console.log("DONE??", done) + this.editor.updateShape({ + id: shape.id, + type: "prompt", + props: { output: partial, agentBinding: done ? null : 'someone' }, + }) + }) + } + + const handlePrompt = () => { + let processedPrompt = shape.props.prompt; + for (const [key, sourceShape] of Object.entries(inputMap)) { + const pattern = `{${key}}`; + if (processedPrompt.includes(pattern)) { + if (isShapeOfType(sourceShape, 'geo')) { + processedPrompt = processedPrompt.replace(pattern, sourceShape.props.text); + } else if (isShapeOfType(sourceShape, 'social')) { + processedPrompt = processedPrompt.replace(pattern, sourceShape.props.value.toString()); + } + } + } + console.log(processedPrompt); + generateText(processedPrompt) + }; + + return ( + + { + this.editor.updateShape({ + id: shape.id, + type: "prompt", + props: { prompt: text.target.value }, + }) + }} + /> + + + ) + } + + // [5] + indicator(shape: IPrompt) { + return + } +} diff --git a/src/PromptTool.ts b/src/PromptTool.ts new file mode 100644 index 0000000..73e5ee8 --- /dev/null +++ b/src/PromptTool.ts @@ -0,0 +1,9 @@ +import { BaseBoxShapeTool } from 'tldraw' + +export class PromptShapeTool extends BaseBoxShapeTool { + static override id = 'prompt' + static override initial = 'idle' + override shapeType = 'prompt' + +} + diff --git a/src/llm.ts b/src/llm.ts new file mode 100644 index 0000000..697d015 --- /dev/null +++ b/src/llm.ts @@ -0,0 +1,31 @@ +import OpenAI from "openai"; + +const openai = new OpenAI({ + apiKey: "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", + // apiKey: import.meta.env.VITE_OPENAI_API_KEY, + dangerouslyAllowBrowser: true, +}); + +export async function llm( + systemPrompt: string, + userPrompt: string, + onToken: (partialResponse: string, done: boolean) => void, +) { + console.log("System Prompt:", systemPrompt); + console.log("User Prompt:", userPrompt); + let partial = ""; + const stream = await openai.chat.completions.create({ + model: "gpt-4o", + messages: [ + { role: "system", content: 'You are a helpful assistant.' }, + { role: "user", content: userPrompt }, + ], + stream: true, + }); + for await (const chunk of stream) { + partial += chunk.choices[0]?.delta?.content || ""; + onToken(partial, false); + } + console.log("Generated:", partial); + onToken(partial, true); +} diff --git a/src/ui.tsx b/src/ui.tsx index 599eee9..ff25e34 100644 --- a/src/ui.tsx +++ b/src/ui.tsx @@ -22,6 +22,16 @@ export const overrides: TLUiOverrides = { editor.setCurrentTool("social") }, }, + prompt: { + id: "prompt", + name: "Prompt", + icon: "code", + kbd: "p", + label: "Prompt", + onSelect: () => { + editor.setCurrentTool("prompt") + }, + }, } }, } diff --git a/yarn.lock b/yarn.lock index ed73e2d..f11dac2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1922,6 +1922,34 @@ __metadata: languageName: node linkType: hard +"@types/node-fetch@npm:^2.6.4": + version: 2.6.11 + resolution: "@types/node-fetch@npm:2.6.11" + dependencies: + "@types/node": "npm:*" + form-data: "npm:^4.0.0" + checksum: 5283d4e0bcc37a5b6d8e629aee880a4ffcfb33e089f4b903b2981b19c623972d1e64af7c3f9540ab990f0f5c89b9b5dda19c5bcb37a8e177079e93683bfd2f49 + languageName: node + linkType: hard + +"@types/node@npm:*": + version: 20.14.11 + resolution: "@types/node@npm:20.14.11" + dependencies: + undici-types: "npm:~5.26.4" + checksum: 5306becc0ff41d81b1e31524bd376e958d0741d1ce892dffd586b9ae0cb6553c62b0d62abd16da8bea6b9a2c17572d360450535d7c073794b0cef9cb4e39691e + languageName: node + linkType: hard + +"@types/node@npm:^18.11.18": + version: 18.19.41 + resolution: "@types/node@npm:18.19.41" + dependencies: + undici-types: "npm:~5.26.4" + checksum: fc078939cdec05ca60c557bff55d8d96c8e5695e925ee144d8c67efcd9717ed1ec1e18a476257f2dcc8382d1158dae22b24a423ef3cb833ff9f441a80e80c0e6 + languageName: node + linkType: hard + "@types/prop-types@npm:*": version: 15.7.5 resolution: "@types/prop-types@npm:15.7.5" @@ -2002,6 +2030,15 @@ __metadata: languageName: node linkType: hard +"abort-controller@npm:^3.0.0": + version: 3.0.0 + resolution: "abort-controller@npm:3.0.0" + dependencies: + event-target-shim: "npm:^5.0.0" + checksum: 90ccc50f010250152509a344eb2e71977fbf8db0ab8f1061197e3275ddf6c61a41a6edfd7b9409c664513131dd96e962065415325ef23efa5db931b382d24ca5 + languageName: node + linkType: hard + "abstract-leveldown@npm:^6.2.1": version: 6.3.0 resolution: "abstract-leveldown@npm:6.3.0" @@ -2053,6 +2090,15 @@ __metadata: languageName: node linkType: hard +"agentkeepalive@npm:^4.2.1": + version: 4.5.0 + resolution: "agentkeepalive@npm:4.5.0" + dependencies: + humanize-ms: "npm:^1.2.1" + checksum: 394ea19f9710f230722996e156607f48fdf3a345133b0b1823244b7989426c16019a428b56c82d3eabef616e938812981d9009f4792ecc66bd6a59e991c62612 + languageName: node + linkType: hard + "aggregate-error@npm:^3.0.0": version: 3.1.0 resolution: "aggregate-error@npm:3.1.0" @@ -2134,6 +2180,13 @@ __metadata: languageName: node linkType: hard +"asynckit@npm:^0.4.0": + version: 0.4.0 + resolution: "asynckit@npm:0.4.0" + checksum: d73e2ddf20c4eb9337e1b3df1a0f6159481050a5de457c55b14ea2e5cb6d90bb69e004c9af54737a5ee0917fcf2c9e25de67777bbe58261847846066ba75bc9d + languageName: node + linkType: hard + "balanced-match@npm:^1.0.0": version: 1.0.2 resolution: "balanced-match@npm:1.0.2" @@ -2328,6 +2381,15 @@ __metadata: languageName: node linkType: hard +"combined-stream@npm:^1.0.8": + version: 1.0.8 + resolution: "combined-stream@npm:1.0.8" + dependencies: + delayed-stream: "npm:~1.0.0" + checksum: 0dbb829577e1b1e839fa82b40c07ffaf7de8a09b935cadd355a73652ae70a88b4320db322f6634a4ad93424292fa80973ac6480986247f1734a1137debf271d5 + languageName: node + linkType: hard + "concurrently@npm:^8.2.0": version: 8.2.0 resolution: "concurrently@npm:8.2.0" @@ -2425,6 +2487,13 @@ __metadata: languageName: node linkType: hard +"delayed-stream@npm:~1.0.0": + version: 1.0.0 + resolution: "delayed-stream@npm:1.0.0" + checksum: d758899da03392e6712f042bec80aa293bbe9e9ff1b2634baae6a360113e708b91326594c8a486d475c69d6259afb7efacdc3537bfcda1c6c648e390ce601b19 + languageName: node + linkType: hard + "detect-node-es@npm:^1.1.0": version: 1.1.0 resolution: "detect-node-es@npm:1.1.0" @@ -2674,6 +2743,13 @@ __metadata: languageName: node linkType: hard +"event-target-shim@npm:^5.0.0": + version: 5.0.1 + resolution: "event-target-shim@npm:5.0.1" + checksum: 0255d9f936215fd206156fd4caa9e8d35e62075d720dc7d847e89b417e5e62cf1ce6c9b4e0a1633a9256de0efefaf9f8d26924b1f3c8620cffb9db78e7d3076b + languageName: node + linkType: hard + "eventemitter3@npm:^4.0.7": version: 4.0.7 resolution: "eventemitter3@npm:4.0.7" @@ -2722,6 +2798,34 @@ __metadata: languageName: node linkType: hard +"form-data-encoder@npm:1.7.2": + version: 1.7.2 + resolution: "form-data-encoder@npm:1.7.2" + checksum: 56553768037b6d55d9de524f97fe70555f0e415e781cb56fc457a68263de3d40fadea2304d4beef2d40b1a851269bd7854e42c362107071892cb5238debe9464 + languageName: node + linkType: hard + +"form-data@npm:^4.0.0": + version: 4.0.0 + resolution: "form-data@npm:4.0.0" + dependencies: + asynckit: "npm:^0.4.0" + combined-stream: "npm:^1.0.8" + mime-types: "npm:^2.1.12" + checksum: cb6f3ac49180be03ff07ba3ff125f9eba2ff0b277fb33c7fc47569fc5e616882c5b1c69b9904c4c4187e97dd0419dd03b134174756f296dec62041e6527e2c6e + languageName: node + linkType: hard + +"formdata-node@npm:^4.3.2": + version: 4.4.1 + resolution: "formdata-node@npm:4.4.1" + dependencies: + node-domexception: "npm:1.0.0" + web-streams-polyfill: "npm:4.0.0-beta.3" + checksum: 74151e7b228ffb33b565cec69182694ad07cc3fdd9126a8240468bb70a8ba66e97e097072b60bcb08729b24c7ce3fd3e0bd7f1f80df6f9f662b9656786e76f6a + languageName: node + linkType: hard + "fs-minipass@npm:^2.0.0": version: 2.1.0 resolution: "fs-minipass@npm:2.1.0" @@ -2828,6 +2932,7 @@ __metadata: "@types/react-dom": "npm:^18.2.7" "@vitejs/plugin-react": "npm:^4.0.3" concurrently: "npm:^8.2.0" + openai: "npm:^4.52.7" partykit: "npm:0.0.27" rbush: "npm:^4.0.0" react: "npm:^18.2.0" @@ -2935,6 +3040,15 @@ __metadata: languageName: node linkType: hard +"humanize-ms@npm:^1.2.1": + version: 1.2.1 + resolution: "humanize-ms@npm:1.2.1" + dependencies: + ms: "npm:^2.0.0" + checksum: f34a2c20161d02303c2807badec2f3b49cbfbbb409abd4f95a07377ae01cfe6b59e3d15ac609cffcd8f2521f0eb37b7e1091acf65da99aa2a4f1ad63c21e7e7a + languageName: node + linkType: hard + "iconv-lite@npm:^0.6.2": version: 0.6.3 resolution: "iconv-lite@npm:0.6.3" @@ -3360,6 +3474,22 @@ __metadata: languageName: node linkType: hard +"mime-db@npm:1.52.0": + version: 1.52.0 + resolution: "mime-db@npm:1.52.0" + checksum: 0557a01deebf45ac5f5777fe7740b2a5c309c6d62d40ceab4e23da9f821899ce7a900b7ac8157d4548ddbb7beffe9abc621250e6d182b0397ec7f10c7b91a5aa + languageName: node + linkType: hard + +"mime-types@npm:^2.1.12": + version: 2.1.35 + resolution: "mime-types@npm:2.1.35" + dependencies: + mime-db: "npm:1.52.0" + checksum: 82fb07ec56d8ff1fc999a84f2f217aa46cb6ed1033fefaabd5785b9a974ed225c90dc72fff460259e66b95b73648596dbcc50d51ed69cdf464af2d237d3149b2 + languageName: node + linkType: hard + "mimic-fn@npm:^2.1.0": version: 2.1.0 resolution: "mimic-fn@npm:2.1.0" @@ -3496,6 +3626,13 @@ __metadata: languageName: node linkType: hard +"ms@npm:^2.0.0": + version: 2.1.3 + resolution: "ms@npm:2.1.3" + checksum: d924b57e7312b3b63ad21fc5b3dc0af5e78d61a1fc7cfb5457edaf26326bf62be5307cc87ffb6862ef1c2b33b0233cdb5d4f01c4c958cc0d660948b65a287a48 + languageName: node + linkType: hard + "mustache@npm:^4.2.0": version: 4.2.0 resolution: "mustache@npm:4.2.0" @@ -3537,6 +3674,27 @@ __metadata: languageName: node linkType: hard +"node-domexception@npm:1.0.0": + version: 1.0.0 + resolution: "node-domexception@npm:1.0.0" + checksum: 5e5d63cda29856402df9472335af4bb13875e1927ad3be861dc5ebde38917aecbf9ae337923777af52a48c426b70148815e890a5d72760f1b4d758cc671b1a2b + languageName: node + linkType: hard + +"node-fetch@npm:^2.6.7": + version: 2.7.0 + resolution: "node-fetch@npm:2.7.0" + dependencies: + whatwg-url: "npm:^5.0.0" + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + checksum: b55786b6028208e6fbe594ccccc213cab67a72899c9234eb59dba51062a299ea853210fcf526998eaa2867b0963ad72338824450905679ff0fa304b8c5093ae8 + languageName: node + linkType: hard + "node-gyp-build@npm:~4.1.0": version: 4.1.1 resolution: "node-gyp-build@npm:4.1.1" @@ -3604,6 +3762,24 @@ __metadata: languageName: node linkType: hard +"openai@npm:^4.52.7": + version: 4.52.7 + resolution: "openai@npm:4.52.7" + dependencies: + "@types/node": "npm:^18.11.18" + "@types/node-fetch": "npm:^2.6.4" + abort-controller: "npm:^3.0.0" + agentkeepalive: "npm:^4.2.1" + form-data-encoder: "npm:1.7.2" + formdata-node: "npm:^4.3.2" + node-fetch: "npm:^2.6.7" + web-streams-polyfill: "npm:^3.2.1" + bin: + openai: bin/cli + checksum: 9689b08ee26bad4834d5b8fb57d05a5a956450b699f0b78d56fc3c89304ef6ac621c3458fa269fb059c33e871faaaecd1f4899287dfbc1ac1039228b247260cd + languageName: node + linkType: hard + "p-map@npm:^4.0.0": version: 4.0.0 resolution: "p-map@npm:4.0.0" @@ -4144,6 +4320,13 @@ __metadata: languageName: node linkType: hard +"tr46@npm:~0.0.3": + version: 0.0.3 + resolution: "tr46@npm:0.0.3" + checksum: 047cb209a6b60c742f05c9d3ace8fa510bff609995c129a37ace03476a9b12db4dbf975e74600830ef0796e18882b2381fb5fb1f6b4f96b832c374de3ab91a11 + languageName: node + linkType: hard + "tree-kill@npm:^1.2.2": version: 1.2.2 resolution: "tree-kill@npm:1.2.2" @@ -4187,6 +4370,13 @@ __metadata: languageName: node linkType: hard +"undici-types@npm:~5.26.4": + version: 5.26.5 + resolution: "undici-types@npm:5.26.5" + checksum: bb673d7876c2d411b6eb6c560e0c571eef4a01c1c19925175d16e3a30c4c428181fb8d7ae802a261f283e4166a0ac435e2f505743aa9e45d893f9a3df017b501 + languageName: node + linkType: hard + "undici@npm:^5.22.1": version: 5.28.3 resolution: "undici@npm:5.28.3" @@ -4337,6 +4527,37 @@ __metadata: languageName: node linkType: hard +"web-streams-polyfill@npm:4.0.0-beta.3": + version: 4.0.0-beta.3 + resolution: "web-streams-polyfill@npm:4.0.0-beta.3" + checksum: a9596779db2766990117ed3a158e0b0e9f69b887a6d6ba0779940259e95f99dc3922e534acc3e5a117b5f5905300f527d6fbf8a9f0957faf1d8e585ce3452e8e + languageName: node + linkType: hard + +"web-streams-polyfill@npm:^3.2.1": + version: 3.3.3 + resolution: "web-streams-polyfill@npm:3.3.3" + checksum: 64e855c47f6c8330b5436147db1c75cb7e7474d924166800e8e2aab5eb6c76aac4981a84261dd2982b3e754490900b99791c80ae1407a9fa0dcff74f82ea3a7f + languageName: node + linkType: hard + +"webidl-conversions@npm:^3.0.0": + version: 3.0.1 + resolution: "webidl-conversions@npm:3.0.1" + checksum: 5612d5f3e54760a797052eb4927f0ddc01383550f542ccd33d5238cfd65aeed392a45ad38364970d0a0f4fea32e1f4d231b3d8dac4a3bdd385e5cf802ae097db + languageName: node + linkType: hard + +"whatwg-url@npm:^5.0.0": + version: 5.0.0 + resolution: "whatwg-url@npm:5.0.0" + dependencies: + tr46: "npm:~0.0.3" + webidl-conversions: "npm:^3.0.0" + checksum: 1588bed84d10b72d5eec1d0faa0722ba1962f1821e7539c535558fb5398d223b0c50d8acab950b8c488b4ba69043fd833cc2697056b167d8ad46fac3995a55d5 + languageName: node + linkType: hard + "which@npm:^2.0.1": version: 2.0.2 resolution: "which@npm:2.0.2"