Files
gooseek/services/master-agents-svc/src/lib/actions/social_search.ts
home 06fe57c765 feat: Go backend, enhanced search, new widgets, Docker deploy
Major changes:
- Add Go backend (backend/) with microservices architecture
- Enhanced master-agents-svc: reranker, content-classifier, stealth-crawler,
  proxy-manager, media-search, fastClassifier, language detection
- New web-svc widgets: KnowledgeCard, ProductCard, ProfileCard, VideoCard,
  UnifiedCard, CardGallery, InlineImageGallery, SourcesPanel, RelatedQuestions
- Improved discover-svc with discover-db integration
- Docker deployment improvements (Caddyfile, vendor.sh, BUILD.md)
- Library-svc: project_id schema migration
- Remove deprecated finance-svc and travel-svc
- Localization improvements across services

Made-with: Cursor
2026-02-27 04:15:32 +03:00

94 lines
3.5 KiB
TypeScript

import z from 'zod';
import type { ResearchAction } from './types.js';
import type { Chunk, SearchResultsResearchBlock } from '../types.js';
import { searchSearxng } from '../searxng.js';
const schema = z.object({
queries: z.array(z.string()).max(6).describe('List of social search queries'),
});
const socialSearchAction: ResearchAction<typeof schema> = {
name: 'social_search',
schema,
getToolDescription: () =>
'Use this tool to perform social media searches for posts, discussions, and trends. Provide up to 6 queries in the user\'s language.',
getDescription: () =>
'Use this tool to perform social media searches for posts, discussions, and trends. Up to 6 queries in the user\'s language.',
enabled: (config) =>
config.sources.includes('discussions') &&
config.classification.classification.skipSearch === false &&
config.classification.classification.discussionSearch === true,
execute: async (input, additionalConfig) => {
input.queries = input.queries.slice(0, 6);
const researchBlock = additionalConfig.session.getBlock(additionalConfig.researchBlockId);
if (researchBlock && researchBlock.type === 'research') {
researchBlock.data.subSteps.push({
id: crypto.randomUUID(),
type: 'searching',
searching: input.queries,
});
additionalConfig.session.updateBlock(additionalConfig.researchBlockId, [
{ op: 'replace', path: '/data/subSteps', value: researchBlock.data.subSteps },
]);
}
const searchResultsBlockId = crypto.randomUUID();
let searchResultsEmitted = false;
const results: Chunk[] = [];
const search = async (q: string) => {
const [page1, page2] = await Promise.all([
searchSearxng(q, {
categories: ['social_media'],
pageno: 1,
}),
searchSearxng(q, {
categories: ['social_media'],
pageno: 2,
}),
]);
const seenUrls = new Set<string>();
const allResults = [...(page1.results ?? []), ...(page2.results ?? [])].filter((r) => {
if (!r.url || seenUrls.has(r.url)) return false;
seenUrls.add(r.url);
return true;
});
const res = { results: allResults };
const resultChunks: Chunk[] = res.results.map((r) => ({
content: r.content || r.title,
metadata: { title: r.title, url: r.url },
}));
results.push(...resultChunks);
if (!searchResultsEmitted && researchBlock && researchBlock.type === 'research') {
searchResultsEmitted = true;
researchBlock.data.subSteps.push({
id: searchResultsBlockId,
type: 'search_results',
reading: resultChunks,
});
additionalConfig.session.updateBlock(additionalConfig.researchBlockId, [
{ op: 'replace', path: '/data/subSteps', value: researchBlock.data.subSteps },
]);
} else if (searchResultsEmitted && researchBlock && researchBlock.type === 'research') {
const subStepIndex = researchBlock.data.subSteps.findIndex((s) => s.id === searchResultsBlockId);
const subStep = researchBlock.data.subSteps[subStepIndex] as SearchResultsResearchBlock | undefined;
if (subStep) {
subStep.reading.push(...resultChunks);
additionalConfig.session.updateBlock(additionalConfig.researchBlockId, [
{ op: 'replace', path: '/data/subSteps', value: researchBlock.data.subSteps },
]);
}
}
};
await Promise.all(input.queries.map(search));
return { type: 'search_results', results };
},
};
export default socialSearchAction;