From 717fd14986fea45f2a539068be67a7f132cdedad Mon Sep 17 00:00:00 2001 From: Ben Sima Date: Fri, 28 Nov 2025 02:08:50 -0500 Subject: Add create fact form in web UI All tests pass. The create fact form has been added to the web UI. Here' 1. **Added `FactCreateForm` data type** - New form type to handle the cr 2. **Added API route** - `POST /kb/create` endpoint that accepts the for 3. **Added handler** - `factCreateHandler` that creates a new fact using 4. **Added form UI** - A collapsible form on the KB page with fields for - Project (required) - Fact Content (required textarea) - Related Files (optional, comma-separated) - Confidence level (0.0-1.0, default 0.8) 5. **Added CSS styles** - Styling for the create fact section in both li Task-Id: t-158.6 --- Omni/Jr/Web.hs | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++++ Omni/Jr/Web/Style.hs | 14 ++++++++++ 2 files changed, 87 insertions(+) diff --git a/Omni/Jr/Web.hs b/Omni/Jr/Web.hs index eb8a751..e58992c 100644 --- a/Omni/Jr/Web.hs +++ b/Omni/Jr/Web.hs @@ -62,6 +62,7 @@ type API = :> QueryParam "type" Text :> Get '[Lucid.HTML] TaskListPage :<|> "kb" :> Get '[Lucid.HTML] KBPage + :<|> "kb" :> "create" :> ReqBody '[FormUrlEncoded] FactCreateForm :> PostRedirect :<|> "kb" :> Capture "id" Int :> Get '[Lucid.HTML] FactDetailPage :<|> "kb" :> Capture "id" Int :> "edit" :> ReqBody '[FormUrlEncoded] FactEditForm :> PostRedirect :<|> "kb" :> Capture "id" Int :> "delete" :> PostRedirect @@ -148,6 +149,16 @@ instance FromForm FactEditForm where let confidence = fromRight "0.8" (lookupUnique "confidence" form) Right (FactEditForm content files confidence) +data FactCreateForm = FactCreateForm Text Text Text Text + +instance FromForm FactCreateForm where + fromForm form = do + project <- parseUnique "project" form + content <- parseUnique "content" form + let files = fromRight "" (lookupUnique "files" form) + let confidence = fromRight "0.8" (lookupUnique "confidence" form) + Right (FactCreateForm project content files confidence) + data EpicsPage = EpicsPage [TaskCore.Task] [TaskCore.Task] newtype RecentActivityPartial = RecentActivityPartial [TaskCore.Task] @@ -570,6 +581,60 @@ instance Lucid.ToHtml KBPage where Lucid.div_ [Lucid.class_ "container"] <| do Lucid.h1_ "Knowledge Base" Lucid.p_ [Lucid.class_ "info-msg"] "Facts learned during task execution." + + Lucid.details_ [Lucid.class_ "create-fact-section"] <| do + Lucid.summary_ [Lucid.class_ "btn btn-primary create-fact-toggle"] "Create New Fact" + Lucid.form_ + [ Lucid.method_ "POST", + Lucid.action_ "/kb/create", + Lucid.class_ "fact-create-form" + ] + <| do + Lucid.div_ [Lucid.class_ "form-group"] <| do + Lucid.label_ [Lucid.for_ "project"] "Project:" + Lucid.input_ + [ Lucid.type_ "text", + Lucid.name_ "project", + Lucid.id_ "project", + Lucid.class_ "form-input", + Lucid.required_ "required", + Lucid.placeholder_ "e.g., Omni/Jr" + ] + Lucid.div_ [Lucid.class_ "form-group"] <| do + Lucid.label_ [Lucid.for_ "content"] "Fact Content:" + Lucid.textarea_ + [ Lucid.name_ "content", + Lucid.id_ "content", + Lucid.class_ "form-textarea", + Lucid.rows_ "4", + Lucid.required_ "required", + Lucid.placeholder_ "Describe the fact or knowledge..." + ] + "" + Lucid.div_ [Lucid.class_ "form-group"] <| do + Lucid.label_ [Lucid.for_ "files"] "Related Files (comma-separated):" + Lucid.input_ + [ Lucid.type_ "text", + Lucid.name_ "files", + Lucid.id_ "files", + Lucid.class_ "form-input", + Lucid.placeholder_ "path/to/file1.hs, path/to/file2.hs" + ] + Lucid.div_ [Lucid.class_ "form-group"] <| do + Lucid.label_ [Lucid.for_ "confidence"] "Confidence (0.0 - 1.0):" + Lucid.input_ + [ Lucid.type_ "number", + Lucid.name_ "confidence", + Lucid.id_ "confidence", + Lucid.class_ "form-input", + Lucid.step_ "0.1", + Lucid.min_ "0", + Lucid.max_ "1", + Lucid.value_ "0.8" + ] + Lucid.div_ [Lucid.class_ "form-actions"] <| do + Lucid.button_ [Lucid.type_ "submit", Lucid.class_ "btn btn-primary"] "Create Fact" + if null facts then Lucid.p_ [Lucid.class_ "empty-msg"] "No facts recorded yet." else Lucid.div_ [Lucid.class_ "task-list"] <| traverse_ renderFactCard facts @@ -1614,6 +1679,7 @@ server = :<|> statsHandler :<|> taskListHandler :<|> kbHandler + :<|> factCreateHandler :<|> factDetailHandler :<|> factEditHandler :<|> factDeleteHandler @@ -1682,6 +1748,13 @@ server = facts <- liftIO Fact.getAllFacts pure (KBPage facts) + factCreateHandler :: FactCreateForm -> Servant.Handler (Headers '[Header "Location" Text] NoContent) + factCreateHandler (FactCreateForm project content filesText confText) = do + let files = filter (not <. Text.null) (Text.splitOn "," (Text.strip filesText)) + confidence = fromMaybe 0.8 (readMaybe (Text.unpack confText)) + fid <- liftIO (Fact.createFact project content files Nothing confidence) + pure <| addHeader ("/kb/" <> tshow fid) NoContent + factDetailHandler :: Int -> Servant.Handler FactDetailPage factDetailHandler fid = do maybeFact <- liftIO (Fact.getFact fid) diff --git a/Omni/Jr/Web/Style.hs b/Omni/Jr/Web/Style.hs index b262037..7d6e7d6 100644 --- a/Omni/Jr/Web/Style.hs +++ b/Omni/Jr/Web/Style.hs @@ -789,6 +789,17 @@ formStyles = do padding (px 16) (px 16) (px 16) (px 16) borderRadius (px 4) (px 4) (px 4) (px 4) border (px 1) solid "#fecaca" + ".create-fact-section" ? do + marginBottom (px 16) + ".create-fact-toggle" ? do + cursor pointer + display inlineBlock + ".fact-create-form" ? do + marginTop (px 12) + padding (px 16) (px 16) (px 16) (px 16) + backgroundColor white + borderRadius (px 4) (px 4) (px 4) (px 4) + border (px 1) solid "#d1d5db" executionDetailsStyles :: Css executionDetailsStyles = do @@ -1274,6 +1285,9 @@ darkModeStyles = backgroundColor "#374151" borderColor "#4b5563" color "#f3f4f6" + ".fact-create-form" ? do + backgroundColor "#1f2937" + borderColor "#374151" -- Responsive dark mode: dropdown content needs background on mobile query Media.screen [Media.maxWidth (px 600)] <| do ".navbar-dropdown-content" ? do -- cgit v1.2.3