diff --git a/package-lock.json b/package-lock.json index ba6deb94082..71256cfc60d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1405,6 +1405,18 @@ "sisteransi": "^1.0.5" } }, + "node_modules/@clack/prompts/node_modules/is-unicode-supported": { + "version": "1.3.0", + "extraneous": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@codemirror/autocomplete": { "version": "6.16.0", "license": "MIT", diff --git a/packages/breadboard-web/public/graphs/hacker_news_algolia_search.json b/packages/breadboard-web/public/graphs/hacker_news_algolia_search.json new file mode 100644 index 00000000000..4d00b5ad6ba --- /dev/null +++ b/packages/breadboard-web/public/graphs/hacker_news_algolia_search.json @@ -0,0 +1,152 @@ +{ + "title": "Hacker News Angolia search ", + "description": "Board which returns API results based on a query using the Hacker News Angolia API", + "version": "0.0.1", + "edges": [ + { + "from": "urlTemplate", + "to": "fetch", + "out": "url", + "in": "url" + }, + { + "from": "urlTemplate", + "to": "output-2", + "out": "url", + "in": "url" + }, + { + "from": "fn-3", + "to": "output-2", + "out": "output", + "in": "output" + }, + { + "from": "query", + "to": "urlTemplate", + "out": "query", + "in": "query" + }, + { + "from": "query", + "to": "urlTemplate", + "out": "page", + "in": "page" + }, + { + "from": "query", + "to": "urlTemplate", + "out": "tags", + "in": "tags" + }, + { + "from": "query", + "to": "fn-3", + "out": "limit", + "in": "limit" + }, + { + "from": "fetch", + "to": "spreadResponse", + "out": "response", + "in": "object" + }, + { + "from": "spreadResponse", + "to": "fn-3", + "out": "hits", + "in": "list" + } + ], + "nodes": [ + { + "id": "output-2", + "type": "output", + "configuration": { + "schema": { + "type": "object", + "properties": { + "url": { + "title": "url", + "description": "The resulting URL that was produced by filling in the placeholders in the template", + "type": "string" + }, + "output": { + "type": "string", + "title": "output" + } + } + } + } + }, + { + "id": "urlTemplate", + "type": "urlTemplate", + "configuration": { + "template": "https://hn.algolia.com/api/v1/search?query={query}&tags={tags}&page={page}" + } + }, + { + "id": "fn-3", + "type": "runJavascript", + "configuration": { + "code": "const fn_3 = ({list,limit})=>{return{output:list.slice(0,limit)}};", + "name": "fn_3", + "raw": true + } + }, + { + "id": "query", + "type": "input", + "configuration": { + "schema": { + "title": "Algolia Limit", + "properties": { + "query": { + "type": "string", + "title": "query", + "default": "JavaScript", + "description": "The query" + }, + "limit": { + "type": "number", + "title": "limit", + "default": "1", + "description": "The limit" + }, + "tags": { + "type": "string", + "title": "tags", + "default": "JavaScript", + "description": "The tags" + }, + "page": { + "type": "string", + "title": "page", + "default": "2", + "description": "the page" + } + } + }, + "type": "string" + } + }, + { + "id": "fetch", + "type": "fetch", + "configuration": { + "method": "GET" + } + }, + { + "id": "spreadResponse", + "type": "runJavascript", + "configuration": { + "code": "const spreadResponse = inputs=>{const object=inputs.object;if(typeof object!==\"object\"){throw new Error(`object is of type ${typeof object} not object`)}return{...object}};", + "name": "spreadResponse", + "raw": true + } + } + ], + "graphs": {} +} \ No newline at end of file diff --git a/packages/breadboard-web/public/graphs/hacker_news_algolia_story_from_id.json b/packages/breadboard-web/public/graphs/hacker_news_algolia_story_from_id.json new file mode 100644 index 00000000000..67e6811facd --- /dev/null +++ b/packages/breadboard-web/public/graphs/hacker_news_algolia_story_from_id.json @@ -0,0 +1,100 @@ +{ + "title": "Hacker News Algolia API Story by ID", + "description": "Board which returns story contents using the Hacker News Algolia API", + "version": "0.0.1", + "edges": [ + { + "from": "main", + "to": "output-2", + "out": "output", + "in": "output" + }, + { + "from": "spreadResponse", + "to": "main", + "out": "*", + "in": "" + }, + { + "from": "fetch", + "to": "spreadResponse", + "out": "response", + "in": "object" + }, + { + "from": "urlTemplate", + "to": "fetch", + "out": "url", + "in": "url" + }, + { + "from": "storyID", + "to": "urlTemplate", + "out": "storyID", + "in": "storyID" + } + ], + "nodes": [ + { + "id": "output-2", + "type": "output", + "configuration": { + "schema": { + "type": "object", + "properties": { + "output": { + "type": "string", + "title": "output" + } + } + } + } + }, + { + "id": "main", + "type": "output", + "configuration": {} + }, + { + "id": "spreadResponse", + "type": "runJavascript", + "configuration": { + "code": "const spreadResponse = inputs=>{const object=inputs.object;if(typeof object!==\"object\"){throw new Error(`object is of type ${typeof object} not object`)}return{...object}};", + "name": "spreadResponse", + "raw": true + } + }, + { + "id": "fetch", + "type": "fetch", + "configuration": { + "method": "GET" + } + }, + { + "id": "urlTemplate", + "type": "urlTemplate", + "configuration": { + "template": "https://hn.algolia.com/api/v1/items/{storyID}" + } + }, + { + "id": "storyID", + "type": "input", + "configuration": { + "schema": { + "title": "Hacker News Story", + "properties": { + "storyID": { + "type": "string", + "title": "Story ID", + "default": "39788322", + "description": "Hacker News Story ID to extract" + } + } + } + } + } + ], + "graphs": {} +} \ No newline at end of file diff --git a/packages/breadboard-web/public/graphs/hacker_news_firebase_story_from_id.json b/packages/breadboard-web/public/graphs/hacker_news_firebase_story_from_id.json new file mode 100644 index 00000000000..387373642a6 --- /dev/null +++ b/packages/breadboard-web/public/graphs/hacker_news_firebase_story_from_id.json @@ -0,0 +1,101 @@ +{ + "title": "Hacker News Firebase API Story by ID", + "description": "Board which returns story contents using the Hacker News Firebase API", + "version": "0.0.1", + "edges": [ + { + "from": "main", + "to": "output-2", + "out": "output", + "in": "output" + }, + { + "from": "spreadResponse", + "to": "main", + "out": "*", + "in": "" + }, + { + "from": "fetch", + "to": "spreadResponse", + "out": "response", + "in": "object" + }, + { + "from": "urlTemplate", + "to": "fetch", + "out": "url", + "in": "url" + }, + { + "from": "storyID", + "to": "urlTemplate", + "out": "storyID", + "in": "storyID" + } + ], + "nodes": [ + { + "id": "output-2", + "type": "output", + "configuration": { + "schema": { + "type": "object", + "properties": { + "output": { + "type": "string", + "title": "output" + } + } + } + } + }, + { + "id": "main", + "type": "output", + "configuration": {} + }, + { + "id": "spreadResponse", + "type": "runJavascript", + "configuration": { + "code": "const spreadResponse = inputs=>{const object=inputs.object;if(typeof object!==\"object\"){throw new Error(`object is of type ${typeof object} not object`)}return{...object}};", + "name": "spreadResponse", + "raw": true + } + }, + { + "id": "fetch", + "type": "fetch", + "configuration": { + "method": "GET" + } + }, + { + "id": "urlTemplate", + "type": "urlTemplate", + "configuration": { + "template": "https://hacker-news.firebaseio.com/v0/item/{storyID}.json" + } + }, + { + "id": "storyID", + "type": "input", + "configuration": { + "schema": { + "title": "Hacker News Story ID", + "properties": { + "storyID": { + "type": "number", + "title": "Story ID", + "default": "39788322", + "description": "Hacker News Story ID to extract" + } + } + }, + "type": "number" + } + } + ], + "graphs": {} +} \ No newline at end of file diff --git a/packages/breadboard-web/public/graphs/hacker_news_firebase_top_story_ids.json b/packages/breadboard-web/public/graphs/hacker_news_firebase_top_story_ids.json new file mode 100644 index 00000000000..ea9451c3567 --- /dev/null +++ b/packages/breadboard-web/public/graphs/hacker_news_firebase_top_story_ids.json @@ -0,0 +1,89 @@ +{ + "title": "Hacker News Firebase API Story IDs", + "description": "Board which returns the top story ID using the Hacker News Firebase API", + "version": "0.0.1", + "edges": [ + { + "from": "main", + "to": "output-2", + "out": "output", + "in": "output" + }, + { + "from": "fn-3", + "to": "main", + "out": "*", + "in": "" + }, + { + "from": "fetch", + "to": "fn-3", + "out": "response", + "in": "list" + }, + { + "from": "limit", + "to": "fn-3", + "out": "limit", + "in": "limit" + } + ], + "nodes": [ + { + "id": "output-2", + "type": "output", + "configuration": { + "schema": { + "type": "object", + "properties": { + "output": { + "type": "string", + "title": "output" + } + } + } + } + }, + { + "id": "main", + "type": "output", + "configuration": {} + }, + { + "id": "fn-3", + "type": "runJavascript", + "configuration": { + "code": "const fn_3 = ({list,limit})=>{return{output:list.slice(0,limit)}};", + "name": "fn_3", + "raw": true + } + }, + { + "id": "fetch", + "type": "fetch", + "configuration": { + "method": "GET", + "url": "https://hacker-news.firebaseio.com/v0/topstories.json" + } + }, + { + "id": "limit", + "type": "input", + "configuration": { + "schema": { + "title": "Hacker News Story", + "properties": { + "limit": { + "type": "number", + "title": "Story Limit", + "default": "1", + "description": "The Number of Hacker News Story ID's to return" + } + } + }, + "type": "number" + } + } + ], + "graphs": {} +} \ No newline at end of file diff --git a/packages/breadboard-web/public/local-boards.json b/packages/breadboard-web/public/local-boards.json index 9e370eb7cca..bf8bf4ad91b 100644 --- a/packages/breadboard-web/public/local-boards.json +++ b/packages/breadboard-web/public/local-boards.json @@ -98,6 +98,26 @@ "url": "/graphs/gemini-simple.json", "version": "0.0.1" }, + { + "title": "Hacker News Angolia search ", + "url": "/graphs/hacker_news_algolia_search.json", + "version": "0.0.1" + }, + { + "title": "Hacker News Algolia API Story by ID", + "url": "/graphs/hacker_news_algolia_story_from_id.json", + "version": "0.0.1" + }, + { + "title": "Hacker News Firebase API Story by ID", + "url": "/graphs/hacker_news_firebase_story_from_id.json", + "version": "0.0.1" + }, + { + "title": "Hacker News Firebase API Story IDs", + "url": "/graphs/hacker_news_firebase_top_story_ids.json", + "version": "0.0.1" + }, { "title": "Healer", "url": "/graphs/healer.json", diff --git a/packages/breadboard-web/src/boards/hacker_news_algolia_search.ts b/packages/breadboard-web/src/boards/hacker_news_algolia_search.ts new file mode 100644 index 00000000000..b420458a0f0 --- /dev/null +++ b/packages/breadboard-web/src/boards/hacker_news_algolia_search.ts @@ -0,0 +1,145 @@ +import { board, base, code, OutputValues } from "@google-labs/breadboard"; + +import { core } from "@google-labs/core-kit"; +import { templates } from "@google-labs/template-kit"; + +export type PostItem = { + author: string; + created_at: string; + created_at_i: number; + id: number; + children: Comment[]; + story_id: number; + type: string; +}; + +export type Comment = PostItem & { + parent_id: number; + text: string; + title: null; +}; + +export type Story = PostItem & { + title: string; + points: number; + url: string; +}; + +export type HackerNewsAlgoliaSearchTags = + | "story" + | "comment" + | "poll" + | "pollopt" + | "show_hn" + | "ask_hn" + | "front_page"; +export type NumericFilterField = "created_at_i" | "points" | "num_comments"; +export type Operator = "<" | "<=" | "=" | ">" | ">="; + +export type HackerNewsSearchNumericFilters = { + operator: Operator; + field: NumericFilterField; + value: number; +}; +export type HackerNewAlgoliaSearchParameters = { + query: string; + tags?: HackerNewsAlgoliaSearchTags[]; + numericFilters?: HackerNewsSearchNumericFilters[]; + page?: number; + limit?: number; +}; + +export type SearchHits = OutputValues & { + hits: PostItem[]; +}; + +const spread = code<{ object: object }>((inputs) => { + const object = inputs.object; + if (typeof object !== "object") { + throw new Error(`object is of type ${typeof object} not object`); + } + return { ...object }; +}); + + +const slice = code<{ list: PostItem[], limit: number }>(({ list, limit }) => { + return { output: list.slice(0, limit) } +}) + +const limitInputSchema = { + type: "number", + title: "limit", + default: "1", + description: "The limit" +} + +const querySchema = { + type: "string", + title: "query", + default: "JavaScript", + description: "The query" +} + +const tagsSchema = { + type: "string", + title: "tags", + default: "JavaScript", + description: "The tags" +} + +const pageSchema = { + type: "string", + title: "page", + default: "2", + description: "the page" +} + +export default await board(() => { + const input = base.input({ + $id: "query", + schema: { + title: "Algolia Limit", + properties: { + query: querySchema, + limit: limitInputSchema, + tags: tagsSchema, + page: pageSchema, + }, + }, + type: "string", + }) + + let baseURL = "https://hn.algolia.com/api/v1/search?query={query}" + + if (input.tags != undefined) { + baseURL = baseURL + "&tags={tags}" + } + + if (input.page != undefined) { + baseURL = baseURL + "&page={page}" + } + + const urlTemplate = templates.urlTemplate({ + $id: "urlTemplate", + template: baseURL, + query:input.query, + page:input.page, + tags:input.tags, + + }); + + const fetchUrl = core.fetch({ $id: "fetch", method: "GET", url: urlTemplate.url }); + const response = spread({ $id: "spreadResponse", object: fetchUrl.response }); + + const sliced = slice({ list: response.hits as unknown as PostItem[], limit: input.limit as unknown as number }) + + return { + url: urlTemplate.url, + output: sliced + } + +}).serialize({ + title: "Hacker News Angolia search ", + description: "Board which returns API results based on a query using the Hacker News Angolia API", + version: "0.0.1", +}) diff --git a/packages/breadboard-web/src/boards/hacker_news_algolia_story_from_id.ts b/packages/breadboard-web/src/boards/hacker_news_algolia_story_from_id.ts new file mode 100644 index 00000000000..26a1a0c018b --- /dev/null +++ b/packages/breadboard-web/src/boards/hacker_news_algolia_story_from_id.ts @@ -0,0 +1,50 @@ +import { board, base, code} from "@google-labs/breadboard"; + +import { core } from "@google-labs/core-kit"; +import { templates } from "@google-labs/template-kit"; + +const storyInputSchema = { + type: "string", + title: "Story ID", + default: "39788322", + description: "Hacker News Story ID to extract", +}; + +const spread = code<{ object: object }>((inputs) => { + const object = inputs.object; + if (typeof object !== "object") { + throw new Error(`object is of type ${typeof object} not object`); + } + return { ...object }; +}); + +export default await board(() => { + const input = base.input({ + $id: "storyID", + schema: { + title: "Hacker News Story", + properties: { + storyID: storyInputSchema + }, + } + }) + + const urlTemplate = templates.urlTemplate({ + $id: "urlTemplate", + template: "https://hn.algolia.com/api/v1/items/{storyID}", + storyID: input.storyID + }); + + const fetchUrl = core.fetch({ $id: "fetch", method: "GET", url: urlTemplate.url}); + const output = base.output({ $id: "main" }); + const response = spread({ $id: "spreadResponse", object: fetchUrl.response }); + + response.to(output) + + return {output} + +}).serialize({ + title: "Hacker News Algolia API Story by ID", + description: "Board which returns story contents using the Hacker News Algolia API", + version: "0.0.1", +}) diff --git a/packages/breadboard-web/src/boards/hacker_news_firebase_story_from_id.ts b/packages/breadboard-web/src/boards/hacker_news_firebase_story_from_id.ts new file mode 100644 index 00000000000..dcf15d6f729 --- /dev/null +++ b/packages/breadboard-web/src/boards/hacker_news_firebase_story_from_id.ts @@ -0,0 +1,51 @@ +import { board, base , code} from "@google-labs/breadboard"; + +import { core } from "@google-labs/core-kit"; +import { templates } from "@google-labs/template-kit"; + +const storyInputSchema = { + type: "number", + title: "Story ID", + default: "39788322", + description: "Hacker News Story ID to extract", +}; + +const spread = code<{ object: object }>((inputs) => { + const object = inputs.object; + if (typeof object !== "object") { + throw new Error(`object is of type ${typeof object} not object`); + } + return { ...object }; +}); + +export default await board(() => { + const input = base.input({ + $id: "storyID", + schema: { + title: "Hacker News Story ID", + properties: { + storyID: storyInputSchema, + }, + }, + type: "number", + }) + + const urlTemplate = templates.urlTemplate({ + $id: "urlTemplate", + template: "https://hacker-news.firebaseio.com/v0/item/{storyID}.json", + storyID: input.storyID + }); + + const fetchUrl = core.fetch({ $id: "fetch", method: "GET", url: urlTemplate.url}); + const output = base.output({ $id: "main" }); + const response = spread({ $id: "spreadResponse", object: fetchUrl.response }); + + response.to(output) + + return {output} + +}).serialize({ + title: "Hacker News Firebase API Story by ID", + description: "Board which returns story contents using the Hacker News Firebase API", + version: "0.0.1" +}) diff --git a/packages/breadboard-web/src/boards/hacker_news_firebase_top_story_ids.ts b/packages/breadboard-web/src/boards/hacker_news_firebase_top_story_ids.ts new file mode 100644 index 00000000000..b002645f9d2 --- /dev/null +++ b/packages/breadboard-web/src/boards/hacker_news_firebase_top_story_ids.ts @@ -0,0 +1,37 @@ +import { board, base, code } from "@google-labs/breadboard"; +import { core } from "@google-labs/core-kit"; + +const limitInputSchema = { + type: "number", + title: "Story Limit", + default: "1", + description: "The Number of Hacker News Story ID's to return" +} + +const slice = code<{ list: number[], limit: number }>(({ list, limit }) => { + return { output: list.slice(0, limit) } +}) + +export default await board(() => { + const input = base.input({ + $id: "limit", + schema: { + title: "Hacker News Story", + properties: { + limit: limitInputSchema + }, + }, + type: "number", + }) + + const { response } = core.fetch({ $id: "fetch", method: "GET", url: "https://hacker-news.firebaseio.com/v0/topstories.json" }); + const output = base.output({ $id: "main" }); + const sliced = slice({ list: response as unknown as number[], limit: input.limit as unknown as number }) + + sliced.to(output) + + return { output } +}).serialize({ + title: "Hacker News Firebase API Story IDs", + description: "Board which returns the top story ID using the Hacker News Firebase API", + version: "0.0.1",}) \ No newline at end of file