Add recipe download and copy yaml (#6201)

This commit is contained in:
Zane
2026-01-05 09:55:50 -08:00
committed by GitHub
parent 901cac9441
commit edcd011543
12 changed files with 300 additions and 19 deletions
+3
View File
@@ -392,6 +392,7 @@ derive_utoipa!(Icon as IconSchema);
super::routes::recipe::set_recipe_slash_command,
super::routes::recipe::save_recipe,
super::routes::recipe::parse_recipe,
super::routes::recipe::recipe_to_yaml,
super::routes::setup::start_openrouter_setup,
super::routes::setup::start_tetrate_setup,
super::routes::tunnel::start_tunnel,
@@ -508,6 +509,8 @@ derive_utoipa!(Icon as IconSchema);
super::routes::errors::ErrorResponse,
super::routes::recipe::ParseRecipeRequest,
super::routes::recipe::ParseRecipeResponse,
super::routes::recipe::RecipeToYamlRequest,
super::routes::recipe::RecipeToYamlResponse,
goose::recipe::Recipe,
goose::recipe::Author,
goose::recipe::Settings,
+32
View File
@@ -137,6 +137,16 @@ pub struct SetSlashCommandRequest {
slash_command: Option<String>,
}
#[derive(Debug, Deserialize, ToSchema)]
pub struct RecipeToYamlRequest {
recipe: Recipe,
}
#[derive(Debug, Serialize, ToSchema)]
pub struct RecipeToYamlResponse {
yaml: String,
}
#[utoipa::path(
post,
path = "/recipes/create",
@@ -520,6 +530,27 @@ async fn parse_recipe(
Ok(Json(ParseRecipeResponse { recipe }))
}
#[utoipa::path(
post,
path = "/recipes/to-yaml",
request_body = RecipeToYamlRequest,
responses(
(status = 200, description = "Recipe converted to YAML successfully", body = RecipeToYamlResponse),
(status = 400, description = "Bad request - Failed to convert recipe to YAML", body = ErrorResponse),
),
tag = "Recipe Management"
)]
async fn recipe_to_yaml(
Json(request): Json<RecipeToYamlRequest>,
) -> Result<Json<RecipeToYamlResponse>, ErrorResponse> {
let yaml = request.recipe.to_yaml().map_err(|e| ErrorResponse {
message: format!("Failed to convert recipe to YAML: {}", e),
status: StatusCode::BAD_REQUEST,
})?;
Ok(Json(RecipeToYamlResponse { yaml }))
}
pub fn routes(state: Arc<AppState>) -> Router {
Router::new()
.route("/recipes/create", post(create_recipe))
@@ -532,6 +563,7 @@ pub fn routes(state: Arc<AppState>) -> Router {
.route("/recipes/slash-command", post(set_recipe_slash_command))
.route("/recipes/save", post(save_recipe))
.route("/recipes/parse", post(parse_recipe))
.route("/recipes/to-yaml", post(recipe_to_yaml))
.with_state(state)
}
+62
View File
@@ -1558,6 +1558,46 @@
}
}
},
"/recipes/to-yaml": {
"post": {
"tags": [
"Recipe Management"
],
"operationId": "recipe_to_yaml",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RecipeToYamlRequest"
}
}
},
"required": true
},
"responses": {
"200": {
"description": "Recipe converted to YAML successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RecipeToYamlResponse"
}
}
}
},
"400": {
"description": "Bad request - Failed to convert recipe to YAML",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/reply": {
"post": {
"tags": [
@@ -4762,6 +4802,28 @@
"user_prompt"
]
},
"RecipeToYamlRequest": {
"type": "object",
"required": [
"recipe"
],
"properties": {
"recipe": {
"$ref": "#/components/schemas/Recipe"
}
}
},
"RecipeToYamlResponse": {
"type": "object",
"required": [
"yaml"
],
"properties": {
"yaml": {
"type": "string"
}
}
},
"RedactedThinkingContent": {
"type": "object",
"required": [
+10 -1
View File
@@ -2,7 +2,7 @@
import type { Client, Options as Options2, TDataShape } from './client';
import { client } from './client.gen';
import type { AddExtensionData, AddExtensionErrors, AddExtensionResponses, AgentAddExtensionData, AgentAddExtensionErrors, AgentAddExtensionResponses, AgentRemoveExtensionData, AgentRemoveExtensionErrors, AgentRemoveExtensionResponses, BackupConfigData, BackupConfigErrors, BackupConfigResponses, CallToolData, CallToolErrors, CallToolResponses, CheckProviderData, ConfirmToolActionData, ConfirmToolActionErrors, ConfirmToolActionResponses, CreateCustomProviderData, CreateCustomProviderErrors, CreateCustomProviderResponses, CreateRecipeData, CreateRecipeErrors, CreateRecipeResponses, CreateScheduleData, CreateScheduleErrors, CreateScheduleResponses, DecodeRecipeData, DecodeRecipeErrors, DecodeRecipeResponses, DeleteRecipeData, DeleteRecipeErrors, DeleteRecipeResponses, DeleteScheduleData, DeleteScheduleErrors, DeleteScheduleResponses, DeleteSessionData, DeleteSessionErrors, DeleteSessionResponses, DetectProviderData, DetectProviderErrors, DetectProviderResponses, DiagnosticsData, DiagnosticsErrors, DiagnosticsResponses, EditMessageData, EditMessageErrors, EditMessageResponses, EncodeRecipeData, EncodeRecipeErrors, EncodeRecipeResponses, ExportSessionData, ExportSessionErrors, ExportSessionResponses, GetCustomProviderData, GetCustomProviderErrors, GetCustomProviderResponses, GetExtensionsData, GetExtensionsErrors, GetExtensionsResponses, GetPricingData, GetPricingResponses, GetProviderModelsData, GetProviderModelsErrors, GetProviderModelsResponses, GetSessionData, GetSessionErrors, GetSessionInsightsData, GetSessionInsightsErrors, GetSessionInsightsResponses, GetSessionResponses, GetSlashCommandsData, GetSlashCommandsResponses, GetToolsData, GetToolsErrors, GetToolsResponses, GetTunnelStatusData, GetTunnelStatusResponses, ImportSessionData, ImportSessionErrors, ImportSessionResponses, InitConfigData, InitConfigErrors, InitConfigResponses, InspectRunningJobData, InspectRunningJobErrors, InspectRunningJobResponses, KillRunningJobData, KillRunningJobResponses, ListRecipesData, ListRecipesErrors, ListRecipesResponses, ListSchedulesData, ListSchedulesErrors, ListSchedulesResponses, ListSessionsData, ListSessionsErrors, ListSessionsResponses, McpUiProxyData, McpUiProxyErrors, McpUiProxyResponses, ParseRecipeData, ParseRecipeErrors, ParseRecipeResponses, PauseScheduleData, PauseScheduleErrors, PauseScheduleResponses, ProvidersData, ProvidersResponses, ReadAllConfigData, ReadAllConfigResponses, ReadConfigData, ReadConfigErrors, ReadConfigResponses, ReadResourceData, ReadResourceErrors, ReadResourceResponses, RecoverConfigData, RecoverConfigErrors, RecoverConfigResponses, RemoveConfigData, RemoveConfigErrors, RemoveConfigResponses, RemoveCustomProviderData, RemoveCustomProviderErrors, RemoveCustomProviderResponses, RemoveExtensionData, RemoveExtensionErrors, RemoveExtensionResponses, ReplyData, ReplyErrors, ReplyResponses, ResumeAgentData, ResumeAgentErrors, ResumeAgentResponses, RunNowHandlerData, RunNowHandlerErrors, RunNowHandlerResponses, SaveRecipeData, SaveRecipeErrors, SaveRecipeResponses, ScanRecipeData, ScanRecipeResponses, ScheduleRecipeData, ScheduleRecipeErrors, ScheduleRecipeResponses, SendTelemetryEventData, SendTelemetryEventResponses, SessionsHandlerData, SessionsHandlerErrors, SessionsHandlerResponses, SetConfigProviderData, SetRecipeSlashCommandData, SetRecipeSlashCommandErrors, SetRecipeSlashCommandResponses, StartAgentData, StartAgentErrors, StartAgentResponses, StartOpenrouterSetupData, StartOpenrouterSetupResponses, StartTetrateSetupData, StartTetrateSetupResponses, StartTunnelData, StartTunnelErrors, StartTunnelResponses, StatusData, StatusResponses, StopTunnelData, StopTunnelErrors, StopTunnelResponses, UnpauseScheduleData, UnpauseScheduleErrors, UnpauseScheduleResponses, UpdateAgentProviderData, UpdateAgentProviderErrors, UpdateAgentProviderResponses, UpdateCustomProviderData, UpdateCustomProviderErrors, UpdateCustomProviderResponses, UpdateFromSessionData, UpdateFromSessionErrors, UpdateFromSessionResponses, UpdateScheduleData, UpdateScheduleErrors, UpdateScheduleResponses, UpdateSessionNameData, UpdateSessionNameErrors, UpdateSessionNameResponses, UpdateSessionUserRecipeValuesData, UpdateSessionUserRecipeValuesErrors, UpdateSessionUserRecipeValuesResponses, UpsertConfigData, UpsertConfigErrors, UpsertConfigResponses, UpsertPermissionsData, UpsertPermissionsErrors, UpsertPermissionsResponses, ValidateConfigData, ValidateConfigErrors, ValidateConfigResponses } from './types.gen';
import type { AddExtensionData, AddExtensionErrors, AddExtensionResponses, AgentAddExtensionData, AgentAddExtensionErrors, AgentAddExtensionResponses, AgentRemoveExtensionData, AgentRemoveExtensionErrors, AgentRemoveExtensionResponses, BackupConfigData, BackupConfigErrors, BackupConfigResponses, CallToolData, CallToolErrors, CallToolResponses, CheckProviderData, ConfirmToolActionData, ConfirmToolActionErrors, ConfirmToolActionResponses, CreateCustomProviderData, CreateCustomProviderErrors, CreateCustomProviderResponses, CreateRecipeData, CreateRecipeErrors, CreateRecipeResponses, CreateScheduleData, CreateScheduleErrors, CreateScheduleResponses, DecodeRecipeData, DecodeRecipeErrors, DecodeRecipeResponses, DeleteRecipeData, DeleteRecipeErrors, DeleteRecipeResponses, DeleteScheduleData, DeleteScheduleErrors, DeleteScheduleResponses, DeleteSessionData, DeleteSessionErrors, DeleteSessionResponses, DetectProviderData, DetectProviderErrors, DetectProviderResponses, DiagnosticsData, DiagnosticsErrors, DiagnosticsResponses, EditMessageData, EditMessageErrors, EditMessageResponses, EncodeRecipeData, EncodeRecipeErrors, EncodeRecipeResponses, ExportSessionData, ExportSessionErrors, ExportSessionResponses, GetCustomProviderData, GetCustomProviderErrors, GetCustomProviderResponses, GetExtensionsData, GetExtensionsErrors, GetExtensionsResponses, GetPricingData, GetPricingResponses, GetProviderModelsData, GetProviderModelsErrors, GetProviderModelsResponses, GetSessionData, GetSessionErrors, GetSessionInsightsData, GetSessionInsightsErrors, GetSessionInsightsResponses, GetSessionResponses, GetSlashCommandsData, GetSlashCommandsResponses, GetToolsData, GetToolsErrors, GetToolsResponses, GetTunnelStatusData, GetTunnelStatusResponses, ImportSessionData, ImportSessionErrors, ImportSessionResponses, InitConfigData, InitConfigErrors, InitConfigResponses, InspectRunningJobData, InspectRunningJobErrors, InspectRunningJobResponses, KillRunningJobData, KillRunningJobResponses, ListRecipesData, ListRecipesErrors, ListRecipesResponses, ListSchedulesData, ListSchedulesErrors, ListSchedulesResponses, ListSessionsData, ListSessionsErrors, ListSessionsResponses, McpUiProxyData, McpUiProxyErrors, McpUiProxyResponses, ParseRecipeData, ParseRecipeErrors, ParseRecipeResponses, PauseScheduleData, PauseScheduleErrors, PauseScheduleResponses, ProvidersData, ProvidersResponses, ReadAllConfigData, ReadAllConfigResponses, ReadConfigData, ReadConfigErrors, ReadConfigResponses, ReadResourceData, ReadResourceErrors, ReadResourceResponses, RecipeToYamlData, RecipeToYamlErrors, RecipeToYamlResponses, RecoverConfigData, RecoverConfigErrors, RecoverConfigResponses, RemoveConfigData, RemoveConfigErrors, RemoveConfigResponses, RemoveCustomProviderData, RemoveCustomProviderErrors, RemoveCustomProviderResponses, RemoveExtensionData, RemoveExtensionErrors, RemoveExtensionResponses, ReplyData, ReplyErrors, ReplyResponses, ResumeAgentData, ResumeAgentErrors, ResumeAgentResponses, RunNowHandlerData, RunNowHandlerErrors, RunNowHandlerResponses, SaveRecipeData, SaveRecipeErrors, SaveRecipeResponses, ScanRecipeData, ScanRecipeResponses, ScheduleRecipeData, ScheduleRecipeErrors, ScheduleRecipeResponses, SendTelemetryEventData, SendTelemetryEventResponses, SessionsHandlerData, SessionsHandlerErrors, SessionsHandlerResponses, SetConfigProviderData, SetRecipeSlashCommandData, SetRecipeSlashCommandErrors, SetRecipeSlashCommandResponses, StartAgentData, StartAgentErrors, StartAgentResponses, StartOpenrouterSetupData, StartOpenrouterSetupResponses, StartTetrateSetupData, StartTetrateSetupResponses, StartTunnelData, StartTunnelErrors, StartTunnelResponses, StatusData, StatusResponses, StopTunnelData, StopTunnelErrors, StopTunnelResponses, UnpauseScheduleData, UnpauseScheduleErrors, UnpauseScheduleResponses, UpdateAgentProviderData, UpdateAgentProviderErrors, UpdateAgentProviderResponses, UpdateCustomProviderData, UpdateCustomProviderErrors, UpdateCustomProviderResponses, UpdateFromSessionData, UpdateFromSessionErrors, UpdateFromSessionResponses, UpdateScheduleData, UpdateScheduleErrors, UpdateScheduleResponses, UpdateSessionNameData, UpdateSessionNameErrors, UpdateSessionNameResponses, UpdateSessionUserRecipeValuesData, UpdateSessionUserRecipeValuesErrors, UpdateSessionUserRecipeValuesResponses, UpsertConfigData, UpsertConfigErrors, UpsertConfigResponses, UpsertPermissionsData, UpsertPermissionsErrors, UpsertPermissionsResponses, ValidateConfigData, ValidateConfigErrors, ValidateConfigResponses } from './types.gen';
export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean> = Options2<TData, ThrowOnError> & {
/**
@@ -315,6 +315,15 @@ export const setRecipeSlashCommand = <ThrowOnError extends boolean = false>(opti
}
});
export const recipeToYaml = <ThrowOnError extends boolean = false>(options: Options<RecipeToYamlData, ThrowOnError>) => (options.client ?? client).post<RecipeToYamlResponses, RecipeToYamlErrors, ThrowOnError>({
url: '/recipes/to-yaml',
...options,
headers: {
'Content-Type': 'application/json',
...options.headers
}
});
export const reply = <ThrowOnError extends boolean = false>(options: Options<ReplyData, ThrowOnError>) => (options.client ?? client).sse.post<ReplyResponses, ReplyErrors, ThrowOnError>({
url: '/reply',
...options,
+33
View File
@@ -732,6 +732,14 @@ export type RecipeParameterInputType = 'string' | 'number' | 'boolean' | 'date'
export type RecipeParameterRequirement = 'required' | 'optional' | 'user_prompt';
export type RecipeToYamlRequest = {
recipe: Recipe;
};
export type RecipeToYamlResponse = {
yaml: string;
};
export type RedactedThinkingContent = {
data: string;
};
@@ -2325,6 +2333,31 @@ export type SetRecipeSlashCommandResponses = {
200: unknown;
};
export type RecipeToYamlData = {
body: RecipeToYamlRequest;
path?: never;
query?: never;
url: '/recipes/to-yaml';
};
export type RecipeToYamlErrors = {
/**
* Bad request - Failed to convert recipe to YAML
*/
400: ErrorResponse;
};
export type RecipeToYamlError = RecipeToYamlErrors[keyof RecipeToYamlErrors];
export type RecipeToYamlResponses = {
/**
* Recipe converted to YAML successfully
*/
200: RecipeToYamlResponse;
};
export type RecipeToYamlResponse2 = RecipeToYamlResponses[keyof RecipeToYamlResponses];
export type ReplyData = {
body: ChatRequest;
path?: never;
+5 -1
View File
@@ -12,7 +12,11 @@ export type McpMethodParams = {
export type McpMethodResponse = {
'ui/open-link': { status: string; message: string };
'ui/message': { status: string; message: string };
'tools/call': { content: unknown[]; isError: boolean; structuredContent?: Record<string, unknown> };
'tools/call': {
content: unknown[];
isError: boolean;
structuredContent?: Record<string, unknown>;
};
'resources/read': { contents: unknown[] };
'notifications/message': Record<string, never>;
ping: Record<string, never>;
@@ -275,7 +275,7 @@ const getToolName = (toolCallName: string): string => {
if (lastIndex === -1) return toolCallName;
return toolCallName.substring(lastIndex + 2);
}
};
// Helper function to extract extension name for tooltip
const getExtensionTooltip = (toolCallName: string): string | null => {
+115 -14
View File
@@ -11,13 +11,16 @@ import {
Clock,
Terminal,
ExternalLink,
Share2,
Copy,
Download,
} from 'lucide-react';
import { ScrollArea } from '../ui/scroll-area';
import { Card } from '../ui/card';
import { Button } from '../ui/button';
import { Skeleton } from '../ui/skeleton';
import { MainPanelLayout } from '../Layout/MainPanelLayout';
import { toastSuccess } from '../../toasts';
import { toastSuccess, toastError } from '../../toasts';
import { useEscapeKey } from '../../hooks/useEscapeKey';
import {
deleteRecipe,
@@ -25,6 +28,7 @@ import {
startAgent,
scheduleRecipe,
setRecipeSlashCommand,
recipeToYaml,
} from '../../api';
import ImportRecipeForm, { ImportRecipeButton } from './ImportRecipeForm';
import CreateEditRecipeModal from './CreateEditRecipeModal';
@@ -38,10 +42,19 @@ import {
trackRecipeDeleted,
trackRecipeStarted,
trackRecipeDeeplinkCopied,
trackRecipeYamlCopied,
trackRecipeExportedToFile,
trackRecipeScheduled,
trackRecipeSlashCommandSet,
getErrorType,
} from '../../utils/analytics';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
DropdownMenuSeparator,
} from '../ui/dropdown-menu';
export default function RecipesView() {
const setView = useNavigation();
@@ -217,13 +230,85 @@ export default function RecipesView() {
} catch (error) {
console.error('Failed to copy deeplink:', error);
trackRecipeDeeplinkCopied(false, getErrorType(error));
toastSuccess({
toastError({
title: 'Copy failed',
msg: 'Failed to copy deeplink to clipboard',
});
}
};
const handleCopyYaml = async (recipeManifest: RecipeManifest) => {
try {
const response = await recipeToYaml({
body: { recipe: recipeManifest.recipe },
throwOnError: true,
});
if (!response.data?.yaml) {
throw new Error('No YAML data returned from API');
}
await navigator.clipboard.writeText(response.data.yaml);
trackRecipeYamlCopied(true);
toastSuccess({
title: 'YAML copied',
msg: 'Recipe YAML has been copied to clipboard',
});
} catch (error) {
console.error('Failed to copy YAML:', error);
trackRecipeYamlCopied(false, getErrorType(error));
toastError({
title: 'Copy failed',
msg: 'Failed to copy recipe YAML to clipboard',
});
}
};
const handleExportFile = async (recipeManifest: RecipeManifest) => {
try {
const response = await recipeToYaml({
body: { recipe: recipeManifest.recipe },
throwOnError: true,
});
if (!response.data?.yaml) {
throw new Error('No YAML data returned from API');
}
const sanitizedTitle = (recipeManifest.recipe.title || 'recipe')
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-|-$/g, '');
const filename = `${sanitizedTitle}.yaml`;
const result = await window.electron.showSaveDialog({
title: 'Export Recipe',
defaultPath: filename,
filters: [
{ name: 'YAML Files', extensions: ['yaml', 'yml'] },
{ name: 'All Files', extensions: ['*'] },
],
});
if (!result.canceled && result.filePath) {
await window.electron.writeFile(result.filePath, response.data.yaml);
trackRecipeExportedToFile(true);
toastSuccess({
title: 'Recipe exported',
msg: `Recipe saved to ${result.filePath}`,
});
}
} catch (error) {
console.error('Failed to export recipe:', error);
trackRecipeExportedToFile(false, getErrorType(error));
toastError({
title: 'Export failed',
msg: 'Failed to export recipe to file',
});
}
};
const handleOpenScheduleDialog = (recipeManifest: RecipeManifest) => {
setScheduleRecipeManifest(recipeManifest);
setScheduleCron(recipeManifest.schedule_cron || '0 0 14 * * *');
@@ -450,18 +535,34 @@ export default function RecipesView() {
>
<Edit className="w-4 h-4" />
</Button>
<Button
onClick={(e) => {
e.stopPropagation();
handleCopyDeeplink(recipeManifestResponse);
}}
variant="outline"
size="sm"
className="h-8 w-8 p-0"
title="Copy deeplink"
>
<Link className="w-4 h-4" />
</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
onClick={(e) => e.stopPropagation()}
variant="outline"
size="sm"
className="h-8 w-8 p-0"
title="Share recipe"
>
<Share2 className="w-4 h-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" onClick={(e) => e.stopPropagation()}>
<DropdownMenuItem onClick={() => handleCopyDeeplink(recipeManifestResponse)}>
<Link className="w-4 h-4" />
Copy Deeplink
</DropdownMenuItem>
<DropdownMenuItem onClick={() => handleCopyYaml(recipeManifestResponse)}>
<Copy className="w-4 h-4" />
Copy YAML
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={() => handleExportFile(recipeManifestResponse)}>
<Download className="w-4 h-4" />
Export to File
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<Button
onClick={(e) => {
e.stopPropagation();
@@ -38,7 +38,8 @@ export default function ExtensionsSection({
onModalClose,
searchTerm = '',
}: ExtensionSectionProps) {
const { getExtensions, addExtension, removeExtension, extensionsList, extensionWarnings } = useConfig();
const { getExtensions, addExtension, removeExtension, extensionsList, extensionWarnings } =
useConfig();
const [selectedExtension, setSelectedExtension] = useState<FixedExtensionEntry | null>(null);
const [isModalOpen, setIsModalOpen] = useState(false);
const [isAddModalOpen, setIsAddModalOpen] = useState(false);
+4 -1
View File
@@ -1766,11 +1766,14 @@ ipcMain.handle('list-files', async (_event, dirPath, extension) => {
}
});
// Handle message box dialogs
ipcMain.handle('show-message-box', async (_event, options) => {
return dialog.showMessageBox(options);
});
ipcMain.handle('show-save-dialog', async (_event, options) => {
return dialog.showSaveDialog(options);
});
ipcMain.handle('get-allowed-extensions', async () => {
return await getAllowList();
});
+17
View File
@@ -20,6 +20,21 @@ interface MessageBoxResponse {
checkboxChecked?: boolean;
}
interface SaveDialogOptions {
title?: string;
defaultPath?: string;
buttonLabel?: string;
filters?: Array<{ name: string; extensions: string[] }>;
message?: string;
nameFieldLabel?: string;
showsTagField?: boolean;
}
interface SaveDialogResponse {
canceled: boolean;
filePath?: string;
}
interface FileResponse {
file: string;
filePath: string;
@@ -58,6 +73,7 @@ type ElectronAPI = {
logInfo: (txt: string) => void;
showNotification: (data: NotificationData) => void;
showMessageBox: (options: MessageBoxOptions) => Promise<MessageBoxResponse>;
showSaveDialog: (options: SaveDialogOptions) => Promise<SaveDialogResponse>;
openInChrome: (url: string) => void;
fetchMetadata: (url: string) => Promise<string>;
reloadApp: () => void;
@@ -158,6 +174,7 @@ const electronAPI: ElectronAPI = {
logInfo: (txt: string) => ipcRenderer.send('logInfo', txt),
showNotification: (data: NotificationData) => ipcRenderer.send('notify', data),
showMessageBox: (options: MessageBoxOptions) => ipcRenderer.invoke('show-message-box', options),
showSaveDialog: (options: SaveDialogOptions) => ipcRenderer.invoke('show-save-dialog', options),
openInChrome: (url: string) => ipcRenderer.send('open-in-chrome', url),
fetchMetadata: (url: string) => ipcRenderer.invoke('fetch-metadata', url),
reloadApp: () => ipcRenderer.send('reload-app'),
+16
View File
@@ -115,6 +115,8 @@ export type AnalyticsEvent =
properties: { success: boolean; error_details?: string; in_new_window?: boolean };
}
| { name: 'recipe_deeplink_copied'; properties: { success: boolean; error_details?: string } }
| { name: 'recipe_yaml_copied'; properties: { success: boolean; error_details?: string } }
| { name: 'recipe_exported_to_file'; properties: { success: boolean; error_details?: string } }
| {
name: 'recipe_scheduled';
properties: { success: boolean; error_details?: string; action: 'add' | 'edit' | 'remove' };
@@ -536,6 +538,20 @@ export function trackRecipeDeeplinkCopied(success: boolean, errorDetails?: strin
});
}
export function trackRecipeYamlCopied(success: boolean, errorDetails?: string): void {
trackEvent({
name: 'recipe_yaml_copied',
properties: { success, error_details: errorDetails },
});
}
export function trackRecipeExportedToFile(success: boolean, errorDetails?: string): void {
trackEvent({
name: 'recipe_exported_to_file',
properties: { success, error_details: errorDetails },
});
}
export function trackRecipeScheduled(
success: boolean,
action: 'add' | 'edit' | 'remove',