diff --git a/PacletInfo.wl b/PacletInfo.wl index 02af72f8..be6b2b71 100644 --- a/PacletInfo.wl +++ b/PacletInfo.wl @@ -1,7 +1,7 @@ PacletObject[<| "Name" -> "Wolfram/Chatbook", "PublisherID" -> "Wolfram", - "Version" -> "0.0.15", + "Version" -> "0.0.16", "WolframVersion" -> "13.2+", "Description" -> "Wolfram Notebooks + LLMs", "License" -> "MIT", diff --git a/Source/Chatbook/Actions.wl b/Source/Chatbook/Actions.wl index 7dc07d30..7d543afb 100644 --- a/Source/Chatbook/Actions.wl +++ b/Source/Chatbook/Actions.wl @@ -42,7 +42,7 @@ ChatbookAction[ "ExclusionToggle" , args___ ] := catchMine @ ExclusionToggl ChatbookAction[ "OpenChatBlockSettings", args___ ] := catchMine @ OpenChatBlockSettings @ args; ChatbookAction[ "OpenChatMenu" , args___ ] := catchMine @ OpenChatMenu @ args; ChatbookAction[ "PersonaManage" , args___ ] := catchMine @ PersonaManage @ args; -(* ChatbookAction[ "PersonaURLInstall" , args___ ] := catchMine @ PersonaURLInstall @ args; *) (* TODO *) +ChatbookAction[ "PersonaURLInstall" , args___ ] := catchMine @ PersonaURLInstall @ args; ChatbookAction[ "Send" , args___ ] := catchMine @ SendChat @ args; ChatbookAction[ "StopChat" , args___ ] := catchMine @ StopChat @ args; ChatbookAction[ "TabLeft" , args___ ] := catchMine @ TabLeft @ args; @@ -112,6 +112,31 @@ definitionNotebookCellQ[ cell_CellObject ] := definitionNotebookCellQ[ cell ] = definitionNotebookCellQ[ ___ ] := False; +(* ::**************************************************************************************************************:: *) +(* ::Section::Closed:: *) +(*PersonaURLInstall*) +(* TODO: this will eventually get merged into the manage personas dialog instead of being a separate menu item *) + +PersonaURLInstall // beginDefinition; + +PersonaURLInstall[ dingbatCell_CellObject ] := Enclose[ + Catch @ Module[ { cellObject, installed, name }, + cellObject = ConfirmMatch[ topParentCell @ dingbatCell, _CellObject, "ParentCell" ]; + installed = PersonaInstallFromURL[ ]; + If[ installed === $Canceled, Throw @ $Canceled ]; + ConfirmAssert[ AssociationQ @ installed, "AssociationQ" ]; + name = ConfirmBy[ installed[ "Name" ], StringQ, "Name" ]; + ConfirmMatch[ + CurrentValue[ cellObject, { TaggingRules, "ChatNotebookSettings", "LLMEvaluator" } ] = name, + name, + "SetLLMEvaluator" + ] + ], + throwInternalFailure[ PersonaURLInstall @ dingbatCell, ## ] & +]; + +PersonaURLInstall // endDefinition; + (* ::**************************************************************************************************************:: *) (* ::Section::Closed:: *) (*PersonaManage*) diff --git a/Source/Chatbook/PersonaInstaller.wl b/Source/Chatbook/PersonaInstaller.wl index bdeae290..ae89303c 100644 --- a/Source/Chatbook/PersonaInstaller.wl +++ b/Source/Chatbook/PersonaInstaller.wl @@ -7,6 +7,7 @@ BeginPackage[ "Wolfram`Chatbook`PersonaInstaller`" ]; `GetInstalledResourcePersonaData; `GetInstalledResourcePersonas; `PersonaInstallFromResourceSystem; +`PersonaInstallFromURL; `PersonaInstall; Begin[ "`Private`" ]; @@ -88,6 +89,110 @@ GetInstalledResourcePersonaData[ ] := catchMine @ Enclose[ GetInstalledResourcePersonaData // endDefinition; +(* ::**************************************************************************************************************:: *) +(* ::Section::Closed:: *) +(*PersonaInstallFromURL*) +PersonaInstallFromURL // beginDefinition; + +PersonaInstallFromURL[ ] := catchMine @ Enclose[ + Module[ { url }, + + url = ConfirmMatch[ + DefinitionNotebookClient`FancyInputString[ "Prompt", "Enter a URL" ], (* FIXME: needs custom dialog *) + _String|$Canceled, + "InputString" + ]; + + If[ url === $Canceled, + $Canceled, + ConfirmBy[ PersonaInstallFromURL @ url, AssociationQ, "Install" ] + ] + ], + throwInternalFailure[ PersonaInstallFromURL[ ], ## ] & +]; + +PersonaInstallFromURL[ url_String ] := Enclose[ + Module[ { ro, file, data }, + ro = ConfirmMatch[ resourceFromURL @ url, _ResourceObject, "ResourceObject" ]; + ConfirmAssert[ ro[ "ResourceType" ] === "Prompt", "ResourceType" ]; + file = ConfirmBy[ PersonaInstall @ ro, FileExistsQ, "PersonaInstall" ]; + data = ConfirmBy[ getPersonaFile @ file, AssociationQ, "GetPersonaFile" ]; + Block[ { SystemOpen = Null & }, PersonaInstallFromResourceSystem[ ] ]; + data + ], + throwInternalFailure[ PersonaInstallFromURL @ url, ## ] & +]; + +PersonaInstallFromURL // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*resourceFromURL*) +resourceFromURL // beginDefinition; + +resourceFromURL[ url_String ] := Block[ { PrintTemporary }, + Quiet[ resourceFromURL0 @ url, { CloudObject::cloudnf, Lookup::invrl, ResourceObject::notfname } ] +]; + +resourceFromURL // endDefinition; + +resourceFromURL0 // beginDefinition; +resourceFromURL0[ url_String ] := With[ { ro = ResourceObject @ url }, ro /; promptResourceQ ]; +resourceFromURL0[ url_String ] := scrapeResourceFromShingle @ url; +resourceFromURL0 // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*scrapeResourceFromShingle*) +scrapeResourceFromShingle // beginDefinition; +(* TODO: we should have something in RSC to do this cleaner/better *) +scrapeResourceFromShingle[ url_String ] := Enclose[ + Module[ { resp, bytes, xml }, + + resp = ConfirmMatch[ URLRead @ url, _HTTPResponse, "URLRead" ]; + ConfirmAssert[ resp[ "StatusCode" ] === 200, "StatusCode" ]; + bytes = ConfirmBy[ resp[ "BodyByteArray" ], ByteArrayQ, "BodyByteArray" ]; + + xml = ConfirmMatch[ + Quiet @ ImportByteArray[ bytes, { "HTML", "XMLObject" } ], + XMLObject[ ___ ][ ___ ], + "XML" + ]; + + ConfirmBy[ + FirstCase[ + xml + , + XMLElement[ "div", { ___, "data-resource-uuid" -> uuid_String, ___ }, _ ] :> + With[ { ro = Quiet @ ResourceObject @ uuid }, ro /; promptResourceQ @ ro ] + , + FirstCase[ + xml + , + XMLElement[ "div", { ___, "data-clipboard-text" -> c2c_String, ___ }, _ ] :> + With[ { ro = Quiet @ ToExpression[ c2c, InputForm ] }, ro /; promptResourceQ @ ro ] + , + Missing[ ] + , + Infinity + ], + Infinity + ], + promptResourceQ, + "ResourceObject" + ] + ], + throwInternalFailure[ scrapeResourceFromShingle @ url, ## ] & +]; + +scrapeResourceFromShingle // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*promptResourceQ*) +promptResourceQ[ ro_ResourceObject ] := ro[ "ResourceType" ] === "Prompt"; +promptResourceQ[ ___ ] := False; + (* ::**************************************************************************************************************:: *) (* ::Section::Closed:: *) (*PersonaInstallFromResourceSystem*) @@ -242,7 +347,7 @@ formatDescription // endDefinition; formatVersion // beginDefinition; formatVersion[ _Missing ] := ""; -formatVersion[ version_String ] := version; +formatVersion[ version: _String|None ] := version; formatVersion // endDefinition; formatIcon // beginDefinition;