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