strapi-plugin-faqchatbot 1.0.0 → 1.0.2

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.
@@ -25026,20 +25026,36 @@ const ChatbotPreview = () => {
25026
25026
  const res = await fetch("/api/faqchatbot/ask", {
25027
25027
  method: "POST",
25028
25028
  headers: { "Content-Type": "application/json" },
25029
- body: JSON.stringify({
25030
- question: userText
25031
- })
25029
+ body: JSON.stringify({ question: userText })
25032
25030
  });
25033
- const data = await res.json();
25034
- setMessages((prev) => [
25035
- ...prev,
25036
- { text: data.content || "No response", isUser: false }
25037
- ]);
25031
+ if (!res.ok) throw new Error("Request failed");
25032
+ const reader = res.body?.getReader();
25033
+ if (!reader) throw new Error("No stream");
25034
+ let botMessage = "";
25035
+ let doneStream = false;
25036
+ setMessages((prev) => [...prev, { text: "", isUser: false }]);
25037
+ while (!doneStream) {
25038
+ const { done, value } = await reader.read();
25039
+ if (done) break;
25040
+ const chunk = new TextDecoder().decode(value);
25041
+ const lines = chunk.split("\n");
25042
+ for (const line of lines) {
25043
+ if (!line.startsWith("data: ")) continue;
25044
+ const text = line.replace("data: ", "").trim();
25045
+ if (text === "[DONE]") {
25046
+ doneStream = true;
25047
+ break;
25048
+ }
25049
+ botMessage += text;
25050
+ setMessages((prev) => {
25051
+ const updated = [...prev];
25052
+ updated[updated.length - 1] = { text: botMessage, isUser: false };
25053
+ return updated;
25054
+ });
25055
+ }
25056
+ }
25038
25057
  } catch (err) {
25039
- setMessages((prev) => [
25040
- ...prev,
25041
- { text: "Error contacting chatbot", isUser: false }
25042
- ]);
25058
+ setMessages((prev) => [...prev, { text: "Error contacting chatbot", isUser: false }]);
25043
25059
  }
25044
25060
  };
25045
25061
  const handleClearHistory = () => {
@@ -25025,20 +25025,36 @@ const ChatbotPreview = () => {
25025
25025
  const res = await fetch("/api/faqchatbot/ask", {
25026
25026
  method: "POST",
25027
25027
  headers: { "Content-Type": "application/json" },
25028
- body: JSON.stringify({
25029
- question: userText
25030
- })
25028
+ body: JSON.stringify({ question: userText })
25031
25029
  });
25032
- const data = await res.json();
25033
- setMessages((prev) => [
25034
- ...prev,
25035
- { text: data.content || "No response", isUser: false }
25036
- ]);
25030
+ if (!res.ok) throw new Error("Request failed");
25031
+ const reader = res.body?.getReader();
25032
+ if (!reader) throw new Error("No stream");
25033
+ let botMessage = "";
25034
+ let doneStream = false;
25035
+ setMessages((prev) => [...prev, { text: "", isUser: false }]);
25036
+ while (!doneStream) {
25037
+ const { done, value } = await reader.read();
25038
+ if (done) break;
25039
+ const chunk = new TextDecoder().decode(value);
25040
+ const lines = chunk.split("\n");
25041
+ for (const line of lines) {
25042
+ if (!line.startsWith("data: ")) continue;
25043
+ const text = line.replace("data: ", "").trim();
25044
+ if (text === "[DONE]") {
25045
+ doneStream = true;
25046
+ break;
25047
+ }
25048
+ botMessage += text;
25049
+ setMessages((prev) => {
25050
+ const updated = [...prev];
25051
+ updated[updated.length - 1] = { text: botMessage, isUser: false };
25052
+ return updated;
25053
+ });
25054
+ }
25055
+ }
25037
25056
  } catch (err) {
25038
- setMessages((prev) => [
25039
- ...prev,
25040
- { text: "Error contacting chatbot", isUser: false }
25041
- ]);
25057
+ setMessages((prev) => [...prev, { text: "Error contacting chatbot", isUser: false }]);
25042
25058
  }
25043
25059
  };
25044
25060
  const handleClearHistory = () => {
@@ -37,7 +37,7 @@ const index = {
37
37
  defaultMessage: PLUGIN_ID
38
38
  },
39
39
  Component: async () => {
40
- const { App } = await Promise.resolve().then(() => require("../_chunks/App-Cn0wk9Bd.js"));
40
+ const { App } = await Promise.resolve().then(() => require("../_chunks/App-BegikOzF.js"));
41
41
  return App;
42
42
  }
43
43
  });
