kbot lazy

This commit is contained in:
lovebird 2025-03-23 01:06:53 +01:00
parent 90a2b1c907
commit 529d754e0a
4 changed files with 189 additions and 59 deletions

View File

@ -1,9 +1,9 @@
{
"model": "gpt-4o-mini",
"model": "google/gemini-exp-1206:free",
"messages": [
{
"role": "user",
"content": "use a formal tone\nspell check the text, fix any errors\nremove emojis\nremove personal preferences or biases\nshorten text if possible but preserve personality\nremove references to preciousplastic, bazar and Discord\nremove any brain/green washing as well suggestions not related to the craft\nContext: howto tutorials, for makers\ndont comment just return as Markdown : An experience review designing a broom hanger mold: These are some steps to design a mold for the injection machine."
"content": "Return a list of useful references (only with links), as Markdown, grouped : Articles, Books, Papers, Youtube, Opensource Designs, ... Dont comment ! : Injection mold design, broom hanger mold, plastic injection, mold making, CAD mold design, DIY injection mold, injection molding process, mold design criteria, plastic product design, custom mold design\n"
},
{
"role": "user",

View File

@ -2,6 +2,7 @@ import { sync as read } from "@polymech/fs/read"
import { sync as exists } from "@polymech/fs/exists"
import { run, OptionsSchema, IKBotTask } from "@polymech/kbot-d";
import { createMarkdownComponent } from "@/base/index.js";
import { filters } from "@/model/howto.js";
export interface Props extends IKBotTask {
language?: string;
@ -12,6 +13,10 @@ export interface Props extends IKBotTask {
renderer?: string;
}
/////////////////////////////////////////////////////////////
//
// Templates
export const template_simple = () => {
return {
router: "openai",
@ -21,6 +26,27 @@ export const template_simple = () => {
};
}
export const keywords_simple = () => {
return {
_router: "openai",
model: "google/gemini-exp-1206:free",
preferences: "none",
mode: "completion",
prompt: "Return a list of max. 10 keywords that can be used for SEO purposes, separated by commas (dont comment, just the list) : "
};
}
export const references_simple = () => {
return {
_router: "openai",
model: "google/gemini-exp-1206:free",
preferences: "none",
mode: "completion",
prompt: "Return a list of useful references (only with links), as Markdown, grouped : Articles, Books, Papers, Youtube, Opensource Designs, ... Dont comment !",
filters: 'code'
};
}
export const template_code_simple = () => {
return {
preferences: "none",
@ -36,6 +62,11 @@ export const template_research = () => {
mode: "completion",
}
}
/////////////////////////////////////////////////////////////
//
// Filters
export enum ToneFlags {
None = 0,
Formal = 1,
@ -128,7 +159,7 @@ export function templateLanguage(
model: "gpt-4o-mini",
preferences: "none",
mode: "completion",
prompt,
prompt,
filters: "code"
}
}
@ -137,7 +168,9 @@ export const templates = {
simple: template_simple,
code_simple: template_code_simple,
research: template_research,
howto: templateLanguage
howto: templateLanguage,
keywords: keywords_simple,
references: references_simple
}
export const filter = async (content: string, tpl: string = 'howto', opts: any = {}) => {
@ -153,4 +186,40 @@ export const filter = async (content: string, tpl: string = 'howto', opts: any =
let result: string | unknown[] = [];
result = await run(options);
return result[0] as string;
}
/////////////////////////////////////////////////////////////
//
// Completion
export const extract = async (content: string, tpl: string = 'keywords', opts: any = {}) => {
if (!content || content.length < 20 || templates[tpl] === undefined) {
return content
}
const template = templates[tpl]();
const options = OptionsSchema().parse({
...template,
prompt: `${template.prompt || ""} : ${content}`,
...opts,
});
let result: string | unknown[] = [];
result = await run(options);
return result[0] as string;
}
/////////////////////////////////////////////////////////////
//
// Completion
export const references = async (content: string, tpl: string = 'references', opts: any = {}) => {
if (!content || content.length < 20 || templates[tpl] === undefined) {
return content
}
const template = templates[tpl]();
const options = OptionsSchema().parse({
...template,
prompt: `${template.prompt || ""} : ${content}`,
...opts,
});
let result: string | unknown[] = [];
result = await run(options);
return result[0] as string;
}

View File

@ -1,7 +1,7 @@
---
import fs from "fs";
import path from "path";
import { decode } from 'html-entities'
import { decode } from "html-entities";
import { IHowto, asset_local_rel } from "@/model/howto.js";
import { Img } from "imagetools/components";
import { i18n as Translate } from "@polymech/astro-base";
@ -12,9 +12,10 @@ import { files, forward_slash } from "@polymech/commons";
import pMap from "p-map";
import { sync as exists } from "@polymech/fs/exists";
import { sync as read } from "@polymech/fs/read";
import { createMarkdownComponent } from "@/base/index.js";
import { createHTMLComponent, createMarkdownComponent } from "@/base/index.js";
import { translate } from "@/base/i18n.js";
import { applyFilters, shortenUrl } from "@/model/howto.js";
import { extract, references } from "@/base/kbot.js";
import {
HOWTO_FILES_WEB,
HOWTO_FILES_ABS,
@ -28,7 +29,7 @@ const { howto } = Astro.props;
const howto_abs = HOWTO_FILES_ABS(howto.slug);
const howto_resources_default = `# Resources`;
const howto_resources_path = path.join(howto_abs, "resources.md");
const howto_resources = exists(howto_resources_path)
let howto_resources = exists(howto_resources_path)
? read(howto_resources_path, "string") || howto_resources_default
: howto_resources_default;
@ -36,15 +37,19 @@ let model_files: any = [...files(howto_abs, "**/**/*.(step|stp)")];
model_files = model_files.map((f) =>
forward_slash(`${howto.slug}/${path.relative(path.resolve(howto_abs), f)}`),
);
const content = async (str: string) =>{
const content = async (str: string) => {
const ret = await translate(
await applyFilters(str),
I18N_SOURCE_LANGUAGE,
Astro.currentLocale,
);
return ret
}
const component = async (str: string) => await createMarkdownComponent(await content(str))
return ret;
};
const component = async (str: string) =>
await createMarkdownComponent(await content(str));
const componentHTML = async (str: string) =>
await createHTMLComponent(await content(str));
const stepsWithFilteredMarkdown = await pMap(
howto.steps,
@ -56,16 +61,21 @@ const stepsWithFilteredMarkdown = await pMap(
);
const Description = component(howto.description);
const Resources = component(howto_resources);
const authorGeo = howto?.user?.geo || {
countryName: "Unknown",
data: { urls: [] },
};
const authorLinks = (howto?.user?.data.urls || []).filter(
(l) => !l.url.includes("one_army") && !l.url.includes("bazar")
(l) => !l.url.includes("one_army") && !l.url.includes("bazar"),
);
//////////////////////////////////////////////////////////////
// Add references from the extracted keywords
const contentAll = `${howto.title} \n Description : ${howto.description} \n Steps: ${howto.steps.map((s) => s.text).join("\n")} \n`;
const keywords = await extract(contentAll) as string;
const references_extra = await references(keywords);
howto_resources = `${howto_resources}\n${references_extra}`;
const Resources = component(howto_resources);
---
<BaseLayout class="markdown-content bg-gray-100">
@ -75,7 +85,7 @@ const authorLinks = (howto?.user?.data.urls || []).filter(
<h1 class="text-4xl font-bold text-gray-800 mb-4">
<Translate>{howto.title}</Translate>
</h1>
<GalleryK images={[{ src: howto.cover_image.src, alt:'' }]} />
<GalleryK images={[{ src: howto.cover_image.src, alt: "" }]} />
<div class="flex gap-2 mb-4">
{
howto.tags.map((tag) => (
@ -105,25 +115,44 @@ const authorLinks = (howto?.user?.data.urls || []).filter(
<strong><Translate>Creator:</Translate></strong>{howto._createdBy}
</li>
<li>
<strong><Translate>Country:</Translate></strong>{authorGeo.countryName}
<strong><Translate>Country:</Translate></strong>{
authorGeo.countryName
}
</li>
<li>
<strong><Translate>Email:</Translate></strong>
<a class="text-orange-600 underline" href={`mailto:${authorLinks.find((link)=>link.name.toLowerCase()==="email")?.url.replace("mailto:","")}`}>
{authorLinks.find((link)=>link.name.toLowerCase()==="email")?.url.replace("mailto:","")}
<a
class="text-orange-600 underline"
href={`mailto:${authorLinks.find((link) => link.name.toLowerCase() === "email")?.url.replace("mailto:", "")}`}
>
{
authorLinks
.find((link) => link.name.toLowerCase() === "email")
?.url.replace("mailto:", "")
}
</a>
</li>
{
authorLinks.filter((l)=>l.name.toLowerCase()!=="email").map((link)=>(
<li>
<strong>{link.name}:</strong>
<a class="text-orange-600 underline" href={link.url} target="_blank">
{shortenUrl(link.url)}
</a>
</li>
))
authorLinks
.filter((l) => l.name.toLowerCase() !== "email")
.map((link) => (
<li>
<strong>{link.name}:</strong>
<a
class="text-orange-600 underline"
href={link.url}
target="_blank"
>
{shortenUrl(link.url)}
</a>
</li>
))
}
<li><strong><Translate>Downloads:</Translate></strong>{howto.total_downloads}</li>
<li>
<strong><Translate>Downloads:</Translate></strong>{
howto.total_downloads
}
</li>
</ul>
</section>
@ -131,41 +160,64 @@ const authorLinks = (howto?.user?.data.urls || []).filter(
<div class="mb-8 markdown-content">
<Description />
</div>
<a href={HOWTO_FILES_WEB(howto.slug)} class="inline-block py-2 px-4 bg-orange-600 hover:bg-orange-700 text-white rounded-full mb-8"><Translate>Browse Files</Translate></a>
<a
href={HOWTO_FILES_WEB(howto.slug)}
class="inline-block py-2 px-4 bg-orange-600 hover:bg-orange-700 text-white rounded-full mb-8"
><Translate>Browse Files</Translate></a
>
</section>
<section class="px-8 py-8 bg-orange-50">
<h2 class="font-bold mb-4 text-xl"><Translate>Table of Contents</Translate></h2>
<h2 class="font-bold mb-4 text-xl">
<Translate>Table of Contents</Translate>
</h2>
<ul class="grid grid-cols-1 md:grid-cols-2 gap-2 list-decimal p-4">
{stepsWithFilteredMarkdown.map((step, idx)=>(
<li>
<a href={`#step-${idx+1}`} class="text-orange-600 hover:underline">
<Translate>{step.title}</Translate>
</a>
</li>
))}
{
stepsWithFilteredMarkdown.map((step, idx) => (
<li>
<a
href={`#step-${idx + 1}`}
class="text-orange-600 hover:underline"
>
<Translate>{step.title}</Translate>
</a>
</li>
))
}
</ul>
</section>
<section class="border-t border-gray-300 bg-gray-50 p-8">
<h2 class="text-2xl font-bold text-gray-800 mb-6"><Translate>Steps</Translate></h2>
<h2 class="text-2xl font-bold text-gray-800 mb-6">
<Translate>Steps</Translate>
</h2>
<ol class="space-y-10">
{stepsWithFilteredMarkdown.map((step, idx) => (
<li id={`step-${idx + 1}`} class="bg-white shadow-sm rounded-lg p-6">
<div class="mb-4 flex items-center">
<span class="bg-orange-600 text-xl font-bold text-white rounded-full h-10 w-10 flex items-center justify-center mr-3">
{idx + 1}
</span>
<h3 class="text-xl font-bold">
<a href={`#step-${idx + 1}`} class="text-orange-600 hover:underline"><Translate>{step.title}</Translate></a>
</h3>
</div>
<div class="markdown-content">
<step.filteredMarkdownComponent />
</div>
{step.images?.length > 0 && <GalleryK images={step.images} />}
</li>
))}
{
stepsWithFilteredMarkdown.map((step, idx) => (
<li
id={`step-${idx + 1}`}
class="bg-white shadow-sm rounded-lg p-6"
>
<div class="mb-4 flex items-center">
<span class="bg-orange-600 text-xl font-bold text-white rounded-full h-10 w-10 flex items-center justify-center mr-3">
{idx + 1}
</span>
<h3 class="text-xl font-bold">
<a
href={`#step-${idx + 1}`}
class="text-orange-600 hover:underline"
>
<Translate>{step.title}</Translate>
</a>
</h3>
</div>
<div class="markdown-content">
<step.filteredMarkdownComponent />
</div>
{step.images?.length > 0 && <GalleryK images={step.images} />}
</li>
))
}
</ol>
</section>
@ -173,9 +225,16 @@ const authorLinks = (howto?.user?.data.urls || []).filter(
<footer class="p-8 text-sm border-t bg-white text-gray-600">
<div class="flex justify-between">
<span><Translate>Created on</Translate>: {new Date(howto._created).toLocaleDateString()}</span>
<span>{howto.votedUsefulBy.length} <Translate>people found this useful</Translate></span>
<span
><Translate>Created on</Translate>: {
new Date(howto._created).toLocaleDateString()
}</span
>
<span
>{howto.votedUsefulBy.length}
<Translate>people found this useful</Translate></span
>
</div>
</footer>
</Wrapper>
</BaseLayout>
</BaseLayout>

View File

@ -78,6 +78,7 @@ export const asset_local_rel = async (item: IHowto, asset: Image) => {
}
return default_image().src
}
export const howtos = async () => {
const src = HOWTO_MIGRATION()
const data = read(src, 'json') as any;
@ -99,11 +100,9 @@ export const howtos = async () => {
label: 'uncategorized'
}
})
howtos = howtos.filter((h) => {
return h.steps.length > 0 && !blacklist.includes(h._createdBy);
});
return howtos
}
@ -194,9 +193,11 @@ export function loader(): Loader {
load
};
}
////////////////////////////////
//
// Utils
// Filters
const urlBlacklist = ["thenounproject.com", "preciousplastic.com"];
const bannedWords = ["wizard", "magic2"];
const wordReplaceMap: Record<string, string> = {
@ -252,6 +253,7 @@ export async function applyFilters(text: string): Promise<string> {
}
return filtered;
}
////////////////////////////////
//
// Interfaces - Old