Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 51 additions & 1 deletion src-frontend/src/components/ai-elements/tool.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export type ToolHeaderProps = {
toolsetName?: string;
className?: string;
state: ToolState;
riskLevel?: number;
};

export const getStatusBadge = (status: ToolState) => {
Expand Down Expand Up @@ -73,6 +74,51 @@ export const getStatusBadge = (status: ToolState) => {
);
};

const getRiskBadge = (riskLevel: number) => {
const normalizedRisk = Math.min(
100,
Math.max(0, Math.ceil(riskLevel / 10) * 10)
);

const configs = [
{
label: "安全",
range: [0, 20],
className: "bg-emerald-50 text-emerald-700 dark:bg-emerald-950/60 dark:text-emerald-400",
},
{
label: "低风险",
range: [30, 50],
className: "bg-amber-50 text-amber-700 dark:bg-amber-950/60 dark:text-amber-400",
},
{
label: "中风险",
range: [60, 70],
className: "bg-orange-50 text-orange-700 dark:bg-orange-950/60 dark:text-orange-400",
},
{
label: "高风险",
range: [80, 100],
className: "bg-red-50 text-red-700 dark:bg-red-950/60 dark:text-red-400",
},
] as const;

const match = configs.find(
(config) =>
normalizedRisk >= config.range[0] && normalizedRisk <= config.range[1]
);

if (!match) {
return null;
}

return (
<Badge className={match.className}>
{match.label} {normalizedRisk}
</Badge>
);
};

