feat: improve citation display and skill persistence
This commit is contained in:
1 parent
61babbdf7d
commit
650102cedb
11 files changed
+706
-123
No files matched your search
@@ -4,10 +4,11 @@ export const DEFAULT_LOCAL_SETTINGS: LocalSettings = {
|
||||
notification: {
|
||||
enabled: true,
|
||||
},
|
||||
display: {
|
||||
streamSpeedMeterEnabled: true,
|
||||
ragModeEnabled: false,
|
||||
},
|
||||
display: {
|
||||
streamSpeedMeterEnabled: true,
|
||||
ragModeEnabled: false,
|
||||
citationCleanupWords: [],
|
||||
},
|
||||
context: {
|
||||
model_name: undefined,
|
||||
mode: undefined,
|
||||
@@ -38,8 +39,13 @@ export interface LocalSettings {
|
||||
* Global switch for skill result display adapters. When disabled, chat
|
||||
* messages render tool results with the legacy generic search UI only.
|
||||
*/
|
||||
ragModeEnabled: boolean;
|
||||
};
|
||||
ragModeEnabled: boolean;
|
||||
/**
|
||||
* Admin-maintained words or short phrases removed from reference snippets
|
||||
* and dialogs, one entry per line in the settings UI.
|
||||
*/
|
||||
citationCleanupWords: string[];
|
||||
};
|
||||
context: Omit<
|
||||
AgentThreadContext,
|
||||
| "thread_id"
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import type { SkillDisplayConfig } from "@/core/skills/type";
|
||||
|
||||
const DEFAULT_CITATION_MAX_ITEMS = 100;
|
||||
const DEFAULT_LIST_MAX_ITEMS = 20;
|
||||
const MAX_DISPLAY_ITEMS = 100;
|
||||
|
||||
export interface DisplaySourceItem {
|
||||
id?: string;
|
||||
title: string;
|
||||
@@ -29,7 +33,7 @@ export const emptyDisplayConfig = (): SkillDisplayConfig => ({
|
||||
linkStrategy: "auto",
|
||||
linkTemplate: null,
|
||||
citationToken: "doubleBracket",
|
||||
maxItems: 8,
|
||||
maxItems: DEFAULT_CITATION_MAX_ITEMS,
|
||||
customInstruction: "",
|
||||
tableColumns: [],
|
||||
});
|
||||
@@ -92,7 +96,7 @@ export function inferDisplayConfig(samplePayload: unknown, skillName?: string):
|
||||
inferred.enabled = true;
|
||||
inferred.mode = skillName === "knowledge-search-v2" ? "citation" : "list";
|
||||
inferred.resultPath = found.path || "results";
|
||||
inferred.maxItems = inferred.mode === "citation" ? 8 : 20;
|
||||
inferred.maxItems = inferMaxItems(found.items.length, inferred.mode);
|
||||
|
||||
for (const [target, candidates] of Object.entries(candidateFields)) {
|
||||
(inferred as unknown as Record<string, string | null>)[target] = pickField(sample, candidates);
|
||||
@@ -159,9 +163,10 @@ function resolveLink(item: Record<string, unknown>, config: SkillDisplayConfig):
|
||||
export function normalizeDisplaySources(payload: unknown, config: SkillDisplayConfig): DisplaySourceItem[] {
|
||||
const rawItems = getByPath(payload, config.resultPath);
|
||||
if (!Array.isArray(rawItems)) return [];
|
||||
const maxItems = resolveMaxItems(config, rawItems.length);
|
||||
return rawItems
|
||||
.filter((item): item is Record<string, unknown> => !!item && typeof item === "object")
|
||||
.slice(0, config.maxItems)
|
||||
.slice(0, maxItems)
|
||||
.map((item, index) => ({
|
||||
id: config.idField ? stringify(item[config.idField]) : undefined,
|
||||
title: config.titleField ? stringify(item[config.titleField], `结果 ${index + 1}`) : `结果 ${index + 1}`,
|
||||
@@ -178,3 +183,26 @@ export function normalizeDisplaySources(payload: unknown, config: SkillDisplayCo
|
||||
raw: item,
|
||||
}));
|
||||
}
|
||||
|
||||
function inferMaxItems(
|
||||
itemCount: number,
|
||||
mode: SkillDisplayConfig["mode"],
|
||||
): number {
|
||||
const fallback =
|
||||
mode === "citation" ? DEFAULT_CITATION_MAX_ITEMS : DEFAULT_LIST_MAX_ITEMS;
|
||||
return Math.min(
|
||||
MAX_DISPLAY_ITEMS,
|
||||
Math.max(fallback, itemCount || fallback),
|
||||
);
|
||||
}
|
||||
|
||||
function resolveMaxItems(config: SkillDisplayConfig, itemCount: number): number {
|
||||
const configured = Number(config.maxItems) || 0;
|
||||
if (config.mode === "citation" && configured <= 8 && itemCount > configured) {
|
||||
return Math.min(itemCount, MAX_DISPLAY_ITEMS);
|
||||
}
|
||||
return Math.min(
|
||||
MAX_DISPLAY_ITEMS,
|
||||
Math.max(1, configured || inferMaxItems(itemCount, config.mode)),
|
||||
);
|
||||
}
|
||||
@@ -15,6 +15,8 @@ export type RuntimeDisplaySource = DisplaySourceItem & {
|
||||
tableColumns?: SkillDisplayConfig["tableColumns"];
|
||||
};
|
||||
|
||||
type RuntimeDisplaySourceDraft = Omit<RuntimeDisplaySource, "index">;
|
||||
|
||||
export type SkillDisplayMap = Map<string, SkillDisplayConfig>;
|
||||
|
||||
type RuntimeToolCall = {
|
||||
@@ -163,6 +165,9 @@ export function collectRuntimeDisplaySources(
|
||||
const sources: RuntimeDisplaySource[] = [];
|
||||
let citationIndex = 0;
|
||||
const displayIndexes = new Map<string, number>();
|
||||
const seenCitationKeys = new Map<string, RuntimeDisplaySource>();
|
||||
const seenDisplayKeys = new Set<string>();
|
||||
const consumedToolMessageIds = new Set<string>();
|
||||
|
||||
for (let index = start; index < limit; index++) {
|
||||
const message = messages[index];
|
||||
@@ -179,16 +184,14 @@ export function collectRuntimeDisplaySources(
|
||||
if (result === undefined) {
|
||||
continue;
|
||||
}
|
||||
const linkedToolMessage = findToolMessageById(toolCall.id, messages);
|
||||
if (linkedToolMessage?.id) {
|
||||
consumedToolMessageIds.add(linkedToolMessage.id);
|
||||
}
|
||||
if (isDisplaySamplePayload(result)) {
|
||||
continue;
|
||||
}
|
||||
const match =
|
||||
toolMatch ??
|
||||
findDisplayConfigForPayload(
|
||||
toolCall.name,
|
||||
result,
|
||||
displays,
|
||||
);
|
||||
const match = toolMatch;
|
||||
const effectiveMode = match?.display.mode ?? "citation";
|
||||
if (allowedModes && !allowedModes.has(effectiveMode)) {
|
||||
continue;
|
||||
@@ -202,24 +205,144 @@ export function collectRuntimeDisplaySources(
|
||||
)
|
||||
? normalizeDefaultDisplaySources(result)
|
||||
: [];
|
||||
for (const item of normalized) {
|
||||
const skillName = match?.skillName ?? toolCall.name;
|
||||
const displayKey = `${skillName}:${effectiveMode}`;
|
||||
const displayIndex = (displayIndexes.get(displayKey) ?? 0) + 1;
|
||||
displayIndexes.set(displayKey, displayIndex);
|
||||
sources.push({
|
||||
appendDisplaySources(
|
||||
sources,
|
||||
normalized.map((item) => ({
|
||||
...item,
|
||||
index: effectiveMode === "citation" ? ++citationIndex : displayIndex,
|
||||
skillName,
|
||||
skillName: match?.skillName ?? toolCall.name,
|
||||
mode: effectiveMode,
|
||||
tableColumns: match?.display.tableColumns,
|
||||
});
|
||||
}
|
||||
})),
|
||||
displayIndexes,
|
||||
seenCitationKeys,
|
||||
seenDisplayKeys,
|
||||
() => ++citationIndex,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for (let index = start; index < limit; index++) {
|
||||
const message = messages[index];
|
||||
if (!message || message.type !== "tool" || consumedToolMessageIds.has(message.id ?? "")) {
|
||||
continue;
|
||||
}
|
||||
const result = parseToolMessagePayload(message);
|
||||
if (result === undefined || isDisplaySamplePayload(result)) {
|
||||
continue;
|
||||
}
|
||||
const toolName = extractToolMessageName(message);
|
||||
const match = null;
|
||||
const effectiveMode = match?.display.mode ?? "citation";
|
||||
if (allowedModes && !allowedModes.has(effectiveMode)) {
|
||||
continue;
|
||||
}
|
||||
const normalized = match
|
||||
? normalizeDisplaySources(result, match.display)
|
||||
: shouldUseDefaultCitationMode(toolName, result)
|
||||
? normalizeDefaultDisplaySources(result)
|
||||
: [];
|
||||
appendDisplaySources(
|
||||
sources,
|
||||
normalized.map((item) => ({
|
||||
...item,
|
||||
skillName: match?.skillName ?? toolName,
|
||||
mode: effectiveMode,
|
||||
tableColumns: match?.display.tableColumns,
|
||||
})),
|
||||
displayIndexes,
|
||||
seenCitationKeys,
|
||||
seenDisplayKeys,
|
||||
() => ++citationIndex,
|
||||
);
|
||||
}
|
||||
return sources;
|
||||
}
|
||||
|
||||
function appendDisplaySources(
|
||||
target: RuntimeDisplaySource[],
|
||||
drafts: RuntimeDisplaySourceDraft[],
|
||||
displayIndexes: Map<string, number>,
|
||||
seenCitationKeys: Map<string, RuntimeDisplaySource>,
|
||||
seenDisplayKeys: Set<string>,
|
||||
nextCitationIndex: () => number,
|
||||
) {
|
||||
for (const draft of drafts) {
|
||||
if (draft.mode === "citation") {
|
||||
const key = citationSourceKey(draft);
|
||||
const existing = seenCitationKeys.get(key);
|
||||
if (existing) {
|
||||
continue;
|
||||
}
|
||||
const source = {
|
||||
...draft,
|
||||
index: nextCitationIndex(),
|
||||
};
|
||||
seenCitationKeys.set(key, source);
|
||||
target.push(source);
|
||||
continue;
|
||||
}
|
||||
|
||||
const stableKey = nonCitationSourceKey(draft);
|
||||
if (seenDisplayKeys.has(stableKey)) {
|
||||
continue;
|
||||
}
|
||||
seenDisplayKeys.add(stableKey);
|
||||
const displayKey = `${draft.skillName}:${draft.mode}`;
|
||||
const displayIndex = (displayIndexes.get(displayKey) ?? 0) + 1;
|
||||
displayIndexes.set(displayKey, displayIndex);
|
||||
target.push({
|
||||
...draft,
|
||||
index: displayIndex,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function reindexRuntimeDisplaySources(
|
||||
sources: RuntimeDisplaySource[],
|
||||
globalSources: RuntimeDisplaySource[],
|
||||
): RuntimeDisplaySource[] {
|
||||
if (sources.length === 0 || globalSources.length === 0) {
|
||||
return sources;
|
||||
}
|
||||
const globalByKey = new Map(
|
||||
globalSources.map((source) => [citationSourceKey(source), source]),
|
||||
);
|
||||
return sources.map((source) => {
|
||||
if (source.mode !== "citation") {
|
||||
return source;
|
||||
}
|
||||
const global = globalByKey.get(citationSourceKey(source));
|
||||
return global
|
||||
? {
|
||||
...source,
|
||||
index: global.index,
|
||||
}
|
||||
: source;
|
||||
});
|
||||
}
|
||||
|
||||
function citationSourceKey(source: RuntimeDisplaySourceDraft): string {
|
||||
const raw = source.raw ?? {};
|
||||
const id =
|
||||
source.id ||
|
||||
source.url ||
|
||||
stringifyMaybe(raw.recUuid) ||
|
||||
stringifyMaybe(raw.uuid) ||
|
||||
stringifyMaybe(raw.docId) ||
|
||||
stringifyMaybe(raw.id);
|
||||
if (id) {
|
||||
return normalizeName(`${source.skillName}:${id}`);
|
||||
}
|
||||
const fallback = [source.skillName, source.title, source.source, source.time]
|
||||
.filter(Boolean)
|
||||
.join("|");
|
||||
return normalizeName(fallback) || JSON.stringify(raw).slice(0, 300);
|
||||
}
|
||||
|
||||
function nonCitationSourceKey(source: RuntimeDisplaySourceDraft): string {
|
||||
return `${source.skillName}:${source.mode}:${citationSourceKey(source)}`;
|
||||
}
|
||||
|
||||
function extractRuntimeToolCalls(message: Message): RuntimeToolCall[] {
|
||||
const calls: RuntimeToolCall[] = [];
|
||||
const direct = (message as { tool_calls?: unknown }).tool_calls;
|
||||
@@ -312,6 +435,18 @@ export function findDisplayConfigForPayload(
|
||||
return best ? { skillName: best.skillName, display: best.display } : null;
|
||||
}
|
||||
|
||||
export function isUnconfiguredSkillInvocation(
|
||||
toolName: string,
|
||||
displays: SkillDisplayMap | undefined,
|
||||
toolArgs?: Record<string, unknown>,
|
||||
): boolean {
|
||||
if (!toolArgs || !canInferSkillFromToolArgs(toolName)) {
|
||||
return false;
|
||||
}
|
||||
const skillName = extractSkillNameFromArgs(toolArgs);
|
||||
return !!skillName && !displays?.has(skillName);
|
||||
}
|
||||
|
||||
export function isDisplaySamplePayload(payload: unknown): boolean {
|
||||
if (!isRecord(payload)) {
|
||||
return false;
|
||||
@@ -443,31 +578,59 @@ export function applyInlineCitations(
|
||||
}
|
||||
}
|
||||
|
||||
const normalized = normalizeExistingCitationLinks(content, byIndex, byUrl, byTitle);
|
||||
const normalized = normalizeExistingCitationLinks(
|
||||
content,
|
||||
byIndex,
|
||||
byUrl,
|
||||
byTitle,
|
||||
citationSources,
|
||||
);
|
||||
|
||||
const markdownLinkPattern = /!?\[(?:[^\[\]]|\[[^\]]*\])*\]\([^)]+\)/g;
|
||||
let cursor = 0;
|
||||
let next = "";
|
||||
let replaced = false;
|
||||
for (const match of normalized.matchAll(markdownLinkPattern)) {
|
||||
const start = match.index ?? 0;
|
||||
next += replaceCitationTokens(normalized.slice(cursor, start), byIndex);
|
||||
const replacedSlice = replaceCitationTokens(
|
||||
normalized.slice(cursor, start),
|
||||
byIndex,
|
||||
citationSources,
|
||||
);
|
||||
if (replacedSlice !== normalized.slice(cursor, start)) {
|
||||
replaced = true;
|
||||
}
|
||||
next += replacedSlice;
|
||||
next += match[0];
|
||||
cursor = start + match[0].length;
|
||||
}
|
||||
next += replaceCitationTokens(normalized.slice(cursor), byIndex);
|
||||
return next;
|
||||
const tail = replaceCitationTokens(normalized.slice(cursor), byIndex, citationSources);
|
||||
if (tail !== normalized.slice(cursor)) {
|
||||
replaced = true;
|
||||
}
|
||||
next += tail;
|
||||
if (replaced || hasCitationMarkdown(next)) {
|
||||
return next;
|
||||
}
|
||||
return appendCitationSummary(next, citationSources);
|
||||
}
|
||||
|
||||
function replaceCitationTokens(
|
||||
content: string,
|
||||
byIndex: Map<number, RuntimeDisplaySource>,
|
||||
orderedSources: RuntimeDisplaySource[],
|
||||
): string {
|
||||
return content
|
||||
.replace(/\[\[(\d+)\]\]/g, (match, rawIndex: string) =>
|
||||
citationMarkdown(match, Number(rawIndex), byIndex),
|
||||
citationMarkdown(match, Number(rawIndex), byIndex, orderedSources),
|
||||
)
|
||||
.replace(/(^|[^:\[!])\[(\d+)\](?!\()/g, (match, prefix: string, rawIndex: string) =>
|
||||
`${prefix}${citationMarkdown(match.slice(prefix.length), Number(rawIndex), byIndex)}`,
|
||||
`${prefix}${citationMarkdown(
|
||||
match.slice(prefix.length),
|
||||
Number(rawIndex),
|
||||
byIndex,
|
||||
orderedSources,
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -476,6 +639,7 @@ function normalizeExistingCitationLinks(
|
||||
byIndex: Map<number, RuntimeDisplaySource>,
|
||||
byUrl: Map<string, RuntimeDisplaySource>,
|
||||
byTitle: Map<string, RuntimeDisplaySource>,
|
||||
orderedSources: RuntimeDisplaySource[],
|
||||
): string {
|
||||
// Resolve every `[citation:LABEL](URL)` link — whether LABEL is a number
|
||||
// ("[1]", "1") or a free-form title — to a numbered reference link that
|
||||
@@ -488,8 +652,9 @@ function normalizeExistingCitationLinks(
|
||||
const numberMatch = label.match(/^\[?(\d+)\]?$/);
|
||||
if (numberMatch) {
|
||||
const index = Number(numberMatch[1]);
|
||||
if (byIndex.has(index)) {
|
||||
return `[citation:[${index}]](#reference-${index})`;
|
||||
const source = byIndex.get(index) ?? orderedSources[index - 1];
|
||||
if (source) {
|
||||
return `[citation:[${source.index}]](#reference-${source.index})`;
|
||||
}
|
||||
}
|
||||
const sourceByUrl = byUrl.get(url);
|
||||
@@ -509,12 +674,32 @@ function citationMarkdown(
|
||||
fallback: string,
|
||||
index: number,
|
||||
byIndex: Map<number, RuntimeDisplaySource>,
|
||||
orderedSources: RuntimeDisplaySource[],
|
||||
): string {
|
||||
const source = byIndex.get(index);
|
||||
const source = byIndex.get(index) ?? orderedSources[index - 1];
|
||||
if (!source) {
|
||||
return fallback;
|
||||
}
|
||||
return `[citation:[${index}]](#reference-${index})`;
|
||||
return `[citation:[${source.index}]](#reference-${source.index})`;
|
||||
}
|
||||
|
||||
function hasCitationMarkdown(content: string): boolean {
|
||||
return /\[citation:\[[^\]]+\]\]\(#reference-[^)]+\)/.test(content);
|
||||
}
|
||||
|
||||
function appendCitationSummary(
|
||||
content: string,
|
||||
citationSources: RuntimeDisplaySource[],
|
||||
): string {
|
||||
const citations = citationSources
|
||||
.slice(0, 5)
|
||||
.map((source) => `[citation:[${source.index}]](#reference-${source.index})`)
|
||||
.join(" ");
|
||||
if (!citations) {
|
||||
return content;
|
||||
}
|
||||
const trimmed = content.trimEnd();
|
||||
return `${trimmed}${trimmed ? "\n\n" : ""}参考来源:${citations}`;
|
||||
}
|
||||
|
||||
function normalizeName(name: string): string {
|
||||
@@ -540,6 +725,10 @@ function findToolResultPayload(
|
||||
if (!toolMessage) {
|
||||
return undefined;
|
||||
}
|
||||
return parseToolMessagePayload(toolMessage);
|
||||
}
|
||||
|
||||
function parseToolMessagePayload(toolMessage: Message): unknown {
|
||||
const structured = extractStructuredToolPayload(toolMessage.content);
|
||||
if (structured !== undefined) {
|
||||
return structured;
|
||||
@@ -555,6 +744,17 @@ function findToolResultPayload(
|
||||
return content;
|
||||
}
|
||||
|
||||
function extractToolMessageName(message: Message): string {
|
||||
return (
|
||||
stringifyMaybe((message as { name?: unknown }).name) ??
|
||||
stringifyMaybe(
|
||||
(message as { additional_kwargs?: Record<string, unknown> }).additional_kwargs
|
||||
?.name,
|
||||
) ??
|
||||
"tool"
|
||||
);
|
||||
}
|
||||
|
||||
function findToolMessageById(
|
||||
toolCallId: string | undefined,
|
||||
messages: Message[],
|
||||
@@ -677,7 +877,7 @@ function parseJSONLikeText(text: string): unknown {
|
||||
try {
|
||||
return JSON.parse(trimmed) as unknown;
|
||||
} catch {
|
||||
return extractJSONFromText(trimmed);
|
||||
return extractJSONFromText(trimmed) ?? extractSearchPayloadFromText(trimmed);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -700,6 +900,83 @@ function extractJSONFromText(text: string): unknown {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function extractSearchPayloadFromText(text: string): unknown {
|
||||
if (!text.includes('"results"') || !text.includes('"recUuid"')) {
|
||||
return undefined;
|
||||
}
|
||||
const results: Record<string, unknown>[] = [];
|
||||
const objectPattern = /\{\s*"index"\s*:\s*\d+[\s\S]*?\n\s*\}/g;
|
||||
for (const match of text.matchAll(objectPattern)) {
|
||||
try {
|
||||
const item = JSON.parse(match[0]) as unknown;
|
||||
if (isRecord(item) && item.recUuid) {
|
||||
results.push(item);
|
||||
}
|
||||
} catch {
|
||||
const item = extractSearchItemFromText(match[0]);
|
||||
if (item) {
|
||||
results.push(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (results.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
query: extractStringField(text, "query") ?? "",
|
||||
count: results.length,
|
||||
source: extractStringField(text, "source") ?? "tool-output",
|
||||
results,
|
||||
};
|
||||
}
|
||||
|
||||
function extractSearchItemFromText(text: string): Record<string, unknown> | null {
|
||||
const recUuid = extractStringField(text, "recUuid");
|
||||
if (!recUuid) {
|
||||
return null;
|
||||
}
|
||||
const score = extractNumberField(text, "relevance_score");
|
||||
return {
|
||||
index: extractNumberField(text, "index") ?? undefined,
|
||||
recUuid,
|
||||
relevance_score: score,
|
||||
title: extractStringField(text, "title") ?? recUuid,
|
||||
source_category: extractStringField(text, "source_category") ?? "",
|
||||
source: extractStringField(text, "source") ?? "",
|
||||
publish_time: extractStringField(text, "publish_time") ?? "",
|
||||
content_preview: extractStringField(text, "content_preview") ?? "",
|
||||
content_length: extractNumberField(text, "content_length") ?? undefined,
|
||||
};
|
||||
}
|
||||
|
||||
function extractStringField(text: string, field: string): string | undefined {
|
||||
const pattern = new RegExp(`"${escapeRegExp(field)}"\\s*:\\s*"((?:\\\\.|[^"\\\\])*)"`);
|
||||
const match = pattern.exec(text);
|
||||
if (!match) {
|
||||
return undefined;
|
||||
}
|
||||
try {
|
||||
return JSON.parse(`"${match[1]}"`) as string;
|
||||
} catch {
|
||||
return match[1];
|
||||
}
|
||||
}
|
||||
|
||||
function extractNumberField(text: string, field: string): number | undefined {
|
||||
const pattern = new RegExp(`"${escapeRegExp(field)}"\\s*:\\s*(-?\\d+(?:\\.\\d+)?)`);
|
||||
const match = pattern.exec(text);
|
||||
if (!match) {
|
||||
return undefined;
|
||||
}
|
||||
const value = Number(match[1]);
|
||||
return Number.isFinite(value) ? value : undefined;
|
||||
}
|
||||
|
||||
function escapeRegExp(value: string): string {
|
||||
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
}
|
||||
|
||||
function extractToolText(content: unknown): string {
|
||||
if (typeof content === "string") {
|
||||
return content.trim();
|
||||
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
setSkillState,
|
||||
validateSkillUpload,
|
||||
} from "./api";
|
||||
import type { SkillState } from "./type";
|
||||
import type { Skill, SkillState } from "./type";
|
||||
|
||||
import { loadSkills } from ".";
|
||||
|
||||
@@ -77,7 +77,13 @@ export function useSetSkillDisplay() {
|
||||
display: Parameters<typeof setSkillDisplay>[1];
|
||||
sample?: Parameters<typeof setSkillDisplay>[2];
|
||||
}) => setSkillDisplay(skillName, display, sample),
|
||||
onSuccess: (_data, { skillName }) => {
|
||||
onSuccess: (data, { skillName }) => {
|
||||
queryClient.setQueryData(["skill", skillName], data);
|
||||
queryClient.setQueriesData<Skill[]>({ queryKey: ["skills"] }, (current) =>
|
||||
Array.isArray(current)
|
||||
? current.map((item) => (item.name === skillName ? { ...item, ...data } : item))
|
||||
: current,
|
||||
);
|
||||
void queryClient.invalidateQueries({ queryKey: ["skills"] });
|
||||
void queryClient.invalidateQueries({ queryKey: ["skill", skillName] });
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user