mirror of
https://github.com/open-webui/computer.git
synced 2026-11-14 18:08:57 +03:00
refac
This commit is contained in:
@@ -7,12 +7,17 @@
|
||||
|
||||
let loading = $state(true);
|
||||
let saving = $state(false);
|
||||
let enabled = $state(true);
|
||||
let toolEnabled = $state(true);
|
||||
let backgroundReview = $state(true);
|
||||
let reviewInterval = $state(10);
|
||||
|
||||
onMount(async () => {
|
||||
try {
|
||||
const config = await getAdminConfig();
|
||||
enabled = config['skills.enabled'] !== false && config['skills.enabled'] !== 'false';
|
||||
toolEnabled =
|
||||
config['skills.tool_enabled'] !== false && config['skills.tool_enabled'] !== 'false';
|
||||
backgroundReview =
|
||||
config['skills.background_review_enabled'] !== false &&
|
||||
config['skills.background_review_enabled'] !== 'false';
|
||||
@@ -27,6 +32,8 @@
|
||||
saving = true;
|
||||
try {
|
||||
await updateConfig({
|
||||
'skills.enabled': enabled,
|
||||
'skills.tool_enabled': toolEnabled,
|
||||
'skills.background_review_enabled': backgroundReview,
|
||||
'skills.review_interval_turns': Math.max(1, Number(reviewInterval) || 10)
|
||||
});
|
||||
@@ -39,48 +46,72 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col min-h-full">
|
||||
<h2 class="text-sm font-medium text-gray-900 dark:text-white mb-4">Skills</h2>
|
||||
|
||||
<div class="flex flex-col h-full">
|
||||
{#if loading}
|
||||
<div class="flex justify-center py-8"><Spinner size={16} /></div>
|
||||
{:else}
|
||||
<div class="flex flex-col gap-2.5">
|
||||
<label class="flex items-center justify-between cursor-pointer">
|
||||
<span class="text-xs text-gray-600 dark:text-gray-400">Background review</span>
|
||||
<ToggleSwitch
|
||||
value={backgroundReview}
|
||||
onchange={(v) => {
|
||||
backgroundReview = v;
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
<p class="text-[0.6875rem] text-gray-400 dark:text-gray-600 -mt-1">
|
||||
Let Computer create or update managed skills after successful reusable work.
|
||||
</p>
|
||||
<div class="flex-1 min-h-0 overflow-y-auto scrollbar-hover pr-1.5 -mr-1.5">
|
||||
<h2 class="text-sm font-medium text-gray-900 dark:text-white mb-4">Skills</h2>
|
||||
|
||||
{#if backgroundReview}
|
||||
<div>
|
||||
<label class="text-xs text-gray-600 dark:text-gray-400" for="skills-review-interval"
|
||||
>Review interval</label
|
||||
>
|
||||
<div class="flex items-center gap-1.5 mt-1">
|
||||
<h3 class="text-xs text-gray-400 dark:text-gray-600 mb-2">Behavior</h3>
|
||||
<div class="flex flex-col gap-2.5">
|
||||
<label class="flex items-center justify-between cursor-pointer">
|
||||
<span class="text-xs text-gray-600 dark:text-gray-400">Enable skills</span>
|
||||
<ToggleSwitch
|
||||
value={enabled}
|
||||
onchange={(v) => {
|
||||
enabled = v;
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
|
||||
{#if enabled}
|
||||
<label class="flex items-center justify-between cursor-pointer">
|
||||
<span class="text-xs text-gray-600 dark:text-gray-400"
|
||||
>Assistant can manage skills</span
|
||||
>
|
||||
<ToggleSwitch
|
||||
value={toolEnabled}
|
||||
onchange={(v) => {
|
||||
toolEnabled = v;
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label class="flex items-center justify-between cursor-pointer">
|
||||
<span class="text-xs text-gray-600 dark:text-gray-400">Background review</span>
|
||||
<ToggleSwitch
|
||||
value={backgroundReview}
|
||||
onchange={(v) => {
|
||||
backgroundReview = v;
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if enabled}
|
||||
<h3 class="text-xs text-gray-400 dark:text-gray-600 mb-2 mt-5">Limits</h3>
|
||||
<div class="flex flex-col gap-2.5">
|
||||
<div>
|
||||
<label class="text-xs text-gray-600 dark:text-gray-400" for="skills-review-interval">
|
||||
Review every
|
||||
</label>
|
||||
<input
|
||||
id="skills-review-interval"
|
||||
type="number"
|
||||
bind:value={reviewInterval}
|
||||
min="1"
|
||||
class="w-16 h-7 px-2 rounded-lg text-xs bg-gray-100 dark:bg-white/6 text-gray-700 dark:text-gray-300 border border-gray-200 dark:border-white/8 outline-none focus:border-blue-400 dark:focus:border-blue-500 transition-colors"
|
||||
class="w-full mt-1 h-7 px-2 rounded-lg text-xs bg-gray-100 dark:bg-white/6 text-gray-700 dark:text-gray-300 border border-gray-200 dark:border-white/8 outline-none transition-colors"
|
||||
/>
|
||||
<span class="text-[0.6875rem] text-gray-400 dark:text-gray-600">turns</span>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="mt-5 pt-4 border-t border-gray-100 dark:border-white/8">
|
||||
<div class="shrink-0 pt-3 flex justify-end">
|
||||
<button
|
||||
class="h-8 px-3 rounded-lg text-xs bg-gray-900 dark:bg-white text-white dark:text-black hover:bg-gray-700 dark:hover:bg-gray-200 transition-colors disabled:opacity-50"
|
||||
class="text-[0.8125rem] text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white transition-colors duration-100 disabled:opacity-50"
|
||||
disabled={saving}
|
||||
onclick={save}
|
||||
>
|
||||
|
||||
@@ -66,7 +66,6 @@
|
||||
'connections',
|
||||
'agents',
|
||||
'models',
|
||||
'skills',
|
||||
'messaging',
|
||||
'gateway',
|
||||
'audio',
|
||||
@@ -74,7 +73,8 @@
|
||||
'web',
|
||||
'toolservers',
|
||||
'subagents',
|
||||
'memory'
|
||||
'memory',
|
||||
'skills'
|
||||
];
|
||||
|
||||
const personalTabs: SettingsTab[] = $derived.by(() => {
|
||||
@@ -95,7 +95,6 @@
|
||||
{ id: 'connections', label: $t('admin.connections'), icon: 'plug' },
|
||||
{ id: 'agents', label: $t('admin.agents'), icon: 'terminal' },
|
||||
{ id: 'models', label: $t('admin.models'), icon: 'cube' },
|
||||
{ id: 'skills', label: 'Skills', icon: 'spark' },
|
||||
{ id: 'messaging', label: $t('admin.messaging'), icon: 'chat-bubble' },
|
||||
{ id: 'gateway', label: $t('admin.gateway.tab'), icon: 'gateway' },
|
||||
{ id: 'audio', label: $t('admin.audio.title'), icon: 'microphone' },
|
||||
@@ -103,7 +102,8 @@
|
||||
{ id: 'web', label: $t('admin.web'), icon: 'globe' },
|
||||
{ id: 'toolservers', label: $t('admin.toolServers'), icon: 'plug' },
|
||||
{ id: 'subagents', label: $t('admin.subagents'), icon: 'user' },
|
||||
{ id: 'memory', label: $t('settings.memory'), icon: 'brain' }
|
||||
{ id: 'memory', label: $t('settings.memory'), icon: 'brain' },
|
||||
{ id: 'skills', label: 'Skills', icon: 'spark' }
|
||||
]);
|
||||
|
||||
onMount(() => {
|
||||
|
||||
@@ -32,8 +32,12 @@ class SkillResponse(BaseModel):
|
||||
@router.get("", response_model=list[SkillResponse])
|
||||
async def list_skills(workspace: str = Query(..., description="Workspace path")):
|
||||
"""List available skills (frontmatter only) for the $ mention picker."""
|
||||
from cptr.models import Config
|
||||
from cptr.utils.skills import discover_skills
|
||||
|
||||
if (await Config.get("skills.enabled")) in (False, "false", "0"):
|
||||
return []
|
||||
|
||||
skills = await asyncio.to_thread(discover_skills, workspace)
|
||||
return [
|
||||
SkillResponse(
|
||||
|
||||
+33
-7
@@ -14,7 +14,13 @@ import uuid
|
||||
from cptr.events import EVENTS, publish_event
|
||||
from cptr.env import CHAT_MAX_ITERATIONS, CHAT_TOOL_COMMAND_MAX_CHARS, CHAT_TOOL_MAX_CHARS
|
||||
from cptr.utils.context import resolve_compact_token_threshold, should_compact
|
||||
from cptr.utils.skills import bump_skill_view, discover_skills, load_skill, format_skill_content
|
||||
from cptr.utils.skills import (
|
||||
bump_skill_view,
|
||||
discover_skills,
|
||||
format_skill_content,
|
||||
get_skill_settings,
|
||||
load_skill,
|
||||
)
|
||||
from cptr.utils.summarize import summarize_messages
|
||||
from cptr.models import Chat, ChatMessage, Config
|
||||
from cptr.socket.main import emit_to_user
|
||||
@@ -186,7 +192,12 @@ def _has_prior_real_chat_content(messages: list[dict], loaded_summary: str | Non
|
||||
)
|
||||
|
||||
|
||||
def _build_skill_create_gate_prompt() -> str:
|
||||
def _build_skill_create_gate_prompt(reason: str = "empty_chat") -> str:
|
||||
if reason == "disabled":
|
||||
return (
|
||||
"[/skills:create] Skill creation is disabled in admin settings. "
|
||||
"Explain this briefly and do not try to create or update a skill."
|
||||
)
|
||||
return (
|
||||
"[/skills:create] The user tried to create a skill before this chat had "
|
||||
"any prior real content. Explain briefly that skill creation needs an "
|
||||
@@ -195,7 +206,12 @@ def _build_skill_create_gate_prompt() -> str:
|
||||
)
|
||||
|
||||
|
||||
def _apply_skills_create_prompt(messages: list[dict], *, allowed: bool) -> bool:
|
||||
def _apply_skills_create_prompt(
|
||||
messages: list[dict],
|
||||
*,
|
||||
allowed: bool,
|
||||
denial_reason: str = "empty_chat",
|
||||
) -> bool:
|
||||
for message in reversed(messages):
|
||||
if message.get("role") != "user":
|
||||
continue
|
||||
@@ -206,7 +222,7 @@ def _apply_skills_create_prompt(messages: list[dict], *, allowed: bool) -> bool:
|
||||
message["content"] = (
|
||||
_build_skill_create_prompt(match.group(1) or "")
|
||||
if allowed
|
||||
else _build_skill_create_gate_prompt()
|
||||
else _build_skill_create_gate_prompt(denial_reason)
|
||||
)
|
||||
return True
|
||||
return False
|
||||
@@ -1449,9 +1465,14 @@ async def run_chat_task(
|
||||
chat_obj = await Chat.get_by_id(chat_id)
|
||||
chat_params = (chat_obj.meta or {}).get("params", {}) if chat_obj else {}
|
||||
messages, loaded_summary = await _load_message_history(chat_id, message_id)
|
||||
skill_settings = await get_skill_settings()
|
||||
skill_authoring_allowed = _has_prior_real_chat_content(messages, loaded_summary)
|
||||
skill_create_denial_reason = "empty_chat"
|
||||
if not skill_settings["enabled"] or not skill_settings["tool_enabled"]:
|
||||
skill_authoring_allowed = False
|
||||
skill_create_denial_reason = "disabled"
|
||||
skill_create_requested = _apply_skills_create_prompt(
|
||||
messages, allowed=skill_authoring_allowed
|
||||
messages, allowed=skill_authoring_allowed, denial_reason=skill_create_denial_reason
|
||||
)
|
||||
memory_message, memory_files = _memory_recall_inputs(messages, regeneration_prompt)
|
||||
system = await _load_system_prompt(
|
||||
@@ -1720,9 +1741,14 @@ async def run_chat_task(
|
||||
chat_models_config = {}
|
||||
builtin_tools = resolve_builtin_tools_config(chat_models_config, model, configured_model)
|
||||
messages, loaded_summary = await _load_message_history(chat_id, message_id)
|
||||
skill_settings = await get_skill_settings()
|
||||
skill_authoring_allowed = _has_prior_real_chat_content(messages, loaded_summary)
|
||||
skill_create_denial_reason = "empty_chat"
|
||||
if not skill_settings["enabled"] or not skill_settings["tool_enabled"]:
|
||||
skill_authoring_allowed = False
|
||||
skill_create_denial_reason = "disabled"
|
||||
skill_create_requested = _apply_skills_create_prompt(
|
||||
messages, allowed=skill_authoring_allowed
|
||||
messages, allowed=skill_authoring_allowed, denial_reason=skill_create_denial_reason
|
||||
)
|
||||
memory_message, memory_files = _memory_recall_inputs(messages, regeneration_prompt)
|
||||
system = await _load_system_prompt(
|
||||
@@ -1742,7 +1768,7 @@ async def run_chat_task(
|
||||
tools = [t for t in tools if t["name"] != "manage_skill"]
|
||||
|
||||
# Remove view_skill tool if no skills are available
|
||||
skills = discover_skills(workspace)
|
||||
skills = discover_skills(workspace) if skill_settings["enabled"] else []
|
||||
if not skills:
|
||||
tools = [t for t in tools if t["name"] != "view_skill"]
|
||||
|
||||
|
||||
@@ -208,7 +208,12 @@ def _render_system_template(template: str, variables: dict[str, str]) -> str:
|
||||
return re.sub(r"\n{3,}", "\n\n", rendered).strip()
|
||||
|
||||
|
||||
def _build_template_variables(workspace: str, model: str = "", memory: str = "") -> dict[str, str]:
|
||||
def _build_template_variables(
|
||||
workspace: str,
|
||||
model: str = "",
|
||||
memory: str = "",
|
||||
skills_enabled: bool = True,
|
||||
) -> dict[str, str]:
|
||||
"""Build the dict of template variable values for the current context."""
|
||||
ws_path = Path(workspace)
|
||||
os_name = platform.system().replace("Darwin", "macOS")
|
||||
@@ -225,8 +230,7 @@ def _build_template_variables(workspace: str, model: str = "", memory: str = "")
|
||||
else:
|
||||
instructions_block = ""
|
||||
|
||||
skills = discover_skills(workspace)
|
||||
skills_block = build_catalog_xml(skills)
|
||||
skills_block = build_catalog_xml(discover_skills(workspace)) if skills_enabled else ""
|
||||
|
||||
return {
|
||||
"WORKSPACE_NAME": ws_path.name if ws_path.is_dir() else "",
|
||||
@@ -310,5 +314,10 @@ async def load_system_prompt(
|
||||
if memory and "{{MEMORY}}" not in template:
|
||||
template = template.rstrip() + "\n\n{{MEMORY}}"
|
||||
|
||||
variables = _build_template_variables(workspace, model, memory)
|
||||
try:
|
||||
skills_enabled = (await Config.get("skills.enabled")) not in (False, "false", "0")
|
||||
except Exception:
|
||||
skills_enabled = True
|
||||
|
||||
variables = _build_template_variables(workspace, model, memory, skills_enabled)
|
||||
return _render_system_template(template, variables)
|
||||
|
||||
@@ -50,6 +50,8 @@ MANAGED_WORKSPACE_SKILL_DIR = ".cptr/skills"
|
||||
MANAGED_GLOBAL_SKILL_DIR = Path.home() / ".cptr" / "skills"
|
||||
ALLOWED_BUNDLE_DIRS = {"references", "templates", "scripts", "assets"}
|
||||
DEFAULT_SKILL_SETTINGS: dict[str, Any] = {
|
||||
"enabled": True,
|
||||
"tool_enabled": True,
|
||||
"background_review_enabled": True,
|
||||
"review_interval_turns": 10,
|
||||
}
|
||||
@@ -712,6 +714,8 @@ async def get_skill_settings() -> dict[str, Any]:
|
||||
settings["review_interval_turns"] = max(1, int(settings["review_interval_turns"]))
|
||||
except (TypeError, ValueError):
|
||||
settings["review_interval_turns"] = DEFAULT_SKILL_SETTINGS["review_interval_turns"]
|
||||
settings["enabled"] = settings.get("enabled") not in (False, "false", "0")
|
||||
settings["tool_enabled"] = settings.get("tool_enabled") not in (False, "false", "0")
|
||||
settings["background_review_enabled"] = settings.get("background_review_enabled") not in (
|
||||
False,
|
||||
"false",
|
||||
@@ -744,7 +748,11 @@ async def review_skills_after_turn(
|
||||
):
|
||||
return
|
||||
settings = await get_skill_settings()
|
||||
if not settings["background_review_enabled"]:
|
||||
if (
|
||||
not settings["enabled"]
|
||||
or not settings["tool_enabled"]
|
||||
or not settings["background_review_enabled"]
|
||||
):
|
||||
return
|
||||
if not tool_names and not skill_create_requested and not loaded_skill_names:
|
||||
return
|
||||
|
||||
@@ -1611,8 +1611,12 @@ async def view_skill(
|
||||
"""Load the full instructions and resource listing for an available skill.
|
||||
:param skill_name: The name of the skill to load (from the <available_skills> catalog).
|
||||
"""
|
||||
from cptr.models import Config
|
||||
from cptr.utils.skills import bump_skill_view, load_skill, format_skill_content
|
||||
|
||||
if (await Config.get("skills.enabled")) in (False, "false", "0"):
|
||||
return "Error: skills are disabled by the administrator."
|
||||
|
||||
# Deduplication: if already activated, return short notice
|
||||
if skill_name in _activated_skills:
|
||||
return f"Skill '{skill_name}' is already loaded in this session. Refer to the existing <skill_content> above."
|
||||
@@ -1654,6 +1658,15 @@ async def manage_skill(
|
||||
update_managed_skill,
|
||||
write_managed_skill_file,
|
||||
)
|
||||
from cptr.models import Config
|
||||
|
||||
if (await Config.get("skills.enabled")) in (False, "false", "0"):
|
||||
return json.dumps({"success": False, "error": "skills are disabled"}, ensure_ascii=False)
|
||||
if (await Config.get("skills.tool_enabled")) in (False, "false", "0"):
|
||||
return json.dumps(
|
||||
{"success": False, "error": "skill management is disabled"},
|
||||
ensure_ascii=False,
|
||||
)
|
||||
|
||||
workspace = __context__.get("workspace", "")
|
||||
try:
|
||||
@@ -2852,6 +2865,17 @@ async def get_tool_list(builtin_tools: dict | None = None) -> list[dict]:
|
||||
memory_enabled = (await Config.get("memory.enabled")) not in (False, "false", "0")
|
||||
if not memory_enabled:
|
||||
tools.pop("update_memory", None)
|
||||
skills_enabled = (await Config.get("skills.enabled")) not in (False, "false", "0")
|
||||
skills_tool_enabled = (await Config.get("skills.tool_enabled")) not in (
|
||||
False,
|
||||
"false",
|
||||
"0",
|
||||
)
|
||||
if not skills_enabled:
|
||||
tools.pop("view_skill", None)
|
||||
tools.pop("manage_skill", None)
|
||||
elif not skills_tool_enabled:
|
||||
tools.pop("manage_skill", None)
|
||||
if (await Config.get("browser.enabled")) in (True, "true", "1"):
|
||||
tools.update(BROWSER_TOOLS)
|
||||
if (await Config.get("subagents.enabled")) in (True, "true", "1"):
|
||||
|
||||
Reference in New Issue
Block a user