Mercurial
view infinite_canvas/main.c @ 280:49e9e591c9bb
Add persistent dictation, prewarmed WebRTC speech input, Copilot SDK routing, animated conversation lifecycle controls, parking, and architecture coverage.
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Tue, 18 Aug 2026 19:14:53 -0700 |
| parents | 8d560f50ed4c |
| children | c57149ad216e |
line wrap: on
line source
#include "infinite_canvas/agent_service.h" #include "infinite_canvas/canvas.h" #include "infinite_canvas/web_surface.h" #if defined(INFINITE_CANVAS_DEV_UI) #include "infinite_canvas/dev_ui.h" #endif #if defined(PLATFORM_WEB) #include <emscripten/emscripten.h> #endif #include <math.h> #include <stdio.h> #include <stdlib.h> #include <string.h> typedef struct { Dowa_Arena *p_arena; Canvas_Camera camera; Canvas_Scene scene; Canvas_Web_Surface web_surface; Canvas_Agent_Service agent_service; Font font; Canvas_Theme theme; boolean owns_font; const char *p_screenshot_path; uint32 frame_count; uint32 screenshot_frame; uint32 pending_conversation_id; uint32 dictation_entity_id; boolean agent_initialized; boolean dictation_overlay_visible; boolean dictation_listening; boolean dictation_requested_active; boolean dictation_send_pending; char dictation_status[128]; char dictation_partial[CANVAS_ENTITY_TEXT_CAPACITY]; char dictation_transcript[CANVAS_ENTITY_TEXT_CAPACITY]; char pending_prompt[CANVAS_ENTITY_TEXT_CAPACITY]; char agent_context[CANVAS_CONTEXT_MAX_LENGTH]; #if defined(INFINITE_CANVAS_DEV_UI) Canvas_Dev_UI dev_ui; #endif } Canvas_App; static Canvas_App *g_app = NULL; static const char *Canvas_App_JSON_String( const char *p_json, const char *p_key, char *p_output, size_t output_capacity) { char needle[128]; snprintf(needle, sizeof(needle), "\"%s\"", p_key); const char *p_cursor = strstr(p_json, needle); if (!p_cursor) return NULL; p_cursor = strchr(p_cursor + strlen(needle), ':'); if (!p_cursor) return NULL; while (*++p_cursor == ' ') {} if (*p_cursor != '"') return NULL; p_cursor++; size_t output = 0; while (*p_cursor && *p_cursor != '"' && output + 1 < output_capacity) { if (*p_cursor == '\\' && p_cursor[1]) { p_cursor++; if (*p_cursor == 'n') p_output[output++] = '\n'; else if (*p_cursor == 't') p_output[output++] = '\t'; else if (*p_cursor != 'r') p_output[output++] = *p_cursor; } else { p_output[output++] = *p_cursor; } p_cursor++; } p_output[output] = '\0'; return p_output; } static uint32 Canvas_App_JSON_Uint(const char *p_json, const char *p_key) { char needle[128]; snprintf(needle, sizeof(needle), "\"%s\"", p_key); const char *p_cursor = strstr(p_json, needle); if (!p_cursor) return 0; p_cursor = strchr(p_cursor + strlen(needle), ':'); if (!p_cursor) return 0; return (uint32)strtoul(p_cursor + 1, NULL, 10); } static Canvas_Entity *Canvas_App_Find_Entity(uint32 entity_id) { for (size_t index = 0; index < Dowa_Array_Length(g_app->scene.p_entities); index++) { if (g_app->scene.p_entities[index].id == entity_id) { return &g_app->scene.p_entities[index]; } } return NULL; } static void Canvas_App_Set_Conversation_Text( Canvas_Entity *p_entity, const char *p_prompt, const char *p_speaker, const char *p_response) { size_t length = 0; int32 written = snprintf( p_entity->text, sizeof(p_entity->text), "You\n"); if (written > 0) length = (size_t)written; written = snprintf( p_entity->text + length, sizeof(p_entity->text) - length, "%.*s\n\n%s\n", 900, p_prompt, p_speaker); if (written > 0) length += (size_t)written; if (length >= sizeof(p_entity->text)) { length = sizeof(p_entity->text) - 1; } snprintf( p_entity->text + length, sizeof(p_entity->text) - length, "%.*s", 900, p_response); Canvas_Conversation_Scroll_To_End(p_entity, g_app->font); } static boolean Canvas_App_Submit_Prompt(const char *p_prompt) { if (!p_prompt || !p_prompt[0]) return FALSE; Canvas_Scene_Build_Visible_Context( &g_app->scene, &g_app->camera, g_app->agent_context, sizeof(g_app->agent_context)); if (!Canvas_Agent_Service_Submit( &g_app->agent_service, p_prompt, g_app->agent_context)) { return FALSE; } snprintf( g_app->pending_prompt, sizeof(g_app->pending_prompt), "%s", p_prompt); Vector2 position = Canvas_Camera_Screen_To_World( &g_app->camera, (Vector2){ (float)g_app->camera.viewport_width * 0.5f - 210.0f, (float)g_app->camera.viewport_height * 0.5f - 150.0f, }); g_app->pending_conversation_id = Canvas_Scene_Add_Conversation( &g_app->scene, "Routing thought...", p_prompt, "Copilot is deciding whether to create or extend a session.", position); Canvas_Entity *p_pending = Canvas_App_Find_Entity(g_app->pending_conversation_id); if (p_pending) p_pending->agent_working = TRUE; return TRUE; } static void Canvas_App_Apply_Agent_Response(const char *p_json) { char action[32] = {0}; char title[128] = {0}; char response[CANVAS_ENTITY_TEXT_CAPACITY] = {0}; Canvas_App_JSON_String(p_json, "action", action, sizeof(action)); Canvas_App_JSON_String(p_json, "title", title, sizeof(title)); if (!Canvas_App_JSON_String( p_json, "response", response, sizeof(response))) { snprintf(response, sizeof(response), "%s", p_json); } uint32 conversation_id = Canvas_App_JSON_Uint(p_json, "conversation_id"); Canvas_Entity *p_pending = Canvas_App_Find_Entity(g_app->pending_conversation_id); if (strcmp(action, "append") == 0 && conversation_id != 0 && Canvas_Scene_Append_Conversation( &g_app->scene, conversation_id, g_app->pending_prompt, response)) { Canvas_Entity *p_conversation = Canvas_App_Find_Entity(conversation_id); Canvas_Conversation_Scroll_To_End( p_conversation, g_app->font); if (p_pending) p_pending->removing = TRUE; } else if (p_pending) { snprintf( p_pending->label, sizeof(p_pending->label), "%s", title[0] ? title : "Copilot session"); Canvas_App_Set_Conversation_Text( p_pending, g_app->pending_prompt, "Copilot", response); p_pending->agent_working = FALSE; } else { Vector2 position = Canvas_Camera_Screen_To_World( &g_app->camera, (Vector2){ (float)g_app->camera.viewport_width * 0.5f - 210.0f, (float)g_app->camera.viewport_height * 0.5f - 150.0f, }); uint32 created_id = Canvas_Scene_Add_Conversation( &g_app->scene, title[0] ? title : "Copilot session", g_app->pending_prompt, response, position); Canvas_Conversation_Scroll_To_End( Canvas_App_Find_Entity(created_id), g_app->font); } g_app->pending_conversation_id = 0; g_app->pending_prompt[0] = '\0'; } static Canvas_Entity *Canvas_App_Ensure_Dictation_Entity(void) { Canvas_Entity *p_entity = Canvas_App_Find_Entity(g_app->dictation_entity_id); if (p_entity) return p_entity; Vector2 center = Canvas_Camera_Screen_To_World( &g_app->camera, (Vector2){ (float)g_app->camera.viewport_width * 0.5f, (float)g_app->camera.viewport_height * 0.5f, }); Vector2 size = {520.0f, 240.0f}; if (!Canvas_Scene_Add( &g_app->scene, CANVAS_ENTITY_TEXT_AREA, (Vector2){ center.x - size.x * 0.5f, center.y - size.y * 0.5f, })) { return NULL; } size_t index = Dowa_Array_Length(g_app->scene.p_entities) - 1; p_entity = &g_app->scene.p_entities[index]; p_entity->size = size; snprintf(p_entity->label, sizeof(p_entity->label), "Dictation"); g_app->dictation_entity_id = p_entity->id; g_app->scene.selected_index = (int32)index; return p_entity; } static void Canvas_App_Sync_Dictation_Entity(void) { Canvas_Entity *p_entity = Canvas_App_Find_Entity(g_app->dictation_entity_id); if (!p_entity) return; char text[CANVAS_ENTITY_TEXT_CAPACITY]; snprintf(text, sizeof(text), "%s", g_app->dictation_transcript); size_t length = strlen(text); if (length > 0 && g_app->dictation_partial[0] && length + 1 < sizeof(text)) { text[length++] = ' '; text[length] = '\0'; } snprintf( text + length, sizeof(text) - length, "%s", g_app->dictation_partial); Canvas_Text_Area_Set_Content( p_entity, g_app->font, text, g_app->dictation_partial[0] ? TRUE : FALSE); } static void Canvas_App_Set_Dictation_Active(boolean active) { g_app->dictation_overlay_visible = TRUE; g_app->dictation_requested_active = active; g_app->dictation_listening = FALSE; g_app->dictation_send_pending = FALSE; if (active) { Canvas_App_Ensure_Dictation_Entity(); Canvas_App_Sync_Dictation_Entity(); } snprintf( g_app->dictation_status, sizeof(g_app->dictation_status), "%s", active ? "Starting microphone..." : "Stopping microphone..."); Canvas_Web_Surface_Set_Dictation_Active( &g_app->web_surface, active); } static boolean Canvas_App_Submit_Dictation(void) { if (!g_app->dictation_transcript[0]) return FALSE; if (!Canvas_App_Submit_Prompt(g_app->dictation_transcript)) { g_app->dictation_send_pending = FALSE; snprintf( g_app->dictation_status, sizeof(g_app->dictation_status), "Copilot is busy; press Enter to retry"); return FALSE; } Canvas_Entity *p_entity = Canvas_App_Find_Entity(g_app->dictation_entity_id); if (p_entity) p_entity->text_muted = FALSE; g_app->dictation_transcript[0] = '\0'; g_app->dictation_partial[0] = '\0'; Canvas_App_Sync_Dictation_Entity(); g_app->dictation_send_pending = FALSE; g_app->dictation_overlay_visible = g_app->dictation_listening; return TRUE; } static void Canvas_App_Append_Dictation(const char *p_text) { if (!p_text || !p_text[0]) return; size_t length = strlen(g_app->dictation_transcript); if (length > 0 && length + 1 < sizeof(g_app->dictation_transcript)) { g_app->dictation_transcript[length++] = ' '; g_app->dictation_transcript[length] = '\0'; } size_t available = sizeof(g_app->dictation_transcript) - length - 1; size_t text_length = strlen(p_text); size_t copy_length = text_length < available ? text_length : available; memcpy(g_app->dictation_transcript + length, p_text, copy_length); g_app->dictation_transcript[length + copy_length] = '\0'; Canvas_App_Sync_Dictation_Entity(); } static void Canvas_App_Update_Dictation_Overlay_Input(void) { if (!g_app->dictation_overlay_visible) return; if (g_app->dictation_listening && IsKeyPressed(KEY_BACKSPACE)) { g_app->dictation_send_pending = FALSE; g_app->dictation_partial[0] = '\0'; g_app->dictation_transcript[0] = '\0'; Canvas_App_Sync_Dictation_Entity(); snprintf( g_app->dictation_status, sizeof(g_app->dictation_status), "Listening"); return; } if (IsKeyPressed(KEY_ESCAPE)) { if (g_app->dictation_listening) { Canvas_Web_Surface_Set_Dictation_Active( &g_app->web_surface, FALSE); g_app->dictation_requested_active = FALSE; g_app->dictation_listening = FALSE; } g_app->dictation_send_pending = FALSE; g_app->dictation_partial[0] = '\0'; g_app->dictation_transcript[0] = '\0'; Canvas_App_Sync_Dictation_Entity(); g_app->dictation_overlay_visible = FALSE; return; } if (!IsKeyPressed(KEY_ENTER)) return; if (g_app->dictation_partial[0]) { g_app->dictation_send_pending = TRUE; snprintf( g_app->dictation_status, sizeof(g_app->dictation_status), "Finishing dictation..."); Canvas_Web_Surface_Commit_Dictation( &g_app->web_surface); } else if (g_app->dictation_transcript[0]) { Canvas_App_Submit_Dictation(); } } static boolean Canvas_App_Dictation_Status_Is_Error(const char *p_status) { return strstr(p_status, "failed") != NULL || strstr(p_status, "Unable") != NULL || strstr(p_status, "closed") != NULL || strstr(p_status, "invalid") != NULL || strstr(p_status, "error") != NULL; } static void Canvas_App_Draw_Listening_Indicator(void) { if (!g_app->dictation_listening) return; float pulse = 0.72f + 0.28f * sinf((float)GetTime() * 5.0f); Vector2 dot = {28.0f, (float)GetScreenHeight() - 28.0f}; DrawCircleV( dot, 5.0f + pulse * 2.0f, Fade(g_app->theme.danger, 0.82f)); DrawTextEx( g_app->font, "Listening", (Vector2){44.0f, dot.y - 8.0f}, 14.0f, 0.0f, g_app->theme.text_muted); } static void Canvas_App_Draw_Frame(void) { int32 width = GetScreenWidth(); int32 height = GetScreenHeight(); Canvas_Camera_Set_Viewport(&g_app->camera, width, height); boolean block_pointer_input = FALSE; #if defined(INFINITE_CANVAS_DEV_UI) block_pointer_input = Canvas_Dev_UI_Contains_Pointer(&g_app->dev_ui); #endif Canvas_Web_Surface_Set_Dark_Mode( &g_app->web_surface, g_app->theme.resolved_mode == CANVAS_THEME_DARK ? TRUE : FALSE); boolean dictation_hotkey = !Canvas_Scene_Is_Text_Editing(&g_app->scene) && !Canvas_Web_Chrome_Is_Editing(&g_app->scene) && IsKeyPressed(KEY_M); if (dictation_hotkey) { Canvas_App_Set_Dictation_Active( g_app->dictation_requested_active ? FALSE : TRUE); } Canvas_App_Update_Dictation_Overlay_Input(); Canvas_Web_Chrome_Action chrome_action = CANVAS_WEB_CHROME_NONE; if (!block_pointer_input) { const char *p_current_url = NULL; if (g_app->scene.selected_index >= 0 && g_app->scene.selected_index < (int32)Dowa_Array_Length(g_app->scene.p_entities)) { const Canvas_Entity *p_selected = &g_app->scene.p_entities[g_app->scene.selected_index]; p_current_url = Canvas_Web_Surface_URL( &g_app->web_surface, p_selected->id); } chrome_action = Canvas_Web_Chrome_Update_Input( &g_app->camera, &g_app->scene, g_app->font, p_current_url); } if (chrome_action == CANVAS_WEB_CHROME_BACK || chrome_action == CANVAS_WEB_CHROME_FORWARD) { const Canvas_Entity *p_selected = &g_app->scene.p_entities[g_app->scene.selected_index]; if (chrome_action == CANVAS_WEB_CHROME_BACK) { Canvas_Web_Surface_Go_Back( &g_app->web_surface, p_selected->id); } else { Canvas_Web_Surface_Go_Forward( &g_app->web_surface, p_selected->id); } } boolean web_input = FALSE; if (!block_pointer_input && !dictation_hotkey && chrome_action == CANVAS_WEB_CHROME_NONE) { web_input = Canvas_Web_Surface_Update_Input( &g_app->web_surface, &g_app->camera, &g_app->scene); } block_pointer_input = block_pointer_input || chrome_action != CANVAS_WEB_CHROME_NONE || web_input; boolean entity_input = Canvas_Scene_Update_Interaction( &g_app->scene, &g_app->camera, g_app->font, block_pointer_input); boolean keyboard_focus = Canvas_Scene_Is_Text_Editing(&g_app->scene) || Canvas_Web_Chrome_Is_Editing(&g_app->scene) || Canvas_Web_Surface_Is_Focused(&g_app->web_surface); if (!keyboard_focus && IsKeyPressed(KEY_P)) { Canvas_Scene_Toggle_Selected_Pin(&g_app->scene, &g_app->camera); } if (!keyboard_focus && !g_app->dictation_listening && (IsKeyPressed(KEY_DELETE) || IsKeyPressed(KEY_BACKSPACE))) { Canvas_Scene_Fade_Out_Selected(&g_app->scene); } Canvas_Update_Camera_Input( &g_app->camera, block_pointer_input || entity_input, keyboard_focus); Canvas_Scene_Sync_Pinned(&g_app->scene, &g_app->camera); Canvas_Scene_Update_Notification_Stack(&g_app->scene, &g_app->camera); char dictation_text[CANVAS_ENTITY_TEXT_CAPACITY]; Canvas_Dictation_Event dictation_event = Canvas_Web_Surface_Consume_Dictation( &g_app->web_surface, dictation_text, sizeof(dictation_text)); if (dictation_event == CANVAS_DICTATION_EVENT_STATUS) { snprintf( g_app->dictation_status, sizeof(g_app->dictation_status), "%.127s", dictation_text); if (strcmp(dictation_text, "Idle") == 0) { g_app->dictation_listening = FALSE; if (g_app->dictation_send_pending) { if (g_app->dictation_partial[0]) { Canvas_App_Append_Dictation( g_app->dictation_partial); g_app->dictation_partial[0] = '\0'; } Canvas_App_Submit_Dictation(); } else if (!g_app->dictation_transcript[0] && !g_app->dictation_partial[0]) { g_app->dictation_overlay_visible = FALSE; } } else if (strcmp(dictation_text, "Listening") == 0 || strcmp(dictation_text, "Speech detected") == 0) { g_app->dictation_listening = g_app->dictation_requested_active; if (strcmp(dictation_text, "Listening") == 0 && g_app->dictation_send_pending) { if (g_app->dictation_partial[0]) { Canvas_App_Append_Dictation( g_app->dictation_partial); g_app->dictation_partial[0] = '\0'; } Canvas_App_Submit_Dictation(); } } else if (Canvas_App_Dictation_Status_Is_Error(dictation_text)) { g_app->dictation_listening = FALSE; g_app->dictation_overlay_visible = TRUE; } } else if (dictation_event == CANVAS_DICTATION_EVENT_PARTIAL) { g_app->dictation_overlay_visible = TRUE; snprintf( g_app->dictation_partial, sizeof(g_app->dictation_partial), "%s", dictation_text); Canvas_App_Sync_Dictation_Entity(); } else if (dictation_event == CANVAS_DICTATION_EVENT_FINAL) { g_app->dictation_overlay_visible = TRUE; Canvas_App_Append_Dictation(dictation_text); g_app->dictation_partial[0] = '\0'; Canvas_App_Sync_Dictation_Entity(); if (g_app->dictation_send_pending) { Canvas_App_Submit_Dictation(); } else { snprintf( g_app->dictation_status, sizeof(g_app->dictation_status), "Ready to send"); } } if (!g_app->dictation_overlay_visible && g_app->scene.selected_index >= 0 && g_app->scene.selected_index < (int32)Dowa_Array_Length(g_app->scene.p_entities) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL) || IsKeyDown(KEY_LEFT_SUPER) || IsKeyDown(KEY_RIGHT_SUPER)) && IsKeyPressed(KEY_ENTER)) { Canvas_Entity *p_selected = &g_app->scene.p_entities[g_app->scene.selected_index]; if (p_selected->type == CANVAS_ENTITY_TEXT_AREA && p_selected->text[0]) { size_t length = strlen(p_selected->text); if (length > 0 && p_selected->text[length - 1] == '\n') { p_selected->text[length - 1] = '\0'; } if (Canvas_App_Submit_Prompt(p_selected->text)) { p_selected->text[0] = '\0'; p_selected->text_cursor = 0; p_selected->text_selection_anchor = 0; } } } char agent_response[CANVAS_AGENT_RESPONSE_CAPACITY]; char agent_error[512]; Canvas_Agent_Status agent_status = g_app->agent_initialized ? Canvas_Agent_Service_Poll( &g_app->agent_service, agent_response, sizeof(agent_response), agent_error, sizeof(agent_error)) : CANVAS_AGENT_IDLE; if (agent_status == CANVAS_AGENT_READY) { Canvas_App_Apply_Agent_Response(agent_response); } else if (agent_status == CANVAS_AGENT_ERROR) { Canvas_Entity *p_pending = Canvas_App_Find_Entity(g_app->pending_conversation_id); if (p_pending) { snprintf(p_pending->label, sizeof(p_pending->label), "Copilot unavailable"); Canvas_App_Set_Conversation_Text( p_pending, g_app->pending_prompt, "System", agent_error); p_pending->agent_working = FALSE; } else if (g_app->pending_prompt[0]) { Vector2 position = Canvas_Camera_Screen_To_World( &g_app->camera, (Vector2){ (float)g_app->camera.viewport_width * 0.5f - 210.0f, (float)g_app->camera.viewport_height * 0.5f - 150.0f, }); Canvas_Scene_Add_Conversation( &g_app->scene, "Copilot unavailable", g_app->pending_prompt, agent_error, position); } g_app->pending_conversation_id = 0; g_app->pending_prompt[0] = '\0'; } Canvas_Web_Surface_Sync(&g_app->web_surface, &g_app->camera, &g_app->scene); BeginDrawing(); ClearBackground(g_app->theme.background); Canvas_Draw_World_Background( &g_app->camera, &g_app->scene, &g_app->theme); for (size_t index = 0; index < Dowa_Array_Length(g_app->scene.p_entities); index++) { Canvas_Draw_World_Entity( &g_app->camera, &g_app->scene, index, g_app->font, &g_app->theme); Canvas_Web_Surface_Draw_Entity( &g_app->web_surface, &g_app->camera, &g_app->scene, index); const Canvas_Entity *p_entity = &g_app->scene.p_entities[index]; const char *p_url = Canvas_Web_Surface_URL( &g_app->web_surface, p_entity->id); Canvas_Draw_Web_Chrome_Entity( &g_app->camera, &g_app->scene, index, g_app->font, &g_app->theme, p_url ? p_url : p_entity->text, Canvas_Web_Surface_Can_Go_Back( &g_app->web_surface, p_entity->id), Canvas_Web_Surface_Can_Go_Forward( &g_app->web_surface, p_entity->id)); } Canvas_Draw_World_Overlays( &g_app->camera, &g_app->scene, g_app->font, &g_app->theme); #if defined(INFINITE_CANVAS_DEV_UI) Canvas_Dev_UI_Draw( &g_app->dev_ui, &g_app->camera, &g_app->scene, g_app->font, &g_app->theme); #endif Canvas_App_Draw_Listening_Indicator(); EndDrawing(); g_app->frame_count++; #if !defined(PLATFORM_WEB) if (g_app->frame_count == 30) { RestoreWindow(); SetWindowFocused(); } #endif if (g_app->p_screenshot_path && g_app->frame_count == g_app->screenshot_frame) { TakeScreenshot(g_app->p_screenshot_path); } } int main(int argc, char **p_argv) { #if !defined(PLATFORM_WEB) int32 subprocess_result = Canvas_Web_Surface_Execute_Subprocess(argc, p_argv); if (subprocess_result >= 0) return subprocess_result; #else (void)argc; (void)p_argv; #endif Dowa_Arena *p_arena = Dowa_Arena_Create(8 * ONE_MEGA_BYTE); if (!p_arena) return 1; g_app = (Canvas_App *)Dowa_Arena_Allocate(p_arena, sizeof(*g_app)); if (!g_app) { Dowa_Arena_Free(p_arena); return 1; } memset(g_app, 0, sizeof(*g_app)); g_app->p_arena = p_arena; snprintf( g_app->dictation_status, sizeof(g_app->dictation_status), "Idle"); g_app->p_screenshot_path = getenv("INFINITE_CANVAS_SCREENSHOT"); g_app->screenshot_frame = 180; const char *p_screenshot_frame = getenv("INFINITE_CANVAS_SCREENSHOT_FRAME"); if (p_screenshot_frame) { uint32 screenshot_frame = (uint32)strtoul(p_screenshot_frame, NULL, 10); if (screenshot_frame > 0) { g_app->screenshot_frame = screenshot_frame; } } Canvas_Theme_Resolve( &g_app->theme, Canvas_Theme_Mode_From_String(getenv("INFINITE_CANVAS_THEME"))); SetConfigFlags(FLAG_WINDOW_RESIZABLE | FLAG_WINDOW_HIGHDPI | FLAG_MSAA_4X_HINT); #if defined(INFINITE_CANVAS_DEV_UI) InitWindow(1920, 1200, "Infinite Canvas"); SetWindowMinSize(1200, 800); #else InitWindow(1280, 720, "Infinite Canvas"); SetWindowMinSize(640, 360); #endif SetTargetFPS(60); #if !defined(PLATFORM_WEB) SetWindowFocused(); #endif if (!Canvas_Web_Surface_Init( &g_app->web_surface, p_arena, g_app->theme.resolved_mode == CANVAS_THEME_DARK ? TRUE : FALSE)) { TraceLog(LOG_WARNING, "Web surface initialization failed"); } g_app->agent_initialized = Canvas_Agent_Service_Init(&g_app->agent_service, p_arena); if (!g_app->agent_initialized) { TraceLog(LOG_WARNING, "Agent service initialization failed"); } g_app->font = LoadFontEx("infinite_canvas/assets/Inter-Variable.ttf", 48, NULL, 0); g_app->owns_font = g_app->font.texture.id != 0; if (!g_app->owns_font) g_app->font = GetFontDefault(); else SetTextureFilter(g_app->font.texture, TEXTURE_FILTER_BILINEAR); Canvas_Camera_Init(&g_app->camera, GetScreenWidth(), GetScreenHeight()); Canvas_Scene_Init(&g_app->scene, p_arena); if (getenv("INFINITE_CANVAS_BROWSER_STRESS")) { Canvas_Scene_Add_Browser_Grid(&g_app->scene); } else { Canvas_Scene_Add_Demo(&g_app->scene); } if (getenv("INFINITE_CANVAS_DICTATION_AUTOSTART")) { Canvas_App_Set_Dictation_Active(TRUE); } const char *p_initial_prompt = getenv("INFINITE_CANVAS_AGENT_PROMPT"); if (p_initial_prompt && p_initial_prompt[0]) { Canvas_App_Submit_Prompt(p_initial_prompt); } const char *p_image_source = getenv("INFINITE_CANVAS_IMAGE_SOURCE"); if (p_image_source) { for (size_t index = 0; index < Dowa_Array_Length(g_app->scene.p_entities); index++) { Canvas_Entity *p_entity = &g_app->scene.p_entities[index]; if (p_entity->type == CANVAS_ENTITY_IMAGE) { snprintf(p_entity->text, sizeof(p_entity->text), "%s", p_image_source); break; } } } #if !defined(PLATFORM_WEB) for (size_t index = 0; index < Dowa_Array_Length(g_app->scene.p_entities); index++) { Canvas_Entity *p_entity = &g_app->scene.p_entities[index]; if (p_entity->type == CANVAS_ENTITY_WEB_CONTENT) { snprintf(p_entity->text, sizeof(p_entity->text), "https://www.google.com"); break; } } #endif #if defined(INFINITE_CANVAS_DEV_UI) Canvas_Dev_UI_Init(&g_app->dev_ui, g_app->font, &g_app->theme); #endif #if defined(PLATFORM_WEB) emscripten_set_main_loop(Canvas_App_Draw_Frame, 0, 1); #else while (!WindowShouldClose()) Canvas_App_Draw_Frame(); if (g_app->agent_initialized) { Canvas_Agent_Service_Shutdown(&g_app->agent_service); } Canvas_Web_Surface_Shutdown(&g_app->web_surface); if (g_app->owns_font) UnloadFont(g_app->font); Dowa_Arena_Free(p_arena); g_app = NULL; CloseWindow(); #endif return 0; }