@xyd-js/ask-ai-widget 0.0.0-build-ae5d7ac-20251012020134 → 0.0.0-build-52f9bb2-20251012023142
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/bin/xyd-ask-ai-widget.mjs +4 -0
- package/dist/server.js +1120 -1120
- package/package.json +10 -4
- package/server.ts +144 -0
- package/src/Widget.tsx +89 -0
- package/widget.tsx +23 -0
package/package.json
CHANGED
|
@@ -1,19 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xyd-js/ask-ai-widget",
|
|
3
|
-
"version": "0.0.0-build-
|
|
3
|
+
"version": "0.0.0-build-52f9bb2-20251012023142",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/server.js",
|
|
6
6
|
"files": [
|
|
7
|
-
"dist"
|
|
7
|
+
"dist",
|
|
8
|
+
"src",
|
|
9
|
+
"widget.tsx",
|
|
10
|
+
"server.ts"
|
|
8
11
|
],
|
|
12
|
+
"bin": {
|
|
13
|
+
"xyd-ask-ai-widget": "./bin/xyd-ask-ai-widget.mjs"
|
|
14
|
+
},
|
|
9
15
|
"dependencies": {
|
|
10
16
|
"@prose-sdk/react": "^0.1.0",
|
|
11
17
|
"react": "^19.0.0",
|
|
12
18
|
"react-dom": "^19.0.0",
|
|
13
|
-
"@xyd-js/ask-ai": "0.0.0-build-
|
|
19
|
+
"@xyd-js/ask-ai": "0.0.0-build-52f9bb2-20251012023142"
|
|
14
20
|
},
|
|
15
21
|
"scripts": {
|
|
16
|
-
"build": "NODE_ENV=production bun build server.ts --outdir dist --target
|
|
22
|
+
"build": "NODE_ENV=production bun build server.ts --outdir dist --target bun",
|
|
17
23
|
"start": "bun dist/server.js",
|
|
18
24
|
"dev": "bun --watch server.ts"
|
|
19
25
|
}
|
package/server.ts
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
import { handler as askAiHandler } from "@xyd-js/ask-ai/node";
|
|
5
|
+
|
|
6
|
+
if (!process.env.ASK_AI_URL) {
|
|
7
|
+
throw new Error("ASK_AI_URL is not set");
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// Start the server
|
|
11
|
+
async function startServer() {
|
|
12
|
+
// Ensure widget is built before starting server
|
|
13
|
+
await ensureWidgetBuilt();
|
|
14
|
+
|
|
15
|
+
// Find an available port
|
|
16
|
+
const port = await findAvailablePort(parseInt(process.env.PORT || "3500"));
|
|
17
|
+
console.log(`🔍 Trying to start server on port ${port}...`);
|
|
18
|
+
|
|
19
|
+
const server = Bun.serve({
|
|
20
|
+
port,
|
|
21
|
+
async fetch(request: Request) {
|
|
22
|
+
const url = new URL(request.url);
|
|
23
|
+
|
|
24
|
+
// Serve the widget bundle at /widget.js
|
|
25
|
+
if (url.pathname === "/widget.js") {
|
|
26
|
+
try {
|
|
27
|
+
const widgetCode = widgetFile();
|
|
28
|
+
|
|
29
|
+
return new Response(widgetCode, {
|
|
30
|
+
headers: {
|
|
31
|
+
"Content-Type": "application/javascript",
|
|
32
|
+
"Cache-Control": "public, max-age=3600", // Cache for 1 hour
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
} catch (error) {
|
|
36
|
+
return new Response(
|
|
37
|
+
"Widget not found. Please run 'bun run build' first.",
|
|
38
|
+
{
|
|
39
|
+
status: 404,
|
|
40
|
+
}
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Handle ask AI requests at /ask
|
|
46
|
+
if (url.pathname === "/ask") {
|
|
47
|
+
return askAiHandler(request);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Default response
|
|
51
|
+
return new Response(
|
|
52
|
+
"Xyd Ask AI Widget Server\n\nEndpoints:\n- GET /widget.js - Widget bundle\n- POST /ask - Ask AI endpoint",
|
|
53
|
+
{
|
|
54
|
+
headers: { "Content-Type": "text/plain" },
|
|
55
|
+
}
|
|
56
|
+
);
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
console.log(`🚀 Widget server running at http://localhost:${server.port}`);
|
|
61
|
+
console.log(`📦 Widget bundle: http://localhost:${server.port}/widget.js`);
|
|
62
|
+
console.log(`🤖 Ask AI endpoint: http://localhost:${server.port}/ask`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function widgetFile() {
|
|
66
|
+
const widgetPath = join(process.cwd(), "dist", "widget.js");
|
|
67
|
+
try {
|
|
68
|
+
const widgetCode = readFileSync(widgetPath, "utf-8");
|
|
69
|
+
return widgetCode;
|
|
70
|
+
} catch (error) {
|
|
71
|
+
return "";
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// Build widget using Bun's build API
|
|
75
|
+
async function buildWidget() {
|
|
76
|
+
console.log("🔨 Building widget using Bun.build()...");
|
|
77
|
+
|
|
78
|
+
const envs = {
|
|
79
|
+
"process.env.NODE_ENV": '"production"',
|
|
80
|
+
"process.env.ASK_AI_URL": `"${process.env.ASK_AI_URL}"`,
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
const result = await Bun.build({
|
|
85
|
+
entrypoints: ["./widget.tsx"],
|
|
86
|
+
outdir: "./dist",
|
|
87
|
+
target: "browser",
|
|
88
|
+
format: "iife",
|
|
89
|
+
minify: true,
|
|
90
|
+
sourcemap: "none",
|
|
91
|
+
define: {
|
|
92
|
+
...envs,
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
if (result.success) {
|
|
97
|
+
console.log("✅ Widget built successfully");
|
|
98
|
+
} else {
|
|
99
|
+
console.error("❌ Widget build failed:", result.logs);
|
|
100
|
+
throw new Error("Build failed");
|
|
101
|
+
}
|
|
102
|
+
} catch (error) {
|
|
103
|
+
console.error("❌ Build error:", error);
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Ensure widget is built
|
|
109
|
+
async function ensureWidgetBuilt() {
|
|
110
|
+
const widgetPath = widgetFile();
|
|
111
|
+
|
|
112
|
+
if (!existsSync(widgetPath)) {
|
|
113
|
+
await buildWidget();
|
|
114
|
+
} else {
|
|
115
|
+
console.log("✅ Widget already built");
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Find an available port
|
|
120
|
+
async function findAvailablePort(
|
|
121
|
+
startPort: number = 3500,
|
|
122
|
+
maxAttempts: number = 10
|
|
123
|
+
): Promise<number> {
|
|
124
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
125
|
+
const port = startPort + i;
|
|
126
|
+
try {
|
|
127
|
+
const testServer = Bun.serve({
|
|
128
|
+
port,
|
|
129
|
+
fetch: () => new Response("test"),
|
|
130
|
+
});
|
|
131
|
+
testServer.stop();
|
|
132
|
+
return port;
|
|
133
|
+
} catch (error) {
|
|
134
|
+
// Port is in use, try next one
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
throw new Error(
|
|
139
|
+
`No available ports found between ${startPort} and ${startPort + maxAttempts - 1}`
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Start the server
|
|
144
|
+
startServer().catch(console.error);
|
package/src/Widget.tsx
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { useState, useEffect, useRef } from "react";
|
|
2
|
+
|
|
3
|
+
import { ProseMd } from "@prose-sdk/react";
|
|
4
|
+
|
|
5
|
+
import { AskAI, useAskAI } from "@xyd-js/ask-ai/react";
|
|
6
|
+
|
|
7
|
+
interface WidgetConfig {
|
|
8
|
+
askAiServer?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface AskWidgetProps {
|
|
12
|
+
config?: WidgetConfig;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function AskWidget({ config = {} }: AskWidgetProps) {
|
|
16
|
+
if (process.env.NODE_ENV === "development") {
|
|
17
|
+
if (!config?.askAiServer) {
|
|
18
|
+
config.askAiServer = "http://localhost:3500";
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const { messages, submit, disabled, loading } = useAskAI(
|
|
22
|
+
config?.askAiServer
|
|
23
|
+
);
|
|
24
|
+
const [dots, setDots] = useState(1);
|
|
25
|
+
const ref = useRef<any>(null);
|
|
26
|
+
|
|
27
|
+
const lastMessage = messages?.[messages.length - 1];
|
|
28
|
+
const isWaitingForAssistant =
|
|
29
|
+
(messages.length > 0 && lastMessage?.type === "user") ||
|
|
30
|
+
(lastMessage?.type === "assistant" && lastMessage?.content === "");
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
ref.current.scrollToBottom();
|
|
34
|
+
}, [messages]);
|
|
35
|
+
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
if (!loading) return;
|
|
38
|
+
|
|
39
|
+
const interval = setInterval(() => {
|
|
40
|
+
setDots((prev) => (prev >= 3 ? 1 : prev + 1));
|
|
41
|
+
}, 500);
|
|
42
|
+
|
|
43
|
+
return () => clearInterval(interval);
|
|
44
|
+
}, [loading]);
|
|
45
|
+
|
|
46
|
+
const getPlaceholder = () => {
|
|
47
|
+
if (loading) {
|
|
48
|
+
return `Loading${".".repeat(dots)}`;
|
|
49
|
+
}
|
|
50
|
+
return "Ask a question";
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<AskAI
|
|
55
|
+
onSubmit={submit as any}
|
|
56
|
+
disabled={disabled}
|
|
57
|
+
placeholder={getPlaceholder()}
|
|
58
|
+
ref={ref}
|
|
59
|
+
>
|
|
60
|
+
<div slot="title">
|
|
61
|
+
<span aria-hidden="true">✨</span>
|
|
62
|
+
{config?.projectName || "Assistant"}
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
{messages.map((message) => (
|
|
66
|
+
<AskAI.Message
|
|
67
|
+
key={message.id}
|
|
68
|
+
type={message.type}
|
|
69
|
+
content={message.type === "user" ? message.content : undefined}
|
|
70
|
+
>
|
|
71
|
+
{message.type === "assistant" ? (
|
|
72
|
+
<_Message message={message.content} />
|
|
73
|
+
) : null}
|
|
74
|
+
</AskAI.Message>
|
|
75
|
+
))}
|
|
76
|
+
|
|
77
|
+
{loading && isWaitingForAssistant ? (
|
|
78
|
+
<AskAI.Message
|
|
79
|
+
type="assistant"
|
|
80
|
+
content={`Loading${".".repeat(dots)}`}
|
|
81
|
+
/>
|
|
82
|
+
) : null}
|
|
83
|
+
</AskAI>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function _Message({ message }: { message: string }) {
|
|
88
|
+
return <ProseMd content={message} />;
|
|
89
|
+
}
|
package/widget.tsx
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { createRoot } from "react-dom/client";
|
|
2
|
+
|
|
3
|
+
import { AskWidget } from "./src/Widget";
|
|
4
|
+
|
|
5
|
+
(async () => {
|
|
6
|
+
// Find the script tag that loaded this widget
|
|
7
|
+
const currentScript = document.currentScript as HTMLScriptElement;
|
|
8
|
+
|
|
9
|
+
// Extract data attributes
|
|
10
|
+
const config = {
|
|
11
|
+
askAiServer:
|
|
12
|
+
currentScript?.dataset["data-server-url"] || process.env.ASK_AI_URL,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// Create a container div and append it to body
|
|
16
|
+
const container = document.createElement("div");
|
|
17
|
+
container.id = "xyd-ask-ai-widget";
|
|
18
|
+
document.body.appendChild(container);
|
|
19
|
+
|
|
20
|
+
// Render to the container
|
|
21
|
+
const root = createRoot(container);
|
|
22
|
+
root.render(<AskWidget config={config} />);
|
|
23
|
+
})();
|