feat: complete debranding of MoreMinimore by removing Dyad dependencies and integrating custom features

This commit is contained in:
Kunthawat Greethong
2025-12-19 09:57:07 +07:00
parent 756b405423
commit 24c79defff
10 changed files with 685 additions and 266 deletions

1
src/custom/index.ts Normal file
View File

@@ -0,0 +1 @@
export const REMOVE_LIMIT_ENABLED = true;

View File

@@ -1,94 +0,0 @@
import fetch from "node-fetch"; // Electron main process might need node-fetch
import log from "electron-log";
import { createLoggedHandler } from "./safe_handle";
import { readSettings } from "../../main/settings"; // Assuming settings are read this way
import { UserBudgetInfo, UserBudgetInfoSchema } from "../ipc_types";
import { IS_TEST_BUILD } from "../utils/test_utils";
import { z } from "zod";
export const UserInfoResponseSchema = z.object({
usedCredits: z.number(),
totalCredits: z.number(),
budgetResetDate: z.string(), // ISO date string from API
userId: z.string(),
});
export type UserInfoResponse = z.infer<typeof UserInfoResponseSchema>;
const logger = log.scope("pro_handlers");
const handle = createLoggedHandler(logger);
export function registerProHandlers() {
// This method should try to avoid throwing errors because this is auxiliary
// information and isn't critical to using the app
handle("get-user-budget", async (): Promise<UserBudgetInfo | null> => {
if (IS_TEST_BUILD) {
// Return mock budget data for E2E tests instead of spamming the API
const resetDate = new Date();
resetDate.setDate(resetDate.getDate() + 30); // Reset in 30 days
return {
usedCredits: 100,
totalCredits: 1000,
budgetResetDate: resetDate,
redactedUserId: "<redacted-user-id-testing>",
};
}
logger.info("Attempting to fetch user budget information.");
const settings = readSettings();
const apiKey = settings.providerSettings?.auto?.apiKey?.value;
if (!apiKey) {
logger.error("LLM Gateway API key (Dyad Pro) is not configured.");
return null;
}
const url = "https://api.dyad.sh/v1/user/info";
const headers = {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
};
try {
// Use native fetch if available, otherwise node-fetch will be used via import
const response = await fetch(url, {
method: "GET",
headers: headers,
});
if (!response.ok) {
const errorBody = await response.text();
logger.error(
`Failed to fetch user budget. Status: ${response.status}. Body: ${errorBody}`,
);
return null;
}
const rawData = await response.json();
// Validate the API response structure
const data = UserInfoResponseSchema.parse(rawData);
// Turn user_abc1234 => "****1234"
// Preserve the last 4 characters so we can correlate bug reports
// with the user.
const redactedUserId =
data.userId.length > 8 ? "****" + data.userId.slice(-4) : "<redacted>";
logger.info("Successfully fetched user budget information.");
// Transform to UserBudgetInfo format
const userBudgetInfo = UserBudgetInfoSchema.parse({
usedCredits: data.usedCredits,
totalCredits: data.totalCredits,
budgetResetDate: new Date(data.budgetResetDate),
redactedUserId: redactedUserId,
});
return userBudgetInfo;
} catch (error: any) {
logger.error(`Error fetching user budget: ${error.message}`, error);
return null;
}
});
}

View File

@@ -1,5 +1,4 @@
import log from "electron-log";
import fetch from "node-fetch";
import { createLoggedHandler } from "./safe_handle";
import { DoesReleaseNoteExistParams } from "../ipc_types";
import { IS_TEST_BUILD } from "../utils/test_utils";
@@ -23,41 +22,9 @@ export function registerReleaseNoteHandlers() {
if (IS_TEST_BUILD) {
return { exists: false };
}
const releaseNoteUrl = `https://www.dyad.sh/docs/releases/${version}`;
logger.debug(`Checking for release note at: ${releaseNoteUrl}`);
try {
const response = await fetch(releaseNoteUrl, { method: "HEAD" }); // Use HEAD to check existence without downloading content
if (response.ok) {
logger.debug(
`Release note found for version ${version} at ${releaseNoteUrl}`,
);
return { exists: true, url: releaseNoteUrl };
} else if (response.status === 404) {
logger.debug(
`Release note not found for version ${version} at ${releaseNoteUrl}`,
);
return { exists: false };
} else {
// Log other non-404 errors but still treat as "not found" for the client,
// as the primary goal is to check existence.
logger.warn(
`Unexpected status code ${response.status} when checking for release note: ${releaseNoteUrl}`,
);
return { exists: false };
}
} catch (error) {
logger.error(
`Error fetching release note for version ${version} at ${releaseNoteUrl}:`,
error,
);
// In case of network errors, etc., assume it doesn't exist or is inaccessible.
// Throwing an error here would propagate to the client and might be too disruptive
// if the check is just for UI purposes (e.g., showing a link).
// Consider if specific errors should be thrown based on requirements.
return { exists: false };
}
// Release notes disabled - removed Dyad API dependency
logger.debug(`Release notes check disabled for version ${version}`);
return { exists: false };
},
);

