diff --git a/mobile-magic/apps/frontend/.env.example b/mobile-magic/apps/frontend/.env.example new file mode 100644 index 0000000..a0c18c2 --- /dev/null +++ b/mobile-magic/apps/frontend/.env.example @@ -0,0 +1,4 @@ +NEXT_PUBLIC_BACKEND_URL= +NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY= +CLERK_SECRET_KEY= +NEXT_PUBLIC_GITHUB_CLIENT_ID= \ No newline at end of file diff --git a/mobile-magic/apps/frontend/.gitignore b/mobile-magic/apps/frontend/.gitignore index e2764f9..86ca0d4 100644 --- a/mobile-magic/apps/frontend/.gitignore +++ b/mobile-magic/apps/frontend/.gitignore @@ -31,7 +31,7 @@ yarn-error.log* .pnpm-debug.log* # env files (can opt-in for committing if needed) -.env* +.env # vercel .vercel diff --git a/mobile-magic/apps/frontend/app/project/[projectId]/Project.tsx b/mobile-magic/apps/frontend/app/project/[projectId]/Project.tsx index ab4111f..4953b09 100644 --- a/mobile-magic/apps/frontend/app/project/[projectId]/Project.tsx +++ b/mobile-magic/apps/frontend/app/project/[projectId]/Project.tsx @@ -12,6 +12,7 @@ import { useRouter } from "next/navigation" import Image from "next/image"; import axios from "axios"; import { PreviewIframe } from "@/components/PreviewIframe"; +import { GitHubModal } from "@/components/GitHubModal"; export const Project: React.FC<{ projectId: string, sessionUrl: string, previewUrl: string, workerUrl: string }> = ({projectId, sessionUrl, previewUrl, workerUrl }) => { const router = useRouter() @@ -97,6 +98,7 @@ export const Project: React.FC<{ projectId: string, sessionUrl: string, previewU
+ diff --git a/mobile-magic/apps/frontend/components/GitHubIcon.tsx b/mobile-magic/apps/frontend/components/GitHubIcon.tsx new file mode 100644 index 0000000..f65c3c1 --- /dev/null +++ b/mobile-magic/apps/frontend/components/GitHubIcon.tsx @@ -0,0 +1,13 @@ +export function GitHubIcon() { + return ( + + GitHub + + + ); +} diff --git a/mobile-magic/apps/frontend/components/GitHubModal.tsx b/mobile-magic/apps/frontend/components/GitHubModal.tsx new file mode 100644 index 0000000..162090a --- /dev/null +++ b/mobile-magic/apps/frontend/components/GitHubModal.tsx @@ -0,0 +1,298 @@ +import React, { useEffect, useState } from "react"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { Button } from "@/components/ui/button"; +import { Copy, ExternalLink } from "lucide-react"; +import { GitHubIcon } from "./GitHubIcon"; +import axios from "axios"; +import { useAuth } from "@clerk/nextjs"; +import { useRouter } from "next/navigation"; + +export const GitHubModal: React.FC<{ + projectId: string; + workerUrl: string; +}> = ({ projectId, workerUrl }) => { + const { getToken } = useAuth(); + const [isConnected, setIsConnected] = useState(false); + const [isRepoCreated, setIsRepoCreated] = useState(false); + const [repoUrl, setRepoUrl] = useState(null); + const [gitHubUsername, setGitHubUsername] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const router = useRouter(); + const [copied, setCopied] = useState(false); + const [cloneUrl, setCloneUrl] = useState(null); + const branch = "main"; + + useEffect(() => { + (async () => { + const code = new URLSearchParams(window.location.search).get("code"); + if (code) { + setIsLoading(true); + try { + const response = await axios.post( + `${process.env.NEXT_PUBLIC_BACKEND_URL}/github/token`, + { + code, + redirectUri: `${window.location.origin}/project/${projectId}`, + }, + { + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${await getToken()}`, + }, + } + ); + + if (response.status != 200) { + console.log("Error exchanging code to access token"); + return; + } + setGitHubUsername(response.data.username); + router.push(`/project/${projectId}`); + } catch (err) { + console.log("GitHub OAuth error:", err); + } finally { + setIsLoading(false); + } + } else { + setIsLoading(true); + try { + const response = await axios.get( + `${process.env.NEXT_PUBLIC_BACKEND_URL}/github/repoUrl?projectId=${projectId}`, + { + headers: { + Authorization: `Bearer ${await getToken()}`, + }, + } + ); + if (response.status == 200) { + setRepoUrl(response.data.repoUrl); + setCloneUrl(getCloneUrl(response.data.repoUrl, "HTTPS")); + } + } catch (error) { + console.log(error); + } finally { + setIsLoading(false); + } + } + })(); + }, []); + + useEffect(() => { + setIsRepoCreated(!!repoUrl); + setIsConnected(!!gitHubUsername); + }, [repoUrl, gitHubUsername]); + + function copyToClipboard(text: string) { + navigator.clipboard.writeText(text); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } + + async function handleOnConnect() { + setIsLoading(true); + try { + const response = await axios.get( + `${process.env.NEXT_PUBLIC_BACKEND_URL}/github/username`, + { + headers: { + Authorization: `Bearer ${await getToken()}`, + }, + } + ); + if (response.status == 200) { + if (response.data.isConnected) + setGitHubUsername(response.data.gitHubUsername); + else { + const redirectUri = encodeURIComponent( + `${window.location.origin}/project/${projectId}` + ); + const authUrl = `https://github.com/login/oauth/authorize?client_id=${process.env.NEXT_PUBLIC_GITHUB_CLIENT_ID}&redirect_uri=${redirectUri}&scope=repo`; + window.location.href = authUrl; + } + } + } catch (error) { + console.log(error); + } finally { + setIsLoading(false); + } + } + + async function handleCreateRepo() { + setIsLoading(true); + try { + const response = await axios.post( + `${process.env.NEXT_PUBLIC_BACKEND_URL}/github/createRepo`, + { + projectId, + workerUrl, + }, + { + headers: { + Authorization: `Bearer ${await getToken()}`, + }, + } + ); + if (response.status == 200) { + setRepoUrl(`${response.data.repoUrl}`); + setCloneUrl(getCloneUrl(response.data.repoUrl, "HTTPS")); + } + } catch (error) { + console.log(error); + } finally { + setIsLoading(false); + } + } + + return ( + + + + + + {isLoading ? ( + + ) : isRepoCreated ? ( +
+
GitHub
+
+

This project is connected to

+

+ {repoUrl?.split("/").slice(-2).join("/") || ""}. +

+

+ Mobile Magic will commit changes to the{" "} + {branch} branch. +

+
+ +
+

Clone

+
+ + + +
+ +
+
+ {cloneUrl} +
+
+ + {copied && ( +
+ Copied +
+ )} +
+
+
+ + +
+ ) : isConnected ? ( +
+
GitHub
+

+ Connect your project to GitHub to save your code and collaborate + with others. +

+
Create in
+ +
+ ) : ( +
+
GitHub
+

+ Connect your project to GitHub to save your code and collaborate + with others. +

+ +
+ )} +
+
+ ); +}; + +function LoadingSpinner() { + return ( +
+
+
+ ); +} + +function getCloneUrl(repoUrl: string, type: "HTTPS" | "SSH" | "CLI"): string { + const usernameRepoName = repoUrl.split("/").slice(-2).join("/"); + if (type == "HTTPS") return `${repoUrl}.git`; + else if (type == "SSH") return `git@github.com:${usernameRepoName}.git`; + else return `gh repo clone ${usernameRepoName}`; +} +// bg-blue-600 hover:bg-blue-700 diff --git a/mobile-magic/apps/frontend/components/ui/popover.tsx b/mobile-magic/apps/frontend/components/ui/popover.tsx new file mode 100644 index 0000000..ca6d8bb --- /dev/null +++ b/mobile-magic/apps/frontend/components/ui/popover.tsx @@ -0,0 +1,48 @@ +"use client" + +import * as React from "react" +import * as PopoverPrimitive from "@radix-ui/react-popover" + +import { cn } from "@/lib/utils" + +function Popover({ + ...props +}: React.ComponentProps) { + return +} + +function PopoverTrigger({ + ...props +}: React.ComponentProps) { + return +} + +function PopoverContent({ + className, + align = "center", + sideOffset = 4, + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +function PopoverAnchor({ + ...props +}: React.ComponentProps) { + return +} + +export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor } diff --git a/mobile-magic/apps/frontend/package.json b/mobile-magic/apps/frontend/package.json index 4025e6f..868a545 100644 --- a/mobile-magic/apps/frontend/package.json +++ b/mobile-magic/apps/frontend/package.json @@ -15,6 +15,7 @@ "@radix-ui/react-avatar": "^1.1.3", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-label": "^2.1.2", + "@radix-ui/react-popover": "^1.1.6", "@radix-ui/react-separator": "^1.1.2", "@radix-ui/react-slot": "^1.1.2", "@radix-ui/react-switch": "^1.1.3", diff --git a/mobile-magic/apps/primary-backend/.env.example b/mobile-magic/apps/primary-backend/.env.example index f1c1ade..57cdee2 100644 --- a/mobile-magic/apps/primary-backend/.env.example +++ b/mobile-magic/apps/primary-backend/.env.example @@ -1 +1,3 @@ -JWT_PUBLIC_KEY= \ No newline at end of file +JWT_PUBLIC_KEY= +GITHUB_CLIENT_ID= +GITHUB_CLIENT_SECRET= \ No newline at end of file diff --git a/mobile-magic/apps/primary-backend/index.ts b/mobile-magic/apps/primary-backend/index.ts index 554ac18..5b0eeba 100644 --- a/mobile-magic/apps/primary-backend/index.ts +++ b/mobile-magic/apps/primary-backend/index.ts @@ -2,6 +2,7 @@ import { prismaClient } from "db/client"; import express from "express"; import cors from "cors"; import { authMiddleware } from "common/middleware"; +import axios from "axios"; const app = express(); @@ -40,6 +41,107 @@ app.get("/prompts/:projectId", authMiddleware, async (req, res) => { res.json({ prompts }); }); +app.get("/github/repoUrl", authMiddleware, async (req, res) => { + const { projectId } = req.query; + const userId = req.userId!; + if (!projectId) { + res.status(400).json({ error: "ProjectId does not exist" }); + return; + } + const project = await prismaClient.project.findFirst({ + where: { + id: projectId as string, + userId, + }, + }); + if (!project) { + res.status(400).json({ error: "Project does not exist" }); + return; + } + res.json({ repoUrl: project.repoUrl }); +}); + +app.get("/github/username", authMiddleware, async (req, res) => { + const userId = req.userId!; + const gitHub = await prismaClient.gitHubUser.findFirst({ + where: { + userId, + }, + }); + if (!gitHub) res.json({ isConnected: false, gitHubUsername: null }); + else res.json({ isConnected: true, gitHubUsername: gitHub.username }); +}); + +app.post("/github/token", authMiddleware, async (req, res) => { + const { code, redirectUri } = req.body; + const userId = req.userId!; + try { + const tokenRes = await axios.post( + "https://github.com/login/oauth/access_token", + { + client_id: process.env.GITHUB_CLIENT_ID, + client_secret: process.env.GITHUB_CLIENT_SECRET, + code, + redirect_uri: redirectUri, + }, + { + headers: { Accept: "application/json" }, + } + ); + if (tokenRes.status != 200) { + console.log("OAuth error"); + res.status(500).send("GitHub OAuth failed."); + return; + } + const accessToken = tokenRes.data.access_token; + const userRes = await axios.get("https://api.github.com/user", { + headers: { Authorization: `Bearer ${accessToken}` }, + }); + if (userRes.status != 200) { + console.log("Error fetching user details"); + res.status(500).send("Error fetching user details"); + return; + } + const username = userRes.data.login; + await prismaClient.gitHubUser.create({ + data: { + userId, + accessToken, + username, + }, + }); + res.json({ username }); + } catch (err) { + console.log("OAuth error:", err); + res.status(500).send("GitHub OAuth failed."); + } +}); + +app.post("/github/createRepo", authMiddleware, async (req, res) => { + const { projectId, workerUrl } = req.body; + const userId = req.userId!; + const github = await prismaClient.gitHubUser.findFirst({ + where: { userId } + }); + if(!github) { + res.status(400).send("Access Token does not exist"); + return; + } + try { + const response = await axios.post(`${workerUrl}/github/createRepo`, { + projectId + }); + if(response.status != 200) { + res.status(500).send("Error while creating a new repo (or) while pushing the code to GitHub"); + return; + } + res.json(response.data); + } catch (error) { + res.status(500).send("Error while creating a new repo (or) while pushing the code to GitHub"); + return; + } +}); + app.listen(9090, () => { console.log("Server is running on port 9090"); }); \ No newline at end of file diff --git a/mobile-magic/apps/primary-backend/package.json b/mobile-magic/apps/primary-backend/package.json index eb8c389..32cd01a 100644 --- a/mobile-magic/apps/primary-backend/package.json +++ b/mobile-magic/apps/primary-backend/package.json @@ -13,6 +13,7 @@ "@types/cors": "^2.8.17", "@types/express": "^5.0.0", "@types/jsonwebtoken": "^9.0.9", + "axios": "^1.8.3", "cors": "^2.8.5", "express": "^4.21.2", "jsonwebtoken": "^9.0.2", diff --git a/mobile-magic/apps/worker/.env.example b/mobile-magic/apps/worker/.env.example index 2be99c8..91e8815 100644 --- a/mobile-magic/apps/worker/.env.example +++ b/mobile-magic/apps/worker/.env.example @@ -1,2 +1,3 @@ ANTHROPIC_API_KEY=your_anthropic_api_key -JWT_PUBLIC_KEY=your_jwt_public_key \ No newline at end of file +JWT_PUBLIC_KEY=your_jwt_public_key +WS_RELAYER_URL= \ No newline at end of file diff --git a/mobile-magic/apps/worker/index.ts b/mobile-magic/apps/worker/index.ts index e0c7967..cfff067 100644 --- a/mobile-magic/apps/worker/index.ts +++ b/mobile-magic/apps/worker/index.ts @@ -2,18 +2,19 @@ import cors from "cors"; import express from "express"; import { prismaClient } from "db/client"; import Anthropic from '@anthropic-ai/sdk'; -import { systemPrompt } from "./systemPrompt"; +import { systemPrompt, systemPromptForRepoName } from "./systemPrompt"; import { ArtifactProcessor } from "./parser"; -import { onFileUpdate, onPromptEnd, onShellCommand } from "./os"; +import { getBaseWorkerDir, onFileUpdate, onPromptEnd, onShellCommand, pushToGitHubFromDockerContainer } from "./os"; import { RelayWebsocket } from "./ws"; +import axios from "axios"; const app = express(); app.use(cors()); app.use(express.json()); +const client = new Anthropic(); app.post("/prompt", async (req, res) => { const { prompt, projectId } = req.body; - const client = new Anthropic(); const project = await prismaClient.project.findUnique({ where: { id: projectId, @@ -98,7 +99,135 @@ app.post("/prompt", async (req, res) => { console.log("error", error); }); - res.json({ response }); + // Wait some time till all the files get updated and then push to GitHub + await new Promise((r) => setTimeout(r, 1000 * 30)); + + let isPushToGitHubSuccess: { + isSuccess: boolean; + message: string; + }; + if(!project.repoUrl) { + isPushToGitHubSuccess = { + isSuccess: false, + message: "Project is not connected to GitHub" + }; + } else { + const github = await prismaClient.gitHubUser.findFirst({ + where: { + userId: project.userId + } + }); + if(!github) + isPushToGitHubSuccess = { + isSuccess: false, + message: "Access Token does not exist" + } + else { + try { + const userRes = await axios.get("https://api.github.com/user", { + headers: { Authorization: `Bearer ${github.accessToken}` }, + }); + if (userRes.status == 401 || userRes.status == 403) { + console.log("Access token is not valid"); + isPushToGitHubSuccess = { + isSuccess: false, + message: "Access token is not valid" + }; + } else { + // For the 3rd parameter in the below function, we should give the complete path of this particular project directory (which is in code-server docker container). For now, given /tmp/project.type for the path. + const isSuccess = await pushToGitHubFromDockerContainer(project.repoUrl, github.accessToken, getBaseWorkerDir(project.type), "Added changes", false); + isPushToGitHubSuccess = { + isSuccess, + message: isSuccess ? "Successfully pushed to GitHub" : "Error while pushing to GitHub" + } + } + } catch (error) { + console.log(error); + isPushToGitHubSuccess = { + isSuccess: false, + message: "Error while checking if access token is valid or not" + }; + } + } + } + res.json({ response, isPushToGitHubSuccess }); +}); + +app.post("/github/createRepo", async (req, res) => { + const { projectId } = req.body; + const project = await prismaClient.project.findFirst({ + where: { + id: projectId + } + }); + if(!project) { + res.status(400).json({ error: "Project does not exist" }); + return; + } + const github = await prismaClient.gitHubUser.findFirst({ + where: { + userId: project.userId + } + }); + if(!github) { + res.status(400).json({ error: "Access token does not exist" }); + return; + } + const initialPrompt = await prismaClient.prompt.findFirst({ + where: { + projectId + }, + orderBy: { + createdAt: "asc" + }, + take: 1 + }); + if(!initialPrompt) { + res.status(400).json({ error: "Initial Prompt does not exist" }); + return; + } + const msg = await client.messages.create({ + model: "claude-3-7-sonnet-20250219", + max_tokens: 1024, + system: systemPromptForRepoName(), + messages: [{ role: "user", content: initialPrompt.content }], + }); + const repoName = msg.content[0].type == "text" && msg.content[0].text || ("repo-" + Math.random().toString()); + try { + const response = await axios.post( + "https://api.github.com/user/repos", + { name: repoName, private: false }, + { + headers: { + Authorization: `Bearer ${github.accessToken}`, + Accept: "application/vnd.github+json" + } + } + ); + if(response.status != 201) { + res.status(400).json({ error: "Error while creating a new repo" }); + return; + } + // For the 3rd parameter in the below function, we should give the complete path of this particular project directory (which is in code-server docker container). For now, given /tmp/project.type for the path. + const isPushSuccess = await pushToGitHubFromDockerContainer(response.data.html_url, github.accessToken, getBaseWorkerDir(project.type), "Initial Commit", true); + if(isPushSuccess) { + await prismaClient.project.update({ + where: { + id: projectId + }, + data: { + repoUrl: response.data.html_url + } + }); + res.json({ repoUrl: response.data.html_url }); + } + else + res.status(500).json({ error: "Error pushing code to GitHub" }); + } catch (error) { + console.log(error); + res.status(500).json({ error: "Error while creating a new repo" }); + return; + } }); app.listen(9091, () => { diff --git a/mobile-magic/apps/worker/os.ts b/mobile-magic/apps/worker/os.ts index d35003b..2003c47 100644 --- a/mobile-magic/apps/worker/os.ts +++ b/mobile-magic/apps/worker/os.ts @@ -1,15 +1,20 @@ import { prismaClient } from "db/client"; import { RelayWebsocket } from "./ws"; +import { exec } from "child_process"; -function getBaseWorkerDir(type: "NEXTJS" | "REACT_NATIVE") { +const DOCKER_CONTAINER_NAME_RUNNING_CODE_SERVER = "my-code-server"; + +export function getBaseWorkerDir(type: "NEXTJS" | "REACT_NATIVE" | "REACT") { if (type === "NEXTJS") { return "/tmp/next-app"; - } - return "/tmp/mobile-app"; + } else if(type === "REACT") + return "/tmp/react-app" + else + return "/tmp/mobile-app"; } -export async function onFileUpdate(filePath: string, fileContent: string, projectId: string, promptId: string, type: "NEXTJS" | "REACT_NATIVE") { +export async function onFileUpdate(filePath: string, fileContent: string, projectId: string, promptId: string, type: "NEXTJS" | "REACT_NATIVE" | "REACT") { await prismaClient.action.create({ data: { projectId, @@ -34,7 +39,7 @@ export async function onShellCommand(shellCommand: string, projectId: string, pr for (const command of commands) { console.log(`Running command: ${command}`); - ws.send(JSON.stringify({ + RelayWebsocket.getInstance().send(JSON.stringify({ event: "admin", data: { type: "command", @@ -54,10 +59,42 @@ export async function onShellCommand(shellCommand: string, projectId: string, pr export function onPromptEnd(promptId: string) { - ws.send(JSON.stringify({ + RelayWebsocket.getInstance().send(JSON.stringify({ event: "admin", data: { type: "prompt-end" } })) +} + +export function pushToGitHubFromDockerContainer(repoUrl: string, accessToken: string, projectPath: string, commitMessage: string, isInitialCommit: boolean): Promise { + const containerName = DOCKER_CONTAINER_NAME_RUNNING_CODE_SERVER; + const gitCommands = ` + cd ${projectPath} && \ + ${isInitialCommit && `git init && \ + git remote add origin ${getRemoteOrigin(repoUrl, accessToken)} && \ + git branch -M main && \ `} + git config user.email "mobile-magic@github.com" && \ + git config user.name "Mobile Magic" && \ + git add . && \ + git commit -m "${commitMessage}" && \ + git push -u origin main + `; + return new Promise((resolve) => { + exec(`docker exec ${containerName} sh -c '${gitCommands}'`, (err, stdout, stderr) => { + if (err) { + console.log("Error:", err.message); + resolve(false); + } else { + console.log("Git push output: ", stdout); + console.log("Stderr: ", stderr); + resolve(true); + } + }); + }); +}; + +function getRemoteOrigin(repoUrl: string, accessToken: string): string { + const userNameRepoName = repoUrl.split("/").slice(-2).join("/"); + return `https://${accessToken}@github.com/${userNameRepoName}.git`; } \ No newline at end of file diff --git a/mobile-magic/apps/worker/package.json b/mobile-magic/apps/worker/package.json index 8012117..1f7b13e 100644 --- a/mobile-magic/apps/worker/package.json +++ b/mobile-magic/apps/worker/package.json @@ -11,6 +11,7 @@ "dependencies": { "@anthropic-ai/sdk": "^0.38.0", "@types/express": "^5.0.0", + "axios": "^1.8.3", "cors": "^2.8.5", "express": "^4.21.2", "common": "*" diff --git a/mobile-magic/apps/worker/systemPrompt.tsx b/mobile-magic/apps/worker/systemPrompt.tsx index 27f3a68..69b2af0 100644 --- a/mobile-magic/apps/worker/systemPrompt.tsx +++ b/mobile-magic/apps/worker/systemPrompt.tsx @@ -1864,3 +1864,9 @@ ${ARTIFACT_INFO} ${projectType === "NEXTJS" ? NEXT_JS_ARTIFACT_INFO : projectType === "REACT_NATIVE" ? REACT_NATIVE_ARTIFACT_INFO : REACT_ARTIFACT_INFO} `; + +export const systemPromptForRepoName = () => `Given prompt will be a React Application (or) React Native Application (or) Next.js Application. +Depending on the prompt, give a good, meaningful and suitable "GitHub Repository Name" for that application. ** Only just give the repository name as the output ** +Example: +User Prompt: Create a todo application. +Response: Todo App`; \ No newline at end of file diff --git a/mobile-magic/apps/worker/ws.ts b/mobile-magic/apps/worker/ws.ts index be5ca02..2539d81 100644 --- a/mobile-magic/apps/worker/ws.ts +++ b/mobile-magic/apps/worker/ws.ts @@ -4,22 +4,26 @@ export class RelayWebsocket { private static instance: RelayWebsocket; private ws: WebSocket; private callbacks: Map void>; + private isOpen: boolean = false; + private bufferedMessages: any[] = []; private constructor(url: string) { this.ws = new WebSocket(url); this.callbacks = new Map(); - this.ws.onmessage = (event) => { - const { callbackId, ...data } = JSON.parse(event.data); + this.ws.onmessage = (eventMessage) => { + const { callbackId, diff, event } = JSON.parse(eventMessage.data); const callback = this.callbacks.get(callbackId); if (callback) { - callback(data); + callback({ diff }); } }; this.ws.onopen = () => { + this.isOpen = true; this.send(JSON.stringify({ event: "api_subscribe", })); + this.bufferedMessages.forEach((m) => this.send(m)); } } @@ -31,11 +35,15 @@ export class RelayWebsocket { } send(message: string) { + if(!this.isOpen) { + this.bufferedMessages.push(message); + return; + } this.ws.send(message); } sendAndAwaitResponse(message: any, callbackId: string): Promise { - this.ws.send(JSON.stringify({...message, callbackId})); + this.send(JSON.stringify({...message, callbackId})); return new Promise((resolve, reject) => { this.callbacks.set(callbackId, resolve); diff --git a/mobile-magic/apps/ws-relayer/index.ts b/mobile-magic/apps/ws-relayer/index.ts index b219a55..3c62524 100644 --- a/mobile-magic/apps/ws-relayer/index.ts +++ b/mobile-magic/apps/ws-relayer/index.ts @@ -17,7 +17,7 @@ Bun.serve({ return new Response("Upgrade failed", { status: 500 }); }, websocket: { - message(ws, message) { + message(ws: any, message: any) { const { event, data }: MessagePayload = JSON.parse(message.toString()); if (event === "subscribe") { SUBSCRIPTIONS.push(ws); @@ -33,14 +33,14 @@ Bun.serve({ } } else if (event === "api_subscribe") { API_SUBSCRIPTIONS.push(ws); - } else if (event === "vscode") { + } else if (event === "vscode_diff") { API_SUBSCRIPTIONS.forEach(ws => ws.send(JSON.stringify(data))); } }, - open(ws) { + open(ws: any) { console.log("open"); }, - close(ws) { + close(ws: any) { console.log("close"); }, diff --git a/mobile-magic/bun.lock b/mobile-magic/bun.lock index ccb1fdc..3c2ccb0 100644 --- a/mobile-magic/bun.lock +++ b/mobile-magic/bun.lock @@ -19,6 +19,7 @@ "@radix-ui/react-avatar": "^1.1.3", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-label": "^2.1.2", + "@radix-ui/react-popover": "^1.1.6", "@radix-ui/react-separator": "^1.1.2", "@radix-ui/react-slot": "^1.1.2", "@radix-ui/react-switch": "^1.1.3", @@ -74,6 +75,7 @@ "@types/cors": "^2.8.17", "@types/express": "^5.0.0", "@types/jsonwebtoken": "^9.0.9", + "axios": "^1.8.3", "common": "*", "cors": "^2.8.5", "express": "^4.21.2", @@ -92,6 +94,8 @@ "dependencies": { "@anthropic-ai/sdk": "^0.38.0", "@types/express": "^5.0.0", + "axios": "^1.8.3", + "common": "*", "cors": "^2.8.5", "express": "^4.21.2", }, @@ -122,6 +126,7 @@ "name": "ws-relayer", "devDependencies": { "@types/bun": "latest", + "common": "*", }, "peerDependencies": { "typescript": "^5.0.0", @@ -490,6 +495,8 @@ "@radix-ui/react-label": ["@radix-ui/react-label@2.1.2", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-zo1uGMTaNlHehDyFQcDZXRJhUPDuukcnHz0/jnrup0JA6qL+AFpAnty+7VKa9esuU5xTblAZzTGYJKSKaBxBhw=="], + "@radix-ui/react-popover": ["@radix-ui/react-popover@1.1.6", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.5", "@radix-ui/react-focus-guards": "1.1.1", "@radix-ui/react-focus-scope": "1.1.2", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-popper": "1.2.2", "@radix-ui/react-portal": "1.1.4", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-slot": "1.1.2", "@radix-ui/react-use-controllable-state": "1.1.0", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-NQouW0x4/GnkFJ/pRqsIS3rM/k97VzKnVb2jB7Gq7VEGPy5g7uNV1ykySFt7eWSp3i2uSGFwaJcvIRJBAHmmFg=="], + "@radix-ui/react-popper": ["@radix-ui/react-popper@1.2.2", "", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.2", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-layout-effect": "1.1.0", "@radix-ui/react-use-rect": "1.1.0", "@radix-ui/react-use-size": "1.1.0", "@radix-ui/rect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Rvqc3nOpwseCyj/rgjlJDYAgyfw7OC1tTkKn2ivhaMGcYt8FSBlahHOZak2i3QwkRXUXgGgzeEe2RuqeEHuHgA=="], "@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.4", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sn2O9k1rPFYVyKd5LAJfo96JlSGVFpa1fS6UuBJfrZadudiw5tAmru+n1x7aMRQ84qDM71Zh1+SzK5QwU0tJfA=="], @@ -814,7 +821,7 @@ "axe-core": ["axe-core@4.10.3", "", {}, "sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg=="], - "axios": ["axios@1.8.2", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } }, "sha512-ls4GYBm5aig9vWx8AWDSGLpnpDQRtWAfrjU+EuytuODrFBkqesN2RkOQCBzrA1RQNHw1SmRMSDDDSwzNAYQ6Rg=="], + "axios": ["axios@1.8.3", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } }, "sha512-iP4DebzoNlP/YN2dpwCgb8zoCmhtkajzS48JvwmkSkXvPI3DHc7m+XYL5tGnSlJtR6nImXZmdCuN5aP8dh1d8A=="], "axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="], @@ -1580,7 +1587,7 @@ "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], - "redis": ["redis@workspace:packages/redis"], + "redis": ["redis@4.7.0", "", { "dependencies": { "@redis/bloom": "1.2.0", "@redis/client": "1.6.0", "@redis/graph": "1.1.1", "@redis/json": "1.0.7", "@redis/search": "1.2.0", "@redis/time-series": "1.1.0" } }, "sha512-zvmkHEAdGMn+hMRXuMBtu4Vo5P6rHQjLoHftu+lBqq8ZTA3RCVC/WzD790bkKKiNFp7d5/9PcSD19fJyyRvOdQ=="], "reflect.getprototypeof": ["reflect.getprototypeof@1.0.10", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" } }, "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw=="], @@ -2100,7 +2107,7 @@ "jsonwebtoken/semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="], - "k8s-orchestrator/redis": ["redis@4.7.0", "", { "dependencies": { "@redis/bloom": "1.2.0", "@redis/client": "1.6.0", "@redis/graph": "1.1.1", "@redis/json": "1.0.7", "@redis/search": "1.2.0", "@redis/time-series": "1.1.0" } }, "sha512-zvmkHEAdGMn+hMRXuMBtu4Vo5P6rHQjLoHftu+lBqq8ZTA3RCVC/WzD790bkKKiNFp7d5/9PcSD19fJyyRvOdQ=="], + "k8s-orchestrator/@types/bun": ["@types/bun@1.2.5", "", { "dependencies": { "bun-types": "1.2.5" } }, "sha512-w2OZTzrZTVtbnJew1pdFmgV99H0/L+Pvw+z1P67HaR18MHOzYnTYOi6qzErhK8HyT+DB782ADVPPE92Xu2/Opg=="], "log-symbols/chalk": ["chalk@2.4.2", "", { "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ=="], @@ -2124,6 +2131,8 @@ "postcss/picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + "primary-backend/@types/bun": ["@types/bun@1.2.5", "", { "dependencies": { "bun-types": "1.2.5" } }, "sha512-w2OZTzrZTVtbnJew1pdFmgV99H0/L+Pvw+z1P67HaR18MHOzYnTYOi6qzErhK8HyT+DB782ADVPPE92Xu2/Opg=="], + "rc/strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="], "react-remove-scroll/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], @@ -2132,8 +2141,6 @@ "react-style-singleton/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "redis/redis": ["redis@4.7.0", "", { "dependencies": { "@redis/bloom": "1.2.0", "@redis/client": "1.6.0", "@redis/graph": "1.1.1", "@redis/json": "1.0.7", "@redis/search": "1.2.0", "@redis/time-series": "1.1.0" } }, "sha512-zvmkHEAdGMn+hMRXuMBtu4Vo5P6rHQjLoHftu+lBqq8ZTA3RCVC/WzD790bkKKiNFp7d5/9PcSD19fJyyRvOdQ=="], - "rxjs/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], "send/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], @@ -2160,6 +2167,10 @@ "use-sidecar/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "worker/@types/bun": ["@types/bun@1.2.5", "", { "dependencies": { "bun-types": "1.2.5" } }, "sha512-w2OZTzrZTVtbnJew1pdFmgV99H0/L+Pvw+z1P67HaR18MHOzYnTYOi6qzErhK8HyT+DB782ADVPPE92Xu2/Opg=="], + + "ws-relayer/@types/bun": ["@types/bun@1.2.5", "", { "dependencies": { "bun-types": "1.2.5" } }, "sha512-w2OZTzrZTVtbnJew1pdFmgV99H0/L+Pvw+z1P67HaR18MHOzYnTYOi6qzErhK8HyT+DB782ADVPPE92Xu2/Opg=="], + "@anthropic-ai/sdk/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="], "@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], @@ -2190,6 +2201,8 @@ "inquirer/ora/log-symbols": ["log-symbols@4.1.0", "", { "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" } }, "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg=="], + "k8s-orchestrator/@types/bun/bun-types": ["bun-types@1.2.5", "", { "dependencies": { "@types/node": "*", "@types/ws": "~8.5.10" } }, "sha512-3oO6LVGGRRKI4kHINx5PIdIgnLRb7l/SprhzqXapmoYkFl5m4j6EvALvbDVuuBFaamB46Ap6HCUxIXNLCGy+tg=="], + "log-symbols/chalk/ansi-styles": ["ansi-styles@3.2.1", "", { "dependencies": { "color-convert": "^1.9.0" } }, "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA=="], "log-symbols/chalk/escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="], @@ -2202,16 +2215,24 @@ "node-plop/inquirer/rxjs": ["rxjs@6.6.7", "", { "dependencies": { "tslib": "^1.9.0" } }, "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ=="], + "primary-backend/@types/bun/bun-types": ["bun-types@1.2.5", "", { "dependencies": { "@types/node": "*", "@types/ws": "~8.5.10" } }, "sha512-3oO6LVGGRRKI4kHINx5PIdIgnLRb7l/SprhzqXapmoYkFl5m4j6EvALvbDVuuBFaamB46Ap6HCUxIXNLCGy+tg=="], + "send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], "snakecase-keys/snake-case/dot-case": ["dot-case@3.0.4", "", { "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w=="], "snakecase-keys/snake-case/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "worker/@types/bun/bun-types": ["bun-types@1.2.5", "", { "dependencies": { "@types/node": "*", "@types/ws": "~8.5.10" } }, "sha512-3oO6LVGGRRKI4kHINx5PIdIgnLRb7l/SprhzqXapmoYkFl5m4j6EvALvbDVuuBFaamB46Ap6HCUxIXNLCGy+tg=="], + + "ws-relayer/@types/bun/bun-types": ["bun-types@1.2.5", "", { "dependencies": { "@types/node": "*", "@types/ws": "~8.5.10" } }, "sha512-3oO6LVGGRRKI4kHINx5PIdIgnLRb7l/SprhzqXapmoYkFl5m4j6EvALvbDVuuBFaamB46Ap6HCUxIXNLCGy+tg=="], + "@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], + "k8s-orchestrator/@types/bun/bun-types/@types/ws": ["@types/ws@8.5.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw=="], + "log-symbols/chalk/ansi-styles/color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="], "log-symbols/chalk/supports-color/has-flag": ["has-flag@3.0.0", "", {}, "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="], @@ -2220,8 +2241,14 @@ "node-plop/inquirer/rxjs/tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="], + "primary-backend/@types/bun/bun-types/@types/ws": ["@types/ws@8.5.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw=="], + "snakecase-keys/snake-case/dot-case/no-case": ["no-case@3.0.4", "", { "dependencies": { "lower-case": "^2.0.2", "tslib": "^2.0.3" } }, "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg=="], + "worker/@types/bun/bun-types/@types/ws": ["@types/ws@8.5.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw=="], + + "ws-relayer/@types/bun/bun-types/@types/ws": ["@types/ws@8.5.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw=="], + "log-symbols/chalk/ansi-styles/color-convert/color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="], "minizlib/rimraf/glob/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], diff --git a/mobile-magic/packages/common/types.ts b/mobile-magic/packages/common/types.ts index b6cfb91..6dbfa2d 100644 --- a/mobile-magic/packages/common/types.ts +++ b/mobile-magic/packages/common/types.ts @@ -6,14 +6,23 @@ export type MessagePayload = { event: "admin"; data: { type: "command" | "update-file" | "prompt-start" | "prompt-end" - content: string; + content?: string; path?: string; }; callbackId?: string; +} | { + event: "vscode_diff"; + data: { + diff: string; + callbackId: string; + } +} | { + event: "api_subscribe"; + data?: null; } export type VscodeMessagePayload = { - event: "vscode_diff"; + // event: "vscode_diff"; diff: string; - callbackId: string; + // callbackId: string; } \ No newline at end of file diff --git a/mobile-magic/packages/db/prisma/migrations/20250312205138_added_model_git_hub_user_and_repo_url_field_in_project_model/migration.sql b/mobile-magic/packages/db/prisma/migrations/20250312205138_added_model_git_hub_user_and_repo_url_field_in_project_model/migration.sql new file mode 100644 index 0000000..e4348c0 --- /dev/null +++ b/mobile-magic/packages/db/prisma/migrations/20250312205138_added_model_git_hub_user_and_repo_url_field_in_project_model/migration.sql @@ -0,0 +1,18 @@ +-- AlterTable +ALTER TABLE "Project" ADD COLUMN "repoUrl" TEXT; + +-- CreateTable +CREATE TABLE "GitHubUser" ( + "id" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "accessToken" TEXT NOT NULL, + "username" TEXT NOT NULL, + + CONSTRAINT "GitHubUser_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "GitHubUser_userId_key" ON "GitHubUser"("userId"); + +-- CreateIndex +CREATE UNIQUE INDEX "GitHubUser_accessToken_key" ON "GitHubUser"("accessToken"); diff --git a/mobile-magic/packages/db/prisma/schema.prisma b/mobile-magic/packages/db/prisma/schema.prisma index c8f151b..6440607 100644 --- a/mobile-magic/packages/db/prisma/schema.prisma +++ b/mobile-magic/packages/db/prisma/schema.prisma @@ -29,6 +29,7 @@ model Project { actions Action[] type ProjectType @default(NEXTJS) userId String + repoUrl String? } enum ProjectType { @@ -59,6 +60,13 @@ model Action { prompt Prompt @relation(fields: [promptId], references: [id]) } +model GitHubUser { + id String @id @default(uuid()) + userId String @unique + accessToken String @unique + username String +} + enum PromptType { USER SYSTEM