update draft issue

This commit is contained in:
2026-08-15 23:24:06 +05:30
parent b349f2db61
commit 69b38abced
8 changed files with 753 additions and 25 deletions

View File

@@ -8,7 +8,8 @@ import { Surface } from '@/components/ds/Surface';
import { IconButton } from '@/components/ds/IconButton';
import { Alert } from '@/components/ds/Alert';
import {
useAssignments, useAssignWorkers, useCreateJobPosting, useMarkInterviewReady,
useAssignments, useAssignWorkers, useCreateJobPosting, useGenerateJobDescription,
useMarkInterviewReady, useUpdateJobPosting,
usePreferences, useRoleCategories,
} from '@/lib/krowHooks';
import { ROLE_CATEGORIES } from '@/lib/roleCategories';
@@ -19,6 +20,7 @@ import { owliverSuggestions } from '@/lib/skills/owliverResolver';
import { useWorkforcePaths } from '@/lib/skills/usePageSkills';
import { profileForEmail } from '@/lib/skillGraph';
import { groupByRecency } from './history';
import { useAssistantPanel } from './AssistantPanelContext';
import { usePageContext } from './PageContext';
import { useAssistantFacts, useConversation, useCurrentUserName } from './useAssistant';
import { buildIntro, buildPrompts } from './dynamic';
@@ -214,6 +216,9 @@ export default function KrowAssistant({
const userName = useCurrentUserName();
const navigate = useNavigate();
/* A question a page has handed over — see the effect below `runPrompt`. */
const { request: panelRequest, consumeRequest } = useAssistantPanel();
/* The app's own router, not a location assignment: a full page load would
discard the thread and the panel state along with it. */
const goToPage = React.useCallback(
@@ -320,6 +325,30 @@ export default function KrowAssistant({
return createJob.mutateAsync(result.data);
}, [createJob]);
/**
* Finishing a draft, through the mutations the Create Position form calls.
*
* `useUpdateJobPosting` is the same update the form and the Positions page
* use, and `useGenerateJobDescription` is the same generator behind the
* form's "Generate Job Description with AI" button. Reusing both is what
* keeps one position record, one description and one set of vetting weights
* — the panel changes the record, it does not keep a copy of it.
*/
const updateJob = useUpdateJobPosting();
const onUpdatePosition = React.useCallback(
(id, data) => updateJob.mutateAsync({ id, data }),
[updateJob]
);
const generateDescription = useGenerateJobDescription();
const onGenerateDescription = React.useCallback(
async (position) => {
const result = await generateDescription.mutateAsync({ data: position, draftId: position.id });
return result || null;
},
[generateDescription]
);
/**
* The workforce picture the panel reasons over.
*
@@ -386,6 +415,8 @@ export default function KrowAssistant({
onNavigate: goToPage,
onAction: performAction,
onCreatePosition: createPosition,
onUpdatePosition,
onGenerateDescription,
onAssignWorkers,
onScheduleInterview,
workforce,
@@ -495,14 +526,14 @@ export default function KrowAssistant({
* bypasses intent routing. A typed question carries none and is routed. Both
* land in the same `send`, so there is a single pipeline to reason about.
*/
const handleMessage = React.useCallback((question, capability = null) => {
const handleMessage = React.useCallback((question, capability = null, positionId = null) => {
const text = String(question).trim();
if (!text || busy) return;
setInput('');
/* Asking something while reading History returns to the conversation — the
answer is about to arrive there, and leaving the list up would hide it. */
setView('chat');
send({ question: text, capability });
send({ question: text, capability, positionId });
}, [busy, send]);
const submit = React.useCallback((text) => handleMessage(text), [handleMessage]);
@@ -519,9 +550,26 @@ export default function KrowAssistant({
navigate(prompt.route);
return;
}
handleMessage(prompt.prompt, prompt.capability);
/* A chip built from a position says which one, so a follow-up acts on the
record the previous answer was about rather than re-deriving it from the
words. */
handleMessage(prompt.prompt, prompt.capability, prompt.positionId);
}, [handleMessage, navigate]);
/**
* A question the page asked on the reader's behalf.
*
* Continue on a draft card is the only source today: the card knows which
* record it is, and the work of finishing it lives here. Deferred while a
* response is streaming — the effect re-runs when the panel is free, so the
* request waits its turn rather than being dropped for arriving mid-answer.
*/
React.useEffect(() => {
if (!panelRequest || busy) return;
handleMessage(panelRequest.question, null, panelRequest.positionId);
consumeRequest(panelRequest.id);
}, [panelRequest, busy, handleMessage, consumeRequest]);
const composer = (
<PromptInput
value={input}