diff --git a/.gitignore b/.gitignore index f188e1986..057ded59f 100644 --- a/.gitignore +++ b/.gitignore @@ -66,3 +66,4 @@ app.*.map.json # This package's specific logs. **/*.log.* +**/*.log diff --git a/examples/custom_backend/assets/data/saved-response-1.json b/examples/custom_backend/assets/data/saved-response-1.json index 9ec9fad8e..da064b09b 100644 --- a/examples/custom_backend/assets/data/saved-response-1.json +++ b/examples/custom_backend/assets/data/saved-response-1.json @@ -5,102 +5,74 @@ "parts": [ { "functionCall": { - "name": "surfaceUpdate", + "name": "uiGenerationTool", "args": { "components": [ { - "id": "root", - "component": { - "Column": { - "children": { - "explicitList": [ - "login_heading", - "username_field", - "password_field", - "login_button" - ] - } - } - } - }, - { - "id": "login_heading", "component": { "Heading": { - "level": "1", - "text": { - "literalString": "Login" - } - } - } - }, - { - "component": { - "TextField": { - "label": { - "literalString": "Username" - }, + "level": "3", "text": { - "path": "username" + "literalString": "Here are some of the things I can help you with:" } } }, - "id": "username_field" + "id": "heading_1" }, { - "id": "password_field", + "id": "multiple_choice_1", "component": { - "TextField": { - "textFieldType": "obscured", - "label": { - "literalString": "Password" - }, - "text": { - "path": "password" - } - } - } - }, - { - "component": { - "Button": { - "child": "login_button_text", - "action": { - "name": "login", - "context": [ - { - "key": "username", - "value": { - "path": "username" - } + "MultipleChoice": { + "options": [ + { + "label": { + "literalString": "Show a cat fact" }, - { - "key": "password", - "value": { - "path": "password" - } + "value": "Show a cat fact" + }, + { + "label": { + "literalString": "Show a dog fact" + }, + "value": "Show a dog fact" + }, + { + "value": "Help me generate UI", + "label": { + "literalString": "Help me generate UI" } - ] + }, + { + "label": { + "literalString": "Answer a question" + }, + "value": "Answer a question" + } + ], + "selections": { + "path": "selection" } } - }, - "id": "login_button" + } }, { "component": { - "Text": { - "text": { - "literalString": "Login" + "Column": { + "children": { + "explicitList": [ + "heading_1", + "multiple_choice_1" + ] } } }, - "id": "login_button_text" + "id": "root" } ], - "surfaceId": "custom_backend_surface" + "surfaceId": "assisting_options" } }, - "thoughtSignature": "CuQMAdHtim9XQ3nDtF1j446uYhI1p7jnQ1AVzPBCY7OUe+u5+NLaUvW+uZJ3gpiVsujt48pOhk5UzZas48McKM2msCTUAyOKVes1TftRrVF18uOLQx2x7UovqwUrSO2RU3rbMaGIJXyyQZArN3J72SZ30YuGx0vtCbciFLSQN+XRy3Zg5rEOy33rhxVFiKf8VSi83YWaVkjLunu5W81Vnvq66IDxgLC3J3OmM14z+bZcHRaPllO+I59bi5I41QGsChUWfILRVZ8QQYYBdoHIn3j7DdziNYN1cOF5962ZIYrren94vMgJZNsE3NVKHHG1Iosqu/as6ajem2JyttGd6CIoc9II++QN2y0Xu4mG4cMhT1uHnRVnswFHAizFnJXBv/VXQ2rqVdLI7sav3MSbGKHhIt+jgYm1ylr0zTCA/GkzhOZWXO/5nQdzGHUFKca+FiMFTZGuFye1W5/39/Oy4gU9uQwCs2znfrFf4hxuWlpt4tYd+7yS/1BZlmDmY7O1E/ZRq5ie931EGFefA4qNkTCYB2JJyo4Yd5mXQLxx7aVCxJn3DUT36b5zMAey7TJrBL66940+U5bqgpNMgoadjUtWw747QNtUyDKjKHQhXss6LtY8k0ntYGaIL5+oFxD927PqP1zpcMtXjRQEDb2k9mJDIMA59r6snYrOqXCeLDiK/Y3xstIG8KYs1Lc4ZDy761cUeL00O8j+M7LzLMtwPPWcaqnSvE78rPH1BI4+ZMrzv9BpGOCwZVQs9hlxiu48tQuWpxs+odz4etxe0AxiCLWCA45gkE01gKCv8q3wzHF9Dqal+yXVB8OXdhkcVXL+DC26mVgscbiisSra2xqq0bhtE+MWiC+snzT1c9V4yVyZbHHMv2SwOBGP0+rfJ9uKeY2s3T3zZEfk2DR0iioxhSA+5Bqt/IjpL5kO5MUwMVZYGQaYr58k05o5ThMBR/Bi3wDWGe2W6x+wPe8Eb3X6rm+rDsHAHzjVs61hdgbRRCeKTTLW1b3f+rylk9Xb7q7tmLiLKDtD6P1l6o1sln6TEZE1/e+ff/8REoRnJujxF5UMb5naj6VfZPjjgPgImWDKWnjrXK31yYuCu8BkfkVJ1DcYlycyRh8jcoaq7UME7gWiX3Yoyf4oHSESjJAHIzoirThy+z4swQ5yIeyIqxB5LZ4pjQCtxAzaZMW3KbvYbVAbCBrQftZcyWFSYu3LqbFVizpI2OoFEHTrH/TITy/5F46vqdcctDjcR6vU2BMa6iq9cEnuoXZ9Tj4Wbdf3Aq4nxW805Sry3b1X3nX6w/oLkM5AkVOp5szfmdGOA4A68wk0JbgM2NZrt6Dx2YJA1jt5PZJbxHM8nqOneAjbzNXxSBF6xg6SWMA3N1u1oB0qRM7UWW5gk8BeZgC6LNLgmMSXTUw31HMwJHauiUdsAtBeZJmBTL9ZuMhcxPsrkDtH7NWEJpaOeVEaPI2PsUtth5jWaKH7kcuHCLxNaKaGGnd6KXpiRQVgPenn1WeGfkUMx0j0QO5YLG4S7UiKVfRmhxJqOWEYDpL5blONLziFpfnYkbWBxKeuS+AEyODOhBzgqlVk1UGMjSjPPDN2RQaKkTfkpP6ona6Ik1l8YbwGG/kHUAWrsvV23wpG3y4LWFrtWnPGpoGumT2e1B0OchMK7glEa7oR5TzPbmye9pq6o9CjLvHJfblVuPQ7NuqAYhHD7ch8+krpkSMOcig2ngOTSgudGJpzV5Ha1SuLDv7xrYWERv6J1SQIZJ+Ad7cKRynVGTOs5YTp7igmD6oJuW9nqhSvIxTNeb2J2QeTlzoEjw35lSoH+oq3IZnSboomGaF+seM/OiVpNXR466lmNsGeoPt/z0kJbj+IH8t03lLmSSiPzALHb60m8Cti3UliPEISWXK0lHdu/H+Q91Xbq+kMUiM7OgOmjcuNejhmdnzct2V6sn/sk+fOtrfB5kM3484gS4e2lLyvKa985JQkbAyAqdrSV5msOHYV3XL25LZhJOgEBtJ3DCOmlzHHCE+m+7mABOtGqwhEVzT+eXYfkzAMvkYxKEgUWH0dTXPK6IjEzauV8kZGyNAEIGJVxzdgg/Bj3xm7mcfegW5KoWf5ty4WteaZzn2qg5sh+8FTtHqfSUJZAaLRifDqC3/ypDcz4G+/RbC8iAU9XeGxcg7v+UAFPGNFoB6TeNXVTgruFAHR7YpvdenXaqhRdmPp7L9WTOKNawSSGj+OHuUKWYgvlSWZTnOKCY0m8Bz7lAd6e+lCBjl5lWk6TiO7CrXWttph106IdSkAsPhTQQKA587PKkbd3h4+XrkzxjYfd9bks0UDs1kCH18SwQSQ1xDyY09VJ5NRVS08rdHxa27z63E1giVObU3xsJ6ZDFk9/zWJVINQSNPK8FbuaW+/kuQ0Taw3k2rYJjJ5AJNB4wAeXXtV2Syr916rLCUF3RQn0+m3ILLoBOOYczHrj7joc+ocsWOStCubxOLNW8afdiY/aoGipQ649FnLnBYRdq4pPIm0qB/lQ+TKlzMe1s7FfzFuPDS4FtrySfrPoBALQ1oHUj/qKeqWKMWz1Np3IQcCuFeIQn1WXAzHIHiMpithAVABPdLe6s4HL8P/m6veMA5RKhH0N2XdSqBoXB3az3cfWI/yM3KtOj2juFFfRDsm3B04ImTPjPlsvnTZDhb4O6DSJp00Hl4iKWNE3UF4zwKuVUh6htO6B9/3/Vwuan1/6/KezJkETntOwGbsHcHBF8SGXSlo+UejQvZGjszCfOcVgRe8U4DwwD4TvTXNRmmEFDQa6fLFIarJ+L0MV8Ov+2ktXhs8xOcncMei+wp5NxA3Tgqe8S+/EsvSDmxFH/e0FH8K/Kgdy3KHq83BFlNSu08S6d2dITEYl9UeznMpyOhHHBRU24ir9wUhBawR7QBQt/RlrKuvapGNm7kfw0rWtikqnDzHlDxvuTviyLCL9SUCqSiX5wNdQYCvsQ0801zodVxcZKJepu95TkCF9+T4Tycf9XKG6o0meT+1OUi+GmKnsucKMV3tW8W1LMdquIyYnExojj6tlxpf6FHvLUG4EJv1IFl2Ftg4eN8FYoSLjB/qEYTZz2crQLOQhhUOXIj9G1+y/AqSXU3QlMxBdzO1G7FXPjB8UqeVxVyPnfvZArWKapWIgVHkLqA5eXq4PfKAVx04M2zLi1jfcmLMv1D99Fcr6HGPE3AGxwkRk9LXOOYE2h1ebPwSNeut2qUUsKojEQeT62NVVfNMoYEHtfPsMjl0mLvkfYyipyWvP/9tLY03BMisSFlRfzHbFVl+QHn5I+a4c38Jw7vqnUJVxZ2w2rgdAFp9b+xP52MjD95UZlWh0S9T7wE2Wh0KBfWGEVhI4DKRVarmiS6TGgZ/zb0SvZ6/xxaRDkP/5E/sYNTQLYc7ltfCnPoDvATSdkZ6Ad1HfNkzQcVH0IiLI/ocrcA3ZMkI3A+TgpiwqL5wRMmHMz9s9uC3AW9vtltwUfXmN0Irfn0snZUziKDgki/rhfFIEOUxtF3oQnly7iQ9YVrl9APjNWrHsKJLayyX61QcvIG2lpEzR18COi744FiJIs9NmxuaP5AqhfkJqaB4XHobch3m5xhm8Wh5FNU2xJx4SMjRRqoA8P+h4xOmlApS1dm1Nuh7hzDj/LQB4dT/ijCPEVaXKDCpv46ZCEIhx1XORC1pC/5b4beicMRQpYyyQlRRlWOBPdNAJ4aWELB+ExdUHmUXA+k65QG4uraLTXu25647Qy15FpvRo0Xh+IfzaHXXcWoSvWCmIUw8iuTxHR8LaKRAYxeqeSch8SD7X/SwTEENhLsfn9ItKq8iVzRW+TOgVeHzaXY5CsCkzbj2tGQJLh1PYuGzjPGsYopieelNyqZ8R0g8ht+Rasqgs+1hChtMxv9kXhgoMTOqAvtT9djltjsPqr3mrQEnpXI2t4TYl1keMfynS1fByov9V2kEGHv9Mei7FoSZURbggVi7YOY4DM5lnBfUYc37fZOV0K5SVS+2ce2Fv2KZOGt1036d9JzEovwUVLHEL1kWtFXlhKDsdY9nOITV6ahsmGbSi05xcr9rOEEZkzI5COFx5NvCTLL+C2dMqXXfbncdRPCtPhIvHTtuu6dAzMgcMH49+HwsGw6tw5waFt0D7tuN6kTrLJHTGIrcLqAzY9Y+gLGFLjCSpSNXnzXXuMbfcIOZw4qKIaMmZADRQuFPQ5Mvbtn025dmX7svlm5ISUZIw8Lv4Vty8fLjsSTD6p0Et7Kc1GxHDXoL4kh1cRs7TOSIRvfOEd+pPdNKtHeY9bWVI3W3dUghHFCVEn+JfwvPP51sqiUVU/bfInEoX0ROBjf3YfTPJBXaYlKnyXco4RSrXehXKdiVJB/nsG9aMjkfYNtI8WgACgJV+MoDqmuAhBI5LhRQVSKeK2awHtc9tL8EbxQWZlhgBwQKne9PxAoxZkL4oivqNbSoXfY94GwEIXAnWwqvSm825g/HIQTeLuME+cz5TBL64GGvLxFd6RnH7Q5Rm++2Oy3GaqNzTYec0jOCBbupnZiWfyUOPUEDxhzeTt5UWo18pmySBA2IJMfx/Jr9mJcTqGh77d0Rfh3NNlj0kJexJ9ZscUOTAOQaEDon4D3GX6UbaOAXhQUhAS66VpE3FeUkYpKaD5FQETB4t9IF62N6DYzxLWhePPNnZAYdXhzOGhwcW79+eO6FNyvrhM9j//1JFhehgnbjVcXAtOSFQmbI9DzGolPJ+OBW8dhKsM+pB8z6ScM3ebJe8cjpQe/mCFDM7hlFfrs2fKR4e8eBg/PhQq4JGiZ0g5kPKTmYRIKJ24oIWammXbXB4MP0CIeqRgYMiZAwdDmlHFnM2eNTVFgJ8SvDJm+/ts2Sc4oX+lsQT/Sbtzqsn+9mCRF5R9cGh9zp058HlRqkyGOd8vKv0TFbUalEosw9PIhDzP6Iy8U5oh4j4LmZVRyMW5udYqArxLrW5gJf+eMR48SlWnxydxvy9ALqw14EM8NzeIS2xtChqW9dlYR5ul5lLViK6DbmvzcbW3iibrl/4LbVYU5IcDqXTn+BfONUFhsgBatKeqKrpeeeIj4nn4QvCKzfzKx1yVQId9ADX4Gw4aTHeP/Rl9PQSxkp+nJmpUInQZMJHH+NBtYaOEDxbBzs0T/jwYdbqAx2BTnL3lUlLn4KXuP3HG8yHVWMovESLef+5hVFcCsBRPPNl6eR3c2sIswyj06jUgytPBbsHFhtezXFroSN3+uQBlMcbF3HPBKwO6MFXbEfZcw4WbeWR+2OnkSkkoPLR0wkCr0ggPNzrQnesoWfBOG/4+Q04NgS+exd+0n+ERIIrwP5Qh2StETbW9L+X7TS5+XQqH29Q17d3kyR2wdzrXyunR7R+NTxLgRDON+qBCMEBfJNenZj9s+cmEHurpJF1O/TWMdgQYA0Kvdq37V90NCI4sfVyE0su3uGSkJHE8cSr/PE+PE2kv5zuw0kAbE3ZEy4TzUktPnNgeJ1lmT5ZFxxyiFlfRX3tG1WbqTtlJB0wbWxJiMLv6LPxuAUQeX9GTZpqPjw11rJ3lTUe421UtCPweI5mzv/EsAZtL/aE9oDdX4KgMl/qTMxIemm65OuLzQpQFZEm9O+pXA7/MGHqcF5hTNeC3CoAHxTqYAM8jaV0hclK0SGtPQQXze06wXEvvJDrwj7URmO7Z9PAIhEoDmAkXy5xNivBwnA5Ulwra0TUmbYY+iduIlq80bfWokiTBF7/zSEay5VON4cFXbgyaTHXx32iqnYJQqrAQHR7YpviyFaFYr7YESurWL+Bcvd+nHy+nhUVijyyzn7ZqAi3IkgiviPLXZl9GBh5QhH/4+GKNveC6rYcQFBxpYZRBEYL2i2fTLM2qDcJbtvTM6A4n5O0pOmvr+zTErghjacZ2D26q8Q8F/eF2b8o4EQoETahOjzHDV8GEJZiF++OpVWSBIbmgs08yttQ5zC3FEXjk1yhoQVLRTnmZBpFoAMxmFF3o7xVS4ybQ==" + "thoughtSignature": "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" } ], "role": "model" @@ -111,17 +83,17 @@ } ], "usageMetadata": { - "promptTokenCount": 4333, - "candidatesTokenCount": 265, - "totalTokenCount": 5613, + "promptTokenCount": 4568, + "candidatesTokenCount": 213, + "totalTokenCount": 5480, "promptTokensDetails": [ { "modality": "TEXT", - "tokenCount": 4333 + "tokenCount": 4568 } ], - "thoughtsTokenCount": 1015 + "thoughtsTokenCount": 699 }, "modelVersion": "gemini-2.5-pro", - "responseId": "xP32aI22C_nn1e8P_JHn0Qs" -} \ No newline at end of file + "responseId": "_Yr5aIIDyZ6q2w_q5Iv4Aw" +} diff --git a/examples/custom_backend/assets/data/saved-response-2.json b/examples/custom_backend/assets/data/saved-response-2.json index 2096103ba..abcfb8acd 100644 --- a/examples/custom_backend/assets/data/saved-response-2.json +++ b/examples/custom_backend/assets/data/saved-response-2.json @@ -5,9 +5,9 @@ "parts": [ { "functionCall": { - "name": "surfaceUpdate", + "name": "uiGenerationTool", "args": { - "surfaceId": "custom_backend_surface", + "surfaceId": "helpOptions", "components": [ { "component": { @@ -15,10 +15,7 @@ "children": { "explicitList": [ "heading", - "checkbox1", - "checkbox2", - "checkbox3", - "submit_button" + "options" ] } } @@ -28,9 +25,9 @@ { "component": { "Heading": { - "level": "1", + "level": "2", "text": { - "literalString": "Select your preferences" + "literalString": "What can I help you with?" } } }, @@ -38,88 +35,39 @@ }, { "component": { - "CheckBox": { - "label": { - "literalString": "Option 1" - }, - "value": { - "path": "form.checkbox1" - } - } - }, - "id": "checkbox1" - }, - { - "component": { - "CheckBox": { - "label": { - "literalString": "Option 2" - }, - "value": { - "path": "form.checkbox2" - } - } - }, - "id": "checkbox2" - }, - { - "id": "checkbox3", - "component": { - "CheckBox": { - "label": { - "literalString": "Option 3" - }, - "value": { - "path": "form.checkbox3" - } - } - } - }, - { - "id": "submit_button", - "component": { - "Button": { - "action": { - "name": "submit_form", - "context": [ - { - "value": { - "path": "form.checkbox1" - }, - "key": "checkbox1" - }, - { - "value": { - "path": "form.checkbox2" - }, - "key": "checkbox2" - }, - { - "value": { - "path": "form.checkbox3" - }, - "key": "checkbox3" + "MultipleChoice": { + "maxAllowedSelections": 1, + "options": [ + { + "value": "answer_questions", + "label": { + "literalString": "Answer questions" } - ] - }, - "child": "submit_button_text" - } - } - }, - { - "component": { - "Text": { - "text": { - "literalString": "Submit" + }, + { + "value": "generate_ui", + "label": { + "literalString": "Generate UI" + } + }, + { + "label": { + "literalString": "Write code" + }, + "value": "write_code" + } + ], + "selections": { + "path": "selection" } } }, - "id": "submit_button_text" + "id": "options" } ] } }, - "thoughtSignature": "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" + "thoughtSignature": "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" } ], "role": "model" @@ -130,17 +78,17 @@ } ], "usageMetadata": { - "promptTokenCount": 4335, - "candidatesTokenCount": 333, - "totalTokenCount": 5357, + "promptTokenCount": 4568, + "candidatesTokenCount": 173, + "totalTokenCount": 5666, "promptTokensDetails": [ { "modality": "TEXT", - "tokenCount": 4335 + "tokenCount": 4568 } ], - "thoughtsTokenCount": 689 + "thoughtsTokenCount": 925 }, "modelVersion": "gemini-2.5-pro", - "responseId": "TP72aOS_PIOy1e8PnICj6As" -} \ No newline at end of file + "responseId": "nIv5aLyvC5C5qtsP98KaqAs" +} diff --git a/examples/custom_backend/assets/data/saved-response-3.json b/examples/custom_backend/assets/data/saved-response-3.json index fb1de0d13..f56a70aae 100644 --- a/examples/custom_backend/assets/data/saved-response-3.json +++ b/examples/custom_backend/assets/data/saved-response-3.json @@ -5,17 +5,17 @@ "parts": [ { "functionCall": { - "name": "surfaceUpdate", + "name": "uiGenerationTool", "args": { "components": [ { "component": { - "List": { + "Column": { "children": { - "template": { - "componentId": "dog_card_template", - "dataBinding": "dogs" - } + "explicitList": [ + "heading_1", + "multiple_choice_1" + ] } } }, @@ -23,27 +23,50 @@ }, { "component": { - "Card": { - "child": "dog_name" + "Heading": { + "level": "1", + "text": { + "literalString": "How can I help you?" + } } }, - "id": "dog_card_template" + "id": "heading_1" }, { "component": { - "Text": { - "text": { - "path": "name" - } + "MultipleChoice": { + "selections": { + "path": "component.multiple_choice_1.selections" + }, + "options": [ + { + "label": { + "literalString": "Generate UI" + }, + "value": "generate_ui" + }, + { + "label": { + "literalString": "Answer questions" + }, + "value": "answer_questions" + }, + { + "value": "summarize_text", + "label": { + "literalString": "Summarize text" + } + } + ] } }, - "id": "dog_name" + "id": "multiple_choice_1" } ], - "surfaceId": "custom_backend_surface" + "surfaceId": "options_surface" } }, - "thoughtSignature": "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" + "thoughtSignature": "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" } ], "role": "model" @@ -54,17 +77,17 @@ } ], "usageMetadata": { - "promptTokenCount": 4342, - "candidatesTokenCount": 108, - "totalTokenCount": 4973, + "promptTokenCount": 4568, + "candidatesTokenCount": 188, + "totalTokenCount": 5495, "promptTokensDetails": [ { "modality": "TEXT", - "tokenCount": 4342 + "tokenCount": 4568 } ], - "thoughtsTokenCount": 523 + "thoughtsTokenCount": 739 }, "modelVersion": "gemini-2.5-pro", - "responseId": "gP72aKf_G9Lj1e8P8bu5iAM" -} \ No newline at end of file + "responseId": "64v5aLfxApaGqtsPr-2lgQc" +} diff --git a/examples/custom_backend/lib/backend.dart b/examples/custom_backend/lib/backend.dart new file mode 100644 index 000000000..a47da0b11 --- /dev/null +++ b/examples/custom_backend/lib/backend.dart @@ -0,0 +1,62 @@ +// Copyright 2025 The Flutter Authors. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter_genui/flutter_genui.dart'; + +import 'debug_utils.dart'; +import 'gemini_client.dart'; + +class UiSchemaDefinition { + final String prompt; + final List tools; + + const UiSchemaDefinition({required this.prompt, required this.tools}); + + factory UiSchemaDefinition.fromJson(Map json) => + UiSchemaDefinition( + prompt: json['prompt'] as String, + tools: (json['tools'] as List) + .map( + (x) => + GenUiFunctionDeclaration.fromJson(x as Map), + ) + .toList(), + ); + + Map toJson() => { + 'type': 'UiSchemaDefinition', + 'prompt': prompt, + 'tools': List.from(tools.map((x) => x.toJson())), + }; +} + +class Backend { + Backend(this.schema); + + final UiSchemaDefinition schema; + + Future sendRequest( + String request, { + required String? savedResponse, + }) async { + final toolCall = await GeminiClient.sendRequest( + tools: schema.tools, + request: '${schema.prompt}\n\nUser request:\n$request', + savedResponse: savedResponse, + ); + + if (toolCall == null) return null; + + if (!schema.tools.map((e) => e.name).contains(toolCall.name)) { + throw Exception( + 'Received unknown tool call: ${toolCall.name}. ' + 'Expected one of: ${schema.tools.map((e) => e.name).toList()}', + ); + } + + debugSaveToFileObject('toolCall', toolCall); + + return parseToolCall(toolCall, toolCall.name); + } +} diff --git a/examples/custom_backend/lib/backend/api.dart b/examples/custom_backend/lib/gemini_client.dart similarity index 92% rename from examples/custom_backend/lib/backend/api.dart rename to examples/custom_backend/lib/gemini_client.dart index 75040603b..f0b378ddb 100644 --- a/examples/custom_backend/lib/backend/api.dart +++ b/examples/custom_backend/lib/gemini_client.dart @@ -11,18 +11,17 @@ import 'package:flutter/services.dart'; import 'package:flutter_genui/flutter_genui.dart'; import 'package:http/http.dart' as http; -import '../debug_utils.dart'; -import 'model.dart'; +import 'debug_utils.dart'; // mode='ANY': // The model is constrained to always predict a function call and // guarantees function schema adherence. // https://ai.google.dev/gemini-api/docs/function-calling?example=meeting#rest_2 -abstract class Backend { - static Future sendRequest( - List tools, - String request, { +abstract class GeminiClient { + static Future sendRequest({ + required List tools, + required String request, required String? savedResponse, }) async { late final String? rawResponse; @@ -48,7 +47,7 @@ abstract class Backend { await rootBundle.loadString(savedResponse); static Future _getRawResponseFromApi( - List tools, + List tools, String request, ) async { debugSaveToFileObject('tools', tools); diff --git a/examples/custom_backend/lib/main.dart b/examples/custom_backend/lib/main.dart index 00cbc3c3a..e526899d8 100644 --- a/examples/custom_backend/lib/main.dart +++ b/examples/custom_backend/lib/main.dart @@ -5,7 +5,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_genui/flutter_genui.dart'; -import 'protocol/protocol.dart'; +import 'backend.dart'; void main() { runApp(const MyApp()); @@ -60,13 +60,28 @@ class _IntegrationTester extends StatefulWidget { State<_IntegrationTester> createState() => _IntegrationTesterState(); } +final _catalog = CoreCatalogItems.asCatalog(); +const _toolName = 'uiGenerationTool'; +final uiSchema = UiSchemaDefinition( + prompt: genUiTechPrompt([_toolName]), + tools: [ + catalogToFunctionDeclaration( + _catalog, + _toolName, + 'Generates Flutter UI based on user requests.', + ), + ], +); + class _IntegrationTesterState extends State<_IntegrationTester> { final _controller = TextEditingController(text: requestText); - final _protocol = Protocol(); - late final GenUiManager _genUi = GenUiManager(catalog: _protocol.catalog); + + final _protocol = Backend(uiSchema); + late final GenUiManager _genUi = GenUiManager(catalog: _catalog); String? _selectedResponse; bool _isLoading = false; String? _errorMessage; + String? _surfaceId; @override Widget build(BuildContext context) { @@ -87,21 +102,22 @@ class _IntegrationTesterState extends State<_IntegrationTester> { 'Sending request for _selectedResponse = ' '$_selectedResponse ...', ); - final messages = await _protocol.sendRequest( + final parsedToolCall = await _protocol.sendRequest( _controller.text, savedResponse: _selectedResponse, ); - if (messages == null) { + if (parsedToolCall == null) { print('No UI received.'); setState(() { _isLoading = false; }); return; } - for (final message in messages) { + _surfaceId = parsedToolCall.surfaceId; + for (final message in parsedToolCall.messages) { _genUi.handleMessage(message); } - print('UI received for surfaceId=$kSurfaceId'); + print('UI received for surfaceId=${parsedToolCall.surfaceId}'); setState(() => _isLoading = false); } catch (e, callStack) { print('Error connecting to backend: $e\n$callStack'); @@ -134,7 +150,15 @@ class _IntegrationTesterState extends State<_IntegrationTester> { if (_errorMessage != null) { return Text('$_errorMessage'); } - return GenUiSurface(surfaceId: kSurfaceId, host: _genUi); + final surfaceId = _surfaceId; + if (surfaceId == null) { + return const Text('_surfaceId == null'); + } + return GenUiSurface( + surfaceId: surfaceId, + host: _genUi, + defaultBuilder: (_) => const Text('Fallback to defaultBuilder'), + ); } } diff --git a/examples/custom_backend/lib/protocol/protocol.dart b/examples/custom_backend/lib/protocol/protocol.dart deleted file mode 100644 index 2942558d7..000000000 --- a/examples/custom_backend/lib/protocol/protocol.dart +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2025 The Flutter Authors. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:flutter_genui/flutter_genui.dart'; - -import '../backend/api.dart'; -import '../backend/model.dart'; -import '../debug_utils.dart'; - -const kSurfaceId = 'custom_backend_surface'; - -class Protocol { - Future?> sendRequest( - String request, { - required String? savedResponse, - }) async { - final tools = [_functionDeclaration()]; - - final toolCall = await Backend.sendRequest( - tools, - _prompt(request), - savedResponse: savedResponse, - ); - - if (toolCall == null || toolCall.name != _toolName) { - return null; - } - - debugSaveToFileObject('toolCall', toolCall); - - final messageJson = {'surfaceUpdate': toolCall.args}; - final surfaceUpdateMessage = A2uiMessage.fromJson(messageJson); - - final beginRenderingMessage = const BeginRendering( - surfaceId: kSurfaceId, - root: 'root', - ); - - return [surfaceUpdateMessage, beginRenderingMessage]; - } - - Catalog get catalog => _catalog; -} - -const _toolName = 'surfaceUpdate'; - -final _catalog = CoreCatalogItems.asCatalog(); - -String _prompt(String request) => - ''' -You are a helpful assistant that provides concise and relevant information. -Always respond in a clear and structured manner. -Always respond with generated UI. -Use the tool $_toolName to generate UI code snippets to satisfy user request. -Ensure one of the generated components has an id of 'root'. - -Use the surfaceId: '$kSurfaceId' - -Use the root component ID: 'root' - -User request: $request -'''; - -FunctionDeclaration _functionDeclaration() { - return FunctionDeclaration( - description: 'Generates UI.', - name: _toolName, - parameters: A2uiSchemas.surfaceUpdateSchema(_catalog), - ); -} diff --git a/examples/custom_backend/test/backend_api_test.dart b/examples/custom_backend/test/backend_api_test.dart index a50202e0e..fff590db3 100644 --- a/examples/custom_backend/test/backend_api_test.dart +++ b/examples/custom_backend/test/backend_api_test.dart @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:custom_backend/backend.dart'; import 'package:custom_backend/main.dart'; -import 'package:custom_backend/protocol/protocol.dart'; import 'package:flutter/material.dart'; import 'package:flutter_genui/flutter_genui.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -18,22 +18,22 @@ void main() { if (savedResponse == null) { continue; } + // To update the saved responses, run the app, select "Request Gemini", - // and copy the console output of the "Response body" to the + // and copy the console output of the "full-response" to the // corresponding `saved-response-X.json` file in `assets/data/`. test( 'sendRequest works for $savedResponse', () async { - final protocol = Protocol(); + final protocol = Backend(uiSchema); final result = await protocol.sendRequest( requestText, savedResponse: savedResponse, ); expect(result, isNotNull); - expect(result, isA>()); - expect(result!.length, 2); - expect(result[0], isA()); - expect(result[1], isA()); + expect(result!.messages.length, 2); + expect(result.messages[0], isA()); + expect(result.messages[1], isA()); }, retry: 3, timeout: const Timeout(Duration(minutes: 2)), diff --git a/packages/flutter_genui/lib/flutter_genui.dart b/packages/flutter_genui/lib/flutter_genui.dart index 15fccc9d2..8f49f7981 100644 --- a/packages/flutter_genui/lib/flutter_genui.dart +++ b/packages/flutter_genui/lib/flutter_genui.dart @@ -12,6 +12,8 @@ export 'src/core/prompt_fragments.dart'; export 'src/core/widget_utilities.dart'; export 'src/core/widgets/chat_primitives.dart'; export 'src/development_utilities/catalog_view.dart'; +export 'src/facade/direct_call_integration/model.dart'; +export 'src/facade/direct_call_integration/utils.dart'; export 'src/model/a2ui_message.dart'; export 'src/model/a2ui_schemas.dart'; export 'src/model/catalog.dart'; diff --git a/packages/flutter_genui/lib/src/facade/direct_call_integration/README.md b/packages/flutter_genui/lib/src/facade/direct_call_integration/README.md new file mode 100644 index 000000000..054d41627 --- /dev/null +++ b/packages/flutter_genui/lib/src/facade/direct_call_integration/README.md @@ -0,0 +1 @@ +Code that helps to use this library in direct call to REST Gemini API. diff --git a/examples/custom_backend/lib/backend/model.dart b/packages/flutter_genui/lib/src/facade/direct_call_integration/model.dart similarity index 50% rename from examples/custom_backend/lib/backend/model.dart rename to packages/flutter_genui/lib/src/facade/direct_call_integration/model.dart index ffca0ed06..7cd58a8f2 100644 --- a/examples/custom_backend/lib/backend/model.dart +++ b/packages/flutter_genui/lib/src/facade/direct_call_integration/model.dart @@ -2,19 +2,55 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -class FunctionDeclaration { +import '../../model/a2ui_message.dart'; + +sealed class Part { + const Part(); + + factory Part.fromJson(Map json) { + switch (json['type'] as String) { + case 'ToolCall': + return ToolCall.fromJson(json); + + default: + throw ArgumentError('Invalid Part type: ${json["type"]}'); + } + } + + Map toJson(); +} + +class ToolCall extends Part { + final dynamic args; + final String name; + + const ToolCall({required this.args, required this.name}); + + factory ToolCall.fromJson(Map json) => + ToolCall(args: json['args'], name: json['name'] as String); + + @override + Map toJson() => { + 'type': 'ToolCall', + 'args': args, + 'name': name, + }; +} + +/// Declaration to be provided to the LLM about a function/tool. +class GenUiFunctionDeclaration { final String description; final String name; final dynamic parameters; - FunctionDeclaration({ + GenUiFunctionDeclaration({ required this.description, required this.name, this.parameters, }); - factory FunctionDeclaration.fromJson(Map json) => - FunctionDeclaration( + factory GenUiFunctionDeclaration.fromJson(Map json) => + GenUiFunctionDeclaration( description: json['description'] as String, name: json['name'] as String, parameters: json['parameters'], @@ -27,34 +63,9 @@ class FunctionDeclaration { }; } -class UiSchemaDefinition { - final String prompt; - final List tools; - - const UiSchemaDefinition({required this.prompt, required this.tools}); - - factory UiSchemaDefinition.fromJson(Map json) => - UiSchemaDefinition( - prompt: json['prompt'] as String, - tools: (json['tools'] as List) - .map((x) => FunctionDeclaration.fromJson(x as Map)) - .toList(), - ); - - Map toJson() => { - 'prompt': prompt, - 'tools': List.from(tools.map((x) => x.toJson())), - }; -} - -class ToolCall { - final dynamic args; - final String name; - - const ToolCall({required this.args, required this.name}); - - factory ToolCall.fromJson(Map json) => - ToolCall(args: json['args'], name: json['name'] as String); +class ParsedToolCall { + final List messages; + final String surfaceId; - Map toJson() => {'args': args, 'name': name}; + ParsedToolCall({required this.messages, required this.surfaceId}); } diff --git a/packages/flutter_genui/lib/src/facade/direct_call_integration/utils.dart b/packages/flutter_genui/lib/src/facade/direct_call_integration/utils.dart new file mode 100644 index 000000000..092578c51 --- /dev/null +++ b/packages/flutter_genui/lib/src/facade/direct_call_integration/utils.dart @@ -0,0 +1,76 @@ +// Copyright 2025 The Flutter Authors. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import '../../model/a2ui_message.dart'; +import '../../model/a2ui_schemas.dart'; +import '../../model/catalog.dart'; +import '../../model/tools.dart'; +import '../../primitives/simple_items.dart'; +import 'model.dart'; + +/// Prompt to be provided to the LLM about how to use the UI generation tools. +String genUiTechPrompt(List toolNames) { + final toolDescription = toolNames.length > 1 + ? 'the following UI generation tools: ' + '${toolNames.map((name) => '"$name"').join(', ')}' + : 'the UI generation tool "${toolNames.first}"'; + + return ''' +To show generated UI to user, use $toolDescription. +When generating UI, always provide a unique $surfaceIdKey to identify the UI surface: + +* To create new UI, use a new $surfaceIdKey. +* To update existing UI, use the existing $surfaceIdKey. + +Use the root component ID: 'root'. +If you want to show new UI, use a new $surfaceIdKey. +If you want to update existing UI, use the existing $surfaceIdKey. +Ensure one of the generated components has an id of 'root'. +'''; +} + +GenUiFunctionDeclaration catalogToFunctionDeclaration( + Catalog catalog, + String toolName, + String toolDescription, +) { + return GenUiFunctionDeclaration( + description: toolDescription, + name: toolName, + parameters: A2uiSchemas.surfaceUpdateSchema(catalog), + ); +} + +Future parseToolCall(ToolCall toolCall, String toolName) async { + assert(toolCall.name == toolName); + + final messageJson = {'surfaceUpdate': toolCall.args}; + final surfaceUpdateMessage = A2uiMessage.fromJson(messageJson); + + final surfaceId = (toolCall.args as JsonMap)[surfaceIdKey] as String; + + final beginRenderingMessage = BeginRendering( + surfaceId: surfaceId, + root: 'root', + ); + + return ParsedToolCall( + messages: [surfaceUpdateMessage, beginRenderingMessage], + surfaceId: surfaceId, + ); +} + +ToolCall catalogExampleToToolCall( + JsonMap example, + String toolName, + String surfaceId, +) { + final messageJson = {'surfaceUpdate': example}; + final surfaceUpdateMessage = A2uiMessage.fromJson(messageJson); + + return ToolCall( + name: toolName, + args: {surfaceIdKey: surfaceId, 'surfaceUpdate': surfaceUpdateMessage}, + ); +}