Merge develop into release/v0.2.0
This commit is contained in:
@@ -18,6 +18,7 @@ from app.models.user_model import User
|
||||
from app.schemas.emotion_schema import (
|
||||
EmotionHealthRequest,
|
||||
EmotionSuggestionsRequest,
|
||||
EmotionGenerateSuggestionsRequest,
|
||||
EmotionTagsRequest,
|
||||
EmotionWordcloudRequest,
|
||||
)
|
||||
@@ -198,7 +199,7 @@ async def get_emotion_suggestions(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""获取个性化情绪建议
|
||||
"""获取个性化情绪建议(从缓存读取)
|
||||
|
||||
Args:
|
||||
request: 包含 group_id 和可选的 config_id
|
||||
@@ -206,7 +207,72 @@ async def get_emotion_suggestions(
|
||||
current_user: 当前用户
|
||||
|
||||
Returns:
|
||||
个性化情绪建议响应
|
||||
缓存的个性化情绪建议响应
|
||||
"""
|
||||
try:
|
||||
api_logger.info(
|
||||
f"用户 {current_user.username} 请求获取个性化情绪建议(缓存)",
|
||||
extra={
|
||||
"group_id": request.group_id,
|
||||
"config_id": request.config_id
|
||||
}
|
||||
)
|
||||
|
||||
# 从缓存获取建议
|
||||
data = await emotion_service.get_cached_suggestions(
|
||||
end_user_id=request.group_id,
|
||||
db=db
|
||||
)
|
||||
|
||||
if data is None:
|
||||
# 缓存不存在或已过期
|
||||
api_logger.info(
|
||||
f"用户 {request.group_id} 的建议缓存不存在或已过期",
|
||||
extra={"group_id": request.group_id}
|
||||
)
|
||||
return fail(
|
||||
BizCode.RESOURCE_NOT_FOUND,
|
||||
"建议缓存不存在或已过期,请调用 /generate_suggestions 接口生成新建议",
|
||||
None
|
||||
)
|
||||
|
||||
api_logger.info(
|
||||
"个性化建议获取成功(缓存)",
|
||||
extra={
|
||||
"group_id": request.group_id,
|
||||
"suggestions_count": len(data.get("suggestions", []))
|
||||
}
|
||||
)
|
||||
|
||||
return success(data=data, msg="个性化建议获取成功(缓存)")
|
||||
|
||||
except Exception as e:
|
||||
api_logger.error(
|
||||
f"获取个性化建议失败: {str(e)}",
|
||||
extra={"group_id": request.group_id},
|
||||
exc_info=True
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"获取个性化建议失败: {str(e)}"
|
||||
)
|
||||
|
||||
|
||||
@router.post("/generate_suggestions", response_model=ApiResponse)
|
||||
async def generate_emotion_suggestions(
|
||||
request: EmotionGenerateSuggestionsRequest,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""生成个性化情绪建议(调用LLM并缓存)
|
||||
|
||||
Args:
|
||||
request: 包含 group_id、可选的 config_id 和 force_refresh
|
||||
db: 数据库会话
|
||||
current_user: 当前用户
|
||||
|
||||
Returns:
|
||||
新生成的个性化情绪建议响应
|
||||
"""
|
||||
try:
|
||||
# 验证 config_id(如果提供)
|
||||
@@ -234,36 +300,44 @@ async def get_emotion_suggestions(
|
||||
return fail(BizCode.INVALID_PARAMETER, "配置ID验证失败", str(e))
|
||||
|
||||
api_logger.info(
|
||||
f"用户 {current_user.username} 请求获取个性化情绪建议",
|
||||
f"用户 {current_user.username} 请求生成个性化情绪建议",
|
||||
extra={
|
||||
"group_id": request.group_id,
|
||||
"config_id": config_id
|
||||
}
|
||||
)
|
||||
|
||||
# 调用服务层
|
||||
# 调用服务层生成建议
|
||||
data = await emotion_service.generate_emotion_suggestions(
|
||||
end_user_id=request.group_id,
|
||||
db=db
|
||||
)
|
||||
|
||||
# 保存到缓存
|
||||
await emotion_service.save_suggestions_cache(
|
||||
end_user_id=request.group_id,
|
||||
suggestions_data=data,
|
||||
db=db,
|
||||
expires_hours=24
|
||||
)
|
||||
|
||||
api_logger.info(
|
||||
"个性化建议获取成功",
|
||||
"个性化建议生成成功",
|
||||
extra={
|
||||
"group_id": request.group_id,
|
||||
"suggestions_count": len(data.get("suggestions", []))
|
||||
}
|
||||
)
|
||||
|
||||
return success(data=data, msg="个性化建议获取成功")
|
||||
return success(data=data, msg="个性化建议生成成功")
|
||||
|
||||
except Exception as e:
|
||||
api_logger.error(
|
||||
f"获取个性化建议失败: {str(e)}",
|
||||
f"生成个性化建议失败: {str(e)}",
|
||||
extra={"group_id": request.group_id},
|
||||
exc_info=True
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"获取个性化建议失败: {str(e)}"
|
||||
detail=f"生成个性化建议失败: {str(e)}"
|
||||
)
|
||||
|
||||
@@ -33,5 +33,12 @@ def get_workspace_list(
|
||||
def get_system_version():
|
||||
"""获取系统版本号+说明"""
|
||||
current_version = settings.SYSTEM_VERSION
|
||||
version_introduction = HomePageService.load_version_introduction(current_version)
|
||||
return success(data={"version": current_version, "introduction": version_introduction}, msg="系统版本获取成功")
|
||||
version_info = HomePageService.load_version_introduction(current_version)
|
||||
return success(
|
||||
data={
|
||||
"version": current_version,
|
||||
"introduction": version_info.get("introduction"),
|
||||
"introduction_en": version_info.get("introduction_en")
|
||||
},
|
||||
msg="系统版本获取成功"
|
||||
)
|
||||
@@ -11,6 +11,7 @@ from app.dependencies import (
|
||||
)
|
||||
from app.models.user_model import User
|
||||
from app.schemas.response_schema import ApiResponse
|
||||
from app.schemas.implicit_memory_schema import GenerateProfileRequest
|
||||
from app.services.implicit_memory_service import ImplicitMemoryService
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from sqlalchemy.orm import Session
|
||||
@@ -133,7 +134,7 @@ async def get_preference_tags(
|
||||
current_user: User = Depends(get_current_user)
|
||||
) -> ApiResponse:
|
||||
"""
|
||||
Get user preference tags with filtering options.
|
||||
Get user preference tags from cache.
|
||||
|
||||
Args:
|
||||
user_id: Target user ID
|
||||
@@ -143,35 +144,56 @@ async def get_preference_tags(
|
||||
end_date: Optional end date filter
|
||||
|
||||
Returns:
|
||||
List of preference tags matching the filters
|
||||
List of preference tags from cache
|
||||
"""
|
||||
api_logger.info(f"Preference tags requested for user: {user_id}")
|
||||
api_logger.info(f"Preference tags requested for user: {user_id} (from cache)")
|
||||
|
||||
try:
|
||||
# Validate inputs
|
||||
validate_user_id(user_id)
|
||||
validate_confidence_threshold(confidence_threshold)
|
||||
validate_date_range(start_date, end_date)
|
||||
|
||||
# Create service with user-specific config
|
||||
service = ImplicitMemoryService(db=db, end_user_id=user_id)
|
||||
|
||||
# Build date range
|
||||
date_range = None
|
||||
if start_date and end_date:
|
||||
from app.schemas.implicit_memory_schema import DateRange
|
||||
date_range = DateRange(start_date=start_date, end_date=end_date)
|
||||
# Get cached profile
|
||||
cached_profile = await service.get_cached_profile(end_user_id=user_id, db=db)
|
||||
|
||||
# Get preference tags
|
||||
tags = await service.get_preference_tags(
|
||||
user_id=user_id,
|
||||
confidence_threshold=confidence_threshold,
|
||||
tag_category=tag_category,
|
||||
date_range=date_range
|
||||
)
|
||||
if cached_profile is None:
|
||||
api_logger.info(f"用户 {user_id} 的画像缓存不存在或已过期")
|
||||
return fail(
|
||||
BizCode.RESOURCE_NOT_FOUND,
|
||||
"画像缓存不存在或已过期,请调用 /generate_profile 接口生成新画像",
|
||||
None
|
||||
)
|
||||
|
||||
api_logger.info(f"Retrieved {len(tags)} preference tags for user: {user_id}")
|
||||
return success(data=[tag.model_dump(mode='json') for tag in tags], msg="偏好标签获取成功")
|
||||
# Extract preferences from cache
|
||||
preferences = cached_profile.get("preferences", [])
|
||||
|
||||
# Apply filters (client-side filtering on cached data)
|
||||
filtered_preferences = []
|
||||
for pref in preferences:
|
||||
# Filter by confidence threshold
|
||||
if confidence_threshold is not None and pref.get("confidence_score", 0) < confidence_threshold:
|
||||
continue
|
||||
|
||||
# Filter by category if specified
|
||||
if tag_category and pref.get("category") != tag_category:
|
||||
continue
|
||||
|
||||
# Filter by date range if specified
|
||||
if start_date or end_date:
|
||||
created_at_ts = pref.get("created_at")
|
||||
if created_at_ts:
|
||||
created_at = datetime.fromtimestamp(created_at_ts / 1000)
|
||||
if start_date and created_at < start_date:
|
||||
continue
|
||||
if end_date and created_at > end_date:
|
||||
continue
|
||||
|
||||
filtered_preferences.append(pref)
|
||||
|
||||
api_logger.info(f"Retrieved {len(filtered_preferences)} preference tags for user: {user_id} (from cache)")
|
||||
return success(data=filtered_preferences, msg="偏好标签获取成功(缓存)")
|
||||
|
||||
except Exception as e:
|
||||
return handle_implicit_memory_error(e, "偏好标签获取", user_id)
|
||||
@@ -186,16 +208,16 @@ async def get_dimension_portrait(
|
||||
current_user: User = Depends(get_current_user)
|
||||
) -> ApiResponse:
|
||||
"""
|
||||
Get user's four-dimension personality portrait.
|
||||
Get user's four-dimension personality portrait from cache.
|
||||
|
||||
Args:
|
||||
user_id: Target user ID
|
||||
include_history: Whether to include historical trend data
|
||||
include_history: Whether to include historical trend data (ignored for cached data)
|
||||
|
||||
Returns:
|
||||
Four-dimension personality portrait with scores and evidence
|
||||
Four-dimension personality portrait from cache
|
||||
"""
|
||||
api_logger.info(f"Dimension portrait requested for user: {user_id}")
|
||||
api_logger.info(f"Dimension portrait requested for user: {user_id} (from cache)")
|
||||
|
||||
try:
|
||||
# Validate inputs
|
||||
@@ -204,13 +226,22 @@ async def get_dimension_portrait(
|
||||
# Create service with user-specific config
|
||||
service = ImplicitMemoryService(db=db, end_user_id=user_id)
|
||||
|
||||
portrait = await service.get_dimension_portrait(
|
||||
user_id=user_id,
|
||||
include_history=include_history
|
||||
)
|
||||
# Get cached profile
|
||||
cached_profile = await service.get_cached_profile(end_user_id=user_id, db=db)
|
||||
|
||||
api_logger.info(f"Dimension portrait retrieved for user: {user_id}")
|
||||
return success(data=portrait.model_dump(mode='json'), msg="四维画像获取成功")
|
||||
if cached_profile is None:
|
||||
api_logger.info(f"用户 {user_id} 的画像缓存不存在或已过期")
|
||||
return fail(
|
||||
BizCode.RESOURCE_NOT_FOUND,
|
||||
"画像缓存不存在或已过期,请调用 /generate_profile 接口生成新画像",
|
||||
None
|
||||
)
|
||||
|
||||
# Extract portrait from cache
|
||||
portrait = cached_profile.get("portrait", {})
|
||||
|
||||
api_logger.info(f"Dimension portrait retrieved for user: {user_id} (from cache)")
|
||||
return success(data=portrait, msg="四维画像获取成功(缓存)")
|
||||
|
||||
except Exception as e:
|
||||
return handle_implicit_memory_error(e, "四维画像获取", user_id)
|
||||
@@ -225,16 +256,16 @@ async def get_interest_area_distribution(
|
||||
current_user: User = Depends(get_current_user)
|
||||
) -> ApiResponse:
|
||||
"""
|
||||
Get user's interest area distribution across four areas.
|
||||
Get user's interest area distribution from cache.
|
||||
|
||||
Args:
|
||||
user_id: Target user ID
|
||||
include_trends: Whether to include trend analysis data
|
||||
include_trends: Whether to include trend analysis data (ignored for cached data)
|
||||
|
||||
Returns:
|
||||
Interest area distribution with percentages and evidence
|
||||
Interest area distribution from cache
|
||||
"""
|
||||
api_logger.info(f"Interest area distribution requested for user: {user_id}")
|
||||
api_logger.info(f"Interest area distribution requested for user: {user_id} (from cache)")
|
||||
|
||||
try:
|
||||
# Validate inputs
|
||||
@@ -243,13 +274,22 @@ async def get_interest_area_distribution(
|
||||
# Create service with user-specific config
|
||||
service = ImplicitMemoryService(db=db, end_user_id=user_id)
|
||||
|
||||
distribution = await service.get_interest_area_distribution(
|
||||
user_id=user_id,
|
||||
include_trends=include_trends
|
||||
)
|
||||
# Get cached profile
|
||||
cached_profile = await service.get_cached_profile(end_user_id=user_id, db=db)
|
||||
|
||||
api_logger.info(f"Interest area distribution retrieved for user: {user_id}")
|
||||
return success(data=distribution.model_dump(mode='json'), msg="兴趣领域分布获取成功")
|
||||
if cached_profile is None:
|
||||
api_logger.info(f"用户 {user_id} 的画像缓存不存在或已过期")
|
||||
return fail(
|
||||
BizCode.RESOURCE_NOT_FOUND,
|
||||
"画像缓存不存在或已过期,请调用 /generate_profile 接口生成新画像",
|
||||
None
|
||||
)
|
||||
|
||||
# Extract interest areas from cache
|
||||
interest_areas = cached_profile.get("interest_areas", {})
|
||||
|
||||
api_logger.info(f"Interest area distribution retrieved for user: {user_id} (from cache)")
|
||||
return success(data=interest_areas, msg="兴趣领域分布获取成功(缓存)")
|
||||
|
||||
except Exception as e:
|
||||
return handle_implicit_memory_error(e, "兴趣领域分布获取", user_id)
|
||||
@@ -266,7 +306,7 @@ async def get_behavior_habits(
|
||||
current_user: User = Depends(get_current_user)
|
||||
) -> ApiResponse:
|
||||
"""
|
||||
Get user's behavioral habits with filtering options.
|
||||
Get user's behavioral habits from cache.
|
||||
|
||||
Args:
|
||||
user_id: Target user ID
|
||||
@@ -275,38 +315,117 @@ async def get_behavior_habits(
|
||||
time_period: Filter by time period (current, past)
|
||||
|
||||
Returns:
|
||||
List of behavioral habits matching the filters
|
||||
List of behavioral habits from cache
|
||||
"""
|
||||
api_logger.info(f"Behavior habits requested for user: {user_id}")
|
||||
api_logger.info(f"Behavior habits requested for user: {user_id} (from cache)")
|
||||
|
||||
try:
|
||||
# Validate inputs
|
||||
validate_user_id(user_id)
|
||||
|
||||
# Convert string confidence level to numerical
|
||||
numerical_confidence = None
|
||||
if confidence_level:
|
||||
confidence_mapping = {
|
||||
"high": 85,
|
||||
"medium": 50,
|
||||
"low": 20
|
||||
}
|
||||
numerical_confidence = confidence_mapping.get(confidence_level.lower())
|
||||
|
||||
# Create service with user-specific config
|
||||
service = ImplicitMemoryService(db=db, end_user_id=user_id)
|
||||
|
||||
habits = await service.get_behavior_habits(
|
||||
user_id=user_id,
|
||||
confidence_level=numerical_confidence,
|
||||
frequency_pattern=frequency_pattern,
|
||||
time_period=time_period
|
||||
)
|
||||
# Get cached profile
|
||||
cached_profile = await service.get_cached_profile(end_user_id=user_id, db=db)
|
||||
|
||||
api_logger.info(f"Retrieved {len(habits)} behavior habits for user: {user_id}")
|
||||
return success(data=[habit.model_dump(mode='json') for habit in habits], msg="行为习惯获取成功")
|
||||
if cached_profile is None:
|
||||
api_logger.info(f"用户 {user_id} 的画像缓存不存在或已过期")
|
||||
return fail(
|
||||
BizCode.RESOURCE_NOT_FOUND,
|
||||
"画像缓存不存在或已过期,请调用 /generate_profile 接口生成新画像",
|
||||
None
|
||||
)
|
||||
|
||||
# Extract habits from cache
|
||||
habits = cached_profile.get("habits", [])
|
||||
|
||||
# Apply filters (client-side filtering on cached data)
|
||||
filtered_habits = []
|
||||
for habit in habits:
|
||||
# Filter by confidence level
|
||||
if confidence_level:
|
||||
confidence_mapping = {
|
||||
"high": 85,
|
||||
"medium": 50,
|
||||
"low": 20
|
||||
}
|
||||
numerical_confidence = confidence_mapping.get(confidence_level.lower())
|
||||
if habit.get("confidence_level", 0) < numerical_confidence:
|
||||
continue
|
||||
|
||||
# Filter by frequency pattern
|
||||
if frequency_pattern and habit.get("frequency_pattern") != frequency_pattern:
|
||||
continue
|
||||
|
||||
# Filter by time period
|
||||
if time_period:
|
||||
is_current = habit.get("is_current", True)
|
||||
if time_period.lower() == "current" and not is_current:
|
||||
continue
|
||||
elif time_period.lower() == "past" and is_current:
|
||||
continue
|
||||
|
||||
filtered_habits.append(habit)
|
||||
|
||||
api_logger.info(f"Retrieved {len(filtered_habits)} behavior habits for user: {user_id} (from cache)")
|
||||
return success(data=filtered_habits, msg="行为习惯获取成功(缓存)")
|
||||
|
||||
except Exception as e:
|
||||
return handle_implicit_memory_error(e, "行为习惯获取", user_id)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@router.post("/generate_profile", response_model=ApiResponse)
|
||||
@cur_workspace_access_guard()
|
||||
async def generate_implicit_memory_profile(
|
||||
request: GenerateProfileRequest,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
) -> ApiResponse:
|
||||
"""
|
||||
Generate complete user profile (all 4 modules) and cache it.
|
||||
|
||||
Args:
|
||||
request: Generate profile request with end_user_id
|
||||
db: Database session
|
||||
current_user: Current authenticated user
|
||||
|
||||
Returns:
|
||||
Complete user profile with all modules
|
||||
"""
|
||||
end_user_id = request.end_user_id
|
||||
api_logger.info(f"Generate profile requested for user: {end_user_id}")
|
||||
|
||||
try:
|
||||
# Validate inputs
|
||||
validate_user_id(end_user_id)
|
||||
|
||||
# Create service with user-specific config
|
||||
service = ImplicitMemoryService(db=db, end_user_id=end_user_id)
|
||||
|
||||
# Generate complete profile (calls LLM for all 4 modules)
|
||||
api_logger.info(f"开始生成完整用户画像: user={end_user_id}")
|
||||
profile_data = await service.generate_complete_profile(user_id=end_user_id)
|
||||
|
||||
# Save to cache
|
||||
await service.save_profile_cache(
|
||||
end_user_id=end_user_id,
|
||||
profile_data=profile_data,
|
||||
db=db,
|
||||
expires_hours=168 # 7 days
|
||||
)
|
||||
|
||||
api_logger.info(f"用户画像生成并缓存成功: user={end_user_id}")
|
||||
|
||||
# Add metadata
|
||||
profile_data["end_user_id"] = end_user_id
|
||||
profile_data["cached"] = False
|
||||
|
||||
return success(data=profile_data, msg="用户画像生成成功")
|
||||
|
||||
except Exception as e:
|
||||
api_logger.error(f"生成用户画像失败: user={end_user_id}, error={str(e)}", exc_info=True)
|
||||
return handle_implicit_memory_error(e, "用户画像生成", end_user_id)
|
||||
|
||||
@@ -27,6 +27,8 @@ from .tool_model import (
|
||||
ToolExecution, ToolType, ToolStatus, AuthType, ExecutionStatus
|
||||
)
|
||||
from .memory_perceptual_model import MemoryPerceptualModel
|
||||
from .emotion_suggestions_cache_model import EmotionSuggestionsCache
|
||||
from .implicit_memory_cache_model import ImplicitMemoryCache
|
||||
|
||||
__all__ = [
|
||||
"Tenants",
|
||||
@@ -76,5 +78,7 @@ __all__ = [
|
||||
"ToolStatus",
|
||||
"AuthType",
|
||||
"ExecutionStatus",
|
||||
"MemoryPerceptualModel"
|
||||
"MemoryPerceptualModel",
|
||||
"EmotionSuggestionsCache",
|
||||
"ImplicitMemoryCache"
|
||||
]
|
||||
|
||||
24
api/app/models/emotion_suggestions_cache_model.py
Normal file
24
api/app/models/emotion_suggestions_cache_model.py
Normal file
@@ -0,0 +1,24 @@
|
||||
"""情绪建议缓存模型"""
|
||||
|
||||
import uuid
|
||||
import datetime
|
||||
from sqlalchemy import Column, String, Text, Integer, DateTime, JSON
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from app.db import Base
|
||||
|
||||
|
||||
class EmotionSuggestionsCache(Base):
|
||||
"""情绪建议缓存表
|
||||
|
||||
用于缓存个性化情绪建议,减少 LLM 调用成本,提升响应速度。
|
||||
"""
|
||||
__tablename__ = "emotion_suggestions_cache"
|
||||
|
||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, index=True)
|
||||
end_user_id = Column(String(255), nullable=False, unique=True, index=True, comment="终端用户ID(组ID)")
|
||||
health_summary = Column(Text, nullable=False, comment="健康状态摘要")
|
||||
suggestions = Column(JSON, nullable=False, comment="建议列表(JSON格式)")
|
||||
generated_at = Column(DateTime, nullable=False, default=datetime.datetime.now, comment="生成时间")
|
||||
expires_at = Column(DateTime, nullable=True, comment="过期时间")
|
||||
created_at = Column(DateTime, default=datetime.datetime.now)
|
||||
updated_at = Column(DateTime, default=datetime.datetime.now, onupdate=datetime.datetime.now)
|
||||
27
api/app/models/implicit_memory_cache_model.py
Normal file
27
api/app/models/implicit_memory_cache_model.py
Normal file
@@ -0,0 +1,27 @@
|
||||
"""隐性记忆缓存模型"""
|
||||
|
||||
import uuid
|
||||
import datetime
|
||||
from sqlalchemy import Column, String, Integer, DateTime, JSON
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from app.db import Base
|
||||
|
||||
|
||||
class ImplicitMemoryCache(Base):
|
||||
"""隐性记忆缓存表
|
||||
|
||||
用于缓存用户的完整隐性记忆画像,包括偏好标签、四维画像、兴趣领域和行为习惯。
|
||||
减少 LLM 调用成本,提升响应速度。
|
||||
"""
|
||||
__tablename__ = "implicit_memory_cache"
|
||||
|
||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, index=True)
|
||||
end_user_id = Column(String(255), nullable=False, unique=True, index=True, comment="终端用户ID")
|
||||
preferences = Column(JSON, nullable=False, comment="偏好标签列表(JSON格式)")
|
||||
portrait = Column(JSON, nullable=False, comment="四维画像对象(JSON格式)")
|
||||
interest_areas = Column(JSON, nullable=False, comment="兴趣领域分布对象(JSON格式)")
|
||||
habits = Column(JSON, nullable=False, comment="行为习惯列表(JSON格式)")
|
||||
generated_at = Column(DateTime, nullable=False, default=datetime.datetime.now, comment="生成时间")
|
||||
expires_at = Column(DateTime, nullable=True, comment="过期时间")
|
||||
created_at = Column(DateTime, default=datetime.datetime.now)
|
||||
updated_at = Column(DateTime, default=datetime.datetime.now, onupdate=datetime.datetime.now)
|
||||
@@ -81,7 +81,7 @@ class DataConfigRepository:
|
||||
n.description AS description,
|
||||
n.entity_type AS entity_type,
|
||||
n.name AS name,
|
||||
n.fact_summary AS fact_summary,
|
||||
COALESCE(n.fact_summary, '') AS fact_summary,
|
||||
n.group_id AS group_id,
|
||||
n.apply_id AS apply_id,
|
||||
n.user_id AS user_id,
|
||||
@@ -115,7 +115,7 @@ class DataConfigRepository:
|
||||
description: n.description,
|
||||
entity_type: n.entity_type,
|
||||
name: n.name,
|
||||
fact_summary: n.fact_summary,
|
||||
fact_summary: COALESCE(n.fact_summary, ''),
|
||||
id: n.id
|
||||
} AS sourceNode,
|
||||
{
|
||||
@@ -132,7 +132,7 @@ class DataConfigRepository:
|
||||
description: m.description,
|
||||
entity_type: m.entity_type,
|
||||
name: m.name,
|
||||
fact_summary: m.fact_summary,
|
||||
fact_summary: COALESCE(m.fact_summary, ''),
|
||||
id: m.id
|
||||
} AS targetNode
|
||||
"""
|
||||
|
||||
163
api/app/repositories/emotion_suggestions_cache_repository.py
Normal file
163
api/app/repositories/emotion_suggestions_cache_repository.py
Normal file
@@ -0,0 +1,163 @@
|
||||
"""情绪建议缓存仓储层"""
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import Optional, Dict, Any
|
||||
import datetime
|
||||
|
||||
from app.models.emotion_suggestions_cache_model import EmotionSuggestionsCache
|
||||
from app.core.logging_config import get_db_logger
|
||||
|
||||
# 获取数据库专用日志器
|
||||
db_logger = get_db_logger()
|
||||
|
||||
|
||||
class EmotionSuggestionsCacheRepository:
|
||||
"""情绪建议缓存仓储类"""
|
||||
|
||||
def __init__(self, db: Session):
|
||||
self.db = db
|
||||
|
||||
def get_by_end_user_id(self, end_user_id: str) -> Optional[EmotionSuggestionsCache]:
|
||||
"""根据终端用户ID获取缓存
|
||||
|
||||
Args:
|
||||
end_user_id: 终端用户ID(组ID)
|
||||
|
||||
Returns:
|
||||
缓存记录,如果不存在返回 None
|
||||
"""
|
||||
try:
|
||||
cache = (
|
||||
self.db.query(EmotionSuggestionsCache)
|
||||
.filter(EmotionSuggestionsCache.end_user_id == end_user_id)
|
||||
.first()
|
||||
)
|
||||
if cache:
|
||||
db_logger.info(f"成功获取用户 {end_user_id} 的情绪建议缓存")
|
||||
else:
|
||||
db_logger.info(f"用户 {end_user_id} 的情绪建议缓存不存在")
|
||||
return cache
|
||||
except Exception as e:
|
||||
db_logger.error(f"获取用户 {end_user_id} 的情绪建议缓存失败: {str(e)}")
|
||||
raise
|
||||
|
||||
def create_or_update(
|
||||
self,
|
||||
end_user_id: str,
|
||||
health_summary: str,
|
||||
suggestions: list,
|
||||
expires_hours: int = 24
|
||||
) -> EmotionSuggestionsCache:
|
||||
"""创建或更新缓存
|
||||
|
||||
Args:
|
||||
end_user_id: 终端用户ID(组ID)
|
||||
health_summary: 健康状态摘要
|
||||
suggestions: 建议列表
|
||||
expires_hours: 过期时间(小时),默认24小时
|
||||
|
||||
Returns:
|
||||
缓存记录
|
||||
"""
|
||||
try:
|
||||
# 查找现有记录
|
||||
cache = self.get_by_end_user_id(end_user_id)
|
||||
|
||||
now = datetime.datetime.now()
|
||||
expires_at = now + datetime.timedelta(hours=expires_hours)
|
||||
|
||||
if cache:
|
||||
# 更新现有记录
|
||||
cache.health_summary = health_summary
|
||||
cache.suggestions = suggestions
|
||||
cache.generated_at = now
|
||||
cache.expires_at = expires_at
|
||||
cache.updated_at = now
|
||||
db_logger.info(f"更新用户 {end_user_id} 的情绪建议缓存")
|
||||
else:
|
||||
# 创建新记录
|
||||
cache = EmotionSuggestionsCache(
|
||||
end_user_id=end_user_id,
|
||||
health_summary=health_summary,
|
||||
suggestions=suggestions,
|
||||
generated_at=now,
|
||||
expires_at=expires_at,
|
||||
created_at=now,
|
||||
updated_at=now
|
||||
)
|
||||
self.db.add(cache)
|
||||
db_logger.info(f"创建用户 {end_user_id} 的情绪建议缓存")
|
||||
|
||||
self.db.commit()
|
||||
self.db.refresh(cache)
|
||||
return cache
|
||||
except Exception as e:
|
||||
self.db.rollback()
|
||||
db_logger.error(f"创建或更新用户 {end_user_id} 的情绪建议缓存失败: {str(e)}")
|
||||
raise
|
||||
|
||||
def delete_by_end_user_id(self, end_user_id: str) -> bool:
|
||||
"""删除缓存
|
||||
|
||||
Args:
|
||||
end_user_id: 终端用户ID(组ID)
|
||||
|
||||
Returns:
|
||||
是否删除成功
|
||||
"""
|
||||
try:
|
||||
cache = self.get_by_end_user_id(end_user_id)
|
||||
if cache:
|
||||
self.db.delete(cache)
|
||||
self.db.commit()
|
||||
db_logger.info(f"删除用户 {end_user_id} 的情绪建议缓存")
|
||||
return True
|
||||
return False
|
||||
except Exception as e:
|
||||
self.db.rollback()
|
||||
db_logger.error(f"删除用户 {end_user_id} 的情绪建议缓存失败: {str(e)}")
|
||||
raise
|
||||
|
||||
@staticmethod
|
||||
def is_expired(cache: EmotionSuggestionsCache) -> bool:
|
||||
"""检查缓存是否过期
|
||||
|
||||
Args:
|
||||
cache: 缓存记录
|
||||
|
||||
Returns:
|
||||
是否过期
|
||||
"""
|
||||
if cache.expires_at is None:
|
||||
return False
|
||||
return datetime.datetime.now() > cache.expires_at
|
||||
|
||||
|
||||
# 便捷函数
|
||||
def get_cache_by_end_user_id(db: Session, end_user_id: str) -> Optional[EmotionSuggestionsCache]:
|
||||
"""根据终端用户ID获取缓存"""
|
||||
repo = EmotionSuggestionsCacheRepository(db)
|
||||
return repo.get_by_end_user_id(end_user_id)
|
||||
|
||||
|
||||
def create_or_update_cache(
|
||||
db: Session,
|
||||
end_user_id: str,
|
||||
health_summary: str,
|
||||
suggestions: list,
|
||||
expires_hours: int = 24
|
||||
) -> EmotionSuggestionsCache:
|
||||
"""创建或更新缓存"""
|
||||
repo = EmotionSuggestionsCacheRepository(db)
|
||||
return repo.create_or_update(end_user_id, health_summary, suggestions, expires_hours)
|
||||
|
||||
|
||||
def delete_cache_by_end_user_id(db: Session, end_user_id: str) -> bool:
|
||||
"""删除缓存"""
|
||||
repo = EmotionSuggestionsCacheRepository(db)
|
||||
return repo.delete_by_end_user_id(end_user_id)
|
||||
|
||||
|
||||
def is_cache_expired(cache: EmotionSuggestionsCache) -> bool:
|
||||
"""检查缓存是否过期"""
|
||||
return EmotionSuggestionsCacheRepository.is_expired(cache)
|
||||
175
api/app/repositories/implicit_memory_cache_repository.py
Normal file
175
api/app/repositories/implicit_memory_cache_repository.py
Normal file
@@ -0,0 +1,175 @@
|
||||
"""隐性记忆缓存仓储层"""
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import Optional, Dict, Any
|
||||
import datetime
|
||||
|
||||
from app.models.implicit_memory_cache_model import ImplicitMemoryCache
|
||||
from app.core.logging_config import get_db_logger
|
||||
|
||||
# 获取数据库专用日志器
|
||||
db_logger = get_db_logger()
|
||||
|
||||
|
||||
class ImplicitMemoryCacheRepository:
|
||||
"""隐性记忆缓存仓储类"""
|
||||
|
||||
def __init__(self, db: Session):
|
||||
self.db = db
|
||||
|
||||
def get_by_end_user_id(self, end_user_id: str) -> Optional[ImplicitMemoryCache]:
|
||||
"""根据终端用户ID获取缓存
|
||||
|
||||
Args:
|
||||
end_user_id: 终端用户ID
|
||||
|
||||
Returns:
|
||||
缓存记录,如果不存在返回 None
|
||||
"""
|
||||
try:
|
||||
cache = (
|
||||
self.db.query(ImplicitMemoryCache)
|
||||
.filter(ImplicitMemoryCache.end_user_id == end_user_id)
|
||||
.first()
|
||||
)
|
||||
if cache:
|
||||
db_logger.info(f"成功获取用户 {end_user_id} 的隐性记忆缓存")
|
||||
else:
|
||||
db_logger.info(f"用户 {end_user_id} 的隐性记忆缓存不存在")
|
||||
return cache
|
||||
except Exception as e:
|
||||
db_logger.error(f"获取用户 {end_user_id} 的隐性记忆缓存失败: {str(e)}")
|
||||
raise
|
||||
|
||||
def create_or_update(
|
||||
self,
|
||||
end_user_id: str,
|
||||
preferences: list,
|
||||
portrait: dict,
|
||||
interest_areas: dict,
|
||||
habits: list,
|
||||
expires_hours: int = 168 # 默认7天
|
||||
) -> ImplicitMemoryCache:
|
||||
"""创建或更新缓存
|
||||
|
||||
Args:
|
||||
end_user_id: 终端用户ID
|
||||
preferences: 偏好标签列表
|
||||
portrait: 四维画像对象
|
||||
interest_areas: 兴趣领域分布对象
|
||||
habits: 行为习惯列表
|
||||
expires_hours: 过期时间(小时),默认168小时(7天)
|
||||
|
||||
Returns:
|
||||
缓存记录
|
||||
"""
|
||||
try:
|
||||
# 查找现有记录
|
||||
cache = self.get_by_end_user_id(end_user_id)
|
||||
|
||||
now = datetime.datetime.now()
|
||||
expires_at = now + datetime.timedelta(hours=expires_hours)
|
||||
|
||||
if cache:
|
||||
# 更新现有记录
|
||||
cache.preferences = preferences
|
||||
cache.portrait = portrait
|
||||
cache.interest_areas = interest_areas
|
||||
cache.habits = habits
|
||||
cache.generated_at = now
|
||||
cache.expires_at = expires_at
|
||||
cache.updated_at = now
|
||||
db_logger.info(f"更新用户 {end_user_id} 的隐性记忆缓存")
|
||||
else:
|
||||
# 创建新记录
|
||||
cache = ImplicitMemoryCache(
|
||||
end_user_id=end_user_id,
|
||||
preferences=preferences,
|
||||
portrait=portrait,
|
||||
interest_areas=interest_areas,
|
||||
habits=habits,
|
||||
generated_at=now,
|
||||
expires_at=expires_at,
|
||||
created_at=now,
|
||||
updated_at=now
|
||||
)
|
||||
self.db.add(cache)
|
||||
db_logger.info(f"创建用户 {end_user_id} 的隐性记忆缓存")
|
||||
|
||||
self.db.commit()
|
||||
self.db.refresh(cache)
|
||||
return cache
|
||||
except Exception as e:
|
||||
self.db.rollback()
|
||||
db_logger.error(f"创建或更新用户 {end_user_id} 的隐性记忆缓存失败: {str(e)}")
|
||||
raise
|
||||
|
||||
def delete_by_end_user_id(self, end_user_id: str) -> bool:
|
||||
"""删除缓存
|
||||
|
||||
Args:
|
||||
end_user_id: 终端用户ID
|
||||
|
||||
Returns:
|
||||
是否删除成功
|
||||
"""
|
||||
try:
|
||||
cache = self.get_by_end_user_id(end_user_id)
|
||||
if cache:
|
||||
self.db.delete(cache)
|
||||
self.db.commit()
|
||||
db_logger.info(f"删除用户 {end_user_id} 的隐性记忆缓存")
|
||||
return True
|
||||
return False
|
||||
except Exception as e:
|
||||
self.db.rollback()
|
||||
db_logger.error(f"删除用户 {end_user_id} 的隐性记忆缓存失败: {str(e)}")
|
||||
raise
|
||||
|
||||
@staticmethod
|
||||
def is_expired(cache: ImplicitMemoryCache) -> bool:
|
||||
"""检查缓存是否过期
|
||||
|
||||
Args:
|
||||
cache: 缓存记录
|
||||
|
||||
Returns:
|
||||
是否过期
|
||||
"""
|
||||
if cache.expires_at is None:
|
||||
return False
|
||||
return datetime.datetime.now() > cache.expires_at
|
||||
|
||||
|
||||
# 便捷函数
|
||||
def get_cache_by_end_user_id(db: Session, end_user_id: str) -> Optional[ImplicitMemoryCache]:
|
||||
"""根据终端用户ID获取缓存"""
|
||||
repo = ImplicitMemoryCacheRepository(db)
|
||||
return repo.get_by_end_user_id(end_user_id)
|
||||
|
||||
|
||||
def create_or_update_cache(
|
||||
db: Session,
|
||||
end_user_id: str,
|
||||
preferences: list,
|
||||
portrait: dict,
|
||||
interest_areas: dict,
|
||||
habits: list,
|
||||
expires_hours: int = 168
|
||||
) -> ImplicitMemoryCache:
|
||||
"""创建或更新缓存"""
|
||||
repo = ImplicitMemoryCacheRepository(db)
|
||||
return repo.create_or_update(
|
||||
end_user_id, preferences, portrait, interest_areas, habits, expires_hours
|
||||
)
|
||||
|
||||
|
||||
def delete_cache_by_end_user_id(db: Session, end_user_id: str) -> bool:
|
||||
"""删除缓存"""
|
||||
repo = ImplicitMemoryCacheRepository(db)
|
||||
return repo.delete_by_end_user_id(end_user_id)
|
||||
|
||||
|
||||
def is_cache_expired(cache: ImplicitMemoryCache) -> bool:
|
||||
"""检查缓存是否过期"""
|
||||
return ImplicitMemoryCacheRepository.is_expired(cache)
|
||||
@@ -332,7 +332,7 @@ RETURN e.id AS id,
|
||||
e.description AS description,
|
||||
e.aliases AS aliases,
|
||||
e.name_embedding AS name_embedding,
|
||||
e.fact_summary AS fact_summary,
|
||||
COALESCE(e.fact_summary, '') AS fact_summary,
|
||||
e.connect_strength AS connect_strength,
|
||||
collect(DISTINCT s.id) AS statement_ids,
|
||||
collect(DISTINCT c.id) AS chunk_ids,
|
||||
|
||||
@@ -30,3 +30,9 @@ class EmotionSuggestionsRequest(BaseModel):
|
||||
"""获取个性化情绪建议请求"""
|
||||
group_id: str = Field(..., description="组ID")
|
||||
config_id: Optional[int] = Field(None, description="配置ID(用于指定LLM模型)")
|
||||
|
||||
|
||||
class EmotionGenerateSuggestionsRequest(BaseModel):
|
||||
"""生成个性化情绪建议请求"""
|
||||
group_id: str = Field(..., description="组ID")
|
||||
config_id: Optional[int] = Field(None, description="配置ID(用于指定LLM模型)")
|
||||
|
||||
@@ -262,3 +262,25 @@ InterestCategory = InterestCategoryResponse
|
||||
InterestAreaDistribution = InterestAreaDistributionResponse
|
||||
BehaviorHabit = BehaviorHabitResponse
|
||||
UserProfile = UserProfileResponse
|
||||
|
||||
|
||||
# Cache-related Schemas
|
||||
|
||||
class GenerateProfileRequest(BaseModel):
|
||||
"""生成完整用户画像请求"""
|
||||
end_user_id: str = Field(..., description="终端用户ID")
|
||||
|
||||
|
||||
class CompleteProfileResponse(BaseModel):
|
||||
"""完整用户画像响应(包含所有模块)"""
|
||||
user_id: str
|
||||
preferences: List[PreferenceTagResponse]
|
||||
portrait: DimensionPortraitResponse
|
||||
interest_areas: InterestAreaDistributionResponse
|
||||
habits: List[BehaviorHabitResponse]
|
||||
generated_at: datetime.datetime
|
||||
cached: bool = Field(False, description="是否来自缓存")
|
||||
|
||||
@field_serializer("generated_at", when_used="json")
|
||||
def _serialize_generated_at(self, dt: datetime.datetime):
|
||||
return int(dt.timestamp() * 1000) if dt else None
|
||||
|
||||
@@ -705,3 +705,85 @@ class EmotionAnalyticsService:
|
||||
health_summary=summary,
|
||||
suggestions=suggestions
|
||||
)
|
||||
|
||||
async def get_cached_suggestions(
|
||||
self,
|
||||
end_user_id: str,
|
||||
db: Session,
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""从缓存获取个性化情绪建议
|
||||
|
||||
Args:
|
||||
end_user_id: 宿主ID(用户组ID)
|
||||
db: 数据库会话
|
||||
|
||||
Returns:
|
||||
Dict: 缓存的建议数据,如果不存在或已过期返回 None
|
||||
"""
|
||||
try:
|
||||
from app.repositories.emotion_suggestions_cache_repository import (
|
||||
EmotionSuggestionsCacheRepository,
|
||||
)
|
||||
|
||||
logger.info(f"尝试从缓存获取情绪建议: user={end_user_id}")
|
||||
|
||||
cache_repo = EmotionSuggestionsCacheRepository(db)
|
||||
cache = cache_repo.get_by_end_user_id(end_user_id)
|
||||
|
||||
if cache is None:
|
||||
logger.info(f"用户 {end_user_id} 的建议缓存不存在")
|
||||
return None
|
||||
|
||||
# 检查是否过期
|
||||
if cache_repo.is_expired(cache):
|
||||
logger.info(f"用户 {end_user_id} 的建议缓存已过期")
|
||||
return None
|
||||
|
||||
logger.info(f"成功从缓存获取建议: user={end_user_id}")
|
||||
|
||||
return {
|
||||
"health_summary": cache.health_summary,
|
||||
"suggestions": cache.suggestions,
|
||||
"generated_at": cache.generated_at.isoformat(),
|
||||
"cached": True
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"从缓存获取建议失败: {str(e)}", exc_info=True)
|
||||
return None
|
||||
|
||||
async def save_suggestions_cache(
|
||||
self,
|
||||
end_user_id: str,
|
||||
suggestions_data: Dict[str, Any],
|
||||
db: Session,
|
||||
expires_hours: int = 24
|
||||
) -> None:
|
||||
"""保存建议到缓存
|
||||
|
||||
Args:
|
||||
end_user_id: 宿主ID(用户组ID)
|
||||
suggestions_data: 建议数据
|
||||
db: 数据库会话
|
||||
expires_hours: 过期时间(小时)
|
||||
"""
|
||||
try:
|
||||
from app.repositories.emotion_suggestions_cache_repository import (
|
||||
EmotionSuggestionsCacheRepository,
|
||||
)
|
||||
|
||||
logger.info(f"保存建议到缓存: user={end_user_id}")
|
||||
|
||||
cache_repo = EmotionSuggestionsCacheRepository(db)
|
||||
cache_repo.create_or_update(
|
||||
end_user_id=end_user_id,
|
||||
health_summary=suggestions_data["health_summary"],
|
||||
suggestions=suggestions_data["suggestions"],
|
||||
expires_hours=expires_hours
|
||||
)
|
||||
|
||||
logger.info(f"建议缓存保存成功: user={end_user_id}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"保存建议缓存失败: {str(e)}", exc_info=True)
|
||||
# 不抛出异常,缓存失败不应影响主流程
|
||||
@@ -11,6 +11,22 @@ from app.repositories.home_page_repository import HomePageRepository
|
||||
from app.schemas.home_page_schema import HomeStatistics, WorkspaceInfo
|
||||
|
||||
class HomePageService:
|
||||
|
||||
DEFAULT_RETURN_DATA: Dict[str, Any] = {
|
||||
"message": "",
|
||||
"introduction": {
|
||||
"codeName": "",
|
||||
"releaseDate": "",
|
||||
"upgradePosition": "",
|
||||
"coreUpgrades": []
|
||||
},
|
||||
"introduction_en": {
|
||||
"codeName": "",
|
||||
"releaseDate": "",
|
||||
"upgradePosition": "",
|
||||
"coreUpgrades": []
|
||||
}
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def get_home_statistics(db: Session, tenant_id: UUID) -> HomeStatistics:
|
||||
@@ -82,60 +98,36 @@ class HomePageService:
|
||||
:param version: 系统版本号(如 "0.2.0")
|
||||
:return: 对应版本的详细介绍
|
||||
"""
|
||||
# 1. 定义 JSON 文件路径(使用 Path 处理跨平台路径问题)
|
||||
json_file_path = Path(__file__).parent.parent / "version_info.json"
|
||||
# 转换为绝对路径,便于调试
|
||||
json_abs_path = json_file_path.resolve()
|
||||
# 2. 定义 JSON 文件路径(简化路径处理,保留绝对路径调试特性)
|
||||
json_abs_path = Path(__file__).parent.parent / "version_info.json"
|
||||
json_abs_path = json_abs_path.resolve()
|
||||
|
||||
# 3. 初始化返回结果(深拷贝默认模板,避免修改原常量)
|
||||
from copy import deepcopy
|
||||
result = deepcopy(HomePageService.DEFAULT_RETURN_DATA)
|
||||
|
||||
try:
|
||||
# 2. 读取 JSON 文件
|
||||
# 4. 简化文件存在性判断(合并逻辑,减少分支)
|
||||
if not json_abs_path.exists():
|
||||
return {
|
||||
"message": f"版本介绍文件不存在:{json_abs_path}",
|
||||
"codeName": "",
|
||||
"releaseDate": "",
|
||||
"upgradePosition": "",
|
||||
"coreUpgrades": []
|
||||
}
|
||||
result["message"] = f"版本介绍文件不存在:{json_abs_path}"
|
||||
return result
|
||||
|
||||
# 5. 读取并解析 JSON 文件(简化文件操作流程)
|
||||
with open(json_abs_path, "r", encoding="utf-8") as f:
|
||||
changelogs = json.load(f)
|
||||
|
||||
# 3. 匹配对应版本的介绍,若版本不存在返回默认提示
|
||||
if version not in changelogs:
|
||||
return {
|
||||
"message": f"暂未查询到 {version} 版本的详细介绍",
|
||||
"codeName": "",
|
||||
"releaseDate": "",
|
||||
"upgradePosition": "",
|
||||
"coreUpgrades": []
|
||||
}
|
||||
return changelogs[version]
|
||||
# 6. 简化版本匹配逻辑,直接返回结果或更新提示信息
|
||||
if version in changelogs:
|
||||
return changelogs[version]
|
||||
result["message"] = f"暂未查询到 {version} 版本的详细介绍"
|
||||
return result
|
||||
|
||||
except FileNotFoundError as e:
|
||||
# 处理文件不存在异常
|
||||
return {
|
||||
"message": f"系统内部错误:{str(e)}",
|
||||
"codeName": "",
|
||||
"releaseDate": "",
|
||||
"upgradePosition": "",
|
||||
"coreUpgrades": []
|
||||
}
|
||||
result["message"] = f"系统内部错误:{str(e)}"
|
||||
return result
|
||||
except json.JSONDecodeError:
|
||||
# 处理 JSON 格式错误
|
||||
return {
|
||||
"message": "版本介绍文件格式错误,无法解析 JSON",
|
||||
"codeName": "",
|
||||
"releaseDate": "",
|
||||
"upgradePosition": "",
|
||||
"coreUpgrades": []
|
||||
}
|
||||
result["message"] = "版本介绍文件格式错误,无法解析 JSON"
|
||||
return result
|
||||
except Exception as e:
|
||||
# 处理其他未知异常
|
||||
return {
|
||||
"message": f"加载版本介绍失败:{str(e)}",
|
||||
"codeName": "",
|
||||
"releaseDate": "",
|
||||
"upgradePosition": "",
|
||||
"coreUpgrades": []
|
||||
}
|
||||
result["message"] = f"加载版本介绍失败:{str(e)}"
|
||||
return result
|
||||
@@ -7,6 +7,7 @@ user profiles from memory summaries.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import asyncio
|
||||
from datetime import datetime
|
||||
from typing import List, Optional
|
||||
|
||||
@@ -372,4 +373,129 @@ class ImplicitMemoryService:
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get behavior habits for user {user_id}: {e}")
|
||||
raise
|
||||
|
||||
|
||||
|
||||
async def generate_complete_profile(
|
||||
self,
|
||||
user_id: str
|
||||
) -> dict:
|
||||
"""生成完整的用户画像(包含所有4个模块)
|
||||
|
||||
Args:
|
||||
user_id: 用户ID
|
||||
|
||||
Returns:
|
||||
Dict: 包含所有模块的完整画像数据
|
||||
"""
|
||||
logger.info(f"生成完整用户画像: user={user_id}")
|
||||
|
||||
try:
|
||||
# 并行调用4个分析方法
|
||||
preferences, portrait, interest_areas, habits = await asyncio.gather(
|
||||
self.get_preference_tags(user_id=user_id),
|
||||
self.get_dimension_portrait(user_id=user_id),
|
||||
self.get_interest_area_distribution(user_id=user_id),
|
||||
self.get_behavior_habits(user_id=user_id)
|
||||
)
|
||||
|
||||
# 转换为可序列化的格式
|
||||
profile_data = {
|
||||
"preferences": [tag.model_dump(mode='json') for tag in preferences],
|
||||
"portrait": portrait.model_dump(mode='json'),
|
||||
"interest_areas": interest_areas.model_dump(mode='json'),
|
||||
"habits": [habit.model_dump(mode='json') for habit in habits]
|
||||
}
|
||||
|
||||
logger.info(f"完整用户画像生成完成: user={user_id}")
|
||||
return profile_data
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"生成完整用户画像失败: {str(e)}", exc_info=True)
|
||||
raise
|
||||
|
||||
async def get_cached_profile(
|
||||
self,
|
||||
end_user_id: str,
|
||||
db: Session
|
||||
) -> Optional[dict]:
|
||||
"""从缓存获取完整用户画像
|
||||
|
||||
Args:
|
||||
end_user_id: 终端用户ID
|
||||
db: 数据库会话
|
||||
|
||||
Returns:
|
||||
Dict: 缓存的画像数据,如果不存在或已过期返回 None
|
||||
"""
|
||||
try:
|
||||
from app.repositories.implicit_memory_cache_repository import (
|
||||
ImplicitMemoryCacheRepository,
|
||||
)
|
||||
|
||||
logger.info(f"尝试从缓存获取用户画像: user={end_user_id}")
|
||||
|
||||
cache_repo = ImplicitMemoryCacheRepository(db)
|
||||
cache = cache_repo.get_by_end_user_id(end_user_id)
|
||||
|
||||
if cache is None:
|
||||
logger.info(f"用户 {end_user_id} 的画像缓存不存在")
|
||||
return None
|
||||
|
||||
# 检查是否过期
|
||||
if cache_repo.is_expired(cache):
|
||||
logger.info(f"用户 {end_user_id} 的画像缓存已过期")
|
||||
return None
|
||||
|
||||
logger.info(f"成功从缓存获取用户画像: user={end_user_id}")
|
||||
|
||||
return {
|
||||
"end_user_id": cache.end_user_id,
|
||||
"preferences": cache.preferences,
|
||||
"portrait": cache.portrait,
|
||||
"interest_areas": cache.interest_areas,
|
||||
"habits": cache.habits,
|
||||
"generated_at": cache.generated_at.isoformat(),
|
||||
"cached": True
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"从缓存获取用户画像失败: {str(e)}", exc_info=True)
|
||||
return None
|
||||
|
||||
async def save_profile_cache(
|
||||
self,
|
||||
end_user_id: str,
|
||||
profile_data: dict,
|
||||
db: Session,
|
||||
expires_hours: int = 168 # 默认7天
|
||||
) -> None:
|
||||
"""保存用户画像到缓存
|
||||
|
||||
Args:
|
||||
end_user_id: 终端用户ID
|
||||
profile_data: 画像数据
|
||||
db: 数据库会话
|
||||
expires_hours: 过期时间(小时),默认168小时(7天)
|
||||
"""
|
||||
try:
|
||||
from app.repositories.implicit_memory_cache_repository import (
|
||||
ImplicitMemoryCacheRepository,
|
||||
)
|
||||
|
||||
logger.info(f"保存用户画像到缓存: user={end_user_id}")
|
||||
|
||||
cache_repo = ImplicitMemoryCacheRepository(db)
|
||||
cache_repo.create_or_update(
|
||||
end_user_id=end_user_id,
|
||||
preferences=profile_data["preferences"],
|
||||
portrait=profile_data["portrait"],
|
||||
interest_areas=profile_data["interest_areas"],
|
||||
habits=profile_data["habits"],
|
||||
expires_hours=expires_hours
|
||||
)
|
||||
|
||||
logger.info(f"用户画像缓存保存成功: user={end_user_id}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"保存用户画像缓存失败: {str(e)}", exc_info=True)
|
||||
# 不抛出异常,缓存失败不应影响主流程
|
||||
|
||||
@@ -1,33 +1,68 @@
|
||||
{
|
||||
"v0.2.0": {
|
||||
"codeName": "启知",
|
||||
"releaseDate": "2026-1-16",
|
||||
"upgradePosition": "本次为架构升级,核心目标是把“被动存储”升级为“主动认知”,让系统具备情绪感知、情景理解与类人记忆机制,为后续多智能体协作与专业场景落地奠定底座。",
|
||||
"coreUpgrades": [
|
||||
"记忆详情:拟人记忆——情绪引擎、情景记忆、短期记忆、工作记忆、感知记忆、显性记忆、隐性记忆,并配套类脑遗忘机制,实现从感知→情绪→情景→长期沉淀的完整人类记忆闭环",
|
||||
"可视化工作流:拖拽式节点编排(LLM、知识库、逻辑、工具),业务落地周期由天缩至小时。",
|
||||
"多模态知识处理:PDF、PPT、MP3、MP4 一键解析,时间感知检索准确率 94.3%,问答对数据即插即用。",
|
||||
"Agent集群内置“记忆-知识-工具-审核”四类角色模板,用户一键生成;主控Agent把复杂任务拆为子任务并行分发,再靠情景记忆统一消解冲突、校验一致性,输出完整报告。"
|
||||
]
|
||||
"introduction": {
|
||||
"codeName": "启知",
|
||||
"releaseDate": "2026-1-16",
|
||||
"upgradePosition": "本次为架构升级,核心目标是把\"被动存储\"升级为\"主动认知\",让系统具备情绪感知、情景理解与类人记忆机制,为后续多智能体协作与专业场景落地奠定底座。",
|
||||
"coreUpgrades": [
|
||||
"记忆详情:拟人记忆——情绪引擎、情景记忆、短期记忆、工作记忆、感知记忆、显性记忆、隐性记忆,并配套类脑遗忘机制,实现从感知→情绪→情景→长期沉淀的完整人类记忆闭环",
|
||||
"可视化工作流:拖拽式节点编排(LLM、知识库、逻辑、工具),业务落地周期由天缩至小时。",
|
||||
"多模态知识处理:PDF、PPT、MP3、MP4 一键解析,时间感知检索准确率 94.3%,问答对数据即插即用。",
|
||||
"Agent集群内置\"记忆-知识-工具-审核\"四类角色模板,用户一键生成;主控Agent把复杂任务拆为子任务并行分发,再靠情景记忆统一消解冲突、校验一致性,输出完整报告。"
|
||||
]
|
||||
},
|
||||
"introduction_en": {
|
||||
"codeName": "Qizhi",
|
||||
"releaseDate": "2026-1-16",
|
||||
"upgradePosition": "This release marks a foundational upgrade to the system’s cognitive architecture. The core objective is to evolve the platform from passive information storage into active cognitive intelligence—enabling emotional awareness, situational understanding, and human-like memory mechanisms. This upgrade lays the groundwork for future multi-agent collaboration and domain-specific, production-grade AI applications.",
|
||||
"coreUpgrades": [
|
||||
"Human-Like Memory Architecture: A comprehensive, human-inspired memory system is introduced, encompassing emotional processing, situational memory, short-term and working memory, perceptual memory, as well as explicit and implicit memory. Combined with brain-inspired forgetting mechanisms, the system now supports a complete cognitive loop—from perception → emotion → context → long-term consolidation, closely mirroring human memory formation.",
|
||||
"Visual Workflow Orchestration: A fully visual, drag-and-drop workflow enables modular composition of LLMs, knowledge bases, logic, and tools. This dramatically reduces the time required to move from experimentation to production—from days to hours.",
|
||||
"Multimodal Knowledge Processing: The system now supports one-click parsing and ingestion of PDF, PPT, MP3, and MP4 content. With time-aware retrieval accuracy reaching 94.3%, structured Q&A data becomes instantly usable for downstream reasoning and generation.",
|
||||
"Built-in Agent Clusters: Predefined role templates across four categories—Memory, Knowledge, Tools, and Review—can be generated with a single click. A Coordinator Agent decomposes complex tasks into parallel subtasks, while situational memory is used to resolve conflicts, validate consistency, and synthesize outputs into a coherent, end-to-end report."
|
||||
]
|
||||
}
|
||||
},
|
||||
"v0.1.0": {
|
||||
"codeName": "初心",
|
||||
"releaseDate": "2025-12-01",
|
||||
"upgradePosition": "这是一款专注于管理和利用AI记忆的工具,支持RAG和知识图谱两种主流存储方式,旨在为AI应用提供持久化、结构化的“记忆”能力。",
|
||||
"coreUpgrades": [
|
||||
"记忆空间:用户可以创建独立的空间来隔离不同记忆,并灵活选择存储方式。",
|
||||
"记忆配置:简化了配置流程,内置自动提取关键信息的“记忆萃取”和管理生命周期的\"遗忘\"引擎。",
|
||||
"知识检索:提供语义、分词和混合三种检索模式,并支持多种参数微调和结果重排序,以提升召回效果。",
|
||||
"全局管理:支持统一设置默认检索参数,并可一键应用到所有知识库。",
|
||||
"测试与调试:内置\"召回测试\"功能,方便用户实时验证检索效果并调整参数,支持通过分享码与他人协作。",
|
||||
"记忆洞察:可查看详细的对话记录、用户画像和分析报告,帮助理解AI的\"记忆\"内容。",
|
||||
"集成与管理:提供API Key用于系统集成,并包含基本的用户管理功能。",
|
||||
"界面与体验:采用现代化的卡片式布局和渐变色设计,注重交互的流畅性和视觉美感。",
|
||||
"起步与使用:文档中提供了清晰的基础使用流程,引导用户从创建空间、配置记忆到测试检索快速上手。",
|
||||
"版本说明与限制: 记忆熊 v0.1.0 版本\"初心\"囊括智能记忆管理的核心思路和基础能力,为后续开发奠定了基础。",
|
||||
"文档资源:用户手册、API文档、FAQ",
|
||||
"问题反馈:GitHub Issues、邮件支持",
|
||||
"致谢:感谢所有参与测试和提供反馈的用户!"
|
||||
]
|
||||
"introduction": {
|
||||
"codeName": "初心",
|
||||
"releaseDate": "2025-12-01",
|
||||
"upgradePosition": "这是一款专注于管理和利用AI记忆的工具,支持RAG和知识图谱两种主流存储方式,旨在为AI应用提供持久化、结构化的\"记忆\"能力。",
|
||||
"coreUpgrades": [
|
||||
"记忆空间:用户可以创建独立的空间来隔离不同记忆,并灵活选择存储方式。",
|
||||
"记忆配置:简化了配置流程,内置自动提取关键信息的\"记忆萃取\"和管理生命周期的\"遗忘\"引擎。",
|
||||
"知识检索:提供语义、分词和混合三种检索模式,并支持多种参数微调和结果重排序,以提升召回效果。",
|
||||
"全局管理:支持统一设置默认检索参数,并可一键应用到所有知识库。",
|
||||
"测试与调试:内置\"召回测试\"功能,方便用户实时验证检索效果并调整参数,支持通过分享码与他人协作。",
|
||||
"记忆洞察:可查看详细的对话记录、用户画像和分析报告,帮助理解AI的\"记忆\"内容。",
|
||||
"集成与管理:提供API Key用于系统集成,并包含基本的用户管理功能。",
|
||||
"界面与体验:采用现代化的卡片式布局和渐变色设计,注重交互的流畅性和视觉美感。",
|
||||
"起步与使用:文档中提供了清晰的基础使用流程,引导用户从创建空间、配置记忆到测试检索快速上手。",
|
||||
"版本说明与限制: 记忆熊 v0.1.0 版本\"初心\"囊括智能记忆管理的核心思路和基础能力,为后续开发奠定了基础。",
|
||||
"文档资源:用户手册、API文档、FAQ",
|
||||
"问题反馈:GitHub Issues、邮件支持",
|
||||
"致谢:感谢所有参与测试和提供反馈的用户!"
|
||||
]
|
||||
},
|
||||
"introduction_en": {
|
||||
"codeName": "Original Intent",
|
||||
"releaseDate": "2025-12-01",
|
||||
"upgradePosition": "A tool focused on managing and utilizing AI memory, supporting both RAG and knowledge graph storage methods, aiming to provide persistent and structured 'memory' capabilities for AI applications.",
|
||||
"coreUpgrades": [
|
||||
"Memory Space: Users can create independent spaces to isolate different memories and flexibly choose storage methods.",
|
||||
"Memory Configuration: Simplified configuration process with built-in 'memory extraction' for automatic key information extraction and 'forgetting' engine for lifecycle management.",
|
||||
"Knowledge Retrieval: Provides semantic, tokenization, and hybrid retrieval modes with various parameter tuning and result reranking to improve recall.",
|
||||
"Global Management: Supports unified default retrieval parameter settings with one-click application to all knowledge bases.",
|
||||
"Testing & Debugging: Built-in 'recall testing' for real-time verification of retrieval effects and parameter adjustment, with sharing code support for collaboration.",
|
||||
"Memory Insights: View detailed conversation records, user profiles, and analysis reports to understand AI 'memory' content.",
|
||||
"Integration & Management: Provides API Key for system integration with basic user management features.",
|
||||
"Interface & Experience: Modern card-based layout with gradient design, focusing on interaction fluidity and visual aesthetics.",
|
||||
"Getting Started: Documentation provides clear basic usage flow, guiding users from creating spaces, configuring memory to testing retrieval.",
|
||||
"Version Notes: MemoryBear v0.1.0 'Original Intent' encompasses core concepts and basic capabilities of intelligent memory management, laying foundation for future development.",
|
||||
"Documentation: User Manual, API Documentation, FAQ",
|
||||
"Feedback: GitHub Issues, Email Support",
|
||||
"Acknowledgments: Thanks to all users who participated in testing and provided feedback!"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
62
api/migrations/versions/1fd7d0e703b3_20260116.py
Normal file
62
api/migrations/versions/1fd7d0e703b3_20260116.py
Normal file
@@ -0,0 +1,62 @@
|
||||
"""20260116
|
||||
|
||||
Revision ID: 1fd7d0e703b3
|
||||
Revises: 9ab9b6393f32
|
||||
Create Date: 2026-01-16 13:17:37.060026
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = '1fd7d0e703b3'
|
||||
down_revision: Union[str, None] = '9ab9b6393f32'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('emotion_suggestions_cache',
|
||||
sa.Column('id', sa.UUID(), nullable=False),
|
||||
sa.Column('end_user_id', sa.String(length=255), nullable=False, comment='终端用户ID(组ID)'),
|
||||
sa.Column('health_summary', sa.Text(), nullable=False, comment='健康状态摘要'),
|
||||
sa.Column('suggestions', sa.JSON(), nullable=False, comment='建议列表(JSON格式)'),
|
||||
sa.Column('generated_at', sa.DateTime(), nullable=False, comment='生成时间'),
|
||||
sa.Column('expires_at', sa.DateTime(), nullable=True, comment='过期时间'),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index(op.f('ix_emotion_suggestions_cache_end_user_id'), 'emotion_suggestions_cache', ['end_user_id'], unique=True)
|
||||
op.create_index(op.f('ix_emotion_suggestions_cache_id'), 'emotion_suggestions_cache', ['id'], unique=False)
|
||||
op.create_table('implicit_memory_cache',
|
||||
sa.Column('id', sa.UUID(), nullable=False),
|
||||
sa.Column('end_user_id', sa.String(length=255), nullable=False, comment='终端用户ID'),
|
||||
sa.Column('preferences', sa.JSON(), nullable=False, comment='偏好标签列表(JSON格式)'),
|
||||
sa.Column('portrait', sa.JSON(), nullable=False, comment='四维画像对象(JSON格式)'),
|
||||
sa.Column('interest_areas', sa.JSON(), nullable=False, comment='兴趣领域分布对象(JSON格式)'),
|
||||
sa.Column('habits', sa.JSON(), nullable=False, comment='行为习惯列表(JSON格式)'),
|
||||
sa.Column('generated_at', sa.DateTime(), nullable=False, comment='生成时间'),
|
||||
sa.Column('expires_at', sa.DateTime(), nullable=True, comment='过期时间'),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index(op.f('ix_implicit_memory_cache_end_user_id'), 'implicit_memory_cache', ['end_user_id'], unique=True)
|
||||
op.create_index(op.f('ix_implicit_memory_cache_id'), 'implicit_memory_cache', ['id'], unique=False)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_index(op.f('ix_implicit_memory_cache_id'), table_name='implicit_memory_cache')
|
||||
op.drop_index(op.f('ix_implicit_memory_cache_end_user_id'), table_name='implicit_memory_cache')
|
||||
op.drop_table('implicit_memory_cache')
|
||||
op.drop_index(op.f('ix_emotion_suggestions_cache_id'), table_name='emotion_suggestions_cache')
|
||||
op.drop_index(op.f('ix_emotion_suggestions_cache_end_user_id'), table_name='emotion_suggestions_cache')
|
||||
op.drop_table('emotion_suggestions_cache')
|
||||
# ### end Alembic commands ###
|
||||
@@ -31,6 +31,12 @@ export interface versionResponse{
|
||||
coreUpgrades: string[];
|
||||
codeName: string;
|
||||
};
|
||||
introduction_en?: {
|
||||
releaseDate: string;
|
||||
upgradePosition: string;
|
||||
coreUpgrades: string[];
|
||||
codeName: string;
|
||||
};
|
||||
}
|
||||
// 首页数据统计
|
||||
export const getDashboardData = `/home-page/workspaces`
|
||||
|
||||
@@ -16,6 +16,7 @@ interface TableComponentProps extends Omit<TableProps, 'pagination'> {
|
||||
rowSelection?: TableProps['rowSelection'];
|
||||
initialData?: Record<string, unknown>[];
|
||||
emptySize?: number;
|
||||
emptyText?: string;
|
||||
isScroll?: boolean;
|
||||
scrollX?: number | string | true; // 支持自定义横向滚动宽度
|
||||
scrollY?: number | string; // 支持自定义纵向滚动高度
|
||||
@@ -46,6 +47,7 @@ const TableComponent = forwardRef<TableRef, TableComponentProps>(({
|
||||
rowSelection,
|
||||
initialData,
|
||||
emptySize = 160,
|
||||
emptyText,
|
||||
isScroll = false,
|
||||
scrollX,
|
||||
scrollY,
|
||||
@@ -169,7 +171,7 @@ const TableComponent = forwardRef<TableRef, TableComponentProps>(({
|
||||
rowSelection={rowSelection}
|
||||
rowClassName={styles.row}
|
||||
className={styles.table}
|
||||
locale={{ emptyText: <Empty size={emptySize} /> }}
|
||||
locale={{ emptyText: <Empty size={emptySize} subTitle={emptyText} /> }}
|
||||
scroll={getScrollConfig()}
|
||||
tableLayout="auto"
|
||||
/>
|
||||
|
||||
@@ -7,7 +7,7 @@ export const en = {
|
||||
viewGuide: 'View Guide',
|
||||
watchVideo: 'Watch Video',
|
||||
viewDetails: 'View Details',
|
||||
changeLog: 'Change Log',
|
||||
changeLog: 'Changelog',
|
||||
latestUpdate: 'Latest Update',
|
||||
appCount: 'Number of Spaces',
|
||||
userCount: 'Number of Users',
|
||||
@@ -81,7 +81,7 @@ export const en = {
|
||||
userManagement: 'User Management',
|
||||
roleManagement: 'Role Management',
|
||||
systemManage: 'System Manage',
|
||||
applicationManagement: 'Application Manage',
|
||||
applicationManagement: 'Application',
|
||||
productManagement: 'Product Management',
|
||||
knowledgeManagement: 'KnowledgeBase ',
|
||||
knowledgeBase: 'Datasets Management',
|
||||
@@ -104,7 +104,7 @@ export const en = {
|
||||
knowledgeCreateDataset: 'Create Dataset',
|
||||
knowledgeDocumentDetails: 'Document Details',
|
||||
userMemoryDetail: 'UserMemory Detail',
|
||||
apiKeyManagement: 'API KEY Management',
|
||||
apiKeyManagement: 'API Key Management',
|
||||
toolManagement: 'Tool Management',
|
||||
emotionEngine: 'Emotion Engine',
|
||||
statementDetail: 'Emotion Memory',
|
||||
@@ -115,27 +115,27 @@ export const en = {
|
||||
spaceConfig: 'Space Configuration'
|
||||
},
|
||||
dashboard: {
|
||||
total_models: 'Total number of available models',
|
||||
total_spaces: 'Number of active spaces',
|
||||
total_users: 'Total number of users',
|
||||
total_running_apps: 'Number of application runs',
|
||||
desc_models: 'Contains {{ account }} LLMs and {{ nums }} Embeddings',
|
||||
desc_spaces: 'more than last week',
|
||||
total_models: 'Available Models',
|
||||
total_spaces: 'Active Spaces',
|
||||
total_users: 'Users',
|
||||
total_running_apps: 'Application Runs',
|
||||
desc_models: 'Includes {{ account }} LLM and {{ nums }} embedding',
|
||||
desc_spaces: 'compared to last week',
|
||||
desc_users: 'New additions this week',
|
||||
desc_running_apps: "Today's success rate",
|
||||
totalMemoryCapacity: 'Total Memory Capacity',
|
||||
totalMemoryCapacity: 'Total Stored Memories',
|
||||
userMemory: 'User Memory',
|
||||
knowledgeBaseCount: 'Knowledge Base Count',
|
||||
apiCallCount: 'API Call Count',
|
||||
knowledgeBaseCount: 'Knowledge Bases',
|
||||
apiCallCount: 'API Calls',
|
||||
comparedToYesterday: 'compared to yesterday',
|
||||
thisWeek: 'this week',
|
||||
thisDay: 'day on day',
|
||||
failureRate: 'Failure Rate',
|
||||
application: 'Application Count',
|
||||
application: 'Applications',
|
||||
total_num: 'Total Memory Capacity',
|
||||
|
||||
lastDays: 'last {{days}} days',
|
||||
lastHalfYear: 'last half year',
|
||||
lastHalfYear: 'last 6 months',
|
||||
lastYear: 'last year',
|
||||
|
||||
enterpriseMemory: 'Enterprise Memory',
|
||||
@@ -201,13 +201,13 @@ export const en = {
|
||||
tagEmpty: 'There are no tag records at the moment~',
|
||||
|
||||
chunk_count: 'Data Chunk',
|
||||
chunk_count_desc: 'Current Processing {{count}} Data Chunks',
|
||||
chunk_count_desc: '{{count}} data chunks currently being processed',
|
||||
statements_count: 'Statements',
|
||||
statements_count_desc: 'Manage {{count}} knowledge statements',
|
||||
statements_count_desc: '{{count}} knowledge statements',
|
||||
triplet_count: 'Entity Relation Extraction',
|
||||
triplet_count_desc: 'Build {{entities_count}} entity nodes and {{relations_count}} relation connections',
|
||||
triplet_count_desc: '{{entities_count}} entity nodes and {{relations_count}} relationships built',
|
||||
temporal_count: 'Time Extraction',
|
||||
temporal_count_desc: 'Record {{count}} time series information',
|
||||
temporal_count_desc: '{{count}} time series records',
|
||||
|
||||
dialogue: 'Dialogue',
|
||||
chunk: 'Chunk',
|
||||
@@ -947,20 +947,20 @@ export const en = {
|
||||
apiEndpoint: 'API Endpoint',
|
||||
apiKey: 'API-Key',
|
||||
returnToApplicationList: 'Return to application list',
|
||||
arrangement: 'Arrangement',
|
||||
arrangement: 'Configuration',
|
||||
api: 'API',
|
||||
release: 'Release',
|
||||
promptConfiguration: 'Prompt Configuration',
|
||||
configurationDesc: 'Define the role, capabilities, and behavioral guidelines of the Agent',
|
||||
aiPrompt: 'AI Prompt',
|
||||
promptPlaceholder: 'You are a professional AI assistant, and your responsibility is to help users solve problems.',
|
||||
knowledgeBaseAssociation: 'Knowledge base association',
|
||||
associatedKnowledgeBase: 'Associated Knowledge Base',
|
||||
knowledgeBaseAssociation: 'Knowledge Base Association',
|
||||
associatedKnowledgeBase: 'Associated Knowledge Bases',
|
||||
addKnowledgeBase: 'Add Knowledge Base',
|
||||
knowledgeEmpty: 'There is currently no knowledge base association',
|
||||
knowledgeEmpty: 'No knowledge base associated.',
|
||||
memoryConfiguration: 'Memory Configuration',
|
||||
dialogueHistoricalMemory: 'Dialogue Historical Memory',
|
||||
dialogueHistoricalMemoryDesc: 'After activation, the memory content in the memory management can be selected',
|
||||
dialogueHistoricalMemory: 'Conversation History Memory',
|
||||
dialogueHistoricalMemoryDesc: 'Enable this to select memory content from memory management.',
|
||||
toolConfiguration: 'Tool Configuration',
|
||||
webSearch: 'Web Search',
|
||||
webSearchDesc: 'Allow the Agent to access the Internet for real-time search',
|
||||
@@ -975,14 +975,14 @@ export const en = {
|
||||
VariableManagementDesc: 'Configure the available variables for the Agent',
|
||||
addVariables: 'Add Variables',
|
||||
variablesEmpty: 'There are currently no variables available',
|
||||
debuggingEmpty: 'Currently, there are no debugging models available',
|
||||
debuggingEmptyDesc: 'Click the "+" button on the page, select and add the model you need',
|
||||
debuggingAndPreview: 'Debugging and Preview',
|
||||
debuggingEmpty: 'No models available for debugging.',
|
||||
debuggingEmptyDesc: 'Click the “+” button to select and add a model.',
|
||||
debuggingAndPreview: 'Preview and Debugging',
|
||||
addModel: 'Add Model',
|
||||
fieldName: 'Field Name',
|
||||
Optional: 'Optional',
|
||||
chatEmpty: 'Send message to start testing',
|
||||
chatPlaceholder: 'Start chatting with the robot…',
|
||||
chatEmpty: 'Send a message to start testing',
|
||||
chatPlaceholder: 'Start a conversation...',
|
||||
|
||||
endpointConfiguration: 'Endpoint Configuration',
|
||||
authenticationMethod: 'Authentication Method',
|
||||
@@ -1009,22 +1009,22 @@ export const en = {
|
||||
whitelistIPDesc: 'supports CIDR',
|
||||
publicAPIDocumentation: 'Public API Documentation',
|
||||
|
||||
versionList: 'Version List',
|
||||
versionListDesc: 'All release records and status',
|
||||
versionList: 'Versions',
|
||||
versionListDesc: 'Release history and status',
|
||||
current: 'Current',
|
||||
rolledBack: 'rolled back',
|
||||
history: 'history',
|
||||
VersionInformation: 'Version Information',
|
||||
publishedOn: 'Published On',
|
||||
publisher: 'Publisher',
|
||||
DetailsOfVersion: 'Details of {{version}} version',
|
||||
publishedOn: 'Published on',
|
||||
publisher: 'Published by',
|
||||
DetailsOfVersion: 'Version Details: {{version}}',
|
||||
exportDSLFile: 'Export DSL file',
|
||||
willRollToThisVersion: 'Will roll to this version',
|
||||
share: 'Share',
|
||||
lastUpdateTime: 'Last Update Time',
|
||||
editor: 'Editor',
|
||||
releaseTime: 'Release Time',
|
||||
changeLog: 'Change Log',
|
||||
changeLog: 'Changelog',
|
||||
fix: 'Fix',
|
||||
optimization: 'Optimization',
|
||||
new: 'New',
|
||||
@@ -1129,7 +1129,7 @@ export const en = {
|
||||
ReplyException: 'Reply exception',
|
||||
|
||||
endpointConfigurationSubTitle: 'Configure API access address and supported HTTP methods',
|
||||
apiKeys: 'API Keys Management',
|
||||
apiKeys: 'API Key Management',
|
||||
apiKeySubTitle: 'Manage API keys, view usage and traffic statistics for each key',
|
||||
addApiKey: 'Add New API Key',
|
||||
apiKeyName: 'Key Name',
|
||||
@@ -1198,7 +1198,7 @@ export const en = {
|
||||
drag: 'Drag and drop to move nodes',
|
||||
zoom: 'Scroll zoom view',
|
||||
memoryDetailEmpty: 'Please select a memory node',
|
||||
memoryDetailEmptyDesc: 'Click on the node in the left graph to view the details of entity memory',
|
||||
memoryDetailEmptyDesc: 'Click a node in the graph to view memory details.',
|
||||
|
||||
totalNumOfMemories: 'Total Number of Memories',
|
||||
footprintCity: 'Footprint City',
|
||||
@@ -1216,20 +1216,20 @@ export const en = {
|
||||
editConfig: 'Edit Config',
|
||||
chooseModel: 'Choose Model',
|
||||
|
||||
nodeStatistics: 'Memory Classification',
|
||||
nodeStatistics: 'Memory Categories',
|
||||
total: 'Total',
|
||||
|
||||
PERCEPTUAL_MEMORY: 'Perceptual Memory',
|
||||
WORKING_MEMORY: 'Working Memory',
|
||||
SHORT_TERM_MEMORY: 'Shot Term Memory',
|
||||
LONG_TERM_MEMORY: 'Long Term Memory',
|
||||
SHORT_TERM_MEMORY: 'Short-Term Memory',
|
||||
LONG_TERM_MEMORY: 'Long-Term Memory',
|
||||
EXPLICIT_MEMORY: 'Explicit Memory',
|
||||
IMPLICIT_MEMORY: 'Implicit Memory',
|
||||
EMOTIONAL_MEMORY: 'Emotional Memory',
|
||||
EPISODIC_MEMORY: 'Episodic Memory',
|
||||
FORGET_MEMORY: 'Forget Memory',
|
||||
|
||||
endUserProfile: 'Core Profile',
|
||||
endUserProfile: 'Profile',
|
||||
editEndUserProfile: 'Edit',
|
||||
other_name: 'Name',
|
||||
position: 'Position',
|
||||
@@ -1239,10 +1239,10 @@ export const en = {
|
||||
hire_date: 'Hire Date',
|
||||
memoryContent: 'Memory Content',
|
||||
created_at: 'Created At',
|
||||
updated_at: 'Updated At',
|
||||
updated_at: 'Last Updated',
|
||||
fullScreen: 'Full Screen',
|
||||
|
||||
memoryWindow: "{{name}}'s Window of Memory",
|
||||
memoryWindow: "{{name}}'s Memory Overview",
|
||||
memory_insight: 'Overall Overview',
|
||||
key_findings: 'Key Findings',
|
||||
behavior_pattern: 'Behavior Pattern',
|
||||
@@ -1315,7 +1315,7 @@ export const en = {
|
||||
tTypeStrict: 'Type Matching Threshold',
|
||||
tOverall: 'Comprehensive Matching Threshold',
|
||||
|
||||
arrangementLayerModule: 'Arrangement Layer Module',
|
||||
arrangementLayerModule: 'Configuration Layer Module',
|
||||
queryMode: 'Query Mode',
|
||||
queryModeSubTitle: 'Control whether to activate deeper search functions',
|
||||
deepRetrieval: 'Deep Retrieval',
|
||||
@@ -1452,12 +1452,12 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re
|
||||
deduplication_desc: 'Deduplication and disambiguation completed, {{count}} unique entities in total'
|
||||
},
|
||||
memoryConversation: {
|
||||
searchPlaceholder: 'Enter user ID...',
|
||||
chatEmpty:'Is there anything I can help you with?',
|
||||
searchPlaceholder: 'Input user ID...',
|
||||
userID: 'User ID',
|
||||
testMemoryConversation: 'Test Memory Conversation',
|
||||
conversationContent: 'Conversation Content',
|
||||
conversationContentEmpty: 'There is currently no conversation content available',
|
||||
conversationContentEmpty: 'No conversation content available.',
|
||||
memoryConversationAnalysis: 'Memory Conversation Analysis',
|
||||
memoryFunction: 'Memory Function',
|
||||
onlineSearch: 'Online Search',
|
||||
@@ -1477,8 +1477,8 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re
|
||||
quickReply: 'Quick Reply',
|
||||
web_search: 'Online search',
|
||||
memory: 'Memory',
|
||||
memoryConversationAnalysisEmpty: 'There is currently no dialogue analysis content available',
|
||||
memoryConversationAnalysisEmptySubTitle: 'After entering your user ID, click on "Test Memory" to view the conversation memory',
|
||||
memoryConversationAnalysisEmpty: 'No conversation analysis available.',
|
||||
memoryConversationAnalysisEmptySubTitle: 'Conversation analysis will appear here.',
|
||||
},
|
||||
login: {
|
||||
title: 'Red Bear Memory Science',
|
||||
@@ -1531,7 +1531,7 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re
|
||||
notFoundDesc: 'Try returning to the previous page',
|
||||
noPermission: 'Oh, this is an exclusive domain for permissions',
|
||||
noPermissionDesc: ' Please contact the administrator to grant permission',
|
||||
tableEmpty: 'There are currently no data',
|
||||
tableEmpty: 'No data available.',
|
||||
loadingEmpty: 'The content is loading…',
|
||||
loadingEmptyDesc: 'Your content is on its way by rocket! It will soon land on your screen'
|
||||
},
|
||||
@@ -1561,12 +1561,12 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re
|
||||
inactive: 'Expired'
|
||||
},
|
||||
tool: {
|
||||
mcp: 'MCP Service',
|
||||
mcp: 'MCP Services',
|
||||
inner: 'Built-in Tools',
|
||||
custom: 'Custom Tools',
|
||||
mcpSearchPlaceholder: 'Search MCP services...',
|
||||
innerSearchPlaceholder: 'Search tools...',
|
||||
customSearchPlaceholder: 'Search custom tools...',
|
||||
mcpSearchPlaceholder: 'Search MCP Services...',
|
||||
innerSearchPlaceholder: 'Search Tools...',
|
||||
customSearchPlaceholder: 'Search Custom Tools...',
|
||||
addService: 'Add MCP Service',
|
||||
editService: 'Edit MCP Service',
|
||||
addServiceSuccess: 'Service added successfully',
|
||||
@@ -1599,7 +1599,7 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re
|
||||
requestHeader: 'Request Headers',
|
||||
config: 'Configuration',
|
||||
auth_type: 'Authentication Type',
|
||||
none: 'No Authentication',
|
||||
none: 'None',
|
||||
api_key: 'API Key',
|
||||
basic_auth: 'Basic Auth',
|
||||
bearer_token: 'Bearer Token',
|
||||
@@ -1727,9 +1727,11 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re
|
||||
created_at: 'Created At',
|
||||
headerName: 'Header Name',
|
||||
null: 'None',
|
||||
tagDesc: 'Multiple tags separated by commas',
|
||||
tagDesc: 'Enter tags (comma-separated)',
|
||||
availableTools: 'Available Tools',
|
||||
name: 'Name',
|
||||
enterNamePlaceholder: 'Please enter a name',
|
||||
toolEmpty: 'No tools detected.',
|
||||
desc: 'Description',
|
||||
method: 'Method',
|
||||
path: 'Path',
|
||||
@@ -2150,34 +2152,34 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re
|
||||
personal: {
|
||||
type: 'Personal',
|
||||
label: 'Current Package',
|
||||
typeDesc: 'For individuals',
|
||||
typeDesc: 'For Individuals',
|
||||
solution: "A person's second brain, capable of storing up to 2000 memories.",
|
||||
targetAudience: 'individual users, students, and first-time users',
|
||||
priceDesc: '/Forever free',
|
||||
priceDesc: '/Free forever',
|
||||
supportServices: 'Community Forum + Email Support',
|
||||
},
|
||||
team: {
|
||||
type: 'Team',
|
||||
label: 'Small Team',
|
||||
typeDesc: 'Small Team Version',
|
||||
typeDesc: 'For Small Teams',
|
||||
solution: "Enable every team to build a shared second brain in seconds.",
|
||||
targetAudience: 'Small teams, early-stage startups, and small projects.',
|
||||
priceDesc: '/Month',
|
||||
priceDesc: '/month',
|
||||
supportServices: 'Standard customer service support',
|
||||
},
|
||||
biz: {
|
||||
type: 'Biz+',
|
||||
label: 'Most Popular',
|
||||
typeDesc: 'Enterprise Growth Edition',
|
||||
typeDesc: 'Enterprise Growth Plan',
|
||||
solution: "Scale your organization with a powerful, enterprise-ready second-brain system.",
|
||||
targetAudience: 'Growing teams, startups, and SMBs requiring advanced memory capabilities.',
|
||||
priceDesc: '/Month/workspace',
|
||||
priceDesc: '/per workspace / month',
|
||||
supportServices: 'Priority customer service support',
|
||||
},
|
||||
commerce: {
|
||||
type: 'Commerce',
|
||||
label: 'Commercial OEM',
|
||||
typeDesc: 'Commercial OEM version',
|
||||
typeDesc: 'Commercial OEM plan',
|
||||
solution: "Seamlessly integrate advanced memory capabilities into your SaaS or enterprise product.",
|
||||
targetAudience: 'Large enterprises, SaaS vendors, and system integrators requiring fully customizable and secure deployment.',
|
||||
priceDesc: 'On-premises deployment',
|
||||
@@ -2196,8 +2198,8 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re
|
||||
supportServices: 'Support Services:',
|
||||
flexibleDeployment: 'Flexible deployment:',
|
||||
reliableGuarantee: 'Reliable guarantee:',
|
||||
alertTitle: 'Intellectual Property Authorization Reminder',
|
||||
alertContent: 'Please note: Using certain AI models (such as GPT-4, Claude, etc.) may involve third-party API call fees, which are not included in the Memory Bear platform subscription fee. You need to pay the relevant fees separately to the model provider. Memory Bear only charges platform management and service fees and does not bear the usage fees of third-party APIs.',
|
||||
alertTitle: 'Third-Party API Usage Notice',
|
||||
alertContent: "Please note: Some AI models(such as GPT- 4, Claude, etc.) may incur third- party API usage fees.These fees are not included in your Memory Bear subscription and must be paid directly to the model provider. Memory Bear charges only for platform management and related services and does not cover or assume responsibility for any third- party API usage fees.",
|
||||
currentAccountType: 'Current Account Type',
|
||||
validUntil: 'Valid Until',
|
||||
orderHistory: 'Order History',
|
||||
|
||||
@@ -509,7 +509,7 @@ export const zh = {
|
||||
VersionInformation: '版本信息',
|
||||
publishedOn: '发布于',
|
||||
publisher: '发布者',
|
||||
DetailsOfVersion: 'v{{version}}版本详情',
|
||||
DetailsOfVersion: '{{version}}版本详情',
|
||||
exportDSLFile: '导出DSL文件',
|
||||
willRollToThisVersion: '将回滚到此版本',
|
||||
share: '分享',
|
||||
@@ -1823,6 +1823,8 @@ export const zh = {
|
||||
tagDesc: '多个标签用逗号分隔',
|
||||
availableTools: '可用工具',
|
||||
name: '名称',
|
||||
enterNamePlaceholder: '请输入名称',
|
||||
toolEmpty: '未检测到工具',
|
||||
desc: '描述',
|
||||
method: '方法',
|
||||
path: '路径',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { cookieUtils } from './request'
|
||||
export const clearAuthData = () => {
|
||||
console.log("Clearing auth data and redirecting to login");
|
||||
sessionStorage.clear();
|
||||
localStorage.clear()
|
||||
localStorage.removeItem('user')
|
||||
localStorage.removeItem('breadcrumbs')
|
||||
cookieUtils.clear();
|
||||
}
|
||||
@@ -445,7 +445,7 @@ const Agent = forwardRef<AgentRef>((_props, ref) => {
|
||||
|
||||
<Space size={10}>
|
||||
<Button type="primary" ghost onClick={handleAddModel}>
|
||||
+{t('application.addModel')}
|
||||
+ {t('application.addModel')}
|
||||
</Button>
|
||||
<div className="rb:w-8 rb:h-8 rb:cursor-pointer rb:bg-[url('@/assets/images/application/clean.svg')]" onClick={handleClearDebugging}></div>
|
||||
</Space>
|
||||
|
||||
@@ -47,11 +47,11 @@ const ReleasePage: FC<{data: Application; refresh: () => void}> = ({data, refres
|
||||
}
|
||||
return (
|
||||
<div className="rb:flex rb:h-[calc(100vh-64px)]">
|
||||
<div className="rb:h-full rb:overflow-y-auto rb:w-[432px] rb:flex-[0_0_auto] rb:border-r-[1px] rb:border-[#DFE4ED] rb:p-[16px]">
|
||||
<div className="rb:h-full rb:overflow-y-auto rb:w-108 rb:flex-[0_0_auto] rb:border-r rb:border-[#DFE4ED] rb:p-4">
|
||||
<Space size={16} direction="vertical" style={{ width: '100%' }}>
|
||||
<div className="rb:leading-[22px] rb:px-[4px]">
|
||||
<div className="rb:leading-5.5 rb:px-1">
|
||||
{t('application.versionList')}
|
||||
<div className="rb:text-[12px] rb:text-[#5B6167] rb:mt-[4px] rb:leading-[16px]">{t('application.versionListDesc')}</div>
|
||||
<div className="rb:text-[12px] rb:text-[#5B6167] rb:mt-1 rb:leading-4">{t('application.versionListDesc')}</div>
|
||||
</div>
|
||||
{releaseList.length === 0
|
||||
? <Empty />
|
||||
@@ -64,8 +64,8 @@ const ReleasePage: FC<{data: Application; refresh: () => void}> = ({data, refres
|
||||
<RbCard
|
||||
key={version.version}
|
||||
title={<>
|
||||
{version.version_name || `v${version.version}`}
|
||||
{tagKey && <Tag color={tagColors[tagKey]} className="rb:ml-[8px]">
|
||||
{version.version_name && version.version_name[0].toLocaleLowerCase() === 'v' ? version.version_name : version.version_name ? `v${version.version_name}` : `v${version.version}`}
|
||||
{tagKey && <Tag color={tagColors[tagKey]} className="rb:ml-2">
|
||||
{tagKey}
|
||||
</Tag>}
|
||||
</>}
|
||||
@@ -76,13 +76,13 @@ const ReleasePage: FC<{data: Application; refresh: () => void}> = ({data, refres
|
||||
headerType="borderless"
|
||||
onClick={() => setSelectedVersion(version)}
|
||||
>
|
||||
<div className="rb:leading-[20px] rb:line-clamp-2 rb:overflow-hidden rb:text-ellipsis rb:whitespace-nowrap">
|
||||
<div className="rb:leading-5 rb:line-clamp-2 rb:overflow-hidden rb:text-ellipsis rb:whitespace-nowrap">
|
||||
<Markdown content={version.release_notes} />
|
||||
</div>
|
||||
<div className="rb:mt-[16px] rb:text-[12px] rb:text-[#5B6167] rb:leading-[16px]">
|
||||
<div className="rb:mt-4 rb:text-[12px] rb:text-[#5B6167] rb:leading-4">
|
||||
{t('application.publishedOn')} {formatDateTime(version.published_at, 'YYYY-MM-DD HH:mm:ss')}
|
||||
</div>
|
||||
<div className="rb:text-[12px] rb:text-[#5B6167] rb:mt-[4px] rb:leading-[16px]">
|
||||
<div className="rb:text-[12px] rb:text-[#5B6167] rb:mt-1 rb:leading-4">
|
||||
{t('application.publisher')}: {version.publisher_name}
|
||||
</div>
|
||||
</RbCard>
|
||||
@@ -91,13 +91,13 @@ const ReleasePage: FC<{data: Application; refresh: () => void}> = ({data, refres
|
||||
}
|
||||
</Space>
|
||||
</div>
|
||||
<div className="rb:h-full rb:overflow-y-auto rb:flex-[1_1_auto] rb:p-[16px]">
|
||||
<div className="rb:h-full rb:overflow-y-auto rb:flex-[1_1_auto] rb:p-4">
|
||||
<Form layout="vertical">
|
||||
<div className={clsx("rb:leading-[22px] rb:px-[4px] rb:flex rb:items-center rb:text-[16px] rb:font-medium rb:mb-[21px]", {
|
||||
<div className={clsx("rb:leading-5.5 rb:px-1 rb:flex rb:items-center rb:text-[16px] rb:font-medium rb:mb-5.25", {
|
||||
'rb:justify-between': selectedVersion,
|
||||
'rb:justify-end': !selectedVersion
|
||||
})}>
|
||||
{selectedVersion && t('application.DetailsOfVersion', { version: selectedVersion.version_name || `v${selectedVersion.version}` || '-' })}
|
||||
{selectedVersion && t('application.DetailsOfVersion', { version: selectedVersion.version_name && selectedVersion.version_name[0].toLocaleLowerCase() === 'v' ? selectedVersion.version_name : selectedVersion.version_name ? `v${selectedVersion.version_name}` : `v${selectedVersion.version}` || '-' })}
|
||||
|
||||
<Space size={10}>
|
||||
{selectedVersion && <>
|
||||
@@ -111,14 +111,14 @@ const ReleasePage: FC<{data: Application; refresh: () => void}> = ({data, refres
|
||||
{selectedVersion &&
|
||||
<Space size={16} direction="vertical" style={{ width: '100%' }}>
|
||||
<RbCard title={t('application.VersionInformation')} headerType="borderless">
|
||||
<div className="rb:grid rb:grid-cols-3 rb:gap-[16px]">
|
||||
<Form.Item label={t('application.releaseTime')} className="rb:mb-[0]!">
|
||||
<div className="rb:grid rb:grid-cols-3 rb:gap-4">
|
||||
<Form.Item label={t('application.releaseTime')} className="rb:mb-0!">
|
||||
<Input value={formatDateTime(selectedVersion.published_at, 'YYYY-MM-DD HH:mm:ss')} disabled />
|
||||
</Form.Item>
|
||||
<Form.Item label={t('application.lastUpdateTime')} className="rb:mb-[0]!">
|
||||
<Form.Item label={t('application.lastUpdateTime')} className="rb:mb-0!">
|
||||
<Input value={formatDateTime(selectedVersion.updated_at, 'YYYY-MM-DD HH:mm:ss')} disabled />
|
||||
</Form.Item>
|
||||
<Form.Item label={t('application.editor')} className="rb:mb-[0]!">
|
||||
<Form.Item label={t('application.editor')} className="rb:mb-0!">
|
||||
<Input value={selectedVersion.publisher_name} disabled />
|
||||
</Form.Item>
|
||||
</div>
|
||||
@@ -131,9 +131,9 @@ const ReleasePage: FC<{data: Application; refresh: () => void}> = ({data, refres
|
||||
<RbCard
|
||||
headerType="borderBL"
|
||||
title={<div className="rb:text-[14px]">{formatDateTime(selectedVersion.published_at, 'YYYY-MM-DD HH:mm:ss')}</div>}
|
||||
extra={<span className="rb:text-[12px] rb:text-[#5B6167] rb:leading-[16px]">{selectedVersion.publisher_name}</span>}
|
||||
extra={<span className="rb:text-[12px] rb:text-[#5B6167] rb:leading-4">{selectedVersion.publisher_name}</span>}
|
||||
>
|
||||
<div className="rb:leading-[20px] rb:font-medium rb:font-regular rb:text-[12px] rb:text-[#5B6167] rb:leading-[16px]">
|
||||
<div className="rb:font-medium rb:font-regular rb:text-[12px] rb:text-[#5B6167] rb:leading-4">
|
||||
<Markdown content={selectedVersion.release_notes} />
|
||||
</div>
|
||||
</RbCard>
|
||||
|
||||
@@ -77,7 +77,8 @@ const TopCardList: FC<{data?: DataResponse}> = ({ data }) => {
|
||||
|
||||
<div className={styles.content}>
|
||||
{item.key === 'spaces' && String(data?.active_workspaces)}
|
||||
{item.key !== 'spaces' && String(data?.[`total_${item.key}` as keyof DataResponse] || item.value || 0)}
|
||||
{item.key === 'running_apps' && String(data?.[`${item.key}` as keyof DataResponse] || item.value || 0)}
|
||||
{item.key !== 'spaces' && item.key !== 'running_apps' && String(data?.[`total_${item.key}` as keyof DataResponse] || item.value || 0)}
|
||||
</div>
|
||||
<div className='rb:flex rb:flex-col rb:items-start'>
|
||||
{item.key === 'models' ? (
|
||||
|
||||
@@ -4,29 +4,32 @@
|
||||
* @Author: yujiangping
|
||||
* @Date: 2026-01-12 16:34:59
|
||||
* @LastEditors: yujiangping
|
||||
* @LastEditTime: 2026-01-13 19:14:30
|
||||
* @LastEditTime: 2026-01-16 13:00:22
|
||||
*/
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button, Divider } from 'antd';
|
||||
import { Divider } from 'antd';
|
||||
// import arrowRight from '@/assets/images/index/arrow_right.svg'
|
||||
import { getVersion, type versionResponse } from '@/api/common'
|
||||
|
||||
const GuideCard: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const { t, i18n } = useTranslation();
|
||||
const [versionInfo, setVersionInfo] = useState<versionResponse | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
// 获取当前语言对应的介绍信息
|
||||
const getIntroduction = () => {
|
||||
if (!versionInfo) return null;
|
||||
const currentLang = i18n.language;
|
||||
return currentLang === 'zh' ? versionInfo.introduction : (versionInfo.introduction_en || versionInfo.introduction);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const fetchVersion = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await getVersion();
|
||||
setVersionInfo(response);
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch version:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -41,27 +44,30 @@ const GuideCard: React.FC = () => {
|
||||
{versionInfo?.version}
|
||||
</span>
|
||||
</div>
|
||||
<div className='rb:flex rb:flex-col rb:text-[#5B6167]'>
|
||||
{versionInfo && (<>
|
||||
<div className='rb:flex rb:items-center rb:gap-2 rb:text-sm rb:text-[#5B6167] rb:leading-5 '>
|
||||
|
||||
<span className='rb:text-xs rb:text-[#5B6167]'>
|
||||
{t('version.releaseDate')}: {versionInfo.introduction?.releaseDate}
|
||||
</span>
|
||||
<Divider type='vertical' />
|
||||
<span className='rb:text-xs rb:text-[#5B6167]'>
|
||||
{t('version.name')}: {versionInfo.introduction?.codeName}
|
||||
</span>
|
||||
</div>
|
||||
<p className='rb:text-sm rb:text-[#5B6167] rb:leading-5 rb:mt-2 '>
|
||||
{versionInfo.introduction?.upgradePosition}
|
||||
</p>
|
||||
{versionInfo.introduction?.coreUpgrades?.map((item,index) => (
|
||||
<p className='rb:text-sm rb:text-[#5B6167] rb:leading-5'>
|
||||
{index + 1}. {item}
|
||||
</p>
|
||||
))}
|
||||
</>)}
|
||||
<div className='rb:flex rb:flex-col rb:max-h-[420px] rb:overflow-y-auto rb:text-[#5B6167]'>
|
||||
{versionInfo && (() => {
|
||||
const introduction = getIntroduction();
|
||||
return introduction ? (<>
|
||||
<div className='rb:flex rb:items-center rb:gap-2 rb:text-sm rb:text-[#5B6167] rb:leading-5 '>
|
||||
|
||||
<span className='rb:text-xs rb:text-[#5B6167]'>
|
||||
{t('version.releaseDate')}: {introduction.releaseDate}
|
||||
</span>
|
||||
<Divider type='vertical' />
|
||||
<span className='rb:text-xs rb:text-[#5B6167]'>
|
||||
{t('version.name')}: {introduction.codeName}
|
||||
</span>
|
||||
</div>
|
||||
<p className='rb:text-sm rb:text-[#5B6167] rb:leading-5 rb:mt-2 '>
|
||||
{introduction.upgradePosition}
|
||||
</p>
|
||||
{introduction.coreUpgrades?.map((item: string, index: number) => (
|
||||
<p key={index} className='rb:text-sm rb:text-[#5B6167] rb:leading-5'>
|
||||
{index + 1}. {item}
|
||||
</p>
|
||||
))}
|
||||
</>) : null;
|
||||
})()}
|
||||
{/* {loading ? (
|
||||
t('index.loading')
|
||||
) : (
|
||||
|
||||
@@ -151,7 +151,7 @@ const MemoryConversation: FC = () => {
|
||||
>
|
||||
<Chat
|
||||
empty={
|
||||
<Empty url={ConversationEmptyIcon} className="rb:h-full" size={[140, 100]} title={t('memoryConversation.conversationContentEmpty')} />
|
||||
<Empty url={ConversationEmptyIcon} className="rb:h-full" size={[140, 100]} title={t('memoryConversation.conversationContentEmpty')} isNeedSubTitle={false} />
|
||||
}
|
||||
contentClassName='rb:h-[calc(100vh-362px)]'
|
||||
data={chatData}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { forwardRef, useImperativeHandle, useState } from 'react';
|
||||
import { Form, Input, Select, Row, Col, App, Button } from 'antd';
|
||||
import { Form, Input, Select, App } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import type { CustomToolItem, CustomToolModalRef, ToolItem } from '../types'
|
||||
@@ -134,9 +134,9 @@ const CustomToolModal = forwardRef<CustomToolModalRef, CustomToolModalProps>(({
|
||||
<Form.Item
|
||||
name="name"
|
||||
label={t('tool.name')}
|
||||
rules={[{ required: true, message: t('common.pleaseEnter') }]}
|
||||
rules={[{ required: true, message: t('common.enterNamePlaceholder') }]}
|
||||
>
|
||||
<Input placeholder={t('common.pleaseEnter')} />
|
||||
<Input placeholder={t('tool.enterNamePlaceholder')} />
|
||||
</Form.Item>
|
||||
{/* 名称和图标 */}
|
||||
{/* <Form.Item label={t('tool.nameAndIcon')} required>
|
||||
@@ -195,6 +195,7 @@ const CustomToolModal = forwardRef<CustomToolModalRef, CustomToolModalProps>(({
|
||||
]}
|
||||
initialData={parseSchemaData.operations || []}
|
||||
emptySize={88}
|
||||
emptyText={t('tool.toolEmpty')}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
@@ -269,7 +270,7 @@ const CustomToolModal = forwardRef<CustomToolModalRef, CustomToolModalProps>(({
|
||||
<Select
|
||||
mode="tags"
|
||||
style={{ width: '100%' }}
|
||||
placeholder={t('common.pleaseEnter')}
|
||||
placeholder={t('tool.tagDesc')}
|
||||
/>
|
||||
</FormItem>
|
||||
</Form>
|
||||
|
||||
Reference in New Issue
Block a user