From cc98b62ad8687138da2716f43cd6fc32963df258 Mon Sep 17 00:00:00 2001 From: Losita <2810873701@qq.com> Date: Fri, 8 May 2026 18:29:49 +0800 Subject: [PATCH] Version: 0.9.83.dev.260508 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 后端: 1.关闭了注册接口 前端: 1.改造了主页 仓库: 1.对部署做了一些改善 --- .env.full.example | 3 + backend/config.docker.yaml | 1 + backend/config.example.yaml | 1 + backend/gateway/api/userauth/handler.go | 33 +- backend/gateway/router/router.go | 15 +- backend/services/agent/node/execute/run.go | 252 +++++- deploy/certs/.gitignore | 3 + deploy/certs/README.md | 15 + docker-compose.full.yml | 5 + frontend/nginx.conf | 24 +- frontend/src/views/HomeView.vue | 848 +++++++++++++-------- 11 files changed, 854 insertions(+), 346 deletions(-) create mode 100644 deploy/certs/.gitignore create mode 100644 deploy/certs/README.md diff --git a/.env.full.example b/.env.full.example index 517602f..edd28b6 100644 --- a/.env.full.example +++ b/.env.full.example @@ -6,6 +6,8 @@ SMARTFLOW_BACKEND_IMAGE=smartflow/backend-suite:latest SMARTFLOW_FRONTEND_IMAGE=smartflow/frontend:latest +ARK_API_KEY= +SMARTFLOW_USERAUTH_ALLOWREGISTER=false SMARTFLOW_NOTIFICATION_FRONTENDBASEURL=https://smartflow.example.com SMARTFLOW_CORS_ALLOWEDORIGINS=http://localhost:5173,https://smartflow.example.com @@ -19,6 +21,7 @@ SMARTFLOW_ATTU_IMAGE=zilliz/attu:v2.4.3 SMARTFLOW_API_PORT=8080 SMARTFLOW_FRONTEND_PORT=80 +SMARTFLOW_FRONTEND_HTTPS_PORT=443 SMARTFLOW_MINIO_API_PORT=9000 SMARTFLOW_MINIO_CONSOLE_PORT=9001 SMARTFLOW_MILVUS_PORT=19530 diff --git a/backend/config.docker.yaml b/backend/config.docker.yaml index 58cf44f..71dd998 100644 --- a/backend/config.docker.yaml +++ b/backend/config.docker.yaml @@ -31,6 +31,7 @@ redis: password: "redis_password_789" userauth: + allowRegister: false rpc: listenOn: "0.0.0.0:9081" endpoints: diff --git a/backend/config.example.yaml b/backend/config.example.yaml index 515cfe6..58468cb 100644 --- a/backend/config.example.yaml +++ b/backend/config.example.yaml @@ -36,6 +36,7 @@ redis: # user/auth zrpc 独立服务与网关客户端配置。 userauth: + allowRegister: false rpc: listenOn: "0.0.0.0:9081" endpoints: diff --git a/backend/gateway/api/userauth/handler.go b/backend/gateway/api/userauth/handler.go index 1f32e7c..75bfc8f 100644 --- a/backend/gateway/api/userauth/handler.go +++ b/backend/gateway/api/userauth/handler.go @@ -14,15 +14,17 @@ import ( ) type UserHandler struct { - client ports.UserCommandClient - captcha *GeeTestService + client ports.UserCommandClient + captcha *GeeTestService + allowRegister bool } // NewUserHandler 只接收 user/auth 客户端与验证码服务,不再直接依赖本地 user service。 -func NewUserHandler(client ports.UserCommandClient, captcha *GeeTestService) *UserHandler { +func NewUserHandler(client ports.UserCommandClient, captcha *GeeTestService, allowRegister bool) *UserHandler { return &UserHandler{ - client: client, - captcha: captcha, + client: client, + captcha: captcha, + allowRegister: allowRegister, } } @@ -39,6 +41,10 @@ func (api *UserHandler) CaptchaRegister(c *gin.Context) { } func (api *UserHandler) UserRegister(c *gin.Context) { + if !api.ensureRegisterEnabled(c) { + return + } + var req registerRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, respond.WrongParamType) @@ -129,3 +135,20 @@ func (api *UserHandler) UserLogout(c *gin.Context) { } c.JSON(http.StatusOK, respond.Ok) } + +// ensureRegisterEnabled 负责统一收口“注册相关入口”的开关判断。 +// 职责边界: +// 1. 只判断当前环境是否允许注册,并向前端返回明确的 403; +// 2. 不负责登录、刷新 token、登出等其它 user/auth 能力; +// 3. 不触发验证码或 RPC 调用,避免“已关闭注册”时仍向下游产生无效流量。 +func (api *UserHandler) ensureRegisterEnabled(c *gin.Context) bool { + if api.allowRegister { + return true + } + + c.JSON(http.StatusForbidden, respond.Response{ + Status: "40301", + Info: "registration is disabled", + }) + return false +} diff --git a/backend/gateway/router/router.go b/backend/gateway/router/router.go index 17c915c..6f5f098 100644 --- a/backend/gateway/router/router.go +++ b/backend/gateway/router/router.go @@ -85,7 +85,11 @@ func RegisterRouters( }) }) - userauthapi.RegisterRoutes(apiGroup, userauthapi.NewUserHandler(authClient, userauthapi.NewGeeTestServiceFromConfig()), authClient, limiter) + userauthapi.RegisterRoutes(apiGroup, userauthapi.NewUserHandler( + authClient, + userauthapi.NewGeeTestServiceFromConfig(), + readUserAuthAllowRegister(), + ), authClient, limiter) forumapi.RegisterRoutes(apiGroup, forumapi.NewHandler(forumClient), authClient, cache, limiter) tokenstoreapi.RegisterRoutes(apiGroup, tokenstoreapi.NewHandler(tokenStoreClient), authClient, cache, limiter) @@ -231,3 +235,12 @@ func compactConfigList(values []string) []string { } return result } + +func readUserAuthAllowRegister() bool { + // 1. 缺省保持“允许注册”,避免历史未补配置的环境升级后被静默改行为。 + // 2. 只有显式把 userauth.allowRegister 设成 false,才真正关闭注册入口。 + if !viper.IsSet("userauth.allowRegister") { + return true + } + return viper.GetBool("userauth.allowRegister") +} diff --git a/backend/services/agent/node/execute/run.go b/backend/services/agent/node/execute/run.go index 87730bc..8e4d1d7 100644 --- a/backend/services/agent/node/execute/run.go +++ b/backend/services/agent/node/execute/run.go @@ -13,55 +13,219 @@ import ( llmservice "github.com/LoveLosita/smartflow/backend/services/llm" ) +// execute 阶段常量: +// 1. 统一约束 execute 节点在流式输出、上下文挂载、历史标记中的固定标识; +// 2. 这些值会在 prompt、stream、history、tool runtime 多处复用,集中定义可避免字符串散落; +// 3. 报告里可将其理解为 execute 节点的“协议层元数据”,负责让编排层、展示层和执行层说同一种语言。 const ( - executeStageName = "execute" - executeStatusBlockID = "execute.status" - executeSpeakBlockID = "execute.speak" - executePinnedKey = "execution_context" - toolAnalyzeHealth = "analyze_health" - executeHistoryKindKey = "newagent_history_kind" + // executeStageName 是 graph/node/stream 共同识别的执行阶段名。 + // 调用目的:用于状态推送、日志埋点、终止态写入时标识“当前发生在 execute 阶段”。 + executeStageName = "execute" + + // executeStatusBlockID 是前端状态区块 ID。 + // 调用目的:把“正在执行第几步”“工具执行中”等状态稳定写到同一个可更新区块,避免重复刷屏。 + executeStatusBlockID = "execute.status" + + // executeSpeakBlockID 是 execute 阶段可见回复文本的输出区块 ID。 + // 调用目的:让模型在 execute 阶段产生的正文、补发文本、thinking 摘要归并到同一个展示区域。 + executeSpeakBlockID = "execute.speak" + + // executePinnedKey 是 execute 专属 pinned context 的 key。 + // 调用目的:把“执行模式、计划步、done_when、轮次预算”等关键信息固定挂到会话上下文,供后续每轮提示词复用。 + executePinnedKey = "execution_context" + + // toolAnalyzeHealth 是粗排后健康度分析工具名。 + // 调用目的:在主动优化链路中复用固定工具标识,避免分支判断时手写魔法字符串。 + toolAnalyzeHealth = "analyze_health" + + // executeHistoryKindKey 是 execute 阶段写入 history extra 时使用的分类 key。 + // 调用目的:给某些“无正文但有语义”的历史记录打标签,方便后续节点识别这是系统插入的流程标记。 + executeHistoryKindKey = "newagent_history_kind" + + // executeHistoryKindStepAdvanced 表示“计划步骤已推进”的历史标记值。 + // 调用目的:在 next_plan 后往历史里写一个轻量哨兵,避免重复推进时无法判断上一步是否已切换。 executeHistoryKindStepAdvanced = "execute_step_advanced" + // maxConsecutiveCorrections 是 execute 阶段允许的最大连续纠错次数。 + // 调用目的:限制模型在 JSON 解析失败、动作不合法、goal_check 缺失等异常情况下的自我修正轮数,防止无限空转。 maxConsecutiveCorrections = 3 ) +// ExecuteNodeInput 描述 execute 节点单轮运行所需的全部输入依赖。 +// +// 职责边界: +// 1. 负责承载 execute 节点真正运行时要读到的“状态 + 上下文 + 模型 + 工具 + 输出能力”; +// 2. 不负责 graph 拓扑与分支决策,这些仍由 compose.Graph 和 branchAfterExecute 管理; +// 3. 不负责具体工具实现或 LLM 协议细节,它只是 node 层与下游能力之间的依赖注入容器。 +// +// 报告可重点说明: +// 1. 这个结构体体现了本项目如何把 Eino agent 的单节点执行显式建模为一个“有完整依赖注入边界”的入口; +// 2. 与把所有依赖散落在全局变量不同,这种做法更便于恢复执行、单测替身注入和不同模型/工具组合切换; +// 3. execute 节点能否安全回环,核心就在于这些输入把“状态、上下文、工具、输出”完整串起来了。 type ExecuteNodeInput struct { - RuntimeState *agentmodel.AgentRuntimeState - ConversationContext *agentmodel.ConversationContext - UserInput string - Client *llmservice.Client - ChunkEmitter *agentstream.ChunkEmitter - ResumeNode string - ToolRegistry *agenttools.ToolRegistry - ScheduleState *schedule.ScheduleState - CompactionStore agentmodel.CompactionStore - WriteSchedulePreview agentmodel.WriteSchedulePreviewFunc + // RuntimeState 是 execute 节点的主运行态容器。 + // 1. 负责承载 CommonState、待确认工具、待追问交互等跨轮次状态; + // 2. execute 节点对 phase、round、plan step、pending interaction 的修改都会落到这里; + // 3. 若为空则无法恢复和推进 agent 执行,因此 prepareExecuteNodeInput 会把它视为必填依赖。 + RuntimeState *agentmodel.AgentRuntimeState + + // ConversationContext 是当前会话的统一上下文快照。 + // 1. 负责保存历史消息、pinned block、工具 schema 等 prompt 装配素材; + // 2. execute 每一轮都会基于它重建 Eino message; + // 3. 它不是持久化存储本身,而是当前 graph 运行时使用的内存态上下文。 + ConversationContext *agentmodel.ConversationContext + + // UserInput 是本轮用户原始输入文本。 + // 1. 主要用于让 execute 节点在必要时回看用户当前明确意图; + // 2. 不直接等价于最终 prompt,全量消息仍由 BuildExecuteMessages 统一组织; + // 3. 保留该字段是为了让 execute 在恢复、追问、纠错时仍能感知本轮入口意图。 + UserInput string + + // Client 是 execute 阶段专用 LLM 客户端。 + // 1. 负责发起流式推理请求,生成结构化 SMARTFLOW_DECISION; + // 2. 它可以与 chat / plan / deliver 阶段使用不同模型配置; + // 3. 若未注入,execute 节点无法完成“思考下一步”的核心职责。 + Client *llmservice.Client + + // ChunkEmitter 是流式输出器。 + // 1. 负责把状态、正文、thinking 摘要、工具调用过程实时推给前端; + // 2. execute 节点的大部分用户可见体验都经由它完成; + // 3. 即使外部没接真实前端,系统也会兜底一个 noop emitter,保证流程代码无需处处判空。 + ChunkEmitter *agentstream.ChunkEmitter + + // ResumeNode 表示挂起交互恢复后应回到的节点名。 + // 1. 主要用于 ask_user / confirm 之后恢复 graph 时定位回 execute; + // 2. 它是 pending interaction 与 graph 路由之间的衔接信息; + // 3. 当前通常写为 "execute",便于中断后继续原执行链路。 + ResumeNode string + + // ToolRegistry 是 execute 阶段可调用工具的统一注册表。 + // 1. 负责提供工具 schema、工具可见性判断和真正的工具执行入口; + // 2. execute 不直接依赖具体工具实现,而是通过注册表做统一调度; + // 3. 这让 agent 的工具能力可以按域动态裁剪,而不必修改 execute 主流程。 + ToolRegistry *agenttools.ToolRegistry + + // ScheduleState 是当前排程态快照。 + // 1. 只有涉及日程构建、挪动、交换等排程工具时才会被真正消费; + // 2. 首轮 execute 会对它做必要初始化,后续轮次复用同一份内存状态; + // 3. 该字段为空时,依赖日程状态的工具会被阻止执行,避免工具在残缺状态上落地。 + ScheduleState *schedule.ScheduleState + + // CompactionStore 是消息压缩存储依赖。 + // 1. 当上下文过长时,execute 会通过它记录或读取统一压缩结果; + // 2. 目的是在长链路中控制 token 成本,同时尽量保留关键执行语义; + // 3. 这是长流程 agent 可持续运行的重要辅助能力,而非核心业务状态。 + CompactionStore agentmodel.CompactionStore + + // WriteSchedulePreview 是排程预览写入函数。 + // 1. 当 execute 阶段调用写排程工具后,可实时把最新预览写给前端/缓存; + // 2. 它只负责预览落盘,不负责真正修改 graph 路由或业务状态; + // 3. 这样用户在确认或微调过程中就能看到中间结果,而不必等到最终 deliver。 + WriteSchedulePreview agentmodel.WriteSchedulePreviewFunc + + // OriginalScheduleState 是进入本轮执行前的原始排程快照。 + // 1. 主要用于需要对比、确认、恢复时保留“变更前基线”; + // 2. executePendingTool 等恢复场景可能会用到这份原始视角; + // 3. 它不是当前正在被修改的工作态,当前工作态仍是 ScheduleState。 OriginalScheduleState *schedule.ScheduleState - AlwaysExecute bool - ThinkingEnabled bool + + // AlwaysExecute 表示是否跳过确认直接执行写工具。 + // 1. 为 true 时,confirm 类动作可以直接落地,不再等待用户二次确认; + // 2. 为 false 时,高风险写工具需先转成 pending confirm,遵守安全确认链路; + // 3. 这给不同入口场景(自动执行/显式确认)保留了统一的 execute 主流程。 + AlwaysExecute bool + + // ThinkingEnabled 控制 execute 阶段是否开启 reasoning/thinking 模式。 + // 1. 它影响 LLM 请求时的推理模式选择; + // 2. 开启后系统会额外汇总 reasoning 摘要并推送给前端; + // 3. 该能力只影响模型推理表现,不改变 execute 节点的状态机协议。 + ThinkingEnabled bool + + // PersistVisibleMessage 是可见消息持久化函数。 + // 1. 负责把应落库的 assistant 可见回复保存下来,供后续历史回放; + // 2. execute 不直接操作数据库,而是通过该函数把持久化职责下沉; + // 3. 这样 node 层仍保持“编排与调度”为主,不把存储细节耦合进来。 PersistVisibleMessage agentmodel.PersistVisibleMessageFunc } +// ExecuteRoundObservation 描述 execute 阶段单轮观测结果。 +// +// 职责边界: +// 1. 负责记录一轮 execute 中“模型做了什么决策、调用了什么工具、结果如何”; +// 2. 不负责承载完整上下文或最终业务结果,它更偏向调试、回放、分析视角; +// 3. 该结构体的价值在于把 agent 的单轮行为沉淀成可审计、可解释的数据切片。 type ExecuteRoundObservation struct { - Round int `json:"round"` - StepIndex int `json:"step_index"` - GoalCheck string `json:"goal_check,omitempty"` - Decision string `json:"decision,omitempty"` - ToolName string `json:"tool_name,omitempty"` - ToolParams string `json:"tool_params,omitempty"` - ToolSuccess bool `json:"tool_success"` - ToolResult string `json:"tool_result,omitempty"` + // Round 是执行轮次编号。 + // 调用目的:标识这是第几轮 execute 回环,便于分析 agent 是否在某一步反复空转。 + Round int `json:"round"` + + // StepIndex 是当前计划步骤索引。 + // 调用目的:把本轮行为与 plan 中的具体步骤关联起来,支持“第几步发生了什么”这类复盘。 + StepIndex int `json:"step_index"` + + // GoalCheck 是模型在 next_plan / done 时给出的完成性核验说明。 + // 调用目的:保留“为什么它认为当前步骤已完成”的证据文本,增强 agent 行为可解释性。 + GoalCheck string `json:"goal_check,omitempty"` + + // Decision 是本轮结构化决策动作摘要。 + // 调用目的:记录 action 层面的选择结果,如 continue / confirm / ask_user / done。 + Decision string `json:"decision,omitempty"` + + // ToolName 是本轮调用的工具名。 + // 调用目的:标识 agent 在这一轮真正把哪项外部能力接入执行链。 + ToolName string `json:"tool_name,omitempty"` + + // ToolParams 是工具调用参数的可读化快照。 + // 调用目的:辅助排查“工具为什么执行出这个结果”,但它只保留观测视图,不替代真实原始参数对象。 + ToolParams string `json:"tool_params,omitempty"` + + // ToolSuccess 表示工具是否执行成功。 + // 调用目的:在轮次观测里快速区分“正常推进”与“工具失败/被阻断”的分支结果。 + ToolSuccess bool `json:"tool_success"` + + // ToolResult 是工具返回结果的摘要文本。 + // 调用目的:保留工具观察结果,供下一轮上下文构造、日志调试或报告中的执行链路说明使用。 + ToolResult string `json:"tool_result,omitempty"` } +// RunExecuteNode 是 Eino agent graph 中 execute 节点的“单轮执行入口”。 +// +// 职责边界: +// 1. 负责把 graph 层传入的运行态、会话上下文、工具目录、流式输出器等依赖整理成一次可执行的 execute 轮次。 +// 2. 负责完成 execute 阶段的一次完整闭环:状态校准 -> 提示词装配 -> LLM 决策 -> 决策落地。 +// 3. 不负责定义 Eino graph 的节点拓扑;节点注册、连边与回环由 graph/common_graph.go 中的 compose.Graph 统一管理。 +// 4. 不负责具体工具实现和流式 JSON 解析细节;工具执行下沉到 tool_runtime.go,模型决策采集下沉到 action_router.go。 +// +// 报告可重点说明: +// 1. 这个函数体现了“Graph 负责编排、Node 负责单轮执行”的 Eino 接入方式。 +// 2. execute 节点不是直接把用户请求一次性做完,而是以“轮次”为单位驱动 ReAct/Plan-and-Execute 风格的 agent 闭环。 +// 3. 每一轮都会基于当前状态重建 Eino message,上下文、计划进度、工具域、历史观察都会重新送入模型,让模型只决定“下一步该做什么”。 func RunExecuteNode(ctx context.Context, input ExecuteNodeInput) error { + // 1. 先做 execute 节点入参归一化: + // 1.1 这里会兜底 RuntimeState / ConversationContext / ChunkEmitter,避免 graph 恢复执行或单测场景出现空指针; + // 1.2 execute 节点后续所有状态推进都依赖这三类对象,因此必须在第一步统一校准; + // 1.3 若最基础的依赖(如 RuntimeState、LLM Client)缺失,直接返回错误,避免带着残缺上下文进入模型决策。 runtimeState, conversationContext, emitter, err := prepareExecuteNodeInput(input) if err != nil { return err } + // 2. 取出 graph 内部跨节点共享的 CommonState。 + // 2.1 Chat / Plan / RoughBuild / Execute 都围绕这份状态协作; + // 2.2 execute 节点真正关心的“当前阶段、计划步、轮次预算、工具域、终止态”等信息都挂在这里; + // 2.3 这也是 Eino graph 在多节点之间传递 agent 运行语义的核心载体。 flowState := runtimeState.EnsureCommonState() + + // 3. 应用待生效的上下文 hook。 + // 3.1 上游节点(如 plan / rough_build)可能只先写入 PendingContextHook,而不立刻改 ActiveToolDomain; + // 3.2 execute 节点在真正请求模型前统一消费这个 hook,保证“提示词里的规则”和“模型看见的工具 schema”同轮生效; + // 3.3 若这里不先同步,模型第一轮可能拿到旧工具域,导致它想做的动作与实际可见工具不一致。 applyPendingContextHook(flowState) + // 4. 若当前存在“待确认写工具”,说明上一轮已经完成了 LLM 决策,本轮是用户确认后的恢复执行。 + // 4.1 这时不应该再让模型重新思考,否则会破坏“确认后执行原工具”的事务语义; + // 4.2 因此这里直接短路进入 executePendingTool,把已确认的工具参数原样执行; + // 4.3 这也是本项目把“模型决策”和“高风险写操作落地”显式拆开的关键安全设计。 if runtimeState.PendingConfirmTool != nil { return executePendingTool( ctx, @@ -75,12 +239,24 @@ func RunExecuteNode(ctx context.Context, input ExecuteNodeInput) error { ) } + // 5. 首轮进入 execute 且携带日程状态时,重置任务处理队列。 + // 5.1 这样可以保证一次新的执行会话从干净的调度处理顺序开始; + // 5.2 只在 RoundUsed == 0 时重置,避免多轮 execute 回环过程中把中间状态反复抹掉; + // 5.3 这类“首轮初始化、后续轮次复用”的策略,是 agent 在长流程里保持状态连续性的基础。 if input.ScheduleState != nil && flowState.RoundUsed == 0 { schedule.ResetTaskProcessingQueue(input.ScheduleState) } + // 6. 把 execute 阶段的关键信息同步到会话 pinned context。 + // 6.1 这里会把执行模式、当前计划步、done_when、轮次预算等内容写成可复用的上下文块; + // 6.2 后续 BuildExecuteMessages 会把这些 pinned block 一并装配进 Eino message; + // 6.3 这样模型每轮都能看到“当前执行到哪一步、什么算完成”,避免在长对话里丢失阶段感。 syncExecutePinnedContext(conversationContext, flowState) + // 7. 先向前端推送 execute 阶段状态,让用户看到 agent 当前正在做什么。 + // 7.1 若当前处于 plan 模式,就明确展示“第几步 / 共几步 + 当前步骤摘要”; + // 7.2 若没有计划,则退化为通用“正在处理请求”文案; + // 7.3 这里的状态推送不影响 graph 路由,但会直接影响产品侧的可观察性与交互体验。 if flowState.HasCurrentPlanStep() { current, total := flowState.PlanProgress() currentStep, _ := flowState.CurrentPlanStep() @@ -105,6 +281,10 @@ func RunExecuteNode(ctx context.Context, input ExecuteNodeInput) error { } } + // 8. 进入真正的模型决策前,先消费一轮执行预算。 + // 8.1 execute 节点在 Eino graph 中是允许回环的;branchAfterExecute 会在未完成时再次回到本节点; + // 8.2 NextRound() 失败时,不再继续请求模型,而是把“达到安全轮次上限”的终止原因写入状态; + // 8.3 这样 deliver 节点后续收口时可以读取统一的 terminal outcome,避免无限循环或异常失控。 if !flowState.NextRound() { flowState.Exhaust( executeStageName, @@ -114,7 +294,16 @@ func RunExecuteNode(ctx context.Context, input ExecuteNodeInput) error { return nil } + // 9. 基于当前 flowState + ConversationContext 重新构建本轮要发给模型的消息。 + // 9.1 BuildExecuteMessages 会产出 []*schema.Message,这是 Eino 模型调用使用的标准消息结构; + // 9.2 消息里不仅有历史对话,还会带上 execute 专属系统提示词、计划步骤约束、工具 schema、pinned context; + // 9.3 这一步体现了本项目的核心思想:不是把 Agent 写死成 if/else,而是把“当前可行动空间”编码进 prompt + state。 messages := agentprompt.BuildExecuteMessages(flowState, conversationContext) + + // 10. 若上下文过长,则在真正调用模型前统一压缩消息。 + // 10.1 压缩逻辑仍围绕统一的 Eino message 结构工作,不破坏后续模型调用接口; + // 10.2 这样既能控制 token 成本,又能尽量保住当前步骤、工具观察、关键 pinned 信息; + // 10.3 对长流程 agent 来说,这一步是“让多轮执行可持续”的关键工程补偿层。 messages = agentshared.CompactUnifiedMessagesIfNeeded(ctx, messages, agentshared.UnifiedCompactInput{ Client: input.Client, CompactionStore: input.CompactionStore, @@ -124,8 +313,15 @@ func RunExecuteNode(ctx context.Context, input ExecuteNodeInput) error { StatusBlockID: executeStatusBlockID, }) + // 11. 在发起模型请求前记录本轮 LLM 上下文快照,便于排查“为什么模型会做出这个动作”。 + // 11.1 这类日志不参与业务逻辑,但对调试 agent prompt、状态迁移和工具选择非常重要; + // 11.2 尤其在 execute 回环链路里,它能帮助我们复盘每一轮看到的上下文是否一致。 agentshared.LogNodeLLMContext(executeStageName, "decision", flowState, messages) + // 12. 调用模型,流式采集 execute 决策。 + // 12.1 下游会使用 Eino 风格的 message 输入向 LLM 发起 Stream 请求; + // 12.2 模型需要输出 {...} 结构化决策,再附带可见回答; + // 12.3 这里拿到的不是“最终答案”,而是 agent 下一步动作的机器可执行描述:继续读工具、请求确认、追问用户、推进计划或结束。 decisionOutput, err := collectExecuteDecisionFromLLM( ctx, input, @@ -138,6 +334,10 @@ func RunExecuteNode(ctx context.Context, input ExecuteNodeInput) error { return err } + // 13. 把模型决策真正落到运行时。 + // 13.1 handleExecuteDecision 会校验 action 合法性,并根据决策修改 flowState / runtimeState / conversation history; + // 13.2 若是 continue + tool_call,会执行读工具或普通工具;若是 confirm,会挂起等待用户确认;若是 next_plan / done,会推进计划或写入终止态; + // 13.3 至此,一次 execute 节点的“思考 + 行动”闭环完成,Eino graph 再根据更新后的状态决定下一跳是回到 execute、转 confirm,还是进入 deliver。 return handleExecuteDecision( ctx, input, diff --git a/deploy/certs/.gitignore b/deploy/certs/.gitignore new file mode 100644 index 0000000..7c9d611 --- /dev/null +++ b/deploy/certs/.gitignore @@ -0,0 +1,3 @@ +* +!.gitignore +!README.md diff --git a/deploy/certs/README.md b/deploy/certs/README.md new file mode 100644 index 0000000..ea9b2bf --- /dev/null +++ b/deploy/certs/README.md @@ -0,0 +1,15 @@ +# HTTPS 证书目录 + +请把生产证书文件放到本目录,并统一命名为: + +- `tls.crt`:证书链文件(如 `fullchain.pem` / `bundle.pem`) +- `tls.key`:私钥文件 + +`docker-compose.full.yml` 会把本目录挂载到前端容器的 `/etc/nginx/certs`, +`frontend/nginx.conf` 也会固定从该路径读取这两个文件。 + +注意: + +1. 证书和私钥属于敏感文件,不要提交到仓库。 +2. 若证书文件名不同,请在部署前重命名为上面的通用名称。 +3. 更新证书后,只需要重建或重启 `frontend` 容器即可生效。 diff --git a/docker-compose.full.yml b/docker-compose.full.yml index 9defe62..99d0055 100644 --- a/docker-compose.full.yml +++ b/docker-compose.full.yml @@ -7,6 +7,8 @@ x-backend-common: &backend-common environment: TZ: Asia/Shanghai SMARTFLOW_CONFIG_FILE: /app/backend/config.docker.yaml + ARK_API_KEY: ${ARK_API_KEY:-} + SMARTFLOW_USERAUTH_ALLOWREGISTER: ${SMARTFLOW_USERAUTH_ALLOWREGISTER:-} SMARTFLOW_NOTIFICATION_FRONTENDBASEURL: ${SMARTFLOW_NOTIFICATION_FRONTENDBASEURL:-} SMARTFLOW_CORS_ALLOWEDORIGINS: ${SMARTFLOW_CORS_ALLOWEDORIGINS:-} volumes: @@ -288,6 +290,9 @@ services: restart: unless-stopped ports: - "${SMARTFLOW_FRONTEND_PORT:-80}:80" + - "${SMARTFLOW_FRONTEND_HTTPS_PORT:-443}:443" + volumes: + - ./deploy/certs:/etc/nginx/certs:ro depends_on: api: condition: service_started diff --git a/frontend/nginx.conf b/frontend/nginx.conf index 755acb4..799a5d2 100644 --- a/frontend/nginx.conf +++ b/frontend/nginx.conf @@ -1,14 +1,30 @@ server { listen 80; - server_name _; + server_name smartmate.lecspace.com _; + return 301 https://$host$request_uri; +} + +server { + listen 443 ssl; + http2 on; + server_name smartmate.lecspace.com; + resolver 127.0.0.11 ipv6=off valid=30s; + + ssl_certificate /etc/nginx/certs/tls.crt; + ssl_certificate_key /etc/nginx/certs/tls.key; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_session_timeout 1d; + ssl_session_cache shared:SSL:10m; + ssl_session_tickets off; root /usr/share/nginx/html; index index.html; # 1. 生产环境统一由前端容器反代后端 API,前端继续使用相对路径 /api/v1。 - # 2. 关闭代理缓冲,避免 Agent SSE 流式响应被 Nginx 缓存后前端长时间收不到数据。 + # 2. 关闭代理缓冲,避免 Agent SSE 响应被 Nginx 缓存后前端长时间收不到数据。 location /api/ { - proxy_pass http://api:8080; + set $api_upstream http://api:8080; + proxy_pass $api_upstream; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; @@ -22,7 +38,7 @@ server { } # 1. Vue Router 走 history 模式时,静态资源未命中需要回落到 index.html。 - # 2. 这里不负责接口代理,接口统一由上面的 /api location 处理。 + # 2. 这里只负责前端页面回落,接口流量统一由上面的 /api 路由处理。 location / { try_files $uri $uri/ /index.html; } diff --git a/frontend/src/views/HomeView.vue b/frontend/src/views/HomeView.vue index 9d4dd37..592b719 100644 --- a/frontend/src/views/HomeView.vue +++ b/frontend/src/views/HomeView.vue @@ -3,12 +3,6 @@ import { computed } from 'vue' import { useRouter } from 'vue-router' import { useAuthStore } from '@/stores/auth' -// 导入资产 -import heroImg from '@/assets/hero-dashboard.png' -import scheduleImg from '@/assets/feature-schedule.png' -import aiImg from '@/assets/feature-ai.png' -import toolsImg from '@/assets/feature-tools.png' - const router = useRouter() const authStore = useAuthStore() @@ -19,26 +13,37 @@ const handleCta = () => { router.push(ctaLink.value) } -const features = [ +const logoImg = 'https://dl2.lecspace.com/SmartFlow-Agent/logo.png' + +const landingImages = { + homeOverview: 'https://dl2.lecspace.com/SmartFlow-Agent/%E4%B8%BB%E9%A1%B5.png', + schedulePage: 'https://dl2.lecspace.com/SmartFlow-Agent/%E6%97%A5%E7%A8%8B%E9%A1%B5.png', + quickNote: 'https://dl2.lecspace.com/SmartFlow-Agent/%E9%9A%8F%E5%8F%A3%E8%AE%B0.png', + taskQuery: 'https://dl2.lecspace.com/SmartFlow-Agent/%E5%BE%85%E5%8A%9E%E6%9F%A5%E8%AF%A2.png', + activeOptimize: 'https://dl2.lecspace.com/SmartFlow-Agent/%E4%B8%BB%E5%8A%A8%E4%BC%98%E5%8C%96.png', + memorySystem: 'https://dl2.lecspace.com/SmartFlow-Agent/%E8%AE%B0%E5%BF%86%E7%B3%BB%E7%BB%9F.png', +} + +const aiCapabilities = [ { title: 'AI 随口记', - desc: '一句话记录作业、DDL、生活小事。AI 自动提取关键时间与内容。', - img: toolsImg + desc: '用户只需要说“提醒我明天中午去菜鸟驿站取快递”,系统就能记录任务、识别截止时间,并自动归类。', + img: landingImages.quickNote }, { - title: '四象限任务池', - desc: '系统化承接日常待办,清晰标注优先级,告别混乱。', - img: heroImg // 复用首页截图展示任务池部分,或在此处根据需要调整 + title: '自然语言待办查询', + desc: '用户可以直接问“我明天有啥任务”,AI 会按条件查询并返回结构化任务卡片。', + img: landingImages.taskQuery }, { - title: '课表智能编排', - desc: '不仅仅是列出任务,而是智能寻找课间或空闲时段,把任务真正排进日程。', - img: scheduleImg + title: '主动排程优化', + desc: '系统能分析当前排程节奏,发现连续高强度任务,并提出可确认的交换建议。', + img: landingImages.activeOptimize }, { title: '长期记忆', - desc: 'AI 会记住你的课程节奏、个人习惯、偏好和长期目标,越用越懂你。', - img: aiImg + desc: '系统会逐渐理解用户关于日程、任务和学习习惯的偏好,让后续对话不用从零开始。', + img: landingImages.memorySystem } ] @@ -48,159 +53,219 @@ const features = [ - +
-

