generated from polymech/site-template
annotations :)
This commit is contained in:
parent
f785d20ca1
commit
d1cc8a4ea2
@ -23,7 +23,7 @@
|
||||
"format": "unix-time"
|
||||
}
|
||||
],
|
||||
"default": "2025-03-23T12:56:20.237Z"
|
||||
"default": "2025-03-23T22:18:10.055Z"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -1,5 +1,5 @@
|
||||
{
|
||||
"_variables": {
|
||||
"lastUpdateCheck": 1741718719942
|
||||
"lastUpdateCheck": 1742758032817
|
||||
}
|
||||
}
|
||||
53
package-lock.json
generated
53
package-lock.json
generated
@ -43,6 +43,7 @@
|
||||
"got": "^14.4.6",
|
||||
"html-entities": "^2.5.2",
|
||||
"imagetools": "file:../astro-components/packages/imagetools",
|
||||
"jsonpath-plus": "^10.3.0",
|
||||
"lighthouse": "^12.3.0",
|
||||
"markdown-it": "^14.1.0",
|
||||
"marked": "^15.0.7",
|
||||
@ -120,6 +121,7 @@
|
||||
"@polymech/i18n": "file:../../../polymech-mono/packages/i18n",
|
||||
"@polymech/kbot-d": "file:../../../polymech-mono/packages/kbot",
|
||||
"@polymech/log": "file:../../../polymech-mono/packages/log",
|
||||
"html-entities": "^2.5.2",
|
||||
"react-jsx-parser": "^2.4.0"
|
||||
}
|
||||
},
|
||||
@ -2330,6 +2332,30 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@jsep-plugin/assignment": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@jsep-plugin/assignment/-/assignment-1.3.0.tgz",
|
||||
"integrity": "sha512-VVgV+CXrhbMI3aSusQyclHkenWSAm95WaiKrMxRFam3JSUiIaQjoMIw2sEs/OX4XifnqeQUN4DYbJjlA8EfktQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 10.16.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"jsep": "^0.4.0||^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jsep-plugin/regex": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@jsep-plugin/regex/-/regex-1.0.4.tgz",
|
||||
"integrity": "sha512-q7qL4Mgjs1vByCaTnDFcBnV9HS7GVPJX5vyVoCgZHNSC9rjwIlmbXG5sUuorR5ndfHAIlJ8pVStxvjXHbNvtUg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 10.16.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"jsep": "^0.4.0||^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@mdx-js/mdx": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.1.0.tgz",
|
||||
@ -8163,6 +8189,15 @@
|
||||
"integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/jsep": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/jsep/-/jsep-1.4.0.tgz",
|
||||
"integrity": "sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 10.16.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jsesc": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
|
||||
@ -8205,6 +8240,24 @@
|
||||
"graceful-fs": "^4.1.6"
|
||||
}
|
||||
},
|
||||
"node_modules/jsonpath-plus": {
|
||||
"version": "10.3.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-10.3.0.tgz",
|
||||
"integrity": "sha512-8TNmfeTCk2Le33A3vRRwtuworG/L5RrgMvdjhKZxvyShO+mBu2fP50OWUjRLNtvw344DdDarFh9buFAZs5ujeA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jsep-plugin/assignment": "^1.3.0",
|
||||
"@jsep-plugin/regex": "^1.0.4",
|
||||
"jsep": "^1.4.0"
|
||||
},
|
||||
"bin": {
|
||||
"jsonpath": "bin/jsonpath-cli.js",
|
||||
"jsonpath-plus": "bin/jsonpath-cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/keyv": {
|
||||
"version": "4.5.4",
|
||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
||||
|
||||
@ -53,6 +53,7 @@
|
||||
"got": "^14.4.6",
|
||||
"html-entities": "^2.5.2",
|
||||
"imagetools": "file:../astro-components/packages/imagetools",
|
||||
"jsonpath-plus": "^10.3.0",
|
||||
"lighthouse": "^12.3.0",
|
||||
"markdown-it": "^14.1.0",
|
||||
"marked": "^15.0.7",
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
228
src/model/annotate-example.ts
Normal file
228
src/model/annotate-example.ts
Normal file
@ -0,0 +1,228 @@
|
||||
import { JSONPath } from 'jsonpath-plus';
|
||||
import { IHowto } from './howto-model.js';
|
||||
import { IAnnotation, AnnotationMode, createDescriptionAnnotation, createStepAnnotationByIndex, createStepAnnotationByTitle } from './annotation.js';
|
||||
|
||||
type StoredAnnotations = Map<string, IAnnotation[]>;
|
||||
|
||||
const annotationStore: StoredAnnotations = new Map();
|
||||
|
||||
/**
|
||||
* Adds an annotation to the store
|
||||
* @param annotation The annotation to add
|
||||
*/
|
||||
export function addAnnotation(annotation: IAnnotation): void {
|
||||
const howtoId = annotation.howtoId;
|
||||
const existingAnnotations = annotationStore.get(howtoId) || [];
|
||||
annotationStore.set(howtoId, [...existingAnnotations, annotation]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find annotations for a specific howto ID
|
||||
* @param howtoId The howto id (slug) to find annotations for
|
||||
* @returns Array of annotations for the specified howto
|
||||
*/
|
||||
export function findAnnotationsForHowto(howtoId: string): IAnnotation[] {
|
||||
return annotationStore.get(howtoId) || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply annotations to a howto object
|
||||
* @param howto The howto object to apply annotations to
|
||||
* @param annotations The annotations to apply
|
||||
* @returns A new howto object with applied annotations
|
||||
*/
|
||||
export function applyAnnotationsToHowto(howto: unknown, annotations: IAnnotation[]): unknown {
|
||||
// Deep copy to avoid mutating the original
|
||||
const modifiedHowto = JSON.parse(JSON.stringify(howto));
|
||||
|
||||
// Filter and sort enabled annotations (if multiple annotations for the same target, apply in order)
|
||||
const enabledAnnotations = annotations
|
||||
.filter(a => a.enabled)
|
||||
.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime());
|
||||
|
||||
// Apply each annotation
|
||||
for (const annotation of enabledAnnotations) {
|
||||
try {
|
||||
const jsonpath = new JSONPath({});
|
||||
const nodes = jsonpath.nodes(modifiedHowto, annotation.target);
|
||||
if (nodes && nodes.length > 0) {
|
||||
for (const node of nodes) {
|
||||
// Apply the annotation content based on the mode
|
||||
const originalValue = node.value;
|
||||
const content = annotation.content.toString();
|
||||
|
||||
switch (annotation.mode) {
|
||||
case AnnotationMode.REPLACE:
|
||||
node.value = content;
|
||||
break;
|
||||
case AnnotationMode.PREPEND:
|
||||
node.value = content + originalValue;
|
||||
break;
|
||||
case AnnotationMode.APPEND:
|
||||
node.value = originalValue + content;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error applying annotation with target ${annotation.target}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
return modifiedHowto;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the annotated version of a howto
|
||||
* @param howto The howto object to annotate
|
||||
* @returns A new howto object with all applicable annotations applied
|
||||
*/
|
||||
export function getAnnotatedHowto(howto: IHowto): IHowto {
|
||||
const annotations = findAnnotationsForHowto(howto.slug);
|
||||
return applyAnnotationsToHowto(howto, annotations) as IHowto;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Example usage
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
|
||||
/**
|
||||
* Example function to demonstrate annotation usage with a sample howto
|
||||
*/
|
||||
export function runAnnotationExample(): void {
|
||||
// Example howto object (partial, with essential fields)
|
||||
const sampleHowto: IHowto = {
|
||||
_id: '038gjWgLjiyYknbEjDeI',
|
||||
_createdBy: 'gus-merckel',
|
||||
slug: 'cut-out-shapes-out-of-plastic-sheets-with-a-cnc-',
|
||||
_modified: '2023-10-27T18:09:36.519Z',
|
||||
_created: '2023-08-23T18:20:09.098Z',
|
||||
title: 'Cut out shapes out of plastic sheets with a CNC',
|
||||
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 ',
|
||||
_contentModifiedTimestamp: '2023-08-23T18:20:09.098Z',
|
||||
moderation: 'accepted',
|
||||
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',
|
||||
_animationKey: 'unique1',
|
||||
images: []
|
||||
},
|
||||
{
|
||||
title: 'Secure sheet',
|
||||
text: 'Using the CNC clamps from the X-Carve, secure the sheet to the table, ',
|
||||
_animationKey: 'unique2',
|
||||
images: []
|
||||
},
|
||||
{
|
||||
title: 'Follow the cutting Wizzard',
|
||||
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!!',
|
||||
_animationKey: 'uniquenisc2v',
|
||||
images: []
|
||||
}
|
||||
],
|
||||
comments: [],
|
||||
mentions: [],
|
||||
_deleted: false,
|
||||
version: '1.0.0',
|
||||
difficulty_level: 'Medium',
|
||||
category: {} as any,
|
||||
cover_image: {} as any,
|
||||
files: [],
|
||||
tags: [],
|
||||
time: '< 5 hours',
|
||||
votedUsefulBy: [],
|
||||
previousSlugs: [],
|
||||
total_views: 232,
|
||||
fileLink: '',
|
||||
creatorCountry: 'mx',
|
||||
total_downloads: 0,
|
||||
moderatorFeedback: '',
|
||||
user: undefined
|
||||
};
|
||||
|
||||
console.log('Original Howto:');
|
||||
console.log(`Title: ${sampleHowto.title}`);
|
||||
console.log(`Description: ${sampleHowto.description}`);
|
||||
console.log(`Step 1: ${sampleHowto.steps[0].title} - ${sampleHowto.steps[0].text.substring(0, 50)}...`);
|
||||
console.log();
|
||||
|
||||
// Create annotations
|
||||
|
||||
// 1. Annotate the main description
|
||||
const descriptionAnnotation = createDescriptionAnnotation(
|
||||
sampleHowto,
|
||||
'Lamu variant: This howto shows a simple method for cutting recycled HDPE plastic sheets using a CNC machine. Follow these steps to turn plastic waste into useful objects!',
|
||||
{
|
||||
source: 'Lamu AI',
|
||||
model: 'Mistral-7B',
|
||||
prompt: 'Rewrite this howto description to be more concise and emphasize recycling',
|
||||
title: 'Lamu variant of description',
|
||||
}
|
||||
);
|
||||
|
||||
// 2. Annotate the first step using index
|
||||
const firstStepAnnotation = createStepAnnotationByIndex(sampleHowto, 0,
|
||||
'Measure your plastic sheet carefully. You need to know the Height, Width, and Thickness. This information will be entered into EASEL - a user-friendly CAM software that works with the X-Carve CNC. EASEL has a great feature that lets you simulate the exact material you're using - including HDPE 2-color sheets!', {
|
||||
source: 'Lamu AI',
|
||||
model: 'GPT-4',
|
||||
mode: AnnotationMode.REPLACE
|
||||
}
|
||||
|
||||
|
||||
// 3. Annotate the 3rd step using title
|
||||
const thirdStepAnnotation = createStepAnnotationByTitle(sampleHowto, 'Follow the cutting Wizzard',
|
||||
'Configure your cutting parameters in the software. Select your desired cutting width and follow the setup process:\n\n1. Double-check that the plastic sheet is securely fastened to the table\n2. Install a 1/8" flat flute bit in the router\n3. Set the coordinate system: position the machine to the bottom left corner of your sheet and set this as the 0,0 position\n4. Raise the bit to safe height, power on the CNC router, and begin the cutting process!\n\nWatch carefully as the CNC precisely carves out your design.', {
|
||||
source: 'Lamu AI',
|
||||
model: 'GPT-4',
|
||||
mode: AnnotationMode.REPLACE,
|
||||
title: 'Restructured CNC wizzard steps'
|
||||
}
|
||||
);
|
||||
|
||||
// Add annotations to the store
|
||||
addAnnotation(descriptionAnnotation);
|
||||
addAnnotation(firstStepAnnotation);
|
||||
addAnnotation(thirdStepAnnotation);
|
||||
|
||||
// Get annotations for the howto
|
||||
const annotations = findAnnotationsForHowto(sampleHowto.slug);
|
||||
console.log(`Found ${annotations.length} annotations for this howto:`);
|
||||
for (const annotation of annotations) {
|
||||
console.log(` - ${annotation.title} (target: ${annotation.target})`);
|
||||
}
|
||||
console.log();
|
||||
|
||||
// Apply annotations to the howto
|
||||
const annotatedHowto = getAnnotatedHowto(sampleHowto);
|
||||
|
||||
// Display the result
|
||||
console.log('Annotated Howto:');
|
||||
console.log(`Title: ${annotatedHowto.title}`);
|
||||
console.log(`Description: ${annotatedHowto.description}`);
|
||||
console.log(`Step 1: ${annotatedHowto.steps[0].title} - ${annotatedHowto.steps[0].text.substring(0, 50)}...`);
|
||||
console.log(`Step 3: ${annotatedHowto.steps[2].title} - ${annotatedHowto.steps[2].text.substring(0, 50)}...`);
|
||||
console.log();
|
||||
|
||||
// Demonstrate applying different annotation modes
|
||||
const appendAnnotation = createStepAnnotationByIndex(sampleHowto, 1,
|
||||
'\n\nSAFETY TIP: Always wear gloves and eye protection when working with the CNC machine.', {
|
||||
source: 'Safety Team',
|
||||
mode: AnnotationMode.APPEND,
|
||||
title: 'Safety tip added to step 2'
|
||||
}
|
||||
);
|
||||
|
||||
addAnnotation(appendAnnotation);
|
||||
const annotatedHowto2 = getAnnotatedHowto(sampleHowto);
|
||||
|
||||
console.log('After adding appended annotation (Step 2):');
|
||||
console.log(`Step 2: ${annotatedHowto2.steps[1].title} - ${annotatedHowto2.steps[1].text}`);
|
||||
|
||||
|
||||
// If running this file directly
|
||||
if (require.main === module) {
|
||||
console.log('Running annotation example...');
|
||||
runAnnotationExample();
|
||||
}
|
||||
135
src/model/annotation.ts
Normal file
135
src/model/annotation.ts
Normal file
@ -0,0 +1,135 @@
|
||||
import { IHowto } from './howto-model.js';
|
||||
|
||||
/**
|
||||
* Annotation mode defines how the annotation should be applied to the target content
|
||||
*/
|
||||
export enum AnnotationMode {
|
||||
REPLACE = 'replace',
|
||||
PREPEND = 'prepend',
|
||||
APPEND = 'append',
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for Howto annotations.
|
||||
* Annotations can provide alternative content for any part of IHowto objects
|
||||
* using jsonpath-plus selectors to target specific fields.
|
||||
*/
|
||||
export interface IAnnotation {
|
||||
/** JSONPath-plus string targeting any field in IHowto or its steps */
|
||||
target: string;
|
||||
/** Source of the annotation (author name or AI system) */
|
||||
source: string;
|
||||
/** Timestamp when the annotation was created */
|
||||
date: string; // ISO string timestamp
|
||||
/** AI model used (if AI generated) */
|
||||
model?: string;
|
||||
/** Prompt used to generate the content (if AI generated) */
|
||||
prompt?: string;
|
||||
/** The alternate content */
|
||||
content: string | Buffer;
|
||||
/** Whether the annotation is enabled */
|
||||
enabled: boolean;
|
||||
/** Version of the annotation */
|
||||
version: string;
|
||||
/** Application mode for the annotation */
|
||||
mode: AnnotationMode;
|
||||
/** Title/description of the annotation */
|
||||
title: string;
|
||||
/** ID of the howto the annotation is associated with */
|
||||
howtoId: string;
|
||||
/** Unique identifier for the annotation */
|
||||
id?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a default annotation with common settings and overrides
|
||||
* @param overrides - Optional properties to override defaults
|
||||
* @returns A new IAnnotation object with defaults applied
|
||||
*/
|
||||
export function createDefaultAnnotation(overrides?: Partial<IAnnotation>): IAnnotation {
|
||||
const now = new Date().toISOString();
|
||||
const id = `annotation_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||
|
||||
return {
|
||||
target: '$', // root jsonPath by default
|
||||
source: 'un-attributed',
|
||||
date: now,
|
||||
content: '',
|
||||
enabled: true,
|
||||
version: '1.0.0', // default version is 1.0.0
|
||||
mode: AnnotationMode.REPLACE,
|
||||
title: 'Untitled Annotation',
|
||||
howtoId: '', // Needs to be specified
|
||||
id,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an annotation for a Howto description
|
||||
* @param howto The howto object
|
||||
* @param content New description content
|
||||
* @param overrides Additional annotation properties to override
|
||||
* @returns A new IAnnotation object targeting the howto description
|
||||
*/
|
||||
export function createDescriptionAnnotation(howto: IHowto, content: string, overrides?: Partial<IAnnotation>): IAnnotation {
|
||||
return createDefaultAnnotation({
|
||||
target: '$.description',
|
||||
content,
|
||||
title: `Description override for ${howto.title}`,
|
||||
howtoId: howto.slug,
|
||||
...overrides
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an annotation for a specific step by index
|
||||
* @param howto The howto object
|
||||
* @param stepIndex Index of the step to target (0-based)
|
||||
* @param content New step description content
|
||||
* @param overrides Additional annotation properties to override
|
||||
* @returns A new IAnnotation object targeting the specified step
|
||||
*/
|
||||
export function createStepAnnotationByIndex(
|
||||
howto: IHowto,
|
||||
stepIndex: number,
|
||||
content: string,
|
||||
overrides?: Partial<IAnnotation>
|
||||
): IAnnotation {
|
||||
if (stepIndex < 0 || stepIndex >= howto.steps.length) {
|
||||
throw new Error(`Invalid step index: ${stepIndex}. Maximum index is ${howto.steps.length - 1}`);
|
||||
}
|
||||
|
||||
const stepTitle = howto.steps[stepIndex].title;
|
||||
|
||||
return createDefaultAnnotation({
|
||||
target: `$.steps[${stepIndex}].text`,
|
||||
content,
|
||||
title: `Step ${stepIndex + 1} (${stepTitle}) override for ${howto.title}`,
|
||||
howtoId: howto.slug,
|
||||
...overrides
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an annotation for a specific step by step title
|
||||
* @param howto The howto object
|
||||
* @param stepTitle Title of the step to target
|
||||
* @param content New step description content
|
||||
* @param overrides Additional annotation properties to override
|
||||
* @returns A new IAnnotation object targeting the specified step
|
||||
*/
|
||||
export function createStepAnnotationByTitle(
|
||||
howto: IHowto,
|
||||
stepTitle: string,
|
||||
content: string,
|
||||
overrides?: Partial<IAnnotation>
|
||||
): IAnnotation {
|
||||
const stepIndex = howto.steps.findIndex(step => step.title.trim() === stepTitle.trim());
|
||||
|
||||
if (stepIndex === -1) {
|
||||
throw new Error(`Step with title "${stepTitle}" not found in howto "${howto.title}"`);
|
||||
}
|
||||
|
||||
return createStepAnnotationByIndex(howto, stepIndex, content, overrides);
|
||||
}
|
||||
@ -1,6 +1,4 @@
|
||||
import { IHowto, IOACategory, IOATag, Step, Image } from './howto-model.js';
|
||||
|
||||
const ITEM_TYPE = 'howto';
|
||||
import { IHowto, ICategory, ITag, IStep, IImage } from './howto-model.js';
|
||||
|
||||
type VersionStatus = 'enabled' | 'under_review' | 'new' | 'discarded';
|
||||
type AuthorType = 'human' | 'ai';
|
||||
@ -10,7 +8,7 @@ type AlternativeId = string;
|
||||
/**
|
||||
* Extended Step interface that includes an enabled flag
|
||||
*/
|
||||
export interface ExtendedStep extends Step {
|
||||
export interface ExtendedStep extends IStep {
|
||||
enabled: boolean; // Controls whether the step is active and visible
|
||||
alternativeId?: AlternativeId; // ID for grouping alternative steps
|
||||
}
|
||||
@ -23,7 +21,7 @@ export interface OrderedStep extends ExtendedStep {
|
||||
}
|
||||
|
||||
/**
|
||||
* Alternative step group for organizing steps with different approaches
|
||||
* Alternative step group for organizing steps with differ`ent approaches
|
||||
*/
|
||||
export interface AlternativeStepGroup {
|
||||
id: AlternativeId;
|
||||
@ -49,15 +47,15 @@ export interface VersionMetadata {
|
||||
/**
|
||||
* A complete how-to version with metadata and ordered steps.
|
||||
*/
|
||||
export interface VersionedHowtoData {
|
||||
export interface VersionedHowtoData {
|
||||
metadata: VersionMetadata;
|
||||
title: string;
|
||||
description: string;
|
||||
tags?: IOATag[];
|
||||
category?: IOACategory;
|
||||
tags?: ITag[];
|
||||
category?: ICategory;
|
||||
difficulty_level?: string;
|
||||
time?: string;
|
||||
cover_image?: Image;
|
||||
cover_image?: IImage;
|
||||
steps: OrderedStep[];
|
||||
alternativeStepGroups?: AlternativeStepGroup[]; // New property for alternative steps
|
||||
files?: Array<{
|
||||
@ -240,7 +238,7 @@ export const utils = {
|
||||
}),
|
||||
|
||||
// Convert legacy steps to ordered ones with enabled flag
|
||||
convertToOrderedSteps: (steps: Step[]): OrderedStep[] =>
|
||||
convertToOrderedSteps: (steps: IStep[]): OrderedStep[] =>
|
||||
steps.map((step, index) => ({
|
||||
...step,
|
||||
order: index + 1,
|
||||
@ -248,7 +246,7 @@ export const utils = {
|
||||
})),
|
||||
|
||||
// Convert ordered steps back to legacy format (unordered)
|
||||
convertFromOrderedSteps: (steps: OrderedStep[]): Step[] =>
|
||||
convertFromOrderedSteps: (steps: OrderedStep[]): IStep[] =>
|
||||
[...steps]
|
||||
.filter(step => step.enabled) // Only include enabled steps
|
||||
.sort((a, b) => a.order - b.order)
|
||||
|
||||
@ -1,177 +1,126 @@
|
||||
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[]
|
||||
}
|
||||
// Existing IHowto interface with added version field
|
||||
export const ITEM_TYPE = 'howto'
|
||||
export interface ICoverImage {
|
||||
name: string
|
||||
downloadUrl: string
|
||||
type: string
|
||||
fullPath: string
|
||||
updated: string
|
||||
size: number
|
||||
timeCreated: string
|
||||
contentType: string
|
||||
}
|
||||
|
||||
export interface IStep {
|
||||
title: string
|
||||
text: string
|
||||
images: IImage[]
|
||||
_animationKey: string
|
||||
}
|
||||
export interface IImage {
|
||||
updated: string
|
||||
size: number
|
||||
fullPath: string
|
||||
timeCreated: string
|
||||
name: string
|
||||
downloadUrl: string
|
||||
contentType: string
|
||||
type: string
|
||||
src: string,
|
||||
alt: string
|
||||
}
|
||||
|
||||
/// Taxonomy
|
||||
export interface ICategory {
|
||||
_created: string
|
||||
_id: string
|
||||
_deleted: boolean
|
||||
label: string
|
||||
_modified: string
|
||||
}
|
||||
|
||||
export interface ITags {
|
||||
[key: string]: boolean
|
||||
}
|
||||
|
||||
export interface ITag {
|
||||
categories: string[]
|
||||
image: string
|
||||
_created: string
|
||||
_deleted: boolean
|
||||
label: string
|
||||
_createdBy: string
|
||||
_modified: string
|
||||
_id: string
|
||||
}
|
||||
|
||||
export interface IUser {
|
||||
_modified: string
|
||||
_id: string
|
||||
subType: string
|
||||
moderation: string
|
||||
_deleted: boolean
|
||||
verified: boolean
|
||||
type: string
|
||||
location: ILocation
|
||||
_created: string
|
||||
geo: IGeo
|
||||
data: any
|
||||
detail: any
|
||||
}
|
||||
|
||||
export interface ILocation {
|
||||
lat: number
|
||||
lng: number
|
||||
}
|
||||
|
||||
export interface IGeo {
|
||||
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: any
|
||||
}
|
||||
|
||||
// Extended IHowto interface with version field
|
||||
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: ICategory
|
||||
difficulty_level: string
|
||||
_id: string
|
||||
tags?: ITag[]
|
||||
total_views: number
|
||||
_contentModifiedTimestamp: string
|
||||
cover_image: IImage
|
||||
comments: any[]
|
||||
moderatorFeedback: string
|
||||
steps: IStep[]
|
||||
moderation: string
|
||||
user?: IUser
|
||||
// Added version field (semver)
|
||||
version?: string
|
||||
}
|
||||
@ -60,7 +60,7 @@ Versions are stored in separate files, allowing for clear version history and co
|
||||
* Individual version files (`v1.json`, `v2.json`)
|
||||
* A current version pointer
|
||||
|
||||
#Ys Suggestions Workflow
|
||||
### Suggestions Workflow
|
||||
|
||||
A key feature is the suggestions workflow, which allows:
|
||||
|
||||
@ -68,7 +68,6 @@ A key feature is the suggestions workflow, which allows:
|
||||
2. Editors to review suggestions and choose which to accept
|
||||
3. Tracking of suggestion metadata (who made it, when, based on which version)
|
||||
|
||||
|
||||
Suggestions follow this flow:
|
||||
|
||||
1. A user or AI creates a suggestion based on an existing version
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
kbotd --preferences ./todos.md \
|
||||
--include=./howto.ts \
|
||||
--include=./../base/kbot.ts \
|
||||
--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
|
||||
--model=anthropic/claude-3.7-sonnet \
|
||||
--model_=gpt-4o \
|
||||
--router_=openai
|
||||
|
||||
@ -5,7 +5,7 @@ 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, IOATag, ITEM_TYPE } from './howto-model.js';
|
||||
import { IHowto, IImage, ITag, ITEM_TYPE } from './howto-model.js';
|
||||
import type { Loader, LoaderContext } from 'astro/loaders'
|
||||
import { sanitizeFilename } from "@polymech/fs/utils"
|
||||
export * from './howto-model.js'
|
||||
@ -47,7 +47,7 @@ const download = async (url, outputPath) => {
|
||||
});
|
||||
}
|
||||
|
||||
export const asset_local_abs = async (item: IHowto, asset: Image) => {
|
||||
export const asset_local_abs = async (item: IHowto, asset: IImage) => {
|
||||
const sanitizedFilename = sanitizeFilename(asset.name)
|
||||
const asset_path = path.join(HOWTO_ROOT(), item.slug, sanitizedFilename)
|
||||
if (exists(asset_path)) {
|
||||
@ -109,7 +109,7 @@ export const downloadFiles = async (dst: string, howto: IHowto) => {
|
||||
}
|
||||
|
||||
|
||||
export const asset_local_rel = async (item: IHowto, asset: Image) => {
|
||||
export const asset_local_rel = async (item: IHowto, asset: IImage) => {
|
||||
const sanitizedFilename = sanitizeFilename(asset.name).toLowerCase()
|
||||
const asset_path = path.join(HOWTO_ROOT(), item.slug, sanitizedFilename)
|
||||
if (exists(asset_path)) {
|
||||
@ -128,7 +128,7 @@ export const howtos = async () => {
|
||||
howtos = howtos.filter((h) => h.moderation == 'accepted');
|
||||
const tags = data.v3_tags;
|
||||
howtos.forEach((howto: IHowto) => {
|
||||
const howtoTags: IOATag[] = []
|
||||
const howtoTags: ITag[] = []
|
||||
for (const ht in howto.tags) {
|
||||
const gt: any = tags.find((t) => t._id === ht) || { label: 'untagged' }
|
||||
gt && howtoTags.push(gt.label || "")
|
||||
|
||||
@ -1,13 +1,22 @@
|
||||
## Todos
|
||||
|
||||
- for Astro, tailwind
|
||||
- no react or additional dependencies
|
||||
- Typescript ESM
|
||||
|
||||
## Todos
|
||||
|
||||
Modify ../base/kbot.ts
|
||||
Create an annotation system, providing alternative content for IHowto (main description, step description)
|
||||
|
||||
- [ ] add new AI templates
|
||||
- [ ] extract required tools, hardware (complete content of an howto will be provided, to be passed to an AI, returning json data, choose the right structure)
|
||||
- [ ] extract required skills(complete content of an howto will be provided, to be passed to an AI, returning json data, choose the right structure)
|
||||
- [ ] learned/gained skills (complete content of an howto will be provided, to be passed to an AI, returning json data, choose the right structure)
|
||||
- [ ] modify ./howto-model.ts, extend IHowto for versioning, add field version (semver)
|
||||
- [ ] create a new interface in ./anotation.ts, and a factory creating defaults:
|
||||
- target:jsonpath-plus string, targeting any field in - IHowto, and its steps
|
||||
- source: authorname / ai system
|
||||
- date : timestamp
|
||||
- model : ai related setting
|
||||
- prompt : ai related setting, the source prompt
|
||||
- content : the alternate content, could be string or binary
|
||||
- enabled: boolean
|
||||
- version: string
|
||||
- mode: replace, prepend, append
|
||||
- title: eg: Lamu variant :)
|
||||
- [ ] create working example code in ./annotate-example.ts : override howto description via jsonpath, and same for step descriptions, by array index and title
|
||||
- [ ] find annotions for a given howto (id=slug)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user