@xyd-js/ask-ai-widget 0.0.0-build-ae5d7ac-20251012020134 → 0.0.0-build-c15a632-20251012021711

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.
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import('../dist/server.js');
3
+
4
+
package/package.json CHANGED
@@ -1,16 +1,22 @@
1
1
  {
2
2
  "name": "@xyd-js/ask-ai-widget",
3
- "version": "0.0.0-build-ae5d7ac-20251012020134",
3
+ "version": "0.0.0-build-c15a632-20251012021711",
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-ae5d7ac-20251012020134"
19
+ "@xyd-js/ask-ai": "0.0.0-build-c15a632-20251012021711"
14
20
  },
15
21
  "scripts": {
16
22
  "build": "NODE_ENV=production bun build server.ts --outdir dist --target node",
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
+ })();