成长型 AI 排程伙伴

-

越用越懂你的
成长型 AI 排程伙伴

+

从一句话,到一张可执行的日程表

- 不仅仅是待办清单。我们将课表、DDL、个人习惯与长期记忆深度融合,
- 通过 AI 为你打造一张真正“能落地”的日程表。 + 时伴 SmartMate 是面向大学生的 AI 排程伙伴,把课表、待办、AI 编排和长期记忆放进同一个日程系统,让计划真正落到每天能执行的时间里。

+
+ AI 随口记 + 课表智能编排 + 预览确认执行 + 长期记忆 +
+ 查看核心闭环
- 时伴工作台 + 首屏总览
- -
+ +
-
-
- 12,000+ - 活跃大学生用户 +
+
+

大学生的时间,不是一张空白日历

+

+ 传统待办只能记录任务,普通日历不理解课程表,普通 AI 聊完也很难真正落地。时伴从学生真实课表出发,把作业、复习、实验、项目推进和临时小事统一放进可执行的安排里。 +

+
    +
  • +
    📋
    +
    + 任务不只是“要做什么”,还要决定“什么时候做”。 +
    +
  • +
  • +
    🎓
    +
    + 课表、水课、空档、DDL 都会影响安排结果。 +
    +
  • +
  • +
    ⚙️
    +
    + AI 生成建议后,用户可以预览、微调、确认,再正式应用。 +
    +
  • +
