test news page

This commit is contained in:
锦恢 2025-07-06 05:26:11 +08:00
parent e43431a725
commit 5bf0623327
20 changed files with 383 additions and 33 deletions

2
.gitignore vendored
View File

@ -18,4 +18,4 @@ resources/service
stats.html stats.html
.openmcp .openmcp
test-vsix test-vsix
resources/changelog/index.html resources/changelog/**

1
news/.gitignore vendored
View File

@ -28,3 +28,4 @@ coverage
*.sw? *.sw?
*.tsbuildinfo *.tsbuildinfo
./src/data.json

5
news/env.d.ts vendored
View File

@ -1 +1,6 @@
/// <reference types="vite/client" /> /// <reference types="vite/client" />
declare module '*.vue' {
import { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}

View File

@ -7,7 +7,7 @@
<link rel="stylesheet" href="/default-dark.css"> <link rel="stylesheet" href="/default-dark.css">
<link rel="stylesheet" href="/vscode.css"> <link rel="stylesheet" href="/vscode.css">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite App</title> <title>OpenMCP News Feature</title>
</head> </head>
<body> <body>

View File

@ -8,6 +8,7 @@
"name": "news", "name": "news",
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"vanilla-tilt": "^1.8.1",
"vue": "^3.5.17" "vue": "^3.5.17"
}, },
"devDependencies": { "devDependencies": {
@ -2983,6 +2984,12 @@
"browserslist": ">= 4.21.0" "browserslist": ">= 4.21.0"
} }
}, },
"node_modules/vanilla-tilt": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/vanilla-tilt/-/vanilla-tilt-1.8.1.tgz",
"integrity": "sha512-hPB1XUsnh+SIeVSW2beb5RnuFxz4ZNgxjGD78o52F49gS4xaoLeEMh9qrQnJrnEn/vjjBI7IlxrrXmz4tGV0Kw==",
"license": "MIT"
},
"node_modules/vite": { "node_modules/vite": {
"version": "7.0.2", "version": "7.0.2",
"resolved": "https://registry.npmjs.org/vite/-/vite-7.0.2.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-7.0.2.tgz",

View File

@ -11,6 +11,7 @@
"type-check": "vue-tsc --build" "type-check": "vue-tsc --build"
}, },
"dependencies": { "dependencies": {
"vanilla-tilt": "^1.8.1",
"vue": "^3.5.17" "vue": "^3.5.17"
}, },
"devDependencies": { "devDependencies": {

View File

@ -5,9 +5,10 @@ import OmMoreFeature from './components/MoreFeature.vue';
import OmCoreFeature from './components/CoreFeature.vue'; import OmCoreFeature from './components/CoreFeature.vue';
import OmSponsor from './components/Sponsor.vue'; import OmSponsor from './components/Sponsor.vue';
import OmResource from './components/Resource.vue'; import OmResource from './components/Resource.vue';
import OmTroubleshoot from './components/Trouble.vue'; import OmTroubleshoot from './components/Troubleshoot.vue';
import OmContributor from './components/Contributor.vue';
import data from './data.json'; import data from './data.json';``
</script> </script>
@ -32,6 +33,9 @@ import data from './data.json';
<!-- 6. 🔧 Troubleshooting --> <!-- 6. 🔧 Troubleshooting -->
<om-troubleshoot /> <om-troubleshoot />
<!-- 7. 👥 Contributors -->
<om-contributor :contributors="data.contributors" />
</main> </main>
</template> </template>
@ -95,15 +99,20 @@ body {
gap: 24px; gap: 24px;
margin: 0 0 32px 0; margin: 0 0 32px 0;
padding: 0 0 16px 0; padding: 0 0 16px 0;
border-bottom: 1px solid #B988D1; }
@media screen and (max-width: 600px) {
.openmcp-header {
flex-direction: column;
align-items: center;
}
} }
.openmcp-logo { .openmcp-logo {
width: 84px; width: 84px;
height: 84px; height: 84px;
border-radius: 16px; border-radius: 16px;
background: var(--vscode-sideBar-background);
box-shadow: 0 2px 8px 0 rgba(185, 136, 209, 0.08);
} }
.margin-bottom { .margin-bottom {

View File

@ -0,0 +1,98 @@
<script setup lang="ts">
import { ref, onMounted, reactive } from 'vue';
import VanillaTilt from 'vanilla-tilt';
const props = defineProps<{
contributors: {
username: string;
avatarUrl: string;
homeUrl: string;
}[];
}>();
const tiltElements = reactive<any[]>([]);
onMounted(() => {
// Tilt
tiltElements.forEach(el => {
VanillaTilt.init(el, {
max: 35,
speed: 400,
glare: true,
'max-glare': 0.2
});
});
});
</script>
<template>
<section class="troubleshoot-section">
<div class="section-title">👥 Contributors</div>
<div class="contributors-grid">
<div v-for="(contributor, idx) in props.contributors"
:key="contributor.username" class="contributor-tilt"
:ref="el => tiltElements[idx] = el"
>
<a :href="contributor.homeUrl" target="_blank">
<img :src="contributor.avatarUrl" :alt="contributor.username" class="contributor-avatar" />
<span class="contributor-name">{{ contributor.username }}</span>
</a>
</div>
</div>
</section>
</template>
<style>
.contributors-container {
max-width: 800px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
.contributors-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
gap: 1.5rem;
margin-top: 2rem;
}
.contributor-tilt {
background: rgba(255, 255, 255, 0.1);
border-radius: 12px;
padding: 1rem;
transition: transform 0.2s;
}
.contributor-tilt a {
text-decoration: none;
color: inherit;
display: flex;
flex-direction: column;
align-items: center;
}
.contributor-avatar {
width: 80px;
height: 80px;
border-radius: 50%;
object-fit: cover;
display: block;
margin: 0 auto 0.5rem;
border: 3px solid #fff;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.contributor-name {
display: block;
font-weight: 500;
color: #B988D1;
text-decoration: none;
}
/* Tilt.js 生成的眩光效果自定义 */
.tilt-glare {
border-radius: 12px !important;
}
</style>

