mirror of
https://github.com/lobehub/lobe-chat.git
synced 2026-06-14 03:30:19 +00:00
✨ feat(followUpAction): add quick-reply chips below assistant messages (#14350)
* ✨ feat(followUpAction): add shared types and JSON schema for follow-up chip extraction * 🐛 fix(followUpAction): tighten JSON schema literal types with top-level as const * ✨ feat(followUpAction): add base + onboarding prompt builders * ✨ feat(followUpAction): add server service to extract chips via fast LLM * 🐛 fix(followUpAction): drop empty chips and consolidate schemas in schema.ts * ✨ feat(followUpAction): expose extract via lambda TRPC router * ✨ feat(followUpAction): add client service wrapper around TRPC mutation * ✨ feat(followUpAction): add zustand store with abort/timeout actions * 🐛 fix(followUpAction): stabilize empty selector ref and abort on reset * ✨ feat(followUpAction): add FollowUpChips component with reply icon style * ✨ feat(followUpAction): add onboarding glue hook with phase/greeting guards * ✨ feat(followUpAction): wire chips + glue hook into onboarding conversation * 🐛 fix(followUpAction): drop unused eslint-disable directive in client service * 🐛 fix(followUpAction): tighten types and align prompt with schema bounds * 🐛 fix(followUpAction): use fresh phase for chip extraction across phase boundaries * 🐛 fix(followUpAction): type SUGGESTION_RESPONSE_JSON_SCHEMA against GenerateObjectSchema The earlier `as const` widened to readonly literal types, which is incompatible with the mutable `GenerateObjectSchema` interface required by `generateObject`. Replace with an explicit type annotation so the literal is checked at definition and stays assignable at the call site. * ⚡️ perf(followUpAction): only refresh user/agent caches at onboarding phase boundaries The previous logic refreshed both useUserStore and the webOnboarding builtin agent after every assistant turn, but their content only changes when the phase advances or onboarding finishes. Compare prev vs next phase/finishedAt from syncOnboardingContext and skip the two refresh calls when neither moved, saving an RPC per intra-phase turn. * 🐛 fix(followUpAction): read finishedAt from agentOnboarding subobject * ♻️ refactor(followUpAction): take agentId from caller and resolve model from agent config Drops the env-var override path on the server. The service is meant to be generic across consumers, so the caller now passes the agentId of the conversation context. The service resolves model/provider from AgentModel.getAgentConfigById, falling back to DEFAULT_SYSTEM_AGENT_CONFIG.topic when the agent has no explicit model. The onboarding caller passes the webOnboarding builtin agent id; future consumers pass theirs. * 🐛 fix(followUpAction): resolve latest text assistant message server-side via topicId * ✨ feat(followUpAction): mirror assistant language and ban deferral chips Two prompt rule changes: 1. Match the assistant message's language instead of forcing English. The chip should be in the script the user would naturally reply in. 2. Prefer questions with explicit options when the message contains several, and ban "Let me think / Skip / You decide / Let me explain" style escape-hatch chips entirely. Every chip must be a concrete reply the user might actually send; the user can always type freely, so meta deferral chips just waste a slot. * 🐛 fix(followUpAction): bump timeout to 20s and silence TRPC-wrapped abort The previous 3s timeout aborted the LLM call before generateObject could respond — a typical extract round-trip is ~10s. Bump to 20s. Also silence the TRPCClientError that wraps the abort: TRPC re-throws DOMException as TRPCClientError("signal is aborted ..."), so the original `instanceof DOMException` check missed it and noise `[FollowUpAction] extract failed` warnings hit the console on every manual clear / new turn. Now we also short-circuit on `signal.aborted`. * feat: enhance chat input functionality with new flags - Added `disableMention` and `disableSlash` props to `ChatInput` and `StoreUpdater` to control mention and slash command triggers. - Introduced `disableFollowUpVariant` and `disableQueue` props to manage placeholder behavior and message queuing during agent streaming. - Updated `FollowUpChips` to handle topic IDs and prevent rendering during message generation. - Refactored onboarding context retrieval to streamline fetching of user persona and state. - Removed deprecated onboarding state API references and adjusted related tests. - Improved follow-up action handling to discard stale results based on active request controllers. Signed-off-by: Innei <tukon479@gmail.com> * ✨ feat: enhance agent marketplace onboarding with summaries and improved state management Signed-off-by: Innei <tukon479@gmail.com> --------- Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
@@ -174,9 +174,64 @@ export const chatGroupAction: StateCreator<
|
||||
- `ChatGroupStoreWithRefresh` for member refresh
|
||||
- `ChatGroupStoreWithInternal` for curd `internal_dispatchChatGroup`
|
||||
|
||||
### Slices That Don't Currently Need `set`
|
||||
|
||||
When a slice doesn't write local state at the moment — e.g. it reads context
|
||||
from `#get()` and forwards calls to another store, or just runs hooks — drop
|
||||
the `#set` field. Otherwise ESLint's `no-unused-vars` flags the unused private
|
||||
field.
|
||||
|
||||
Mark the constructor's `set` param as `_set` and `void _set` it to keep the
|
||||
`(set, get, api)` shape aligned with `StateCreator`. This is **a snapshot of
|
||||
the current need, not a permanent contract** — if a later change needs `set`,
|
||||
restore the `#set` field and use it; do not invent a workaround to keep the
|
||||
"unused" form.
|
||||
|
||||
```ts
|
||||
type Setter = StoreSetter<ConversationStore>;
|
||||
|
||||
export const toolSlice = (set: Setter, get: () => ConversationStore, _api?: unknown) =>
|
||||
new ToolActionImpl(set, get, _api);
|
||||
|
||||
export class ToolActionImpl {
|
||||
readonly #get: () => ConversationStore;
|
||||
|
||||
// Mark unused params with `_` prefix and `void _x` so the constructor still
|
||||
// matches StateCreator's `(set, get, api)` shape without triggering unused
|
||||
// diagnostics.
|
||||
constructor(_set: Setter, get: () => ConversationStore, _api?: unknown) {
|
||||
void _set;
|
||||
void _api;
|
||||
this.#get = get;
|
||||
}
|
||||
|
||||
approveToolCall = async (id: string) => {
|
||||
const { context, hooks } = this.#get();
|
||||
await useChatStore.getState().approveToolCalling(id, '', context);
|
||||
hooks.onToolCallComplete?.(id, undefined);
|
||||
};
|
||||
}
|
||||
|
||||
export type ToolAction = Pick<ToolActionImpl, keyof ToolActionImpl>;
|
||||
```
|
||||
|
||||
Rules of thumb:
|
||||
|
||||
- If a slice doesn't currently call `set`, drop `#set` (use `_set` + `void _set`
|
||||
in the constructor). When a later edit needs `set`, restore `#set` and use it.
|
||||
- Don't add `setNamespace` for slices that don't write state. Add it when the
|
||||
slice starts writing state.
|
||||
- Never leave `#set` declared but unused "for future use" — lint will fail and
|
||||
re-adding it later costs nothing.
|
||||
|
||||
### Do / Don't
|
||||
|
||||
- **Do**: keep constructor signature aligned with `StateCreator` params `(set, get, api)`.
|
||||
- **Do**: use `#private` to avoid `set/get` being exposed.
|
||||
- **Do**: use `flattenActions` instead of spreading class instances.
|
||||
- **Do**: drop `#set` (and use `_set` + `void _set` in the constructor) for
|
||||
delegate-only slices that never write state — keeps lint green without
|
||||
breaking the `(set, get, api)` shape.
|
||||
- **Don't**: keep both old slice objects and class actions active at the same time.
|
||||
- **Don't**: keep an unused `#set` field "for future use" — it fails ESLint and
|
||||
re-adding it later costs nothing.
|
||||
|
||||
Reference in New Issue
Block a user