refactor:删除未使用的文件,添加文章总结功能及相关处理逻辑

This commit is contained in:
李东云
2025-02-12 16:58:02 +08:00
parent 5900791890
commit 72512ac993
8 changed files with 183 additions and 146 deletions

1
global.d.ts vendored
View File

@@ -1 +0,0 @@
declare module '@elysiajs/html';

40
history/2025/2/12.md Normal file
View File

@@ -0,0 +1,40 @@
好的,以下是对这几篇文章的简要汇总和投资策略:
**简要汇总:**
* **文章1:** 指数在尾盘受地产和券商拉升突破了3220点阻力位但成交量放大有限市场热度略有下降。科技股表现活跃但作者认为市场整体突围难度大。关注半导体光刻胶和H股的恒生科技指数。
* **文章2:** 作者因乌龙指被锁仓但仍积极参与交易关注北交所30cm的刺激。
* **文章3:** 市场震荡,成交额未达预期,板块轮动较快。重点关注数字经济、传媒、机器人等板块的短线机会,并强调盘中操作节奏的重要性。
* **文章4:** 认为市场进入调整初期,应关注风险,谨慎操作。强调滚动买点的时机选择,以及强趋势股和题材的梳理。
**投资策略(仅供参考):**
* **投资理念:** 关注市场波动,把握结构性机会,控制风险。
* **短线50%**
* **重点关注:** 科技股数字经济、AI、半导体光刻胶等、传媒、机器人等板块的强势个股。
* **操作策略:**
* **快进快出:** 重点关注盘中异动,把握脉冲机会,避免追高。
* **止损止盈:** 严格设置止损位,及时止盈。
* **分仓操作:** 采用分仓操作,降低单只股票的风险。
* **中线30%**
* **关注方向:**
* 人工智能:长期看好人工智能发展,可适当配置相关个股。
* 大消费:关注消费复苏的潜力。
* **操作策略:**
* **价值投资:** 选择基本面良好,有成长潜力的个股进行中线布局。
* **分批建仓:** 采取分批建仓的方式,降低建仓成本。
* **长线20%**
* **关注方向:** 选择具有长期投资价值的行业龙头企业或指数基金,比如:
* 新能源:长期看好新能源领域,如光伏、储能等。
* 医疗健康:人口老龄化加速,医疗健康需求将持续增长。
* **操作策略:**
* **长期持有:** 长期持有,分享企业成长收益。
* **定投:** 采用定投的方式,平滑投资成本。
**风险提示:**
* 市场波动风险:股市有风险,投资需谨慎。
* 板块轮动风险:市场风格可能快速切换,注意把握节奏。
* 个股风险:个股基本面可能发生变化,注意风险控制。
请注意,以上策略仅供参考,投资者应根据自身风险承受能力和投资目标,谨慎决策。

143
index.ts
View File