View File

@ -64,9 +64,18 @@ const props = defineProps({
</div> </div>
</div> </div>
</header> </header>
<hr>
</template> </template>
<style> <style>
hr {
border: none;
border-top: 1.5px solid #B988D1;
height: 0;
margin: 24px 0;
}
.openmcp-header h1 { .openmcp-header h1 {
font-size: 2.1rem; font-size: 2.1rem;
font-weight: bold; font-weight: bold;
@ -74,7 +83,6 @@ const props = defineProps({
display: flex; display: flex;
justify-content: center; justify-content: center;
color: #B988D1; color: #B988D1;
letter-spacing: 1px;
line-height: 1.2; line-height: 1.2;
} }
@ -82,6 +90,7 @@ const props = defineProps({
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 16px; gap: 16px;
justify-content: center;
margin-top: 4px; margin-top: 4px;
} }

View File

@ -0,0 +1,5 @@
<template>
<div>
</div>
</template>

View File

@ -42,8 +42,4 @@
.resource-list a:hover { .resource-list a:hover {
color: #8e5bbf; color: #8e5bbf;
} }
.troubleshoot-section {
margin-bottom: 32px;
}
</style> </style>

View File

@ -13,7 +13,7 @@
Github Github
</a> </a>
<br> <br>
<a class="sponsor-item" href="sponsor-item" target="_blank"> <a class="sponsor-item" href="https://afdian.com/a/kirigaya" target="_blank">
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"> <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
<path <path
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm.31-8.86c-1.77-.45-2.34-.94-2.34-1.67 0-.84.79-1.43 2.1-1.43 1.38 0 1.9.66 1.94 1.64h1.71c-.05-1.34-.87-2.57-2.49-2.97V5H10.9v1.69c-1.51.32-2.72 1.3-2.72 2.81 0 1.79 1.49 2.69 3.66 3.21 1.95.46 2.34 1.15 2.34 1.87 0 .53-.39 1.39-2.1 1.39-1.6 0-2.23-.72-2.32-1.64H8.04c.1 1.7 1.36 2.66 2.86 2.97V19h2.34v-1.67c1.52-.29 2.72-1.16 2.73-2.77-.01-2.2-1.9-2.96-3.66-3.42z"> d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm.31-8.86c-1.77-.45-2.34-.94-2.34-1.67 0-.84.79-1.43 2.1-1.43 1.38 0 1.9.66 1.94 1.64h1.71c-.05-1.34-.87-2.57-2.49-2.97V5H10.9v1.69c-1.51.32-2.72 1.3-2.72 2.81 0 1.79 1.49 2.69 3.66 3.21 1.95.46 2.34 1.15 2.34 1.87 0 .53-.39 1.39-2.1 1.39-1.6 0-2.23-.72-2.32-1.64H8.04c.1 1.7 1.36 2.66 2.86 2.97V19h2.34v-1.67c1.52-.29 2.72-1.16 2.73-2.77-.01-2.2-1.9-2.96-3.66-3.42z">

View File

@ -1,4 +1,4 @@
<script> <script setup lang="ts">
</script> </script>
<template> <template>
@ -18,6 +18,11 @@
</template> </template>
<style> <style>
.troubleshoot-section {
margin-bottom: 20px;
padding-bottom: 16px;
}
.troubleshoot-list { .troubleshoot-list {
list-style: disc inside; list-style: disc inside;
padding-left: 18px; padding-left: 18px;

View File

@ -1,11 +1,48 @@
{ {
"version": "0.1.9", "version": "0.1.9",
"changelogs": [ "changelogs": [
"Add mook feature: Automatically fill test tool form data using random seeds or AI generation.", "Add mook feature: Automatically fill in test tool form data using random seeds or AI generation",
"Add tool self-check feature: In OpenMCP's tool section, click 'Tool Self-Check' on the right of 'Tool Module' to enter self-check mode, where users can define the execution topology order of tools and perform automatic detection in one go.", "Add tool self-check feature: Under openmcp's tool, click 'Tool Self-Check' on the right side of 'Tool Module' to enter self-check mode. In this mode, users can define the topological order of tool execution and perform automatic detection in one go.",
"Fix issue #44: Complete platform adaptation for link redirection.", "Fix issue #44: Complete platform adaptation for link redirection",
"Fix issue #36: Successful startup when not opening a folder.", "Fix issue #36: Successful startup when not opening a folder",
"Fix issue #45: Support for array-type parameters.", "Fix issue #45: Array type parameters not supported",
"Fix abnormal style when pasting multi-line conversations into the dialog box." "Fix abnormal dialog style when pasting multi-line conversations into the dialog box"
],
"contributors": [
{
"username": "LSTM-Kirigaya",
"avatarUrl": "https://avatars.githubusercontent.com/u/59416203?v=4",
"homeUrl": "https://github.com/LSTM-Kirigaya"
},
{
"username": "li1553770945",
"avatarUrl": "https://avatars.githubusercontent.com/u/55867654?v=4",
"homeUrl": "https://github.com/li1553770945"
},
{
"username": "STUzhy",
"avatarUrl": "https://avatars.githubusercontent.com/u/129645384?v=4",
"homeUrl": "https://github.com/STUzhy"
},
{
"username": "appli456",
"avatarUrl": "https://avatars.githubusercontent.com/u/8943691?v=4",
"homeUrl": "https://github.com/appli456"
},
{
"username": "cybermanhao",
"avatarUrl": "https://avatars.githubusercontent.com/u/37235140?v=4",
"homeUrl": "https://github.com/cybermanhao"
},
{
"username": "ArcStellar2025",
"avatarUrl": "https://avatars.githubusercontent.com/u/115577936?v=4",
"homeUrl": "https://github.com/ArcStellar2025"
},
{
"username": "ZYD045692",
"avatarUrl": "https://avatars.githubusercontent.com/u/206822796?v=4",
"homeUrl": "https://github.com/ZYD045692"
}
] ]
} }

View File

@ -107,6 +107,12 @@
"title": "%openmcp.hook.test-ocr.title%", "title": "%openmcp.hook.test-ocr.title%",
"category": "openmcp", "category": "openmcp",
"icon": "$(test)" "icon": "$(test)"
},
{
"command": "openmcp.hook.test-news",
"title": "%openmcp.hook.test-news.title%",
"category": "openmcp",
"icon": "$(test)"
} }
], ],
"menus": { "menus": {

View File

@ -13,5 +13,6 @@
"openmcp.hook.test-ocr.title": "OCR をテスト", "openmcp.hook.test-ocr.title": "OCR をテスト",
"openmcp.sidebar.workspace-connection.view.title": "MCP 接続(ワークスペース)", "openmcp.sidebar.workspace-connection.view.title": "MCP 接続(ワークスペース)",
"openmcp.sidebar.installed-connection.view.title": "インストール済み MCP サーバー", "openmcp.sidebar.installed-connection.view.title": "インストール済み MCP サーバー",
"openmcp.sidebar.help.view.title": "はじめに・ヘルプ" "openmcp.sidebar.help.view.title": "はじめに・ヘルプ",
"openmcp.hook.test-news.title": "テストニュースレポート"
} }

View File

@ -13,5 +13,6 @@
"openmcp.hook.test-ocr.title": "Test OCR", "openmcp.hook.test-ocr.title": "Test OCR",
"openmcp.sidebar.workspace-connection.view.title": "MCP Connections (Workspace)", "openmcp.sidebar.workspace-connection.view.title": "MCP Connections (Workspace)",
"openmcp.sidebar.installed-connection.view.title": "Installed MCP Servers", "openmcp.sidebar.installed-connection.view.title": "Installed MCP Servers",
"openmcp.sidebar.help.view.title": "Getting Started & Help" "openmcp.sidebar.help.view.title": "Getting Started & Help",
"openmcp.hook.test-news.title": "test news report"
} }

