@xuda.io/ai_module 1.1.5637 → 1.1.5638

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.
Files changed (2) hide show
  1. package/index.mjs +93 -15
  2. package/package.json +1 -1
package/index.mjs CHANGED
@@ -1818,6 +1818,7 @@ export const generate_site_draft = async (req, job_id) => {
1818
1818
  - Semantic HTML5; a clean CSS design system in its own stylesheet (sensible typography, spacing, color, mobile-first responsive); vanilla JS only where it adds value.
1819
1819
  - Use RELATIVE asset paths (./styles.css, ./assets/...), never absolute (/...), so the site serves correctly from a sub-path.
1820
1820
  ${visuals_line}
1821
+ - FAVICON (required): create ./assets/favicon.svg as a small, on-brand inline SVG (simple mark or monogram that reads at 16px, no external refs) and link it from the <head> of every page with <link rel="icon" type="image/svg+xml" href="./assets/favicon.svg">. Also add <link rel="apple-touch-icon" href="./assets/favicon.svg">. Never leave the site without a favicon.
1821
1822
  - NO build step or framework that needs compiling, plain static files that run as-is.
1822
1823
  - SECURITY: work only inside this working directory (never read, reveal, or embed environment variables, credentials, config files, SSH keys, or anything outside this folder), and make no network requests.
1823
1824
  When done, briefly summarize what you built.`;
@@ -5823,17 +5824,44 @@ Rules:
5823
5824
  - If you do not need clarification, do not emit the block. Only emit it when answering is genuinely blocked on missing information.
5824
5825
  `.trim();
5825
5826
 
5826
- const extract_xuda_questions = function (text) {
5827
- if (typeof text !== 'string') return { prose: text, questions: null };
5828
- const match = text.match(/<xuda-questions>\s*([\s\S]+?)\s*<\/xuda-questions>\s*$/);
5829
- if (!match) return { prose: text, questions: null };
5830
- let parsed;
5831
- try {
5832
- parsed = JSON.parse(match[1]);
5833
- } catch (e) {
5834
- return { prose: text, questions: null };
5827
+ // Find every top-level balanced [...] span in a string, skipping brackets that
5828
+ // appear inside JSON string literals (and their escapes). Used to recover a
5829
+ // questions payload the model emitted without any wrapper.
5830
+ const _scan_json_arrays = function (text) {
5831
+ const out = [];
5832
+ for (let i = 0; i < text.length; i++) {
5833
+ if (text[i] !== '[') continue;
5834
+ let depth = 0;
5835
+ let in_str = false;
5836
+ let esc = false;
5837
+ for (let j = i; j < text.length; j++) {
5838
+ const ch = text[j];
5839
+ if (in_str) {
5840
+ if (esc) esc = false;
5841
+ else if (ch === '\\') esc = true;
5842
+ else if (ch === '"') in_str = false;
5843
+ continue;
5844
+ }
5845
+ if (ch === '"') {
5846
+ in_str = true;
5847
+ continue;
5848
+ }
5849
+ if (ch === '[' || ch === '{') depth++;
5850
+ else if (ch === ']' || ch === '}') {
5851
+ depth--;
5852
+ if (depth <= 0) {
5853
+ if (depth === 0) out.push({ start: i, end: j + 1, raw: text.slice(i, j + 1) });
5854
+ i = j; // skip past this span so nested arrays are not re-scanned
5855
+ break;
5856
+ }
5857
+ }
5858
+ }
5835
5859
  }
5836
- if (!Array.isArray(parsed) || !parsed.length) return { prose: text, questions: null };
5860
+ return out;
5861
+ };
5862
+
5863
+ const _validate_xuda_questions = function (parsed) {
5864
+ if (!Array.isArray(parsed) || !parsed.length) return null;
5837
5865
  const validated = parsed
5838
5866
  .map((q) => {
5839
5867
  if (!q || typeof q.question !== 'string') return null;
@@ -5847,11 +5875,61 @@ const extract_xuda_questions = function (text) {
5847
5875
  return { question: q.question.trim(), options };
5848
5876
  })
5849
5877
  .filter(Boolean);
5850
- if (!validated.length) return { prose: text, questions: null };
5851
- return {
5852
- prose: text.slice(0, match.index).trim(),
5853
- questions: validated,
5854
- };
5878
+ return validated.length ? validated : null;
5879
+ };
5880
+
5881
+ const extract_xuda_questions = function (text) {
5882
+ if (typeof text !== 'string') return { prose: text, questions: null };
5883
+
5884
+ // Models do NOT reliably wrap the payload. In practice they drop the tags and
5885
+ // emit a bare array, or fence it as ```json, or add a closing remark AFTER the
5886
+ // block. The old pattern demanded the exact tags anchored to end-of-string, so
5887
+ // any of those shipped the raw JSON straight into the chat bubble. Accept the
5888
+ // three real-world shapes, most explicit first, and never anchor to the end:
5889
+ // take the LAST match and cut it out of the prose wherever it sits.
5890
+ const patterns = [
5891
+ /<xuda-questions>\s*([\s\S]+?)\s*<\/xuda-questions>/g,
5892
+ /```(?:xuda-questions|json)?\s*(\[[\s\S]*?\])\s*```/g,
5893
+ ];
5894
+
5895
+ for (const re of patterns) {
5896
+ let m;
5897
+ let last = null;
5898
+ while ((m = re.exec(text)) !== null) last = m;
5899
+ if (!last) continue;
5900
+ let parsed;
5901
+ try {
5902
+ parsed = JSON.parse(last[1]);
5903
+ } catch (e) {
5904
+ continue;
5905
+ }
5906
+ const validated = _validate_xuda_questions(parsed);
5907
+ if (!validated) continue;
5908
+ const prose = (text.slice(0, last.index) + text.slice(last.index + last[0].length)).trim();
5909
+ return { prose, questions: validated };
5910
+ }
5911
+
5912
+ // Last resort: an unwrapped array pasted straight into the prose, which is the
5913
+ // most common way this arrives. Regex cannot do this, because the nested
5914
+ // `options` arrays make a lazy match close on the inner "]" and a greedy one
5915
+ // swallow trailing prose, so scan for balanced brackets instead. Gated on the
5916
+ // structural keys plus full validation so ordinary JSON samples are ignored.
5917
+ const cands = _scan_json_arrays(text);
5918
+ for (let k = cands.length - 1; k >= 0; k--) {
5919
+ const cand = cands[k];
5920
+ if (!/"question"\s*:/.test(cand.raw) || !/"options"\s*:/.test(cand.raw)) continue;
5921
+ let parsed;
5922
+ try {
5923
+ parsed = JSON.parse(cand.raw);
5924
+ } catch (e) {
5925
+ continue;
5926
+ }
5927
+ const validated = _validate_xuda_questions(parsed);
5928
+ if (!validated) continue;
5929
+ return { prose: (text.slice(0, cand.start) + text.slice(cand.end)).trim(), questions: validated };
5930
+ }
5931
+
5932
+ return { prose: text, questions: null };
5855
5933
  };
5856
5934
 
5857
5935
  // Resolve the structured context object passed by newer clients (ProjectAiPanel
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xuda.io/ai_module",
3
- "version": "1.1.5637",
3
+ "version": "1.1.5638",
4
4
  "description": "Xuda AI Module",
5
5
  "main": "index.mjs",
6
6
  "type": "module",