feat: release contextual omnichannel customer platform

Add Telegram, WhatsApp and email account management, realtime messaging, contextual multilingual AI, customer intelligence, knowledge workflows, enterprise controls, a unified design system and complete repository documentation.
This commit is contained in:
wmq
2026-07-30 02:29:53 +08:00
parent 9804341463
commit af49d22ca7
66 changed files with 3181 additions and 554 deletions
+85
View File
@@ -0,0 +1,85 @@
import { parsePhoneNumberFromString } from 'libphonenumber-js'
const VALID_SOURCE = new Set(['manual', 'account-profile', 'phone-prefix', 'unknown'])
export function normalizeCountryCode(value) {
const code = String(value || '').trim().toUpperCase()
return /^[A-Z]{2}$/.test(code) ? code : ''
}
export function inferCountryFromPhone(value) {
const raw = String(value || '').trim()
if (!raw) return ''
const normalized = raw.startsWith('+') ? raw : `+${raw.replace(/[^\d]/g, '')}`
try {
return normalizeCountryCode(parsePhoneNumberFromString(normalized)?.country)
} catch {
return ''
}
}
export function resolveCustomerCountry(input = {}, existing = {}, options = {}) {
const manualCountry = normalizeCountryCode(input.country)
if (options.manual === true) {
return {
country: manualCountry,
countrySource: manualCountry ? 'manual' : 'unknown',
countryConfidence: manualCountry ? 1 : 0,
countryInferredAt: new Date().toISOString(),
}
}
const existingSource = VALID_SOURCE.has(existing.countrySource)
? existing.countrySource
: 'unknown'
if (existingSource === 'manual' && normalizeCountryCode(existing.country)) {
return {
country: normalizeCountryCode(existing.country),
countrySource: 'manual',
countryConfidence: 1,
countryInferredAt: existing.countryInferredAt || existing.updatedAt,
}
}
const accountCountry = normalizeCountryCode(
input.accountCountry || input.countryCode || input.profileCountry,
)
if (accountCountry) {
return {
country: accountCountry,
countrySource: 'account-profile',
countryConfidence: 0.98,
countryInferredAt: new Date().toISOString(),
}
}
const phoneCountry = inferCountryFromPhone(input.phone || existing.phone)
if (phoneCountry) {
return {
country: phoneCountry,
countrySource: 'phone-prefix',
countryConfidence: 0.92,
countryInferredAt: new Date().toISOString(),
}
}
const preserved = normalizeCountryCode(input.country || existing.country)
const preservedSource = VALID_SOURCE.has(input.countrySource)
? input.countrySource
: existingSource
if (preserved && preservedSource !== 'unknown') {
return {
country: preserved,
countrySource: preservedSource,
countryConfidence: Number(input.countryConfidence ?? existing.countryConfidence ?? 0.7),
countryInferredAt: input.countryInferredAt || existing.countryInferredAt,
}
}
return {
country: '',
countrySource: 'unknown',
countryConfidence: 0,
countryInferredAt: null,
}
}