This commit is contained in:
Nan 2025-04-30 14:06:10 +08:00
parent e9fa30b0fe
commit 0db6b6be56
19 changed files with 2183 additions and 275 deletions

View File

@ -0,0 +1,179 @@
.voiceRecognitionContainer {
display: flex;
flex-direction: column;
width: 100%;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background-color: #1e1e1e;
border-radius: 10px;
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.3);
color: #ffffff;
}
.title {
text-align: center;
margin-bottom: 20px;
font-size: 24px;
}
.statusContainer {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 20px;
}
.statusIndicator {
display: flex;
align-items: center;
background-color: rgba(0, 0, 0, 0.5);
padding: 8px 16px;
border-radius: 20px;
margin-bottom: 10px;
}
.statusDot {
width: 12px;
height: 12px;
border-radius: 50%;
margin-right: 10px;
&.idle {
background-color: #888888;
}
&.recording {
background-color: #ff9800;
animation: pulse 1.5s infinite;
}
&.training {
background-color: #2196f3;
animation: pulse 1.5s infinite;
}
&.recognizing {
background-color: #9c27b0;
animation: pulse 1.5s infinite;
}
&.trained {
background-color: #4caf50;
}
&.matched {
background-color: #4caf50;
}
&.not_matched {
background-color: #f44336;
}
&.error {
background-color: #f44336;
}
}
.statusText {
font-size: 14px;
}
.message {
font-size: 16px;
text-align: center;
margin: 0;
}
.visualizerContainer {
width: 100%;
height: 150px;
margin-bottom: 20px;
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 5px;
overflow: hidden;
}
.controlsContainer {
display: flex;
justify-content: space-between;
margin-bottom: 20px;
@media (max-width: 600px) {
flex-direction: column;
}
}
.trainingControls,
.recognitionControls {
display: flex;
flex-direction: column;
width: 48%;
@media (max-width: 600px) {
width: 100%;
margin-bottom: 20px;
}
h3 {
margin-bottom: 10px;
font-size: 18px;
}
}
.button {
padding: 10px 15px;
margin-bottom: 10px;
border: none;
border-radius: 5px;
background-color: #2196f3;
color: white;
font-size: 14px;
cursor: pointer;
transition: background-color 0.3s;
&:hover:not(:disabled) {
background-color: #1976d2;
}
&:disabled {
background-color: #cccccc;
color: #666666;
cursor: not-allowed;
}
}
.resultContainer {
margin-top: 20px;
padding: 15px;
background-color: rgba(0, 0, 0, 0.3);
border-radius: 5px;
}
.scoreBar {
width: 100%;
height: 20px;
background-color: rgba(255, 255, 255, 0.1);
border-radius: 10px;
overflow: hidden;
margin-bottom: 10px;
}
.scoreIndicator {
height: 100%;
background: linear-gradient(to right, #f44336, #ffeb3b, #4caf50);
border-radius: 10px;
transition: width 0.5s ease-in-out;
}
.scoreValue {
text-align: center;
font-size: 16px;
font-weight: bold;
}
@keyframes pulse {
0% { box-shadow: 0 0 0 0 rgba(255, 255, 255, 0.7); }
70% { box-shadow: 0 0 0 10px rgba(255, 255, 255, 0); }
100% { box-shadow: 0 0 0 0 rgba(255, 255, 255, 0); }
}

View File

@ -0,0 +1,506 @@
import React, { useState, useEffect, useRef } from "react";
import * as tf from "@tensorflow/tfjs";
import { VoicePrint } from "./voice-print/voice-print";
import styles from "./TensorFlow.module.scss";
// 声纹识别状态
enum VoiceRecognitionStatus {
IDLE = "空闲",
RECORDING = "录制中",
TRAINING = "训练中",
RECOGNIZING = "识别中",
TRAINED = "已训练",
MATCHED = "声纹匹配",
NOT_MATCHED = "声纹不匹配",
ERROR = "错误",
}
// 声纹特征提取参数
const SAMPLE_RATE = 16000; // 采样率
const FFT_SIZE = 1024; // FFT大小
const MEL_BINS = 40; // Mel滤波器数量
const FRAME_LENGTH = 25; // 帧长度(ms)
const FRAME_STEP = 10; // 帧步长(ms)
const FEATURE_LENGTH = 100; // 特征序列长度
const TensorFlow: React.FC = () => {
// 状态管理
const [status, setStatus] = useState<VoiceRecognitionStatus>(
VoiceRecognitionStatus.IDLE,
);
const [message, setMessage] = useState<string>("");
const [isRecording, setIsRecording] = useState<boolean>(false);
const [isTrained, setIsTrained] = useState<boolean>(false);
const [matchScore, setMatchScore] = useState<number>(0);
const [frequencies, setFrequencies] = useState<Uint8Array | undefined>(
undefined,
);
// 引用
const audioContextRef = useRef<AudioContext | null>(null);
const analyserRef = useRef<AnalyserNode | null>(null);
const mediaStreamRef = useRef<MediaStream | null>(null);
const recordedChunksRef = useRef<Float32Array[]>([]);
const modelRef = useRef<tf.LayersModel | null>(null);
const voiceprintRef = useRef<Float32Array | null>(null);
const animationFrameRef = useRef<number | null>(null);
// 初始化
useEffect(() => {
// 检查是否有保存的声纹模型
const savedVoiceprint = localStorage.getItem("userVoiceprint");
if (savedVoiceprint) {
try {
voiceprintRef.current = new Float32Array(JSON.parse(savedVoiceprint));
setIsTrained(true);
setStatus(VoiceRecognitionStatus.TRAINED);
setMessage("已加载保存的声纹模型");
} catch (error) {
console.error("加载保存的声纹模型失败:", error);
}
}
// 加载TensorFlow模型
loadModel();
return () => {
stopRecording();
if (animationFrameRef.current) {
cancelAnimationFrame(animationFrameRef.current);
}
};
}, []);
// 加载声纹识别模型
const loadModel = async () => {
try {
// 创建简单的声纹识别模型
const model = tf.sequential();
// 添加卷积层处理音频特征
model.add(
tf.layers.conv1d({
inputShape: [FEATURE_LENGTH, MEL_BINS],
filters: 32,
kernelSize: 3,
activation: "relu",
}),
);
model.add(tf.layers.maxPooling1d({ poolSize: 2 }));
model.add(
tf.layers.conv1d({
filters: 64,
kernelSize: 3,
activation: "relu",
}),
);
model.add(tf.layers.maxPooling1d({ poolSize: 2 }));
model.add(tf.layers.flatten());
// 添加全连接层
model.add(tf.layers.dense({ units: 128, activation: "relu" }));
model.add(tf.layers.dropout({ rate: 0.5 }));
// 输出层 - 声纹特征向量
model.add(tf.layers.dense({ units: 64, activation: "linear" }));
// 编译模型
model.compile({
optimizer: "adam",
loss: "meanSquaredError",
});
modelRef.current = model;
console.log("声纹识别模型已加载");
} catch (error) {
console.error("加载模型失败:", error);
setStatus(VoiceRecognitionStatus.ERROR);
setMessage("加载模型失败");
}
};
// 开始录音
const startRecording = async (isTraining: boolean = false) => {
try {
if (isRecording) return;
// 重置录音数据
recordedChunksRef.current = [];
// 请求麦克风权限
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
mediaStreamRef.current = stream;
// 创建音频上下文
const audioContext = new (window.AudioContext ||
(window as any).webkitAudioContext)();
audioContextRef.current = audioContext;
// 创建分析器节点用于可视化
const analyser = audioContext.createAnalyser();
analyser.fftSize = FFT_SIZE;
analyserRef.current = analyser;
// 创建音频源
const source = audioContext.createMediaStreamSource(stream);
source.connect(analyser);
// 创建处理器节点
const processor = audioContext.createScriptProcessor(4096, 1, 1);
// 处理音频数据
processor.onaudioprocess = (e) => {
const inputData = e.inputBuffer.getChannelData(0);
recordedChunksRef.current.push(new Float32Array(inputData));
};
// 连接节点
analyser.connect(processor);
processor.connect(audioContext.destination);
// 更新状态
setIsRecording(true);
setStatus(
isTraining
? VoiceRecognitionStatus.RECORDING
: VoiceRecognitionStatus.RECOGNIZING,
);
setMessage(
isTraining ? "请说话3-5秒钟用于训练..." : "请说话进行声纹识别...",
);
// 开始频谱可视化
startVisualization();
// 设置自动停止录音训练模式下5秒后自动停止
if (isTraining) {
setTimeout(() => {
stopRecording();
trainVoiceprint();
}, 5000);
}
} catch (error) {
console.error("开始录音失败:", error);
setStatus(VoiceRecognitionStatus.ERROR);
setMessage("无法访问麦克风,请检查权限");
}
};
// 停止录音
const stopRecording = () => {
if (!isRecording) return;
// 停止所有音频流
if (mediaStreamRef.current) {
mediaStreamRef.current.getTracks().forEach((track) => track.stop());
mediaStreamRef.current = null;
}
// 关闭音频上下文
if (audioContextRef.current) {
audioContextRef.current.close();
audioContextRef.current = null;
}
// 停止可视化
if (animationFrameRef.current) {
cancelAnimationFrame(animationFrameRef.current);
animationFrameRef.current = null;
}
setIsRecording(false);
setFrequencies(undefined);
};
// 开始频谱可视化
const startVisualization = () => {
const analyser = analyserRef.current;
if (!analyser) return;
const bufferLength = analyser.frequencyBinCount;
const dataArray = new Uint8Array(bufferLength);
const updateVisualization = () => {
if (!analyser) return;
analyser.getByteFrequencyData(dataArray);
setFrequencies(dataArray);
animationFrameRef.current = requestAnimationFrame(updateVisualization);
};
updateVisualization();
};
// 提取音频特征
const extractFeatures = async (
audioData: Float32Array[],
): Promise<tf.Tensor | null> => {
try {
// 合并所有音频块
const mergedData = new Float32Array(
audioData.reduce((acc, chunk) => acc + chunk.length, 0),
);
let offset = 0;
for (const chunk of audioData) {
mergedData.set(chunk, offset);
offset += chunk.length;
}
// 转换为张量
const audioTensor = tf.tensor1d(mergedData);
// 计算梅尔频谱图 (简化版)
// 在实际应用中,这里应该使用更复杂的信号处理方法
// 如MFCC (Mel-frequency cepstral coefficients)
const frameLength = Math.round((SAMPLE_RATE * FRAME_LENGTH) / 1000);
const frameStep = Math.round((SAMPLE_RATE * FRAME_STEP) / 1000);
// 使用短时傅里叶变换提取特征
// 注意这是简化版实际应用中应使用专业的DSP库
const frames = [];
for (let i = 0; i + frameLength <= mergedData.length; i += frameStep) {
const frame = mergedData.slice(i, i + frameLength);
frames.push(Array.from(frame));
}
// 限制帧数
const limitedFrames = frames.slice(0, FEATURE_LENGTH);
// 如果帧数不足,用零填充
while (limitedFrames.length < FEATURE_LENGTH) {
limitedFrames.push(new Array(frameLength).fill(0));
}
// 创建特征张量
const featureTensor = tf.tensor(limitedFrames);
// 简化的梅尔频谱计算
// 在实际应用中应使用更准确的方法
const melSpectrogram = tf.tidy(() => {
// 应用FFT (简化)
const fftMag = featureTensor.abs();
// 降维到MEL_BINS
const reshaped = fftMag.reshape([FEATURE_LENGTH, -1]);
const melFeatures = reshaped.slice([0, 0], [FEATURE_LENGTH, MEL_BINS]);
// 归一化
const normalized = melFeatures.div(tf.scalar(255.0));
return normalized.expandDims(0); // 添加批次维度
});
return melSpectrogram;
} catch (error) {
console.error("特征提取失败:", error);
return null;
}
};
// 训练声纹模型
const trainVoiceprint = async () => {
if (recordedChunksRef.current.length === 0 || !modelRef.current) {
setStatus(VoiceRecognitionStatus.ERROR);
setMessage("没有录音数据或模型未加载");
return;
}
setStatus(VoiceRecognitionStatus.TRAINING);
setMessage("正在训练声纹模型...");
try {
// 提取特征
const features = await extractFeatures(recordedChunksRef.current);
if (!features) throw new Error("特征提取失败");
// 使用模型提取声纹特征向量
const voiceprint = tf.tidy(() => {
// 前向传播获取声纹特征
const prediction = modelRef.current!.predict(features) as tf.Tensor;
// 归一化特征向量
return tf.div(prediction, tf.norm(prediction));
});
// 保存声纹特征
const voiceprintData = await voiceprint.data();
voiceprintRef.current = new Float32Array(voiceprintData);
// 保存到localStorage
localStorage.setItem(
"userVoiceprint",
JSON.stringify(Array.from(voiceprintData)),
);
setIsTrained(true);
setStatus(VoiceRecognitionStatus.TRAINED);
setMessage("声纹模型训练完成并已保存");
// 清理
voiceprint.dispose();
features.dispose();
} catch (error) {
console.error("训练失败:", error);
setStatus(VoiceRecognitionStatus.ERROR);
setMessage("声纹训练失败");
}
};
// 识别声纹
const recognizeVoice = async () => {
if (!isTrained || !voiceprintRef.current) {
setStatus(VoiceRecognitionStatus.ERROR);
setMessage("请先训练声纹模型");
return;
}
if (recordedChunksRef.current.length === 0 || !modelRef.current) {
setStatus(VoiceRecognitionStatus.ERROR);
setMessage("没有录音数据或模型未加载");
return;
}
try {
// 提取特征
const features = await extractFeatures(recordedChunksRef.current);
if (!features) throw new Error("特征提取失败");
// 使用模型提取声纹特征向量
const currentVoiceprint = tf.tidy(() => {
// 前向传播获取声纹特征
const prediction = modelRef.current!.predict(features) as tf.Tensor;
// 归一化特征向量
return tf.div(prediction, tf.norm(prediction));
});
// 计算与保存的声纹的余弦相似度
const similarity = tf.tidy(() => {
const savedVoiceprint = tf.tensor1d(voiceprintRef.current!);
// 计算点积
const dotProduct = tf.sum(
tf.mul(currentVoiceprint.reshape([-1]), savedVoiceprint),
);
return dotProduct;
});
// 获取相似度分数 (范围从-1到1越接近1表示越相似)
const similarityScore = await similarity.data();
const score = similarityScore[0];
setMatchScore(score);
// 判断是否为同一人 (阈值可调整)
const threshold = 0.7;
const isMatch = score > threshold;
setStatus(
isMatch
? VoiceRecognitionStatus.MATCHED
: VoiceRecognitionStatus.NOT_MATCHED,
);
setMessage(
isMatch
? `声纹匹配成功!相似度: ${(score * 100).toFixed(2)}%`
: `声纹不匹配。相似度: ${(score * 100).toFixed(2)}%`,
);
// 清理
currentVoiceprint.dispose();
features.dispose();
similarity.dispose();
} catch (error) {
console.error("识别失败:", error);
setStatus(VoiceRecognitionStatus.ERROR);
setMessage("声纹识别失败");
}
};
// 清除训练数据
const clearTrainedData = () => {
localStorage.removeItem("userVoiceprint");
voiceprintRef.current = null;
setIsTrained(false);
setStatus(VoiceRecognitionStatus.IDLE);
setMessage("声纹数据已清除");
};
return (
<div className={styles.voiceRecognitionContainer}>
<h2 className={styles.title}></h2>
<div className={styles.statusContainer}>
<div className={styles.statusIndicator}>
<div
className={`${styles.statusDot} ${styles[status.toLowerCase()]}`}
></div>
<span className={styles.statusText}>{status}</span>
</div>
<p className={styles.message}>{message}</p>
</div>
<div className={styles.visualizerContainer}>
<VoicePrint frequencies={frequencies} isActive={isRecording} />
</div>
<div className={styles.controlsContainer}>
<div className={styles.trainingControls}>
<h3></h3>
<button
className={styles.button}
onClick={() => startRecording(true)}
disabled={isRecording}
>
</button>
<button
className={styles.button}
onClick={clearTrainedData}
disabled={!isTrained}
>
</button>
</div>
<div className={styles.recognitionControls}>
<h3></h3>
<button
className={styles.button}
onClick={() => startRecording(false)}
disabled={isRecording || !isTrained}
>
</button>
<button
className={styles.button}
onClick={() => {
stopRecording();
recognizeVoice();
}}
disabled={!isRecording}
>
</button>
</div>
</div>
{status === VoiceRecognitionStatus.MATCHED ||
status === VoiceRecognitionStatus.NOT_MATCHED ? (
<div className={styles.resultContainer}>
<div className={styles.scoreBar}>
<div
className={styles.scoreIndicator}
style={{ width: `${Math.max(0, matchScore * 100)}%` }}
></div>
</div>
<div className={styles.scoreValue}>
: {(matchScore * 100).toFixed(2)}%
</div>
</div>
) : null}
</div>
);
};
export default TensorFlow;

View File

@ -0,0 +1,64 @@
.container {
position: relative;
padding: 15px;
border-bottom: 1px solid var(--gray);
}
.avatarContainer {
display: flex;
align-items: center;
cursor: pointer;
}
.avatar {
width: 40px;
height: 40px;
border-radius: 50%;
object-fit: cover;
margin-right: 10px;
border: 2px solid var(--primary);
}
.userInfo {
flex: 1;
overflow: hidden;
}
.nickname {
font-size: 16px;
font-weight: bold;
color: var(--black);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.userId {
font-size: 12px;
color: var(--black-50);
margin-top: 2px;
}
.menu {
position: absolute;
top: 100%;
left: 0;
width: 120px;
background-color: var(--white);
border-radius: 4px;
box-shadow: var(--card-shadow);
z-index: 1000;
overflow: hidden;
}
.menuItem {
padding: 10px 15px;
font-size: 14px;
color: var(--black);
cursor: pointer;
transition: background-color 0.3s;
&:hover {
background-color: var(--gray);
}
}

View File

@ -0,0 +1,113 @@
import React, { useState, useRef, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import styles from "./WechatAuthor.module.scss";
import { Path } from "../constant";
import { useAccessStore } from "../store";
import { safeLocalStorage } from "../utils";
import { showConfirm } from "./ui-lib";
interface WechatUserInfo {
id: string;
nickname: string;
avatar: string;
accessToken: string;
}
export function WechatAuthor() {
const navigate = useNavigate();
const accessStore = useAccessStore();
const storage = safeLocalStorage();
const [userInfo, setUserInfo] = useState<WechatUserInfo | null>(null);
const [showMenu, setShowMenu] = useState(false);
const menuRef = useRef<HTMLDivElement>(null);
// 加载用户信息
useEffect(() => {
const userInfoStr = storage.getItem("wechat_user_info");
if (userInfoStr) {
try {
const parsedInfo = JSON.parse(userInfoStr);
setUserInfo(parsedInfo);
} catch (e) {
console.error("Failed to parse user info", e);
}
}
}, []);
// 点击外部关闭菜单
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (menuRef.current && !menuRef.current.contains(event.target as Node)) {
setShowMenu(false);
}
};
document.addEventListener("mousedown", handleClickOutside);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, []);
// 处理登出
const handleLogout = async () => {
const confirmed = await showConfirm("确定要退出登录吗?");
if (confirmed) {
// 清除登录信息
storage.removeItem("wechat_user_info");
// 更新访问状态
accessStore.update((access) => {
access.accessToken = "";
access.wechatLoggedIn = false;
});
// 跳转到登录页
navigate(Path.Home);
}
setShowMenu(false);
};
// 如果没有用户信息,显示登录按钮
if (!accessStore.wechatLoggedIn) {
return (
<div className={styles.container}>
<div
className={styles.loginPrompt}
onClick={() => navigate(Path.Login)}
>
</div>
</div>
);
}
return (
<div className={styles.container}>
<div
className={styles.avatarContainer}
onClick={() => setShowMenu(true)}
onContextMenu={(e) => {
e.preventDefault();
setShowMenu(true);
}}
>
<img
src={userInfo?.avatar}
alt={userInfo?.nickname}
className={styles.avatar}
/>
<div className={styles.userInfo}>
<div className={styles.nickname}>{userInfo?.nickname}</div>
<div className={styles.userId}>ID: {userInfo?.id}</div>
</div>
</div>
{showMenu && (
<div className={styles.menu} ref={menuRef}>
<div className={styles.menuItem} onClick={handleLogout}>
退
</div>
</div>
)}
</div>
);
}

View File

@ -0,0 +1,195 @@
.container {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background-color: var(--gray);
}
.loginCard {
width: 400px;
background-color: var(--white);
border-radius: 10px;
box-shadow: var(--card-shadow);
padding: 30px;
display: flex;
flex-direction: column;
align-items: center;
}
.header {
text-align: center;
margin-bottom: 30px;
h2 {
font-size: 24px;
margin-bottom: 10px;
color: var(--primary);
}
.subtitle {
font-size: 14px;
color: var(--black-50);
}
}
.qrcodeContainer {
width: 240px;
height: 240px;
position: relative;
margin-bottom: 20px;
display: flex;
justify-content: center;
align-items: center;
}
.loadingWrapper {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
width: 100%;
p {
margin-top: 15px;
color: var(--black-50);
font-size: 14px;
}
}
.loadingIcon {
width: 40px;
height: 40px;
animation: spin 1.5s linear infinite;
}
.qrcodeWrapper {
position: relative;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
&:hover .qrcodeOverlay {
opacity: 1;
}
}
.qrcode {
width: 200px;
height: 200px;
padding: 10px;
background-color: white;
border: 1px solid var(--gray);
border-radius: 8px;
}
.qrcodeOverlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.7);
display: flex;
justify-content: center;
align-items: center;
opacity: 0;
transition: opacity 0.3s;
border-radius: 8px;
p {
color: white;
font-size: 16px;
}
}
.qrcodeHint {
margin-top: 15px;
color: var(--black-50);
font-size: 14px;
text-align: center;
}
.statusWrapper {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
width: 100%;
}
.statusIcon {
width: 60px;
height: 60px;
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 20px;
}
.successIcon {
width: 60px;
height: 60px;
color: var(--success);
}
.errorIcon {
width: 60px;
height: 60px;
color: var(--error);
}
.statusText {
font-size: 16px;
text-align: center;
color: var(--black);
margin-bottom: 20px;
}
.footer {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
}
.expireHint {
font-size: 12px;
color: var(--black-50);
margin-bottom: 15px;
}
.refreshButton {
padding: 8px 20px;
background-color: var(--primary);
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: background-color 0.3s;
&:hover {
background-color: var(--primary-dark);
}
&:disabled {
background-color: var(--gray);
cursor: not-allowed;
}
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}

