@farming-labs/theme 0.1.19 → 0.1.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/docs-api.d.mts +3 -1
- package/dist/docs-api.mjs +241 -3
- package/dist/docs-layout.mjs +2 -1
- package/dist/tanstack-layout.mjs +2 -1
- package/package.json +2 -2
package/dist/docs-api.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ChangelogConfig, DocsI18nConfig, DocsMcpConfig, DocsSearchConfig, OrderingItem } from "@farming-labs/docs";
|
|
1
|
+
import { ChangelogConfig, DocsI18nConfig, DocsMcpConfig, DocsSearchConfig, FeedbackConfig, OrderingItem } from "@farming-labs/docs";
|
|
2
2
|
|
|
3
3
|
//#region src/docs-api.d.ts
|
|
4
4
|
interface AIProviderConfig {
|
|
@@ -41,6 +41,8 @@ interface DocsAPIOptions {
|
|
|
41
41
|
i18n?: DocsI18nConfig;
|
|
42
42
|
/** Search configuration */
|
|
43
43
|
search?: boolean | DocsSearchConfig;
|
|
44
|
+
/** Feedback configuration */
|
|
45
|
+
feedback?: boolean | FeedbackConfig;
|
|
44
46
|
}
|
|
45
47
|
interface DocsMCPAPIOptions {
|
|
46
48
|
rootDir?: string;
|
package/dist/docs-api.mjs
CHANGED
|
@@ -32,6 +32,201 @@ const FILE_EXTS = [
|
|
|
32
32
|
"jsx",
|
|
33
33
|
"js"
|
|
34
34
|
];
|
|
35
|
+
const DEFAULT_AGENT_FEEDBACK_ROUTE = "/api/docs/agent/feedback";
|
|
36
|
+
const DEFAULT_AGENT_FEEDBACK_PAYLOAD_SCHEMA = {
|
|
37
|
+
type: "object",
|
|
38
|
+
additionalProperties: false,
|
|
39
|
+
properties: {
|
|
40
|
+
task: {
|
|
41
|
+
type: "string",
|
|
42
|
+
description: "Short description of what the agent was trying to do."
|
|
43
|
+
},
|
|
44
|
+
understanding: {
|
|
45
|
+
type: "string",
|
|
46
|
+
description: "How well the docs supported the task, e.g. \"partial\" or \"clear\"."
|
|
47
|
+
},
|
|
48
|
+
outcome: {
|
|
49
|
+
type: "string",
|
|
50
|
+
description: "What happened after reading the docs, e.g. \"implemented\" or \"blocked\"."
|
|
51
|
+
},
|
|
52
|
+
confidence: {
|
|
53
|
+
type: "number",
|
|
54
|
+
minimum: 0,
|
|
55
|
+
maximum: 1,
|
|
56
|
+
description: "Confidence score from 0 to 1."
|
|
57
|
+
},
|
|
58
|
+
neededCodeReading: {
|
|
59
|
+
type: "boolean",
|
|
60
|
+
description: "Whether the agent still needed to inspect repository code."
|
|
61
|
+
},
|
|
62
|
+
missingContext: {
|
|
63
|
+
type: "array",
|
|
64
|
+
items: { type: "string" },
|
|
65
|
+
description: "Important details the docs did not provide clearly enough."
|
|
66
|
+
},
|
|
67
|
+
docIssues: {
|
|
68
|
+
type: "array",
|
|
69
|
+
items: { type: "string" },
|
|
70
|
+
description: "Specific documentation problems encountered during the task."
|
|
71
|
+
},
|
|
72
|
+
suggestedImprovement: {
|
|
73
|
+
type: "string",
|
|
74
|
+
description: "Concrete suggestion for improving the docs page or examples."
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
required: ["task", "outcome"]
|
|
78
|
+
};
|
|
79
|
+
function normalizeAgentFeedbackRoute(route, fallback = DEFAULT_AGENT_FEEDBACK_ROUTE) {
|
|
80
|
+
if (!route || route.trim().length === 0) return fallback;
|
|
81
|
+
const normalized = `/${route}`.replace(/\/+/g, "/");
|
|
82
|
+
return normalized !== "/" ? normalized.replace(/\/+$/, "") : fallback;
|
|
83
|
+
}
|
|
84
|
+
function buildAgentFeedbackSchema(payloadSchema) {
|
|
85
|
+
return {
|
|
86
|
+
type: "object",
|
|
87
|
+
additionalProperties: false,
|
|
88
|
+
properties: {
|
|
89
|
+
context: {
|
|
90
|
+
type: "object",
|
|
91
|
+
additionalProperties: false,
|
|
92
|
+
properties: {
|
|
93
|
+
page: { type: "string" },
|
|
94
|
+
url: { type: "string" },
|
|
95
|
+
slug: { type: "string" },
|
|
96
|
+
locale: { type: "string" },
|
|
97
|
+
source: { type: "string" }
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
payload: payloadSchema
|
|
101
|
+
},
|
|
102
|
+
required: ["payload"]
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function resolveAgentFeedbackConfig(feedback) {
|
|
106
|
+
const route = normalizeAgentFeedbackRoute();
|
|
107
|
+
const disabled = {
|
|
108
|
+
enabled: false,
|
|
109
|
+
route,
|
|
110
|
+
schemaRoute: `${route}/schema`,
|
|
111
|
+
payloadSchema: DEFAULT_AGENT_FEEDBACK_PAYLOAD_SCHEMA,
|
|
112
|
+
schema: buildAgentFeedbackSchema(DEFAULT_AGENT_FEEDBACK_PAYLOAD_SCHEMA)
|
|
113
|
+
};
|
|
114
|
+
if (!feedback || typeof feedback !== "object") return disabled;
|
|
115
|
+
const agent = feedback.agent;
|
|
116
|
+
if (!agent) return disabled;
|
|
117
|
+
if (agent === true) return {
|
|
118
|
+
enabled: true,
|
|
119
|
+
route,
|
|
120
|
+
schemaRoute: `${route}/schema`,
|
|
121
|
+
payloadSchema: DEFAULT_AGENT_FEEDBACK_PAYLOAD_SCHEMA,
|
|
122
|
+
schema: buildAgentFeedbackSchema(DEFAULT_AGENT_FEEDBACK_PAYLOAD_SCHEMA)
|
|
123
|
+
};
|
|
124
|
+
const resolvedRoute = normalizeAgentFeedbackRoute(agent.route, route);
|
|
125
|
+
const resolvedSchemaRoute = normalizeAgentFeedbackRoute(agent.schemaRoute, `${resolvedRoute}/schema`);
|
|
126
|
+
const payloadSchema = agent.schema ?? DEFAULT_AGENT_FEEDBACK_PAYLOAD_SCHEMA;
|
|
127
|
+
return {
|
|
128
|
+
enabled: agent.enabled !== false,
|
|
129
|
+
route: resolvedRoute,
|
|
130
|
+
schemaRoute: resolvedSchemaRoute,
|
|
131
|
+
payloadSchema,
|
|
132
|
+
schema: buildAgentFeedbackSchema(payloadSchema),
|
|
133
|
+
onFeedback: agent.onFeedback
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
function resolveAgentFeedbackRequest(url, feedback) {
|
|
137
|
+
if (!feedback.enabled) return null;
|
|
138
|
+
const feedbackMode = url.searchParams.get("feedback")?.trim();
|
|
139
|
+
const schemaMode = url.searchParams.get("schema")?.trim();
|
|
140
|
+
if (feedbackMode === "agent") return { kind: schemaMode === "1" || schemaMode === "true" ? "schema" : "submit" };
|
|
141
|
+
const pathname = normalizeUrlPath(url.pathname);
|
|
142
|
+
if (pathname === feedback.schemaRoute) return { kind: "schema" };
|
|
143
|
+
if (pathname === feedback.route) return { kind: "submit" };
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
function isPlainObject(value) {
|
|
147
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
148
|
+
}
|
|
149
|
+
function normalizeAgentFeedbackContext(value) {
|
|
150
|
+
if (!isPlainObject(value)) return void 0;
|
|
151
|
+
const context = {};
|
|
152
|
+
if (typeof value.page === "string") context.page = value.page;
|
|
153
|
+
if (typeof value.url === "string") context.url = value.url;
|
|
154
|
+
if (typeof value.slug === "string") context.slug = value.slug;
|
|
155
|
+
if (typeof value.locale === "string") context.locale = value.locale;
|
|
156
|
+
if (typeof value.source === "string") context.source = value.source;
|
|
157
|
+
return Object.keys(context).length > 0 ? context : void 0;
|
|
158
|
+
}
|
|
159
|
+
async function parseAgentFeedbackData(request) {
|
|
160
|
+
let body;
|
|
161
|
+
try {
|
|
162
|
+
body = await request.json();
|
|
163
|
+
} catch {
|
|
164
|
+
return {
|
|
165
|
+
ok: false,
|
|
166
|
+
response: Response.json({ error: "Agent feedback body must be valid JSON" }, { status: 400 })
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
if (!isPlainObject(body)) return {
|
|
170
|
+
ok: false,
|
|
171
|
+
response: Response.json({ error: "Agent feedback body must be an object" }, { status: 400 })
|
|
172
|
+
};
|
|
173
|
+
if (!isPlainObject(body.payload)) return {
|
|
174
|
+
ok: false,
|
|
175
|
+
response: Response.json({ error: "Agent feedback body must include a payload object" }, { status: 400 })
|
|
176
|
+
};
|
|
177
|
+
return {
|
|
178
|
+
ok: true,
|
|
179
|
+
data: {
|
|
180
|
+
context: normalizeAgentFeedbackContext(body.context),
|
|
181
|
+
payload: body.payload
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
function validateAgentFeedbackPayload(value, schema, valuePath = "payload") {
|
|
186
|
+
const schemaType = typeof schema.type === "string" ? schema.type : void 0;
|
|
187
|
+
if (Array.isArray(schema.enum) && !schema.enum.some((entry) => Object.is(entry, value))) return `${valuePath} must be one of the configured enum values`;
|
|
188
|
+
if (schemaType === "object" || !schemaType && (schema.properties || schema.required)) {
|
|
189
|
+
if (!isPlainObject(value)) return `${valuePath} must be an object`;
|
|
190
|
+
const properties = isPlainObject(schema.properties) ? schema.properties : {};
|
|
191
|
+
const required = Array.isArray(schema.required) ? schema.required.filter((entry) => typeof entry === "string") : [];
|
|
192
|
+
for (const key of required) if (!(key in value)) return `${valuePath}.${key} is required`;
|
|
193
|
+
if (schema.additionalProperties === false) {
|
|
194
|
+
for (const key of Object.keys(value)) if (!(key in properties)) return `${valuePath}.${key} is not allowed`;
|
|
195
|
+
}
|
|
196
|
+
for (const [key, propertySchema] of Object.entries(properties)) {
|
|
197
|
+
if (!(key in value)) continue;
|
|
198
|
+
if (!isPlainObject(propertySchema)) continue;
|
|
199
|
+
const error = validateAgentFeedbackPayload(value[key], propertySchema, `${valuePath}.${key}`);
|
|
200
|
+
if (error) return error;
|
|
201
|
+
}
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
if (schemaType === "array") {
|
|
205
|
+
if (!Array.isArray(value)) return `${valuePath} must be an array`;
|
|
206
|
+
if (!isPlainObject(schema.items)) return void 0;
|
|
207
|
+
for (const [index, item] of value.entries()) {
|
|
208
|
+
const error = validateAgentFeedbackPayload(item, schema.items, `${valuePath}[${index}]`);
|
|
209
|
+
if (error) return error;
|
|
210
|
+
}
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
if (schemaType === "string") {
|
|
214
|
+
if (typeof value !== "string") return `${valuePath} must be a string`;
|
|
215
|
+
if (typeof schema.minLength === "number" && value.length < schema.minLength) return `${valuePath} must be at least ${schema.minLength} characters`;
|
|
216
|
+
if (typeof schema.maxLength === "number" && value.length > schema.maxLength) return `${valuePath} must be at most ${schema.maxLength} characters`;
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
if (schemaType === "number") {
|
|
220
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return `${valuePath} must be a number`;
|
|
221
|
+
if (typeof schema.minimum === "number" && value < schema.minimum) return `${valuePath} must be >= ${schema.minimum}`;
|
|
222
|
+
if (typeof schema.maximum === "number" && value > schema.maximum) return `${valuePath} must be <= ${schema.maximum}`;
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
if (schemaType === "boolean") {
|
|
226
|
+
if (typeof value !== "boolean") return `${valuePath} must be a boolean`;
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
35
230
|
function readEntry(root) {
|
|
36
231
|
for (const ext of FILE_EXTS) {
|
|
37
232
|
const configPath = path.join(root, `docs.config.${ext}`);
|
|
@@ -444,6 +639,15 @@ function findDocsMcpPage(entry, pages, requestedPath) {
|
|
|
444
639
|
for (const page of pages) if (normalizePathSegment(page.slug) === normalizedSlug) return page;
|
|
445
640
|
return null;
|
|
446
641
|
}
|
|
642
|
+
function resolveMarkdownRequest(entry, url) {
|
|
643
|
+
if (url.searchParams.get("format")?.trim() === "markdown") return { requestedPath: url.searchParams.get("path")?.trim() ?? "" };
|
|
644
|
+
const pathname = normalizeUrlPath(url.pathname);
|
|
645
|
+
const normalizedEntry = `/${normalizePathSegment(entry)}`;
|
|
646
|
+
if (pathname === `${normalizedEntry}.md`) return { requestedPath: "" };
|
|
647
|
+
const slugPrefix = `${normalizedEntry}/`;
|
|
648
|
+
if (pathname.startsWith(slugPrefix) && pathname.endsWith(".md")) return { requestedPath: pathname.slice(slugPrefix.length, -3) };
|
|
649
|
+
return null;
|
|
650
|
+
}
|
|
447
651
|
function renderMarkdownDocument(page) {
|
|
448
652
|
if ("agentRawContent" in page && page.agentRawContent !== void 0) return page.agentRawContent;
|
|
449
653
|
const lines = [`# ${page.title}`, `URL: ${page.url}`];
|
|
@@ -599,6 +803,7 @@ function createDocsAPI(options) {
|
|
|
599
803
|
const appDir = getNextAppDir(root);
|
|
600
804
|
const contentDir = options?.contentDir ?? path.join(appDir, entry);
|
|
601
805
|
const changelogConfig = resolveChangelogConfig(options?.changelog);
|
|
806
|
+
const agentFeedbackConfig = resolveAgentFeedbackConfig(options?.feedback);
|
|
602
807
|
const i18n = resolveDocsI18n(options?.i18n ?? readI18nConfig(root));
|
|
603
808
|
const aiConfig = options?.ai ?? readAIConfig(root);
|
|
604
809
|
const searchConfig = options?.search;
|
|
@@ -722,9 +927,21 @@ function createDocsAPI(options) {
|
|
|
722
927
|
async GET(request) {
|
|
723
928
|
const ctx = resolveContextFromRequest(request);
|
|
724
929
|
const url = new URL(request.url);
|
|
725
|
-
const
|
|
726
|
-
if (
|
|
727
|
-
|
|
930
|
+
const agentFeedbackRequest = resolveAgentFeedbackRequest(url, agentFeedbackConfig);
|
|
931
|
+
if (agentFeedbackRequest) {
|
|
932
|
+
if (agentFeedbackRequest.kind === "submit") return Response.json({ error: "Method Not Allowed" }, {
|
|
933
|
+
status: 405,
|
|
934
|
+
headers: { Allow: "POST" }
|
|
935
|
+
});
|
|
936
|
+
return new Response(JSON.stringify(agentFeedbackConfig.schema, null, 2), { headers: {
|
|
937
|
+
"Content-Type": "application/schema+json; charset=utf-8",
|
|
938
|
+
"Cache-Control": "public, max-age=0, s-maxage=3600",
|
|
939
|
+
"X-Robots-Tag": "noindex"
|
|
940
|
+
} });
|
|
941
|
+
}
|
|
942
|
+
const markdownRequest = resolveMarkdownRequest(entry, url);
|
|
943
|
+
if (markdownRequest) {
|
|
944
|
+
const document = await getMarkdownDocument(ctx, markdownRequest.requestedPath);
|
|
728
945
|
if (!document) return new Response("Not Found", {
|
|
729
946
|
status: 404,
|
|
730
947
|
headers: {
|
|
@@ -738,6 +955,7 @@ function createDocsAPI(options) {
|
|
|
738
955
|
"X-Robots-Tag": "noindex"
|
|
739
956
|
} });
|
|
740
957
|
}
|
|
958
|
+
const format = url.searchParams.get("format");
|
|
741
959
|
if (format === "llms") return new Response(getLlmsContent(ctx).llmsTxt, { headers: {
|
|
742
960
|
"Content-Type": "text/plain; charset=utf-8",
|
|
743
961
|
"Cache-Control": "public, max-age=3600"
|
|
@@ -759,6 +977,26 @@ function createDocsAPI(options) {
|
|
|
759
977
|
return new Response(JSON.stringify(results), { headers: { "Content-Type": "application/json" } });
|
|
760
978
|
},
|
|
761
979
|
async POST(request) {
|
|
980
|
+
const agentFeedbackRequest = resolveAgentFeedbackRequest(new URL(request.url), agentFeedbackConfig);
|
|
981
|
+
if (agentFeedbackRequest) {
|
|
982
|
+
if (agentFeedbackRequest.kind === "schema") return Response.json({ error: "Method Not Allowed" }, {
|
|
983
|
+
status: 405,
|
|
984
|
+
headers: { Allow: "GET" }
|
|
985
|
+
});
|
|
986
|
+
const parsed = await parseAgentFeedbackData(request);
|
|
987
|
+
if (!parsed.ok) return parsed.response;
|
|
988
|
+
const payloadError = validateAgentFeedbackPayload(parsed.data.payload, agentFeedbackConfig.payloadSchema);
|
|
989
|
+
if (payloadError) return Response.json({ error: payloadError }, { status: 400 });
|
|
990
|
+
if (!agentFeedbackConfig.onFeedback) return Response.json({
|
|
991
|
+
ok: true,
|
|
992
|
+
handled: false
|
|
993
|
+
}, { status: 202 });
|
|
994
|
+
await agentFeedbackConfig.onFeedback(parsed.data);
|
|
995
|
+
return Response.json({
|
|
996
|
+
ok: true,
|
|
997
|
+
handled: true
|
|
998
|
+
}, { status: 201 });
|
|
999
|
+
}
|
|
762
1000
|
if (!aiConfig.enabled) return Response.json({ error: "AI is not enabled. Set `ai: { enabled: true }` in your docs.config to enable it." }, { status: 404 });
|
|
763
1001
|
return handleAskAI(request, getIndexes(resolveContextFromRequest(request)), aiConfig);
|
|
764
1002
|
}
|
package/dist/docs-layout.mjs
CHANGED
|
@@ -716,8 +716,9 @@ function resolveFeedbackConfig(feedback) {
|
|
|
716
716
|
...defaults,
|
|
717
717
|
enabled: true
|
|
718
718
|
};
|
|
719
|
+
const hasHumanFeedbackConfig = feedback.enabled !== void 0 || feedback.question !== void 0 || feedback.placeholder !== void 0 || feedback.positiveLabel !== void 0 || feedback.negativeLabel !== void 0 || feedback.submitLabel !== void 0 || feedback.onFeedback !== void 0;
|
|
719
720
|
return {
|
|
720
|
-
enabled: feedback.enabled !== false,
|
|
721
|
+
enabled: feedback.enabled === true || feedback.enabled !== false && hasHumanFeedbackConfig,
|
|
721
722
|
question: feedback.question ?? defaults.question,
|
|
722
723
|
placeholder: feedback.placeholder ?? defaults.placeholder,
|
|
723
724
|
positiveLabel: feedback.positiveLabel ?? defaults.positiveLabel,
|
package/dist/tanstack-layout.mjs
CHANGED
|
@@ -192,8 +192,9 @@ function resolveFeedbackConfig(feedback) {
|
|
|
192
192
|
...defaults,
|
|
193
193
|
enabled: true
|
|
194
194
|
};
|
|
195
|
+
const hasHumanFeedbackConfig = feedback.enabled !== void 0 || feedback.question !== void 0 || feedback.placeholder !== void 0 || feedback.positiveLabel !== void 0 || feedback.negativeLabel !== void 0 || feedback.submitLabel !== void 0 || feedback.onFeedback !== void 0;
|
|
195
196
|
return {
|
|
196
|
-
enabled: feedback.enabled !== false,
|
|
197
|
+
enabled: feedback.enabled === true || feedback.enabled !== false && hasHumanFeedbackConfig,
|
|
197
198
|
question: feedback.question ?? defaults.question,
|
|
198
199
|
placeholder: feedback.placeholder ?? defaults.placeholder,
|
|
199
200
|
positiveLabel: feedback.positiveLabel ?? defaults.positiveLabel,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@farming-labs/theme",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.21",
|
|
4
4
|
"description": "Theme package for @farming-labs/docs — layout, provider, MDX components, and styles",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"docs",
|
|
@@ -133,7 +133,7 @@
|
|
|
133
133
|
"tsdown": "^0.20.3",
|
|
134
134
|
"typescript": "^5.9.3",
|
|
135
135
|
"vitest": "^3.2.4",
|
|
136
|
-
"@farming-labs/docs": "0.1.
|
|
136
|
+
"@farming-labs/docs": "0.1.21"
|
|
137
137
|
},
|
|
138
138
|
"peerDependencies": {
|
|
139
139
|
"@farming-labs/docs": ">=0.0.1",
|