Teams集成-会议侧边栏应用开发-实时转写
Teams虽然提供了转写的接口,但是不是实时的,即便使用订阅事件也不是实时的,为了达到实时转写的效果,使用recall.ai的转录和assembly_ai的转写实现。
前提:除Teams会议侧边栏应用开发-会议转写-CSDN博客的基本要求外,还需要修改用户的安全设置及设置Teams 工作账号,参考:Setup Guide (recall.ai)
一、服务端需要实现4个服务端点:
1)开始录音(创建机器人)
/** Send's a Recall Bot to start recording the call*/
server.post('/start-recording', async (req, res) => {const meeting_url = req.body.meetingUrl;try {if (!meeting_url) {return res.status(400).json({ error: 'Missing meetingUrl' });}console.log('recall bot start recording', meeting_url);const url = 'https://us-west-2.recall.ai/api/v1/bot/';const options = {method: 'POST',headers: {accept: 'application/json','content-type': 'application/json',Authorization: `Token ${RECALL_API_KEY}`},body: JSON.stringify({bot_name: 'teams bot',real_time_transcription: {destination_url: 'https://shortly-adapted-akita.ngrok-free.app/transcription?secret=' + WEBHOOK_SECRET,partial_results: false},transcription_options: {provider: 'assembly_ai'},meeting_url: meeting_url})};const response = await fetch(url, options);const bot = await response.json();local_botId = bot.idconsole.log('botId:', local_botId);res.send(200, JSON.stringify({botId: local_botId}));} catch (error) {console.error("start-recoding error:", error);}
});
2)停止录音
/*
* Tells the Recall Bot to stop recording the call
*/
server.post('/stop-recording', async (req, res) => {try {const botId = local_botId;if (!botId) {res.send(400, JSON.stringify({ error: 'Missing botId' }));}await fetch(`https://us-west-2.recall.ai/api/v1/bot/${botId}/leave_call`, {method: 'POST',headers: {'Content-Type': 'application/json',Accept: 'application/json',Authorization: `Token ${RECALL_API_KEY}`},});console.log('recall bot stopped');res.send(200, {})} catch (error) {console.error("stop-recoding error:", error);}
});
3)轮询机器人状态
/*
* Gets the current state of the Recall Bot
*/
server.get('/recording-state', async (req, res) => {try {const botId = local_botId;if (!botId) {res.send(400, JSON.stringify({ error: 'Missing botId' }));}const response = await fetch(`https://us-west-2.recall.ai/api/v1/bot/${botId}`, {method: 'GET',headers: {'Content-Type': 'application/json',Accept: 'application/json',Authorization: `Token ${RECALL_API_KEY}`},});const bot = await response.json();const latestStatus = bot.status_changes.slice(-1)[0].code;console.log('state:', latestStatus);res.send(200, JSON.stringify({state: latestStatus,transcript: db.transcripts[botId] || [],}));} catch (error) {console.error("recoding-state error:", error);}
});
4)接收转写存储在db中(本例使用的是内存)
/** Receives transcription webhooks from the Recall Bot*/
server.post('/transcription', async (req, res) => {try {console.log('transcription webhook received: ', req.body);const { bot_id, transcript } = req.body.data;if (!db.transcripts[bot_id]) {db.transcripts[bot_id] = [];}if (transcript){db.transcripts[bot_id].push(transcript);}res.send(200, JSON.stringify({ success: true }));} catch (error) {console.error("transcription error:", error);}
});
完整的服务端代码:
import restify from "restify";
import send from "send";
import fs from "fs";
import fetch from "node-fetch";
import path from 'path';
import { fileURLToPath } from 'url';
import { storeToken, getToken } from './redisClient.js';
import { WebSocketServer, WebSocket } from 'ws';const __filename = fileURLToPath(import.meta.url);
console.log('__filename: ', __filename);const __dirname = path.dirname(__filename);
console.log('__dirname: ', __dirname);// Create HTTP server.
const server = restify.createServer({key: process.env.SSL_KEY_FILE ? fs.readFileSync(process.env.SSL_KEY_FILE) : undefined,certificate: process.env.SSL_CRT_FILE ? fs.readFileSync(process.env.SSL_CRT_FILE) : undefined,formatters: {"text/html": function (req, res, body) {return body;},},
});server.use(restify.plugins.bodyParser());
server.use(restify.plugins.queryParser());server.get("/static/*",restify.plugins.serveStatic({directory: __dirname,})
);server.listen(process.env.port || process.env.PORT || 3000, function () {console.log(`\n${server.name} listening to ${server.url}`);
});// Adding tabs to our app. This will setup routes to various views
// Setup home page
server.get("/config", (req, res, next) => {send(req, __dirname + "/config/config.html").pipe(res);
});// Setup the static tab
server.get("/meetingTab", (req, res, next) => {send(req, __dirname + "/panel/panel.html").pipe(res);
});//获得用户token
server.get('/auth', (req, res, next) => {res.status(200);res.send(`
<!DOCTYPE html>
<html>
<head><script>// Function to handle the token storageasync function handleToken() {const hash = window.location.hash.substring(1);const hashParams = new URLSearchParams(hash);const access_token = hashParams.get('access_token');console.log('Received hash parameters:', hashParams);if (access_token) {console.log('Access token found:', access_token);localStorage.setItem("access_token", access_token);console.log('Access token stored in localStorage');try {const response = await fetch('https://shortly-adapted-akita.ngrok-free.app/store_user_token', {method: 'POST',headers: {'Content-Type': 'application/json'},body: JSON.stringify({ "user_token" : access_token })});if (response.ok) {console.log('Token stored successfully');} else {console.error('Failed to store token:', response.statusText);}} catch (error) {console.error('Error storing token:', error);}} else {console.log('No access token found');}window.close();}// Call the function to handle the tokenhandleToken();</script>
</head>
<body></body>
</html>`);next();
});// 存储 user_token
server.post('/store_user_token', async (req, res) => {const user_token = req.body.user_token;if (!user_token) {res.status(400);res.send('user_token are required');}try {// Store user tokenawait storeToken('user_token', user_token);console.log('user_token stored in Redis');} catch (err) {console.error('user_token store Error:', err);}res.status(200); res.send('Token stored successfully');
});// 获取 user_token
server.get('/get_user_token', async (req, res) => {try {// Store user tokenconst user_token = await getToken('user_token');console.log('user_token get in Redis');res.send({"user_token": user_token});} catch (err) {console.error('user_token get Error:', err);}
});//应用token
let app_token = '';
const app_token_refresh_interval = 3000 * 1000; // 3000秒const getAppToken = async () => {try {// 构建请求体const requestBody = new URLSearchParams({"grant_type": "client_credentials","client_id": "Azure注册应用ID","client_secret": "Azure注册应用密钥","scope": "https://graph.microsoft.com/.default",}).toString();// 获取app令牌const tokenUrl = `https://login.microsoftonline.com/864168b4-813c-411a-827a-af408f70c665/oauth2/v2.0/token`;const tokenResponse = await fetch(tokenUrl, {method: 'POST',headers: {'Content-Type': 'application/x-www-form-urlencoded',},body: requestBody,});if (!tokenResponse.ok) {const errorData = await tokenResponse.json();throw new Error(errorData.error_description);}const tokenData = await tokenResponse.json();app_token = tokenData.access_token;console.log("app_token received!");} catch (error) {console.error('Error getting app token:', error);}
};// 定期刷新 app_token
setInterval(getAppToken, app_token_refresh_interval);// 确保在服务器启动时获取 app_token
getAppToken();//存储机器人转写信息
const db = {transcripts: {// [bot id]: [transcript]},
};const RECALL_API_KEY = '你的recall.ai的API KEY';
const WEBHOOK_SECRET = '在recall.ai配置webhook端点时的密钥';let local_botId = null;
/** Send's a Recall Bot to start recording the call*/
server.post('/start-recording', async (req, res) => {const meeting_url = req.body.meetingUrl;try {if (!meeting_url) {return res.status(400).json({ error: 'Missing meetingUrl' });}console.log('recall bot start recording', meeting_url);const url = 'https://us-west-2.recall.ai/api/v1/bot/';const options = {method: 'POST',headers: {accept: 'application/json','content-type': 'application/json',Authorization: `Token ${RECALL_API_KEY}`},body: JSON.stringify({bot_name: 'teams bot',real_time_transcription: {destination_url: 'https://shortly-adapted-akita.ngrok-free.app/transcription?secret=' + WEBHOOK_SECRET,partial_results: false},transcription_options: {provider: 'assembly_ai'},meeting_url: meeting_url})};const response = await fetch(url, options);const bot = await response.json();local_botId = bot.idconsole.log('botId:', local_botId);res.send(200, JSON.stringify({botId: local_botId}));} catch (error) {console.error("start-recoding error:", error);}
});/*
* Tells the Recall Bot to stop recording the call
*/
server.post('/stop-recording', async (req, res) => {try {const botId = local_botId;if (!botId) {res.send(400, JSON.stringify({ error: 'Missing botId' }));}await fetch(`https://us-west-2.recall.ai/api/v1/bot/${botId}/leave_call`, {method: 'POST',headers: {'Content-Type': 'application/json',Accept: 'application/json',Authorization: `Token ${RECALL_API_KEY}`},});console.log('recall bot stopped');res.send(200, {})} catch (error) {console.error("stop-recoding error:", error);}
});/*
* Gets the current state of the Recall Bot
*/
server.get('/recording-state', async (req, res) => {try {const botId = local_botId;if (!botId) {res.send(400, JSON.stringify({ error: 'Missing botId' }));}const response = await fetch(`https://us-west-2.recall.ai/api/v1/bot/${botId}`, {method: 'GET',headers: {'Content-Type': 'application/json',Accept: 'application/json',Authorization: `Token ${RECALL_API_KEY}`},});const bot = await response.json();const latestStatus = bot.status_changes.slice(-1)[0].code;console.log('state:', latestStatus);res.send(200, JSON.stringify({state: latestStatus,transcript: db.transcripts[botId] || [],}));} catch (error) {console.error("recoding-state error:", error);}
});
/** Receives transcription webhooks from the Recall Bot*/
server.post('/transcription', async (req, res) => {try {console.log('transcription webhook received: ', req.body);const { bot_id, transcript } = req.body.data;if (!db.transcripts[bot_id]) {db.transcripts[bot_id] = [];}if (transcript){db.transcripts[bot_id].push(transcript);}res.send(200, JSON.stringify({ success: true }));} catch (error) {console.error("transcription error:", error);}
});
二、页面需要实现开始录音和停止录音按钮及转写显示。
完整的页面代码:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Meeting Transcripts</title><script src="https://res.cdn.office.net/teams-js/2.0.0/js/MicrosoftTeams.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js"></script><style>.subtitle {display: flex;align-items: center;margin-bottom: 10px;}.speaker-photo {width: 20px;height: 20px;border-radius: 50%;margin-right: 10px;}button {padding: 5px 10px; /* 调整按钮的 padding 以减小高度 */font-size: 14px; /* 调整按钮的字体大小 */margin-right: 10px;}#transcript {margin-top: 20px;padding: 10px;border: 1px solid #ccc;min-height: 100px;width: 100%;}</style>
</head>
<body><h2>Meeting Transcripts</h2><button id="startRecording">Start Recording</button><button id="stopRecording" disabled>Stop Recording</button><div id="transcripts"></div><script>const clientId = 'Azure注册应用ID';const tenantId = 'Azure注册应用租户ID';const authUrl = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/authorize`;const redirectUri = 'https://shortly-adapted-akita.ngrok-free.app/auth'; // 确保与服务器端一致const scope = 'user.read';let user_token = null;let meetingOrganizerUserId = null;let participants = {}; // 用于存储参会者的信息let userPhotoCache = {}; // 用于缓存用户头像let tokenFetched = false; // 标志变量,用于跟踪是否已经获取了 user_tokenlet displayedTranscriptIds = new Set(); // 用于存储已经显示的转录片段的 IDconst getUserInfo = async (userId, accessToken) => {const graphUrl = `https://graph.microsoft.com/v1.0/users/${userId}`;const response = await fetch(graphUrl, {headers: {'Authorization': `Bearer ${accessToken}`}});if (response.status === 401) {// 如果 token 超期,重新触发 initAuthenticationinitAuthentication();return null;}const userInfo = await response.json();return userInfo;};const getUserPhoto = async (userId, accessToken) => {if (userPhotoCache[userId]) {return userPhotoCache[userId];}const graphUrl = `https://graph.microsoft.com/v1.0/users/${userId}/photo/$value`;const response = await fetch(graphUrl, {headers: {'Authorization': `Bearer ${accessToken}`}});if (!response.ok) {const errorData = await response.json();console.error('Error fetching user photo:', errorData);return null;}const photoBlob = await response.blob();const photoUrl = URL.createObjectURL(photoBlob);userPhotoCache[userId] = photoUrl; // 缓存头像 URLreturn photoUrl;};const getMeetingDetails = async (user_token, joinMeetingId) => {const apiUrl = `https://graph.microsoft.com/v1.0/me/onlineMeetings?$filter=joinMeetingIdSettings/joinMeetingId eq '${joinMeetingId}'`;const response = await fetch(apiUrl, {method: 'GET',headers: {'Authorization': `Bearer ${user_token}`,'Content-Type': 'application/json'}});if (!response.ok) {const errorData = await response.json();throw new Error(`getMeetingDetails status: ${response.status}, message: ${errorData.error}`);}const data = await response.json();return data.value[0];};const getTranscriptContent = async (transcripts) => {const subtitles = [];try {transcripts.forEach(transcript => {const startTime = transcript.words[0].start_time;const endTime = transcript.words[transcript.words.length - 1].end_time;const speaker = transcript.speaker;const content = transcript.words.map(word => word.text).join(' ');subtitles.push({ startTime, endTime, speaker, content, id: transcript.original_transcript_id });});return subtitles;} catch (error) {console.error('getTranscriptContent error:', error);return subtitles;}};const displaySubtitle = async (subtitle, transcriptElement, accessToken) => {const subtitleElement = document.createElement('div');subtitleElement.classList.add('subtitle');// 获取说话者的头像const speakerUserId = participants[subtitle.speaker];const speakerPhotoUrl = speakerUserId ? await getUserPhoto(speakerUserId, accessToken) : 'default-avatar.png';// 创建头像元素const speakerPhotoElement = document.createElement('img');speakerPhotoElement.src = speakerPhotoUrl;speakerPhotoElement.alt = subtitle.speaker;speakerPhotoElement.classList.add('speaker-photo');// 创建输出字符串const output = `${subtitle.startTime} - ${subtitle.endTime}\n${subtitle.content}`;subtitleElement.appendChild(speakerPhotoElement);subtitleElement.appendChild(document.createTextNode(output));transcriptElement.appendChild(subtitleElement);};const init = async () => {try {if (!tokenFetched) {const response = await fetch('https://shortly-adapted-akita.ngrok-free.app/get_user_token');const data = await response.json();if (response.ok) {user_token = data.user_token;console.log('user token retrieved:', user_token);tokenFetched = true;} else {console.error('Failed to get token:', response.statusText);return;}}console.log('User Token:', user_token);const joinMeetingId = '45756456529'; // 替换为你要查询的 joinMeetingIdtry {const meetingDetails = await getMeetingDetails(user_token, joinMeetingId);console.log('Meeting Details:', meetingDetails);meetingOrganizerUserId = meetingDetails.participants.organizer.identity.user.id;const meetingId = meetingDetails.id; // 获取会议 IDconsole.log('Organizer User ID:', meetingOrganizerUserId);console.log('Meeting ID:', meetingId);// 获取主持人信息const organizerInfo = await getUserInfo(meetingOrganizerUserId, user_token);const organizerDisplayName = organizerInfo.displayName;participants[organizerDisplayName] = meetingOrganizerUserId;// 获取参会者信息const attendeesPromises = meetingDetails.participants.attendees.map(async attendee => {const userId = attendee.identity.user.id;const userInfo = await getUserInfo(userId, user_token);const displayName = userInfo.displayName;participants[displayName] = userId;});await Promise.all(attendeesPromises);} catch (error) {console.error('Error fetching meeting details:', error);}} catch (error) {console.error('Error getting token:', error);}};const initAuthentication = () => {microsoftTeams.app.initialize();microsoftTeams.authentication.authenticate({url: `${authUrl}?client_id=${clientId}&response_type=token&redirect_uri=${encodeURIComponent(redirectUri)}&scope=${encodeURIComponent(scope)}`,width: 600,height: 535,successCallback: async (result) => {console.log('Authentication success:', result);},failureCallback: (error) => {console.error('Authentication failed:', error);}});};// 设置较长的轮询时间来防止 user_token 的超期setInterval(initAuthentication, 3000000); // 每3000秒(50分钟)轮询一次initAuthentication();init();// 录音控制功能const startRecordingButton = document.getElementById('startRecording');const stopRecordingButton = document.getElementById('stopRecording');const transcriptDiv = document.getElementById('transcript');let recordingInterval;// Function to start recordingasync function startRecording() {const meetingUrl = await getMeetingUrl();if (!meetingUrl) return;try {const response = await fetch('/start-recording', {method: 'POST',headers: {'Content-Type': 'application/json',},body: JSON.stringify({ meetingUrl }),});if (response.ok) {const data = await response.json();console.log('Bot started:', data);startRecordingButton.disabled = true;stopRecordingButton.disabled = false;startPolling();} else {console.error('Failed to start recording:', response.statusText);}} catch (error) {console.error('Error starting recording:', error);}}// Function to stop recordingasync function stopRecording() {try {const response = await fetch('/stop-recording', {method: 'POST',});if (response.ok) {console.log('Bot stopped');startRecordingButton.disabled = false;stopRecordingButton.disabled = true;clearInterval(recordingInterval);} else {console.error('Failed to stop recording:', response.statusText);}} catch (error) {console.error('Error stopping recording:', error);}}// Function to poll the recording stateasync function pollRecordingState() {try {const response = await fetch('/recording-state');if (response.ok) {const data = await response.json();updateUI(data);} else {console.error('Failed to get recording state:', response.statusText);}} catch (error) {console.error('Error polling recording state:', error);}}// Function to update the UI based on the recording statefunction updateUI(data) {const { state, transcript } = data;console.log(state, transcript);// Update the transcript displayconst transcriptsContainer = document.getElementById('transcripts');const transcriptElement = document.createDocumentFragment(); // 使用 DocumentFragment 优化 DOM 操作if (transcript.length > 0) {getTranscriptContent(transcript).then(subtitles => {subtitles.forEach(subtitle => {if (!displayedTranscriptIds.has(subtitle.id)) {displaySubtitle(subtitle, transcriptElement, user_token);displayedTranscriptIds.add(subtitle.id); // 添加到已显示的转录片段 ID 集合中}});}).catch(error => {const errorElement = document.createElement('div');errorElement.innerHTML = `<strong>${error}</strong>`;transcriptElement.appendChild(errorElement);}).finally(() => {transcriptsContainer.appendChild(transcriptElement); // 一次性插入 DOM});}// Update button states based on the recording stateif (state === 'recording') {startRecordingButton.disabled = true;stopRecordingButton.disabled = false;} else if (state === 'stopped') {startRecordingButton.disabled = false;stopRecordingButton.disabled = true;}}// Function to start polling the recording state every 2 secondsfunction startPolling() {recordingInterval = setInterval(pollRecordingState, 2000);}// Event listeners for buttonsstartRecordingButton.addEventListener('click', startRecording);stopRecordingButton.addEventListener('click', stopRecording);// Function to get the meeting URL from the meeting detailsasync function getMeetingUrl() {const joinMeetingId = '45756456529'; // 替换为你要查询的 joinMeetingIdtry {const meetingDetails = await getMeetingDetails(user_token, joinMeetingId);return meetingDetails.joinWebUrl;} catch (error) {console.error('Error fetching meeting URL:', error);return null;}}</script>
</body>
</html>
最终效果:
相关文章:

Teams集成-会议侧边栏应用开发-实时转写
Teams虽然提供了转写的接口,但是不是实时的,即便使用订阅事件也不是实时的,为了达到实时转写的效果,使用recall.ai的转录和assembly_ai的转写实现。 前提:除Teams会议侧边栏应用开发-会议转写-CSDN博客的基本要求外&a…...

归并排序,外排序,计数排序(非比较排序)
归并排序:(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序…...

使用离火插件yoloV8数据标注,模型训练
1. 启动 2.相关配置 2.1 data.yaml path: D:/yolo-tool/yaunshen-yolov8/YOLOv8ys/YOLOv8-CUDA10.2/1/datasets/ceshi001 train: images val: images names: [蔡徐坤,篮球] 2.2 cfg.yaml # Ultralytics YOLOv8, GPL-3.0 license # Default training settings and hyp…...

JavaScript 学习
一、输出 为方便调试可以输出内容,但是用户是看不到的。要在开发者模式中看。 console . log ( "Hello" ); 二、外部文件引用 可以直接在html中写JS <head> <meta charset"utf-8"> <script> console.log("he…...

【算法】分治:归并之 912.排序数组(medium)
系列专栏 双指针 模拟算法 分治思想 目录 1、题目链接 2、题目介绍 3、解法 解决方案选择 解题步骤 4、代码 1、题目链接 912. 排序数组 - 力扣(LeetCode) 2、题目介绍 给你一个整数数组 nums,请你将该数组升序排列。 你必须在 …...

Cocos 3.8.3 实现外描边效果(逃课玩法)
本来想着用Cocos 的Shader Graph照搬Unity的思路来加外描边,发现不行,然后我就想弄两个物体不就行了吗,一个是放大的版本,再放大的版本上加一个材质,这个材质面剔除选择前面的面剔除就行了,果不其然还真行。…...

著名建筑物检测与识别系统源码分享
著名建筑物检测与识别检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Comp…...

使用php生成图片
可以用这方法生成图片 水印 字体可以在资源绑定下载,如果字体路径不对,则不会输出文字图片 public function generateImage($text,$id) { header("Cache-Control: no-cache, must-revalidate"); header("Expires: Mon, 26 Jul 1997 05:0…...

C++ 数据类型分类
在C中,数据类型可以大致分为内置类型(Built-in Types)、标准库类型(Standard Library Types)和自定义类型(User-Defined Types)三大类。 内置类型(Built-in Types) 内置…...

java安装更新jdk11后设置环境JAVA_HOME
背景,已经安装成功,但是环境还是java1.8 java -version openjdk version "11.0.23" 2024-04-16 LTS OpenJDK Runtime Environment (Red_Hat-11.0.23.0.9-2.el7_9) (build 11.0.23+9-LTS) OpenJDK 64-Bit Server VM (Red_Hat-11.0.23.0.9-2.el7_9) (build 11.0.…...

Java.动态代理
1.创建一个接口 package Mydynamicproxy1;public interface Star {public abstract String sing(String str);public abstract void dance(String str); }2.创建一个BigStar类,要实现Star这个接口 package Mydynamicproxy1;public class BigStar implements Star{…...

SpringBoot自定义异常
前言 在前后端开发中,后端接口返回的数据都是JSON格式的,但是后端可能会出现一些可以未知从异常,在后端抛出这些异常的时候,也需要返回相同格式的JSON数据,这时候就需要我们设置全局异常处理器。在后端开发中…...

华为源NAT技术与目的NAT技术
1)源NAT对报文源地址进行转换,分为NAT NO-PAT,NAPT,EASY-IP,三元组NAT; (1)NAT NO-PAT原理: no-port address translation:非端口地址转换:只转换地址,不转换端口&…...

人工智能与机器学习原理精解【25】
文章目录 正则化概述一、正则化的种类二、正则化的定义三、正则化的计算四、正则化的性质五、正则化的例子 公式与计算一、正则化的种类Dropout正则化一、基本思想二、实现方法三、作用机制四、使用注意事项五、总结Dropout正则化的例子和公式。一、Dropout正则化的例子二、Dro…...

一篇文章讲清楚synchronized关键字的作用及原理
概述 在应用Sychronized关键字时需要把握如下注意点: 一把锁只能同时被一个线程获取,没有获得锁的线程只能等待; 每个实例都对应有自己的一把锁(this),不同实例之间互不影响;例外:锁对象是*.class以及synchronized修…...

深度学习模型之BERT的24个小模型源码与预训练紧凑模型的重要性
原始信息 论文: Well-Read Students Learn Better: On the Importance of Pre-training Compact Models作者:Iulia Turc, Ming-Wei Chang, Kenton Lee, Kristina Toutanova地址:arxiv.org/pdf/1908.08…中文:阅读良好的学生学得更…...

【HarmonyOS】深入理解@Observed装饰器和@ObjectLink装饰器:嵌套类对象属性变化
【HarmonyOS】深入理解Observed装饰器和ObjectLink装饰器:嵌套类对象属性变化 前言 之前就Observed和ObjectLink写过一篇讲解博客【HarmonyOS】 多层嵌套对象通过ObjectLink和Observed实现渲染更新处理! 其中就Observe监听类的使用,Object…...

Java笔试面试题AI答之设计模式(1)
文章目录 1. 简述什么是设计模式 ?2. 叙述常见Java设计模式分类 ?3. Java 设计模式的六大原则 ?4. 简述对 MVC 的理解, MVC 有什么优缺点?MVC 的三个核心部分:MVC 的优点:MVC 的缺点:…...

java调用opencv部署到centos7
1、官网下载opencv https://opencv.org/releases/ 2、下载opencv并解压 unzip opencv-3.4.7.zip cd opencv-3.4.7 mkdir build cd build/ 3、安装cmake yum remove cmake -y ; yum install -y gcc gcc-c make automake openssl openssl-devel wget https://cmake.org/files/…...

【python qdrant 向量数据库 完整示例代码】
测试一下python版本的dqrant向量数据库的效果,完整代码如下: 安装库 !pip install qdrant-client>1.1.1 !pip install -U sentence-transformers导入 from qdrant_client import models, QdrantClient from sentence_transformers import SentenceT…...

初识C语言(三)
感兴趣的朋友们可以留个关注,我们共同交流,相互促进学习。 文章目录 前言 八、函数 九、数组 (1)数组的定义 (2)数组的下标和使用 十、操作符 (1)算数操作符 (2ÿ…...

用通义灵码如何快速合理解决遗留代码问题?
本文首先介绍了遗留代码的概念,并对遗留代码进行了分类。针对不同类型的遗留代码,提供了相应的处理策略。此外,本文重点介绍了通义灵码在维护遗留代码过程中能提供哪些支持。 什么是遗留代码 与过时技术相关的代码: 与不再受支持的…...

新书推荐——《Python贝叶斯深度学习》
在过去的十年中,机器学习领域取得了长足的进步,并因此激发了公众的想象力。但我们必须记住,尽管这些算法令人印象深刻,但它们并非完美无缺。本书旨在通过平实的语言介绍如何在深度学习中利用贝叶斯推理,帮助读者掌握开…...

数据结构-3.1.栈的基本概念
一.栈的定义: 栈和线性表的区别:栈只能在表尾一端进行插入或者删除的操作,而线性表可以在任意一个地方进行插入或者删除 二.有关栈的关键术语: 三.栈的基本操作: 1.回顾线性表的基本操作: 2.栈的基本操作&…...

关于 NLP 应用方向与深度训练的核心流程
文章目录 主流应用方向核心流程(5步)1.选定语言模型结构2.收集标注数据3.forward 正向传播4.backward 反向传播5.使用模型预测真实场景 主流应用方向 文本分类文本匹配序列标注生成式任务 核心流程(5步) 基本流程实现的先后顺序…...

linux如何启用ipv6随机地址
简介 在 IPv6 中,临时随机地址(Temporary IPv6 Address)是一种为了提高隐私和安全而设计的功能。通常,默认的 IPv6 地址是基于设备的 MAC 地址生成的,容易导致跟踪和识别设备。启用临时 IPv6 地址可以避免这个问题&am…...

探索 Android DataBinding:实现数据与视图的完美融合
在 Android 开发中,数据与视图的交互一直是一个关键的问题。为了更好地实现数据的展示和更新,Google 推出了 DataBinding 库,它为开发者提供了一种简洁、高效的方式来处理数据与视图之间的绑定关系,大大提高了开发效率和代码的可读…...

Java 编码系列:线程基础与最佳实践
引言 在多任务处理和并发编程中,线程是不可或缺的一部分。Java 提供了丰富的线程管理和并发控制机制,使得开发者可以轻松地实现多线程应用。本文将深入探讨 Java 线程的基础知识,包括 Thread 类、Runnable 接口、Callable 接口以及线程的生命…...

《深度学习》—— ResNet 残差神经网络
文章目录 一、什么是ResNet?二、残差结构(Residual Structure)三、Batch Normalization(BN----批归一化) 一、什么是ResNet? ResNet 网络是在 2015年 由微软实验室中的何凯明等几位大神提出,斩获…...

针对考研的C语言学习(定制化快速掌握重点3)
1.数组常见错误 数组传参实际传递的是数组的起始地址,若在函数中改变数组内容,数组本身也会发生变化 #include<stdio.h> void change_ch(char* str) {str[0] H; } int main() {char ch[] "hello";change_ch(ch);printf("%s\n&q…...