feat: migrate npm registry from Verdaccio to Gitea Packages
Some checks failed
Monorepo Pipeline / ⚡ Prioritize Release (push) Successful in 1s
Monorepo Pipeline / 🧹 Lint (push) Failing after 35s
Monorepo Pipeline / 🧪 Test (push) Failing after 35s
Monorepo Pipeline / 🏗️ Build (push) Failing after 12s
Monorepo Pipeline / 🚀 Release (push) Has been skipped
Monorepo Pipeline / 🐳 Build Image Processor (push) Has been skipped
Monorepo Pipeline / 🐳 Build Directus (Base) (push) Has been skipped
Monorepo Pipeline / 🐳 Build Gatekeeper (Product) (push) Has been skipped
Monorepo Pipeline / 🐳 Build Build-Base (push) Has been skipped
Monorepo Pipeline / 🐳 Build Production Runtime (push) Has been skipped
Some checks failed
Monorepo Pipeline / ⚡ Prioritize Release (push) Successful in 1s
Monorepo Pipeline / 🧹 Lint (push) Failing after 35s
Monorepo Pipeline / 🧪 Test (push) Failing after 35s
Monorepo Pipeline / 🏗️ Build (push) Failing after 12s
Monorepo Pipeline / 🚀 Release (push) Has been skipped
Monorepo Pipeline / 🐳 Build Image Processor (push) Has been skipped
Monorepo Pipeline / 🐳 Build Directus (Base) (push) Has been skipped
Monorepo Pipeline / 🐳 Build Gatekeeper (Product) (push) Has been skipped
Monorepo Pipeline / 🐳 Build Build-Base (push) Has been skipped
Monorepo Pipeline / 🐳 Build Production Runtime (push) Has been skipped
This commit is contained in:
121
packages/concept-engine/src/steps/00b-research.ts
Normal file
121
packages/concept-engine/src/steps/00b-research.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
// ============================================================================
|
||||
// Step 00b: Research — Industry Research via @mintel/journaling (No LLM hallus)
|
||||
// Uses Serper API for real web search results about the industry/company.
|
||||
// ============================================================================
|
||||
|
||||
import type { ConceptState, StepResult } from "../types.js";
|
||||
|
||||
interface ResearchResult {
|
||||
companyContext: string[];
|
||||
industryInsights: string[];
|
||||
competitorInfo: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Research the company and industry using real web search data.
|
||||
* Uses @mintel/journaling's ResearchAgent — results are grounded in real sources.
|
||||
*
|
||||
* NOTE: The journaling package can cause unhandled rejections that crash the process.
|
||||
* We wrap each call in an additional safety layer.
|
||||
*/
|
||||
export async function executeResearch(
|
||||
state: ConceptState,
|
||||
): Promise<StepResult<ResearchResult>> {
|
||||
const startTime = Date.now();
|
||||
|
||||
const companyName = state.siteProfile?.companyInfo?.name || "";
|
||||
const websiteTopic = state.siteProfile?.services?.slice(0, 3).join(", ") || "";
|
||||
const domain = state.siteProfile?.domain || "";
|
||||
|
||||
if (!companyName && !websiteTopic && !domain) {
|
||||
return {
|
||||
success: true,
|
||||
data: { companyContext: [], industryInsights: [], competitorInfo: [] },
|
||||
usage: { step: "00b-research", model: "none", promptTokens: 0, completionTokens: 0, cost: 0, durationMs: 0 },
|
||||
};
|
||||
}
|
||||
|
||||
// Safety wrapper: catch ANY unhandled rejections during this step
|
||||
const safeCall = <T>(fn: () => Promise<T>, fallback: T): Promise<T> => {
|
||||
return new Promise<T>((resolve) => {
|
||||
const handler = (err: any) => {
|
||||
console.warn(` ⚠️ Unhandled rejection caught in research: ${err?.message || err}`);
|
||||
process.removeListener("unhandledRejection", handler);
|
||||
resolve(fallback);
|
||||
};
|
||||
process.on("unhandledRejection", handler);
|
||||
|
||||
fn()
|
||||
.then((result) => {
|
||||
process.removeListener("unhandledRejection", handler);
|
||||
resolve(result);
|
||||
})
|
||||
.catch((err) => {
|
||||
process.removeListener("unhandledRejection", handler);
|
||||
console.warn(` ⚠️ Research call failed: ${err?.message || err}`);
|
||||
resolve(fallback);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
try {
|
||||
const { ResearchAgent } = await import("@mintel/journaling");
|
||||
const agent = new ResearchAgent(process.env.OPENROUTER_API_KEY || "");
|
||||
|
||||
const results: ResearchResult = {
|
||||
companyContext: [],
|
||||
industryInsights: [],
|
||||
competitorInfo: [],
|
||||
};
|
||||
|
||||
// 1. Research the company itself
|
||||
if (companyName || domain) {
|
||||
const searchQuery = companyName
|
||||
? `${companyName} ${websiteTopic} Unternehmen`
|
||||
: `site:${domain}`;
|
||||
|
||||
console.log(` 🔍 Researching: "${searchQuery}"...`);
|
||||
const facts = await safeCall(
|
||||
() => agent.researchTopic(searchQuery),
|
||||
[] as any[],
|
||||
);
|
||||
results.companyContext = (facts || [])
|
||||
.filter((f: any) => f?.fact || f?.value || f?.text || f?.statement)
|
||||
.map((f: any) => f.fact || f.value || f.text || f.statement)
|
||||
.slice(0, 5);
|
||||
}
|
||||
|
||||
// 2. Industry research
|
||||
if (websiteTopic) {
|
||||
console.log(` 🔍 Researching industry: "${websiteTopic}"...`);
|
||||
const insights = await safeCall(
|
||||
() => agent.researchCompetitors(websiteTopic),
|
||||
[] as any[],
|
||||
);
|
||||
results.industryInsights = (insights || []).slice(0, 5);
|
||||
}
|
||||
|
||||
const totalFacts = results.companyContext.length + results.industryInsights.length + results.competitorInfo.length;
|
||||
console.log(` 📊 Research found ${totalFacts} data points.`);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: results,
|
||||
usage: {
|
||||
step: "00b-research",
|
||||
model: "serper/datacommons",
|
||||
promptTokens: 0,
|
||||
completionTokens: 0,
|
||||
cost: 0,
|
||||
durationMs: Date.now() - startTime,
|
||||
},
|
||||
};
|
||||
} catch (err) {
|
||||
console.warn(` ⚠️ Research step skipped: ${(err as Error).message}`);
|
||||
return {
|
||||
success: true,
|
||||
data: { companyContext: [], industryInsights: [], competitorInfo: [] },
|
||||
usage: { step: "00b-research", model: "none", promptTokens: 0, completionTokens: 0, cost: 0, durationMs: Date.now() - startTime },
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user