112 lines
3.0 KiB
TypeScript
112 lines
3.0 KiB
TypeScript
import { parseArgs } from "@std/cli/parse-args";
|
|
import type { Args } from "@std/cli/parse-args";
|
|
import { ensureDir } from "@std/fs";
|
|
import { parse, stringify } from "@std/yaml";
|
|
import { answerQuery, cleanTextFromHtml, getNewsUrls } from "./websearch.ts";
|
|
|
|
export interface Config {
|
|
model?: string;
|
|
search_url?: string;
|
|
}
|
|
|
|
const parsed: Args = parseArgs(Deno.args, {
|
|
boolean: ["help"],
|
|
string: ["model", "search_url"],
|
|
});
|
|
|
|
if (parsed.help) {
|
|
console.log(`
|
|
Usage:
|
|
websearch [--model MODEL] [--search_url SEARCH_URL] QUERY
|
|
|
|
Options:
|
|
--model Ollama model to use
|
|
--search_url URL for SearXNG endpoint
|
|
|
|
Arguments:
|
|
QUERY The topic to search for news to summarize
|
|
|
|
Configuration:
|
|
The websearch configuration file is stored in the XDG Config directory
|
|
/home/$USER/.config/websearch/config.yml. Both the model and search_url
|
|
can be customized as an alternative to providing the options for each.
|
|
If both the Ollama model and SearXNG endpoint are successful, the
|
|
configuration is automatically saved/updated.
|
|
`);
|
|
Deno.exit();
|
|
}
|
|
|
|
const configDir = `${Deno.env.get("HOME")}/.config/websearch`;
|
|
const configPath = `${Deno.env.get("HOME")}/.config/websearch/config.yml`;
|
|
|
|
async function loadConfig(): Promise<Config> {
|
|
try {
|
|
const yamlString = await Deno.readTextFile(configPath);
|
|
return parse(yamlString) as Config;
|
|
} catch {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
async function saveConfig(config: Config) {
|
|
await ensureDir(configDir);
|
|
await Deno.writeTextFile(
|
|
configPath,
|
|
stringify(config),
|
|
);
|
|
}
|
|
|
|
async function main(args: Args) {
|
|
const query = args._.join(" ");
|
|
if (!query) {
|
|
throw new Error("Please provide a search query");
|
|
}
|
|
console.log(`Query: ${query}`);
|
|
|
|
try {
|
|
const config = await loadConfig();
|
|
if (!config.model) {
|
|
if (args.model) {
|
|
config.model = args.model;
|
|
} else {
|
|
throw new Error("Provide --model or add Ollama model to configuration");
|
|
}
|
|
} else if (args.model && args.model !== config.model) {
|
|
config.model = args.model;
|
|
}
|
|
if (!config.search_url) {
|
|
if (args.search_url) {
|
|
config.search_url = args.search_url;
|
|
} else {
|
|
throw new Error(
|
|
"Provide --search_url or add search_url to configuration",
|
|
);
|
|
}
|
|
} else if (args.search_url && args.search_url !== config.search_url) {
|
|
config.search_url = args.search_url;
|
|
}
|
|
|
|
const urls = await getNewsUrls(config, query);
|
|
if (!urls || urls.length === 0) {
|
|
console.log("No results");
|
|
Deno.exit(1);
|
|
}
|
|
|
|
const cleanedTexts = await Promise.all(
|
|
urls.map((url) => cleanTextFromHtml(url)),
|
|
);
|
|
await answerQuery(config, query, cleanedTexts.join("\n\n"));
|
|
await saveConfig(config);
|
|
} catch (error: any) {
|
|
console.error(`Error processing query "${query}":`, error.message);
|
|
Deno.exit(1);
|
|
}
|
|
}
|
|
|
|
if (import.meta.main) {
|
|
main(parsed).catch((error) => {
|
|
console.error("Unhandled exception:", error);
|
|
Deno.exit(1);
|
|
});
|
|
}
|