mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-07 05:39:05 +08:00
Merge branch 'v2' of github.com:CherryHQ/cherry-studio into refactor/ocr
This commit is contained in:
commit
472f2b1a6f
252
.github/issue-checker.yml
vendored
252
.github/issue-checker.yml
vendored
@ -1,252 +0,0 @@
|
|||||||
default-mode:
|
|
||||||
add:
|
|
||||||
remove: [pull_request_target, issues]
|
|
||||||
|
|
||||||
labels:
|
|
||||||
# <!-- [Ss]kip `LABEL` --> 跳过一个 label
|
|
||||||
# <!-- [Rr]emove `LABEL` --> 去掉一个 label
|
|
||||||
|
|
||||||
# skips and removes
|
|
||||||
- name: skip all
|
|
||||||
content:
|
|
||||||
regexes: '[Ss]kip (?:[Aa]ll |)[Ll]abels?'
|
|
||||||
- name: remove all
|
|
||||||
content:
|
|
||||||
regexes: '[Rr]emove (?:[Aa]ll |)[Ll]abels?'
|
|
||||||
|
|
||||||
- name: skip kind/bug
|
|
||||||
content:
|
|
||||||
regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)kind/bug(?:`|)'
|
|
||||||
- name: remove kind/bug
|
|
||||||
content:
|
|
||||||
regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)kind/bug(?:`|)'
|
|
||||||
|
|
||||||
- name: skip kind/enhancement
|
|
||||||
content:
|
|
||||||
regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)kind/enhancement(?:`|)'
|
|
||||||
- name: remove kind/enhancement
|
|
||||||
content:
|
|
||||||
regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)kind/enhancement(?:`|)'
|
|
||||||
|
|
||||||
- name: skip kind/question
|
|
||||||
content:
|
|
||||||
regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)kind/question(?:`|)'
|
|
||||||
- name: remove kind/question
|
|
||||||
content:
|
|
||||||
regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)kind/question(?:`|)'
|
|
||||||
|
|
||||||
- name: skip area/Connectivity
|
|
||||||
content:
|
|
||||||
regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)area/Connectivity(?:`|)'
|
|
||||||
- name: remove area/Connectivity
|
|
||||||
content:
|
|
||||||
regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)area/Connectivity(?:`|)'
|
|
||||||
|
|
||||||
- name: skip area/UI/UX
|
|
||||||
content:
|
|
||||||
regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)area/UI/UX(?:`|)'
|
|
||||||
- name: remove area/UI/UX
|
|
||||||
content:
|
|
||||||
regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)area/UI/UX(?:`|)'
|
|
||||||
|
|
||||||
- name: skip kind/documentation
|
|
||||||
content:
|
|
||||||
regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)kind/documentation(?:`|)'
|
|
||||||
- name: remove kind/documentation
|
|
||||||
content:
|
|
||||||
regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)kind/documentation(?:`|)'
|
|
||||||
|
|
||||||
- name: skip client:linux
|
|
||||||
content:
|
|
||||||
regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)client:linux(?:`|)'
|
|
||||||
- name: remove client:linux
|
|
||||||
content:
|
|
||||||
regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)client:linux(?:`|)'
|
|
||||||
|
|
||||||
- name: skip client:mac
|
|
||||||
content:
|
|
||||||
regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)client:mac(?:`|)'
|
|
||||||
- name: remove client:mac
|
|
||||||
content:
|
|
||||||
regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)client:mac(?:`|)'
|
|
||||||
|
|
||||||
- name: skip client:win
|
|
||||||
content:
|
|
||||||
regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)client:win(?:`|)'
|
|
||||||
- name: remove client:win
|
|
||||||
content:
|
|
||||||
regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)client:win(?:`|)'
|
|
||||||
|
|
||||||
- name: skip sig/Assistant
|
|
||||||
content:
|
|
||||||
regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)sig/Assistant(?:`|)'
|
|
||||||
- name: remove sig/Assistant
|
|
||||||
content:
|
|
||||||
regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)sig/Assistant(?:`|)'
|
|
||||||
|
|
||||||
- name: skip sig/Data
|
|
||||||
content:
|
|
||||||
regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)sig/Data(?:`|)'
|
|
||||||
- name: remove sig/Data
|
|
||||||
content:
|
|
||||||
regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)sig/Data(?:`|)'
|
|
||||||
|
|
||||||
- name: skip sig/MCP
|
|
||||||
content:
|
|
||||||
regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)sig/MCP(?:`|)'
|
|
||||||
- name: remove sig/MCP
|
|
||||||
content:
|
|
||||||
regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)sig/MCP(?:`|)'
|
|
||||||
|
|
||||||
- name: skip sig/RAG
|
|
||||||
content:
|
|
||||||
regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)sig/RAG(?:`|)'
|
|
||||||
- name: remove sig/RAG
|
|
||||||
content:
|
|
||||||
regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)sig/RAG(?:`|)'
|
|
||||||
|
|
||||||
- name: skip lgtm
|
|
||||||
content:
|
|
||||||
regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)lgtm(?:`|)'
|
|
||||||
- name: remove lgtm
|
|
||||||
content:
|
|
||||||
regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)lgtm(?:`|)'
|
|
||||||
|
|
||||||
- name: skip License
|
|
||||||
content:
|
|
||||||
regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)License(?:`|)'
|
|
||||||
- name: remove License
|
|
||||||
content:
|
|
||||||
regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)License(?:`|)'
|
|
||||||
|
|
||||||
# `Dev Team`
|
|
||||||
- name: Dev Team
|
|
||||||
mode:
|
|
||||||
add: [pull_request_target, issues]
|
|
||||||
author_association:
|
|
||||||
- COLLABORATOR
|
|
||||||
|
|
||||||
# Area labels
|
|
||||||
- name: area/Connectivity
|
|
||||||
content: area/Connectivity
|
|
||||||
regexes: '代理|[Pp]roxy'
|
|
||||||
skip-if:
|
|
||||||
- skip all
|
|
||||||
- skip area/Connectivity
|
|
||||||
remove-if:
|
|
||||||
- remove all
|
|
||||||
- remove area/Connectivity
|
|
||||||
|
|
||||||
- name: area/UI/UX
|
|
||||||
content: area/UI/UX
|
|
||||||
regexes: '界面|[Uu][Ii]|重叠|按钮|图标|组件|渲染|菜单|栏目|头像|主题|样式|[Cc][Ss][Ss]'
|
|
||||||
skip-if:
|
|
||||||
- skip all
|
|
||||||
- skip area/UI/UX
|
|
||||||
remove-if:
|
|
||||||
- remove all
|
|
||||||
- remove area/UI/UX
|
|
||||||
|
|
||||||
# Kind labels
|
|
||||||
- name: kind/documentation
|
|
||||||
content: kind/documentation
|
|
||||||
regexes: '文档|教程|[Dd]oc(s|umentation)|[Rr]eadme'
|
|
||||||
skip-if:
|
|
||||||
- skip all
|
|
||||||
- skip kind/documentation
|
|
||||||
remove-if:
|
|
||||||
- remove all
|
|
||||||
- remove kind/documentation
|
|
||||||
|
|
||||||
# Client labels
|
|
||||||
- name: client:linux
|
|
||||||
content: client:linux
|
|
||||||
regexes: '(?:[Ll]inux|[Uu]buntu|[Dd]ebian)'
|
|
||||||
skip-if:
|
|
||||||
- skip all
|
|
||||||
- skip client:linux
|
|
||||||
remove-if:
|
|
||||||
- remove all
|
|
||||||
- remove client:linux
|
|
||||||
|
|
||||||
- name: client:mac
|
|
||||||
content: client:mac
|
|
||||||
regexes: '(?:[Mm]ac|[Mm]acOS|[Oo]SX)'
|
|
||||||
skip-if:
|
|
||||||
- skip all
|
|
||||||
- skip client:mac
|
|
||||||
remove-if:
|
|
||||||
- remove all
|
|
||||||
- remove client:mac
|
|
||||||
|
|
||||||
- name: client:win
|
|
||||||
content: client:win
|
|
||||||
regexes: '(?:[Ww]in|[Ww]indows)'
|
|
||||||
skip-if:
|
|
||||||
- skip all
|
|
||||||
- skip client:win
|
|
||||||
remove-if:
|
|
||||||
- remove all
|
|
||||||
- remove client:win
|
|
||||||
|
|
||||||
# SIG labels
|
|
||||||
- name: sig/Assistant
|
|
||||||
content: sig/Assistant
|
|
||||||
regexes: '快捷助手|[Aa]ssistant'
|
|
||||||
skip-if:
|
|
||||||
- skip all
|
|
||||||
- skip sig/Assistant
|
|
||||||
remove-if:
|
|
||||||
- remove all
|
|
||||||
- remove sig/Assistant
|
|
||||||
|
|
||||||
- name: sig/Data
|
|
||||||
content: sig/Data
|
|
||||||
regexes: '[Ww]ebdav|坚果云|备份|同步|数据|Obsidian|Notion|Joplin|思源'
|
|
||||||
skip-if:
|
|
||||||
- skip all
|
|
||||||
- skip sig/Data
|
|
||||||
remove-if:
|
|
||||||
- remove all
|
|
||||||
- remove sig/Data
|
|
||||||
|
|
||||||
- name: sig/MCP
|
|
||||||
content: sig/MCP
|
|
||||||
regexes: '[Mm][Cc][Pp]'
|
|
||||||
skip-if:
|
|
||||||
- skip all
|
|
||||||
- skip sig/MCP
|
|
||||||
remove-if:
|
|
||||||
- remove all
|
|
||||||
- remove sig/MCP
|
|
||||||
|
|
||||||
- name: sig/RAG
|
|
||||||
content: sig/RAG
|
|
||||||
regexes: '知识库|[Rr][Aa][Gg]'
|
|
||||||
skip-if:
|
|
||||||
- skip all
|
|
||||||
- skip sig/RAG
|
|
||||||
remove-if:
|
|
||||||
- remove all
|
|
||||||
- remove sig/RAG
|
|
||||||
|
|
||||||
# Other labels
|
|
||||||
- name: lgtm
|
|
||||||
content: lgtm
|
|
||||||
regexes: '(?:[Ll][Gg][Tt][Mm]|[Ll]ooks [Gg]ood [Tt]o [Mm]e)'
|
|
||||||
skip-if:
|
|
||||||
- skip all
|
|
||||||
- skip lgtm
|
|
||||||
remove-if:
|
|
||||||
- remove all
|
|
||||||
- remove lgtm
|
|
||||||
|
|
||||||
- name: License
|
|
||||||
content: License
|
|
||||||
regexes: '(?:[Ll]icense|[Cc]opyright|[Mm][Ii][Tt]|[Aa]pache)'
|
|
||||||
skip-if:
|
|
||||||
- skip all
|
|
||||||
- skip License
|
|
||||||
remove-if:
|
|
||||||
- remove all
|
|
||||||
- remove License
|
|
||||||
1
.github/workflows/delete-branch.yml
vendored
1
.github/workflows/delete-branch.yml
vendored
@ -13,6 +13,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Delete merged branch
|
- name: Delete merged branch
|
||||||
uses: actions/github-script@v8
|
uses: actions/github-script@v8
|
||||||
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
github.rest.git.deleteRef({
|
github.rest.git.deleteRef({
|
||||||
|
|||||||
25
.github/workflows/issue-checker.yml
vendored
25
.github/workflows/issue-checker.yml
vendored
@ -1,25 +0,0 @@
|
|||||||
name: 'Issue Checker'
|
|
||||||
|
|
||||||
on:
|
|
||||||
issues:
|
|
||||||
types: [opened, edited]
|
|
||||||
pull_request_target:
|
|
||||||
types: [opened, edited]
|
|
||||||
issue_comment:
|
|
||||||
types: [created, edited]
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
issues: write
|
|
||||||
pull-requests: write
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
triage:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: MaaAssistantArknights/issue-checker@v1.14
|
|
||||||
with:
|
|
||||||
repo-token: '${{ secrets.GITHUB_TOKEN }}'
|
|
||||||
configuration-path: .github/issue-checker.yml
|
|
||||||
not-before: 2022-08-05T00:00:00Z
|
|
||||||
include-title: 1
|
|
||||||
@ -1,44 +0,0 @@
|
|||||||
diff --git a/dist/index.js b/dist/index.js
|
|
||||||
index 53f411e55a4c9a06fd29bb4ab8161c4ad15980cd..71b91f196c8b886ed90dd237dec5625d79d5677e 100644
|
|
||||||
--- a/dist/index.js
|
|
||||||
+++ b/dist/index.js
|
|
||||||
@@ -12676,10 +12676,13 @@ var OpenAIResponsesLanguageModel = class {
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (value.item.type === "message") {
|
|
||||||
- controller.enqueue({
|
|
||||||
- type: "text-end",
|
|
||||||
- id: value.item.id
|
|
||||||
- });
|
|
||||||
+ // Fix for gpt-5-codex: use currentTextId to ensure text-end matches text-start
|
|
||||||
+ if (currentTextId) {
|
|
||||||
+ controller.enqueue({
|
|
||||||
+ type: "text-end",
|
|
||||||
+ id: currentTextId
|
|
||||||
+ });
|
|
||||||
+ }
|
|
||||||
currentTextId = null;
|
|
||||||
} else if (isResponseOutputItemDoneReasoningChunk(value)) {
|
|
||||||
const activeReasoningPart = activeReasoning[value.item.id];
|
|
||||||
diff --git a/dist/index.mjs b/dist/index.mjs
|
|
||||||
index 7719264da3c49a66c2626082f6ccaae6e3ef5e89..090fd8cf142674192a826148428ed6a0c4a54e35 100644
|
|
||||||
--- a/dist/index.mjs
|
|
||||||
+++ b/dist/index.mjs
|
|
||||||
@@ -12670,10 +12670,13 @@ var OpenAIResponsesLanguageModel = class {
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (value.item.type === "message") {
|
|
||||||
- controller.enqueue({
|
|
||||||
- type: "text-end",
|
|
||||||
- id: value.item.id
|
|
||||||
- });
|
|
||||||
+ // Fix for gpt-5-codex: use currentTextId to ensure text-end matches text-start
|
|
||||||
+ if (currentTextId) {
|
|
||||||
+ controller.enqueue({
|
|
||||||
+ type: "text-end",
|
|
||||||
+ id: currentTextId
|
|
||||||
+ });
|
|
||||||
+ }
|
|
||||||
currentTextId = null;
|
|
||||||
} else if (isResponseOutputItemDoneReasoningChunk(value)) {
|
|
||||||
const activeReasoningPart = activeReasoning[value.item.id];
|
|
||||||
@ -158,7 +158,7 @@
|
|||||||
"@opentelemetry/sdk-trace-base": "^2.0.0",
|
"@opentelemetry/sdk-trace-base": "^2.0.0",
|
||||||
"@opentelemetry/sdk-trace-node": "^2.0.0",
|
"@opentelemetry/sdk-trace-node": "^2.0.0",
|
||||||
"@opentelemetry/sdk-trace-web": "^2.0.0",
|
"@opentelemetry/sdk-trace-web": "^2.0.0",
|
||||||
"@opeoginni/github-copilot-openai-compatible": "patch:@opeoginni/github-copilot-openai-compatible@npm%3A0.1.18#~/.yarn/patches/@opeoginni-github-copilot-openai-compatible-npm-0.1.18-3f65760532.patch",
|
"@opeoginni/github-copilot-openai-compatible": "0.1.19",
|
||||||
"@playwright/test": "^1.52.0",
|
"@playwright/test": "^1.52.0",
|
||||||
"@radix-ui/react-context-menu": "^2.2.16",
|
"@radix-ui/react-context-menu": "^2.2.16",
|
||||||
"@reduxjs/toolkit": "^2.2.5",
|
"@reduxjs/toolkit": "^2.2.5",
|
||||||
|
|||||||
@ -19,7 +19,6 @@ import process from 'node:process'
|
|||||||
import { registerIpc } from './ipc'
|
import { registerIpc } from './ipc'
|
||||||
import { agentService } from './services/agents'
|
import { agentService } from './services/agents'
|
||||||
import { apiServerService } from './services/ApiServerService'
|
import { apiServerService } from './services/ApiServerService'
|
||||||
import { configManager } from './services/ConfigManager'
|
|
||||||
import mcpService from './services/MCPService'
|
import mcpService from './services/MCPService'
|
||||||
import { nodeTraceService } from './services/NodeTraceService'
|
import { nodeTraceService } from './services/NodeTraceService'
|
||||||
import {
|
import {
|
||||||
@ -42,12 +41,12 @@ const logger = loggerService.withContext('MainEntry')
|
|||||||
/**
|
/**
|
||||||
* Disable hardware acceleration if setting is enabled
|
* Disable hardware acceleration if setting is enabled
|
||||||
*/
|
*/
|
||||||
//FIXME should not use configManager, use usePreference instead
|
//FIXME should not use preferenceService before initialization
|
||||||
//TODO 我们需要调整配置管理的加载位置,以保证其在 preferenceService 初始化之前被调用
|
//TODO 我们需要调整配置管理的加载位置,以保证其在 preferenceService 初始化之前被调用
|
||||||
const disableHardwareAcceleration = configManager.getDisableHardwareAcceleration()
|
// const disableHardwareAcceleration = preferenceService.get('app.disable_hardware_acceleration')
|
||||||
if (disableHardwareAcceleration) {
|
// if (disableHardwareAcceleration) {
|
||||||
app.disableHardwareAcceleration()
|
// app.disableHardwareAcceleration()
|
||||||
}
|
// }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disable chromium's window animations
|
* Disable chromium's window animations
|
||||||
|
|||||||
@ -169,7 +169,7 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
|
|||||||
windows.forEach((window) => {
|
windows.forEach((window) => {
|
||||||
window.webContents.session.setSpellCheckerLanguages(languages)
|
window.webContents.session.setSpellCheckerLanguages(languages)
|
||||||
})
|
})
|
||||||
configManager.set('spellCheckLanguages', languages)
|
preferenceService.set('app.spell_check.languages', languages)
|
||||||
})
|
})
|
||||||
|
|
||||||
// launch on boot
|
// launch on boot
|
||||||
@ -264,12 +264,15 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.handle(IpcChannel.Config_Set, (_, key: string, value: any, isNotify: boolean = false) => {
|
ipcMain.handle(IpcChannel.Config_Set, (_, key: string) => {
|
||||||
configManager.set(key, value, isNotify)
|
// Legacy config handler - will be deprecated
|
||||||
|
logger.warn(`Legacy Config_Set called for key: ${key}`)
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.handle(IpcChannel.Config_Get, (_, key: string) => {
|
ipcMain.handle(IpcChannel.Config_Get, (_, key: string) => {
|
||||||
return configManager.get(key)
|
// Legacy config handler - will be deprecated
|
||||||
|
logger.warn(`Legacy Config_Get called for key: ${key}`)
|
||||||
|
return undefined
|
||||||
})
|
})
|
||||||
|
|
||||||
// // theme
|
// // theme
|
||||||
@ -280,7 +283,7 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
|
|||||||
ipcMain.handle(IpcChannel.App_HandleZoomFactor, (_, delta: number, reset: boolean = false) => {
|
ipcMain.handle(IpcChannel.App_HandleZoomFactor, (_, delta: number, reset: boolean = false) => {
|
||||||
const windows = BrowserWindow.getAllWindows()
|
const windows = BrowserWindow.getAllWindows()
|
||||||
handleZoomFactor(windows, delta, reset)
|
handleZoomFactor(windows, delta, reset)
|
||||||
return configManager.getZoomFactor()
|
return preferenceService.get('app.zoom_factor')
|
||||||
})
|
})
|
||||||
|
|
||||||
// clear cache
|
// clear cache
|
||||||
|
|||||||
@ -1,9 +1,8 @@
|
|||||||
import { preferenceService } from '@data/PreferenceService'
|
import { preferenceService } from '@data/PreferenceService'
|
||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
import { isWin } from '@main/constant'
|
import { isWin } from '@main/constant'
|
||||||
import { configManager } from '@main/services/ConfigManager'
|
|
||||||
import { getIpCountry } from '@main/utils/ipService'
|
import { getIpCountry } from '@main/utils/ipService'
|
||||||
import { generateUserAgent } from '@main/utils/systemInfo'
|
import { generateUserAgent, getClientId } from '@main/utils/systemInfo'
|
||||||
import { FeedUrl } from '@shared/config/constant'
|
import { FeedUrl } from '@shared/config/constant'
|
||||||
import { UpgradeChannel } from '@shared/data/preference/preferenceTypes'
|
import { UpgradeChannel } from '@shared/data/preference/preferenceTypes'
|
||||||
import { IpcChannel } from '@shared/IpcChannel'
|
import { IpcChannel } from '@shared/IpcChannel'
|
||||||
@ -39,7 +38,7 @@ export default class AppUpdater {
|
|||||||
autoUpdater.requestHeaders = {
|
autoUpdater.requestHeaders = {
|
||||||
...autoUpdater.requestHeaders,
|
...autoUpdater.requestHeaders,
|
||||||
'User-Agent': generateUserAgent(),
|
'User-Agent': generateUserAgent(),
|
||||||
'X-Client-Id': configManager.getClientId()
|
'X-Client-Id': getClientId()
|
||||||
}
|
}
|
||||||
|
|
||||||
autoUpdater.on('error', (error) => {
|
autoUpdater.on('error', (error) => {
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { preferenceService } from '@data/PreferenceService'
|
||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
import { isDev } from '@main/constant'
|
import { isDev } from '@main/constant'
|
||||||
import { CacheBatchSpanProcessor, FunctionSpanExporter } from '@mcp-trace/trace-core'
|
import { CacheBatchSpanProcessor, FunctionSpanExporter } from '@mcp-trace/trace-core'
|
||||||
@ -7,7 +8,6 @@ import { context, trace } from '@opentelemetry/api'
|
|||||||
import { BrowserWindow, ipcMain } from 'electron'
|
import { BrowserWindow, ipcMain } from 'electron'
|
||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
|
|
||||||
import { ConfigKeys, configManager } from './ConfigManager'
|
|
||||||
import { spanCacheService } from './SpanCacheService'
|
import { spanCacheService } from './SpanCacheService'
|
||||||
|
|
||||||
export const TRACER_NAME = 'CherryStudio'
|
export const TRACER_NAME = 'CherryStudio'
|
||||||
@ -91,8 +91,13 @@ export function openTraceWindow(topicId: string, traceId: string, autoOpen = tru
|
|||||||
} else {
|
} else {
|
||||||
traceWin.loadFile(path.join(__dirname, '../renderer/traceWindow.html'))
|
traceWin.loadFile(path.join(__dirname, '../renderer/traceWindow.html'))
|
||||||
}
|
}
|
||||||
|
let unsubscribeLanguage: (() => void) | null = null
|
||||||
|
|
||||||
traceWin.on('closed', () => {
|
traceWin.on('closed', () => {
|
||||||
configManager.unsubscribe(ConfigKeys.Language, setLanguageCallback)
|
if (unsubscribeLanguage) {
|
||||||
|
unsubscribeLanguage()
|
||||||
|
unsubscribeLanguage = null
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
traceWin?.destroy()
|
traceWin?.destroy()
|
||||||
} finally {
|
} finally {
|
||||||
@ -106,13 +111,15 @@ export function openTraceWindow(topicId: string, traceId: string, autoOpen = tru
|
|||||||
topicId,
|
topicId,
|
||||||
modelName
|
modelName
|
||||||
})
|
})
|
||||||
traceWin!.webContents.send('set-language', { lang: configManager.get(ConfigKeys.Language) })
|
traceWin!.webContents.send('set-language', { lang: preferenceService.get('app.language') })
|
||||||
configManager.subscribe(ConfigKeys.Language, setLanguageCallback)
|
unsubscribeLanguage = preferenceService.subscribeChange('app.language', setLanguageCallback)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const setLanguageCallback = (lang: string) => {
|
const setLanguageCallback = (lang: string | null) => {
|
||||||
traceWin!.webContents.send('set-language', { lang })
|
if (lang) {
|
||||||
|
traceWin?.webContents.send('set-language', { lang })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const setTraceWindowTitle = (title: string) => {
|
export const setTraceWindowTitle = (title: string) => {
|
||||||
|
|||||||
@ -14,7 +14,6 @@ import { join } from 'path'
|
|||||||
|
|
||||||
import icon from '../../../build/icon.png?asset'
|
import icon from '../../../build/icon.png?asset'
|
||||||
import { titleBarOverlayDark, titleBarOverlayLight } from '../config'
|
import { titleBarOverlayDark, titleBarOverlayLight } from '../config'
|
||||||
import { configManager } from './ConfigManager'
|
|
||||||
import { contextMenu } from './ContextMenu'
|
import { contextMenu } from './ContextMenu'
|
||||||
import { initSessionUserAgent } from './WebviewService'
|
import { initSessionUserAgent } from './WebviewService'
|
||||||
|
|
||||||
@ -87,7 +86,7 @@ export class WindowService {
|
|||||||
webSecurity: false,
|
webSecurity: false,
|
||||||
webviewTag: true,
|
webviewTag: true,
|
||||||
allowRunningInsecureContent: true,
|
allowRunningInsecureContent: true,
|
||||||
zoomFactor: configManager.getZoomFactor(),
|
zoomFactor: preferenceService.get('app.zoom_factor'),
|
||||||
backgroundThrottling: false
|
backgroundThrottling: false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -120,10 +119,10 @@ export class WindowService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private setupSpellCheck(mainWindow: BrowserWindow) {
|
private setupSpellCheck(mainWindow: BrowserWindow) {
|
||||||
const enableSpellCheck = configManager.get('enableSpellCheck', false)
|
const enableSpellCheck = preferenceService.get('app.spell_check.enabled')
|
||||||
if (enableSpellCheck) {
|
if (enableSpellCheck) {
|
||||||
try {
|
try {
|
||||||
const spellCheckLanguages = configManager.get('spellCheckLanguages', []) as string[]
|
const spellCheckLanguages = preferenceService.get('app.spell_check.languages')
|
||||||
spellCheckLanguages.length > 0 && mainWindow.webContents.session.setSpellCheckerLanguages(spellCheckLanguages)
|
spellCheckLanguages.length > 0 && mainWindow.webContents.session.setSpellCheckerLanguages(spellCheckLanguages)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Failed to set spell check languages:', error as Error)
|
logger.error('Failed to set spell check languages:', error as Error)
|
||||||
@ -175,7 +174,7 @@ export class WindowService {
|
|||||||
|
|
||||||
private setupWindowEvents(mainWindow: BrowserWindow) {
|
private setupWindowEvents(mainWindow: BrowserWindow) {
|
||||||
mainWindow.once('ready-to-show', () => {
|
mainWindow.once('ready-to-show', () => {
|
||||||
mainWindow.webContents.setZoomFactor(configManager.getZoomFactor())
|
mainWindow.webContents.setZoomFactor(preferenceService.get('app.zoom_factor'))
|
||||||
|
|
||||||
// show window only when laucn to tray not set
|
// show window only when laucn to tray not set
|
||||||
const isLaunchToTray = preferenceService.get('app.tray.on_launch')
|
const isLaunchToTray = preferenceService.get('app.tray.on_launch')
|
||||||
@ -204,14 +203,14 @@ export class WindowService {
|
|||||||
// and resize ipc
|
// and resize ipc
|
||||||
//
|
//
|
||||||
mainWindow.on('will-resize', () => {
|
mainWindow.on('will-resize', () => {
|
||||||
mainWindow.webContents.setZoomFactor(configManager.getZoomFactor())
|
mainWindow.webContents.setZoomFactor(preferenceService.get('app.zoom_factor'))
|
||||||
mainWindow.webContents.send(IpcChannel.Windows_Resize, mainWindow.getSize())
|
mainWindow.webContents.send(IpcChannel.Windows_Resize, mainWindow.getSize())
|
||||||
})
|
})
|
||||||
|
|
||||||
// set the zoom factor again when the window is going to restore
|
// set the zoom factor again when the window is going to restore
|
||||||
// minimize and restore will cause zoom reset
|
// minimize and restore will cause zoom reset
|
||||||
mainWindow.on('restore', () => {
|
mainWindow.on('restore', () => {
|
||||||
mainWindow.webContents.setZoomFactor(configManager.getZoomFactor())
|
mainWindow.webContents.setZoomFactor(preferenceService.get('app.zoom_factor'))
|
||||||
})
|
})
|
||||||
|
|
||||||
// ARCH: as `will-resize` is only for Win & Mac,
|
// ARCH: as `will-resize` is only for Win & Mac,
|
||||||
@ -219,7 +218,7 @@ export class WindowService {
|
|||||||
// but `resize` will fliker the ui
|
// but `resize` will fliker the ui
|
||||||
if (isLinux) {
|
if (isLinux) {
|
||||||
mainWindow.on('resize', () => {
|
mainWindow.on('resize', () => {
|
||||||
mainWindow.webContents.setZoomFactor(configManager.getZoomFactor())
|
mainWindow.webContents.setZoomFactor(preferenceService.get('app.zoom_factor'))
|
||||||
mainWindow.webContents.send(IpcChannel.Windows_Resize, mainWindow.getSize())
|
mainWindow.webContents.send(IpcChannel.Windows_Resize, mainWindow.getSize())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -40,7 +40,8 @@ vi.mock('@main/utils/locales', () => ({
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
vi.mock('@main/utils/systemInfo', () => ({
|
vi.mock('@main/utils/systemInfo', () => ({
|
||||||
generateUserAgent: vi.fn(() => 'test-user-agent')
|
generateUserAgent: vi.fn(() => 'test-user-agent'),
|
||||||
|
getClientId: vi.fn(() => 'test-client-id')
|
||||||
}))
|
}))
|
||||||
|
|
||||||
vi.mock('electron', () => ({
|
vi.mock('electron', () => ({
|
||||||
|
|||||||
@ -264,7 +264,7 @@ describe('file', () => {
|
|||||||
const buffer = iconv.encode(content, 'GB18030')
|
const buffer = iconv.encode(content, 'GB18030')
|
||||||
|
|
||||||
// 模拟文件读取和编码检测
|
// 模拟文件读取和编码检测
|
||||||
vi.spyOn(fsPromises, 'readFile').mockResolvedValue(buffer)
|
vi.spyOn(fsPromises, 'readFile').mockResolvedValue(buffer as unknown as string)
|
||||||
vi.spyOn(chardet, 'detectFile').mockResolvedValue('GB18030')
|
vi.spyOn(chardet, 'detectFile').mockResolvedValue('GB18030')
|
||||||
|
|
||||||
const result = await readTextFileWithAutoEncoding(mockFilePath)
|
const result = await readTextFileWithAutoEncoding(mockFilePath)
|
||||||
@ -276,7 +276,7 @@ describe('file', () => {
|
|||||||
const buffer = iconv.encode(content, 'UTF-8')
|
const buffer = iconv.encode(content, 'UTF-8')
|
||||||
|
|
||||||
// 模拟文件读取
|
// 模拟文件读取
|
||||||
vi.spyOn(fsPromises, 'readFile').mockResolvedValue(buffer)
|
vi.spyOn(fsPromises, 'readFile').mockResolvedValue(buffer as unknown as string)
|
||||||
vi.spyOn(chardet, 'detectFile').mockResolvedValue('GB18030')
|
vi.spyOn(chardet, 'detectFile').mockResolvedValue('GB18030')
|
||||||
|
|
||||||
const result = await readTextFileWithAutoEncoding(mockFilePath)
|
const result = await readTextFileWithAutoEncoding(mockFilePath)
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
|
import { preferenceService } from '@data/PreferenceService'
|
||||||
import { app } from 'electron'
|
import { app } from 'electron'
|
||||||
import macosRelease from 'macos-release'
|
import macosRelease from 'macos-release'
|
||||||
import os from 'os'
|
import os from 'os'
|
||||||
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* System information interface
|
* System information interface
|
||||||
@ -90,3 +92,19 @@ export function generateUserAgent(): string {
|
|||||||
|
|
||||||
return `Mozilla/5.0 (${systemInfo.osString}; ${systemInfo.archString}) AppleWebKit/537.36 (KHTML, like Gecko) CherryStudio/${systemInfo.appVersion} Chrome/124.0.0.0 Safari/537.36`
|
return `Mozilla/5.0 (${systemInfo.osString}; ${systemInfo.archString}) AppleWebKit/537.36 (KHTML, like Gecko) CherryStudio/${systemInfo.appVersion} Chrome/124.0.0.0 Safari/537.36`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get or generate a unique client ID
|
||||||
|
* @returns {string} Client ID
|
||||||
|
*/
|
||||||
|
export function getClientId(): string {
|
||||||
|
let clientId = preferenceService.get('app.user.id')
|
||||||
|
|
||||||
|
// If it's the placeholder value, generate a new UUID
|
||||||
|
if (!clientId || clientId.length === 0) {
|
||||||
|
clientId = uuidv4()
|
||||||
|
preferenceService.set('app.user.id', clientId)
|
||||||
|
}
|
||||||
|
|
||||||
|
return clientId
|
||||||
|
}
|
||||||
|
|||||||
@ -1,13 +1,12 @@
|
|||||||
|
import { preferenceService } from '@data/PreferenceService'
|
||||||
import type { BrowserWindow } from 'electron'
|
import type { BrowserWindow } from 'electron'
|
||||||
|
|
||||||
import { configManager } from '../services/ConfigManager'
|
|
||||||
|
|
||||||
export function handleZoomFactor(wins: BrowserWindow[], delta: number, reset: boolean = false) {
|
export function handleZoomFactor(wins: BrowserWindow[], delta: number, reset: boolean = false) {
|
||||||
if (reset) {
|
if (reset) {
|
||||||
wins.forEach((win) => {
|
wins.forEach((win) => {
|
||||||
win.webContents.setZoomFactor(1)
|
win.webContents.setZoomFactor(1)
|
||||||
})
|
})
|
||||||
configManager.setZoomFactor(1)
|
preferenceService.set('app.zoom_factor', 1)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -15,12 +14,12 @@ export function handleZoomFactor(wins: BrowserWindow[], delta: number, reset: bo
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentZoom = configManager.getZoomFactor()
|
const currentZoom = preferenceService.get('app.zoom_factor')
|
||||||
const newZoom = Number((currentZoom + delta).toFixed(1))
|
const newZoom = Number((currentZoom + delta).toFixed(1))
|
||||||
if (newZoom >= 0.5 && newZoom <= 2.0) {
|
if (newZoom >= 0.5 && newZoom <= 2.0) {
|
||||||
wins.forEach((win) => {
|
wins.forEach((win) => {
|
||||||
win.webContents.setZoomFactor(newZoom)
|
win.webContents.setZoomFactor(newZoom)
|
||||||
})
|
})
|
||||||
configManager.setZoomFactor(newZoom)
|
preferenceService.set('app.zoom_factor', newZoom)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import type { LanguageModelMiddleware } from 'ai'
|
|||||||
import { extractReasoningMiddleware, simulateStreamingMiddleware } from 'ai'
|
import { extractReasoningMiddleware, simulateStreamingMiddleware } from 'ai'
|
||||||
|
|
||||||
import { noThinkMiddleware } from './noThinkMiddleware'
|
import { noThinkMiddleware } from './noThinkMiddleware'
|
||||||
|
import { toolChoiceMiddleware } from './toolChoiceMiddleware'
|
||||||
|
|
||||||
const logger = loggerService.withContext('AiSdkMiddlewareBuilder')
|
const logger = loggerService.withContext('AiSdkMiddlewareBuilder')
|
||||||
|
|
||||||
@ -32,6 +33,8 @@ export interface AiSdkMiddlewareConfig {
|
|||||||
uiMessages?: Message[]
|
uiMessages?: Message[]
|
||||||
// 内置搜索配置
|
// 内置搜索配置
|
||||||
webSearchPluginConfig?: WebSearchPluginConfig
|
webSearchPluginConfig?: WebSearchPluginConfig
|
||||||
|
// 知识库识别开关,默认开启
|
||||||
|
knowledgeRecognition?: 'off' | 'on'
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -122,6 +125,15 @@ export class AiSdkMiddlewareBuilder {
|
|||||||
export function buildAiSdkMiddlewares(config: AiSdkMiddlewareConfig): LanguageModelMiddleware[] {
|
export function buildAiSdkMiddlewares(config: AiSdkMiddlewareConfig): LanguageModelMiddleware[] {
|
||||||
const builder = new AiSdkMiddlewareBuilder()
|
const builder = new AiSdkMiddlewareBuilder()
|
||||||
|
|
||||||
|
// 0. 知识库强制调用中间件(必须在最前面,确保第一轮强制调用知识库)
|
||||||
|
if (config.knowledgeRecognition === 'off') {
|
||||||
|
builder.add({
|
||||||
|
name: 'force-knowledge-first',
|
||||||
|
middleware: toolChoiceMiddleware('builtin_knowledge_search')
|
||||||
|
})
|
||||||
|
logger.debug('Added toolChoice middleware to force knowledge base search on first round')
|
||||||
|
}
|
||||||
|
|
||||||
// 1. 根据provider添加特定中间件
|
// 1. 根据provider添加特定中间件
|
||||||
if (config.provider) {
|
if (config.provider) {
|
||||||
addProviderSpecificMiddlewares(builder, config)
|
addProviderSpecificMiddlewares(builder, config)
|
||||||
|
|||||||
45
src/renderer/src/aiCore/middleware/toolChoiceMiddleware.ts
Normal file
45
src/renderer/src/aiCore/middleware/toolChoiceMiddleware.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { loggerService } from '@logger'
|
||||||
|
import type { LanguageModelMiddleware } from 'ai'
|
||||||
|
|
||||||
|
const logger = loggerService.withContext('toolChoiceMiddleware')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tool Choice Middleware
|
||||||
|
* Controls tool selection strategy across multiple rounds of tool calls:
|
||||||
|
* - First round: Forces the model to call a specific tool (e.g., knowledge base search)
|
||||||
|
* - Subsequent rounds: Allows the model to automatically choose any available tool
|
||||||
|
*
|
||||||
|
* This ensures knowledge base is consulted first while still enabling MCP tools
|
||||||
|
* and other capabilities in follow-up interactions.
|
||||||
|
*
|
||||||
|
* @param forceFirstToolName - The tool name to force on the first round
|
||||||
|
* @returns LanguageModelMiddleware
|
||||||
|
*/
|
||||||
|
export function toolChoiceMiddleware(forceFirstToolName: string): LanguageModelMiddleware {
|
||||||
|
let toolCallRound = 0
|
||||||
|
|
||||||
|
return {
|
||||||
|
middlewareVersion: 'v2',
|
||||||
|
|
||||||
|
transformParams: async ({ params }) => {
|
||||||
|
toolCallRound++
|
||||||
|
|
||||||
|
const transformedParams = { ...params }
|
||||||
|
|
||||||
|
if (toolCallRound === 1) {
|
||||||
|
// First round: force the specified tool
|
||||||
|
logger.debug(`Round ${toolCallRound}: Forcing tool choice to '${forceFirstToolName}'`)
|
||||||
|
transformedParams.toolChoice = {
|
||||||
|
type: 'tool',
|
||||||
|
toolName: forceFirstToolName
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Subsequent rounds: allow automatic tool selection
|
||||||
|
logger.debug(`Round ${toolCallRound}: Using automatic tool choice`)
|
||||||
|
transformedParams.toolChoice = { type: 'auto' }
|
||||||
|
}
|
||||||
|
|
||||||
|
return transformedParams
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -5,6 +5,7 @@ import {
|
|||||||
GEMINI_FLASH_MODEL_REGEX,
|
GEMINI_FLASH_MODEL_REGEX,
|
||||||
getThinkModelType,
|
getThinkModelType,
|
||||||
isDeepSeekHybridInferenceModel,
|
isDeepSeekHybridInferenceModel,
|
||||||
|
isDoubaoSeedAfter251015,
|
||||||
isDoubaoThinkingAutoModel,
|
isDoubaoThinkingAutoModel,
|
||||||
isGrok4FastReasoningModel,
|
isGrok4FastReasoningModel,
|
||||||
isGrokReasoningModel,
|
isGrokReasoningModel,
|
||||||
@ -171,6 +172,10 @@ export function getReasoningEffort(assistant: Assistant, model: Model): Reasonin
|
|||||||
|
|
||||||
// Doubao 思考模式支持
|
// Doubao 思考模式支持
|
||||||
if (isSupportedThinkingTokenDoubaoModel(model)) {
|
if (isSupportedThinkingTokenDoubaoModel(model)) {
|
||||||
|
if (isDoubaoSeedAfter251015(model)) {
|
||||||
|
return { reasoningEffort }
|
||||||
|
}
|
||||||
|
// Comment below this line seems weird. reasoning is high instead of null/undefined. Who wrote this?
|
||||||
// reasoningEffort 为空,默认开启 enabled
|
// reasoningEffort 为空,默认开启 enabled
|
||||||
if (reasoningEffort === 'high') {
|
if (reasoningEffort === 'high') {
|
||||||
return { thinking: { type: 'enabled' } }
|
return { thinking: { type: 'enabled' } }
|
||||||
@ -227,12 +232,12 @@ export function getReasoningEffort(assistant: Assistant, model: Model): Reasonin
|
|||||||
const supportedOptions = MODEL_SUPPORTED_REASONING_EFFORT[modelType]
|
const supportedOptions = MODEL_SUPPORTED_REASONING_EFFORT[modelType]
|
||||||
if (supportedOptions.includes(reasoningEffort)) {
|
if (supportedOptions.includes(reasoningEffort)) {
|
||||||
return {
|
return {
|
||||||
reasoning_effort: reasoningEffort
|
reasoningEffort
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 如果不支持,fallback到第一个支持的值
|
// 如果不支持,fallback到第一个支持的值
|
||||||
return {
|
return {
|
||||||
reasoning_effort: supportedOptions[0]
|
reasoningEffort: supportedOptions[0]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
45
src/renderer/src/components/ConfirmDialog.tsx
Normal file
45
src/renderer/src/components/ConfirmDialog.tsx
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { Button } from '@heroui/react'
|
||||||
|
import { CheckIcon, XIcon } from 'lucide-react'
|
||||||
|
import type { FC } from 'react'
|
||||||
|
import { createPortal } from 'react-dom'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
x: number
|
||||||
|
y: number
|
||||||
|
message: string
|
||||||
|
onConfirm: () => void
|
||||||
|
onCancel: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const ConfirmDialog: FC<Props> = ({ x, y, message, onConfirm, onCancel }) => {
|
||||||
|
if (typeof document === 'undefined') {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return createPortal(
|
||||||
|
<>
|
||||||
|
<div className="fixed inset-0 z-[99998] bg-transparent" onClick={onCancel} />
|
||||||
|
<div
|
||||||
|
className="-translate-x-1/2 -translate-y-full fixed z-[99999] mt-[-8px] transform"
|
||||||
|
style={{
|
||||||
|
left: `${x}px`,
|
||||||
|
top: `${y}px`
|
||||||
|
}}>
|
||||||
|
<div className="flex min-w-[160px] items-center rounded-lg border border-[var(--color-border)] bg-[var(--color-background)] p-3 shadow-[0_4px_12px_rgba(0,0,0,0.15)]">
|
||||||
|
<div className="mr-2 text-sm leading-[1.4]">{message}</div>
|
||||||
|
<div className="flex justify-center gap-2">
|
||||||
|
<Button onPress={onCancel} radius="full" className="h-6 w-6 min-w-0 p-1" color="danger">
|
||||||
|
<XIcon className="text-danger-foreground" size={16} />
|
||||||
|
</Button>
|
||||||
|
<Button onPress={onConfirm} radius="full" className="h-6 w-6 min-w-0 p-1" color="success">
|
||||||
|
<CheckIcon className="text-success-foreground" size={16} />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>,
|
||||||
|
document.body
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ConfirmDialog
|
||||||
@ -14,6 +14,7 @@ export interface CustomTagProps {
|
|||||||
closable?: boolean
|
closable?: boolean
|
||||||
onClose?: () => void
|
onClose?: () => void
|
||||||
onClick?: MouseEventHandler<HTMLDivElement>
|
onClick?: MouseEventHandler<HTMLDivElement>
|
||||||
|
onContextMenu?: MouseEventHandler<HTMLDivElement>
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
inactive?: boolean
|
inactive?: boolean
|
||||||
}
|
}
|
||||||
@ -28,6 +29,7 @@ const CustomTag: FC<CustomTagProps> = ({
|
|||||||
closable = false,
|
closable = false,
|
||||||
onClose,
|
onClose,
|
||||||
onClick,
|
onClick,
|
||||||
|
onContextMenu,
|
||||||
disabled,
|
disabled,
|
||||||
inactive
|
inactive
|
||||||
}) => {
|
}) => {
|
||||||
@ -40,6 +42,7 @@ const CustomTag: FC<CustomTagProps> = ({
|
|||||||
$closable={closable}
|
$closable={closable}
|
||||||
$clickable={!disabled && !!onClick}
|
$clickable={!disabled && !!onClick}
|
||||||
onClick={disabled ? undefined : onClick}
|
onClick={disabled ? undefined : onClick}
|
||||||
|
onContextMenu={disabled ? undefined : onContextMenu}
|
||||||
style={{
|
style={{
|
||||||
...(disabled && { cursor: 'not-allowed' }),
|
...(disabled && { cursor: 'not-allowed' }),
|
||||||
...style
|
...style
|
||||||
@ -57,7 +60,7 @@ const CustomTag: FC<CustomTagProps> = ({
|
|||||||
)}
|
)}
|
||||||
</Tag>
|
</Tag>
|
||||||
),
|
),
|
||||||
[actualColor, children, closable, disabled, icon, onClick, onClose, size, style]
|
[actualColor, children, closable, disabled, icon, onClick, onClose, onContextMenu, size, style]
|
||||||
)
|
)
|
||||||
|
|
||||||
return tooltip ? (
|
return tooltip ? (
|
||||||
|
|||||||
166
src/renderer/src/config/__test__/reasoning.test.ts
Normal file
166
src/renderer/src/config/__test__/reasoning.test.ts
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
import { describe, expect, it, vi } from 'vitest'
|
||||||
|
|
||||||
|
import { isDoubaoSeedAfter251015, isDoubaoThinkingAutoModel } from '../models/reasoning'
|
||||||
|
|
||||||
|
// FIXME: Idk why it's imported. Maybe circular dependency somewhere
|
||||||
|
vi.mock('@renderer/services/AssistantService.ts', () => ({
|
||||||
|
getDefaultAssistant: () => {
|
||||||
|
return {
|
||||||
|
id: 'default',
|
||||||
|
name: 'default',
|
||||||
|
emoji: '😀',
|
||||||
|
prompt: '',
|
||||||
|
topics: [],
|
||||||
|
messages: [],
|
||||||
|
type: 'assistant',
|
||||||
|
regularPhrases: [],
|
||||||
|
settings: {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
describe('Doubao Models', () => {
|
||||||
|
describe('isDoubaoThinkingAutoModel', () => {
|
||||||
|
it('should return false for invalid models', () => {
|
||||||
|
expect(
|
||||||
|
isDoubaoThinkingAutoModel({
|
||||||
|
id: 'doubao-seed-1-6-251015',
|
||||||
|
name: 'doubao-seed-1-6-251015',
|
||||||
|
provider: '',
|
||||||
|
group: ''
|
||||||
|
})
|
||||||
|
).toBe(false)
|
||||||
|
expect(
|
||||||
|
isDoubaoThinkingAutoModel({
|
||||||
|
id: 'doubao-seed-1-6-lite-251015',
|
||||||
|
name: 'doubao-seed-1-6-lite-251015',
|
||||||
|
provider: '',
|
||||||
|
group: ''
|
||||||
|
})
|
||||||
|
).toBe(false)
|
||||||
|
expect(
|
||||||
|
isDoubaoThinkingAutoModel({
|
||||||
|
id: 'doubao-seed-1-6-thinking-250715',
|
||||||
|
name: 'doubao-seed-1-6-thinking-250715',
|
||||||
|
provider: '',
|
||||||
|
group: ''
|
||||||
|
})
|
||||||
|
).toBe(false)
|
||||||
|
expect(
|
||||||
|
isDoubaoThinkingAutoModel({
|
||||||
|
id: 'doubao-seed-1-6-flash',
|
||||||
|
name: 'doubao-seed-1-6-flash',
|
||||||
|
provider: '',
|
||||||
|
group: ''
|
||||||
|
})
|
||||||
|
).toBe(false)
|
||||||
|
expect(
|
||||||
|
isDoubaoThinkingAutoModel({
|
||||||
|
id: 'doubao-seed-1-6-thinking',
|
||||||
|
name: 'doubao-seed-1-6-thinking',
|
||||||
|
provider: '',
|
||||||
|
group: ''
|
||||||
|
})
|
||||||
|
).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return true for valid models', () => {
|
||||||
|
expect(
|
||||||
|
isDoubaoThinkingAutoModel({
|
||||||
|
id: 'doubao-seed-1-6-250615',
|
||||||
|
name: 'doubao-seed-1-6-250615',
|
||||||
|
provider: '',
|
||||||
|
group: ''
|
||||||
|
})
|
||||||
|
).toBe(true)
|
||||||
|
expect(
|
||||||
|
isDoubaoThinkingAutoModel({
|
||||||
|
id: 'Doubao-Seed-1.6',
|
||||||
|
name: 'Doubao-Seed-1.6',
|
||||||
|
provider: '',
|
||||||
|
group: ''
|
||||||
|
})
|
||||||
|
).toBe(true)
|
||||||
|
expect(
|
||||||
|
isDoubaoThinkingAutoModel({
|
||||||
|
id: 'doubao-1-5-thinking-pro-m',
|
||||||
|
name: 'doubao-1-5-thinking-pro-m',
|
||||||
|
provider: '',
|
||||||
|
group: ''
|
||||||
|
})
|
||||||
|
).toBe(true)
|
||||||
|
expect(
|
||||||
|
isDoubaoThinkingAutoModel({
|
||||||
|
id: 'doubao-seed-1.6-lite',
|
||||||
|
name: 'doubao-seed-1.6-lite',
|
||||||
|
provider: '',
|
||||||
|
group: ''
|
||||||
|
})
|
||||||
|
).toBe(true)
|
||||||
|
expect(
|
||||||
|
isDoubaoThinkingAutoModel({
|
||||||
|
id: 'doubao-1-5-thinking-pro-m-12345',
|
||||||
|
name: 'doubao-1-5-thinking-pro-m-12345',
|
||||||
|
provider: '',
|
||||||
|
group: ''
|
||||||
|
})
|
||||||
|
).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('isDoubaoSeedAfter251015', () => {
|
||||||
|
it('should return true for models matching the pattern', () => {
|
||||||
|
expect(
|
||||||
|
isDoubaoSeedAfter251015({
|
||||||
|
id: 'doubao-seed-1-6-251015',
|
||||||
|
name: '',
|
||||||
|
provider: '',
|
||||||
|
group: ''
|
||||||
|
})
|
||||||
|
).toBe(true)
|
||||||
|
expect(
|
||||||
|
isDoubaoSeedAfter251015({
|
||||||
|
id: 'doubao-seed-1-6-lite-251015',
|
||||||
|
name: '',
|
||||||
|
provider: '',
|
||||||
|
group: ''
|
||||||
|
})
|
||||||
|
).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return false for models not matching the pattern', () => {
|
||||||
|
expect(
|
||||||
|
isDoubaoSeedAfter251015({
|
||||||
|
id: 'doubao-seed-1-6-250615',
|
||||||
|
name: '',
|
||||||
|
provider: '',
|
||||||
|
group: ''
|
||||||
|
})
|
||||||
|
).toBe(false)
|
||||||
|
expect(
|
||||||
|
isDoubaoSeedAfter251015({
|
||||||
|
id: 'Doubao-Seed-1.6',
|
||||||
|
name: '',
|
||||||
|
provider: '',
|
||||||
|
group: ''
|
||||||
|
})
|
||||||
|
).toBe(false)
|
||||||
|
expect(
|
||||||
|
isDoubaoSeedAfter251015({
|
||||||
|
id: 'doubao-1-5-thinking-pro-m',
|
||||||
|
name: '',
|
||||||
|
provider: '',
|
||||||
|
group: ''
|
||||||
|
})
|
||||||
|
).toBe(false)
|
||||||
|
expect(
|
||||||
|
isDoubaoSeedAfter251015({
|
||||||
|
id: 'doubao-seed-1-6-lite-251016',
|
||||||
|
name: '',
|
||||||
|
provider: '',
|
||||||
|
group: ''
|
||||||
|
})
|
||||||
|
).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@ -31,6 +31,7 @@ export const MODEL_SUPPORTED_REASONING_EFFORT: ReasoningEffortConfig = {
|
|||||||
qwen_thinking: ['low', 'medium', 'high'] as const,
|
qwen_thinking: ['low', 'medium', 'high'] as const,
|
||||||
doubao: ['auto', 'high'] as const,
|
doubao: ['auto', 'high'] as const,
|
||||||
doubao_no_auto: ['high'] as const,
|
doubao_no_auto: ['high'] as const,
|
||||||
|
doubao_after_251015: ['minimal', 'low', 'medium', 'high'] as const,
|
||||||
hunyuan: ['auto'] as const,
|
hunyuan: ['auto'] as const,
|
||||||
zhipu: ['auto'] as const,
|
zhipu: ['auto'] as const,
|
||||||
perplexity: ['low', 'medium', 'high'] as const,
|
perplexity: ['low', 'medium', 'high'] as const,
|
||||||
@ -51,6 +52,7 @@ export const MODEL_SUPPORTED_OPTIONS: ThinkingOptionConfig = {
|
|||||||
qwen_thinking: MODEL_SUPPORTED_REASONING_EFFORT.qwen_thinking,
|
qwen_thinking: MODEL_SUPPORTED_REASONING_EFFORT.qwen_thinking,
|
||||||
doubao: ['off', ...MODEL_SUPPORTED_REASONING_EFFORT.doubao] as const,
|
doubao: ['off', ...MODEL_SUPPORTED_REASONING_EFFORT.doubao] as const,
|
||||||
doubao_no_auto: ['off', ...MODEL_SUPPORTED_REASONING_EFFORT.doubao_no_auto] as const,
|
doubao_no_auto: ['off', ...MODEL_SUPPORTED_REASONING_EFFORT.doubao_no_auto] as const,
|
||||||
|
doubao_after_251015: MODEL_SUPPORTED_REASONING_EFFORT.doubao_after_251015,
|
||||||
hunyuan: ['off', ...MODEL_SUPPORTED_REASONING_EFFORT.hunyuan] as const,
|
hunyuan: ['off', ...MODEL_SUPPORTED_REASONING_EFFORT.hunyuan] as const,
|
||||||
zhipu: ['off', ...MODEL_SUPPORTED_REASONING_EFFORT.zhipu] as const,
|
zhipu: ['off', ...MODEL_SUPPORTED_REASONING_EFFORT.zhipu] as const,
|
||||||
perplexity: MODEL_SUPPORTED_REASONING_EFFORT.perplexity,
|
perplexity: MODEL_SUPPORTED_REASONING_EFFORT.perplexity,
|
||||||
@ -85,6 +87,8 @@ export const getThinkModelType = (model: Model): ThinkingModelType => {
|
|||||||
} else if (isSupportedThinkingTokenDoubaoModel(model)) {
|
} else if (isSupportedThinkingTokenDoubaoModel(model)) {
|
||||||
if (isDoubaoThinkingAutoModel(model)) {
|
if (isDoubaoThinkingAutoModel(model)) {
|
||||||
thinkingModelType = 'doubao'
|
thinkingModelType = 'doubao'
|
||||||
|
} else if (isDoubaoSeedAfter251015(model)) {
|
||||||
|
thinkingModelType = 'doubao_after_251015'
|
||||||
} else {
|
} else {
|
||||||
thinkingModelType = 'doubao_no_auto'
|
thinkingModelType = 'doubao_no_auto'
|
||||||
}
|
}
|
||||||
@ -308,14 +312,21 @@ export const DOUBAO_THINKING_MODEL_REGEX =
|
|||||||
/doubao-(?:1[.-]5-thinking-vision-pro|1[.-]5-thinking-pro-m|seed-1[.-]6(?:-flash)?(?!-(?:thinking)(?:-|$)))(?:-[\w-]+)*/i
|
/doubao-(?:1[.-]5-thinking-vision-pro|1[.-]5-thinking-pro-m|seed-1[.-]6(?:-flash)?(?!-(?:thinking)(?:-|$)))(?:-[\w-]+)*/i
|
||||||
|
|
||||||
// 支持 auto 的 Doubao 模型 doubao-seed-1.6-xxx doubao-seed-1-6-xxx doubao-1-5-thinking-pro-m-xxx
|
// 支持 auto 的 Doubao 模型 doubao-seed-1.6-xxx doubao-seed-1-6-xxx doubao-1-5-thinking-pro-m-xxx
|
||||||
|
// Auto thinking is no longer supported after version 251015, see https://console.volcengine.com/ark/region:ark+cn-beijing/model/detail?Id=doubao-seed-1-6
|
||||||
export const DOUBAO_THINKING_AUTO_MODEL_REGEX =
|
export const DOUBAO_THINKING_AUTO_MODEL_REGEX =
|
||||||
/doubao-(1-5-thinking-pro-m|seed-1[.-]6)(?!-(?:flash|thinking)(?:-|$))(?:-[\w-]+)*/i
|
/doubao-(1-5-thinking-pro-m|seed-1[.-]6)(?!-(?:flash|thinking)(?:-|$))(?:-lite)?(?!-251015)(?:-\d+)?$/i
|
||||||
|
|
||||||
export function isDoubaoThinkingAutoModel(model: Model): boolean {
|
export function isDoubaoThinkingAutoModel(model: Model): boolean {
|
||||||
const modelId = getLowerBaseModelName(model.id)
|
const modelId = getLowerBaseModelName(model.id)
|
||||||
return DOUBAO_THINKING_AUTO_MODEL_REGEX.test(modelId) || DOUBAO_THINKING_AUTO_MODEL_REGEX.test(model.name)
|
return DOUBAO_THINKING_AUTO_MODEL_REGEX.test(modelId) || DOUBAO_THINKING_AUTO_MODEL_REGEX.test(model.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isDoubaoSeedAfter251015(model: Model): boolean {
|
||||||
|
const pattern = new RegExp(/doubao-seed-1-6-(?:lite-)?251015/i)
|
||||||
|
const result = pattern.test(model.id)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
export function isSupportedThinkingTokenDoubaoModel(model?: Model): boolean {
|
export function isSupportedThinkingTokenDoubaoModel(model?: Model): boolean {
|
||||||
if (!model) {
|
if (!model) {
|
||||||
return false
|
return false
|
||||||
|
|||||||
@ -14,6 +14,7 @@ export function usePaintings() {
|
|||||||
const aihubmix_image_upscale = useAppSelector((state) => state.paintings.aihubmix_image_upscale)
|
const aihubmix_image_upscale = useAppSelector((state) => state.paintings.aihubmix_image_upscale)
|
||||||
const openai_image_generate = useAppSelector((state) => state.paintings.openai_image_generate)
|
const openai_image_generate = useAppSelector((state) => state.paintings.openai_image_generate)
|
||||||
const openai_image_edit = useAppSelector((state) => state.paintings.openai_image_edit)
|
const openai_image_edit = useAppSelector((state) => state.paintings.openai_image_edit)
|
||||||
|
const ovms_paintings = useAppSelector((state) => state.paintings.ovms_paintings)
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -27,6 +28,7 @@ export function usePaintings() {
|
|||||||
aihubmix_image_upscale,
|
aihubmix_image_upscale,
|
||||||
openai_image_generate,
|
openai_image_generate,
|
||||||
openai_image_edit,
|
openai_image_edit,
|
||||||
|
ovms_paintings,
|
||||||
addPainting: (namespace: keyof PaintingsState, painting: PaintingAction) => {
|
addPainting: (namespace: keyof PaintingsState, painting: PaintingAction) => {
|
||||||
dispatch(addPainting({ namespace, painting }))
|
dispatch(addPainting({ namespace, painting }))
|
||||||
return painting
|
return painting
|
||||||
|
|||||||
@ -538,6 +538,7 @@
|
|||||||
"context": "Clear Context {{Command}}"
|
"context": "Clear Context {{Command}}"
|
||||||
},
|
},
|
||||||
"new_topic": "New Topic {{Command}}",
|
"new_topic": "New Topic {{Command}}",
|
||||||
|
"paste_text_file_confirm": "Paste into input bar?",
|
||||||
"pause": "Pause",
|
"pause": "Pause",
|
||||||
"placeholder": "Type your message here, press {{key}} to send - @ to Select Model, / to Include Tools",
|
"placeholder": "Type your message here, press {{key}} to send - @ to Select Model, / to Include Tools",
|
||||||
"placeholder_without_triggers": "Type your message here, press {{key}} to send",
|
"placeholder_without_triggers": "Type your message here, press {{key}} to send",
|
||||||
|
|||||||
@ -538,6 +538,7 @@
|
|||||||
"context": "清除上下文 {{Command}}"
|
"context": "清除上下文 {{Command}}"
|
||||||
},
|
},
|
||||||
"new_topic": "新话题 {{Command}}",
|
"new_topic": "新话题 {{Command}}",
|
||||||
|
"paste_text_file_confirm": "粘贴到输入框?",
|
||||||
"pause": "暂停",
|
"pause": "暂停",
|
||||||
"placeholder": "在这里输入消息,按 {{key}} 发送 - @ 选择模型, / 选择工具",
|
"placeholder": "在这里输入消息,按 {{key}} 发送 - @ 选择模型, / 选择工具",
|
||||||
"placeholder_without_triggers": "在这里输入消息,按 {{key}} 发送",
|
"placeholder_without_triggers": "在这里输入消息,按 {{key}} 发送",
|
||||||
|
|||||||
@ -538,6 +538,7 @@
|
|||||||
"context": "清除上下文 {{Command}}"
|
"context": "清除上下文 {{Command}}"
|
||||||
},
|
},
|
||||||
"new_topic": "新話題 {{Command}}",
|
"new_topic": "新話題 {{Command}}",
|
||||||
|
"paste_text_file_confirm": "[to be translated]:粘贴到输入框?",
|
||||||
"pause": "暫停",
|
"pause": "暫停",
|
||||||
"placeholder": "在此輸入您的訊息,按 {{key}} 傳送 - @ 選擇模型,/ 包含工具",
|
"placeholder": "在此輸入您的訊息,按 {{key}} 傳送 - @ 選擇模型,/ 包含工具",
|
||||||
"placeholder_without_triggers": "在此輸入您的訊息,按 {{key}} 傳送",
|
"placeholder_without_triggers": "在此輸入您的訊息,按 {{key}} 傳送",
|
||||||
|
|||||||
@ -538,6 +538,7 @@
|
|||||||
"context": "Καθαρισμός ενδιάμεσων {{Command}}"
|
"context": "Καθαρισμός ενδιάμεσων {{Command}}"
|
||||||
},
|
},
|
||||||
"new_topic": "Νέο θέμα {{Command}}",
|
"new_topic": "Νέο θέμα {{Command}}",
|
||||||
|
"paste_text_file_confirm": "[to be translated]:粘贴到输入框?",
|
||||||
"pause": "Παύση",
|
"pause": "Παύση",
|
||||||
"placeholder": "Εισάγετε μήνυμα εδώ...",
|
"placeholder": "Εισάγετε μήνυμα εδώ...",
|
||||||
"placeholder_without_triggers": "Γράψτε το μήνυμά σας εδώ, πατήστε {{key}} για αποστολή",
|
"placeholder_without_triggers": "Γράψτε το μήνυμά σας εδώ, πατήστε {{key}} για αποστολή",
|
||||||
|
|||||||
@ -538,6 +538,7 @@
|
|||||||
"context": "Limpiar contexto {{Command}}"
|
"context": "Limpiar contexto {{Command}}"
|
||||||
},
|
},
|
||||||
"new_topic": "Nuevo tema {{Command}}",
|
"new_topic": "Nuevo tema {{Command}}",
|
||||||
|
"paste_text_file_confirm": "[to be translated]:粘贴到输入框?",
|
||||||
"pause": "Pausar",
|
"pause": "Pausar",
|
||||||
"placeholder": "Escribe aquí tu mensaje...",
|
"placeholder": "Escribe aquí tu mensaje...",
|
||||||
"placeholder_without_triggers": "Escribe tu mensaje aquí, presiona {{key}} para enviar",
|
"placeholder_without_triggers": "Escribe tu mensaje aquí, presiona {{key}} para enviar",
|
||||||
|
|||||||
@ -538,6 +538,7 @@
|
|||||||
"context": "Effacer le contexte {{Command}}"
|
"context": "Effacer le contexte {{Command}}"
|
||||||
},
|
},
|
||||||
"new_topic": "Nouveau sujet {{Command}}",
|
"new_topic": "Nouveau sujet {{Command}}",
|
||||||
|
"paste_text_file_confirm": "[to be translated]:粘贴到输入框?",
|
||||||
"pause": "Pause",
|
"pause": "Pause",
|
||||||
"placeholder": "Entrez votre message ici...",
|
"placeholder": "Entrez votre message ici...",
|
||||||
"placeholder_without_triggers": "Tapez votre message ici, appuyez sur {{key}} pour envoyer",
|
"placeholder_without_triggers": "Tapez votre message ici, appuyez sur {{key}} pour envoyer",
|
||||||
|
|||||||
@ -538,6 +538,7 @@
|
|||||||
"context": "コンテキストをクリア {{Command}}"
|
"context": "コンテキストをクリア {{Command}}"
|
||||||
},
|
},
|
||||||
"new_topic": "新しいトピック {{Command}}",
|
"new_topic": "新しいトピック {{Command}}",
|
||||||
|
"paste_text_file_confirm": "[to be translated]:粘贴到输入框?",
|
||||||
"pause": "一時停止",
|
"pause": "一時停止",
|
||||||
"placeholder": "ここにメッセージを入力し、{{key}} を押して送信...",
|
"placeholder": "ここにメッセージを入力し、{{key}} を押して送信...",
|
||||||
"placeholder_without_triggers": "ここにメッセージを入力し、{{key}} を押して送信...",
|
"placeholder_without_triggers": "ここにメッセージを入力し、{{key}} を押して送信...",
|
||||||
|
|||||||
@ -538,6 +538,7 @@
|
|||||||
"context": "Limpar contexto {{Command}}"
|
"context": "Limpar contexto {{Command}}"
|
||||||
},
|
},
|
||||||
"new_topic": "Novo tópico {{Command}}",
|
"new_topic": "Novo tópico {{Command}}",
|
||||||
|
"paste_text_file_confirm": "[to be translated]:粘贴到输入框?",
|
||||||
"pause": "Pausar",
|
"pause": "Pausar",
|
||||||
"placeholder": "Digite sua mensagem aqui...",
|
"placeholder": "Digite sua mensagem aqui...",
|
||||||
"placeholder_without_triggers": "Escreve a tua mensagem aqui, pressiona {{key}} para enviar",
|
"placeholder_without_triggers": "Escreve a tua mensagem aqui, pressiona {{key}} para enviar",
|
||||||
|
|||||||
@ -538,6 +538,7 @@
|
|||||||
"context": "Очистить контекст {{Command}}"
|
"context": "Очистить контекст {{Command}}"
|
||||||
},
|
},
|
||||||
"new_topic": "Новый топик {{Command}}",
|
"new_topic": "Новый топик {{Command}}",
|
||||||
|
"paste_text_file_confirm": "[to be translated]:粘贴到输入框?",
|
||||||
"pause": "Остановить",
|
"pause": "Остановить",
|
||||||
"placeholder": "Введите ваше сообщение здесь, нажмите {{key}} для отправки...",
|
"placeholder": "Введите ваше сообщение здесь, нажмите {{key}} для отправки...",
|
||||||
"placeholder_without_triggers": "Напишите сообщение здесь, нажмите {{key}} для отправки",
|
"placeholder_without_triggers": "Напишите сообщение здесь, нажмите {{key}} для отправки",
|
||||||
|
|||||||
@ -12,8 +12,8 @@ import {
|
|||||||
GlobalOutlined,
|
GlobalOutlined,
|
||||||
LinkOutlined
|
LinkOutlined
|
||||||
} from '@ant-design/icons'
|
} from '@ant-design/icons'
|
||||||
import { ColFlex } from '@cherrystudio/ui'
|
import { ColFlex, Tooltip } from '@cherrystudio/ui'
|
||||||
import { Tooltip } from '@cherrystudio/ui'
|
import ConfirmDialog from '@renderer/components/ConfirmDialog'
|
||||||
import CustomTag from '@renderer/components/Tags/CustomTag'
|
import CustomTag from '@renderer/components/Tags/CustomTag'
|
||||||
import { useAttachment } from '@renderer/hooks/useAttachment'
|
import { useAttachment } from '@renderer/hooks/useAttachment'
|
||||||
import FileManager from '@renderer/services/FileManager'
|
import FileManager from '@renderer/services/FileManager'
|
||||||
@ -21,13 +21,15 @@ import type { FileMetadata } from '@renderer/types'
|
|||||||
import { formatFileSize } from '@renderer/utils'
|
import { formatFileSize } from '@renderer/utils'
|
||||||
import { Image } from 'antd'
|
import { Image } from 'antd'
|
||||||
import { isEmpty } from 'lodash'
|
import { isEmpty } from 'lodash'
|
||||||
import type { FC } from 'react'
|
import type { FC, MouseEvent } from 'react'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
files: FileMetadata[]
|
files: FileMetadata[]
|
||||||
setFiles: (files: FileMetadata[]) => void
|
setFiles: (files: FileMetadata[]) => void
|
||||||
|
onAttachmentContextMenu?: (file: FileMetadata, event: MouseEvent<HTMLDivElement>) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const MAX_FILENAME_DISPLAY_LENGTH = 20
|
const MAX_FILENAME_DISPLAY_LENGTH = 20
|
||||||
@ -133,24 +135,91 @@ export const FileNameRender: FC<{ file: FileMetadata }> = ({ file }) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const AttachmentPreview: FC<Props> = ({ files, setFiles }) => {
|
const AttachmentPreview: FC<Props> = ({ files, setFiles, onAttachmentContextMenu }) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const [contextMenu, setContextMenu] = useState<{
|
||||||
|
file: FileMetadata
|
||||||
|
x: number
|
||||||
|
y: number
|
||||||
|
} | null>(null)
|
||||||
|
|
||||||
|
const handleContextMenu = async (file: FileMetadata, event: MouseEvent<HTMLDivElement>) => {
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
|
||||||
|
// 获取被点击元素的位置
|
||||||
|
const target = event.currentTarget as HTMLElement
|
||||||
|
const rect = target.getBoundingClientRect()
|
||||||
|
|
||||||
|
// 计算对话框位置:附件标签的中心位置
|
||||||
|
const x = rect.left + rect.width / 2
|
||||||
|
const y = rect.top
|
||||||
|
|
||||||
|
try {
|
||||||
|
const isText = await window.api.file.isTextFile(file.path)
|
||||||
|
if (!isText) {
|
||||||
|
setContextMenu(null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setContextMenu({
|
||||||
|
file,
|
||||||
|
x,
|
||||||
|
y
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
setContextMenu(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleConfirm = () => {
|
||||||
|
if (contextMenu && onAttachmentContextMenu) {
|
||||||
|
// Create a synthetic mouse event for the callback
|
||||||
|
const syntheticEvent = {
|
||||||
|
preventDefault: () => {},
|
||||||
|
stopPropagation: () => {}
|
||||||
|
} as MouseEvent<HTMLDivElement>
|
||||||
|
onAttachmentContextMenu(contextMenu.file, syntheticEvent)
|
||||||
|
}
|
||||||
|
setContextMenu(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
setContextMenu(null)
|
||||||
|
}
|
||||||
|
|
||||||
if (isEmpty(files)) {
|
if (isEmpty(files)) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ContentContainer>
|
<>
|
||||||
{files.map((file) => (
|
<ContentContainer>
|
||||||
<CustomTag
|
{files.map((file) => (
|
||||||
key={file.id}
|
<CustomTag
|
||||||
icon={getFileIcon(file.ext)}
|
key={file.id}
|
||||||
color="#37a5aa"
|
icon={getFileIcon(file.ext)}
|
||||||
closable
|
color="#37a5aa"
|
||||||
onClose={() => setFiles(files.filter((f) => f.id !== file.id))}>
|
closable
|
||||||
<FileNameRender file={file} />
|
onClose={() => setFiles(files.filter((f) => f.id !== file.id))}
|
||||||
</CustomTag>
|
onContextMenu={(event) => {
|
||||||
))}
|
void handleContextMenu(file, event)
|
||||||
</ContentContainer>
|
}}>
|
||||||
|
<FileNameRender file={file} />
|
||||||
|
</CustomTag>
|
||||||
|
))}
|
||||||
|
</ContentContainer>
|
||||||
|
|
||||||
|
{contextMenu && (
|
||||||
|
<ConfirmDialog
|
||||||
|
x={contextMenu.x}
|
||||||
|
y={contextMenu.y}
|
||||||
|
message={t('chat.input.paste_text_file_confirm')}
|
||||||
|
onConfirm={handleConfirm}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -296,6 +296,53 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
|
|||||||
}
|
}
|
||||||
}, [isTranslating, text, getLanguageByLangcode, targetLanguage, setTimeoutTimer, resizeTextArea])
|
}, [isTranslating, text, getLanguageByLangcode, targetLanguage, setTimeoutTimer, resizeTextArea])
|
||||||
|
|
||||||
|
const appendTxtContentToInput = useCallback(
|
||||||
|
async (file: FileType, event: React.MouseEvent<HTMLDivElement>) => {
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
|
||||||
|
try {
|
||||||
|
const targetPath = file.path
|
||||||
|
const content = await window.api.file.readExternal(targetPath, true)
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(content)
|
||||||
|
} catch (clipboardError) {
|
||||||
|
logger.warn('Failed to copy txt attachment content to clipboard:', clipboardError as Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
setText((prev) => {
|
||||||
|
if (!prev) {
|
||||||
|
return content
|
||||||
|
}
|
||||||
|
|
||||||
|
const needsSeparator = !prev.endsWith('\n')
|
||||||
|
return needsSeparator ? `${prev}\n${content}` : prev + content
|
||||||
|
})
|
||||||
|
|
||||||
|
setFiles((prev) => prev.filter((currentFile) => currentFile.id !== file.id))
|
||||||
|
|
||||||
|
setTimeoutTimer(
|
||||||
|
'appendTxtAttachment',
|
||||||
|
() => {
|
||||||
|
const textArea = textareaRef.current?.resizableTextArea?.textArea
|
||||||
|
if (textArea) {
|
||||||
|
const end = textArea.value.length
|
||||||
|
textArea.focus()
|
||||||
|
textArea.setSelectionRange(end, end)
|
||||||
|
}
|
||||||
|
|
||||||
|
resizeTextArea(true)
|
||||||
|
},
|
||||||
|
0
|
||||||
|
)
|
||||||
|
} catch (error) {
|
||||||
|
logger.warn('Failed to append txt attachment content:', error as Error)
|
||||||
|
window.toast.error(t('chat.input.file_error'))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[resizeTextArea, setTimeoutTimer, t]
|
||||||
|
)
|
||||||
|
|
||||||
const handleKeyDown = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
const handleKeyDown = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||||
// 按下Tab键,自动选中${xxx}
|
// 按下Tab键,自动选中${xxx}
|
||||||
if (event.key === 'Tab' && inputFocus) {
|
if (event.key === 'Tab' && inputFocus) {
|
||||||
@ -834,7 +881,9 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
|
|||||||
id="inputbar"
|
id="inputbar"
|
||||||
className={classNames('inputbar-container', inputFocus && 'focus', isFileDragging && 'file-dragging')}
|
className={classNames('inputbar-container', inputFocus && 'focus', isFileDragging && 'file-dragging')}
|
||||||
ref={containerRef}>
|
ref={containerRef}>
|
||||||
{files.length > 0 && <AttachmentPreview files={files} setFiles={setFiles} />}
|
{files.length > 0 && (
|
||||||
|
<AttachmentPreview files={files} setFiles={setFiles} onAttachmentContextMenu={appendTxtContentToInput} />
|
||||||
|
)}
|
||||||
{selectedKnowledgeBases.length > 0 && (
|
{selectedKnowledgeBases.length > 0 && (
|
||||||
<KnowledgeBaseInput
|
<KnowledgeBaseInput
|
||||||
selectedKnowledgeBases={selectedKnowledgeBases}
|
selectedKnowledgeBases={selectedKnowledgeBases}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { usePreference } from '@data/hooks/usePreference'
|
import { usePreference } from '@data/hooks/usePreference'
|
||||||
import { cn } from '@heroui/react'
|
import { cn, Tooltip } from '@heroui/react'
|
||||||
import { DeleteIcon, EditIcon } from '@renderer/components/Icons'
|
import { DeleteIcon, EditIcon } from '@renderer/components/Icons'
|
||||||
import { useSessions } from '@renderer/hooks/agents/useSessions'
|
import { useSessions } from '@renderer/hooks/agents/useSessions'
|
||||||
import AgentSettingsPopup from '@renderer/pages/settings/AgentSettings/AgentSettingsPopup'
|
import AgentSettingsPopup from '@renderer/pages/settings/AgentSettings/AgentSettingsPopup'
|
||||||
@ -44,17 +44,13 @@ const AgentItem: FC<AgentItemProps> = ({ agent, isActive, onDelete, onPress }) =
|
|||||||
<AgentNameWrapper>
|
<AgentNameWrapper>
|
||||||
<AgentLabel agent={agent} />
|
<AgentLabel agent={agent} />
|
||||||
</AgentNameWrapper>
|
</AgentNameWrapper>
|
||||||
|
{isActive && (
|
||||||
|
<MenuButton>
|
||||||
|
<SessionCount>{sessions.length}</SessionCount>
|
||||||
|
</MenuButton>
|
||||||
|
)}
|
||||||
|
{!isActive && <BotIcon />}
|
||||||
</AssistantNameRow>
|
</AssistantNameRow>
|
||||||
{isActive && (
|
|
||||||
<MenuButton>
|
|
||||||
<SessionCount>{sessions.length}</SessionCount>
|
|
||||||
</MenuButton>
|
|
||||||
)}
|
|
||||||
{!isActive && (
|
|
||||||
<BotIcon>
|
|
||||||
<Bot size={16} className="text-primary" />
|
|
||||||
</BotIcon>
|
|
||||||
)}
|
|
||||||
</Container>
|
</Container>
|
||||||
</ContextMenuTrigger>
|
</ContextMenuTrigger>
|
||||||
<ContextMenuContent>
|
<ContextMenuContent>
|
||||||
@ -111,29 +107,27 @@ export const AgentNameWrapper: React.FC<React.HTMLAttributes<HTMLDivElement>> =
|
|||||||
export const MenuButton: React.FC<React.HTMLAttributes<HTMLDivElement>> = ({ className, ...props }) => (
|
export const MenuButton: React.FC<React.HTMLAttributes<HTMLDivElement>> = ({ className, ...props }) => (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'absolute top-[6px] right-[9px] flex h-[22px] min-h-[22px] w-[22px] flex-row items-center justify-center rounded-full border border-[var(--color-border)] bg-[var(--color-background)] px-[5px]',
|
'flex h-5 min-h-5 w-5 flex-row items-center justify-center rounded-full border border-[var(--color-border)] bg-[var(--color-background)]',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
||||||
export const BotIcon: React.FC<React.HTMLAttributes<HTMLDivElement>> = ({ className, ...props }) => (
|
export const BotIcon: React.FC<React.HTMLAttributes<HTMLDivElement>> = ({ ...props }) => {
|
||||||
<div
|
const { t } = useTranslation()
|
||||||
className={cn(
|
return (
|
||||||
'absolute top-[8px] right-[12px] flex flex-row items-center justify-center rounded-full text-[14px] text-[var(--color-text)]',
|
<Tooltip content={t('common.agent_one')} delay={500} closeDelay={0}>
|
||||||
className
|
<MenuButton {...props}>
|
||||||
)}
|
<Bot size={14} className="text-primary" />
|
||||||
{...props}
|
</MenuButton>
|
||||||
/>
|
</Tooltip>
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export const SessionCount: React.FC<React.HTMLAttributes<HTMLDivElement>> = ({ className, ...props }) => (
|
export const SessionCount: React.FC<React.HTMLAttributes<HTMLDivElement>> = ({ className, ...props }) => (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn('flex flex-row items-center justify-center rounded-full text-[var(--color-text)] text-xs', className)}
|
||||||
'flex flex-row items-center justify-center rounded-full text-[10px] text-[var(--color-text)]',
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|||||||
690
src/renderer/src/pages/paintings/OvmsPage.tsx
Normal file
690
src/renderer/src/pages/paintings/OvmsPage.tsx
Normal file
@ -0,0 +1,690 @@
|
|||||||
|
import { PlusOutlined, RedoOutlined } from '@ant-design/icons'
|
||||||
|
import { Button, RowFlex, Switch, Tooltip } from '@cherrystudio/ui'
|
||||||
|
import { useCache } from '@data/hooks/useCache'
|
||||||
|
import { loggerService } from '@logger'
|
||||||
|
import { Navbar, NavbarCenter, NavbarRight } from '@renderer/components/app/Navbar'
|
||||||
|
import Scrollbar from '@renderer/components/Scrollbar'
|
||||||
|
import { isMac } from '@renderer/config/constant'
|
||||||
|
import { getProviderLogo } from '@renderer/config/providers'
|
||||||
|
import { LanguagesEnum } from '@renderer/config/translate'
|
||||||
|
import { usePaintings } from '@renderer/hooks/usePaintings'
|
||||||
|
import { useAllProviders } from '@renderer/hooks/useProvider'
|
||||||
|
import { useSettings } from '@renderer/hooks/useSettings'
|
||||||
|
import { getProviderLabel } from '@renderer/i18n/label'
|
||||||
|
import FileManager from '@renderer/services/FileManager'
|
||||||
|
import { translateText } from '@renderer/services/TranslateService'
|
||||||
|
import type { FileMetadata, OvmsPainting } from '@renderer/types'
|
||||||
|
import { getErrorMessage, uuid } from '@renderer/utils'
|
||||||
|
import { Avatar, Input, InputNumber, Select, Slider } from 'antd'
|
||||||
|
import TextArea from 'antd/es/input/TextArea'
|
||||||
|
import { Info } from 'lucide-react'
|
||||||
|
import type { FC } from 'react'
|
||||||
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { useLocation, useNavigate } from 'react-router-dom'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
|
||||||
|
import SendMessageButton from '../home/Inputbar/SendMessageButton'
|
||||||
|
import { SettingHelpLink, SettingTitle } from '../settings'
|
||||||
|
import Artboard from './components/Artboard'
|
||||||
|
import PaintingsList from './components/PaintingsList'
|
||||||
|
import {
|
||||||
|
type ConfigItem,
|
||||||
|
createDefaultOvmsPainting,
|
||||||
|
createOvmsConfig,
|
||||||
|
DEFAULT_OVMS_PAINTING,
|
||||||
|
getOvmsModels,
|
||||||
|
OVMS_MODELS
|
||||||
|
} from './config/ovmsConfig'
|
||||||
|
|
||||||
|
const logger = loggerService.withContext('OvmsPage')
|
||||||
|
|
||||||
|
const OvmsPage: FC<{ Options: string[] }> = ({ Options }) => {
|
||||||
|
const { addPainting, removePainting, updatePainting, ovms_paintings } = usePaintings()
|
||||||
|
const ovmsPaintings = useMemo(() => ovms_paintings || [], [ovms_paintings])
|
||||||
|
const [painting, setPainting] = useState<OvmsPainting>(ovmsPaintings[0] || DEFAULT_OVMS_PAINTING)
|
||||||
|
const [currentImageIndex, setCurrentImageIndex] = useState(0)
|
||||||
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
|
const [abortController, setAbortController] = useState<AbortController | null>(null)
|
||||||
|
const [spaceClickCount, setSpaceClickCount] = useState(0)
|
||||||
|
const [isTranslating, setIsTranslating] = useState(false)
|
||||||
|
const [availableModels, setAvailableModels] = useState<Array<{ label: string; value: string }>>([])
|
||||||
|
const [ovmsConfig, setOvmsConfig] = useState<ConfigItem[]>([])
|
||||||
|
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const providers = useAllProviders()
|
||||||
|
const providerOptions = Options.map((option) => {
|
||||||
|
const provider = providers.find((p) => p.id === option)
|
||||||
|
if (provider) {
|
||||||
|
return {
|
||||||
|
label: getProviderLabel(provider.id),
|
||||||
|
value: provider.id
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
label: 'Unknown Provider',
|
||||||
|
value: undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const [generating, setGenerating] = useCache('chat.generating')
|
||||||
|
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const location = useLocation()
|
||||||
|
const { autoTranslateWithSpace } = useSettings()
|
||||||
|
const spaceClickTimer = useRef<NodeJS.Timeout>(null)
|
||||||
|
const ovmsProvider = providers.find((p) => p.id === 'ovms')!
|
||||||
|
|
||||||
|
const getNewPainting = useCallback(() => {
|
||||||
|
if (availableModels.length > 0) {
|
||||||
|
return createDefaultOvmsPainting(availableModels)
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...DEFAULT_OVMS_PAINTING,
|
||||||
|
id: uuid()
|
||||||
|
}
|
||||||
|
}, [availableModels])
|
||||||
|
|
||||||
|
const textareaRef = useRef<any>(null)
|
||||||
|
|
||||||
|
// Load available models on component mount
|
||||||
|
useEffect(() => {
|
||||||
|
const loadModels = () => {
|
||||||
|
try {
|
||||||
|
// Get OVMS provider to access its models
|
||||||
|
const ovmsProvider = providers.find((p) => p.id === 'ovms')
|
||||||
|
const providerModels = ovmsProvider?.models || []
|
||||||
|
|
||||||
|
// Filter and format models for image generation
|
||||||
|
const filteredModels = getOvmsModels(providerModels)
|
||||||
|
setAvailableModels(filteredModels)
|
||||||
|
setOvmsConfig(createOvmsConfig(filteredModels))
|
||||||
|
|
||||||
|
// Update painting if it doesn't have a valid model
|
||||||
|
if (filteredModels.length > 0 && !filteredModels.some((m) => m.value === painting.model)) {
|
||||||
|
const defaultPainting = createDefaultOvmsPainting(filteredModels)
|
||||||
|
setPainting(defaultPainting)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`Failed to load OVMS models: ${error}`)
|
||||||
|
// Use default config if loading fails
|
||||||
|
setOvmsConfig(createOvmsConfig())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadModels()
|
||||||
|
}, [providers, painting.model]) // Re-run when providers change
|
||||||
|
|
||||||
|
const updatePaintingState = (updates: Partial<OvmsPainting>) => {
|
||||||
|
const updatedPainting = { ...painting, ...updates }
|
||||||
|
setPainting(updatedPainting)
|
||||||
|
updatePainting('ovms_paintings', updatedPainting)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleError = (error: unknown) => {
|
||||||
|
if (error instanceof Error && error.name !== 'AbortError') {
|
||||||
|
window.modal.error({
|
||||||
|
content: getErrorMessage(error),
|
||||||
|
centered: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const downloadImages = async (urls: string[]) => {
|
||||||
|
const downloadedFiles = await Promise.all(
|
||||||
|
urls.map(async (url) => {
|
||||||
|
try {
|
||||||
|
if (!url?.trim()) {
|
||||||
|
logger.error('Image URL is empty, possibly due to prohibited prompt')
|
||||||
|
window.toast.warning(t('message.empty_url'))
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return await window.api.file.download(url)
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`Failed to download image: ${error}`)
|
||||||
|
if (
|
||||||
|
error instanceof Error &&
|
||||||
|
(error.message.includes('Failed to parse URL') || error.message.includes('Invalid URL'))
|
||||||
|
) {
|
||||||
|
window.toast.warning(t('message.empty_url'))
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
return downloadedFiles.filter((file): file is FileMetadata => file !== null)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onGenerate = async () => {
|
||||||
|
if (painting.files.length > 0) {
|
||||||
|
const confirmed = await window.modal.confirm({
|
||||||
|
content: t('paintings.regenerate.confirm'),
|
||||||
|
centered: true
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!confirmed) return
|
||||||
|
await FileManager.deleteFiles(painting.files)
|
||||||
|
}
|
||||||
|
|
||||||
|
const prompt = textareaRef.current?.resizableTextArea?.textArea?.value || ''
|
||||||
|
updatePaintingState({ prompt })
|
||||||
|
|
||||||
|
if (!painting.model || !painting.prompt) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const controller = new AbortController()
|
||||||
|
setAbortController(controller)
|
||||||
|
setIsLoading(true)
|
||||||
|
setGenerating(true)
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Prepare request body for OVMS
|
||||||
|
const requestBody = {
|
||||||
|
model: painting.model,
|
||||||
|
prompt: painting.prompt,
|
||||||
|
size: painting.size || '512x512',
|
||||||
|
num_inference_steps: painting.num_inference_steps || 4,
|
||||||
|
rng_seed: painting.rng_seed || 0
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info('OVMS API request:', requestBody)
|
||||||
|
|
||||||
|
const response = await fetch(`${ovmsProvider.apiHost}images/generations`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(requestBody),
|
||||||
|
signal: controller.signal
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.json().catch(() => ({ error: { message: `HTTP ${response.status}` } }))
|
||||||
|
logger.error('OVMS API error:', errorData)
|
||||||
|
throw new Error(errorData.error?.message || 'Image generation failed')
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json()
|
||||||
|
logger.info('OVMS API response:', data)
|
||||||
|
|
||||||
|
// Handle base64 encoded images
|
||||||
|
if (data.data && data.data.length > 0) {
|
||||||
|
const base64s = data.data.filter((item) => item.b64_json).map((item) => item.b64_json)
|
||||||
|
|
||||||
|
if (base64s.length > 0) {
|
||||||
|
const validFiles = await Promise.all(
|
||||||
|
base64s.map(async (base64) => {
|
||||||
|
return await window.api.file.saveBase64Image(base64)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
await FileManager.addFiles(validFiles)
|
||||||
|
updatePaintingState({ files: validFiles, urls: validFiles.map((file) => file.name) })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle URL-based images if available
|
||||||
|
const urls = data.data.filter((item) => item.url).map((item) => item.url)
|
||||||
|
|
||||||
|
if (urls.length > 0) {
|
||||||
|
const validFiles = await downloadImages(urls)
|
||||||
|
await FileManager.addFiles(validFiles)
|
||||||
|
updatePaintingState({ files: validFiles, urls })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error: unknown) {
|
||||||
|
handleError(error)
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false)
|
||||||
|
setGenerating(false)
|
||||||
|
setAbortController(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleRetry = async (painting: OvmsPainting) => {
|
||||||
|
setIsLoading(true)
|
||||||
|
try {
|
||||||
|
const validFiles = await downloadImages(painting.urls)
|
||||||
|
await FileManager.addFiles(validFiles)
|
||||||
|
updatePaintingState({ files: validFiles, urls: painting.urls })
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error)
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onCancel = () => {
|
||||||
|
abortController?.abort()
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextImage = () => {
|
||||||
|
setCurrentImageIndex((prev) => (prev + 1) % painting.files.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
const prevImage = () => {
|
||||||
|
setCurrentImageIndex((prev) => (prev - 1 + painting.files.length) % painting.files.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleAddPainting = () => {
|
||||||
|
const newPainting = addPainting('ovms_paintings', getNewPainting())
|
||||||
|
updatePainting('ovms_paintings', newPainting)
|
||||||
|
setPainting(newPainting)
|
||||||
|
return newPainting
|
||||||
|
}
|
||||||
|
|
||||||
|
const onDeletePainting = (paintingToDelete: OvmsPainting) => {
|
||||||
|
if (paintingToDelete.id === painting.id) {
|
||||||
|
const currentIndex = ovmsPaintings.findIndex((p) => p.id === paintingToDelete.id)
|
||||||
|
|
||||||
|
if (currentIndex > 0) {
|
||||||
|
setPainting(ovmsPaintings[currentIndex - 1])
|
||||||
|
} else if (ovmsPaintings.length > 1) {
|
||||||
|
setPainting(ovmsPaintings[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
removePainting('ovms_paintings', paintingToDelete)
|
||||||
|
}
|
||||||
|
|
||||||
|
const translate = async () => {
|
||||||
|
if (isTranslating) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!painting.prompt) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
setIsTranslating(true)
|
||||||
|
const translatedText = await translateText(painting.prompt, LanguagesEnum.enUS)
|
||||||
|
updatePaintingState({ prompt: translatedText })
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Translation failed:', error as Error)
|
||||||
|
} finally {
|
||||||
|
setIsTranslating(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleKeyDown = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||||
|
if (autoTranslateWithSpace && event.key === ' ') {
|
||||||
|
setSpaceClickCount((prev) => prev + 1)
|
||||||
|
|
||||||
|
if (spaceClickTimer.current) {
|
||||||
|
clearTimeout(spaceClickTimer.current)
|
||||||
|
}
|
||||||
|
|
||||||
|
spaceClickTimer.current = setTimeout(() => {
|
||||||
|
setSpaceClickCount(0)
|
||||||
|
}, 200)
|
||||||
|
|
||||||
|
if (spaceClickCount === 2) {
|
||||||
|
setSpaceClickCount(0)
|
||||||
|
setIsTranslating(true)
|
||||||
|
translate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleProviderChange = (providerId: string) => {
|
||||||
|
const routeName = location.pathname.split('/').pop()
|
||||||
|
if (providerId !== routeName) {
|
||||||
|
navigate('../' + providerId, { replace: true })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle random seed generation
|
||||||
|
const handleRandomSeed = () => {
|
||||||
|
const randomSeed = Math.floor(Math.random() * 2147483647)
|
||||||
|
updatePaintingState({ rng_seed: randomSeed })
|
||||||
|
return randomSeed
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render configuration form
|
||||||
|
const renderConfigForm = (item: ConfigItem) => {
|
||||||
|
switch (item.type) {
|
||||||
|
case 'select': {
|
||||||
|
const isDisabled = typeof item.disabled === 'function' ? item.disabled(item, painting) : item.disabled
|
||||||
|
const selectOptions =
|
||||||
|
typeof item.options === 'function'
|
||||||
|
? item.options(item, painting).map((option) => ({
|
||||||
|
...option,
|
||||||
|
label: option.label.startsWith('paintings.') ? t(option.label) : option.label
|
||||||
|
}))
|
||||||
|
: item.options?.map((option) => ({
|
||||||
|
...option,
|
||||||
|
label: option.label.startsWith('paintings.') ? t(option.label) : option.label
|
||||||
|
}))
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
listHeight={500}
|
||||||
|
disabled={isDisabled}
|
||||||
|
value={painting[item.key!] || item.initialValue}
|
||||||
|
options={selectOptions as any}
|
||||||
|
onChange={(v) => updatePaintingState({ [item.key!]: v })}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
case 'slider': {
|
||||||
|
return (
|
||||||
|
<SliderContainer>
|
||||||
|
<Slider
|
||||||
|
min={item.min}
|
||||||
|
max={item.max}
|
||||||
|
step={item.step}
|
||||||
|
value={(painting[item.key!] || item.initialValue) as number}
|
||||||
|
onChange={(v) => updatePaintingState({ [item.key!]: v })}
|
||||||
|
/>
|
||||||
|
<StyledInputNumber
|
||||||
|
min={item.min}
|
||||||
|
max={item.max}
|
||||||
|
step={item.step}
|
||||||
|
value={(painting[item.key!] || item.initialValue) as number}
|
||||||
|
onChange={(v) => updatePaintingState({ [item.key!]: v })}
|
||||||
|
/>
|
||||||
|
</SliderContainer>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
case 'input':
|
||||||
|
return (
|
||||||
|
<Input
|
||||||
|
value={(painting[item.key!] || item.initialValue) as string}
|
||||||
|
onChange={(e) => updatePaintingState({ [item.key!]: e.target.value })}
|
||||||
|
suffix={
|
||||||
|
item.key === 'rng_seed' ? (
|
||||||
|
<RedoOutlined onClick={handleRandomSeed} style={{ cursor: 'pointer', color: 'var(--color-text-2)' }} />
|
||||||
|
) : (
|
||||||
|
item.suffix
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
case 'inputNumber':
|
||||||
|
return (
|
||||||
|
<InputNumber
|
||||||
|
min={item.min}
|
||||||
|
max={item.max}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
value={(painting[item.key!] || item.initialValue) as number}
|
||||||
|
onChange={(v) => updatePaintingState({ [item.key!]: v })}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
case 'textarea':
|
||||||
|
return (
|
||||||
|
<TextArea
|
||||||
|
value={(painting[item.key!] || item.initialValue) as string}
|
||||||
|
onChange={(e) => updatePaintingState({ [item.key!]: e.target.value })}
|
||||||
|
spellCheck={false}
|
||||||
|
rows={4}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
case 'switch':
|
||||||
|
return (
|
||||||
|
<RowFlex>
|
||||||
|
<Switch
|
||||||
|
checked={(painting[item.key!] || item.initialValue) as boolean}
|
||||||
|
onChange={(checked) => updatePaintingState({ [item.key!]: checked })}
|
||||||
|
/>
|
||||||
|
</RowFlex>
|
||||||
|
)
|
||||||
|
default:
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render configuration item
|
||||||
|
const renderConfigItem = (item: ConfigItem, index: number) => {
|
||||||
|
return (
|
||||||
|
<div key={index}>
|
||||||
|
<SettingTitle style={{ marginBottom: 5, marginTop: 15 }}>
|
||||||
|
{t(item.title!)}
|
||||||
|
{item.tooltip && (
|
||||||
|
<Tooltip title={t(item.tooltip)}>
|
||||||
|
<InfoIcon />
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</SettingTitle>
|
||||||
|
{renderConfigForm(item)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onSelectPainting = (newPainting: OvmsPainting) => {
|
||||||
|
if (generating) return
|
||||||
|
setPainting(newPainting)
|
||||||
|
setCurrentImageIndex(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (ovmsPaintings.length === 0) {
|
||||||
|
const newPainting = getNewPainting()
|
||||||
|
addPainting('ovms_paintings', newPainting)
|
||||||
|
setPainting(newPainting)
|
||||||
|
}
|
||||||
|
}, [ovmsPaintings, addPainting, getNewPainting])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const timer = spaceClickTimer.current
|
||||||
|
return () => {
|
||||||
|
if (timer) {
|
||||||
|
clearTimeout(timer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<Navbar>
|
||||||
|
<NavbarCenter style={{ borderRight: 'none' }}>{t('paintings.title')}</NavbarCenter>
|
||||||
|
{isMac && (
|
||||||
|
<NavbarRight style={{ justifyContent: 'flex-end' }}>
|
||||||
|
<Button size="sm" className="nodrag" startContent={<PlusOutlined />} onPress={handleAddPainting}>
|
||||||
|
{t('paintings.button.new.image')}
|
||||||
|
</Button>
|
||||||
|
</NavbarRight>
|
||||||
|
)}
|
||||||
|
</Navbar>
|
||||||
|
<ContentContainer id="content-container">
|
||||||
|
<LeftContainer>
|
||||||
|
<Scrollbar>
|
||||||
|
<div style={{ padding: '20px' }}>
|
||||||
|
<ProviderTitleContainer>
|
||||||
|
<SettingTitle style={{ marginBottom: 5 }}>{t('common.provider')}</SettingTitle>
|
||||||
|
<SettingHelpLink
|
||||||
|
target="_blank"
|
||||||
|
href="https://docs.openvino.ai/2025/model-server/ovms_demos_image_generation.html">
|
||||||
|
{t('paintings.learn_more')}
|
||||||
|
<ProviderLogo
|
||||||
|
shape="square"
|
||||||
|
src={getProviderLogo(ovmsProvider.id)}
|
||||||
|
size={16}
|
||||||
|
style={{ marginLeft: 5 }}
|
||||||
|
/>
|
||||||
|
</SettingHelpLink>
|
||||||
|
</ProviderTitleContainer>
|
||||||
|
|
||||||
|
<Select
|
||||||
|
value={providerOptions.find((p) => p.value === 'ovms')?.value || 'ovms'}
|
||||||
|
onChange={handleProviderChange}
|
||||||
|
style={{ width: '100%', marginBottom: 15 }}>
|
||||||
|
{providerOptions.map((provider) => (
|
||||||
|
<Select.Option value={provider.value} key={provider.value}>
|
||||||
|
<SelectOptionContainer>
|
||||||
|
<ProviderLogo shape="square" src={getProviderLogo(provider.value || '')} size={16} />
|
||||||
|
{provider.label}
|
||||||
|
</SelectOptionContainer>
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
|
||||||
|
{/* Render configuration items using JSON config */}
|
||||||
|
{ovmsConfig.map(renderConfigItem)}
|
||||||
|
</div>
|
||||||
|
</Scrollbar>
|
||||||
|
</LeftContainer>
|
||||||
|
<MainContainer>
|
||||||
|
<Artboard
|
||||||
|
painting={painting}
|
||||||
|
isLoading={isLoading}
|
||||||
|
currentImageIndex={currentImageIndex}
|
||||||
|
onPrevImage={prevImage}
|
||||||
|
onNextImage={nextImage}
|
||||||
|
onCancel={onCancel}
|
||||||
|
retry={handleRetry}
|
||||||
|
/>
|
||||||
|
<InputContainer>
|
||||||
|
<Textarea
|
||||||
|
ref={textareaRef}
|
||||||
|
variant="borderless"
|
||||||
|
disabled={isLoading}
|
||||||
|
value={painting.prompt}
|
||||||
|
spellCheck={false}
|
||||||
|
onChange={(e) => updatePaintingState({ prompt: e.target.value })}
|
||||||
|
placeholder={isTranslating ? t('paintings.translating') : t('paintings.prompt_placeholder')}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
/>
|
||||||
|
<Toolbar>
|
||||||
|
<ToolbarMenu>
|
||||||
|
<SendMessageButton
|
||||||
|
sendMessage={onGenerate}
|
||||||
|
disabled={isLoading || !painting.model || painting.model === OVMS_MODELS[0]?.value}
|
||||||
|
/>
|
||||||
|
</ToolbarMenu>
|
||||||
|
</Toolbar>
|
||||||
|
</InputContainer>
|
||||||
|
</MainContainer>
|
||||||
|
<PaintingsList
|
||||||
|
namespace="ovms_paintings"
|
||||||
|
paintings={ovmsPaintings}
|
||||||
|
selectedPainting={painting}
|
||||||
|
onSelectPainting={onSelectPainting}
|
||||||
|
onDeletePainting={onDeletePainting}
|
||||||
|
onNewPainting={handleAddPainting}
|
||||||
|
/>
|
||||||
|
</ContentContainer>
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const Container = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
`
|
||||||
|
|
||||||
|
const ContentContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: row;
|
||||||
|
height: 100%;
|
||||||
|
background-color: var(--color-background);
|
||||||
|
overflow: hidden;
|
||||||
|
`
|
||||||
|
|
||||||
|
const LeftContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
background-color: var(--color-background);
|
||||||
|
max-width: var(--assistants-width);
|
||||||
|
border-right: 0.5px solid var(--color-border);
|
||||||
|
overflow: hidden;
|
||||||
|
`
|
||||||
|
|
||||||
|
const MainContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
background-color: var(--color-background);
|
||||||
|
`
|
||||||
|
|
||||||
|
const InputContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 95px;
|
||||||
|
max-height: 95px;
|
||||||
|
position: relative;
|
||||||
|
border: 1px solid var(--color-border-soft);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
margin: 0 20px 15px 20px;
|
||||||
|
border-radius: 10px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const Textarea = styled(TextArea)`
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 0;
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
resize: none !important;
|
||||||
|
overflow: auto;
|
||||||
|
width: auto;
|
||||||
|
`
|
||||||
|
|
||||||
|
const Toolbar = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding: 0 8px;
|
||||||
|
padding-bottom: 0;
|
||||||
|
height: 40px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const ToolbarMenu = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const InfoIcon = styled(Info)`
|
||||||
|
margin-left: 5px;
|
||||||
|
cursor: help;
|
||||||
|
color: var(--color-text-2);
|
||||||
|
opacity: 0.6;
|
||||||
|
width: 14px;
|
||||||
|
height: 16px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const SliderContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
|
||||||
|
.ant-slider {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const StyledInputNumber = styled(InputNumber)`
|
||||||
|
width: 70px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const ProviderLogo = styled(Avatar)`
|
||||||
|
border: 0.5px solid var(--color-border);
|
||||||
|
`
|
||||||
|
|
||||||
|
const ProviderTitleContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const SelectOptionContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
`
|
||||||
|
|
||||||
|
export default OvmsPage
|
||||||
@ -5,19 +5,20 @@ import { useAppDispatch } from '@renderer/store'
|
|||||||
import { setDefaultPaintingProvider } from '@renderer/store/settings'
|
import { setDefaultPaintingProvider } from '@renderer/store/settings'
|
||||||
import type { PaintingProvider, SystemProviderId } from '@renderer/types'
|
import type { PaintingProvider, SystemProviderId } from '@renderer/types'
|
||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import { useEffect, useMemo } from 'react'
|
import { useEffect, useMemo, useState } from 'react'
|
||||||
import { Route, Routes, useParams } from 'react-router-dom'
|
import { Route, Routes, useParams } from 'react-router-dom'
|
||||||
|
|
||||||
import AihubmixPage from './AihubmixPage'
|
import AihubmixPage from './AihubmixPage'
|
||||||
import DmxapiPage from './DmxapiPage'
|
import DmxapiPage from './DmxapiPage'
|
||||||
import NewApiPage from './NewApiPage'
|
import NewApiPage from './NewApiPage'
|
||||||
|
import OvmsPage from './OvmsPage'
|
||||||
import SiliconPage from './SiliconPage'
|
import SiliconPage from './SiliconPage'
|
||||||
import TokenFluxPage from './TokenFluxPage'
|
import TokenFluxPage from './TokenFluxPage'
|
||||||
import ZhipuPage from './ZhipuPage'
|
import ZhipuPage from './ZhipuPage'
|
||||||
|
|
||||||
const logger = loggerService.withContext('PaintingsRoutePage')
|
const logger = loggerService.withContext('PaintingsRoutePage')
|
||||||
|
|
||||||
const BASE_OPTIONS: SystemProviderId[] = ['zhipu', 'aihubmix', 'silicon', 'dmxapi', 'tokenflux']
|
const BASE_OPTIONS: SystemProviderId[] = ['zhipu', 'aihubmix', 'silicon', 'dmxapi', 'tokenflux', 'ovms']
|
||||||
|
|
||||||
const PaintingsRoutePage: FC = () => {
|
const PaintingsRoutePage: FC = () => {
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
@ -27,28 +28,41 @@ const PaintingsRoutePage: FC = () => {
|
|||||||
const Options = useMemo(() => {
|
const Options = useMemo(() => {
|
||||||
return [...BASE_OPTIONS, ...providers.filter((p) => isNewApiProvider(p)).map((p) => p.id)]
|
return [...BASE_OPTIONS, ...providers.filter((p) => isNewApiProvider(p)).map((p) => p.id)]
|
||||||
}, [providers])
|
}, [providers])
|
||||||
|
const [ovmsStatus, setOvmsStatus] = useState<'not-installed' | 'not-running' | 'running'>('not-running')
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const checkStatus = async () => {
|
||||||
|
const status = await window.api.ovms.getStatus()
|
||||||
|
setOvmsStatus(status)
|
||||||
|
}
|
||||||
|
checkStatus()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const validOptions = Options.filter((option) => option !== 'ovms' || ovmsStatus === 'running')
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
logger.debug(`defaultPaintingProvider: ${provider}`)
|
logger.debug(`defaultPaintingProvider: ${provider}`)
|
||||||
if (provider && Options.includes(provider)) {
|
if (provider && validOptions.includes(provider)) {
|
||||||
dispatch(setDefaultPaintingProvider(provider as PaintingProvider))
|
dispatch(setDefaultPaintingProvider(provider as PaintingProvider))
|
||||||
}
|
}
|
||||||
}, [provider, dispatch, Options])
|
}, [provider, dispatch, validOptions])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="*" element={<ZhipuPage Options={Options} />} />
|
<Route path="*" element={<ZhipuPage Options={validOptions} />} />
|
||||||
<Route path="/zhipu" element={<ZhipuPage Options={Options} />} />
|
<Route path="/zhipu" element={<ZhipuPage Options={validOptions} />} />
|
||||||
<Route path="/aihubmix" element={<AihubmixPage Options={Options} />} />
|
<Route path="/aihubmix" element={<AihubmixPage Options={validOptions} />} />
|
||||||
<Route path="/silicon" element={<SiliconPage Options={Options} />} />
|
<Route path="/silicon" element={<SiliconPage Options={validOptions} />} />
|
||||||
<Route path="/dmxapi" element={<DmxapiPage Options={Options} />} />
|
<Route path="/dmxapi" element={<DmxapiPage Options={validOptions} />} />
|
||||||
<Route path="/tokenflux" element={<TokenFluxPage Options={Options} />} />
|
<Route path="/tokenflux" element={<TokenFluxPage Options={validOptions} />} />
|
||||||
|
<Route path="/ovms" element={<OvmsPage Options={validOptions} />} />
|
||||||
{/* new-api family providers are mounted dynamically below */}
|
{/* new-api family providers are mounted dynamically below */}
|
||||||
{providers
|
{providers
|
||||||
.filter((p) => isNewApiProvider(p))
|
.filter((p) => isNewApiProvider(p))
|
||||||
.map((p) => (
|
.map((p) => (
|
||||||
<Route key={p.id} path={`/${p.id}`} element={<NewApiPage Options={Options} />} />
|
<Route key={p.id} path={`/${p.id}`} element={<NewApiPage Options={validOptions} />} />
|
||||||
))}
|
))}
|
||||||
|
<Route path="/new-api" element={<NewApiPage Options={validOptions} />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
129
src/renderer/src/pages/paintings/config/ovmsConfig.tsx
Normal file
129
src/renderer/src/pages/paintings/config/ovmsConfig.tsx
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
import type { PaintingAction } from '@renderer/types'
|
||||||
|
import { uuid } from '@renderer/utils'
|
||||||
|
|
||||||
|
// Configuration item type definition
|
||||||
|
export type ConfigItem = {
|
||||||
|
type: 'select' | 'radio' | 'slider' | 'input' | 'switch' | 'inputNumber' | 'textarea' | 'title' | 'description'
|
||||||
|
key?: keyof PaintingAction | 'commonModel'
|
||||||
|
title?: string
|
||||||
|
tooltip?: string
|
||||||
|
options?:
|
||||||
|
| Array<{
|
||||||
|
label: string
|
||||||
|
title?: string
|
||||||
|
value?: string | number
|
||||||
|
icon?: string
|
||||||
|
}>
|
||||||
|
| ((
|
||||||
|
config: ConfigItem,
|
||||||
|
painting: Partial<PaintingAction>
|
||||||
|
) => Array<{ label: string; value: string | number; icon?: string }>)
|
||||||
|
min?: number
|
||||||
|
max?: number
|
||||||
|
step?: number
|
||||||
|
suffix?: React.ReactNode
|
||||||
|
content?: string
|
||||||
|
disabled?: boolean | ((config: ConfigItem, painting: Partial<PaintingAction>) => boolean)
|
||||||
|
initialValue?: string | number | boolean
|
||||||
|
required?: boolean
|
||||||
|
condition?: (painting: PaintingAction) => boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size options for OVMS
|
||||||
|
const SIZE_OPTIONS = [
|
||||||
|
{ label: '512x512', value: '512x512' },
|
||||||
|
{ label: '768x768', value: '768x768' },
|
||||||
|
{ label: '1024x1024', value: '1024x1024' }
|
||||||
|
]
|
||||||
|
|
||||||
|
// Available OVMS models for image generation - will be populated dynamically
|
||||||
|
export const OVMS_MODELS = [{ label: 'no available model', value: 'none' }]
|
||||||
|
|
||||||
|
// Function to get available OVMS models from provider
|
||||||
|
export const getOvmsModels = (
|
||||||
|
providerModels?: Array<{ id: string; name: string }>
|
||||||
|
): Array<{ label: string; value: string }> => {
|
||||||
|
if (!providerModels || providerModels.length === 0) {
|
||||||
|
// Fallback to static models if no provider models
|
||||||
|
return OVMS_MODELS
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter provider models for image generation (SD, Stable-Diffusion, Stable Diffusion, FLUX)
|
||||||
|
const imageGenerationModels = providerModels.filter((model) => {
|
||||||
|
const modelName = model.name.toLowerCase()
|
||||||
|
return (
|
||||||
|
modelName.startsWith('sd') ||
|
||||||
|
modelName.startsWith('stable-diffusion') ||
|
||||||
|
modelName.startsWith('stable diffusion') ||
|
||||||
|
modelName.startsWith('flux')
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Convert to the expected format
|
||||||
|
const formattedModels = imageGenerationModels.map((model) => ({
|
||||||
|
label: model.name,
|
||||||
|
value: model.id
|
||||||
|
}))
|
||||||
|
|
||||||
|
// Return formatted models or fallback to static models
|
||||||
|
return formattedModels.length > 0 ? formattedModels : OVMS_MODELS
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create configuration function
|
||||||
|
export const createOvmsConfig = (models?: Array<{ label: string; value: string }>): ConfigItem[] => {
|
||||||
|
const availableModels = models || OVMS_MODELS
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
type: 'select',
|
||||||
|
key: 'model',
|
||||||
|
title: 'paintings.model',
|
||||||
|
options: availableModels,
|
||||||
|
initialValue: availableModels[0]?.value || 'Select Model Here'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'select',
|
||||||
|
key: 'size',
|
||||||
|
title: 'paintings.image.size',
|
||||||
|
options: SIZE_OPTIONS,
|
||||||
|
initialValue: '512x512'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'inputNumber',
|
||||||
|
key: 'num_inference_steps',
|
||||||
|
title: 'paintings.inference_steps',
|
||||||
|
tooltip: 'paintings.inference_steps_tip',
|
||||||
|
min: 1,
|
||||||
|
max: 100,
|
||||||
|
initialValue: 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'inputNumber',
|
||||||
|
key: 'rng_seed',
|
||||||
|
title: 'paintings.seed',
|
||||||
|
tooltip: 'paintings.seed_tip',
|
||||||
|
initialValue: 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default painting configuration for OVMS
|
||||||
|
export const DEFAULT_OVMS_PAINTING: PaintingAction = {
|
||||||
|
id: uuid(),
|
||||||
|
model: '',
|
||||||
|
prompt: '',
|
||||||
|
size: '512x512',
|
||||||
|
num_inference_steps: 4,
|
||||||
|
rng_seed: 0,
|
||||||
|
files: [],
|
||||||
|
urls: []
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to create default painting with dynamic model
|
||||||
|
export const createDefaultOvmsPainting = (models?: Array<{ label: string; value: string }>): PaintingAction => {
|
||||||
|
const availableModels = models || OVMS_MODELS
|
||||||
|
return {
|
||||||
|
...DEFAULT_OVMS_PAINTING,
|
||||||
|
id: uuid(),
|
||||||
|
model: availableModels[0]?.value || 'Select Model Here'
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -139,7 +139,8 @@ export async function fetchChatCompletion({
|
|||||||
enableGenerateImage: capabilities.enableGenerateImage,
|
enableGenerateImage: capabilities.enableGenerateImage,
|
||||||
enableUrlContext: capabilities.enableUrlContext,
|
enableUrlContext: capabilities.enableUrlContext,
|
||||||
mcpTools,
|
mcpTools,
|
||||||
uiMessages
|
uiMessages,
|
||||||
|
knowledgeRecognition: assistant.knowledgeRecognition
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Call AI Completions ---
|
// --- Call AI Completions ---
|
||||||
|
|||||||
@ -2321,7 +2321,8 @@ const migrateConfig = {
|
|||||||
// @ts-ignore upscale
|
// @ts-ignore upscale
|
||||||
aihubmix_image_upscale: state?.paintings?.upscale || [],
|
aihubmix_image_upscale: state?.paintings?.upscale || [],
|
||||||
openai_image_generate: state?.paintings?.openai_image_generate || [],
|
openai_image_generate: state?.paintings?.openai_image_generate || [],
|
||||||
openai_image_edit: state?.paintings?.openai_image_edit || []
|
openai_image_edit: state?.paintings?.openai_image_edit || [],
|
||||||
|
ovms_paintings: []
|
||||||
}
|
}
|
||||||
|
|
||||||
return state
|
return state
|
||||||
@ -2682,6 +2683,7 @@ const migrateConfig = {
|
|||||||
provider.anthropicApiHost = 'https://open.cherryin.net'
|
provider.anthropicApiHost = 'https://open.cherryin.net'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
state.paintings.ovms_paintings = []
|
||||||
return state
|
return state
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('migrate 163 error', error as Error)
|
logger.error('migrate 163 error', error as Error)
|
||||||
|
|||||||
@ -20,7 +20,9 @@ const initialState: PaintingsState = {
|
|||||||
aihubmix_image_upscale: [],
|
aihubmix_image_upscale: [],
|
||||||
// OpenAI
|
// OpenAI
|
||||||
openai_image_generate: [],
|
openai_image_generate: [],
|
||||||
openai_image_edit: []
|
openai_image_edit: [],
|
||||||
|
// OVMS
|
||||||
|
ovms_paintings: []
|
||||||
}
|
}
|
||||||
|
|
||||||
const paintingsSlice = createSlice({
|
const paintingsSlice = createSlice({
|
||||||
|
|||||||
@ -91,6 +91,7 @@ const ThinkModelTypes = [
|
|||||||
'qwen_thinking',
|
'qwen_thinking',
|
||||||
'doubao',
|
'doubao',
|
||||||
'doubao_no_auto',
|
'doubao_no_auto',
|
||||||
|
'doubao_after_251015',
|
||||||
'hunyuan',
|
'hunyuan',
|
||||||
'zhipu',
|
'zhipu',
|
||||||
'perplexity',
|
'perplexity',
|
||||||
@ -279,7 +280,7 @@ export type PaintingParams = {
|
|||||||
providerId?: string
|
providerId?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PaintingProvider = 'zhipu' | 'aihubmix' | 'silicon' | 'dmxapi' | 'new-api'
|
export type PaintingProvider = 'zhipu' | 'aihubmix' | 'silicon' | 'dmxapi' | 'new-api' | 'ovms'
|
||||||
|
|
||||||
export interface Painting extends PaintingParams {
|
export interface Painting extends PaintingParams {
|
||||||
model?: string
|
model?: string
|
||||||
@ -379,8 +380,18 @@ export interface TokenFluxPainting extends PaintingParams {
|
|||||||
status?: 'starting' | 'processing' | 'succeeded' | 'failed' | 'cancelled'
|
status?: 'starting' | 'processing' | 'succeeded' | 'failed' | 'cancelled'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface OvmsPainting extends PaintingParams {
|
||||||
|
model?: string
|
||||||
|
prompt?: string
|
||||||
|
size?: string
|
||||||
|
num_inference_steps?: number
|
||||||
|
rng_seed?: number
|
||||||
|
safety_check?: boolean
|
||||||
|
response_format?: 'url' | 'b64_json'
|
||||||
|
}
|
||||||
|
|
||||||
export type PaintingAction = Partial<
|
export type PaintingAction = Partial<
|
||||||
GeneratePainting & RemixPainting & EditPainting & ScalePainting & DmxapiPainting & TokenFluxPainting
|
GeneratePainting & RemixPainting & EditPainting & ScalePainting & DmxapiPainting & TokenFluxPainting & OvmsPainting
|
||||||
> &
|
> &
|
||||||
PaintingParams
|
PaintingParams
|
||||||
|
|
||||||
@ -401,6 +412,8 @@ export interface PaintingsState {
|
|||||||
// OpenAI
|
// OpenAI
|
||||||
openai_image_generate: Partial<GeneratePainting> & PaintingParams[]
|
openai_image_generate: Partial<GeneratePainting> & PaintingParams[]
|
||||||
openai_image_edit: Partial<EditPainting> & PaintingParams[]
|
openai_image_edit: Partial<EditPainting> & PaintingParams[]
|
||||||
|
// OVMS
|
||||||
|
ovms_paintings: OvmsPainting[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MinAppType = {
|
export type MinAppType = {
|
||||||
|
|||||||
@ -79,6 +79,7 @@ export type ReasoningEffortOptionalParams = {
|
|||||||
thinking?: { type: 'disabled' | 'enabled' | 'auto'; budget_tokens?: number }
|
thinking?: { type: 'disabled' | 'enabled' | 'auto'; budget_tokens?: number }
|
||||||
reasoning?: { max_tokens?: number; exclude?: boolean; effort?: string; enabled?: boolean } | OpenAI.Reasoning
|
reasoning?: { max_tokens?: number; exclude?: boolean; effort?: string; enabled?: boolean } | OpenAI.Reasoning
|
||||||
reasoningEffort?: OpenAI.Chat.Completions.ChatCompletionCreateParams['reasoning_effort'] | 'none' | 'auto'
|
reasoningEffort?: OpenAI.Chat.Completions.ChatCompletionCreateParams['reasoning_effort'] | 'none' | 'auto'
|
||||||
|
// WARN: This field will be overwrite to undefined by aisdk if the provider is openai-compatible. Use reasoningEffort instead.
|
||||||
reasoning_effort?: OpenAI.Chat.Completions.ChatCompletionCreateParams['reasoning_effort'] | 'none' | 'auto'
|
reasoning_effort?: OpenAI.Chat.Completions.ChatCompletionCreateParams['reasoning_effort'] | 'none' | 'auto'
|
||||||
enable_thinking?: boolean
|
enable_thinking?: boolean
|
||||||
thinking_budget?: number
|
thinking_budget?: number
|
||||||
|
|||||||
390
yarn.lock
390
yarn.lock
@ -90,7 +90,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@ai-sdk/anthropic@npm:2.0.30, @ai-sdk/anthropic@npm:^2.0.27":
|
"@ai-sdk/anthropic@npm:2.0.30":
|
||||||
version: 2.0.30
|
version: 2.0.30
|
||||||
resolution: "@ai-sdk/anthropic@npm:2.0.30"
|
resolution: "@ai-sdk/anthropic@npm:2.0.30"
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -102,6 +102,18 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@ai-sdk/anthropic@npm:^2.0.27":
|
||||||
|
version: 2.0.34
|
||||||
|
resolution: "@ai-sdk/anthropic@npm:2.0.34"
|
||||||
|
dependencies:
|
||||||
|
"@ai-sdk/provider": "npm:2.0.0"
|
||||||
|
"@ai-sdk/provider-utils": "npm:3.0.12"
|
||||||
|
peerDependencies:
|
||||||
|
zod: ^3.25.76 || ^4.1.8
|
||||||
|
checksum: 10c0/e760bf4e1833d23e74cee43e5ca2844fc068307444d7c3d6ca71713c4599b583321619a73d22593c8157ecbee7a969d9f1b17c7baf3589aeb49de70b547f5b24
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@ai-sdk/azure@npm:^2.0.49":
|
"@ai-sdk/azure@npm:^2.0.49":
|
||||||
version: 2.0.53
|
version: 2.0.53
|
||||||
resolution: "@ai-sdk/azure@npm:2.0.53"
|
resolution: "@ai-sdk/azure@npm:2.0.53"
|
||||||
@ -204,7 +216,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@ai-sdk/openai@npm:2.0.52, @ai-sdk/openai@npm:^2.0.48":
|
"@ai-sdk/openai@npm:2.0.52":
|
||||||
version: 2.0.52
|
version: 2.0.52
|
||||||
resolution: "@ai-sdk/openai@npm:2.0.52"
|
resolution: "@ai-sdk/openai@npm:2.0.52"
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -228,6 +240,18 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@ai-sdk/openai@npm:^2.0.48":
|
||||||
|
version: 2.0.53
|
||||||
|
resolution: "@ai-sdk/openai@npm:2.0.53"
|
||||||
|
dependencies:
|
||||||
|
"@ai-sdk/provider": "npm:2.0.0"
|
||||||
|
"@ai-sdk/provider-utils": "npm:3.0.12"
|
||||||
|
peerDependencies:
|
||||||
|
zod: ^3.25.76 || ^4.1.8
|
||||||
|
checksum: 10c0/acb014c7e4d99be0502fe2190c3b91c76ee86ade25e80dad939ffd113a5f013f29a81f06e13fa0e6a76b49fcb8cc524aab180fc1a622ceb8d3dac58fd655de1c
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@ai-sdk/perplexity@npm:^2.0.13":
|
"@ai-sdk/perplexity@npm:^2.0.13":
|
||||||
version: 2.0.13
|
version: 2.0.13
|
||||||
resolution: "@ai-sdk/perplexity@npm:2.0.13"
|
resolution: "@ai-sdk/perplexity@npm:2.0.13"
|
||||||
@ -3268,13 +3292,20 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@eslint/js@npm:9.37.0, @eslint/js@npm:^9.22.0, @eslint/js@npm:^9.24.0":
|
"@eslint/js@npm:9.37.0":
|
||||||
version: 9.37.0
|
version: 9.37.0
|
||||||
resolution: "@eslint/js@npm:9.37.0"
|
resolution: "@eslint/js@npm:9.37.0"
|
||||||
checksum: 10c0/84f98a6213522fc76ea104bd910f606136200bd918544e056a7a22442d3f9d5c3c5cd7f4cdf2499d49b1fa140155b87d597a1f16d01644920f05c228e9ca0378
|
checksum: 10c0/84f98a6213522fc76ea104bd910f606136200bd918544e056a7a22442d3f9d5c3c5cd7f4cdf2499d49b1fa140155b87d597a1f16d01644920f05c228e9ca0378
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@eslint/js@npm:^9.22.0, @eslint/js@npm:^9.24.0":
|
||||||
|
version: 9.38.0
|
||||||
|
resolution: "@eslint/js@npm:9.38.0"
|
||||||
|
checksum: 10c0/b4a0d561ab93f0b1bc6a3f5e3f83764c9cccade59f2c54f1d718c1dcc71ac4d1be97bef7300cca641932d72e7555c79a7bf07e4e4ce1d0a1ddccc84d6440d2a6
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@eslint/object-schema@npm:^2.1.6":
|
"@eslint/object-schema@npm:^2.1.6":
|
||||||
version: 2.1.6
|
version: 2.1.6
|
||||||
resolution: "@eslint/object-schema@npm:2.1.6"
|
resolution: "@eslint/object-schema@npm:2.1.6"
|
||||||
@ -5860,9 +5891,9 @@ __metadata:
|
|||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@lezer/common@npm:^1.0.0, @lezer/common@npm:^1.0.2, @lezer/common@npm:^1.0.3, @lezer/common@npm:^1.1.0, @lezer/common@npm:^1.2.0, @lezer/common@npm:^1.2.1":
|
"@lezer/common@npm:^1.0.0, @lezer/common@npm:^1.0.2, @lezer/common@npm:^1.0.3, @lezer/common@npm:^1.1.0, @lezer/common@npm:^1.2.0, @lezer/common@npm:^1.2.1":
|
||||||
version: 1.2.3
|
version: 1.3.0
|
||||||
resolution: "@lezer/common@npm:1.2.3"
|
resolution: "@lezer/common@npm:1.3.0"
|
||||||
checksum: 10c0/fe9f8e111080ef94037a34ca2af1221c8d01c1763ba5ecf708a286185c76119509a5d19d924c8842172716716ddce22d7834394670c4a9432f0ba9f3b7c0f50d
|
checksum: 10c0/e164094920761c2f56c8634d0ae9261ea7c5e6b8202aa08773febc59b8d8284dde5bc7a810c9438e27b978e5ad67d0db03af1ed72924df61b8fa2704acb55deb
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@ -6691,27 +6722,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@opeoginni/github-copilot-openai-compatible@npm:0.1.18":
|
"@opeoginni/github-copilot-openai-compatible@npm:0.1.19":
|
||||||
version: 0.1.18
|
version: 0.1.19
|
||||||
resolution: "@opeoginni/github-copilot-openai-compatible@npm:0.1.18"
|
resolution: "@opeoginni/github-copilot-openai-compatible@npm:0.1.19"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@ai-sdk/openai": "npm:^2.0.42"
|
"@ai-sdk/openai": "npm:^2.0.42"
|
||||||
"@ai-sdk/openai-compatible": "npm:^1.0.19"
|
"@ai-sdk/openai-compatible": "npm:^1.0.19"
|
||||||
"@ai-sdk/provider": "npm:^2.1.0-beta.4"
|
"@ai-sdk/provider": "npm:^2.1.0-beta.4"
|
||||||
"@ai-sdk/provider-utils": "npm:^3.0.10"
|
"@ai-sdk/provider-utils": "npm:^3.0.10"
|
||||||
checksum: 10c0/31b87ed150883bbdd33a0203e45831859560fdf174f0285384fdcb1d01fc4a56ca15f31d648e8d6d3a2d4d5c6e327ddecbf422543eeefaa7e8fdd7dc2f2a3b08
|
checksum: 10c0/dfb01832d7c704b2eb080fc09d31b07fc26e5ac4e648ce219dc0d80cf044ef3cae504427781ec2ce3c5a2459c9c81d043046a255642108d5b3de0f83f4a9f20a
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"@opeoginni/github-copilot-openai-compatible@patch:@opeoginni/github-copilot-openai-compatible@npm%3A0.1.18#~/.yarn/patches/@opeoginni-github-copilot-openai-compatible-npm-0.1.18-3f65760532.patch":
|
|
||||||
version: 0.1.18
|
|
||||||
resolution: "@opeoginni/github-copilot-openai-compatible@patch:@opeoginni/github-copilot-openai-compatible@npm%3A0.1.18#~/.yarn/patches/@opeoginni-github-copilot-openai-compatible-npm-0.1.18-3f65760532.patch::version=0.1.18&hash=1cf9d0"
|
|
||||||
dependencies:
|
|
||||||
"@ai-sdk/openai": "npm:^2.0.42"
|
|
||||||
"@ai-sdk/openai-compatible": "npm:^1.0.19"
|
|
||||||
"@ai-sdk/provider": "npm:^2.1.0-beta.4"
|
|
||||||
"@ai-sdk/provider-utils": "npm:^3.0.10"
|
|
||||||
checksum: 10c0/cfffc031d2742068d20baed0e0ade6e9182c29ee7a425fa64262c04023ae75220b8b944ad2c9554255681e325fa1a70ec5e1f961b5f7370c871e70cbaeac0e79
|
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@ -6743,6 +6762,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@oxc-project/types@npm:=0.95.0":
|
||||||
|
version: 0.95.0
|
||||||
|
resolution: "@oxc-project/types@npm:0.95.0"
|
||||||
|
checksum: 10c0/3ab486ff14eaa87d0b7d84763db001791e9d103281eefa87934c0d46d7fd721b83fc4b72ad3435a1974ecba04c2e902ce249cb664e16d58e691a438acd26dd4b
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@oxlint-tsgolint/darwin-arm64@npm:0.2.0":
|
"@oxlint-tsgolint/darwin-arm64@npm:0.2.0":
|
||||||
version: 0.2.0
|
version: 0.2.0
|
||||||
resolution: "@oxlint-tsgolint/darwin-arm64@npm:0.2.0"
|
resolution: "@oxlint-tsgolint/darwin-arm64@npm:0.2.0"
|
||||||
@ -9029,6 +9055,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@rolldown/binding-android-arm64@npm:1.0.0-beta.44":
|
||||||
|
version: 1.0.0-beta.44
|
||||||
|
resolution: "@rolldown/binding-android-arm64@npm:1.0.0-beta.44"
|
||||||
|
conditions: os=android & cpu=arm64
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@rolldown/binding-darwin-arm64@npm:1.0.0-beta.43":
|
"@rolldown/binding-darwin-arm64@npm:1.0.0-beta.43":
|
||||||
version: 1.0.0-beta.43
|
version: 1.0.0-beta.43
|
||||||
resolution: "@rolldown/binding-darwin-arm64@npm:1.0.0-beta.43"
|
resolution: "@rolldown/binding-darwin-arm64@npm:1.0.0-beta.43"
|
||||||
@ -9036,6 +9069,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@rolldown/binding-darwin-arm64@npm:1.0.0-beta.44":
|
||||||
|
version: 1.0.0-beta.44
|
||||||
|
resolution: "@rolldown/binding-darwin-arm64@npm:1.0.0-beta.44"
|
||||||
|
conditions: os=darwin & cpu=arm64
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@rolldown/binding-darwin-arm64@npm:1.0.0-beta.9-commit.d91dfb5":
|
"@rolldown/binding-darwin-arm64@npm:1.0.0-beta.9-commit.d91dfb5":
|
||||||
version: 1.0.0-beta.9-commit.d91dfb5
|
version: 1.0.0-beta.9-commit.d91dfb5
|
||||||
resolution: "@rolldown/binding-darwin-arm64@npm:1.0.0-beta.9-commit.d91dfb5"
|
resolution: "@rolldown/binding-darwin-arm64@npm:1.0.0-beta.9-commit.d91dfb5"
|
||||||
@ -9050,6 +9090,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@rolldown/binding-darwin-x64@npm:1.0.0-beta.44":
|
||||||
|
version: 1.0.0-beta.44
|
||||||
|
resolution: "@rolldown/binding-darwin-x64@npm:1.0.0-beta.44"
|
||||||
|
conditions: os=darwin & cpu=x64
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@rolldown/binding-darwin-x64@npm:1.0.0-beta.9-commit.d91dfb5":
|
"@rolldown/binding-darwin-x64@npm:1.0.0-beta.9-commit.d91dfb5":
|
||||||
version: 1.0.0-beta.9-commit.d91dfb5
|
version: 1.0.0-beta.9-commit.d91dfb5
|
||||||
resolution: "@rolldown/binding-darwin-x64@npm:1.0.0-beta.9-commit.d91dfb5"
|
resolution: "@rolldown/binding-darwin-x64@npm:1.0.0-beta.9-commit.d91dfb5"
|
||||||
@ -9064,6 +9111,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@rolldown/binding-freebsd-x64@npm:1.0.0-beta.44":
|
||||||
|
version: 1.0.0-beta.44
|
||||||
|
resolution: "@rolldown/binding-freebsd-x64@npm:1.0.0-beta.44"
|
||||||
|
conditions: os=freebsd & cpu=x64
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@rolldown/binding-freebsd-x64@npm:1.0.0-beta.9-commit.d91dfb5":
|
"@rolldown/binding-freebsd-x64@npm:1.0.0-beta.9-commit.d91dfb5":
|
||||||
version: 1.0.0-beta.9-commit.d91dfb5
|
version: 1.0.0-beta.9-commit.d91dfb5
|
||||||
resolution: "@rolldown/binding-freebsd-x64@npm:1.0.0-beta.9-commit.d91dfb5"
|
resolution: "@rolldown/binding-freebsd-x64@npm:1.0.0-beta.9-commit.d91dfb5"
|
||||||
@ -9078,6 +9132,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@rolldown/binding-linux-arm-gnueabihf@npm:1.0.0-beta.44":
|
||||||
|
version: 1.0.0-beta.44
|
||||||
|
resolution: "@rolldown/binding-linux-arm-gnueabihf@npm:1.0.0-beta.44"
|
||||||
|
conditions: os=linux & cpu=arm
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@rolldown/binding-linux-arm-gnueabihf@npm:1.0.0-beta.9-commit.d91dfb5":
|
"@rolldown/binding-linux-arm-gnueabihf@npm:1.0.0-beta.9-commit.d91dfb5":
|
||||||
version: 1.0.0-beta.9-commit.d91dfb5
|
version: 1.0.0-beta.9-commit.d91dfb5
|
||||||
resolution: "@rolldown/binding-linux-arm-gnueabihf@npm:1.0.0-beta.9-commit.d91dfb5"
|
resolution: "@rolldown/binding-linux-arm-gnueabihf@npm:1.0.0-beta.9-commit.d91dfb5"
|
||||||
@ -9092,6 +9153,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@rolldown/binding-linux-arm64-gnu@npm:1.0.0-beta.44":
|
||||||
|
version: 1.0.0-beta.44
|
||||||
|
resolution: "@rolldown/binding-linux-arm64-gnu@npm:1.0.0-beta.44"
|
||||||
|
conditions: os=linux & cpu=arm64 & libc=glibc
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@rolldown/binding-linux-arm64-gnu@npm:1.0.0-beta.9-commit.d91dfb5":
|
"@rolldown/binding-linux-arm64-gnu@npm:1.0.0-beta.9-commit.d91dfb5":
|
||||||
version: 1.0.0-beta.9-commit.d91dfb5
|
version: 1.0.0-beta.9-commit.d91dfb5
|
||||||
resolution: "@rolldown/binding-linux-arm64-gnu@npm:1.0.0-beta.9-commit.d91dfb5"
|
resolution: "@rolldown/binding-linux-arm64-gnu@npm:1.0.0-beta.9-commit.d91dfb5"
|
||||||
@ -9106,6 +9174,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@rolldown/binding-linux-arm64-musl@npm:1.0.0-beta.44":
|
||||||
|
version: 1.0.0-beta.44
|
||||||
|
resolution: "@rolldown/binding-linux-arm64-musl@npm:1.0.0-beta.44"
|
||||||
|
conditions: os=linux & cpu=arm64 & libc=musl
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@rolldown/binding-linux-arm64-musl@npm:1.0.0-beta.9-commit.d91dfb5":
|
"@rolldown/binding-linux-arm64-musl@npm:1.0.0-beta.9-commit.d91dfb5":
|
||||||
version: 1.0.0-beta.9-commit.d91dfb5
|
version: 1.0.0-beta.9-commit.d91dfb5
|
||||||
resolution: "@rolldown/binding-linux-arm64-musl@npm:1.0.0-beta.9-commit.d91dfb5"
|
resolution: "@rolldown/binding-linux-arm64-musl@npm:1.0.0-beta.9-commit.d91dfb5"
|
||||||
@ -9120,6 +9195,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@rolldown/binding-linux-x64-gnu@npm:1.0.0-beta.44":
|
||||||
|
version: 1.0.0-beta.44
|
||||||
|
resolution: "@rolldown/binding-linux-x64-gnu@npm:1.0.0-beta.44"
|
||||||
|
conditions: os=linux & cpu=x64 & libc=glibc
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@rolldown/binding-linux-x64-gnu@npm:1.0.0-beta.9-commit.d91dfb5":
|
"@rolldown/binding-linux-x64-gnu@npm:1.0.0-beta.9-commit.d91dfb5":
|
||||||
version: 1.0.0-beta.9-commit.d91dfb5
|
version: 1.0.0-beta.9-commit.d91dfb5
|
||||||
resolution: "@rolldown/binding-linux-x64-gnu@npm:1.0.0-beta.9-commit.d91dfb5"
|
resolution: "@rolldown/binding-linux-x64-gnu@npm:1.0.0-beta.9-commit.d91dfb5"
|
||||||
@ -9134,6 +9216,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@rolldown/binding-linux-x64-musl@npm:1.0.0-beta.44":
|
||||||
|
version: 1.0.0-beta.44
|
||||||
|
resolution: "@rolldown/binding-linux-x64-musl@npm:1.0.0-beta.44"
|
||||||
|
conditions: os=linux & cpu=x64 & libc=musl
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@rolldown/binding-linux-x64-musl@npm:1.0.0-beta.9-commit.d91dfb5":
|
"@rolldown/binding-linux-x64-musl@npm:1.0.0-beta.9-commit.d91dfb5":
|
||||||
version: 1.0.0-beta.9-commit.d91dfb5
|
version: 1.0.0-beta.9-commit.d91dfb5
|
||||||
resolution: "@rolldown/binding-linux-x64-musl@npm:1.0.0-beta.9-commit.d91dfb5"
|
resolution: "@rolldown/binding-linux-x64-musl@npm:1.0.0-beta.9-commit.d91dfb5"
|
||||||
@ -9148,6 +9237,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@rolldown/binding-openharmony-arm64@npm:1.0.0-beta.44":
|
||||||
|
version: 1.0.0-beta.44
|
||||||
|
resolution: "@rolldown/binding-openharmony-arm64@npm:1.0.0-beta.44"
|
||||||
|
conditions: os=openharmony & cpu=arm64
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@rolldown/binding-wasm32-wasi@npm:1.0.0-beta.43":
|
"@rolldown/binding-wasm32-wasi@npm:1.0.0-beta.43":
|
||||||
version: 1.0.0-beta.43
|
version: 1.0.0-beta.43
|
||||||
resolution: "@rolldown/binding-wasm32-wasi@npm:1.0.0-beta.43"
|
resolution: "@rolldown/binding-wasm32-wasi@npm:1.0.0-beta.43"
|
||||||
@ -9157,6 +9253,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@rolldown/binding-wasm32-wasi@npm:1.0.0-beta.44":
|
||||||
|
version: 1.0.0-beta.44
|
||||||
|
resolution: "@rolldown/binding-wasm32-wasi@npm:1.0.0-beta.44"
|
||||||
|
dependencies:
|
||||||
|
"@napi-rs/wasm-runtime": "npm:^1.0.7"
|
||||||
|
conditions: cpu=wasm32
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@rolldown/binding-wasm32-wasi@npm:1.0.0-beta.9-commit.d91dfb5":
|
"@rolldown/binding-wasm32-wasi@npm:1.0.0-beta.9-commit.d91dfb5":
|
||||||
version: 1.0.0-beta.9-commit.d91dfb5
|
version: 1.0.0-beta.9-commit.d91dfb5
|
||||||
resolution: "@rolldown/binding-wasm32-wasi@npm:1.0.0-beta.9-commit.d91dfb5"
|
resolution: "@rolldown/binding-wasm32-wasi@npm:1.0.0-beta.9-commit.d91dfb5"
|
||||||
@ -9173,6 +9278,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@rolldown/binding-win32-arm64-msvc@npm:1.0.0-beta.44":
|
||||||
|
version: 1.0.0-beta.44
|
||||||
|
resolution: "@rolldown/binding-win32-arm64-msvc@npm:1.0.0-beta.44"
|
||||||
|
conditions: os=win32 & cpu=arm64
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@rolldown/binding-win32-arm64-msvc@npm:1.0.0-beta.9-commit.d91dfb5":
|
"@rolldown/binding-win32-arm64-msvc@npm:1.0.0-beta.9-commit.d91dfb5":
|
||||||
version: 1.0.0-beta.9-commit.d91dfb5
|
version: 1.0.0-beta.9-commit.d91dfb5
|
||||||
resolution: "@rolldown/binding-win32-arm64-msvc@npm:1.0.0-beta.9-commit.d91dfb5"
|
resolution: "@rolldown/binding-win32-arm64-msvc@npm:1.0.0-beta.9-commit.d91dfb5"
|
||||||
@ -9187,6 +9299,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@rolldown/binding-win32-ia32-msvc@npm:1.0.0-beta.44":
|
||||||
|
version: 1.0.0-beta.44
|
||||||
|
resolution: "@rolldown/binding-win32-ia32-msvc@npm:1.0.0-beta.44"
|
||||||
|
conditions: os=win32 & cpu=ia32
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@rolldown/binding-win32-ia32-msvc@npm:1.0.0-beta.9-commit.d91dfb5":
|
"@rolldown/binding-win32-ia32-msvc@npm:1.0.0-beta.9-commit.d91dfb5":
|
||||||
version: 1.0.0-beta.9-commit.d91dfb5
|
version: 1.0.0-beta.9-commit.d91dfb5
|
||||||
resolution: "@rolldown/binding-win32-ia32-msvc@npm:1.0.0-beta.9-commit.d91dfb5"
|
resolution: "@rolldown/binding-win32-ia32-msvc@npm:1.0.0-beta.9-commit.d91dfb5"
|
||||||
@ -9201,6 +9320,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@rolldown/binding-win32-x64-msvc@npm:1.0.0-beta.44":
|
||||||
|
version: 1.0.0-beta.44
|
||||||
|
resolution: "@rolldown/binding-win32-x64-msvc@npm:1.0.0-beta.44"
|
||||||
|
conditions: os=win32 & cpu=x64
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@rolldown/binding-win32-x64-msvc@npm:1.0.0-beta.9-commit.d91dfb5":
|
"@rolldown/binding-win32-x64-msvc@npm:1.0.0-beta.9-commit.d91dfb5":
|
||||||
version: 1.0.0-beta.9-commit.d91dfb5
|
version: 1.0.0-beta.9-commit.d91dfb5
|
||||||
resolution: "@rolldown/binding-win32-x64-msvc@npm:1.0.0-beta.9-commit.d91dfb5"
|
resolution: "@rolldown/binding-win32-x64-msvc@npm:1.0.0-beta.9-commit.d91dfb5"
|
||||||
@ -9222,6 +9348,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@rolldown/pluginutils@npm:1.0.0-beta.44":
|
||||||
|
version: 1.0.0-beta.44
|
||||||
|
resolution: "@rolldown/pluginutils@npm:1.0.0-beta.44"
|
||||||
|
checksum: 10c0/945edb7883cc2a2ae2d139b9cb94093b318ec92757a3f7056b343f1cbfd4a76a5ba75a7a1043e9cb579eaeff362b20df2282c8112517580811f94385b2fffcf9
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@rolldown/pluginutils@npm:1.0.0-beta.9-commit.d91dfb5":
|
"@rolldown/pluginutils@npm:1.0.0-beta.9-commit.d91dfb5":
|
||||||
version: 1.0.0-beta.9-commit.d91dfb5
|
version: 1.0.0-beta.9-commit.d91dfb5
|
||||||
resolution: "@rolldown/pluginutils@npm:1.0.0-beta.9-commit.d91dfb5"
|
resolution: "@rolldown/pluginutils@npm:1.0.0-beta.9-commit.d91dfb5"
|
||||||
@ -12101,7 +12234,20 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@typescript-eslint/scope-manager@npm:8.46.1, @typescript-eslint/scope-manager@npm:^8.43.0":
|
"@typescript-eslint/project-service@npm:8.46.2":
|
||||||
|
version: 8.46.2
|
||||||
|
resolution: "@typescript-eslint/project-service@npm:8.46.2"
|
||||||
|
dependencies:
|
||||||
|
"@typescript-eslint/tsconfig-utils": "npm:^8.46.2"
|
||||||
|
"@typescript-eslint/types": "npm:^8.46.2"
|
||||||
|
debug: "npm:^4.3.4"
|
||||||
|
peerDependencies:
|
||||||
|
typescript: ">=4.8.4 <6.0.0"
|
||||||
|
checksum: 10c0/03e87bcbca6af3f95bf54d4047a8b4d12434126c27d7312e804499a9459e1c847fe045f83fe8e3b22c3dc3925baad0aa2a1a5476d0d51f73a493dc5909a53dbf
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@typescript-eslint/scope-manager@npm:8.46.1":
|
||||||
version: 8.46.1
|
version: 8.46.1
|
||||||
resolution: "@typescript-eslint/scope-manager@npm:8.46.1"
|
resolution: "@typescript-eslint/scope-manager@npm:8.46.1"
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -12111,6 +12257,16 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@typescript-eslint/scope-manager@npm:8.46.2, @typescript-eslint/scope-manager@npm:^8.43.0":
|
||||||
|
version: 8.46.2
|
||||||
|
resolution: "@typescript-eslint/scope-manager@npm:8.46.2"
|
||||||
|
dependencies:
|
||||||
|
"@typescript-eslint/types": "npm:8.46.2"
|
||||||
|
"@typescript-eslint/visitor-keys": "npm:8.46.2"
|
||||||
|
checksum: 10c0/42f52ee621a3a0ef2233e7d3384d9dbd76218f5c906a9cce3152a1f55c060a3d3614c7b8fff5270bdf48e8fcc003e732d3f003f283ea6fb204d64a2f6bb3ea9c
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@typescript-eslint/tsconfig-utils@npm:8.46.1, @typescript-eslint/tsconfig-utils@npm:^8.46.1":
|
"@typescript-eslint/tsconfig-utils@npm:8.46.1, @typescript-eslint/tsconfig-utils@npm:^8.46.1":
|
||||||
version: 8.46.1
|
version: 8.46.1
|
||||||
resolution: "@typescript-eslint/tsconfig-utils@npm:8.46.1"
|
resolution: "@typescript-eslint/tsconfig-utils@npm:8.46.1"
|
||||||
@ -12120,7 +12276,16 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@typescript-eslint/type-utils@npm:8.46.1, @typescript-eslint/type-utils@npm:^8.0.0, @typescript-eslint/type-utils@npm:^8.43.0":
|
"@typescript-eslint/tsconfig-utils@npm:8.46.2, @typescript-eslint/tsconfig-utils@npm:^8.46.2":
|
||||||
|
version: 8.46.2
|
||||||
|
resolution: "@typescript-eslint/tsconfig-utils@npm:8.46.2"
|
||||||
|
peerDependencies:
|
||||||
|
typescript: ">=4.8.4 <6.0.0"
|
||||||
|
checksum: 10c0/23e34ad296347417e42234945138022fb045d180fde69941483884a38e85fa55d5449420d2a660c0ebf1794a445add2f13e171c8dd64e4e83f594e2c4e35bf4d
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@typescript-eslint/type-utils@npm:8.46.1":
|
||||||
version: 8.46.1
|
version: 8.46.1
|
||||||
resolution: "@typescript-eslint/type-utils@npm:8.46.1"
|
resolution: "@typescript-eslint/type-utils@npm:8.46.1"
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -12136,14 +12301,37 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@typescript-eslint/types@npm:8.46.1, @typescript-eslint/types@npm:^8.43.0, @typescript-eslint/types@npm:^8.46.1":
|
"@typescript-eslint/type-utils@npm:^8.0.0, @typescript-eslint/type-utils@npm:^8.43.0":
|
||||||
|
version: 8.46.2
|
||||||
|
resolution: "@typescript-eslint/type-utils@npm:8.46.2"
|
||||||
|
dependencies:
|
||||||
|
"@typescript-eslint/types": "npm:8.46.2"
|
||||||
|
"@typescript-eslint/typescript-estree": "npm:8.46.2"
|
||||||
|
"@typescript-eslint/utils": "npm:8.46.2"
|
||||||
|
debug: "npm:^4.3.4"
|
||||||
|
ts-api-utils: "npm:^2.1.0"
|
||||||
|
peerDependencies:
|
||||||
|
eslint: ^8.57.0 || ^9.0.0
|
||||||
|
typescript: ">=4.8.4 <6.0.0"
|
||||||
|
checksum: 10c0/e12fc65e4b58c1ab6fe65f5486265b7afe9a9a6730e3529aca927ddfc22e5913eb28999fc83e68ea1b49097e1edbbae1f61dd724b0bb0e7586fb24ecda1d4938
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@typescript-eslint/types@npm:8.46.1, @typescript-eslint/types@npm:^8.46.1":
|
||||||
version: 8.46.1
|
version: 8.46.1
|
||||||
resolution: "@typescript-eslint/types@npm:8.46.1"
|
resolution: "@typescript-eslint/types@npm:8.46.1"
|
||||||
checksum: 10c0/90887acaa5b33b45af20cf7f87ec4ae098c0daa88484245473e73903fa6e542f613247c22148132167891ca06af6549a60b9d2fd14a65b22871e016901ce3756
|
checksum: 10c0/90887acaa5b33b45af20cf7f87ec4ae098c0daa88484245473e73903fa6e542f613247c22148132167891ca06af6549a60b9d2fd14a65b22871e016901ce3756
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@typescript-eslint/typescript-estree@npm:8.46.1, @typescript-eslint/typescript-estree@npm:^8.43.0":
|
"@typescript-eslint/types@npm:8.46.2, @typescript-eslint/types@npm:^8.43.0, @typescript-eslint/types@npm:^8.46.2":
|
||||||
|
version: 8.46.2
|
||||||
|
resolution: "@typescript-eslint/types@npm:8.46.2"
|
||||||
|
checksum: 10c0/611716bae2369a1b8001c7f6cc03c5ecadfb956643cbbe27269defd28a61d43fe52eda008d7a09568b0be50c502e8292bf767b246366004283476e9a971b6fbc
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@typescript-eslint/typescript-estree@npm:8.46.1":
|
||||||
version: 8.46.1
|
version: 8.46.1
|
||||||
resolution: "@typescript-eslint/typescript-estree@npm:8.46.1"
|
resolution: "@typescript-eslint/typescript-estree@npm:8.46.1"
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -12163,7 +12351,27 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@typescript-eslint/utils@npm:8.46.1, @typescript-eslint/utils@npm:^8.43.0, @typescript-eslint/utils@npm:^8.8.1":
|
"@typescript-eslint/typescript-estree@npm:8.46.2, @typescript-eslint/typescript-estree@npm:^8.43.0":
|
||||||
|
version: 8.46.2
|
||||||
|
resolution: "@typescript-eslint/typescript-estree@npm:8.46.2"
|
||||||
|
dependencies:
|
||||||
|
"@typescript-eslint/project-service": "npm:8.46.2"
|
||||||
|
"@typescript-eslint/tsconfig-utils": "npm:8.46.2"
|
||||||
|
"@typescript-eslint/types": "npm:8.46.2"
|
||||||
|
"@typescript-eslint/visitor-keys": "npm:8.46.2"
|
||||||
|
debug: "npm:^4.3.4"
|
||||||
|
fast-glob: "npm:^3.3.2"
|
||||||
|
is-glob: "npm:^4.0.3"
|
||||||
|
minimatch: "npm:^9.0.4"
|
||||||
|
semver: "npm:^7.6.0"
|
||||||
|
ts-api-utils: "npm:^2.1.0"
|
||||||
|
peerDependencies:
|
||||||
|
typescript: ">=4.8.4 <6.0.0"
|
||||||
|
checksum: 10c0/ad7dbf352982bc6e16473ef19fc7d209fffeb147a732db8a2464e0ec33e7fbbc24ce3f23d01bdf99d503626c582a476debf4c90c527d755eeb99b863476d9f5f
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@typescript-eslint/utils@npm:8.46.1":
|
||||||
version: 8.46.1
|
version: 8.46.1
|
||||||
resolution: "@typescript-eslint/utils@npm:8.46.1"
|
resolution: "@typescript-eslint/utils@npm:8.46.1"
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -12178,6 +12386,21 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@typescript-eslint/utils@npm:8.46.2, @typescript-eslint/utils@npm:^8.43.0, @typescript-eslint/utils@npm:^8.8.1":
|
||||||
|
version: 8.46.2
|
||||||
|
resolution: "@typescript-eslint/utils@npm:8.46.2"
|
||||||
|
dependencies:
|
||||||
|
"@eslint-community/eslint-utils": "npm:^4.7.0"
|
||||||
|
"@typescript-eslint/scope-manager": "npm:8.46.2"
|
||||||
|
"@typescript-eslint/types": "npm:8.46.2"
|
||||||
|
"@typescript-eslint/typescript-estree": "npm:8.46.2"
|
||||||
|
peerDependencies:
|
||||||
|
eslint: ^8.57.0 || ^9.0.0
|
||||||
|
typescript: ">=4.8.4 <6.0.0"
|
||||||
|
checksum: 10c0/600b70730077ed85a6e278e06771f3933cdafce242f979e4af1c1b41290bf1efb14d20823c25c38a3a792def69b18eb9410af28bb228fe86027ad7859753c62d
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@typescript-eslint/visitor-keys@npm:8.46.1":
|
"@typescript-eslint/visitor-keys@npm:8.46.1":
|
||||||
version: 8.46.1
|
version: 8.46.1
|
||||||
resolution: "@typescript-eslint/visitor-keys@npm:8.46.1"
|
resolution: "@typescript-eslint/visitor-keys@npm:8.46.1"
|
||||||
@ -12188,6 +12411,16 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@typescript-eslint/visitor-keys@npm:8.46.2":
|
||||||
|
version: 8.46.2
|
||||||
|
resolution: "@typescript-eslint/visitor-keys@npm:8.46.2"
|
||||||
|
dependencies:
|
||||||
|
"@typescript-eslint/types": "npm:8.46.2"
|
||||||
|
eslint-visitor-keys: "npm:^4.2.1"
|
||||||
|
checksum: 10c0/2067cd9a3c90b3817242cc49b5fa77428e1b92b28e16a12f45c2b399acbba7bd17e503553e5e68924e40078477a5c247dfa12e7709c24fe11c0b17a0c8486c33
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@typescript/native-preview-darwin-arm64@npm:7.0.0-dev.20251016.1":
|
"@typescript/native-preview-darwin-arm64@npm:7.0.0-dev.20251016.1":
|
||||||
version: 7.0.0-dev.20251016.1
|
version: 7.0.0-dev.20251016.1
|
||||||
resolution: "@typescript/native-preview-darwin-arm64@npm:7.0.0-dev.20251016.1"
|
resolution: "@typescript/native-preview-darwin-arm64@npm:7.0.0-dev.20251016.1"
|
||||||
@ -13065,7 +13298,7 @@ __metadata:
|
|||||||
"@opentelemetry/sdk-trace-base": "npm:^2.0.0"
|
"@opentelemetry/sdk-trace-base": "npm:^2.0.0"
|
||||||
"@opentelemetry/sdk-trace-node": "npm:^2.0.0"
|
"@opentelemetry/sdk-trace-node": "npm:^2.0.0"
|
||||||
"@opentelemetry/sdk-trace-web": "npm:^2.0.0"
|
"@opentelemetry/sdk-trace-web": "npm:^2.0.0"
|
||||||
"@opeoginni/github-copilot-openai-compatible": "patch:@opeoginni/github-copilot-openai-compatible@npm%3A0.1.18#~/.yarn/patches/@opeoginni-github-copilot-openai-compatible-npm-0.1.18-3f65760532.patch"
|
"@opeoginni/github-copilot-openai-compatible": "npm:0.1.19"
|
||||||
"@playwright/test": "npm:^1.52.0"
|
"@playwright/test": "npm:^1.52.0"
|
||||||
"@radix-ui/react-context-menu": "npm:^2.2.16"
|
"@radix-ui/react-context-menu": "npm:^2.2.16"
|
||||||
"@radix-ui/react-tabs": "npm:^1.1.13"
|
"@radix-ui/react-tabs": "npm:^1.1.13"
|
||||||
@ -13504,20 +13737,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"ansis@npm:=4.2.0, ansis@npm:^4.2.0":
|
"ansis@npm:=4.2.0, ansis@npm:^4.0.0, ansis@npm:^4.1.0, ansis@npm:^4.2.0":
|
||||||
version: 4.2.0
|
version: 4.2.0
|
||||||
resolution: "ansis@npm:4.2.0"
|
resolution: "ansis@npm:4.2.0"
|
||||||
checksum: 10c0/cd6a7a681ecd36e72e0d79c1e34f1f3bcb1b15bcbb6f0f8969b4228062d3bfebbef468e09771b00d93b2294370b34f707599d4a113542a876de26823b795b5d2
|
checksum: 10c0/cd6a7a681ecd36e72e0d79c1e34f1f3bcb1b15bcbb6f0f8969b4228062d3bfebbef468e09771b00d93b2294370b34f707599d4a113542a876de26823b795b5d2
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"ansis@npm:^4.0.0, ansis@npm:^4.1.0":
|
|
||||||
version: 4.1.0
|
|
||||||
resolution: "ansis@npm:4.1.0"
|
|
||||||
checksum: 10c0/df62d017a7791babdaf45b93f930d2cfd6d1dab5568b610735c11434c9a5ef8f513740e7cfd80bcbc3530fc8bd892b88f8476f26621efc251230e53cbd1a2c24
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"antd@npm:5.27.0":
|
"antd@npm:5.27.0":
|
||||||
version: 5.27.0
|
version: 5.27.0
|
||||||
resolution: "antd@npm:5.27.0"
|
resolution: "antd@npm:5.27.0"
|
||||||
@ -24283,7 +24509,16 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"playwright@npm:1.56.0, playwright@npm:^1.52.0":
|
"playwright-core@npm:1.56.1":
|
||||||
|
version: 1.56.1
|
||||||
|
resolution: "playwright-core@npm:1.56.1"
|
||||||
|
bin:
|
||||||
|
playwright-core: cli.js
|
||||||
|
checksum: 10c0/ffd40142b99c68678b387445d5b42f1fee4ab0b65d983058c37f342e5629f9cdbdac0506ea80a0dfd41a8f9f13345bad54e9a8c35826ef66dc765f4eb3db8da7
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"playwright@npm:1.56.0":
|
||||||
version: 1.56.0
|
version: 1.56.0
|
||||||
resolution: "playwright@npm:1.56.0"
|
resolution: "playwright@npm:1.56.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -24298,6 +24533,21 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"playwright@npm:^1.52.0":
|
||||||
|
version: 1.56.1
|
||||||
|
resolution: "playwright@npm:1.56.1"
|
||||||
|
dependencies:
|
||||||
|
fsevents: "npm:2.3.2"
|
||||||
|
playwright-core: "npm:1.56.1"
|
||||||
|
dependenciesMeta:
|
||||||
|
fsevents:
|
||||||
|
optional: true
|
||||||
|
bin:
|
||||||
|
playwright: cli.js
|
||||||
|
checksum: 10c0/8e9965aede86df0f4722063385748498977b219630a40a10d1b82b8bd8d4d4e9b6b65ecbfa024331a30800163161aca292fb6dd7446c531a1ad25f4155625ab4
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"plist@npm:3.1.0, plist@npm:^3.0.4, plist@npm:^3.0.5, plist@npm:^3.1.0":
|
"plist@npm:3.1.0, plist@npm:^3.0.4, plist@npm:^3.0.5, plist@npm:^3.1.0":
|
||||||
version: 3.1.0
|
version: 3.1.0
|
||||||
resolution: "plist@npm:3.1.0"
|
resolution: "plist@npm:3.1.0"
|
||||||
@ -26427,7 +26677,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"rolldown@npm:1.0.0-beta.43, rolldown@npm:latest":
|
"rolldown@npm:1.0.0-beta.43":
|
||||||
version: 1.0.0-beta.43
|
version: 1.0.0-beta.43
|
||||||
resolution: "rolldown@npm:1.0.0-beta.43"
|
resolution: "rolldown@npm:1.0.0-beta.43"
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -26534,6 +26784,61 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"rolldown@npm:latest":
|
||||||
|
version: 1.0.0-beta.44
|
||||||
|
resolution: "rolldown@npm:1.0.0-beta.44"
|
||||||
|
dependencies:
|
||||||
|
"@oxc-project/types": "npm:=0.95.0"
|
||||||
|
"@rolldown/binding-android-arm64": "npm:1.0.0-beta.44"
|
||||||
|
"@rolldown/binding-darwin-arm64": "npm:1.0.0-beta.44"
|
||||||
|
"@rolldown/binding-darwin-x64": "npm:1.0.0-beta.44"
|
||||||
|
"@rolldown/binding-freebsd-x64": "npm:1.0.0-beta.44"
|
||||||
|
"@rolldown/binding-linux-arm-gnueabihf": "npm:1.0.0-beta.44"
|
||||||
|
"@rolldown/binding-linux-arm64-gnu": "npm:1.0.0-beta.44"
|
||||||
|
"@rolldown/binding-linux-arm64-musl": "npm:1.0.0-beta.44"
|
||||||
|
"@rolldown/binding-linux-x64-gnu": "npm:1.0.0-beta.44"
|
||||||
|
"@rolldown/binding-linux-x64-musl": "npm:1.0.0-beta.44"
|
||||||
|
"@rolldown/binding-openharmony-arm64": "npm:1.0.0-beta.44"
|
||||||
|
"@rolldown/binding-wasm32-wasi": "npm:1.0.0-beta.44"
|
||||||
|
"@rolldown/binding-win32-arm64-msvc": "npm:1.0.0-beta.44"
|
||||||
|
"@rolldown/binding-win32-ia32-msvc": "npm:1.0.0-beta.44"
|
||||||
|
"@rolldown/binding-win32-x64-msvc": "npm:1.0.0-beta.44"
|
||||||
|
"@rolldown/pluginutils": "npm:1.0.0-beta.44"
|
||||||
|
dependenciesMeta:
|
||||||
|
"@rolldown/binding-android-arm64":
|
||||||
|
optional: true
|
||||||
|
"@rolldown/binding-darwin-arm64":
|
||||||
|
optional: true
|
||||||
|
"@rolldown/binding-darwin-x64":
|
||||||
|
optional: true
|
||||||
|
"@rolldown/binding-freebsd-x64":
|
||||||
|
optional: true
|
||||||
|
"@rolldown/binding-linux-arm-gnueabihf":
|
||||||
|
optional: true
|
||||||
|
"@rolldown/binding-linux-arm64-gnu":
|
||||||
|
optional: true
|
||||||
|
"@rolldown/binding-linux-arm64-musl":
|
||||||
|
optional: true
|
||||||
|
"@rolldown/binding-linux-x64-gnu":
|
||||||
|
optional: true
|
||||||
|
"@rolldown/binding-linux-x64-musl":
|
||||||
|
optional: true
|
||||||
|
"@rolldown/binding-openharmony-arm64":
|
||||||
|
optional: true
|
||||||
|
"@rolldown/binding-wasm32-wasi":
|
||||||
|
optional: true
|
||||||
|
"@rolldown/binding-win32-arm64-msvc":
|
||||||
|
optional: true
|
||||||
|
"@rolldown/binding-win32-ia32-msvc":
|
||||||
|
optional: true
|
||||||
|
"@rolldown/binding-win32-x64-msvc":
|
||||||
|
optional: true
|
||||||
|
bin:
|
||||||
|
rolldown: bin/cli.mjs
|
||||||
|
checksum: 10c0/e8a8e50856cbde6333d6ec813955dd40c0b7b146066cc5c50db8c5b094fcc6a7db206b47289f382aceabb08b9966a439ff1e5cfbfa068e90e50a8dd43f179312
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"rollup-plugin-visualizer@npm:^5.12.0":
|
"rollup-plugin-visualizer@npm:^5.12.0":
|
||||||
version: 5.14.0
|
version: 5.14.0
|
||||||
resolution: "rollup-plugin-visualizer@npm:5.14.0"
|
resolution: "rollup-plugin-visualizer@npm:5.14.0"
|
||||||
@ -27813,13 +28118,20 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"tailwindcss@npm:4.1.14, tailwindcss@npm:^4.1.13":
|
"tailwindcss@npm:4.1.14":
|
||||||
version: 4.1.14
|
version: 4.1.14
|
||||||
resolution: "tailwindcss@npm:4.1.14"
|
resolution: "tailwindcss@npm:4.1.14"
|
||||||
checksum: 10c0/c7e9ebfb241707b2a3eb7d465fd326cc8fcfa22e7215e01f67cccec32db8a49a19e17d1f694fc5d0435d55350ea3f863521c52c9bbe6bd790c2009dc8ff516a1
|
checksum: 10c0/c7e9ebfb241707b2a3eb7d465fd326cc8fcfa22e7215e01f67cccec32db8a49a19e17d1f694fc5d0435d55350ea3f863521c52c9bbe6bd790c2009dc8ff516a1
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"tailwindcss@npm:^4.1.13":
|
||||||
|
version: 4.1.15
|
||||||
|
resolution: "tailwindcss@npm:4.1.15"
|
||||||
|
checksum: 10c0/9023538f33c5d49003a19f68297d1b7d158fc9963a4c4023c588930665efbb192f020ad9f6566b007c2ce14458baeceb24337270c29eaa92ed753a8493594e43
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"tapable@npm:^2.2.0":
|
"tapable@npm:^2.2.0":
|
||||||
version: 2.3.0
|
version: 2.3.0
|
||||||
resolution: "tapable@npm:2.3.0"
|
resolution: "tapable@npm:2.3.0"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user