View File

@@ -20,7 +20,6 @@ import { registerLanguageModelHandlers } from "./handlers/language_model_handler
import { registerReleaseNoteHandlers } from "./handlers/release_note_handlers";
import { registerImportHandlers } from "./handlers/import_handlers";
import { registerSessionHandlers } from "./handlers/session_handlers";
import { registerProHandlers } from "./handlers/pro_handlers";
import { registerContextPathsHandlers } from "./handlers/context_paths_handlers";
import { registerAppUpgradeHandlers } from "./handlers/app_upgrade_handlers";
import { registerCapacitorHandlers } from "./handlers/capacitor_handlers";
@@ -60,7 +59,6 @@ export function registerIpcHandlers() {
registerReleaseNoteHandlers();
registerImportHandlers();
registerSessionHandlers();
registerProHandlers();
registerContextPathsHandlers();
registerAppUpgradeHandlers();
registerCapacitorHandlers();

View File

@@ -19,14 +19,14 @@ import log from "electron-log";
import { FREE_OPENROUTER_MODEL_NAMES } from "../shared/language_model_constants";
import { getLanguageModelProviders } from "../shared/language_model_helpers";
import { LanguageModelProvider } from "../ipc_types";
import { createDyadEngine } from "./llm_engine_provider";
// import { createDyadEngine } from "./llm_engine_provider"; // Removed - Dyad Engine dependency
import { LM_STUDIO_BASE_URL } from "./lm_studio_utils";
import { createOllamaProvider } from "./ollama_provider";
import { getOllamaApiUrl } from "../handlers/local_model_ollama_handler";
import { createFallback } from "./fallback_ai_model";
const dyadEngineUrl = process.env.DYAD_ENGINE_URL;
// const dyadEngineUrl = process.env.DYAD_ENGINE_URL; // Removed - Dyad Engine dependency
const AUTO_MODELS = [
{
@@ -73,56 +73,13 @@ export async function getModelClient(
throw new Error(`Configuration not found for provider: ${model.provider}`);
}
// Handle Dyad Pro override
// Dyad Pro functionality removed - eliminated Dyad Engine dependency
// All models now use direct provider connections
if (dyadApiKey && settings.enableDyadPro) {
// Check if the selected provider supports Dyad Pro (has a gateway prefix) OR
// we're using local engine.
// IMPORTANT: some providers like OpenAI have an empty string gateway prefix,
// so we do a nullish and not a truthy check here.
if (providerConfig.gatewayPrefix != null || dyadEngineUrl) {
const enableSmartFilesContext = settings.enableProSmartFilesContextMode;
const provider = createDyadEngine({
apiKey: dyadApiKey,
baseURL: dyadEngineUrl ?? "https://engine.dyad.sh/v1",
originalProviderId: model.provider,
dyadOptions: {
enableLazyEdits:
settings.selectedChatMode === "ask"
? false
: settings.enableProLazyEditsMode &&
settings.proLazyEditsMode !== "v2",
enableSmartFilesContext,
enableWebSearch: settings.enableProWebSearch,
},
settings,
});
logger.info(
`\x1b[1;97;44m Using Dyad Pro API key for model: ${model.name} \x1b[0m`,
);
logger.info(
`\x1b[1;30;42m Using Dyad Pro engine: ${dyadEngineUrl ?? "<prod>"} \x1b[0m`,
);
// Do not use free variant (for openrouter).
const modelName = model.name.split(":free")[0];
const autoModelClient = {
model: provider(`${providerConfig.gatewayPrefix || ""}${modelName}`),
builtinProviderId: model.provider,
};
return {
modelClient: autoModelClient,
isEngineEnabled: true,
isSmartContextEnabled: enableSmartFilesContext,
};
} else {
logger.warn(
`Dyad Pro enabled, but provider ${model.provider} does not have a gateway prefix defined. Falling back to direct provider connection.`,
);
// Fall through to regular provider logic if gateway prefix is missing
}
logger.warn(
`Dyad Pro was enabled but has been disabled to remove Dyad API dependency. Falling back to direct provider connection.`,
);
// Fall through to regular provider logic
}
// Handle 'auto' provider by trying each model in AUTO_MODELS until one works
if (model.provider === "auto") {

View File

@@ -1,71 +1,14 @@
import {
type Template,
type ApiTemplate,
localTemplatesData,
} from "../../shared/templates";
import log from "electron-log";
const logger = log.scope("template_utils");
// In-memory cache for API templates
let apiTemplatesCache: Template[] | null = null;
let apiTemplatesFetchPromise: Promise<Template[]> | null = null;
// Convert API template to our Template interface
function convertApiTemplate(apiTemplate: ApiTemplate): Template {
return {
id: `${apiTemplate.githubOrg}/${apiTemplate.githubRepo}`,
title: apiTemplate.title,
description: apiTemplate.description,
imageUrl: apiTemplate.imageUrl,
githubUrl: `https://github.com/${apiTemplate.githubOrg}/${apiTemplate.githubRepo}`,
isOfficial: false,
};
}
// Fetch templates from API with caching
export async function fetchApiTemplates(): Promise<Template[]> {
// Return cached data if available
if (apiTemplatesCache) {
return apiTemplatesCache;
}
// Return existing promise if fetch is already in progress
if (apiTemplatesFetchPromise) {
return apiTemplatesFetchPromise;
}
// Start new fetch
apiTemplatesFetchPromise = (async (): Promise<Template[]> => {
try {
const response = await fetch("https://api.dyad.sh/v1/templates");
if (!response.ok) {
throw new Error(
`Failed to fetch templates: ${response.status} ${response.statusText}`,
);
}
const apiTemplates: ApiTemplate[] = await response.json();
const convertedTemplates = apiTemplates.map(convertApiTemplate);
// Cache the result
apiTemplatesCache = convertedTemplates;
return convertedTemplates;
} catch (error) {
logger.error("Failed to fetch API templates:", error);
// Reset the promise so we can retry later
apiTemplatesFetchPromise = null;
return []; // Return empty array on error
}
})();
return apiTemplatesFetchPromise;
}
// Get all templates (local + API)
// Get all templates (local only - API templates removed)
export async function getAllTemplates(): Promise<Template[]> {
const apiTemplates = await fetchApiTemplates();
return [...localTemplatesData, ...apiTemplates];
return [...localTemplatesData];
}
export async function getTemplateOrThrow(

View File

@@ -4,7 +4,7 @@ import { registerIpcHandlers } from "./ipc/ipc_host";
import dotenv from "dotenv";
// @ts-ignore
import started from "electron-squirrel-startup";
import { updateElectronApp, UpdateSourceType } from "update-electron-app";
// import { updateElectronApp, UpdateSourceType } from "update-electron-app"; // Removed - Dyad API dependency
import log from "electron-log";
import {
getSettingsFilePath,
@@ -107,23 +107,9 @@ export async function onReady() {
await onFirstRunMaybe(settings);
createWindow();
logger.info("Auto-update enabled=", settings.enableAutoUpdate);
if (settings.enableAutoUpdate) {
// Technically we could just pass the releaseChannel directly to the host,
// but this is more explicit and falls back to stable if there's an unknown
// release channel.
const postfix = settings.releaseChannel === "beta" ? "beta" : "stable";
const host = `https://api.dyad.sh/v1/update/${postfix}`;
logger.info("Auto-update release channel=", postfix);
updateElectronApp({
logger,
updateSource: {
type: UpdateSourceType.ElectronPublicUpdateService,
repo: "dyad-sh/dyad",
host,
},
}); // additional configuration options available
}
logger.info("Auto-update disabled - removed Dyad API dependency");
// Auto-update functionality removed to eliminate Dyad API dependency
// Users can manually update by downloading new releases from GitHub
}
export async function onFirstRunMaybe(settings: UserSettings) {