create-wirejs-app 2.0.168-llm → 2.0.170
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/package.json +1 -1
- package/templates/default/api/apps/admin.ts +1 -1
- package/templates/default/api/apps/llm/index.ts +63 -0
- package/templates/default/api/apps/llm/infra.ts +322 -0
- package/templates/default/api/apps/llm/prompts.ts +21 -0
- package/templates/default/api/apps/llm/tooled-handler.ts +116 -0
- package/templates/default/api/apps/llm/tools.ts +150 -0
- package/templates/default/api/apps/llm/types.ts +42 -0
- package/templates/default/api/apps/llm/utils.ts +164 -0
- package/templates/default/api/index.ts +8 -6
- package/templates/default/api/package.json +8 -0
- package/templates/default/deployment-config.ts +6 -0
- package/templates/default/package.json +5 -5
- package/templates/default/src/ssg/index.ts +2 -2
- package/templates/default/src/ssg/llm-test.ts +493 -0
- package/templates/default/src/ssg/realtime-test.ts +2 -1
- package/templates/default/src/ssg/web-worker-test.ts +2 -1
- package/templates/default/web-worker/package.json +3 -0
- package/templates/default/api/apps/llm.ts +0 -57
- package/templates/default/src/ssg/chatbot.ts +0 -218
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { LLMMessage, ToolDefinition as BaseToolDefinition } from "wirejs-resources";
|
|
2
|
+
|
|
3
|
+
export type Role = 'assistant' | 'user' | 'step' | 'tool';
|
|
4
|
+
|
|
5
|
+
export type Chunk = {
|
|
6
|
+
mid: number;
|
|
7
|
+
seq: number;
|
|
8
|
+
pad: string; // security padding
|
|
9
|
+
data: ChunkData;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type ChunkData =
|
|
13
|
+
| { type: 'start' }
|
|
14
|
+
| { type: 'end' }
|
|
15
|
+
| { type: 'title', value: string }
|
|
16
|
+
| { type: 'status', status: string }
|
|
17
|
+
| { type: 'text', text: string, role: Role }
|
|
18
|
+
;
|
|
19
|
+
|
|
20
|
+
export type Conversation = {
|
|
21
|
+
userId: string;
|
|
22
|
+
conversationId: string;
|
|
23
|
+
name: string;
|
|
24
|
+
createdAt: number;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type WorkflowStep = {
|
|
28
|
+
role: 'step';
|
|
29
|
+
content: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type ConversationMessage = (WorkflowStep | LLMMessage) & {
|
|
33
|
+
conversationId: string;
|
|
34
|
+
mid: number;
|
|
35
|
+
createdAt: number;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export type ToolDefinition = BaseToolDefinition & {
|
|
39
|
+
execute: (...args: any) => Promise<any>
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type Message = LLMMessage;
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
|
|
2
|
+
import { randomUUID } from 'crypto';
|
|
3
|
+
import { JSDOM } from 'jsdom';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Generate a random number of random characters.
|
|
7
|
+
*
|
|
8
|
+
* Intended for inclusion in chunks sent over the wire for security purposes. Chunks
|
|
9
|
+
* are otherwise more susceptible to snooping.
|
|
10
|
+
*
|
|
11
|
+
* @returns
|
|
12
|
+
*/
|
|
13
|
+
export const pad = () => randomUUID().slice(0, 1 + Math.floor(Math.random() * 16));
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Generate a 64 bit integer ID based on time and randomness. Suitable for
|
|
17
|
+
* non-globally unique IDs (sub-IDs) that just need to be sortable based on
|
|
18
|
+
* create or update time.
|
|
19
|
+
*
|
|
20
|
+
* @returns
|
|
21
|
+
*/
|
|
22
|
+
export const intId = () => Math.floor((Date.now() + Math.random()) * 10_000);
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Cleans up quotes from a title that may have been provided by an LLM.
|
|
26
|
+
*
|
|
27
|
+
* @param title
|
|
28
|
+
* @returns
|
|
29
|
+
*/
|
|
30
|
+
export const cleanTitle = (title: string) => {
|
|
31
|
+
let cleanTitle = title.trim();
|
|
32
|
+
if ((cleanTitle.startsWith('"') && cleanTitle.endsWith('"')) ||
|
|
33
|
+
(cleanTitle.startsWith("'") && cleanTitle.endsWith("'"))) {
|
|
34
|
+
cleanTitle = cleanTitle.slice(1, -1).trim();
|
|
35
|
+
}
|
|
36
|
+
return cleanTitle;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Extract "content"-like text from HTML using common patterns to ignore headers, footers,
|
|
41
|
+
* navigation, extra whitespace, etc.
|
|
42
|
+
*/
|
|
43
|
+
export const extractContentFromHtml = (html: string): string => {
|
|
44
|
+
if (!html.includes('<html') && !html.includes('<!DOCTYPE')) {
|
|
45
|
+
return html;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
console.log(`[HTML] Starting JSDOM extraction from ${html.length} chars`);
|
|
50
|
+
|
|
51
|
+
// Load HTML into JSDOM for DOM manipulation
|
|
52
|
+
const dom = new JSDOM(html);
|
|
53
|
+
const document = dom.window.document;
|
|
54
|
+
|
|
55
|
+
// Remove unwanted elements entirely using querySelectorAll and remove
|
|
56
|
+
const removeSelectors = [
|
|
57
|
+
'script, style, nav, header, footer, aside',
|
|
58
|
+
'.mw-navigation, .navbox, .infobox, .sidebar', // Wikipedia-specific
|
|
59
|
+
'[class*="nav"], [class*="menu"], [class*="sidebar"]', // Common patterns
|
|
60
|
+
'.reference, .citation, sup.reference', // Citations
|
|
61
|
+
'.printfooter, .catlinks', // Wikipedia footer stuff
|
|
62
|
+
'table.ambox, .hatnote' // Wikipedia message boxes
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
removeSelectors.forEach(selector => {
|
|
66
|
+
document.querySelectorAll(selector).forEach(element => element.remove());
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Extract text with cleaned up whitespace
|
|
70
|
+
const bodyElement = document.body;
|
|
71
|
+
let text = (bodyElement?.textContent || '')
|
|
72
|
+
.replace(/\s+/g, ' ') // Normalize whitespace
|
|
73
|
+
.replace(/\[\d+\]/g, '') // Remove citation numbers [1], [2], etc.
|
|
74
|
+
.replace(/\s*\n\s*/g, '\n') // Clean line breaks
|
|
75
|
+
.replace(/\n{3,}/g, '\n\n') // Limit consecutive newlines
|
|
76
|
+
.trim();
|
|
77
|
+
|
|
78
|
+
console.log(`[HTML] Final JSDOM extracted text: ${text.length} chars`);
|
|
79
|
+
return text;
|
|
80
|
+
} catch (error) {
|
|
81
|
+
console.error('Error extracting text with JSDOM:', error);
|
|
82
|
+
// Fallback to simple regex approach
|
|
83
|
+
console.log('[HTML] Falling back to regex extraction');
|
|
84
|
+
return html.replace(/<[^>]*>/g, ' ').replace(/\s+/g, ' ').trim();
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Convert async generator to array (for Node 20 compatibility)
|
|
90
|
+
*/
|
|
91
|
+
export async function fromAsync<T>(gen: AsyncGenerator<T>): Promise<T[]> {
|
|
92
|
+
const items: T[] = [];
|
|
93
|
+
for await (const item of gen) {
|
|
94
|
+
items.push(item);
|
|
95
|
+
}
|
|
96
|
+
return items;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Removes leading whitespace on every line of input text.
|
|
101
|
+
*
|
|
102
|
+
* This works by finding the minimum indent depth of a block of multi-line text and
|
|
103
|
+
* left-trimming every line by the common (minimum) indent amount.
|
|
104
|
+
*
|
|
105
|
+
* @param content
|
|
106
|
+
* @returns
|
|
107
|
+
*/
|
|
108
|
+
function dedentString(content: string) {
|
|
109
|
+
return dedentLines(content.split('\n')).join('\n');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function findMinimumIndent(lines: string[]) {
|
|
113
|
+
let minimumIndent: number | undefined = undefined;
|
|
114
|
+
for (const line of lines) {
|
|
115
|
+
if (line.trim() === '') continue;
|
|
116
|
+
|
|
117
|
+
const whitespace = line.match(/^\s+/)?.[0];
|
|
118
|
+
if (!whitespace) continue;
|
|
119
|
+
|
|
120
|
+
if (minimumIndent === undefined || whitespace.length < minimumIndent) {
|
|
121
|
+
minimumIndent = whitespace.length;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return minimumIndent;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function dedentLines(lines: string[]) {
|
|
128
|
+
const output: string[] = [];
|
|
129
|
+
const minimumIndent = findMinimumIndent(lines);
|
|
130
|
+
for (const line of lines) {
|
|
131
|
+
output.push(line.substring(minimumIndent ?? 0));
|
|
132
|
+
}
|
|
133
|
+
return output;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function dedent(content: TemplateStringsArray, ...values: any[]) {
|
|
137
|
+
const PH = '%%%___DEDENT_PLACEHOLDER___%%%';
|
|
138
|
+
const combined = Array.from(content).join(PH);
|
|
139
|
+
const dedentedContent = dedentString(combined).split(PH);
|
|
140
|
+
return String.raw({ raw: dedentedContent }, ...values);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function parseLLMJson(raw: string) {
|
|
144
|
+
if (!raw || typeof raw !== "string") {
|
|
145
|
+
throw new Error("Empty or non-string LLM output");
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
let text = raw.trim();
|
|
149
|
+
|
|
150
|
+
if (text.startsWith("```")) {
|
|
151
|
+
text = text.replace(/^```[a-zA-Z]*\s*/, "");
|
|
152
|
+
text = text.replace(/```$/, "");
|
|
153
|
+
text = text.trim();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (text.toLowerCase().startsWith("json")) {
|
|
157
|
+
const after = text.slice(4).trim();
|
|
158
|
+
if (after.startsWith("{")) {
|
|
159
|
+
text = after;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return JSON.parse(text);
|
|
164
|
+
}
|
|
@@ -4,12 +4,12 @@ import { Todos } from './apps/todos.js';
|
|
|
4
4
|
import { Wiki } from './apps/wiki.js';
|
|
5
5
|
import { Store } from './apps/store.js';
|
|
6
6
|
import { Admin } from './apps/admin.js';
|
|
7
|
-
import { LLM } from './apps/llm.js';
|
|
7
|
+
import { LLM } from './apps/llm/index.js';
|
|
8
8
|
|
|
9
|
+
export type * from './apps/llm/index.js';
|
|
9
10
|
export type * from './apps/todos.js';
|
|
10
11
|
export type * from './apps/store.js';
|
|
11
12
|
export type * from './apps/admin.js';
|
|
12
|
-
export type * from './apps/llm.js';
|
|
13
13
|
|
|
14
14
|
const authService = new AuthenticationService('app', 'core-users');
|
|
15
15
|
|
|
@@ -22,16 +22,18 @@ export const admin = Admin(auth);
|
|
|
22
22
|
export const llm = LLM(auth);
|
|
23
23
|
|
|
24
24
|
new Endpoint('app', 'sample-endpoint', {
|
|
25
|
-
description: "Sample endpoint to show
|
|
26
|
-
handle() {
|
|
25
|
+
description: "Sample endpoint to show programmatic endpoint creation.",
|
|
26
|
+
handle(context) {
|
|
27
|
+
context.responseHeaders['Content-Type'] = 'text/html; charset=utf-8';
|
|
27
28
|
return "<html><body><p>Hello!</p><p><a href='/'>Back.</a></body></html>";
|
|
28
29
|
}
|
|
29
30
|
});
|
|
30
31
|
|
|
31
32
|
new Endpoint('app', 'sample-wildcard-endpoint', {
|
|
32
33
|
path: 'wildcard-endpoint/%',
|
|
33
|
-
description: "Sample endpoint to show
|
|
34
|
+
description: "Sample endpoint to show programmatic wildcard endpoint creation.",
|
|
34
35
|
handle(context) {
|
|
36
|
+
context.responseHeaders['Content-Type'] = 'text/html; charset=utf-8';
|
|
35
37
|
return `<html>
|
|
36
38
|
<body>
|
|
37
39
|
<h2>${context.location.toString()
|
|
@@ -42,4 +44,4 @@ new Endpoint('app', 'sample-wildcard-endpoint', {
|
|
|
42
44
|
</body>
|
|
43
45
|
</html>`;
|
|
44
46
|
}
|
|
45
|
-
});
|
|
47
|
+
});
|
|
@@ -11,5 +11,13 @@
|
|
|
11
11
|
"types": "./index.ts",
|
|
12
12
|
"wirejs:client": "./dist/index.client.js",
|
|
13
13
|
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"jsdom": "^25.0.0",
|
|
17
|
+
"wirejs-module-payments-stripe": "*",
|
|
18
|
+
"wirejs-resources": "*"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@types/jsdom": "^21.0.0"
|
|
14
22
|
}
|
|
15
23
|
}
|
|
@@ -12,13 +12,13 @@
|
|
|
12
12
|
"dompurify": "^3.2.3",
|
|
13
13
|
"marked": "^15.0.6",
|
|
14
14
|
"wirejs-dom": "^1.0.44",
|
|
15
|
-
"wirejs-resources": "^0.1.
|
|
16
|
-
"wirejs-components": "^0.1.
|
|
17
|
-
"wirejs-module-payments-stripe": "^0.1.
|
|
18
|
-
"wirejs-web-worker": "^1.0.
|
|
15
|
+
"wirejs-resources": "^0.1.165",
|
|
16
|
+
"wirejs-components": "^0.1.108",
|
|
17
|
+
"wirejs-module-payments-stripe": "^0.1.59",
|
|
18
|
+
"wirejs-web-worker": "^1.0.62"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
|
-
"wirejs-scripts": "^3.0.
|
|
21
|
+
"wirejs-scripts": "^3.0.163",
|
|
22
22
|
"typescript": "^5.7.3"
|
|
23
23
|
},
|
|
24
24
|
"scripts": {
|
|
@@ -9,11 +9,11 @@ export async function generate() {
|
|
|
9
9
|
<p>It comes with some sample API methods and pages.</p>
|
|
10
10
|
<ul>
|
|
11
11
|
<li><a href='/todo-app.html'>Todo App</a></li>
|
|
12
|
-
<li><a href='/simple-wiki/index
|
|
12
|
+
<li><a href='/simple-wiki/index'>Simple Wiki</a></li>
|
|
13
13
|
<li><a href='/realtime-test.html'>Realtime Test</a></li>
|
|
14
14
|
<li><a href='/web-worker-test.html'>Web Worker Test</a></li>
|
|
15
15
|
<li><a href='/storefront.html'>Storefront</a></li>
|
|
16
|
-
<li><a href='/
|
|
16
|
+
<li><a href='/llm-test.html'>LLM Test</a></li>
|
|
17
17
|
<li><a href='/admin.html'>Admin</a></li>
|
|
18
18
|
</ul>
|
|
19
19
|
</div>`
|