chore(version): 0.9.0
This commit is contained in:
parent
7506d04c55
commit
d558572d97
16
.github/workflows/release.yml
vendored
16
.github/workflows/release.yml
vendored
@ -2,11 +2,6 @@ name: Release
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
|
||||||
version:
|
|
||||||
description: 'Version (e.g. v1.2.3)'
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- v*.*.*
|
- v*.*.*
|
||||||
@ -20,8 +15,7 @@ jobs:
|
|||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
# windows-latest, ubuntu-latest
|
os: [macos-13, macos-latest, windows-latest, ubuntu-latest]
|
||||||
os: [macos-13, macos-latest]
|
|
||||||
arch: [x64, arm64]
|
arch: [x64, arm64]
|
||||||
exclude:
|
exclude:
|
||||||
- os: windows-latest
|
- os: windows-latest
|
||||||
@ -41,6 +35,9 @@ jobs:
|
|||||||
node-version: 20
|
node-version: 20
|
||||||
arch: ${{ matrix.arch }}
|
arch: ${{ matrix.arch }}
|
||||||
|
|
||||||
|
- name: Install corepack
|
||||||
|
run: corepack enable && corepack prepare yarn@4.3.1 --activate
|
||||||
|
|
||||||
- name: Get yarn cache directory path
|
- name: Get yarn cache directory path
|
||||||
id: yarn-cache-dir-path
|
id: yarn-cache-dir-path
|
||||||
run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT
|
run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT
|
||||||
@ -55,9 +52,6 @@ jobs:
|
|||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-yarn-
|
${{ runner.os }}-yarn-
|
||||||
|
|
||||||
- name: Install corepack
|
|
||||||
run: corepack enable && corepack prepare yarn@4.3.1 --activate
|
|
||||||
|
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: yarn install
|
run: yarn install
|
||||||
|
|
||||||
@ -70,7 +64,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build Mac
|
- name: Build Mac
|
||||||
if: matrix.os == 'macos-13' || matrix.os == 'macos-latest'
|
if: matrix.os == 'macos-13' || matrix.os == 'macos-latest'
|
||||||
run: yarn build:mac
|
run: yarn build:mac && mv dist/latest-mac.yml dist/latest-mac-${{ matrix.arch }}.yml
|
||||||
env:
|
env:
|
||||||
CSC_LINK: ${{ secrets.CSC_LINK }}
|
CSC_LINK: ${{ secrets.CSC_LINK }}
|
||||||
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
|
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
|
||||||
|
|||||||
@ -48,47 +48,22 @@ mac:
|
|||||||
- NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder.
|
- NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder.
|
||||||
- NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder.
|
- NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder.
|
||||||
notarize: false
|
notarize: false
|
||||||
target:
|
|
||||||
- target: dmg
|
|
||||||
arch:
|
|
||||||
- arm64
|
|
||||||
- x64
|
|
||||||
- target: zip
|
|
||||||
arch:
|
|
||||||
- arm64
|
|
||||||
- x64
|
|
||||||
dmg:
|
dmg:
|
||||||
artifactName: ${productName}-${version}-${arch}.${ext}
|
artifactName: ${productName}-${version}-${arch}.${ext}
|
||||||
linux:
|
linux:
|
||||||
target:
|
target:
|
||||||
- target: AppImage
|
- target: AppImage
|
||||||
arch:
|
|
||||||
- arm64
|
|
||||||
- x64
|
|
||||||
# - snap
|
|
||||||
# - deb
|
|
||||||
maintainer: electronjs.org
|
maintainer: electronjs.org
|
||||||
category: Utility
|
category: Utility
|
||||||
appImage:
|
appImage:
|
||||||
artifactName: ${productName}-${version}-${arch}.${ext}
|
artifactName: ${productName}-${version}-${arch}.${ext}
|
||||||
npmRebuild: false
|
|
||||||
publish:
|
publish:
|
||||||
provider: generic
|
provider: generic
|
||||||
url: https://cherrystudio.ocool.online
|
url: https://cherrystudio.ocool.online
|
||||||
electronDownload:
|
electronDownload:
|
||||||
mirror: https://npmmirror.com/mirrors/electron/
|
mirror: https://npmmirror.com/mirrors/electron/
|
||||||
afterPack: scripts/removeLocales.js
|
afterPack: scripts/removeLocales.js
|
||||||
# afterSign: scripts/notarize.js
|
afterSign: scripts/notarize.js
|
||||||
releaseInfo:
|
releaseInfo:
|
||||||
releaseNotes: |
|
releaseNotes: |
|
||||||
增加小程序快捷入口
|
增加知识库功能
|
||||||
增加ThinkAny、纳米搜索小程序
|
|
||||||
优化 Markdown 列表显示
|
|
||||||
可以多次点击上传文件按钮上传文件
|
|
||||||
大屏幕默认使用更大的输入框
|
|
||||||
设置中增加显示设置模块
|
|
||||||
支持 SVG 预览
|
|
||||||
Mermaid 图表支持复制源码
|
|
||||||
增加复制最后一条消息快捷键
|
|
||||||
o1模型默认开启流式输出
|
|
||||||
长文本粘贴为文件支持修改阈值
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "CherryStudio",
|
"name": "CherryStudio",
|
||||||
"version": "0.8.27",
|
"version": "0.9.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "A powerful AI assistant for producer.",
|
"description": "A powerful AI assistant for producer.",
|
||||||
"main": "./out/main/index.js",
|
"main": "./out/main/index.js",
|
||||||
|
|||||||
@ -6,510 +6,558 @@ const config = {
|
|||||||
CACHE_KEY: 'cherry-studio-latest-release',
|
CACHE_KEY: 'cherry-studio-latest-release',
|
||||||
VERSION_DB: 'versions.json',
|
VERSION_DB: 'versions.json',
|
||||||
LOG_FILE: 'logs.json',
|
LOG_FILE: 'logs.json',
|
||||||
MAX_LOGS: 1000 // 最多保存多少条日志
|
MAX_LOGS: 1000 // 最多保存多少条日志
|
||||||
};
|
}
|
||||||
|
|
||||||
// Worker 入口函数
|
// Worker 入口函数
|
||||||
const worker = {
|
const worker = {
|
||||||
// 定时器触发配置
|
// 定时器触发配置
|
||||||
scheduled: {
|
scheduled: {
|
||||||
cron: '*/1 * * * *' // 每分钟执行一次
|
cron: '*/1 * * * *' // 每分钟执行一次
|
||||||
},
|
},
|
||||||
|
|
||||||
// 定时器执行函数 - 只负责检查和更新
|
// 定时器执行函数 - 只负责检查和更新
|
||||||
async scheduled(event, env, ctx) {
|
async scheduled(event, env, ctx) {
|
||||||
try {
|
try {
|
||||||
await initDataFiles(env);
|
await initDataFiles(env)
|
||||||
console.log('开始定时检查新版本...');
|
console.log('开始定时检查新版本...')
|
||||||
// 使用新的 checkNewRelease 函数
|
// 使用新的 checkNewRelease 函数
|
||||||
await checkNewRelease(env);
|
await checkNewRelease(env)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('定时任务执行失败:', error);
|
console.error('定时任务执行失败:', error)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// HTTP 请求处理函数 - 只负责返回数据
|
// HTTP 请求处理函数 - 只负责返回数据
|
||||||
async fetch(request, env, ctx) {
|
async fetch(request, env, ctx) {
|
||||||
if (!env || !env.R2_BUCKET) {
|
if (!env || !env.R2_BUCKET) {
|
||||||
return new Response(JSON.stringify({
|
return new Response(
|
||||||
error: 'R2 存储桶未正确配置'
|
JSON.stringify({
|
||||||
}), {
|
error: 'R2 存储桶未正确配置'
|
||||||
status: 500,
|
}),
|
||||||
headers: { 'Content-Type': 'application/json' }
|
{
|
||||||
});
|
status: 500,
|
||||||
|
headers: { 'Content-Type': 'application/json' }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = new URL(request.url)
|
||||||
|
const filename = url.pathname.slice(1)
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 处理文件下载请求
|
||||||
|
if (filename) {
|
||||||
|
return await handleDownload(env, filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = new URL(request.url);
|
// 只返回缓存的版本信息
|
||||||
const filename = url.pathname.slice(1);
|
return await getCachedRelease(env)
|
||||||
|
} catch (error) {
|
||||||
try {
|
return new Response(
|
||||||
// 处理文件下载请求
|
JSON.stringify({
|
||||||
if (filename) {
|
error: error.message,
|
||||||
return await handleDownload(env, filename);
|
stack: error.stack
|
||||||
}
|
}),
|
||||||
|
{
|
||||||
// 只返回缓存的版本信息
|
status: 500,
|
||||||
return await getCachedRelease(env);
|
headers: { 'Content-Type': 'application/json' }
|
||||||
} catch (error) {
|
}
|
||||||
return new Response(JSON.stringify({
|
)
|
||||||
error: error.message,
|
}
|
||||||
stack: error.stack
|
|
||||||
}), {
|
|
||||||
status: 500,
|
|
||||||
headers: { 'Content-Type': 'application/json' }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export default worker;
|
export default worker
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 添加日志记录函数
|
* 添加日志记录函数
|
||||||
*/
|
*/
|
||||||
async function addLog(env, type, event, details = null) {
|
async function addLog(env, type, event, details = null) {
|
||||||
try {
|
try {
|
||||||
const logFile = await env.R2_BUCKET.get(config.LOG_FILE);
|
const logFile = await env.R2_BUCKET.get(config.LOG_FILE)
|
||||||
let logs = { logs: [] };
|
let logs = { logs: [] }
|
||||||
|
|
||||||
if (logFile) {
|
if (logFile) {
|
||||||
logs = JSON.parse(await logFile.text());
|
logs = JSON.parse(await logFile.text())
|
||||||
}
|
}
|
||||||
|
|
||||||
logs.logs.unshift({
|
logs.logs.unshift({
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
type,
|
type,
|
||||||
event,
|
event,
|
||||||
details
|
details
|
||||||
});
|
})
|
||||||
|
|
||||||
// 保持日志数量在限制内
|
// 保持日志数量在限制内
|
||||||
if (logs.logs.length > config.MAX_LOGS) {
|
if (logs.logs.length > config.MAX_LOGS) {
|
||||||
logs.logs = logs.logs.slice(0, config.MAX_LOGS);
|
logs.logs = logs.logs.slice(0, config.MAX_LOGS)
|
||||||
}
|
}
|
||||||
|
|
||||||
await env.R2_BUCKET.put(config.LOG_FILE, JSON.stringify(logs, null, 2));
|
await env.R2_BUCKET.put(config.LOG_FILE, JSON.stringify(logs, null, 2))
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('写入日志失败:', error);
|
console.error('写入日志失败:', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取最新版本信息
|
* 获取最新版本信息
|
||||||
*/
|
*/
|
||||||
async function getLatestRelease(env) {
|
async function getLatestRelease(env) {
|
||||||
try {
|
try {
|
||||||
const cached = await env.R2_BUCKET.get(config.CACHE_KEY);
|
const cached = await env.R2_BUCKET.get(config.CACHE_KEY)
|
||||||
if (!cached) {
|
if (!cached) {
|
||||||
// 如果缓存不存在,先检查版本数据库
|
// 如果缓存不存在,先检查版本数据库
|
||||||
const versionDB = await env.R2_BUCKET.get(config.VERSION_DB);
|
const versionDB = await env.R2_BUCKET.get(config.VERSION_DB)
|
||||||
if (versionDB) {
|
if (versionDB) {
|
||||||
const versions = JSON.parse(await versionDB.text());
|
const versions = JSON.parse(await versionDB.text())
|
||||||
if (versions.latestVersion) {
|
if (versions.latestVersion) {
|
||||||
// 从版本数据库重建缓存
|
// 从版本数据库重建缓存
|
||||||
const latestVersion = versions.versions[versions.latestVersion];
|
const latestVersion = versions.versions[versions.latestVersion]
|
||||||
const cacheData = {
|
const cacheData = {
|
||||||
version: latestVersion.version,
|
version: latestVersion.version,
|
||||||
publishedAt: latestVersion.publishedAt,
|
publishedAt: latestVersion.publishedAt,
|
||||||
changelog: latestVersion.changelog,
|
changelog: latestVersion.changelog,
|
||||||
downloads: latestVersion.files
|
downloads: latestVersion.files
|
||||||
.filter(file => file.uploaded)
|
.filter((file) => file.uploaded)
|
||||||
.map(file => ({
|
.map((file) => ({
|
||||||
name: file.name,
|
name: file.name,
|
||||||
url: `https://${config.R2_CUSTOM_DOMAIN}/${file.name}`,
|
url: `https://${config.R2_CUSTOM_DOMAIN}/${file.name}`,
|
||||||
size: formatFileSize(file.size)
|
size: formatFileSize(file.size)
|
||||||
}))
|
}))
|
||||||
};
|
|
||||||
// 更新缓存
|
|
||||||
await env.R2_BUCKET.put(config.CACHE_KEY, JSON.stringify(cacheData));
|
|
||||||
return new Response(JSON.stringify(cacheData), {
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Access-Control-Allow-Origin': '*'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// 如果版本数据库也没有数据,才执行检查更新
|
// 更新缓存
|
||||||
const data = await checkNewRelease(env);
|
await env.R2_BUCKET.put(config.CACHE_KEY, JSON.stringify(cacheData))
|
||||||
return new Response(JSON.stringify(data), {
|
return new Response(JSON.stringify(cacheData), {
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Access-Control-Allow-Origin': '*'
|
'Access-Control-Allow-Origin': '*'
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
// 如果版本数据库也没有数据,才执行检查更新
|
||||||
|
const data = await checkNewRelease(env)
|
||||||
|
return new Response(JSON.stringify(data), {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Origin': '*'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const data = await cached.text();
|
const data = await cached.text()
|
||||||
return new Response(data, {
|
return new Response(data, {
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Access-Control-Allow-Origin': '*'
|
'Access-Control-Allow-Origin': '*'
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await addLog(env, 'ERROR', '获取版本信息失败', error.message);
|
await addLog(env, 'ERROR', '获取版本信息失败', error.message)
|
||||||
return new Response(JSON.stringify({
|
return new Response(
|
||||||
error: '获取版本信息失败: ' + error.message,
|
JSON.stringify({
|
||||||
detail: '请稍<E8AFB7><E7A88D><EFBFBD>再试'
|
error: '获取版本信息失败: ' + error.message,
|
||||||
}), {
|
detail: '请稍后再试'
|
||||||
status: 500,
|
}),
|
||||||
headers: {
|
{
|
||||||
'Content-Type': 'application/json',
|
status: 500,
|
||||||
'Access-Control-Allow-Origin': '*'
|
headers: {
|
||||||
}
|
'Content-Type': 'application/json',
|
||||||
});
|
'Access-Control-Allow-Origin': '*'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 修改下载处理函数,直接接收 env
|
// 修改下载处理函数,直接接收 env
|
||||||
async function handleDownload(env, filename) {
|
async function handleDownload(env, filename) {
|
||||||
try {
|
try {
|
||||||
const object = await env.R2_BUCKET.get(filename);
|
const object = await env.R2_BUCKET.get(filename)
|
||||||
|
|
||||||
if (!object) {
|
if (!object) {
|
||||||
return new Response('文件未找到', { status: 404 });
|
return new Response('文件未找到', { status: 404 })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置响应头
|
// 设置响应头
|
||||||
const headers = new Headers();
|
const headers = new Headers()
|
||||||
object.writeHttpMetadata(headers);
|
object.writeHttpMetadata(headers)
|
||||||
headers.set('etag', object.httpEtag);
|
headers.set('etag', object.httpEtag)
|
||||||
headers.set('Content-Disposition', `attachment; filename="${filename}"`);
|
headers.set('Content-Disposition', `attachment; filename="${filename}"`)
|
||||||
|
|
||||||
return new Response(object.body, {
|
return new Response(object.body, {
|
||||||
headers
|
headers
|
||||||
});
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('下载文件时发生错误:', error);
|
console.error('下载文件时发生错误:', error)
|
||||||
return new Response('获取文件失败', { status: 500 });
|
return new Response('获取文件失败', { status: 500 })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据文件扩展名获取对应的 Content-Type
|
* 根据文件扩展名获取对应的 Content-Type
|
||||||
*/
|
*/
|
||||||
function getContentType(filename) {
|
function getContentType(filename) {
|
||||||
const ext = filename.split('.').pop().toLowerCase();
|
const ext = filename.split('.').pop().toLowerCase()
|
||||||
const types = {
|
const types = {
|
||||||
'exe': 'application/x-msdownload', // Windows 可执行文件
|
exe: 'application/x-msdownload', // Windows 可执行文件
|
||||||
'dmg': 'application/x-apple-diskimage', // macOS 安装包
|
dmg: 'application/x-apple-diskimage', // macOS 安装包
|
||||||
'zip': 'application/zip', // 压缩包
|
zip: 'application/zip', // 压缩包
|
||||||
'AppImage': 'application/x-executable', // Linux 可执行文件
|
AppImage: 'application/x-executable', // Linux 可执行文件
|
||||||
'blockmap': 'application/octet-stream' // 更新文件
|
blockmap: 'application/octet-stream' // 更新文件
|
||||||
};
|
}
|
||||||
return types[ext] || 'application/octet-stream';
|
return types[ext] || 'application/octet-stream'
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 格式化文件大小
|
* 格式化文件大小
|
||||||
* 将字节转换为人类可读的格式(B, KB, MB, GB)
|
* 将字节转换为人类可读的格式(B, KB, MB, GB)
|
||||||
*/
|
*/
|
||||||
function formatFileSize(bytes) {
|
function formatFileSize(bytes) {
|
||||||
const units = ['B', 'KB', 'MB', 'GB'];
|
const units = ['B', 'KB', 'MB', 'GB']
|
||||||
let size = bytes;
|
let size = bytes
|
||||||
let unitIndex = 0;
|
let unitIndex = 0
|
||||||
|
|
||||||
while (size >= 1024 && unitIndex < units.length - 1) {
|
while (size >= 1024 && unitIndex < units.length - 1) {
|
||||||
size /= 1024;
|
size /= 1024
|
||||||
unitIndex++;
|
unitIndex++
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${size.toFixed(2)} ${units[unitIndex]}`;
|
return `${size.toFixed(2)} ${units[unitIndex]}`
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 版本号比较函数
|
* 版本号比较函数
|
||||||
* 用于对版本号进行排序
|
* 用于对版本号进行排序
|
||||||
*/
|
*/
|
||||||
function compareVersions(a, b) {
|
function compareVersions(a, b) {
|
||||||
const partsA = a.replace('v', '').split('.');
|
const partsA = a.replace('v', '').split('.')
|
||||||
const partsB = b.replace('v', '').split('.');
|
const partsB = b.replace('v', '').split('.')
|
||||||
|
|
||||||
for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {
|
for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {
|
||||||
const numA = parseInt(partsA[i] || 0);
|
const numA = parseInt(partsA[i] || 0)
|
||||||
const numB = parseInt(partsB[i] || 0);
|
const numB = parseInt(partsB[i] || 0)
|
||||||
|
|
||||||
if (numA !== numB) {
|
if (numA !== numB) {
|
||||||
return numA - numB;
|
return numA - numB
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 初始化数据文件
|
* 初始化数据文件
|
||||||
*/
|
*/
|
||||||
async function initDataFiles(env) {
|
async function initDataFiles(env) {
|
||||||
try {
|
try {
|
||||||
// 检查并初始化版本数据库
|
// 检查并初始化版本数据库
|
||||||
const versionDB = await env.R2_BUCKET.get(config.VERSION_DB);
|
const versionDB = await env.R2_BUCKET.get(config.VERSION_DB)
|
||||||
if (!versionDB) {
|
if (!versionDB) {
|
||||||
const initialVersions = {
|
const initialVersions = {
|
||||||
versions: {},
|
versions: {},
|
||||||
latestVersion: null,
|
latestVersion: null,
|
||||||
lastChecked: new Date().toISOString()
|
lastChecked: new Date().toISOString()
|
||||||
};
|
|
||||||
await env.R2_BUCKET.put(config.VERSION_DB, JSON.stringify(initialVersions, null, 2));
|
|
||||||
await addLog(env, 'INFO', 'versions.json 初始化成功');
|
|
||||||
}
|
}
|
||||||
|
await env.R2_BUCKET.put(config.VERSION_DB, JSON.stringify(initialVersions, null, 2))
|
||||||
|
await addLog(env, 'INFO', 'versions.json 初始化成功')
|
||||||
|
}
|
||||||
|
|
||||||
// 检查并初始化日志文件
|
// 检查并初始化日志文件
|
||||||
const logFile = await env.R2_BUCKET.get(config.LOG_FILE);
|
const logFile = await env.R2_BUCKET.get(config.LOG_FILE)
|
||||||
if (!logFile) {
|
if (!logFile) {
|
||||||
const initialLogs = {
|
const initialLogs = {
|
||||||
logs: [{
|
logs: [
|
||||||
timestamp: new Date().toISOString(),
|
{
|
||||||
type: 'INFO',
|
timestamp: new Date().toISOString(),
|
||||||
event: '系统初始化'
|
type: 'INFO',
|
||||||
}]
|
event: '系统初始化'
|
||||||
};
|
}
|
||||||
await env.R2_BUCKET.put(config.LOG_FILE, JSON.stringify(initialLogs, null, 2));
|
]
|
||||||
console.log('logs.json 初始化成功');
|
|
||||||
}
|
}
|
||||||
|
await env.R2_BUCKET.put(config.LOG_FILE, JSON.stringify(initialLogs, null, 2))
|
||||||
|
console.log('logs.json 初始化成功')
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('初始化数据文件失败:', error);
|
console.error('初始化数据文件失败:', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 新增:只获取缓存的版本信息
|
// 新增:只获取缓存的版本信息
|
||||||
async function getCachedRelease(env) {
|
async function getCachedRelease(env) {
|
||||||
try {
|
try {
|
||||||
const cached = await env.R2_BUCKET.get(config.CACHE_KEY);
|
const cached = await env.R2_BUCKET.get(config.CACHE_KEY)
|
||||||
if (!cached) {
|
if (!cached) {
|
||||||
// 如果缓存不存在,从版本数据库获取
|
// 如果缓存不存在,从版本数据库获取
|
||||||
const versionDB = await env.R2_BUCKET.get(config.VERSION_DB);
|
const versionDB = await env.R2_BUCKET.get(config.VERSION_DB)
|
||||||
if (versionDB) {
|
if (versionDB) {
|
||||||
const versions = JSON.parse(await versionDB.text());
|
const versions = JSON.parse(await versionDB.text())
|
||||||
if (versions.latestVersion) {
|
if (versions.latestVersion) {
|
||||||
const latestVersion = versions.versions[versions.latestVersion];
|
const latestVersion = versions.versions[versions.latestVersion]
|
||||||
const cacheData = {
|
const cacheData = {
|
||||||
version: latestVersion.version,
|
version: latestVersion.version,
|
||||||
publishedAt: latestVersion.publishedAt,
|
publishedAt: latestVersion.publishedAt,
|
||||||
changelog: latestVersion.changelog,
|
changelog: latestVersion.changelog,
|
||||||
downloads: latestVersion.files
|
downloads: latestVersion.files
|
||||||
.filter(file => file.uploaded)
|
.filter((file) => file.uploaded)
|
||||||
.map(file => ({
|
.map((file) => ({
|
||||||
name: file.name,
|
name: file.name,
|
||||||
url: `https://${config.R2_CUSTOM_DOMAIN}/${file.name}`,
|
url: `https://${config.R2_CUSTOM_DOMAIN}/${file.name}`,
|
||||||
size: formatFileSize(file.size)
|
size: formatFileSize(file.size)
|
||||||
}))
|
}))
|
||||||
};
|
|
||||||
// 重建缓存
|
|
||||||
await env.R2_BUCKET.put(config.CACHE_KEY, JSON.stringify(cacheData));
|
|
||||||
return new Response(JSON.stringify(cacheData), {
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Access-Control-Allow-Origin': '*'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// 如果没有任何数据,返回错误
|
// 重建缓存
|
||||||
return new Response(JSON.stringify({
|
await env.R2_BUCKET.put(config.CACHE_KEY, JSON.stringify(cacheData))
|
||||||
error: '没有可用的版本信息'
|
return new Response(JSON.stringify(cacheData), {
|
||||||
}), {
|
headers: {
|
||||||
status: 404,
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Access-Control-Allow-Origin': '*'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 返回缓存数据
|
|
||||||
return new Response(await cached.text(), {
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Access-Control-Allow-Origin': '*'
|
'Access-Control-Allow-Origin': '*'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 如果没有任何数据,返回错误
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
error: '没有可用的版本信息'
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
status: 404,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Origin': '*'
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回缓存数据
|
||||||
|
return new Response(await cached.text(), {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Origin': '*'
|
||||||
|
}
|
||||||
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await addLog(env, 'ERROR', '获取缓存版本信息失败', error.message);
|
await addLog(env, 'ERROR', '获取缓存版本信息失败', error.message)
|
||||||
throw error;
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 新增:只检查新版本并更新
|
// 新增:只检查新版本并更新
|
||||||
async function checkNewRelease(env) {
|
async function checkNewRelease(env) {
|
||||||
try {
|
try {
|
||||||
// 获取 GitHub 最新版本
|
// 获取 GitHub 最新版本
|
||||||
const githubResponse = await fetch('https://api.github.com/repos/kangfenmao/cherry-studio/releases/latest', {
|
const githubResponse = await fetch('https://api.github.com/repos/kangfenmao/cherry-studio/releases/latest', {
|
||||||
headers: { 'User-Agent': 'CloudflareWorker' },
|
headers: { 'User-Agent': 'CloudflareWorker' }
|
||||||
});
|
})
|
||||||
|
|
||||||
if (!githubResponse.ok) {
|
if (!githubResponse.ok) {
|
||||||
throw new Error('GitHub API 请求失败');
|
throw new Error('GitHub API 请求失败')
|
||||||
}
|
}
|
||||||
|
|
||||||
const releaseData = await githubResponse.json();
|
const releaseData = await githubResponse.json()
|
||||||
const version = releaseData.tag_name;
|
const version = releaseData.tag_name
|
||||||
|
|
||||||
// 获取版本数据库
|
// 获取版本数据库
|
||||||
const versionDB = await env.R2_BUCKET.get(config.VERSION_DB);
|
const versionDB = await env.R2_BUCKET.get(config.VERSION_DB)
|
||||||
let versions = { versions: {}, latestVersion: null, lastChecked: new Date().toISOString() };
|
let versions = { versions: {}, latestVersion: null, lastChecked: new Date().toISOString() }
|
||||||
|
|
||||||
if (versionDB) {
|
if (versionDB) {
|
||||||
versions = JSON.parse(await versionDB.text());
|
versions = JSON.parse(await versionDB.text())
|
||||||
}
|
}
|
||||||
|
|
||||||
// 移除版本检查,改为记录是否有文件更新的标志
|
// 移除版本检查,改为记录是否有文件更新的标志
|
||||||
let hasUpdates = false;
|
let hasUpdates = false
|
||||||
if (versions.latestVersion !== version) {
|
if (versions.latestVersion !== version) {
|
||||||
await addLog(env, 'INFO', `发现新版本: ${version}`);
|
await addLog(env, 'INFO', `发现新版本: ${version}`)
|
||||||
hasUpdates = true;
|
hasUpdates = true
|
||||||
} else {
|
} else {
|
||||||
await addLog(env, 'INFO', `版本 ${version} 文件完整性检查开始`);
|
await addLog(env, 'INFO', `版本 ${version} 文件完整性检查开始`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 准备新版本记录
|
// 准备新版本记录
|
||||||
const versionRecord = {
|
const versionRecord = {
|
||||||
version,
|
version,
|
||||||
publishedAt: releaseData.published_at,
|
publishedAt: releaseData.published_at,
|
||||||
uploadedAt: null,
|
uploadedAt: null,
|
||||||
files: releaseData.assets.map(asset => ({
|
files: releaseData.assets.map((asset) => ({
|
||||||
name: asset.name,
|
name: asset.name,
|
||||||
size: asset.size,
|
size: asset.size,
|
||||||
uploaded: false
|
uploaded: false
|
||||||
})),
|
})),
|
||||||
changelog: releaseData.body
|
changelog: releaseData.body
|
||||||
};
|
}
|
||||||
|
|
||||||
// 检查并上传文件
|
// 检查并上传文件
|
||||||
for (const asset of releaseData.assets) {
|
for (const asset of releaseData.assets) {
|
||||||
try {
|
try {
|
||||||
const existingFile = await env.R2_BUCKET.get(asset.name);
|
const existingFile = await env.R2_BUCKET.get(asset.name)
|
||||||
// 检查文件是否存在且大小是否一致
|
// 检查文件是否存在且大小是否一致
|
||||||
if (!existingFile || existingFile.size !== asset.size) {
|
if (!existingFile || existingFile.size !== asset.size) {
|
||||||
hasUpdates = true;
|
hasUpdates = true
|
||||||
const response = await fetch(asset.browser_download_url);
|
const response = await fetch(asset.browser_download_url)
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`下载失败: HTTP ${response.status}`);
|
throw new Error(`下载失败: HTTP ${response.status}`)
|
||||||
}
|
|
||||||
|
|
||||||
const file = await response.arrayBuffer();
|
|
||||||
await env.R2_BUCKET.put(asset.name, file, {
|
|
||||||
httpMetadata: { contentType: getContentType(asset.name) }
|
|
||||||
});
|
|
||||||
|
|
||||||
// 更新文件状态
|
|
||||||
const fileIndex = versionRecord.files.findIndex(f => f.name === asset.name);
|
|
||||||
if (fileIndex !== -1) {
|
|
||||||
versionRecord.files[fileIndex].uploaded = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
await addLog(env, 'INFO', `文件${existingFile ? '更新' : '上传'}成功: ${asset.name}`);
|
|
||||||
} else {
|
|
||||||
// 文件存在且大小相同,标记为已上传
|
|
||||||
const fileIndex = versionRecord.files.findIndex(f => f.name === asset.name);
|
|
||||||
if (fileIndex !== -1) {
|
|
||||||
versionRecord.files[fileIndex].uploaded = true;
|
|
||||||
}
|
|
||||||
await addLog(env, 'INFO', `文件完整性验证通过: ${asset.name}`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
await addLog(env, 'ERROR', `文件处理失败: ${asset.name}`, error.message);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// 只有在有更新或是新版本时才更新数据库和缓存
|
const file = await response.arrayBuffer()
|
||||||
if (hasUpdates) {
|
await env.R2_BUCKET.put(asset.name, file, {
|
||||||
// 更新版本记录
|
httpMetadata: { contentType: getContentType(asset.name) }
|
||||||
versionRecord.uploadedAt = new Date().toISOString();
|
})
|
||||||
versions.versions[version] = versionRecord;
|
|
||||||
versions.latestVersion = version;
|
|
||||||
|
|
||||||
// 保存版本数据库
|
// 更新文件状态
|
||||||
await env.R2_BUCKET.put(config.VERSION_DB, JSON.stringify(versions, null, 2));
|
const fileIndex = versionRecord.files.findIndex((f) => f.name === asset.name)
|
||||||
|
if (fileIndex !== -1) {
|
||||||
// 更新缓存
|
versionRecord.files[fileIndex].uploaded = true
|
||||||
const cacheData = {
|
|
||||||
version,
|
|
||||||
publishedAt: releaseData.published_at,
|
|
||||||
changelog: releaseData.body,
|
|
||||||
downloads: versionRecord.files
|
|
||||||
.filter(file => file.uploaded)
|
|
||||||
.map(file => ({
|
|
||||||
name: file.name,
|
|
||||||
url: `https://${config.R2_CUSTOM_DOMAIN}/${file.name}`,
|
|
||||||
size: formatFileSize(file.size)
|
|
||||||
}))
|
|
||||||
};
|
|
||||||
|
|
||||||
await env.R2_BUCKET.put(config.CACHE_KEY, JSON.stringify(cacheData));
|
|
||||||
await addLog(env, 'INFO', hasUpdates ? '更新完成' : '文件完整性检查完成');
|
|
||||||
|
|
||||||
// 清理旧版本
|
|
||||||
const versionList = Object.keys(versions.versions).sort((a, b) => compareVersions(b, a));
|
|
||||||
if (versionList.length > 2) {
|
|
||||||
// 获取需要保留的两个最新版本
|
|
||||||
const keepVersions = versionList.slice(0, 2);
|
|
||||||
// 获取所有需要删除的版本
|
|
||||||
const oldVersions = versionList.slice(2);
|
|
||||||
|
|
||||||
// 先获取 R2 桶中的所有文件列表
|
|
||||||
const allFiles = await listAllFiles(env);
|
|
||||||
|
|
||||||
// 获取需要保留的文件名列表
|
|
||||||
const keepFiles = new Set();
|
|
||||||
for (const keepVersion of keepVersions) {
|
|
||||||
const versionFiles = versions.versions[keepVersion].files;
|
|
||||||
versionFiles.forEach(file => keepFiles.add(file.name));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 删除所有旧版本文件
|
|
||||||
for (const oldVersion of oldVersions) {
|
|
||||||
const oldFiles = versions.versions[oldVersion].files;
|
|
||||||
for (const file of oldFiles) {
|
|
||||||
try {
|
|
||||||
if (file.uploaded) {
|
|
||||||
await env.R2_BUCKET.delete(file.name);
|
|
||||||
await addLog(env, 'INFO', `删除旧文件: ${file.name}`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
await addLog(env, 'ERROR', `删除旧文件失败: ${file.name}`, error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
delete versions.versions[oldVersion];
|
|
||||||
}
|
|
||||||
|
|
||||||
// 清理可能遗留的旧文件
|
|
||||||
for (const file of allFiles) {
|
|
||||||
if (!keepFiles.has(file.name)) {
|
|
||||||
try {
|
|
||||||
await env.R2_BUCKET.delete(file.name);
|
|
||||||
await addLog(env, 'INFO', `删除遗留文件: ${file.name}`);
|
|
||||||
} catch (error) {
|
|
||||||
await addLog(env, 'ERROR', `删除遗留文件失败: ${file.name}`, error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 保存更新后的版本数据库
|
|
||||||
await env.R2_BUCKET.put(config.VERSION_DB, JSON.stringify(versions, null, 2));
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
await addLog(env, 'INFO', '所有文件完整性检查通过,无需更新');
|
await addLog(env, 'INFO', `文件${existingFile ? '更新' : '上传'}成功: ${asset.name}`)
|
||||||
|
} else {
|
||||||
|
// 文件存在且大小相同,标记为已上传
|
||||||
|
const fileIndex = versionRecord.files.findIndex((f) => f.name === asset.name)
|
||||||
|
if (fileIndex !== -1) {
|
||||||
|
versionRecord.files[fileIndex].uploaded = true
|
||||||
|
}
|
||||||
|
await addLog(env, 'INFO', `文件完整性验证通过: ${asset.name}`)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
await addLog(env, 'ERROR', `文件处理失败: ${asset.name}`, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只有在有更新或是新版本时才更新数据库和缓存
|
||||||
|
if (hasUpdates) {
|
||||||
|
// 更新版本记录
|
||||||
|
versionRecord.uploadedAt = new Date().toISOString()
|
||||||
|
versions.versions[version] = versionRecord
|
||||||
|
versions.latestVersion = version
|
||||||
|
|
||||||
|
// 保存版本数据库
|
||||||
|
await env.R2_BUCKET.put(config.VERSION_DB, JSON.stringify(versions, null, 2))
|
||||||
|
|
||||||
|
// 更新缓存
|
||||||
|
const cacheData = {
|
||||||
|
version,
|
||||||
|
publishedAt: releaseData.published_at,
|
||||||
|
changelog: releaseData.body,
|
||||||
|
downloads: versionRecord.files
|
||||||
|
.filter((file) => file.uploaded)
|
||||||
|
.map((file) => ({
|
||||||
|
name: file.name,
|
||||||
|
url: `https://${config.R2_CUSTOM_DOMAIN}/${file.name}`,
|
||||||
|
size: formatFileSize(file.size)
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
return hasUpdates ? cacheData : null;
|
await env.R2_BUCKET.put(config.CACHE_KEY, JSON.stringify(cacheData))
|
||||||
|
await addLog(env, 'INFO', hasUpdates ? '更新完成' : '文件完整性检查完成')
|
||||||
|
|
||||||
|
// 清理旧版本
|
||||||
|
const versionList = Object.keys(versions.versions).sort((a, b) => compareVersions(b, a))
|
||||||
|
if (versionList.length > 2) {
|
||||||
|
// 获取需要保留的两个最新版本
|
||||||
|
const keepVersions = versionList.slice(0, 2)
|
||||||
|
// 获取所有需要删除的版本
|
||||||
|
const oldVersions = versionList.slice(2)
|
||||||
|
|
||||||
|
// 先获取 R2 桶中的所有文件列表
|
||||||
|
const allFiles = await listAllFiles(env)
|
||||||
|
|
||||||
|
// 获取需要保留的文件名列表
|
||||||
|
const keepFiles = new Set()
|
||||||
|
for (const keepVersion of keepVersions) {
|
||||||
|
const versionFiles = versions.versions[keepVersion].files
|
||||||
|
versionFiles.forEach((file) => keepFiles.add(file.name))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除所有旧版本文件
|
||||||
|
for (const oldVersion of oldVersions) {
|
||||||
|
const oldFiles = versions.versions[oldVersion].files
|
||||||
|
for (const file of oldFiles) {
|
||||||
|
try {
|
||||||
|
if (file.uploaded) {
|
||||||
|
await env.R2_BUCKET.delete(file.name)
|
||||||
|
await addLog(env, 'INFO', `删除旧文件: ${file.name}`)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
await addLog(env, 'ERROR', `删除旧文件失败: ${file.name}`, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete versions.versions[oldVersion]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理可能遗留的旧文件
|
||||||
|
for (const file of allFiles) {
|
||||||
|
if (!keepFiles.has(file.name)) {
|
||||||
|
try {
|
||||||
|
await env.R2_BUCKET.delete(file.name)
|
||||||
|
await addLog(env, 'INFO', `删除遗留文件: ${file.name}`)
|
||||||
|
} catch (error) {
|
||||||
|
await addLog(env, 'ERROR', `删除遗留文件失败: ${file.name}`, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存更新后的版本数据库
|
||||||
|
await env.R2_BUCKET.put(config.VERSION_DB, JSON.stringify(versions, null, 2))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await addLog(env, 'INFO', '所有文件完整性检查通过,无需更新')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 合并 Mac yml 文件
|
||||||
|
await mergeMacYmlFiles(env)
|
||||||
|
|
||||||
|
return hasUpdates ? cacheData : null
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await addLog(env, 'ERROR', '检查新版本失败', error.message);
|
await addLog(env, 'ERROR', '检查新版本失败', error.message)
|
||||||
throw error;
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 新增:获取 R2 桶中的所有文件列表
|
// 新增:获取 R2 桶中的所有文件列表
|
||||||
async function listAllFiles(env) {
|
async function listAllFiles(env) {
|
||||||
const files = [];
|
const files = []
|
||||||
let cursor;
|
let cursor
|
||||||
|
|
||||||
do {
|
do {
|
||||||
const listed = await env.R2_BUCKET.list({ cursor, include: ['customMetadata'] });
|
const listed = await env.R2_BUCKET.list({ cursor, include: ['customMetadata'] })
|
||||||
files.push(...listed.objects);
|
files.push(...listed.objects)
|
||||||
cursor = listed.cursor;
|
cursor = listed.cursor
|
||||||
} while (cursor);
|
} while (cursor)
|
||||||
|
|
||||||
return files;
|
return files
|
||||||
|
}
|
||||||
|
|
||||||
|
async function mergeMacYmlFiles(env) {
|
||||||
|
try {
|
||||||
|
const macX64Yml = await env.R2_BUCKET.get('latest-mac-x64.yml')
|
||||||
|
const macArm64Yml = await env.R2_BUCKET.get('latest-mac-arm64.yml')
|
||||||
|
|
||||||
|
if (!macX64Yml || !macArm64Yml) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const x64Content = await macX64Yml.text()
|
||||||
|
const arm64Content = await macArm64Yml.text()
|
||||||
|
|
||||||
|
// 使用正则表达式提取 files 部分
|
||||||
|
const filesRegex = /files:\n( - url:[\s\S]+?)(?=\npath:)/
|
||||||
|
const x64Files = x64Content.match(filesRegex)[1]
|
||||||
|
const arm64Files = arm64Content.match(filesRegex)[1]
|
||||||
|
|
||||||
|
// 合并内容
|
||||||
|
const mergedContent = arm64Content.replace(filesRegex, `files:\n${arm64Files}\n${x64Files}`)
|
||||||
|
|
||||||
|
// 保存合并后的文件
|
||||||
|
await env.R2_BUCKET.put('latest-mac.yml', mergedContent, {
|
||||||
|
httpMetadata: { contentType: 'application/x-yaml' }
|
||||||
|
})
|
||||||
|
|
||||||
|
await addLog(env, 'INFO', 'Mac yml 文件合并成功')
|
||||||
|
} catch (error) {
|
||||||
|
await addLog(env, 'ERROR', 'Mac yml 文件合并失败', error.message)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,279 +0,0 @@
|
|||||||
/* eslint-disable react-hooks/rules-of-hooks */
|
|
||||||
import { db } from '@renderer/databases/index'
|
|
||||||
import KnowledgeQueue from '@renderer/queue/KnowledgeQueue'
|
|
||||||
import FileManager from '@renderer/services/FileManager'
|
|
||||||
import { getKnowledgeBaseParams } from '@renderer/services/KnowledgeService'
|
|
||||||
import { RootState } from '@renderer/store'
|
|
||||||
import {
|
|
||||||
addBase,
|
|
||||||
addFiles as addFilesAction,
|
|
||||||
addItem,
|
|
||||||
clearAllProcessing,
|
|
||||||
clearCompletedProcessing,
|
|
||||||
deleteBase,
|
|
||||||
removeItem as removeItemAction,
|
|
||||||
renameBase,
|
|
||||||
updateBase,
|
|
||||||
updateBases,
|
|
||||||
updateItemProcessingStatus,
|
|
||||||
updateNotes
|
|
||||||
} from '@renderer/store/knowledge'
|
|
||||||
import { FileType, KnowledgeBase, ProcessingStatus } from '@renderer/types'
|
|
||||||
import { KnowledgeItem } from '@renderer/types'
|
|
||||||
import { runAsyncFunction } from '@renderer/utils'
|
|
||||||
import { useEffect, useState } from 'react'
|
|
||||||
import { useDispatch, useSelector } from 'react-redux'
|
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
|
||||||
|
|
||||||
export const useKnowledge = (baseId: string) => {
|
|
||||||
const dispatch = useDispatch()
|
|
||||||
const base = useSelector((state: RootState) => state.knowledge.bases.find((b) => b.id === baseId))
|
|
||||||
|
|
||||||
// 重命名知识库
|
|
||||||
const renameKnowledgeBase = (name: string) => {
|
|
||||||
dispatch(renameBase({ baseId, name }))
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新知识库
|
|
||||||
const updateKnowledgeBase = (base: KnowledgeBase) => {
|
|
||||||
dispatch(updateBase(base))
|
|
||||||
}
|
|
||||||
|
|
||||||
// 批量添加文件
|
|
||||||
const addFiles = (files: FileType[]) => {
|
|
||||||
const filesItems: KnowledgeItem[] = files.map((file) => ({
|
|
||||||
id: uuidv4(),
|
|
||||||
type: 'file' as const,
|
|
||||||
content: file,
|
|
||||||
created_at: Date.now(),
|
|
||||||
updated_at: Date.now(),
|
|
||||||
processingStatus: 'pending',
|
|
||||||
processingProgress: 0,
|
|
||||||
processingError: '',
|
|
||||||
retryCount: 0
|
|
||||||
}))
|
|
||||||
dispatch(addFilesAction({ baseId, items: filesItems }))
|
|
||||||
setTimeout(() => KnowledgeQueue.checkAllBases(), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加URL
|
|
||||||
const addUrl = (url: string) => {
|
|
||||||
const newUrlItem: KnowledgeItem = {
|
|
||||||
id: uuidv4(),
|
|
||||||
type: 'url' as const,
|
|
||||||
content: url,
|
|
||||||
created_at: Date.now(),
|
|
||||||
updated_at: Date.now(),
|
|
||||||
processingStatus: 'pending',
|
|
||||||
processingProgress: 0,
|
|
||||||
processingError: '',
|
|
||||||
retryCount: 0
|
|
||||||
}
|
|
||||||
dispatch(addItem({ baseId, item: newUrlItem }))
|
|
||||||
setTimeout(() => KnowledgeQueue.checkAllBases(), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加笔记
|
|
||||||
const addNote = async (content: string) => {
|
|
||||||
const noteId = uuidv4()
|
|
||||||
const note: KnowledgeItem = {
|
|
||||||
id: noteId,
|
|
||||||
type: 'note',
|
|
||||||
content,
|
|
||||||
created_at: Date.now(),
|
|
||||||
updated_at: Date.now()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 存储完整笔记到数据库
|
|
||||||
await db.knowledge_notes.add(note)
|
|
||||||
|
|
||||||
// 在 store 中只存储引用
|
|
||||||
const noteRef: KnowledgeItem = {
|
|
||||||
id: noteId,
|
|
||||||
baseId,
|
|
||||||
type: 'note',
|
|
||||||
content: '', // store中不需要存储实际内容
|
|
||||||
created_at: Date.now(),
|
|
||||||
updated_at: Date.now(),
|
|
||||||
processingStatus: 'pending',
|
|
||||||
processingProgress: 0,
|
|
||||||
processingError: '',
|
|
||||||
retryCount: 0
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch(updateNotes({ baseId, item: noteRef }))
|
|
||||||
setTimeout(() => KnowledgeQueue.checkAllBases(), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新笔记内容
|
|
||||||
const updateNoteContent = async (noteId: string, content: string) => {
|
|
||||||
const note = await db.knowledge_notes.get(noteId)
|
|
||||||
if (note) {
|
|
||||||
const updatedNote = {
|
|
||||||
...note,
|
|
||||||
content,
|
|
||||||
updated_at: Date.now()
|
|
||||||
}
|
|
||||||
await db.knowledge_notes.put(updatedNote)
|
|
||||||
dispatch(updateNotes({ baseId, item: updatedNote }))
|
|
||||||
}
|
|
||||||
setTimeout(() => KnowledgeQueue.checkAllBases(), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取笔记内容
|
|
||||||
const getNoteContent = async (noteId: string) => {
|
|
||||||
return await db.knowledge_notes.get(noteId)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 移除项目
|
|
||||||
const removeItem = async (item: KnowledgeItem) => {
|
|
||||||
dispatch(removeItemAction({ baseId, item }))
|
|
||||||
if (base) {
|
|
||||||
if (item?.uniqueId) {
|
|
||||||
await window.api.knowledgeBase.remove({ uniqueId: item.uniqueId, base: getKnowledgeBaseParams(base) })
|
|
||||||
}
|
|
||||||
if (item.type === 'file' && typeof item.content === 'object') {
|
|
||||||
await FileManager.deleteFile(item.content.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新处理状态
|
|
||||||
const updateItemStatus = (itemId: string, status: ProcessingStatus, progress?: number, error?: string) => {
|
|
||||||
dispatch(
|
|
||||||
updateItemProcessingStatus({
|
|
||||||
baseId,
|
|
||||||
itemId,
|
|
||||||
status,
|
|
||||||
progress,
|
|
||||||
error
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取特定项目的处理状态
|
|
||||||
const getProcessingStatus = (itemId: string) => {
|
|
||||||
return base?.items.find((item) => item.id === itemId)?.processingStatus
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取特定类型的所有处理项
|
|
||||||
const getProcessingItemsByType = (type: 'file' | 'url' | 'note') => {
|
|
||||||
return base?.items.filter((item) => item.type === type && item.processingStatus !== undefined) || []
|
|
||||||
}
|
|
||||||
|
|
||||||
// 清除已完成的项目
|
|
||||||
const clearCompleted = () => {
|
|
||||||
dispatch(clearCompletedProcessing({ baseId }))
|
|
||||||
}
|
|
||||||
|
|
||||||
// 清除所有处理状态
|
|
||||||
const clearAll = () => {
|
|
||||||
dispatch(clearAllProcessing({ baseId }))
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加 Sitemap
|
|
||||||
const addSitemap = (url: string) => {
|
|
||||||
const newSitemapItem: KnowledgeItem = {
|
|
||||||
id: uuidv4(),
|
|
||||||
type: 'sitemap' as const,
|
|
||||||
content: url,
|
|
||||||
created_at: Date.now(),
|
|
||||||
updated_at: Date.now(),
|
|
||||||
processingStatus: 'pending',
|
|
||||||
processingProgress: 0,
|
|
||||||
processingError: '',
|
|
||||||
retryCount: 0
|
|
||||||
}
|
|
||||||
dispatch(addItem({ baseId, item: newSitemapItem }))
|
|
||||||
setTimeout(() => KnowledgeQueue.checkAllBases(), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add directory support
|
|
||||||
const addDirectory = (path: string) => {
|
|
||||||
const newDirectoryItem: KnowledgeItem = {
|
|
||||||
id: uuidv4(),
|
|
||||||
type: 'directory',
|
|
||||||
content: path,
|
|
||||||
created_at: Date.now(),
|
|
||||||
updated_at: Date.now(),
|
|
||||||
processingStatus: 'pending',
|
|
||||||
processingProgress: 0,
|
|
||||||
processingError: '',
|
|
||||||
retryCount: 0
|
|
||||||
}
|
|
||||||
dispatch(addItem({ baseId, item: newDirectoryItem }))
|
|
||||||
setTimeout(() => KnowledgeQueue.checkAllBases(), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
const fileItems = base?.items.filter((item) => item.type === 'file') || []
|
|
||||||
const directoryItems = base?.items.filter((item) => item.type === 'directory') || []
|
|
||||||
const urlItems = base?.items.filter((item) => item.type === 'url') || []
|
|
||||||
const sitemapItems = base?.items.filter((item) => item.type === 'sitemap') || []
|
|
||||||
const [noteItems, setNoteItems] = useState<KnowledgeItem[]>([])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const notes = base?.items.filter((item) => item.type === 'note') || []
|
|
||||||
runAsyncFunction(async () => {
|
|
||||||
const newNoteItems = await Promise.all(
|
|
||||||
notes.map(async (item) => {
|
|
||||||
const note = await db.knowledge_notes.get(item.id)
|
|
||||||
return { ...item, content: note?.content || '' }
|
|
||||||
})
|
|
||||||
)
|
|
||||||
setNoteItems(newNoteItems.filter((note) => note !== undefined) as KnowledgeItem[])
|
|
||||||
})
|
|
||||||
}, [base?.items])
|
|
||||||
|
|
||||||
return {
|
|
||||||
base,
|
|
||||||
fileItems,
|
|
||||||
urlItems,
|
|
||||||
sitemapItems,
|
|
||||||
noteItems,
|
|
||||||
renameKnowledgeBase,
|
|
||||||
updateKnowledgeBase,
|
|
||||||
addFiles,
|
|
||||||
addUrl,
|
|
||||||
addSitemap,
|
|
||||||
addNote,
|
|
||||||
updateNoteContent,
|
|
||||||
getNoteContent,
|
|
||||||
updateItemStatus,
|
|
||||||
getProcessingStatus,
|
|
||||||
getProcessingItemsByType,
|
|
||||||
clearCompleted,
|
|
||||||
clearAll,
|
|
||||||
removeItem,
|
|
||||||
directoryItems,
|
|
||||||
addDirectory
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useKnowledgeBases = () => {
|
|
||||||
const dispatch = useDispatch()
|
|
||||||
const bases = useSelector((state: RootState) => state.knowledge.bases)
|
|
||||||
|
|
||||||
const addKnowledgeBase = (base: KnowledgeBase) => {
|
|
||||||
dispatch(addBase(base))
|
|
||||||
}
|
|
||||||
|
|
||||||
const renameKnowledgeBase = (baseId: string, name: string) => {
|
|
||||||
dispatch(renameBase({ baseId, name }))
|
|
||||||
}
|
|
||||||
|
|
||||||
const deleteKnowledgeBase = (baseId: string) => {
|
|
||||||
dispatch(deleteBase({ baseId }))
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateKnowledgeBases = (bases: KnowledgeBase[]) => {
|
|
||||||
dispatch(updateBases(bases))
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
bases,
|
|
||||||
addKnowledgeBase,
|
|
||||||
renameKnowledgeBase,
|
|
||||||
deleteKnowledgeBase,
|
|
||||||
updateKnowledgeBases
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user