-
- 850,000+ - AI 编排日程任务 -
-
- 98.2% - 计划执行达成率 +
+ 日程页
- -
-
-
-

告别碎片化与混乱

-

为了解决大学生真实的计划难题而生

-
-
-
-

传统工具的困境

-
    -
  • - 日历太“空”: - 只有格子,不知道课间 20 分钟能干什么。 -
  • -
  • - 清单太“满”: - 列了一堆 DDL,却不知道该从哪一个开始。 -
  • -
  • - AI 太“乱”: - 直接改乱日程,让人感到失去掌控。 -
  • -
-
-
-

时伴的解决之道

-
    -
  • - 感知课表缝隙: - 自动识别课间与空课,填入最适合的任务。 -
  • -
  • - 智能排序优先级: - 基于 DDL 与个人状态,自动给出当日最优解。 -
  • -
  • - 预览确认机制: - AI 负责提案,你负责最终决定,完美平衡。 -
  • -
-
-
-
-
- - -
+ +
-

极简操作闭环

+

一条能落地的 AI 排程闭环

+

AI 不是装饰,而是参与“理解、判断、编排、优化、记忆”的关键节点

-
-
-
💬
- 01 - 随口说需求 + +
+
+
+

1. 输入与理解

+

随口说需求 → AI 识别任务 → 判断截止时间与优先级 → 归入四象限任务池

