@roll-agent/smart-reply-agent 0.1.0 → 0.1.1
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.
- package/dist/ai/model-registry.d.ts +0 -1
- package/dist/ai/model-registry.js +1 -205
- package/dist/ai/structured-output.d.ts +0 -1
- package/dist/ai/structured-output.js +1 -78
- package/dist/errors/app-error.d.ts +0 -1
- package/dist/errors/app-error.js +1 -95
- package/dist/errors/error-codes.d.ts +0 -1
- package/dist/errors/error-codes.js +1 -115
- package/dist/errors/error-factory.d.ts +0 -1
- package/dist/errors/error-factory.js +1 -86
- package/dist/errors/error-utils.d.ts +0 -1
- package/dist/errors/error-utils.js +1 -188
- package/dist/errors/index.d.ts +0 -1
- package/dist/errors/index.js +1 -5
- package/dist/index.d.ts +0 -1
- package/dist/index.js +1 -12
- package/dist/log-control.d.ts +0 -1
- package/dist/log-control.js +1 -15
- package/dist/pipeline/age-eligibility.d.ts +0 -1
- package/dist/pipeline/age-eligibility.js +1 -176
- package/dist/pipeline/candidate-context.d.ts +0 -1
- package/dist/pipeline/candidate-context.js +1 -31
- package/dist/pipeline/candidate-utils.d.ts +0 -1
- package/dist/pipeline/candidate-utils.js +1 -33
- package/dist/pipeline/classification.d.ts +0 -1
- package/dist/pipeline/classification.js +1 -206
- package/dist/pipeline/context-builder.d.ts +0 -1
- package/dist/pipeline/context-builder.js +1 -404
- package/dist/pipeline/pipeline-progress.d.ts +0 -1
- package/dist/pipeline/pipeline-progress.js +1 -33
- package/dist/pipeline/reply-gate.d.ts +0 -1
- package/dist/pipeline/reply-gate.js +1 -139
- package/dist/pipeline/smart-reply.d.ts +0 -1
- package/dist/pipeline/smart-reply.js +1 -418
- package/dist/pipeline.d.ts +0 -1
- package/dist/pipeline.js +1 -12
- package/dist/services/brand-alias.d.ts +0 -1
- package/dist/services/brand-alias.js +1 -184
- package/dist/services/brand-config-selectors.d.ts +0 -1
- package/dist/services/brand-config-selectors.js +1 -30
- package/dist/services/config-loader.d.ts +0 -1
- package/dist/services/config-loader.js +1 -45
- package/dist/services/duliday-api.d.ts +0 -1
- package/dist/services/duliday-api.js +1 -160
- package/dist/services/duliday-mapper.d.ts +0 -1
- package/dist/services/duliday-mapper.js +1 -536
- package/dist/tools/generate-reply.d.ts +0 -1
- package/dist/tools/generate-reply.js +1 -132
- package/dist/tools/sync-brand-data.d.ts +0 -1
- package/dist/tools/sync-brand-data.js +1 -114
- package/dist/types/brand-resolution.d.ts +0 -1
- package/dist/types/brand-resolution.js +1 -37
- package/dist/types/classification.d.ts +0 -1
- package/dist/types/classification.js +1 -30
- package/dist/types/config.d.ts +0 -1
- package/dist/types/config.js +1 -7
- package/dist/types/duliday-api.d.ts +0 -1
- package/dist/types/duliday-api.js +1 -235
- package/dist/types/geocoding.d.ts +0 -1
- package/dist/types/geocoding.js +1 -12
- package/dist/types/reply-policy.d.ts +0 -1
- package/dist/types/reply-policy.js +1 -332
- package/dist/types/zhipin.d.ts +0 -1
- package/dist/types/zhipin.js +1 -123
- package/package.json +3 -3
- package/dist/ai/model-registry.d.ts.map +0 -1
- package/dist/ai/model-registry.js.map +0 -1
- package/dist/ai/structured-output.d.ts.map +0 -1
- package/dist/ai/structured-output.js.map +0 -1
- package/dist/errors/app-error.d.ts.map +0 -1
- package/dist/errors/app-error.js.map +0 -1
- package/dist/errors/error-codes.d.ts.map +0 -1
- package/dist/errors/error-codes.js.map +0 -1
- package/dist/errors/error-factory.d.ts.map +0 -1
- package/dist/errors/error-factory.js.map +0 -1
- package/dist/errors/error-utils.d.ts.map +0 -1
- package/dist/errors/error-utils.js.map +0 -1
- package/dist/errors/index.d.ts.map +0 -1
- package/dist/errors/index.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/log-control.d.ts.map +0 -1
- package/dist/log-control.js.map +0 -1
- package/dist/pipeline/age-eligibility.d.ts.map +0 -1
- package/dist/pipeline/age-eligibility.js.map +0 -1
- package/dist/pipeline/candidate-context.d.ts.map +0 -1
- package/dist/pipeline/candidate-context.js.map +0 -1
- package/dist/pipeline/candidate-utils.d.ts.map +0 -1
- package/dist/pipeline/candidate-utils.js.map +0 -1
- package/dist/pipeline/classification.d.ts.map +0 -1
- package/dist/pipeline/classification.js.map +0 -1
- package/dist/pipeline/context-builder.d.ts.map +0 -1
- package/dist/pipeline/context-builder.js.map +0 -1
- package/dist/pipeline/pipeline-progress.d.ts.map +0 -1
- package/dist/pipeline/pipeline-progress.js.map +0 -1
- package/dist/pipeline/reply-gate.d.ts.map +0 -1
- package/dist/pipeline/reply-gate.js.map +0 -1
- package/dist/pipeline/smart-reply.d.ts.map +0 -1
- package/dist/pipeline/smart-reply.js.map +0 -1
- package/dist/pipeline.d.ts.map +0 -1
- package/dist/pipeline.js.map +0 -1
- package/dist/services/brand-alias.d.ts.map +0 -1
- package/dist/services/brand-alias.js.map +0 -1
- package/dist/services/brand-config-selectors.d.ts.map +0 -1
- package/dist/services/brand-config-selectors.js.map +0 -1
- package/dist/services/config-loader.d.ts.map +0 -1
- package/dist/services/config-loader.js.map +0 -1
- package/dist/services/duliday-api.d.ts.map +0 -1
- package/dist/services/duliday-api.js.map +0 -1
- package/dist/services/duliday-mapper.d.ts.map +0 -1
- package/dist/services/duliday-mapper.js.map +0 -1
- package/dist/tools/generate-reply.d.ts.map +0 -1
- package/dist/tools/generate-reply.js.map +0 -1
- package/dist/tools/sync-brand-data.d.ts.map +0 -1
- package/dist/tools/sync-brand-data.js.map +0 -1
- package/dist/types/brand-resolution.d.ts.map +0 -1
- package/dist/types/brand-resolution.js.map +0 -1
- package/dist/types/classification.d.ts.map +0 -1
- package/dist/types/classification.js.map +0 -1
- package/dist/types/config.d.ts.map +0 -1
- package/dist/types/config.js.map +0 -1
- package/dist/types/duliday-api.d.ts.map +0 -1
- package/dist/types/duliday-api.js.map +0 -1
- package/dist/types/geocoding.d.ts.map +0 -1
- package/dist/types/geocoding.js.map +0 -1
- package/dist/types/reply-policy.d.ts.map +0 -1
- package/dist/types/reply-policy.js.map +0 -1
- package/dist/types/zhipin.d.ts.map +0 -1
- package/dist/types/zhipin.js.map +0 -1
|
@@ -1,184 +1 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { AppError } from "../errors/app-error.js";
|
|
3
|
-
import { ErrorCode } from "../errors/error-codes.js";
|
|
4
|
-
// ========== Types ==========
|
|
5
|
-
const DulidayBrandItemSchema = z.object({
|
|
6
|
-
id: z.number(),
|
|
7
|
-
name: z.string(),
|
|
8
|
-
aliases: z.array(z.string()),
|
|
9
|
-
projectIdList: z.array(z.number()),
|
|
10
|
-
});
|
|
11
|
-
const DulidayBrandListResponseSchema = z.object({
|
|
12
|
-
code: z.number(),
|
|
13
|
-
message: z.string().optional(),
|
|
14
|
-
data: z.object({
|
|
15
|
-
result: z.array(DulidayBrandItemSchema),
|
|
16
|
-
total: z.number(),
|
|
17
|
-
}),
|
|
18
|
-
});
|
|
19
|
-
// ========== Cache ==========
|
|
20
|
-
const CACHE_TTL_MS = 5 * 60 * 1000;
|
|
21
|
-
let cache = null;
|
|
22
|
-
function isCacheValid() {
|
|
23
|
-
return cache !== null && Date.now() - cache.timestamp < CACHE_TTL_MS;
|
|
24
|
-
}
|
|
25
|
-
// ========== Constants ==========
|
|
26
|
-
const REQUEST_TIMEOUT_MS = 30_000;
|
|
27
|
-
function getDulidayBrandListUrl() {
|
|
28
|
-
const endpoint = process.env.DULIDAY_BRAND_LIST_URL;
|
|
29
|
-
return typeof endpoint === "string" && endpoint.trim().length > 0 ? endpoint : undefined;
|
|
30
|
-
}
|
|
31
|
-
// ========== API Fetch ==========
|
|
32
|
-
async function fetchBrandAliasesFromApi(dulidayToken) {
|
|
33
|
-
const token = dulidayToken || process.env.DULIDAY_TOKEN;
|
|
34
|
-
const endpoint = getDulidayBrandListUrl();
|
|
35
|
-
if (!token) {
|
|
36
|
-
throw new AppError({
|
|
37
|
-
code: ErrorCode.CONFIG_MISSING_FIELD,
|
|
38
|
-
message: "DULIDAY_TOKEN 未配置,无法获取品牌别名数据",
|
|
39
|
-
userMessage: "品牌别名数据加载失败:缺少 Duliday Token 配置",
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
if (!endpoint) {
|
|
43
|
-
throw new AppError({
|
|
44
|
-
code: ErrorCode.CONFIG_MISSING_FIELD,
|
|
45
|
-
message: "DULIDAY_BRAND_LIST_URL 未配置,无法获取品牌别名数据",
|
|
46
|
-
userMessage: "品牌别名数据加载失败:缺少 Duliday 品牌接口配置",
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
const controller = new AbortController();
|
|
50
|
-
const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
51
|
-
try {
|
|
52
|
-
const response = await fetch(endpoint, {
|
|
53
|
-
method: "POST",
|
|
54
|
-
headers: { "Content-Type": "application/json", "Duliday-Token": token },
|
|
55
|
-
body: JSON.stringify({ pageNum: 1, pageSize: 1000 }),
|
|
56
|
-
signal: controller.signal,
|
|
57
|
-
});
|
|
58
|
-
if (!response.ok) {
|
|
59
|
-
throw new AppError({
|
|
60
|
-
code: ErrorCode.NETWORK_HTTP_ERROR,
|
|
61
|
-
message: `Duliday 品牌列表 API 返回 HTTP ${response.status}: ${response.statusText}`,
|
|
62
|
-
userMessage: `品牌别名数据加载失败:服务端返回 ${response.status}`,
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
const json = await response.json();
|
|
66
|
-
const parsed = DulidayBrandListResponseSchema.safeParse(json);
|
|
67
|
-
if (!parsed.success) {
|
|
68
|
-
throw new AppError({
|
|
69
|
-
code: ErrorCode.VALIDATION_SCHEMA_ERROR,
|
|
70
|
-
message: `Duliday 品牌列表响应格式校验失败: ${parsed.error.message}`,
|
|
71
|
-
userMessage: "品牌别名数据加载失败:响应格式异常",
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
return parsed.data.data.result;
|
|
75
|
-
}
|
|
76
|
-
catch (error) {
|
|
77
|
-
if (error instanceof AppError)
|
|
78
|
-
throw error;
|
|
79
|
-
if (error instanceof Error && error.name === "AbortError") {
|
|
80
|
-
throw new AppError({
|
|
81
|
-
code: ErrorCode.NETWORK_TIMEOUT,
|
|
82
|
-
message: `Duliday 品牌列表 API 请求超时 (${REQUEST_TIMEOUT_MS}ms)`,
|
|
83
|
-
userMessage: "品牌别名数据加载超时,请稍后重试",
|
|
84
|
-
cause: error,
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
throw new AppError({
|
|
88
|
-
code: ErrorCode.SYSTEM_DEPENDENCY_FAILED,
|
|
89
|
-
message: `获取品牌别名数据失败: ${error instanceof Error ? error.message : String(error)}`,
|
|
90
|
-
userMessage: "品牌别名数据加载失败,请检查网络连接",
|
|
91
|
-
...(error instanceof Error ? { cause: error } : {}),
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
finally {
|
|
95
|
-
clearTimeout(timeoutId);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
// ========== Map Builders ==========
|
|
99
|
-
function buildMapsFromApiData(apiData, actualBrands, brandMapping) {
|
|
100
|
-
const dictionary = {};
|
|
101
|
-
const aliasMap = new Map();
|
|
102
|
-
const normalizeAlias = (value) => value.trim();
|
|
103
|
-
const toAliasMapKey = (value) => normalizeAlias(value)
|
|
104
|
-
.toLowerCase()
|
|
105
|
-
.replace(/[\s._-]+/g, "");
|
|
106
|
-
const registerAlias = (alias, brandName) => {
|
|
107
|
-
const key = toAliasMapKey(alias);
|
|
108
|
-
if (!key)
|
|
109
|
-
return;
|
|
110
|
-
const existing = aliasMap.get(key);
|
|
111
|
-
if (!existing || brandName.length > existing.length) {
|
|
112
|
-
aliasMap.set(key, brandName);
|
|
113
|
-
}
|
|
114
|
-
};
|
|
115
|
-
const sortedApiData = [...apiData].sort((a, b) => b.name.length - a.name.length);
|
|
116
|
-
for (const item of sortedApiData) {
|
|
117
|
-
const brandName = normalizeAlias(item.name);
|
|
118
|
-
if (!brandName)
|
|
119
|
-
continue;
|
|
120
|
-
const allAliases = Array.from(new Set([brandName, ...item.aliases.map(normalizeAlias).filter(Boolean)].filter((alias) => alias !== brandName)));
|
|
121
|
-
allAliases.unshift(brandName);
|
|
122
|
-
dictionary[brandName] = allAliases;
|
|
123
|
-
for (const alias of allAliases) {
|
|
124
|
-
registerAlias(alias, brandName);
|
|
125
|
-
}
|
|
126
|
-
if (brandMapping && item.projectIdList.length > 0) {
|
|
127
|
-
for (const projectId of item.projectIdList) {
|
|
128
|
-
const localBrandName = brandMapping[String(projectId)];
|
|
129
|
-
if (!localBrandName || localBrandName === brandName)
|
|
130
|
-
continue;
|
|
131
|
-
if (!localBrandName.includes(brandName))
|
|
132
|
-
continue;
|
|
133
|
-
const prefix = localBrandName.replace(brandName, "");
|
|
134
|
-
if (!prefix)
|
|
135
|
-
continue;
|
|
136
|
-
const regionalAliases = [localBrandName];
|
|
137
|
-
for (const parentAlias of allAliases) {
|
|
138
|
-
if (parentAlias === brandName)
|
|
139
|
-
continue;
|
|
140
|
-
regionalAliases.push(`${prefix}${parentAlias}`);
|
|
141
|
-
}
|
|
142
|
-
if (!dictionary[localBrandName]) {
|
|
143
|
-
dictionary[localBrandName] = regionalAliases;
|
|
144
|
-
}
|
|
145
|
-
else {
|
|
146
|
-
const existing = new Set(dictionary[localBrandName]);
|
|
147
|
-
for (const alias of regionalAliases) {
|
|
148
|
-
if (!existing.has(alias))
|
|
149
|
-
dictionary[localBrandName].push(alias);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
for (const alias of regionalAliases) {
|
|
153
|
-
registerAlias(alias, localBrandName);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
for (const brand of actualBrands) {
|
|
159
|
-
if (!dictionary[brand]) {
|
|
160
|
-
dictionary[brand] = [brand];
|
|
161
|
-
registerAlias(brand, brand);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
return { dictionary, aliasMap };
|
|
165
|
-
}
|
|
166
|
-
// ========== Public API ==========
|
|
167
|
-
async function ensureCachePopulated(dulidayToken) {
|
|
168
|
-
if (isCacheValid())
|
|
169
|
-
return;
|
|
170
|
-
const apiData = await fetchBrandAliasesFromApi(dulidayToken);
|
|
171
|
-
// 品牌映射在 Agent 模式下为空(不依赖 DB)
|
|
172
|
-
const actualBrands = new Set();
|
|
173
|
-
const { dictionary, aliasMap } = buildMapsFromApiData(apiData, actualBrands);
|
|
174
|
-
cache = { aliasMap, dictionary, timestamp: Date.now() };
|
|
175
|
-
}
|
|
176
|
-
export async function getSharedBrandDictionary(dulidayToken) {
|
|
177
|
-
await ensureCachePopulated(dulidayToken);
|
|
178
|
-
return cache.dictionary;
|
|
179
|
-
}
|
|
180
|
-
export async function getSharedBrandAliasMap(dulidayToken) {
|
|
181
|
-
await ensureCachePopulated(dulidayToken);
|
|
182
|
-
return cache.aliasMap;
|
|
183
|
-
}
|
|
184
|
-
//# sourceMappingURL=brand-alias.js.map
|
|
1
|
+
import{z as e}from"zod";import{AppError as t}from"../errors/app-error.js";import{ErrorCode as o}from"../errors/error-codes.js";const s=e.object({id:e.number(),name:e.string(),aliases:e.array(e.string()),projectIdList:e.array(e.number())}),n=e.object({code:e.number(),message:e.string().optional(),data:e.object({result:e.array(s),total:e.number()})}),r=3e5;let a=null;function i(){return null!==a&&Date.now()-a.timestamp<r}const c=3e4;function u(){const e=process.env.DULIDAY_BRAND_LIST_URL;return"string"==typeof e&&e.trim().length>0?e:void 0}async function f(e){const s=e||process.env.DULIDAY_TOKEN,r=u();if(!s)throw new t({code:o.CONFIG_MISSING_FIELD,message:"DULIDAY_TOKEN 未配置,无法获取品牌别名数据",userMessage:"品牌别名数据加载失败:缺少 Duliday Token 配置"});if(!r)throw new t({code:o.CONFIG_MISSING_FIELD,message:"DULIDAY_BRAND_LIST_URL 未配置,无法获取品牌别名数据",userMessage:"品牌别名数据加载失败:缺少 Duliday 品牌接口配置"});const a=new AbortController,i=setTimeout(()=>a.abort(),c);try{const e=await fetch(r,{method:"POST",headers:{"Content-Type":"application/json","Duliday-Token":s},body:JSON.stringify({pageNum:1,pageSize:1e3}),signal:a.signal});if(!e.ok)throw new t({code:o.NETWORK_HTTP_ERROR,message:`Duliday 品牌列表 API 返回 HTTP ${e.status}: ${e.statusText}`,userMessage:`品牌别名数据加载失败:服务端返回 ${e.status}`});const i=await e.json(),c=n.safeParse(i);if(!c.success)throw new t({code:o.VALIDATION_SCHEMA_ERROR,message:`Duliday 品牌列表响应格式校验失败: ${c.error.message}`,userMessage:"品牌别名数据加载失败:响应格式异常"});return c.data.data.result}catch(e){if(e instanceof t)throw e;if(e instanceof Error&&"AbortError"===e.name)throw new t({code:o.NETWORK_TIMEOUT,message:"Duliday 品牌列表 API 请求超时 (30000ms)",userMessage:"品牌别名数据加载超时,请稍后重试",cause:e});throw new t({code:o.SYSTEM_DEPENDENCY_FAILED,message:`获取品牌别名数据失败: ${e instanceof Error?e.message:String(e)}`,userMessage:"品牌别名数据加载失败,请检查网络连接",...e instanceof Error?{cause:e}:{}})}finally{clearTimeout(i)}}function l(e,t,o){const s={},n=new Map,r=e=>e.trim(),a=(e,t)=>{const o=r(e).toLowerCase().replace(/[\s._-]+/g,"");if(!o)return;const s=n.get(o);(!s||t.length>s.length)&&n.set(o,t)},i=[...e].sort((e,t)=>t.name.length-e.name.length);for(const e of i){const t=r(e.name);if(!t)continue;const n=Array.from(new Set([t,...e.aliases.map(r).filter(Boolean)].filter(e=>e!==t)));n.unshift(t),s[t]=n;for(const e of n)a(e,t);if(o&&e.projectIdList.length>0)for(const r of e.projectIdList){const e=o[String(r)];if(!e||e===t)continue;if(!e.includes(t))continue;const i=e.replace(t,"");if(!i)continue;const c=[e];for(const e of n)e!==t&&c.push(`${i}${e}`);if(s[e]){const t=new Set(s[e]);for(const o of c)t.has(o)||s[e].push(o)}else s[e]=c;for(const t of c)a(t,e)}}for(const e of t)s[e]||(s[e]=[e],a(e,e));return{dictionary:s,aliasMap:n}}async function m(e){if(i())return;const t=await f(e),o=new Set,{dictionary:s,aliasMap:n}=l(t,o);a={aliasMap:n,dictionary:s,timestamp:Date.now()}}export async function getSharedBrandDictionary(e){return await m(e),a.dictionary}export async function getSharedBrandAliasMap(e){return await m(e),a.aliasMap}
|
|
@@ -5,4 +5,3 @@ export declare function findBrandByName(data: ZhipinData, brandName?: string | n
|
|
|
5
5
|
export declare function resolveDefaultBrand(data: ZhipinData): Brand | undefined;
|
|
6
6
|
export declare function resolveDefaultBrandName(data: ZhipinData): string;
|
|
7
7
|
export declare function resolvePrimaryCity(data: ZhipinData, brandName?: string | null): string | undefined;
|
|
8
|
-
//# sourceMappingURL=brand-config-selectors.d.ts.map
|
|
@@ -1,30 +1 @@
|
|
|
1
|
-
export function getAllStores(
|
|
2
|
-
return data.brands.flatMap((brand) => brand.stores);
|
|
3
|
-
}
|
|
4
|
-
export function getAvailableBrandNames(data) {
|
|
5
|
-
return data.brands.map((brand) => brand.name);
|
|
6
|
-
}
|
|
7
|
-
export function findBrandByName(data, brandName) {
|
|
8
|
-
if (!brandName) {
|
|
9
|
-
return undefined;
|
|
10
|
-
}
|
|
11
|
-
return data.brands.find((brand) => brand.name === brandName);
|
|
12
|
-
}
|
|
13
|
-
export function resolveDefaultBrand(data) {
|
|
14
|
-
if (data.meta.defaultBrandId) {
|
|
15
|
-
const exact = data.brands.find((brand) => brand.id === data.meta.defaultBrandId);
|
|
16
|
-
if (exact) {
|
|
17
|
-
return exact;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
return data.brands[0];
|
|
21
|
-
}
|
|
22
|
-
export function resolveDefaultBrandName(data) {
|
|
23
|
-
return resolveDefaultBrand(data)?.name ?? "";
|
|
24
|
-
}
|
|
25
|
-
export function resolvePrimaryCity(data, brandName) {
|
|
26
|
-
const stores = brandName ? (findBrandByName(data, brandName)?.stores ?? []) : getAllStores(data);
|
|
27
|
-
return stores.find((store) => typeof store.city === "string" && store.city.trim().length > 0)
|
|
28
|
-
?.city;
|
|
29
|
-
}
|
|
30
|
-
//# sourceMappingURL=brand-config-selectors.js.map
|
|
1
|
+
export function getAllStores(e){return e.brands.flatMap(e=>e.stores)}export function getAvailableBrandNames(e){return e.brands.map(e=>e.name)}export function findBrandByName(e,r){if(r)return e.brands.find(e=>e.name===r)}export function resolveDefaultBrand(e){if(e.meta.defaultBrandId){const r=e.brands.find(r=>r.id===e.meta.defaultBrandId);if(r)return r}return e.brands[0]}export function resolveDefaultBrandName(e){return resolveDefaultBrand(e)?.name??""}export function resolvePrimaryCity(e,r){const n=r?findBrandByName(e,r)?.stores??[]:getAllStores(e);return n.find(e=>"string"==typeof e.city&&e.city.trim().length>0)?.city}
|
|
@@ -4,4 +4,3 @@ export declare function loadBrandConfig(): ZhipinData;
|
|
|
4
4
|
export declare function saveBrandConfig(data: ZhipinData): void;
|
|
5
5
|
export declare function loadReplyPolicy(): ReplyPolicyConfig;
|
|
6
6
|
export declare function saveReplyPolicy(policy: ReplyPolicyConfig): void;
|
|
7
|
-
//# sourceMappingURL=config-loader.d.ts.map
|
|
@@ -1,45 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { ZhipinDataSchema } from "../types/zhipin.js";
|
|
4
|
-
import { ReplyPolicyConfigSchema, DEFAULT_REPLY_POLICY } from "../types/reply-policy.js";
|
|
5
|
-
const DATA_DIR = join(import.meta.dirname, "../../data");
|
|
6
|
-
const BRAND_CONFIG_PATH = join(DATA_DIR, "brand-config.json");
|
|
7
|
-
const REPLY_POLICY_PATH = join(DATA_DIR, "reply-policy.json");
|
|
8
|
-
const CACHE_TTL_MS = 5 * 60 * 1000;
|
|
9
|
-
let brandConfigCache = null;
|
|
10
|
-
let replyPolicyCache = null;
|
|
11
|
-
function isFresh(loadedAt) {
|
|
12
|
-
return Date.now() - loadedAt < CACHE_TTL_MS;
|
|
13
|
-
}
|
|
14
|
-
export function loadBrandConfig() {
|
|
15
|
-
if (brandConfigCache && isFresh(brandConfigCache.loadedAt)) {
|
|
16
|
-
return brandConfigCache.data;
|
|
17
|
-
}
|
|
18
|
-
const raw = readFileSync(BRAND_CONFIG_PATH, "utf-8");
|
|
19
|
-
const data = ZhipinDataSchema.parse(JSON.parse(raw));
|
|
20
|
-
brandConfigCache = { data, loadedAt: Date.now() };
|
|
21
|
-
return data;
|
|
22
|
-
}
|
|
23
|
-
export function saveBrandConfig(data) {
|
|
24
|
-
writeFileSync(BRAND_CONFIG_PATH, JSON.stringify(data, null, 2), "utf-8");
|
|
25
|
-
brandConfigCache = { data, loadedAt: Date.now() };
|
|
26
|
-
}
|
|
27
|
-
export function loadReplyPolicy() {
|
|
28
|
-
if (replyPolicyCache && isFresh(replyPolicyCache.loadedAt)) {
|
|
29
|
-
return replyPolicyCache.data;
|
|
30
|
-
}
|
|
31
|
-
try {
|
|
32
|
-
const raw = readFileSync(REPLY_POLICY_PATH, "utf-8");
|
|
33
|
-
const data = ReplyPolicyConfigSchema.parse(JSON.parse(raw));
|
|
34
|
-
replyPolicyCache = { data, loadedAt: Date.now() };
|
|
35
|
-
return data;
|
|
36
|
-
}
|
|
37
|
-
catch {
|
|
38
|
-
return DEFAULT_REPLY_POLICY;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
export function saveReplyPolicy(policy) {
|
|
42
|
-
writeFileSync(REPLY_POLICY_PATH, JSON.stringify(policy, null, 2), "utf-8");
|
|
43
|
-
replyPolicyCache = { data: policy, loadedAt: Date.now() };
|
|
44
|
-
}
|
|
45
|
-
//# sourceMappingURL=config-loader.js.map
|
|
1
|
+
import{readFileSync as t,writeFileSync as o}from"node:fs";import{join as n}from"node:path";import{ZhipinDataSchema as a}from"../types/zhipin.js";import{ReplyPolicyConfigSchema as e,DEFAULT_REPLY_POLICY as r}from"../types/reply-policy.js";const d=n(import.meta.dirname,"../../data"),i=n(d,"brand-config.json"),l=n(d,"reply-policy.json"),p=3e5;let f=null,u=null;function s(t){return Date.now()-t<p}export function loadBrandConfig(){if(f&&s(f.loadedAt))return f.data;const o=t(i,"utf-8"),n=a.parse(JSON.parse(o));return f={data:n,loadedAt:Date.now()},n}export function saveBrandConfig(t){o(i,JSON.stringify(t,null,2),"utf-8"),f={data:t,loadedAt:Date.now()}}export function loadReplyPolicy(){if(u&&s(u.loadedAt))return u.data;try{const o=t(l,"utf-8"),n=e.parse(JSON.parse(o));return u={data:n,loadedAt:Date.now()},n}catch{return r}}export function saveReplyPolicy(t){o(l,JSON.stringify(t,null,2),"utf-8"),u={data:t,loadedAt:Date.now()}}
|
|
@@ -1,160 +1 @@
|
|
|
1
|
-
import {
|
|
2
|
-
// ========== Constants ==========
|
|
3
|
-
const JOB_LIST_CACHE_TTL_MS = 60_000;
|
|
4
|
-
const JOB_LIST_PAGE_SIZE = 200;
|
|
5
|
-
const REQUEST_TIMEOUT_MS = 20_000;
|
|
6
|
-
// ========== Cache ==========
|
|
7
|
-
let jobListCache = null;
|
|
8
|
-
let inflightJobListRequest = null;
|
|
9
|
-
// ========== Env Helpers ==========
|
|
10
|
-
export function getDulidayJobListEndpoint() {
|
|
11
|
-
const endpoint = process.env.DULIDAY_JOB_LIST_URL;
|
|
12
|
-
return typeof endpoint === "string" && endpoint.trim().length > 0 ? endpoint : undefined;
|
|
13
|
-
}
|
|
14
|
-
export function getDulidayToken() {
|
|
15
|
-
const token = process.env.DULIDAY_TOKEN;
|
|
16
|
-
return typeof token === "string" && token.trim().length > 0 ? token : undefined;
|
|
17
|
-
}
|
|
18
|
-
// ========== Name Helpers ==========
|
|
19
|
-
export function normalizeName(value) {
|
|
20
|
-
return (value ?? "").trim();
|
|
21
|
-
}
|
|
22
|
-
export function buildCityCandidates(cityName) {
|
|
23
|
-
const city = normalizeName(cityName);
|
|
24
|
-
if (!city)
|
|
25
|
-
return [];
|
|
26
|
-
const candidates = new Set([city]);
|
|
27
|
-
if (city.endsWith("市"))
|
|
28
|
-
candidates.add(city.slice(0, -1));
|
|
29
|
-
else
|
|
30
|
-
candidates.add(`${city}市`);
|
|
31
|
-
return Array.from(candidates).filter(Boolean);
|
|
32
|
-
}
|
|
33
|
-
export function buildBrandCandidates(brandAlias, cityName) {
|
|
34
|
-
const brand = normalizeName(brandAlias);
|
|
35
|
-
if (!brand)
|
|
36
|
-
return [];
|
|
37
|
-
const candidates = new Set([brand]);
|
|
38
|
-
const cityCandidates = buildCityCandidates(cityName);
|
|
39
|
-
for (const city of cityCandidates) {
|
|
40
|
-
if (brand.startsWith(city)) {
|
|
41
|
-
const stripped = brand.slice(city.length).trim();
|
|
42
|
-
if (stripped)
|
|
43
|
-
candidates.add(stripped);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
return Array.from(candidates).filter(Boolean);
|
|
47
|
-
}
|
|
48
|
-
// ========== Response Parsing ==========
|
|
49
|
-
const jobListResponseSchema = z.object({
|
|
50
|
-
data: z
|
|
51
|
-
.object({
|
|
52
|
-
result: z.array(z.unknown()).optional(),
|
|
53
|
-
list: z.array(z.unknown()).optional(),
|
|
54
|
-
total: z.number().optional(),
|
|
55
|
-
})
|
|
56
|
-
.nullable()
|
|
57
|
-
.optional(),
|
|
58
|
-
result: z.array(z.unknown()).optional(),
|
|
59
|
-
});
|
|
60
|
-
export function extractResults(payload) {
|
|
61
|
-
const parsed = jobListResponseSchema.safeParse(payload);
|
|
62
|
-
if (!parsed.success)
|
|
63
|
-
return { items: [], total: 0 };
|
|
64
|
-
const data = parsed.data;
|
|
65
|
-
const items = data.data?.result ?? data.data?.list ?? (Array.isArray(data.result) ? data.result : []);
|
|
66
|
-
const total = data.data?.total ?? (Array.isArray(items) ? items.length : 0);
|
|
67
|
-
return {
|
|
68
|
-
items: Array.isArray(items) ? items : [],
|
|
69
|
-
total: typeof total === "number" ? total : 0,
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
// ========== Single Page Fetch (with cache) ==========
|
|
73
|
-
export const FULL_INCLUDE_OPTIONS = {
|
|
74
|
-
includeBasicInfo: true,
|
|
75
|
-
includeJobSalary: true,
|
|
76
|
-
includeWelfare: true,
|
|
77
|
-
includeHiringRequirement: true,
|
|
78
|
-
includeWorkTime: true,
|
|
79
|
-
};
|
|
80
|
-
const DEFAULT_INCLUDE_OPTIONS = {
|
|
81
|
-
includeBasicInfo: true,
|
|
82
|
-
includeHiringRequirement: true,
|
|
83
|
-
};
|
|
84
|
-
export async function fetchJobListPage(token, endpoint, pageNum, pageSize, options) {
|
|
85
|
-
const shouldUseCache = process.env.NODE_ENV !== "test";
|
|
86
|
-
const brandCandidates = buildBrandCandidates(options?.brandAlias, options?.cityName);
|
|
87
|
-
const cityCandidates = buildCityCandidates(options?.cityName);
|
|
88
|
-
const includeOpts = options?.include ?? DEFAULT_INCLUDE_OPTIONS;
|
|
89
|
-
const cacheKey = JSON.stringify({
|
|
90
|
-
token,
|
|
91
|
-
brandCandidates,
|
|
92
|
-
cityCandidates,
|
|
93
|
-
pageNum,
|
|
94
|
-
includeOpts,
|
|
95
|
-
});
|
|
96
|
-
const now = Date.now();
|
|
97
|
-
if (shouldUseCache &&
|
|
98
|
-
jobListCache &&
|
|
99
|
-
jobListCache.cacheKey === cacheKey &&
|
|
100
|
-
now - jobListCache.fetchedAt < JOB_LIST_CACHE_TTL_MS) {
|
|
101
|
-
return jobListCache.payload;
|
|
102
|
-
}
|
|
103
|
-
if (shouldUseCache && inflightJobListRequest?.cacheKey === cacheKey) {
|
|
104
|
-
return inflightJobListRequest.promise;
|
|
105
|
-
}
|
|
106
|
-
const requestPromise = (async () => {
|
|
107
|
-
const controller = new AbortController();
|
|
108
|
-
const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
109
|
-
const queryParam = {};
|
|
110
|
-
if (brandCandidates.length > 0)
|
|
111
|
-
queryParam.brandAliasList = brandCandidates;
|
|
112
|
-
if (cityCandidates.length > 0)
|
|
113
|
-
queryParam.cityNameList = cityCandidates;
|
|
114
|
-
const requestBody = {
|
|
115
|
-
pageNum,
|
|
116
|
-
pageSize,
|
|
117
|
-
queryParam,
|
|
118
|
-
options: includeOpts,
|
|
119
|
-
};
|
|
120
|
-
try {
|
|
121
|
-
const response = await fetch(endpoint, {
|
|
122
|
-
method: "POST",
|
|
123
|
-
headers: { "Content-Type": "application/json", "Duliday-Token": token },
|
|
124
|
-
body: JSON.stringify(requestBody),
|
|
125
|
-
signal: controller.signal,
|
|
126
|
-
});
|
|
127
|
-
if (!response.ok)
|
|
128
|
-
throw new Error(`Duliday job list fetch failed: ${response.status}`);
|
|
129
|
-
const payload = await response.json();
|
|
130
|
-
jobListCache = { cacheKey, payload, fetchedAt: Date.now() };
|
|
131
|
-
return payload;
|
|
132
|
-
}
|
|
133
|
-
finally {
|
|
134
|
-
clearTimeout(timeoutId);
|
|
135
|
-
inflightJobListRequest = null;
|
|
136
|
-
}
|
|
137
|
-
})();
|
|
138
|
-
if (shouldUseCache)
|
|
139
|
-
inflightJobListRequest = { cacheKey, promise: requestPromise };
|
|
140
|
-
return requestPromise;
|
|
141
|
-
}
|
|
142
|
-
// ========== All Pages Fetch ==========
|
|
143
|
-
export async function fetchAllJobListPages(token, endpoint, options) {
|
|
144
|
-
const allItems = [];
|
|
145
|
-
let page = 1;
|
|
146
|
-
let total = 0;
|
|
147
|
-
while (true) {
|
|
148
|
-
const payload = await fetchJobListPage(token, endpoint, page, JOB_LIST_PAGE_SIZE, options);
|
|
149
|
-
const result = extractResults(payload);
|
|
150
|
-
if (page === 1)
|
|
151
|
-
total = result.total;
|
|
152
|
-
allItems.push(...result.items);
|
|
153
|
-
if (result.items.length < JOB_LIST_PAGE_SIZE || allItems.length >= total) {
|
|
154
|
-
break;
|
|
155
|
-
}
|
|
156
|
-
page += 1;
|
|
157
|
-
}
|
|
158
|
-
return { items: allItems, total };
|
|
159
|
-
}
|
|
160
|
-
//# sourceMappingURL=duliday-api.js.map
|
|
1
|
+
import{z as t}from"zod";const e=6e4,n=200,a=2e4;let r=null,i=null;export function getDulidayJobListEndpoint(){const t=process.env.DULIDAY_JOB_LIST_URL;return"string"==typeof t&&t.trim().length>0?t:void 0}export function getDulidayToken(){const t=process.env.DULIDAY_TOKEN;return"string"==typeof t&&t.trim().length>0?t:void 0}export function normalizeName(t){return(t??"").trim()}export function buildCityCandidates(t){const e=normalizeName(t);if(!e)return[];const n=new Set([e]);return e.endsWith("市")?n.add(e.slice(0,-1)):n.add(`${e}市`),Array.from(n).filter(Boolean)}export function buildBrandCandidates(t,e){const n=normalizeName(t);if(!n)return[];const a=new Set([n]),r=buildCityCandidates(e);for(const t of r)if(n.startsWith(t)){const e=n.slice(t.length).trim();e&&a.add(e)}return Array.from(a).filter(Boolean)}const o=t.object({data:t.object({result:t.array(t.unknown()).optional(),list:t.array(t.unknown()).optional(),total:t.number().optional()}).nullable().optional(),result:t.array(t.unknown()).optional()});export function extractResults(t){const e=o.safeParse(t);if(!e.success)return{items:[],total:0};const n=e.data,a=n.data?.result??n.data?.list??(Array.isArray(n.result)?n.result:[]),r=n.data?.total??(Array.isArray(a)?a.length:0);return{items:Array.isArray(a)?a:[],total:"number"==typeof r?r:0}}export const FULL_INCLUDE_OPTIONS={includeBasicInfo:!0,includeJobSalary:!0,includeWelfare:!0,includeHiringRequirement:!0,includeWorkTime:!0};const s={includeBasicInfo:!0,includeHiringRequirement:!0};export async function fetchJobListPage(t,n,o,l,c){const u="test"!==process.env.NODE_ENV,d=buildBrandCandidates(c?.brandAlias,c?.cityName),f=buildCityCandidates(c?.cityName),y=c?.include??s,m=JSON.stringify({token:t,brandCandidates:d,cityCandidates:f,pageNum:o,includeOpts:y}),p=Date.now();if(u&&r&&r.cacheKey===m&&p-r.fetchedAt<e)return r.payload;if(u&&i?.cacheKey===m)return i.promise;const h=(async()=>{const e=new AbortController,s=setTimeout(()=>e.abort(),a),c={};d.length>0&&(c.brandAliasList=d),f.length>0&&(c.cityNameList=f);const u={pageNum:o,pageSize:l,queryParam:c,options:y};try{const a=await fetch(n,{method:"POST",headers:{"Content-Type":"application/json","Duliday-Token":t},body:JSON.stringify(u),signal:e.signal});if(!a.ok)throw new Error(`Duliday job list fetch failed: ${a.status}`);const i=await a.json();return r={cacheKey:m,payload:i,fetchedAt:Date.now()},i}finally{clearTimeout(s),i=null}})();return u&&(i={cacheKey:m,promise:h}),h}export async function fetchAllJobListPages(t,e,a){const r=[];let i=1,o=0;for(;;){const s=extractResults(await fetchJobListPage(t,e,i,n,a));if(1===i&&(o=s.total),r.push(...s.items),s.items.length<n||r.length>=o)break;i+=1}return{items:r,total:o}}
|
|
@@ -43,4 +43,3 @@ export type RequirementsInput = Pick<ParsedPosition, "basicPersonalRequirements"
|
|
|
43
43
|
export declare function generateRequirements(p: RequirementsInput): string[];
|
|
44
44
|
export declare function parseTimeStringToSeconds(timeStr: unknown): number;
|
|
45
45
|
export {};
|
|
46
|
-
//# sourceMappingURL=duliday-mapper.d.ts.map
|