Version: 0.9.25.dev.260417
后端: 1. AIHub 模型分级从 Worker/Strategist 两级重构为 Lite/Pro/Max 三级 - AIHub 结构体从 Worker + Strategist 改为 Lite + Pro + Max,分别对应轻量(标题生成)、标准(Chat 路由/闲聊/交付总结)、高能力(Plan 规划/Execute ReAct)三个能力层级 - config.example.yaml 新增 liteModel / proModel / maxModel 三个模型配置项,替代原 workerModel / strategistModel - 启动层 InitEino 改为创建三个独立模型实例,抽取公共 baseURL 和 apiKey 减少重复 - pickChatModel 统一返回 Pro 模型,旧 strategist 参数不再生效;pickTitleModel 从 Worker 切到 Lite - runNewAgentGraph 按 Plan/Execute→Max、Chat/Deliver→Pro 分级注入;Graph 出错回退也切到 Pro - Memory 模块初始化从 Worker 改为 Pro 2. Plan 节点从"两阶段评估"简化为"单轮深度规划",thinking 开关改为全配置化 - 移除 Phase 1(快速评估 1600 token)+ Phase 2(深度规划 3200 token)的两轮调用逻辑,改为单轮不限 token 深度规划 - PlanDecision 移除 need_thinking 字段,prompt 规则和 JSON contract 同步删除该字段 - 各节点(Plan / Execute / Deliver)thinking 开关从硬编码改为从 AgentGraphDeps 读取,由 config.yaml 的 agent.thinking 段按节点注入 - 新增 agent.thinking 配置段(plan / execute / deliver / memory 四个独立布尔开关),config.example.yaml 补齐默认值 - 新增 resolveThinkingMode 公共函数,plan / execute / deliver 和 memory 决策/抽取链路统一使用 3. Memory 模块 LLM 调用支持 thinking 开关 - Config 新增 LLMThinking 字段,config_loader 从 agent.thinking.memory 读取 - LLMDecisionOrchestrator.Compare 和 LLMWriteOrchestrator.ExtractFacts 的 thinking 模式从硬编码 Disabled 改为读取配置 前端: 1. 移除助手输入区模型选择器及全部偏好持久化逻辑 - 删除 ModelType 类型、selectedModel ref、MODEL_PREFERENCE_STORAGE_KEY 常量 - 删除 isModelType / loadModelPreferenceMap / persistModelPreferenceMap / savePreferredModel / resolvePreferredModel / applyPreferredModelForConversation 六个函数及 modelPreferenceMap ref - 删除 selectedModel watch 监听、发送消息时的 savePreferredModel 调用、切会话时的 applyPreferredModelForConversation 调用、会话迁移时的模型偏好迁移 - fetchChatStream 的 model 参数硬编码为 'worker' - 删除模板中"模型"下拉选择器(标准/策略)及对应的全局样式 .assistant-model-select-panel 2. 上下文窗口指示器简化为仅显示总占用 - ContextWindowMeter 移除 msg0~msg3 四段彩色分段逻辑(ContextSegment 接口、segments computed、v-for 渲染) - 进度条改为单一蓝色条,按 total/budget 比例填充;超预算时变红 - Tooltip 简化为仅显示"总计 X / 预算 Y(Z%)" 仓库:无
This commit is contained in:
@@ -83,7 +83,7 @@ func Start() {
|
||||
memoryMetrics := memoryobserve.NewMetricsRegistry()
|
||||
memoryModule := memory.NewModuleWithObserve(
|
||||
db,
|
||||
infrallm.WrapArkClient(aiHub.Worker),
|
||||
infrallm.WrapArkClient(aiHub.Pro),
|
||||
ragRuntime,
|
||||
memoryCfg,
|
||||
memory.ObserveDeps{
|
||||
|
||||
@@ -67,16 +67,27 @@ time:
|
||||
|
||||
# 智能体模型与规划参数。
|
||||
agent:
|
||||
# 日常执行链路使用的主模型。
|
||||
workerModel: "doubao-seed-2-0-code-preview-260215"
|
||||
# 规划、拆解、策略推导使用的模型。
|
||||
strategistModel: "doubao-seed-2-0-code-preview-260215"
|
||||
# 轻量模型:标题生成等低复杂度、低延迟场景。
|
||||
liteModel: "doubao-seed-2-0-code-preview-260215"
|
||||
# 标准模型:Chat 路由/闲聊/深度回答/Deliver 总结。
|
||||
proModel: "doubao-seed-2-0-code-preview-260215"
|
||||
# 高能力模型:Plan 规划 + Execute ReAct 等深度推理场景。
|
||||
maxModel: "doubao-seed-2-0-code-preview-260215"
|
||||
# 模型服务根路径。
|
||||
baseURL: "https://ark.cn-beijing.volces.com/api/v3"
|
||||
# 日内并发优化并发度,建议按模型配额调整。
|
||||
dailyRefineConcurrency: 7
|
||||
# 周级跨天配平额度上限,防止过度调整。
|
||||
weeklyAdjustBudget: 5
|
||||
thinking:
|
||||
# plan 节点(单轮深度规划),默认开 thinking。
|
||||
plan: true
|
||||
# execute 节点(ReAct 深度推理),默认开 thinking。
|
||||
execute: true
|
||||
# deliver 节点(交付总结),默认关 thinking。
|
||||
deliver: false
|
||||
# 记忆模块(决策比对 + 抽取),默认关 thinking。
|
||||
memory: false
|
||||
|
||||
# 通用 RAG 配置。
|
||||
rag:
|
||||
|
||||
@@ -8,32 +8,53 @@ import (
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// AIHub 存储不同能力的模型实例
|
||||
// AIHub 存储三级模型的实例,按能力分级调度。
|
||||
//
|
||||
// 分级策略:
|
||||
// 1. Lite:轻量模型,用于标题生成等低复杂度、低延迟场景;
|
||||
// 2. Pro:标准模型,用于 Chat 路由/闲聊/深度回答/Deliver 总结;
|
||||
// 3. Max:高能力模型,用于 Plan 规划和 Execute ReAct 等需要深度推理的场景。
|
||||
type AIHub struct {
|
||||
Strategist *ark.ChatModel // 智力担当:处理复杂排程逻辑
|
||||
Worker *ark.ChatModel // 效率担当:处理简单任务、总结
|
||||
Lite *ark.ChatModel // 轻量模型:标题生成等低复杂度任务
|
||||
Pro *ark.ChatModel // 标准模型:Chat 路由、闲聊、交付总结
|
||||
Max *ark.ChatModel // 高能力模型:Plan 规划、Execute ReAct
|
||||
}
|
||||
|
||||
func InitEino() (*AIHub, error) {
|
||||
ctx := context.Background()
|
||||
worker, err := ark.NewChatModel(ctx, &ark.ChatModelConfig{
|
||||
Model: viper.GetString("agent.workerModel"), // 使用的模型版本
|
||||
BaseURL: viper.GetString("agent.baseURL"), // Eino API 的基础 URL
|
||||
APIKey: os.Getenv("ARK_API_KEY"), // API 密钥
|
||||
baseURL := viper.GetString("agent.baseURL")
|
||||
apiKey := os.Getenv("ARK_API_KEY")
|
||||
|
||||
// 1. Lite 模型:标题生成等低复杂度场景,优先控制成本和延迟。
|
||||
lite, err := ark.NewChatModel(ctx, &ark.ChatModelConfig{
|
||||
Model: viper.GetString("agent.liteModel"),
|
||||
BaseURL: baseURL,
|
||||
APIKey: apiKey,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
strategist, err := ark.NewChatModel(ctx, &ark.ChatModelConfig{
|
||||
Model: viper.GetString("agent.strategistModel"), // 使用的模型版本
|
||||
BaseURL: viper.GetString("agent.baseURL"), // Eino API 的基础 URL
|
||||
APIKey: os.Getenv("ARK_API_KEY"), // API 密钥
|
||||
// 2. Pro 模型:Chat 路由/闲聊/交付总结等标准复杂度场景。
|
||||
pro, err := ark.NewChatModel(ctx, &ark.ChatModelConfig{
|
||||
Model: viper.GetString("agent.proModel"),
|
||||
BaseURL: baseURL,
|
||||
APIKey: apiKey,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 3. Max 模型:Plan 规划和 Execute ReAct 等需要深度推理的场景。
|
||||
maxModel, err := ark.NewChatModel(ctx, &ark.ChatModelConfig{
|
||||
Model: viper.GetString("agent.maxModel"),
|
||||
BaseURL: baseURL,
|
||||
APIKey: apiKey,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &AIHub{
|
||||
Strategist: strategist,
|
||||
Worker: worker,
|
||||
Lite: lite,
|
||||
Pro: pro,
|
||||
Max: maxModel,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -72,6 +72,9 @@ type Config struct {
|
||||
// 2. 默认 0.5,与"守门员"prompt 的 confidence>=0.5 输出规则配合;
|
||||
// 3. fallback 路径 confidence 设为 0.45,低于默认阈值,LLM 不可用时不写入。
|
||||
WriteMinConfidence float64
|
||||
|
||||
// 记忆模块 LLM 调用是否开启 thinking,由 config.yaml 的 agent.thinking.memory 注入。
|
||||
LLMThinking bool
|
||||
}
|
||||
|
||||
// NormalizeReadMode 统一读取模式字符串。
|
||||
|
||||
@@ -62,10 +62,7 @@ func (o *LLMDecisionOrchestrator) Compare(
|
||||
infrallm.GenerateOptions{
|
||||
Temperature: 0.1,
|
||||
MaxTokens: defaultDecisionCompareMaxTokens,
|
||||
Thinking: infrallm.ThinkingModeDisabled,
|
||||
Metadata: map[string]any{
|
||||
"stage": "memory_decision_compare",
|
||||
},
|
||||
Thinking: resolveMemoryThinkingMode(o.cfg.LLMThinking),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
@@ -128,3 +125,11 @@ func buildDecisionCompareUserPrompt(fact memorymodel.NormalizedFact, candidate m
|
||||
candidate.MemoryType, candidate.Content,
|
||||
)
|
||||
}
|
||||
|
||||
// resolveMemoryThinkingMode 根据配置布尔值返回对应的 ThinkingMode。
|
||||
func resolveMemoryThinkingMode(enabled bool) infrallm.ThinkingMode {
|
||||
if enabled {
|
||||
return infrallm.ThinkingModeEnabled
|
||||
}
|
||||
return infrallm.ThinkingModeDisabled
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ func (o *LLMWriteOrchestrator) ExtractFacts(ctx context.Context, payload memorym
|
||||
infrallm.GenerateOptions{
|
||||
Temperature: clampTemperature(o.cfg.LLMTemperature),
|
||||
MaxTokens: defaultMemoryExtractMaxTokens,
|
||||
Thinking: infrallm.ThinkingModeDisabled,
|
||||
Thinking: resolveMemoryThinkingMode(o.cfg.LLMThinking),
|
||||
Metadata: map[string]any{
|
||||
"stage": "memory_extract",
|
||||
"user_id": payload.UserID,
|
||||
|
||||
@@ -40,6 +40,7 @@ func LoadConfigFromViper() memorymodel.Config {
|
||||
DecisionFallbackMode: viper.GetString("memory.decision.fallbackMode"),
|
||||
WriteMode: viper.GetString("memory.write.mode"),
|
||||
WriteMinConfidence: viper.GetFloat64("memory.write.minConfidence"),
|
||||
LLMThinking: viper.GetBool("agent.thinking.memory"),
|
||||
}
|
||||
|
||||
if cfg.Threshold <= 0 {
|
||||
|
||||
@@ -72,6 +72,11 @@ type AgentGraphDeps struct {
|
||||
RoughBuildFunc RoughBuildFunc // 按 Service 注入,粗排算法入口
|
||||
WriteSchedulePreview WriteSchedulePreviewFunc // 按 Service 注入,排程预览写入入口
|
||||
|
||||
// thinking 开关:由 config.yaml 的 agent.thinking 段注入,各节点按需读取。
|
||||
ThinkingPlan bool
|
||||
ThinkingExecute bool
|
||||
ThinkingDeliver bool
|
||||
|
||||
// 记忆预取管线:由 service 层启动的后台检索 goroutine 写入。
|
||||
// channel 携带已渲染的文本内容(非原始 ItemDTO),节点直接写入 pinned block。
|
||||
MemoryFuture chan string // buffered(1),携带 renderMemoryPinnedContentByMode 的输出
|
||||
|
||||
@@ -52,7 +52,6 @@ type PlanDecision struct {
|
||||
Action PlanAction `json:"action"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
Complexity PlanComplexity `json:"complexity"`
|
||||
NeedThinking bool `json:"need_thinking"`
|
||||
PlanSteps []PlanStep `json:"plan_steps,omitempty"`
|
||||
NeedsRoughBuild bool `json:"needs_rough_build,omitempty"`
|
||||
TaskClassIDs []int `json:"task_class_ids,omitempty"`
|
||||
|
||||
@@ -120,6 +120,7 @@ func (n *AgentNodes) Plan(ctx context.Context, st *newagentmodel.AgentGraphState
|
||||
ChunkEmitter: st.EnsureChunkEmitter(),
|
||||
ResumeNode: "plan",
|
||||
AlwaysExecute: st.Request.AlwaysExecute,
|
||||
ThinkingEnabled: st.Deps.ThinkingPlan,
|
||||
},
|
||||
); err != nil {
|
||||
return nil, err
|
||||
@@ -230,6 +231,7 @@ func (n *AgentNodes) Execute(ctx context.Context, st *newagentmodel.AgentGraphSt
|
||||
WriteSchedulePreview: st.Deps.WriteSchedulePreview,
|
||||
OriginalScheduleState: st.OriginalScheduleState,
|
||||
AlwaysExecute: st.Request.AlwaysExecute,
|
||||
ThinkingEnabled: st.Deps.ThinkingExecute,
|
||||
},
|
||||
); err != nil {
|
||||
return nil, err
|
||||
@@ -277,6 +279,7 @@ func (n *AgentNodes) Deliver(ctx context.Context, st *newagentmodel.AgentGraphSt
|
||||
ConversationContext: st.EnsureConversationContext(),
|
||||
Client: st.Deps.ResolveDeliverClient(),
|
||||
ChunkEmitter: st.EnsureChunkEmitter(),
|
||||
ThinkingEnabled: st.Deps.ThinkingDeliver,
|
||||
},
|
||||
); err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -32,6 +32,7 @@ type DeliverNodeInput struct {
|
||||
ConversationContext *newagentmodel.ConversationContext
|
||||
Client *infrallm.Client
|
||||
ChunkEmitter *newagentstream.ChunkEmitter
|
||||
ThinkingEnabled bool // 是否开启 thinking,由 config.yaml 的 agent.thinking.deliver 注入
|
||||
}
|
||||
|
||||
// RunDeliverNode 执行一轮交付节点逻辑。
|
||||
@@ -64,7 +65,7 @@ func RunDeliverNode(ctx context.Context, input DeliverNodeInput) error {
|
||||
}
|
||||
|
||||
// 2. 调 LLM 生成交付总结。
|
||||
summary := generateDeliverSummary(ctx, input.Client, flowState, conversationContext)
|
||||
summary := generateDeliverSummary(ctx, input.Client, flowState, conversationContext, input.ThinkingEnabled)
|
||||
|
||||
// 3. 伪流式推送总结。
|
||||
if strings.TrimSpace(summary) != "" {
|
||||
@@ -98,6 +99,7 @@ func generateDeliverSummary(
|
||||
client *infrallm.Client,
|
||||
flowState *newagentmodel.CommonState,
|
||||
conversationContext *newagentmodel.ConversationContext,
|
||||
thinkingEnabled bool,
|
||||
) string {
|
||||
if flowState != nil {
|
||||
switch {
|
||||
@@ -119,7 +121,7 @@ func generateDeliverSummary(
|
||||
infrallm.GenerateOptions{
|
||||
Temperature: 0.5,
|
||||
MaxTokens: 800,
|
||||
Thinking: infrallm.ThinkingModeDisabled,
|
||||
Thinking: resolveThinkingMode(thinkingEnabled),
|
||||
Metadata: map[string]any{
|
||||
"stage": deliverStageName,
|
||||
},
|
||||
|
||||
@@ -59,6 +59,7 @@ type ExecuteNodeInput struct {
|
||||
WriteSchedulePreview newagentmodel.WriteSchedulePreviewFunc
|
||||
OriginalScheduleState *schedule.ScheduleState
|
||||
AlwaysExecute bool // true 时写工具跳过确认闸门直接执行
|
||||
ThinkingEnabled bool // 是否开启 thinking,由 config.yaml 的 agent.thinking.execute 注入
|
||||
}
|
||||
|
||||
// ExecuteRoundObservation 记录执行阶段每轮的关键观察。
|
||||
@@ -203,7 +204,7 @@ func RunExecuteNode(ctx context.Context, input ExecuteNodeInput) error {
|
||||
infrallm.GenerateOptions{
|
||||
Temperature: 1.0, // thinking 模式强制要求 temperature=1
|
||||
MaxTokens: 16000, // 需为 thinking chain 留出足够预算
|
||||
Thinking: infrallm.ThinkingModeEnabled,
|
||||
Thinking: resolveThinkingMode(input.ThinkingEnabled),
|
||||
Metadata: map[string]any{
|
||||
"stage": executeStageName,
|
||||
"step_index": flowState.CurrentStep,
|
||||
|
||||
@@ -35,19 +35,19 @@ type PlanNodeInput struct {
|
||||
ChunkEmitter *newagentstream.ChunkEmitter
|
||||
ResumeNode string
|
||||
AlwaysExecute bool // true 时计划生成后自动确认,不进入 confirm 节点
|
||||
ThinkingEnabled bool // 是否开启 thinking,由 config.yaml 的 agent.thinking.plan 注入
|
||||
}
|
||||
|
||||
// RunPlanNode 执行一轮规划节点逻辑。
|
||||
//
|
||||
// 步骤说明:
|
||||
// 1. 先校验最小依赖,并推送一条”正在规划”的状态,避免用户空等;
|
||||
// 2. Phase 1(快速评估):不开 thinking,让 LLM 同时产出复杂度评估和规划结果;
|
||||
// 3. Phase 2(深度规划):若 LLM 自评需要深度思考且规划已完成,开 thinking 重跑;
|
||||
// 4. 若模型先对用户说了话,则先把 speak 伪流式推给前端,并写回 history;
|
||||
// 5. 最后按 action 推进流程:
|
||||
// 5.1 continue:继续停留在 planning;
|
||||
// 5.2 ask_user:打开 pending interaction,后续交给 interrupt 收口;
|
||||
// 5.3 plan_done:固化完整计划,刷新 pinned context,并进入 waiting_confirm。
|
||||
// 1. 先校验最小依赖,并推送一条"正在规划"的状态,避免用户空等;
|
||||
// 2. 单轮深度规划:开 thinking、无 token 上限,让 LLM 一步到位产出完整计划;
|
||||
// 3. 若模型先对用户说了话,则先把 speak 伪流式推给前端,并写回 history;
|
||||
// 4. 最后按 action 推进流程:
|
||||
// 4.1 continue:继续停留在 planning;
|
||||
// 4.2 ask_user:打开 pending interaction,后续交给 interrupt 收口;
|
||||
// 4.3 plan_done:固化完整计划,刷新 pinned context,并进入 waiting_confirm。
|
||||
func RunPlanNode(ctx context.Context, input PlanNodeInput) error {
|
||||
runtimeState, conversationContext, emitter, err := preparePlanNodeInput(input)
|
||||
if err != nil {
|
||||
@@ -69,68 +69,31 @@ func RunPlanNode(ctx context.Context, input PlanNodeInput) error {
|
||||
// 2. 构造本轮规划输入。
|
||||
messages := newagentprompt.BuildPlanMessages(flowState, conversationContext, input.UserInput)
|
||||
|
||||
// 3. Phase 1:快速评估(开 thinking),让 LLM 同时产出复杂度评估和规划结果。
|
||||
// 3. 单轮深度规划:由配置决定是否开启 thinking,不做 token 上限约束。
|
||||
decision, rawResult, err := infrallm.GenerateJSON[newagentmodel.PlanDecision](
|
||||
ctx,
|
||||
input.Client,
|
||||
messages,
|
||||
infrallm.GenerateOptions{
|
||||
Temperature: 0.2,
|
||||
MaxTokens: 1600,
|
||||
Thinking: infrallm.ThinkingModeEnabled,
|
||||
Thinking: resolveThinkingMode(input.ThinkingEnabled),
|
||||
Metadata: map[string]any{
|
||||
"stage": planStageName,
|
||||
"phase": "assessment",
|
||||
"phase": "planning",
|
||||
},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
if rawResult != nil && strings.TrimSpace(rawResult.Text) != "" {
|
||||
return fmt.Errorf("规划评估解析失败,原始输出=%s,错误=%w", strings.TrimSpace(rawResult.Text), err)
|
||||
return fmt.Errorf("规划解析失败,原始输出=%s,错误=%w", strings.TrimSpace(rawResult.Text), err)
|
||||
}
|
||||
return fmt.Errorf("规划评估阶段模型调用失败: %w", err)
|
||||
return fmt.Errorf("规划阶段模型调用失败: %w", err)
|
||||
}
|
||||
if err := decision.Validate(); err != nil {
|
||||
return fmt.Errorf("规划评估决策不合法: %w", err)
|
||||
return fmt.Errorf("规划决策不合法: %w", err)
|
||||
}
|
||||
|
||||
// 4. Phase 2:若 LLM 自评需要深度思考且本轮规划已完成,则开启 thinking 重跑。
|
||||
// 条件:NeedThinking=true + Action=plan_done → 说明 LLM 认为当前无 thinking 的计划质量不够。
|
||||
// 其他 action(continue / ask_user)不需要 thinking,直接用 Phase 1 结果。
|
||||
if decision.NeedThinking && decision.Action == newagentmodel.PlanActionDone {
|
||||
if err := emitter.EmitStatus(
|
||||
planStatusBlockID,
|
||||
planStageName,
|
||||
"deep_planning",
|
||||
"正在深入思考,生成更完善的计划。",
|
||||
false,
|
||||
); err != nil {
|
||||
return fmt.Errorf("深度规划状态推送失败: %w", err)
|
||||
}
|
||||
|
||||
deepDecision, _, deepErr := infrallm.GenerateJSON[newagentmodel.PlanDecision](
|
||||
ctx,
|
||||
input.Client,
|
||||
messages,
|
||||
infrallm.GenerateOptions{
|
||||
Temperature: 0.2,
|
||||
MaxTokens: 3200,
|
||||
Thinking: infrallm.ThinkingModeEnabled,
|
||||
Metadata: map[string]any{
|
||||
"stage": planStageName,
|
||||
"phase": "deep_planning",
|
||||
},
|
||||
},
|
||||
)
|
||||
if deepErr == nil && deepDecision != nil {
|
||||
if validateErr := deepDecision.Validate(); validateErr == nil {
|
||||
decision = deepDecision
|
||||
}
|
||||
}
|
||||
// 深度规划失败时静默降级到 Phase 1 结果,不中断流程。
|
||||
}
|
||||
|
||||
// 5. 若模型先对用户说了话,且不是 ask_user(ask_user 交给 interrupt 收口),则先以伪流式推送,再写回 history。
|
||||
// 4. 若模型先对用户说了话,且不是 ask_user(ask_user 交给 interrupt 收口),则先以伪流式推送,再写回 history。
|
||||
if strings.TrimSpace(decision.Speak) != "" && decision.Action != newagentmodel.PlanActionAskUser {
|
||||
if err := emitter.EmitPseudoAssistantText(
|
||||
ctx,
|
||||
@@ -144,7 +107,7 @@ func RunPlanNode(ctx context.Context, input PlanNodeInput) error {
|
||||
conversationContext.AppendHistory(schema.AssistantMessage(decision.Speak, nil))
|
||||
}
|
||||
|
||||
// 6. 按规划动作推进流程状态。
|
||||
// 5. 按规划动作推进流程状态。
|
||||
switch decision.Action {
|
||||
case newagentmodel.PlanActionContinue:
|
||||
flowState.Phase = newagentmodel.PhasePlanning
|
||||
@@ -169,10 +132,10 @@ func RunPlanNode(ctx context.Context, input PlanNodeInput) error {
|
||||
}
|
||||
}
|
||||
// always_execute 开启时,计划层跳过确认闸门,直接进入执行阶段。
|
||||
// 这样可以与 Execute 节点的“写工具跳过确认”语义保持一致。
|
||||
// 这样可以与 Execute 节点的"写工具跳过确认"语义保持一致。
|
||||
if input.AlwaysExecute {
|
||||
// 1. 自动执行模式不会经过 Confirm 卡片,因此这里先把完整计划明确展示给用户。
|
||||
// 2. 摘要格式复用 Confirm 节点,保证“手动确认”和“自动执行”两条链路文案一致。
|
||||
// 2. 摘要格式复用 Confirm 节点,保证"手动确认"和"自动执行"两条链路文案一致。
|
||||
// 3. 推流后同步写入历史,确保后续 Execute 阶段的上下文也能看到这份计划。
|
||||
summary := strings.TrimSpace(buildPlanSummary(decision.PlanSteps))
|
||||
if summary != "" {
|
||||
@@ -296,3 +259,12 @@ func buildPinnedPlanText(steps []newagentmodel.PlanStep) string {
|
||||
}
|
||||
return strings.TrimSpace(strings.Join(lines, "\n\n"))
|
||||
}
|
||||
|
||||
// resolveThinkingMode 根据配置布尔值返回对应的 ThinkingMode。
|
||||
// 供 plan / execute / deliver 节点统一使用。
|
||||
func resolveThinkingMode(enabled bool) infrallm.ThinkingMode {
|
||||
if enabled {
|
||||
return infrallm.ThinkingModeEnabled
|
||||
}
|
||||
return infrallm.ThinkingModeDisabled
|
||||
}
|
||||
|
||||
@@ -21,8 +21,7 @@ const planSystemPrompt = `
|
||||
5. plan_steps 必须使用自然语言,便于后端将完整 plan 重新注入到后续上下文顶部。
|
||||
6. 只输出 JSON,不要输出 markdown,不要输出额外解释,不要在 JSON 外再补文字。
|
||||
7. 每次输出前先评估任务复杂度:simple(简单明确,无复杂依赖)、moderate(多步操作,需要一定推理)、complex(需要深度推理、多方案比较或复杂依赖关系)。
|
||||
8. 根据复杂度判断 need_thinking:你是否需要深度思考才能生成高质量计划?当不确定时倾向于 false。
|
||||
9. 粗排识别规则:若满足以下两个条件,在 action=plan_done 时附加 needs_rough_build=true 和 task_class_ids:
|
||||
8. 粗排识别规则:若满足以下两个条件,在 action=plan_done 时附加 needs_rough_build=true 和 task_class_ids:
|
||||
条件1:用户输入中存在"任务类 ID"字段(见上下文"任务类 ID"部分);
|
||||
条件2:用户意图明确是"批量安排/帮我排课/把任务类排进日程"等批量调度需求。
|
||||
满足时:后端会在用户确认计划后自动运行粗排算法(硬性约束已由算法保证,无需 LLM 校验)。
|
||||
@@ -99,7 +98,6 @@ func BuildPlanDecisionContractText() string {
|
||||
- action:只能是 %s / %s / %s
|
||||
- reason:给后端和日志看的简短说明
|
||||
- complexity:任务复杂度,只能是 simple / moderate / complex
|
||||
- need_thinking:是否需要深度思考才能生成高质量计划,只能是 true / false
|
||||
- plan_steps:仅当 action=%s 时允许返回;返回时必须是完整计划,不是增量
|
||||
- plan_steps[].content:步骤正文,必填
|
||||
- plan_steps[].done_when:可选,建议写"什么情况下算这一步做完"
|
||||
@@ -112,7 +110,6 @@ func BuildPlanDecisionContractText() string {
|
||||
"action": "%s",
|
||||
"reason": "当前信息已足够继续规划",
|
||||
"complexity": "moderate",
|
||||
"need_thinking": false
|
||||
}
|
||||
|
||||
{
|
||||
@@ -120,7 +117,6 @@ func BuildPlanDecisionContractText() string {
|
||||
"action": "%s",
|
||||
"reason": "当前时间范围仍不明确",
|
||||
"complexity": "simple",
|
||||
"need_thinking": false
|
||||
}
|
||||
|
||||
{
|
||||
@@ -128,7 +124,7 @@ func BuildPlanDecisionContractText() string {
|
||||
"action": "%s",
|
||||
"reason": "当前计划已具备执行条件",
|
||||
"complexity": "simple",
|
||||
"need_thinking": false,
|
||||
|
||||
"plan_steps": [
|
||||
{
|
||||
"content": "先确认本周可用时间范围",
|
||||
|
||||
@@ -104,14 +104,10 @@ func thinkingModeToBool(mode string) bool {
|
||||
|
||||
// pickChatModel 根据请求选择模型。
|
||||
// 当前约定:
|
||||
// - strategist:策略模型;
|
||||
// - 其余值默认 worker(包含空字符串场景)。
|
||||
// - 旧链路已全面切到 newAgent graph,这里仅作为 runNormalChatFlow 回退时的模型选择入口;
|
||||
// - 统一返回 Pro 模型,旧 strategist 参数不再生效。
|
||||
func (s *AgentService) pickChatModel(requestModel string) (*ark.ChatModel, string) {
|
||||
modelName := strings.TrimSpace(requestModel)
|
||||
if strings.EqualFold(modelName, "strategist") {
|
||||
return s.AIHub.Strategist, "strategist"
|
||||
}
|
||||
return s.AIHub.Worker, "worker"
|
||||
return s.AIHub.Pro, "pro"
|
||||
}
|
||||
|
||||
// PersistChatHistory 是 Agent 聊天链路唯一的“消息持久化入口”。
|
||||
|
||||
@@ -278,15 +278,15 @@ func (s *AgentService) generateConversationTitle(ctx context.Context, history []
|
||||
}
|
||||
|
||||
// pickTitleModel 选择用于标题生成的模型。
|
||||
// 优先 worker(成本低、速度快);worker 不可用时回退 strategist。
|
||||
// 优先 Lite(成本低、速度快);Lite 不可用时回退 Pro。
|
||||
func (s *AgentService) pickTitleModel() *ark.ChatModel {
|
||||
if s.AIHub == nil {
|
||||
return nil
|
||||
}
|
||||
if s.AIHub.Worker != nil {
|
||||
return s.AIHub.Worker
|
||||
if s.AIHub.Lite != nil {
|
||||
return s.AIHub.Lite
|
||||
}
|
||||
return s.AIHub.Strategist
|
||||
return s.AIHub.Pro
|
||||
}
|
||||
|
||||
// buildConversationTitleUserPrompt 把消息历史拼成可读文本供模型总结。
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
newagenttools "github.com/LoveLosita/smartflow/backend/newAgent/tools"
|
||||
schedule "github.com/LoveLosita/smartflow/backend/newAgent/tools/schedule"
|
||||
"github.com/cloudwego/eino/schema"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
agentchat "github.com/LoveLosita/smartflow/backend/agent/chat"
|
||||
"github.com/LoveLosita/smartflow/backend/conv"
|
||||
@@ -149,10 +150,12 @@ func (s *AgentService) runNewAgentGraph(
|
||||
graphRequest.Normalize()
|
||||
|
||||
// 7. 适配 LLM clients(从 AIHub 的 ark.ChatModel 转换为 newAgent LLM Client)。
|
||||
chatClient := infrallm.WrapArkClient(s.AIHub.Worker)
|
||||
planClient := infrallm.WrapArkClient(s.AIHub.Worker)
|
||||
executeClient := infrallm.WrapArkClient(s.AIHub.Worker)
|
||||
deliverClient := infrallm.WrapArkClient(s.AIHub.Worker)
|
||||
// 7.1 Chat/Deliver 使用 Pro 模型:路由分流、闲聊、交付总结属于标准复杂度。
|
||||
// 7.2 Plan/Execute 使用 Max 模型:规划和 ReAct 循环需要深度推理能力。
|
||||
chatClient := infrallm.WrapArkClient(s.AIHub.Pro)
|
||||
planClient := infrallm.WrapArkClient(s.AIHub.Max)
|
||||
executeClient := infrallm.WrapArkClient(s.AIHub.Max)
|
||||
deliverClient := infrallm.WrapArkClient(s.AIHub.Pro)
|
||||
|
||||
// 8. 适配 SSE emitter。
|
||||
sseEmitter := newagentstream.NewSSEPayloadEmitter(outChan)
|
||||
@@ -173,6 +176,9 @@ func (s *AgentService) runNewAgentGraph(
|
||||
RoughBuildFunc: s.makeRoughBuildFunc(),
|
||||
WriteSchedulePreview: s.makeWriteSchedulePreviewFunc(),
|
||||
MemoryFuture: memoryFuture,
|
||||
ThinkingPlan: viper.GetBool("agent.thinking.plan"),
|
||||
ThinkingExecute: viper.GetBool("agent.thinking.execute"),
|
||||
ThinkingDeliver: viper.GetBool("agent.thinking.deliver"),
|
||||
}
|
||||
|
||||
// 10. 构造 AgentGraphRunInput 并运行 graph。
|
||||
@@ -190,8 +196,8 @@ func (s *AgentService) runNewAgentGraph(
|
||||
log.Printf("[ERROR] newAgent graph 执行失败 trace=%s chat=%s: %v", traceID, chatID, graphErr)
|
||||
pushErrNonBlocking(errChan, fmt.Errorf("graph 执行失败: %w", graphErr))
|
||||
|
||||
// Graph 出错时回退普通聊天,保证可用性。
|
||||
s.runNormalChatFlow(requestCtx, s.AIHub.Worker, resolvedModelName, userMessage, "", nil, retryMeta, thinkingModeToBool(thinkingMode), userID, chatID, traceID, requestStart, outChan, errChan)
|
||||
// Graph 出错时回退普通聊天,保证可用性。回退使用 Pro 模型。
|
||||
s.runNormalChatFlow(requestCtx, s.AIHub.Pro, resolvedModelName, userMessage, "", nil, retryMeta, thinkingModeToBool(thinkingMode), userID, chatID, traceID, requestStart, outChan, errChan)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -3,14 +3,6 @@ import { computed } from 'vue'
|
||||
|
||||
import type { ConversationContextStats } from '@/types/dashboard'
|
||||
|
||||
interface ContextSegment {
|
||||
key: 'msg0' | 'msg1' | 'msg2' | 'msg3'
|
||||
label: string
|
||||
value: number
|
||||
widthPercent: number
|
||||
color: string
|
||||
}
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
stats?: ConversationContextStats | null
|
||||
@@ -33,6 +25,14 @@ const usagePercent = computed(() => {
|
||||
return Math.round((safeStats.value.total / safeStats.value.budget) * 100)
|
||||
})
|
||||
|
||||
const barWidthPercent = computed(() => {
|
||||
if (!safeStats.value || safeStats.value.budget <= 0) {
|
||||
return 0
|
||||
}
|
||||
// 1. 按 total / budget 计算宽度,上限 100%(超预算时撑满进度条)。
|
||||
return Math.min(100, (safeStats.value.total / safeStats.value.budget) * 100)
|
||||
})
|
||||
|
||||
const isOverBudget = computed(() => {
|
||||
if (!safeStats.value) {
|
||||
return false
|
||||
@@ -40,31 +40,6 @@ const isOverBudget = computed(() => {
|
||||
return safeStats.value.total > safeStats.value.budget
|
||||
})
|
||||
|
||||
const segments = computed<ContextSegment[]>(() => {
|
||||
const stats = safeStats.value
|
||||
if (!stats) {
|
||||
return []
|
||||
}
|
||||
|
||||
// 1. 进度条固定做成紧凑胶囊,因此按 max(total, budget) 计算比例,既保留预算留白,也兼容超预算占满。
|
||||
// 2. 四段颜色继续对应后端 msg0~msg3 的真实语义,避免前端为了视觉压缩而打乱统计含义。
|
||||
// 3. 零值段不渲染,减少窄尺寸下的噪点,让小组件也能保留基本可读性。
|
||||
const base = Math.max(stats.total, stats.budget, 1)
|
||||
const rawSegments = [
|
||||
{ key: 'msg0', label: '规则', value: stats.msg0, color: 'linear-gradient(90deg, #2556c7, #3b82f6)' },
|
||||
{ key: 'msg1', label: '历史', value: stats.msg1, color: 'linear-gradient(90deg, #0f766e, #14b8a6)' },
|
||||
{ key: 'msg2', label: '执行', value: stats.msg2, color: 'linear-gradient(90deg, #b45309, #f59e0b)' },
|
||||
{ key: 'msg3', label: '当前', value: stats.msg3, color: 'linear-gradient(90deg, #15803d, #22c55e)' },
|
||||
] as const
|
||||
|
||||
return rawSegments
|
||||
.filter((segment) => segment.value > 0)
|
||||
.map((segment) => ({
|
||||
...segment,
|
||||
widthPercent: Math.max(0, Math.min(100, (segment.value / base) * 100)),
|
||||
}))
|
||||
})
|
||||
|
||||
const usageText = computed(() => {
|
||||
if (props.loading) {
|
||||
return '...'
|
||||
@@ -86,9 +61,7 @@ const tooltipText = computed(() => {
|
||||
return props.disabled ? '新会话发送首条消息后展示上下文窗口统计' : '当前会话暂无上下文窗口统计'
|
||||
}
|
||||
|
||||
const segmentText = segments.value.map((segment) => `${segment.label} ${segment.value}`).join(' / ')
|
||||
const usageSummary = `总计 ${safeStats.value.total} / 预算 ${safeStats.value.budget}(${usagePercent.value}%)`
|
||||
return segmentText ? `${usageSummary};${segmentText}` : usageSummary
|
||||
return `总计 ${safeStats.value.total} / 预算 ${safeStats.value.budget}(${usagePercent.value}%)`
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -106,18 +79,7 @@ const tooltipText = computed(() => {
|
||||
|
||||
<div class="assistant-context-meter__track" aria-hidden="true">
|
||||
<div v-if="loading" class="assistant-context-meter__loading-bar" />
|
||||
|
||||
<template v-else>
|
||||
<div
|
||||
v-for="segment in segments"
|
||||
:key="segment.key"
|
||||
class="assistant-context-meter__segment"
|
||||
:style="{
|
||||
width: `${segment.widthPercent}%`,
|
||||
background: segment.color,
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
<div v-else-if="barWidthPercent > 0" class="assistant-context-meter__bar" :style="{ width: `${barWidthPercent}%` }" />
|
||||
</div>
|
||||
|
||||
<span class="assistant-context-meter__value">{{ usageText }}</span>
|
||||
@@ -195,7 +157,6 @@ const tooltipText = computed(() => {
|
||||
background:
|
||||
linear-gradient(180deg, rgba(232, 238, 246, 0.95), rgba(243, 247, 251, 0.95)),
|
||||
#edf2f7;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.assistant-context-meter--disabled .assistant-context-meter__track {
|
||||
@@ -204,9 +165,15 @@ const tooltipText = computed(() => {
|
||||
#eef2f7;
|
||||
}
|
||||
|
||||
.assistant-context-meter__segment {
|
||||
.assistant-context-meter__bar {
|
||||
height: 100%;
|
||||
flex: 0 0 auto;
|
||||
border-radius: inherit;
|
||||
background: linear-gradient(90deg, #2556c7, #3b82f6);
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.assistant-context-meter--danger .assistant-context-meter__bar {
|
||||
background: linear-gradient(90deg, #b42318, #ef4444);
|
||||
}
|
||||
|
||||
.assistant-context-meter__loading-bar {
|
||||
|
||||
@@ -48,7 +48,6 @@ interface StreamEventPayload {
|
||||
error?: StreamErrorPayload
|
||||
}
|
||||
|
||||
type ModelType = 'worker' | 'strategist'
|
||||
|
||||
interface ConversationGroup {
|
||||
key: string
|
||||
@@ -86,7 +85,7 @@ const conversationLoadingMore = ref(false)
|
||||
const chatLoading = ref(false)
|
||||
const historyExpanded = ref(true)
|
||||
const selectedConversationId = ref('')
|
||||
const selectedModel = ref<ModelType>('worker')
|
||||
|
||||
const selectedThinkingMode = ref<ThinkingModeType>('auto')
|
||||
const messageInput = ref('')
|
||||
const historyPanelWidth = ref(props.initialHistoryWidth)
|
||||
@@ -120,7 +119,7 @@ const quickActions = [
|
||||
'给我一个更稳妥的推进方案',
|
||||
]
|
||||
|
||||
const MODEL_PREFERENCE_STORAGE_KEY = 'smartflow.assistant.model.byConversation.v1'
|
||||
|
||||
const DEFAULT_PLANNING_PROMPT = '请基于这些任务类帮我做一版智能编排。'
|
||||
|
||||
let messageScrollRaf = 0
|
||||
@@ -336,85 +335,6 @@ const contextStatsDisabled = computed(() => {
|
||||
return !selectedConversationId.value || isDraftConversationId(selectedConversationId.value)
|
||||
})
|
||||
|
||||
function isModelType(value: unknown): value is ModelType {
|
||||
return value === 'worker' || value === 'strategist'
|
||||
}
|
||||
|
||||
function loadModelPreferenceMap() {
|
||||
if (typeof window === 'undefined') {
|
||||
return {} as Record<string, ModelType>
|
||||
}
|
||||
|
||||
try {
|
||||
const raw = window.localStorage.getItem(MODEL_PREFERENCE_STORAGE_KEY)
|
||||
if (!raw) {
|
||||
return {} as Record<string, ModelType>
|
||||
}
|
||||
|
||||
const parsed = JSON.parse(raw) as unknown
|
||||
const normalized: Record<string, ModelType> = {}
|
||||
const entries = typeof parsed === 'object' && parsed ? Object.entries(parsed) : []
|
||||
|
||||
// 1. 只接收结构合法且值在白名单内的记录,避免脏数据把模型值污染为非法字符串。
|
||||
// 2. 键为空字符串的记录直接丢弃,防止“新建会话未落库”场景写入无效索引。
|
||||
// 3. 解析失败时回退为空对象,不阻塞聊天主流程。
|
||||
for (const [conversationId, model] of entries) {
|
||||
if (!conversationId || !isModelType(model)) {
|
||||
continue
|
||||
}
|
||||
normalized[conversationId] = model
|
||||
}
|
||||
|
||||
return normalized
|
||||
} catch {
|
||||
return {} as Record<string, ModelType>
|
||||
}
|
||||
}
|
||||
|
||||
const modelPreferenceMap = ref<Record<string, ModelType>>(loadModelPreferenceMap())
|
||||
|
||||
function persistModelPreferenceMap() {
|
||||
if (typeof window === 'undefined') {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
window.localStorage.setItem(MODEL_PREFERENCE_STORAGE_KEY, JSON.stringify(modelPreferenceMap.value))
|
||||
} catch {
|
||||
// 1. 本地存储失败只影响“记忆体验”,不影响消息收发主链路。
|
||||
// 2. 这里静默处理,避免用户每次切模型都被错误提示打断。
|
||||
// 3. 若用户清理缓存或隐私模式限制写入,后续会自动退化为会话内临时选择。
|
||||
}
|
||||
}
|
||||
|
||||
function savePreferredModel(conversationId: string, model: ModelType) {
|
||||
if (!conversationId || modelPreferenceMap.value[conversationId] === model) {
|
||||
return
|
||||
}
|
||||
|
||||
modelPreferenceMap.value = {
|
||||
...modelPreferenceMap.value,
|
||||
[conversationId]: model,
|
||||
}
|
||||
persistModelPreferenceMap()
|
||||
}
|
||||
|
||||
function resolvePreferredModel(conversationId: string) {
|
||||
if (!conversationId) {
|
||||
return null
|
||||
}
|
||||
|
||||
return modelPreferenceMap.value[conversationId] ?? null
|
||||
}
|
||||
|
||||
function applyPreferredModelForConversation(conversationId: string) {
|
||||
const preferredModel = resolvePreferredModel(conversationId)
|
||||
if (!preferredModel || preferredModel === selectedModel.value) {
|
||||
return
|
||||
}
|
||||
|
||||
selectedModel.value = preferredModel
|
||||
}
|
||||
|
||||
function ensureConversationBucket(conversationId: string) {
|
||||
if (!conversationMessagesMap[conversationId]) {
|
||||
@@ -476,16 +396,6 @@ function migrateConversationState(fromConversationId: string, toConversationId:
|
||||
delete conversationMetaMap[fromConversationId]
|
||||
}
|
||||
|
||||
if (modelPreferenceMap.value[fromConversationId]) {
|
||||
const migratedModelMap = { ...modelPreferenceMap.value }
|
||||
if (!migratedModelMap[toConversationId]) {
|
||||
migratedModelMap[toConversationId] = migratedModelMap[fromConversationId]!
|
||||
}
|
||||
delete migratedModelMap[fromConversationId]
|
||||
modelPreferenceMap.value = migratedModelMap
|
||||
persistModelPreferenceMap()
|
||||
}
|
||||
|
||||
const latestMap = new Map<string, ConversationListItem>()
|
||||
const deduplicated: ConversationListItem[] = []
|
||||
const seen = new Set<string>()
|
||||
@@ -1299,7 +1209,6 @@ async function loadConversationContextStats(conversationId: string, forceReload
|
||||
async function selectConversation(conversationId: string) {
|
||||
cancelEditUserMessage()
|
||||
selectedConversationId.value = conversationId
|
||||
applyPreferredModelForConversation(conversationId)
|
||||
await Promise.allSettled([
|
||||
loadConversationMessages(conversationId),
|
||||
ensureConversationMeta(conversationId),
|
||||
@@ -1502,7 +1411,7 @@ async function streamAssistantReply(
|
||||
const response = await fetchChatStream({
|
||||
conversation_id: isDraftConversationId(draftConversationId) ? undefined : draftConversationId,
|
||||
message: text,
|
||||
model: selectedModel.value,
|
||||
model: 'worker',
|
||||
thinking: selectedThinkingMode.value,
|
||||
extra: requestExtra,
|
||||
})
|
||||
@@ -1577,8 +1486,6 @@ async function sendMessage(preset?: string) {
|
||||
if (!selectedConversationId.value || shouldStartFreshPlanningConversation) {
|
||||
selectedConversationId.value = draftConversationId
|
||||
}
|
||||
savePreferredModel(draftConversationId, selectedModel.value)
|
||||
|
||||
ensureConversationBucket(draftConversationId)
|
||||
unavailableHistoryMap[draftConversationId] = false
|
||||
|
||||
@@ -1734,16 +1641,6 @@ watch(
|
||||
},
|
||||
)
|
||||
|
||||
watch(
|
||||
selectedModel,
|
||||
(nextModel) => {
|
||||
const conversationId = selectedConversationId.value
|
||||
if (!conversationId) {
|
||||
return
|
||||
}
|
||||
savePreferredModel(conversationId, nextModel)
|
||||
},
|
||||
)
|
||||
|
||||
onMounted(async () => {
|
||||
reasoningTicker = window.setInterval(() => {
|
||||
@@ -2126,20 +2023,6 @@ onBeforeUnmount(() => {
|
||||
</el-select>
|
||||
</div>
|
||||
|
||||
<div class="assistant-toolbar__pill assistant-toolbar__pill--select assistant-toolbar__pill--ds-model">
|
||||
<span class="assistant-toolbar__select-label">模型</span>
|
||||
<el-select
|
||||
v-model="selectedModel"
|
||||
class="assistant-toolbar__select-box"
|
||||
size="small"
|
||||
popper-class="assistant-model-select-panel"
|
||||
placement="top-start"
|
||||
:teleported="true"
|
||||
>
|
||||
<el-option value="worker" label="标准" />
|
||||
<el-option value="strategist" label="策略" />
|
||||
</el-select>
|
||||
</div>
|
||||
|
||||
<ContextWindowMeter
|
||||
class="assistant-toolbar__context-meter"
|
||||
@@ -3183,7 +3066,6 @@ onBeforeUnmount(() => {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.assistant-toolbar__pill--ds-model,
|
||||
.assistant-toolbar__pill--ds-thinking {
|
||||
height: 32px;
|
||||
padding: 0 8px 0 10px;
|
||||
@@ -3200,10 +3082,6 @@ onBeforeUnmount(() => {
|
||||
min-width: 138px;
|
||||
}
|
||||
|
||||
.assistant-toolbar__pill--ds-model {
|
||||
min-width: 144px;
|
||||
}
|
||||
|
||||
.assistant-toolbar__context-meter {
|
||||
width: 144px;
|
||||
min-width: 144px;
|
||||
@@ -3435,30 +3313,5 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
.assistant-model-select-panel.el-popper {
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(15, 23, 42, 0.1);
|
||||
box-shadow: 0 10px 28px rgba(15, 23, 42, 0.14);
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
.assistant-model-select-panel .el-select-dropdown__item {
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
border-radius: 8px;
|
||||
padding: 0 12px;
|
||||
color: #4d5d73;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.assistant-model-select-panel .el-select-dropdown__item.hover,
|
||||
.assistant-model-select-panel .el-select-dropdown__item:hover {
|
||||
background: rgba(51, 95, 194, 0.1);
|
||||
}
|
||||
|
||||
.assistant-model-select-panel .el-select-dropdown__item.is-selected {
|
||||
color: #2f56b0;
|
||||
background: rgba(51, 95, 194, 0.16);
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user