+
+
+ 输入与理解 +
-
-
-
🧠
- 02 - AI 识别任务 + +
+
+

2. 编排与确认

+

结合课表生成安排 → 预览微调 → 确认应用

+
+
+ 编排与确认 +
-
-
-
🗓️
- 03 - 基于课表编排 -
-
-
-
👀
- 04 - 预览微调 -
-
-
-
- 05 - 确认应用 + +
+
+

3. 优化与沉淀

+

主动优化建议 → 记忆沉淀,越用越懂你

+
+
+ 优化与沉淀 +
- -
+ +
-

核心能力

-
-
-
-

{{ feature.title }}

-

{{ feature.desc }}

+

AI 不只聊天,而是进入任务执行链路

+
+
+
+
-
- +
+

{{ cap.title }}

+

{{ cap.desc }}

- + +
+
+
+

不是待办工具,也不是普通聊天机器人

+
+
+
+
+
01
+

课表原生

+

以学生课程表为底座,而不是把大学生日程当成普通空白日历。

+
+
+
02
+

确认式 AI 执行

+

AI 可以生成计划、查询任务、提出优化,但关键改动先预览、再确认,避免误操作。

+
+
+
03
+

记忆驱动的个性化

+

系统能沉淀偏好和上下文,越用越懂用户的学习节奏。