@@ -1,143 +0,0 @@
import { Elysia } from "elysia";
import { html } from '@elysiajs/html'
import { JSDOM } from 'jsdom';
import { GoogleGenerativeAI } from "@google/generative-ai";
const genAI = new GoogleGenerativeAI(process.env.API_KEY || "");
const model = genAI.getGenerativeModel({ model: "gemini-2.0-flash-lite-preview-02-05" });
async function callGeminiAPI(articleContent: string): Promise<any> {
// articleContent = `You are a helpful assistant that summarizes WeChat articles.use Chinese: ${articleContent}`;
// const response = (await model.generateContent(articleContent)).response;
// return response.text();
const {
GoogleGenerativeAI,
HarmCategory,
HarmBlockThreshold,
} = require("@google/generative-ai");
const apiKey = process.env.API_KEY;
const genAI = new GoogleGenerativeAI(apiKey);
const model = genAI.getGenerativeModel({
model: "gemini-2.0-flash",
});
const generationConfig = {
temperature: 1,
topP: 0.95,
topK: 40,
maxOutputTokens: 8192,
responseMimeType: "text/plain",
};
async function run(articleContent: string) {
const chatSession = model.startChat({
generationConfig,
history: [
{
role: "user",
parts: [
{ text: "接下来我会给你提供几篇文章,用---和换行分割。如果是投资主题,我希望你能帮我总结和设计出一个短中长线的投资策略:" },
],
},
{
role: "model",
parts: [
{ text: "请提供你说的文章,我会根据内容总结并设计投资策略。你需要将几篇文章用`---`和换行分割,像你例子里那样。" },
],
},
{
role: "user",
parts: [
{ text: articleContent },
],
}
],
});
const result = await chatSession.sendMessage("INSERT_INPUT_HERE");
console.log(result.response.text());
return result.response.text();
}
return run(articleContent);
}
const app = new Elysia()
.use(html())
.get("/", () => `
<!DOCTYPE html>
<html>
<head>
<title>文章总结</title>
</head>
<body>
<h1>输入微信文章链接</h1>
<form action="/summarize" method="post">
<label for="articleUrl">文章链接:</label><br>
<textarea id="articleUrl" name="articleUrl" rows="4" cols="50"></textarea><br><br>
<input type="submit" value="提交">
</form>
</body>
</html>
`)
.post("/summarize", async ({ body }) => {
try {
const { articleUrl } = body as { articleUrl: string };
const urls = articleUrl.split('\n').filter(url => url.trim() !== '');
async function fetchArticle(url: string): Promise<string> {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to fetch article: Status Code ${response.status}`);
}
return await response.text();
}
let articleText = '';
for (const url of urls) {
try {
const articleHTML = await fetchArticle(url);
const dom = new JSDOM(articleHTML);
const jsContent = dom.window.document.querySelector("#js_content");
const content = jsContent ? jsContent.textContent : "";
articleText += content + '\n---\n';
} catch (error) {
console.error(`Failed to process article from ${url}:`, error);
// Optionally, you might want to handle the error more gracefully,
// such as skipping the article or returning a default value.
}
}
// return articleText;
const geminiResponse = await callGeminiAPI(articleText);
const today = new Date();
const fs = require('fs');
const path = require('path');
const fileName = `history/${today.getFullYear()}/${today.getMonth() + 1}/${today.getDate()}.md`;
const dir = path.dirname(fileName);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
fs.writeFileSync(fileName, geminiResponse);
return new Response(geminiResponse, {
headers: {
"Content-Type": "text/markdown; charset=utf-8",
},
});
} catch (error: any) {
console.error("Error:", error);
return { error: error.message };
}
})
.listen({ port: 3000 });
console.log(
`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`
);

View File

@@ -9,8 +9,8 @@
}, },
"scripts": { "scripts": {
"start": "bun run dev", "start": "bun run dev",
"build": "bun build index.ts --outfile=dist/lockon --compile", "build": "bun build src/index.ts --outfile=dist/lockon --compile",
"dev": "bun run index.ts --watch" "dev": "bun run src/index.ts --watch"
}, },
"type": "module", "type": "module",
"dependencies": { "dependencies": {

View File

@@ -0,0 +1,54 @@
import type { Context } from "elysia";
import { JSDOM } from "jsdom";
import callGeminiAPI from "../llm/gemini";
import fs from 'fs';
import path from 'path';
export async function summarizeHandler({ body }: Context) {
try {
const { articleUrl } = body as { articleUrl: string };
const urls = articleUrl.split('\n').filter(url => url.trim() !== '');
async function fetchArticle(url: string): Promise<string> {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to fetch article: Status Code ${response.status}`);
}
return await response.text();
}
let articleTexts = [];
for (const url of urls) {
try {
const articleHTML = await fetchArticle(url);
const dom = new JSDOM(articleHTML);
const jsContent = dom.window.document.querySelector("#js_content");
const content = jsContent ? jsContent.textContent : "";
articleTexts.push(content);
} catch (error) {
console.error(`Failed to process article from ${url}:`, error);
// Optionally, you might want to handle the error more gracefully,
// such as skipping the article or returning a default value.
}
}
const geminiResponse = await callGeminiAPI(articleTexts);
const today = new Date();
const fileName = `history/${today.getFullYear()}/${today.getMonth() + 1}/${today.getDate()}.md`;
const dir = path.dirname(fileName);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
fs.writeFileSync(fileName, geminiResponse);
return new Response(geminiResponse, {
headers: {
"Content-Type": "text/markdown; charset=utf-8",
},
});
} catch (error: any) {
console.error("Error:", error);
return { error: error.message };
}
}

20
src/index.ts Normal file
View File

@@ -0,0 +1,20 @@
import { Elysia } from "elysia";
import { html } from '@elysiajs/html'
import fs from 'fs';
import path from 'path';
import { summarizeHandler } from "./controller/summarizeHandler";
const app = new Elysia()
.use(html())
.get("/", () => {
// 读取 HTML 文件
const filePath = path.join(process.cwd(), 'index.html');
const htmlContent = fs.readFileSync(filePath, 'utf8');
return htmlContent;
})
.post("/summarize", summarizeHandler)
.listen({ port: 3000 });
console.log(
`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`
);

50
src/llm/gemini.ts Normal file
View File

@@ -0,0 +1,50 @@
async function callGeminiAPI(articleContent: string[]): Promise<any> {
// articleContent = `You are a helpful assistant that summarizes WeChat articles.use Chinese: ${articleContent}`;
// const response = (await model.generateContent(articleContent)).response;
// return response.text();
const {
GoogleGenerativeAI,
HarmCategory,
HarmBlockThreshold,
} = require("@google/generative-ai");
const genAI = new GoogleGenerativeAI(process.env.API_KEY || "");
console.log(process.env.API_KEY);
const model = genAI.getGenerativeModel({
model: "gemini-2.0-flash-lite-preview-02-05"
});
const generationConfig = {
temperature: 1,
topP: 0.95,
topK: 40,
maxOutputTokens: 8192,
responseMimeType: "text/plain",
};
async function run(articleContent: string[]) {
const chatSession = model.startChat({
generationConfig,
history: [
{
role: "user",
parts: [
{ text: "这里有几篇文章,用 --- 分割,帮我简要汇总一下,控制在 300 字以内。" },
{ text: "如果你认为是投资主题,我希望你能额外帮我总结和设计出一个短中长线的投资策略,要求详略得当,短线权重大一些,长线一笔带过就好" }
],
},
],
});
const result = await chatSession.sendMessage(articleContent.join("\n---\n"));
console.log(result.response.text());
return result.response.text();
}
return run(articleContent);
}
export default callGeminiAPI;

17
src/view/form.html Normal file
View File

@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html>
<head>
<title>文章总结</title>
</head>
<body>
<h1>输入微信文章链接</h1>
<form action="/summarize" method="post">
<label for="articleUrl">文章链接:</label><br>
<textarea id="articleUrl" name="articleUrl" rows="4" cols="50"></textarea><br><br>
<input type="submit" value="提交">
</form>
</body>
</html>