Hallo, können Sie dieses funktionierende Update auch für Azure erstellen?
Falcos Korrektur sollte sowohl für Azure- als auch für OpenAI-Endpunkte funktionieren. Sie durchlaufen denselben Code. FWIW, Sie müssen enable_responses_api aktivieren.
Leider erscheint diese Einstellung nicht, nachdem Azure als Anbieter ausgewählt wurde.
Entschuldigen Sie die Wartezeit. Ich habe die anbieterspezifischen Optionen hier hinzugefügt:
Unterstützt es Vertex AI in GCP?
Haben Sie es über OpenAI compatibility | Generative AI on Vertex AI | Google Cloud versucht?
Ich hoffe, dies ist der richtige Ort, um dies zu melden. Da gibt es einen kleinen Rechtschreibfehler.
Danke für die Meldung. Es ging bei einer Bearbeitung im Juni verloren. Einige Teile des ersten Beitrags sind in HTML-Kommentaren versteckt, und ich glaube, ich habe bemerkt, dass der WYSIWYG-Editor dies bei einer früheren Bearbeitung kaputt gemacht hat. Aber seit der Bearbeitung gab es viele Updates, so dass es bereits behoben sein könnte.
Ich versuche, alles über die Discord KI-Funktion herauszufinden, aber ich kann absolut nichts finden
Wie funktioniert sie? Oder was kann sie tun? Ich spreche von der Discord-Suche.
Oh ja, ja, ja, das würde ich LIEBEN.
Wir überlegen, Abonnements einzuführen, nur um Zugang zu KI-Funktionen zu haben. Aber gestaffelter KI-Zugang wäre noch besser! Danke für den Vorschlag.
Ich habe einen Fehler gefunden (und einen PR erstellt, um ihn zu beheben), als ich versucht habe, den Discord Persona Bot zu verwenden
Job exception: context muss eine Instanz von BotContext sein
/var/www/discourse/plugins/discourse-ai/lib/personas/bot.rb:55:in `reply'
/var/www/discourse/plugins/discourse-ai/lib/discord/bot/persona_replier.rb:25:in `handle_interaction!'
/var/www/discourse/plugins/discourse-ai/app/jobs/regular/stream_discord_reply.rb:13:in `execute'
Wann wird DeepSeek unterstützt? Es ist ein LLM, das von einem chinesischen Technologieunternehmen entwickelt wurde und relativ kostengünstig ist.
Danke schön. ![]()
Es wird bereits unterstützt. Haben Sie eine Funktion darauf ausprobiert, die nicht funktioniert hat?
最近马斯克开源了推特推荐算法,能否在这个插件实现?
以下是某个论坛用户写的油猴脚本实现推特推荐算法,理论上是用于任何discourse论坛:
// ==UserScript==
// @name *** Algorithm
// @namespace http://tampermonkey.net/
// @version 0.9
// @description 融合 X 算法和 DeepSeek AI 的智能推荐系统,提供个性化主题推荐
// @author ***
// @match ****
// @grant GM_xmlhttpRequest
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_addStyle
// @connect 2c2ch1u11-share-api-0.hf.space
// ==/UserScript==
(function() {
'use strict';
const API_KEY = '***';
const API_URL = '***/v1/chat/completions';
const MODEL = 'deepseek-chat';
// ========== 算法配置参数(可调整优化) ==========
const CONFIG = {
// X 算法权重参数
WEIGHT_LIKES: 0.5,
WEIGHT_REPLIES: 13.5,
WEIGHT_VIEWS: 0.015,
PINNED_BOOST: 2.0,
TIME_DECAY_FACTOR: 1.5,
TIME_DECAY_OFFSET: 2,
// 评分最大值参数
X_SCORE_MAX: 45,
AI_SCORE_MAX: 55,
// 评分阈值参数
MAX_SCORE: 100,
MIN_DISPLAY_SCORE: 20,
// 缓存参数
PROFILE_CACHE_TTL: 86400000, // 用户画像缓存:24小时
TOPIC_SCORE_CACHE_TTL: 3600000, // 单个主题AI评分缓存:1小时
// API 参数
MAX_TOPICS_PER_BATCH: 30,
MAX_LIKED_TITLES: 15, // 点赞主题数量
MAX_REPLIED_TITLES: 10, // 回复主题数量
MAX_CREATED_TITLES: 5, // 创建主题数量
API_RETRY_COUNT: 2,
API_RETRY_DELAY: 1000,
// 用户画像分析维度
PROFILE_CATEGORIES: ['技术兴趣', '内容偏好', '互动习惯', '专业领域', '阅读深度'],
};
let isRecommendMode = false;
let scoreMap = {};
let allTopicsData = {};
let sortTimeout = null;
let userProfile = "";
let isLoading = false;
let isProcessingNewTopics = false;
// ========== CSS 样式 ==========
GM_addStyle(`
.nav-pills > li > a,
.nav-pills > li.ember-view > a,
.navigation-container .nav-pills > li > a {
border-bottom: 3px solid transparent !important;
transition: all 0.2s ease;
}
body.recommend-mode-active .nav-pills > li:not(#nav-item-recommend) > a,
body.recommend-mode-active .nav-pills > li.ember-view:not(#nav-item-recommend) > a,
body.recommend-mode-active .navigation-container .nav-pills > li:not(#nav-item-recommend) > a {
color: var(--primary-medium) !important;
border-bottom-color: transparent !important;
}
.nav-pills li#nav-item-recommend.active > a,
body.recommend-mode-active .nav-pills li#nav-item-recommend > a {
color: var(--tertiary) !important;
border-bottom: 3px solid var(--tertiary) !important;
font-weight: 600;
}
.nav-pills li#nav-item-recommend > a:hover {
color: var(--tertiary) !important;
opacity: 0.8;
}
.recommend-loading-container {
width: 100%;
padding: 60px 20px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
min-height: 300px;
}
.recommend-loading-text {
color: var(--primary-medium);
font-size: 15px;
margin-bottom: 24px;
text-align: center;
}
.recommend-spinner {
width: 36px;
height: 36px;
border: 3px solid var(--primary-low);
border-top: 3px solid var(--primary-medium);
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.manus-score-badge {
font-size: 0.75em;
color: var(--tertiary);
margin-left: 8px;
padding: 2px 6px;
background: var(--tertiary-very-low);
border-radius: 4px;
font-weight: 500;
transition: all 0.3s ease;
}
.manus-score-badge.calculating {
color: var(--primary-medium);
background: var(--primary-very-low);
animation: pulse 1.5s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.manus-score-loading {
font-size: 0.75em;
color: var(--primary-medium);
margin-left: 8px;
}
.recommend-full-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: var(--secondary);
z-index: 100;
min-height: 500px;
}
.recommend-loading-row {
position: relative;
z-index: 101;
}
`);
// ========== 主题AI评分缓存函数 ==========
function getTopicScoreCache() {
return GM_getValue('topic_ai_scores_cache', {});
}
function setTopicScoreCache(topicId, aiScore) {
const cache = getTopicScoreCache();
cache[topicId] = { score: aiScore, time: Date.now() };
GM_setValue('topic_ai_scores_cache', cache);
}
function getCachedTopicScore(topicId) {
const cache = getTopicScoreCache();
const entry = cache[topicId];
if (entry && (Date.now() - entry.time < CONFIG.TOPIC_SCORE_CACHE_TTL)) {
return entry.score;
}
return null;
}
function cleanExpiredCache() {
const cache = getTopicScoreCache();
const now = Date.now();
let cleaned = false;
for (const [id, entry] of Object.entries(cache)) {
if (now - entry.time > CONFIG.TOPIC_SCORE_CACHE_TTL) {
delete cache[id];
cleaned = true;
}
}
if (cleaned) {
GM_setValue('topic_ai_scores_cache', cache);
}
}
// ========== 注入推荐标签页 ==========
function injectRecommendTab() {
if (document.querySelector('#nav-item-recommend')) return;
const navPills = document.querySelector('.nav-pills');
if (!navPills) return;
const recommendTab = document.createElement('li');
recommendTab.id = 'nav-item-recommend';
recommendTab.className = 'ember-view nav-item_recommend';
recommendTab.innerHTML = `<a href="javascript:void(0)">推荐</a>`;
const latestTab = navPills.querySelector('.nav-item_latest') ||
navPills.querySelector('[data-filter-type="latest"]')?.parentElement ||
navPills.firstChild;
navPills.insertBefore(recommendTab, latestTab);
recommendTab.addEventListener('click', async (e) => {
e.preventDefault();
e.stopPropagation();
if (isLoading) return;
isRecommendMode = true;
document.body.classList.add('recommend-mode-active');
navPills.querySelectorAll('li').forEach(li => {
li.classList.remove('active');
li.querySelector('a')?.classList.remove('active');
});
recommendTab.classList.add('active');
cleanExpiredCache();
showLoading(true);
await startRecommendation();
showLoading(false);
sortAndDisplayRows();
});
navPills.querySelectorAll('li:not(#nav-item-recommend)').forEach(tab => {
tab.addEventListener('click', () => {
isRecommendMode = false;
document.body.classList.remove('recommend-mode-active');
recommendTab.classList.remove('active');
document.querySelectorAll('.manus-score-badge').forEach(b => b.remove());
document.querySelectorAll('.manus-score-loading').forEach(b => b.remove());
});
});
window.addEventListener('popstate', () => {
if (isRecommendMode) {
isRecommendMode = false;
document.body.classList.remove('recommend-mode-active');
recommendTab.classList.remove('active');
}
});
}
// ========== 加载提示 ==========
let originalTableContent = null;
function showLoading(show, text = '正在融合 X 算法与 DeepSeek AI推荐内容...') {
isLoading = show;
const topicListContainer = document.querySelector('.topic-list-container') ||
document.querySelector('.topic-list')?.parentElement ||
document.querySelector('.topic-list');
const tbody = document.querySelector('.topic-list tbody');
if (!topicListContainer) return;
if (show) {
if (!originalTableContent && tbody) {
originalTableContent = tbody.innerHTML;
}
if (tbody) {
tbody.innerHTML = `
<tr class="recommend-loading-row">
<td colspan="100%">
<div class="recommend-loading-container">
<div class="recommend-loading-text">${text}</div>
<div class="recommend-spinner"></div>
</div>
</td>
</tr>
`;
}
topicListContainer.style.position = 'relative';
let overlay = topicListContainer.querySelector('.recommend-full-overlay');
if (!overlay) {
overlay = document.createElement('div');
overlay.className = 'recommend-full-overlay';
topicListContainer.appendChild(overlay);
}
} else {
topicListContainer.querySelector('.recommend-full-overlay')?.remove();
if (originalTableContent && tbody) {
tbody.innerHTML = originalTableContent;
originalTableContent = null;
}
}
}
// ========== 提取标题的辅助函数 ==========
function extractTitles(data, maxCount) {
if (!data?.user_actions) return '';
const titles = [...new Set(
data.user_actions
.filter(a => a.title)
.map(a => a.title)
)].slice(0, maxCount);
return titles.length > 0 ? titles.join('\n') : '';
}
// ========== 优化:更详细的用户画像获取 ==========
async function fetchUserProfile(username) {
if (!username) {
console.log('[推荐] 未登录,使用默认画像');
return "默认";
}
const cacheKey = `user_profile_v9_${username}`;
const cached = GM_getValue(cacheKey);
if (cached && (Date.now() - cached.time < CONFIG.PROFILE_CACHE_TTL)) {
console.log(`[推荐] 用户画像(缓存):\n${cached.profile}`);
return cached.profile;
}
try {
// 获取多种用户行为数据
const [likedData, repliedData, createdData] = await Promise.all([
fetch(`/user_actions.json?username=${username}&filter=1`).then(r => r.json()).catch(() => null), // 点赞
fetch(`/user_actions.json?username=${username}&filter=5`).then(r => r.json()).catch(() => null), // 回复
fetch(`/user_actions.json?username=${username}&filter=4`).then(r => r.json()).catch(() => null), // 创建
]);
// 提取不同类型的标题
const likedTitles = extractTitles(likedData, CONFIG.MAX_LIKED_TITLES);
const repliedTitles = extractTitles(repliedData, CONFIG.MAX_REPLIED_TITLES);
const createdTitles = extractTitles(createdData, CONFIG.MAX_CREATED_TITLES);
if (!likedTitles && !repliedTitles && !createdTitles) return "默认";
// 构建更详细的分析提示词
const prompt = `你是一个专业的用户行为分析师。请基于以下用户行为数据,生成一个详细的用户画像:
**用户点赞的主题**(最能反映兴趣):
${likedTitles || '无数据'}
**用户回复过的主题**(反映参与度和专业领域):
${repliedTitles || '无数据'}
**用户创建的主题**(反映主动关注点):
${createdTitles || '无数据'}
请从以下维度分析用户画像,每个维度用一句话概括:
1. **技术兴趣**:用户关注哪些技术栈、工具或平台
2. **内容偏好**:偏好教程、讨论、资讯还是问答类内容
3. **互动习惯**:是深度参与者还是浅层浏览者
4. **专业领域**:主要专注的技术领域或行业方向
5. **阅读深度**:偏好入门级、进阶级还是专家级内容
输出格式(不要编号,直接输出5行):
技术兴趣:[分析结果]
内容偏好:[分析结果]
互动习惯:[分析结果]
专业领域:[分析结果]
阅读深度:[分析结果]`;
const profile = await callDeepSeek(prompt, false);
if (profile && profile.length > 0) {
GM_setValue(cacheKey, { time: Date.now(), profile: profile });
console.log(`[推荐] 用户画像:\n${profile}`);
return profile;
}
return "默认";
} catch (e) {
console.error('[推荐] 获取用户画像失败:', e);
return "默认";
}
}
// ========== 启动推荐流程 ==========
async function startRecommendation() {
try {
const currentUser = getCurrentUserInfo();
if (!userProfile) {
userProfile = await fetchUserProfile(currentUser?.username);
}
const response = await fetch('/latest.json');
const data = await response.json();
const topics = data.topic_list.topics;
topics.forEach(t => {
allTopicsData[t.id] = t;
});
const uncachedTopics = [];
const xScoresRaw = {};
const aiScoresMap = {};
topics.forEach(topic => {
xScoresRaw[topic.id] = calculateXScore(topic);
const cachedScore = getCachedTopicScore(topic.id);
if (cachedScore !== null) {
aiScoresMap[topic.id] = cachedScore;
} else {
uncachedTopics.push(topic);
}
});
if (uncachedTopics.length > 0) {
await batchScoreTopics(uncachedTopics, aiScoresMap);
}
calculateFinalScores(topics, xScoresRaw, aiScoresMap);
} catch (e) {
console.error('[推荐] 推荐流程失败:', e);
}
}
// ========== 批量评分主题 ==========
async function batchScoreTopics(topics, aiScoresMap) {
for (let i = 0; i < topics.length; i += CONFIG.MAX_TOPICS_PER_BATCH) {
const batch = topics.slice(i, i + CONFIG.MAX_TOPICS_PER_BATCH);
const aiScores = await getAIScoresForBatch(batch);
batch.forEach(topic => {
const aiScore = aiScores[topic.id] || 5;
setTopicScoreCache(topic.id, aiScore);
aiScoresMap[topic.id] = aiScore;
});
}
}
// ========== 优化:更精准的批量主题评分 ==========
async function getAIScoresForBatch(topics) {
const topicList = topics.map((t, idx) =>
`${idx + 1}. [ID:${t.id}] ${t.title}`
).join('\n');
const prompt = `你是一个内容推荐专家。请基于用户画像,为以下主题打分。
**用户画像**:
${userProfile}
**待评分主题**:
${topicList}
**评分标准**(0-10分):
- **9-10分**:与用户画像高度匹配,用户极可能感兴趣
- **7-8分**:与用户画像较匹配,用户很可能感兴趣
- **5-6分**:与用户画像部分匹配,用户可能感兴趣
- **3-4分**:与用户画像关联较弱,用户不太可能感兴趣
- **0-2分**:与用户画像无关或相反,用户不感兴趣
**评分考虑因素**:
1. 主题内容与用户技术兴趣的匹配度
2. 主题类型与用户内容偏好的一致性
3. 主题深度与用户阅读深度的适配性
4. 主题领域与用户专业领域的相关性
请返回JSON格式,键为主题ID(纯数字),值为评分(0-10的数字)。
示例:{"123": 8.5, "456": 5.0}
只返回JSON,不要其他文字。`;
const scores = await callDeepSeek(prompt, true);
// 清洗和验证评分
const cleanedScores = {};
for (const [id, score] of Object.entries(scores)) {
const numId = parseInt(id, 10);
const numScore = parseFloat(score);
if (!isNaN(numId) && !isNaN(numScore) && numScore >= 0 && numScore <= 10) {
cleanedScores[numId] = Math.round(numScore * 10) / 10;
}
}
return cleanedScores;
}
// ========== 优化:更稳健的最终评分计算 ==========
function calculateFinalScores(topics, xScoresRaw, aiScoresMap) {
const xScores = Object.values(xScoresRaw);
if (xScores.length === 0) return;
// 使用分位数归一化,避免极端值影响
const sortedX = [...xScores].sort((a, b) => a - b);
const p5 = sortedX[Math.floor(sortedX.length * 0.05)]; // 5%分位数
const p95 = sortedX[Math.floor(sortedX.length * 0.95)]; // 95%分位数
const rangeX = p95 - p5;
topics.forEach(topic => {
const id = topic.id;
let xScoreNormalized;
if (rangeX === 0) {
xScoreNormalized = CONFIG.X_SCORE_MAX / 2;
} else {
// 限制在 p5 到 p95 范围内
const clampedX = Math.max(p5, Math.min(p95, xScoresRaw[id]));
xScoreNormalized = ((clampedX - p5) / rangeX) * CONFIG.X_SCORE_MAX;
}
const aiScore = aiScoresMap[id] || 5;
const aiScoreNormalized = (aiScore / 10) * CONFIG.AI_SCORE_MAX;
// 最终评分:X算法(45%) + AI评分(55%)
const finalScore = xScoreNormalized + aiScoreNormalized;
scoreMap[id] = Math.round(finalScore * 10) / 10;
});
}
// ========== 计算新主题的最终评分 ==========
function calculateFinalScoresForNew(topicIds, xScoresRaw, aiScoresMap) {
const allXScores = Object.values(scoreMap).length > 0
? [...Object.values(xScoresRaw), ...Object.keys(allTopicsData).map(id => calculateXScore(allTopicsData[id]))]
: Object.values(xScoresRaw);
if (allXScores.length === 0) return;
const sortedX = [...allXScores].sort((a, b) => a - b);
const p5 = sortedX[Math.floor(sortedX.length * 0.05)];
const p95 = sortedX[Math.floor(sortedX.length * 0.95)];
const rangeX = p95 - p5;
topicIds.forEach(id => {
if (xScoresRaw[id] === undefined) return;
let xScoreNormalized;
if (rangeX === 0) {
xScoreNormalized = CONFIG.X_SCORE_MAX / 2;
} else {
const clampedX = Math.max(p5, Math.min(p95, xScoresRaw[id]));
xScoreNormalized = ((clampedX - p5) / rangeX) * CONFIG.X_SCORE_MAX;
}
const aiScore = aiScoresMap[id] || 5;
const aiScoreNormalized = (aiScore / 10) * CONFIG.AI_SCORE_MAX;
const finalScore = xScoreNormalized + aiScoreNormalized;
scoreMap[id] = Math.round(finalScore * 10) / 10;
});
}
// ========== 处理新加载的主题(滚动加载) ==========
async function processNewTopics(newRows) {
if (!isRecommendMode || isProcessingNewTopics) return;
isProcessingNewTopics = true;
try {
const newTopicIds = [];
newRows.forEach(row => {
const id = row.getAttribute('data-topic-id');
if (id && !scoreMap[id]) {
newTopicIds.push(id);
}
});
if (newTopicIds.length === 0) {
isProcessingNewTopics = false;
return;
}
newTopicIds.forEach(id => {
const row = document.querySelector(`tr[data-topic-id="${id}"]`);
if (row) {
addCalculatingBadge(row);
}
});
const uncachedTopics = [];
const xScoresRaw = {};
const aiScoresMap = {};
for (const id of newTopicIds) {
const topicData = allTopicsData[id] || await fetchTopicData(id);
if (!topicData) continue;
allTopicsData[id] = topicData;
xScoresRaw[id] = calculateXScore(topicData);
const cachedScore = getCachedTopicScore(id);
if (cachedScore !== null) {
aiScoresMap[id] = cachedScore;
} else {
uncachedTopics.push(topicData);
}
}
if (uncachedTopics.length > 0) {
await batchScoreTopics(uncachedTopics, aiScoresMap);
}
calculateFinalScoresForNew(newTopicIds, xScoresRaw, aiScoresMap);
updateRowBadges();
} catch (e) {
console.error('[推荐] 处理新主题失败:', e);
}
isProcessingNewTopics = false;
}
// ========== 获取单个主题数据 ==========
async function fetchTopicData(topicId) {
try {
const row = document.querySelector(`tr[data-topic-id="${topicId}"]`);
if (row) {
const title = row.querySelector('.title')?.textContent?.trim() || '';
const replies = parseInt(row.querySelector('.posts')?.textContent) || 0;
const views = parseInt(row.querySelector('.views')?.textContent?.replace(/[k,]/gi, '')) || 0;
const likes = parseInt(row.querySelector('.likes')?.textContent) || 0;
return {
id: topicId,
title: title,
posts_count: replies,
views: views,
like_count: likes,
created_at: new Date().toISOString(),
pinned: row.classList.contains('pinned')
};
}
return null;
} catch (e) {
return null;
}
}
// ========== 排序并显示 ==========
function sortAndDisplayRows() {
if (!isRecommendMode) return;
const tbody = document.querySelector('.topic-list tbody');
if (!tbody) return;
const rows = Array.from(tbody.querySelectorAll('tr.topic-list-item'));
rows.sort((a, b) => {
const idA = a.getAttribute('data-topic-id');
const idB = b.getAttribute('data-topic-id');
const scoreA = scoreMap[idA] || 0;
const scoreB = scoreMap[idB] || 0;
return scoreB - scoreA;
});
let hiddenCount = 0;
rows.forEach(row => {
const id = row.getAttribute('data-topic-id');
const score = scoreMap[id];
if (score === undefined) {
row.style.display = '';
addCalculatingBadge(row);
} else if (score < CONFIG.MIN_DISPLAY_SCORE) {
row.style.display = 'none';
hiddenCount++;
} else {
row.style.display = '';
addScoreBadge(row);
}
tbody.appendChild(row);
});
}
// ========== 更新所有行的徽章 ==========
function updateRowBadges() {
if (!isRecommendMode) return;
const rows = document.querySelectorAll('tr.topic-list-item');
rows.forEach(row => {
const id = row.getAttribute('data-topic-id');
const score = scoreMap[id];
if (score === undefined) {
addCalculatingBadge(row);
} else if (score < CONFIG.MIN_DISPLAY_SCORE) {
row.style.display = 'none';
} else {
row.style.display = '';
addScoreBadge(row);
}
});
}
// ========== 添加评分徽章 ==========
function addScoreBadge(row) {
const id = row.getAttribute('data-topic-id');
const score = scoreMap[id];
row.querySelector('.manus-score-loading')?.remove();
if (score !== undefined) {
let badge = row.querySelector('.manus-score-badge');
if (!badge) {
badge = document.createElement('span');
badge.className = 'manus-score-badge';
const titleContainer = row.querySelector('.link-top-line') ||
row.querySelector('.title')?.parentElement ||
row.querySelector('.main-link') ||
row.querySelector('td:first-child');
if (titleContainer) {
titleContainer.appendChild(badge);
}
}
if (badge) {
badge.textContent = `🔥 ${score.toFixed(1)}`;
badge.classList.remove('calculating');
}
}
}
// ========== 添加"计算中"徽章 ==========
function addCalculatingBadge(row) {
const id = row.getAttribute('data-topic-id');
if (scoreMap[id] !== undefined) return;
let badge = row.querySelector('.manus-score-badge');
if (!badge) {
badge = document.createElement('span');
badge.className = 'manus-score-badge calculating';
const titleContainer = row.querySelector('.link-top-line') ||
row.querySelector('.title')?.parentElement ||
row.querySelector('.main-link') ||
row.querySelector('td:first-child');
if (titleContainer) {
titleContainer.appendChild(badge);
}
}
if (badge) {
badge.textContent = '⏳ 计算中...';
badge.classList.add('calculating');
}
}
// ========== 优化:改进 JSON 清洗逻辑 ==========
function cleanJsonResponse(content) {
if (!content) return content;
let cleaned = content.trim();
// 移除 Markdown 代码块标记
cleaned = cleaned.replace(/^```(?:json)?\s*/i, '').replace(/```\s*$/i, '');
// 移除可能的文字说明
const jsonMatch = cleaned.match(/\{[\s\S]*\}/);
if (jsonMatch) {
cleaned = jsonMatch[0];
}
return cleaned.trim();
}
// ========== API 调用 ==========
async function callDeepSeek(prompt, isJson = true, retryCount = 0) {
return new Promise((resolve) => {
GM_xmlhttpRequest({
method: "POST",
url: API_URL,
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${API_KEY}`
},
data: JSON.stringify({
model: MODEL,
messages: [{ role: "user", content: prompt }],
temperature: 0.3,
max_tokens: 1000
}),
timeout: 30000,
onload: (res) => {
try {
if (res.status >= 200 && res.status < 300) {
let content = JSON.parse(res.responseText).choices[0].message.content;
if (isJson) {
content = cleanJsonResponse(content);
resolve(JSON.parse(content));
} else {
resolve(content);
}
} else {
throw new Error(`HTTP ${res.status}`);
}
} catch(e) {
console.error('[推荐] API 响应解析失败:', e);
if (retryCount < CONFIG.API_RETRY_COUNT) {
setTimeout(() => {
callDeepSeek(prompt, isJson, retryCount + 1).then(resolve);
}, CONFIG.API_RETRY_DELAY);
} else {
resolve(isJson ? {} : "");
}
}
},
onerror: (err) => {
console.error('[推荐] API 调用失败:', err);
if (retryCount < CONFIG.API_RETRY_COUNT) {
setTimeout(() => {
callDeepSeek(prompt, isJson, retryCount + 1).then(resolve);
}, CONFIG.API_RETRY_DELAY);
} else {
resolve(isJson ? {} : "");
}
},
ontimeout: () => {
console.error('[推荐] API 调用超时');
if (retryCount < CONFIG.API_RETRY_COUNT) {
setTimeout(() => {
callDeepSeek(prompt, isJson, retryCount + 1).then(resolve);
}, CONFIG.API_RETRY_DELAY);
} else {
resolve(isJson ? {} : "");
}
}
});
});
}
// ========== 优化:更智能的 X 算法评分 ==========
function calculateXScore(topic) {
const now = new Date();
const created = new Date(topic.created_at);
const hoursOld = Math.max(0, (now - created) / (1000 * 60 * 60));
// 基础互动评分
const likeScore = (topic.like_count || 0) * CONFIG.WEIGHT_LIKES;
const replyScore = (topic.posts_count || 0) * CONFIG.WEIGHT_REPLIES;
const viewScore = (topic.views || 0) * CONFIG.WEIGHT_VIEWS;
// 计算互动率加成(高互动率的内容质量更高)
const views = topic.views || 1;
const engagementRate = ((topic.like_count || 0) + (topic.posts_count || 0)) / views;
const engagementBoost = 1 + Math.min(engagementRate * 10, 2); // 最多2倍加成
let score = (likeScore + replyScore + viewScore) * engagementBoost;
// 置顶加成
if (topic.pinned) {
score *= CONFIG.PINNED_BOOST;
}
// 时间衰减(新鲜度)
const timeFactor = Math.pow(hoursOld + CONFIG.TIME_DECAY_OFFSET, CONFIG.TIME_DECAY_FACTOR);
return score / timeFactor;
}
// ========== 获取当前用户信息 ==========
function getCurrentUserInfo() {
try {
// 方法 1: Discourse 容器
const container = window.Discourse?.__container__;
if (container) {
const currentUser = container.lookup('service:current-user');
if (currentUser?.username) {
return {
username: currentUser.username,
id: currentUser.id,
name: currentUser.name,
trust_level: currentUser.trust_level
};
}
}
// 方法 2: User.current()
if (window.User?.current?.()?.username) {
const user = window.User.current();
return { username: user.username, id: user.id };
}
// 方法 3: #current-user
const userLink = document.querySelector('#current-user a[data-user-card]');
if (userLink) {
return { username: userLink.getAttribute('data-user-card') };
}
// 方法 4: header
const headerUser = document.querySelector('.header-dropdown-toggle.current-user button');
if (headerUser) {
const img = headerUser.querySelector('img');
if (img?.alt) {
return { username: img.alt };
}
}
// 方法 5: d-header
const anyUserCard = document.querySelector('.d-header [data-user-card]');
if (anyUserCard) {
return { username: anyUserCard.getAttribute('data-user-card') };
}
// 方法 6: preload 数据
const preloadData = document.querySelector('#data-preloaded');
if (preloadData) {
try {
const data = JSON.parse(preloadData.dataset.preloaded || '{}');
const currentUser = JSON.parse(data.currentUser || '{}');
if (currentUser.username) {
return { username: currentUser.username };
}
} catch (e) {}
}
return null;
} catch (e) {
return null;
}
}
// ========== 监听DOM变化 ==========
const listObserver = new MutationObserver((mutations) => {
if (!isRecommendMode || isLoading) return;
const newRows = [];
mutations.forEach(m => {
m.addedNodes.forEach(node => {
if (node.nodeType === 1) {
if (node.classList?.contains('topic-list-item')) {
newRows.push(node);
} else if (node.querySelectorAll) {
node.querySelectorAll('.topic-list-item').forEach(row => newRows.push(row));
}
}
});
});
if (newRows.length > 0) {
newRows.forEach(row => {
const id = row.getAttribute('data-topic-id');
if (id && scoreMap[id] === undefined) {
addCalculatingBadge(row);
} else if (scoreMap[id] !== undefined) {
if (scoreMap[id] >= CONFIG.MIN_DISPLAY_SCORE) {
addScoreBadge(row);
} else {
row.style.display = 'none';
}
}
});
clearTimeout(sortTimeout);
sortTimeout = setTimeout(() => processNewTopics(newRows), 500);
}
});
const navObserver = new MutationObserver(() => {
injectRecommendTab();
const tbody = document.querySelector('.topic-list tbody');
if (tbody && !tbody.dataset.observing) {
tbody.dataset.observing = 'true';
listObserver.observe(tbody, { childList: true });
}
});
// ========== 启动脚本 ==========
navObserver.observe(document.body, { childList: true, subtree: true });
injectRecommendTab();
console.log('[推荐系统] 已加载 - 融合 X 算法与 DeepSeek AI');
})();
以下是在discourse论坛的效果:


