Files
novelty-seeking/experiments/assessment/frontend/src/hooks/useRatings.ts
gbanyan 43c025e060 feat: Add experiments framework and novelty-driven agent loop
- Add complete experiments directory with pilot study infrastructure
  - 5 experimental conditions (direct, expert-only, attribute-only, full-pipeline, random-perspective)
  - Human assessment tool with React frontend and FastAPI backend
  - AUT flexibility analysis with jump signal detection
  - Result visualization and metrics computation

- Add novelty-driven agent loop module (experiments/novelty_loop/)
  - NoveltyDrivenTaskAgent with expert perspective perturbation
  - Three termination strategies: breakthrough, exhaust, coverage
  - Interactive CLI demo with colored output
  - Embedding-based novelty scoring

- Add DDC knowledge domain classification data (en/zh)
- Add CLAUDE.md project documentation
- Update research report with experiment findings

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 10:16:21 +08:00

134 lines
3.1 KiB
TypeScript

/**
* Hook for managing rating submission.
*/
import { useState, useCallback } from 'react';
import type { RatingState, DimensionKey } from '../types';
import * as api from '../services/api';
interface UseRatingsOptions {
raterId: string | null;
queryId: string | null;
ideaId: string | null;
onSuccess?: () => void;
}
export function useRatings({ raterId, queryId, ideaId, onSuccess }: UseRatingsOptions) {
const [ratings, setRatings] = useState<RatingState>({
originality: null,
elaboration: null,
coherence: null,
usefulness: null,
});
const [submitting, setSubmitting] = useState(false);
const [error, setError] = useState<string | null>(null);
// Set a single rating
const setRating = useCallback((dimension: DimensionKey, value: number | null) => {
setRatings((prev) => ({ ...prev, [dimension]: value }));
}, []);
// Reset all ratings
const resetRatings = useCallback(() => {
setRatings({
originality: null,
elaboration: null,
coherence: null,
usefulness: null,
});
setError(null);
}, []);
// Check if all ratings are set
const isComplete = useCallback(() => {
return (
ratings.originality !== null &&
ratings.elaboration !== null &&
ratings.coherence !== null &&
ratings.usefulness !== null
);
}, [ratings]);
// Submit rating
const submit = useCallback(async () => {
if (!raterId || !queryId || !ideaId) {
setError('Missing required information');
return false;
}
if (!isComplete()) {
setError('Please rate all dimensions');
return false;
}
setSubmitting(true);
setError(null);
try {
await api.submitRating({
rater_id: raterId,
idea_id: ideaId,
query_id: queryId,
originality: ratings.originality,
elaboration: ratings.elaboration,
coherence: ratings.coherence,
usefulness: ratings.usefulness,
skipped: false,
});
resetRatings();
onSuccess?.();
return true;
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to submit rating');
return false;
} finally {
setSubmitting(false);
}
}, [raterId, queryId, ideaId, ratings, isComplete, resetRatings, onSuccess]);
// Skip idea
const skip = useCallback(async () => {
if (!raterId || !queryId || !ideaId) {
setError('Missing required information');
return false;
}
setSubmitting(true);
setError(null);
try {
await api.submitRating({
rater_id: raterId,
idea_id: ideaId,
query_id: queryId,
originality: null,
elaboration: null,
coherence: null,
usefulness: null,
skipped: true,
});
resetRatings();
onSuccess?.();
return true;
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to skip idea');
return false;
} finally {
setSubmitting(false);
}
}, [raterId, queryId, ideaId, resetRatings, onSuccess]);
return {
ratings,
setRating,
resetRatings,
isComplete,
submit,
skip,
submitting,
error,
};
}