export type ToolBreadcrumbProps = {
toolsetName?: string;
toolName: string;
Expand Down Expand Up @@ -100,6 +146,7 @@ export const ToolHeader = ({
toolsetName,
state,
toolName,
riskLevel,
...props
}: ToolHeaderProps) => (
<CollapsibleTrigger
Expand All @@ -114,7 +161,10 @@ export const ToolHeader = ({
<ToolBreadcrumb toolsetName={toolsetName} toolName={toolName} />
{getStatusBadge(state)}
</div>
<ChevronDownIcon className="size-4 text-muted-foreground transition-transform group-data-[state=open]:rotate-180" />
<div className="flex items-center gap-2">
{riskLevel && getRiskBadge(riskLevel)}
<ChevronDownIcon className="size-4 text-muted-foreground transition-transform group-data-[state=open]:rotate-180" />
</div>
</CollapsibleTrigger>
);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { useDebounceFn } from "ahooks";
import { SIDEBAR_NAMESPACE } from "@/i18n/resources";
import { SettingItem } from "@/components/custom/item/SettingItem";
import { useServerSettingsStore } from "@/stores/server-settings-store";
import { Skeleton } from "@/components/ui/skeleton";
import { Switch } from "@/components/ui/switch";
import { Input } from "@/components/ui/input";
import { AppSettings } from "@/api/generated/schemas";

export function AgentSettings() {
const { t } = useTranslation(SIDEBAR_NAMESPACE);
const { current: serverSettings, setPartial: setPartialServerSettings } = useServerSettingsStore();
const [localSettings, setLocalSettings] = useState(serverSettings);
const [disabled, setDisabled] = useState(false);

useEffect(() => {
setLocalSettings(serverSettings);
}, [serverSettings]);

const { run: handleUpdateSettings } = useDebounceFn((update: Partial<AppSettings>) => {
const updatePromise = setPartialServerSettings(update);
if (updatePromise === null) {
return;
}
setDisabled(true);
updatePromise.finally(() => setDisabled(false));
}, { wait: 300 });

const handleValueChange = (update: Partial<AppSettings>) => {
setLocalSettings((prev) => {
if (prev === null) {
return null;
}
return { ...prev, ...update };
});
handleUpdateSettings(update);
};

return (
<div className="px-4 py-2">
<SettingItem title={t("settings.agents.smart_approve.title")}>
{localSettings === null ? (
<Skeleton className="h-4.5 w-8" />
) : (
<Switch
checked={localSettings.smart_approve}
onCheckedChange={(checked) => handleValueChange({ smart_approve: checked })}
disabled={disabled}
/>
)}
</SettingItem>

<SettingItem title={t("settings.agents.smart_approve_threshold.title")}>
{localSettings === null ? (
<Skeleton className="h-9 w-24" />
) : (
<Input
type="number"
value={localSettings.smart_approve_threshold}
min={0}
max={100}
step={10}
onChange={(e) => handleValueChange({ smart_approve_threshold: Number(e.target.value) })}
disabled={disabled}
/>
)}
</SettingItem>
</div>
);
}
12 changes: 9 additions & 3 deletions src-frontend/src/features/SideBar/views/SettingsView/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ import { ScrollArea } from "@/components/ui/scroll-area";
import { SIDEBAR_NAMESPACE } from "@/i18n/resources";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { cn } from "@/lib/utils";
import { SideBarHeader } from "../../components/SideBarHeader";
import { DevSettings } from "./DevSettings";
import { GeneralSettings } from "./GeneralSettings";
import { HelperModelSettings } from "./HelperModelSettings";
import { ProviderSettings } from "./ProviderSettings";
import { HelperModelSettings } from "./HelperModelSettings";
import { AgentSettings } from "./AgentSettings";
import { DevSettings } from "./DevSettings";
import { SideBarHeader } from "../../components/SideBarHeader";

export function SettingsView() {
const { t } = useTranslation(SIDEBAR_NAMESPACE);
Expand All @@ -28,6 +29,11 @@ export function SettingsView() {
title: t("settings.tabs.helper_model"),
content: <HelperModelSettings />,
},
{
id: "agents",
title: t("settings.tabs.agents"),
content: <AgentSettings />,
},
{
id: "dev",
title: t("settings.tabs.dev"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { useAgentTaskAction } from "../../hooks/use-agent-task";
import { useToolName } from "../../hooks/use-tool-name";
import { useToolState } from "../../hooks/use-tool-state";
import { shouldShowConfirmation, ToolConfirmation } from "./BuiltInToolMessage/components/ToolConfirmation";
import { ToolMessageMetadata } from "@/api/generated/schemas";

export function GeneralToolMessage({ message }: ToolMessageProps) {
const { reviewTool } = useAgentTaskAction();
Expand All @@ -21,6 +22,7 @@ export function GeneralToolMessage({ message }: ToolMessageProps) {
toolName={toolName}
toolsetName={toolsetName}
state={toolState}
riskLevel={(message.metadata as ToolMessageMetadata).risk_level}
/>
<ToolContent>
<ToolInput input={toolArguments} />
Expand Down
9 changes: 9 additions & 0 deletions src-frontend/src/i18n/locales/en/sidebar.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@
"placeholder": "Plugins view"
},
"settings": {
"agents": {
"smart_approve": {
"title": "Smart approve"
},
"smart_approve_threshold": {
"title": "Smart approve threshold"
}
},
"dev": {
"devtools": {
"open_button": "Open",
Expand Down Expand Up @@ -85,6 +93,7 @@
}
},
"tabs": {
"agents": "Agents",
"dev": "Development",
"general": "General",
"helper_model": "Helper model",
Expand Down
9 changes: 9 additions & 0 deletions src-frontend/src/i18n/locales/zh_CN/sidebar.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@
"placeholder": "插件视图"
},
"settings": {
"agents": {
"smart_approve": {
"title": "智能批准"
},
"smart_approve_threshold": {
"title": "智能批准阈值"
}
},
"dev": {
"devtools": {
"open_button": "打开",
Expand Down Expand Up @@ -85,6 +93,7 @@
}
},
"tabs": {
"agents": "Agents",
"dev": "开发",
"general": "通用",
"helper_model": "助手模型",
Expand Down
3 changes: 2 additions & 1 deletion src-frontend/src/stores/server-settings-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ type ServerSettingsStore = {
current: AppSettings | null;
currentPromise: Promise<AppSettings>;
isLoading: boolean;
setPartial: (settings: Partial<AppSettings>) => void;
setPartial: (settings: Partial<AppSettings>) => Promise<AppSettings> | null;
};

export const useServerSettingsStore = create<ServerSettingsStore>()((set, get) => ({
Expand All @@ -28,5 +28,6 @@ export const useServerSettingsStore = create<ServerSettingsStore>()((set, get) =
return settings;
});
set({ currentPromise: updatePromise });
return updatePromise;
},
}));
4 changes: 4 additions & 0 deletions src-frontend/src/styles/base.css
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,7 @@ body {
input[type="password"]::-ms-reveal {
display: none !important;
}

.dark input[type="number"] {
color-scheme: dark;
}
2 changes: 1 addition & 1 deletion src-server/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name = "dais-server"
version = "0.1.0"
requires-python = ">=3.14"
dependencies = [
"dais-sdk==0.8.10",
"dais-sdk==0.8.16",
"dais-shell==0.1.2",

"alembic==1.18.4",
Expand Down
6 changes: 3 additions & 3 deletions src-server/src/agent/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from dataclasses import asdict
from typing import Self, cast
from dais_sdk.tool import Toolset
from dais_sdk.types import ToolDef
from dais_sdk.types import Message, ToolDef
from .tool import use_mcp_toolset_manager, BuiltinToolsetManager, McpToolsetManager, BuiltInToolset
from .prompts import BASE_INSTRUCTION, NO_WORKSPACE_INSTRUCTION, NO_AGENT_INSTRUCTION
from .types import ContextUsage
Expand Down Expand Up @@ -56,7 +56,7 @@ def __init__(self,
task_id: int,
*,
usage: task_models.TaskUsage,
messages: list[task_models.TaskMessage],
messages: list[Message],
workspace: workspace_schemas.WorkspaceRead,
agent: agent_schemas.AgentRead,
provider: provider_schemas.ProviderRead,
Expand Down Expand Up @@ -142,7 +142,7 @@ def model(self) -> provider_schemas.LlmModelRead:
return self._model

@property
def messages(self) -> list[task_models.TaskMessage]:
def messages(self) -> list[Message]:
return self._messages

@property
Expand Down
2 changes: 1 addition & 1 deletion src-server/src/agent/prompts/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from .instruction import BASE_INSTRUCTION
from .title_summarization import TITLE_SUMMARIZATION_INSTRUCTION
from .one_turns import *
from .built_in_agents import *

USER_IGNORED_TOOL_CALL_RESULT = "[System Message] User ignored this tool call."
Expand Down
23 changes: 23 additions & 0 deletions src-server/src/agent/prompts/one_turns/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from dais_sdk import LLM
from ....db import db_context
from ....services.llm_model import LlmModelService

from .title_summarization import TitleSummarization
from .tool_call_safety_audit import ToolCallSafetyAudit, ToolCallSafetyAuditInput, ToolCallSafetyAuditOutput

async def create_one_turn_llm(model_id: int) -> LLM:
async with db_context() as db_session:
model = await LlmModelService(db_session).get_model_by_id(model_id)
provider = model.provider
provider = LLM.create_provider(provider.type,
provider.base_url,
api_key=provider.api_key)
return LLM(model.name, provider=provider)

__all__ = [
"create_one_turn_llm",
"TitleSummarization",
"ToolCallSafetyAudit",
"ToolCallSafetyAuditInput",
"ToolCallSafetyAuditOutput",
]
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
TITLE_SUMMARIZATION_INSTRUCTION = """\
INSTRUCTION = """\
Generate a concise title for the following task/conversation in {LANGUAGE}.

CRITICAL: Your response must contain ONLY the title itself - no explanations, no "Here is the title:", no quotes, no punctuation at the end.
Expand All @@ -10,3 +10,11 @@
- Use {LANGUAGE} language
- Output format: plain text title only
"""

# --- --- --- --- --- ---

from dais_sdk import LLM, OneTurn

class TitleSummarization(OneTurn):
def __init__(self, llm: LLM):
super().__init__(llm, INSTRUCTION, output="text")
Loading