Supabase support: client, auth & SQL

This commit is contained in:
Will Chen
2025-04-22 16:45:47 -07:00
parent ec43482d6c
commit 4294ce5767
15 changed files with 694 additions and 9 deletions

View File

@@ -37,7 +37,7 @@ import { getGitAuthor } from "../utils/git_author";
import killPort from "kill-port";
import util from "util";
import log from "electron-log";
import { getSupabaseProjectName } from "../utils/supabase_management_client";
import { getSupabaseProjectName } from "../../supabase_admin/supabase_management_client";
const logger = log.scope("app_handlers");

View File

@@ -4,6 +4,10 @@ import { db } from "../../db";
import { chats, messages } from "../../db/schema";
import { and, eq, isNull } from "drizzle-orm";
import { SYSTEM_PROMPT } from "../../prompts/system_prompt";
import {
SUPABASE_AVAILABLE_SYSTEM_PROMPT,
SUPABASE_NOT_AVAILABLE_SYSTEM_PROMPT,
} from "../../prompts/supabase_prompt";
import { getDyadAppPath } from "../../paths/paths";
import { readSettings } from "../../main/settings";
import type { ChatResponseEnd, ChatStreamParams } from "../ipc_types";
@@ -13,6 +17,10 @@ import { streamTestResponse } from "./testing_chat_handlers";
import { getTestResponse } from "./testing_chat_handlers";
import { getModelClient } from "../utils/get_model_client";
import log from "electron-log";
import {
getSupabaseContext,
getSupabaseClientCode,
} from "../../supabase_admin/supabase_context";
const logger = log.scope("chat_stream_handlers");
@@ -158,12 +166,23 @@ export function registerChatStreamHandlers() {
) {
messageHistory.pop();
}
let systemPrompt = SYSTEM_PROMPT;
if (updatedChat.app?.supabaseProjectId) {
systemPrompt +=
"\n\n" +
SUPABASE_AVAILABLE_SYSTEM_PROMPT +
"\n\n" +
(await getSupabaseContext({
supabaseProjectId: updatedChat.app.supabaseProjectId,
}));
} else {
systemPrompt += "\n\n" + SUPABASE_NOT_AVAILABLE_SYSTEM_PROMPT;
}
const { textStream } = streamText({
maxTokens: 8_000,
temperature: 0,
model: modelClient,
system: SYSTEM_PROMPT,
system: systemPrompt,
messages: [
...messageHistory,
// Add the enhanced user prompt
@@ -190,6 +209,18 @@ export function registerChatStreamHandlers() {
try {
for await (const textPart of textStream) {
fullResponse += textPart;
if (
fullResponse.includes("$$SUPABASE_CLIENT_CODE$$") &&
updatedChat.app?.supabaseProjectId
) {
const supabaseClientCode = await getSupabaseClientCode({
projectId: updatedChat.app?.supabaseProjectId,
});
fullResponse = fullResponse.replace(
"$$SUPABASE_CLIENT_CODE$$",
supabaseClientCode
);
}
// Store the current partial response
partialResponses.set(req.chatId, fullResponse);

View File

@@ -13,6 +13,7 @@ import {
getDyadAddDependencyTags,
getDyadChatSummaryTag,
getDyadDeleteTags,
getDyadExecuteSqlTags,
getDyadRenameTags,
getDyadWriteTags,
processFullResponseActions,
@@ -76,7 +77,7 @@ const getProposalHandler = async (
const proposalWriteFiles = getDyadWriteTags(messageContent);
const proposalRenameFiles = getDyadRenameTags(messageContent);
const proposalDeleteFiles = getDyadDeleteTags(messageContent);
const proposalExecuteSqlQueries = getDyadExecuteSqlTags(messageContent);
const packagesAdded = getDyadAddDependencyTags(messageContent);
const filesChanged = [
@@ -108,6 +109,7 @@ const getProposalHandler = async (
securityRisks: [], // Keep empty
filesChanged,
packagesAdded,
sqlQueries: proposalExecuteSqlQueries,
};
logger.log(
"Generated code proposal. title=",

View File

@@ -5,7 +5,7 @@ import log from "electron-log";
import { db } from "../../db";
import { eq } from "drizzle-orm";
import { apps } from "../../db/schema";
import { getSupabaseClient } from "../utils/supabase_management_client";
import { getSupabaseClient } from "../../supabase_admin/supabase_management_client";
const logger = log.scope("supabase_handlers");

View File

@@ -9,6 +9,7 @@ import { getGithubUser } from "../handlers/github_handlers";
import { getGitAuthor } from "../utils/git_author";
import log from "electron-log";
import { executeAddDependency } from "./executeAddDependency";
import { executeSupabaseSql } from "../../supabase_admin/supabase_management_client";
const logger = log.scope("response_processor");
@@ -101,6 +102,31 @@ export function getDyadChatSummaryTag(fullResponse: string): string | null {
return null;
}
export function getDyadExecuteSqlTags(fullResponse: string): string[] {
const dyadExecuteSqlRegex =
/<dyad-execute-sql>([\s\S]*?)<\/dyad-execute-sql>/g;
let match;
const queries: string[] = [];
while ((match = dyadExecuteSqlRegex.exec(fullResponse)) !== null) {
let content = match[1].trim();
// Handle markdown code blocks if present
const contentLines = content.split("\n");
if (contentLines[0]?.startsWith("```")) {
contentLines.shift();
}
if (contentLines[contentLines.length - 1]?.startsWith("```")) {
contentLines.pop();
}
content = contentLines.join("\n");
queries.push(content);
}
return queries;
}
export async function processFullResponseActions(
fullResponse: string,
chatId: number,
@@ -134,6 +160,9 @@ export async function processFullResponseActions(
const dyadRenameTags = getDyadRenameTags(fullResponse);
const dyadDeletePaths = getDyadDeleteTags(fullResponse);
const dyadAddDependencyPackages = getDyadAddDependencyTags(fullResponse);
const dyadExecuteSqlQueries = chatWithApp.app.supabaseProjectId
? getDyadExecuteSqlTags(fullResponse)
: [];
const message = await db.query.messages.findFirst({
where: and(
@@ -148,6 +177,17 @@ export async function processFullResponseActions(
return {};
}
// Handle SQL execution tags
if (dyadExecuteSqlQueries.length > 0) {
for (const query of dyadExecuteSqlQueries) {
const result = await executeSupabaseSql({
supabaseProjectId: chatWithApp.app.supabaseProjectId!,
query,
});
}
logger.log(`Executed ${dyadExecuteSqlQueries.length} SQL queries`);
}
// TODO: Handle add dependency tags
if (dyadAddDependencyPackages.length > 0) {
await executeAddDependency({
@@ -249,7 +289,8 @@ export async function processFullResponseActions(
writtenFiles.length > 0 ||
renamedFiles.length > 0 ||
deletedFiles.length > 0 ||
dyadAddDependencyPackages.length > 0;
dyadAddDependencyPackages.length > 0 ||
dyadExecuteSqlQueries.length > 0;
if (hasChanges) {
// Stage all written files
for (const file of writtenFiles) {
@@ -272,6 +313,8 @@ export async function processFullResponseActions(
changes.push(
`added ${dyadAddDependencyPackages.join(", ")} package(s)`
);
if (dyadExecuteSqlQueries.length > 0)
changes.push(`executed ${dyadExecuteSqlQueries.length} SQL queries`);
// Use chat summary, if provided, or default for commit message
await git.commit({

View File

@@ -1,28 +0,0 @@
import { readSettings } from "../../main/settings";
import { SupabaseManagementAPI } from "supabase-management-js";
// Function to get the Supabase Management API client
export async function getSupabaseClient(): Promise<SupabaseManagementAPI> {
const settings = readSettings();
// Check if Supabase token exists in settings
const supabaseAccessToken = settings.supabase?.accessToken?.value;
if (!supabaseAccessToken) {
throw new Error(
"Supabase access token not found. Please authenticate first."
);
}
return new SupabaseManagementAPI({
accessToken: supabaseAccessToken,
});
}
export async function getSupabaseProjectName(
projectId: string
): Promise<string> {
const supabase = await getSupabaseClient();
const projects = await supabase.getProjects();
const project = projects?.find((p) => p.id === projectId);
return project?.name || `<project not found for: ${projectId}>`;
}