mirror of
https://github.com/YFGaia/dify-plus.git
synced 2026-06-14 20:41:21 +08:00
Compare commits
9 Commits
1.8.1
..
1.8.1-fix.2
| Author | SHA1 | Date | |
|---|---|---|---|
| d6fe1858db | |||
| 2e70cb143b | |||
| bcd2ff4a45 | |||
| 52e99cf180 | |||
| 88fd431534 | |||
| e1a1b1f799 | |||
| 316fa371f2 | |||
| c507fc2675 | |||
| 2186654be7 |
@@ -135,7 +135,8 @@ Dify-Plus,该名字不是说比 Dify 项目牛的意思,意思是想说比 D
|
||||
- Dify-plus官方交流群3(已满)
|
||||
- Dify-plus&Coze开源交流群4
|
||||
|
||||
<img width="200" height="583" alt="image" src="https://github.com/user-attachments/assets/e467e5ec-d493-46e7-8b6d-8d984a304508" />
|
||||
<img width="200" height="583" alt="image" src="https://github.com/user-attachments/assets/3ac4598c-7255-4ade-99aa-8bc0f039797e" />
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
type GVA_MODEL struct {
|
||||
ID uint `gorm:"primarykey" json:"ID"` // 主键ID
|
||||
ID uint `gorm:"primarykey;autoIncrement" json:"ID"` // 主键ID
|
||||
CreatedAt time.Time // 创建时间
|
||||
UpdatedAt time.Time // 更新时间
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` // 删除时间
|
||||
|
||||
@@ -35,6 +35,7 @@ func (e *ensureTables) MigrateTable(ctx context.Context) (context.Context, error
|
||||
if !ok {
|
||||
return ctx, system.ErrMissingDBContext
|
||||
}
|
||||
|
||||
tables := []interface{}{
|
||||
sysModel.SysApi{},
|
||||
sysModel.SysUser{},
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package initialize
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
@@ -37,6 +39,7 @@ func Gorm() *gorm.DB {
|
||||
|
||||
func RegisterTables() {
|
||||
db := global.GVA_DB
|
||||
|
||||
err := db.AutoMigrate(
|
||||
system.SysApi{},
|
||||
system.SysIgnoreApi{},
|
||||
@@ -78,6 +81,11 @@ func RegisterTables() {
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
//// 如果是PostgreSQL数据库,创建必要的序列
|
||||
//if global.GVA_CONFIG.System.DbType == "pgsql" {
|
||||
// createPostgreSQLSequences(db)
|
||||
//}
|
||||
|
||||
err = bizModel()
|
||||
|
||||
if err != nil {
|
||||
@@ -86,3 +94,74 @@ func RegisterTables() {
|
||||
}
|
||||
global.GVA_LOG.Info("register table success")
|
||||
}
|
||||
|
||||
// createPostgreSQLSequences 为PostgreSQL数据库创建必要的序列
|
||||
func createPostgreSQLSequences(db *gorm.DB) {
|
||||
// 需要创建序列的表列表
|
||||
tables := []string{
|
||||
"sys_users",
|
||||
"sys_apis",
|
||||
"sys_base_menus",
|
||||
"sys_authorities",
|
||||
"sys_dictionaries",
|
||||
"sys_operation_records",
|
||||
"sys_auto_code_histories",
|
||||
"sys_dictionary_details",
|
||||
"sys_base_menu_parameters",
|
||||
"sys_base_menu_btns",
|
||||
"sys_authority_btns",
|
||||
"sys_auto_code_packages",
|
||||
"sys_export_templates",
|
||||
"conditions",
|
||||
"join_templates",
|
||||
"sys_params",
|
||||
"exa_files",
|
||||
"exa_customers",
|
||||
"exa_file_chunks",
|
||||
"exa_file_upload_and_downloads",
|
||||
"account_ding_talk_extends",
|
||||
"app_request_test_batches",
|
||||
"app_request_tests",
|
||||
"system_integrations",
|
||||
"forwarding_extends",
|
||||
"batch_workflows",
|
||||
"batch_workflow_tasks",
|
||||
"sys_user_global_codes",
|
||||
}
|
||||
|
||||
for _, table := range tables {
|
||||
sequenceName := fmt.Sprintf("%s_id_seq", table)
|
||||
|
||||
// 检查序列是否已存在
|
||||
var exists bool
|
||||
checkSQL := "SELECT EXISTS (SELECT 1 FROM pg_sequences WHERE sequencename = ?)"
|
||||
if err := db.Raw(checkSQL, sequenceName).Scan(&exists).Error; err != nil {
|
||||
log.Printf("检查序列 %s 是否存在时出错: %v", sequenceName, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !exists {
|
||||
// 创建序列
|
||||
createSQL := fmt.Sprintf("CREATE SEQUENCE IF NOT EXISTS %s START 1 INCREMENT 1", sequenceName)
|
||||
if err := db.Exec(createSQL).Error; err != nil {
|
||||
log.Printf("创建序列 %s 时出错: %v", sequenceName, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// 将序列设置为表的默认值
|
||||
alterSQL := fmt.Sprintf("ALTER TABLE %s ALTER COLUMN id SET DEFAULT nextval('%s')", table, sequenceName)
|
||||
if err := db.Exec(alterSQL).Error; err != nil {
|
||||
log.Printf("设置表 %s 的ID默认值时出错: %v", table, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// 更新序列的当前值(如果表中已有数据)
|
||||
updateSQL := fmt.Sprintf("SELECT setval('%s', COALESCE((SELECT MAX(id) FROM %s), 1), true)", sequenceName, table)
|
||||
if err := db.Exec(updateSQL).Error; err != nil {
|
||||
log.Printf("更新序列 %s 的当前值时出错: %v", sequenceName, err)
|
||||
}
|
||||
|
||||
log.Printf("成功为表 %s 创建序列 %s", table, sequenceName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,10 @@ func GormPgSql() *gorm.DB {
|
||||
sqlDB, _ := db.DB()
|
||||
sqlDB.SetMaxIdleConns(p.MaxIdleConns)
|
||||
sqlDB.SetMaxOpenConns(p.MaxOpenConns)
|
||||
|
||||
// 为PostgreSQL创建必要的序列
|
||||
//createPostgreSQLSequences(db)
|
||||
|
||||
return db
|
||||
}
|
||||
}
|
||||
@@ -45,6 +49,10 @@ func GormPgSqlByConfig(p config.Pgsql) *gorm.DB {
|
||||
sqlDB, _ := db.DB()
|
||||
sqlDB.SetMaxIdleConns(p.MaxIdleConns)
|
||||
sqlDB.SetMaxOpenConns(p.MaxOpenConns)
|
||||
|
||||
// 为PostgreSQL创建必要的序列
|
||||
//createPostgreSQLSequences(db)
|
||||
|
||||
return db
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ type SysUser struct {
|
||||
Authority SysAuthority `json:"authority" gorm:"foreignKey:AuthorityId;references:AuthorityId;comment:用户角色"` // 用户角色
|
||||
Authorities []SysAuthority `json:"authorities" gorm:"many2many:sys_user_authority;"` // 多用户角色
|
||||
Phone string `json:"phone" gorm:"comment:用户手机号"` // 用户手机号
|
||||
Email string `json:"email" gorm:"comment:用户邮箱"` // 用户邮箱
|
||||
Email string `json:"email" gorm:"index;comment:用户邮箱"` // 用户邮箱
|
||||
Enable int `json:"enable" gorm:"default:1;comment:用户是否被冻结 1正常 2冻结"` //用户是否被冻结 1正常 2冻结
|
||||
OriginSetting common.JSONMap `json:"originSetting" form:"originSetting" gorm:"type:text;default:null;column:origin_setting;comment:配置;"` //配置
|
||||
}
|
||||
|
||||
@@ -128,12 +128,6 @@ func RegisterUser(u system.SysUser, token string) (err error) {
|
||||
global.GVA_LOG.Debug("注册用户信息:", zap.Any("1", 1))
|
||||
var acc gaia.Account
|
||||
if err = global.GVA_DB.Where("email=?", u.Email).First(&acc).Error; err == nil {
|
||||
// 用户已存在,更新密码
|
||||
global.GVA_LOG.Info(fmt.Sprintf("account %s already exists, updating password", acc.Name))
|
||||
global.GVA_DB.Model(&acc).Updates(&map[string]interface{}{
|
||||
"password": passwordHashed,
|
||||
"password_salt": salt,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
// 默认以root执行
|
||||
|
||||
+22
-17
@@ -1,7 +1,5 @@
|
||||
package utils
|
||||
|
||||
import "math"
|
||||
|
||||
// InArray @author: [Fantasia](https://www.npc0.com)
|
||||
// @function: InArray
|
||||
// @description: 判断是否在数组中
|
||||
@@ -52,20 +50,27 @@ func InStringArray(value string, array []string) (isIn bool) {
|
||||
// @description: 字符串加星号
|
||||
// @return: err error, conf config.Server
|
||||
func AddAsteriskToString(s string) string {
|
||||
// 计算要插入的星号数量
|
||||
num := 0
|
||||
stars := ""
|
||||
// 计算插入位置
|
||||
insertPos := len(s) / 2
|
||||
numStars := int(math.Ceil(float64(len(s)) / 5))
|
||||
for i := 0; i < numStars; i++ {
|
||||
if num > 8 {
|
||||
continue
|
||||
}
|
||||
stars += "*"
|
||||
num += 1
|
||||
// 处理空字符串或长度不足的情况
|
||||
if len(s) == 0 {
|
||||
return ""
|
||||
}
|
||||
// 插入星号
|
||||
result := s[:insertPos] + stars + s[insertPos:]
|
||||
return result
|
||||
if len(s) == 1 {
|
||||
return "*"
|
||||
}
|
||||
if len(s) == 2 {
|
||||
return s[:1] + "*"
|
||||
}
|
||||
if len(s) <= 4 {
|
||||
return s[:1] + "***" + s[len(s)-1:]
|
||||
}
|
||||
if len(s) <= 8 {
|
||||
return s[:2] + "***" + s[len(s)-2:]
|
||||
}
|
||||
|
||||
// 保留前6个字符和后6个字符,中间用星号替换
|
||||
prefix := s[:6]
|
||||
suffix := s[len(s)-6:]
|
||||
middle := "********"
|
||||
|
||||
return prefix + middle + suffix
|
||||
}
|
||||
|
||||
@@ -198,7 +198,7 @@ const config = ref({
|
||||
user_name_field: "",
|
||||
user_email_field: "",
|
||||
user_id_field: "",
|
||||
scope: "",
|
||||
scope: "openid profile email",
|
||||
token_auth_method: "client_secret_post",
|
||||
redirect_uri: "",
|
||||
discovery_url: "",
|
||||
@@ -249,10 +249,11 @@ const testConnection = async () => {
|
||||
const base = config.value.server_url || ''
|
||||
const authPath = config.value.authorize_url || ''
|
||||
let authorizeUrl = authPath.startsWith('http://') || authPath.startsWith('https://') ? authPath : `${base}${authPath}`
|
||||
let redirectUriRaw = config.value.redirect_uri || `${location.protocol}//${location.host}/api/base/auth2/callback`
|
||||
let redirectUriRaw = config.value.redirect_uri || `${location.protocol}//${location.host}/admin/api/base/auth2/callback`
|
||||
let redirectUri = encodeURIComponent(redirectUriRaw)
|
||||
let authorizeLike = authorizeUrl.includes('?') ? '&' : '?'
|
||||
let scope = encodeURIComponent(config.value.scope || 'openid profile email')
|
||||
window.open(`${authorizeUrl}?client_id=${encodeURIComponent(config.value.app_id)}&response_type=code&scope=${scope}&redirect_uri=${redirectUri}`)
|
||||
window.open(`${authorizeUrl}${authorizeLike}&client_id=${encodeURIComponent(config.value.app_id)}&response_type=code&scope=${scope}&redirect_uri=${redirectUri}`)
|
||||
}
|
||||
|
||||
const initForm = async() => {
|
||||
|
||||
@@ -73,6 +73,8 @@ class OAuthCallback(Resource):
|
||||
if not oauth_provider:
|
||||
return {"error": "Invalid provider"}, 400
|
||||
|
||||
# extend: 兼容casdoor
|
||||
id_token = None
|
||||
code = request.args.get("code")
|
||||
state = request.args.get("state")
|
||||
# Fallback: some providers may return tokens directly in query (implicit/hybrid flow)
|
||||
@@ -95,12 +97,38 @@ class OAuthCallback(Resource):
|
||||
invite_token = state
|
||||
|
||||
try:
|
||||
token = token_from_query or oauth_provider.get_access_token(code) # type: ignore[arg-type]
|
||||
if token_from_query is not None:
|
||||
token = token_from_query
|
||||
else:
|
||||
# Extend: Start 兼容casdoor
|
||||
response_json = oauth_provider.get_access_token(code)
|
||||
token = response_json.get("access_token")
|
||||
if not token:
|
||||
return {"error": f"Error in OAuth: {response_json}"}, 502
|
||||
id_token = response_json.get("id_token")
|
||||
# Extend: Stop 兼容casdoor
|
||||
# 检查token是否有效
|
||||
if not token or token.strip() == "":
|
||||
logger.error("OAuth2 access token is empty for provider %s", provider)
|
||||
return {"error": "OAuth2 access token is empty or invalid"}, 400
|
||||
|
||||
user_info = oauth_provider.get_user_info(token)
|
||||
except requests.RequestException as e:
|
||||
error_text = e.response.text if e.response else str(e)
|
||||
logger.exception("An error occurred during the OAuth process with %s: %s", provider, error_text)
|
||||
return {"error": "OAuth process failed"}, 400
|
||||
error_status = e.response.status_code if e.response else "Unknown"
|
||||
logger.exception("An error occurred during the OAuth process with %s (Status: %s): %s",
|
||||
provider, error_status, error_text)
|
||||
|
||||
# 提供更具体的错误信息
|
||||
if error_status == 401:
|
||||
return {"error": f"OAuth2 authentication failed (401 Unauthorized): {error_text}"}, 400
|
||||
elif error_status == 400:
|
||||
return {"error": f"OAuth2 bad request (400): {error_text}"}, 400
|
||||
else:
|
||||
return {"error": f"OAuth process failed (Status: {error_status}): {error_text}"}, 400
|
||||
except ValueError as e:
|
||||
logger.exception("OAuth2 configuration error for provider %s: %s", provider, str(e))
|
||||
return {"error": f"OAuth2 configuration error: {str(e)}"}, 400
|
||||
|
||||
if invite_token and RegisterService.is_valid_invite_token(invite_token):
|
||||
invitation = RegisterService._get_invitation_by_token(token=invite_token)
|
||||
@@ -146,9 +174,10 @@ class OAuthCallback(Resource):
|
||||
account=account,
|
||||
ip_address=extract_remote_ip(request),
|
||||
)
|
||||
|
||||
# extend: 兼容casdoor
|
||||
return redirect(
|
||||
f"{dify_config.CONSOLE_WEB_URL}?access_token={token_pair.access_token}&refresh_token={token_pair.refresh_token}"
|
||||
f"{dify_config.CONSOLE_WEB_URL}?access_token={token_pair.access_token}"
|
||||
f"&refresh_token={token_pair.refresh_token}&id_token={id_token}"
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -106,7 +106,7 @@ class TextApi(Resource):
|
||||
}
|
||||
)
|
||||
@validate_app_token(fetch_user_arg=FetchUserArg(fetch_from=WhereisUserArg.JSON))
|
||||
def post(self, app_model: App, end_user: EndUser):
|
||||
def post(self, app_model: App, end_user: EndUser, api_token: ApiToken): # 二开部分End - 密钥额度限制,新增api_token
|
||||
"""Convert text to audio using text-to-speech.
|
||||
|
||||
Converts the provided text to audio using the specified voice.
|
||||
|
||||
@@ -30,6 +30,7 @@ from libs import helper
|
||||
from libs.helper import uuid_value
|
||||
from models.model import ApiToken, App, AppMode, EndUser # 二开部分End - 密钥额度限制,新增ApiToken
|
||||
from services.app_generate_service import AppGenerateService
|
||||
from services.app_generate_service_extend import AppGenerateServiceExtend
|
||||
from services.errors.app import IsDraftWorkflowError, WorkflowIdFormatError, WorkflowNotFoundError
|
||||
from services.errors.llm import InvokeRateLimitError
|
||||
|
||||
@@ -204,6 +205,13 @@ class ChatApi(Resource):
|
||||
# # ------------------- 二开部分End - 密钥额度限制 -------------------
|
||||
|
||||
try:
|
||||
# ------------------- 二开部分Begin - 添加使用统计 -------------------
|
||||
AppGenerateServiceExtend.calculate_cumulative_usage(
|
||||
app_model=app_model,
|
||||
args=args,
|
||||
) # Extend: App
|
||||
# ------------------- 二开部分End - 添加使用统计 -------------------
|
||||
|
||||
response = AppGenerateService.generate(
|
||||
app_model=app_model, user=end_user, args=args, invoke_from=InvokeFrom.SERVICE_API, streaming=streaming
|
||||
)
|
||||
|
||||
@@ -673,10 +673,10 @@ class WorkflowAppGenerateTaskPipeline:
|
||||
if isinstance(
|
||||
event,
|
||||
(
|
||||
QueueNodeFailedEvent,
|
||||
QueueNodeInIterationFailedEvent,
|
||||
QueueNodeInLoopFailedEvent,
|
||||
QueueNodeExceptionEvent,
|
||||
QueueNodeFailedEvent,
|
||||
QueueNodeInIterationFailedEvent,
|
||||
QueueNodeInLoopFailedEvent,
|
||||
QueueNodeExceptionEvent,
|
||||
),
|
||||
):
|
||||
yield from self._handle_node_failed_events(
|
||||
|
||||
@@ -36,7 +36,6 @@ from tasks.extend.update_account_money_when_workflow_node_execution_created_exte
|
||||
update_account_money_when_workflow_node_execution_created_extend,
|
||||
)
|
||||
|
||||
|
||||
# 二开部分End - 密钥额度限制
|
||||
|
||||
@dataclass
|
||||
@@ -215,9 +214,9 @@ class WorkflowCycleManager:
|
||||
self,
|
||||
*,
|
||||
event: QueueNodeFailedEvent
|
||||
| QueueNodeInIterationFailedEvent
|
||||
| QueueNodeInLoopFailedEvent
|
||||
| QueueNodeExceptionEvent,
|
||||
| QueueNodeInIterationFailedEvent
|
||||
| QueueNodeInLoopFailedEvent
|
||||
| QueueNodeExceptionEvent,
|
||||
) -> WorkflowNodeExecution:
|
||||
"""
|
||||
Workflow node execution failed
|
||||
@@ -361,7 +360,7 @@ class WorkflowCycleManager:
|
||||
node_exec
|
||||
for node_exec in self._node_execution_cache.values()
|
||||
if node_exec.workflow_execution_id == workflow_execution_id
|
||||
and node_exec.status == WorkflowNodeExecutionStatus.RUNNING
|
||||
and node_exec.status == WorkflowNodeExecutionStatus.RUNNING
|
||||
]
|
||||
|
||||
for node_execution in running_node_executions:
|
||||
|
||||
+43
-31
@@ -66,13 +66,7 @@ class GitHubOAuth(OAuth):
|
||||
headers = {"Accept": "application/json"}
|
||||
response = requests.post(self._TOKEN_URL, data=data, headers=headers)
|
||||
|
||||
response_json = response.json()
|
||||
access_token = response_json.get("access_token")
|
||||
|
||||
if not access_token:
|
||||
raise ValueError(f"Error in GitHub OAuth: {response_json}")
|
||||
|
||||
return access_token
|
||||
return response.json()
|
||||
|
||||
def get_raw_user_info(self, token: str):
|
||||
headers = {"Authorization": f"token {token}"}
|
||||
@@ -120,13 +114,7 @@ class GoogleOAuth(OAuth):
|
||||
headers = {"Accept": "application/json"}
|
||||
response = requests.post(self._TOKEN_URL, data=data, headers=headers)
|
||||
|
||||
response_json = response.json()
|
||||
access_token = response_json.get("access_token")
|
||||
|
||||
if not access_token:
|
||||
raise ValueError(f"Error in Google OAuth: {response_json}")
|
||||
|
||||
return access_token
|
||||
return response.json()
|
||||
|
||||
def get_raw_user_info(self, token: str):
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
@@ -239,9 +227,7 @@ class OaOAuth(OAuth):
|
||||
'response_type': 'code',
|
||||
'redirect_uri': dify_config.CONSOLE_API_URL + "/console/api/oauth/authorize/oauth2",
|
||||
'client_id': integration.app_id,
|
||||
# 重要:未设置 scope 时,Casdoor /api/userinfo 仅返回 openid 最小字段
|
||||
# 从配置读取 scope,默认请求更完整的信息
|
||||
'scope': (config.get('scope') if isinstance(config, dict) and config.get('scope') else 'openid profile email'),
|
||||
'scope': config.get('scope'),
|
||||
}
|
||||
if invite_token:
|
||||
params['state'] = invite_token
|
||||
@@ -249,7 +235,7 @@ class OaOAuth(OAuth):
|
||||
|
||||
endpoints = self._resolve_endpoints(config)
|
||||
auth_url = endpoints.get('authorize_url')
|
||||
return f"{auth_url}?{query_string}"
|
||||
return f"{auth_url}{'&' if "?" in auth_url else '?'}{query_string}"
|
||||
|
||||
def get_access_token(self, code: str):
|
||||
auto2_conf = self.get_auto2_conf()
|
||||
@@ -281,17 +267,12 @@ class OaOAuth(OAuth):
|
||||
if not code:
|
||||
return ""
|
||||
|
||||
response = requests.post(token_url, data=data, headers=headers, auth=auth)
|
||||
response = requests.post(token_url, data=data, headers=headers, auth=auth, timeout=30)
|
||||
response.encoding = "utf-8"
|
||||
if response.status_code != 200:
|
||||
return ""
|
||||
try:
|
||||
response_json = response.json()
|
||||
except:
|
||||
return ""
|
||||
access_token = response_json.get("access_token")
|
||||
|
||||
return access_token
|
||||
return response.json()
|
||||
|
||||
def get_raw_user_info(self, token: str):
|
||||
auto2_conf = self.get_auto2_conf()
|
||||
@@ -299,14 +280,45 @@ class OaOAuth(OAuth):
|
||||
return ""
|
||||
config = auto2_conf.get('config')
|
||||
endpoints = self._resolve_endpoints(config)
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
userinfo_url = endpoints.get('userinfo_url')
|
||||
response = requests.get(userinfo_url, headers=headers)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
# 检查token是否为空
|
||||
if not token or token.strip() == "":
|
||||
raise ValueError("OAuth2 access token is empty or invalid")
|
||||
|
||||
# 尝试不同的Authorization header格式
|
||||
auth_formats = [
|
||||
f"Bearer {token}",
|
||||
f"Token {token}",
|
||||
token
|
||||
]
|
||||
|
||||
last_error = None
|
||||
for auth_header in auth_formats:
|
||||
try:
|
||||
headers = {"Authorization": auth_header}
|
||||
response = requests.get(f"{endpoints.get('userinfo_url')}", headers=headers, timeout=30)
|
||||
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
elif response.status_code == 401:
|
||||
last_error = f"401 Unauthorized: {response.text}"
|
||||
continue
|
||||
else:
|
||||
last_error = f"HTTP {response.status_code}: {response.text}"
|
||||
continue
|
||||
|
||||
except requests.RequestException as e:
|
||||
last_error = str(e)
|
||||
continue
|
||||
|
||||
# 如果所有格式都失败,抛出最后一个错误
|
||||
if last_error:
|
||||
raise requests.RequestException(f"All authentication formats failed. Last error: {last_error}")
|
||||
else:
|
||||
raise requests.RequestException("Failed to get user info with any authentication format")
|
||||
|
||||
def _transform_user_info(self, raw_info: dict) -> OAuthUserInfo:
|
||||
|
||||
|
||||
# 检查 raw_info 是否为空或为 None
|
||||
auto2_conf = self.get_auto2_conf()
|
||||
if not raw_info or not isinstance(raw_info, dict) or auto2_conf.get('integration') is None:
|
||||
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
"""add_account_money_extend_unique_constraint
|
||||
|
||||
Revision ID: 012_account_money_extend_unique
|
||||
Revises: 011_system_integration_fields
|
||||
Create Date: 2025-10-21 18:00:00.000000
|
||||
|
||||
"""
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
from sqlalchemy.engine.reflection import Inspector
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '012_account_money_extend_unique'
|
||||
down_revision = '011_system_integration_fields'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
conn = op.get_bind()
|
||||
inspector = Inspector.from_engine(conn)
|
||||
tables = inspector.get_table_names()
|
||||
|
||||
if 'account_money_extend' in tables:
|
||||
# 首先删除重复数据,只保留每个account_id中updated_at最大的记录
|
||||
conn.execute(sa.text("""
|
||||
DELETE FROM account_money_extend
|
||||
WHERE id NOT IN (
|
||||
SELECT DISTINCT ON (account_id) id
|
||||
FROM account_money_extend
|
||||
ORDER BY account_id, updated_at DESC
|
||||
)
|
||||
"""))
|
||||
|
||||
# 删除现有的普通索引
|
||||
with op.batch_alter_table('account_money_extend', schema=None) as batch_op:
|
||||
try:
|
||||
batch_op.drop_index('idx_account_money_account_id')
|
||||
except Exception:
|
||||
# 如果索引不存在,忽略错误
|
||||
pass
|
||||
|
||||
# 创建唯一约束
|
||||
with op.batch_alter_table('account_money_extend', schema=None) as batch_op:
|
||||
batch_op.create_unique_constraint('idx_account_money_account_id_unique', ['account_id'])
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
if 'account_money_extend' in tables:
|
||||
with op.batch_alter_table('account_money_extend', schema=None) as batch_op:
|
||||
try:
|
||||
batch_op.drop_constraint('idx_account_money_account_id_unique', type_='unique')
|
||||
except Exception:
|
||||
# 如果约束不存在,忽略错误
|
||||
pass
|
||||
|
||||
# 重新创建普通索引
|
||||
batch_op.create_index('idx_account_money_account_id', ['account_id'], unique=False)
|
||||
# ### end Alembic commands ###
|
||||
@@ -7,7 +7,7 @@ class AccountMoneyExtend(db.Model):
|
||||
__tablename__ = "account_money_extend"
|
||||
__table_args__ = (
|
||||
db.PrimaryKeyConstraint("id", name="account_money_pkey"),
|
||||
db.Index("idx_account_money_account_id", "account_id"),
|
||||
db.UniqueConstraint("account_id", name="idx_account_money_account_id_unique"),
|
||||
)
|
||||
|
||||
id = db.Column(StringUUID, server_default=db.text("uuid_generate_v4()"))
|
||||
|
||||
@@ -88,7 +88,7 @@ class DatabaseRecommendAppRetrieval(RecommendAppRetrievalBase):
|
||||
category = class_dick[classId]
|
||||
recommended_app_result = {
|
||||
"id": recommended_app.id,
|
||||
"app": recommended_app.app,
|
||||
"app": recommended_app.app,
|
||||
"app_id": recommended_app.app_id,
|
||||
"description": description,
|
||||
"copyright": site.copyright,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
from flask_login import current_user
|
||||
|
||||
from configs import dify_config
|
||||
@@ -37,8 +36,8 @@ class WorkspaceService:
|
||||
tenant_extend_service = TenantExtendService
|
||||
super_admin_id = tenant_extend_service.get_super_admin_id().id
|
||||
super_admin_tenant_id = tenant_extend_service.get_super_admin_tenant_id().id
|
||||
tenant_info["admin_extend"] = (super_admin_id == current_user.id)
|
||||
tenant_info["tenant_extend"] = (super_admin_tenant_id == tenant.id)
|
||||
tenant_info["admin_extend"] = super_admin_id == current_user.id
|
||||
tenant_info["tenant_extend"] = super_admin_tenant_id == tenant.id
|
||||
# ----------------------- 二开部分Stop 添加用户权限 - ----------------------
|
||||
|
||||
can_replace_logo = FeatureService.get_features(tenant.id).can_replace_logo
|
||||
|
||||
@@ -582,7 +582,7 @@ x-shared-env: &shared-api-worker-env
|
||||
services:
|
||||
# API service
|
||||
api:
|
||||
image: ccr.ccs.tencentyun.com/yfgaia/dify-plus-api:1.8.1
|
||||
image: ccr.ccs.tencentyun.com/yfgaia/dify-plus-api:1.8.1.fix
|
||||
restart: always
|
||||
environment:
|
||||
# Use the shared environment variables.
|
||||
@@ -611,7 +611,7 @@ services:
|
||||
# worker service
|
||||
# The Celery worker for processing the queue.
|
||||
worker:
|
||||
image: ccr.ccs.tencentyun.com/yfgaia/dify-plus-api:1.8.1
|
||||
image: ccr.ccs.tencentyun.com/yfgaia/dify-plus-api:1.8.1.fix
|
||||
restart: always
|
||||
environment:
|
||||
# Use the shared environment variables.
|
||||
@@ -636,7 +636,7 @@ services:
|
||||
# worker-gaia service
|
||||
# The Celery worker-gaia for processing the queue.
|
||||
worker-gaia:
|
||||
image: ccr.ccs.tencentyun.com/yfgaia/dify-plus-api:1.8.1
|
||||
image: ccr.ccs.tencentyun.com/yfgaia/dify-plus-api:1.8.1.fix
|
||||
restart: always
|
||||
environment:
|
||||
# Use the shared environment variables.
|
||||
@@ -661,7 +661,7 @@ services:
|
||||
# worker-dataset service
|
||||
# The Celery worker-dataset for processing the queue.
|
||||
worker-dataset:
|
||||
image: ccr.ccs.tencentyun.com/yfgaia/dify-plus-api:1.8.1
|
||||
image: ccr.ccs.tencentyun.com/yfgaia/dify-plus-api:1.8.1.fix
|
||||
restart: always
|
||||
environment:
|
||||
# Use the shared environment variables.
|
||||
@@ -686,7 +686,7 @@ services:
|
||||
# beat service
|
||||
# The Celery worker for schedule tasks.
|
||||
beat:
|
||||
image: ccr.ccs.tencentyun.com/yfgaia/dify-plus-api:1.8.1
|
||||
image: ccr.ccs.tencentyun.com/yfgaia/dify-plus-api:1.8.1.fix
|
||||
restart: always
|
||||
environment:
|
||||
# Use the shared environment variables.
|
||||
@@ -710,7 +710,7 @@ services:
|
||||
|
||||
# Frontend web application.
|
||||
web:
|
||||
image: ccr.ccs.tencentyun.com/yfgaia/dify-plus-web:1.8.1
|
||||
image: ccr.ccs.tencentyun.com/yfgaia/dify-plus-web:1.8.1.fix.3
|
||||
restart: always
|
||||
environment:
|
||||
CONSOLE_API_URL: ${CONSOLE_API_URL:-}
|
||||
@@ -1375,7 +1375,7 @@ services:
|
||||
|
||||
# Extend - admin-web
|
||||
admin-web:
|
||||
image: ccr.ccs.tencentyun.com/yfgaia/dify-plus-admin-web:1.8.1
|
||||
image: ccr.ccs.tencentyun.com/yfgaia/dify-plus-admin-web:1.8.1.fix
|
||||
restart: always
|
||||
ports:
|
||||
- '8081:8081'
|
||||
@@ -1387,7 +1387,7 @@ services:
|
||||
|
||||
# Extend - admin-server
|
||||
admin-server:
|
||||
image: ccr.ccs.tencentyun.com/yfgaia/dify-plus-admin-server:1.8.1
|
||||
image: ccr.ccs.tencentyun.com/yfgaia/dify-plus-admin-server:1.8.1.fix
|
||||
restart: always
|
||||
environment:
|
||||
# JWT signing key must match API's SECRET_KEY for token compatibility
|
||||
|
||||
@@ -348,7 +348,9 @@ const AppCard = ({ app, onRefresh, onApp }: AppCardProps) => {
|
||||
<button className='mx-1 flex h-8 cursor-pointer items-center gap-2 rounded-lg px-3 hover:bg-state-base-hover' onClick={onClickInstalledApp}>
|
||||
<span className='system-sm-regular text-text-secondary'>{t('app.openInExplore')}</span>
|
||||
</button>
|
||||
{/* <>------start SyncToAppTemplate-------</> */}
|
||||
</>
|
||||
}
|
||||
{/* <>------start SyncToAppTemplate-------</> */}
|
||||
{(userProfile.admin_extend && userProfile.tenant_extend && !onApp) && (
|
||||
<>
|
||||
<Divider className="!my-1"/>
|
||||
@@ -372,8 +374,6 @@ const AppCard = ({ app, onRefresh, onApp }: AppCardProps) => {
|
||||
</>
|
||||
)}
|
||||
{/* <>------start SyncToAppTemplate-------</> */}
|
||||
</>
|
||||
}
|
||||
<Divider className="my-1" />
|
||||
{
|
||||
systemFeatures.webapp_auth.enabled && isCurrentWorkspaceEditor && <>
|
||||
|
||||
@@ -122,6 +122,22 @@ const List = () => {
|
||||
{ value: 'completion', text: t('app.types.completion'), icon: <RiFile4Line className='mr-1 h-[14px] w-[14px]' /> },
|
||||
]
|
||||
|
||||
// Extend: start 取消同步至应用模板
|
||||
const recommendedApps = data?.at(-1)?.recommended_apps ?? [] // app recommended apps[]string
|
||||
useEffect(() => {
|
||||
const hasMore = data?.at(-1)?.has_more ?? true
|
||||
let observer: IntersectionObserver | undefined
|
||||
if (anchorRef.current) {
|
||||
observer = new IntersectionObserver((entries) => {
|
||||
if (entries[0].isIntersecting && !isLoading && hasMore)
|
||||
setSize((size: number) => size + 1)
|
||||
}, { rootMargin: '100px' })
|
||||
observer.observe(anchorRef.current)
|
||||
}
|
||||
return () => observer?.disconnect()
|
||||
}, [isLoading, setSize, anchorRef, mutate, data])
|
||||
// Extend: stop 取消同步至应用模板
|
||||
|
||||
useEffect(() => {
|
||||
if (localStorage.getItem(NEED_REFRESH_APP_LIST_KEY) === '1') {
|
||||
localStorage.removeItem(NEED_REFRESH_APP_LIST_KEY)
|
||||
@@ -213,7 +229,8 @@ const List = () => {
|
||||
{isCurrentWorkspaceEditor
|
||||
&& <NewAppCard ref={newAppCardRef} onSuccess={mutate} />}
|
||||
{data.map(({ data: apps }) => apps.map(app => (
|
||||
<AppCard key={app.id} app={app} onRefresh={mutate} />
|
||||
// Extend: 取消同步至应用模板
|
||||
<AppCard key={app.id} app={app} onRefresh={mutate} onApp={recommendedApps.includes(app.id)} />
|
||||
)))}
|
||||
</div>
|
||||
: <div className='relative grid grow grid-cols-1 content-start gap-4 overflow-hidden px-12 pt-2 sm:grid-cols-1 md:grid-cols-2 xl:grid-cols-4 2xl:grid-cols-5 2k:grid-cols-6'>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next'
|
||||
import { RiKey2Line } from '@remixicon/react'
|
||||
import Button from '@/app/components/base/button'
|
||||
import SecretKeyModal from '@/app/components/develop/secret-key/secret-key-modal'
|
||||
import {useAppContext} from "@/context/app-context";
|
||||
|
||||
type ISecretKeyButtonProps = {
|
||||
className?: string
|
||||
@@ -12,8 +13,11 @@ type ISecretKeyButtonProps = {
|
||||
}
|
||||
|
||||
const SecretKeyButton = ({ className, appId, textCls }: ISecretKeyButtonProps) => {
|
||||
const { isCurrentWorkspaceManager } = useAppContext()
|
||||
const [isVisible, setVisible] = useState(false)
|
||||
const { t } = useTranslation()
|
||||
if (!isCurrentWorkspaceManager)
|
||||
return ""
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
|
||||
@@ -40,8 +40,8 @@ const AppCard = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn('group flex col-span-1 bg-white border-2 border-solid border-transparent rounded-lg shadow-sm min-h-[160px] flex flex-col transition-all duration-200 ease-in-out cursor-pointer hover:shadow-lg')}>
|
||||
<div className='flex pt-[14px] px-[14px] pb-3 h-[66px] items-center gap-3 grow-0 shrink-0'>
|
||||
<div className={cn('group relative col-span-1 flex cursor-pointer flex-col overflow-hidden rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg pb-2 shadow-sm transition-all duration-200 ease-in-out hover:shadow-lg')}>
|
||||
<div className='flex h-[66px] shrink-0 grow-0 items-center gap-3 px-[14px] pb-3 pt-[14px]'>
|
||||
<div className='relative shrink-0'>
|
||||
<AppIcon
|
||||
size='large'
|
||||
@@ -50,7 +50,7 @@ const AppCard = ({
|
||||
background={app.app.icon_background}
|
||||
imageUrl={app.app.icon_url}
|
||||
/>
|
||||
<span className='absolute bottom-[-3px] right-[-3px] w-4 h-4 p-0.5 bg-white rounded border-[0.5px] border-[rgba(0,0,0,0.02)] shadow-sm'>
|
||||
<span className='absolute bottom-[-3px] right-[-3px] w-4 h-4 p-0.5 rounded border-[0.5px] border-[rgba(0,0,0,0.02)] shadow-sm'>
|
||||
{appBasicInfo.mode === 'advanced-chat' && (
|
||||
<ChatBot className='w-3 h-3 text-[#1570EF]' />
|
||||
)}
|
||||
@@ -69,7 +69,7 @@ const AppCard = ({
|
||||
</span>
|
||||
</div>
|
||||
<div className='grow w-0 py-[1px]'>
|
||||
<div className='flex items-center text-sm leading-5 font-semibold text-gray-800'>
|
||||
<div className='flex items-center text-sm font-semibold leading-5 text-text-secondary'>
|
||||
<div className='truncate' title={appBasicInfo.name}>{appBasicInfo.name}</div>
|
||||
</div>
|
||||
<div className='flex items-center text-[10px] leading-[18px] text-gray-500 font-medium'>
|
||||
|
||||
@@ -186,8 +186,8 @@ const Apps = ({
|
||||
|
||||
return (
|
||||
<div className={cn(
|
||||
'flex flex-col',
|
||||
pageType === PageType.EXPLORE ? 'h-full border-l border-gray-200' : 'h-[calc(100%-56px)]',
|
||||
'flex flex-col border-divider-regular',
|
||||
pageType === PageType.EXPLORE ? 'h-full border-l' : 'h-[calc(100%-56px)]',
|
||||
)}>
|
||||
{pageType === PageType.EXPLORE && (
|
||||
<div className='shrink-0 pt-6 px-12'>
|
||||
@@ -225,7 +225,7 @@ const Apps = ({
|
||||
{/* extend: Application Center Search Stop */}
|
||||
</div>
|
||||
<div className={cn(
|
||||
'relative flex flex-1 pb-6 flex-col overflow-auto bg-gray-100 shrink-0 grow',
|
||||
'relative flex flex-1 pb-6 flex-col overflow-auto shrink-0 grow',
|
||||
pageType === PageType.EXPLORE ? 'mt-6' : 'mt-0 pt-2',
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -68,11 +68,15 @@ export default function AppSelector() {
|
||||
if (localStorage?.getItem('conversationIdInfo'))
|
||||
localStorage.removeItem('conversationIdInfo')
|
||||
// 二开部分 - End 解决切换账号对话记录不存在问题
|
||||
|
||||
let id_token = null
|
||||
if (localStorage) {
|
||||
id_token = localStorage.getItem('logout_id_token')
|
||||
}
|
||||
// Start: Automatic login/logout Extend
|
||||
console.log(systemFeatures, 2344)
|
||||
if (window.location !== undefined && `${systemFeatures.is_custom_auth2_logout}` !== '' && systemFeatures.is_custom_auth2_logout !== undefined)
|
||||
window.location.href = `${systemFeatures.is_custom_auth2_logout}&redirect_url=${window.location.href}`
|
||||
let logout_url = systemFeatures.is_custom_auth2_logout
|
||||
let is_connector = logout_url.includes('?') ? '&' : '?'
|
||||
if (window.location !== undefined && logout_url !== '')
|
||||
window.location.href = `${logout_url}${is_connector}id_token_hint=${id_token}&redirect_url=${window.location.href}`
|
||||
// Stop: Automatic login/logout Extend
|
||||
router.push('/signin')
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import type { UserMoney } from '@/models/common-extend'
|
||||
import cn from 'classnames'
|
||||
|
||||
const AccountMoneyExtend = () => {
|
||||
const [userMoney, setUserMoney] = useState<UserMoney>({ used_quota: 0, total_quota: 15 })
|
||||
const [userMoney, setUserMoney] = useState<UserMoney>({ used_quota: 0, total_quota: 0 })
|
||||
const [isFetched, setIsFetched] = useState(false)
|
||||
const exchangeRate = 7.26 // 美元转人民币固定汇率
|
||||
|
||||
@@ -24,9 +24,13 @@ const AccountMoneyExtend = () => {
|
||||
|
||||
// 计算额度(确保使用数字类型)
|
||||
const usedQuota = Number(userMoney.used_quota) || 0
|
||||
const totalQuota = Number(userMoney.total_quota) || 15
|
||||
const totalQuota = Number(userMoney.total_quota) || 0
|
||||
const remainingQuota = totalQuota - usedQuota
|
||||
|
||||
// 当总额度为0时不显示
|
||||
if (totalQuota === 0)
|
||||
return null
|
||||
|
||||
// 转换为人民币并保留2位小数
|
||||
const usedRMB = (usedQuota * exchangeRate).toFixed(2)
|
||||
const totalRMB = (totalQuota * exchangeRate).toFixed(2)
|
||||
|
||||
@@ -19,6 +19,13 @@ const SwrInitializer = ({
|
||||
}: SwrInitializerProps) => {
|
||||
const router = useRouter()
|
||||
const searchParams = useSearchParams()
|
||||
// extend: start 兼容casdoor 退出登录
|
||||
const tokenId = searchParams.get('id_token')
|
||||
if (tokenId && tokenId !== 'None') {
|
||||
if (window.location !== undefined)
|
||||
localStorage?.setItem('logout_id_token', tokenId)
|
||||
}
|
||||
// extend: stop 兼容casdoor 退出登录
|
||||
const consoleToken = decodeURIComponent(searchParams.get('access_token') || '')
|
||||
const refreshToken = decodeURIComponent(searchParams.get('refresh_token') || '')
|
||||
// Extend Start DingTalk login compatible
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import useSWR from 'swr'
|
||||
import { fetchAppList } from '@/service/apps'
|
||||
import { createContext, useContext, useContextSelector } from 'use-context-selector'
|
||||
import type { FC, ReactNode } from 'react'
|
||||
import { fetchCurrentWorkspace, fetchLangGeniusVersion, fetchUserProfile } from '@/service/common'
|
||||
@@ -10,6 +11,7 @@ import MaintenanceNotice from '@/app/components/header/maintenance-notice'
|
||||
import { noop } from 'lodash-es'
|
||||
|
||||
export type AppContextValue = {
|
||||
mutateApps: VoidFunction
|
||||
userProfile: UserProfileResponse
|
||||
mutateUserProfile: VoidFunction
|
||||
currentWorkspace: ICurrentWorkspace
|
||||
@@ -57,6 +59,7 @@ const initialWorkspaceInfo: ICurrentWorkspace = {
|
||||
}
|
||||
|
||||
const AppContext = createContext<AppContextValue>({
|
||||
mutateApps: noop,
|
||||
userProfile: userProfilePlaceholder,
|
||||
currentWorkspace: initialWorkspaceInfo,
|
||||
isCurrentWorkspaceManager: false,
|
||||
@@ -79,6 +82,7 @@ export type AppContextProviderProps = {
|
||||
}
|
||||
|
||||
export const AppContextProvider: FC<AppContextProviderProps> = ({ children }) => {
|
||||
const { mutate: mutateApps } = useSWR({ url: '/apps', params: { } }, fetchAppList)
|
||||
const { data: userProfileResponse, mutate: mutateUserProfile, error: userProfileError } = useSWR({ url: '/account/profile', params: {} }, fetchUserProfile)
|
||||
const { data: currentWorkspaceResponse, mutate: mutateCurrentWorkspace, isLoading: isLoadingCurrentWorkspace } = useSWR({ url: '/workspaces/current', params: {} }, fetchCurrentWorkspace)
|
||||
|
||||
@@ -123,8 +127,12 @@ export const AppContextProvider: FC<AppContextProviderProps> = ({ children }) =>
|
||||
setCurrentWorkspace(currentWorkspaceResponse)
|
||||
}, [currentWorkspaceResponse])
|
||||
|
||||
if (!userProfile)
|
||||
return <Loading type='app' />
|
||||
|
||||
return (
|
||||
<AppContext.Provider value={{
|
||||
mutateApps,
|
||||
userProfile,
|
||||
mutateUserProfile,
|
||||
langGeniusVersionInfo,
|
||||
|
||||
+11
-11
@@ -101,19 +101,19 @@
|
||||
"lexical": "^0.30.0",
|
||||
"line-clamp": "^1.0.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"mermaid": "11.4.1",
|
||||
"mermaid": "11.10.0",
|
||||
"mime": "^4.0.4",
|
||||
"mitt": "^3.0.1",
|
||||
"negotiator": "^0.6.3",
|
||||
"next": "15.5.0",
|
||||
"next": "~15.5.9",
|
||||
"next-themes": "^0.4.3",
|
||||
"papaparse": "^5.5.3",
|
||||
"pinyin-pro": "^3.25.0",
|
||||
"qrcode.react": "^4.2.0",
|
||||
"qs": "^6.13.0",
|
||||
"react": "19.1.1",
|
||||
"react": "19.2.3",
|
||||
"react-18-input-autosize": "^3.0.0",
|
||||
"react-dom": "19.1.1",
|
||||
"react-dom": "19.2.3",
|
||||
"react-easy-crop": "^5.1.0",
|
||||
"react-error-boundary": "^4.1.2",
|
||||
"react-headless-pagination": "^1.1.6",
|
||||
@@ -164,9 +164,9 @@
|
||||
"@happy-dom/jest-environment": "^17.4.4",
|
||||
"@mdx-js/loader": "^3.1.0",
|
||||
"@mdx-js/react": "^3.1.0",
|
||||
"@next/bundle-analyzer": "15.5.0",
|
||||
"@next/eslint-plugin-next": "15.5.0",
|
||||
"@next/mdx": "15.5.0",
|
||||
"@next/bundle-analyzer": "15.5.9",
|
||||
"@next/eslint-plugin-next": "15.5.9",
|
||||
"@next/mdx": "15.5.9",
|
||||
"@rgrove/parse-xml": "^4.1.0",
|
||||
"@storybook/addon-essentials": "8.5.0",
|
||||
"@storybook/addon-interactions": "8.5.0",
|
||||
@@ -188,8 +188,8 @@
|
||||
"@types/negotiator": "^0.6.3",
|
||||
"@types/node": "18.15.0",
|
||||
"@types/qs": "^6.9.16",
|
||||
"@types/react": "19.1.11",
|
||||
"@types/react-dom": "19.1.7",
|
||||
"@types/react": "~19.2.7",
|
||||
"@types/react-dom": "~19.2.3",
|
||||
"@types/react-slider": "^1.3.6",
|
||||
"@types/react-syntax-highlighter": "^15.5.13",
|
||||
"@types/react-window": "^1.8.8",
|
||||
@@ -226,8 +226,8 @@
|
||||
"uglify-js": "^3.19.3"
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/react": "19.1.11",
|
||||
"@types/react-dom": "19.1.7",
|
||||
"@types/react": "~19.2.7",
|
||||
"@types/react-dom": "~19.2.3",
|
||||
"string-width": "4.2.3"
|
||||
},
|
||||
"lint-staged": {
|
||||
|
||||
Generated
+915
-868
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user