@farming-labs/theme 0.1.20 → 0.1.22
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 +228 -0
- 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}`);
|
|
@@ -608,6 +803,7 @@ function createDocsAPI(options) {
|
|
|
608
803
|
const appDir = getNextAppDir(root);
|
|
609
804
|
const contentDir = options?.contentDir ?? path.join(appDir, entry);
|
|
610
805
|
const changelogConfig = resolveChangelogConfig(options?.changelog);
|
|
806
|
+
const agentFeedbackConfig = resolveAgentFeedbackConfig(options?.feedback);
|
|
611
807
|
const i18n = resolveDocsI18n(options?.i18n ?? readI18nConfig(root));
|
|
612
808
|
const aiConfig = options?.ai ?? readAIConfig(root);
|
|
613
809
|
const searchConfig = options?.search;
|
|
@@ -731,6 +927,18 @@ function createDocsAPI(options) {
|
|
|
731
927
|
async GET(request) {
|
|
732
928
|
const ctx = resolveContextFromRequest(request);
|
|
733
929
|
const url = new URL(request.url);
|
|
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
|
+
}
|
|
734
942
|
const markdownRequest = resolveMarkdownRequest(entry, url);
|
|
735
943
|
if (markdownRequest) {
|
|
736
944
|
const document = await getMarkdownDocument(ctx, markdownRequest.requestedPath);
|
|
@@ -769,6 +977,26 @@ function createDocsAPI(options) {
|
|
|
769
977
|
return new Response(JSON.stringify(results), { headers: { "Content-Type": "application/json" } });
|
|
770
978
|
},
|
|
771
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
|
+
}
|
|
772
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 });
|
|
773
1001
|
return handleAskAI(request, getIndexes(resolveContextFromRequest(request)), aiConfig);
|
|
774
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.22",
|
|
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.22"
|
|
137
137
|
},
|
|
138
138
|
"peerDependencies": {
|
|
139
139
|
"@farming-labs/docs": ">=0.0.1",
|