@@ -36,7 +36,7 @@ const index = {
36
36
  defaultMessage: PLUGIN_ID
37
37
  },
38
38
  Component: async () => {
39
- const { App } = await import("../_chunks/App-B02Agl-6.mjs");
39
+ const { App } = await import("../_chunks/App-_ybXHESK.mjs");
40
40
  return App;
41
41
  }
42
42
  });
@@ -97,9 +97,19 @@ const config$1 = ({ strapi }) => ({
97
97
  ctx.body = data;
98
98
  }
99
99
  });
100
- const openai$1 = new OpenAI__default.default({
101
- apiKey: process.env.OPENAI_API_KEY
102
- });
100
+ async function getOpenAI$1(strapi) {
101
+ const pluginStore = strapi.store({
102
+ environment: null,
103
+ type: "plugin",
104
+ name: "faqchatbot"
105
+ });
106
+ const settings = await pluginStore.get({ key: "settings" });
107
+ const key = settings?.openaiKey;
108
+ if (!key) {
109
+ throw new Error("OpenAI key not configured in plugin settings");
110
+ }
111
+ return new OpenAI__default.default({ apiKey: key });
112
+ }
103
113
  async function getContactLink(strapi) {
104
114
  const pluginStore = strapi.store({
105
115
  environment: null,
@@ -171,12 +181,13 @@ async function getActiveCollections(strapi) {
171
181
  return [];
172
182
  }
173
183
  }
174
- async function rephraseQuestion(history, question) {
184
+ async function rephraseQuestion(strapi, history, question) {
175
185
  if (!history || !Array.isArray(history) || history.length === 0) {
176
186
  return question;
177
187
  }
178
188
  try {
179
- const response = await openai$1.chat.completions.create({
189
+ const openai = await getOpenAI$1(strapi);
190
+ const response = await openai.chat.completions.create({
180
191
  model: "gpt-4o-mini",
181
192
  temperature: 0,
182
193
  messages: [
@@ -318,7 +329,8 @@ function cosineSimilarity(a, b) {
318
329
  return dot / (Math.sqrt(normA) * Math.sqrt(normB));
319
330
  }
320
331
  async function searchFAQ(question, strapi) {
321
- const embedding = await openai$1.embeddings.create({
332
+ const openai = await getOpenAI$1(strapi);
333
+ const embedding = await openai.embeddings.create({
322
334
  model: "text-embedding-3-small",
323
335
  input: question
324
336
  });
@@ -352,8 +364,9 @@ async function searchFAQ(question, strapi) {
352
364
  }
353
365
  return scored.slice(0, 3).map((s) => s.answer);
354
366
  }
355
- async function simplePlanner(question, activeCollections, instructions) {
356
- const response = await openai$1.chat.completions.create({
367
+ async function simplePlanner(strapi, question, activeCollections, instructions) {
368
+ const openai = await getOpenAI$1(strapi);
369
+ const response = await openai.chat.completions.create({
357
370
  model: "gpt-4o-mini",
358
371
  temperature: 0,
359
372
  messages: [
@@ -524,9 +537,10 @@ ${JSON.stringify(activeCollections, null, 2)}
524
537
  return null;
525
538
  }
526
539
  }
527
- async function realtimeInterpreterAI(question, realtimeData) {
540
+ async function realtimeInterpreterAI(strapi, question, realtimeData) {
528
541
  if (!realtimeData) return null;
529
- const response = await openai$1.chat.completions.create({
542
+ const openai = await getOpenAI$1(strapi);
543
+ const response = await openai.chat.completions.create({
530
544
  model: "gpt-4o-mini",
531
545
  temperature: 0.2,
532
546
  messages: [
@@ -559,13 +573,14 @@ ${JSON.stringify(realtimeData)}
559
573
  const text = response.choices[0].message.content;
560
574
  return text;
561
575
  }
562
- async function finalAggregator(ctx, question, faq, realtimeMeta, realtimeText, contactLink, instructions) {
576
+ async function finalAggregator(strapi, ctx, question, faq, realtimeMeta, realtimeText, contactLink, instructions) {
563
577
  ctx.set("Content-Type", "text/event-stream");
564
578
  ctx.set("Cache-Control", "no-cache");
565
579
  ctx.set("Connection", "keep-alive");
566
580
  ctx.status = 200;
567
581
  ctx.res.flushHeaders?.();
568
- const stream = await openai$1.chat.completions.create({
582
+ const openai = await getOpenAI$1(strapi);
583
+ const stream = await openai.chat.completions.create({
569
584
  model: "gpt-4o-mini",
570
585
  temperature: 0.3,
571
586
  stream: true,
@@ -704,18 +719,19 @@ const ask = ({ strapi }) => ({
704
719
  const activeCollections = await getActiveCollections(strapi);
705
720
  if (!activeCollections || activeCollections.length === 0) {
706
721
  }
707
- const rewritten = await rephraseQuestion(history, question);
722
+ const rewritten = await rephraseQuestion(strapi, history, question);
708
723
  const contactLink = await getContactLink(strapi);
709
724
  const faqResults = await searchFAQ(rewritten, strapi);
710
- const plan = await simplePlanner(rewritten, activeCollections, instructions);
725
+ const plan = await simplePlanner(strapi, rewritten, activeCollections, instructions);
711
726
  let realtimeResults = null;
712
727
  let realtimeAIText = null;
713
728
  if (plan && plan.collection) {
714
729
  realtimeResults = await searchRealtime(strapi, plan, activeCollections);
715
- realtimeAIText = await realtimeInterpreterAI(rewritten, realtimeResults);
730
+ realtimeAIText = await realtimeInterpreterAI(strapi, rewritten, realtimeResults);
716
731
  } else {
717
732
  }
718
733
  await finalAggregator(
734
+ strapi,
719
735
  ctx,
720
736
  rewritten,
721
737
  faqResults,
@@ -834,13 +850,23 @@ const routes = {
834
850
  admin,
835
851
  "content-api": contentApi
836
852
  };
837
- const openai = new OpenAI__default.default({
838
- apiKey: process.env.OPENAI_API_KEY
839
- });
853
+ async function getOpenAI(strapi) {
854
+ const pluginStore = strapi.store({
855
+ environment: null,
856
+ type: "plugin",
857
+ name: "faqchatbot"
858
+ });
859
+ const settings = await pluginStore.get({ key: "settings" });
860
+ const key = settings?.openaiKey;
861
+ if (!key) return null;
862
+ return new OpenAI__default.default({ apiKey: key });
863
+ }
840
864
  const embed = ({ strapi }) => ({
841
865
  async generateEmbedding(text) {
842
866
  try {
843
- if (!process.env.OPENAI_API_KEY) {
867
+ const openai = await getOpenAI(strapi);
868
+ if (!openai) {
869
+ strapi.log.warn("OpenAI key missing – skipping embedding");
844
870
  return null;
845
871
  }
846
872
  const response = await openai.embeddings.create({
@@ -94,9 +94,19 @@ const config$1 = ({ strapi }) => ({
94
94
  ctx.body = data;
95
95
  }
96
96
  });
97
- const openai$1 = new OpenAI({
98
- apiKey: process.env.OPENAI_API_KEY
99
- });
97
+ async function getOpenAI$1(strapi) {
98
+ const pluginStore = strapi.store({
99
+ environment: null,
100
+ type: "plugin",
101
+ name: "faqchatbot"
102
+ });
103
+ const settings = await pluginStore.get({ key: "settings" });
104
+ const key = settings?.openaiKey;
105
+ if (!key) {
106
+ throw new Error("OpenAI key not configured in plugin settings");
107
+ }
108
+ return new OpenAI({ apiKey: key });
109
+ }
100
110
  async function getContactLink(strapi) {
101
111
  const pluginStore = strapi.store({
102
112
  environment: null,
@@ -168,12 +178,13 @@ async function getActiveCollections(strapi) {
168
178
  return [];
169
179
  }
170
180
  }
171
- async function rephraseQuestion(history, question) {
181
+ async function rephraseQuestion(strapi, history, question) {
172
182
  if (!history || !Array.isArray(history) || history.length === 0) {
173
183
  return question;
174
184
  }
175
185
  try {
176
- const response = await openai$1.chat.completions.create({
186
+ const openai = await getOpenAI$1(strapi);
187
+ const response = await openai.chat.completions.create({
177
188
  model: "gpt-4o-mini",
178
189
  temperature: 0,
179
190
  messages: [
@@ -315,7 +326,8 @@ function cosineSimilarity(a, b) {
315
326
  return dot / (Math.sqrt(normA) * Math.sqrt(normB));
316
327
  }
317
328
  async function searchFAQ(question, strapi) {
318
- const embedding = await openai$1.embeddings.create({
329
+ const openai = await getOpenAI$1(strapi);
330
+ const embedding = await openai.embeddings.create({
319
331
  model: "text-embedding-3-small",
320
332
  input: question
321
333
  });
@@ -349,8 +361,9 @@ async function searchFAQ(question, strapi) {
349
361
  }
350
362
  return scored.slice(0, 3).map((s) => s.answer);
351
363
  }
352
- async function simplePlanner(question, activeCollections, instructions) {
353
- const response = await openai$1.chat.completions.create({
364
+ async function simplePlanner(strapi, question, activeCollections, instructions) {
365
+ const openai = await getOpenAI$1(strapi);
366
+ const response = await openai.chat.completions.create({
354
367
  model: "gpt-4o-mini",
355
368
  temperature: 0,
356
369
  messages: [
@@ -521,9 +534,10 @@ ${JSON.stringify(activeCollections, null, 2)}
521
534
  return null;
522
535
  }
523
536
  }
524
- async function realtimeInterpreterAI(question, realtimeData) {
537
+ async function realtimeInterpreterAI(strapi, question, realtimeData) {
525
538
  if (!realtimeData) return null;
526
- const response = await openai$1.chat.completions.create({
539
+ const openai = await getOpenAI$1(strapi);
540
+ const response = await openai.chat.completions.create({
527
541
  model: "gpt-4o-mini",
528
542
  temperature: 0.2,
529
543
  messages: [
@@ -556,13 +570,14 @@ ${JSON.stringify(realtimeData)}
556
570
  const text = response.choices[0].message.content;
557
571
  return text;
558
572
  }
559
- async function finalAggregator(ctx, question, faq, realtimeMeta, realtimeText, contactLink, instructions) {
573
+ async function finalAggregator(strapi, ctx, question, faq, realtimeMeta, realtimeText, contactLink, instructions) {
560
574
  ctx.set("Content-Type", "text/event-stream");
561
575
  ctx.set("Cache-Control", "no-cache");
562
576
  ctx.set("Connection", "keep-alive");
563
577
  ctx.status = 200;
564
578
  ctx.res.flushHeaders?.();
565
- const stream = await openai$1.chat.completions.create({
579
+ const openai = await getOpenAI$1(strapi);
580
+ const stream = await openai.chat.completions.create({
566
581
  model: "gpt-4o-mini",
567
582
  temperature: 0.3,
568
583
  stream: true,
@@ -701,18 +716,19 @@ const ask = ({ strapi }) => ({
701
716
  const activeCollections = await getActiveCollections(strapi);
702
717
  if (!activeCollections || activeCollections.length === 0) {
703
718
  }
704
- const rewritten = await rephraseQuestion(history, question);
719
+ const rewritten = await rephraseQuestion(strapi, history, question);
705
720
  const contactLink = await getContactLink(strapi);
706
721
  const faqResults = await searchFAQ(rewritten, strapi);
707
- const plan = await simplePlanner(rewritten, activeCollections, instructions);
722
+ const plan = await simplePlanner(strapi, rewritten, activeCollections, instructions);
708
723
  let realtimeResults = null;
709
724
  let realtimeAIText = null;
710
725
  if (plan && plan.collection) {
711
726
  realtimeResults = await searchRealtime(strapi, plan, activeCollections);
712
- realtimeAIText = await realtimeInterpreterAI(rewritten, realtimeResults);
727
+ realtimeAIText = await realtimeInterpreterAI(strapi, rewritten, realtimeResults);
713
728
  } else {
714
729
  }
715
730
  await finalAggregator(
731
+ strapi,
716
732
  ctx,
717
733
  rewritten,
718
734
  faqResults,
@@ -831,13 +847,23 @@ const routes = {
831
847
  admin,
832
848
  "content-api": contentApi
833
849
  };
834
- const openai = new OpenAI({
835
- apiKey: process.env.OPENAI_API_KEY
836
- });
850
+ async function getOpenAI(strapi) {
851
+ const pluginStore = strapi.store({
852
+ environment: null,
853
+ type: "plugin",
854
+ name: "faqchatbot"
855
+ });
856
+ const settings = await pluginStore.get({ key: "settings" });
857
+ const key = settings?.openaiKey;
858
+ if (!key) return null;
859
+ return new OpenAI({ apiKey: key });
860
+ }
837
861
  const embed = ({ strapi }) => ({
838
862
  async generateEmbedding(text) {
839
863
  try {
840
- if (!process.env.OPENAI_API_KEY) {
864
+ const openai = await getOpenAI(strapi);
865
+ if (!openai) {
866
+ strapi.log.warn("OpenAI key missing – skipping embedding");
841
867
  return null;
842
868
  }
843
869
  const response = await openai.embeddings.create({
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.0.0",
2
+ "version": "1.0.2",
3
3
  "keywords": [],
4
4
  "type": "commonjs",
5
5
  "main": "dist/server/index.js",