View File

@ -13,5 +13,6 @@
"openmcp.hook.test-ocr.title": "测试 OCR", "openmcp.hook.test-ocr.title": "测试 OCR",
"openmcp.sidebar.workspace-connection.view.title": "MCP 连接 (工作区)", "openmcp.sidebar.workspace-connection.view.title": "MCP 连接 (工作区)",
"openmcp.sidebar.installed-connection.view.title": "安装的 MCP 服务器", "openmcp.sidebar.installed-connection.view.title": "安装的 MCP 服务器",
"openmcp.sidebar.help.view.title": "入门与帮助" "openmcp.sidebar.help.view.title": "入门与帮助",
"openmcp.hook.test-news.title": "测试新闻报告"
} }

View File

@ -0,0 +1,167 @@
import { readFileSync, writeFileSync } from 'fs';
import fetch from 'node-fetch';
import path from 'path';
import { OpenAI } from 'openai';
const client = new OpenAI({
baseURL: 'https://api.deepseek.com',
apiKey: process.env.DEEPSEEK_API_TOKEN,
});
async function fetchContributorsFromRepo(owner: string, repo: string) {
let page = 1;
const per_page = 100;
let contributors: any[] = [];
while (true) {
const res = await fetch(
`https://api.github.com/repos/${owner}/${repo}/contributors?per_page=${per_page}&page=${page}`,
{
headers: {
'User-Agent': 'openmcp-client-script'
}
}
);
if (!res.ok) {
throw new Error(`GitHub API error: ${res.status} ${res.statusText}`);
}
const data = await res.json();
if (data.length === 0) break;
contributors = contributors.concat(data);
page++;
}
return contributors;
}
async function fetchAndMergeContributors() {
const repos = [
{ owner: 'LSTM-Kirigaya', repo: 'openmcp-client' },
{ owner: 'LSTM-Kirigaya', repo: 'openmcp-document' }
];
const allContributors: Record<string, { username: string, avatarUrl: string, homeUrl: string, contributions: number }> = {};
for (const { owner, repo } of repos) {
const contributors = await fetchContributorsFromRepo(owner, repo);
for (const c of contributors) {
const username = c.login;
if (!allContributors[username]) {
allContributors[username] = {
username,
avatarUrl: c.avatar_url,
homeUrl: c.html_url,
contributions: c.contributions
};
} else {
allContributors[username].contributions += c.contributions;
}
}
}
// 按贡献次数排序
const result = Object.values(allContributors)
.sort((a, b) => b.contributions - a.contributions)
.map(({ contributions, ...rest }) => rest); // 去掉contributions字段
return result;
}
const PRJ_PATH = path.join(path.dirname(import.meta.dirname));
async function getVersionAndContent() {
const changelog = readFileSync( path.join(PRJ_PATH, 'CHANGELOG.md'), { encoding: 'utf-8' });
const newContent = changelog.split('## [main]')[1];
const version = newContent.split('\n')[0].trim();
// 提取 main 版本下的 markdown 列表内容
const contentLines = newContent.split('\n').slice(1);
const content = contentLines
.filter(line => line.trim().startsWith('- ') || line.trim().startsWith('* '))
.map(line => line.replace(/^[-*]\s*/, '').trim())
.join('\n');
// 让大模型翻译并输出 json
const prompt = `
Translate the following markdown list to English and return a JSON array of strings (do not include any markdown or extra text, only valid JSON array):
${content}
`;
const completion = await client.chat.completions.create({
model: 'deepseek-chat',
messages: [
{ role: 'system', content: 'You are a helpful assistant for translation and JSON formatting.' },
{ role: 'user', content: prompt }
],
temperature: 0.2,
max_tokens: 1024
});
// 解析大模型返回的 JSON
let changelogs: string[] = [];
try {
const choices = completion.choices;
if (!choices || choices.length === 0 || !choices[0].message || !choices[0].message.content) {
throw new Error('Invalid response from LLM');
}
const text = choices[0].message.content.trim();
// 按行过滤:只保留非 ``` 开头的行
const clearText = text.split('\n').filter(line => !line.startsWith('```')).join('\n')
changelogs = JSON.parse(clearText);
} catch (e) {
throw new Error('Failed to parse LLM output as JSON: ' + e);
}
return { version, changelogs };
}
const contributors = await fetchAndMergeContributors();
const { version, changelogs } = await getVersionAndContent();
const newsData = {
version,
changelogs,
contributors
};
const newsDataPath = path.join(PRJ_PATH, 'news', 'src', 'data.json');
writeFileSync(newsDataPath, JSON.stringify(newsData, null, 2), { encoding: 'utf-8' });
console.log('News data updated successfully:', newsDataPath);
import { execSync } from 'child_process';
// ...前面的代码...
// 写入 news/src/data.json
writeFileSync(newsDataPath, JSON.stringify(newsData, null, 2), { encoding: 'utf-8' });
console.log('News data updated successfully:', newsDataPath);
// 1. 进入 news 目录并编译
const newsDir = path.join(PRJ_PATH, 'news');
console.log('Building news SPA...');
execSync('npm run build', { cwd: newsDir, stdio: 'inherit' });
// 2. 拷贝编译产物到 resources/changelog
const buildOutputDir = path.join(newsDir, 'dist');
const targetDir = path.join(PRJ_PATH, 'resources', 'changelog');
// 简单递归复制函数
import { existsSync, mkdirSync, readdirSync, statSync, copyFileSync } from 'fs';
function copyDir(src: string, dest: string) {
if (!existsSync(dest)) mkdirSync(dest, { recursive: true });
for (const file of readdirSync(src)) {
const srcFile = path.join(src, file);
const destFile = path.join(dest, file);
if (file.endsWith('.css')) {
continue;
}
if (statSync(srcFile).isDirectory()) {
copyDir(srcFile, destFile);
} else {
copyFileSync(srcFile, destFile);
}
}
}
copyDir(buildOutputDir, targetDir);
console.log('Build output copied to:', targetDir);

View File

@ -2,7 +2,7 @@ import { RegisterCommand } from "../common/index.js";
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import * as path from 'path'; import * as path from 'path';
import Tesseract from 'tesseract.js'; import Tesseract from 'tesseract.js';
import { revealOpenMcpNewsWebviewPanel } from "../webview/webview.service.js";
export class HookController { export class HookController {
@ -40,7 +40,8 @@ export class HookController {
} }
} }
@RegisterCommand('openmcp.hook.test-news')
async testNews(context: vscode.ExtensionContext) {
revealOpenMcpNewsWebviewPanel(context);
}
} }