diff --git a/Assets/DisplayFunctions.wxf b/Assets/DisplayFunctions.wxf index 14ed96bb..674588ee 100644 Binary files a/Assets/DisplayFunctions.wxf and b/Assets/DisplayFunctions.wxf differ diff --git a/Assets/Icons.wxf b/Assets/Icons.wxf index 277b1f24..a88ca1c8 100644 Binary files a/Assets/Icons.wxf and b/Assets/Icons.wxf differ diff --git a/Developer/Resources/Icons/ServiceIconAnthropic.wl b/Developer/Resources/Icons/ServiceIconAnthropic.wl new file mode 100644 index 00000000..dec08cd1 --- /dev/null +++ b/Developer/Resources/Icons/ServiceIconAnthropic.wl @@ -0,0 +1,42 @@ +Graphics[ + { + Thickness[ 0.0625 ], + Style[ + { + FilledCurve[ + { + { { 0, 2, 0 }, { 0, 1, 0 }, { 0, 1, 0 }, { 0, 1, 0 }, { 0, 1, 0 }, { 0, 1, 0 }, { 0, 1, 0 }, { 0, 1, 0 } }, + { { 0, 2, 0 }, { 0, 1, 0 }, { 0, 1, 0 } } + }, + { + { + { 5.0525, 13.543 }, + { 1.1665, 3.5431 }, + { 3.3395, 3.5431 }, + { 4.1345, 5.6431 }, + { 8.1995, 5.6431 }, + { 8.9945, 3.5431 }, + { 11.167, 3.5431 }, + { 7.2805, 13.543 }, + { 5.0525, 13.543 } + }, + { { 6.1675, 11.015 }, { 7.4975, 7.5011 }, { 4.8375, 7.5011 }, { 6.1675, 11.015 } } + } + ] + }, + FaceForm @ RGBColor[ 0.12157, 0.12157, 0.11765, 1.0 ] + ], + Style[ + { + FilledCurve[ + { { { 0, 2, 0 }, { 0, 1, 0 }, { 0, 1, 0 }, { 0, 1, 0 } } }, + { { { 9.1665, 13.543 }, { 13.042, 3.5432 }, { 15.167, 3.5432 }, { 11.293, 13.543 }, { 9.1665, 13.543 } } } + ] + }, + FaceForm @ RGBColor[ 0.12157, 0.12157, 0.11765, 1.0 ] + ] + }, + ImageSize -> { 17.0, 17.0 }, + PlotRange -> { { -0.5, 16.5 }, { -0.5, 16.5 } }, + AspectRatio -> Automatic +] \ No newline at end of file diff --git a/Developer/Resources/Icons/ServiceIconOpenAI.wl b/Developer/Resources/Icons/ServiceIconOpenAI.wl new file mode 100644 index 00000000..107ffc3c --- /dev/null +++ b/Developer/Resources/Icons/ServiceIconOpenAI.wl @@ -0,0 +1,311 @@ +Graphics[ + { + Thickness[ 0.0625 ], + Style[ + { + FilledCurve[ + { + { { 0, 2, 0 }, { 0, 1, 0 }, { 0, 1, 0 }, { 0, 1, 0 }, { 0, 1, 0 }, { 0, 1, 0 } }, + { + { 0, 2, 0 }, + { 0, 1, 0 }, + { 1, 3, 3 }, + { 1, 3, 3 }, + { 1, 3, 3 }, + { 1, 3, 3 }, + { 1, 3, 3 }, + { 0, 1, 0 }, + { 1, 3, 3 }, + { 1, 3, 3 }, + { 0, 1, 0 } + }, + { + { 1, 4, 3 }, + { 0, 1, 0 }, + { 1, 3, 3 }, + { 1, 3, 3 }, + { 0, 1, 0 }, + { 0, 1, 0 }, + { 0, 1, 0 }, + { 1, 3, 3 }, + { 1, 3, 3 }, + { 1, 3, 3 }, + { 1, 3, 3 } + }, + { { 0, 2, 0 }, { 0, 1, 0 }, { 0, 1, 0 }, { 1, 3, 3 }, { 1, 3, 3 }, { 1, 3, 3 }, { 1, 3, 3 }, { 0, 1, 0 }, { 1, 3, 3 }, { 1, 3, 3 } }, + { { 1, 4, 3 }, { 0, 1, 0 }, { 0, 1, 0 }, { 1, 3, 3 }, { 1, 3, 3 }, { 0, 1, 0 }, { 0, 1, 0 }, { 0, 1, 0 }, { 1, 3, 3 }, { 1, 3, 3 } }, + { { 1, 4, 3 }, { 1, 3, 3 }, { 0, 1, 0 }, { 1, 3, 3 }, { 1, 3, 3 }, { 0, 1, 0 }, { 0, 1, 0 }, { 0, 1, 0 }, { 1, 3, 3 }, { 1, 3, 3 } }, + { { 1, 4, 3 }, { 1, 3, 3 }, { 0, 1, 0 }, { 1, 3, 3 }, { 1, 3, 3 }, { 0, 1, 0 }, { 0, 1, 0 }, { 0, 1, 0 }, { 1, 3, 3 } }, + { + { 1, 4, 3 }, + { 1, 3, 3 }, + { 1, 3, 3 }, + { 1, 3, 3 }, + { 1, 3, 3 }, + { 1, 3, 3 }, + { 1, 3, 3 }, + { 1, 3, 3 }, + { 1, 3, 3 }, + { 1, 3, 3 }, + { 1, 3, 3 }, + { 1, 3, 3 }, + { 1, 3, 3 }, + { 1, 3, 3 }, + { 1, 3, 3 }, + { 1, 3, 3 }, + { 1, 3, 3 }, + { 1, 3, 3 }, + { 1, 3, 3 }, + { 1, 3, 3 }, + { 1, 3, 3 }, + { 1, 3, 3 }, + { 1, 3, 3 }, + { 1, 3, 3 } + } + }, + { + { + { 6.3529, 8.9548 }, + { 8.0239, 9.9098 }, + { 9.6949, 8.9548 }, + { 9.6949, 7.0448 }, + { 8.0239, 6.0898 }, + { 6.3529, 7.0448 }, + { 6.3529, 8.9548 } + }, + { + { 5.6849, 7.4268 }, + { 4.3959, 8.1908 }, + { 4.3959, 11.772 }, + { 4.3959, 12.297 }, + { 4.5389, 12.87 }, + { 4.8249, 13.3 }, + { 5.1119, 13.777 }, + { 5.5409, 14.111 }, + { 6.0189, 14.35 }, + { 6.4959, 14.589 }, + { 7.0689, 14.684 }, + { 7.5939, 14.589 }, + { 8.1189, 14.541 }, + { 8.6449, 14.302 }, + { 9.0739, 13.968 }, + { 9.0739, 13.968 }, + { 9.0269, 13.92 }, + { 8.9789, 13.92 }, + { 5.9229, 12.154 }, + { 5.8279, 12.106 }, + { 5.7799, 12.058 }, + { 5.7319, 11.963 }, + { 5.6849, 11.867 }, + { 5.6849, 11.82 }, + { 5.6849, 11.724 }, + { 5.6849, 7.4268 } + }, + { + { 13.705, 10.101 }, + { 13.705, 10.101 }, + { 13.658, 10.149 }, + { 13.61, 10.149 }, + { 10.554, 11.915 }, + { 10.459, 11.963 }, + { 10.411, 11.963 }, + { 10.316, 11.963 }, + { 10.22, 11.963 }, + { 10.125, 11.963 }, + { 10.077, 11.915 }, + { 6.3529, 9.7668 }, + { 6.3529, 11.247 }, + { 9.4559, 13.061 }, + { 9.9339, 13.347 }, + { 10.459, 13.443 }, + { 11.032, 13.443 }, + { 11.557, 13.443 }, + { 12.082, 13.252 }, + { 12.56, 12.918 }, + { 12.989, 12.583 }, + { 13.371, 12.154 }, + { 13.562, 11.676 }, + { 13.753, 11.199 }, + { 13.801, 10.626 }, + { 13.705, 10.101 } + }, + { + { 12.416, 8.1908 }, + { 8.6919, 10.34 }, + { 9.9809, 11.103 }, + { 13.037, 9.3368 }, + { 13.514, 9.0498 }, + { 13.896, 8.6688 }, + { 14.135, 8.1908 }, + { 14.374, 7.7138 }, + { 14.517, 7.1888 }, + { 14.469, 6.6158 }, + { 14.421, 6.0898 }, + { 14.231, 5.5648 }, + { 13.896, 5.1358 }, + { 13.562, 4.7058 }, + { 13.132, 4.3718 }, + { 12.607, 4.1808 }, + { 12.607, 7.8088 }, + { 12.607, 7.9048 }, + { 12.607, 7.9998 }, + { 12.56, 8.0478 }, + { 12.56, 8.0478 }, + { 12.512, 8.1428 }, + { 12.416, 8.1908 } + }, + { + { 1.8649, 10.626 }, + { 2.1989, 11.199 }, + { 2.7249, 11.629 }, + { 3.3449, 11.867 }, + { 3.3449, 11.772 }, + { 3.3449, 8.2388 }, + { 3.3449, 8.1428 }, + { 3.3449, 8.0478 }, + { 3.3929, 7.9998 }, + { 3.4409, 7.9048 }, + { 3.4879, 7.8568 }, + { 3.5839, 7.8088 }, + { 7.3079, 5.6608 }, + { 6.0189, 4.8968 }, + { 2.9629, 6.6628 }, + { 2.2949, 7.0448 }, + { 1.8169, 7.6658 }, + { 1.6259, 8.3818 }, + { 1.4359, 9.0978 }, + { 1.4829, 9.9578 }, + { 1.8649, 10.626 } + }, + { + { 2.6769, 3.9898 }, + { 2.3429, 4.5628 }, + { 2.1989, 5.2308 }, + { 2.3429, 5.8988 }, + { 2.3429, 5.8988 }, + { 2.3899, 5.8518 }, + { 2.4379, 5.8518 }, + { 5.4939, 4.0848 }, + { 5.5889, 4.0378 }, + { 5.6369, 4.0378 }, + { 5.7319, 4.0378 }, + { 5.8279, 4.0378 }, + { 5.9229, 4.0378 }, + { 5.9709, 4.0848 }, + { 9.6949, 6.2338 }, + { 9.6949, 4.7538 }, + { 6.5919, 2.9388 }, + { 5.9229, 2.5568 }, + { 5.1589, 2.4618 }, + { 4.4429, 2.6528 }, + { 3.6789, 2.8438 }, + { 3.0589, 3.3208 }, + { 2.6769, 3.9898 } + }, + { + { 8.8359, 1.3638 }, + { 8.0719, 1.3638 }, + { 7.4989, 1.6028 }, + { 6.9739, 2.0318 }, + { 6.9739, 2.0318 }, + { 7.0209, 2.0798 }, + { 7.0689, 2.0798 }, + { 10.125, 3.8468 }, + { 10.22, 3.8938 }, + { 10.268, 3.9418 }, + { 10.316, 4.0378 }, + { 10.363, 4.1328 }, + { 10.363, 4.1808 }, + { 10.363, 4.2758 }, + { 10.363, 8.5728 }, + { 11.652, 7.8088 }, + { 11.652, 4.2758 }, + { 11.7, 2.6048 }, + { 10.363, 1.3638 }, + { 8.8359, 1.3638 } + }, + { + { 14.565, 9.3848 }, + { 14.756, 9.9098 }, + { 14.803, 10.435 }, + { 14.756, 10.96 }, + { 14.708, 11.485 }, + { 14.517, 12.011 }, + { 14.278, 12.488 }, + { 13.849, 13.204 }, + { 13.228, 13.777 }, + { 12.512, 14.111 }, + { 11.748, 14.445 }, + { 10.936, 14.541 }, + { 10.125, 14.35 }, + { 9.7429, 14.732 }, + { 9.3129, 15.066 }, + { 8.8359, 15.305 }, + { 8.3579, 15.543 }, + { 7.7849, 15.639 }, + { 7.2599, 15.639 }, + { 6.4479, 15.639 }, + { 5.6369, 15.4 }, + { 4.9679, 14.923 }, + { 4.2999, 14.445 }, + { 3.8229, 13.777 }, + { 3.5839, 13.013 }, + { 3.0109, 12.87 }, + { 2.5339, 12.631 }, + { 2.0559, 12.345 }, + { 1.6259, 12.011 }, + { 1.2919, 11.581 }, + { 1.0059, 11.151 }, + { 0.5759, 10.435 }, + { 0.4329, 9.6228 }, + { 0.5279, 8.8118 }, + { 0.6239, 7.9998 }, + { 0.9579, 7.2358 }, + { 1.4829, 6.6158 }, + { 1.2919, 6.0898 }, + { 1.2449, 5.5648 }, + { 1.2919, 5.0398 }, + { 1.3399, 4.5148 }, + { 1.5309, 3.9898 }, + { 1.7699, 3.5118 }, + { 2.1989, 2.7958 }, + { 2.8199, 2.2228 }, + { 3.5359, 1.8888 }, + { 4.2999, 1.5548 }, + { 5.1119, 1.4588 }, + { 5.9229, 1.6498 }, + { 6.3049, 1.2678 }, + { 6.7349, 0.9338 }, + { 7.2119, 0.6958 }, + { 7.6899, 0.4568 }, + { 8.2629, 0.3608 }, + { 8.7879, 0.3608 }, + { 9.5989, 0.3608 }, + { 10.411, 0.5998 }, + { 11.08, 1.0768 }, + { 11.748, 1.5548 }, + { 12.225, 2.2228 }, + { 12.464, 2.9868 }, + { 12.989, 3.0828 }, + { 13.514, 3.3208 }, + { 13.944, 3.6558 }, + { 14.374, 3.9898 }, + { 14.756, 4.3718 }, + { 14.994, 4.8488 }, + { 15.424, 5.5648 }, + { 15.567, 6.3768 }, + { 15.472, 7.1888 }, + { 15.376, 7.9998 }, + { 15.09, 8.7638 }, + { 14.565, 9.3848 } + } + } + ] + }, + FaceForm @ RGBColor[ 0.2, 0.2, 0.2, 1.0 ] + ] + }, + ImageSize -> { 17.0, 17.0 }, + PlotRange -> { { -0.5, 16.5 }, { -0.5, 16.5 } }, + AspectRatio -> Automatic +] \ No newline at end of file diff --git a/Developer/Resources/Icons/ServiceIconPaLM.wl b/Developer/Resources/Icons/ServiceIconPaLM.wl new file mode 100644 index 00000000..7d5c4463 --- /dev/null +++ b/Developer/Resources/Icons/ServiceIconPaLM.wl @@ -0,0 +1,118 @@ +Graphics[ + { + Thickness[ 0.0625 ], + Style[ + { + FilledCurve[ + { { { 1, 4, 3 }, { 0, 1, 0 }, { 1, 3, 3 }, { 1, 3, 3 }, { 0, 1, 0 }, { 0, 1, 0 }, { 1, 3, 3 } } }, + { + { + { 8.1487, 1.0 }, + { 10.039, 1.0 }, + { 11.62, 1.63 }, + { 12.775, 2.698 }, + { 10.523, 4.448 }, + { 9.8927, 4.027 }, + { 9.0937, 3.771 }, + { 8.1487, 3.771 }, + { 6.3227, 3.771 }, + { 4.7767, 5.002 }, + { 4.2227, 6.664 }, + { 1.9017, 6.664 }, + { 1.9017, 4.862 }, + { 3.0507, 2.575 }, + { 5.4127, 1.0 }, + { 8.1487, 1.0 } + } + } + ] + }, + FaceForm @ RGBColor[ 0.20392, 0.65882, 0.32549, 1.0 ] + ], + Style[ + { + FilledCurve[ + { { { 1, 4, 3 }, { 0, 1, 0 }, { 0, 1, 0 }, { 0, 1, 0 }, { 1, 3, 3 }, { 0, 1, 0 }, { 0, 1, 0 }, { 1, 3, 3 } } }, + { + { + { 14.851, 7.8425 }, + { 14.851, 8.3035 }, + { 14.81, 8.7405 }, + { 14.74, 9.1665 }, + { 8.1483, 9.1665 }, + { 8.1483, 6.5355 }, + { 11.923, 6.5355 }, + { 11.753, 5.6725 }, + { 11.258, 4.9435 }, + { 10.523, 4.4475 }, + { 10.523, 2.6975 }, + { 12.774, 2.6975 }, + { 14.093, 3.9165 }, + { 14.851, 5.7135 }, + { 14.851, 7.8425 } + } + } + ] + }, + FaceForm @ RGBColor[ 0.25882, 0.52157, 0.95686, 1.0 ] + ], + Style[ + { + FilledCurve[ + { { { 1, 4, 3 }, { 1, 3, 3 }, { 0, 1, 0 }, { 0, 1, 0 }, { 1, 3, 3 }, { 1, 3, 3 }, { 0, 1, 0 } } }, + { + { + { 4.2229, 6.6642 }, + { 4.0769, 7.0842 }, + { 4.0009, 7.5332 }, + { 4.0009, 8.0002 }, + { 4.0009, 8.4662 }, + { 4.0829, 8.9162 }, + { 4.2229, 9.3362 }, + { 4.2229, 11.138 }, + { 1.9009, 11.138 }, + { 1.4229, 10.193 }, + { 1.1489, 9.1312 }, + { 1.1489, 8.0002 }, + { 1.1489, 6.8682 }, + { 1.4229, 5.8062 }, + { 1.9009, 4.8622 }, + { 4.2229, 6.6642 } + } + } + ] + }, + FaceForm @ RGBColor[ 0.98431, 0.73725, 0.019608, 1.0 ] + ], + Style[ + { + FilledCurve[ + { { { 1, 4, 3 }, { 0, 1, 0 }, { 1, 3, 3 }, { 1, 3, 3 }, { 0, 1, 0 }, { 1, 3, 3 } } }, + { + { + { 8.1487, 12.229 }, + { 9.1817, 12.229 }, + { 10.103, 11.873 }, + { 10.832, 11.179 }, + { 12.827, 13.174 }, + { 11.62, 14.306 }, + { 10.039, 15.0 }, + { 8.1487, 15.0 }, + { 5.4127, 15.0 }, + { 3.0507, 13.425 }, + { 1.9017, 11.138 }, + { 4.2227, 9.3362 }, + { 4.7767, 10.998 }, + { 6.3227, 12.229 }, + { 8.1487, 12.229 } + } + } + ] + }, + FaceForm @ RGBColor[ 0.91765, 0.26275, 0.20784, 1.0 ] + ] + }, + ImageSize -> { 17.0, 17.0 }, + PlotRange -> { { -0.5, 16.5 }, { -0.5, 16.5 } }, + AspectRatio -> Automatic +] \ No newline at end of file diff --git a/Developer/StylesheetBuilder.wl b/Developer/StylesheetBuilder.wl index bda263a0..f9954595 100644 --- a/Developer/StylesheetBuilder.wl +++ b/Developer/StylesheetBuilder.wl @@ -569,22 +569,24 @@ $ChatbookStylesheet = Notebook[ BuildChatbookStylesheet[ ] := BuildChatbookStylesheet @ $styleSheetTarget; BuildChatbookStylesheet[ target_ ] := - Module[ { exported }, - exported = Export[ target, $ChatbookStylesheet, "NB" ]; - PacletInstall[ "Wolfram/PacletCICD" ]; - Needs[ "Wolfram`PacletCICD`" -> None ]; - SetOptions[ - ResourceFunction[ "SaveReadableNotebook" ], - "RealAccuracy" -> 10, - "ExcludedNotebookOptions" -> { - ExpressionUUID, - FrontEndVersion, - WindowMargins, - WindowSize - } - ]; - Wolfram`PacletCICD`FormatNotebooks @ exported; - exported + Block[ { $Context = "Global`", $ContextPath = { "System`", "Global`" } }, + Module[ { exported }, + exported = Export[ target, $ChatbookStylesheet, "NB" ]; + PacletInstall[ "Wolfram/PacletCICD" ]; + Needs[ "Wolfram`PacletCICD`" -> None ]; + SetOptions[ + ResourceFunction[ "SaveReadableNotebook" ], + "RealAccuracy" -> 10, + "ExcludedNotebookOptions" -> { + ExpressionUUID, + FrontEndVersion, + WindowMargins, + WindowSize + } + ]; + Wolfram`PacletCICD`FormatNotebooks @ exported; + exported + ] ]; diff --git a/FrontEnd/StyleSheets/Chatbook.nb b/FrontEnd/StyleSheets/Chatbook.nb index 0e952e2c..08db8e93 100644 --- a/FrontEnd/StyleSheets/Chatbook.nb +++ b/FrontEnd/StyleSheets/Chatbook.nb @@ -746,6 +746,11 @@ Notebook[ Magnification -> 1, FontSize -> 0.1 ], + PrivateCellOptions -> { + "AccentStyle" -> { + CellTrayWidgets -> <|"ChatIncluded" -> <|"Condition" -> True|>|> + } + }, TaggingRules -> <|"ChatNotebookSettings" -> <||>|>, CellTrayWidgets -> <| "GearMenu" -> <|"Condition" -> False|>, @@ -1152,16 +1157,11 @@ Notebook[ } ] } - |>, - PrivateCellOptions -> { - "AccentStyle" -> { - CellTrayWidgets -> <|"ChatIncluded" -> <|"Condition" -> True|>|> - } - } + |> ], Cell[ StyleData["ChatStyleSheetInformation"], - TaggingRules -> <|"StyleSheetVersion" -> "1.3.1.3910074547"|> + TaggingRules -> <|"StyleSheetVersion" -> "1.3.4.3910935567"|> ], Cell[ StyleData["Text"], @@ -11703,6 +11703,415 @@ Notebook[ ]) } ], + Cell[ + StyleData["ServiceIconAnthropic"], + TemplateBoxOptions -> { + DisplayFunction -> + (Function[ + GraphicsBox[ + { + Thickness[0.0625], + StyleBox[ + { + FilledCurveBox[ + { + { + {0, 2, 0}, + {0, 1, 0}, + {0, 1, 0}, + {0, 1, 0}, + {0, 1, 0}, + {0, 1, 0}, + {0, 1, 0}, + {0, 1, 0} + }, + {{0, 2, 0}, {0, 1, 0}, {0, 1, 0}} + }, + { + { + {5.0525, 13.543}, + {1.1665, 3.5431}, + {3.3395, 3.5431}, + {4.1345, 5.6431}, + {8.1995, 5.6431}, + {8.9945, 3.5431}, + {11.167, 3.5431}, + {7.2805, 13.543}, + {5.0525, 13.543} + }, + { + {6.1675, 11.015}, + {7.4975, 7.5011}, + {4.8375, 7.5011}, + {6.1675, 11.015} + } + } + ] + }, + {FaceForm[RGBColor[0.12157, 0.12157, 0.11765, 1.0]]}, + StripOnInput -> False + ], + StyleBox[ + { + FilledCurveBox[ + {{{0, 2, 0}, {0, 1, 0}, {0, 1, 0}, {0, 1, 0}}}, + { + { + {9.1665, 13.543}, + {13.042, 3.5432}, + {15.167, 3.5432}, + {11.293, 13.543}, + {9.1665, 13.543} + } + } + ] + }, + {FaceForm[RGBColor[0.12157, 0.12157, 0.11765, 1.0]]}, + StripOnInput -> False + ] + }, + ImageSize -> {17.0, 17.0}, + PlotRange -> {{-0.5, 16.5}, {-0.5, 16.5}}, + AspectRatio -> Automatic + ] + ]) + } + ], + Cell[ + StyleData["ServiceIconOpenAI"], + TemplateBoxOptions -> { + DisplayFunction -> + (Function[ + GraphicsBox[ + { + Thickness[0.0625], + StyleBox[ + { + FilledCurveBox[ + { + { + {0, 2, 0}, + {0, 1, 0}, + {0, 1, 0}, + {0, 1, 0}, + {0, 1, 0}, + {0, 1, 0} + }, + { + {0, 2, 0}, + {0, 1, 0}, + {1, 3, 3}, + {1, 3, 3}, + {1, 3, 3}, + {1, 3, 3}, + {1, 3, 3}, + {0, 1, 0}, + {1, 3, 3}, + {1, 3, 3}, + {0, 1, 0} + }, + { + {1, 4, 3}, + {0, 1, 0}, + {1, 3, 3}, + {1, 3, 3}, + {0, 1, 0}, + {0, 1, 0}, + {0, 1, 0}, + {1, 3, 3}, + {1, 3, 3}, + {1, 3, 3}, + {1, 3, 3} + }, + { + {0, 2, 0}, + {0, 1, 0}, + {0, 1, 0}, + {1, 3, 3}, + {1, 3, 3}, + {1, 3, 3}, + {1, 3, 3}, + {0, 1, 0}, + {1, 3, 3}, + {1, 3, 3} + }, + { + {1, 4, 3}, + {0, 1, 0}, + {0, 1, 0}, + {1, 3, 3}, + {1, 3, 3}, + {0, 1, 0}, + {0, 1, 0}, + {0, 1, 0}, + {1, 3, 3}, + {1, 3, 3} + }, + { + {1, 4, 3}, + {1, 3, 3}, + {0, 1, 0}, + {1, 3, 3}, + {1, 3, 3}, + {0, 1, 0}, + {0, 1, 0}, + {0, 1, 0}, + {1, 3, 3}, + {1, 3, 3} + }, + { + {1, 4, 3}, + {1, 3, 3}, + {0, 1, 0}, + {1, 3, 3}, + {1, 3, 3}, + {0, 1, 0}, + {0, 1, 0}, + {0, 1, 0}, + {1, 3, 3} + }, + { + {1, 4, 3}, + {1, 3, 3}, + {1, 3, 3}, + {1, 3, 3}, + {1, 3, 3}, + {1, 3, 3}, + {1, 3, 3}, + {1, 3, 3}, + {1, 3, 3}, + {1, 3, 3}, + {1, 3, 3}, + {1, 3, 3}, + {1, 3, 3}, + {1, 3, 3}, + {1, 3, 3}, + {1, 3, 3}, + {1, 3, 3}, + {1, 3, 3}, + {1, 3, 3}, + {1, 3, 3}, + {1, 3, 3}, + {1, 3, 3}, + {1, 3, 3}, + {1, 3, 3} + } + }, + { + { + {6.3529, 8.9548}, + {8.0239, 9.9098}, + {9.6949, 8.9548}, + {9.6949, 7.0448}, + {8.0239, 6.0898}, + {6.3529, 7.0448}, + {6.3529, 8.9548} + }, + CompressedData[ + "\n1:eJxTTMoPSmViYGCQAmIQrSj/JSdsr5iDwW51fu6tsg7CnxzPp00VdAhSX9C5\nIVEBzldla5zq3K0O53+65JskMEPDAcS9qi7kYMt1fXHBXk2HjQ9fTt3kI+ww\nayYIaDl0bQAKFIk4PP+98uOlXm0HkLEPNcUc3LZ9/nvFQsfh3uT21ihhCQdj\nENis41AZscL07G9JB8UNRRkTdXUdBI/v2tHrJuMw8W2NvWmcrsOqhJAg9QQ5\nuLzS32+lD2wUHBa5Ag0U0XWoSTQKNfBSdNhyomzf/Fk6Dt6RbRbXVJUcwvh0\nN819r43BB3GP8io5AF0P9IK2wy1poAlfFeH8R0Dv8GwUd2DSbhe76afhwJDf\nyHLUX9yh+cCpha5mGg5Mszik58mJO7CADJTVcGA92m9Y/lbMYbIEUOStugMs\nfKc5d+c8343gLwEZvxjBXzb7iMKGIgQfFh8AiR6qEA==\n " + ], + CompressedData[ + "\n1:eJxTTMoPSmViYGCQAmIQrRnTf+hrhrbDwVMLXbcZqzig8807HROeBmg72JvG\n7fL0UXHYIdf6OtACwT//PfjxUmlVB5F17g+rrqg7AEV5mF6rOEyWYAnje6vu\ncN+/d3reJQQ/X6j5wKmFCH6hLdf1xQUIPgMIOCD4jVOdu3PUVeDmz+KQnheX\nKengplrKNKtDGc6PsNxyoqxOzUGjrmdn9kslhzUyUSnW8loOzBXcKhr3lB26\nc57/XrlRC+4+kLcuPNJyWPXxkm+SgBqcX75vvpS+rDqc7wLSqKnhIK1/V4Wt\nUctBHuj9HXKaDntKgC68punwrgYYEr80HS4q3f5Zp6XpcBoYbJ/3ajkwabeL\n3fTTcAA5a5+8toO6IQfQSeoOK455m3c2ajvcPAcMuFQ1iPgsbQdgKAk1O6hi\nhD8ARCyk8Q==\n " + ], + CompressedData[ + "\n1:eJxTTMoPSmViYGAQB2IQvch12+e/VzQcgtQXdG5IVHDIM2nY7pCk6LDO/WGV\nyDoVh69ekW0W35Qdbv+sy9pjouaQ+/z3yo9CWg6vpm7iKVyj5LDLk4dJm13b\noXbdtqR6SSWHGXlCzQdOaTvoKsp/yQlTdGh9HbhDzlUHbn5xxsS3Nft1HDi7\n5JPf3ZJzSBKIsNzCoeuQ/6H1ZMhBGYfHS2cfUfig43CjsdhtSpmUQ4r1ff/e\n6zoOKjv/tH+JlnAAGr7QtUzHAaQ9ykkMbt8b/d3q/N0iDpZbTpTtk9d2MBE0\ns9l7Scjhrgpb41RnLYeKOYuUd9YIOoCsO2Gm6TCrHCQgAOe/YgHpkIfzn61T\nfdI8D8E/rmk16fR/eQd5sIc0HZLrb9pWSihg8BdI6QOt1HS4vOexiKynggN6\n+AIA6SqZow==\n " + ], + CompressedData[ + "\n1:eJxTTMoPSmViYGAQBWIQnfb9SeLCa3/te6fnCTU7qDropEo+ipjO6HDz3Pfg\nx6lqDlO+scXPOMPqYBq3y5PHSd2hSz75XdQhLodpzt05z3cj+KpsjVOduxH8\nixNj/jlXKcD5l/c8FpH1RPCT62/aVkooOLSdDDm4QoXb4bim1aTT/+Udmord\npnxr43Z4tk71SfM8eQeP/bWyFs+5HV7cXPMrJlfewUE48fDl1TwOr1hMBM1s\n5B3eHLBU9rKWhZizRMzh3uT21ihhCQeg5N6gacIOoj1er1i2sDvsVufn3rpM\nykFl55/2L9FMDmWF0rwPdGUc8hYz7mEV+msPlKw8vlLO4Sf/y+3rmX/ZHwQa\nW31YweG9hat70c9v9n8kiq8LGSk51F7YHPl153f7reY/DqW8UnZADz8Aqo+U\nXg==\n " + ], + CompressedData[ + "\n1:eJxTTMoPSmViYGAQB2IQPcNHtMcri9WhaqmOs8xrfgeF5HdRTvuYHEIPrlji\n5yDkoJMq+ShiOqPDuVXnr4a9EYHLu035xhY/QxyD/2yd6pNmOWYHkLBdhrjD\nowjx7RcbEHyXX29fH/gp6jD/LMhAAQfVUqZZHNFiDrvV+bm3qgk4KHtVN+v3\nIPisR/sNy98i+Az5jSxH/cXh/Ecvp27i2YjgP9jHN8f4kTjcfLHfp9+dTFZ2\nkLVId8l8LwHnG4EcyizsALJeOk7K4f4D7skrm9jh5mX5fu4LLmFxyA2rXbdt\nkYjDivBTRkc2Mjt8m353cvtRQYekhddM3luwOjBzdskn5/E6yLx+ZCZ1gM2B\n94HuhAUVHA4hJSrT/0/gckAPXwAX85Sr\n " + ], + CompressedData[ + "\n1:eJxTTMoPSmViYGAQAWIQ/bp4q+jv1YoOXX1PPslf+mpvoLVS+IKKApx/8qn9\nkvv/ZB2+lz6YI7j0p72BzzIut6fSDuZSB6IVHBkc0PnCnxzPp4nKOBgf2aiX\nt5jBQfD4rh29bgg+Awg4qDjsYRUSsT/G51Boy3V9cYGKQ3Og59wGNX6HD8uP\neZt3qjjU/7YqONfB75Av1Hzg1EIVh93q/Nxb1QQcEp5eULq9U8VhziLlnX/a\nEfxZ5SARBL/m04aAbClBOB/sTlVFh+e/V3685Kvu8IrFRNDMRh7Oh6lPAwN1\nh7TvTxIXXmOB64eFB3p4AQDL0Y6N\n " + ], + CompressedData[ + "\n1:eJxdVHtQVGUUXwlWopV3y8Iswt1dYJfdu5thBPj4fomCmIo8SiW0knZKR1rk\nYQ4SDeGKUYKISAUFoeE4OEpoKYVJFpPPRsQJbEeeatEq7Y5Ly2OB7t2dvX/0\nzdz55nzfOeee3+/8zkdt06VqXXg8XhLzsftQof/pRIrGdp6uxLVLgg0Ha9/1\nLaURfXPJ+u5uKVa88eCWpI5GiN6YcqFfxt0/SrmwUG8MQ7fEMFm8gwZrpUyE\nY5v3pthz7jR8SzuvfeUmR0G1yPXVChXYNIZJOZrrfgltbVOintly34kEG92R\nH4k/p0+abx9UojFIc0/GVyDhuyczd2JUsIcVR+B4zjKP3hEVTokz3loyEIam\nVYyDPw0euyBDFLvOqlCjPZC1pUgKtowru2nE/t6VvbhRAh1bEKWGMe/bZ6db\nKGRWXh6X16vhXRjdsvx8KDrymUKFGhzQZ/R0SEPwfZLARZmowZixM1bqEczZ\ne0s1a5uvBXK2dA1z8okQN2+wS41Di96jqQE/fMCgqu6jEbB6g00U5cPVb+W/\n/unaGk8OL/ze/LmnRQC67Yt/TvAj4RO99GKq0B2M97Gciwr8cWoqM3uVq4P3\neAWyu2bb9ubysIt1aJVj0mv0/JmnpoiTb/dlda80tFgIS98eRQQ8f3zus0iR\nibCsJa8Lx+b9MQwjw8TZz0ue9VFN5wzk6gNyfGC5FDv6d5VX3B8kwexBA4Uk\n9Rw18+8I+VURd/j6XAhUdz5/GHxijKQdpSoKzGK8f+vs5vF2K+kryUs4sjuI\n+7+s3VZmeU2Ewq9V8WLjE1IekjWWsULI3Zv0V9N/kvuDV1U8HZczTjY+0vwQ\n4eWLfFnt3KHiCeKI88LQM9UnP0yxEQcOAVTawOFNtfMQ+pI568YWPuxxlXyw\n1stX5mHldp1JnySA2bKGgTpLnPyzKAoGJ0h569BoTa6/o+8brWR4tKZN8E0A\n9i1IPnNEO0VYNl6MCUQdg/Kv1RaSnhrR+JEpCOzWOvSYmIRMwj4x1ndXZc7G\nPyTNHglhBZcX4m/XxUzr7pGGpQxD+0MddQb0kp1PTza+XU1xtig8ze3Y81LO\nHkhmGntbBnb6KheNEIVdmOFg3Q7HmYlT/8767TrPk3N4KWaKqh7LYSif0Vrv\nz8dYEXlh65QCPf2DDHPuDp3OVyLdTqwHAu7+Zk0bUcKlw83XnyxAHhNddEkF\nJ9/O+d5T3yRtL/KBPb2NtstUqfUDk61MeFcNZz9tjIzXSTSYKbMYeksC8fFO\nVtlq6OwNFsOerlMNp35OJzIvjlqNL7cqrnuXUfj/+/Mf6Ef9+w==\n " + ] + } + ] + }, + {FaceForm[RGBColor[0.2, 0.2, 0.2, 1.0]]}, + StripOnInput -> False + ] + }, + ImageSize -> {17.0, 17.0}, + PlotRange -> {{-0.5, 16.5}, {-0.5, 16.5}}, + AspectRatio -> Automatic + ] + ]) + } + ], + Cell[ + StyleData["ServiceIconPaLM"], + TemplateBoxOptions -> { + DisplayFunction -> + (Function[ + GraphicsBox[ + { + Thickness[0.0625], + StyleBox[ + { + FilledCurveBox[ + { + { + {1, 4, 3}, + {0, 1, 0}, + {1, 3, 3}, + {1, 3, 3}, + {0, 1, 0}, + {0, 1, 0}, + {1, 3, 3} + } + }, + { + { + {8.1487, 1.0}, + {10.039, 1.0}, + {11.62, 1.63}, + {12.775, 2.698}, + {10.523, 4.448}, + {9.8927, 4.027}, + {9.0937, 3.771}, + {8.1487, 3.771}, + {6.3227, 3.771}, + {4.7767, 5.002}, + {4.2227, 6.664}, + {1.9017, 6.664}, + {1.9017, 4.862}, + {3.0507, 2.575}, + {5.4127, 1.0}, + {8.1487, 1.0} + } + } + ] + }, + {FaceForm[RGBColor[0.20392, 0.65882, 0.32549, 1.0]]}, + StripOnInput -> False + ], + StyleBox[ + { + FilledCurveBox[ + { + { + {1, 4, 3}, + {0, 1, 0}, + {0, 1, 0}, + {0, 1, 0}, + {1, 3, 3}, + {0, 1, 0}, + {0, 1, 0}, + {1, 3, 3} + } + }, + { + { + {14.851, 7.8425}, + {14.851, 8.3035}, + {14.81, 8.7405}, + {14.74, 9.1665}, + {8.1483, 9.1665}, + {8.1483, 6.5355}, + {11.923, 6.5355}, + {11.753, 5.6725}, + {11.258, 4.9435}, + {10.523, 4.4475}, + {10.523, 2.6975}, + {12.774, 2.6975}, + {14.093, 3.9165}, + {14.851, 5.7135}, + {14.851, 7.8425} + } + } + ] + }, + {FaceForm[RGBColor[0.25882, 0.52157, 0.95686, 1.0]]}, + StripOnInput -> False + ], + StyleBox[ + { + FilledCurveBox[ + { + { + {1, 4, 3}, + {1, 3, 3}, + {0, 1, 0}, + {0, 1, 0}, + {1, 3, 3}, + {1, 3, 3}, + {0, 1, 0} + } + }, + { + { + {4.2229, 6.6642}, + {4.0769, 7.0842}, + {4.0009, 7.5332}, + {4.0009, 8.0002}, + {4.0009, 8.4662}, + {4.0829, 8.9162}, + {4.2229, 9.3362}, + {4.2229, 11.138}, + {1.9009, 11.138}, + {1.4229, 10.193}, + {1.1489, 9.1312}, + {1.1489, 8.0002}, + {1.1489, 6.8682}, + {1.4229, 5.8062}, + {1.9009, 4.8622}, + {4.2229, 6.6642} + } + } + ] + }, + {FaceForm[RGBColor[0.98431, 0.73725, 0.019608, 1.0]]}, + StripOnInput -> False + ], + StyleBox[ + { + FilledCurveBox[ + { + { + {1, 4, 3}, + {0, 1, 0}, + {1, 3, 3}, + {1, 3, 3}, + {0, 1, 0}, + {1, 3, 3} + } + }, + { + { + {8.1487, 12.229}, + {9.1817, 12.229}, + {10.103, 11.873}, + {10.832, 11.179}, + {12.827, 13.174}, + {11.62, 14.306}, + {10.039, 15.0}, + {8.1487, 15.0}, + {5.4127, 15.0}, + {3.0507, 13.425}, + {1.9017, 11.138}, + {4.2227, 9.3362}, + {4.7767, 10.998}, + {6.3227, 12.229}, + {8.1487, 12.229} + } + } + ] + }, + {FaceForm[RGBColor[0.91765, 0.26275, 0.20784, 1.0]]}, + StripOnInput -> False + ] + }, + ImageSize -> {17.0, 17.0}, + PlotRange -> {{-0.5, 16.5}, {-0.5, 16.5}}, + AspectRatio -> Automatic + ] + ]) + } + ], Cell[ StyleData["SideChatIcon"], TemplateBoxOptions -> { diff --git a/PacletInfo.wl b/PacletInfo.wl index 304048e8..fdf93604 100644 --- a/PacletInfo.wl +++ b/PacletInfo.wl @@ -1,7 +1,7 @@ PacletObject[ <| "Name" -> "Wolfram/Chatbook", "PublisherID" -> "Wolfram", - "Version" -> "1.3.4", + "Version" -> "1.3.5", "WolframVersion" -> "13.3+", "Description" -> "Wolfram Notebooks + LLMs", "License" -> "MIT", diff --git a/Scripts/FormatFiles.wls b/Scripts/FormatFiles.wls index 6ff8f48d..d5624bd6 100644 --- a/Scripts/FormatFiles.wls +++ b/Scripts/FormatFiles.wls @@ -160,9 +160,11 @@ makeReadable[ file_, formatted_File ] := (* ::**************************************************************************************************************:: *) (* ::Section::Closed:: *) (*Run*) -If[ MemberQ[ $ScriptCommandLine, "--unformat" ], - makeUnreadable /@ getFiles[ ], - makeReadable /@ Echo[ getFiles[ ] ] +Block[ { $Context = "Global`", $ContextPath = { "System`", "Global`" } }, + If[ MemberQ[ $ScriptCommandLine, "--unformat" ], + makeUnreadable /@ getFiles[ ], + makeReadable /@ Echo[ getFiles[ ] ] + ] ] (* :!CodeAnalysis::EndBlock:: *) \ No newline at end of file diff --git a/Source/Chatbook/CloudToolbar.wl b/Source/Chatbook/CloudToolbar.wl new file mode 100644 index 00000000..b33a91ab --- /dev/null +++ b/Source/Chatbook/CloudToolbar.wl @@ -0,0 +1,225 @@ +(* ::Section::Closed:: *) +(*Package Header*) +BeginPackage[ "Wolfram`Chatbook`CloudToolbar`" ]; + +HoldComplete[ + `makeChatCloudDockedCellContents; +]; + +Begin[ "`Private`" ]; + +Needs[ "Wolfram`Chatbook`" ]; +Needs[ "Wolfram`Chatbook`Common`" ]; +Needs[ "Wolfram`Chatbook`Dialogs`" ]; +Needs[ "Wolfram`Chatbook`Dynamics`" ]; +Needs[ "Wolfram`Chatbook`PreferencesContent`" ]; +Needs[ "Wolfram`Chatbook`Services`" ]; + +(* ::**************************************************************************************************************:: *) +(* ::Section::Closed:: *) +(*Configuration*) +$notebookTypeLabelOptions = Sequence[ + FontColor -> RGBColor[ "#333333" ], + FontFamily -> "Source Sans Pro", + FontSize -> 16, + FontWeight -> "DemiBold" +]; + +(* ::**************************************************************************************************************:: *) +(* ::Section::Closed:: *) +(*Docked Cell Contents*) + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*makeChatCloudDockedCellContents*) +makeChatCloudDockedCellContents // beginDefinition; + +makeChatCloudDockedCellContents[ ] := + Block[ { $preferencesScope := EvaluationNotebook[ ] }, + DynamicWrapper[ + Grid[ + { + { + Item[ $cloudChatBanner, Alignment -> Left ], + Item[ "", ItemSize -> Fit ], + makePersonaSelector[ ], + cloudModelSelector[ ] + } + }, + Alignment -> { Left, Baseline }, + Dividers -> { { False, False, False, True }, False }, + Spacings -> { 2, 0 }, + BaseStyle -> { "Text", FontSize -> 14, FontColor -> GrayLevel[ 0.4 ] }, + FrameStyle -> Directive[ Thickness[ 2 ], GrayLevel[ 0.9 ] ] + ], + Needs[ "GeneralUtilities`" -> None ]; + CurrentValue[ EvaluationNotebook[ ], TaggingRules ] = + GeneralUtilities`ToAssociations @ Replace[ + CurrentValue[ EvaluationNotebook[ ], TaggingRules ], + Except[ KeyValuePattern @ { } ] :> <| |> + ] + ] + ]; + +makeChatCloudDockedCellContents // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*cloudModelSelector*) +cloudModelSelector // beginDefinition; + +cloudModelSelector[ ] := + DynamicModule[ { serviceSelector, modelSelector }, + + serviceSelector = PopupMenu[ + Dynamic[ + Replace[ + CurrentValue[ EvaluationNotebook[ ], { TaggingRules, "ChatNotebookSettings", "Model" } ], + { + _String|Inherited :> "OpenAI", + KeyValuePattern[ "Service" -> service_String ] :> service, + _ :> Set[ + CurrentValue[ + EvaluationNotebook[ ], + { TaggingRules, "ChatNotebookSettings", "Model" } + ], + $DefaultModel + ][ "Service" ] + } + ], + Function[ + CurrentValue[ + EvaluationNotebook[ ], + { TaggingRules, "ChatNotebookSettings", "Model", "Service" } + ] = #1; + + CurrentValue[ + EvaluationNotebook[ ], + { TaggingRules, "ChatNotebookSettings", "Model", "Name" } + ] = Automatic; + + cloudModelNameSelector[ Dynamic @ modelSelector, #1 ] + ] + ], + KeyValueMap[ + #1 -> Row @ { inlineTemplateBoxes[ #2[ "Icon" ] ], Spacer[ 1 ], #2[ "Service" ] } &, + $availableServices + ] + ]; + + cloudModelNameSelector[ + Dynamic @ modelSelector, + Replace[ + CurrentValue[ EvaluationNotebook[ ], { TaggingRules, "ChatNotebookSettings", "Model" } ], + { + _String|Inherited :> "OpenAI", + KeyValuePattern[ "Service" -> service_String ] :> service, + _ :> Set[ + CurrentValue[ EvaluationNotebook[ ], { TaggingRules, "ChatNotebookSettings", "Model" } ], + $DefaultModel + ][ "Service" ] + } + ] + ]; + + Row @ { + "LLM Service: ", serviceSelector, + Spacer[ 5 ], + "Model: ", Dynamic @ modelSelector + } + ]; + +cloudModelSelector // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*cloudModelNameSelector*) +cloudModelNameSelector // beginDefinition; + +cloudModelNameSelector[ Dynamic[ modelSelector_ ], service_String ] := + modelSelector = DynamicModule[ { display, models }, + display = ProgressIndicator[ Appearance -> "Percolate" ]; + Dynamic[ display ], + Initialization :> ( + models = getServiceModelList @ service; + If[ SameQ[ + CurrentValue[ EvaluationNotebook[ ], { TaggingRules, "ChatNotebookSettings", "Model", "Name" } ], + Automatic + ], + CurrentValue[ EvaluationNotebook[ ], { TaggingRules, "ChatNotebookSettings", "Model", "Name" } ] = + First[ models, <| "Name" -> Automatic |> ][ "Name" ] + ]; + + display = PopupMenu[ + Dynamic[ + Replace[ + CurrentChatSettings[ EvaluationNotebook[ ], "Model" ], + { KeyValuePattern[ "Name" -> model_String ] :> model, _ :> Automatic } + ], + Function[ + CurrentValue[ + EvaluationNotebook[ ], + { TaggingRules, "ChatNotebookSettings", "Model", "Name" } + ] = #1 + ] + ], + (#Name -> #DisplayName &) /@ models + ] + ), + SynchronousInitialization -> False + ]; + +cloudModelNameSelector // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Section::Closed:: *) +(*Notebook Type Label*) + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*$cloudChatBanner*) +$cloudChatBanner := $cloudChatBanner = cvExpand @ PaneSelector[ + { True -> $chatDrivenNotebookLabel, False -> $chatEnabledNotebookLabel }, + Dynamic @ TrueQ @ cv[ EvaluationNotebook[ ], "ChatDrivenNotebook" ], + ImageSize -> Automatic +]; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*$chatDrivenNotebookLabel*) +$chatDrivenNotebookLabel := Grid[ + { + { + "", + chatbookIcon[ "ChatDrivenNotebookIcon", False ], + Style[ "Chat-Driven Notebook", $notebookTypeLabelOptions ] + } + }, + Alignment -> { Automatic, Center }, + Spacings -> 0.5 +]; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*$chatEnabledNotebookLabel*) +$chatEnabledNotebookLabel := Grid[ + { + { + "", + chatbookIcon[ "ChatEnabledNotebookIcon", False ], + Style[ "Chat-Enabled Notebook", $notebookTypeLabelOptions ] + } + }, + Alignment -> { Automatic, Center }, + Spacings -> 0.5 +]; + +(* ::**************************************************************************************************************:: *) +(* ::Section::Closed:: *) +(*Package Footer*) +If[ Wolfram`ChatbookInternal`$BuildingMX, + $cloudChatBanner; +]; + +End[ ]; +EndPackage[ ]; diff --git a/Source/Chatbook/Common.wl b/Source/Chatbook/Common.wl index 5c854554..1429776c 100644 --- a/Source/Chatbook/Common.wl +++ b/Source/Chatbook/Common.wl @@ -31,6 +31,7 @@ BeginPackage[ "Wolfram`Chatbook`Common`" ]; `$$textData; `$$textDataList; `$$unspecified; +`$$feObj; `$catchTopTag; `beginDefinition; @@ -107,9 +108,14 @@ $$nestedCellStyle = cellStylePattern @ $nestedCellStyles; $$textDataItem = (_String|_Cell|_StyleBox|_ButtonBox); $$textDataList = { $$textDataItem... }; $$textData = $$textDataItem | $$textDataList; -$$optionsSequence = (Rule|RuleDelayed)[ _Symbol|_String, _ ] ...; -$$size = Infinity | (_Real|_Integer)? NonNegative; -$$unspecified = _Missing | Automatic | Inherited; + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*Other Argument Patterns *) +$$optionsSequence = (Rule|RuleDelayed)[ _Symbol|_String, _ ] ...; +$$size = Infinity | (_Real|_Integer)? NonNegative; +$$unspecified = _Missing | Automatic | Inherited; +$$feObj = _FrontEndObject | $FrontEndSession | _NotebookObject | _CellObject | _BoxObject; (* ::**************************************************************************************************************:: *) (* ::Section::Closed:: *) diff --git a/Source/Chatbook/Dialogs.wl b/Source/Chatbook/Dialogs.wl index 4c38c048..3e8048e4 100644 --- a/Source/Chatbook/Dialogs.wl +++ b/Source/Chatbook/Dialogs.wl @@ -32,9 +32,13 @@ Needs[ "Wolfram`Chatbook`ResourceInstaller`" ]; (* ::**************************************************************************************************************:: *) (* ::Section::Closed:: *) (*Configuration*) +$inDialog = False; $dialogHeaderMargins = { { 30, 30 }, { 13, 9 } }; $dialogSubHeaderMargins = { { 30, 30 }, { 0, 9 } }; $dialogBodyMargins = { { 30, 30 }, { 13, 5 } }; +$paneHeaderMargins = { { 5, 30 }, { 13, 9 } }; +$paneSubHeaderMargins = { { 5, 30 }, { 0, 9 } }; +$paneBodyMargins = { { 5, 30 }, { 13, 5 } }; $baseStyle := $baseStyles[ "Default" ]; $baseStyles = <| @@ -42,6 +46,10 @@ $baseStyles = <| "DialogSubHeader" -> { FontSize -> 14, FontWeight -> "DemiBold" } |>; +$headerMargins := If[ TrueQ @ $inDialog, $dialogHeaderMargins , $paneHeaderMargins ]; +$subHeaderMargins := If[ TrueQ @ $inDialog, $dialogSubHeaderMargins, $paneSubHeaderMargins ]; +$bodyMargins := If[ TrueQ @ $inDialog, $dialogBodyMargins , $paneBodyMargins ]; + (* ::**************************************************************************************************************:: *) (* ::Section::Closed:: *) (*Create Dialogs*) @@ -139,11 +147,12 @@ dialogPane // beginDefinition; dialogPane[ expr_ ] := dialogPane[ expr, "DialogBody" ]; dialogPane[ expr_, style_ ] := dialogPane[ expr, style, Automatic ]; +dialogPane[ expr_, style_, margins_ ] := dialogPane[ expr, style, margins, $inDialog ]; -dialogPane[ exprs_List, style_, margins_ ] := dialogPane[ exprs, style, margins ] = +dialogPane[ exprs_List, style_, margins_, inDialog_ ] := dialogPane[ exprs, style, margins, inDialog ] = dialogPane0[ #, style, margins ] & /@ exprs; -dialogPane[ expr_, style_, margins_ ] := dialogPane[ expr, style, margins ] = +dialogPane[ expr_, style_, margins_, inDialog_ ] := dialogPane[ expr, style, margins, inDialog ] = { dialogPane0[ expr, style, margins ], SpanFromLeft }; dialogPane // endDefinition; @@ -173,9 +182,9 @@ dialogBaseStyle // endDefinition; (* ::Subsubsection::Closed:: *) (*dialogMargins*) dialogMargins // beginDefinition; -dialogMargins[ "DialogBody" , margins_ ] := autoMargins[ margins, $dialogBodyMargins ]; -dialogMargins[ "DialogHeader" , margins_ ] := autoMargins[ margins, $dialogHeaderMargins ]; -dialogMargins[ "DialogSubHeader", margins_ ] := autoMargins[ margins, $dialogSubHeaderMargins ]; +dialogMargins[ "DialogBody" , margins_ ] := autoMargins[ margins, $bodyMargins ]; +dialogMargins[ "DialogHeader" , margins_ ] := autoMargins[ margins, $headerMargins ]; +dialogMargins[ "DialogSubHeader", margins_ ] := autoMargins[ margins, $subHeaderMargins ]; dialogMargins[ _ , margins_ ] := margins; dialogMargins // endDefinition; @@ -247,6 +256,7 @@ redDialogButtonLabel // endDefinition; cvExpand // beginDefinition; cvExpand // Attributes = { HoldFirst }; cvExpand[ expr_ ] := expr /. $cvRules; +cvExpand /: SetDelayed[ lhs_, cvExpand[ rhs_ ] ] := Unevaluated @ SetDelayed[ lhs, rhs ] /. $cvRules; cvExpand // endDefinition; (* ::**************************************************************************************************************:: *) diff --git a/Source/Chatbook/Dynamics.wl b/Source/Chatbook/Dynamics.wl index 23b1b099..899a382a 100644 --- a/Source/Chatbook/Dynamics.wl +++ b/Source/Chatbook/Dynamics.wl @@ -17,10 +17,12 @@ Needs[ "Wolfram`Chatbook`Common`" ]; (* ::Section::Closed:: *) (*Configuration*) $dynamicTriggers = <| - "ChatBlock" :> $chatBlockTrigger, - "Models" :> $modelsTrigger, - "Personas" :> $personasTrigger, - "Tools" :> $toolsTrigger + "ChatBlock" :> $chatBlockTrigger, + "Models" :> $modelsTrigger, + "Personas" :> $personasTrigger, + "Preferences" :> $preferencesTrigger, + "Services" :> $servicesTrigger, + "Tools" :> $toolsTrigger |>; Cases[ $dynamicTriggers, sym_Symbol :> (sym = 0) ]; diff --git a/Source/Chatbook/Main.wl b/Source/Chatbook/Main.wl index 2a62801c..5f1baeca 100644 --- a/Source/Chatbook/Main.wl +++ b/Source/Chatbook/Main.wl @@ -31,6 +31,7 @@ BeginPackage[ "Wolfram`Chatbook`" ]; `GetChatHistory; `GetExpressionURI; `GetExpressionURIs; +`InvalidateServiceCache; `MakeExpressionURI; `SetModel; `SetToolOptions; @@ -97,6 +98,7 @@ Block[ { $ContextPath }, Get[ "Wolfram`Chatbook`ToolManager`" ]; Get[ "Wolfram`Chatbook`PersonaManager`" ]; Get[ "Wolfram`Chatbook`ChatHistory`" ]; + Get[ "Wolfram`Chatbook`CloudToolbar`" ]; ]; (* ::**************************************************************************************************************:: *) diff --git a/Source/Chatbook/Menus.wl b/Source/Chatbook/Menus.wl index 48edc471..5676a4b9 100644 --- a/Source/Chatbook/Menus.wl +++ b/Source/Chatbook/Menus.wl @@ -3,141 +3,351 @@ *) -BeginPackage["Wolfram`Chatbook`Menus`"] +(* ::Section::Closed:: *) +(*Package Header*) +BeginPackage[ "Wolfram`Chatbook`Menus`" ]; -Needs["GeneralUtilities`" -> None] +(* :!CodeAnalysis::BeginBlock:: *) + +HoldComplete[ + `attachMenuCell; + `AttachSubmenu; + `MakeMenu; + `menuMagnification; + `removeChatMenus; +]; + +Needs[ "GeneralUtilities`" -> None ]; GeneralUtilities`SetUsage[MakeMenu, " MakeMenu[$$] returns an expression representing a menu of actions. The generated menu expression may depend on styles from the Chatbook stylesheet. -"] +"]; GeneralUtilities`SetUsage[AttachSubmenu, " AttachSubmenu[parentMenu$, submenu$] attaches submenu$ to parentMenu$, taking care to attach to the left or right side based on heuristic for available space. -"] +"]; + +Begin[ "`Private`" ]; + +Needs[ "Wolfram`Chatbook`Common`" ]; +Needs[ "Wolfram`Chatbook`ErrorUtils`" ]; +Needs[ "Wolfram`Chatbook`FrontEnd`" ]; + +(* ::**************************************************************************************************************:: *) +(* ::Section::Closed:: *) +(*Configuration*) +$submenuItems = False; + +(* ::**************************************************************************************************************:: *) +(* ::Section::Closed:: *) +(*MakeMenu*) +MakeMenu // beginDefinition; + +MakeMenu[ items_List ] := + MakeMenu[ items, Automatic ]; -Begin["`Private`"] +MakeMenu[ items_List, frameColor_ ] := + MakeMenu[ items, frameColor, Automatic ]; -Needs["Wolfram`Chatbook`Common`"] -Needs["Wolfram`Chatbook`ErrorUtils`"] +MakeMenu[ items_List, Automatic, width_ ] := + MakeMenu[ items, GrayLevel[ 0.85 ], width ]; -(*========================================================*) +MakeMenu[ items_List, frameColor_, Automatic ] := + MakeMenu[ items, frameColor, 200 ]; -SetFallthroughError[MakeMenu] +MakeMenu[ items_List, frameColor_, width_ ] /; + ! $submenuItems && MemberQ[ items, KeyValuePattern[ "Type" -> "Submenu" ] ] := + Block[ { $submenuItems = True }, MakeMenu[ items, frameColor, width ] ]; -MakeMenu[ - items_List, - frameColor_, - width_ -] := - Pane[ - RawBoxes @ TemplateBox[ - { - ToBoxes @ Column[ menuItem /@ items, ItemSize -> Automatic, Spacings -> 0, Alignment -> Left ], - FrameMargins -> 3, - Background -> GrayLevel[ 0.98 ], - RoundingRadius -> 3, - FrameStyle -> Directive[ AbsoluteThickness[ 1 ], frameColor ], - ImageMargins -> 0 - }, - "Highlighted" - ], - ImageSize -> { width, Automatic } - ]; +MakeMenu[ items_List, frameColor_, width_ ] := + RawBoxes @ TemplateBox[ + { + ToBoxes @ Pane[ + Column[ menuItem /@ items, ItemSize -> Automatic, Spacings -> 0, Alignment -> Left ], + AppearanceElements -> None, + ImageSize -> { width, UpTo[ 450 ] }, + Scrollbars -> { False, Automatic } + ], + Background -> GrayLevel[ 0.98 ], + FrameMargins -> 3, + FrameStyle -> Directive[ AbsoluteThickness[ 1 ], frameColor ], + ImageMargins -> 0, + RoundingRadius -> 3 + }, + "Highlighted" + ]; -(*====================================*) +MakeMenu // endDefinition; -SetFallthroughError[menuItem] +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*menuItem*) +menuItem // beginDefinition; -menuItem[ { args__ } ] := menuItem @ args; +menuItem[ spec: KeyValuePattern[ "Data" -> content_ ] ] := + menuItem @ <| spec, "Data" :> content |>; -menuItem[ Delimiter ] := RawBoxes @ TemplateBox[ { }, "ChatMenuItemDelimiter" ]; +menuItem[ spec: KeyValuePattern @ { "Type" -> "Submenu", "Data" :> content_ } ] := + EventHandler[ + Block[ { $submenuItems = False }, + menuItem[ + Lookup[ spec, "Icon", Spacer[ 0 ] ], + submenuLabel @ Lookup[ spec, "Label", "" ], + None + ] + ], + { + "MouseEntered" :> With[ { root = EvaluationBox[ ] }, AttachSubmenu[ root, content ] ], + "MouseDown" :> With[ { root = EvaluationBox[ ] }, AttachSubmenu[ root, content ] ] + } + ]; -menuItem[ label_ :> action_ ] := menuItem[Graphics[{}, ImageSize -> 0], label, Hold[action]] +menuItem[ { args__ } ] := + menuItem @ args; -menuItem[ section_ ] := RawBoxes @ TemplateBox[ { ToBoxes @ section }, "ChatMenuSection" ]; +menuItem[ Delimiter ] := + addSubmenuHandler @ RawBoxes @ TemplateBox[ { }, "ChatMenuItemDelimiter" ]; + +menuItem[ label_ :> action_ ] := + menuItem[ Graphics[ { }, ImageSize -> 0 ], label, Hold @ action ]; + +menuItem[ section_ ] := + addSubmenuHandler @ RawBoxes @ TemplateBox[ { ToBoxes @ section }, "ChatMenuSection" ]; menuItem[ name_String, label_, code_ ] := - With[ { icon = chatbookIcon @ name }, - If[ MissingQ @ icon, - menuItem[ RawBoxes @ TemplateBox[ { name }, "ChatMenuItemToolbarIcon" ], label, code ], - menuItem[ icon, label, code ] - ] - ]; + With[ { icon = chatbookIcon @ name }, + If[ MissingQ @ icon, + menuItem[ RawBoxes @ TemplateBox[ { name }, "ChatMenuItemToolbarIcon" ], label, code ], + menuItem[ icon, label, code ] + ] + ]; menuItem[ icon_, label_, action_String ] := - menuItem[ - icon, - label, - Hold @ With[ - { $CellContext`cell = EvaluationCell[ ] }, - { $CellContext`root = ParentCell @ $CellContext`cell }, - Quiet @ Needs[ "Wolfram`Chatbook`" -> None ]; - Symbol[ "Wolfram`Chatbook`ChatbookAction" ][ action, $CellContext`root ]; - NotebookDelete @ $CellContext`cell; - ] - ]; + menuItem[ + icon, + label, + Hold @ With[ + { $CellContext`cell = EvaluationCell[ ] }, + { $CellContext`root = ParentCell @ $CellContext`cell }, + Quiet @ Needs[ "Wolfram`Chatbook`" -> None ]; + Symbol[ "Wolfram`Chatbook`ChatbookAction" ][ action, $CellContext`root ]; + NotebookDelete @ $CellContext`cell; + ] + ]; menuItem[ None, content_, None ] := - content; + addSubmenuHandler @ content; menuItem[ icon_, label_, None ] := - menuItem[ - icon, - label, - Hold[ - MessageDialog[ "Not Implemented" ]; - NotebookDelete @ EvaluationCell[ ]; - ] - ]; + menuItem[ icon, label, Hold @ Null ]; menuItem[ icon_, label_, code_ ] := - RawBoxes @ TemplateBox[ { ToBoxes @ icon, ToBoxes @ label, code }, "ChatMenuItem" ]; - -(*========================================================*) - -AttachSubmenu[ - parentMenu_CellObject, - submenu_ -] := With[{ - mouseX = MousePosition["WindowScaled"][[1]] -}, { - (* Note: Depending on the X coordinate of the users mouse - when they click the 'Advanced Settings' button, either - show the attached submenu to the left or right of the - outer menu. This ensures that this submenu doesn't touch - the right edge of the notebook window when it is opened - from the 'Chat Settings' notebook toolbar. *) - positions = If[ - TrueQ[mouseX < 0.5], - { - {Right, Bottom}, - {Left, Bottom} - }, - { - {Left, Bottom}, - {Right, Bottom} - } - ] -}, - AttachCell[ - EvaluationCell[], - submenu, - positions[[1]], - {50, 50}, - positions[[2]], - RemovalConditions -> "MouseExit" - ] -] - -(*========================================================*) + addSubmenuHandler @ RawBoxes @ TemplateBox[ { ToBoxes @ icon, ToBoxes @ label, code }, "ChatMenuItem" ]; + +menuItem // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*addSubmenuHandler*) +addSubmenuHandler // beginDefinition; + +addSubmenuHandler[ expr_ ] /; $submenuItems := EventHandler[ + expr, + { + "MouseEntered" :> NotebookDelete @ Cells[ + EvaluationCell[ ], + AttachedCell -> True, + CellStyle -> "AttachedChatMenu" + ] + } +]; + +addSubmenuHandler[ expr_ ] := expr; + +addSubmenuHandler // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*submenuLabel*) +submenuLabel // beginDefinition; + +submenuLabel[ label_ ] := Grid[ + { { Item[ label, ItemSize -> Fit, Alignment -> Left ], RawBoxes @ TemplateBox[ { }, "Triangle" ] } }, + Spacings -> 0 +]; + +submenuLabel // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Section::Closed:: *) +(*AttachSubmenu*) +AttachSubmenu // beginDefinition; + +AttachSubmenu[ parentMenu_, submenu: Cell[ __, "AttachedChatMenu", ___ ] ] := Enclose[ + Module[ { parentInfo, root, pos, oPos, offsetX, offsetY, magnification, tags, attached }, + + NotebookDelete @ Cells[ parentMenu, AttachedCell -> True, CellStyle -> "AttachedChatMenu" ]; + + parentInfo = Replace[ + Association @ CurrentValue[ parentMenu, { TaggingRules, "MenuData" } ], + Except[ _? AssociationQ ] :> <| |> + ]; + + { pos, oPos } = ConfirmMatch[ determineAttachmentPosition @ parentInfo, { { _, _ }, { _, _ } }, "Position" ]; + offsetX = If[ MatchQ[ pos, { Left, _ } ], -3, 3 ]; + offsetY = If[ MatchQ[ pos, { _, Top } ], 5, -5 ]; + + magnification = Replace[ + Lookup[ parentInfo, "Magnification", AbsoluteCurrentValue[ parentMenu, Magnification ] ], + Except[ _? NumberQ ] :> If[ $OperatingSystem === "Windows", 0.75, 1 ] + ]; + + tags = <| "MenuData" -> <| parentInfo, "Magnification" -> magnification, "Position" -> { pos, oPos } |> |>; + + attached = AttachCell[ + parentMenu, + Append[ submenu, Unevaluated @ Sequence[ Magnification -> magnification, TaggingRules -> tags ] ], + pos, + Offset[ { offsetX, offsetY }, { 0, 0 } ], + oPos, + RemovalConditions -> { "MouseClickOutside", "EvaluatorQuit" } + ]; + + If[ ! MatchQ[ tags[ "MenuData", "Root" ], _CellObject ], + CurrentValue[ attached, { TaggingRules, "MenuData", "Root" } ] = attached; + ]; + + attached + ], + throwInternalFailure +]; + +AttachSubmenu[ parentMenu_, Cell[ boxes_, style__String, opts: OptionsPattern[ ] ] ] := + AttachSubmenu[ parentMenu, Cell[ boxes, style, "AttachedChatMenu", opts ] ]; + +AttachSubmenu[ parentMenu_, expr: Except[ _Cell ] ] := + AttachSubmenu[ parentMenu, Cell[ BoxData @ ToBoxes @ expr, "AttachedChatMenu" ] ]; + +AttachSubmenu[ expr_ ] := + AttachSubmenu[ EvaluationCell[ ], expr ]; + +AttachSubmenu // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*menuMagnification*) +menuMagnification // beginDefinition; +menuMagnification[ obj_ ] := menuMagnification[ $OperatingSystem, AbsoluteCurrentValue[ obj, Magnification ] ]; +menuMagnification[ "Windows", magnification_? NumberQ ] := Min[ magnification * 0.75, 1.5 ]; +menuMagnification[ _, magnification_ ] := magnification; +menuMagnification // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*determineAttachmentPosition*) +determineAttachmentPosition // beginDefinition; + +determineAttachmentPosition[ KeyValuePattern[ "Position" -> { { pH_, pV_ }, { oH_, oV_ } } ] ] := + { { pH, pV }, { oH, chooseVerticalOffset @ MousePosition[ "WindowScaled" ] } }; + +determineAttachmentPosition[ _Association ] := + determineAttachmentPosition @ MousePosition[ "WindowScaled" ]; + +determineAttachmentPosition[ pos_List ] := + determineAttachmentPosition[ pos, quadrant @ pos ]; + +determineAttachmentPosition[ { x_, y_ }, { h_, v_ } ] := { + { Replace[ h, { Left -> Right, Right -> Left } ], v }, + { h, chooseVerticalOffset @ { x, y } } +}; + +determineAttachmentPosition[ None ] := + { { Right, Top }, { Left, Top } }; + +determineAttachmentPosition // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*chooseVerticalOffset*) +chooseVerticalOffset // beginDefinition; +chooseVerticalOffset[ { x_, y_ } ] /; y < 0.33 := Top; +chooseVerticalOffset[ { x_, y_ } ] /; y > 0.67 := Bottom; +chooseVerticalOffset[ { x_, y_ } ] := Center; +chooseVerticalOffset // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*quadrant*) +quadrant // beginDefinition; +quadrant[ None ] := None; +quadrant[ { x_? NumberQ, y_? NumberQ } ] := quadrant[ TrueQ[ x >= 0.5 ], TrueQ[ y >= 0.67 ] ]; +quadrant[ True , True ] := { Right, Bottom }; +quadrant[ True , False ] := { Right, Top }; +quadrant[ False, True ] := { Left , Bottom }; +quadrant[ False, False ] := { Left , Top }; +quadrant // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Section::Closed:: *) +(*Attaching and Removing Menus*) + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*removeChatMenus*) +removeChatMenus // beginDefinition; + +removeChatMenus[ obj: $$feObj ] := + With[ { root = CurrentValue[ obj, { TaggingRules, "MenuData", "Root" } ] }, + NotebookDelete @ root /; MatchQ[ root, _CellObject ] + ]; + +removeChatMenus[ box_BoxObject ] := + removeChatMenus @ parentCell @ box; + +removeChatMenus[ cell_CellObject ] /; MemberQ[ cellStyles @ cell, "AttachedChatMenu" ] := + removeChatMenus @ parentCell @ cell; + +removeChatMenus[ cell_CellObject ] := + NotebookDelete @ Cells[ cell, AttachedCell -> True, CellStyle -> "AttachedChatMenu" ]; + +(* Cell has already been removed: *) +removeChatMenus[ $Failed ] := + Null; + +removeChatMenus // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*attachMenuCell*) +attachMenuCell // beginDefinition; + +attachMenuCell[ parent: $$feObj, args___ ] := + Module[ { attached, root }, + attached = AttachCell[ parent, args ]; + + root = Replace[ + CurrentValue[ parent, { TaggingRules, "MenuData", "Root" } ], + Except[ _CellObject ] :> attached + ]; + + CurrentValue[ attached, { TaggingRules, "MenuData", "Root" } ] = root; + + attached + ]; + +attachMenuCell // endDefinition; +(* ::**************************************************************************************************************:: *) +(* ::Section::Closed:: *) +(*Package Footer*) If[ Wolfram`ChatbookInternal`$BuildingMX, Null; ]; -End[] +(* :!CodeAnalysis::EndBlock:: *) -EndPackage[] \ No newline at end of file +End[ ]; +EndPackage[ ]; \ No newline at end of file diff --git a/Source/Chatbook/Models.wl b/Source/Chatbook/Models.wl index d1a38e7e..0b8ba160 100644 --- a/Source/Chatbook/Models.wl +++ b/Source/Chatbook/Models.wl @@ -17,9 +17,10 @@ BeginPackage[ "Wolfram`Chatbook`Models`" ]; Begin[ "`Private`" ]; Needs[ "Wolfram`Chatbook`" ]; -Needs[ "Wolfram`Chatbook`Common`" ]; Needs[ "Wolfram`Chatbook`Actions`" ]; +Needs[ "Wolfram`Chatbook`Common`" ]; Needs[ "Wolfram`Chatbook`Dynamics`" ]; +Needs[ "Wolfram`Chatbook`UI`" ]; (* ::**************************************************************************************************************:: *) (* ::Section::Closed:: *) @@ -139,9 +140,12 @@ modelName // endDefinition; (*toModelName*) toModelName // beginDefinition; -toModelName[ KeyValuePattern @ { "Service" -> service_, "Model" -> model_ } ] := +toModelName[ KeyValuePattern @ { "Service" -> service_, "Name"|"Model" -> model_ } ] := toModelName @ { service, model }; +toModelName[ KeyValuePattern[ "Name"|"Model" -> model_ ] ] := + toModelName @ model; + toModelName[ { service_String, name_String } ] := toModelName @ name; toModelName[ name_String? StringQ ] := toModelName[ name ] = @@ -264,13 +268,31 @@ fineTunedModelName // endDefinition; (* ::Subsection::Closed:: *) (*modelIcon*) modelIcon // beginDefinition; -modelIcon[ KeyValuePattern[ "Icon" -> icon_ ] ] := icon; -modelIcon[ KeyValuePattern[ "Name" -> name_String ] ] := modelIcon @ name; -modelIcon[ name0_String ] := With[ { name = toModelName @ name0 }, modelIcon @ name /; name =!= name0 ]; -modelIcon[ name_String ] /; StringStartsQ[ name, "ft:" ] := modelIcon @ StringDelete[ name, StartOfString~~"ft:" ]; -modelIcon[ gpt_String ] /; StringStartsQ[ gpt, "gpt-3.5" ] := RawBoxes @ TemplateBox[ { }, "ModelGPT35" ]; -modelIcon[ gpt_String ] /; StringStartsQ[ gpt, "gpt-4" ] := RawBoxes @ TemplateBox[ { }, "ModelGPT4" ]; -modelIcon[ name_String ] := $defaultModelIcon; + +modelIcon[ KeyValuePattern[ "Icon" -> icon_ ] ] := + icon; + +modelIcon[ KeyValuePattern @ { "Name" -> name_String, "Service" -> service_String } ] := + Replace[ modelIcon @ name, $defaultModelIcon :> serviceIcon @ service ]; + +modelIcon[ KeyValuePattern[ "Name" -> name_String ] ] := + modelIcon @ name; + +modelIcon[ name0_String ] := + With[ { name = toModelName @ name0 }, modelIcon @ name /; name =!= name0 ]; + +modelIcon[ name_String ] /; StringStartsQ[ name, "ft:" ] := + modelIcon @ StringDelete[ name, StartOfString~~"ft:" ]; + +modelIcon[ gpt_String ] /; StringStartsQ[ gpt, "gpt-3.5" ] := + RawBoxes @ TemplateBox[ { }, "ModelGPT35" ]; + +modelIcon[ gpt_String ] /; StringStartsQ[ gpt, "gpt-4" ] := + RawBoxes @ TemplateBox[ { }, "ModelGPT4" ]; + +modelIcon[ name_String ] := + $defaultModelIcon; + modelIcon // endDefinition; (* ::**************************************************************************************************************:: *) @@ -284,25 +306,31 @@ standardizeModelData[ list_List ] := standardizeModelData[ name_String ] := standardizeModelData[ name ] = standardizeModelData @ <| "Name" -> name |>; -standardizeModelData[ model: KeyValuePattern @ { "Name" -> _String, "DisplayName" -> _String, "Icon" -> _ } ] := - Association @ model; - -standardizeModelData[ model_Association? AssociationQ ] := +standardizeModelData[ model: KeyValuePattern @ { } ] := standardizeModelData[ model ] = <| - "Name" -> modelName @ model, "DisplayName" -> modelDisplayName @ model, + "FineTuned" -> fineTunedModelQ @ model, "Icon" -> modelIcon @ model, + "Multimodal" -> multimodalModelQ @ model, + "Name" -> modelName @ model, + "Snapshot" -> snapshotModelQ @ model, model |>; standardizeModelData[ service_String, models_List ] := standardizeModelData[ service, # ] & /@ models; +standardizeModelData[ service_String, model_String ] := + standardizeModelData @ <| "Service" -> service, "Name" -> model |>; + standardizeModelData[ service_String, model_ ] := With[ { as = standardizeModelData @ model }, - (standardizeModelData[ service, model ] = <| "ServiceName" -> service, as |>) /; AssociationQ @ as + (standardizeModelData[ service, model ] = <| "Service" -> service, as |>) /; AssociationQ @ as ]; +standardizeModelData[ KeyValuePattern[ "Service" -> service_String ], model_ ] := + standardizeModelData[ service, model ]; + standardizeModelData // endDefinition; (* ::**************************************************************************************************************:: *) diff --git a/Source/Chatbook/PersonaManager.wl b/Source/Chatbook/PersonaManager.wl index e3f29111..943a819a 100644 --- a/Source/Chatbook/PersonaManager.wl +++ b/Source/Chatbook/PersonaManager.wl @@ -47,54 +47,36 @@ CreatePersonaManagerPanel[ ] := DynamicModule[{favorites, delimColor}, Framed[ Grid[ { - { - Pane[ - Style["Add & Manage Personas", "DialogHeader"], - FrameMargins -> Dynamic[CurrentValue[{StyleDefinitions, "DialogHeader", CellMargins}]], - ImageSize -> {501, Automatic}]}, - { - Pane[ - Dynamic[ - StringTemplate["`n1` personas being shown in the prompt menu. `n2` total personas available."][ - <| - "n1" -> If[ListQ[#], Length[#], "\[LongDash]"]&[CurrentValue[$FrontEnd, {PrivateFrontEndOptions, "InterfaceSettings", "Chatbook", "VisiblePersonas"}]], - "n2" -> If[Length[#] > 0, Length[#], "\[LongDash]"]&[$CachedPersonaData]|>], - TrackedSymbols :> {$CachedPersonaData}], - BaseStyle -> "DialogBody", - FrameMargins -> Dynamic[Replace[CurrentValue[{StyleDefinitions, "DialogBody", CellMargins}], {{l_, r_}, {b_, t_}} :> {{l, r}, {0, t}}]]]}, - { - Pane[ - Grid[{{ + If[ TrueQ @ $inDialog, dialogHeader[ "Add & Manage Personas" ], Nothing ], + + (* ----- Install Personas ----- *) + dialogSubHeader[ "Install Personas" ], + dialogBody[ + Grid @ { + { "Install from", Button[ - NotebookTools`Mousedown[ - Framed["Prompt Repository", BaseStyle -> "ButtonGray1Normal", BaselinePosition -> Baseline], - Framed["Prompt Repository", BaseStyle -> "ButtonGray1Hover", BaselinePosition -> Baseline], - Framed["Prompt Repository", BaseStyle -> "ButtonGray1Pressed", BaselinePosition -> Baseline], - BaseStyle -> "DialogTextBasic"], + grayDialogButtonLabel[ "Prompt Repository \[UpperRightArrow]" ], ResourceInstallFromRepository[ "Prompt" ], - Appearance -> "Suppressed", BaselinePosition -> Baseline, Method -> "Queued"], + Appearance -> "Suppressed", + BaselinePosition -> Baseline, + Method -> "Queued" + ], Button[ - NotebookTools`Mousedown[ - Framed["URL", BaseStyle -> "ButtonGray1Normal", BaselinePosition -> Baseline], - Framed["URL", BaseStyle -> "ButtonGray1Hover", BaselinePosition -> Baseline], - Framed["URL", BaseStyle -> "ButtonGray1Pressed", BaselinePosition -> Baseline], - BaseStyle -> "DialogTextBasic"], + grayDialogButtonLabel[ "URL" ], Block[ { PrintTemporary }, ResourceInstallFromURL[ "Prompt" ] ], - Appearance -> "Suppressed", BaselinePosition -> Baseline, Method -> "Queued"](* , - (* FIXME: FUTURE *) - Button[ - NotebookTools`Mousedown[ - Framed["File", BaseStyle -> "ButtonGray1Normal", BaselinePosition -> Baseline], - Framed["File", BaseStyle -> "ButtonGray1Hover", BaselinePosition -> Baseline], - Framed["File", BaseStyle -> "ButtonGray1Pressed", BaselinePosition -> Baseline], - BaseStyle -> "DialogTextBasic"], - If[AssociationQ[PersonaInstallFromFile[]], GetPersonaData[]], - Appearance -> "Suppressed", BaselinePosition -> Baseline, Method -> "Queued"] *)}}], - BaseStyle -> "DialogBody", - FrameMargins -> Dynamic[Replace[CurrentValue[{StyleDefinitions, "DialogBody", CellMargins}], {{l_, r_}, {b_, t_}} :> {{l, r}, {15, 5}}]]]}, + Appearance -> "Suppressed", + BaselinePosition -> Baseline, + Method -> "Queued" + ] + } + } + ], + + (* ----- Configure and Enable Personas ----- *) + dialogSubHeader[ "Manage and Enable Personas", { Automatic, { 5, Automatic } } ], { - Pane[#, AppearanceElements -> None, ImageSize -> {Full, UpTo[300]}, Scrollbars -> {False, Automatic}]& @ + If[ $inDialog, Pane[#, AppearanceElements -> None, ImageSize -> {Full, UpTo[300]}, Scrollbars -> {False, Automatic}], # ]& @ Dynamic[ Grid[ Prepend[ @@ -105,7 +87,7 @@ CreatePersonaManagerPanel[ ] := DynamicModule[{favorites, delimColor}, KeySort[$CachedPersonaData]]], {"", "In Menu", "", "Name", ""(*FITME*), (*"Description",*) "Version", ""}], Alignment -> {{Center, Center, {Left}}, Center}, - Background -> {{}, {RGBColor["#e5e5e5"]}}, + Background -> {{}, {RGBColor["#e5e5e5"], {White}}}, BaseStyle -> "DialogBody", Dividers -> Dynamic @ { {}, @@ -113,44 +95,78 @@ CreatePersonaManagerPanel[ ] := DynamicModule[{favorites, delimColor}, {{True}}, { 2 -> False, - Length[favorites] + 2 -> Directive[delimColor, AbsoluteThickness[5]]}}}, + Length[favorites] + 2 -> Directive[delimColor, AbsoluteThickness[5]] + } + } + }, FrameStyle -> Dynamic[delimColor], ItemSize -> {{Automatic, Automatic, Automatic, Automatic, Fit, {Automatic}}, {}}, Spacings -> { {{{1}}, {2 -> 1, 4 -> 0.5}}, - 0.5}], - TrackedSymbols :> {$CachedPersonaData}]}, - { - Item[ - Button[(* give Default properties using specific FEExpression *) - NotebookTools`Mousedown[ - Framed["OK", BaseStyle -> "ButtonRed1Normal", BaselinePosition -> Baseline], - Framed["OK", BaseStyle -> "ButtonRed1Hover", BaselinePosition -> Baseline], - Framed["OK", BaseStyle -> "ButtonRed1Pressed", BaselinePosition -> Baseline], - BaseStyle -> "DialogTextBasic"], - DialogReturn @ channelCleanup[ ], - Appearance -> FEPrivate`FrontEndResource["FEExpressions", "DefaultSuppressMouseDownNinePatchAppearance"], - ImageMargins -> {{0, 31}, {14, 14}}, - ImageSize -> Automatic ], - Alignment -> {Right, Center}]}}, + {{{0.5}}, {Length[favorites] + 2 -> 1}} + } + ], + TrackedSymbols :> {$CachedPersonaData} + ] + }, + + (* ----- Dialog Buttons ----- *) + If[ TrueQ @ $inDialog, + { + Item[ + Button[(* give Default properties using specific FEExpression *) + redDialogButtonLabel[ "OK" ], + DialogReturn @ channelCleanup[ ], + Appearance -> FEPrivate`FrontEndResource["FEExpressions", "DefaultSuppressMouseDownNinePatchAppearance"], + ImageMargins -> {{0, 31}, {14, 14}}, + ImageSize -> Automatic + ], + Alignment -> {Right, Center} + ] + }, + { "" } + ] + }, Alignment -> Left, - BaseStyle -> {FontSize -> 1}, (* useful setting in case we want fixed-width columns; ItemSize would scale at the same rate as ImageSize *) - Dividers -> {{}, {2 -> True, 4 -> Directive[delimColor, AbsoluteThickness[5]], -2 -> Directive[delimColor, AbsoluteThickness[5]]}}, + BaseStyle -> $baseStyle, (* useful setting in case we want fixed-width columns; ItemSize would scale at the same rate as ImageSize *) + Dividers -> { + {}, + If[ TrueQ @ $inDialog, + { + 2 -> True, + 4 -> True, + -2 -> Directive[delimColor, AbsoluteThickness[5]] + }, + { + 3 -> True + } + ] + }, FrameStyle -> Dynamic[delimColor], - Spacings -> {0, 0}], + Spacings -> {0, 0} + ], ContentPadding -> 0, FrameMargins -> -1, FrameStyle -> None, - ImageSize -> {501, All}], + ImageSize -> { If[ TrueQ @ $inDialog, 501, Automatic ], All}], Initialization :> ( delimColor = CurrentValue[{StyleDefinitions, "DialogDelimiter", CellFrameColor}]; GetPersonaData[]; (* sets $CachedPersonaData *) (* make sure there are no unexpected extra personas *) - CurrentValue[$FrontEnd, {PrivateFrontEndOptions, "InterfaceSettings", "Chatbook", "VisiblePersonas"}] = - Intersection[ - CurrentValue[$FrontEnd, {PrivateFrontEndOptions, "InterfaceSettings", "Chatbook", "VisiblePersonas"}], - Keys[$CachedPersonaData]]), - Deinitialization :> (CurrentValue[$FrontEnd, {PrivateFrontEndOptions, "InterfaceSettings", "Chatbook", "PersonaFavorites"}] = favorites) + Enclose[ + CurrentValue[$FrontEnd, {PrivateFrontEndOptions, "InterfaceSettings", "Chatbook", "VisiblePersonas"}] = + ConfirmBy[ + Intersection[ + CurrentValue[$FrontEnd, {PrivateFrontEndOptions, "InterfaceSettings", "Chatbook", "VisiblePersonas"}], + Keys[$CachedPersonaData] + ], + ListQ + ] + ] + ), + Deinitialization :> If[ MatchQ[ favorites, { ___String } ], + CurrentValue[$FrontEnd, {PrivateFrontEndOptions, "InterfaceSettings", "Chatbook","PersonaFavorites"}] = favorites + ] ]; CreatePersonaManagerPanel // endDefinition; @@ -257,18 +273,24 @@ formatPacletLink[ origin_String, url_, pacletName_ ] := formatPacletLink // endDefinition; addRemovePersonaListingCheckbox // beginDefinition; + addRemovePersonaListingCheckbox[ name_String ] := - DynamicModule[{val}, - Checkbox[ - Dynamic[val, - Function[ - val = #; - CurrentValue[$FrontEnd, {PrivateFrontEndOptions, "InterfaceSettings", "Chatbook", "VisiblePersonas"}] = - If[#, - Union[Replace[CurrentValue[$FrontEnd, {PrivateFrontEndOptions, "InterfaceSettings", "Chatbook", "VisiblePersonas"}], Except[{___String}] :> {}], {name}] - , - DeleteCases[CurrentValue[$FrontEnd, {PrivateFrontEndOptions, "InterfaceSettings", "Chatbook", "VisiblePersonas"}], name]]]]], - Initialization :> (val = MemberQ[CurrentValue[$FrontEnd, {PrivateFrontEndOptions, "InterfaceSettings", "Chatbook", "VisiblePersonas"}], name])]; + With[ + { + path = { PrivateFrontEndOptions, "InterfaceSettings", "Chatbook", "VisiblePersonas" }, + core = $corePersonaNames + }, + Checkbox @ Dynamic[ + MemberQ[ CurrentValue[ $FrontEnd, path, core ], name ], + Function[ + CurrentValue[ $FrontEnd, path ] = + With[ { current = Replace[ CurrentValue[ $FrontEnd, path ], Except[ { ___String } ] :> core ] }, + If[ #, Union[ current, { name } ], Complement[ current, { name } ] ] + ] + ] + ] + ]; + addRemovePersonaListingCheckbox // endDefinition; uninstallButton // beginDefinition; diff --git a/Source/Chatbook/PreferencesContent.wl b/Source/Chatbook/PreferencesContent.wl new file mode 100644 index 00000000..45771a8b --- /dev/null +++ b/Source/Chatbook/PreferencesContent.wl @@ -0,0 +1,1733 @@ +(* ::Section::Closed:: *) +(*Package Header*) +BeginPackage[ "Wolfram`Chatbook`PreferencesContent`" ]; + +HoldComplete[ + `$preferencesScope; + `createPreferencesContent; + `makeModelSelector; + `makePersonaSelector; + `openPreferencesPage; +]; + +Begin[ "`Private`" ]; + +Needs[ "Wolfram`Chatbook`" ]; +Needs[ "Wolfram`Chatbook`Common`" ]; +Needs[ "Wolfram`Chatbook`Dynamics`" ]; +Needs[ "Wolfram`Chatbook`Errors`" ]; +Needs[ "Wolfram`Chatbook`Models`" ]; +Needs[ "Wolfram`Chatbook`PersonaManager`" ]; +Needs[ "Wolfram`Chatbook`Personas`" ]; +Needs[ "Wolfram`Chatbook`PreferencesUtils`" ]; +Needs[ "Wolfram`Chatbook`Services`" ]; +Needs[ "Wolfram`Chatbook`Settings`" ]; +Needs[ "Wolfram`Chatbook`ToolManager`" ]; +Needs[ "Wolfram`Chatbook`UI`" ]; + +(* ::**************************************************************************************************************:: *) +(* ::Section::Closed:: *) +(*Configuration*) +$preferencesPages = { "Notebooks", "Services", "Personas", "Tools" }; +$$preferencesPage = Alternatives @@ $preferencesPages; + +$preferencesScope := $FrontEnd; +$inFrontEndScope := MatchQ[ OwnValues @ $preferencesScope, { _ :> $FrontEnd|_FrontEndObject } ]; + +(* ::**************************************************************************************************************:: *) +(* ::Section::Closed:: *) +(*Scope Utilities*) + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*expandScope*) +expandScope // beginDefinition; +expandScope // Attributes = { HoldFirst }; + +expandScope[ expr_ ] := ReleaseHold[ + HoldComplete @ expr /. + OwnValues @ $preferencesScope /. + HoldPattern @ $scopePlaceholder :> $preferencesScope +]; + +expandScope // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*scopeInitialization*) +scopeInitialization // beginDefinition; + +scopeInitialization[ Initialization :> init_ ] := + expandScope[ Initialization :> Block[ { $scopePlaceholder := $preferencesScope }, init ] ]; + +scopeInitialization /: RuleDelayed[ Initialization, scopeInitialization[ init_ ] ] := + scopeInitialization[ Initialization :> init ]; + +scopeInitialization // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*scopedDynamic*) +scopedDynamic // beginDefinition; +scopedDynamic // Attributes = { HoldFirst }; + +scopedDynamic[ expr_, handlers0: Except[ _Rule|_RuleDelayed ], args___ ] := + With[ { handlers = handlers0 /. (f_ &) :> (Block[ { $scopePlaceholder = $preferencesScope }, f ] &) }, + expandScope @ Dynamic[ expr, handlers, args ] + ]; + +scopedDynamic[ args___ ] := + expandScope @ Dynamic @ args; + +scopedDynamic // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*scopedTrackedDynamic*) +scopedTrackedDynamic // beginDefinition; +scopedTrackedDynamic // Attributes = { HoldFirst }; + +scopedTrackedDynamic[ expr_, args___ ] := + expandScope @ trackedDynamic[ Block[ { $scopePlaceholder = $preferencesScope }, expr ], args ]; + +scopedTrackedDynamic // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*currentTabPageDynamic*) +currentTabPageDynamic // beginDefinition; + +currentTabPageDynamic[ scope_FrontEndObject ] := Dynamic @ CurrentValue[ + $FrontEnd, + { PrivateFrontEndOptions, "DialogSettings", "Preferences", "TabSettings", "AI", "Top" }, + "Notebooks" +]; + +currentTabPageDynamic[ scope_ ] := Dynamic @ CurrentValue[ + scope, + { TaggingRules, "ChatNotebookSettings", "CurrentPreferencesTab" }, + "Notebooks" +]; + +currentTabPageDynamic // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*currentTabPage*) +currentTabPage // beginDefinition; + +currentTabPage[ scope_FrontEndObject ] := CurrentValue[ + $FrontEnd, + { PrivateFrontEndOptions, "DialogSettings", "Preferences", "TabSettings", "AI", "Top" }, + "Notebooks" +]; + +currentTabPage[ scope_ ] := CurrentValue[ + scope, + { TaggingRules, "ChatNotebookSettings", "CurrentPreferencesTab" }, + "Notebooks" +]; + +currentTabPage // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Section::Closed:: *) +(*Main*) + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*createPreferencesContent*) +(* Displays all the content found in the "AI Settings" tab in the preferences dialog. *) +createPreferencesContent // beginDefinition; + +createPreferencesContent[ ] := Enclose[ + Module[ { notebookSettings, serviceSettings, personaSettings, toolSettings, tabView, reset }, + + (* Retrieve the dynamic content for each preferences tab, confirming that it matches the expected types: *) + notebookSettings = ConfirmMatch[ preferencesContent[ "Notebooks" ], _Dynamic | _DynamicModule, "Notebooks" ]; + serviceSettings = ConfirmMatch[ preferencesContent[ "Services" ], _Dynamic | _DynamicModule, "Services" ]; + personaSettings = ConfirmMatch[ preferencesContent[ "Personas" ], _Dynamic | _DynamicModule, "Personas" ]; + toolSettings = ConfirmMatch[ preferencesContent[ "Tools" ], _Dynamic | _DynamicModule, "Tools" ]; + + (* Create a TabView for the preferences content, with the tab state stored in the FE's private options: *) + tabView = TabView[ + { + { "Notebooks", "Notebooks" -> notebookSettings }, + { "Services" , "Services" -> serviceSettings }, + { "Personas" , "Personas" -> personaSettings }, + { "Tools" , "Tools" -> toolSettings } + }, + currentTabPageDynamic @ $preferencesScope, + Background -> None, + FrameMargins -> { { 2, 2 }, { 2, 3 } }, + ImageMargins -> { { 10, 10 }, { 2, 2 } }, + ImageSize -> { 640, Automatic }, + LabelStyle -> "feTabView" (* Defined in the SystemDialog stylesheet: *) + ]; + + (* Create a reset button that will reset preferences to default settings: *) + reset = Pane[ $resetButton, ImageMargins -> { { 20, 0 }, { 0, 10 } }, ImageSize -> 640 ]; + + (* Arrange the TabView and reset button in a Grid layout with vertical spacers: *) + Grid[ + { + $verticalSpacer, + { tabView, "" }, + $verticalSpacer, + { reset, SpanFromLeft }, + $verticalSpacer + }, + Alignment -> Left, + BaseStyle -> "defaultGrid", (* Defined in the SystemDialog stylesheet *) + AutoDelete -> False, + FrameStyle -> { AbsoluteThickness[ 1 ], GrayLevel[ 0.898 ] }, + Dividers -> { False, { 4 -> True } }, + Spacings -> { 0, 0.7 } + ] + ], + throwInternalFailure +]; + +createPreferencesContent // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*preferencesContent*) +(* Define dynamic content for each of the preferences tabs. *) +preferencesContent // beginDefinition; + +(* Content for the "Notebooks" tab: *) +preferencesContent[ "Notebooks" ] := scopedTrackedDynamic[ notebookSettingsPanel[ ], { "Models" } ]; + +(* Content for the "Personas" tab: *) +preferencesContent[ "Personas" ] := scopedTrackedDynamic[ personaSettingsPanel[ ], { "Personas" } ]; + +(* Content for the "Services" tab: *) +preferencesContent[ "Services" ] := scopedTrackedDynamic[ servicesSettingsPanel[ ], { "Models", "Services" } ]; + +(* Content for the "Tools" tab: *) +preferencesContent[ "Tools" ] := toolSettingsPanel[ ]; + +preferencesContent // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Section::Closed:: *) +(*Notebooks*) + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*notebookSettingsPanel*) +notebookSettingsPanel // beginDefinition; + +notebookSettingsPanel[ ] := Pane[ + DynamicModule[ + (* Display a progress indicator until content is loaded via initialization: *) + { display = ProgressIndicator[ Appearance -> "Percolate" ] }, + Dynamic[ display ], + (* createNotebookSettingsPanel is called to initialize the content of the panel: *) + Initialization :> scopeInitialization[ display = createNotebookSettingsPanel[ ] ], + SynchronousInitialization -> False + ], + FrameMargins -> { { 8, 8 }, { 13, 13 } }, + Spacings -> { 0, 1.5 } +]; + +notebookSettingsPanel // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*createNotebookSettingsPanel*) +createNotebookSettingsPanel // beginDefinition; + +createNotebookSettingsPanel[ ] := Enclose[ + Module[ + { + defaultSettingsContent, + interfaceLabel, interfaceContent, + featuresLabel, featuresContent, + content + }, + + (* Retrieve and confirm the content for default settings: *) + defaultSettingsContent = ConfirmMatch[ + scopedTrackedDynamic[ makeDefaultSettingsContent[ ], "Preferences" ], + _Dynamic, + "DefaultSettings" + ]; + + (* Label for the interface section using a style from SystemDialog.nb: *) + interfaceLabel = Style[ "Chat Notebook Cells", "subsectionText" ]; + + (* Retrieve and confirm the content for the chat notebook interface, + ensuring it is not an error from makeInterfaceContent: *) + interfaceContent = ConfirmMatch[ + makeInterfaceContent[ ], + Except[ _makeInterfaceContent ], + "Interface" + ]; + + (* Label for the features section using a style from SystemDialog.nb: *) + featuresLabel = Style[ "Features", "subsectionText" ]; + + (* Retrieve and confirm the content for the chat notebook features, + ensuring it is not an error from makeFeaturesContent: *) + featuresContent = ConfirmMatch[ + makeFeaturesContent[ ], + Except[ _makeFeaturesContent ], + "Features" + ]; + + (* Assemble the default settings and interface content into a grid layout: *) + content = Grid[ + { + { defaultSettingsContent }, + { Spacer[ 1 ] }, + { Spacer[ 1 ] }, + { interfaceLabel }, + { interfaceContent }, + { Spacer[ 1 ] }, + { Spacer[ 1 ] }, + { featuresLabel }, + { featuresContent } + }, + Alignment -> { Left, Baseline }, + Dividers -> { False, { 3 -> True, 7 -> True } }, + ItemSize -> { Fit, Automatic }, + Spacings -> { 0, 0.7 } + ]; + + (* Cache the content in case panel is redrawn: *) + createNotebookSettingsPanel[ ] = content + ], + throwInternalFailure +]; + +createNotebookSettingsPanel // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*makeDefaultSettingsContent*) +makeDefaultSettingsContent // beginDefinition; + +makeDefaultSettingsContent[ ] := Enclose[ + Module[ { assistanceCheckbox, personaSelector, modelSelector, temperatureInput, openAICompletionURLInput }, + (* Checkbox to enable automatic assistance for normal shift-enter evaluations: *) + assistanceCheckbox = ConfirmMatch[ makeAssistanceCheckbox[ ], _Style, "AssistanceCheckbox" ]; + (* The personaSelector is a pop-up menu for selecting the default persona: *) + personaSelector = ConfirmMatch[ makePersonaSelector[ ], _Dynamic, "PersonaSelector" ]; + (* The modelSelector is a dynamic module containing menus to select the service and model separately: *) + modelSelector = ConfirmMatch[ makeModelSelector[ ], _Dynamic, "ModelSelector" ]; + (* The temperatureInput is an input field for setting the default 'temperature' for responses: *) + temperatureInput = ConfirmMatch[ makeTemperatureInput[ ], _Style, "TemperatureInput" ]; + (* The openAICompletionURLInput is an input field for setting URL used for API calls to OpenAI: *) + openAICompletionURLInput = ConfirmMatch[ + makeOpenAICompletionURLInput[ ], + _Style|Nothing, (* Returns Nothing if we're relying on LLMServices for API requests *) + "OpenAICompletionURLInput" + ]; + + (* Assemble the persona selector, model selector, and temperature slider into a grid layout: *) + Grid[ + List /@ { + assistanceCheckbox, + personaSelector, + modelSelector, + temperatureInput, + openAICompletionURLInput + }, + Alignment -> { Left, Baseline }, + Spacings -> { 0, 0.7 } + ] + ], + throwInternalFailure +]; + +makeDefaultSettingsContent // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*makePersonaSelector*) +makePersonaSelector // beginDefinition; +makePersonaSelector[ ] := scopedTrackedDynamic[ makePersonaSelector0[ ], { "Personas" } ]; +makePersonaSelector // endDefinition; + +makePersonaSelector0 // beginDefinition; + +(* Top-level function without arguments, calls the version with personas from GetPersonasAssociation *) +makePersonaSelector0[ ] := + makePersonaSelector0 @ GetPersonasAssociation[ ]; + +(* Overload of makePersonaSelector0 that takes an Association of personas, + converts it to a list of labels for PopupMenu *) +makePersonaSelector0[ personas_Association? AssociationQ ] := + makePersonaSelector0 @ KeyValueMap[ personaPopupLabel, personas ]; + +(* Overload of makePersonaSelector0 that takes a list of rules where each rule is a string to an association, + creates a PopupMenu with this list *) +makePersonaSelector0[ personas: { (_String -> _).. } ] := + highlightControl[ + Row @ { + "Persona:", + Spacer[ 3 ], + PopupMenu[ + scopedDynamic[ + CurrentChatSettings[ $preferencesScope, "LLMEvaluator" ], + Function[ + CurrentValue[ + $preferencesScope, + { TaggingRules, "ChatNotebookSettings", "LLMEvaluator" } + ] = #1 + ] + ], + personas + ] + }, + "Notebooks", + "LLMEvaluator" + ]; + +makePersonaSelector0 // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsubsection::Closed:: *) +(*personaPopupLabel*) +personaPopupLabel // beginDefinition; + +personaPopupLabel[ name_String, persona_Association ] := popupValue[ + name, + personaDisplayName[ name, persona ], + getPersonaMenuIcon[ persona, "Full" ] +]; + +personaPopupLabel // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*makeModelSelector*) +makeModelSelector // beginDefinition; +makeModelSelector[ ] := scopedTrackedDynamic[ makeModelSelector0[ ], { "Models", "Services" } ]; +makeModelSelector // endDefinition; + + +makeModelSelector0 // beginDefinition; + +makeModelSelector0[ ] := + makeModelSelector0 @ $availableServices; + +makeModelSelector0[ services_Association? AssociationQ ] := Enclose[ + DynamicModule[ { default, service, model, state, serviceSelector, modelSelector, highlight }, + + default = currentChatSettings[ $preferencesScope, "Model" ]; + service = ConfirmBy[ extractServiceName @ default, StringQ, "ServiceName" ]; + model = ConfirmBy[ extractModelName @ default , StringQ, "ModelName" ]; + state = If[ modelListCachedQ @ service, "Loaded", "Loading" ]; + + modelSelector = If[ state === "Loaded", + makeModelNameSelector[ + Dynamic @ service, + Dynamic @ model, + Dynamic @ modelSelector, + Dynamic @ state + ], + "" + ]; + + serviceSelector = makeServiceSelector[ + Dynamic @ service, + Dynamic @ model, + Dynamic @ modelSelector, + Dynamic @ state, + services + ]; + + highlight = highlightControl[ Row @ { #1, Spacer[ 1 ], #2 }, "Notebooks", #3 ] &; + + highlightControl[ + Row @ { + highlight[ "LLM Service:", serviceSelector, "ModelService" ], + Spacer[ 5 ], + highlight[ + "Model:", + Dynamic[ + If[ state === "Loading", $loadingPopupMenu, modelSelector ], + TrackedSymbols :> { state, modelSelector } + ], + "ModelName" + ] + }, + "Notebooks", + "Model" + ], + + Initialization :> scopeInitialization[ + modelSelector = catchAlways @ makeModelNameSelector[ + Dynamic @ service, + Dynamic @ model, + Dynamic @ modelSelector, + Dynamic @ state + ]; + state = "Loaded"; + ], + SynchronousInitialization -> False, + SynchronousUpdating -> False, + UnsavedVariables :> { state } + ], + throwInternalFailure +]; + +makeModelSelector0 // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsubsection::Closed:: *) +(*makeServiceSelector*) +makeServiceSelector // beginDefinition; + +makeServiceSelector[ + Dynamic[ service_ ], + Dynamic[ model_ ], + Dynamic[ modelSelector_ ], + Dynamic[ state_ ], + services_ +] := + PopupMenu[ + scopedDynamic[ + extractServiceName @ CurrentChatSettings[ $preferencesScope, "Model" ], + serviceSelectCallback[ Dynamic @ service, Dynamic @ model, Dynamic @ modelSelector, Dynamic @ state ] + ], + KeyValueMap[ popupValue[ #1, #2[ "Service" ], #2[ "Icon" ] ] &, services ] + ]; + +makeServiceSelector // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsubsubsection::Closed:: *) +(*serviceSelectCallback*) +serviceSelectCallback // beginDefinition; + +serviceSelectCallback[ Dynamic[ service_ ], Dynamic[ model_ ], Dynamic[ modelSelector_ ], Dynamic[ state_ ] ] := + serviceSelectCallback[ #1, Dynamic @ service, Dynamic @ model, Dynamic @ modelSelector, Dynamic @ state ] &; + +serviceSelectCallback[ + selected_String, (* The value chosen via PopupMenu *) + Dynamic[ service_Symbol ], + Dynamic[ model_Symbol ], + Dynamic[ modelSelector_Symbol ], + Dynamic[ state_Symbol ] +] := catchAlways[ + service = selected; + + (* Switch model name selector to loading view: *) + If[ ! modelListCachedQ @ selected, state = "Loading" ]; + + (* Now that a new service has been selected, switch the model name to a previously used value for this service + if available. If service has not been chosen before, determine the initial model from the registered service. *) + model = getServiceDefaultModel @ selected; + + (* Store the service/model in FE settings: *) + CurrentChatSettings[ $preferencesScope, "Model" ] = <| "Service" -> service, "Name" -> model |>; + + (* Finish loading the model name selector: *) + If[ state === "Loading", + expandScope @ SessionSubmit[ + Block[ { $scopePlaceholder := $preferencesScope }, + modelSelector = makeModelNameSelector[ + Dynamic @ service, + Dynamic @ model, + Dynamic @ modelSelector, + Dynamic @ state + ] + ]; + state = "Loaded" + ], + modelSelector = makeModelNameSelector[ + Dynamic @ service, + Dynamic @ model, + Dynamic @ modelSelector, + Dynamic @ state + ] + ] +]; + +serviceSelectCallback // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsubsection::Closed:: *) +(*makeModelNameSelector*) +makeModelNameSelector // beginDefinition; + +makeModelNameSelector[ + Dynamic[ service_ ], + Dynamic[ model_ ], + Dynamic[ modelSelector_ ], + Dynamic[ state_ ] +] := Enclose[ + Catch @ Module[ { models, current, default, fallback }, + + ensureServiceName @ service; + ConfirmAssert[ StringQ @ service, "ServiceName" ]; + + models = ConfirmMatch[ + Block[ { $allowConnectionDialog = False }, getServiceModelList @ service ], + { __Association } | Missing[ "NotConnected" ] | Missing[ "NoModelList" ], + "ServiceModelList" + ]; + + If[ models === Missing[ "NotConnected" ], + Throw @ serviceConnectButton[ + Dynamic @ service, + Dynamic @ model, + Dynamic @ modelSelector, + Dynamic @ state + ] + ]; + + If[ models === Missing[ "NoModelList" ], + Throw @ modelNameInputField[ + Dynamic @ service, + Dynamic @ model, + Dynamic @ modelSelector, + Dynamic @ state + ] + ]; + + current = extractModelName @ CurrentChatSettings[ $preferencesScope, "Model" ]; + default = ConfirmBy[ getServiceDefaultModel @ service, StringQ, "DefaultName" ]; + fallback = <| "Service" -> service, "Name" -> default |>; + + If[ ! MemberQ[ models, KeyValuePattern[ "Name" -> current ] ], + CurrentValue[ $preferencesScope, { TaggingRules, "ChatNotebookSettings", "Model" } ] = fallback + ]; + + With[ { m = fallback }, + PopupMenu[ + scopedDynamic[ + Replace[ + extractModelName @ CurrentChatSettings[ $preferencesScope, "Model" ], + { + Except[ _String ] :> ( + CurrentValue[ $preferencesScope, { TaggingRules, "ChatNotebookSettings", "Model" } ] = m + ) + } + ], + modelSelectCallback[ Dynamic @ service, Dynamic @ model ] + ], + Block[ { $noIcons = TrueQ @ $cloudNotebooks }, + Map[ popupValue[ #[ "Name" ], #[ "DisplayName" ], #[ "Icon" ] ] &, models ] + ], + ImageSize -> Automatic + ] + ] + ], + throwInternalFailure +]; + +makeModelNameSelector // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsubsection::Closed:: *) +(*modelNameInputField*) +modelNameInputField // beginDefinition; + +modelNameInputField[ Dynamic[ service_ ], Dynamic[ model_ ], Dynamic[ modelSelector_ ], Dynamic[ state_ ] ] := + prefsInputField[ + "Model:", + scopedDynamic[ + Replace[ + extractModelName @ CurrentChatSettings[ $preferencesScope, "Model" ], + Except[ _String ] :> "" + ], + { None, modelSelectCallback[ Dynamic @ service, Dynamic @ model ] } + ], + String, + ImageSize -> { 200, Automatic } + ]; + +modelNameInputField // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsubsection::Closed:: *) +(*serviceConnectButton*) +serviceConnectButton // beginDefinition; + +serviceConnectButton[ + Dynamic[ service_ ], + Dynamic[ model_ ], + Dynamic[ modelSelector_ ], + Dynamic[ state_ ] +] := + Button[ + "Connect for model list", + Needs[ "Wolfram`LLMFunctions`" -> None ]; + Replace[ + (* cSpell: ignore genconerr *) + Quiet[ Wolfram`LLMFunctions`APIs`Common`ConnectToService @ service, { ServiceConnect::genconerr } ], + _ServiceObject :> + If[ ListQ @ getServiceModelList @ service, + serviceSelectCallback[ + service, + Dynamic @ service, + Dynamic @ model, + Dynamic @ modelSelector, + Dynamic @ state + ] + ] + ], + Method -> "Queued" + ]; + +serviceConnectButton // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsubsubsection::Closed:: *) +(*modelSelectCallback*) +modelSelectCallback // beginDefinition; + +modelSelectCallback[ Dynamic[ service_ ], Dynamic[ model_ ] ] := + modelSelectCallback[ #1, Dynamic @ service, Dynamic @ model ] &; + +modelSelectCallback[ + selected_String, (* The value chosen via PopupMenu *) + Dynamic[ service_Symbol ], + Dynamic[ model_Symbol ] +] := catchAlways @ Enclose[ + model = selected; + + ensureServiceName @ service; + ConfirmAssert[ StringQ @ service, "ServiceName" ]; + + (* Remember the selected model for the given service, so it will be automatically chosen + when choosing this service again: *) + CurrentValue[ $preferencesScope, { TaggingRules, "ChatNotebookSettings", "ServiceDefaultModel", service } ] = model; + + (* Store the service/model in FE settings: *) + CurrentValue[ $preferencesScope, { TaggingRules, "ChatNotebookSettings", "Model" } ] = <| + "Service" -> service, + "Name" -> model + |> + , + throwInternalFailure +]; + +modelSelectCallback // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*makeAssistanceCheckbox*) +makeAssistanceCheckbox // beginDefinition; + +makeAssistanceCheckbox[ ] := highlightControl[ + prefsCheckbox[ + scopedDynamic[ + TrueQ @ CurrentChatSettings[ $preferencesScope, "Assistance" ], + (CurrentChatSettings[ $preferencesScope, "Assistance" ] = #1) & + ], + infoTooltip[ + "Enable automatic assistance", + "If enabled, automatic AI provided suggestions will be added following evaluation results." + ] + ], + "Notebooks", + "Assistance" +]; + +makeAssistanceCheckbox // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*makeTemperatureInput*) +makeTemperatureInput // beginDefinition; + +makeTemperatureInput[ ] := highlightControl[ + prefsInputField[ + "Temperature:", + scopedDynamic[ + CurrentChatSettings[ $preferencesScope, "Temperature" ], + { + None, + If[ NumberQ @ #, CurrentChatSettings[ $preferencesScope, "Temperature" ] = # ] & + } + ], + Number, + ImageSize -> { 50, Automatic } + ], + "Notebooks", + "Temperature" +]; + +makeTemperatureInput // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*makeOpenAICompletionURLInput*) +makeOpenAICompletionURLInput // beginDefinition; + +makeOpenAICompletionURLInput[ ] := + makeOpenAICompletionURLInput @ $useLLMServices; + +makeOpenAICompletionURLInput[ True ] := + Nothing; + +makeOpenAICompletionURLInput[ False ] := highlightControl[ + prefsInputField[ + "Chat Completion URL:", + scopedDynamic[ + (* cSpell: ignore AIAPI *) + CurrentChatSettings[ $preferencesScope, "OpenAIAPICompletionURL" ], + { + None, + If[ StringQ @ #, CurrentChatSettings[ $preferencesScope, "OpenAIAPICompletionURL" ] = # ] & + } + ], + String, + ImageSize -> { 200, Automatic } + ], + "Notebooks", + "OpenAIAPICompletionURL" +]; + +makeOpenAICompletionURLInput // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*makeInterfaceContent*) +makeInterfaceContent // beginDefinition; + +makeInterfaceContent[ ] := Enclose[ + Module[ { formatCheckbox, includeHistory, chatHistoryLength, mergeMessages }, + + formatCheckbox = ConfirmMatch[ makeFormatCheckbox[ ] , _Style, "FormatCheckbox" ]; + includeHistory = ConfirmMatch[ makeIncludeHistoryCheckbox[ ], _Style, "ChatHistory" ]; + chatHistoryLength = ConfirmMatch[ makeChatHistoryLengthInput[ ], _Style, "ChatHistoryLength" ]; + mergeMessages = ConfirmMatch[ makeMergeMessagesCheckbox[ ] , _Style, "MergeMessages" ]; + + Grid[ + { + { formatCheckbox }, + { includeHistory }, + { chatHistoryLength }, + { mergeMessages } + }, + Alignment -> { Left, Baseline }, + Spacings -> { 0, 0.7 } + ] + ], + throwInternalFailure +]; + +makeInterfaceContent // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*makeFormatCheckbox*) +makeFormatCheckbox // beginDefinition; + +makeFormatCheckbox[ ] := highlightControl[ + prefsCheckbox[ + scopedDynamic[ + TrueQ @ CurrentChatSettings[ $preferencesScope, "AutoFormat" ], + (CurrentChatSettings[ $preferencesScope, "AutoFormat" ] = #1) & + ], + "Format chat output" + ], + "Notebooks", + "AutoFormat" +]; + +makeFormatCheckbox // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*makeIncludeHistoryCheckbox*) +makeIncludeHistoryCheckbox // beginDefinition; + +makeIncludeHistoryCheckbox[ ] := highlightControl[ + prefsCheckbox[ + scopedDynamic[ + MatchQ[ CurrentChatSettings[ $preferencesScope, "IncludeHistory" ], True|Automatic ], + (CurrentChatSettings[ $preferencesScope, "IncludeHistory" ] = #1) & + ], + infoTooltip[ + "Include chat history", + "If enabled, cells preceding the chat input will be included as additional context for the LLM." + ] + ], + "Notebooks", + "IncludeHistory" +]; + +makeIncludeHistoryCheckbox // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*makeChatHistoryLengthInput*) +makeChatHistoryLengthInput // beginDefinition; + +makeChatHistoryLengthInput[ ] := highlightControl[ + infoTooltip[ + prefsInputField[ + "Chat history length:", + scopedDynamic[ + CurrentChatSettings[ $preferencesScope, "ChatHistoryLength" ], + { + None, + If[ NumberQ @ # && NonNegative @ #, + CurrentChatSettings[ $preferencesScope, "ChatHistoryLength" ] = Floor[ # ] + ] & + } + ], + Number, + ImageSize -> { 50, Automatic } + ], + "Maximum number of cells to include in chat history" + ], + "Notebooks", + "ChatHistoryLength" +]; + +makeChatHistoryLengthInput // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*makeMergeMessagesCheckbox*) +makeMergeMessagesCheckbox // beginDefinition; + +makeMergeMessagesCheckbox[ ] := highlightControl[ + prefsCheckbox[ + scopedDynamic[ + MatchQ[ CurrentChatSettings[ $preferencesScope, "MergeMessages" ], True|Automatic ], + (CurrentChatSettings[ $preferencesScope, "MergeMessages" ] = #1) & + ], + infoTooltip[ + "Merge chat messages", + "If enabled, adjacent cells with the same author will be merged into a single chat message." + ] + ], + "Notebooks", + "MergeMessages" +]; + +makeMergeMessagesCheckbox // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*makeFeaturesContent*) +makeFeaturesContent // beginDefinition; + +makeFeaturesContent[ ] := Enclose[ + Module[ { multimodal, toolsEnabled, toolFrequency }, + + multimodal = ConfirmMatch[ makeMultimodalMenu[ ] , _Style, "Multimodal" ]; + toolsEnabled = ConfirmMatch[ makeToolsEnabledMenu[ ] , _Style, "ToolsEnabled" ]; + toolFrequency = ConfirmMatch[ makeToolCallFrequencySelector[ ], _Style, "ToolFrequency" ]; + + Grid[ + { + { multimodal }, + { toolsEnabled }, + { toolFrequency } + }, + Alignment -> { Left, Baseline }, + Spacings -> { 0, 0.7 } + ] + ], + throwInternalFailure +]; + +makeFeaturesContent // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*makeMultimodalMenu*) +makeMultimodalMenu // beginDefinition; + +makeMultimodalMenu[ ] := highlightControl[ + Grid[ + { + { + Style[ "Enable multimodal content: ", "leadinText" ], + PopupMenu[ + scopedDynamic @ CurrentChatSettings[ $preferencesScope, "Multimodal" ], + { + Automatic -> "Automatic by model", + True -> "Always enabled", + False -> "Always disabled" + }, + MenuStyle -> "controlText" + ] + } + }, + Alignment -> { Left, Baseline }, + Spacings -> 0.5 + ], + "Notebooks", + "Multimodal" +]; + +makeMultimodalMenu // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*makeToolsEnabledMenu*) +makeToolsEnabledMenu // beginDefinition; + +makeToolsEnabledMenu[ ] := highlightControl[ + Grid[ + { + { + Style[ "Enable tools: ", "leadinText" ], + PopupMenu[ + scopedDynamic @ CurrentChatSettings[ $preferencesScope, "ToolsEnabled" ], + { + Automatic -> "Automatic by model", + True -> "Always enabled", + False -> "Always disabled" + }, + MenuStyle -> "controlText" + ] + } + }, + Alignment -> { Left, Baseline }, + BaselinePosition -> 1, + Spacings -> 0.5 + ], + "Notebooks", + "ToolsEnabled" +]; + +makeToolsEnabledMenu // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*makeToolCallFrequencySelector*) +makeToolCallFrequencySelector // beginDefinition; + +makeToolCallFrequencySelector[ ] := highlightControl[ + DynamicModule[ { type, frequency }, + Grid[ + { + { + Style[ "Tool call frequency:", "leadinText" ], + PopupMenu[ + scopedDynamic[ + type, + Function[ + If[ # === Automatic + , + type = Automatic; + CurrentChatSettings[ $preferencesScope, "ToolCallFrequency" ] = Automatic + , + type = "Custom"; + CurrentChatSettings[ $preferencesScope, "ToolCallFrequency" ] = 0.5 + ] + ] + ], + { + Automatic -> "Automatic", + "Custom" -> "Custom" + }, + MenuStyle -> "controlText" + ], + PaneSelector[ + { + Automatic -> "", + "Custom" -> Grid[ + { + { + Spacer[ 5 ], + Style[ "Rare", "defaultSubtext" ], + Slider[ + scopedDynamic[ + frequency, + { + Function[ frequency = # ], + Function[ + CurrentChatSettings[ $preferencesScope, "ToolCallFrequency" ] = #; + frequency = # + ] + } + ], + ImageSize -> { 100, Automatic } + ], + Style[ "Often", "defaultSubtext" ] + } + }, + Spacings -> { 0.4, 0.7 } + ] + }, + Dynamic[ type ], + ImageSize -> Automatic + ] + } + }, + Alignment -> { Left, Baseline }, + BaselinePosition -> 1, + Spacings -> 0.5 + ], + Initialization :> scopeInitialization @ + With[ { val = CurrentChatSettings[ $preferencesScope, "ToolCallFrequency" ] }, + type = If[ NumberQ @ val, "Custom", Automatic ]; + frequency = If[ NumberQ @ val, val, 0.5 ]; + ] + ], + "Notebooks", + "ToolCallFrequency" +]; + +makeToolCallFrequencySelector // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Section::Closed:: *) +(*Services*) + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*servicesSettingsPanel*) +servicesSettingsPanel // beginDefinition; + +servicesSettingsPanel[ ] := Enclose[ + Module[ { settingsLabel, settings, serviceGrid }, + + settingsLabel = Style[ "Registered Services", "subsectionText" ]; + settings = ConfirmMatch[ makeModelSelector[ ], _Dynamic, "ServicesSettings" ]; + serviceGrid = ConfirmMatch[ makeServiceGrid[ ], _Grid, "ServiceGrid" ]; + + Pane[ + Grid[ + { + { settingsLabel }, + { settings }, + { serviceGrid } + }, + Alignment -> { Left, Baseline }, + ItemSize -> { Fit, Automatic }, + Spacings -> { 0, 0.7 } + ], + ImageMargins -> 8 + ] + ], + throwInternalFailure +]; + +servicesSettingsPanel // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*makeServiceGrid*) +makeServiceGrid // beginDefinition; + +makeServiceGrid[ ] := Grid[ + Join[ + { { Spacer[ 1 ], "Service", SpanFromLeft, "Authentication", "", Spacer[ 1 ] } }, + KeyValueMap[ makeServiceGridRow, $availableServices ] + ], + Alignment -> { Left, Baseline }, + Background -> { { }, { GrayLevel[ 0.898 ], { White } } }, + ItemSize -> { { Automatic, Automatic, Scaled[ .3 ], Fit, Automatic }, Automatic }, + Dividers -> { True, All }, + FrameStyle -> GrayLevel[ 0.898 ], + Spacings -> { Automatic, 0.7 } +]; + +makeServiceGrid // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*makeServiceGridRow*) +makeServiceGridRow // beginDefinition; + +makeServiceGridRow[ name_String, data_Association ] := { + Spacer[ 1 ], + resizeMenuIcon @ inlineTemplateBoxes @ serviceIcon @ data, + name, + makeServiceAuthenticationDisplay @ name, + deleteServiceButton @ name, + Spacer[ 1 ] +}; + +makeServiceGridRow // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*deleteServiceButton*) +deleteServiceButton // beginDefinition; + +deleteServiceButton[ "OpenAI" ] := Framed[ + Button[ + Insert[ chatbookIcon[ "ToolManagerBin", False ], GrayLevel[ 0.8 ], { 1, 1, 1 } ], + Null, + Enabled -> False, + $deleteServiceButtonOptions + ], + $deleteServiceButtonFrameOptions +]; + +deleteServiceButton[ service_String ] := Tooltip[ + Framed[ + Button[ + $trashBin, + Needs[ "LLMServices`" -> None ]; + LLMServices`UnregisterService @ service; + disconnectService @ service; + updateDynamics[ { "Services", "Preferences" } ], + $deleteServiceButtonOptions + ], + $deleteServiceButtonFrameOptions + ], + "Unregister service connection" +]; + +deleteServiceButton // endDefinition; + + +$deleteServiceButtonOptions = Sequence[ + Appearance -> "Suppressed", + ContentPadding -> False, + FrameMargins -> 0, + ImageMargins -> 0, + Method -> "Queued" +]; + + +$deleteServiceButtonFrameOptions = Sequence[ + ContentPadding -> False, + FrameMargins -> 0, + FrameStyle -> Transparent, + ImageMargins -> 0 +]; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*makeServiceAuthenticationDisplay*) +makeServiceAuthenticationDisplay // beginDefinition; + +makeServiceAuthenticationDisplay[ service_String ] := + DynamicModule[ { display }, + display = ProgressIndicator[ Appearance -> "Percolate" ]; + Dynamic[ display, TrackedSymbols :> { display } ], + Initialization :> scopeInitialization @ createServiceAuthenticationDisplay[ service, Dynamic[ display ] ], + SynchronousInitialization -> False + ]; + +makeServiceAuthenticationDisplay // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsubsubsection::Closed:: *) +(*createServiceAuthenticationDisplay*) +createServiceAuthenticationDisplay // beginDefinition; + +createServiceAuthenticationDisplay[ service_, Dynamic[ display_ ] ] := Enclose[ + Module[ { type }, + type = ConfirmBy[ credentialType @ service, StringQ, "CredentialType" ]; + display = Row[ + { + If[ type === "None", + Style[ "\[Checkmark]", ShowContents -> False ], + Style[ "\[Checkmark]", FontColor -> Gray ] + ], + Spacer[ 20 ], + connectOrDisconnectButton[ service, type, Dynamic @ display ] + }, + Alignment -> { Left, Baseline } + ]; + ], + throwInternalFailure +]; + +createServiceAuthenticationDisplay // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsubsubsection::Closed:: *) +(*connectOrDisconnectButton*) +connectOrDisconnectButton // beginDefinition; + +connectOrDisconnectButton[ service_String, "None", Dynamic[ display_ ] ] := + Button[ + "Connect", + display = ProgressIndicator[ Appearance -> "Percolate" ]; + clearConnectionCache[ service, False ]; + Quiet[ Wolfram`LLMFunctions`APIs`Common`ConnectToService @ service, { ServiceConnect::genconerr } ]; + createServiceAuthenticationDisplay[ service, Dynamic @ display ], + Method -> "Queued" + ]; + +connectOrDisconnectButton[ service_String, "SystemCredential"|"Environment"|"ServiceConnect", Dynamic[ display_ ] ] := + Button[ + "Disconnect", + display = ProgressIndicator[ Appearance -> "Percolate" ]; + disconnectService @ service; + createServiceAuthenticationDisplay[ service, Dynamic @ display ], + Method -> "Queued" + ]; + +connectOrDisconnectButton // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Section::Closed:: *) +(*Personas*) + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*personaSettingsPanel*) +personaSettingsPanel // beginDefinition; + +personaSettingsPanel[ ] := + DynamicModule[ + { display = ProgressIndicator[ Appearance -> "Percolate" ] }, + Dynamic[ display ], + Initialization :> scopeInitialization[ display = CreatePersonaManagerPanel[ ] ], + SynchronousInitialization -> False, + UnsavedVariables :> { display } + ]; + +personaSettingsPanel // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Section::Closed:: *) +(*Tools*) + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*toolSettingsPanel*) +toolSettingsPanel // beginDefinition; + +toolSettingsPanel[ ] := + DynamicModule[ + { display = ProgressIndicator[ Appearance -> "Percolate" ] }, + Dynamic[ display ], + Initialization :> scopeInitialization[ display = CreateLLMToolManagerPanel[ ] ], + SynchronousInitialization -> False, + UnsavedVariables :> { display } + ]; + +toolSettingsPanel // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Section::Closed:: *) +(*Common*) + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*prefsInputField*) +prefsInputField // beginDefinition; + +(* cSpell: ignore leadin *) +prefsInputField[ label_, value_Dynamic, type_, opts: OptionsPattern[ ] ] := Grid[ + { { label, prefsInputField0[ value, type, opts ] } }, + Alignment -> { Automatic, Baseline }, + BaseStyle -> { "leadinText" }, + Spacings -> 0.5 +]; + +prefsInputField // endDefinition; + + +prefsInputField0 // beginDefinition; + +prefsInputField0[ value_Dynamic, type_, opts: OptionsPattern[ ] ] := RawBoxes @ StyleBox[ + TemplateBox[ + { + value, + type, + DeleteDuplicatesBy[ + Flatten @ { + opts, + Alignment -> { Left, Top }, + BaseStyle -> { "controlText" }, + ContinuousAction -> False, + ImageMargins -> { { Automatic, Automatic }, { Automatic, Automatic } } + }, + ToString @* First + ], + Automatic + }, + "InputFieldAppearance:RoundedFrame" + ], + FrameBoxOptions -> { BaselinePosition -> Top -> Scaled[ 1.3 ] } +]; + +prefsInputField0 // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*prefsCheckbox*) +prefsCheckbox // beginDefinition; + +prefsCheckbox[ value_Dynamic, label_, rest___ ] := + Grid[ + { { Checkbox[ value, rest ], clickableCheckboxLabel[ value, label ] } }, + Alignment -> { Left, Center }, + Spacings -> 0.2 + ]; + +prefsCheckbox // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*clickableCheckboxLabel*) +clickableCheckboxLabel // beginDefinition; + +clickableCheckboxLabel[ value_Dynamic, label_ ] := Toggler[ + value, + { True -> label, False -> label }, + BaselinePosition -> Scaled[ 0.2 ], + BaseStyle -> { "checkboxText" } +]; + +clickableCheckboxLabel // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*infoTooltip*) +infoTooltip // beginDefinition; +infoTooltip[ label_, tooltip_ ] := Row @ { label, Spacer[ 3 ], Tooltip[ $infoIcon, tooltip ] }; +infoTooltip // endDefinition; + +$infoIcon = chatbookIcon[ "InformationTooltip", False ]; + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*popupValue*) +popupValue // beginDefinition; + +popupValue[ value_String ] := + value -> value; + +popupValue[ value_String, label: Except[ $$unspecified ] ] := + value -> label; + +popupValue[ value_String, label: Except[ $$unspecified ], icon: Except[ $$unspecified ] ] := + If[ TrueQ @ $noIcons, + value -> label, + value -> Row[ { resizeMenuIcon @ inlineTemplateBoxes @ icon, label }, Spacer[ 1 ] ] + ]; + +popupValue // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*ensureServiceName*) +ensureServiceName // beginDefinition; +ensureServiceName // Attributes = { HoldFirst }; + +ensureServiceName[ symbol_Symbol ] := + With[ { service = symbol }, + service /; StringQ @ service + ]; + +ensureServiceName[ symbol_Symbol ] := + With[ { service = extractServiceName @ CurrentChatSettings[ $preferencesScope, "Model" ] }, + (symbol = service) /; StringQ @ service + ]; + +ensureServiceName // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*extractServiceName*) +extractServiceName // beginDefinition; +extractServiceName[ _String ] := "OpenAI"; +extractServiceName[ KeyValuePattern[ "Service" -> service_String ] ] := service; +extractServiceName[ _ ] := $DefaultModel[ "Service" ]; +extractServiceName // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*extractModelName*) +extractModelName // beginDefinition; +extractModelName[ name_String ] := name; +extractModelName[ KeyValuePattern[ "Name" -> name_String ] ] := name; +extractModelName[ _ ] := $DefaultModel[ "Name" ]; +extractModelName // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*getServiceDefaultModel*) +getServiceDefaultModel // beginDefinition; + +getServiceDefaultModel[ selected_String ] := Replace[ + (* Use the last model name that was selected for this service if it exists: *) + CurrentValue[ + $preferencesScope, + { TaggingRules, "ChatNotebookSettings", "ServiceDefaultModel", selected } + ], + + (* Otherwise determine a starting model from the registered service: *) + $$unspecified :> ( + CurrentValue[ + $preferencesScope, + { TaggingRules, "ChatNotebookSettings", "ServiceDefaultModel", selected } + ] = chooseDefaultModelName @ selected + ) +]; + +getServiceDefaultModel // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsubsubsection::Closed:: *) +(*chooseDefaultModelName*) +(* + Choose a default initial model according to the following rules: + 1. If the service name is the same as the one in $DefaultModel, use the model name in $DefaultModel. + 2. If the registered service specifies a "DefaultModel" property, we'll use that. + 3. If the model list is already cached for the service, we'll use the first model in that list. + 4. Otherwise, give Automatic to indicate a model name that must be resolved later. +*) +chooseDefaultModelName // beginDefinition; +chooseDefaultModelName[ service_String ] /; service === $DefaultModel[ "Service" ] := $DefaultModel[ "Name" ]; +chooseDefaultModelName[ service_String ] := chooseDefaultModelName @ $availableServices @ service; +chooseDefaultModelName[ KeyValuePattern[ "DefaultModel" -> model_ ] ] := toModelName @ model; +chooseDefaultModelName[ KeyValuePattern[ "CachedModels" -> models_List ] ] := chooseDefaultModelName @ models; +chooseDefaultModelName[ { model_, ___ } ] := toModelName @ model; +chooseDefaultModelName[ service_ ] := Automatic; +chooseDefaultModelName // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Section::Closed:: *) +(*ServiceConnection Utilities*) + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*disconnectService*) +disconnectService // beginDefinition; + +disconnectService[ service_String ] := + With[ { key = credentialKey @ service }, + Unset @ SystemCredential @ key; + SetEnvironment[ key -> None ]; + clearConnectionCache @ service; + If[ extractServiceName @ CurrentChatSettings[ $preferencesScope, "Model" ] === service, + CurrentChatSettings[ $preferencesScope, "Model" ] = $DefaultModel + ]; + updateDynamics[ "Services" ] + ]; + +disconnectService // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*credentialType*) +credentialType // beginDefinition; +credentialType[ service_String? systemCredentialQ ] := "SystemCredential"; +credentialType[ service_String? environmentCredentialQ ] := "Environment"; +credentialType[ service_String? savedConnectionQ ] := "ServiceConnect"; +credentialType[ service_String? serviceConnectionQ ] := "ServiceConnect"; +credentialType[ service_String ] := "None"; +credentialType // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*systemCredentialQ*) +systemCredentialQ // beginDefinition; +systemCredentialQ[ service_String ] := StringQ[ SystemCredential @ credentialKey @ service ]; +systemCredentialQ // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*environmentCredentialQ*) +environmentCredentialQ // beginDefinition; +environmentCredentialQ[ service_String ] := StringQ[ Environment @ credentialKey @ service ]; +environmentCredentialQ // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*credentialKey*) +credentialKey // beginDefinition; +credentialKey[ service_String ] := ToUpperCase @ service <> "_API_KEY"; +credentialKey // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*savedConnectionQ*) +savedConnectionQ // beginDefinition; + +savedConnectionQ[ service_String ] := ( + Needs[ "OAuth`" -> None ]; + MatchQ[ ServiceConnections`SavedConnections @ service, { __ } ] +); + +savedConnectionQ // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*serviceConnectionQ*) +serviceConnectionQ // beginDefinition; + +serviceConnectionQ[ service_String ] := ( + Needs[ "OAuth`" -> None ]; + MatchQ[ ServiceConnections`ServiceConnections @ service, { __ } ] +); + +serviceConnectionQ // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*clearConnectionCache*) +clearConnectionCache // beginDefinition; + +clearConnectionCache[ service_String ] := + clearConnectionCache[ service, True ]; + +clearConnectionCache[ service_String, delete: True|False ] := ( + Needs[ "Wolfram`LLMFunctions`" -> None ]; + If[ delete, + Needs[ "OAuth`" -> None ]; + ServiceConnections`DeleteConnection /@ ServiceConnections`SavedConnections @ service; + ServiceConnections`DeleteConnection /@ ServiceConnections`ServiceConnections @ service; + ]; + If[ AssociationQ @ Wolfram`LLMFunctions`APIs`Common`$ConnectionCache, + KeyDropFrom[ Wolfram`LLMFunctions`APIs`Common`$ConnectionCache, service ] + ]; + InvalidateServiceCache[ ]; +); + +clearConnectionCache // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Section::Closed:: *) +(*UI Elements*) + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*$loadingPopupMenu*) +$loadingPopupMenu = PopupMenu[ "x", { "x" -> ProgressIndicator[ Appearance -> "Percolate" ] }, Enabled -> False ]; + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*$verticalSpacer*) +$verticalSpacer = { Pane[ "", ImageSize -> { Automatic, 20 } ], SpanFromLeft }; + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*$trashBin*) +$trashBin := Mouseover[ + Insert[ chatbookIcon[ "ToolManagerBin", False ], GrayLevel[ 0.65 ], { 1, 1, 1 } ], + Insert[ chatbookIcon[ "ToolManagerBin", False ], Hue[ 0.59, 0.9, 0.93 ], { 1, 1, 1 } ] +]; + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*$resetButton*) +$resetButton := + Module[ { icon, label }, + icon = Style[ + Dynamic @ RawBoxes @ FEPrivate`FrontEndResource[ "FEBitmaps", "SyntaxColorResetIcon" ][ + RGBColor[ 0.3921, 0.3921, 0.3921 ] + ], + GraphicsBoxOptions -> { BaselinePosition -> Scaled[ 0.1 ] } + ]; + + label = Grid[ + { { icon, Dynamic @ FEPrivate`FrontEndResource[ "PreferencesDialog", "ResetAllSettingsText" ] } }, + Alignment -> { Automatic, Baseline } + ]; + + expandScope @ Button[ + label, + + Needs[ "Wolfram`Chatbook`" -> None ]; + resetChatPreferences @ currentTabPage @ $preferencesScope + , + BaseStyle -> { + FontFamily -> Dynamic @ FrontEnd`CurrentValue[ "ControlsFontFamily" ], + FontSize -> Dynamic @ FrontEnd`CurrentValue[ "ControlsFontSize" ], + FontColor -> Black + }, + ImageSize -> Automatic, + Method -> "Queued" + ] + ]; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*resetChatPreferences*) +resetChatPreferences // beginDefinition; + +resetChatPreferences[ "Notebooks" ] := expandScope[ + FrontEndExecute @ FrontEnd`RemoveOptions[ + $preferencesScope, + { LLMEvaluator, { TaggingRules, "ChatNotebookSettings" } } + ]; + updateDynamics[ "Preferences" ]; +]; + +resetChatPreferences[ "Personas" ] := + (* TODO: this won't work when $preferencesScope is something other than $FrontEnd *) + With[ { path = Sequence[ PrivateFrontEndOptions, "InterfaceSettings", "Chatbook" ] }, + (* TODO: choice dialog to uninstall personas *) + CurrentValue[ $preferencesScope, { path, "VisiblePersonas" } ] = $corePersonaNames; + CurrentValue[ $preferencesScope, { path, "PersonaFavorites" } ] = $corePersonaNames; + resetChatPreferences[ "Notebooks" ]; + ]; + +resetChatPreferences[ "Tools" ] := ( + (* TODO: choice dialog to uninstall tools *) + resetChatPreferences[ "Notebooks" ]; +); + +resetChatPreferences[ "Services" ] := ( + (* TODO: choice dialog to clear service connections *) + Needs[ "LLMServices`" -> None ]; + LLMServices`ResetServices[ ]; + InvalidateServiceCache[ ]; + resetChatPreferences[ "Notebooks" ]; +); + +resetChatPreferences // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Section::Closed:: *) +(*Navigation*) + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*openPreferencesPage*) +openPreferencesPage // beginDefinition; + +openPreferencesPage[ ] := + openPreferencesPage[ "Notebooks" ]; + +openPreferencesPage[ page: $$preferencesPage ] := + NotebookTools`OpenPreferencesDialog @ { "AI", page }; + +openPreferencesPage[ page: $$preferencesPage, id_ ] := + NotebookTools`OpenPreferencesDialog[ { "AI", page }, { "AI", page, id } ]; + +openPreferencesPage // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*highlightControl*) +highlightControl // beginDefinition; +highlightControl[ expr_, tab_, id_ ] /; $inFrontEndScope := Style[ expr, Background -> highlightColor[ tab, id ] ]; +highlightControl[ expr_, tab_, id_ ] := Style @ expr; +highlightControl // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*highlightColor*) +highlightColor // beginDefinition; + +highlightColor[ tab_, id_ ] := + With[ + { + hid := FrontEnd`AbsoluteCurrentValue[ + $FrontEndSession, + { PrivateFrontEndOptions, "DialogSettings", "Preferences", "ControlHighlight", "HighlightID" }, + None + ], + color := FrontEnd`AbsoluteCurrentValue[ EvaluationNotebook[ ], { TaggingRules, "HighlightColor" }, None ] + }, + Dynamic @ If[ hid === { "AI", tab, id }, color, None ] + ]; + +highlightColor // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Section::Closed:: *) +(*Package Footer*) +If[ Wolfram`ChatbookInternal`$BuildingMX, + Null; +]; + +End[ ]; +EndPackage[ ]; diff --git a/Source/Chatbook/Prompting.wl b/Source/Chatbook/Prompting.wl index 7b81f476..f25f4ec6 100644 --- a/Source/Chatbook/Prompting.wl +++ b/Source/Chatbook/Prompting.wl @@ -170,7 +170,8 @@ becomes ``Styled message``."; $basePromptComponents[ "SpecialURI" ] = "\ * You will occasionally see markdown links with special URI schemes, e.g. ![label](scheme://content-id) that represent \ -interactive interface elements. You can use these in your responses to display the same elements to the user."; +interactive interface elements. You can use these in your responses to display the same elements to the user, but they \ +must be formatted as image links (include the '!' at the beginning). If you do not include the '!', the link will fail."; $basePromptComponents[ "SpecialURIAudio" ] = "\ * ![label](audio://content-id) represents an interactive audio player."; diff --git a/Source/Chatbook/Sandbox.wl b/Source/Chatbook/Sandbox.wl index 954bc983..3fa1f771 100644 --- a/Source/Chatbook/Sandbox.wl +++ b/Source/Chatbook/Sandbox.wl @@ -432,7 +432,7 @@ initializeExpressions[ flat: HoldComplete @ Association @ OrderlessPatternSequen ReplacePart[ flat, Thread[ pos -> Extract[ flat, pos ] ] ] ]; -initializeExpressions[ failed: HoldComplete[ _Failure ] ] := +initializeExpressions[ failed: HoldComplete[ _Failure|$Failed|$Aborted ] ] := failed; initializeExpressions // endDefinition; diff --git a/Source/Chatbook/SendChat.wl b/Source/Chatbook/SendChat.wl index 85402828..5249e805 100644 --- a/Source/Chatbook/SendChat.wl +++ b/Source/Chatbook/SendChat.wl @@ -67,7 +67,7 @@ $buffer = ""; sendChat // beginDefinition; sendChat[ evalCell_, nbo_, settings0_ ] /; $useLLMServices := catchTopAs[ ChatbookAction ] @ Enclose[ - Module[ { cells0, cells, target, settings, id, key, messages, data, persona, cell, cellObject, container, task }, + Module[ { cells0, cells, target, settings, messages, data, persona, cell, cellObject, container, task }, initFETaskWidget @ nbo; @@ -92,15 +92,8 @@ sendChat[ evalCell_, nbo_, settings0_ ] /; $useLLMServices := catchTopAs[ Chatbo AppendTo[ settings, "ChatGroupSettings" -> getChatGroupSettings @ evalCell ] ]; - id = Lookup[ settings, "ID" ]; - key = toAPIKey[ Automatic, id ]; - If[ ! settings[ "IncludeHistory" ], cells = { evalCell } ]; - If[ ! StringQ @ key, throwFailure[ "NoAPIKey" ] ]; - - settings[ "OpenAIKey" ] = key; - { messages, data } = Reap[ ConfirmMatch[ constructMessages[ settings, cells ], @@ -307,12 +300,13 @@ sendChat // endDefinition; (*makeHTTPRequest*) makeHTTPRequest // beginDefinition; -(* cSpell: ignore ENDTOOLCALL *) +(* cSpell: ignore ENDTOOLCALL, AIAPI *) makeHTTPRequest[ settings_Association? AssociationQ, messages: { __Association } ] := - Enclose @ Module[ { key, stream, model, tokens, temperature, topP, freqPenalty, presPenalty, data, body, apiCompletionURL }, + Enclose @ Module[ + { key, stream, model, tokens, temperature, topP, freqPenalty, presPenalty, data, body, apiCompletionURL }, - key = ConfirmBy[ Lookup[ settings, "OpenAIKey" ], StringQ ]; - stream = True; + key = ConfirmBy[ Lookup[ settings, "OpenAIKey" ], StringQ ]; + stream = True; apiCompletionURL = ConfirmBy[ Lookup[ settings, "OpenAIAPICompletionURL" ], StringQ ]; (* model parameters *) @@ -1445,6 +1439,7 @@ getNamedLLMEvaluator // endDefinition; toolsEnabledQ[ KeyValuePattern[ "ToolsEnabled" -> enabled: True|False ] ] := enabled; toolsEnabledQ[ KeyValuePattern[ "ToolCallFrequency" -> freq: (_Integer|_Real)? NonPositive ] ] := False; toolsEnabledQ[ KeyValuePattern[ "Model" -> model_ ] ] := toolsEnabledQ @ toModelName @ model; +toolsEnabledQ[ model: KeyValuePattern @ { "Service" -> _, "Name" -> _ } ] := toolsEnabledQ @ toModelName @ model; toolsEnabledQ[ "chat-bison-001" ] := False; toolsEnabledQ[ model_String ] := ! TrueQ @ StringContainsQ[ model, "gpt-3", IgnoreCase -> True ]; toolsEnabledQ[ ___ ] := False; diff --git a/Source/Chatbook/Services.wl b/Source/Chatbook/Services.wl index b7cce46c..b0d4a7e2 100644 --- a/Source/Chatbook/Services.wl +++ b/Source/Chatbook/Services.wl @@ -5,10 +5,16 @@ BeginPackage[ "Wolfram`Chatbook`Services`" ]; (* :!CodeAnalysis::BeginBlock:: *) HoldComplete[ + `$allowConnectionDialog; `$availableServices; `$enableLLMServices; + `$serviceCache; `$servicesLoaded; `$useLLMServices; + `getAvailableServiceNames; + `getAvailableServices; + `getServiceModelList; + `modelListCachedQ; ]; Begin[ "`Private`" ]; @@ -16,23 +22,166 @@ Begin[ "`Private`" ]; Needs[ "Wolfram`Chatbook`" ]; Needs[ "Wolfram`Chatbook`Common`" ]; Needs[ "Wolfram`Chatbook`Models`" ]; +Needs[ "Wolfram`Chatbook`UI`" ]; + +$ContextAliases[ "llm`" ] = "LLMServices`"; (* ::**************************************************************************************************************:: *) (* ::Section::Closed:: *) (*Configuration*) -$enableLLMServices = Automatic; -$servicesLoaded := False; -$useLLMServices := MatchQ[ $enableLLMServices, Automatic|True ] && TrueQ @ $llmServicesAvailable; +$allowConnectionDialog = True; +$enableLLMServices = Automatic; +$modelListCache = <| |>; +$modelSortOrder = { "Snapshot", "FineTuned", "DisplayName" }; +$servicesLoaded = False; +$useLLMServices := MatchQ[ $enableLLMServices, Automatic|True ] && TrueQ @ $llmServicesAvailable; +$serviceCache = None; $llmServicesAvailable := $llmServicesAvailable = ( PacletInstall[ "Wolfram/LLMFunctions" ]; PacletNewerQ[ PacletObject[ "Wolfram/LLMFunctions" ], "1.2.2" ] ); +(* ::**************************************************************************************************************:: *) +(* ::Section::Closed:: *) +(*InvalidateServiceCache*) +InvalidateServiceCache // beginDefinition; +InvalidateServiceCache[ ] := ($serviceCache = None; Null); +InvalidateServiceCache // endDefinition; + (* ::**************************************************************************************************************:: *) (* ::Section::Closed:: *) (*Available Services*) +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*modelListCachedQ*) +modelListCachedQ // beginDefinition; +modelListCachedQ[ service_String ] := ListQ @ $serviceCache[ service, "CachedModels" ]; +modelListCachedQ // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*$availableServiceNames*) +$availableServiceNames := getAvailableServiceNames[ ]; + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*getAvailableServiceNames*) +getAvailableServiceNames // beginDefinition; +getAvailableServiceNames[ ] := getAvailableServiceNames @ $availableServices; +getAvailableServiceNames[ services_Association ] := Keys @ services; +getAvailableServiceNames // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*getServiceInformation*) +getServiceInformation // beginDefinition; +getServiceInformation[ service_String ] := getServiceInformation[ service, $availableServices ]; +getServiceInformation[ service_String, services_Association ] := Lookup[ services, service, Missing[ "NotAvailable" ] ]; +getServiceInformation // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*getServiceModels*) +getServiceModelList // beginDefinition; + +getServiceModelList[ KeyValuePattern[ "Service" -> service_String ] ] := + getServiceModelList @ service; + +getServiceModelList[ service_String ] := + With[ { models = $availableServices[ service, "CachedModels" ] }, + If[ ListQ @ models, + models, + getServiceModelList[ service, $availableServices[ service ] ] + ] + ]; + +getServiceModelList[ service_String, info_Association ] := + getServiceModelList[ service, info, getModelListQuietly @ info ]; + +getServiceModelList[ service_, info_, Missing[ "NotConnected" ] ] := + Missing[ "NotConnected" ]; + +getServiceModelList[ "OpenAI", info_, models: { "gpt-4", "gpt-3.5-turbo-0613" } ] := + With[ { full = getOpenAIChatModels[ ] }, + getServiceModelList[ "OpenAI", info, full ] /; MatchQ[ full, Except[ models, { __String } ] ] + ]; + +getServiceModelList[ service_String, info_, models0_List ] := Enclose[ + Module[ { models }, + models = ConfirmMatch[ preprocessModelList[ service, models0 ], { ___Association }, "Models" ]; + ConfirmAssert[ AssociationQ @ $serviceCache[ service ], "ServiceCache" ]; + $serviceCache[ service, "CachedModels" ] = models; + updateDynamics[ "Services" ]; + models + ], + throwInternalFailure +]; + +getServiceModelList // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*preprocessModelList*) +preprocessModelList // beginDefinition; + +preprocessModelList[ service_, models0_List ] := Enclose[ + Module[ { models, ordering, sorted }, + models = ConfirmMatch[ standardizeModelData[ service, models0 ], { ___Association }, "Models" ]; + ordering = Lookup /@ ConfirmMatch[ $modelSortOrder, { __String }, "ModelSortOrder" ]; + sorted = SortBy[ models, ordering ]; + sorted + ], + throwInternalFailure +]; + +preprocessModelList // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*getModelListQuietly*) +getModelListQuietly // beginDefinition; + +getModelListQuietly[ info_Association ] /; ! $allowConnectionDialog := + Block[ { $allowConnectionDialog = True, DialogInput = $Failed & }, + getModelListQuietly @ info + ]; + +(* cSpell: ignore nprmtv, genconerr, invs, nolink *) +getModelListQuietly[ info_Association ] := Quiet[ + checkModelList[ info, Check[ info[ "ModelList" ], Missing[ "NotConnected" ], DialogInput::nprmtv ] ], + { DialogInput::nprmtv, ServiceConnect::genconerr, ServiceConnect::invs, ServiceExecute::nolink } +]; + +getModelListQuietly // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*checkModelList*) +checkModelList // beginDefinition; + +checkModelList[ info_, models_List ] := + models; + +checkModelList[ info_, $Canceled | $Failed | Missing[ "NotConnected" ] ] := + Missing[ "NotConnected" ]; + +checkModelList[ info_, Failure[ "ConfirmationFailed", KeyValuePattern[ "Expression" :> expr_ ] ] ] := + checkModelList[ info, expr ]; + +checkModelList[ info_, _ServiceExecute ] := ( + If[ AssociationQ @ Wolfram`LLMFunctions`APIs`Common`$ConnectionCache, + KeyDropFrom[ Wolfram`LLMFunctions`APIs`Common`$ConnectionCache, info[ "Service" ] ] + ]; + Missing[ "NotConnected" ] +); + +checkModelList[ info_, other_ ] := + Missing[ "NoModelList" ]; + +checkModelList // endDefinition; + (* ::**************************************************************************************************************:: *) (* ::Subsection::Closed:: *) (*$availableServices*) @@ -43,51 +192,70 @@ $availableServices := getAvailableServices[ ]; (*getAvailableServices*) getAvailableServices // beginDefinition; getAvailableServices[ ] := getAvailableServices @ $useLLMServices; -getAvailableServices[ False ] := $fallBackServices; +getAvailableServices[ False ] := getAvailableServices0 @ $fallBackServices; getAvailableServices[ True ] := getAvailableServices0[ ]; getAvailableServices // endDefinition; getAvailableServices0 // beginDefinition; +getAvailableServices0[ ] := + With[ { services = $serviceCache }, services /; AssociationQ @ services ]; + getAvailableServices0[ ] := ( PacletInstall[ "Wolfram/LLMFunctions" ]; Needs[ "LLMServices`" -> None ]; - getAvailableServices0 @ LLMServices`LLMServiceInformation[ LLMServices`ChatSubmit, "Services" ] + getAvailableServices0 @ llm`LLMServiceInformation @ llm`ChatSubmit ); getAvailableServices0[ services0_Association? AssociationQ ] := Enclose[ - Catch @ Module[ { services, withServiceName, withModels }, - - services = Replace[ services0, <| |> :> $fallBackServices ]; - withServiceName = Association @ KeyValueMap[ #1 -> <| "ServiceName" -> #1, #2 |> &, services ]; - - withModels = Replace[ - withServiceName, - as: KeyValuePattern @ { "ServiceName" -> service_String, "ModelList" -> func_ } :> - RuleCondition @ With[ { models = func[ ] }, - If[ ListQ @ models, (* workaround for KeyValuePattern bug *) - <| as, "Models" -> standardizeModelData[ service, models ] |>, - as - ] - ], - { 1 } + Catch @ Module[ { services, withServiceName, withIcon, preCached }, + + services = ConfirmMatch[ + Replace[ services0, <| |> :> $fallBackServices ], + _Association? (AllTrue[ AssociationQ ]), + "Services" ]; - $servicesLoaded = True; + withServiceName = Association @ KeyValueMap[ #1 -> <| "Service" -> #1, #2 |> &, services ]; + withIcon = Association[ #, "Icon" -> serviceIcon @ # ] & /@ withServiceName; + + preCached = ConfirmMatch[ + checkLiteralModelLists /@ withIcon, + _Association? (AllTrue[ AssociationQ ]), + "CacheCheck" + ]; - getAvailableServices0[ services0 ] = withModels + $servicesLoaded = True; + $serviceCache = preCached ], - throwInternalFailure[ getAvailableServices0[ ], ## ] & + throwInternalFailure ]; getAvailableServices0 // endDefinition; +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*checkLiteralModelLists*) +checkLiteralModelLists // beginDefinition; + +checkLiteralModelLists[ service: KeyValuePattern[ "ModelList" -> models_List ] ] := + Association[ service, "CachedModels" -> preprocessModelList[ service, models ] ]; + +checkLiteralModelLists[ service: KeyValuePattern[ "ModelList" :> models: { (_String | KeyValuePattern @ { })... } ] ] := + Association[ service, "CachedModels" -> preprocessModelList[ service, models ] ]; + +checkLiteralModelLists[ service_Association ] := + service; + +checkLiteralModelLists // endDefinition; + (* ::**************************************************************************************************************:: *) (* ::Subsection::Closed:: *) (*$fallBackServices*) $fallBackServices = <| "OpenAI" -> <| - "ModelList" -> getOpenAIChatModels + "Icon" -> chatbookIcon[ "ServiceIconOpenAI" ], + "ModelList" :> getOpenAIChatModels[ ] |> |>; diff --git a/Source/Chatbook/Settings.wl b/Source/Chatbook/Settings.wl index 54d6cbe3..3e36c89f 100644 --- a/Source/Chatbook/Settings.wl +++ b/Source/Chatbook/Settings.wl @@ -66,15 +66,14 @@ $defaultChatSettings = <| (* ::**************************************************************************************************************:: *) (* ::Subsection::Closed:: *) (*Argument Patterns*) -$$feObj = _FrontEndObject | $FrontEndSession | _NotebookObject | _CellObject | _BoxObject; $$validRootSettingValue = Inherited | _? (AssociationQ@*Association); (* ::**************************************************************************************************************:: *) (* ::Section::Closed:: *) (*Defaults*) -$ChatPost = None; -$ChatPre = None; -$DefaultModel := If[ $VersionNumber >= 13.3, "gpt-4", "gpt-3.5-turbo" ]; +$ChatPost = None; +$ChatPre = None; +$DefaultModel = <| "Service" -> "OpenAI", "Name" -> "gpt-4" |>; (* ::**************************************************************************************************************:: *) (* ::Subsection::Closed:: *) diff --git a/Source/Chatbook/ToolManager.wl b/Source/Chatbook/ToolManager.wl index d2e5f0a1..e8de9d2d 100644 --- a/Source/Chatbook/ToolManager.wl +++ b/Source/Chatbook/ToolManager.wl @@ -50,10 +50,15 @@ CreateLLMToolManagerDialog // endDefinition; (*CreateLLMToolManagerPanel*) CreateLLMToolManagerPanel // beginDefinition; -CreateLLMToolManagerPanel[ ] := catchMine @ trackedDynamic[ - CreateLLMToolManagerPanel[ getFullToolList[ ], getFullPersonaList[ ] ], - { "Tools", "Personas" } -]; +CreateLLMToolManagerPanel[ ] := catchMine @ + With[ { inDialog = $inDialog }, + trackedDynamic[ + Block[ { $inDialog = inDialog }, + CreateLLMToolManagerPanel[ getFullToolList[ ], getFullPersonaList[ ] ] + ], + { "Tools", "Personas" } + ] + ]; CreateLLMToolManagerPanel[ tools0_List, personas_List ] := catchMine @ cvExpand @ Module[ @@ -95,8 +100,8 @@ CreateLLMToolManagerPanel[ tools0_List, personas_List ] := { sH = 0, sV = 0, - w = 167, - h = 180, + w = If[ TrueQ @ $inDialog, 167, UpTo[ 230 ] ], + h = If[ TrueQ @ $inDialog, 180, Automatic ], row = None, column = None, scopeMode = $FrontEnd &, @@ -131,8 +136,9 @@ CreateLLMToolManagerPanel[ tools0_List, personas_List ] := DynamicWrapper[ Grid[ { + If[ TrueQ @ $inDialog, dialogHeader[ "Add & Manage LLM Tools" ], Nothing ], + (* ----- Install Tools ----- *) - dialogHeader[ "Add & Manage LLM Tools" ], dialogSubHeader[ "Install Tools" ], dialogBody[ Grid @ { @@ -157,8 +163,6 @@ CreateLLMToolManagerPanel[ tools0_List, personas_List ] := ], (* ----- Configure and Enable Tools ----- *) - - dialogSubHeader[ "Manage and Enable Tools" ], dialogBody[ Grid @ { @@ -176,12 +180,15 @@ CreateLLMToolManagerPanel[ tools0_List, personas_List ] := { Append[ Map[ - Function @ EventHandler[ - Pane[ #, FrameMargins -> { { 0, 0 }, { 2, 2 } } ], - { "MouseEntered" :> FEPrivate`Set[ { row, column }, { None, None } ] } + Function @ Item[ + EventHandler[ + Pane[ #, FrameMargins -> { { 0, 0 }, { 2, 2 } } ], + { "MouseEntered" :> FEPrivate`Set[ { row, column }, { None, None } ] } + ], + Background -> GrayLevel[ 0.898 ] ], { - "Tool", + Row @ { Spacer[ 4 ], "Tool" }, Row @ { Spacer[ 4 ], "Enabled for\[VeryThinSpace]:", @@ -229,7 +236,7 @@ CreateLLMToolManagerPanel[ tools0_List, personas_List ] := (* Checkbox grid: *) linkedPane[ Grid[ - Table[ + (*fitLastColumn @*) Table[ If[ And[ StringQ @ personaToolLookup @ tools[[ i, "CanonicalName" ]], UnsameQ[ @@ -286,7 +293,8 @@ CreateLLMToolManagerPanel[ tools0_List, personas_List ] := ] ], Dynamic @ { sH, sV }, - Scrollbars -> { Automatic, False } + Scrollbars -> { Automatic, False }, + AppearanceElements -> If[ TrueQ @ $inDialog, Automatic, None ] ], (* All/None and clear column: *) @@ -317,11 +325,12 @@ CreateLLMToolManagerPanel[ tools0_List, personas_List ] := ] } }, - Alignment -> { Left, Top }, - BaseStyle -> $baseStyle, - ItemSize -> { 0, 0 }, - Spacings -> { 0, 0 }, - Dividers -> { + Alignment -> { Left, Top }, + Background -> White, + BaseStyle -> $baseStyle, + ItemSize -> { 0, 0 }, + Spacings -> { 0, 0 }, + Dividers -> { { False, $dividerCol, $dividerCol, { False } }, { False, False , $dividerCol, { False } } } @@ -330,6 +339,7 @@ CreateLLMToolManagerPanel[ tools0_List, personas_List ] := PassEventsDown -> True ], { { Automatic, 0 }, Automatic } ], + (* ----- Dialog Buttons ----- *) If[ TrueQ @ $inDialog, { Item[ @@ -354,7 +364,16 @@ CreateLLMToolManagerPanel[ tools0_List, personas_List ] := }, Alignment -> { Left, Top }, BaseStyle -> $baseStyle, - Dividers -> { False, { False, $dividerCol, False, $dividerCol, False, { False } } }, + Dividers -> { + False, + { + If[ TrueQ @ $inDialog, Sequence @@ { False, $dividerCol }, False ], + False, + $dividerCol, + False, + { False } + } + }, ItemSize -> { Automatic, 0 }, Spacings -> { 0, 0 } ], @@ -438,6 +457,20 @@ CreateLLMToolManagerPanel[ tools0_List, personas_List ] := CreateLLMToolManagerPanel // endDefinition; +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*fitLastColumn*) +fitLastColumn // beginDefinition; + +fitLastColumn[ grid_? MatrixQ ] := + MapAt[ + Item[ #, ItemSize -> { Fit, Automatic }, Alignment -> { Left, Automatic } ] &, + grid, + { All, -1 } + ]; + +fitLastColumn // endDefinition; + (* ::**************************************************************************************************************:: *) (* ::Subsection::Closed:: *) (*addPersonaSource*) @@ -578,7 +611,7 @@ toolModelWarning[ scope_, enabled_, model_? toolsEnabledQ ] := ""; toolModelWarning[ scope_, enabled_, model_ ] := toolModelWarning0[ scope, model ]; toolModelWarning // endDefinition; - +(* FIXME: add a link to open the Notebooks preferences tab *) toolModelWarning0 // beginDefinition; toolModelWarning0[ scope_, model_String ] := Enclose[ @@ -589,8 +622,8 @@ toolModelWarning0[ scope_, model_String ] := Enclose[ Grid[ { { Spacer[ 5 ], - Style[ "\[WarningSign]", FontWeight -> Bold, FontColor -> Darker @ Orange, FontSize -> 18 ], - message + Style[ "\[WarningSign]", FontWeight -> Bold, FontColor -> Gray, FontSize -> 18 ], + Style[ message, FontColor -> Gray, FontSlant -> Italic ] } }, Alignment -> { Left, Top } ] diff --git a/Source/Chatbook/UI.wl b/Source/Chatbook/UI.wl index 8f201353..5a67436a 100644 --- a/Source/Chatbook/UI.wl +++ b/Source/Chatbook/UI.wl @@ -22,34 +22,46 @@ GeneralUtilities`SetUsage[CreateToolbarContent, " CreateToolbarContent[] is called by the NotebookToolbar to generate the content of the 'Notebook AI Settings' attached menu. "] -`getPersonaIcon; -`getPersonaMenuIcon; -`personaDisplayName; -`resizeMenuIcon; - +HoldComplete[ + `getModelMenuIcon; + `getPersonaIcon; + `getPersonaMenuIcon; + `labeledCheckbox; + `makeAutomaticResultAnalysisCheckbox; + `makeTemperatureSlider; + `makeToolCallFrequencySlider; + `modelGroupName; + `personaDisplayName; + `resizeMenuIcon; + `serviceIcon; + `showSnapshotModelsQ; + `tr; +]; Begin["`Private`"] -Needs[ "Wolfram`Chatbook`" ]; -Needs[ "Wolfram`Chatbook`Actions`" ]; -Needs[ "Wolfram`Chatbook`Common`" ]; -Needs[ "Wolfram`Chatbook`Dynamics`" ]; -Needs[ "Wolfram`Chatbook`Errors`" ]; -Needs[ "Wolfram`Chatbook`ErrorUtils`" ]; -Needs[ "Wolfram`Chatbook`FrontEnd`" ]; -Needs[ "Wolfram`Chatbook`Menus`" ]; -Needs[ "Wolfram`Chatbook`Models`" ]; -Needs[ "Wolfram`Chatbook`Personas`" ]; -Needs[ "Wolfram`Chatbook`PreferencesUtils`" ]; -Needs[ "Wolfram`Chatbook`Serialization`" ]; -Needs[ "Wolfram`Chatbook`Services`" ]; -Needs[ "Wolfram`Chatbook`Settings`" ]; -Needs[ "Wolfram`Chatbook`Utils`" ]; +Needs[ "Wolfram`Chatbook`" ]; +Needs[ "Wolfram`Chatbook`Actions`" ]; +Needs[ "Wolfram`Chatbook`CloudToolbar`" ]; +Needs[ "Wolfram`Chatbook`Common`" ]; +Needs[ "Wolfram`Chatbook`Dynamics`" ]; +Needs[ "Wolfram`Chatbook`Errors`" ]; +Needs[ "Wolfram`Chatbook`ErrorUtils`" ]; +Needs[ "Wolfram`Chatbook`FrontEnd`" ]; +Needs[ "Wolfram`Chatbook`Menus`" ]; +Needs[ "Wolfram`Chatbook`Models`" ]; +Needs[ "Wolfram`Chatbook`Personas`" ]; +Needs[ "Wolfram`Chatbook`PreferencesContent`" ]; +Needs[ "Wolfram`Chatbook`PreferencesUtils`" ]; +Needs[ "Wolfram`Chatbook`Serialization`" ]; +Needs[ "Wolfram`Chatbook`Services`" ]; +Needs[ "Wolfram`Chatbook`Settings`" ]; +Needs[ "Wolfram`Chatbook`Utils`" ]; (* ::**************************************************************************************************************:: *) (* ::Section::Closed:: *) (*Configuration*) -$chatMenuWidth = 260; +$chatMenuWidth = 220; (* ::**************************************************************************************************************:: *) (* ::Section::Closed:: *) @@ -58,196 +70,12 @@ $chatMenuWidth = 260; (* ::**************************************************************************************************************:: *) (* ::Subsection::Closed:: *) (*MakeChatCloudDockedCellContents*) -MakeChatCloudDockedCellContents[] := Grid[ - {{ - Item[$cloudChatBanner, Alignment -> Left], - Item["", ItemSize -> Fit], - Row[{"Persona", Spacer[5], trackedDynamic[$cloudPersonaChooser, "Personas"]}], - Row[{"Model", Spacer[5], trackedDynamic[$cloudModelChooser, "Models"]}] - }}, - Dividers -> {{False, False, False, True}, False}, - Spacings -> {2, 0}, - BaseStyle -> {"Text", FontSize -> 14, FontColor -> GrayLevel[0.4]}, - FrameStyle -> Directive[Thickness[2], GrayLevel[0.9]] -] - -(* ::**************************************************************************************************************:: *) -(* ::Subsubsection::Closed:: *) -(*$cloudPersonaChooser*) -$cloudPersonaChooser := PopupMenu[ - Dynamic[ - Replace[ - CurrentValue[EvaluationNotebook[], {TaggingRules, "ChatNotebookSettings", "LLMEvaluator"}], - Inherited :> Lookup[$defaultChatSettings, "LLMEvaluator", "CodeAssistant"] - ], - Function[CurrentValue[EvaluationNotebook[], {TaggingRules, "ChatNotebookSettings", "LLMEvaluator"}] = #] - ], - KeyValueMap[ - Function[{key, as}, key -> Grid[{{resizeMenuIcon[getPersonaMenuIcon[as]], personaDisplayName[key, as]}}]], - GetCachedPersonaData[] - ], - ImageSize -> {Automatic, 30}, - Alignment -> {Left, Baseline}, - BaseStyle -> {FontSize -> 12} -] - -(* ::**************************************************************************************************************:: *) -(* ::Subsubsection::Closed:: *) -(*$cloudModelChooser*) -$cloudModelChooser := PopupMenu[ - Dynamic[ - Replace[ - CurrentValue[EvaluationNotebook[], {TaggingRules, "ChatNotebookSettings", "Model"}], - Inherited :> Lookup[$defaultChatSettings, "Model", "gpt-3.5-turbo"] - ], - Function[CurrentValue[EvaluationNotebook[], {TaggingRules, "ChatNotebookSettings", "Model"}] = #] - ], - KeyValueMap[ - {modelName, settings} |-> ( - modelName -> Grid[{{getModelMenuIcon[settings], modelDisplayName[modelName]}}] - ), - getModelsMenuItems[] - ], - ImageSize -> {Automatic, 30}, - Alignment -> {Left, Baseline}, - BaseStyle -> {FontSize -> 12} -] - -(* ::**************************************************************************************************************:: *) -(* ::Subsubsection::Closed:: *) -(*$cloudChatBanner*) -$cloudChatBanner := PaneSelector[ - { - True -> Grid[ - { - { - "", - chatbookIcon[ "ChatDrivenNotebookIcon", False ], - Style[ - "Chat-Driven Notebook", - FontColor -> RGBColor[ "#333333" ], - FontFamily -> "Source Sans Pro", - FontSize -> 16, - FontWeight -> "DemiBold" - ] - } - }, - Alignment -> { Automatic, Center }, - Spacings -> 0.5 - ], - False -> Grid[ - { - { - "", - chatbookIcon[ "ChatEnabledNotebookIcon", False ], - Style[ - "Chat-Enabled Notebook", - FontColor -> RGBColor[ "#333333" ], - FontFamily -> "Source Sans Pro", - FontSize -> 16, - FontWeight -> "DemiBold" - ] - } - }, - Alignment -> { Automatic, Center }, - Spacings -> 0.5 - ] - }, - Dynamic @ TrueQ @ CurrentValue[ - EvaluationNotebook[ ], - { TaggingRules, "ChatNotebookSettings", "ChatDrivenNotebook" } - ], - ImageSize -> Automatic -] +MakeChatCloudDockedCellContents[] := makeChatCloudDockedCellContents[ ]; (* ::**************************************************************************************************************:: *) (* ::Section::Closed:: *) (*Preferences Panel*) -CreatePreferencesContent[] := Module[{ - personas = GetPersonasAssociation[], - chatbookSettings, - llmEvaluatorNamesSettings, - services, - grid -}, - - llmEvaluatorNamesSettings = Grid[ - Prepend[ - KeyValueMap[ - {persona, personaSettings} |-> { - resizeMenuIcon @ getPersonaMenuIcon[personaSettings, "Full"], - personaDisplayName[persona, personaSettings], - Replace[Lookup[personaSettings, "Description", None], { - None | _?MissingQ -> "", - desc_?StringQ :> desc, - other_ :> ( - ChatbookWarning[ - "Unexpected non-String persona `` description: ``", - InputForm[persona], - InputForm[other] - ]; - other - ) - }] - }, - personas - ], - {"", "Name", "Description"} - ], - Background -> {None, {1 -> GrayLevel[0.95]}}, - Dividers -> {False, {False, {1 -> True, 2 -> True}}}, - Alignment -> {Left, Center} - ]; - - chatbookSettings = makeFrontEndAndNotebookSettingsContent[$FrontEnd]; - - (* services = Grid[{ - {"" , "Name" , "State" }, - {chatbookIcon["OpenAILogo", False] , "OpenAI" , "" }, - {"" , "Bard" , Style["Coming soon", Italic] }, - {"" , "Claude" , Style["Coming soon", Italic] } - }, - Background -> {None, {1 -> GrayLevel[0.95]}}, - Dividers -> {False, {False, {1 -> True, 2 -> True}}}, - Alignment -> {Left, Center} - ]; *) - - (*-----------------------------------------*) - (* Return the complete settings expression *) - (*-----------------------------------------*) - - PreferencesPane[ - { - PreferencesSection[ - Style[tr["Chat Notebook Interface"], "subsectionText"], - chatbookSettings - ], - PreferencesSection[ - Style[tr["Installed Personas"], "subsectionText"], - llmEvaluatorNamesSettings - ] - (* PreferencesSection[ - Style[tr["LLM Service Providers"], "subsectionText"], - services - ] *) - }, - PreferencesResetButton[ - FrontEndExecute @ FrontEnd`RemoveOptions[$FrontEnd, { - System`LLMEvaluator, - {TaggingRules, "ChatNotebookSettings"} - }]; - - CurrentValue[ - $FrontEnd, - { - PrivateFrontEndOptions, - "InterfaceSettings", - "ChatNotebooks" - } - ] = Inherited; - ] - ] -] +CreatePreferencesContent[ ] := trackedDynamic[ createPreferencesContent[ ], { "Preferences" } ]; (* ::**************************************************************************************************************:: *) (* ::Section::Closed:: *) @@ -259,29 +87,12 @@ CreateToolbarContent[] := With[{ CurrentValue[menuCell, {TaggingRules, "IsChatEnabled"}] = TrueQ[CurrentValue[nbObj, {StyleDefinitions, "ChatInput", Evaluatable}]]; + CurrentValue[menuCell, {TaggingRules, "MenuData", "Root"}] = menuCell; + PaneSelector[ { True :> ( - Dynamic @ Refresh[ - Column[{ - Pane[ - makeEnableAIChatFeaturesLabel[True], - ImageMargins -> {{5, 20}, {2.5, 2.5}} - ], - - Pane[ - makeAutomaticResultAnalysisCheckbox[EvaluationNotebook[]], - ImageMargins -> {{5, 20}, {2.5, 2.5}} - ], - - makeChatActionMenu[ - "Toolbar", - EvaluationNotebook[], - Automatic - ] - }], - None - ] + Dynamic[ makeToolbarMenuContent @ menuCell, SingleEvaluation -> True, DestroyAfterEvaluation -> True ] ), False :> ( Dynamic @ Refresh[ @@ -293,7 +104,29 @@ CreateToolbarContent[] := With[{ Dynamic @ CurrentValue[menuCell, {TaggingRules, "IsChatEnabled"}], ImageSize -> Automatic ] -] +]; + +makeToolbarMenuContent[ menuCell_ ] := Enclose[ + Module[ { items, item1, item2, new }, + + items = ConfirmBy[ makeChatActionMenu[ "Toolbar", EvaluationNotebook[ ], Automatic, "List" ], ListQ, "Items" ]; + + item1 = Pane[ + makeEnableAIChatFeaturesLabel @ True, + ImageMargins -> { { 5, 20 }, { 2.5, 2.5 } } + ]; + + item2 = Pane[ + makeAutomaticResultAnalysisCheckbox @ EvaluationNotebook[ ], + ImageMargins -> { { 5, 20 }, { 2.5, 2.5 } } + ]; + + new = Join[ { { None, item1, None }, { None, item2, None } }, items ]; + + MakeMenu[ new, Transparent, $chatMenuWidth ] + ], + throwInternalFailure +]; (*====================================*) @@ -346,11 +179,7 @@ tryMakeChatEnabledNotebook[ "", RawBoxes @ Cell["Are you sure you wish to continue?", "Text"] }], - Background -> White, - WindowMargins -> ConfirmReplace[ - MousePosition["ScreenAbsolute"], - {x_, y_} :> {{x, Automatic}, {Automatic, y}} - ] + Background -> White ], _?BooleanQ ] @@ -381,7 +210,7 @@ makeEnableAIChatFeaturesLabel[enabled_?BooleanQ] := SetFallthroughError[makeAutomaticResultAnalysisCheckbox] makeAutomaticResultAnalysisCheckbox[ - target : $FrontEnd | $FrontEndSession | _NotebookObject + target : _FrontEndObject | $FrontEndSession | _NotebookObject ] := With[{ setterFunction = ConfirmReplace[target, { $FrontEnd | $FrontEndSession :> ( @@ -450,7 +279,7 @@ makeAutomaticResultAnalysisCheckbox[ SetFallthroughError[labeledCheckbox] -labeledCheckbox[value_, label_, enabled_ : Automatic] := +labeledCheckbox[value_, label_, enabled_ : Automatic] := Style[ Row[ { Checkbox[ @@ -468,51 +297,12 @@ labeledCheckbox[value_, label_, enabled_ : Automatic] := Preferences.nb *) CheckboxBoxOptions -> { ImageMargins -> 0 } } - ] + ], + LineBreakWithin -> False +] (*====================================*) -makeToolCallFrequencySlider[ obj_ ] := Pane[ - Grid[ - { - { - labeledCheckbox[ - Dynamic[ - currentChatSettings[ obj, "ToolCallFrequency" ] === Automatic, - Function[ - If[ TrueQ[ # ], - CurrentValue[ obj, { TaggingRules, "ChatNotebookSettings", "ToolCallFrequency" } ] = Inherited, - CurrentValue[ obj, { TaggingRules, "ChatNotebookSettings", "ToolCallFrequency" } ] = 0.5 - ] - ] - ], - Style[ "Choose automatically", "ChatMenuLabel" ] - ] - }, - { - Pane[ - Slider[ - Dynamic[ - Replace[ currentChatSettings[ obj, "ToolCallFrequency" ], Automatic -> 0.5 ], - (CurrentValue[ obj, { TaggingRules, "ChatNotebookSettings", "ToolCallFrequency" } ] = #) & - ], - { 0, 1, 0.01 }, - (* Enabled -> Dynamic[ currentChatSettings[ obj, "ToolCallFrequency" ] =!= Automatic ], *) - ImageSize -> { 150, Automatic }, - ImageMargins -> { { 5, 0 }, { 5, 5 } } - ], - ImageSize -> { 180, Automatic }, - BaseStyle -> { FontSize -> 12 } - ], - SpanFromLeft - } - }, - Alignment -> Left, - Spacings -> { Automatic, 0 } - ], - ImageMargins -> { { 5, 0 }, { 5, 5 } } -]; - makeToolCallFrequencySlider[ obj_ ] := Module[ { checkbox, slider }, checkbox = labeledCheckbox[ @@ -579,198 +369,15 @@ makeTemperatureSlider[ BaseStyle -> { FontSize -> 12 } ] -(* cSpell: ignore AIAPI *) -makeOpenAIAPICompletionURLForm[value_]:= Pane[ - InputField[value, - String, - ImageSize -> {240, Automatic}, - BaseStyle -> {FontSize -> 12}] -] - (*=========================================*) (* Common preferences content construction *) (*=========================================*) -SetFallthroughError[makeFrontEndAndNotebookSettingsContent] - -makeFrontEndAndNotebookSettingsContent[ - targetObj : _FrontEndObject | $FrontEndSession | _NotebookObject -] := Module[{ - personas = GetPersonasAssociation[], - defaultPersonaPopupItems, - setModelPopupItems, - modelPopupItems -}, - defaultPersonaPopupItems = KeyValueMap[ - {persona, personaSettings} |-> ( - persona -> Row[{ - resizeMenuIcon[ - getPersonaMenuIcon[personaSettings, "Full"] - ], - personaDisplayName[persona, personaSettings] - }, Spacer[1]] - ), - personas - ]; - - (*----------------------------*) - (* Compute the models to show *) - (*----------------------------*) - - setModelPopupItems[] := ( - modelPopupItems = KeyValueMap[ - {modelName, settings} |-> ( - modelName -> Row[{ - getModelMenuIcon[settings, "Full"], - modelDisplayName[modelName] - }, Spacer[1]] - ), - getModelsMenuItems[] - ]; - ); - - (* Initial value. Called again if 'show snapshot models' changes. *) - setModelPopupItems[]; - - (*---------------------------------*) - (* Return the toolbar menu content *) - (*---------------------------------*) - - Grid[ - { - {Row[{ - tr["Default Persona:"], - PopupMenu[ - Dynamic[ - currentChatSettings[ - targetObj, - "LLMEvaluator" - ], - Function[{newValue}, - CurrentValue[ - targetObj, - {TaggingRules, "ChatNotebookSettings", "LLMEvaluator"} - ] = newValue - ] - ], - defaultPersonaPopupItems - ] - }, Spacer[3]]}, - {Row[{ - tr["Default Model:"], - (* Note: Dynamic[PopupMenu[..]] so that changing the - 'show snapshot models' option updates the popup. *) - Dynamic @ PopupMenu[ - Dynamic[ - currentChatSettings[ - targetObj, - "Model" - ], - Function[{newValue}, - CurrentValue[ - targetObj, - {TaggingRules, "ChatNotebookSettings", "Model"} - ] = newValue - ] - ], - modelPopupItems, - (* This is shown if the user selects a snapshot model, - and then unchecks the 'show snapshot models' option. *) - Dynamic[ - Style[ - With[{ - modelName = currentChatSettings[targetObj, "Model"] - }, { - settings = standardizeModelData[modelName] - }, - Row[{ - getModelMenuIcon[settings, "Full"], - modelDisplayName[modelName] - }, Spacer[1]] - ], - Italic - ] - ] - ] - }, Spacer[3]]}, - {Row[{ - tr["Default Tool Call Frequency:"], - makeToolCallFrequencySlider[ targetObj ] - }, Spacer[3]]}, - {Row[{ - tr["Default Temperature:"], - makeTemperatureSlider[ - Dynamic[ - currentChatSettings[targetObj, "Temperature"], - newValue |-> ( - CurrentValue[ - targetObj, - {TaggingRules, "ChatNotebookSettings", "Temperature"} - ] = newValue; - ) - ] - ] - }, Spacer[3]]}, - - If[ TrueQ @ $useLLMServices, - Nothing, - {Row[{ - tr["Chat Completion URL:"], - makeOpenAIAPICompletionURLForm[ - Dynamic[ - currentChatSettings[targetObj, "OpenAIAPICompletionURL"], - newValue |-> ( - CurrentValue[ - targetObj, - {TaggingRules, "ChatNotebookSettings", "OpenAIAPICompletionURL"} - ] = newValue; - ) - ] - ] - }, Spacer[3]]}], - { - labeledCheckbox[ - Dynamic[ - showSnapshotModelsQ[], - newValue |-> ( - CurrentValue[$FrontEnd, { - PrivateFrontEndOptions, - "InterfaceSettings", - "ChatNotebooks", - "ShowSnapshotModels" - }] = newValue; - - setModelPopupItems[]; - ) - ], - Row[{ - "Show temporary snapshot LLM models", - Spacer[3], - Tooltip[ - chatbookIcon["InformationTooltip", False], -"If enabled, temporary snapshot models will be included in the model selection menus. -\nSnapshot models are models that are frozen at a particular date, will not be -continuously updated, and have an expected discontinuation date." - ] - }] - ] - }, - { - makeAutomaticResultAnalysisCheckbox[targetObj] - } - }, - Alignment -> {Left, Baseline}, - Spacings -> {0, 0.7} - ] -] - -(*======================================*) - showSnapshotModelsQ[] := TrueQ @ CurrentValue[$FrontEnd, { PrivateFrontEndOptions, "InterfaceSettings", - "ChatNotebooks", + "Chatbook", "ShowSnapshotModels" }] @@ -795,6 +402,10 @@ tr[name_?StringQ] := name (* ::**************************************************************************************************************:: *) (* ::Section::Closed:: *) (*Cell Dingbats*) + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*MakeChatInputActiveCellDingbat*) MakeChatInputActiveCellDingbat[ ] := DynamicModule[ { cell }, trackedDynamic[ MakeChatInputActiveCellDingbat @ cell, { "ChatBlock" } ], @@ -833,20 +444,20 @@ MakeChatInputActiveCellDingbat[cell_CellObject] := Module[{ ImageMargins -> 0, ContentPadding -> False ], - ( - AttachCell[ + With[ { pos = Replace[ MousePosition[ "WindowScaled" ], { { _, y_ } :> y, _ :> 0 } ] }, + attachMenuCell[ EvaluationCell[], makeChatActionMenu[ "Input", parentCell[EvaluationCell[]], EvaluationCell[] ], - {Left, Bottom}, + {Left, If[ pos < 0.5, Bottom, Top ]}, Offset[{0, 0}, {Left, Top}], - {Left, Top}, + {Left, If[ pos < 0.5, Top, Bottom ]}, RemovalConditions -> {"EvaluatorQuit", "MouseClickOutside"} - ]; - ), + ] + ], Appearance -> $suppressButtonAppearance, ImageMargins -> 0, FrameMargins -> 0, @@ -856,8 +467,9 @@ MakeChatInputActiveCellDingbat[cell_CellObject] := Module[{ button ]; -(*====================================*) - +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*MakeChatInputCellDingbat*) MakeChatInputCellDingbat[] := PaneSelector[ { @@ -875,8 +487,9 @@ MakeChatInputCellDingbat[] := ImageSize -> All ] -(*====================================*) - +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*MakeChatDelimiterCellDingbat*) MakeChatDelimiterCellDingbat[ ] := DynamicModule[ { cell }, trackedDynamic[ MakeChatDelimiterCellDingbat @ cell, { "ChatBlock" } ], @@ -923,20 +536,20 @@ MakeChatDelimiterCellDingbat[cell_CellObject] := Module[{ ImageMargins -> 0, ContentPadding -> False ], - ( - AttachCell[ + With[ { pos = Replace[ MousePosition[ "WindowScaled" ], { { _, y_ } :> y, _ :> 0 } ] }, + attachMenuCell[ EvaluationCell[], makeChatActionMenu[ "Delimiter", parentCell[EvaluationCell[]], EvaluationCell[] ], - {Left, Bottom}, + {Left, If[ pos < 0.5, Bottom, Top ]}, Offset[{0, 0}, {Left, Top}], - {Left, Top}, + {Left, If[ pos < 0.5, Top, Bottom ]}, RemovalConditions -> {"EvaluatorQuit", "MouseClickOutside"} ]; - ), + ], Appearance -> $suppressButtonAppearance, ImageMargins -> 0, FrameMargins -> 0, @@ -946,8 +559,9 @@ MakeChatDelimiterCellDingbat[cell_CellObject] := Module[{ button ]; -(*====================================*) - +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*makeChatActionMenu*) SetFallthroughError[makeChatActionMenu] makeChatActionMenu[ @@ -955,7 +569,8 @@ makeChatActionMenu[ targetObj : _CellObject | _NotebookObject, (* The cell that will be the parent of the attached cell that contains this chat action menu content. *) - attachedCellParent : _CellObject | Automatic + attachedCellParent : _CellObject | Automatic, + format_ : "Cell" ] := With[{ closeMenu = ConfirmReplace[attachedCellParent, { parent_CellObject -> Function[ @@ -974,7 +589,6 @@ makeChatActionMenu[ }] }, Module[{ personas = GetPersonasAssociation[], - models, actionCallback }, (*--------------------------------*) @@ -1041,14 +655,6 @@ makeChatActionMenu[ ]; ]; - (*--------------------------------*) - (* Process models list *) - (*--------------------------------*) - - models = getModelsMenuItems[]; - - RaiseConfirmMatch[models, <| (_?StringQ -> _?AssociationQ)... |>]; - (*--------------------------------*) actionCallback = Function[{field, value}, Replace[field, { @@ -1067,13 +673,6 @@ makeChatActionMenu[ SetOptions[targetObj, CellDingbat -> Inherited]; ]; ), - "Model" :> ( - CurrentValue[ - targetObj, - {TaggingRules, "ChatNotebookSettings", "Model"} - ] = value; - closeMenu[]; - ), "Role" :> ( CurrentValue[ targetObj, @@ -1091,9 +690,10 @@ makeChatActionMenu[ }]]; makeChatActionMenuContent[ + targetObj, containerType, personas, - models, + format, "ActionCallback" -> actionCallback, "PersonaValue" -> currentValueOrigin[ targetObj, @@ -1120,8 +720,9 @@ makeChatActionMenu[ ] ]] -(*====================================*) - +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*makeChatActionMenuContent*) SetFallthroughError[makeChatActionMenuContent] Options[makeChatActionMenuContent] = { @@ -1134,9 +735,10 @@ Options[makeChatActionMenuContent] = { } makeChatActionMenuContent[ + targetObj_, containerType : "Input" | "Delimiter" | "Toolbar", personas_?AssociationQ, - models_?AssociationQ, + format_, OptionsPattern[] ] := With[{ callback = OptionValue["ActionCallback"] @@ -1212,21 +814,6 @@ makeChatActionMenuContent[ ], personas ], - {"Models"}, - KeyValueMap[ - {model, settings} |-> ( - { - alignedMenuIcon[ - model, - modelValue, - getModelMenuIcon[settings] - ], - modelDisplayName[model], - Hold[callback["Model", model]] - } - ), - models - ], { ConfirmReplace[containerType, { "Input" | "Toolbar" -> Nothing, @@ -1243,80 +830,321 @@ makeChatActionMenuContent[ {alignedMenuIcon[getIcon["PersonaOther"]], "Add & Manage Personas\[Ellipsis]", "PersonaManage"}, {alignedMenuIcon[getIcon["ToolManagerRepository"]], "Add & Manage Tools\[Ellipsis]", "ToolManage"}, Delimiter, - { - alignedMenuIcon[getIcon["AdvancedSettings"]], - Grid[ - {{ - Item["Advanced Settings", ItemSize -> Fit, Alignment -> Left], - RawBoxes[TemplateBox[{}, "Triangle"]] - }}, - Spacings -> 0 - ], - Hold @ AttachSubmenu[ - EvaluationCell[], - advancedSettingsMenu - ] - } - } - ]; + <| + "Label" -> "Models", + "Type" -> "Submenu", + "Icon" -> alignedMenuIcon @ getIcon[ "ChatBlockSettingsMenuIcon" ], + "Data" :> createServiceMenu[ targetObj, ParentCell @ EvaluationCell[ ] ] + |>, + <| + "Label" -> "Advanced Settings", + "Type" -> "Submenu", + "Icon" -> alignedMenuIcon @ getIcon[ "AdvancedSettings" ], + "Data" -> advancedSettingsMenu + |> + } + ]; - menu = MakeMenu[ - menuItems, - GrayLevel[0.85], - $chatMenuWidth - ]; + Replace[ + format, + { + "List" :> menuItems, + "Expression" :> makeChatMenuExpression @ menuItems, + "Cell" :> makeChatMenuCell[ menuItems, menuMagnification @ targetObj ], + expr_ :> throwInternalFailure[ makeChatActionMenuContent, expr ] + } + ] +]]; - menu -]] +(* ::**************************************************************************************************************:: *) +(* ::Subsubsubsection::Closed:: *) +(*makeChatMenuExpression*) +makeChatMenuExpression // beginDefinition; +makeChatMenuExpression[ menuItems_ ] := MakeMenu[ menuItems, GrayLevel[ 0.85 ], $chatMenuWidth ]; +makeChatMenuExpression // endDefinition; -(*====================================*) +(* ::**************************************************************************************************************:: *) +(* ::Subsubsubsection::Closed:: *) +(*makeChatMenuCell*) +makeChatMenuCell // beginDefinition; + +makeChatMenuCell[ menuItems_ ] := + makeChatMenuCell[ menuItems, CurrentValue[ Magnification ] ]; + +makeChatMenuCell[ menuItems_, magnification_ ] := + Cell[ + BoxData @ ToBoxes @ makeChatMenuExpression @ menuItems, + "AttachedChatMenu", + Magnification -> magnification + ]; -(* getIcon[filename_?StringQ] := Module[{ - icon -}, - icon = Import @ FileNameJoin @ { - PacletObject[ "Wolfram/Chatbook" ][ "AssetLocation", "Icons" ], - filename - }; - - If[!MatchQ[icon, _Graphics], - Raise[ - ChatbookError, - "Unexpected result loading icon from from file ``: ``", - filename, - InputForm[icon] - ]; - ]; +makeChatMenuCell // endDefinition; - (* NOTE: If the graphic doesn't have an existing BaselinePosition set, - use a default baseline that looks vertically centered for most visually - balanced icons. *) - If[BaselinePosition /. Options[icon, BaselinePosition] === Automatic, - (* TODO: Force the image size too. *) - icon = Show[icon, BaselinePosition -> Scaled[0.24]]; - ]; +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*getIcon*) +getIcon[ name_ ] := RawBoxes @ TemplateBox[ { }, name ]; + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*Model selection submenu*) + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*createServiceMenu*) +createServiceMenu // beginDefinition; + +createServiceMenu[ obj_, root_ ] := + With[ { model = currentChatSettings[ obj, "Model" ] }, + MakeMenu[ + Join[ + { "Services" }, + (createServiceItem[ obj, model, root, #1 ] &) /@ getAvailableServiceNames[ ] + ], + GrayLevel[ 0.85 ], + 140 + ] + ]; - (* Cache the icon so we don't have to load it from disk again. *) - getIcon[filename] = icon; +createServiceMenu // endDefinition; - icon -] *) +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*createServiceItem*) +createServiceItem // beginDefinition; -getIcon[ name_ ] := RawBoxes @ TemplateBox[ { }, name ]; +createServiceItem[ obj_, model_, root_, service_String ] := <| + "Type" -> "Submenu", + "Label" -> service, + "Icon" -> serviceIcon[ model, service ], + "Data" :> dynamicModelMenu[ obj, root, model, service ] +|>; +createServiceItem // endDefinition; +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*serviceIcon*) +serviceIcon // beginDefinition; -(*========================================================*) -(* Chat settings lookup helpers *) -(*========================================================*) +serviceIcon[ KeyValuePattern[ "Service" -> service_String ], service_String ] := + alignedMenuIcon[ $currentSelectionCheck, serviceIcon @ service ]; + +serviceIcon[ _String, "OpenAI" ] := + alignedMenuIcon[ $currentSelectionCheck, serviceIcon @ "OpenAI" ]; + +serviceIcon[ _, service_String ] := + alignedMenuIcon[ Style[ $currentSelectionCheck, ShowContents -> False ], serviceIcon @ service ]; + +serviceIcon[ KeyValuePattern @ { "Service" -> _String, "Icon" -> icon: Except[ "" ] } ] := + icon; + +serviceIcon[ KeyValuePattern[ "Service" -> service_String ] ] := + serviceIcon @ service; + +serviceIcon[ "OpenAI" ] := chatbookIcon[ "ServiceIconOpenAI" , True ]; +serviceIcon[ "Anthropic" ] := chatbookIcon[ "ServiceIconAnthropic", True ]; +serviceIcon[ "PaLM" ] := chatbookIcon[ "ServiceIconPaLM" , True ]; +serviceIcon[ service_String ] := Replace[ $availableServices[ service, "Icon" ], $$unspecified -> "" ]; + +serviceIcon // endDefinition; + +$currentSelectionCheck = Style[ "\[Checkmark]", FontColor -> GrayLevel[ 0.25 ] ]; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*dynamicModelMenu*) +dynamicModelMenu // beginDefinition; + +dynamicModelMenu[ obj_, root_, model_, service_? modelListCachedQ ] := + Module[ { display }, + makeServiceModelMenu[ Dynamic @ display, obj, root, model, service ]; + display + ]; + +dynamicModelMenu[ obj_, root_, model_, service_ ] := + DynamicModule[ { display }, + display = MakeMenu[ + { + { service }, + { + None, + Pane[ + Column @ { + Style[ "Getting available models\[Ellipsis]", "ChatMenuLabel" ], + ProgressIndicator[ Appearance -> "Percolate" ] + }, + ImageMargins -> 5 + ], + None + } + }, + GrayLevel[ 0.85 ], + 200 + ]; + + Dynamic[ display, TrackedSymbols :> { display } ], + Initialization :> Quiet[ + Needs[ "Wolfram`Chatbook`" -> None ]; + catchAlways @ makeServiceModelMenu[ Dynamic @ display, obj, root, model, service ] + ], + SynchronousInitialization -> False + ]; + +dynamicModelMenu // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*makeServiceModelMenu*) +makeServiceModelMenu // beginDefinition; + +makeServiceModelMenu[ display_, obj_, root_, currentModel_, service_String ] := + makeServiceModelMenu[ + display, + obj, + root, + currentModel, + service, + Block[ { $allowConnectionDialog = False }, getServiceModelList @ service ] + ]; + +makeServiceModelMenu[ Dynamic[ display_ ], obj_, root_, currentModel_, service_String, models_List ] := + display = MakeMenu[ + Join[ { service }, groupMenuModels[ obj, root, currentModel, models ] ], + GrayLevel[ 0.85 ], + 280 + ]; + +makeServiceModelMenu[ Dynamic[ display_ ], obj_, root_, currentModel_, service_String, Missing[ "NotConnected" ] ] := + display = MakeMenu[ + { + { service }, + { + Spacer[ 0 ], + "Connect for model list", + Hold[ + display = simpleModelMenuDisplay[ service, ProgressIndicator[ Appearance -> "Percolate" ] ]; + makeServiceModelMenu[ + Dynamic @ display, + obj, + root, + currentModel, + service, + getServiceModelList @ service + ] + ] + } + }, + GrayLevel[ 0.85 ], + 200 + ]; + +makeServiceModelMenu // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*simpleModelMenuDisplay*) +simpleModelMenuDisplay // beginDefinition; +simpleModelMenuDisplay[ service_, expr_ ] := MakeMenu[ { { service }, { None, expr, None } }, GrayLevel[ 0.85 ], 200 ]; +simpleModelMenuDisplay // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*groupMenuModels*) +groupMenuModels // beginDefinition; + +groupMenuModels[ obj_, root_, currentModel_, models_List ] := + groupMenuModels[ obj, root, currentModel, GroupBy[ models, modelGroupName ] ]; + +groupMenuModels[ obj_, root_, currentModel_, models_Association ] /; Length @ models === 1 := + modelMenuItem[ obj, root, currentModel ] /@ First @ models; + +groupMenuModels[ obj_, root_, currentModel_, models_Association ] := + Flatten[ KeyValueMap[ menuModelGroup[ obj, root, currentModel ], models ], 1 ]; +groupMenuModels // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*menuModelGroup*) +menuModelGroup // beginDefinition; + +menuModelGroup[ obj_, root_, currentModel_ ] := + menuModelGroup[ obj, root, currentModel, ## ] &; + +menuModelGroup[ obj_, root_, currentModel_, None, models_List ] := + modelMenuItem[ obj, root, currentModel ] /@ models; + +menuModelGroup[ obj_, root_, currentModel_, name_String, models_List ] := + Join[ { name }, modelMenuItem[ obj, root, currentModel ] /@ models ]; + +menuModelGroup // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*modelGroupName*) +modelGroupName // beginDefinition; +modelGroupName[ KeyValuePattern[ "FineTuned" -> True ] ] := "Fine Tuned Models"; +modelGroupName[ KeyValuePattern[ "Snapshot" -> True ] ] := "Snapshot Models"; +modelGroupName[ _ ] := None; +modelGroupName // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*modelMenuItem*) +modelMenuItem // beginDefinition; + +modelMenuItem[ obj_, root_, currentModel_ ] := modelMenuItem[ obj, root, currentModel, #1 ] &; + +modelMenuItem[ + obj_, + root_, + currentModel_, + model: KeyValuePattern @ { "Name" -> name_, "Icon" -> icon_, "DisplayName" -> displayName_ } +] := { + alignedMenuIcon[ modelSelectionCheckmark[ currentModel, name ], icon ], + displayName, + Hold[ removeChatMenus @ EvaluationCell[ ]; setModel[ obj, model ] ] +}; + +modelMenuItem // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*modelSelectionCheckmark*) +modelSelectionCheckmark // beginDefinition; +modelSelectionCheckmark[ KeyValuePattern[ "Name" -> model_String ], model_String ] := $currentSelectionCheck; +modelSelectionCheckmark[ model_String, model_String ] := $currentSelectionCheck; +modelSelectionCheckmark[ _, _ ] := Style[ $currentSelectionCheck, ShowContents -> False ]; +modelSelectionCheckmark // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*setModel*) +setModel // beginDefinition; + +setModel[ obj_, KeyValuePattern @ { "Service" -> service_String, "Name" -> model_String } ] := ( + CurrentValue[ obj, { TaggingRules, "ChatNotebookSettings", "Model" } ] = + <| "Service" -> service, "Name" -> model |> +); + +setModel // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*Chat settings lookup helpers*) + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*absoluteCurrentValue*) SetFallthroughError[absoluteCurrentValue] absoluteCurrentValue[cell_, {TaggingRules, "ChatNotebookSettings", key_}] := currentChatSettings[cell, key] absoluteCurrentValue[cell_, keyPath_] := AbsoluteCurrentValue[cell, keyPath] -(*====================================*) - +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*currentValueOrigin*) currentValueOrigin // beginDefinition; (* @@ -1355,8 +1183,9 @@ currentValueOrigin[ currentValueOrigin // endDefinition; -(*====================================*) - +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*getModelsMenuItems*) getModelsMenuItems[] := Module[{ items }, @@ -1378,10 +1207,13 @@ getModelsMenuItems[] := Module[{ ] -(*========================================================*) -(* Menu construction helpers *) -(*========================================================*) +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*Menu construction helpers*) +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*alignedMenuIcon*) SetFallthroughError[alignedMenuIcon] alignedMenuIcon[possible_, current_, icon_] := alignedMenuIcon[styleListItem[possible, current], icon] @@ -1389,8 +1221,9 @@ alignedMenuIcon[check_, icon_] := Row[{check, " ", resizeMenuIcon[icon]}] (* If menu item does not utilize a checkmark, use an invisible one to ensure it is left-aligned with others *) alignedMenuIcon[icon_] := alignedMenuIcon[Style["\[Checkmark]", ShowContents -> False], icon] -(*====================================*) - +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*resizeMenuIcon*) resizeMenuIcon[ icon: _Graphics|_Graphics3D ] := Show[ icon, ImageSize -> { 21, 21 } ]; @@ -1401,8 +1234,9 @@ resizeMenuIcon[ icon_ ] := Pane[ ContentPadding -> False ]; -(*====================================*) - +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*styleListItem*) SetFallthroughError[styleListItem] (* @@ -1430,10 +1264,13 @@ styleListItem[ }] ) -(*========================================================*) -(* Persona property lookup helpers *) -(*========================================================*) +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*Persona property lookup helpers*) +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*personaDisplayName*) SetFallthroughError[personaDisplayName] personaDisplayName[name_String] := personaDisplayName[name, GetCachedPersonaData[name]] @@ -1441,8 +1278,9 @@ personaDisplayName[name_String, data_Association] := personaDisplayName[name, da personaDisplayName[name_String, displayName_String] := displayName personaDisplayName[name_String, _] := name -(*====================================*) - +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*getPersonaMenuIcon*) SetFallthroughError[getPersonaMenuIcon]; getPersonaMenuIcon[ name_String ] := getPersonaMenuIcon @ Lookup[ GetPersonasAssociation[ ], name ]; @@ -1462,14 +1300,18 @@ getPersonaMenuIcon[ expr_, "Full" ] := icon_ :> icon }] - +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*getPersonaIcon*) getPersonaIcon[ expr_ ] := getPersonaMenuIcon[ expr, "Full" ]; +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*Model property lookup helpers*) -(*========================================================*) -(* Model property lookup helpers *) -(*========================================================*) - +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*getModelMenuIcon*) SetFallthroughError[getModelMenuIcon] getModelMenuIcon[settings_?AssociationQ] := Module[{}, @@ -1490,11 +1332,13 @@ getModelMenuIcon[settings_?AssociationQ, "Full"] := icon_ :> icon }] +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*Generic Utilities*) -(*========================================================*) -(* Generic Utilities *) -(*========================================================*) - +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*nestedLookup*) SetFallthroughError[nestedLookup] Attributes[nestedLookup] = {HoldRest} @@ -1517,12 +1361,14 @@ nestedLookup[as_, key:Except[_List], default_] := nestedLookup[as_, keys_] := nestedLookup[as, keys, Missing["KeySequenceAbsent", keys]] +(* ::**************************************************************************************************************:: *) +(* ::Section::Closed:: *) +(*Package Footer*) +If[ Wolfram`ChatbookInternal`$BuildingMX, + Null; +]; -(*========================================================*) - - -End[] - -EndPackage[] +(* :!CodeAnalysis::EndBlock:: *) -(* :!CodeAnalysis::EndBlock:: *) \ No newline at end of file +End[ ]; +EndPackage[ ]; \ No newline at end of file diff --git a/Tests/CurrentChatSettings.wlt b/Tests/CurrentChatSettings.wlt index 82ebc151..1b2b0b2e 100644 --- a/Tests/CurrentChatSettings.wlt +++ b/Tests/CurrentChatSettings.wlt @@ -34,7 +34,7 @@ VerificationTest[ VerificationTest[ CurrentChatSettings[ "Model" ], - _String | Automatic, + KeyValuePattern @ { "Service" -> _String, "Name" -> _String } | _String | Automatic, SameTest -> MatchQ, TestID -> "CurrentChatSettings@@Tests/CurrentChatSettings.wlt:35,1-40,2" ] @@ -44,14 +44,14 @@ VerificationTest[ (*Scoped*) VerificationTest[ UsingFrontEnd @ CurrentChatSettings[ $FrontEnd, "Model" ], - _String | Automatic, + KeyValuePattern @ { "Service" -> _String, "Name" -> _String } | _String | Automatic, SameTest -> MatchQ, TestID -> "CurrentChatSettings@@Tests/CurrentChatSettings.wlt:45,1-50,2" ] VerificationTest[ UsingFrontEnd @ CurrentChatSettings[ $FrontEndSession, "Model" ], - _String | Automatic, + KeyValuePattern @ { "Service" -> _String, "Name" -> _String } | _String | Automatic, SameTest -> MatchQ, TestID -> "CurrentChatSettings@@Tests/CurrentChatSettings.wlt:52,1-57,2" ] @@ -83,9 +83,13 @@ VerificationTest[ } } ], - { Except[ "BlockModel", _String ], "BlockModel", "BlockModel" }, + { + Except[ "BlockModel", KeyValuePattern @ { "Service" -> _String, "Name" -> _String } ], + "BlockModel", + "BlockModel" + }, SameTest -> MatchQ, - TestID -> "CurrentChatSettings-ChatBlocks@@Tests/CurrentChatSettings.wlt:75,1-89,2" + TestID -> "CurrentChatSettings-ChatBlocks@@Tests/CurrentChatSettings.wlt:75,1-93,2" ] VerificationTest[ @@ -102,7 +106,7 @@ VerificationTest[ ], { "NotebookModel", "BlockModel", "BlockModel" }, SameTest -> MatchQ, - TestID -> "CurrentChatSettings-ChatBlocks@@Tests/CurrentChatSettings.wlt:91,1-106,2" + TestID -> "CurrentChatSettings-ChatBlocks@@Tests/CurrentChatSettings.wlt:95,1-110,2" ] (* ::**************************************************************************************************************:: *) @@ -125,5 +129,5 @@ VerificationTest[ ], Except[ _? FailureQ ], SameTest -> MatchQ, - TestID -> "CurrentChatSettings-Regression@@Tests/CurrentChatSettings.wlt:115,1-129,2" + TestID -> "CurrentChatSettings-Regression@@Tests/CurrentChatSettings.wlt:119,1-133,2" ]