From 23e58fd2deb455df743d56868bdad55f4e13e2a8 Mon Sep 17 00:00:00 2001 From: Zyntro Date: Sun, 30 Aug 2026 04:32:54 +0700 Subject: [PATCH] Add files via upload Signed-off-by: Zyntro --- RubricScoring-2.jsx.txt | 315 ++++++++++++++++++++++++++++++++++++ example_fastapi_endpoint.py | 116 +++++++++++++ example_langchain_rag.py | 96 +++++++++++ fastapi-research-SKILL.md | 63 ++++++++ langchain-research-SKILL.md | 60 +++++++ 5 files changed, 650 insertions(+) create mode 100644 RubricScoring-2.jsx.txt create mode 100644 example_fastapi_endpoint.py create mode 100644 example_langchain_rag.py create mode 100644 fastapi-research-SKILL.md create mode 100644 langchain-research-SKILL.md diff --git a/RubricScoring-2.jsx.txt b/RubricScoring-2.jsx.txt new file mode 100644 index 0000000..0abc799 --- /dev/null +++ b/RubricScoring-2.jsx.txt @@ -0,0 +1,315 @@ +import { useMemo, useState } from "react"; +import { + ChevronDown, + CircleAlert, + Gauge, + Minus, + Plus, + RotateCcw, +} from "lucide-react"; + +/** + * RubricScoring + * --------------------------------------------------------------- + * Weighted rubric scoring panel for evaluating a submission against + * a set of criteria. Each criterion carries a weight (%) and is + * scored 1–5; the total is the weighted average, shown as a + * percentage and a letter-style band. + * + * Design notes: + * - High-density technical layout (compact rows, thin borders, + * monospace numerics) suited to an evaluation/dashboard context + * rather than a marketing surface. + * - No emoji anywhere — status is conveyed with lucide-react icons. + * - Full interactive-state coverage: hover, focus-visible, disabled, + * and an empty state when no score has been given yet. + */ + +const DEFAULT_CRITERIA = [ + { + id: "req", + label: "ความครบถ้วนของ Requirement", + description: "ครอบคลุมทุกเงื่อนไขที่ระบุใน spec หรือ user story", + weight: 30, + }, + { + id: "code", + label: "คุณภาพโค้ด", + description: "โครงสร้างชัดเจน อ่านง่าย มี error handling ที่เหมาะสม", + weight: 25, + }, + { + id: "test", + label: "การทดสอบ", + description: "มี test ครอบคลุม edge case และรันผ่านจริง", + weight: 20, + }, + { + id: "doc", + label: "เอกสารประกอบ", + description: "README / comment เพียงพอให้คนอื่นเข้าใจและต่อยอดได้", + weight: 15, + }, + { + id: "perf", + label: "ประสิทธิภาพ", + description: "ไม่มี bottleneck ที่ชัดเจน ใช้ resource อย่างสมเหตุสมผล", + weight: 10, + }, +]; + +const SCORE_LABELS = { + 1: "ต้องแก้ไขมาก", + 2: "ต้องปรับปรุง", + 3: "ผ่านเกณฑ์", + 4: "ดี", + 5: "ดีเยี่ยม", +}; + +function bandForPercent(pct) { + if (pct >= 90) return { label: "ดีเยี่ยม", tone: "band-excellent" }; + if (pct >= 75) return { label: "ดี", tone: "band-good" }; + if (pct >= 60) return { label: "ผ่านเกณฑ์", tone: "band-pass" }; + return { label: "ต้องปรับปรุง", tone: "band-low" }; +} + +function ScoreStepper({ value, onChange, disabled }) { + const clamp = (n) => Math.min(5, Math.max(1, n)); + + return ( +
+ + +
+ + {value ?? "—"} + /5 + + + {value ? SCORE_LABELS[value] : "ยังไม่ให้คะแนน"} + +
+ + +
+ ); +} + +export default function RubricScoring({ + title = "แบบประเมิน Rubric", + criteria = DEFAULT_CRITERIA, + onSubmit, +}) { + const [scores, setScores] = useState({}); + const [notes, setNotes] = useState({}); + const [openNoteId, setOpenNoteId] = useState(null); + + const totalWeight = useMemo( + () => criteria.reduce((sum, c) => sum + c.weight, 0), + [criteria] + ); + + const scoredCount = Object.keys(scores).length; + const isComplete = scoredCount === criteria.length; + + const weightedPercent = useMemo(() => { + if (scoredCount === 0) return null; + const earned = criteria.reduce((sum, c) => { + const s = scores[c.id]; + if (s == null) return sum; + return sum + (s / 5) * c.weight; + }, 0); + const weightScored = criteria.reduce( + (sum, c) => (scores[c.id] != null ? sum + c.weight : sum), + 0 + ); + if (weightScored === 0) return null; + // Show progress against total possible weight, not just what's scored, + // so the number reflects the whole rubric rather than a partial subset. + return Math.round((earned / totalWeight) * 100); + }, [scores, criteria, totalWeight, scoredCount]); + + const band = weightedPercent != null ? bandForPercent(weightedPercent) : null; + + const handleScore = (id, value) => { + setScores((prev) => ({ ...prev, [id]: value })); + }; + + const handleReset = () => { + setScores({}); + setNotes({}); + setOpenNoteId(null); + }; + + return ( +
+ {/* Header */} +
+
+

+ {title} +

+

+ {criteria.length} เกณฑ์ · น้ำหนักรวม {totalWeight}% +

+
+ + +
+ + {/* Criteria rows */} +
    + {criteria.map((c) => { + const value = scores[c.id] ?? null; + const noteOpen = openNoteId === c.id; + + return ( +
  • +
    +
    +
    + + {c.weight}% + +

    + {c.label} +

    +
    +

    + {c.description} +

    + + + + {noteOpen && ( +