diff --git a/src/model/.kbot/params.json b/src/model/.kbot/params.json index 4e39d2f..8cd6687 100644 --- a/src/model/.kbot/params.json +++ b/src/model/.kbot/params.json @@ -1,5 +1,5 @@ { - "model": "anthropic/claude-3.7-sonnet", + "model": "anthropic/claude-3.7-sonnet:thinking", "messages": [ { "role": "user", @@ -7,121 +7,18 @@ }, { "role": "user", - "content": "USER Preferences : ## Todos\r\n\r\n- for Astro, tailwind\r\n- no react or additional dependencies\r\n- IHowto: import { IHowto } from \"@/model/howto.js\"\r\n- For text, use {text} (import { i18n as Translate } from \"@polymech/astro-base\")\r\n- data is provided via `import { getCollection } from 'astro:content'\r\nconst items = await getCollection('howtos')`\r\n\r\n### Example Tailwind Sidebar\r\n\r\n\r\n \r\n \r\n\r\n## Todos\r\n\r\n- [ ] create minimal Astro component, a sidebar using flowbit (see example) for the given interface (\"IHowto\") and sample data ../components/libary/howto.astro, use the 'category' field, save as ../components/howtos/sidebar2.astro\r\n" + "content": "USER Preferences : ## Todos\r\n\r\n- we use Typescript, ESM, minimal dependencies, functional programming\r\n- dont document or comment, return just the code, ready to go\r\n\r\n## Todos\r\n\r\n- [ ] function, to convert a howto (howto-model.ts) to json-ld structured data, satisfying Google, \"toJSON-LD\"\r\n" }, { "role": "user", - "path": "howto.ts", - "content": "import * as path from 'path'\r\nimport { findUp } from 'find-up'\r\nimport { sync as read } from '@polymech/fs/read'\r\nimport { sync as exists } from '@polymech/fs/exists'\r\nimport { filesEx, forward_slash, resolveConfig, resolve } from '@polymech/commons'\r\nimport { ICADNodeSchema, IComponentConfig } from '@polymech/commons/component'\r\nimport { RenderedContent, DataEntry } from \"astro:content\"\r\nimport type { Loader, LoaderContext } from 'astro/loaders'\r\nimport { get } from '@polymech/commons/component'\r\n\r\nimport {\r\n CAD_MAIN_MATCH, PRODUCT_BRANCHES,\r\n CAD_EXTENSIONS, CAD_MODEL_EXT, PRODUCT_DIR, PRODUCT_GLOB,\r\n PRODUCT_ROOT, CAD_EXPORT_CONFIGURATIONS,\r\n CAD_DEFAULT_CONFIGURATION,\r\n CAD_URL,\r\n parseBoolean,\r\n DEFAULT_CONTACT,\r\n default_image,\r\n HOWTO_ROOT,\r\n HOWTO_GLOB\r\n} from 'config/config.js'\r\n\r\nimport { env } from '@/base/index.js'\r\nimport { gallery } from '@/base/media.js';\r\n\r\nimport { PFilterValid } from '@polymech/commons/filter'\r\n\r\nimport { IAssemblyData } from '@polymech/cad'\r\nimport { logger as log } from '@/base/index.js'\r\nimport { translate } from \"@/base/i18n.js\"\r\nimport { I18N_SOURCE_LANGUAGE } from \"config/config.js\"\r\nimport { slugify } from \"@/base/strings.js\"\r\nimport { HOWTO_MIGRATION } from '@/app/config.js'\r\n\r\nexport const ITEM_TYPE = 'howto'\r\n\r\n//export const load = () => get(`${HOWTO_ROOT()}/${HOWTO_GLOB}`, HOWTO_ROOT(), ITEM_TYPE)\r\n\r\nexport const howtos = async () => {\r\n const src = HOWTO_MIGRATION()\r\n //const kb_out = path.resolve(substitute(\"${KB_ROOT}\", DEFAULT_ROOTS));\r\n const data = read(src, 'json') as any;\r\n let howtos = data.v3_howtos as any[]\r\n howtos = howtos.filter((h) => h.moderation == 'accepted');\r\n const tags = data.v3_tags;\r\n\r\n let output = {\r\n tags: {},\r\n howtows: {}\r\n }\r\n howtos.forEach((howto) => {\r\n const howtoTags: any = [];\r\n for (const ht in howto.tags) {\r\n const gt: any = tags.find((t) => t._id === ht) || { label: 'untagged' };\r\n if (gt) {\r\n howtoTags.push(gt.label || \"\");\r\n if (!output.tags[gt.label]) {\r\n output.tags[gt.label] = []\r\n }\r\n output.tags[gt.label].push(howto.slug.trim());\r\n }\r\n }\r\n\r\n howto.user = data.v3_mappins.find((u) => u._id == howto._createdBy);\r\n howto.tags = howtoTags;\r\n /*\r\n howto.image = `/howtos/${howto.slug}/${encodeURIComponent(sanitize(howto.cover_image.name))}`,\r\n howto.cover_image.name = sanitize(howto.cover_image.name);\r\n */\r\n //output.howtows[howto.slug.trim()] = howto;\r\n\r\n });\r\n return howtos\r\n}\r\n\r\nexport const defaults = async (data: any, cwd: string, root: string) => {\r\n let defaultsJSON = await findUp('defaults.json', {\r\n stopAt: root,\r\n cwd: cwd\r\n });\r\n try {\r\n if (defaultsJSON) {\r\n data = {\r\n ...read(defaultsJSON, 'json') as any,\r\n ...data,\r\n };\r\n }\r\n } catch (error) {\r\n\r\n }\r\n return data;\r\n};\r\n\r\n\r\nconst onItem = async (item: any, ctx: LoaderContext) => {\r\n return item\r\n\r\n if (!item || !item.data) {\r\n ctx.logger.error(`Error completing ${''}: no data`);\r\n return\r\n }\r\n let data: any = item.data\r\n const itemRel = data.rel\r\n const itemRelMin = itemRel.replace('products/', '')\r\n const itemDir = PRODUCT_DIR(itemRel)\r\n const default_profile = env(itemRel)\r\n data.product_rel = itemRelMin\r\n data.assets = {\r\n renderings: [],\r\n gallery: []\r\n }\r\n data.body = (read(path.join(itemDir, 'templates/shared', 'body.md')) as string) || \"\"\r\n data.resources = (read(path.join(itemDir, 'templates/shared', 'resources.md')) as string) || \"\"\r\n //////////////////////////////////////////\r\n //\r\n // Item Shared Resources\r\n //\r\n let resourcesDefaultPath = await findUp('resources.md', {\r\n stopAt: PRODUCT_ROOT(),\r\n cwd: itemDir\r\n }) || \"\"\r\n data.shared = (read(resourcesDefaultPath) as string) || \"\"\r\n //////////////////////////////////////////\r\n //\r\n // Readme\r\n //\r\n let readmePath = path.join(itemDir, 'Readme.md')\r\n data.readme = (read(readmePath) as string) || \"\"\r\n //////////////////////////////////////////\r\n //\r\n // Variables\r\n //\r\n data = await defaults(data, itemDir, PRODUCT_ROOT());\r\n data = {\r\n ...data,\r\n ...default_profile.variables,\r\n product_rel_min: itemRelMin,\r\n }\r\n // data = resolveConfig(data as Record) as IComponentConfigEx\r\n item.data = data\r\n //////////////////////////////////////////\r\n //\r\n // Extensions, CAD, Media, etcetera etcetera :)\r\n //\r\n //data.cad = await cad(item)\r\n data.assets.gallery = await gallery('media/gallery', data.rel)\r\n}\r\n\r\nexport function loader(): Loader {\r\n\r\n const load = async ({\r\n config,\r\n logger,\r\n watcher,\r\n parseData,\r\n store,\r\n generateDigest }: LoaderContext) => {\r\n\r\n store.clear()\r\n let items = await howtos()\r\n for (const item of items) {\r\n const id = item.slug\r\n const data = {\r\n slug: item.slug,\r\n id,\r\n title: item.title,\r\n type: ITEM_TYPE,\r\n components: [],\r\n item\r\n }\r\n //const parsedData = await parseData({ id, data: data }); \r\n const storeItem = {\r\n digest: await generateDigest(data),\r\n filePath: id,\r\n id: `${item.slug}`,\r\n data: data\r\n }\r\n await onItem(storeItem, {\r\n logger,\r\n watcher,\r\n parseData,\r\n store,\r\n generateDigest\r\n } as any)\r\n\r\n storeItem.data['config'] = JSON.stringify(storeItem.data, null, 2)\r\n store.set(storeItem)\r\n }\r\n }\r\n return {\r\n name: `astro:store:${ITEM_TYPE}`,\r\n load\r\n };\r\n}\r\n\r\n\r\nexport interface IHowto {\r\n _createdBy: string\r\n mentions: any[]\r\n _deleted: boolean\r\n fileLink: string\r\n slug: string\r\n _modified: string\r\n previousSlugs: string[]\r\n _created: string\r\n description: string\r\n votedUsefulBy: string[]\r\n creatorCountry: string\r\n total_downloads: number\r\n title: string\r\n time: string\r\n files: any[]\r\n difficulty_level: string\r\n _id: string\r\n tags?: Tags\r\n total_views: number\r\n _contentModifiedTimestamp: string\r\n cover_image: CoverImage\r\n comments: any[]\r\n moderatorFeedback: string\r\n steps: Step[]\r\n moderation: string\r\n}\r\n\r\nexport interface Tags {\r\n [key: string]: boolean\r\n}\r\n\r\nexport interface CoverImage {\r\n name: string\r\n downloadUrl: string\r\n type: string\r\n fullPath: string\r\n updated: string\r\n size: number\r\n timeCreated: string\r\n contentType: string\r\n}\r\n\r\nexport interface Step {\r\n title: string\r\n text: string\r\n images: Image[]\r\n _animationKey: string\r\n}\r\nexport interface Image {\r\n updated: string\r\n size: number\r\n fullPath: string\r\n timeCreated: string\r\n name: string\r\n downloadUrl: string\r\n contentType: string\r\n type: string\r\n}\r\n\r\n" + "path": "howto-model.ts", + "content": "export const ITEM_TYPE = 'howto'\r\n////////////////////////////////\r\n//\r\n// Interfaces - Old\r\nexport interface IHowto {\r\n _createdBy: string\r\n mentions: any[]\r\n _deleted: boolean\r\n fileLink: string\r\n slug: string\r\n _modified: string\r\n previousSlugs: string[]\r\n _created: string\r\n description: string\r\n votedUsefulBy: string[]\r\n creatorCountry: string\r\n total_downloads: number\r\n title: string\r\n time: string\r\n files: any[]\r\n category: IOACategory\r\n difficulty_level: string\r\n _id: string\r\n tags?: IOATag[]\r\n total_views: number\r\n _contentModifiedTimestamp: string\r\n cover_image: Image\r\n comments: any[]\r\n moderatorFeedback: string\r\n steps: Step[]\r\n moderation: string\r\n user?: User\r\n}\r\n\r\nexport interface Tags {\r\n [key: string]: boolean\r\n}\r\n\r\nexport interface CoverImage {\r\n name: string\r\n downloadUrl: string\r\n type: string\r\n fullPath: string\r\n updated: string\r\n size: number\r\n timeCreated: string\r\n contentType: string\r\n}\r\n\r\nexport interface Step {\r\n title: string\r\n text: string\r\n images: Image[]\r\n _animationKey: string\r\n}\r\nexport interface Image {\r\n updated: string\r\n size: number\r\n fullPath: string\r\n timeCreated: string\r\n name: string\r\n downloadUrl: string\r\n contentType: string\r\n type: string\r\n src: string,\r\n alt: string\r\n}\r\n\r\n/// Taxonomy\r\nexport interface IOACategory {\r\n _created: string\r\n _id: string\r\n _deleted: boolean\r\n label: string\r\n _modified: string\r\n}\r\n\r\nexport interface IOATag {\r\n categories: string[]\r\n image: string\r\n _created: string\r\n _deleted: boolean\r\n label: string\r\n _createdBy: string\r\n _modified: string\r\n _id: string\r\n}\r\n\r\nexport interface User {\r\n _modified: string\r\n _id: string\r\n subType: string\r\n moderation: string\r\n _deleted: boolean\r\n verified: boolean\r\n type: string\r\n location: Location\r\n _created: string\r\n geo: Geo\r\n data: Data\r\n detail: Detail\r\n}\r\n\r\nexport interface Location {\r\n lat: number\r\n lng: number\r\n}\r\n\r\nexport interface Geo {\r\n latitude: number\r\n lookupSource: string\r\n longitude: number\r\n localityLanguageRequested: string\r\n continent: string\r\n continentCode: string\r\n countryName: string\r\n countryCode: string\r\n principalSubdivision: string\r\n principalSubdivisionCode: string\r\n city: string\r\n locality: string\r\n postcode: string\r\n plusCode: string\r\n localityInfo: LocalityInfo\r\n}\r\n\r\nexport interface LocalityInfo {\r\n administrative: Administrative[]\r\n informative: Informative[]\r\n}\r\n\r\nexport interface Administrative {\r\n name: string\r\n description: string\r\n isoName?: string\r\n order: number\r\n adminLevel: number\r\n isoCode?: string\r\n wikidataId: string\r\n geonameId: number\r\n}\r\n\r\nexport interface Informative {\r\n name: string\r\n description: string\r\n isoName?: string\r\n order: number\r\n isoCode?: string\r\n wikidataId?: string\r\n geonameId?: number\r\n}\r\n\r\nexport interface Data {\r\n urls: Url[]\r\n description: string\r\n services: Service[]\r\n title: string\r\n images: any[]\r\n}\r\n\r\nexport interface Url {\r\n name: string\r\n url: string\r\n}\r\n\r\nexport interface Service {\r\n welding: boolean\r\n assembling: boolean\r\n machining: boolean\r\n electronics: boolean\r\n molds: boolean\r\n}\r\n\r\nexport interface Detail {\r\n services: any[]\r\n urls: any[]\r\n}\r\n" }, { "role": "user", - "path": "howto.json", - "content": "{\r\n \"_createdBy\": \"gus-merckel\",\r\n \"mentions\": [],\r\n \"_deleted\": false,\r\n \"fileLink\": \"\",\r\n \"slug\": \"cut-out-shapes-out-of-plastic-sheets-with-a-cnc-\",\r\n \"_modified\": \"2023-10-27T18:09:36.519Z\",\r\n \"previousSlugs\": [\r\n \"cut-out-shapes-out-of-plastic-sheets-with-a-cnc-\"\r\n ],\r\n \"_created\": \"2023-08-23T18:20:09.098Z\",\r\n \"description\": \"In this how to, I will show you our process to cut HDPE Sheets using a X-Carve CNC.\\n\\nHere is the full video in spanish with subtitles https://www.youtube.com/watch?v=4LrrFz802To \",\r\n \"votedUsefulBy\": [\r\n \"sigolene\",\r\n \"mattia\",\r\n \"uillinoispreciousplastics\"\r\n ],\r\n \"creatorCountry\": \"mx\",\r\n \"total_downloads\": 0,\r\n \"title\": \"Cut out shapes out of plastic sheets with a CNC \",\r\n \"time\": \"< 5 hours\",\r\n \"files\": [],\r\n \"difficulty_level\": \"Medium\",\r\n \"_id\": \"038gjWgLjiyYknbEjDeI\",\r\n \"tags\": {\r\n \"RTCBJAFa05YBVVBy0KeO\": true\r\n },\r\n \"category\":\"machines\",\r\n \"total_views\": 232,\r\n \"_contentModifiedTimestamp\": \"2023-08-23T18:20:09.098Z\",\r\n \"cover_image\": {\r\n \"name\": \"IMG_20200605_142311.jpg\",\r\n \"downloadUrl\": \"https://firebasestorage.googleapis.com/v0/b/onearmyworld.appspot.com/o/uploads%2Fhowtos%2F038gjWgLjiyYknbEjDeI%2FIMG_20200605_142311.jpg?alt=media&token=c272c174-1adc-45af-967b-771adce7295d\",\r\n \"type\": \"image/jpeg\",\r\n \"fullPath\": \"uploads/howtos/038gjWgLjiyYknbEjDeI/IMG_20200605_142311.jpg\",\r\n \"updated\": \"2021-04-05T15:09:00.605Z\",\r\n \"size\": 124661,\r\n \"timeCreated\": \"2021-04-05T15:09:00.605Z\",\r\n \"contentType\": \"image/jpeg\"\r\n },\r\n \"comments\": [],\r\n \"moderatorFeedback\": \"\",\r\n \"steps\": [\r\n {\r\n \"title\": \"Measure the plastic sheet\",\r\n \"text\": \"For this step we need to measure our plastic sheet: Height, Width and Thickness. Our X-Carve machine works with the CAM Software EASEL, for me, the easiest software for CNC milling out there. \\n\\nThe cool thing about Easel (https://easel.inventables.com/) is that you can \\\"simulate\\\" your actual material and THEY EVEN HAVE HDPE 2-Colors in their cutting material lists!!\\n\\n\\n\",\r\n \"images\": [\r\n {\r\n \"fullPath\": \"uploads/howtos/pbo0Pe44aTngvlD04kGf/1.jpg\",\r\n \"name\": \"1.jpg\",\r\n \"size\": 74095,\r\n \"type\": \"image/jpeg\",\r\n \"timeCreated\": \"2021-03-26T19:42:05.766Z\",\r\n \"contentType\": \"image/jpeg\",\r\n \"downloadUrl\": \"https://firebasestorage.googleapis.com/v0/b/onearmyworld.appspot.com/o/uploads%2Fhowtos%2Fpbo0Pe44aTngvlD04kGf%2F1.jpg?alt=media&token=293d733d-05a5-494a-9340-47f4564f1939\",\r\n \"updated\": \"2021-03-26T19:42:05.766Z\"\r\n },\r\n {\r\n \"contentType\": \"image/jpeg\",\r\n \"timeCreated\": \"2021-03-26T19:42:05.669Z\",\r\n \"updated\": \"2021-03-26T19:42:05.669Z\",\r\n \"size\": 69665,\r\n \"downloadUrl\": \"https://firebasestorage.googleapis.com/v0/b/onearmyworld.appspot.com/o/uploads%2Fhowtos%2Fpbo0Pe44aTngvlD04kGf%2F2.jpg?alt=media&token=004f50f1-97ac-4df4-9ba9-f463aa4cbca3\",\r\n \"fullPath\": \"uploads/howtos/pbo0Pe44aTngvlD04kGf/2.jpg\",\r\n \"name\": \"2.jpg\",\r\n \"type\": \"image/jpeg\"\r\n }\r\n ],\r\n \"_animationKey\": \"unique1\"\r\n },\r\n {\r\n \"text\": \"Using the CNC clamps from the X-Carve, secure the sheet to the table, \",\r\n \"_animationKey\": \"unique2\",\r\n \"images\": [\r\n {\r\n \"updated\": \"2021-03-26T19:42:06.249Z\",\r\n \"size\": 55544,\r\n \"fullPath\": \"uploads/howtos/pbo0Pe44aTngvlD04kGf/3.jpg\",\r\n \"timeCreated\": \"2021-03-26T19:42:06.249Z\",\r\n \"name\": \"3.jpg\",\r\n \"downloadUrl\": \"https://firebasestorage.googleapis.com/v0/b/onearmyworld.appspot.com/o/uploads%2Fhowtos%2Fpbo0Pe44aTngvlD04kGf%2F3.jpg?alt=media&token=0b9c1914-1c75-429e-b34a-1e2b3706edef\",\r\n \"contentType\": \"image/jpeg\",\r\n \"type\": \"image/jpeg\"\r\n }\r\n ],\r\n \"title\": \"Secure sheet \"\r\n },\r\n {\r\n \"title\": \"Choosing a file to cut \",\r\n \"text\": \"Now we go to our illustrator, such as Inkscape to design a vector file or download and open source one frome https://thenounproject.com/.\\n\\nWe download the SVG file, which is an open source vector format and import it to Easel. \\n\",\r\n \"images\": [\r\n {\r\n \"downloadUrl\": \"https://firebasestorage.googleapis.com/v0/b/onearmyworld.appspot.com/o/uploads%2Fhowtos%2Fpbo0Pe44aTngvlD04kGf%2F4.jpg?alt=media&token=1cd2d49d-9335-4bb1-ac2a-e625322ca604\",\r\n \"contentType\": \"image/jpeg\",\r\n \"timeCreated\": \"2021-03-26T19:42:06.727Z\",\r\n \"updated\": \"2021-03-26T19:42:06.727Z\",\r\n \"name\": \"4.jpg\",\r\n \"size\": 42952,\r\n \"type\": \"image/jpeg\",\r\n \"fullPath\": \"uploads/howtos/pbo0Pe44aTngvlD04kGf/4.jpg\"\r\n },\r\n {\r\n \"size\": 69255,\r\n \"fullPath\": \"uploads/howtos/pbo0Pe44aTngvlD04kGf/5.jpg\",\r\n \"updated\": \"2021-03-26T19:42:06.833Z\",\r\n \"timeCreated\": \"2021-03-26T19:42:06.833Z\",\r\n \"downloadUrl\": \"https://firebasestorage.googleapis.com/v0/b/onearmyworld.appspot.com/o/uploads%2Fhowtos%2Fpbo0Pe44aTngvlD04kGf%2F5.jpg?alt=media&token=7cca786a-7d47-43bb-900b-b8d101c276b4\",\r\n \"name\": \"5.jpg\",\r\n \"contentType\": \"image/jpeg\",\r\n \"type\": \"image/jpeg\"\r\n }\r\n ],\r\n \"_animationKey\": \"unique3\"\r\n },\r\n {\r\n \"text\": \"Now with the file we can choose the width we want to carve/cut and then we go to cut and start the wizzard:\\n- We check that the sheet is fixed.\\n- We also specify the cutting bit, we are using a 1/8 flat flute bit. \\n- We tell the machine where the coordinate 0-0 is, which we always choose as the down left corner.\\n- We raise the bit, turn on the Router!!!\\n\\nAND PUM THE MAGIC BEGINS!!\",\r\n \"title\": \"Follow the cutting Wizzard\",\r\n \"images\": [\r\n {\r\n \"timeCreated\": \"2021-03-26T19:42:07.493Z\",\r\n \"size\": 72226,\r\n \"fullPath\": \"uploads/howtos/pbo0Pe44aTngvlD04kGf/6.jpg\",\r\n \"updated\": \"2021-03-26T19:42:07.493Z\",\r\n \"downloadUrl\": \"https://firebasestorage.googleapis.com/v0/b/onearmyworld.appspot.com/o/uploads%2Fhowtos%2Fpbo0Pe44aTngvlD04kGf%2F6.jpg?alt=media&token=ba7195dd-7771-435f-a188-057457697332\",\r\n \"contentType\": \"image/jpeg\",\r\n \"type\": \"image/jpeg\",\r\n \"name\": \"6.jpg\"\r\n },\r\n {\r\n \"fullPath\": \"uploads/howtos/pbo0Pe44aTngvlD04kGf/7.jpg\",\r\n \"size\": 52424,\r\n \"downloadUrl\": \"https://firebasestorage.googleapis.com/v0/b/onearmyworld.appspot.com/o/uploads%2Fhowtos%2Fpbo0Pe44aTngvlD04kGf%2F7.jpg?alt=media&token=a3d5820c-cfe2-484e-8f76-f861ab8b756d\",\r\n \"contentType\": \"image/jpeg\",\r\n \"type\": \"image/jpeg\",\r\n \"timeCreated\": \"2021-03-26T19:42:07.308Z\",\r\n \"updated\": \"2021-03-26T19:42:07.308Z\",\r\n \"name\": \"7.jpg\"\r\n },\r\n {\r\n \"fullPath\": \"uploads/howtos/pbo0Pe44aTngvlD04kGf/8.jpg\",\r\n \"name\": \"8.jpg\",\r\n \"type\": \"image/jpeg\",\r\n \"timeCreated\": \"2021-03-26T19:42:07.346Z\",\r\n \"size\": 55264,\r\n \"contentType\": \"image/jpeg\",\r\n \"downloadUrl\": \"https://firebasestorage.googleapis.com/v0/b/onearmyworld.appspot.com/o/uploads%2Fhowtos%2Fpbo0Pe44aTngvlD04kGf%2F8.jpg?alt=media&token=1c9816d7-3a99-4f41-8d3c-acc2670240f6\",\r\n \"updated\": \"2021-03-26T19:42:07.346Z\"\r\n }\r\n ],\r\n \"_animationKey\": \"uniquenisc2v\"\r\n },\r\n {\r\n \"text\": \"You take now your glasses or object and postprocess them and of course show it to your friends, family and so on.\\n\\n\\n\",\r\n \"images\": [\r\n {\r\n \"fullPath\": \"uploads/howtos/pbo0Pe44aTngvlD04kGf/9.jpg\",\r\n \"contentType\": \"image/jpeg\",\r\n \"timeCreated\": \"2021-03-26T19:42:08.147Z\",\r\n \"downloadUrl\": \"https://firebasestorage.googleapis.com/v0/b/onearmyworld.appspot.com/o/uploads%2Fhowtos%2Fpbo0Pe44aTngvlD04kGf%2F9.jpg?alt=media&token=4dcfe37d-e1ad-41e5-a590-40b4c37c5e1a\",\r\n \"name\": \"9.jpg\",\r\n \"updated\": \"2021-03-26T19:42:08.147Z\",\r\n \"type\": \"image/jpeg\",\r\n \"size\": 82214\r\n }\r\n ],\r\n \"_animationKey\": \"uniquesgl34\",\r\n \"title\": \"Post-production and show case\"\r\n },\r\n {\r\n \"_animationKey\": \"uniquem4y0yi\",\r\n \"title\": \"Hack it and try it yourself\",\r\n \"text\": \"You can try this project with other types of CNC machines, even manual Routers or manual saw, as I did on this video: https://youtu.be/gxkcffQD3eQ, but the important thing is that you share what you do and help this community to grow!!!\\n\\nShare your ideas and comments!\",\r\n \"images\": [\r\n {\r\n \"contentType\": \"image/jpeg\",\r\n \"timeCreated\": \"2021-04-05T15:09:01.445Z\",\r\n \"fullPath\": \"uploads/howtos/038gjWgLjiyYknbEjDeI/IMG_20200605_142311.jpg\",\r\n \"type\": \"image/jpeg\",\r\n \"downloadUrl\": \"https://firebasestorage.googleapis.com/v0/b/onearmyworld.appspot.com/o/uploads%2Fhowtos%2F038gjWgLjiyYknbEjDeI%2FIMG_20200605_142311.jpg?alt=media&token=f94152ff-f923-4054-a3ad-d8ec588856fa\",\r\n \"size\": 124661,\r\n \"updated\": \"2021-04-05T15:09:01.445Z\",\r\n \"name\": \"IMG_20200605_142311.jpg\"\r\n }\r\n ]\r\n }\r\n ],\r\n \"moderation\": \"accepted\"\r\n }" + "path": "howto_sample.json", + "content": "{\n \"_createdBy\": \"gus-merckel\",\n \"mentions\": [],\n \"_deleted\": false,\n \"fileLink\": \"\",\n \"slug\": \"cut-out-shapes-out-of-plastic-sheets-with-a-cnc-\",\n \"_modified\": \"2023-10-27T18:09:36.519Z\",\n \"previousSlugs\": [\n \"cut-out-shapes-out-of-plastic-sheets-with-a-cnc-\"\n ],\n \"_created\": \"2023-08-23T18:20:09.098Z\",\n \"description\": \"In this how to, I will show you our process to cut HDPE Sheets using a X-Carve CNC.\\n\\nHere is the full video in spanish with subtitles https://www.youtube.com/watch?v=4LrrFz802To \",\n \"votedUsefulBy\": [\n \"sigolene\",\n \"mattia\",\n \"uillinoispreciousplastics\"\n ],\n \"creatorCountry\": \"mx\",\n \"total_downloads\": 0,\n \"title\": \"Cut out shapes out of plastic sheets with a CNC \",\n \"time\": \"< 5 hours\",\n \"files\": [],\n \"difficulty_level\": \"Medium\",\n \"_id\": \"038gjWgLjiyYknbEjDeI\",\n \"tags\": [\n \"HDPE\"\n ],\n \"total_views\": 232,\n \"_contentModifiedTimestamp\": \"2023-08-23T18:20:09.098Z\",\n \"cover_image\": {\n \"name\": \"IMG_20200605_142311.jpg\",\n \"downloadUrl\": \"https://firebasestorage.googleapis.com/v0/b/onearmyworld.appspot.com/o/uploads%2Fhowtos%2F038gjWgLjiyYknbEjDeI%2FIMG_20200605_142311.jpg?alt=media&token=c272c174-1adc-45af-967b-771adce7295d\",\n \"type\": \"image/jpeg\",\n \"fullPath\": \"uploads/howtos/038gjWgLjiyYknbEjDeI/IMG_20200605_142311.jpg\",\n \"updated\": \"2021-04-05T15:09:00.605Z\",\n \"size\": 124661,\n \"timeCreated\": \"2021-04-05T15:09:00.605Z\",\n \"contentType\": \"image/jpeg\"\n },\n \"comments\": [],\n \"moderatorFeedback\": \"\",\n \"steps\": [\n {\n \"title\": \"Measure the plastic sheet\",\n \"text\": \"For this step we need to measure our plastic sheet: Height, Width and Thickness. Our X-Carve machine works with the CAM Software EASEL, for me, the easiest software for CNC milling out there. \\n\\nThe cool thing about Easel (https://easel.inventables.com/) is that you can \\\"simulate\\\" your actual material and THEY EVEN HAVE HDPE 2-Colors in their cutting material lists!!\\n\\n\\n\",\n \"images\": [\n {\n \"fullPath\": \"uploads/howtos/pbo0Pe44aTngvlD04kGf/1.jpg\",\n \"name\": \"1.jpg\",\n \"size\": 74095,\n \"type\": \"image/jpeg\",\n \"timeCreated\": \"2021-03-26T19:42:05.766Z\",\n \"contentType\": \"image/jpeg\",\n \"downloadUrl\": \"https://firebasestorage.googleapis.com/v0/b/onearmyworld.appspot.com/o/uploads%2Fhowtos%2Fpbo0Pe44aTngvlD04kGf%2F1.jpg?alt=media&token=293d733d-05a5-494a-9340-47f4564f1939\",\n \"updated\": \"2021-03-26T19:42:05.766Z\",\n \"src\": \"/resources/howtos/cut-out-shapes-out-of-plastic-sheets-with-a-cnc-/1.jpg\",\n \"alt\": \"1.jpg\"\n },\n {\n \"contentType\": \"image/jpeg\",\n \"timeCreated\": \"2021-03-26T19:42:05.669Z\",\n \"updated\": \"2021-03-26T19:42:05.669Z\",\n \"size\": 69665,\n \"downloadUrl\": \"https://firebasestorage.googleapis.com/v0/b/onearmyworld.appspot.com/o/uploads%2Fhowtos%2Fpbo0Pe44aTngvlD04kGf%2F2.jpg?alt=media&token=004f50f1-97ac-4df4-9ba9-f463aa4cbca3\",\n \"fullPath\": \"uploads/howtos/pbo0Pe44aTngvlD04kGf/2.jpg\",\n \"name\": \"2.jpg\",\n \"type\": \"image/jpeg\",\n \"src\": \"/resources/howtos/cut-out-shapes-out-of-plastic-sheets-with-a-cnc-/2.jpg\",\n \"alt\": \"2.jpg\"\n }\n ],\n \"_animationKey\": \"unique1\"\n },\n {\n \"text\": \"Using the CNC clamps from the X-Carve, secure the sheet to the table, \",\n \"_animationKey\": \"unique2\",\n \"images\": [\n {\n \"updated\": \"2021-03-26T19:42:06.249Z\",\n \"size\": 55544,\n \"fullPath\": \"uploads/howtos/pbo0Pe44aTngvlD04kGf/3.jpg\",\n \"timeCreated\": \"2021-03-26T19:42:06.249Z\",\n \"name\": \"3.jpg\",\n \"downloadUrl\": \"https://firebasestorage.googleapis.com/v0/b/onearmyworld.appspot.com/o/uploads%2Fhowtos%2Fpbo0Pe44aTngvlD04kGf%2F3.jpg?alt=media&token=0b9c1914-1c75-429e-b34a-1e2b3706edef\",\n \"contentType\": \"image/jpeg\",\n \"type\": \"image/jpeg\",\n \"src\": \"/resources/howtos/cut-out-shapes-out-of-plastic-sheets-with-a-cnc-/3.jpg\",\n \"alt\": \"3.jpg\"\n }\n ],\n \"title\": \"Secure sheet \"\n },\n {\n \"title\": \"Choosing a file to cut \",\n \"text\": \"Now we go to our illustrator, such as Inkscape to design a vector file or download and open source one frome https://thenounproject.com/.\\n\\nWe download the SVG file, which is an open source vector format and import it to Easel. \\n\",\n \"images\": [\n {\n \"downloadUrl\": \"https://firebasestorage.googleapis.com/v0/b/onearmyworld.appspot.com/o/uploads%2Fhowtos%2Fpbo0Pe44aTngvlD04kGf%2F4.jpg?alt=media&token=1cd2d49d-9335-4bb1-ac2a-e625322ca604\",\n \"contentType\": \"image/jpeg\",\n \"timeCreated\": \"2021-03-26T19:42:06.727Z\",\n \"updated\": \"2021-03-26T19:42:06.727Z\",\n \"name\": \"4.jpg\",\n \"size\": 42952,\n \"type\": \"image/jpeg\",\n \"fullPath\": \"uploads/howtos/pbo0Pe44aTngvlD04kGf/4.jpg\",\n \"src\": \"/resources/howtos/cut-out-shapes-out-of-plastic-sheets-with-a-cnc-/4.jpg\",\n \"alt\": \"4.jpg\"\n },\n {\n \"size\": 69255,\n \"fullPath\": \"uploads/howtos/pbo0Pe44aTngvlD04kGf/5.jpg\",\n \"updated\": \"2021-03-26T19:42:06.833Z\",\n \"timeCreated\": \"2021-03-26T19:42:06.833Z\",\n \"downloadUrl\": \"https://firebasestorage.googleapis.com/v0/b/onearmyworld.appspot.com/o/uploads%2Fhowtos%2Fpbo0Pe44aTngvlD04kGf%2F5.jpg?alt=media&token=7cca786a-7d47-43bb-900b-b8d101c276b4\",\n \"name\": \"5.jpg\",\n \"contentType\": \"image/jpeg\",\n \"type\": \"image/jpeg\",\n \"src\": \"/resources/howtos/cut-out-shapes-out-of-plastic-sheets-with-a-cnc-/5.jpg\",\n \"alt\": \"5.jpg\"\n }\n ],\n \"_animationKey\": \"unique3\"\n },\n {\n \"text\": \"Now with the file we can choose the width we want to carve/cut and then we go to cut and start the wizzard:\\n- We check that the sheet is fixed.\\n- We also specify the cutting bit, we are using a 1/8 flat flute bit. \\n- We tell the machine where the coordinate 0-0 is, which we always choose as the down left corner.\\n- We raise the bit, turn on the Router!!!\\n\\nAND PUM THE MAGIC BEGINS!!\",\n \"title\": \"Follow the cutting Wizzard\",\n \"images\": [\n {\n \"timeCreated\": \"2021-03-26T19:42:07.493Z\",\n \"size\": 72226,\n \"fullPath\": \"uploads/howtos/pbo0Pe44aTngvlD04kGf/6.jpg\",\n \"updated\": \"2021-03-26T19:42:07.493Z\",\n \"downloadUrl\": \"https://firebasestorage.googleapis.com/v0/b/onearmyworld.appspot.com/o/uploads%2Fhowtos%2Fpbo0Pe44aTngvlD04kGf%2F6.jpg?alt=media&token=ba7195dd-7771-435f-a188-057457697332\",\n \"contentType\": \"image/jpeg\",\n \"type\": \"image/jpeg\",\n \"name\": \"6.jpg\",\n \"src\": \"/resources/howtos/cut-out-shapes-out-of-plastic-sheets-with-a-cnc-/6.jpg\",\n \"alt\": \"6.jpg\"\n },\n {\n \"fullPath\": \"uploads/howtos/pbo0Pe44aTngvlD04kGf/7.jpg\",\n \"size\": 52424,\n \"downloadUrl\": \"https://firebasestorage.googleapis.com/v0/b/onearmyworld.appspot.com/o/uploads%2Fhowtos%2Fpbo0Pe44aTngvlD04kGf%2F7.jpg?alt=media&token=a3d5820c-cfe2-484e-8f76-f861ab8b756d\",\n \"contentType\": \"image/jpeg\",\n \"type\": \"image/jpeg\",\n \"timeCreated\": \"2021-03-26T19:42:07.308Z\",\n \"updated\": \"2021-03-26T19:42:07.308Z\",\n \"name\": \"7.jpg\",\n \"src\": \"/resources/howtos/cut-out-shapes-out-of-plastic-sheets-with-a-cnc-/7.jpg\",\n \"alt\": \"7.jpg\"\n },\n {\n \"fullPath\": \"uploads/howtos/pbo0Pe44aTngvlD04kGf/8.jpg\",\n \"name\": \"8.jpg\",\n \"type\": \"image/jpeg\",\n \"timeCreated\": \"2021-03-26T19:42:07.346Z\",\n \"size\": 55264,\n \"contentType\": \"image/jpeg\",\n \"downloadUrl\": \"https://firebasestorage.googleapis.com/v0/b/onearmyworld.appspot.com/o/uploads%2Fhowtos%2Fpbo0Pe44aTngvlD04kGf%2F8.jpg?alt=media&token=1c9816d7-3a99-4f41-8d3c-acc2670240f6\",\n \"updated\": \"2021-03-26T19:42:07.346Z\",\n \"src\": \"/resources/howtos/cut-out-shapes-out-of-plastic-sheets-with-a-cnc-/8.jpg\",\n \"alt\": \"8.jpg\"\n }\n ],\n \"_animationKey\": \"uniquenisc2v\"\n },\n {\n \"text\": \"You take now your glasses or object and postprocess them and of course show it to your friends, family and so on.\\n\\n\\n\",\n \"images\": [\n {\n \"fullPath\": \"uploads/howtos/pbo0Pe44aTngvlD04kGf/9.jpg\",\n \"contentType\": \"image/jpeg\",\n \"timeCreated\": \"2021-03-26T19:42:08.147Z\",\n \"downloadUrl\": \"https://firebasestorage.googleapis.com/v0/b/onearmyworld.appspot.com/o/uploads%2Fhowtos%2Fpbo0Pe44aTngvlD04kGf%2F9.jpg?alt=media&token=4dcfe37d-e1ad-41e5-a590-40b4c37c5e1a\",\n \"name\": \"9.jpg\",\n \"updated\": \"2021-03-26T19:42:08.147Z\",\n \"type\": \"image/jpeg\",\n \"size\": 82214,\n \"src\": \"/resources/howtos/cut-out-shapes-out-of-plastic-sheets-with-a-cnc-/9.jpg\",\n \"alt\": \"9.jpg\"\n }\n ],\n \"_animationKey\": \"uniquesgl34\",\n \"title\": \"Post-production and show case\"\n },\n {\n \"_animationKey\": \"uniquem4y0yi\",\n \"title\": \"Hack it and try it yourself\",\n \"text\": \"You can try this project with other types of CNC machines, even manual Routers or manual saw, as I did on this video: https://youtu.be/gxkcffQD3eQ, but the important thing is that you share what you do and help this community to grow!!!\\n\\nShare your ideas and comments!\",\n \"images\": [\n {\n \"contentType\": \"image/jpeg\",\n \"timeCreated\": \"2021-04-05T15:09:01.445Z\",\n \"fullPath\": \"uploads/howtos/038gjWgLjiyYknbEjDeI/IMG_20200605_142311.jpg\",\n \"type\": \"image/jpeg\",\n \"downloadUrl\": \"https://firebasestorage.googleapis.com/v0/b/onearmyworld.appspot.com/o/uploads%2Fhowtos%2F038gjWgLjiyYknbEjDeI%2FIMG_20200605_142311.jpg?alt=media&token=f94152ff-f923-4054-a3ad-d8ec588856fa\",\n \"size\": 124661,\n \"updated\": \"2021-04-05T15:09:01.445Z\",\n \"name\": \"IMG_20200605_142311.jpg\",\n \"src\": \"/resources/howtos/cut-out-shapes-out-of-plastic-sheets-with-a-cnc-/IMG_20200605_142311.jpg\",\n \"alt\": \"IMG_20200605_142311.jpg\"\n }\n ]\n }\n ],\n \"moderation\": \"accepted\",\n \"user\": {\n \"_modified\": \"2024-01-08T13:28:33.484Z\",\n \"_id\": \"gus-merckel\",\n \"subType\": \"mix\",\n \"moderation\": \"accepted\",\n \"_deleted\": false,\n \"verified\": false,\n \"type\": \"workspace\",\n \"location\": {\n \"lat\": 19.3935,\n \"lng\": -99.1656\n },\n \"_created\": \"2024-01-08T13:28:33.484Z\",\n \"geo\": {\n \"latitude\": 19.3935,\n \"lookupSource\": \"coordinates\",\n \"longitude\": -99.1656,\n \"localityLanguageRequested\": \"en\",\n \"continent\": \"North America\",\n \"continentCode\": \"NA\",\n \"countryName\": \"Mexico\",\n \"countryCode\": \"MX\",\n \"principalSubdivision\": \"Ciudad de Mexico\",\n \"principalSubdivisionCode\": \"MX-CMX\",\n \"city\": \"Mexico City\",\n \"locality\": \"Benito Juarez\",\n \"postcode\": \"03103\",\n \"plusCode\": \"76F29RVM+CQ\",\n \"localityInfo\": {\n \"administrative\": [\n {\n \"name\": \"Mexico\",\n \"description\": \"country in North America\",\n \"isoName\": \"Mexico\",\n \"order\": 2,\n \"adminLevel\": 2,\n \"isoCode\": \"MX\",\n \"wikidataId\": \"Q96\",\n \"geonameId\": 3996063\n },\n {\n \"name\": \"Mexico City\",\n \"description\": \"capital and largest city of Mexico\",\n \"order\": 5,\n \"adminLevel\": 4,\n \"wikidataId\": \"Q1489\",\n \"geonameId\": 3530597\n },\n {\n \"name\": \"Ciudad de Mexico\",\n \"description\": \"capital and largest city of Mexico\",\n \"isoName\": \"Ciudad de Mexico\",\n \"order\": 6,\n \"adminLevel\": 4,\n \"isoCode\": \"MX-CMX\",\n \"wikidataId\": \"Q1489\",\n \"geonameId\": 3527646\n },\n {\n \"name\": \"Benito Juarez\",\n \"description\": \"territorial demarcation of the Mexico City in Mexico\",\n \"order\": 7,\n \"adminLevel\": 6,\n \"wikidataId\": \"Q2356998\",\n \"geonameId\": 3827406\n }\n ],\n \"informative\": [\n {\n \"name\": \"North America\",\n \"description\": \"continent and northern subcontinent of the Americas\",\n \"isoName\": \"North America\",\n \"order\": 1,\n \"isoCode\": \"NA\",\n \"wikidataId\": \"Q49\",\n \"geonameId\": 6255149\n },\n {\n \"name\": \"America/Mexico_City\",\n \"description\": \"time zone\",\n \"order\": 3\n },\n {\n \"name\": \"Greater Mexico City\",\n \"description\": \"geographical object\",\n \"order\": 4,\n \"wikidataId\": \"Q665894\"\n },\n {\n \"name\": \"03103\",\n \"description\": \"postal code\",\n \"order\": 8\n }\n ]\n }\n },\n \"data\": {\n \"urls\": [\n {\n \"name\": \"Email\",\n \"url\": \"mailto:gustavomerckel@gmail.com\"\n },\n {\n \"name\": \"Facebook\",\n \"url\": \"https://www.facebook.com/pl%c3%a1stico-chido-110888520718193\"\n },\n {\n \"name\": \"sponsor the work\",\n \"url\": \"https://www.patreon.com/one_army\"\n }\n ],\n \"description\": \"Plástico Chido builds and modifies the PP machines, and also experiments with CNC and Laser Cut\",\n \"services\": [\n {\n \"welding\": false,\n \"assembling\": false,\n \"machining\": false,\n \"electronics\": false,\n \"molds\": false\n }\n ],\n \"title\": \"Plástico Chido\",\n \"images\": []\n },\n \"detail\": {\n \"services\": [],\n \"urls\": []\n }\n },\n \"category\": {\n \"label\": \"uncategorized\"\n }\n}" } ], - "tools": [ - { - "type": "function", - "function": { - "name": "remove_file", - "description": "Remove a file at given path", - "parameters": { - "type": "object", - "properties": { - "path": { - "type": "string" - } - }, - "required": [ - "path" - ] - } - } - }, - { - "type": "function", - "function": { - "name": "rename_file", - "description": "Rename or move a file or directory", - "parameters": { - "type": "object", - "properties": { - "src": { - "type": "string" - }, - "dst": { - "type": "string" - } - }, - "required": [ - "path" - ] - } - } - }, - { - "type": "function", - "function": { - "name": "modify_project_files", - "description": "Create or modify existing project files in one shot, preferably used for creating project structure)", - "parameters": { - "type": "object", - "properties": { - "files": { - "type": "array", - "items": { - "type": "object", - "properties": { - "path": { - "type": "string" - }, - "content": { - "type": "string", - "description": "base64 encoded string" - } - }, - "required": [ - "path", - "content" - ] - } - } - }, - "required": [ - "files" - ] - } - } - }, - { - "type": "function", - "function": { - "name": "write_file", - "description": "Writes to a file, given a path and content (base64). No directory or file exists check needed!", - "parameters": { - "type": "object", - "properties": { - "file": { - "type": "object", - "properties": { - "path": { - "type": "string" - }, - "content": { - "type": "string", - "description": "base64 encoded string" - } - } - } - }, - "required": [ - "file" - ] - } - } - } - ], - "tool_choice": "auto", - "parallel_tool_calls": false + "tools": [] } \ No newline at end of file diff --git a/src/model/howto-model.ts b/src/model/howto-model.ts new file mode 100644 index 0000000..faf0c93 --- /dev/null +++ b/src/model/howto-model.ts @@ -0,0 +1,177 @@ +export const ITEM_TYPE = 'howto' +//////////////////////////////// +// +// Interfaces - Old +export interface IHowto { + _createdBy: string + mentions: any[] + _deleted: boolean + fileLink: string + slug: string + _modified: string + previousSlugs: string[] + _created: string + description: string + votedUsefulBy: string[] + creatorCountry: string + total_downloads: number + title: string + time: string + files: any[] + category: IOACategory + difficulty_level: string + _id: string + tags?: IOATag[] + total_views: number + _contentModifiedTimestamp: string + cover_image: Image + comments: any[] + moderatorFeedback: string + steps: Step[] + moderation: string + user?: User +} + +export interface Tags { + [key: string]: boolean +} + +export interface CoverImage { + name: string + downloadUrl: string + type: string + fullPath: string + updated: string + size: number + timeCreated: string + contentType: string +} + +export interface Step { + title: string + text: string + images: Image[] + _animationKey: string +} +export interface Image { + updated: string + size: number + fullPath: string + timeCreated: string + name: string + downloadUrl: string + contentType: string + type: string + src: string, + alt: string +} + +/// Taxonomy +export interface IOACategory { + _created: string + _id: string + _deleted: boolean + label: string + _modified: string +} + +export interface IOATag { + categories: string[] + image: string + _created: string + _deleted: boolean + label: string + _createdBy: string + _modified: string + _id: string +} + +export interface User { + _modified: string + _id: string + subType: string + moderation: string + _deleted: boolean + verified: boolean + type: string + location: Location + _created: string + geo: Geo + data: Data + detail: Detail +} + +export interface Location { + lat: number + lng: number +} + +export interface Geo { + latitude: number + lookupSource: string + longitude: number + localityLanguageRequested: string + continent: string + continentCode: string + countryName: string + countryCode: string + principalSubdivision: string + principalSubdivisionCode: string + city: string + locality: string + postcode: string + plusCode: string + localityInfo: LocalityInfo +} + +export interface LocalityInfo { + administrative: Administrative[] + informative: Informative[] +} + +export interface Administrative { + name: string + description: string + isoName?: string + order: number + adminLevel: number + isoCode?: string + wikidataId: string + geonameId: number +} + +export interface Informative { + name: string + description: string + isoName?: string + order: number + isoCode?: string + wikidataId?: string + geonameId?: number +} + +export interface Data { + urls: Url[] + description: string + services: Service[] + title: string + images: any[] +} + +export interface Url { + name: string + url: string +} + +export interface Service { + welding: boolean + assembling: boolean + machining: boolean + electronics: boolean + molds: boolean +} + +export interface Detail { + services: any[] + urls: any[] +} diff --git a/src/model/howto-workflow.sh b/src/model/howto-workflow.sh new file mode 100644 index 0000000..48bdfe4 --- /dev/null +++ b/src/model/howto-workflow.sh @@ -0,0 +1,11 @@ +kbotd --preferences ./todos-workflow.md \ + --include=./howto-model.ts \ + --include=./howto_sample.json \ + --disable=terminal,git,npm,user,interact,search,email,web \ + --disableTools=read_file,read_files,list_files,file_exists,web \ + --model=anthropic/claude-3.7-sonnet:thinking \ + --mode=completion \ + --filters=code \ + --dst=./json-ld-howto.ts + + diff --git a/src/model/howto.ts b/src/model/howto.ts index 499ffb2..8fa7fcd 100644 --- a/src/model/howto.ts +++ b/src/model/howto.ts @@ -5,10 +5,12 @@ import { sync as read } from '@polymech/fs/read' import { sync as exists } from '@polymech/fs/exists' import { sync as mkdir } from '@polymech/fs/dir' import { sync as rm } from '@polymech/fs/remove' - +import { IHowto, Image } from './howto-model.js'; import type { Loader, LoaderContext } from 'astro/loaders' import { sanitizeFilename } from "@polymech/fs/utils" import { filter as language } from "@/base/kbot.js"; +export * from './howto-model.js' + import { HOWTO_FILES_WEB, HOWTO_FILES_ABS, @@ -35,8 +37,8 @@ export const ITEM_TYPE = 'howto' //export const load = () => get(`${HOWTO_ROOT()}/${HOWTO_GLOB}`, HOWTO_ROOT(), ITEM_TYPE) export const item_path = (item: any) => `${HOWTO_ROOT()}/${item.data.slug}` -const blacklist = []; -const blacklist_mafia = ['precious-plastic', 'fair-enough', 'mad-plastic-labs', 'the-flipflopi', 'easymoulds', 'plasticpreneur', 'sustainable-design-studio']; +const blacklist_ = []; +const blacklist = ['precious-plastic', 'fair-enough', 'mad-plastic-labs', 'the-flipflopi', 'easymoulds', 'plasticpreneur', 'sustainable-design-studio']; const download = async (url, outputPath) => { const stream = createWriteStream(outputPath); @@ -141,7 +143,7 @@ export const howtos = async () => { label: 'uncategorized' } }) - howtos = howtos.filter((h) => { + howtos = howtos.filter((h:IHowto) => { return h.steps.length > 0 && !blacklist.includes(h._createdBy); }); return howtos @@ -297,179 +299,3 @@ export async function applyFilters(text: string): Promise { return filtered; } -//////////////////////////////// -// -// Interfaces - Old -export interface IHowto { - _createdBy: string - mentions: any[] - _deleted: boolean - fileLink: string - slug: string - _modified: string - previousSlugs: string[] - _created: string - description: string - votedUsefulBy: string[] - creatorCountry: string - total_downloads: number - title: string - time: string - files: any[] - category: IOACategory - difficulty_level: string - _id: string - tags?: Tags - total_views: number - _contentModifiedTimestamp: string - cover_image: Image - comments: any[] - moderatorFeedback: string - steps: Step[] - moderation: string - user?: User -} - -export interface Tags { - [key: string]: boolean -} - -export interface CoverImage { - name: string - downloadUrl: string - type: string - fullPath: string - updated: string - size: number - timeCreated: string - contentType: string -} - -export interface Step { - title: string - text: string - images: Image[] - _animationKey: string -} -export interface Image { - updated: string - size: number - fullPath: string - timeCreated: string - name: string - downloadUrl: string - contentType: string - type: string - src: string, - alt: string -} - -/// Taxonomy -export interface IOACategory { - _created: string - _id: string - _deleted: boolean - label: string - _modified: string -} - -export interface IOATag { - categories: string[] - image: string - _created: string - _deleted: boolean - label: string - _createdBy: string - _modified: string - _id: string -} - -export interface User { - _modified: string - _id: string - subType: string - moderation: string - _deleted: boolean - verified: boolean - type: string - location: Location - _created: string - geo: Geo - data: Data - detail: Detail -} - -export interface Location { - lat: number - lng: number -} - -export interface Geo { - latitude: number - lookupSource: string - longitude: number - localityLanguageRequested: string - continent: string - continentCode: string - countryName: string - countryCode: string - principalSubdivision: string - principalSubdivisionCode: string - city: string - locality: string - postcode: string - plusCode: string - localityInfo: LocalityInfo -} - -export interface LocalityInfo { - administrative: Administrative[] - informative: Informative[] -} - -export interface Administrative { - name: string - description: string - isoName?: string - order: number - adminLevel: number - isoCode?: string - wikidataId: string - geonameId: number -} - -export interface Informative { - name: string - description: string - isoName?: string - order: number - isoCode?: string - wikidataId?: string - geonameId?: number -} - -export interface Data { - urls: Url[] - description: string - services: Service[] - title: string - images: any[] -} - -export interface Url { - name: string - url: string -} - -export interface Service { - welding: boolean - assembling: boolean - machining: boolean - electronics: boolean - molds: boolean -} - -export interface Detail { - services: any[] - urls: any[] -} diff --git a/src/model/howto_sample.js b/src/model/howto_sample.js deleted file mode 100644 index edd4baa..0000000 --- a/src/model/howto_sample.js +++ /dev/null @@ -1,339 +0,0 @@ -{ - "_createdBy": "gus-merckel", - "mentions": [], - "_deleted": false, - "fileLink": "", - "slug": "cut-out-shapes-out-of-plastic-sheets-with-a-cnc-", - "_modified": "2023-10-27T18:09:36.519Z", - "previousSlugs": [ - "cut-out-shapes-out-of-plastic-sheets-with-a-cnc-" - ], - "_created": "2023-08-23T18:20:09.098Z", - "description": "In this how to, I will show you our process to cut HDPE Sheets using a X-Carve CNC.\n\nHere is the full video in spanish with subtitles https://www.youtube.com/watch?v=4LrrFz802To ", - "votedUsefulBy": [ - "sigolene", - "mattia", - "uillinoispreciousplastics" - ], - "creatorCountry": "mx", - "total_downloads": 0, - "title": "Cut out shapes out of plastic sheets with a CNC ", - "time": "< 5 hours", - "files": [], - "difficulty_level": "Medium", - "_id": "038gjWgLjiyYknbEjDeI", - "tags": [ - "HDPE" - ], - "total_views": 232, - "_contentModifiedTimestamp": "2023-08-23T18:20:09.098Z", - "cover_image": { - "name": "IMG_20200605_142311.jpg", - "downloadUrl": "https://firebasestorage.googleapis.com/v0/b/onearmyworld.appspot.com/o/uploads%2Fhowtos%2F038gjWgLjiyYknbEjDeI%2FIMG_20200605_142311.jpg?alt=media&token=c272c174-1adc-45af-967b-771adce7295d", - "type": "image/jpeg", - "fullPath": "uploads/howtos/038gjWgLjiyYknbEjDeI/IMG_20200605_142311.jpg", - "updated": "2021-04-05T15:09:00.605Z", - "size": 124661, - "timeCreated": "2021-04-05T15:09:00.605Z", - "contentType": "image/jpeg" - }, - "comments": [], - "moderatorFeedback": "", - "steps": [ - { - "title": "Measure the plastic sheet", - "text": "For this step we need to measure our plastic sheet: Height, Width and Thickness. Our X-Carve machine works with the CAM Software EASEL, for me, the easiest software for CNC milling out there. \n\nThe cool thing about Easel (https://easel.inventables.com/) is that you can \"simulate\" your actual material and THEY EVEN HAVE HDPE 2-Colors in their cutting material lists!!\n\n\n", - "images": [ - { - "fullPath": "uploads/howtos/pbo0Pe44aTngvlD04kGf/1.jpg", - "name": "1.jpg", - "size": 74095, - "type": "image/jpeg", - "timeCreated": "2021-03-26T19:42:05.766Z", - "contentType": "image/jpeg", - "downloadUrl": "https://firebasestorage.googleapis.com/v0/b/onearmyworld.appspot.com/o/uploads%2Fhowtos%2Fpbo0Pe44aTngvlD04kGf%2F1.jpg?alt=media&token=293d733d-05a5-494a-9340-47f4564f1939", - "updated": "2021-03-26T19:42:05.766Z", - "src": "/resources/howtos/cut-out-shapes-out-of-plastic-sheets-with-a-cnc-/1.jpg", - "alt": "1.jpg" - }, - { - "contentType": "image/jpeg", - "timeCreated": "2021-03-26T19:42:05.669Z", - "updated": "2021-03-26T19:42:05.669Z", - "size": 69665, - "downloadUrl": "https://firebasestorage.googleapis.com/v0/b/onearmyworld.appspot.com/o/uploads%2Fhowtos%2Fpbo0Pe44aTngvlD04kGf%2F2.jpg?alt=media&token=004f50f1-97ac-4df4-9ba9-f463aa4cbca3", - "fullPath": "uploads/howtos/pbo0Pe44aTngvlD04kGf/2.jpg", - "name": "2.jpg", - "type": "image/jpeg", - "src": "/resources/howtos/cut-out-shapes-out-of-plastic-sheets-with-a-cnc-/2.jpg", - "alt": "2.jpg" - } - ], - "_animationKey": "unique1" - }, - { - "text": "Using the CNC clamps from the X-Carve, secure the sheet to the table, ", - "_animationKey": "unique2", - "images": [ - { - "updated": "2021-03-26T19:42:06.249Z", - "size": 55544, - "fullPath": "uploads/howtos/pbo0Pe44aTngvlD04kGf/3.jpg", - "timeCreated": "2021-03-26T19:42:06.249Z", - "name": "3.jpg", - "downloadUrl": "https://firebasestorage.googleapis.com/v0/b/onearmyworld.appspot.com/o/uploads%2Fhowtos%2Fpbo0Pe44aTngvlD04kGf%2F3.jpg?alt=media&token=0b9c1914-1c75-429e-b34a-1e2b3706edef", - "contentType": "image/jpeg", - "type": "image/jpeg", - "src": "/resources/howtos/cut-out-shapes-out-of-plastic-sheets-with-a-cnc-/3.jpg", - "alt": "3.jpg" - } - ], - "title": "Secure sheet " - }, - { - "title": "Choosing a file to cut ", - "text": "Now we go to our illustrator, such as Inkscape to design a vector file or download and open source one frome https://thenounproject.com/.\n\nWe download the SVG file, which is an open source vector format and import it to Easel. \n", - "images": [ - { - "downloadUrl": "https://firebasestorage.googleapis.com/v0/b/onearmyworld.appspot.com/o/uploads%2Fhowtos%2Fpbo0Pe44aTngvlD04kGf%2F4.jpg?alt=media&token=1cd2d49d-9335-4bb1-ac2a-e625322ca604", - "contentType": "image/jpeg", - "timeCreated": "2021-03-26T19:42:06.727Z", - "updated": "2021-03-26T19:42:06.727Z", - "name": "4.jpg", - "size": 42952, - "type": "image/jpeg", - "fullPath": "uploads/howtos/pbo0Pe44aTngvlD04kGf/4.jpg", - "src": "/resources/howtos/cut-out-shapes-out-of-plastic-sheets-with-a-cnc-/4.jpg", - "alt": "4.jpg" - }, - { - "size": 69255, - "fullPath": "uploads/howtos/pbo0Pe44aTngvlD04kGf/5.jpg", - "updated": "2021-03-26T19:42:06.833Z", - "timeCreated": "2021-03-26T19:42:06.833Z", - "downloadUrl": "https://firebasestorage.googleapis.com/v0/b/onearmyworld.appspot.com/o/uploads%2Fhowtos%2Fpbo0Pe44aTngvlD04kGf%2F5.jpg?alt=media&token=7cca786a-7d47-43bb-900b-b8d101c276b4", - "name": "5.jpg", - "contentType": "image/jpeg", - "type": "image/jpeg", - "src": "/resources/howtos/cut-out-shapes-out-of-plastic-sheets-with-a-cnc-/5.jpg", - "alt": "5.jpg" - } - ], - "_animationKey": "unique3" - }, - { - "text": "Now with the file we can choose the width we want to carve/cut and then we go to cut and start the wizzard:\n- We check that the sheet is fixed.\n- We also specify the cutting bit, we are using a 1/8 flat flute bit. \n- We tell the machine where the coordinate 0-0 is, which we always choose as the down left corner.\n- We raise the bit, turn on the Router!!!\n\nAND PUM THE MAGIC BEGINS!!", - "title": "Follow the cutting Wizzard", - "images": [ - { - "timeCreated": "2021-03-26T19:42:07.493Z", - "size": 72226, - "fullPath": "uploads/howtos/pbo0Pe44aTngvlD04kGf/6.jpg", - "updated": "2021-03-26T19:42:07.493Z", - "downloadUrl": "https://firebasestorage.googleapis.com/v0/b/onearmyworld.appspot.com/o/uploads%2Fhowtos%2Fpbo0Pe44aTngvlD04kGf%2F6.jpg?alt=media&token=ba7195dd-7771-435f-a188-057457697332", - "contentType": "image/jpeg", - "type": "image/jpeg", - "name": "6.jpg", - "src": "/resources/howtos/cut-out-shapes-out-of-plastic-sheets-with-a-cnc-/6.jpg", - "alt": "6.jpg" - }, - { - "fullPath": "uploads/howtos/pbo0Pe44aTngvlD04kGf/7.jpg", - "size": 52424, - "downloadUrl": "https://firebasestorage.googleapis.com/v0/b/onearmyworld.appspot.com/o/uploads%2Fhowtos%2Fpbo0Pe44aTngvlD04kGf%2F7.jpg?alt=media&token=a3d5820c-cfe2-484e-8f76-f861ab8b756d", - "contentType": "image/jpeg", - "type": "image/jpeg", - "timeCreated": "2021-03-26T19:42:07.308Z", - "updated": "2021-03-26T19:42:07.308Z", - "name": "7.jpg", - "src": "/resources/howtos/cut-out-shapes-out-of-plastic-sheets-with-a-cnc-/7.jpg", - "alt": "7.jpg" - }, - { - "fullPath": "uploads/howtos/pbo0Pe44aTngvlD04kGf/8.jpg", - "name": "8.jpg", - "type": "image/jpeg", - "timeCreated": "2021-03-26T19:42:07.346Z", - "size": 55264, - "contentType": "image/jpeg", - "downloadUrl": "https://firebasestorage.googleapis.com/v0/b/onearmyworld.appspot.com/o/uploads%2Fhowtos%2Fpbo0Pe44aTngvlD04kGf%2F8.jpg?alt=media&token=1c9816d7-3a99-4f41-8d3c-acc2670240f6", - "updated": "2021-03-26T19:42:07.346Z", - "src": "/resources/howtos/cut-out-shapes-out-of-plastic-sheets-with-a-cnc-/8.jpg", - "alt": "8.jpg" - } - ], - "_animationKey": "uniquenisc2v" - }, - { - "text": "You take now your glasses or object and postprocess them and of course show it to your friends, family and so on.\n\n\n", - "images": [ - { - "fullPath": "uploads/howtos/pbo0Pe44aTngvlD04kGf/9.jpg", - "contentType": "image/jpeg", - "timeCreated": "2021-03-26T19:42:08.147Z", - "downloadUrl": "https://firebasestorage.googleapis.com/v0/b/onearmyworld.appspot.com/o/uploads%2Fhowtos%2Fpbo0Pe44aTngvlD04kGf%2F9.jpg?alt=media&token=4dcfe37d-e1ad-41e5-a590-40b4c37c5e1a", - "name": "9.jpg", - "updated": "2021-03-26T19:42:08.147Z", - "type": "image/jpeg", - "size": 82214, - "src": "/resources/howtos/cut-out-shapes-out-of-plastic-sheets-with-a-cnc-/9.jpg", - "alt": "9.jpg" - } - ], - "_animationKey": "uniquesgl34", - "title": "Post-production and show case" - }, - { - "_animationKey": "uniquem4y0yi", - "title": "Hack it and try it yourself", - "text": "You can try this project with other types of CNC machines, even manual Routers or manual saw, as I did on this video: https://youtu.be/gxkcffQD3eQ, but the important thing is that you share what you do and help this community to grow!!!\n\nShare your ideas and comments!", - "images": [ - { - "contentType": "image/jpeg", - "timeCreated": "2021-04-05T15:09:01.445Z", - "fullPath": "uploads/howtos/038gjWgLjiyYknbEjDeI/IMG_20200605_142311.jpg", - "type": "image/jpeg", - "downloadUrl": "https://firebasestorage.googleapis.com/v0/b/onearmyworld.appspot.com/o/uploads%2Fhowtos%2F038gjWgLjiyYknbEjDeI%2FIMG_20200605_142311.jpg?alt=media&token=f94152ff-f923-4054-a3ad-d8ec588856fa", - "size": 124661, - "updated": "2021-04-05T15:09:01.445Z", - "name": "IMG_20200605_142311.jpg", - "src": "/resources/howtos/cut-out-shapes-out-of-plastic-sheets-with-a-cnc-/IMG_20200605_142311.jpg", - "alt": "IMG_20200605_142311.jpg" - } - ] - } - ], - "moderation": "accepted", - "user": { - "_modified": "2024-01-08T13:28:33.484Z", - "_id": "gus-merckel", - "subType": "mix", - "moderation": "accepted", - "_deleted": false, - "verified": false, - "type": "workspace", - "location": { - "lat": 19.3935, - "lng": -99.1656 - }, - "_created": "2024-01-08T13:28:33.484Z", - "geo": { - "latitude": 19.3935, - "lookupSource": "coordinates", - "longitude": -99.1656, - "localityLanguageRequested": "en", - "continent": "North America", - "continentCode": "NA", - "countryName": "Mexico", - "countryCode": "MX", - "principalSubdivision": "Ciudad de Mexico", - "principalSubdivisionCode": "MX-CMX", - "city": "Mexico City", - "locality": "Benito Juarez", - "postcode": "03103", - "plusCode": "76F29RVM+CQ", - "localityInfo": { - "administrative": [ - { - "name": "Mexico", - "description": "country in North America", - "isoName": "Mexico", - "order": 2, - "adminLevel": 2, - "isoCode": "MX", - "wikidataId": "Q96", - "geonameId": 3996063 - }, - { - "name": "Mexico City", - "description": "capital and largest city of Mexico", - "order": 5, - "adminLevel": 4, - "wikidataId": "Q1489", - "geonameId": 3530597 - }, - { - "name": "Ciudad de Mexico", - "description": "capital and largest city of Mexico", - "isoName": "Ciudad de Mexico", - "order": 6, - "adminLevel": 4, - "isoCode": "MX-CMX", - "wikidataId": "Q1489", - "geonameId": 3527646 - }, - { - "name": "Benito Juarez", - "description": "territorial demarcation of the Mexico City in Mexico", - "order": 7, - "adminLevel": 6, - "wikidataId": "Q2356998", - "geonameId": 3827406 - } - ], - "informative": [ - { - "name": "North America", - "description": "continent and northern subcontinent of the Americas", - "isoName": "North America", - "order": 1, - "isoCode": "NA", - "wikidataId": "Q49", - "geonameId": 6255149 - }, - { - "name": "America/Mexico_City", - "description": "time zone", - "order": 3 - }, - { - "name": "Greater Mexico City", - "description": "geographical object", - "order": 4, - "wikidataId": "Q665894" - }, - { - "name": "03103", - "description": "postal code", - "order": 8 - } - ] - } - }, - "data": { - "urls": [ - { - "name": "Email", - "url": "mailto:gustavomerckel@gmail.com" - }, - { - "name": "Facebook", - "url": "https://www.facebook.com/pl%c3%a1stico-chido-110888520718193" - }, - { - "name": "sponsor the work", - "url": "https://www.patreon.com/one_army" - } - ], - "description": "Plástico Chido builds and modifies the PP machines, and also experiments with CNC and Laser Cut", - "services": [ - { - "welding": false, - "assembling": false, - "machining": false, - "electronics": false, - "molds": false - } - ], - "title": "Plástico Chido", - "images": [] - }, - "detail": { - "services": [], - "urls": [] - } - }, - "category": { - "label": "uncategorized" - } -} \ No newline at end of file diff --git a/src/model/json-ld-howto.ts b/src/model/json-ld-howto.ts new file mode 100644 index 0000000..805f506 --- /dev/null +++ b/src/model/json-ld-howto.ts @@ -0,0 +1,94 @@ +import { IHowto } from './howto-model.js'; + +export const toJSONLD = (howto: IHowto) => { + const jsonLD: any = { + "@context": "https://schema.org", + "@type": "HowTo", + "name": howto.title, + "description": howto.description, + }; + + if (howto.cover_image?.downloadUrl) { + jsonLD.image = howto.cover_image.downloadUrl; + } + + if (howto.time) { + let duration = ""; + const timeMatch = howto.time.match(/(\d+)\s*(hour|minute|second|day|week|month|year)/i); + if (timeMatch) { + const value = parseInt(timeMatch[1], 10); + const unit = timeMatch[2].toLowerCase(); + + switch(unit) { + case 'hour': duration = `PT${value}H`; break; + case 'minute': duration = `PT${value}M`; break; + case 'second': duration = `PT${value}S`; break; + case 'day': duration = `P${value}D`; break; + case 'week': duration = `P${value}W`; break; + case 'month': duration = `P${value}M`; break; + case 'year': duration = `P${value}Y`; break; + } + } else if (howto.time.includes('<')) { + const lessTimeMatch = howto.time.match(/< (\d+)\s*(hour|minute|second|day|week|month|year)/i); + if (lessTimeMatch) { + const value = parseInt(lessTimeMatch[1], 10); + const unit = lessTimeMatch[2].toLowerCase(); + + switch(unit) { + case 'hour': duration = `PT${value - 1}H`; break; + case 'minute': duration = `PT${value - 1}M`; break; + case 'second': duration = `PT${value - 1}S`; break; + case 'day': duration = `P${value - 1}D`; break; + case 'week': duration = `P${value - 1}W`; break; + case 'month': duration = `P${value - 1}M`; break; + case 'year': duration = `P${value - 1}Y`; break; + } + } + } + + if (duration) { + jsonLD.totalTime = duration; + } + } + + if (howto.difficulty_level) { + jsonLD.difficultyLevel = howto.difficulty_level; + } + + if (howto.category?.label) { + jsonLD.category = howto.category.label; + } + + if (howto.tags && howto.tags.length > 0) { + const keywords = howto.tags.map(tag => + typeof tag === 'string' ? tag : tag?.label || '' + ).filter(Boolean).join(', '); + + if (keywords) { + jsonLD.keywords = keywords; + } + } + + if (howto.steps && howto.steps.length > 0) { + jsonLD.step = howto.steps.map((step, index) => { + const stepObj: any = { + "@type": "HowToStep", + "name": step.title, + "text": step.text, + "position": index + 1 + }; + + if (step.images && step.images.length > 0) { + if (step.images.length === 1) { + stepObj.image = step.images[0].downloadUrl; + } else { + stepObj.image = step.images.map(image => image.downloadUrl); + } + } + + return stepObj; + }); + } + + return jsonLD; +}; \ No newline at end of file diff --git a/src/model/todos-workflow.md b/src/model/todos-workflow.md new file mode 100644 index 0000000..9676743 --- /dev/null +++ b/src/model/todos-workflow.md @@ -0,0 +1,9 @@ +## Todos + +- we use Typescript, ESM, minimal dependencies, functional programming +- dont document or comment, return just the code, ready to go +- no additional dependencies + +## Todos + +- [ ] function, to convert a howto (howto-model.ts) to json-ld structured data, satisfying Google, "toJSON-LD"