+
+
+
+ 主动排程优化 + 记忆系统 +
+
+
+
+ + +
+
+
+
+

从前端工作台到 Agent 执行链路

+

+ 时伴采用前后端分离架构,前端负责工作台交互和结果确认,后端通过 Agent Graph、工具调用、任务服务、课表服务和记忆服务完成理解、编排、查询与优化。AI 输出不会直接绕过业务规则,而是进入可追踪、可确认的执行链路。 +

+
+ Vue 前端工作台 + API 网关 + Agent Graph 分阶段执行 + 工具调用与结构化结果卡片 + 任务 / 课表 / 记忆服务 + 预览确认流 + 异步事件与长期记忆沉淀 + 可扩展至考研复习/项目推进 +
+
+
+ 架构与落地 +
+
+
+
+ + +
+
+
+

当前 Demo 已能完整演示

+

从“回答问题”进入“辅助执行”,把模糊计划转成可执行日程

+
+ +
+
✔️ 减少手动拆任务、排时间的成本
+
✔️ 降低 DDL 和临时事项遗漏风险
+
+
+
+ +

让计划不再停在待办列表里

-

加入数万名大学生,开启高效的智能校园生活。

+

时伴把任务理解、课表编排、主动优化和长期记忆接成一个闭环,让大学生日程管理真正可执行。

@@ -209,13 +274,18 @@ const features = [