View File

@ -0,0 +1,174 @@
import React, { useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { Path } from "../constant";
import styles from "./WechatLogin.module.scss";
import LoadingIcon from "../icons/loading.svg";
// import QRCodeImage from "../icons/wechat-qrcode-mock.svg"; // 假设有一个模拟的二维码SVG
import SuccessIcon from "../icons/confirm.svg";
import ErrorIcon from "../icons/close.svg";
import Locale from "../locales";
import { useAccessStore } from "../store";
import { safeLocalStorage } from "../utils";
// 登录状态枚举
enum LoginStatus {
LOADING = "loading",
READY = "ready",
SCANNED = "scanned",
CONFIRMED = "confirmed",
SUCCESS = "success",
ERROR = "error",
}
export function WechatLogin() {
const navigate = useNavigate();
const [status, setStatus] = useState<LoginStatus>(LoginStatus.LOADING);
const [errorMessage, setErrorMessage] = useState<string>("");
const accessStore = useAccessStore();
const storage = safeLocalStorage();
// 模拟登录流程
useEffect(() => {
// 初始加载
const timer1 = setTimeout(() => {
setStatus(LoginStatus.READY);
}, 1000);
return () => {
clearTimeout(timer1);
};
}, []);
// 模拟二维码扫描和确认过程
const simulateLogin = () => {
// 模拟扫码
setStatus(LoginStatus.SCANNED);
// 模拟确认
setTimeout(() => {
setStatus(LoginStatus.CONFIRMED);
// 模拟登录成功
setTimeout(() => {
setStatus(LoginStatus.SUCCESS);
// 存储登录信息
const mockUserInfo = {
id: "wx_" + Math.floor(Math.random() * 1000000),
nickname: "微信用户",
avatar: "https://placekitten.com/100/100", // 模拟头像
accessToken: "mock_token_" + Date.now(),
};
storage.setItem("wechat_user_info", JSON.stringify(mockUserInfo));
// 更新访问状态
accessStore.update((access) => {
access.accessToken = mockUserInfo.accessToken;
access.wechatLoggedIn = true;
});
// 登录成功后跳转
setTimeout(() => {
navigate(Path.Chat);
}, 2000);
}, 1000);
}, 2000);
};
// 刷新二维码
const refreshQRCode = () => {
setStatus(LoginStatus.LOADING);
setTimeout(() => {
setStatus(LoginStatus.READY);
}, 1000);
};
// 处理登录错误
const handleLoginError = () => {
setStatus(LoginStatus.ERROR);
setErrorMessage("登录失败,请稍后重试");
};
return (
<div className={styles.container}>
<div className={styles.loginCard}>
<div className={styles.header}>
<h2>{Locale.Auth.Title}</h2>
<p className={styles.subtitle}>使</p>
</div>
<div className={styles.qrcodeContainer}>
{status === LoginStatus.LOADING && (
<div className={styles.loadingWrapper}>
<LoadingIcon className={styles.loadingIcon} />
<p>...</p>
</div>
)}
{status === LoginStatus.READY && (
<div className={styles.qrcodeWrapper} onClick={simulateLogin}>
{/* <QRCodeImage className={styles.qrcode} /> */}
<div className={styles.qrcodeOverlay}>
<p></p>
</div>
<p className={styles.qrcodeHint}>使</p>
</div>
)}
{status === LoginStatus.SCANNED && (
<div className={styles.statusWrapper}>
<div className={styles.statusIcon}>
<LoadingIcon className={styles.loadingIcon} />
</div>
<p className={styles.statusText}></p>
</div>
)}
{status === LoginStatus.CONFIRMED && (
<div className={styles.statusWrapper}>
<div className={styles.statusIcon}>
<LoadingIcon className={styles.loadingIcon} />
</div>
<p className={styles.statusText}>...</p>
</div>
)}
{status === LoginStatus.SUCCESS && (
<div className={styles.statusWrapper}>
<div className={styles.statusIcon}>
<SuccessIcon className={styles.successIcon} />
</div>
<p className={styles.statusText}>...</p>
</div>
)}
{status === LoginStatus.ERROR && (
<div className={styles.statusWrapper}>
<div className={styles.statusIcon}>
<ErrorIcon className={styles.errorIcon} />
</div>
<p className={styles.statusText}>{errorMessage}</p>
<button className={styles.refreshButton} onClick={refreshQRCode}>
</button>
</div>
)}
</div>
{(status === LoginStatus.READY || status === LoginStatus.LOADING) && (
<div className={styles.footer}>
<p className={styles.expireHint}>2</p>
<button
className={styles.refreshButton}
onClick={refreshQRCode}
disabled={status === LoginStatus.LOADING}
>
</button>
</div>
)}
</div>
</div>
);
}

View File

@ -0,0 +1,31 @@
import React, { useEffect } from "react";
import { useNavigate, useLocation } from "react-router-dom";
import { Path } from "../constant";
import { useAccessStore } from "../store";
import { safeLocalStorage } from "../utils";
// 不需要登录就可以访问的路径
const PUBLIC_PATHS = [Path.Home, Path.Login];
export function AuthWrapper({ children }: { children: React.ReactNode }) {
const navigate = useNavigate();
const location = useLocation();
const accessStore = useAccessStore();
const storage = safeLocalStorage();
useEffect(() => {
// 检查当前路径是否需要登录
const isPublicPath = PUBLIC_PATHS.includes(location.pathname as Path);
// 检查是否已登录
const userInfoStr = storage.getItem("wechat_user_info");
const isLoggedIn = userInfoStr && accessStore.wechatLoggedIn;
// 如果需要登录但未登录,重定向到登录页
if (!isPublicPath && !isLoggedIn) {
navigate(Path.Login);
}
}, [location.pathname, navigate, accessStore.wechatLoggedIn]);
return <>{children}</>;
}

View File

@ -620,6 +620,7 @@
flex-direction: column; flex-direction: column;
border-top: var(--border-in-light); border-top: var(--border-in-light);
box-shadow: var(--card-shadow); box-shadow: var(--card-shadow);
// height: 15vh; // 添加固定高度为视窗高度的15%
.chat-input-actions { .chat-input-actions {
.chat-input-action { .chat-input-action {

View File

@ -30,6 +30,7 @@ import { type ClientApi, getClientApi } from "../client/api";
import { useAccessStore } from "../store"; import { useAccessStore } from "../store";
import clsx from "clsx"; import clsx from "clsx";
import { initializeMcpSystem, isMcpEnabled } from "../mcp/actions"; import { initializeMcpSystem, isMcpEnabled } from "../mcp/actions";
import LoginPage from "../pages/login";
export function Loading(props: { noLogo?: boolean }) { export function Loading(props: { noLogo?: boolean }) {
return ( return (
@ -198,6 +199,7 @@ function Screen() {
})} })}
/> />
<WindowContent> <WindowContent>
{/* <AuthWrapper></AuthWrapper> 只有登录时才可以路由到其他页面,相当于拦截器*/}
<Routes> <Routes>
<Route path={Path.Home} element={<Chat />} /> <Route path={Path.Home} element={<Chat />} />
<Route path={Path.NewChat} element={<NewChat />} /> <Route path={Path.NewChat} element={<NewChat />} />
@ -207,6 +209,7 @@ function Screen() {
<Route path={Path.Chat} element={<Chat />} /> <Route path={Path.Chat} element={<Chat />} />
<Route path={Path.Settings} element={<Settings />} /> <Route path={Path.Settings} element={<Settings />} />
<Route path={Path.McpMarket} element={<McpMarketPage />} /> <Route path={Path.McpMarket} element={<McpMarketPage />} />
<Route path={Path.Login} element={<LoginPage />} />
{/* <Route path={Path.Interview} element={<InterviewPage/>}/> */} {/* <Route path={Path.Interview} element={<InterviewPage/>}/> */}
</Routes> </Routes>
</WindowContent> </WindowContent>

View File

@ -0,0 +1,163 @@
.interview-overlay {
position: fixed;
top: 5px;
right: 5px;
width: 33vw;
height: 85vh;
background-color: #1e1e1e;
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 10px;
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.3);
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: flex-start;
color: #ffffff;
z-index: 1000;
padding: 20px;
overflow-y: auto;
&.dragging {
cursor: col-resize;
}
.drag-handle {
position: absolute;
left: 0;
top: 0;
width: 5px;
height: 100%;
cursor: col-resize;
background-color: transparent;
&:hover {
background-color: rgba(255, 255, 255, 0.2);
}
}
.content-container {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: flex-start;
width: 100%;
}
.status-indicator {
display: flex;
align-items: center;
justify-content: flex-start;
margin-bottom: 1rem;
background-color: rgba(0, 0, 0, 0.5);
padding: 0.5rem 1rem;
border-radius: 1rem;
width: fit-content;
.indicator-dot {
width: 10px;
height: 10px;
border-radius: 50%;
margin-right: 10px;
&.listening {
background-color: #4caf50;
box-shadow: 0 0 10px #4caf50;
animation: pulse 1.5s infinite;
}
&.not-listening {
background-color: #ff6b6b;
}
}
.status-text {
font-size: 0.9rem;
}
}
.error-message {
color: #ff6b6b;
margin-bottom: 1rem;
background-color: rgba(0, 0, 0, 0.5);
padding: 0.75rem 1rem;
border-radius: 0.5rem;
width: 100%;
text-align: center;
}
.transcript-display {
width: 100%;
margin-bottom: 1rem;
padding: 1rem;
background-color: rgba(0, 0, 0, 0.5);
border-radius: 0.5rem;
max-height: 120px;
overflow-y: auto;
text-align: left;
font-size: 0.9rem;
line-height: 1.5;
border: 1px solid rgba(0, 0, 0, 0.5);
}
.button-container {
display: flex;
justify-content: space-between;
gap: 0.5rem;
margin-top: 1rem;
width: 100%;
.button {
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
border-radius: 0.5rem;
padding: 0.5rem 1rem;
font-size: 0.9rem;
cursor: pointer;
transition: all 0.2s ease;
flex: 1;
color: white;
border: none;
&.pause-button {
background-color: #ff9800;
&:hover {
background-color: #f57c00;
}
&.paused {
background-color: #4caf50;
&:hover {
background-color: #45a049;
}
}
}
&.stop-button {
background-color: rgba(0, 0, 0, 0.5);
&:hover {
background-color: #000000;
}
}
&.clear-button {
background-color: transparent;
border: 1px solid rgba(0, 0, 0, 0.5);
&:hover {
background-color: rgba(0, 0, 0, 0.5);
}
}
}
}
}
@keyframes pulse {
0% { box-shadow: 0 0 0 0 rgba(76, 175, 80, 0.7); }
70% { box-shadow: 0 0 0 10px rgba(76, 175, 80, 0); }
100% { box-shadow: 0 0 0 0 rgba(76, 175, 80, 0); }
}

View File

@ -3,6 +3,7 @@ import StopIcon from "../icons/pause.svg";
import SpeechRecognition, { import SpeechRecognition, {
useSpeechRecognition, useSpeechRecognition,
} from "react-speech-recognition"; } from "react-speech-recognition";
import "./interview-overlay.scss";
interface InterviewOverlayProps { interface InterviewOverlayProps {
onClose: () => void; onClose: () => void;
@ -16,11 +17,17 @@ export const InterviewOverlay: React.FC<InterviewOverlayProps> = ({
submitMessage, submitMessage,
}) => { }) => {
const [visible, setVisible] = useState(true); const [visible, setVisible] = useState(true);
const [countdown, setCountdown] = useState(20); // const [countdown, setCountdown] = useState(20);
const countdownRef = useRef(countdown); // const countdownRef = useRef(countdown);
const intervalIdRef = useRef<NodeJS.Timeout | null>(null); // const intervalIdRef = useRef<NodeJS.Timeout | null>(null);
// 添加暂停状态 // 添加暂停状态
const [isPaused, setIsPaused] = useState(false); const [isPaused, setIsPaused] = useState(false);
// 添加宽度状态和拖动状态
const [width, setWidth] = useState("33vw");
const [isDragging, setIsDragging] = useState(false);
const isDraggingRef = useRef(isDragging);
const dragStartXRef = useRef(0);
const initialWidthRef = useRef(0);
// 使用 react-speech-recognition 的钩子 // 使用 react-speech-recognition 的钩子
const { const {
@ -37,12 +44,6 @@ export const InterviewOverlay: React.FC<InterviewOverlayProps> = ({
useEffect(() => { useEffect(() => {
transcriptRef.current = transcript; transcriptRef.current = transcript;
onTextUpdate(transcript); onTextUpdate(transcript);
// 当有新的语音识别结果时,重置倒计时
if (transcript) {
setCountdown(20);
countdownRef.current = 20;
}
}, [transcript, onTextUpdate]); }, [transcript, onTextUpdate]);
// 检查浏览器是否支持语音识别 // 检查浏览器是否支持语音识别
@ -62,25 +63,9 @@ export const InterviewOverlay: React.FC<InterviewOverlayProps> = ({
continuous: true, continuous: true,
language: "zh-CN", language: "zh-CN",
}); });
// 设置倒计时
intervalIdRef.current = setInterval(() => {
setCountdown((prev) => {
const newCount = prev - 1;
countdownRef.current = newCount;
if (newCount <= 0) {
stopRecognition();
}
return newCount;
});
}, 1000);
} }
return () => { return () => {
if (intervalIdRef.current) {
clearInterval(intervalIdRef.current);
}
SpeechRecognition.stopListening(); SpeechRecognition.stopListening();
}; };
}, [visible, isPaused]); }, [visible, isPaused]);
@ -88,17 +73,10 @@ export const InterviewOverlay: React.FC<InterviewOverlayProps> = ({
const stopRecognition = () => { const stopRecognition = () => {
try { try {
SpeechRecognition.stopListening(); SpeechRecognition.stopListening();
// 提交最终结果 // 提交最终结果
if (transcriptRef.current) { if (transcriptRef.current) {
submitMessage(transcriptRef.current); submitMessage(transcriptRef.current);
} }
// 清理倒计时
if (intervalIdRef.current) {
clearInterval(intervalIdRef.current);
}
// 关闭overlay // 关闭overlay
setVisible(false); setVisible(false);
onClose(); onClose();
@ -110,150 +88,108 @@ export const InterviewOverlay: React.FC<InterviewOverlayProps> = ({
// 添加暂停/恢复功能 // 添加暂停/恢复功能
const togglePause = () => { const togglePause = () => {
if (!isPaused) { if (!isPaused) {
// 暂停 // 使用更强制的中断方式
SpeechRecognition.stopListening(); SpeechRecognition.abortListening();
if (intervalIdRef.current) { // 然后再调用正常的停止方法确保完全停止
clearInterval(intervalIdRef.current); setTimeout(() => {
} SpeechRecognition.stopListening();
}, 0);
// 提交当前文本 if (transcriptRef.current && transcriptRef.current.trim() !== "") {
if (transcriptRef.current) { // 使用setTimeout将提交操作放到下一个事件循环避免阻塞UI更新
submitMessage(transcriptRef.current); setTimeout(() => {
resetTranscript(); submitMessage(transcriptRef.current);
resetTranscript();
}, 0);
} }
} else { } else {
// 恢复
console.log("recover ");
// 先确保停止当前可能存在的监听 // 先确保停止当前可能存在的监听
SpeechRecognition.abortListening(); SpeechRecognition.abortListening();
// 短暂延迟后重新启动监听 // 短暂延迟后重新启动监听
setTimeout(() => { setTimeout(() => {
SpeechRecognition.startListening({ SpeechRecognition.startListening({
continuous: true, continuous: true,
language: "zh-CN", language: "zh-CN",
}); });
// 重置文本 // 重置文本
resetTranscript(); resetTranscript();
}, 100); }, 0);
// 重新设置倒计时
intervalIdRef.current = setInterval(() => {
setCountdown((prev) => {
const newCount = prev - 1;
countdownRef.current = newCount;
if (newCount <= 0) {
stopRecognition();
}
return newCount;
});
}, 1000);
} }
setIsPaused(!isPaused); setIsPaused(!isPaused);
}; };
// 添加拖动相关的事件处理函数
const handleDragStart = (e: React.MouseEvent) => {
setIsDragging(() => {
isDraggingRef.current = true;
return true;
});
dragStartXRef.current = e.clientX;
initialWidthRef.current = parseInt(width);
document.addEventListener("mousemove", handleDragMove);
document.addEventListener("mouseup", handleDragEnd);
};
const handleDragMove = (e: MouseEvent) => {
if (isDraggingRef.current) {
const deltaX = e.clientX - dragStartXRef.current;
const newWidth = Math.max(
15,
Math.min(
80,
initialWidthRef.current - (deltaX / window.innerWidth) * 100,
),
);
console.log(`mouse have moved Width:${newWidth}vw`);
setWidth(`${newWidth}vw`);
}
};
const handleDragEnd = () => {
setIsDragging(() => {
isDraggingRef.current = false;
return false;
});
document.removeEventListener("mousemove", handleDragMove);
document.removeEventListener("mouseup", handleDragEnd);
};
// 组件卸载时清理事件监听器
useEffect(() => {
return () => {
document.removeEventListener("mousemove", handleDragMove);
document.removeEventListener("mouseup", handleDragEnd);
};
}, []);
if (!visible) { if (!visible) {
return null; return null;
} }
return ( return (
<div <div
style={{ className={`interview-overlay ${isDragging ? "dragging" : ""}`}
position: "fixed", style={{ width }}
top: "20px",
right: "20px",
width: "33vw",
height: "100vh",
// maxHeight: "80vh",
backgroundColor: "#1e1e1e", // 替换 var(--gray)
border: "1px solid rgba(255, 255, 255, 0.2)", // 替换 var(--border-in-light)
borderRadius: "10px",
boxShadow: "0 5px 20px rgba(0, 0, 0, 0.3)", // 替换 var(--shadow)
display: "flex",
flexDirection: "column",
alignItems: "flex-start",
justifyContent: "flex-start",
color: "#ffffff", // 替换 C 为白色
zIndex: 1000,
padding: "20px",
overflowY: "auto",
}}
> >
<div {/* 添加左侧拖动条 */}
style={{ <div className="drag-handle" onMouseDown={handleDragStart} />
display: "flex",
flexDirection: "column",
alignItems: "flex-start",
justifyContent: "flex-start",
width: "100%",
}}
>
<h2
style={{
fontSize: "1.5rem",
fontWeight: "500",
marginBottom: "1rem",
textAlign: "left",
color: "#ffffff", // 替换 var(--white)
}}
>
{" "}
<span
style={{
color: countdown <= 5 ? "#ff6b6b" : "#4caf50",
fontWeight: "bold",
}}
>
{countdown}
</span>{" "}
</h2>
<div className="content-container">
{/* 语音识别状态指示器 */} {/* 语音识别状态指示器 */}
<div <div className="status-indicator">
style={{
display: "flex",
alignItems: "center",
justifyContent: "flex-start",
marginBottom: "1rem",
backgroundColor: "rgba(0, 0, 0, 0.5)", // 替换 var(--black-50)
padding: "0.5rem 1rem",
borderRadius: "1rem",
width: "fit-content",
}}
>
<div <div
style={{ className={`indicator-dot ${
width: "10px", listening ? "listening" : "not-listening"
height: "10px", }`}
borderRadius: "50%",
backgroundColor: listening ? "#4caf50" : "#ff6b6b",
marginRight: "10px",
boxShadow: listening ? "0 0 10px #4caf50" : "none",
animation: listening ? "pulse 1.5s infinite" : "none",
}}
/> />
<span style={{ fontSize: "0.9rem" }}> <span className="status-text">
{listening ? "正在监听..." : isPaused ? "已暂停" : "未监听"} {listening ? "正在监听..." : isPaused ? "已暂停" : "未监听"}
</span> </span>
</div> </div>
{/* 错误提示 */} {/* 错误提示 */}
{(!browserSupportsSpeechRecognition || !isMicrophoneAvailable) && ( {(!browserSupportsSpeechRecognition || !isMicrophoneAvailable) && (
<div <div className="error-message">
style={{
color: "#ff6b6b",
marginBottom: "1rem",
backgroundColor: "rgba(0, 0, 0, 0.5)", // 替换 var(--black-50)
padding: "0.75rem 1rem",
borderRadius: "0.5rem",
width: "100%",
textAlign: "center",
}}
>
{!browserSupportsSpeechRecognition {!browserSupportsSpeechRecognition
? "您的浏览器不支持语音识别功能,请使用Chrome浏览器" ? "您的浏览器不支持语音识别功能,请使用Chrome浏览器"
: "无法访问麦克风,请检查麦克风权限"} : "无法访问麦克风,请检查麦克风权限"}
@ -261,137 +197,28 @@ export const InterviewOverlay: React.FC<InterviewOverlayProps> = ({
)} )}
{/* 识别文本显示区域 */} {/* 识别文本显示区域 */}
{transcript && ( {transcript && <div className="transcript-display">{transcript}</div>}
<div
style={{
width: "100%",
marginBottom: "1rem",
padding: "1rem",
backgroundColor: "rgba(0, 0, 0, 0.5)", // 替换 var(--black-50)
borderRadius: "0.5rem",
maxHeight: "120px",
overflowY: "auto",
textAlign: "left",
fontSize: "0.9rem",
lineHeight: "1.5",
border: "1px solid rgba(0, 0, 0, 0.5)", // 替换 var(--black-50)
}}
>
{transcript}
</div>
)}
{/* 按钮区域 */} {/* 按钮区域 */}
<div <div className="button-container">
style={{
display: "flex",
justifyContent: "space-between",
gap: "0.5rem",
marginTop: "1rem",
width: "100%",
}}
>
{/* 暂停/恢复按钮 */} {/* 暂停/恢复按钮 */}
<button <button
onClick={togglePause} onClick={togglePause}
style={{ className={`button pause-button ${isPaused ? "paused" : ""}`}
display: "flex",
alignItems: "center",
justifyContent: "center",
gap: "0.5rem",
backgroundColor: isPaused ? "#4caf50" : "#ff9800",
color: "white",
border: "none",
borderRadius: "0.5rem",
padding: "0.5rem 1rem",
fontSize: "0.9rem",
cursor: "pointer",
transition: "all 0.2s ease",
flex: "1",
}}
onMouseOver={(e) =>
(e.currentTarget.style.backgroundColor = isPaused
? "#45a049"
: "#f57c00")
}
onMouseOut={(e) =>
(e.currentTarget.style.backgroundColor = isPaused
? "#4caf50"
: "#ff9800")
}
> >
<span>{isPaused ? "▶️ 恢复监听" : "⏸️ 暂停并发送"}</span> <span>{isPaused ? "▶️ 恢复监听" : "⏸️ 暂停并发送"}</span>
</button> </button>
<button <button onClick={stopRecognition} className="button stop-button">
onClick={stopRecognition}
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
gap: "0.5rem",
backgroundColor: "rgba(0, 0, 0, 0.5)", // 替换 var(--black-50)
color: "white",
border: "none",
borderRadius: "0.5rem",
padding: "0.5rem 1rem",
fontSize: "0.9rem",
cursor: "pointer",
transition: "all 0.2s ease",
flex: "1",
}}
onMouseOver={
(e) => (e.currentTarget.style.backgroundColor = "#000000") // 替换 var(--black)
}
onMouseOut={
(e) =>
(e.currentTarget.style.backgroundColor = "rgba(0, 0, 0, 0.5)") // 替换 var(--black-50)
}
>
<StopIcon /> <StopIcon />
<span></span> <span></span>
</button> </button>
<button <button onClick={resetTranscript} className="button clear-button">
onClick={resetTranscript}
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
gap: "0.5rem",
backgroundColor: "transparent",
color: "white",
border: "1px solid rgba(0, 0, 0, 0.5)", // 替换 var(--black-50)
borderRadius: "0.5rem",
padding: "0.5rem 1rem",
fontSize: "0.9rem",
cursor: "pointer",
transition: "all 0.2s ease",
flex: "1",
}}
onMouseOver={
(e) =>
(e.currentTarget.style.backgroundColor = "rgba(0, 0, 0, 0.5)") // 替换 var(--black-50)
}
onMouseOut={(e) =>
(e.currentTarget.style.backgroundColor = "transparent")
}
>
<span>🗑 </span> <span>🗑 </span>
</button> </button>
</div> </div>
</div> </div>
{/* 添加脉冲动画 */}
<style>
{`
@keyframes pulse {
0% { box-shadow: 0 0 0 0 rgba(76, 175, 80, 0.7); }
70% { box-shadow: 0 0 0 10px rgba(76, 175, 80, 0); }
100% { box-shadow: 0 0 0 0 rgba(76, 175, 80, 0); }
}
`}
</style>
</div> </div>
); );
}; };

View File

@ -32,6 +32,8 @@ import { Selector, showConfirm } from "./ui-lib";
import clsx from "clsx"; import clsx from "clsx";
import { isMcpEnabled } from "../mcp/actions"; import { isMcpEnabled } from "../mcp/actions";
import { WechatAuthor } from "./WechatAuthor";
const DISCOVERY = [ const DISCOVERY = [
{ name: Locale.Plugin.Name, path: Path.Plugins }, { name: Locale.Plugin.Name, path: Path.Plugins },
{ name: "Stable Diffusion", path: Path.Sd }, { name: "Stable Diffusion", path: Path.Sd },
@ -223,6 +225,7 @@ export function SideBarTail(props: {
); );
} }
// 在侧边栏组件中添加WechatAuthor
export function SideBar(props: { className?: string }) { export function SideBar(props: { className?: string }) {
useHotKey(); useHotKey();
const { onDragStart, shouldNarrow } = useDragSideBar(); const { onDragStart, shouldNarrow } = useDragSideBar();
@ -248,6 +251,7 @@ export function SideBar(props: { className?: string }) {
shouldNarrow={shouldNarrow} shouldNarrow={shouldNarrow}
{...props} {...props}
> >
<WechatAuthor /> {/* 添加到最顶部 */}
<SideBarHeader <SideBarHeader
title="NextChat" title="NextChat"
subTitle="Build your own AI assistant." subTitle="Build your own AI assistant."

View File

@ -39,14 +39,16 @@ export const SILICONFLOW_BASE_URL = "https://api.siliconflow.cn";
export const CACHE_URL_PREFIX = "/api/cache"; export const CACHE_URL_PREFIX = "/api/cache";
export const UPLOAD_URL = `${CACHE_URL_PREFIX}/upload`; export const UPLOAD_URL = `${CACHE_URL_PREFIX}/upload`;
// 添加到现有的Path枚举中
export enum Path { export enum Path {
Home = "/", Home = "/",
Chat = "/chat", Chat = "/chat",
Settings = "/settings", Settings = "/settings",
NewChat = "/new-chat", NewChat = "/new-chat",
Masks = "/masks", Masks = "/masks",
Plugins = "/plugins",
Auth = "/auth", Auth = "/auth",
Login = "/login", // 添加登录路径
Plugins = "/plugins",
Sd = "/sd", Sd = "/sd",
SdNew = "/sd-new", SdNew = "/sd-new",
Artifacts = "/artifacts", Artifacts = "/artifacts",

View File

@ -0,0 +1,51 @@
<svg xmlns="http://www.w3.org/2000/svg" width="210" height="210" viewBox="0 0 21 21" style="border: 1px solid #eee;">
<!-- 定义颜色(黑色和白色) -->
<defs>
<rect id="black" width="1" height="1" fill="#000" />
<rect id="white" width="1" height="1" fill="#fff" stroke="#eee" stroke-width="0.1" />
</defs>
<!-- 位置探测图形(左上、右上、左下) -->
<!-- 左上探测图形7x7 -->
<g>
<use xlink:href="#black" x="0" y="0" width="7" height="7" /> <!-- 外框 -->
<use xlink:href="#white" x="1" y="1" width="5" height="5" /> <!-- 内框 -->
<use xlink:href="#black" x="2" y="2" width="3" height="3" /> <!-- 中心 -->
</g>
<!-- 右上探测图形 -->
<g>
<use xlink:href="#black" x="14" y="0" width="7" height="7" />
<use xlink:href="#white" x="15" y="1" width="5" height="5" />
<use xlink:href="#black" x="16" y="2" width="3" height="3" />
</g>
<!-- 左下探测图形 -->
<g>
<use xlink:href="#black" x="0" y="14" width="7" height="7" />
<use xlink:href="#white" x="1" y="15" width="5" height="5" />
<use xlink:href="#black" x="2" y="16" width="3" height="3" />
</g>
<!-- 定位图形(水平和垂直) -->
<g>
<!-- 水平定位线第6、12、18行 -->
<rect x="6" y="0" width="1" height="21" fill="#000" />
<rect x="12" y="0" width="1" height="21" fill="#000" />
<!-- 垂直定位线第6、12、18列 -->
<rect x="0" y="6" width="21" height="1" fill="#000" />
<rect x="0" y="12" width="21" height="1" fill="#000" />
</g>
<!-- 随机数据模块(非真实数据,模拟展示) -->
<g>
<!-- 生成随机黑白模块(排除已定义区域) -->
<rect x="7" y="7" width="1" height="1" fill="#000" />
<rect x="8" y="7" width="1" height="1" fill="#000" />
<rect x="9" y="7" width="1" height="1" fill="#fff" />
<rect x="10" y="7" width="1" height="1" fill="#000" />
<!-- 省略中间模块,可根据需要扩展 -->
<rect x="19" y="19" width="1" height="1" fill="#000" />
</g>
<!-- 背景白色模块(覆盖未定义区域) -->
<rect x="0" y="0" width="21" height="21" fill="#fff" stroke="#eee" stroke-width="0.1" />
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

23
app/pages/login.tsx Normal file
View File

@ -0,0 +1,23 @@
import React from "react";
import { WechatLogin } from "../components/WechatLogin";
import { useNavigate } from "react-router-dom";
import { Path } from "../constant";
import { useAccessStore } from "../store";
import { useEffect } from "react";
import { safeLocalStorage } from "../utils";
export default function LoginPage() {
const navigate = useNavigate();
const accessStore = useAccessStore();
const storage = safeLocalStorage();
// 检查是否已登录
useEffect(() => {
const userInfoStr = storage.getItem("wechat_user_info");
if (userInfoStr && accessStore.wechatLoggedIn) {
navigate(Path.Chat);
}
}, [navigate, accessStore.wechatLoggedIn]);
return <WechatLogin />;
}

View File

@ -142,6 +142,9 @@ const DEFAULT_ACCESS_STATE = {
defaultModel: "", defaultModel: "",
visionModels: "", visionModels: "",
// 添加微信登录状态
wechatLoggedIn: false,
accessToken: "",
// tts config // tts config
edgeTTSVoiceName: "zh-CN-YunxiNeural", edgeTTSVoiceName: "zh-CN-YunxiNeural",
}; };

432
package-lock.json generated
View File

@ -12,6 +12,7 @@
"@modelcontextprotocol/sdk": "^1.0.4", "@modelcontextprotocol/sdk": "^1.0.4",
"@next/third-parties": "^14.1.0", "@next/third-parties": "^14.1.0",
"@svgr/webpack": "^6.5.1", "@svgr/webpack": "^6.5.1",
"@tensorflow/tfjs": "^4.22.0",
"@vercel/analytics": "^0.1.11", "@vercel/analytics": "^0.1.11",
"@vercel/speed-insights": "^1.0.2", "@vercel/speed-insights": "^1.0.2",
"axios": "^1.7.5", "axios": "^1.7.5",
@ -4236,6 +4237,355 @@
"node": ">= 10" "node": ">= 10"
} }
}, },
"node_modules/@tensorflow/tfjs": {
"version": "4.22.0",
"resolved": "https://registry.npmmirror.com/@tensorflow/tfjs/-/tfjs-4.22.0.tgz",
"integrity": "sha512-0TrIrXs6/b7FLhLVNmfh8Sah6JgjBPH4mZ8JGb7NU6WW+cx00qK5BcAZxw7NCzxj6N8MRAIfHq+oNbPUNG5VAg==",
"license": "Apache-2.0",
"dependencies": {
"@tensorflow/tfjs-backend-cpu": "4.22.0",
"@tensorflow/tfjs-backend-webgl": "4.22.0",
"@tensorflow/tfjs-converter": "4.22.0",
"@tensorflow/tfjs-core": "4.22.0",
"@tensorflow/tfjs-data": "4.22.0",
"@tensorflow/tfjs-layers": "4.22.0",
"argparse": "^1.0.10",
"chalk": "^4.1.0",
"core-js": "3.29.1",
"regenerator-runtime": "^0.13.5",
"yargs": "^16.0.3"
},
"bin": {
"tfjs-custom-module": "dist/tools/custom_module/cli.js"
}
},
"node_modules/@tensorflow/tfjs-backend-cpu": {
"version": "4.22.0",
"resolved": "https://registry.npmmirror.com/@tensorflow/tfjs-backend-cpu/-/tfjs-backend-cpu-4.22.0.tgz",
"integrity": "sha512-1u0FmuLGuRAi8D2c3cocHTASGXOmHc/4OvoVDENJayjYkS119fcTcQf4iHrtLthWyDIPy3JiPhRrZQC9EwnhLw==",
"license": "Apache-2.0",
"dependencies": {
"@types/seedrandom": "^2.4.28",
"seedrandom": "^3.0.5"
},
"engines": {
"yarn": ">= 1.3.2"
},
"peerDependencies": {
"@tensorflow/tfjs-core": "4.22.0"
}
},
"node_modules/@tensorflow/tfjs-backend-webgl": {
"version": "4.22.0",
"resolved": "https://registry.npmmirror.com/@tensorflow/tfjs-backend-webgl/-/tfjs-backend-webgl-4.22.0.tgz",
"integrity": "sha512-H535XtZWnWgNwSzv538czjVlbJebDl5QTMOth4RXr2p/kJ1qSIXE0vZvEtO+5EC9b00SvhplECny2yDewQb/Yg==",
"license": "Apache-2.0",
"dependencies": {
"@tensorflow/tfjs-backend-cpu": "4.22.0",
"@types/offscreencanvas": "~2019.3.0",
"@types/seedrandom": "^2.4.28",
"seedrandom": "^3.0.5"
},
"engines": {
"yarn": ">= 1.3.2"
},
"peerDependencies": {
"@tensorflow/tfjs-core": "4.22.0"
}
},
"node_modules/@tensorflow/tfjs-converter": {
"version": "4.22.0",
"resolved": "https://registry.npmmirror.com/@tensorflow/tfjs-converter/-/tfjs-converter-4.22.0.tgz",
"integrity": "sha512-PT43MGlnzIo+YfbsjM79Lxk9lOq6uUwZuCc8rrp0hfpLjF6Jv8jS84u2jFb+WpUeuF4K33ZDNx8CjiYrGQ2trQ==",
"license": "Apache-2.0",
"peerDependencies": {
"@tensorflow/tfjs-core": "4.22.0"
}
},
"node_modules/@tensorflow/tfjs-core": {
"version": "4.22.0",
"resolved": "https://registry.npmmirror.com/@tensorflow/tfjs-core/-/tfjs-core-4.22.0.tgz",
"integrity": "sha512-LEkOyzbknKFoWUwfkr59vSB68DMJ4cjwwHgicXN0DUi3a0Vh1Er3JQqCI1Hl86GGZQvY8ezVrtDIvqR1ZFW55A==",
"license": "Apache-2.0",
"dependencies": {
"@types/long": "^4.0.1",
"@types/offscreencanvas": "~2019.7.0",
"@types/seedrandom": "^2.4.28",
"@webgpu/types": "0.1.38",
"long": "4.0.0",
"node-fetch": "~2.6.1",
"seedrandom": "^3.0.5"
},
"engines": {
"yarn": ">= 1.3.2"
}
},
"node_modules/@tensorflow/tfjs-core/node_modules/@types/offscreencanvas": {
"version": "2019.7.3",
"resolved": "https://registry.npmmirror.com/@types/offscreencanvas/-/offscreencanvas-2019.7.3.tgz",
"integrity": "sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A==",
"license": "MIT"
},
"node_modules/@tensorflow/tfjs-core/node_modules/node-fetch": {
"version": "2.6.13",
"resolved": "https://registry.npmmirror.com/node-fetch/-/node-fetch-2.6.13.tgz",
"integrity": "sha512-StxNAxh15zr77QvvkmveSQ8uCQ4+v5FkvNTj0OESmiHu+VRi/gXArXtkWMElOsOUNLtUEvI4yS+rdtOHZTwlQA==",
"license": "MIT",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"node_modules/@tensorflow/tfjs-core/node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmmirror.com/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
"license": "MIT"
},
"node_modules/@tensorflow/tfjs-core/node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
"license": "BSD-2-Clause"
},
"node_modules/@tensorflow/tfjs-core/node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmmirror.com/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"license": "MIT",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"node_modules/@tensorflow/tfjs-data": {
"version": "4.22.0",
"resolved": "https://registry.npmmirror.com/@tensorflow/tfjs-data/-/tfjs-data-4.22.0.tgz",
"integrity": "sha512-dYmF3LihQIGvtgJrt382hSRH4S0QuAp2w1hXJI2+kOaEqo5HnUPG0k5KA6va+S1yUhx7UBToUKCBHeLHFQRV4w==",
"license": "Apache-2.0",
"dependencies": {
"@types/node-fetch": "^2.1.2",
"node-fetch": "~2.6.1",
"string_decoder": "^1.3.0"
},
"peerDependencies": {
"@tensorflow/tfjs-core": "4.22.0",
"seedrandom": "^3.0.5"
}
},
"node_modules/@tensorflow/tfjs-data/node_modules/node-fetch": {
"version": "2.6.13",
"resolved": "https://registry.npmmirror.com/node-fetch/-/node-fetch-2.6.13.tgz",
"integrity": "sha512-StxNAxh15zr77QvvkmveSQ8uCQ4+v5FkvNTj0OESmiHu+VRi/gXArXtkWMElOsOUNLtUEvI4yS+rdtOHZTwlQA==",
"license": "MIT",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"node_modules/@tensorflow/tfjs-data/node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmmirror.com/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
"license": "MIT"
},
"node_modules/@tensorflow/tfjs-data/node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
"license": "BSD-2-Clause"
},
"node_modules/@tensorflow/tfjs-data/node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmmirror.com/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"license": "MIT",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"node_modules/@tensorflow/tfjs-layers": {
"version": "4.22.0",
"resolved": "https://registry.npmmirror.com/@tensorflow/tfjs-layers/-/tfjs-layers-4.22.0.tgz",
"integrity": "sha512-lybPj4ZNj9iIAPUj7a8ZW1hg8KQGfqWLlCZDi9eM/oNKCCAgchiyzx8OrYoWmRrB+AM6VNEeIT+2gZKg5ReihA==",
"license": "Apache-2.0 AND MIT",
"peerDependencies": {
"@tensorflow/tfjs-core": "4.22.0"
}
},
"node_modules/@tensorflow/tfjs/node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/@tensorflow/tfjs/node_modules/argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmmirror.com/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"license": "MIT",
"dependencies": {
"sprintf-js": "~1.0.2"
}
},
"node_modules/@tensorflow/tfjs/node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/@tensorflow/tfjs/node_modules/cliui": {
"version": "7.0.4",
"resolved": "https://registry.npmmirror.com/cliui/-/cliui-7.0.4.tgz",
"integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
"license": "ISC",
"dependencies": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.0",
"wrap-ansi": "^7.0.0"
}
},
"node_modules/@tensorflow/tfjs/node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"license": "MIT",
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/@tensorflow/tfjs/node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"license": "MIT"
},
"node_modules/@tensorflow/tfjs/node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"license": "MIT"
},
"node_modules/@tensorflow/tfjs/node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/@tensorflow/tfjs/node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/@tensorflow/tfjs/node_modules/regenerator-runtime": {
"version": "0.13.11",
"resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
"license": "MIT"
},
"node_modules/@tensorflow/tfjs/node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@tensorflow/tfjs/node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"license": "MIT",
"dependencies": {
"has-flag": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@tensorflow/tfjs/node_modules/yargs": {
"version": "16.2.0",
"resolved": "https://registry.npmmirror.com/yargs/-/yargs-16.2.0.tgz",
"integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
"license": "MIT",
"dependencies": {
"cliui": "^7.0.2",
"escalade": "^3.1.1",
"get-caller-file": "^2.0.5",
"require-directory": "^2.1.1",
"string-width": "^4.2.0",
"y18n": "^5.0.5",
"yargs-parser": "^20.2.2"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@tensorflow/tfjs/node_modules/yargs-parser": {
"version": "20.2.9",
"resolved": "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-20.2.9.tgz",
"integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
"license": "ISC",
"engines": {
"node": ">=10"
}
},
"node_modules/@testing-library/dom": { "node_modules/@testing-library/dom": {
"version": "10.4.0", "version": "10.4.0",
"resolved": "https://registry.npmmirror.com/@testing-library/dom/-/dom-10.4.0.tgz", "resolved": "https://registry.npmmirror.com/@testing-library/dom/-/dom-10.4.0.tgz",
@ -4791,6 +5141,12 @@
"@types/lodash": "*" "@types/lodash": "*"
} }
}, },
"node_modules/@types/long": {
"version": "4.0.2",
"resolved": "https://registry.npmmirror.com/@types/long/-/long-4.0.2.tgz",
"integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==",
"license": "MIT"
},
"node_modules/@types/mdast": { "node_modules/@types/mdast": {
"version": "3.0.11", "version": "3.0.11",
"resolved": "https://registry.npmmirror.com/@types/mdast/-/mdast-3.0.11.tgz", "resolved": "https://registry.npmmirror.com/@types/mdast/-/mdast-3.0.11.tgz",
@ -4810,12 +5166,27 @@
"version": "20.11.30", "version": "20.11.30",
"resolved": "https://registry.npmmirror.com/@types/node/-/node-20.11.30.tgz", "resolved": "https://registry.npmmirror.com/@types/node/-/node-20.11.30.tgz",
"integrity": "sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==", "integrity": "sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"undici-types": "~5.26.4" "undici-types": "~5.26.4"
} }
}, },
"node_modules/@types/node-fetch": {
"version": "2.6.12",
"resolved": "https://registry.npmmirror.com/@types/node-fetch/-/node-fetch-2.6.12.tgz",
"integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==",
"license": "MIT",
"dependencies": {
"@types/node": "*",
"form-data": "^4.0.0"
}
},
"node_modules/@types/offscreencanvas": {
"version": "2019.3.0",
"resolved": "https://registry.npmmirror.com/@types/offscreencanvas/-/offscreencanvas-2019.3.0.tgz",
"integrity": "sha512-esIJx9bQg+QYF0ra8GnvfianIY8qWB0GBx54PK5Eps6m+xTj86KLavHv6qDhzKcu5UUOgNfJ2pWaIIV7TRUd9Q==",
"license": "MIT"
},
"node_modules/@types/parse-json": { "node_modules/@types/parse-json": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmmirror.com/@types/parse-json/-/parse-json-4.0.0.tgz", "resolved": "https://registry.npmmirror.com/@types/parse-json/-/parse-json-4.0.0.tgz",
@ -4875,6 +5246,12 @@
"integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==", "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/seedrandom": {
"version": "2.4.34",
"resolved": "https://registry.npmmirror.com/@types/seedrandom/-/seedrandom-2.4.34.tgz",
"integrity": "sha512-ytDiArvrn/3Xk6/vtylys5tlY6eo7Ane0hvcx++TKo6RxQXuVfW0AF/oeWqAj9dN29SyhtawuXstgmPlwNcv/A==",
"license": "MIT"
},
"node_modules/@types/spark-md5": { "node_modules/@types/spark-md5": {
"version": "3.0.4", "version": "3.0.4",
"resolved": "https://registry.npmmirror.com/@types/spark-md5/-/spark-md5-3.0.4.tgz", "resolved": "https://registry.npmmirror.com/@types/spark-md5/-/spark-md5-3.0.4.tgz",
@ -5245,6 +5622,12 @@
"@xtuc/long": "4.2.2" "@xtuc/long": "4.2.2"
} }
}, },
"node_modules/@webgpu/types": {
"version": "0.1.38",
"resolved": "https://registry.npmmirror.com/@webgpu/types/-/types-0.1.38.tgz",
"integrity": "sha512-7LrhVKz2PRh+DD7+S+PVaFd5HxaWQvoMqBbsV9fNJO1pjUs1P8bM2vQVNfk+3URTqbuTI7gkXi0rfsN0IadoBA==",
"license": "BSD-3-Clause"
},
"node_modules/@xtuc/ieee754": { "node_modules/@xtuc/ieee754": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmmirror.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz", "resolved": "https://registry.npmmirror.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
@ -5396,7 +5779,6 @@
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=8" "node": ">=8"
@ -6480,6 +6862,17 @@
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/core-js": {
"version": "3.29.1",
"resolved": "https://registry.npmmirror.com/core-js/-/core-js-3.29.1.tgz",
"integrity": "sha512-+jwgnhg6cQxKYIIjGtAHq2nwUOolo9eoFZ4sHfUH09BLXBgxnH4gA0zEd+t+BO2cNB8idaBtZFcFTRjQJRJmAw==",
"hasInstallScript": true,
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/core-js"
}
},
"node_modules/core-js-compat": { "node_modules/core-js-compat": {
"version": "3.29.1", "version": "3.29.1",
"resolved": "https://registry.npmmirror.com/core-js-compat/-/core-js-compat-3.29.1.tgz", "resolved": "https://registry.npmmirror.com/core-js-compat/-/core-js-compat-3.29.1.tgz",
@ -9066,7 +9459,6 @@
"version": "2.0.5", "version": "2.0.5",
"resolved": "https://registry.npmmirror.com/get-caller-file/-/get-caller-file-2.0.5.tgz", "resolved": "https://registry.npmmirror.com/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"dev": true,
"license": "ISC", "license": "ISC",
"engines": { "engines": {
"node": "6.* || 8.* || >= 10.*" "node": "6.* || 8.* || >= 10.*"
@ -13068,6 +13460,12 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/long": {
"version": "4.0.0",
"resolved": "https://registry.npmmirror.com/long/-/long-4.0.0.tgz",
"integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==",
"license": "Apache-2.0"
},
"node_modules/longest-streak": { "node_modules/longest-streak": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmmirror.com/longest-streak/-/longest-streak-3.1.0.tgz", "resolved": "https://registry.npmmirror.com/longest-streak/-/longest-streak-3.1.0.tgz",
@ -15564,7 +15962,6 @@
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmmirror.com/require-directory/-/require-directory-2.1.1.tgz", "resolved": "https://registry.npmmirror.com/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
@ -15819,7 +16216,6 @@
"version": "5.2.1", "version": "5.2.1",
"resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz", "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"dev": true,
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
@ -15915,6 +16311,12 @@
"url": "https://opencollective.com/webpack" "url": "https://opencollective.com/webpack"
} }
}, },
"node_modules/seedrandom": {
"version": "3.0.5",
"resolved": "https://registry.npmmirror.com/seedrandom/-/seedrandom-3.0.5.tgz",
"integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==",
"license": "MIT"
},
"node_modules/semver": { "node_modules/semver": {
"version": "6.3.1", "version": "6.3.1",
"resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz",
@ -16097,7 +16499,6 @@
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.0.3.tgz", "resolved": "https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
"dev": true,
"license": "BSD-3-Clause" "license": "BSD-3-Clause"
}, },
"node_modules/stable": { "node_modules/stable": {
@ -16160,6 +16561,15 @@
"node": ">=10.0.0" "node": ">=10.0.0"
} }
}, },
"node_modules/string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"license": "MIT",
"dependencies": {
"safe-buffer": "~5.2.0"
}
},
"node_modules/string-argv": { "node_modules/string-argv": {
"version": "0.3.1", "version": "0.3.1",
"resolved": "https://registry.npmmirror.com/string-argv/-/string-argv-0.3.1.tgz", "resolved": "https://registry.npmmirror.com/string-argv/-/string-argv-0.3.1.tgz",
@ -16336,7 +16746,6 @@
"version": "6.0.1", "version": "6.0.1",
"resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"ansi-regex": "^5.0.1" "ansi-regex": "^5.0.1"
@ -17101,7 +17510,6 @@
"version": "5.26.5", "version": "5.26.5",
"resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-5.26.5.tgz", "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/unicode-canonical-property-names-ecmascript": { "node_modules/unicode-canonical-property-names-ecmascript": {
@ -17766,7 +18174,6 @@
"version": "7.0.0", "version": "7.0.0",
"resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"ansi-styles": "^4.0.0", "ansi-styles": "^4.0.0",
@ -17871,7 +18278,6 @@
"version": "4.3.0", "version": "4.3.0",
"resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"color-convert": "^2.0.1" "color-convert": "^2.0.1"
@ -17887,7 +18293,6 @@
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz", "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"color-name": "~1.1.4" "color-name": "~1.1.4"
@ -17900,21 +18305,18 @@
"version": "1.1.4", "version": "1.1.4",
"resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/wrap-ansi/node_modules/emoji-regex": { "node_modules/wrap-ansi/node_modules/emoji-regex": {
"version": "8.0.0", "version": "8.0.0",
"resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz", "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=8" "node": ">=8"
@ -17924,7 +18326,6 @@
"version": "4.2.3", "version": "4.2.3",
"resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz", "resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"emoji-regex": "^8.0.0", "emoji-regex": "^8.0.0",
@ -17998,7 +18399,6 @@
"version": "5.0.8", "version": "5.0.8",
"resolved": "https://registry.npmmirror.com/y18n/-/y18n-5.0.8.tgz", "resolved": "https://registry.npmmirror.com/y18n/-/y18n-5.0.8.tgz",
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
"dev": true,
"license": "ISC", "license": "ISC",
"engines": { "engines": {
"node": ">=10" "node": ">=10"

View File

@ -26,6 +26,7 @@
"@modelcontextprotocol/sdk": "^1.0.4", "@modelcontextprotocol/sdk": "^1.0.4",
"@next/third-parties": "^14.1.0", "@next/third-parties": "^14.1.0",
"@svgr/webpack": "^6.5.1", "@svgr/webpack": "^6.5.1",
"@tensorflow/tfjs": "^4.22.0",
"@vercel/analytics": "^0.1.11", "@vercel/analytics": "^0.1.11",
"@vercel/speed-insights": "^1.0.2", "@vercel/speed-insights": "^1.0.2",
"axios": "^1.7.5", "axios": "^1.7.5",

170
yarn.lock
View File

@ -1620,6 +1620,73 @@
"@tauri-apps/cli-win32-ia32-msvc" "1.5.11" "@tauri-apps/cli-win32-ia32-msvc" "1.5.11"
"@tauri-apps/cli-win32-x64-msvc" "1.5.11" "@tauri-apps/cli-win32-x64-msvc" "1.5.11"
"@tensorflow/tfjs-backend-cpu@4.22.0":
version "4.22.0"
resolved "https://registry.npmmirror.com/@tensorflow/tfjs-backend-cpu/-/tfjs-backend-cpu-4.22.0.tgz"
integrity sha512-1u0FmuLGuRAi8D2c3cocHTASGXOmHc/4OvoVDENJayjYkS119fcTcQf4iHrtLthWyDIPy3JiPhRrZQC9EwnhLw==
dependencies:
"@types/seedrandom" "^2.4.28"
seedrandom "^3.0.5"
"@tensorflow/tfjs-backend-webgl@4.22.0":
version "4.22.0"
resolved "https://registry.npmmirror.com/@tensorflow/tfjs-backend-webgl/-/tfjs-backend-webgl-4.22.0.tgz"
integrity sha512-H535XtZWnWgNwSzv538czjVlbJebDl5QTMOth4RXr2p/kJ1qSIXE0vZvEtO+5EC9b00SvhplECny2yDewQb/Yg==
dependencies:
"@tensorflow/tfjs-backend-cpu" "4.22.0"
"@types/offscreencanvas" "~2019.3.0"
"@types/seedrandom" "^2.4.28"
seedrandom "^3.0.5"
"@tensorflow/tfjs-converter@4.22.0":
version "4.22.0"
resolved "https://registry.npmmirror.com/@tensorflow/tfjs-converter/-/tfjs-converter-4.22.0.tgz"
integrity sha512-PT43MGlnzIo+YfbsjM79Lxk9lOq6uUwZuCc8rrp0hfpLjF6Jv8jS84u2jFb+WpUeuF4K33ZDNx8CjiYrGQ2trQ==
"@tensorflow/tfjs-core@4.22.0":
version "4.22.0"
resolved "https://registry.npmmirror.com/@tensorflow/tfjs-core/-/tfjs-core-4.22.0.tgz"
integrity sha512-LEkOyzbknKFoWUwfkr59vSB68DMJ4cjwwHgicXN0DUi3a0Vh1Er3JQqCI1Hl86GGZQvY8ezVrtDIvqR1ZFW55A==
dependencies:
"@types/long" "^4.0.1"
"@types/offscreencanvas" "~2019.7.0"
"@types/seedrandom" "^2.4.28"
"@webgpu/types" "0.1.38"
long "4.0.0"
node-fetch "~2.6.1"
seedrandom "^3.0.5"
"@tensorflow/tfjs-data@4.22.0":
version "4.22.0"
resolved "https://registry.npmmirror.com/@tensorflow/tfjs-data/-/tfjs-data-4.22.0.tgz"
integrity sha512-dYmF3LihQIGvtgJrt382hSRH4S0QuAp2w1hXJI2+kOaEqo5HnUPG0k5KA6va+S1yUhx7UBToUKCBHeLHFQRV4w==
dependencies:
"@types/node-fetch" "^2.1.2"
node-fetch "~2.6.1"
string_decoder "^1.3.0"
"@tensorflow/tfjs-layers@4.22.0":
version "4.22.0"
resolved "https://registry.npmmirror.com/@tensorflow/tfjs-layers/-/tfjs-layers-4.22.0.tgz"
integrity sha512-lybPj4ZNj9iIAPUj7a8ZW1hg8KQGfqWLlCZDi9eM/oNKCCAgchiyzx8OrYoWmRrB+AM6VNEeIT+2gZKg5ReihA==
"@tensorflow/tfjs@^4.22.0":
version "4.22.0"
resolved "https://registry.npmmirror.com/@tensorflow/tfjs/-/tfjs-4.22.0.tgz"
integrity sha512-0TrIrXs6/b7FLhLVNmfh8Sah6JgjBPH4mZ8JGb7NU6WW+cx00qK5BcAZxw7NCzxj6N8MRAIfHq+oNbPUNG5VAg==
dependencies:
"@tensorflow/tfjs-backend-cpu" "4.22.0"
"@tensorflow/tfjs-backend-webgl" "4.22.0"
"@tensorflow/tfjs-converter" "4.22.0"
"@tensorflow/tfjs-core" "4.22.0"
"@tensorflow/tfjs-data" "4.22.0"
"@tensorflow/tfjs-layers" "4.22.0"
argparse "^1.0.10"
chalk "^4.1.0"
core-js "3.29.1"
regenerator-runtime "^0.13.5"
yargs "^16.0.3"
"@testing-library/dom@^10.0.0", "@testing-library/dom@^10.4.0": "@testing-library/dom@^10.0.0", "@testing-library/dom@^10.4.0":
version "10.4.0" version "10.4.0"
resolved "https://registry.npmmirror.com/@testing-library/dom/-/dom-10.4.0.tgz" resolved "https://registry.npmmirror.com/@testing-library/dom/-/dom-10.4.0.tgz"
@ -1872,6 +1939,11 @@
resolved "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.7.tgz" resolved "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.7.tgz"
integrity sha512-8wTvZawATi/lsmNu10/j2hk1KEP0IvjubqPE3cu1Xz7xfXXt5oCq3SNUz4fMIP4XGF9Ky+Ue2tBA3hcS7LSBlA== integrity sha512-8wTvZawATi/lsmNu10/j2hk1KEP0IvjubqPE3cu1Xz7xfXXt5oCq3SNUz4fMIP4XGF9Ky+Ue2tBA3hcS7LSBlA==
"@types/long@^4.0.1":
version "4.0.2"
resolved "https://registry.npmmirror.com/@types/long/-/long-4.0.2.tgz"
integrity sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==
"@types/mdast@^3.0.0": "@types/mdast@^3.0.0":
version "3.0.11" version "3.0.11"
resolved "https://registry.npmmirror.com/@types/mdast/-/mdast-3.0.11.tgz" resolved "https://registry.npmmirror.com/@types/mdast/-/mdast-3.0.11.tgz"
@ -1884,6 +1956,14 @@
resolved "https://registry.npmmirror.com/@types/ms/-/ms-0.7.31.tgz" resolved "https://registry.npmmirror.com/@types/ms/-/ms-0.7.31.tgz"
integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA== integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==
"@types/node-fetch@^2.1.2":
version "2.6.12"
resolved "https://registry.npmmirror.com/@types/node-fetch/-/node-fetch-2.6.12.tgz"
integrity sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==
dependencies:
"@types/node" "*"
form-data "^4.0.0"
"@types/node@*", "@types/node@^20.11.30": "@types/node@*", "@types/node@^20.11.30":
version "20.11.30" version "20.11.30"
resolved "https://registry.npmmirror.com/@types/node/-/node-20.11.30.tgz" resolved "https://registry.npmmirror.com/@types/node/-/node-20.11.30.tgz"
@ -1891,6 +1971,16 @@
dependencies: dependencies:
undici-types "~5.26.4" undici-types "~5.26.4"
"@types/offscreencanvas@~2019.3.0":
version "2019.3.0"
resolved "https://registry.npmmirror.com/@types/offscreencanvas/-/offscreencanvas-2019.3.0.tgz"
integrity sha512-esIJx9bQg+QYF0ra8GnvfianIY8qWB0GBx54PK5Eps6m+xTj86KLavHv6qDhzKcu5UUOgNfJ2pWaIIV7TRUd9Q==
"@types/offscreencanvas@~2019.7.0":
version "2019.7.3"
resolved "https://registry.npmmirror.com/@types/offscreencanvas/-/offscreencanvas-2019.7.3.tgz"
integrity sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A==
"@types/parse-json@^4.0.0": "@types/parse-json@^4.0.0":
version "4.0.0" version "4.0.0"
resolved "https://registry.npmmirror.com/@types/parse-json/-/parse-json-4.0.0.tgz" resolved "https://registry.npmmirror.com/@types/parse-json/-/parse-json-4.0.0.tgz"
@ -1936,6 +2026,11 @@
resolved "https://registry.npmmirror.com/@types/scheduler/-/scheduler-0.16.3.tgz" resolved "https://registry.npmmirror.com/@types/scheduler/-/scheduler-0.16.3.tgz"
integrity sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ== integrity sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==
"@types/seedrandom@^2.4.28":
version "2.4.34"
resolved "https://registry.npmmirror.com/@types/seedrandom/-/seedrandom-2.4.34.tgz"
integrity sha512-ytDiArvrn/3Xk6/vtylys5tlY6eo7Ane0hvcx++TKo6RxQXuVfW0AF/oeWqAj9dN29SyhtawuXstgmPlwNcv/A==
"@types/spark-md5@^3.0.4": "@types/spark-md5@^3.0.4":
version "3.0.4" version "3.0.4"
resolved "https://registry.npmmirror.com/@types/spark-md5/-/spark-md5-3.0.4.tgz" resolved "https://registry.npmmirror.com/@types/spark-md5/-/spark-md5-3.0.4.tgz"
@ -2149,6 +2244,11 @@
"@webassemblyjs/ast" "1.11.6" "@webassemblyjs/ast" "1.11.6"
"@xtuc/long" "4.2.2" "@xtuc/long" "4.2.2"
"@webgpu/types@0.1.38":
version "0.1.38"
resolved "https://registry.npmmirror.com/@webgpu/types/-/types-0.1.38.tgz"
integrity sha512-7LrhVKz2PRh+DD7+S+PVaFd5HxaWQvoMqBbsV9fNJO1pjUs1P8bM2vQVNfk+3URTqbuTI7gkXi0rfsN0IadoBA==
"@xtuc/ieee754@^1.2.0": "@xtuc/ieee754@^1.2.0":
version "1.2.0" version "1.2.0"
resolved "https://registry.npmmirror.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz" resolved "https://registry.npmmirror.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz"
@ -2290,6 +2390,13 @@ arg@^4.1.0:
resolved "https://registry.npmmirror.com/arg/-/arg-4.1.3.tgz" resolved "https://registry.npmmirror.com/arg/-/arg-4.1.3.tgz"
integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==
argparse@^1.0.10:
version "1.0.10"
resolved "https://registry.npmmirror.com/argparse/-/argparse-1.0.10.tgz"
integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==
dependencies:
sprintf-js "~1.0.2"
argparse@^1.0.7: argparse@^1.0.7:
version "1.0.10" version "1.0.10"
resolved "https://registry.npmmirror.com/argparse/-/argparse-1.0.10.tgz" resolved "https://registry.npmmirror.com/argparse/-/argparse-1.0.10.tgz"
@ -2735,6 +2842,15 @@ client-only@0.0.1:
resolved "https://registry.npmmirror.com/client-only/-/client-only-0.0.1.tgz" resolved "https://registry.npmmirror.com/client-only/-/client-only-0.0.1.tgz"
integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA== integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==
cliui@^7.0.2:
version "7.0.4"
resolved "https://registry.npmmirror.com/cliui/-/cliui-7.0.4.tgz"
integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==
dependencies:
string-width "^4.2.0"
strip-ansi "^6.0.0"
wrap-ansi "^7.0.0"
cliui@^8.0.1: cliui@^8.0.1:
version "8.0.1" version "8.0.1"
resolved "https://registry.npmmirror.com/cliui/-/cliui-8.0.1.tgz" resolved "https://registry.npmmirror.com/cliui/-/cliui-8.0.1.tgz"
@ -2867,6 +2983,11 @@ core-js-compat@^3.25.1:
dependencies: dependencies:
browserslist "^4.21.5" browserslist "^4.21.5"
core-js@3.29.1:
version "3.29.1"
resolved "https://registry.npmmirror.com/core-js/-/core-js-3.29.1.tgz"
integrity sha512-+jwgnhg6cQxKYIIjGtAHq2nwUOolo9eoFZ4sHfUH09BLXBgxnH4gA0zEd+t+BO2cNB8idaBtZFcFTRjQJRJmAw==
cose-base@^1.0.0: cose-base@^1.0.0:
version "1.0.3" version "1.0.3"
resolved "https://registry.npmmirror.com/cose-base/-/cose-base-1.0.3.tgz" resolved "https://registry.npmmirror.com/cose-base/-/cose-base-1.0.3.tgz"
@ -5666,6 +5787,11 @@ log-update@^4.0.0:
slice-ansi "^4.0.0" slice-ansi "^4.0.0"
wrap-ansi "^6.2.0" wrap-ansi "^6.2.0"
long@4.0.0:
version "4.0.0"
resolved "https://registry.npmmirror.com/long/-/long-4.0.0.tgz"
integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==
longest-streak@^3.0.0: longest-streak@^3.0.0:
version "3.1.0" version "3.1.0"
resolved "https://registry.npmmirror.com/longest-streak/-/longest-streak-3.1.0.tgz" resolved "https://registry.npmmirror.com/longest-streak/-/longest-streak-3.1.0.tgz"
@ -6373,6 +6499,13 @@ node-fetch@^3.3.1:
fetch-blob "^3.1.4" fetch-blob "^3.1.4"
formdata-polyfill "^4.0.10" formdata-polyfill "^4.0.10"
node-fetch@~2.6.1:
version "2.6.13"
resolved "https://registry.npmmirror.com/node-fetch/-/node-fetch-2.6.13.tgz"
integrity sha512-StxNAxh15zr77QvvkmveSQ8uCQ4+v5FkvNTj0OESmiHu+VRi/gXArXtkWMElOsOUNLtUEvI4yS+rdtOHZTwlQA==
dependencies:
whatwg-url "^5.0.0"
node-int64@^0.4.0: node-int64@^0.4.0:
version "0.4.0" version "0.4.0"
resolved "https://registry.npmmirror.com/node-int64/-/node-int64-0.4.0.tgz" resolved "https://registry.npmmirror.com/node-int64/-/node-int64-0.4.0.tgz"
@ -6925,6 +7058,11 @@ regenerate@^1.4.2:
resolved "https://registry.npmmirror.com/regenerate/-/regenerate-1.4.2.tgz" resolved "https://registry.npmmirror.com/regenerate/-/regenerate-1.4.2.tgz"
integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==
regenerator-runtime@^0.13.5:
version "0.13.11"
resolved "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz"
integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==
regenerator-runtime@^0.14.0: regenerator-runtime@^0.14.0:
version "0.14.1" version "0.14.1"
resolved "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz" resolved "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz"
@ -7162,7 +7300,7 @@ sade@^1.7.3:
dependencies: dependencies:
mri "^1.1.0" mri "^1.1.0"
safe-buffer@^5.1.0: safe-buffer@^5.1.0, safe-buffer@~5.2.0:
version "5.2.1" version "5.2.1"
resolved "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz" resolved "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
@ -7213,6 +7351,11 @@ schema-utils@^3.1.1, schema-utils@^3.2.0:
ajv "^6.12.5" ajv "^6.12.5"
ajv-keywords "^3.5.2" ajv-keywords "^3.5.2"
seedrandom@^3.0.5:
version "3.0.5"
resolved "https://registry.npmmirror.com/seedrandom/-/seedrandom-3.0.5.tgz"
integrity sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==
semver@^6.1.1, semver@^6.1.2, semver@^6.3.0, semver@^6.3.1: semver@^6.1.1, semver@^6.1.2, semver@^6.3.0, semver@^6.3.1:
version "6.3.1" version "6.3.1"
resolved "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz" resolved "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz"
@ -7394,6 +7537,13 @@ streamsearch@^1.1.0:
resolved "https://registry.npmmirror.com/streamsearch/-/streamsearch-1.1.0.tgz" resolved "https://registry.npmmirror.com/streamsearch/-/streamsearch-1.1.0.tgz"
integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==
string_decoder@^1.3.0:
version "1.3.0"
resolved "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.3.0.tgz"
integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==
dependencies:
safe-buffer "~5.2.0"
string-argv@^0.3.1: string-argv@^0.3.1:
version "0.3.1" version "0.3.1"
resolved "https://registry.npmmirror.com/string-argv/-/string-argv-0.3.1.tgz" resolved "https://registry.npmmirror.com/string-argv/-/string-argv-0.3.1.tgz"
@ -8320,11 +8470,29 @@ yaml@^2.2.2:
resolved "https://registry.npmmirror.com/yaml/-/yaml-2.3.1.tgz" resolved "https://registry.npmmirror.com/yaml/-/yaml-2.3.1.tgz"
integrity sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ== integrity sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==
yargs-parser@^20.2.2:
version "20.2.9"
resolved "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-20.2.9.tgz"
integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==
yargs-parser@^21.1.1: yargs-parser@^21.1.1:
version "21.1.1" version "21.1.1"
resolved "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-21.1.1.tgz" resolved "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-21.1.1.tgz"
integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==
yargs@^16.0.3:
version "16.2.0"
resolved "https://registry.npmmirror.com/yargs/-/yargs-16.2.0.tgz"
integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==
dependencies:
cliui "^7.0.2"
escalade "^3.1.1"
get-caller-file "^2.0.5"
require-directory "^2.1.1"
string-width "^4.2.0"
y18n "^5.0.5"
yargs-parser "^20.2.2"
yargs@^17.3.1, yargs@^17.7.2: yargs@^17.3.1, yargs@^17.7.2:
version "17.7.2" version "17.7.2"
resolved "https://registry.npmmirror.com/yargs/-/yargs-17.7.2.tgz" resolved "https://registry.npmmirror.com/yargs/-/yargs-17.7.2.tgz"