fix: skill list i18n corrected
This commit is contained in:
+64
-11
@@ -20,6 +20,50 @@ DATA_FILE = str(get_data_root() / "skills.json")
|
|||||||
SKILL_HUB_DIR = str(get_workspace_root() / "skills")
|
SKILL_HUB_DIR = str(get_workspace_root() / "skills")
|
||||||
BACKEND_BUILTIN_SKILLS_DIR = str(Path(__file__).resolve().parents[1] / "skills_builtin")
|
BACKEND_BUILTIN_SKILLS_DIR = str(Path(__file__).resolve().parents[1] / "skills_builtin")
|
||||||
|
|
||||||
|
SOURCE_LOCAL_IMPORT = "local_import"
|
||||||
|
SOURCE_SYSTEM_BUILTIN = "system_builtin"
|
||||||
|
SOURCE_BACKEND_GENERATED = "backend_generated"
|
||||||
|
SOURCE_UPLOADED_FILE = "uploaded_file"
|
||||||
|
|
||||||
|
STATUS_SAFE = "safe"
|
||||||
|
STATUS_LOW_RISK = "low_risk"
|
||||||
|
|
||||||
|
_SOURCE_ALIASES = {
|
||||||
|
SOURCE_LOCAL_IMPORT: SOURCE_LOCAL_IMPORT,
|
||||||
|
"本地导入": SOURCE_LOCAL_IMPORT,
|
||||||
|
"Local Import": SOURCE_LOCAL_IMPORT,
|
||||||
|
SOURCE_SYSTEM_BUILTIN: SOURCE_SYSTEM_BUILTIN,
|
||||||
|
"系统内置": SOURCE_SYSTEM_BUILTIN,
|
||||||
|
"System Built-in": SOURCE_SYSTEM_BUILTIN,
|
||||||
|
SOURCE_BACKEND_GENERATED: SOURCE_BACKEND_GENERATED,
|
||||||
|
"后台生成": SOURCE_BACKEND_GENERATED,
|
||||||
|
"Backend Generated": SOURCE_BACKEND_GENERATED,
|
||||||
|
SOURCE_UPLOADED_FILE: SOURCE_UPLOADED_FILE,
|
||||||
|
"文件上传": SOURCE_UPLOADED_FILE,
|
||||||
|
"File Upload": SOURCE_UPLOADED_FILE,
|
||||||
|
}
|
||||||
|
|
||||||
|
_STATUS_ALIASES = {
|
||||||
|
STATUS_SAFE: STATUS_SAFE,
|
||||||
|
"安全": STATUS_SAFE,
|
||||||
|
"Safe": STATUS_SAFE,
|
||||||
|
STATUS_LOW_RISK: STATUS_LOW_RISK,
|
||||||
|
"低风险": STATUS_LOW_RISK,
|
||||||
|
"Low Risk": STATUS_LOW_RISK,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _normalize_source(value: Optional[str]) -> str:
|
||||||
|
if not value:
|
||||||
|
return SOURCE_LOCAL_IMPORT
|
||||||
|
return _SOURCE_ALIASES.get(value, value)
|
||||||
|
|
||||||
|
|
||||||
|
def _normalize_status(value: Optional[str]) -> str:
|
||||||
|
if not value:
|
||||||
|
return STATUS_SAFE
|
||||||
|
return _STATUS_ALIASES.get(value, value)
|
||||||
|
|
||||||
def _ensure_skill_hub_dir() -> None:
|
def _ensure_skill_hub_dir() -> None:
|
||||||
os.makedirs(SKILL_HUB_DIR, exist_ok=True)
|
os.makedirs(SKILL_HUB_DIR, exist_ok=True)
|
||||||
|
|
||||||
@@ -30,9 +74,9 @@ class Skill(BaseModel):
|
|||||||
content: str = Field(..., description="The content/prompt/logic of the skill")
|
content: str = Field(..., description="The content/prompt/logic of the skill")
|
||||||
type: str = Field("python", description="Type of the skill (python, sql, api)")
|
type: str = Field("python", description="Type of the skill (python, sql, api)")
|
||||||
project_id: Optional[int] = Field(None, description="The ID of the project this skill belongs to")
|
project_id: Optional[int] = Field(None, description="The ID of the project this skill belongs to")
|
||||||
source: str = Field("本地导入", description="Source of the skill (e.g., 本地导入, GitHub 导入)")
|
source: str = Field(SOURCE_LOCAL_IMPORT, description="Stable source key of the skill")
|
||||||
installation_time: str = Field(default_factory=lambda: datetime.now().strftime("%Y年%m月%d日"), description="Time when the skill was installed")
|
installation_time: str = Field(default_factory=lambda: datetime.now().strftime("%Y年%m月%d日"), description="Time when the skill was installed")
|
||||||
status: str = Field("安全", description="Security status of the skill (e.g., 安全, 低风险)")
|
status: str = Field(STATUS_SAFE, description="Stable security status key")
|
||||||
file_path: Optional[str] = Field(None, description="Path to the skill folder in skill-hub")
|
file_path: Optional[str] = Field(None, description="Path to the skill folder in skill-hub")
|
||||||
is_builtin: bool = Field(False, description="Whether this is a system builtin skill")
|
is_builtin: bool = Field(False, description="Whether this is a system builtin skill")
|
||||||
|
|
||||||
@@ -43,9 +87,9 @@ class SkillCreate(BaseModel):
|
|||||||
content: str
|
content: str
|
||||||
type: str = "python"
|
type: str = "python"
|
||||||
project_id: Optional[int] = None
|
project_id: Optional[int] = None
|
||||||
source: str = "本地导入"
|
source: str = SOURCE_LOCAL_IMPORT
|
||||||
installation_time: Optional[str] = None
|
installation_time: Optional[str] = None
|
||||||
status: str = "安全"
|
status: str = STATUS_SAFE
|
||||||
file_path: Optional[str] = None
|
file_path: Optional[str] = None
|
||||||
|
|
||||||
class SkillUpdate(BaseModel):
|
class SkillUpdate(BaseModel):
|
||||||
@@ -183,6 +227,7 @@ def _scan_builtin_skills(data: List[Dict[str, Any]], registered_paths: set, sour
|
|||||||
existing["file_path"] = skill_dir
|
existing["file_path"] = skill_dir
|
||||||
existing["is_builtin"] = True
|
existing["is_builtin"] = True
|
||||||
existing["source"] = source_name
|
existing["source"] = source_name
|
||||||
|
existing["status"] = STATUS_SAFE
|
||||||
registered_paths.add(skill_dir)
|
registered_paths.add(skill_dir)
|
||||||
else:
|
else:
|
||||||
new_skill = {
|
new_skill = {
|
||||||
@@ -194,7 +239,7 @@ def _scan_builtin_skills(data: List[Dict[str, Any]], registered_paths: set, sour
|
|||||||
"project_id": None,
|
"project_id": None,
|
||||||
"source": source_name,
|
"source": source_name,
|
||||||
"installation_time": datetime.now().strftime("%Y年%m月%d日"),
|
"installation_time": datetime.now().strftime("%Y年%m月%d日"),
|
||||||
"status": "安全",
|
"status": STATUS_SAFE,
|
||||||
"file_path": skill_dir,
|
"file_path": skill_dir,
|
||||||
"is_builtin": True
|
"is_builtin": True
|
||||||
}
|
}
|
||||||
@@ -209,6 +254,8 @@ def load_skills(project_id: Optional[int] = None) -> List[Dict[str, Any]]:
|
|||||||
|
|
||||||
# Sync registered skills with their SKILL.md if available
|
# Sync registered skills with their SKILL.md if available
|
||||||
for item in data:
|
for item in data:
|
||||||
|
item["source"] = _normalize_source(item.get("source"))
|
||||||
|
item["status"] = _normalize_status(item.get("status"))
|
||||||
if item.get("id") in ("nl2sql", "visualization") or item.get("is_builtin"):
|
if item.get("id") in ("nl2sql", "visualization") or item.get("is_builtin"):
|
||||||
item["is_builtin"] = True
|
item["is_builtin"] = True
|
||||||
else:
|
else:
|
||||||
@@ -228,8 +275,8 @@ def load_skills(project_id: Optional[int] = None) -> List[Dict[str, Any]]:
|
|||||||
item["content"] = metadata_res["content"]
|
item["content"] = metadata_res["content"]
|
||||||
|
|
||||||
# Scan builtin skills
|
# Scan builtin skills
|
||||||
_scan_builtin_skills(data, registered_paths, NANOBOT_BUILTIN_SKILLS_DIR, "系统内置")
|
_scan_builtin_skills(data, registered_paths, NANOBOT_BUILTIN_SKILLS_DIR, SOURCE_SYSTEM_BUILTIN)
|
||||||
_scan_builtin_skills(data, registered_paths, BACKEND_BUILTIN_SKILLS_DIR, "系统内置")
|
_scan_builtin_skills(data, registered_paths, BACKEND_BUILTIN_SKILLS_DIR, SOURCE_SYSTEM_BUILTIN)
|
||||||
|
|
||||||
# Scan for unregistered skills in SKILL_HUB_DIR (1-level deep to match nanobot's behavior)
|
# Scan for unregistered skills in SKILL_HUB_DIR (1-level deep to match nanobot's behavior)
|
||||||
if os.path.exists(SKILL_HUB_DIR):
|
if os.path.exists(SKILL_HUB_DIR):
|
||||||
@@ -254,9 +301,9 @@ def load_skills(project_id: Optional[int] = None) -> List[Dict[str, Any]]:
|
|||||||
"content": metadata_res.get("content") or "",
|
"content": metadata_res.get("content") or "",
|
||||||
"type": "agentskill",
|
"type": "agentskill",
|
||||||
"project_id": deduced_project_id,
|
"project_id": deduced_project_id,
|
||||||
"source": "后台生成",
|
"source": SOURCE_BACKEND_GENERATED,
|
||||||
"installation_time": datetime.now().strftime("%Y年%m月%d日"),
|
"installation_time": datetime.now().strftime("%Y年%m月%d日"),
|
||||||
"status": "安全",
|
"status": STATUS_SAFE,
|
||||||
"file_path": skill_dir,
|
"file_path": skill_dir,
|
||||||
"is_builtin": item in ("nl2sql", "visualization")
|
"is_builtin": item in ("nl2sql", "visualization")
|
||||||
}
|
}
|
||||||
@@ -389,9 +436,9 @@ async def upload_skill(
|
|||||||
"content": metadata_res.get("content") or "",
|
"content": metadata_res.get("content") or "",
|
||||||
"type": "agentskill",
|
"type": "agentskill",
|
||||||
"project_id": project_id,
|
"project_id": project_id,
|
||||||
"source": "文件上传",
|
"source": SOURCE_UPLOADED_FILE,
|
||||||
"installation_time": datetime.now().strftime("%Y年%m月%d日"),
|
"installation_time": datetime.now().strftime("%Y年%m月%d日"),
|
||||||
"status": "安全",
|
"status": STATUS_SAFE,
|
||||||
"file_path": final_skill_dir
|
"file_path": final_skill_dir
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -420,6 +467,8 @@ def create_skill(skill: SkillCreate):
|
|||||||
raise HTTPException(status_code=400, detail="Skill with this ID already exists in this project")
|
raise HTTPException(status_code=400, detail="Skill with this ID already exists in this project")
|
||||||
|
|
||||||
new_skill_dict = skill.dict()
|
new_skill_dict = skill.dict()
|
||||||
|
new_skill_dict["source"] = _normalize_source(new_skill_dict.get("source"))
|
||||||
|
new_skill_dict["status"] = _normalize_status(new_skill_dict.get("status"))
|
||||||
if not new_skill_dict.get("installation_time"):
|
if not new_skill_dict.get("installation_time"):
|
||||||
new_skill_dict["installation_time"] = datetime.now().strftime("%Y年%m月%d日")
|
new_skill_dict["installation_time"] = datetime.now().strftime("%Y年%m月%d日")
|
||||||
if not new_skill_dict.get("file_path"):
|
if not new_skill_dict.get("file_path"):
|
||||||
@@ -455,6 +504,10 @@ def update_skill(skill_id: str, skill: SkillUpdate, project_id: Optional[int] =
|
|||||||
continue
|
continue
|
||||||
updated_item = item.copy()
|
updated_item = item.copy()
|
||||||
update_data = skill.dict(exclude_unset=True)
|
update_data = skill.dict(exclude_unset=True)
|
||||||
|
if "source" in update_data:
|
||||||
|
update_data["source"] = _normalize_source(update_data.get("source"))
|
||||||
|
if "status" in update_data:
|
||||||
|
update_data["status"] = _normalize_status(update_data.get("status"))
|
||||||
updated_item.update(update_data)
|
updated_item.update(update_data)
|
||||||
if updated_item.get("file_path"):
|
if updated_item.get("file_path"):
|
||||||
_write_skill_markdown(
|
_write_skill_markdown(
|
||||||
|
|||||||
@@ -301,6 +301,11 @@
|
|||||||
"safe": "Safe",
|
"safe": "Safe",
|
||||||
"lowRisk": "Low Risk",
|
"lowRisk": "Low Risk",
|
||||||
"localImport": "Local Import",
|
"localImport": "Local Import",
|
||||||
|
"systemBuiltin": "System Built-in",
|
||||||
|
"backendGenerated": "Backend Generated",
|
||||||
|
"uploadedFile": "File Upload",
|
||||||
|
"filterBySource": "Filter by source",
|
||||||
|
"allSources": "All sources",
|
||||||
"zhipuAi": "ZhipuAI",
|
"zhipuAi": "ZhipuAI",
|
||||||
"dashScope": "DashScope",
|
"dashScope": "DashScope",
|
||||||
"volcengine": "Volcengine",
|
"volcengine": "Volcengine",
|
||||||
|
|||||||
@@ -316,6 +316,11 @@
|
|||||||
"safe": "安全",
|
"safe": "安全",
|
||||||
"lowRisk": "低风险",
|
"lowRisk": "低风险",
|
||||||
"localImport": "本地导入",
|
"localImport": "本地导入",
|
||||||
|
"systemBuiltin": "系统内置",
|
||||||
|
"backendGenerated": "后台生成",
|
||||||
|
"uploadedFile": "文件上传",
|
||||||
|
"filterBySource": "筛选来源",
|
||||||
|
"allSources": "全部来源",
|
||||||
"zhipuAi": "ZhipuAI (智谱)",
|
"zhipuAi": "ZhipuAI (智谱)",
|
||||||
"dashScope": "DashScope (通义千问)",
|
"dashScope": "DashScope (通义千问)",
|
||||||
"volcengine": "Volcengine (火山引擎)",
|
"volcengine": "Volcengine (火山引擎)",
|
||||||
|
|||||||
@@ -40,6 +40,30 @@ interface MCPServer {
|
|||||||
status?: string;
|
status?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const SOURCE_LOCAL_IMPORT = "local_import";
|
||||||
|
const SOURCE_SYSTEM_BUILTIN = "system_builtin";
|
||||||
|
const SOURCE_BACKEND_GENERATED = "backend_generated";
|
||||||
|
const SOURCE_UPLOADED_FILE = "uploaded_file";
|
||||||
|
|
||||||
|
const STATUS_SAFE = "safe";
|
||||||
|
const STATUS_LOW_RISK = "low_risk";
|
||||||
|
|
||||||
|
const normalizeSkillSource = (value?: string): string => {
|
||||||
|
if (!value) return SOURCE_LOCAL_IMPORT;
|
||||||
|
if (value === SOURCE_LOCAL_IMPORT || value === "本地导入" || value === "Local Import") return SOURCE_LOCAL_IMPORT;
|
||||||
|
if (value === SOURCE_SYSTEM_BUILTIN || value === "系统内置" || value === "System Built-in") return SOURCE_SYSTEM_BUILTIN;
|
||||||
|
if (value === SOURCE_BACKEND_GENERATED || value === "后台生成" || value === "Backend Generated") return SOURCE_BACKEND_GENERATED;
|
||||||
|
if (value === SOURCE_UPLOADED_FILE || value === "文件上传" || value === "File Upload") return SOURCE_UPLOADED_FILE;
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
const normalizeSkillStatus = (value?: string): string => {
|
||||||
|
if (!value) return STATUS_SAFE;
|
||||||
|
if (value === STATUS_SAFE || value === "安全" || value === "Safe") return STATUS_SAFE;
|
||||||
|
if (value === STATUS_LOW_RISK || value === "低风险" || value === "Low Risk") return STATUS_LOW_RISK;
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
const dedupeSkillsById = (skills: Skill[]): Skill[] => {
|
const dedupeSkillsById = (skills: Skill[]): Skill[] => {
|
||||||
const map = new Map<string, Skill>();
|
const map = new Map<string, Skill>();
|
||||||
for (const skill of skills) {
|
for (const skill of skills) {
|
||||||
@@ -60,7 +84,7 @@ export function Skills() {
|
|||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||||||
const [editingSkill, setEditingSkill] = useState<Skill | null>(null);
|
const [editingSkill, setEditingSkill] = useState<Skill | null>(null);
|
||||||
const [newSkill, setNewSkill] = useState<Partial<Skill>>({ type: 'python', content: '', source: t('localImport'), status: t('safe') });
|
const [newSkill, setNewSkill] = useState<Partial<Skill>>({ type: 'python', content: '', source: SOURCE_LOCAL_IMPORT, status: STATUS_SAFE });
|
||||||
|
|
||||||
// MCP state
|
// MCP state
|
||||||
const [mcpServers, setMcpServers] = useState<MCPServer[]>([]);
|
const [mcpServers, setMcpServers] = useState<MCPServer[]>([]);
|
||||||
@@ -76,6 +100,14 @@ export function Skills() {
|
|||||||
const { currentProject } = useProjectStore();
|
const { currentProject } = useProjectStore();
|
||||||
const { hasMcpError, refresh: refreshMcpHealth } = useMcpHealthStore();
|
const { hasMcpError, refresh: refreshMcpHealth } = useMcpHealthStore();
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
const getSourceLabel = (source: string): string => {
|
||||||
|
if (source === 'all') return t('allSources');
|
||||||
|
if (source === SOURCE_SYSTEM_BUILTIN) return t('systemBuiltin');
|
||||||
|
if (source === SOURCE_BACKEND_GENERATED) return t('backendGenerated');
|
||||||
|
if (source === SOURCE_UPLOADED_FILE) return t('uploadedFile');
|
||||||
|
if (source === SOURCE_LOCAL_IMPORT) return t('localImport');
|
||||||
|
return source;
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchSkills = async () => {
|
const fetchSkills = async () => {
|
||||||
@@ -128,12 +160,12 @@ export function Skills() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Get unique sources for the filter dropdown
|
// Get unique sources for the filter dropdown
|
||||||
const uniqueSources = Array.from(new Set(skills.map(s => s.source))).filter(Boolean);
|
const uniqueSources = Array.from(new Set(skills.map(s => normalizeSkillSource(s.source)))).filter(Boolean);
|
||||||
|
|
||||||
// Filtered skills
|
// Filtered skills
|
||||||
const filteredSkills = sourceFilter === 'all'
|
const filteredSkills = sourceFilter === 'all'
|
||||||
? skills
|
? skills
|
||||||
: skills.filter(skill => skill.source === sourceFilter);
|
: skills.filter(skill => normalizeSkillSource(skill.source) === sourceFilter);
|
||||||
|
|
||||||
const fetchMcpServers = async () => {
|
const fetchMcpServers = async () => {
|
||||||
if (!currentProject) return;
|
if (!currentProject) return;
|
||||||
@@ -203,7 +235,7 @@ export function Skills() {
|
|||||||
await api.post<Skill>('/api/v1/skills', skillToCreate);
|
await api.post<Skill>('/api/v1/skills', skillToCreate);
|
||||||
}
|
}
|
||||||
await fetchSkills();
|
await fetchSkills();
|
||||||
setNewSkill({ type: 'python', content: '', source: t('localImport'), status: t('safe') });
|
setNewSkill({ type: 'python', content: '', source: SOURCE_LOCAL_IMPORT, status: STATUS_SAFE });
|
||||||
setEditingSkill(null);
|
setEditingSkill(null);
|
||||||
setIsDialogOpen(false);
|
setIsDialogOpen(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -214,7 +246,11 @@ export function Skills() {
|
|||||||
|
|
||||||
const handleEditSkill = (skill: Skill) => {
|
const handleEditSkill = (skill: Skill) => {
|
||||||
setEditingSkill(skill);
|
setEditingSkill(skill);
|
||||||
setNewSkill(skill);
|
setNewSkill({
|
||||||
|
...skill,
|
||||||
|
source: normalizeSkillSource(skill.source),
|
||||||
|
status: normalizeSkillStatus(skill.status),
|
||||||
|
});
|
||||||
setIsDialogOpen(true);
|
setIsDialogOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -341,12 +377,16 @@ export function Skills() {
|
|||||||
{uniqueSources.length > 0 && (
|
{uniqueSources.length > 0 && (
|
||||||
<Select value={sourceFilter} onValueChange={(val) => { if (val) setSourceFilter(val); }}>
|
<Select value={sourceFilter} onValueChange={(val) => { if (val) setSourceFilter(val); }}>
|
||||||
<SelectTrigger className="w-[140px] h-9">
|
<SelectTrigger className="w-[140px] h-9">
|
||||||
<SelectValue placeholder={t('filterBySource', '筛选来源')} />
|
<SelectValue placeholder={t('filterBySource')}>
|
||||||
|
{getSourceLabel(sourceFilter)}
|
||||||
|
</SelectValue>
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="all">{t('allSources', '全部来源')}</SelectItem>
|
<SelectItem value="all">{t('allSources')}</SelectItem>
|
||||||
{uniqueSources.map(source => (
|
{uniqueSources.map(source => (
|
||||||
<SelectItem key={source} value={source}>{source}</SelectItem>
|
<SelectItem key={source} value={source}>
|
||||||
|
{getSourceLabel(source)}
|
||||||
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
@@ -450,23 +490,33 @@ export function Skills() {
|
|||||||
</div>
|
</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="py-4 px-4 text-muted-foreground text-sm">
|
<TableCell className="py-4 px-4 text-muted-foreground text-sm">
|
||||||
<div className="truncate" title={skill.source}>{skill.source}</div>
|
<div className="truncate" title={skill.source}>
|
||||||
|
{normalizeSkillSource(skill.source) === SOURCE_SYSTEM_BUILTIN ? t('systemBuiltin') :
|
||||||
|
normalizeSkillSource(skill.source) === SOURCE_BACKEND_GENERATED ? t('backendGenerated') :
|
||||||
|
normalizeSkillSource(skill.source) === SOURCE_UPLOADED_FILE ? t('uploadedFile') :
|
||||||
|
normalizeSkillSource(skill.source) === SOURCE_LOCAL_IMPORT ? t('localImport') :
|
||||||
|
skill.source}
|
||||||
|
</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="py-4 px-4 text-muted-foreground text-center text-xs">
|
<TableCell className="py-4 px-4 text-muted-foreground text-center text-xs">
|
||||||
<div className="truncate">{skill.installation_time}</div>
|
<div className="truncate">{skill.installation_time}</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="py-4 px-4 text-center">
|
<TableCell className="py-4 px-4 text-center">
|
||||||
<div className={`inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[10px] md:text-xs font-medium whitespace-nowrap ${
|
<div className={`inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[10px] md:text-xs font-medium whitespace-nowrap ${
|
||||||
skill.status === t('safe')
|
normalizeSkillStatus(skill.status) === STATUS_SAFE
|
||||||
? 'bg-green-50 text-green-700 border border-green-100'
|
? 'bg-green-50 text-green-700 border border-green-100'
|
||||||
: 'bg-amber-50 text-amber-700 border border-amber-100'
|
: 'bg-amber-50 text-amber-700 border border-amber-100'
|
||||||
}`}>
|
}`}>
|
||||||
{skill.status === t('safe') ? (
|
{normalizeSkillStatus(skill.status) === STATUS_SAFE ? (
|
||||||
<ShieldCheck className="h-3 w-3" />
|
<ShieldCheck className="h-3 w-3" />
|
||||||
) : (
|
) : (
|
||||||
<AlertCircle className="h-3 w-3" />
|
<AlertCircle className="h-3 w-3" />
|
||||||
)}
|
)}
|
||||||
{skill.status}
|
{normalizeSkillStatus(skill.status) === STATUS_SAFE
|
||||||
|
? t('safe')
|
||||||
|
: normalizeSkillStatus(skill.status) === STATUS_LOW_RISK
|
||||||
|
? t('lowRisk')
|
||||||
|
: skill.status}
|
||||||
</div>
|
</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="py-4 px-4 text-right">
|
<TableCell className="py-4 px-4 text-right">
|
||||||
@@ -610,7 +660,7 @@ export function Skills() {
|
|||||||
setIsDialogOpen(open);
|
setIsDialogOpen(open);
|
||||||
if (!open) {
|
if (!open) {
|
||||||
setEditingSkill(null);
|
setEditingSkill(null);
|
||||||
setNewSkill({ type: 'python', content: '', source: t('localImport'), status: t('safe') });
|
setNewSkill({ type: 'python', content: '', source: SOURCE_LOCAL_IMPORT, status: STATUS_SAFE });
|
||||||
}
|
}
|
||||||
}}>
|
}}>
|
||||||
<DialogContent className="sm:max-w-[600px] max-h-[90vh] flex flex-col rounded-2xl p-0 overflow-hidden">
|
<DialogContent className="sm:max-w-[600px] max-h-[90vh] flex flex-col rounded-2xl p-0 overflow-hidden">
|
||||||
@@ -651,7 +701,7 @@ export function Skills() {
|
|||||||
<div className="grid gap-1.5">
|
<div className="grid gap-1.5">
|
||||||
<Label htmlFor="status" className="text-muted-foreground font-medium text-sm">{t('status')}</Label>
|
<Label htmlFor="status" className="text-muted-foreground font-medium text-sm">{t('status')}</Label>
|
||||||
<Select
|
<Select
|
||||||
value={newSkill.status}
|
value={normalizeSkillStatus(newSkill.status)}
|
||||||
onValueChange={(val) => { if (val) setNewSkill({...newSkill, status: val}) }}
|
onValueChange={(val) => { if (val) setNewSkill({...newSkill, status: val}) }}
|
||||||
disabled={editingSkill?.is_builtin}
|
disabled={editingSkill?.is_builtin}
|
||||||
>
|
>
|
||||||
@@ -659,8 +709,8 @@ export function Skills() {
|
|||||||
<SelectValue placeholder={t('selectStatus')} />
|
<SelectValue placeholder={t('selectStatus')} />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent className="rounded-lg">
|
<SelectContent className="rounded-lg">
|
||||||
<SelectItem value={t('safe')}>{t('safe')}</SelectItem>
|
<SelectItem value={STATUS_SAFE}>{t('safe')}</SelectItem>
|
||||||
<SelectItem value={t('lowRisk')}>{t('lowRisk')}</SelectItem>
|
<SelectItem value={STATUS_LOW_RISK}>{t('lowRisk')}</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user