@xuda.io/ai_module 1.1.5612 → 1.1.5614

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 (3) hide show
  1. package/index.mjs +112 -23
  2. package/index_ms.mjs +4 -0
  3. package/package.json +1 -1
package/index.mjs CHANGED
@@ -4266,14 +4266,17 @@ export const create_conversation = async function (req, job_id, headers) {
4266
4266
  // write conversation to host after thumbnail update
4267
4267
  // get sender conversation
4268
4268
 
4269
- const conversation_doc = await db_module.get_app_couch_doc_native(account_profile_info.app_id, conversation_doc._id);
4269
+ // Re-fetch the freshest copy of the conversation doc after process_conversation
4270
+ // had a chance to write thumbnail / title updates. Renamed (was `conversation_doc`)
4271
+ // to avoid TDZ shadowing the outer-scope `conversation_doc` we read for the lookup id.
4272
+ const fresh_conversation_doc = await db_module.get_app_couch_doc_native(account_profile_info.app_id, conversation_doc._id);
4270
4273
 
4271
4274
  const recipient_account_profile_info = await get_active_account_profile_info(recipient_uid);
4272
- let recipient_conversation_doc = await db_module.get_app_couch_doc_native(recipient_account_profile_info.app_id, conversation_doc._id);
4273
- recipient_conversation_doc.title = conversation_doc.title;
4275
+ let recipient_conversation_doc = await db_module.get_app_couch_doc_native(recipient_account_profile_info.app_id, fresh_conversation_doc._id);
4276
+ recipient_conversation_doc.title = fresh_conversation_doc.title;
4274
4277
 
4275
- recipient_conversation_doc.thumbnail_request_ts = conversation_doc.thumbnail_request_ts;
4276
- recipient_conversation_doc.chat_image = conversation_doc.chat_image;
4278
+ recipient_conversation_doc.thumbnail_request_ts = fresh_conversation_doc.thumbnail_request_ts;
4279
+ recipient_conversation_doc.chat_image = fresh_conversation_doc.chat_image;
4277
4280
  recipient_conversation_doc.direction = 'in';
4278
4281
  await db_module.save_app_couch_doc_native(recipient_account_profile_info.app_id, recipient_conversation_doc);
4279
4282
 
@@ -5927,13 +5930,7 @@ Return a concise answer. Include only non-sensitive findings and mention when th
5927
5930
 
5928
5931
  await fs.promises.mkdir(dump_dir, { recursive: true });
5929
5932
  const sitemap_xml = await fetch_text(`${website_base}/sitemap.xml`, 8000);
5930
- const priority_urls = [
5931
- `${website_base}/`,
5932
- `${website_base}/about`,
5933
- `${website_base}/legal/privacy-policy`,
5934
- `${website_base}/legal/terms`,
5935
- `${website_base}/contact`,
5936
- ];
5933
+ const priority_urls = [`${website_base}/`, `${website_base}/about`, `${website_base}/legal/privacy-policy`, `${website_base}/legal/terms`, `${website_base}/contact`];
5937
5934
  const sitemap_urls = [...sitemap_xml.matchAll(/<loc>([\s\S]*?)<\/loc>/gi)]
5938
5935
  .map((match) => decode_html(match[1]).trim())
5939
5936
  .filter((url) => {
@@ -6029,10 +6026,7 @@ Return a concise answer. Include only non-sensitive findings and mention when th
6029
6026
  };
6030
6027
 
6031
6028
  try {
6032
- const data = await Promise.race([
6033
- run_lookup(),
6034
- new Promise((_, reject) => setTimeout(() => reject(new Error('xuda.ai website lookup timed out')), 20000)),
6035
- ]);
6029
+ const data = await Promise.race([run_lookup(), new Promise((_, reject) => setTimeout(() => reject(new Error('xuda.ai website lookup timed out')), 20000))]);
6036
6030
  return JSON.stringify(data, null, 2);
6037
6031
  } catch (err) {
6038
6032
  return `xuda.ai website lookup failed: ${err.message || String(err)}`;
@@ -8197,6 +8191,14 @@ export const get_profile_picture = async function (uid, account_type = 'business
8197
8191
  const uniqueId = Date.now() + '_' + Math.random().toString(36).substring(7);
8198
8192
  const tempOutputPath = path.join(tempDir, `output_${uniqueId}.png`);
8199
8193
 
8194
+ // Normalize: visitor accounts created through the chat widget can arrive with
8195
+ // account_type missing, null, or some other value. Treat anything that isn't
8196
+ // explicitly 'business' as 'personal' so the chat flow doesn't crash before
8197
+ // ever reaching the agent reply.
8198
+ if (account_type !== 'personal' && account_type !== 'business') {
8199
+ account_type = 'personal';
8200
+ }
8201
+
8200
8202
  const { email, create_avatar } = metadata;
8201
8203
 
8202
8204
  let prompt,
@@ -9176,6 +9178,78 @@ export const is_spam_email = async function (uid, email, subject, account_profil
9176
9178
  } catch (error) {}
9177
9179
  };
9178
9180
 
9181
+ // AI screening for a marketplace publish. Receives plain-JSON context from
9182
+ // marketplace_module (zod schemas cannot cross the broker, so the prompt +
9183
+ // structured schema must live here). Returns a structured verdict across the
9184
+ // approval rule dimensions. The caller (marketplace_approval_review) merges
9185
+ // this with deterministic account-standing checks and decides the final stat.
9186
+ export const marketplace_review_item = async function (req) {
9187
+ const { uid, item_type, item = {}, account_context = {}, account_name = '', model = _conf.default_ai_model, account_profile_info } = req || {};
9188
+
9189
+ const check_schema = z.object({
9190
+ flag: z.boolean().describe('true if this concern is present'),
9191
+ severity: z.enum(['none', 'low', 'medium', 'high']).describe('how serious the concern is'),
9192
+ note: z.string().describe('one short sentence of evidence; empty string if no concern'),
9193
+ });
9194
+
9195
+ const response_format = z.object({
9196
+ decision: z.enum(['approve', 'reject', 'manual_review']).describe('approve = safe to publish; reject = clear violation; manual_review = uncertain, needs a human'),
9197
+ confidence: z.number().min(0).max(100).describe('confidence in the decision, 0-100'),
9198
+ checks: z.object({
9199
+ ai_bot_activity: check_schema.describe('automated/bot publishing: high velocity, near-duplicate items, low-effort generated bulk'),
9200
+ commercial_undisclosed: check_schema.describe('undisclosed commercial intent: external paid links, lead-gen, off-platform payment, advertising inconsistent with declared price/category'),
9201
+ harmful_code: check_schema.describe('destructive ops, data exfiltration, crypto-miners, obfuscation, malicious network calls in the submitted code'),
9202
+ suspicious_activity: check_schema.describe('mismatched metadata, hidden payloads, evasion patterns'),
9203
+ malformed_account_name: check_schema.describe('gibberish, homoglyphs, embedded URLs, or impersonation in the account name'),
9204
+ spam_low_quality: check_schema.describe('spam, filler, or low-quality content with no real utility'),
9205
+ impersonation_trademark: check_schema.describe('impersonates a brand/person or abuses a trademark'),
9206
+ prohibited_content: check_schema.describe('adult, illegal, hateful, or otherwise disallowed content'),
9207
+ plagiarism_duplicate: check_schema.describe('appears copied from or duplicates an existing item'),
9208
+ secrets_pii_leak: check_schema.describe('hardcoded API keys, tokens, credentials, or exposed PII in the code'),
9209
+ }),
9210
+ reasons: z.array(z.string()).describe('short user-facing lines explaining the decision (empty if approved cleanly)'),
9211
+ });
9212
+
9213
+ const prompt = `You are the xuda marketplace moderation reviewer. Decide whether the following ${item_type} should be published to the public marketplace.
9214
+
9215
+ Account name: ${account_name}
9216
+ Account context (signals): ${JSON.stringify(account_context)}
9217
+
9218
+ Item:
9219
+ - name: ${item.name || ''}
9220
+ - description: ${item.description || ''}
9221
+ - category: ${item.category || ''}
9222
+ - tags: ${JSON.stringify(item.tags || [])}
9223
+ - price: ${item.price ?? 0}
9224
+ - code/content summary: ${item.code_summary || '(none provided)'}
9225
+
9226
+ Evaluate every check honestly. Set decision to:
9227
+ - "approve" when no medium/high concerns and the item is genuine and useful.
9228
+ - "reject" when there is a clear high-severity violation (harmful code, leaked secrets, prohibited content, obvious impersonation/spam, or clear bot abuse).
9229
+ - "manual_review" when concerns are present but ambiguous, or you are not confident.
9230
+ Keep "reasons" concise, factual, and user-facing (no internal jargon).`;
9231
+
9232
+ const ret = await submit_chat_gpt_prompt({
9233
+ uid,
9234
+ prompt,
9235
+ model,
9236
+ response_format,
9237
+ metadata: { func: 'marketplace_review_item', item_type },
9238
+ account_profile_info,
9239
+ });
9240
+
9241
+ if (ret.code < 0) {
9242
+ return { code: ret.code, data: ret.data };
9243
+ }
9244
+
9245
+ try {
9246
+ const verdict = JSON.parse(ret.data);
9247
+ return { code: 1, data: { ...verdict, model } };
9248
+ } catch (err) {
9249
+ return { code: -1, data: `marketplace_review_item parse error: ${err.message}` };
9250
+ }
9251
+ };
9252
+
9179
9253
  export const is_business_contact = async function (uid, email, name, subject, body, account_profile_info) {
9180
9254
  let prompt = `detect if the email "${email}", subject "${subject}", name "${name}", or body "${body}" is business or personal. Return true if it is a business , false otherwise.
9181
9255
 
@@ -14254,7 +14328,7 @@ export const get_widget_embed_snippet = async function (req, job_id, headers) {
14254
14328
  };
14255
14329
 
14256
14330
  const _widget_signup_core = async function (req, job_id, headers, opts) {
14257
- const { profile_id, email, name, widget_origin, google_sub, signup_method } = req;
14331
+ const { profile_id, email, name, widget_origin, google_sub, signup_method, visitor_lang } = req;
14258
14332
  const [owner_uid_part, pid_part] = String(profile_id || '').split('.');
14259
14333
  if (!owner_uid_part || !pid_part) return { code: -404, data: 'invalid_profile' };
14260
14334
 
@@ -14266,19 +14340,19 @@ const _widget_signup_core = async function (req, job_id, headers, opts) {
14266
14340
  if (ap_doc.widget_enabled === false) return { code: -404, data: 'widget_disabled' };
14267
14341
 
14268
14342
  const visitor_uid = await _widget_find_or_create_visitor(email, name, google_sub);
14269
- return await _widget_signup_finalize(visitor_uid, email, name, widget_origin, signup_method, account_profile_info, canonical_owner_uid, canonical_profile_id, ap_doc, job_id, headers);
14343
+ return await _widget_signup_finalize(visitor_uid, email, name, widget_origin, signup_method, account_profile_info, canonical_owner_uid, canonical_profile_id, ap_doc, job_id, headers, visitor_lang);
14270
14344
  };
14271
14345
 
14272
14346
  export const widget_signup = async function (req, job_id, headers) {
14273
14347
  try {
14274
- let { profile_id, email, name, widget_origin } = req;
14348
+ let { profile_id, email, name, widget_origin, visitor_lang } = req;
14275
14349
  if (typeof email !== 'string' || typeof name !== 'string') return { code: -1, data: 'invalid_input' };
14276
14350
  email = email.toLowerCase().trim();
14277
14351
  name = name.trim();
14278
14352
  if (!email || !name) return { code: -1, data: 'invalid_input' };
14279
14353
  if (!WIDGET_EMAIL_RE.test(email)) return { code: -1, data: 'invalid_email' };
14280
14354
 
14281
- return await _widget_signup_core({ profile_id, email, name, widget_origin, signup_method: 'email' }, job_id, headers);
14355
+ return await _widget_signup_core({ profile_id, email, name, widget_origin, visitor_lang, signup_method: 'email' }, job_id, headers);
14282
14356
  } catch (err) {
14283
14357
  return { code: -1, data: err.message || String(err) };
14284
14358
  }
@@ -14286,7 +14360,7 @@ export const widget_signup = async function (req, job_id, headers) {
14286
14360
 
14287
14361
  export const widget_signup_google = async function (req, job_id, headers) {
14288
14362
  try {
14289
- const { profile_id, id_token, widget_origin } = req;
14363
+ const { profile_id, id_token, widget_origin, visitor_lang } = req;
14290
14364
  if (!id_token || typeof id_token !== 'string') return { code: -1, data: 'missing_id_token' };
14291
14365
 
14292
14366
  let payload;
@@ -14302,13 +14376,13 @@ export const widget_signup_google = async function (req, job_id, headers) {
14302
14376
  const name = String(payload.name || payload.email?.split('@')[0] || 'Friend').trim();
14303
14377
  if (!email) return { code: -1, data: 'no_email_in_token' };
14304
14378
 
14305
- return await _widget_signup_core({ profile_id, email, name, widget_origin, google_sub: payload.sub, signup_method: 'google' }, job_id, headers);
14379
+ return await _widget_signup_core({ profile_id, email, name, widget_origin, visitor_lang, google_sub: payload.sub, signup_method: 'google' }, job_id, headers);
14306
14380
  } catch (err) {
14307
14381
  return { code: -1, data: err.message || String(err) };
14308
14382
  }
14309
14383
  };
14310
14384
 
14311
- const _widget_signup_finalize = async function (visitor_uid, email, name, widget_origin, signup_method, account_profile_info, canonical_owner_uid, canonical_profile_id, ap_doc, job_id, headers) {
14385
+ const _widget_signup_finalize = async function (visitor_uid, email, name, widget_origin, signup_method, account_profile_info, canonical_owner_uid, canonical_profile_id, ap_doc, job_id, headers, visitor_lang) {
14312
14386
  try {
14313
14387
  let team_req_doc = await _widget_find_existing_team_req(visitor_uid, canonical_profile_id);
14314
14388
  let assigned_uid;
@@ -14372,12 +14446,20 @@ const _widget_signup_finalize = async function (visitor_uid, email, name, widget
14372
14446
 
14373
14447
  const widget_token = `wds_${crypto.randomUUID().replace(/-/g, '')}`;
14374
14448
  const now = Date.now();
14449
+ // Normalize visitor_lang to a short tag (e.g. "ja-JP" → "ja"). The
14450
+ // auto-reply agent reads this from the session to respond in the
14451
+ // visitor's language. Empty / missing → English fallback at reply time.
14452
+ const visitor_lang_base = String(visitor_lang || '')
14453
+ .toLowerCase()
14454
+ .split('-')[0]
14455
+ .slice(0, 8);
14375
14456
  const session_doc = {
14376
14457
  _id: widget_token,
14377
14458
  docType: 'widget_session',
14378
14459
  stat: 2,
14379
14460
  state,
14380
14461
  visitor_uid,
14462
+ visitor_lang: visitor_lang_base || '',
14381
14463
  team_req_id: team_req_doc._id,
14382
14464
  assigned_uid,
14383
14465
  owner_uid: canonical_owner_uid,
@@ -14396,6 +14478,12 @@ const _widget_signup_finalize = async function (visitor_uid, email, name, widget
14396
14478
  const assigned_avatar = assigned_info_ret?.data?.profile_picture || assigned_info_ret?.data?.profile_avatar || '';
14397
14479
  const greeting = ap_doc?.widget_config?.greeting || WIDGET_DEFAULT_GREETING;
14398
14480
 
14481
+ // Surface the visitor's first name so the chat widget can personalize the
14482
+ // greeting on the page ("Hi {visitor_first_name}, I'm Valentina..."). The
14483
+ // widget reads this from the persisted session and replaces the
14484
+ // {visitor_name} placeholder client-side.
14485
+ const visitor_first_name = _widget_split_name(name).first_name || '';
14486
+
14399
14487
  return {
14400
14488
  code: 1,
14401
14489
  data: {
@@ -14405,6 +14493,7 @@ const _widget_signup_finalize = async function (visitor_uid, email, name, widget
14405
14493
  assigned_avatar,
14406
14494
  greeting,
14407
14495
  conversation_id,
14496
+ visitor_first_name,
14408
14497
  signup_method: signup_method || 'email',
14409
14498
  account_active: signup_method === 'google',
14410
14499
  signin_url: signup_method === 'google' ? `https://${_conf.domain || 'xuda.ai'}/dashboard/login` : undefined,
package/index_ms.mjs CHANGED
@@ -260,3 +260,7 @@ export const widget_send_message = async function (...args) {
260
260
  export const widget_get_messages = async function (...args) {
261
261
  return await broker.send_to_queue("widget_get_messages", ...args);
262
262
  };
263
+
264
+ export const marketplace_review_item = async function (...args) {
265
+ return await broker.send_to_queue("marketplace_review_item", ...args);
266
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xuda.io/ai_module",
3
- "version": "1.1.5612",
3
+ "version": "1.1.5614",
4
4
  "description": "Xuda AI Module",
5
5
  "main": "index.mjs",
6
6
  "type": "module",