diff --git a/bin/assets/plugins/aiassistant.json b/bin/assets/plugins/aiassistant.json index c54ec0849..104e8ae7d 100644 --- a/bin/assets/plugins/aiassistant.json +++ b/bin/assets/plugins/aiassistant.json @@ -4,6 +4,42 @@ "anthropic": { "api_url": "https://api.anthropic.com/v1/messages", "models": [ + { + "cache_configuration": { + "max_cache_anchors": 4, + "min_total_token": 2048, + "should_speculate": true + }, + "default_temperature": 1.0, + "display_name": "Claude Opus 4.5", + "max_output_tokens": 8192, + "max_tokens": 200000, + "name": "claude-opus-4-5-20251101" + }, + { + "cache_configuration": { + "max_cache_anchors": 4, + "min_total_token": 2048, + "should_speculate": true + }, + "default_temperature": 1.0, + "display_name": "Claude Haiku 4.5", + "max_output_tokens": 8192, + "max_tokens": 200000, + "name": "claude-haiku-4-5-20251001" + }, + { + "cache_configuration": { + "max_cache_anchors": 4, + "min_total_token": 2048, + "should_speculate": true + }, + "default_temperature": 1.0, + "display_name": "Claude Sonnet 4.5", + "max_output_tokens": 8192, + "max_tokens": 200000, + "name": "claude-sonnet-4-5-20250929" + }, { "cache_configuration": { "max_cache_anchors": 4, @@ -39,71 +75,6 @@ "max_output_tokens": 8192, "max_tokens": 200000, "name": "claude-sonnet-4-20250514" - }, - { - "cache_configuration": { - "max_cache_anchors": 4, - "min_total_token": 2048, - "should_speculate": true - }, - "default_temperature": 1.0, - "display_name": "Claude Sonnet 3.5", - "max_output_tokens": 8192, - "max_tokens": 200000, - "name": "claude-3-5-sonnet-20241022" - }, - { - "cache_configuration": { - "max_cache_anchors": 4, - "min_total_token": 2048, - "should_speculate": true - }, - "default_temperature": 1.0, - "display_name": "Claude Sonnet 3.7", - "max_output_tokens": 8192, - "max_tokens": 200000, - "name": "claude-3-7-sonnet-20250219" - }, - { - "cache_configuration": { - "max_cache_anchors": 4, - "min_total_token": 2048, - "should_speculate": true - }, - "default_temperature": 1.0, - "display_name": "Claude Haiku 3.5", - "max_output_tokens": 8192, - "max_tokens": 200000, - "name": "claude-3-5-haiku-20241022", - "cheapest": true - }, - { - "cache_configuration": null, - "default_temperature": 1.0, - "display_name": "Claude Opus 3", - "max_output_tokens": 4096, - "max_tokens": 200000, - "name": "claude-3-opus-20240229" - }, - { - "cache_configuration": null, - "default_temperature": 1.0, - "display_name": "Claude Sonnet 3", - "max_output_tokens": 4096, - "max_tokens": 200000, - "name": "claude-3-sonnet-20240229" - }, - { - "cache_configuration": { - "max_cache_anchors": 4, - "min_total_token": 2048, - "should_speculate": true - }, - "default_temperature": 1.0, - "display_name": "Claude Haiku 3", - "max_output_tokens": 4096, - "max_tokens": 200000, - "name": "claude-3-haiku-20240307" } ], "version": 1 @@ -131,26 +102,6 @@ "google": { "api_url": "https://generativelanguage.googleapis.com/v1beta/openai/chat/completions", "models": [ - { - "name": "gemini-1.5-pro", - "display_name": "Gemini 1.5 Pro", - "max_tokens": 2000000 - }, - { - "name": "gemini-1.5-flash", - "display_name": "Gemini 1.5 Flash", - "max_tokens": 1000000 - }, - { - "name": "gemini-2.0-flash", - "display_name": "Gemini 2.0 Flash", - "max_tokens": 1000000 - }, - { - "name": "gemini-2.0-flash-thinking-exp", - "display_name": "Gemini 2.0 Flash Thinking", - "max_tokens": 1000000 - }, { "name": "gemini-2.5-flash-lite", "display_name": "Gemini 2.5 Flash Lite", @@ -221,14 +172,6 @@ "api_url": "https://api.openai.com/v1/chat/completions", "display_name": "OpenAI", "models": [ - { - "max_tokens": 8192, - "name": "gpt-4" - }, - { - "max_tokens": 128000, - "name": "gpt-4-turbo-preview" - }, { "max_tokens": 128000, "name": "gpt-4o" @@ -279,6 +222,24 @@ { "name": "gpt-5-nano", "cheapest": true + }, + { + "name": "gpt-5-pro" + }, + { + "name": "gpt-5.1" + }, + { + "name": "gpt-5.2" + }, + { + "name": "gpt-5.2-pro" + }, + { + "name": "gpt-5.1-codex-max" + }, + { + "name": "gpt-5.1-codex" } ], "version": 1 @@ -288,11 +249,20 @@ "display_name": "xAI", "models": [ { - "name": "grok-2-latest", + "name": "grok-4-1-fast-non-reasoning-latest", "cheapest": true }, + { + "name": "grok-code-fast-1" + }, + { + "name": "grok-4-1-fast-reasoning-latest" + }, { "name": "grok-3-latest" + }, + { + "name": "grok-4-latest" } ] }, @@ -300,6 +270,21 @@ "api_url": "https://models.github.ai/inference/chat/completions", "display_name": "GitHub", "models": [ + { + "name": "openai/gpt-5", + "display_name": "GitHub OpenAI GPT-5", + "tool_calling": true + }, + { + "name": "openai/gpt-5-mini", + "display_name": "GitHub OpenAI GPT-5 mini", + "tool_calling": true + }, + { + "name": "openai/gpt-5-nano", + "display_name": "GitHub OpenAI GPT-5 nano", + "tool_calling": true + }, { "name": "openai/gpt-4.1", "display_name": "GitHub OpenAI GPT-4.1", @@ -385,12 +370,12 @@ "display_name": "OpenRouter", "models": [ { - "name": "x-ai/grok-4-fast:free", - "display_name": "xAI: Grok 4 Fast (free)" + "name": "x-ai/grok-4-fast", + "display_name": "xAI: Grok 4 Fast" }, { - "name": "deepseek/deepseek-chat-v3.1:free", - "display_name": "DeepSeek: DeepSeek V3.1 (free)" + "name": "deepseek/deepseek-chat-v3.1", + "display_name": "DeepSeek: DeepSeek V3.1" }, { "name": "x-ai/grok-code-fast-1", @@ -419,6 +404,14 @@ { "name": "google/gemini-2.5-flash-lite", "display_name": "Google: Gemini 2.5 Flash Lite" + }, + { + "name": "openai/gpt-5.2", + "display_name": "OpenAI: GPT-5.2" + }, + { + "name": "openai/gpt-5.2-pro", + "display_name": "OpenAI: GPT-5 Pro" } ] } diff --git a/src/eepp/ui/css/stylesheetspecification.cpp b/src/eepp/ui/css/stylesheetspecification.cpp index c87240b98..f50acd552 100644 --- a/src/eepp/ui/css/stylesheetspecification.cpp +++ b/src/eepp/ui/css/stylesheetspecification.cpp @@ -825,54 +825,103 @@ void StyleSheetSpecification::registerDefaultShorthandParsers() { value = String::trim( value ); if ( value.empty() ) return {}; - std::vector properties; + const std::vector& propNames = shorthand->getProperties(); std::vector values = String::split( value, ',' ); - std::unordered_map> tmpProperties; + + std::vector xValues; + std::vector yValues; + + // Helper to identify keywords that can actually take an offset + auto isDirectionalKeyword = []( const std::string& s ) { + return s == "left" || s == "right" || s == "top" || s == "bottom"; + }; + + // Helper to identify explicit axis direction + auto isYAxis = []( const std::string& s ) { + return String::startsWith( s, "top" ) || String::startsWith( s, "bottom" ); + }; + + auto isXAxis = []( const std::string& s ) { + return String::startsWith( s, "left" ) || String::startsWith( s, "right" ); + }; for ( auto& val : values ) { - std::vector pos = String::split( val, ' ' ); - bool lastWasKeyword = false; - bool isXAxis = true; - std::string xAxis = ""; - std::string yAxis = ""; - - for ( const auto& data : pos ) { - bool isKeyword = isKeywordPosition( data ); - if ( isXAxis && ( isKeyword && lastWasKeyword ) ) - isXAxis = false; - - if ( isXAxis ) - xAxis += data + " "; - else - yAxis += data + " "; - - if ( isXAxis && ( !isKeyword && !lastWasKeyword ) ) - isXAxis = false; - - lastWasKeyword = isKeyword; + // 1. Tokenize + std::vector rawTokens = String::split( val, ' ' ); + std::vector tokens; + for ( const auto& t : rawTokens ) { + std::string clean = String::trim( t ); + if ( !clean.empty() ) + tokens.push_back( clean ); } - if ( xAxis.empty() ) - xAxis = "center"; + if ( tokens.empty() ) + continue; - if ( yAxis.empty() ) - yAxis = "center"; + // 2. Group Components correctly + std::vector components; + size_t idx = 0; + while ( idx < tokens.size() ) { + std::string current = tokens[idx]; - if ( String::startsWith( xAxis, "top" ) || String::startsWith( xAxis, "bottom" ) ) - std::swap( xAxis, yAxis ); + // Check if we should merge with next token. + // Rule: We only merge if 'current' is a directional keyword AND + // 'next' is NOT a keyword (implies it's a length/percent). + // Example: "right 10px" -> merge. "3px 3px" -> don't merge. + if ( isDirectionalKeyword( current ) ) { + size_t nextIdx = idx + 1; + if ( nextIdx < tokens.size() && !isKeywordPosition( tokens[nextIdx] ) ) { + current += " " + tokens[nextIdx]; + idx++; // Skip the next token since we consumed it + } + } - String::trimInPlace( xAxis ); - String::trimInPlace( yAxis ); + components.push_back( current ); + idx++; + } - tmpProperties[propNames[0]].emplace_back( xAxis ); - tmpProperties[propNames[1]].emplace_back( yAxis ); + // 3. Assign Axes + std::string xAxis = "center"; + std::string yAxis = "center"; + + if ( components.size() == 1 ) { + // Case: "bottom" -> Y=bottom, X=center + // Case: "10px" -> X=10px, Y=center + if ( isYAxis( components[0] ) ) + yAxis = components[0]; + else + xAxis = components[0]; + } else if ( components.size() >= 2 ) { + // Case: "10px 20px" -> X=10px, Y=20px + // Case: "top right" -> Y=top, X=right (Swap) + // Case: "right 10px top" -> X=right 10px, Y=top + + std::string c1 = components[0]; + std::string c2 = components[1]; + + // By default, first is X, second is Y. + // We swap ONLY if the first is clearly Vertical OR the second is clearly + // Horizontal. + if ( isYAxis( c1 ) || isXAxis( c2 ) ) { + yAxis = c1; + xAxis = c2; + } else { + xAxis = c1; + yAxis = c2; + } + } + + xValues.push_back( xAxis ); + yValues.push_back( yAxis ); } - for ( auto& props : tmpProperties ) { - properties.push_back( - StyleSheetProperty( props.first, String::join( props.second, ',' ) ) ); - } + std::vector properties; + if ( !propNames.empty() ) + properties.emplace_back( propNames[0], String::join( xValues, ',' ) ); + if ( propNames.size() > 1 ) + properties.emplace_back( propNames[1], String::join( yValues, ',' ) ); + return properties; }; diff --git a/src/eepp/ui/iconmanager.cpp b/src/eepp/ui/iconmanager.cpp index 0cfdffad0..c40c09392 100644 --- a/src/eepp/ui/iconmanager.cpp +++ b/src/eepp/ui/iconmanager.cpp @@ -114,6 +114,7 @@ UIIconTheme* IconManager::init( const std::string& iconThemeName, FontTrueType* { "volume-down-fill", 0xF29B }, { "volume-mute-fill", 0xF29D }, { "pause-fill", 0xEFD7 }, + { "spy-line", 0xF17F }, } ) { iconTheme->add( UIGlyphIcon::New( icon.first, remixIconFont, icon.second ) ); } diff --git a/src/eepp/ui/uitextinput.cpp b/src/eepp/ui/uitextinput.cpp index 25898bf37..db18279ef 100644 --- a/src/eepp/ui/uitextinput.cpp +++ b/src/eepp/ui/uitextinput.cpp @@ -200,7 +200,7 @@ void UITextInput::alignFix() { Vector2f rOffset( mRealAlignOffset ); UITextView::alignFix(); - if ( Font::getHorizontalAlign( getFlags() ) == UI_HALIGN_LEFT ) { + if ( mAllowEditing && Font::getHorizontalAlign( getFlags() ) == UI_HALIGN_LEFT ) { Float tW = getVisibleTextCache()->findCharacterPos( selCurInit() ).x; mCurPos.x = tW; mCurPos.y = 0; diff --git a/src/tools/ecode/plugins/aiassistant/chatui.cpp b/src/tools/ecode/plugins/aiassistant/chatui.cpp index 1f0d0472b..44bda3f4e 100644 --- a/src/tools/ecode/plugins/aiassistant/chatui.cpp +++ b/src/tools/ecode/plugins/aiassistant/chatui.cpp @@ -30,6 +30,9 @@ using namespace EE::Window; namespace ecode { +static const char* DEFAULT_PROVIDER = "google"; +static const char* DEFAULT_MODEL = "gemini-2.5-flash"; + const char* LLMChat::roleToString( Role role ) { switch ( role ) { case Role::User: @@ -142,6 +145,13 @@ DropDownList.role_ui { border-radius: 8dp; margin-bottom: 4dp; } +.llm_chat_input { + background-image: none; +} +.llm_chat_input.incognito { + background-image: icon(spy-line, 16dp); + background-position: top 8dp right 8dp; +} @@ -160,17 +170,17 @@ DropDownList.role_ui { - - - - + + + - + - + - - + + + @@ -207,8 +217,8 @@ LLMChatUI::LLMChatUI( PluginManager* manager ) : mChatsList = findByClass( "llm_chats" ); mModelDDL = findByClass( "model_ui" ); - mRefreshModels = find( "refresh_model_ui" ); - mRefreshModels->onClick( [this]( auto ) { execute( "ai-refresh-local-models" ); } ); + // mRefreshModels = find( "refresh_model_ui" ); + // mRefreshModels->onClick( [this]( auto ) { execute( "ai-refresh-local-models" ); } ); mChatMore = find( "llm_more" ); mChatMore->onClick( [this]( auto ) { execute( "ai-show-menu" ); } ); @@ -262,9 +272,9 @@ LLMChatUI::LLMChatUI( PluginManager* manager ) : mChatUserRole = find( "llm_user" ); mChatUserRole->onClick( [this]( auto ) { execute( "ai-chat-toggle-role" ); } ); - mChatPrivate = find( "llm_private_chat" ); + /* mChatPrivate = find( "llm_private_chat" ); mChatPrivate->on( Event::OnValueChange, - [this]( auto ) { mChatIsPrivate = mChatPrivate->isSelected(); } ); + [this]( auto ) { mChatIsPrivate = mChatPrivate->isSelected(); } ); */ auto setCmd = [this]( const std::string& name, const CommandCallback& cb ) { setCommand( name, cb ); @@ -373,7 +383,15 @@ LLMChatUI::LLMChatUI( PluginManager* manager ) : mLocateInput->getDocument().selectAll(); } ); - setCmd( "ai-toggle-private-chat", [this] { mChatPrivate->toggleSelection(); } ); + setCmd( "ai-toggle-private-chat", [this] { + mChatIsPrivate = !mChatIsPrivate; + /* mChatPrivate->toggleSelection(); */ + + if ( mChatIsPrivate ) + mChatInput->addClass( "incognito" ); + else + mChatInput->removeClass( "incognito" ); + } ); setCmd( "ai-toggle-lock-chat", [this] { renameChat( mSummary, true ); } ); @@ -466,6 +484,8 @@ LLMChatUI::LLMChatUI( PluginManager* manager ) : getKeyBindings().getCommandKeybindString( "ai-clone-chat" ) ) ->setId( "ai-clone-chat" ); + menu->addSeparator(); + menu->addCheckBox( i18n( "lock_chat_memory", "Lock Chat Memory" ), mChatLocked, getKeyBindings().getCommandKeybindString( "ai-toggle-lock-chat" ) ) ->setTooltipText( @@ -474,6 +494,19 @@ LLMChatUI::LLMChatUI( PluginManager* manager ) : "memory will only be able to be deleted manually in the chat history." ) ) ->setId( "ai-toggle-lock-chat" ); + menu->addCheckBox( i18n( "toggle_private_chat", "Toggle Private Chat" ), mChatIsPrivate, + getKeyBindings().getCommandKeybindString( "ai-toggle-private-chat" ) ) + ->setTooltipText( i18n( "toggle_private_chat_tooltip", + "Private chats won't be saved into the chat history." ) ) + ->setId( "ai-toggle-private-chat" ); + + menu->addSeparator(); + + menu->add( i18n( "refresh_model_ui", "Refresh Local Models" ), + getUISceneNode()->findIconDrawable( "refresh", PixelDensity::dpToPxI( 12 ) ), + getKeyBindings().getCommandKeybindString( "ai-refresh-local-models" ) ) + ->setId( "ai-refresh-local-models" ); + menu->on( Event::OnItemClicked, [this]( const Event* event ) { UIMenuItem* item = event->getNode()->asType(); std::string id( item->getId() ); @@ -537,10 +570,10 @@ LLMChatUI::LLMChatUI( PluginManager* manager ) : appendShortcutToTooltip( mChatStop, "ai-prompt" ); appendShortcutToTooltip( mChatAdd, "ai-add-chat" ); appendShortcutToTooltip( mChatSettings, "ai-settings" ); - appendShortcutToTooltip( mChatPrivate, "ai-toggle-private-chat" ); + // appendShortcutToTooltip( mChatPrivate, "ai-toggle-private-chat" ); appendShortcutToTooltip( mChatMore, "ai-show-menu" ); appendShortcutToTooltip( mChatUserRole, "ai-chat-toggle-role" ); - appendShortcutToTooltip( mRefreshModels, "ai-refresh-local-models" ); + // appendShortcutToTooltip( mRefreshModels, "ai-refresh-local-models" ); addKb( mChatInput, "mod+keypad enter", "ai-prompt", true, false ); addKb( mChatInput, "mod+shift+keypad enter", "ai-add-chat", true, false ); @@ -1045,13 +1078,13 @@ LLMModel LLMChatUI::findModel( const std::string& provider, const std::string& m return *modelIt; } } - if ( provider == "openai" && model == "gpt-5" ) + if ( provider == DEFAULT_PROVIDER && model == DEFAULT_MODEL ) return mCurModel; // Do not stack-overflow if something is really wrong return getDefaultModel(); } LLMModel LLMChatUI::getDefaultModel() { - return findModel( "openai", "gpt-5" ); + return findModel( DEFAULT_PROVIDER, DEFAULT_MODEL ); } std::string LLMChatUI::getNewFilePath( const std::string& uuid, const std::string& summary,