Ciao, puoi rendere anche questo aggiornamento funzionante per Azure?
La correzione di Falco dovrebbe funzionare sia per gli endpoint Azure che OpenAI. Passano attraverso lo stesso codice. Per tua informazione, devi abilitare enable_responses_api.
Purtroppo, questa impostazione non compare dopo aver selezionato Azure come provider.
Mi scuso per l’attesa. Ho aggiunto qui le opzioni specifiche del provider:
Supporta Vertex AI in GCP?
Hai provato a usare OpenAI compatibility | Generative AI on Vertex AI | Google Cloud?
Grazie per la segnalazione. È andato perso durante una modifica a giugno. Alcune parti del primo post sono nascoste nei commenti HTML e penso di aver notato che l’editor WYSIWYG lo ha interrotto durante una modifica precedente. Ma ci sono stati molti aggiornamenti da quando è avvenuta la modifica, quindi potrebbe essere già stato risolto.
Sto cercando di capire qualcosa sulla funzione AI di Discord ma non riesco a trovare assolutamente nulla
Come funziona? o cosa può fare? Sto parlando della ricerca di Discord però
oh sì sì sì lo ADOREREI.
Stiamo pensando di aggiungere abbonamenti solo per avere accesso alle funzionalità AI. Ma avere un accesso AI a più livelli sarebbe ancora meglio! Grazie per il suggerimento.
Ho trovato un bug (e ho creato una PR per risolverlo) quando ho provato a usare il Discord Persona Bot
Job exception: context must be an instance of BotContext
/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'
Quando sarà supportato DeepSeek? È un LLM sviluppato da un’azienda tecnologica cinese ed è relativamente economico.
Grazie. ![]()
È già supportato, hai provato qualche funzionalità che non ha funzionato?
Recentemente, Elon Musk ha reso open source l’algoritmo di raccomandazione di Twitter. È possibile implementarlo in questa estensione?
Di seguito è riportata l’implementazione di uno script Tampermonkey scritta da un utente di un forum per l’algoritmo di raccomandazione di Twitter, teoricamente utilizzabile su qualsiasi forum Discourse:
// ==UserScript==
// @name *** Algoritmo
// @namespace http://tampermonkey.net/
// @version 0.9
// @description Sistema di raccomandazione intelligente che integra l'algoritmo X e DeepSeek AI, fornendo raccomandazioni personalizzate di argomenti
// @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';
// ========== Parametri di configurazione dell'algoritmo (regolabili per ottimizzazione) ==========
const CONFIG = {
// Parametri di peso dell'algoritmo 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,
// Parametri per il punteggio massimo
X_SCORE_MAX: 45,
AI_SCORE_MAX: 55,
// Parametri di soglia del punteggio
MAX_SCORE: 100,
MIN_DISPLAY_SCORE: 20,
// Parametri di cache
PROFILE_CACHE_TTL: 86400000, // Cache del profilo utente: 24 ore
TOPIC_SCORE_CACHE_TTL: 3600000, // Cache del punteggio AI per singolo argomento: 1 ora
// Parametri API
MAX_TOPICS_PER_BATCH: 30,
MAX_LIKED_TITLES: 15, // Numero di titoli dei argomenti "mi piace"
MAX_REPLIED_TITLES: 10, // Numero di titoli dei argomenti con risposte
MAX_CREATED_TITLES: 5, // Numero di titoli dei argomenti creati
API_RETRY_COUNT: 2,
API_RETRY_DELAY: 1000,
// Dimensioni di analisi del profilo utente
PROFILE_CATEGORIES: ['Interesse tecnologico', 'Preferenze di contenuto', 'Abitudini di interazione', 'Settore professionale', 'Profondità di lettura'],
};
let isRecommendMode = false;
let scoreMap = {};
let allTopicsData = {};
let sortTimeout = null;
let userProfile = "";
let isLoading = false;
let isProcessingNewTopics = false;
// ========== Stili 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;
}
`);
// ========== Funzioni di cache per il punteggio AI degli argomenti ==========
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);
}
}
// ========== Iniezione della scheda Raccomandazioni ==========
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)">Raccomandati</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');
}
});
}
// ========== Indicatore di caricamento ==========
let originalTableContent = null;
function showLoading(show, text = 'Integrazione in corso dell\'algoritmo X e delle raccomandazioni 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;
}
}
}
// ========== Funzione ausiliaria per l'estrazione dei titoli ==========
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') : '';
}
// ========== Ottimizzazione: recupero dettagliato del profilo utente ==========
async function fetchUserProfile(username) {
if (!username) {
console.log('[Raccomandazione] Non effettuato l\'accesso, utilizzo del profilo predefinito');
return "Predefinito";
}
const cacheKey = `user_profile_v9_${username}`;
const cached = GM_getValue(cacheKey);
if (cached && (Date.now() - cached.time < CONFIG.PROFILE_CACHE_TTL)) {
console.log(`[Raccomandazione] Profilo utente (cache):\n${cached.profile}`);
return cached.profile;
}
try {
// Recupero vari tipi di dati sulle azioni utente
const [likedData, repliedData, createdData] = await Promise.all([
fetch(`/user_actions.json?username=${username}&filter=1`).then(r => r.json()).catch(() => null), // Like
fetch(`/user_actions.json?username=${username}&filter=5`).then(r => r.json()).catch(() => null), // Risposte
fetch(`/user_actions.json?username=${username}&filter=4`).then(r => r.json()).catch(() => null), // Creazione
]);
// Estrazione di diversi tipi di titoli
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 "Predefinito";
// Costruzione di un prompt di analisi più dettagliato
const prompt = `Sei un professionista dell\'analisi del comportamento degli utenti. Genera un profilo utente dettagliato basato sui seguenti dati comportamentali:
**Argomenti ai quali l\'utente ha messo mi piace** (riflettono meglio gli interessi):
${likedTitles || 'Nessun dato'}
**Argomenti ai quali l\'utente ha risposto** (riflettono il livello di partecipazione e le aree di competenza):
${repliedTitles || 'Nessun dato'}
**Argomenti creati dall\'utente** (riflettono i punti di attenzione attiva):
${createdTitles || 'Nessun dato'}
Analizza il profilo utente secondo le seguenti dimensioni, riassumendo ciascuna con una frase:
1. **Interessi tecnici**: Quali stack tecnologici, strumenti o piattaforme interessano l\'utente
2. **Preferenze di contenuto**: Preferisce tutorial, discussioni, notizie o contenuti Q&A
3. **Abitudini di interazione**: È un partecipante attivo o un navigatore superficiale
4. **Aree di competenza**: Campi tecnici o settori industriali principali di focus
5. **Profondità di lettura**: Preferisce contenuti introduttivi, avanzati o esperti
Formato di output (senza numerazione, output diretto di 5 righe):
Interessi tecnici: [Risultato dell\'analisi]
Preferenze di contenuto: [Risultato dell\'analisi]
Abitudini di interazione: [Risultato dell\'analisi]
Aree di competenza: [Risultato dell\'analisi]
Profondità di lettura: [Risultato dell\'analisi]`;
const profile = await callDeepSeek(prompt, false);
if (profile && profile.length > 0) {
GM_setValue(cacheKey, { time: Date.now(), profile: profile });
console.log(`[Raccomandazione] Profilo utente:\n${profile}`);
return profile;
}
return "Predefinito";
} catch (e) {
console.error('[Raccomandazione] Errore nel recupero del profilo utente:', e);
return "Predefinito";
}
}
// ========== Avvio del processo di raccomandazione ==========
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('[Raccomandazione] Errore nel processo di raccomandazione:', e);
}
}
// ========== Punteggio batch degli argomenti ==========
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;
});
}
}
// ========== Ottimizzazione: punteggio batch più preciso ==========
async function getAIScoresForBatch(topics) {
const topicList = topics.map((t, idx) =>
`${idx + 1}. [ID:${t.id}] ${t.title}`
).join('\n');
const prompt = `Sei un esperto di raccomandazione di contenuti. Assegna un punteggio ai seguenti argomenti in base al profilo utente.
**Profilo utente**:
${userProfile}
**Argomenti da valutare**:
${topicList}
**Criteri di punteggio** (0-10 punti):
- **9-10 punti**: Alta corrispondenza con il profilo utente, l\'utente è molto probabilmente interessato
- **7-8 punti**: Buona corrispondenza con il profilo utente, l\'utente è probabilmente interessato
- **5-6 punti**: Corrispondenza parziale con il profilo utente, l\'utente potrebbe essere interessato
- **3-4 punti**: Debole correlazione con il profilo utente, l\'utente è probabilmente poco interessato
- **0-2 punti**: Nessuna correlazione o opposta al profilo utente, l\'utente non è interessato
**Fattori da considerare nel punteggio**:
1. Corrispondenza tra il contenuto dell\'argomento e gli interessi tecnici dell\'utente
2. Coerenza tra il tipo di argomento e le preferenze di contenuto dell\'utente
3. Adeguatezza tra la profondità dell\'argomento e la profondità di lettura dell\'utente
4. Rilevanza tra il campo dell\'argomento e le aree di competenza dell\'utente
Restituisci il formato JSON, con le chiavi come ID argomento (solo numeri) e i valori come punteggio (numero da 0 a 10).
Esempio: {"123": 8.5, "456": 5.0}
Restituisci solo JSON, senza altro testo.`;
const scores = await callDeepSeek(prompt, true);
// Pulizia e verifica dei punteggi
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;
}
// ========== Ottimizzazione: calcolo del punteggio finale più robusto ==========
function calculateFinalScores(topics, xScoresRaw, aiScoresMap) {
const xScores = Object.values(xScoresRaw);
if (xScores.length === 0) return;
// Utilizzo della normalizzazione per quantili per evitare l\'influenza di valori estremi
const sortedX = [...xScores].sort((a, b) => a - b);
const p5 = sortedX[Math.floor(sortedX.length * 0.05)]; // Quantile al 5%
const p95 = sortedX[Math.floor(sortedX.length * 0.95)]; // Quantile al 95%
const rangeX = p95 - p5;
topics.forEach(topic => {
const id = topic.id;
let xScoreNormalized;
if (rangeX === 0) {
xScoreNormalized = CONFIG.X_SCORE_MAX / 2;
} else {
// Limitato all\'intervallo da p5 a 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;
// Punteggio finale: Algoritmo X (45%) + Punteggio AI (55%)
const finalScore = xScoreNormalized + aiScoreNormalized;
scoreMap[id] = Math.round(finalScore * 10) / 10;
});
}
// ========== Calcolo del punteggio finale per nuovi argomenti ==========
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;
});
}
// ========== Elaborazione di nuovi argomenti caricati (caricamento a scorrimento) ==========
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('[Raccomandazione] Errore nell\'elaborazione dei nuovi argomenti:', e);
}
isProcessingNewTopics = false;
}
// ========== Recupero dei dati di un singolo argomento ==========
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;
}
}
// ========== Ordinamento e visualizzazione ==========
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);
});
}
// ========== Aggiornamento di tutti i badge delle righe ==========
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);
}
});
}
// ========== Aggiunta del badge del punteggio ==========
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');
}
}
}
// ========== Aggiunta del badge "Calcolo in corso" ==========
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 = '⏳ Calcolo in corso...';
badge.classList.add('calculating');
}
}
// ========== Ottimizzazione: miglioramento della logica di pulizia JSON ==========
function cleanJsonResponse(content) {
if (!content) return content;
let cleaned = content.trim();
// Rimozione dei marcatori dei blocchi di codice Markdown
cleaned = cleaned.replace(/^```(?:json)?\s*/i, '').replace(/```\s*$/i, '');
// Rimozione di eventuali note testuali
const jsonMatch = cleaned.match(/\{[\s\S]*\}/);
if (jsonMatch) {
cleaned = jsonMatch[0];
}
return cleaned.trim();
}
// ========== Chiamata 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('[Raccomandazione] Errore nell\'analisi della risposta 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('[Raccomandazione] Errore nella chiamata 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('[Raccomandazione] Timeout della chiamata API');
if (retryCount < CONFIG.API_RETRY_COUNT) {
setTimeout(() => {
callDeepSeek(prompt, isJson, retryCount + 1).then(resolve);
}, CONFIG.API_RETRY_DELAY);
} else {
resolve(isJson ? {} : "");
}
}
});
});
}
// ========== Ottimizzazione: punteggio Algoritmo X più intelligente ==========
function calculateXScore(topic) {
const now = new Date();
const created = new Date(topic.created_at);
const hoursOld = Math.max(0, (now - created) / (1000 * 60 * 60));
// Punteggio base di interazione
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;
// Calcolo del bonus del tasso di interazione (contenuti con alto tasso di interazione hanno una qualità superiore)
const views = topic.views || 1;
const engagementRate = ((topic.like_count || 0) + (topic.posts_count || 0)) / views;
const engagementBoost = 1 + Math.min(engagementRate * 10, 2); // Bonus massimo 2 volte
let score = (likeScore + replyScore + viewScore) * engagementBoost;
// Bonus per i pinned
if (topic.pinned) {
score *= CONFIG.PINNED_BOOST;
}
// Decadimento temporale (freschezza)
const timeFactor = Math.pow(hoursOld + CONFIG.TIME_DECAY_OFFSET, CONFIG.TIME_DECAY_FACTOR);
return score / timeFactor;
}
// ========== Recupero delle informazioni dell\'utente corrente ==========
function getCurrentUserInfo() {
try {
// Metodo 1: Contenitore 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
};
}
}
// Metodo 2: User.current()
if (window.User?.current?.()?.username) {
const user = window.User.current();
return { username: user.username, id: user.id };
}
// Metodo 3: #current-user
const userLink = document.querySelector('#current-user a[data-user-card]');
if (userLink) {
return { username: userLink.getAttribute('data-user-card') };
}
// Metodo 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 };
}
}
// Metodo 5: d-header
const anyUserCard = document.querySelector('.d-header [data-user-card]');
if (anyUserCard) {
return { username: anyUserCard.getAttribute('data-user-card') };
}
// Metodo 6: dati 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;
}
}
// ========== Ascolto delle modifiche 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 });
}
});
// ========== Avvio dello script ==========
navObserver.observe(document.body, { childList: true, subtree: true });
injectRecommendTab();
console.log('[Sistema di raccomandazione] Caricato - Fusione tra Algoritmo X e DeepSeek AI');
})();
Di seguito l'effetto sul forum Discourse:
Un messaggio è stato diviso in un nuovo argomento: Richiesta di un selettore di date nelle impostazioni di traduzione AI
2 messaggi sono stati spostati in un nuovo argomento: Perché gli argomenti si traducono più velocemente dei messaggi


