mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-05-28 17:16:29 +03:00
Add resource_link support.
Agent Config WIP.
This commit is contained in:
Binary file not shown.
@@ -299,6 +299,7 @@ UIIconTheme* IconManager::init( const std::string& iconThemeName, FontTrueType*
|
||||
{ "chat-sparkle", 0xec4f },
|
||||
{ "inspect", 0xebd1 },
|
||||
{ "link", 0xeb15 },
|
||||
{ "agent", 0xec67 },
|
||||
|
||||
} ) {
|
||||
iconTheme->add( UIGlyphIcon::New( icon.first, codIconFont, icon.second ) );
|
||||
|
||||
@@ -241,6 +241,63 @@ void ACPClient::loadSession(
|
||||
} );
|
||||
}
|
||||
|
||||
void ACPClient::setConfigOption(
|
||||
const SetConfigOptionRequest& req,
|
||||
const std::function<void( const SetConfigOptionResponse&,
|
||||
const std::optional<ResponseError>& )>& cb ) {
|
||||
auto fallback = [this, req, cb]() {
|
||||
if ( req.configId == "model" ) {
|
||||
write( { { "method", "session/set_model" },
|
||||
{ "params", { { "sessionId", req.sessionId }, { "modelId", req.optionId } } } },
|
||||
[req, cb]( const IdType&, const json& resp2 ) {
|
||||
if ( resp2.contains( "result" ) && cb ) {
|
||||
cb( SetConfigOptionResponse( resp2["result"], req.configId,
|
||||
req.optionId ),
|
||||
std::nullopt );
|
||||
} else if ( resp2.contains( "error" ) && cb ) {
|
||||
cb( {}, ResponseError( resp2["error"] ) );
|
||||
}
|
||||
} );
|
||||
} else if ( req.configId == "mode" ) {
|
||||
write( { { "method", "session/set_mode" },
|
||||
{ "params", { { "sessionId", req.sessionId }, { "modeId", req.optionId } } } },
|
||||
[req, cb]( const IdType&, const json& resp2 ) {
|
||||
if ( resp2.contains( "result" ) && cb ) {
|
||||
cb( SetConfigOptionResponse( resp2["result"], req.configId,
|
||||
req.optionId ),
|
||||
std::nullopt );
|
||||
} else if ( resp2.contains( "error" ) && cb ) {
|
||||
cb( {}, ResponseError( resp2["error"] ) );
|
||||
}
|
||||
} );
|
||||
} else if ( cb ) {
|
||||
cb( {}, ResponseError{ -32601, "Method not found" } );
|
||||
}
|
||||
};
|
||||
|
||||
if ( mLegacyConfigOnly ) {
|
||||
fallback();
|
||||
return;
|
||||
}
|
||||
|
||||
write( { { "method", "session/set_config_option" }, { "params", req.toJson() } },
|
||||
[this, req, fallback, cb]( const IdType&, const json& resp ) {
|
||||
if ( resp.contains( "result" ) && cb ) {
|
||||
cb( SetConfigOptionResponse( resp["result"], req.configId, req.optionId ),
|
||||
std::nullopt );
|
||||
} else if ( resp.contains( "error" ) ) {
|
||||
ResponseError err( resp["error"] );
|
||||
if ( err.code == -32601 ) {
|
||||
mLegacyConfigOnly = true;
|
||||
fallback();
|
||||
return;
|
||||
}
|
||||
if ( cb )
|
||||
cb( {}, err );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
void ACPClient::listSessions(
|
||||
const ListSessionsRequest& req,
|
||||
const std::function<void( const ListSessionsResponse&, const std::optional<ResponseError>& )>&
|
||||
|
||||
@@ -52,6 +52,9 @@ class ACPClient {
|
||||
void loadSession( const LoadSessionRequest& req,
|
||||
const std::function<void( const LoadSessionResponse&,
|
||||
const std::optional<ResponseError>& )>& cb );
|
||||
void setConfigOption( const SetConfigOptionRequest& req,
|
||||
const std::function<void( const SetConfigOptionResponse&,
|
||||
const std::optional<ResponseError>& )>& cb );
|
||||
void listSessions( const ListSessionsRequest& req,
|
||||
const std::function<void( const ListSessionsResponse&,
|
||||
const std::optional<ResponseError>& )>& cb );
|
||||
@@ -102,6 +105,7 @@ class ACPClient {
|
||||
std::map<IdType, JsonReplyHandler> mHandlers;
|
||||
|
||||
std::string mReceiveBuffer;
|
||||
bool mLegacyConfigOnly{ false };
|
||||
|
||||
void readStdOut( const char* bytes, size_t n );
|
||||
void readStdErr( const char* bytes, size_t n );
|
||||
|
||||
@@ -2,6 +2,84 @@
|
||||
|
||||
namespace ecode { namespace acp {
|
||||
|
||||
json parseLegacyConfigOptions( const json& body, json configOptions, const std::string& forceId,
|
||||
const std::string& forceValue ) {
|
||||
if ( configOptions.is_null() ) {
|
||||
configOptions = json::array();
|
||||
}
|
||||
|
||||
auto updateOrAdd = [&configOptions]( json newOpt ) {
|
||||
bool found = false;
|
||||
for ( auto& opt : configOptions ) {
|
||||
if ( opt.is_object() && opt.contains( "id" ) && opt["id"] == newOpt["id"] ) {
|
||||
opt = std::move( newOpt );
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( !found ) {
|
||||
configOptions.push_back( std::move( newOpt ) );
|
||||
}
|
||||
};
|
||||
|
||||
if ( body.contains( "models" ) && body["models"].is_object() ) {
|
||||
auto models = body["models"];
|
||||
if ( models.contains( "availableModels" ) && models["availableModels"].is_array() ) {
|
||||
json modelConfig = { { "id", "model" },
|
||||
{ "name", "Model" },
|
||||
{ "type", "select" },
|
||||
{ "category", "model" },
|
||||
{ "options", json::array() } };
|
||||
for ( const auto& model : models["availableModels"] ) {
|
||||
modelConfig["options"].push_back(
|
||||
{ { "id", model.value( "modelId", "" ) }, { "name", model.value( "name", "" ) } } );
|
||||
}
|
||||
if ( models.contains( "currentModelId" ) ) {
|
||||
modelConfig["currentValue"] = models.value( "currentModelId", "" );
|
||||
modelConfig["default"] = models.value( "currentModelId", "" );
|
||||
} else if ( !modelConfig["options"].empty() ) {
|
||||
modelConfig["currentValue"] = modelConfig["options"][0]["id"];
|
||||
modelConfig["default"] = modelConfig["options"][0]["id"];
|
||||
}
|
||||
updateOrAdd( std::move( modelConfig ) );
|
||||
}
|
||||
}
|
||||
|
||||
if ( body.contains( "modes" ) && body["modes"].is_object() ) {
|
||||
auto modes = body["modes"];
|
||||
if ( modes.contains( "availableModes" ) && modes["availableModes"].is_array() ) {
|
||||
json modeConfig = { { "id", "mode" },
|
||||
{ "name", "Mode" },
|
||||
{ "type", "select" },
|
||||
{ "category", "mode" },
|
||||
{ "options", json::array() } };
|
||||
for ( const auto& mode : modes["availableModes"] ) {
|
||||
modeConfig["options"].push_back(
|
||||
{ { "id", mode.value( "id", "" ) }, { "name", mode.value( "name", "" ) } } );
|
||||
}
|
||||
if ( modes.contains( "currentModeId" ) ) {
|
||||
modeConfig["currentValue"] = modes.value( "currentModeId", "" );
|
||||
modeConfig["default"] = modes.value( "currentModeId", "" );
|
||||
} else if ( !modeConfig["options"].empty() ) {
|
||||
modeConfig["currentValue"] = modeConfig["options"][0]["id"];
|
||||
modeConfig["default"] = modeConfig["options"][0]["id"];
|
||||
}
|
||||
updateOrAdd( std::move( modeConfig ) );
|
||||
}
|
||||
}
|
||||
|
||||
if ( !forceId.empty() ) {
|
||||
for ( auto& opt : configOptions ) {
|
||||
if ( opt.is_object() && opt.contains( "id" ) && opt["id"] == forceId ) {
|
||||
opt["currentValue"] = forceValue;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return configOptions;
|
||||
}
|
||||
|
||||
ClientCapabilities::ClientCapabilities( const json& body ) {
|
||||
if ( body.contains( "terminal" ) )
|
||||
terminal = body.value( "terminal", false );
|
||||
@@ -58,6 +136,7 @@ NewSessionResponse::NewSessionResponse( const json& body ) {
|
||||
sessionId = body.value( "sessionId", "" );
|
||||
if ( body.contains( "configOptions" ) )
|
||||
configOptions = body["configOptions"];
|
||||
configOptions = parseLegacyConfigOptions( body, configOptions );
|
||||
}
|
||||
|
||||
json LoadSessionRequest::toJson() const {
|
||||
@@ -73,6 +152,18 @@ json LoadSessionRequest::toJson() const {
|
||||
LoadSessionResponse::LoadSessionResponse( const json& body ) {
|
||||
if ( body.contains( "configOptions" ) )
|
||||
configOptions = body["configOptions"];
|
||||
configOptions = parseLegacyConfigOptions( body, configOptions );
|
||||
}
|
||||
|
||||
json SetConfigOptionRequest::toJson() const {
|
||||
return { { "sessionId", sessionId }, { "configId", configId }, { "optionId", optionId } };
|
||||
}
|
||||
|
||||
SetConfigOptionResponse::SetConfigOptionResponse( const json& body, const std::string& configId,
|
||||
const std::string& optionId ) {
|
||||
if ( body.contains( "configOptions" ) )
|
||||
configOptions = body["configOptions"];
|
||||
configOptions = parseLegacyConfigOptions( body, configOptions, configId, optionId );
|
||||
}
|
||||
|
||||
SessionInfo::SessionInfo( const json& body ) {
|
||||
|
||||
@@ -11,6 +11,9 @@ using json = nlohmann::json;
|
||||
|
||||
namespace ecode { namespace acp {
|
||||
|
||||
json parseLegacyConfigOptions( const json& body, json configOptions, const std::string& forceId = "",
|
||||
const std::string& forceValue = "" );
|
||||
|
||||
struct ClientCapabilities {
|
||||
bool terminal{ false };
|
||||
bool fsReadTextFile{ false };
|
||||
@@ -79,6 +82,23 @@ struct LoadSessionResponse {
|
||||
LoadSessionResponse( const json& body );
|
||||
};
|
||||
|
||||
struct SetConfigOptionRequest {
|
||||
std::string sessionId;
|
||||
std::string configId;
|
||||
std::string optionId;
|
||||
|
||||
SetConfigOptionRequest() = default;
|
||||
json toJson() const;
|
||||
};
|
||||
|
||||
struct SetConfigOptionResponse {
|
||||
json configOptions;
|
||||
|
||||
SetConfigOptionResponse() = default;
|
||||
SetConfigOptionResponse( const json& body, const std::string& configId = "",
|
||||
const std::string& optionId = "" );
|
||||
};
|
||||
|
||||
struct SessionInfo {
|
||||
std::string sessionId;
|
||||
std::string cwd;
|
||||
@@ -111,6 +131,8 @@ struct ResponseError {
|
||||
json data;
|
||||
|
||||
ResponseError() = default;
|
||||
ResponseError( int code, std::string message, json data = {} ) :
|
||||
code( code ), message( std::move( message ) ), data( std::move( data ) ) {}
|
||||
ResponseError( const json& body ) {
|
||||
if ( body.contains( "code" ) )
|
||||
code = body["code"].get<int>();
|
||||
|
||||
@@ -39,6 +39,7 @@ bool AgentSession::start( const std::function<void( bool )>& onReady ) {
|
||||
return;
|
||||
}
|
||||
mSessionId = nres.sessionId;
|
||||
mConfigOptions = nres.configOptions;
|
||||
if ( onReady )
|
||||
onReady( true );
|
||||
} );
|
||||
@@ -71,7 +72,7 @@ bool AgentSession::startLoaded( const std::string& sessionId,
|
||||
lreq.sessionId = sessionId;
|
||||
lreq.cwd = mClient->isReady() ? mClient->getConfig().workingDirectory : "";
|
||||
mClient->loadSession(
|
||||
lreq, [this, sessionId, onReady]( const LoadSessionResponse&,
|
||||
lreq, [this, sessionId, onReady]( const LoadSessionResponse& lres,
|
||||
const std::optional<ResponseError>& err ) {
|
||||
if ( err ) {
|
||||
if ( onReady )
|
||||
@@ -79,6 +80,7 @@ bool AgentSession::startLoaded( const std::string& sessionId,
|
||||
return;
|
||||
}
|
||||
mSessionId = sessionId;
|
||||
mConfigOptions = lres.configOptions;
|
||||
if ( onReady )
|
||||
onReady( true );
|
||||
} );
|
||||
@@ -146,6 +148,12 @@ void AgentSession::setupClient() {
|
||||
};
|
||||
|
||||
mClient->onSessionUpdate = [this]( const json& msg ) {
|
||||
auto sessionUpdate = msg.value( "sessionUpdate", "" );
|
||||
if ( sessionUpdate == "config_options_update" && msg.contains( "configOptions" ) ) {
|
||||
mConfigOptions = msg["configOptions"];
|
||||
} else if ( msg.contains( "models" ) || msg.contains( "modes" ) ) {
|
||||
mConfigOptions = parseLegacyConfigOptions( msg, mConfigOptions );
|
||||
}
|
||||
if ( onSessionUpdate )
|
||||
onSessionUpdate( msg );
|
||||
};
|
||||
|
||||
@@ -44,6 +44,8 @@ class AgentSession {
|
||||
|
||||
std::string getSessionId() const { return mSessionId; }
|
||||
ACPClient* getClient() const { return mClient.get(); }
|
||||
json getConfigOptions() const { return mConfigOptions; }
|
||||
void setConfigOptions( const json& opts ) { mConfigOptions = opts; }
|
||||
|
||||
void setTerminalData( const std::string& terminalId, UITerminal* uiTerm );
|
||||
|
||||
@@ -51,6 +53,7 @@ class AgentSession {
|
||||
std::shared_ptr<ThreadPool> mThreadPool;
|
||||
std::unique_ptr<ACPClient> mClient;
|
||||
std::string mSessionId;
|
||||
json mConfigOptions;
|
||||
bool mIsPrompting{ false };
|
||||
|
||||
struct TermData {
|
||||
|
||||
@@ -532,6 +532,7 @@ DropDownList.role_ui {
|
||||
<PushButton id="llm_more" class="llm_button" tooltip="@string(more_options, More Options)" icon="icon(more-fill, 14dp)" min-width="32dp" />
|
||||
<PushButton class="model_ui" lw="0" lw8="1" lh="mp" margin-left="4dp" margin-right="4dp" tooltip="@string(select_model, Select Model)" />
|
||||
<PushButton class="agent_ui" lw="0" lw8="1" lh="mp" margin-left="4dp" margin-right="4dp" tooltip="@string(select_agent, Select Agent)" visible="false" />
|
||||
<PushButton class="agent_config_ui" tooltip="@string(agent_config, Agent Config)" icon="icon(agent, 14dp)" min-width="32dp" margin-right="4dp" visible="false" />
|
||||
<SelectButton id="llm_agent_mode" class="llm_button" tooltip="@string(toggle_agent_mode, Toggle Agent Mode)" icon="icon(robot-2, 14dp)" min-width="32dp" margin-right="4dp" select-on-click="true" />
|
||||
<PushButton id="llm_settings_but" class="llm_button" text="@string(settings, Settings)" tooltip="@string(settings, Settings)" icon="icon(settings, 14dp)" min-width="32dp" margin-right="4dp" />
|
||||
<PushButton id="llm_add_chat" class="llm_button" text="@string(add, Add)" tooltip="@string(add_message, Add Message)" icon="icon(add, 15dp)" min-width="32dp" margin-right="4dp" />
|
||||
@@ -598,6 +599,8 @@ LLMChatUI::LLMChatUI( PluginManager* manager ) :
|
||||
mModelBtn->onClick( [this]( auto ) { execute( "ai-select-model" ); } );
|
||||
mAgentBtn = findByClass<UIPushButton>( "agent_ui" );
|
||||
mAgentBtn->onClick( [this]( auto ) { execute( "ai-select-agent" ); } );
|
||||
mAgentConfigBtn = findByClass<UIPushButton>( "agent_config_ui" );
|
||||
mAgentConfigBtn->onClick( [this]( auto ) { showAgentConfigMenu(); } );
|
||||
|
||||
mChatAgentMode = find<UISelectButton>( "llm_agent_mode" );
|
||||
mChatAgentMode->on( Event::OnValueChange, [this]( auto ) {
|
||||
@@ -1519,6 +1522,106 @@ void LLMChatUI::hideSelectAgent() {
|
||||
mLocateAgentBarLayout->setVisible( false );
|
||||
}
|
||||
|
||||
void LLMChatUI::showAgentConfigMenu() {
|
||||
if ( !mAgentSession || !mAgentSession->getClient()->isReady() ) {
|
||||
if ( !mAgentSession )
|
||||
setupAgentSession();
|
||||
|
||||
if ( mAgentSession && !mAgentSession->getClient()->isReady() ) {
|
||||
mAgentConfigBtn->setEnabled( false );
|
||||
mAgentSession->start( [this]( bool ready ) {
|
||||
runOnMainThread( [this, ready]() {
|
||||
mAgentConfigBtn->setEnabled( true );
|
||||
if ( ready ) {
|
||||
showAgentConfigMenu();
|
||||
} else {
|
||||
NotificationCenter::instance()->addNotification(
|
||||
i18n( "failed_to_start_agent", "Failed to start agent process." ) );
|
||||
mAgentSession.reset();
|
||||
}
|
||||
} );
|
||||
} );
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
auto configOptions = mAgentSession->getConfigOptions();
|
||||
if ( !configOptions.is_array() || configOptions.empty() ) {
|
||||
NotificationCenter::instance()->addNotification(
|
||||
i18n( "no_agent_configs", "No Config Options Available" ) );
|
||||
return;
|
||||
}
|
||||
|
||||
UIPopUpMenu* menu = UIPopUpMenu::New();
|
||||
|
||||
for ( const auto& opt : configOptions ) {
|
||||
if ( !opt.is_object() || !opt.contains( "id" ) || !opt.contains( "name" ) ||
|
||||
!opt.contains( "options" ) || !opt["options"].is_array() )
|
||||
continue;
|
||||
|
||||
UIPopUpMenu* subMenu = UIPopUpMenu::New();
|
||||
std::string optId = opt["id"].get<std::string>();
|
||||
std::string currentVal =
|
||||
opt.contains( "currentValue" ) ? opt["currentValue"].get<std::string>() : "";
|
||||
|
||||
for ( const auto& subopt : opt["options"] ) {
|
||||
if ( !subopt.is_object() || !subopt.contains( "id" ) || !subopt.contains( "name" ) )
|
||||
continue;
|
||||
|
||||
std::string subId = subopt["id"].get<std::string>();
|
||||
std::string subName = subopt["name"].get<std::string>();
|
||||
|
||||
Drawable* icon = nullptr;
|
||||
if ( subId == currentVal ) {
|
||||
icon = getUISceneNode()->findIconDrawable( "ok", PixelDensity::dpToPxI( 12 ) );
|
||||
}
|
||||
|
||||
auto* item = subMenu->add( subName, icon );
|
||||
item->setId( subId );
|
||||
}
|
||||
|
||||
subMenu->on( Event::OnItemClicked, [this, optId]( const Event* event ) {
|
||||
UIMenuItem* item = event->getNode()->asType<UIMenuItem>();
|
||||
std::string subId( item->getId() );
|
||||
acp::SetConfigOptionRequest req;
|
||||
req.sessionId = mAgentSession->getSessionId();
|
||||
req.configId = optId;
|
||||
req.optionId = subId;
|
||||
mAgentSession->getClient()->setConfigOption(
|
||||
req, [this, optId, subId]( const acp::SetConfigOptionResponse& res,
|
||||
const std::optional<acp::ResponseError>& err ) {
|
||||
if ( err ) {
|
||||
runOnMainThread( [this, err]() {
|
||||
NotificationCenter::instance()->addNotification(
|
||||
i18n( "agent_config_error", "Failed to update agent config: " ) +
|
||||
err->message );
|
||||
} );
|
||||
} else {
|
||||
auto newOpts = res.configOptions;
|
||||
if ( newOpts.empty() ) {
|
||||
newOpts = acp::parseLegacyConfigOptions(
|
||||
{}, mAgentSession->getConfigOptions(), optId, subId );
|
||||
}
|
||||
mAgentSession->setConfigOptions( newOpts );
|
||||
}
|
||||
} );
|
||||
} );
|
||||
|
||||
menu->addSubMenu( opt["name"].get<std::string>(), nullptr, subMenu );
|
||||
}
|
||||
|
||||
if ( menu->getCount() == 0 ) {
|
||||
menu->add( i18n( "no_agent_configs", "No Config Options Available" ) )->setEnabled( false );
|
||||
}
|
||||
|
||||
menu->runOnMainThread( [this, menu] {
|
||||
auto pos( mAgentConfigBtn->getScreenPos() );
|
||||
UIMenu::findBestMenuPos( pos, menu, nullptr, nullptr, mAgentConfigBtn );
|
||||
menu->showAtScreenPosition( pos );
|
||||
} );
|
||||
}
|
||||
|
||||
void LLMChatUI::initSelectAgent() {
|
||||
mLocateAgentBarLayout = findByClass<UIVLinearLayoutCommandExecuter>( "llm_chat_select_agent" );
|
||||
mLocateAgentInput = findByClass<UITextInput>( "llm_chat_select_agent_input" );
|
||||
@@ -1646,6 +1749,7 @@ void LLMChatUI::writeToLastChat( const std::string& text ) {
|
||||
void LLMChatUI::updateAgentModeUI() {
|
||||
mModelBtn->setVisible( !mIsAgentMode );
|
||||
mAgentBtn->setVisible( mIsAgentMode );
|
||||
mAgentConfigBtn->setVisible( mIsAgentMode );
|
||||
mChatAdd->setVisible( !mIsAgentMode );
|
||||
mChatUserRole->setVisible( !mIsAgentMode );
|
||||
|
||||
@@ -1998,10 +2102,10 @@ nlohmann::json LLMChatUI::promptToContentBlocks( std::string text ) {
|
||||
path = prjPath + path;
|
||||
}
|
||||
|
||||
std::string fileBuffer;
|
||||
if ( FileSystem::fileGet( path, fileBuffer ) ) {
|
||||
j.push_back( { { "type", "resource" },
|
||||
{ "resource", { { "path", path }, { "content", fileBuffer } } } } );
|
||||
if ( FileSystem::fileExists( path ) ) {
|
||||
j.push_back( { { "type", "resource_link" },
|
||||
{ "name", FileSystem::fileNameFromPath( path ) },
|
||||
{ "uri", "file://" + path } } );
|
||||
}
|
||||
|
||||
lastPos = matches[0].end;
|
||||
|
||||
@@ -116,6 +116,7 @@ class LLMChatUI : public UILinearLayout, public WidgetCommandExecuter {
|
||||
UIScrollView* mChatScrollView{ nullptr };
|
||||
UIPushButton* mModelBtn{ nullptr };
|
||||
UIPushButton* mAgentBtn{ nullptr };
|
||||
UIPushButton* mAgentConfigBtn{ nullptr };
|
||||
|
||||
// Locate file
|
||||
UIVLinearLayoutCommandExecuter* mLocateBarLayout{ nullptr };
|
||||
@@ -274,6 +275,8 @@ class LLMChatUI : public UILinearLayout, public WidgetCommandExecuter {
|
||||
|
||||
void hideSelectModel();
|
||||
|
||||
void showAgentConfigMenu();
|
||||
|
||||
void insertFileToDocument( std::string path, std::shared_ptr<TextDocument> cdoc );
|
||||
|
||||
void replaceFileLinksToContents( std::string& text );
|
||||
|
||||
Reference in New Issue
Block a user