Add streamer pause/resume feature and UI updates

Introduces a pause/resume button for streamers in the UI and backend, including API endpoint and status handling. Refactors streamer status display, updates related TypeScript interfaces, and improves modal forms. Also updates @douyinfe/semi-ui and related dependencies to v2.86.0, and bumps workspace version to 1.1.10. Minor logging and trait changes in Rust backend for improved clarity and error handling.
This commit is contained in:
ForgQi committed 2025-09-29 03:18:21 +08:00
1 parent d4b6a5fb22
commit 92b5a30df5
21 files changed
+270 -148

No files matched your search

Generated
+2 -2
View File
@@ -395,7 +395,7 @@ dependencies = [
[[package]]
name = "biliup-cli"
version = "1.1.9"
version = "1.1.10"
dependencies = [
"async-channel",
"async-trait",
@@ -3641,7 +3641,7 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]]
name = "stream-gears"
version = "1.1.9"
version = "1.1.10"
dependencies = [
"async-channel",
"async-trait",
+1 -1
View File
@@ -7,7 +7,7 @@ members = [
resolver = "3"
[workspace.package]
version = "1.1.9"
version = "1.1.10"
[workspace.dependencies]
serde = { version = "1.0", features = ["derive"] }
+41 -25
View File
@@ -1,21 +1,21 @@
'use client'
import {
Layout,
Nav,
Button,
Tag,
Typography,
Popconfirm,
Notification,
Card,
Layout,
Nav,
Button,
Tag,
Typography,
Popconfirm,
Notification,
Card, Dropdown,
} from '@douyinfe/semi-ui'
import {
IconHelpCircle,
IconPlusCircle,
IconVideoListStroked,
IconEdit2Stroked,
IconDeleteStroked,
IconWrench,
IconHelpCircle,
IconPlusCircle,
IconVideoListStroked,
IconEdit2Stroked,
IconDeleteStroked,
IconWrench, IconTreeTriangleDown, IconPause, IconPlay,
} from '@douyinfe/semi-icons'
import { List, ButtonGroup } from '@douyinfe/semi-ui'
import React, { useState } from 'react'
@@ -24,6 +24,7 @@ import TemplateModal from '../../ui/TemplateModal'
import OverrideModal from '../../ui/OverrideModal'
import { LiveStreamerEntity, put, requestDelete, sendRequest } from '../../lib/api-streamer'
import useSWRMutation from 'swr/mutation'
import {PauseButton} from "@/app/ui/StreamerActions/PauseButton";
export default function Home() {
const { Header, Content } = Layout
@@ -54,25 +55,28 @@ export default function Home() {
return values
}
const data: LiveStreamerEntity[] | undefined = streamers?.map(live => {
let status
let statusTag
switch (live.status) {
case 'Working':
status = <Tag color="red">直播中</Tag>
statusTag = <Tag color="red">直播中</Tag>
break
case 'Idle':
status = <Tag color="green">空闲</Tag>
statusTag = <Tag color="green">空闲</Tag>
break
case 'Pending':
status = <Tag color="grey">未知</Tag>
statusTag = <Tag color="grey">等待下载</Tag>
break
case 'Inspecting':
status = <Tag color="indigo">检测/上传中</Tag>
statusTag = <Tag color="indigo">检测/上传中</Tag>
break
case 'OutOfSchedule':
status = <Tag color="green">非录播时间</Tag>
statusTag = <Tag color="green">非录播时间</Tag>
break
case 'Pause':
statusTag = <Tag color="pink">暂停中</Tag>
break
}
return { ...handleEntityPostprocessor(live), status }
return { ...handleEntityPostprocessor(live), statusTag }
})
const handleOk = async (values: any) => {
@@ -96,6 +100,7 @@ export default function Home() {
const handleUpdate = async (values: any) => {
console.log(values);
delete values.status
delete values.statusTag
if (values?.postprocessor) {
values.postprocessor = values.postprocessor.map(
({ cmd, value }: { cmd: string; value: string }) => (cmd === 'rm' ? 'rm' : { [cmd]: value })
@@ -112,6 +117,7 @@ export default function Home() {
throw e
}
}
return (
<>
<Header
@@ -214,7 +220,7 @@ export default function Home() {
}
}
>
<div style={{ position: 'absolute', right: 20, top: 9 }}>{item.status}</div>
<div style={{ position: 'absolute', right: 20, top: 9 }}>{item.statusTag}</div>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<h3
style={{
@@ -248,9 +254,7 @@ export default function Home() {
<Button theme="borderless" icon={<IconEdit2Stroked />}></Button>
</TemplateModal>
<span className="semi-button-group-line semi-button-group-line-borderless semi-button-group-line-primary"></span>
<OverrideModal onOk={handleUpdate} entity={item}>
<Button theme="borderless" icon={<IconWrench />}></Button>
</OverrideModal>
<PauseButton streamer={item}/>
<span className="semi-button-group-line semi-button-group-line-borderless semi-button-group-line-primary"></span>
<Popconfirm
title="确定是否要删除?"
@@ -260,6 +264,18 @@ export default function Home() {
>
<Button theme="borderless" icon={<IconDeleteStroked />}></Button>
</Popconfirm>
<span className="semi-button-group-line semi-button-group-line-borderless semi-button-group-line-primary"></span>
<Dropdown render={
<Dropdown.Menu>
<Dropdown.Item>
<OverrideModal onOk={handleUpdate} entity={item}>
<Button theme="borderless" icon={<IconWrench />}></Button>
</OverrideModal>
</Dropdown.Item>
</Dropdown.Menu>
} trigger="click" position="bottomRight">
<Button theme="borderless" type="primary" icon={<IconTreeTriangleDown />}></Button>
</Dropdown>
</ButtonGroup>
</div>
</Card>
+2 -1
View File
@@ -100,7 +100,8 @@ export interface LiveStreamerEntity {
split_time?: number;
split_size?: number;
upload_id?: number;
status?: string | React.ReactNode;
status?: string;
statusTag?: React.ReactNode;
format?: string;
time_range?: string | Date[];
excluded_keywords?: string[];
-6
View File
@@ -222,12 +222,6 @@ const OverrideModal: React.FC<TemplateModalProps> = ({ children, entity, onOk })
stopValidateWithError={true}
/>
<Form.Input
field="filename_prefix"
label="文件名模板(filename_prefix)"
placeholder="{streamer}%Y-%m-%dT%H_%M_%S"
/>
<Form.InputNumber
field="filtering_threshold"
label="碎片过滤(filtering_threshold)"
+52
View File
@@ -0,0 +1,52 @@
import React, { useState } from 'react';
import { useSWRConfig } from 'swr';
import useSWRMutation from 'swr/mutation';
import {Button} from "@douyinfe/semi-ui";
import {IconPause, IconPlay} from "@douyinfe/semi-icons";
import {API_BASE, LiveStreamerEntity} from "@/app/lib/api-streamer";
interface PauseButtonProps {
streamer: LiveStreamerEntity;
onSuccess?: () => void;
onError?: (error: Error) => void;
}
// 暂停主播
export const pauseStreamer = async (url: string, ) => {
const response = await fetch(API_BASE + url,
{
method: 'PUT',
// headers: {'Content-Type': 'application/json'},
}
);
return response;
};
export const PauseButton: React.FC<PauseButtonProps> = ({
streamer,
onSuccess,
onError
}) => {
const { mutate } = useSWRConfig();
const { trigger: pauseTrigger } = useSWRMutation(
`/v1/streamers/${streamer.id}/pause`,
pauseStreamer
);
const handlePause = async () => {
try {
await pauseTrigger();
// 重新加载列表数据
await mutate('/v1/streamers');
onSuccess?.();
} catch (error) {
console.error('暂停失败:', error);
onError?.(error as Error);
}
};
console.log("????????????", streamer.status)
return (
<Button onClick={handlePause} icon={streamer.status === 'Pause'? <IconPlay />: <IconPause />} theme="borderless" aria-label="暂停" />
);
};
+6
View File
@@ -155,6 +155,12 @@ const TemplateModal: React.FC<TemplateModalProps> = ({ children, entity, onOk })
rules={[{ required: true, message }]}
/>
<Form.Input
field="filename_prefix"
label="文件名模板(可选)"
placeholder="{streamer}%Y-%m-%dT%H_%M_%S"
/>
<Form.Select
showClear
field="upload_streamers_id"
+55 -4
View File
@@ -5,7 +5,7 @@ use crate::server::config::Config;
use crate::server::core::download_manager::ActorHandle;
use crate::server::errors::{report_to_response, AppError};
use crate::server::infrastructure::connection_pool::ConnectionPool;
use crate::server::infrastructure::context::Worker;
use crate::server::infrastructure::context::{Worker, WorkerStatus};
use crate::server::infrastructure::dto::LiveStreamerResponse;
use crate::server::infrastructure::models::{
Configuration, FileItem, InsertConfiguration,
@@ -22,7 +22,7 @@ use axum::response::{IntoResponse, Response};
use axum::{debug_handler, Json};
use biliup::credential::Credential;
use clap::ValueEnum;
use error_stack::{Report, ResultExt};
use error_stack::{bail, Report, ResultExt};
use ormlite::{Insert, Model};
use serde::Deserialize;
use serde_json::json;
@@ -36,6 +36,7 @@ use tokio::fs;
use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncSeekExt, AsyncWriteExt, BufReader};
use tokio::time::{interval, MissedTickBehavior};
use tracing::{debug, error, info};
use crate::server::core::downloader::Downloader;
use crate::server::infrastructure::models::live_streamer::{InsertLiveStreamer, LiveStreamer};
use crate::server::infrastructure::models::upload_streamer::{InsertUploadStreamer, UploadStreamer};
@@ -94,7 +95,7 @@ pub async fn post_streamers_endpoint(
.await
.map_err(report_to_response)?;
info!(workers=?service_register.workers, "successfully inserted new live streamers");
info!(workers=?live_streamers, "successfully inserted new live streamers");
Ok(Json(live_streamers))
}
@@ -141,10 +142,60 @@ pub async fn delete_streamers_endpoint(
.await
.map_err(report_to_response)?;
let live_streamers = del_streamer(&pool, id).await.map_err(report_to_response)?;
info!(workers=?service_register.workers, "successfully inserted new live streamers");
info!(workers=?live_streamers, "successfully inserted new live streamers");
Ok(Json(live_streamers))
}
#[axum::debug_handler(state = ServiceRegister)]
pub async fn pause_streamers_endpoint(
State(service_register): State<ServiceRegister>,
State(pool): State<ConnectionPool>,
State(workers): State<Arc<RwLock<Vec<Arc<Worker>>>>>,
Path(id): Path<i64>,
) -> Result<Json<()>, Response> {
let option = workers
.read()
.unwrap()
.clone()
.into_iter()
.find(|worker| {
worker.live_streamer.id == id
});
if let Some(w) = option {
let manager = service_register.get_manager(&w.live_streamer.url).ok_or(AppError::Unknown).map_err(report_to_response)?;
let monitor = manager.ensure_monitor(pool.clone());
let worker_status = w.downloader_status.read().unwrap().clone();
let d = match worker_status {
WorkerStatus::Working(d) => {
*w.downloader_status.write().unwrap() = WorkerStatus::Pause;
Some(d.clone())
},
WorkerStatus::Pending => {
monitor.rooms_handle.toggle(w.clone()).await;
*w.downloader_status.write().unwrap() = WorkerStatus::Pause;
None
}
WorkerStatus::Idle => {
monitor.rooms_handle.toggle(w.clone()).await;
*w.downloader_status.write().unwrap() = WorkerStatus::Pause;
None
}
WorkerStatus::Pause => {
monitor.rooms_handle.toggle(w.clone()).await;
*w.downloader_status.write().unwrap() = WorkerStatus::Idle;
None
}
};
if let Some(d) = d {
d.stop().await.map_err(report_to_response)?;
}
info!(workers=?service_register.workers, "successfully pause live streamers");
}
Ok(Json(()))
}
pub async fn get_configuration(
State(config): State<Arc<RwLock<Config>>>,
) -> Result<Json<Config>, Response> {
@@ -57,10 +57,6 @@ impl DownloadGuard {
impl Drop for DownloadGuard {
fn drop(&mut self) {
// 确保状态更新和资源清理
self.worker
.change_status(Stage::Download, WorkerStatus::Idle);
// 异步清理任务
let danmaku = self.danmaku_client.clone();
let rooms_handle = self.rooms_handle.clone();
@@ -72,7 +68,13 @@ impl Drop for DownloadGuard {
error!("Error stopping danmaku client: {}", e);
}
}
rooms_handle.toggle(worker).await
if let WorkerStatus::Pause = *worker.downloader_status.read().unwrap() {
} else {
// 确保状态更新和资源清理
worker
.change_status(Stage::Download, WorkerStatus::Idle);
rooms_handle.toggle(worker).await;
};
});
}
}
@@ -318,7 +320,7 @@ impl DownloadTask {
.unwrap_or_else(|| stream_info.suffix.clone());
// 创建录制器
let recorder = Recorder::new(
streamer.filename_prefix,
streamer.filename_prefix.or(config.filename_prefix.clone()),
&streamer.remark,
&stream_info.streamer_info.title,
&suffix,
@@ -151,13 +151,13 @@ async fn upload_single_file(file_path: &Path, context: &UploadContext) -> AppRes
let video_path = file_path;
info!(
"{:?}",
"开始上传文件:{:?}",
video_path
.canonicalize()
.change_context(AppError::Unknown)?
.to_str()
);
info!("{line:?}");
info!("线路选择:{line:?}");
let video_file = VideoFile::new(video_path).change_context(AppError::Unknown)?;
let total_size = video_file.total_size;
let file_name = video_file.file_name.clone();
@@ -274,11 +274,11 @@ pub async fn execute_postprocessor(video_paths: Vec<PathBuf>, ctx: &Context) ->
let result = InsertFileItem {
file,
streamer_info_id: 0,
}.insert(&pool).await;
}.insert(&pool).await;
info!(result=?result);
});
p.as_path()
p.as_path()
}).collect();
process_video(&paths, &processor).await?;
}
@@ -134,7 +134,7 @@ pub trait Downloader: Send + Sync {
) -> AppResult<DownloadStatus>;
/// 停止下载
async fn stop(&self) -> Result<(), Box<dyn std::error::Error>>;
async fn stop(&self) -> AppResult<()>;
/// 滚动保存(用于弹幕等)
///
@@ -400,10 +400,10 @@ impl Downloader for FfmpegDownloader {
}
}
async fn stop(&self) -> Result<(), Box<dyn std::error::Error>> {
async fn stop(&self) -> AppResult<()> {
let mut handle = self.process_handle.write().await;
if let Some(child) = &mut *handle {
child.kill().await?;
child.kill().await.change_context(AppError::Unknown)?;
Ok(())
} else {
Err(AppError::Custom("Process handle not found".to_string()).into())
@@ -149,7 +149,7 @@ impl Downloader for StreamGears {
}
/// 停止下载
async fn stop(&self) -> Result<(), Box<dyn std::error::Error>> {
async fn stop(&self) -> AppResult<()> {
// 仅发出取消信号并更新状态
// 如果底层下载函数不支持取消,这里不能真正中断正在进行的下载
self.token.cancel();
+2 -3
View File
@@ -31,9 +31,8 @@ async fn start_client(
loop {
// 获取下一个要检查的房间
if let Some(room) = rooms_handle.next().await {
let ulr = room.get_streamer().url;
let url = room.get_streamer().url;
interval = room.get_config().event_loop_interval;
info!("[{platform_name}] room: {ulr}");
let mut ctx = Context::new(room.clone(), Default::default(), pool.clone());
// 检查直播状态
match plugin.check_status(&mut ctx).await.unwrap() {
@@ -52,7 +51,7 @@ async fn start_client(
continue
}
};
info!("room: {ulr} is live -> {:?}", stream_info);
info!(url=url, "room: is live -> 开播了");
// 更新状态为等待中
room.change_status(Stage::Download, WorkerStatus::Pending);
stream_info.streamer_info = insert;
@@ -147,7 +147,7 @@ pub enum Stage {
}
/// 工作器状态枚举
#[derive(Default)]
#[derive(Default, Clone)]
pub enum WorkerStatus {
/// 正在工作
Working(Arc<dyn Downloader>),
@@ -156,6 +156,8 @@ pub enum WorkerStatus {
/// 空闲状态(默认)
#[default]
Idle,
/// 下载暂停中
Pause
}
// 简单 Debug:打印状态名,忽略内部 downloader
@@ -165,6 +167,7 @@ impl fmt::Debug for WorkerStatus {
WorkerStatus::Working(_) => "Working",
WorkerStatus::Pending => "Pending",
WorkerStatus::Idle => "Idle",
WorkerStatus::Pause => "Pause"
};
f.write_str(name)
}
+3 -8
View File
@@ -1,19 +1,13 @@
use crate::server::api::bilibili_endpoints::{
archive_pre_endpoint, get_myinfo_endpoint, get_proxy_endpoint,
};
use crate::server::api::endpoints::{
add_upload_streamer_endpoint, add_user_endpoint, delete_streamers_endpoint,
delete_template_endpoint, delete_user_endpoint, get_configuration, get_qrcode, get_status,
get_streamer_info, get_streamers_endpoint, get_upload_streamer_endpoint,
get_upload_streamers_endpoint, get_users_endpoint, get_videos, login_by_qrcode,
post_streamers_endpoint, post_uploads, put_configuration, put_streamers_endpoint,
};
use crate::server::api::endpoints::{add_upload_streamer_endpoint, add_user_endpoint, delete_streamers_endpoint, delete_template_endpoint, delete_user_endpoint, get_configuration, get_qrcode, get_status, get_streamer_info, get_streamers_endpoint, get_upload_streamer_endpoint, get_upload_streamers_endpoint, get_users_endpoint, get_videos, login_by_qrcode, pause_streamers_endpoint, post_streamers_endpoint, post_uploads, put_configuration, put_streamers_endpoint};
use crate::server::infrastructure::service_register::ServiceRegister;
use axum::Router;
use axum::body::Body;
use axum::http::Request;
use axum::response::IntoResponse;
use axum::routing::{delete, get, post};
use axum::routing::{delete, get, post, put};
use tower::ServiceExt;
use tower_http::services::ServeFile;
/// 创建应用程序路由
@@ -27,6 +21,7 @@ pub fn router(service_register: ServiceRegister) -> Router<()> {
.put(put_streamers_endpoint), // 更新主播
)
.route("/v1/streamers/{id}", delete(delete_streamers_endpoint)) // 删除主播
.route("/v1/streamers/{id}/pause", put(pause_streamers_endpoint)) // 删除主播
// 配置管理路由
.route(
"/v1/configuration",
+4 -4
View File
@@ -15,7 +15,7 @@ use rsa::{Pkcs1v15Encrypt, RsaPublicKey, pkcs8::DecodePublicKey};
use serde::{Deserialize, Serialize};
use serde_json::{Value, json};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use tracing::info;
use tracing::{debug, info};
use url::Url;
// const APP_KEY: &str = "ae57252b0c09105d";
@@ -63,7 +63,7 @@ pub fn bilibili_from_cookies(file: impl AsRef<Path>, proxy: Option<&str>) -> Res
pub fn bilibili_from_info(login_info: LoginInfo, proxy: Option<&str>) -> Result<BiliBili> {
let client = Credential::new(proxy);
client.set_cookie(&login_info.cookie_info);
info!("通过cookie登录");
debug!("通过cookie登录");
Ok(BiliBili {
client: client.0.client,
login_info,
@@ -85,7 +85,7 @@ pub async fn login_by_cookies(file: impl AsRef<Path>, proxy: Option<&str>) -> Re
serde_json::to_writer_pretty(std::io::BufWriter::new(&file), &new_info)?;
bilibili_from_info(new_info, proxy)
} else {
info!("无需更新cookie");
debug!("无需更新cookie");
bilibili_from_info(login_info, proxy)
}
}
@@ -174,7 +174,7 @@ impl Credential {
_ => return Err(Kind::Custom(response.to_string())),
};
info!("验证cookie");
debug!("验证cookie");
Ok(refresh)
}
+2 -2
View File
@@ -49,7 +49,7 @@ impl Downloader for DanmakuClient {
}
/// Stops the danmaku recording
async fn stop(&self) -> Result<(), Box<dyn std::error::Error>> {
async fn stop(&self) -> AppResult<()> {
let py_client = self.py_client.clone();
// Call the DanmakuClient's stop method (not the trait method)
tokio::task::spawn_blocking(move || {
@@ -59,7 +59,7 @@ impl Downloader for DanmakuClient {
Ok::<_, PyErr>(())
})
})
.await??;
.await.change_context(AppError::Unknown)?.change_context(AppError::Unknown)?;
Ok(())
}
+1 -2
View File
@@ -72,7 +72,6 @@ impl DownloadPlugin for PyPlugin {
}
async fn check_status(&self, ctx: &mut Context) -> Result<StreamStatus, Report<AppError>> {
info!("Checking status");
match call_via_threads(self.plugin.clone(), &ctx.worker.live_streamer.url)
.await
.change_context(AppError::Unknown)?
@@ -250,7 +249,7 @@ pub fn stream_info_from_py(
// lt.to_object(py)
// }
// };self.update_headers(self.stream_headers)
Ok((
StreamInfoExt {
streamer_info: StreamerInfo {
+77 -73
View File
@@ -8,7 +8,7 @@
"name": "my-app",
"version": "0.1.0",
"dependencies": {
"@douyinfe/semi-ui": "^2.75.0",
"@douyinfe/semi-ui": "^2.86.0",
"artplayer": "^5.2.1",
"mpegts.js": "^1.8.0",
"next": "^14.2.26",
@@ -97,39 +97,39 @@
}
},
"node_modules/@douyinfe/semi-animation": {
"version": "2.77.1",
"resolved": "https://registry.npmjs.org/@douyinfe/semi-animation/-/semi-animation-2.77.1.tgz",
"integrity": "sha512-Q1D7whvQe0D+mPov8hXeH/e1uR/iBhpGGcW1LCTL2pSVMEZEYGJLf2KeXTTiCIgRVWm0PRH3Sux7auJ64zg7vw==",
"version": "2.86.0",
"resolved": "https://registry.npmjs.org/@douyinfe/semi-animation/-/semi-animation-2.86.0.tgz",
"integrity": "sha512-+MerLT6TJnJSbTnKQ5Za1HwxiM7snhfMdg2IX/0KXxwFYhkHsnkj07krZBNKMKWDJpfG2qkb9z7KD/NQH7KL6w==",
"license": "MIT",
"dependencies": {
"bezier-easing": "^2.1.0"
}
},
"node_modules/@douyinfe/semi-animation-react": {
"version": "2.77.1",
"resolved": "https://registry.npmjs.org/@douyinfe/semi-animation-react/-/semi-animation-react-2.77.1.tgz",
"integrity": "sha512-imELR02pufgGFkZURfTd9oBUtZPYhHvXv9WsYoRvEoBM9U7yzxrR6Fb/Lc3TH+WHVJ2oZHH2S0APS5t1MceEOw==",
"version": "2.86.0",
"resolved": "https://registry.npmjs.org/@douyinfe/semi-animation-react/-/semi-animation-react-2.86.0.tgz",
"integrity": "sha512-nThSOLD6iU8gyNALDYAsbXgcmM9eAJe7F1Sb5zvOi56cA4M52Wk50AuS9InAbGTJjzxNY7FkBu1KMepxXDHEIg==",
"license": "MIT",
"dependencies": {
"@douyinfe/semi-animation": "2.77.1",
"@douyinfe/semi-animation-styled": "2.77.1",
"@douyinfe/semi-animation": "2.86.0",
"@douyinfe/semi-animation-styled": "2.86.0",
"classnames": "^2.2.6"
}
},
"node_modules/@douyinfe/semi-animation-styled": {
"version": "2.77.1",
"resolved": "https://registry.npmjs.org/@douyinfe/semi-animation-styled/-/semi-animation-styled-2.77.1.tgz",
"integrity": "sha512-FBRroqVJroel1CXmBgV58ulZHG2xUVInJF7k0FAag54noKKaToEobSxRjiTJ6JHne3ZDU1M6sBqpbzYJElFnPQ==",
"version": "2.86.0",
"resolved": "https://registry.npmjs.org/@douyinfe/semi-animation-styled/-/semi-animation-styled-2.86.0.tgz",
"integrity": "sha512-5x5WA8i1vuwb7rygScWmqxwgNggvpGd0rtDwAqBX8l5KoDSml2ikmR015w+SaoOD/UGwJB44IJ02UYYcxZw9kg==",
"license": "MIT"
},
"node_modules/@douyinfe/semi-foundation": {
"version": "2.77.1",
"resolved": "https://registry.npmjs.org/@douyinfe/semi-foundation/-/semi-foundation-2.77.1.tgz",
"integrity": "sha512-DAXRy8ryLNzbKAiTAv+RrivGCoMU0asv2cO7PNV5aBq0ICB8XXn97FHyZo6Wb5NpqpyMhOaOr8Ro1bfpd0FeaA==",
"version": "2.86.0",
"resolved": "https://registry.npmjs.org/@douyinfe/semi-foundation/-/semi-foundation-2.86.0.tgz",
"integrity": "sha512-IX6Vzcen16u+Z1fbYPCAnZIqxfnAk7kI9CBJ9u6rZM8LIEP/HMYSEdMmCg8Cigw4GiSbjdlDfy6miy5tXWJmAQ==",
"license": "MIT",
"dependencies": {
"@douyinfe/semi-animation": "2.77.1",
"@douyinfe/semi-json-viewer-core": "2.77.1",
"@douyinfe/semi-animation": "2.86.0",
"@douyinfe/semi-json-viewer-core": "2.86.0",
"@mdx-js/mdx": "^3.0.1",
"async-validator": "^3.5.0",
"classnames": "^2.2.6",
@@ -145,9 +145,9 @@
}
},
"node_modules/@douyinfe/semi-icons": {
"version": "2.77.1",
"resolved": "https://registry.npmjs.org/@douyinfe/semi-icons/-/semi-icons-2.77.1.tgz",
"integrity": "sha512-IbGqYzbjzCoSd+//HlO/Gn1c3XmbulQwGys+JgDfQhYIbPeGyhQfLk56Q7ku3vJGC8BGy7dUmR9MbeTf1UQGtw==",
"version": "2.86.0",
"resolved": "https://registry.npmjs.org/@douyinfe/semi-icons/-/semi-icons-2.86.0.tgz",
"integrity": "sha512-KEDlYYP1wdOqN28Ck0YcdCx7mSks8SRY4w4KKbXPaROzYNEyT2BRcJxwysMHfxL2IDfsroHrRPJsX9pnrmQqTg==",
"license": "MIT",
"dependencies": {
"classnames": "^2.2.6"
@@ -157,44 +157,44 @@
}
},
"node_modules/@douyinfe/semi-illustrations": {
"version": "2.77.1",
"resolved": "https://registry.npmjs.org/@douyinfe/semi-illustrations/-/semi-illustrations-2.77.1.tgz",
"integrity": "sha512-FlESLOPaY0SadiSIFcP4gqJUk+CYkd4rHK6YP9bfjmU26v7h1S02H7pGLLV1lS0WnY4j0ad4zqRV9tbXFvba9g==",
"version": "2.86.0",
"resolved": "https://registry.npmjs.org/@douyinfe/semi-illustrations/-/semi-illustrations-2.86.0.tgz",
"integrity": "sha512-ZRgtgPor1X9lZP/8IOT4xZOmio+tWBUb9Sb2MAjTj52ZtJB6u4jz34y4TnoqRjoGRYWwNvOQmNjBk9NiHoAYkg==",
"license": "MIT",
"peerDependencies": {
"react": ">=16.0.0"
}
},
"node_modules/@douyinfe/semi-json-viewer-core": {
"version": "2.77.1",
"resolved": "https://registry.npmjs.org/@douyinfe/semi-json-viewer-core/-/semi-json-viewer-core-2.77.1.tgz",
"integrity": "sha512-LOW+7ga2OzFIL9pGKftwHfl1kKLTV3x6Cs857iyvq9GIF/GHbAboiHcKUy2OZIHfy66zvP+Focs+yhfZG7IcZw==",
"version": "2.86.0",
"resolved": "https://registry.npmjs.org/@douyinfe/semi-json-viewer-core/-/semi-json-viewer-core-2.86.0.tgz",
"integrity": "sha512-Xk8CkQ/VKACUZfLg9D5vds9vy2WvWzI5ezioBNKRF5GPnonYeL6vwFz0rpzSL4dKdS17Ndg5KexWV4g9mExgLg==",
"license": "MIT",
"dependencies": {
"jsonc-parser": "^3.3.1"
}
},
"node_modules/@douyinfe/semi-theme-default": {
"version": "2.77.1",
"resolved": "https://registry.npmjs.org/@douyinfe/semi-theme-default/-/semi-theme-default-2.77.1.tgz",
"integrity": "sha512-Rug75C7jjSqmCP2L2tBI0K4dnXuo4GardzwSzdSjxDkiaIXwOwR5KE0K1FRbKWkQ7xmxbyRu4S6Pff+CDEJ/lA==",
"version": "2.86.0",
"resolved": "https://registry.npmjs.org/@douyinfe/semi-theme-default/-/semi-theme-default-2.86.0.tgz",
"integrity": "sha512-l1zVriNXVxBribjTL7hGyiHrymum3UOPpiurWZdleK7oKWPVtkWdDh+cdVkDS/QynEY7P+m0zFCye1GbjNPkNg==",
"license": "MIT"
},
"node_modules/@douyinfe/semi-ui": {
"version": "2.77.1",
"resolved": "https://registry.npmjs.org/@douyinfe/semi-ui/-/semi-ui-2.77.1.tgz",
"integrity": "sha512-eIy7kr9OleCwlNRby3VICSGScHM23Zt2u7TJpID68qN3WrfQowGaB4wQ/0k5bvpLzv463HQnVWFk5aak+v46yw==",
"version": "2.86.0",
"resolved": "https://registry.npmjs.org/@douyinfe/semi-ui/-/semi-ui-2.86.0.tgz",
"integrity": "sha512-UAwM3+qO25Zkvy3a+7Jk8xBt5/103eP/t9x2pqvRITLeP2XqIikXruXV3bSLO1HDlCt9VyORqxC9UEYFOutk5Q==",
"license": "MIT",
"dependencies": {
"@dnd-kit/core": "^6.0.8",
"@dnd-kit/sortable": "^7.0.2",
"@dnd-kit/utilities": "^3.2.1",
"@douyinfe/semi-animation": "2.77.1",
"@douyinfe/semi-animation-react": "2.77.1",
"@douyinfe/semi-foundation": "2.77.1",
"@douyinfe/semi-icons": "2.77.1",
"@douyinfe/semi-illustrations": "2.77.1",
"@douyinfe/semi-theme-default": "2.77.1",
"@douyinfe/semi-animation": "2.86.0",
"@douyinfe/semi-animation-react": "2.86.0",
"@douyinfe/semi-foundation": "2.86.0",
"@douyinfe/semi-icons": "2.86.0",
"@douyinfe/semi-illustrations": "2.86.0",
"@douyinfe/semi-theme-default": "2.86.0",
"async-validator": "^3.5.0",
"classnames": "^2.2.6",
"copy-text-to-clipboard": "^2.1.1",
@@ -414,15 +414,16 @@
"license": "MIT"
},
"node_modules/@mdx-js/mdx": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.1.0.tgz",
"integrity": "sha512-/QxEhPAvGwbQmy1Px8F899L5Uc2KZ6JtXwlCgJmjSTBedwOZkByYcBG4GceIGPXRDsmfxhHazuS+hlOShRLeDw==",
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.1.1.tgz",
"integrity": "sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==",
"license": "MIT",
"dependencies": {
"@types/estree": "^1.0.0",
"@types/estree-jsx": "^1.0.0",
"@types/hast": "^3.0.0",
"@types/mdx": "^2.0.0",
"acorn": "^8.0.0",
"collapse-white-space": "^2.0.0",
"devlop": "^1.0.0",
"estree-util-is-identifier-name": "^3.0.0",
@@ -1895,9 +1896,9 @@
}
},
"node_modules/decode-named-character-reference": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.1.0.tgz",
"integrity": "sha512-Wy+JTSbFThEOXQIR2L6mxJvEs+veIzpmqD7ynWxMXGpnk3smkHQOp6forLdHsKpAMW9iJpaBBIxz285t1n1C3w==",
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz",
"integrity": "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==",
"license": "MIT",
"dependencies": {
"character-entities": "^2.0.0"
@@ -4055,9 +4056,9 @@
}
},
"node_modules/lottie-web": {
"version": "5.12.2",
"resolved": "https://registry.npmjs.org/lottie-web/-/lottie-web-5.12.2.tgz",
"integrity": "sha512-uvhvYPC8kGPjXT3MyKMrL3JitEAmDMp30lVkuq/590Mw9ok6pWcFCwXJveo0t5uqYw1UREQHofD+jVpdjBv8wg==",
"version": "5.13.0",
"resolved": "https://registry.npmjs.org/lottie-web/-/lottie-web-5.13.0.tgz",
"integrity": "sha512-+gfBXl6sxXMPe8tKQm7qzLnUy5DUPJPKIyRHwtpCpyUEYjHYRJC/5gjUvdkuO2c3JllrPtHXH5UJJK8LRYl5yQ==",
"license": "MIT"
},
"node_modules/markdown-extensions": {
@@ -5647,9 +5648,9 @@
}
},
"node_modules/property-information": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/property-information/-/property-information-7.0.0.tgz",
"integrity": "sha512-7D/qOz/+Y4X/rzSB6jKxKUsQnphO046ei8qxG59mtM3RG3DHgTK81HrxrmoDVINJb8NKT5ZsRbwHvQ6B68Iyhg==",
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz",
"integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==",
"license": "MIT",
"funding": {
"type": "github",
@@ -5862,9 +5863,9 @@
}
},
"node_modules/recma-jsx": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/recma-jsx/-/recma-jsx-1.0.0.tgz",
"integrity": "sha512-5vwkv65qWwYxg+Atz95acp8DMu1JDSqdGkA2Of1j6rCreyFUE/gp15fC8MnGEuG1W68UKjM6x6+YTWIh7hZM/Q==",
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/recma-jsx/-/recma-jsx-1.0.1.tgz",
"integrity": "sha512-huSIy7VU2Z5OLv6oFLosQGGDqPqdO1iq6bWNAdhzMxSJP7RAso4fCZ1cKu8j9YHCZf3TPrq4dw3okhrylgcd7w==",
"license": "MIT",
"dependencies": {
"acorn-jsx": "^5.0.0",
@@ -5876,6 +5877,9 @@
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
},
"peerDependencies": {
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
"node_modules/recma-parse": {
@@ -5992,9 +5996,9 @@
}
},
"node_modules/remark-mdx": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-3.1.0.tgz",
"integrity": "sha512-Ngl/H3YXyBV9RcRNdlYsZujAmhsxwzxpDzpDEhFBVAGthS4GDgnctpDjgFl/ULx5UEDzqtW1cyBSNKqYYrqLBA==",
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-3.1.1.tgz",
"integrity": "sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg==",
"license": "MIT",
"dependencies": {
"mdast-util-mdx": "^3.0.0",
@@ -6022,9 +6026,9 @@
}
},
"node_modules/remark-rehype": {
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.1.tgz",
"integrity": "sha512-g/osARvjkBXb6Wo0XvAeXQohVta8i84ACbenPpoSsxTOQH/Ae0/RGP4WZgnMH5pMLpsj4FG7OHmcIcXxpza8eQ==",
"version": "11.1.2",
"resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz",
"integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==",
"license": "MIT",
"dependencies": {
"@types/hast": "^3.0.0",
@@ -6415,12 +6419,12 @@
}
},
"node_modules/source-map": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",
"integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==",
"version": "0.7.6",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz",
"integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==",
"license": "BSD-3-Clause",
"engines": {
"node": ">= 8"
"node": ">= 12"
}
},
"node_modules/source-map-js": {
@@ -6653,18 +6657,18 @@
}
},
"node_modules/style-to-js": {
"version": "1.1.16",
"resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.16.tgz",
"integrity": "sha512-/Q6ld50hKYPH3d/r6nr117TZkHR0w0kGGIVfpG9N6D8NymRPM9RqCUv4pRpJ62E5DqOYx2AFpbZMyCPnjQCnOw==",
"version": "1.1.17",
"resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.17.tgz",
"integrity": "sha512-xQcBGDxJb6jjFCTzvQtfiPn6YvvP2O8U1MDIPNfJQlWMYfktPy+iGsHE7cssjs7y84d9fQaK4UF3RIJaAHSoYA==",
"license": "MIT",
"dependencies": {
"style-to-object": "1.0.8"
"style-to-object": "1.0.9"
}
},
"node_modules/style-to-object": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.8.tgz",
"integrity": "sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==",
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.9.tgz",
"integrity": "sha512-G4qppLgKu/k6FwRpHiGiKPaPTFcG3g4wNVX/Qsfu+RqQM30E7Tyu/TEgxcL9PNLF5pdRLwQdE3YKKf+KF2Dzlw==",
"license": "MIT",
"dependencies": {
"inline-style-parser": "0.2.4"
@@ -7108,9 +7112,9 @@
}
},
"node_modules/vfile-message": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz",
"integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==",
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz",
"integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==",
"license": "MIT",
"dependencies": {
"@types/unist": "^3.0.0",
+1 -1
View File
@@ -9,7 +9,7 @@
"lint": "next lint"
},
"dependencies": {
"@douyinfe/semi-ui": "^2.75.0",
"@douyinfe/semi-ui": "^2.86.0",
"artplayer": "^5.2.1",
"mpegts.js": "^1.8.0",
"next": "^14.2.26",