diff --git a/services/cms/src/utils/Gutenberg/ai/abilities.ts b/services/cms/src/utils/Gutenberg/ai/abilities.ts index 5a328daae6c..3355e6eb133 100644 --- a/services/cms/src/utils/Gutenberg/ai/abilities.ts +++ b/services/cms/src/utils/Gutenberg/ai/abilities.ts @@ -4,6 +4,7 @@ import type { AbilityDefinition } from "./types" import { requestParagraphSuggestions } from "@/services/backend/ai-suggestions" import type { + ParagraphSuggestionAction, ParagraphSuggestionContext, ParagraphSuggestionRequest, } from "@/shared-module/common/bindings" @@ -52,7 +53,7 @@ const buildParagraphSuggestionMeta = ( } export const buildParagraphSuggestionRequest = ( - action: string, + action: ParagraphSuggestionAction, input: ParagraphAbilityInput, ): ParagraphSuggestionRequest => ({ action, @@ -90,7 +91,7 @@ const fixSpellingAbility: AbilityDefinition< } function createPlaceholderAbility( - abilityName: string, + abilityName: ParagraphSuggestionAction, label: string, description: string, ): AbilityDefinition { diff --git a/services/cms/src/utils/Gutenberg/ai/menu.ts b/services/cms/src/utils/Gutenberg/ai/menu.ts index 2b9fcd64dee..2c7240e6d4c 100644 --- a/services/cms/src/utils/Gutenberg/ai/menu.ts +++ b/services/cms/src/utils/Gutenberg/ai/menu.ts @@ -1,9 +1,6 @@ -export type AiActionGroupId = - | "generate" - | "improve" - | "structure" - | "learning-support" - | "summaries" +import type { ParagraphSuggestionAction } from "@/shared-module/common/bindings" + +export type AiActionGroupId = "improve" | "structure" | "learning-support" | "summaries" export type AiSubmenuId = "tone" | "translate" @@ -15,7 +12,7 @@ export interface AiActionMeta { export interface AiActionDefinition { id: string - abilityName: string + abilityName: ParagraphSuggestionAction labelKey: string group: AiActionGroupId | AiSubmenuId meta?: AiActionMeta @@ -33,37 +30,37 @@ export interface AiSubmenuGroup { actions: AiActionDefinition[] } +/** Locale keys for group labels; use with t() so types match cms.json. */ +export type AiGroupLabelKey = + | "ai-group-improve" + | "ai-group-structure" + | "ai-group-learning-support" + | "ai-group-summaries" + +/** Locale keys for action labels; use with t() so types match cms.json. */ +export type AiActionLabelKey = + | "ai-improve-fix-spelling-grammar" + | "ai-improve-clarity" + | "ai-improve-flow" + | "ai-improve-concise" + | "ai-improve-expand-detail" + | "ai-improve-academic-style" + | "ai-structure-reorder-sentences" + | "ai-learning-simplify-beginners" + | "ai-summaries-one-sentence" + | "ai-summaries-two-three-sentences" + | "ai-summaries-key-takeaway" + | "ai-tone-academic-formal" + | "ai-tone-friendly-conversational" + | "ai-tone-encouraging-supportive" + | "ai-tone-neutral-objective" + | "ai-tone-confident" + | "ai-tone-serious" + | "ai-translate-english" + | "ai-translate-finnish" + | "ai-translate-swedish" + export const AI_ACTIONS: AiActionDefinition[] = [ - { - id: "generate-draft-from-notes", - abilityName: "moocfi/ai/generate-draft-from-notes", - labelKey: "ai-generate-draft-from-notes", - group: "generate", - }, - { - id: "generate-continue-paragraph", - abilityName: "moocfi/ai/generate-continue-paragraph", - labelKey: "ai-generate-continue-paragraph", - group: "generate", - }, - { - id: "generate-add-example", - abilityName: "moocfi/ai/generate-add-example", - labelKey: "ai-generate-add-example", - group: "generate", - }, - { - id: "generate-add-counterpoint", - abilityName: "moocfi/ai/generate-add-counterpoint", - labelKey: "ai-generate-add-counterpoint", - group: "generate", - }, - { - id: "generate-add-concluding-sentence", - abilityName: "moocfi/ai/generate-add-concluding-sentence", - labelKey: "ai-generate-add-concluding-sentence", - group: "generate", - }, { id: "improve-fix-spelling-grammar", abilityName: "moocfi/fix-spelling", @@ -100,72 +97,18 @@ export const AI_ACTIONS: AiActionDefinition[] = [ labelKey: "ai-improve-academic-style", group: "improve", }, - { - id: "structure-create-topic-sentence", - abilityName: "moocfi/ai/structure-create-topic-sentence", - labelKey: "ai-structure-create-topic-sentence", - group: "structure", - }, { id: "structure-reorder-sentences", abilityName: "moocfi/ai/structure-reorder-sentences", labelKey: "ai-structure-reorder-sentences", group: "structure", }, - { - id: "structure-split-into-paragraphs", - abilityName: "moocfi/ai/structure-split-into-paragraphs", - labelKey: "ai-structure-split-into-paragraphs", - group: "structure", - }, - { - id: "structure-combine-into-one", - abilityName: "moocfi/ai/structure-combine-into-one", - labelKey: "ai-structure-combine-into-one", - group: "structure", - }, - { - id: "structure-to-bullets", - abilityName: "moocfi/ai/structure-to-bullets", - labelKey: "ai-structure-to-bullets", - group: "structure", - }, - { - id: "structure-from-bullets", - abilityName: "moocfi/ai/structure-from-bullets", - labelKey: "ai-structure-from-bullets", - group: "structure", - }, { id: "learning-simplify-beginners", abilityName: "moocfi/ai/learning-simplify-beginners", labelKey: "ai-learning-simplify-beginners", group: "learning-support", }, - { - id: "learning-add-definitions", - abilityName: "moocfi/ai/learning-add-definitions", - labelKey: "ai-learning-add-definitions", - group: "learning-support", - }, - { - id: "learning-add-analogy", - abilityName: "moocfi/ai/learning-add-analogy", - labelKey: "ai-learning-add-analogy", - group: "learning-support", - }, - { - id: "learning-add-practice-question", - abilityName: "moocfi/ai/learning-add-practice-question", - labelKey: "ai-learning-add-practice-question", - group: "learning-support", - }, - { - id: "learning-add-check-understanding", - abilityName: "moocfi/ai/learning-add-check-understanding", - labelKey: "ai-learning-add-check-understanding", - group: "learning-support", - }, { id: "summaries-one-sentence", abilityName: "moocfi/ai/summaries-one-sentence", @@ -187,11 +130,6 @@ export const AI_ACTIONS: AiActionDefinition[] = [ ] export const AI_GROUPS: AiMenuGroup[] = [ - { - id: "generate", - labelKey: "ai-group-generate", - actions: AI_ACTIONS.filter((action) => action.group === "generate"), - }, { id: "improve", labelKey: "ai-group-improve", diff --git a/services/cms/src/utils/Gutenberg/withParagraphAiToolbarAction.tsx b/services/cms/src/utils/Gutenberg/withParagraphAiToolbarAction.tsx index 8f67ff48dfc..dcc2a37df8e 100644 --- a/services/cms/src/utils/Gutenberg/withParagraphAiToolbarAction.tsx +++ b/services/cms/src/utils/Gutenberg/withParagraphAiToolbarAction.tsx @@ -30,10 +30,13 @@ import { AI_TRANSLATE_SUBMENU, type AiActionDefinition, type AiActionGroupId, + type AiActionLabelKey, + type AiGroupLabelKey, } from "./ai/menu" import DiffFormatter from "@/shared-module/common/components/DiffFormatter" import { useDialog } from "@/shared-module/common/components/dialogs/DialogProvider" +import { baseTheme } from "@/shared-module/common/styles" const PARAGRAPH_BLOCK_NAME = "core/paragraph" @@ -42,14 +45,9 @@ const SUBMENU_TRANSLATE = "translate" as const type SubmenuState = AiActionGroupId | typeof SUBMENU_TONE | typeof SUBMENU_TRANSLATE | null -const WP_ADMIN_BORDER_COLOR = "var(--wp-admin-border-color, var(--wp-admin-theme-color-darker-20))" -const WP_ADMIN_SURFACE_COLOR = - "var(--wp-components-color-background, var(--wp-admin-theme-color-darker-20))" -const WP_ADMIN_ACCENT_COLOR = "var(--wp-admin-theme-color, var(--wp-admin-theme-color-darker-10))" -const WP_ADMIN_ACCENT_SURFACE_COLOR = - "var(--wp-admin-theme-color-darker-20, var(--wp-admin-theme-color))" -const WP_ADMIN_TEXT_COLOR = - "var(--wp-components-color-foreground, var(--wp-admin-theme-color-darker-10))" +const BORDER_COLOR = baseTheme.colors.gray[200] +const ACCENT_COLOR = baseTheme.colors.green[600] +const TEXT_COLOR = baseTheme.colors.gray[700] interface ParagraphBlockProps { name: string @@ -201,7 +199,7 @@ const withParagraphAiToolbarAction = createHigherOrderComponent((BlockEdit) => { className={css` min-width: 220px; padding-right: 0.5rem; - border-right: ${hasSubmenu ? `1px solid ${WP_ADMIN_BORDER_COLOR}` : "none"}; + border-right: ${hasSubmenu ? `1px solid ${BORDER_COLOR}` : "none"}; `} > @@ -216,7 +214,7 @@ const withParagraphAiToolbarAction = createHigherOrderComponent((BlockEdit) => { aria-haspopup="menu" aria-expanded={submenu === group.id} > - {t(group.labelKey as "ai-group-generate")} + {t(group.labelKey as AiGroupLabelKey)} ))} @@ -248,7 +246,7 @@ const withParagraphAiToolbarAction = createHigherOrderComponent((BlockEdit) => { margin-left: 0.5rem; `} > - + {sectionGroup.actions.map((action) => ( { void handleAction(action, onClose) }} > - {t(action.labelKey as "ai-generate-draft-from-notes")} + {t(action.labelKey as AiActionLabelKey)} ))} @@ -277,7 +275,7 @@ const withParagraphAiToolbarAction = createHigherOrderComponent((BlockEdit) => { void handleAction(action, onClose) }} > - {t(action.labelKey as "ai-tone-academic-formal")} + {t(action.labelKey as AiActionLabelKey)} ))} @@ -298,7 +296,7 @@ const withParagraphAiToolbarAction = createHigherOrderComponent((BlockEdit) => { void handleAction(action, onClose) }} > - {t(action.labelKey as "ai-translate-english")} + {t(action.labelKey as AiActionLabelKey)} ))} @@ -324,11 +322,12 @@ const withParagraphAiToolbarAction = createHigherOrderComponent((BlockEdit) => { const ParagraphAiSuggestionDialog = ({ originalText, - originalHtml, - allowedHtmlTagNames, + originalHtml: _originalHtml, + allowedHtmlTagNames: _allowedHtmlTagNames, suggestions, onSelectionChange, }: ParagraphAiSuggestionDialogProps) => { + const { t } = useTranslation() const [selectedIndex, setSelectedIndex] = useState(0) const handleSelect = (index: number) => { @@ -336,96 +335,72 @@ const withParagraphAiToolbarAction = createHigherOrderComponent((BlockEdit) => { onSelectionChange?.(index) } - const suggestionText = extractPlainTextFromHtml(suggestions[selectedIndex] ?? "") - const diffChanges = diffWords(originalText ?? "", suggestionText) - - let originalPreviewHtml = "" - let suggestionPreviewHtml = "" - try { - originalPreviewHtml = sanitizeParagraphHtml(originalHtml, { - allowedTagNames: allowedHtmlTagNames, - }) - suggestionPreviewHtml = sanitizeParagraphHtml(suggestions[selectedIndex] ?? "", { - allowedTagNames: allowedHtmlTagNames, - }) - } catch { - originalPreviewHtml = "" - suggestionPreviewHtml = "" - } + const hasMultiple = suggestions.length > 1 return (
-
- {suggestions.map((suggestion, index) => ( + {suggestions.map((suggestion, index) => { + const suggestionText = extractPlainTextFromHtml(suggestion) + const diffChanges = diffWords(originalText ?? "", suggestionText) + const isSelected = selectedIndex === index + + return ( - ))} -
-
-

- -

-
- {} -
-
-
+ ) + })}
) } diff --git a/services/headless-lms/chatbot/src/cms_ai_suggestion.rs b/services/headless-lms/chatbot/src/cms_ai_suggestion.rs index 3112a7a9f9a..d52a9d8aa5e 100644 --- a/services/headless-lms/chatbot/src/cms_ai_suggestion.rs +++ b/services/headless-lms/chatbot/src/cms_ai_suggestion.rs @@ -14,6 +14,7 @@ use crate::{ }; use headless_lms_models::application_task_default_language_models::TaskLMSpec; use headless_lms_models::chatbot_conversation_messages::MessageRole; +use headless_lms_models::cms_ai::ParagraphSuggestionAction; use headless_lms_utils::{ApplicationConfiguration, prelude::BackendError}; /// Structured LLM response for CMS paragraph suggestions. @@ -22,11 +23,110 @@ struct CmsParagraphSuggestionResponse { suggestions: Vec, } +/// Returns a short human-readable instruction for the given action id. Used so the model +/// sees a precise operation description instead of an opaque id, and so we can stress +/// "only this, nothing else" per action. +fn action_instruction(action: ParagraphSuggestionAction) -> String { + let s = match action { + ParagraphSuggestionAction::GenerateDraftFromNotes => { + "Write a coherent draft paragraph using only the ideas present in the notes. Fill in connective wording as needed, but do not invent facts, examples, or claims not supported by the notes." + } + ParagraphSuggestionAction::GenerateContinueParagraph => { + "Append 1-3 new sentences that continue the paragraph's current idea and tone. Keep the existing paragraph text verbatim, and do not rewrite, reorder, or delete any existing words." + } + ParagraphSuggestionAction::GenerateAddExample => { + "Append one concise example that directly supports the paragraph's main point. Keep the existing text unchanged, and do not introduce unrelated facts or a new topic." + } + ParagraphSuggestionAction::GenerateAddCounterpoint => { + "Append one concise, relevant counterpoint or limitation that stays in scope with the paragraph's topic. Keep the existing text unchanged, and do not drift into a different topic." + } + ParagraphSuggestionAction::GenerateAddConcludingSentence => { + "Append exactly one concluding sentence that reinforces the paragraph's main point. Do not introduce new claims, and keep the existing text unchanged." + } + ParagraphSuggestionAction::FixSpelling => { + "Correct spelling, grammar, punctuation, and obvious typos only. Do not change meaning, tone, structure, or phrasing beyond what is required for correctness." + } + ParagraphSuggestionAction::ImproveClarity => { + "Improve clarity and readability with minimal rewrites. Preserve meaning and tone, and do not add new ideas, remove important details, or change the overall message." + } + ParagraphSuggestionAction::ImproveFlow => { + "Improve flow by refining transitions and, if needed, lightly reordering sentences for smoother reading. Preserve all original claims and details, and do not add new content or change the tone." + } + ParagraphSuggestionAction::ImproveConcise => { + "Make the paragraph more concise by removing redundancy and tightening phrasing. Preserve meaning and tone, and do not remove important details or add new ideas." + } + ParagraphSuggestionAction::ImproveExpandDetail => { + "Expand the paragraph with specific, on-topic supporting detail that deepens existing ideas. Preserve the original claims and tone, and do not add unrelated content or change the topic." + } + ParagraphSuggestionAction::ImproveAcademicStyle => { + "Strengthen the academic style by making the wording more precise, formal, and discipline-appropriate. Preserve meaning and evidence level, and do not add or remove content beyond stylistic refinement." + } + ParagraphSuggestionAction::StructureCreateTopicSentence => { + "Add or refine a topic sentence that clearly introduces the paragraph's main idea. Do not rewrite unrelated parts of the paragraph beyond minimal adjustments needed to fit the topic sentence." + } + ParagraphSuggestionAction::StructureReorderSentences => { + "Reorder the existing sentences to improve logical progression. Do not add, remove, split, combine, or materially rewrite sentence content." + } + ParagraphSuggestionAction::StructureSplitIntoParagraphs => { + "Split the content into multiple paragraphs at appropriate break points. Preserve wording and order, and do not add, remove, or rewrite content beyond paragraph breaks." + } + ParagraphSuggestionAction::StructureCombineIntoOne => { + "Combine the content into a single well-formed paragraph. Preserve wording and order, and do not add, remove, or rewrite content beyond joining the paragraphs." + } + ParagraphSuggestionAction::StructureToBullets => { + "Convert the content into bullet points while preserving the original information and order. Do not add, remove, or materially rewrite content beyond the format change." + } + ParagraphSuggestionAction::StructureFromBullets => { + "Convert the bullet points into prose while preserving the original information and order. Do not add, remove, or materially rewrite content beyond the format change." + } + ParagraphSuggestionAction::LearningSimplifyBeginners => { + "Simplify the explanation for beginners using clearer language and less assumed prior knowledge. Preserve factual accuracy, and do not add new concepts, tangents, or unrelated examples." + } + ParagraphSuggestionAction::LearningAddDefinitions => { + "Add brief inline definitions for technical or unfamiliar terms at their first relevant mention. Keep the existing explanation intact, and do not rewrite unrelated parts of the paragraph." + } + ParagraphSuggestionAction::LearningAddAnalogy => { + "Append one short, relevant analogy that clarifies the core concept. Keep the existing text unchanged, and do not introduce unrelated ideas or a separate topic." + } + ParagraphSuggestionAction::LearningAddPracticeQuestion => { + "Append exactly one practice question that tests the paragraph's key idea. Do not include an answer, and keep the existing text unchanged." + } + ParagraphSuggestionAction::LearningAddCheckUnderstanding => { + "Append exactly one quick check-for-understanding question followed by a one-sentence model answer. Keep the existing text unchanged, and do not add extra explanation beyond that." + } + ParagraphSuggestionAction::SummariesOneSentence => { + "Summarize the paragraph in exactly one sentence. Preserve the central meaning, and do not add interpretation, advice, or extra detail." + } + ParagraphSuggestionAction::SummariesTwoThreeSentences => { + "Summarize the paragraph in exactly two or three sentences. Preserve the central meaning, and do not add interpretation, advice, or extra detail." + } + ParagraphSuggestionAction::SummariesKeyTakeaway => { + "State exactly one sentence describing the single most important takeaway for students. Do not add extra interpretation, advice, or supporting detail." + } + ParagraphSuggestionAction::ToneAcademicFormal + | ParagraphSuggestionAction::ToneFriendlyConversational + | ParagraphSuggestionAction::ToneEncouragingSupportive + | ParagraphSuggestionAction::ToneNeutralObjective + | ParagraphSuggestionAction::ToneConfident + | ParagraphSuggestionAction::ToneSerious => { + "Adjust wording only as needed to match the target tone. Preserve all facts, claims, detail level, and overall structure, and do not add, remove, or materially reframe content." + } + ParagraphSuggestionAction::TranslateEnglish + | ParagraphSuggestionAction::TranslateFinnish + | ParagraphSuggestionAction::TranslateSwedish => { + "Translate the paragraph into the target language while preserving meaning, domain terminology, and inline formatting. Do not add, omit, simplify, paraphrase, or reinterpret the content." + } + }; + s.to_string() +} + /// System prompt for generating multiple alternative paragraph suggestions for CMS content. const SYSTEM_PROMPT: &str = r#"You are helping course staff improve a single paragraph of course material. Your task is to generate several alternative versions of the given paragraph based on the requested action. +Critical: Perform only the requested action. Do not make any additional edits beyond what is strictly necessary to complete that action. Preserve wording, structure, tone, detail level, and sentence order unless the requested action explicitly requires changing them. For example: if the action is spelling/grammar, only fix spelling and grammar; if it is translation, only translate; if it is tone, only change tone; if it is clarity, only improve clarity. Do not also summarize, expand, simplify, reorder, or otherwise rewrite unless the requested action requires it. + General rules: - Always preserve the original meaning and important details unless the action explicitly asks to add or remove content. - Maintain a clear, pedagogical tone appropriate for course materials. @@ -34,13 +134,12 @@ General rules: About the suggestions: - Produce multiple alternative rewrites of the same paragraph. -- Each suggestion must be meaningfully different in structure, emphasis, or level of detail. -- Do NOT output suggestions that only differ by tiny edits (e.g. one or two word changes). +- Do not output duplicate or near-duplicate suggestions. - Keep each suggestion self-contained and suitable for direct insertion into the material. You will receive: - The original paragraph text. -- The requested action (e.g. fix spelling, improve clarity, change tone, translate). +- The requested action (a precise instruction; follow it and do nothing else). - Optional metadata such as target tone and target language. Your output must follow the JSON schema exactly: @@ -48,12 +147,12 @@ Your output must follow the JSON schema exactly: "suggestions": ["...", "...", "..."] }"#; -/// User prompt prefix; the concrete action and metadata will be appended. -const USER_PROMPT_PREFIX: &str = "Generate multiple rewritten versions of the paragraph according to the requested action and metadata. The paragraph may contain inline HTML markup valid inside a Gutenberg paragraph; preserve existing inline tags (links, emphasis, code, sub/superscripts) where possible, do not introduce block-level elements, and do not add new formatting to spans of text that were previously unformatted. Return JSON only."; +/// User prompt prefix; the concrete action instruction and paragraph will be appended. +const USER_PROMPT_PREFIX: &str = "Apply only the requested action to the paragraph below. Do not make any other changes. The paragraph may contain inline HTML markup valid inside a Gutenberg paragraph; preserve existing inline tags (links, emphasis, code, sub/superscripts) where possible, do not introduce block-level elements, and do not add new formatting to spans of text that were previously unformatted. Return JSON only."; /// Input payload for CMS paragraph suggestions. pub struct CmsParagraphSuggestionInput { - pub action: String, + pub action: ParagraphSuggestionAction, pub content: String, pub is_html: bool, pub meta_tone: Option, @@ -76,9 +175,11 @@ pub async fn generate_paragraph_suggestions( meta_setting_type, } = input; + let action_instruction = action_instruction(*action); + let mut system_instructions = SYSTEM_PROMPT.to_owned(); system_instructions.push_str("\n\nRequested action: "); - system_instructions.push_str(action); + system_instructions.push_str(&action_instruction); if let Some(tone) = meta_tone { system_instructions.push_str("\nTarget tone: "); system_instructions.push_str(tone); @@ -95,8 +196,9 @@ pub async fn generate_paragraph_suggestions( let paragraph_source = content.as_str(); let user_message_content = format!( - "{prefix}\n\nOriginal paragraph (may include inline HTML):\n{paragraph}", + "{prefix}\n\nRequested action: {action_instruction}\n\nOriginal paragraph (may include inline HTML):\n{paragraph}", prefix = USER_PROMPT_PREFIX, + action_instruction = action_instruction, paragraph = paragraph_source ); diff --git a/services/headless-lms/models/src/cms_ai.rs b/services/headless-lms/models/src/cms_ai.rs new file mode 100644 index 00000000000..798df40c50d --- /dev/null +++ b/services/headless-lms/models/src/cms_ai.rs @@ -0,0 +1,116 @@ +use crate::prelude::*; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[cfg_attr(feature = "ts_rs", derive(TS))] +pub enum ParagraphSuggestionAction { + #[serde(rename = "moocfi/ai/generate-draft-from-notes")] + GenerateDraftFromNotes, + #[serde(rename = "moocfi/ai/generate-continue-paragraph")] + GenerateContinueParagraph, + #[serde(rename = "moocfi/ai/generate-add-example")] + GenerateAddExample, + #[serde(rename = "moocfi/ai/generate-add-counterpoint")] + GenerateAddCounterpoint, + #[serde(rename = "moocfi/ai/generate-add-concluding-sentence")] + GenerateAddConcludingSentence, + #[serde(rename = "moocfi/fix-spelling")] + FixSpelling, + #[serde(rename = "moocfi/ai/improve-clarity")] + ImproveClarity, + #[serde(rename = "moocfi/ai/improve-flow")] + ImproveFlow, + #[serde(rename = "moocfi/ai/improve-concise")] + ImproveConcise, + #[serde(rename = "moocfi/ai/improve-expand-detail")] + ImproveExpandDetail, + #[serde(rename = "moocfi/ai/improve-academic-style")] + ImproveAcademicStyle, + #[serde(rename = "moocfi/ai/structure-create-topic-sentence")] + StructureCreateTopicSentence, + #[serde(rename = "moocfi/ai/structure-reorder-sentences")] + StructureReorderSentences, + #[serde(rename = "moocfi/ai/structure-split-into-paragraphs")] + StructureSplitIntoParagraphs, + #[serde(rename = "moocfi/ai/structure-combine-into-one")] + StructureCombineIntoOne, + #[serde(rename = "moocfi/ai/structure-to-bullets")] + StructureToBullets, + #[serde(rename = "moocfi/ai/structure-from-bullets")] + StructureFromBullets, + #[serde(rename = "moocfi/ai/learning-simplify-beginners")] + LearningSimplifyBeginners, + #[serde(rename = "moocfi/ai/learning-add-definitions")] + LearningAddDefinitions, + #[serde(rename = "moocfi/ai/learning-add-analogy")] + LearningAddAnalogy, + #[serde(rename = "moocfi/ai/learning-add-practice-question")] + LearningAddPracticeQuestion, + #[serde(rename = "moocfi/ai/learning-add-check-understanding")] + LearningAddCheckUnderstanding, + #[serde(rename = "moocfi/ai/summaries-one-sentence")] + SummariesOneSentence, + #[serde(rename = "moocfi/ai/summaries-two-three-sentences")] + SummariesTwoThreeSentences, + #[serde(rename = "moocfi/ai/summaries-key-takeaway")] + SummariesKeyTakeaway, + #[serde(rename = "moocfi/ai/tone-academic-formal")] + ToneAcademicFormal, + #[serde(rename = "moocfi/ai/tone-friendly-conversational")] + ToneFriendlyConversational, + #[serde(rename = "moocfi/ai/tone-encouraging-supportive")] + ToneEncouragingSupportive, + #[serde(rename = "moocfi/ai/tone-neutral-objective")] + ToneNeutralObjective, + #[serde(rename = "moocfi/ai/tone-confident")] + ToneConfident, + #[serde(rename = "moocfi/ai/tone-serious")] + ToneSerious, + #[serde(rename = "moocfi/ai/translate-english")] + TranslateEnglish, + #[serde(rename = "moocfi/ai/translate-finnish")] + TranslateFinnish, + #[serde(rename = "moocfi/ai/translate-swedish")] + TranslateSwedish, +} + +impl ParagraphSuggestionAction { + /// Returns the API string value for this action. + pub fn as_str(self) -> &'static str { + match self { + Self::GenerateDraftFromNotes => "moocfi/ai/generate-draft-from-notes", + Self::GenerateContinueParagraph => "moocfi/ai/generate-continue-paragraph", + Self::GenerateAddExample => "moocfi/ai/generate-add-example", + Self::GenerateAddCounterpoint => "moocfi/ai/generate-add-counterpoint", + Self::GenerateAddConcludingSentence => "moocfi/ai/generate-add-concluding-sentence", + Self::FixSpelling => "moocfi/fix-spelling", + Self::ImproveClarity => "moocfi/ai/improve-clarity", + Self::ImproveFlow => "moocfi/ai/improve-flow", + Self::ImproveConcise => "moocfi/ai/improve-concise", + Self::ImproveExpandDetail => "moocfi/ai/improve-expand-detail", + Self::ImproveAcademicStyle => "moocfi/ai/improve-academic-style", + Self::StructureCreateTopicSentence => "moocfi/ai/structure-create-topic-sentence", + Self::StructureReorderSentences => "moocfi/ai/structure-reorder-sentences", + Self::StructureSplitIntoParagraphs => "moocfi/ai/structure-split-into-paragraphs", + Self::StructureCombineIntoOne => "moocfi/ai/structure-combine-into-one", + Self::StructureToBullets => "moocfi/ai/structure-to-bullets", + Self::StructureFromBullets => "moocfi/ai/structure-from-bullets", + Self::LearningSimplifyBeginners => "moocfi/ai/learning-simplify-beginners", + Self::LearningAddDefinitions => "moocfi/ai/learning-add-definitions", + Self::LearningAddAnalogy => "moocfi/ai/learning-add-analogy", + Self::LearningAddPracticeQuestion => "moocfi/ai/learning-add-practice-question", + Self::LearningAddCheckUnderstanding => "moocfi/ai/learning-add-check-understanding", + Self::SummariesOneSentence => "moocfi/ai/summaries-one-sentence", + Self::SummariesTwoThreeSentences => "moocfi/ai/summaries-two-three-sentences", + Self::SummariesKeyTakeaway => "moocfi/ai/summaries-key-takeaway", + Self::ToneAcademicFormal => "moocfi/ai/tone-academic-formal", + Self::ToneFriendlyConversational => "moocfi/ai/tone-friendly-conversational", + Self::ToneEncouragingSupportive => "moocfi/ai/tone-encouraging-supportive", + Self::ToneNeutralObjective => "moocfi/ai/tone-neutral-objective", + Self::ToneConfident => "moocfi/ai/tone-confident", + Self::ToneSerious => "moocfi/ai/tone-serious", + Self::TranslateEnglish => "moocfi/ai/translate-english", + Self::TranslateFinnish => "moocfi/ai/translate-finnish", + Self::TranslateSwedish => "moocfi/ai/translate-swedish", + } + } +} diff --git a/services/headless-lms/models/src/lib.rs b/services/headless-lms/models/src/lib.rs index 00c0233f7bc..c685e070976 100644 --- a/services/headless-lms/models/src/lib.rs +++ b/services/headless-lms/models/src/lib.rs @@ -19,6 +19,7 @@ pub mod chatbot_conversation_messages_citations; pub mod chatbot_conversation_suggested_messages; pub mod chatbot_conversations; pub mod chatbot_page_sync_statuses; +pub mod cms_ai; pub mod code_giveaway_codes; pub mod code_giveaways; pub mod course_background_question_answers; diff --git a/services/headless-lms/server/src/controllers/cms/ai_suggestions.rs b/services/headless-lms/server/src/controllers/cms/ai_suggestions.rs index 141135e28e1..985c81cbed4 100644 --- a/services/headless-lms/server/src/controllers/cms/ai_suggestions.rs +++ b/services/headless-lms/server/src/controllers/cms/ai_suggestions.rs @@ -1,45 +1,9 @@ //! Controllers for requests starting with `/api/v0/cms/ai-suggestions`. use headless_lms_models::application_task_default_language_models::{self, ApplicationTask}; +use headless_lms_models::cms_ai::ParagraphSuggestionAction; use crate::prelude::*; -const ALLOWED_PARAGRAPH_ACTIONS: &[&str] = &[ - "moocfi/ai/generate-draft-from-notes", - "moocfi/ai/generate-continue-paragraph", - "moocfi/ai/generate-add-example", - "moocfi/ai/generate-add-counterpoint", - "moocfi/ai/generate-add-concluding-sentence", - "moocfi/fix-spelling", - "moocfi/ai/improve-clarity", - "moocfi/ai/improve-flow", - "moocfi/ai/improve-concise", - "moocfi/ai/improve-expand-detail", - "moocfi/ai/improve-academic-style", - "moocfi/ai/structure-create-topic-sentence", - "moocfi/ai/structure-reorder-sentences", - "moocfi/ai/structure-split-into-paragraphs", - "moocfi/ai/structure-combine-into-one", - "moocfi/ai/structure-to-bullets", - "moocfi/ai/structure-from-bullets", - "moocfi/ai/learning-simplify-beginners", - "moocfi/ai/learning-add-definitions", - "moocfi/ai/learning-add-analogy", - "moocfi/ai/learning-add-practice-question", - "moocfi/ai/learning-add-check-understanding", - "moocfi/ai/summaries-one-sentence", - "moocfi/ai/summaries-two-three-sentences", - "moocfi/ai/summaries-key-takeaway", - "moocfi/ai/tone-academic-formal", - "moocfi/ai/tone-friendly-conversational", - "moocfi/ai/tone-encouraging-supportive", - "moocfi/ai/tone-neutral-objective", - "moocfi/ai/tone-confident", - "moocfi/ai/tone-serious", - "moocfi/ai/translate-english", - "moocfi/ai/translate-finnish", - "moocfi/ai/translate-swedish", -]; - #[derive(Debug, Serialize, Deserialize)] #[cfg_attr(feature = "ts_rs", derive(TS))] pub struct ParagraphSuggestionMeta { @@ -59,7 +23,7 @@ pub struct ParagraphSuggestionContext { #[derive(Debug, Serialize, Deserialize)] #[cfg_attr(feature = "ts_rs", derive(TS))] pub struct ParagraphSuggestionRequest { - pub action: String, + pub action: ParagraphSuggestionAction, pub content: String, pub is_html: bool, pub meta: Option, @@ -97,14 +61,6 @@ async fn suggest_paragraph( )); } - if !ALLOWED_PARAGRAPH_ACTIONS.contains(&payload.action.as_str()) { - return Err(ControllerError::new( - ControllerErrorType::BadRequest, - "Unsupported paragraph suggestion action.".to_string(), - None, - )); - } - // Authorize: prefer page-level edit permission when page_id is available, // otherwise require that the user can teach at least one course. let token = if let Some(ParagraphSuggestionContext { @@ -125,7 +81,7 @@ async fn suggest_paragraph( let meta = payload.meta.as_ref(); let generator_input = headless_lms_chatbot::cms_ai_suggestion::CmsParagraphSuggestionInput { - action: payload.action.clone(), + action: payload.action, content: payload.content.clone(), is_html: payload.is_html, meta_tone: meta.and_then(|m| m.tone.clone()), diff --git a/services/headless-lms/server/src/ts_binding_generator.rs b/services/headless-lms/server/src/ts_binding_generator.rs index 92597510c7e..89d77a2bba1 100644 --- a/services/headless-lms/server/src/ts_binding_generator.rs +++ b/services/headless-lms/server/src/ts_binding_generator.rs @@ -55,6 +55,7 @@ fn models(target: &mut File) { chapters::ChapterStatus, chapters::ChapterUpdate, chapters::ChapterWithStatus, + cms_ai::ParagraphSuggestionAction, chapters::DatabaseChapter, chapters::NewChapter, chapters::UserCourseInstanceChapterProgress, diff --git a/shared-module/packages/common/src/bindings.guard.ts b/shared-module/packages/common/src/bindings.guard.ts index a807ded84f5..1dfcf28f6e5 100644 --- a/shared-module/packages/common/src/bindings.guard.ts +++ b/shared-module/packages/common/src/bindings.guard.ts @@ -5,7 +5,7 @@ * Generated type guards for "bindings.ts". * WARNING: Do not manually change this file. */ -import { Action, ActionOnResource, Resource, ErrorData, ErrorResponse, SpecRequest, ConsentQuery, ConsentResponse, ConsentDenyQuery, CertificateAllRequirements, CertificateConfiguration, CertificateConfigurationAndRequirements, CertificateTextAnchor, PaperSize, Chapter, ChapterStatus, ChapterUpdate, ChapterWithStatus, DatabaseChapter, NewChapter, UserCourseInstanceChapterProgress, ChapterAvailability, UserChapterProgress, CourseUserInfo, ChapterLockPreview, UnreturnedExercise, ChatbotConfiguration, NewChatbotConf, VerbosityLevel, ReasoningEffortLevel, ChatbotConfigurationModel, ChatbotConversationMessage, MessageRole, ChatbotConversationMessageCitation, ChatbotConversationMessageToolCall, ChatbotConversationMessageToolOutput, ChatbotConversationSuggestedMessage, ChatbotConversation, ChatbotConversationInfo, CodeGiveawayCode, CodeGiveaway, CodeGiveawayStatus, NewCodeGiveaway, CourseBackgroundQuestionAnswer, NewCourseBackgroundQuestionAnswer, CourseBackgroundQuestion, CourseBackgroundQuestionType, CourseBackgroundQuestionsAndAnswers, CourseCustomPrivacyPolicyCheckboxText, CourseEnrollmentInfo, CourseEnrollmentsInfo, CourseInstanceEnrollment, CourseInstanceEnrollmentsInfo, ChapterScore, CourseInstance, CourseInstanceForm, PointMap, Points, CourseModuleCompletion, CourseModuleCompletionWithRegistrationInfo, AutomaticCompletionRequirements, CompletionPolicy, CourseModule, ModifiedModule, ModuleUpdates, NewCourseModule, NewModule, Course, CourseMaterialCourse, CourseBreadcrumbInfo, CourseCount, CourseStructure, CourseUpdate, NewCourse, CourseLanguageVersionNavigationInfo, EmailTemplate, EmailTemplateNew, EmailTemplateUpdate, EmailTemplateType, CourseExam, Exam, ExamEnrollment, ExamInstructions, ExamInstructionsUpdate, NewExam, OrgExam, ExerciseRepository, ExerciseRepositoryStatus, CourseMaterialExerciseServiceInfo, ExerciseServiceInfoApi, ExerciseService, ExerciseServiceIframeRenderingInfo, ExerciseServiceNewOrUpdate, AnswerRequiringAttention, ExerciseAnswersInCourseRequiringAttentionCount, ExerciseSlideSubmission, ExerciseSlideSubmissionAndUserExerciseState, ExerciseSlideSubmissionAndUserExerciseStateList, ExerciseSlideSubmissionCount, ExerciseSlideSubmissionCountByExercise, ExerciseSlideSubmissionCountByWeekAndHour, ExerciseSlideSubmissionInfo, CourseMaterialExerciseSlide, ExerciseSlide, ExerciseTaskGrading, ExerciseTaskGradingResult, UserPointsUpdateStrategy, ExerciseTaskSubmission, PeerOrSelfReviewsReceived, CourseMaterialExerciseTask, ExerciseTask, ActivityProgress, CourseMaterialExercise, Exercise, ExerciseGradingStatus, ExerciseStatus, ExerciseStatusSummaryForUser, GradingProgress, ExerciseResetLog, Feedback, FeedbackBlock, FeedbackCount, NewFeedback, FlaggedAnswer, NewFlaggedAnswer, NewFlaggedAnswerWithToken, ReportReason, GeneratedCertificate, CertificateUpdateRequest, Term, TermUpdate, AverageMetric, CohortActivity, CountResult, StudentsByCountryTotalsResult, CustomViewExerciseSubmissions, CustomViewExerciseTaskGrading, CustomViewExerciseTaskSpec, CustomViewExerciseTaskSubmission, CustomViewExerciseTasks, CourseCompletionStats, DomainCompletionStats, GlobalCourseModuleStatEntry, GlobalStatEntry, TimeGranularity, AnswerRequiringAttentionWithTasks, AnswersRequiringAttention, StudentExerciseSlideSubmission, StudentExerciseSlideSubmissionResult, StudentExerciseTaskSubmission, StudentExerciseTaskSubmissionResult, CourseMaterialPeerOrSelfReviewData, CourseMaterialPeerOrSelfReviewDataAnswerToReview, CourseMaterialPeerOrSelfReviewQuestionAnswer, CourseMaterialPeerOrSelfReviewSubmission, CompletionRegistrationLink, CourseInstanceCompletionSummary, ManualCompletionPreview, ManualCompletionPreviewUser, TeacherManualCompletion, TeacherManualCompletionRequest, UserCompletionInformation, UserCourseModuleCompletion, UserModuleCompletionStatus, UserWithModuleCompletions, ProgressOverview, CompletionGridRow, CertificateGridRow, UserMarketingConsent, MaterialReference, NewMaterialReference, Organization, AuthorizedClientInfo, PageAudioFile, HistoryChangeReason, PageHistory, PageVisitDatumSummaryByCourse, PageVisitDatumSummaryByCoursesCountries, PageVisitDatumSummaryByCourseDeviceTypes, PageVisitDatumSummaryByPages, CmsPageExercise, CmsPageExerciseSlide, CmsPageExerciseTask, CmsPageUpdate, ContentManagementPage, CoursePageWithUserData, ExerciseWithExerciseTasks, HistoryRestoreData, IsChapterFrontPage, NewPage, Page, PageChapterAndCourseInformation, PageDetailsUpdate, PageInfo, PageNavigationInformation, PageRoutingData, PageSearchResult, PageWithExercises, SearchRequest, PartnerBlockNew, PartnersBlock, CmsPeerOrSelfReviewConfig, CmsPeerOrSelfReviewConfiguration, CourseMaterialPeerOrSelfReviewConfig, PeerOrSelfReviewConfig, PeerReviewProcessingStrategy, PeerOrSelfReviewAnswer, PeerOrSelfReviewQuestionAndAnswer, PeerOrSelfReviewQuestionSubmission, PeerReviewWithQuestionsAndAnswers, CmsPeerOrSelfReviewQuestion, PeerOrSelfReviewQuestion, PeerOrSelfReviewQuestionType, PeerOrSelfReviewSubmission, PeerOrSelfReviewSubmissionWithSubmissionOwner, PeerReviewQueueEntry, PendingRole, PlaygroundExample, PlaygroundExampleData, PrivacyLink, BlockProposal, BlockProposalAction, BlockProposalInfo, EditedBlockNoLongerExistsData, EditedBlockStillExistsData, NewProposedBlockEdit, ProposalStatus, EditProposalInfo, NewProposedPageEdits, PageProposal, ProposalCount, NewRegrading, NewRegradingIdType, Regrading, RegradingInfo, RegradingSubmissionInfo, RepositoryExercise, NewResearchForm, NewResearchFormQuestion, NewResearchFormQuestionAnswer, ResearchForm, ResearchFormQuestion, ResearchFormQuestionAnswer, RoleDomain, RoleInfo, RoleUser, UserRole, StudentCountry, SuspectedCheaters, ThresholdData, NewTeacherGradingDecision, TeacherDecisionType, TeacherGradingDecision, UserChapterLockingStatus, ChapterLockingStatus, UserCourseExerciseServiceVariable, UserCourseSettings, UserDetail, ExerciseUserCounts, ReviewingStage, UserCourseChapterExerciseProgress, UserCourseProgress, UserExerciseState, UserResearchConsent, User, UploadResult, CreateAccountDetails, Login, LoginResponse, VerifyEmailRequest, UserInfo, SaveCourseSettingsPayload, ChaptersWithStatus, CourseMaterialCourseModule, ExamData, ExamEnrollmentData, CourseMaterialPeerOrSelfReviewDataWithToken, CourseInfo, CertificateConfigurationUpdate, GetFeedbackQuery, CopyCourseRequest, CopyCourseMode, ExamCourseInfo, NewExerciseRepository, ExerciseServiceWithError, ExerciseSubmissions, MarkAsRead, PlaygroundViewsMessage, GetEditProposalsQuery, RoleQuery, BulkUserDetailsRequest, UserDetailsRequest, UserInfoPayload, CronJobInfo, DeploymentInfo, EventInfo, IngressInfo, JobInfo, PodDisruptionBudgetInfo, PodInfo, ServiceInfo, ServicePortInfo, ParagraphSuggestionMeta, ParagraphSuggestionContext, ParagraphSuggestionRequest, ParagraphSuggestionResponse, HealthStatus, SystemHealthStatus, Pagination, OEmbedResponse, GutenbergBlock } from "./bindings"; +import { Action, ActionOnResource, Resource, ErrorData, ErrorResponse, SpecRequest, ConsentQuery, ConsentResponse, ConsentDenyQuery, CertificateAllRequirements, CertificateConfiguration, CertificateConfigurationAndRequirements, CertificateTextAnchor, PaperSize, Chapter, ChapterStatus, ChapterUpdate, ChapterWithStatus, ParagraphSuggestionAction, DatabaseChapter, NewChapter, UserCourseInstanceChapterProgress, ChapterAvailability, UserChapterProgress, CourseUserInfo, ChapterLockPreview, UnreturnedExercise, ChatbotConfiguration, NewChatbotConf, VerbosityLevel, ReasoningEffortLevel, ChatbotConfigurationModel, ChatbotConversationMessage, MessageRole, ChatbotConversationMessageCitation, ChatbotConversationMessageToolCall, ChatbotConversationMessageToolOutput, ChatbotConversationSuggestedMessage, ChatbotConversation, ChatbotConversationInfo, CodeGiveawayCode, CodeGiveaway, CodeGiveawayStatus, NewCodeGiveaway, CourseBackgroundQuestionAnswer, NewCourseBackgroundQuestionAnswer, CourseBackgroundQuestion, CourseBackgroundQuestionType, CourseBackgroundQuestionsAndAnswers, CourseCustomPrivacyPolicyCheckboxText, CourseEnrollmentInfo, CourseEnrollmentsInfo, CourseInstanceEnrollment, CourseInstanceEnrollmentsInfo, ChapterScore, CourseInstance, CourseInstanceForm, PointMap, Points, CourseModuleCompletion, CourseModuleCompletionWithRegistrationInfo, AutomaticCompletionRequirements, CompletionPolicy, CourseModule, ModifiedModule, ModuleUpdates, NewCourseModule, NewModule, Course, CourseMaterialCourse, CourseBreadcrumbInfo, CourseCount, CourseStructure, CourseUpdate, NewCourse, CourseLanguageVersionNavigationInfo, EmailTemplate, EmailTemplateNew, EmailTemplateUpdate, EmailTemplateType, CourseExam, Exam, ExamEnrollment, ExamInstructions, ExamInstructionsUpdate, NewExam, OrgExam, ExerciseRepository, ExerciseRepositoryStatus, CourseMaterialExerciseServiceInfo, ExerciseServiceInfoApi, ExerciseService, ExerciseServiceIframeRenderingInfo, ExerciseServiceNewOrUpdate, AnswerRequiringAttention, ExerciseAnswersInCourseRequiringAttentionCount, ExerciseSlideSubmission, ExerciseSlideSubmissionAndUserExerciseState, ExerciseSlideSubmissionAndUserExerciseStateList, ExerciseSlideSubmissionCount, ExerciseSlideSubmissionCountByExercise, ExerciseSlideSubmissionCountByWeekAndHour, ExerciseSlideSubmissionInfo, CourseMaterialExerciseSlide, ExerciseSlide, ExerciseTaskGrading, ExerciseTaskGradingResult, UserPointsUpdateStrategy, ExerciseTaskSubmission, PeerOrSelfReviewsReceived, CourseMaterialExerciseTask, ExerciseTask, ActivityProgress, CourseMaterialExercise, Exercise, ExerciseGradingStatus, ExerciseStatus, ExerciseStatusSummaryForUser, GradingProgress, ExerciseResetLog, Feedback, FeedbackBlock, FeedbackCount, NewFeedback, FlaggedAnswer, NewFlaggedAnswer, NewFlaggedAnswerWithToken, ReportReason, GeneratedCertificate, CertificateUpdateRequest, Term, TermUpdate, AverageMetric, CohortActivity, CountResult, StudentsByCountryTotalsResult, CustomViewExerciseSubmissions, CustomViewExerciseTaskGrading, CustomViewExerciseTaskSpec, CustomViewExerciseTaskSubmission, CustomViewExerciseTasks, CourseCompletionStats, DomainCompletionStats, GlobalCourseModuleStatEntry, GlobalStatEntry, TimeGranularity, AnswerRequiringAttentionWithTasks, AnswersRequiringAttention, StudentExerciseSlideSubmission, StudentExerciseSlideSubmissionResult, StudentExerciseTaskSubmission, StudentExerciseTaskSubmissionResult, CourseMaterialPeerOrSelfReviewData, CourseMaterialPeerOrSelfReviewDataAnswerToReview, CourseMaterialPeerOrSelfReviewQuestionAnswer, CourseMaterialPeerOrSelfReviewSubmission, CompletionRegistrationLink, CourseInstanceCompletionSummary, ManualCompletionPreview, ManualCompletionPreviewUser, TeacherManualCompletion, TeacherManualCompletionRequest, UserCompletionInformation, UserCourseModuleCompletion, UserModuleCompletionStatus, UserWithModuleCompletions, ProgressOverview, CompletionGridRow, CertificateGridRow, UserMarketingConsent, MaterialReference, NewMaterialReference, Organization, AuthorizedClientInfo, PageAudioFile, HistoryChangeReason, PageHistory, PageVisitDatumSummaryByCourse, PageVisitDatumSummaryByCoursesCountries, PageVisitDatumSummaryByCourseDeviceTypes, PageVisitDatumSummaryByPages, CmsPageExercise, CmsPageExerciseSlide, CmsPageExerciseTask, CmsPageUpdate, ContentManagementPage, CoursePageWithUserData, ExerciseWithExerciseTasks, HistoryRestoreData, IsChapterFrontPage, NewPage, Page, PageChapterAndCourseInformation, PageDetailsUpdate, PageInfo, PageNavigationInformation, PageRoutingData, PageSearchResult, PageWithExercises, SearchRequest, PartnerBlockNew, PartnersBlock, CmsPeerOrSelfReviewConfig, CmsPeerOrSelfReviewConfiguration, CourseMaterialPeerOrSelfReviewConfig, PeerOrSelfReviewConfig, PeerReviewProcessingStrategy, PeerOrSelfReviewAnswer, PeerOrSelfReviewQuestionAndAnswer, PeerOrSelfReviewQuestionSubmission, PeerReviewWithQuestionsAndAnswers, CmsPeerOrSelfReviewQuestion, PeerOrSelfReviewQuestion, PeerOrSelfReviewQuestionType, PeerOrSelfReviewSubmission, PeerOrSelfReviewSubmissionWithSubmissionOwner, PeerReviewQueueEntry, PendingRole, PlaygroundExample, PlaygroundExampleData, PrivacyLink, BlockProposal, BlockProposalAction, BlockProposalInfo, EditedBlockNoLongerExistsData, EditedBlockStillExistsData, NewProposedBlockEdit, ProposalStatus, EditProposalInfo, NewProposedPageEdits, PageProposal, ProposalCount, NewRegrading, NewRegradingIdType, Regrading, RegradingInfo, RegradingSubmissionInfo, RepositoryExercise, NewResearchForm, NewResearchFormQuestion, NewResearchFormQuestionAnswer, ResearchForm, ResearchFormQuestion, ResearchFormQuestionAnswer, RoleDomain, RoleInfo, RoleUser, UserRole, StudentCountry, SuspectedCheaters, ThresholdData, NewTeacherGradingDecision, TeacherDecisionType, TeacherGradingDecision, UserChapterLockingStatus, ChapterLockingStatus, UserCourseExerciseServiceVariable, UserCourseSettings, UserDetail, ExerciseUserCounts, ReviewingStage, UserCourseChapterExerciseProgress, UserCourseProgress, UserExerciseState, UserResearchConsent, User, UploadResult, CreateAccountDetails, Login, LoginResponse, VerifyEmailRequest, UserInfo, SaveCourseSettingsPayload, ChaptersWithStatus, CourseMaterialCourseModule, ExamData, ExamEnrollmentData, CourseMaterialPeerOrSelfReviewDataWithToken, CourseInfo, CertificateConfigurationUpdate, GetFeedbackQuery, CopyCourseRequest, CopyCourseMode, ExamCourseInfo, NewExerciseRepository, ExerciseServiceWithError, ExerciseSubmissions, MarkAsRead, PlaygroundViewsMessage, GetEditProposalsQuery, RoleQuery, BulkUserDetailsRequest, UserDetailsRequest, UserInfoPayload, CronJobInfo, DeploymentInfo, EventInfo, IngressInfo, JobInfo, PodDisruptionBudgetInfo, PodInfo, ServiceInfo, ServicePortInfo, ParagraphSuggestionMeta, ParagraphSuggestionContext, ParagraphSuggestionRequest, ParagraphSuggestionResponse, HealthStatus, SystemHealthStatus, Pagination, OEmbedResponse, GutenbergBlock } from "./bindings"; export function isAction(obj: unknown): obj is Action { const typedObj = obj as Action @@ -440,6 +440,46 @@ export function isChapterWithStatus(obj: unknown): obj is ChapterWithStatus { ) } +export function isParagraphSuggestionAction(obj: unknown): obj is ParagraphSuggestionAction { + const typedObj = obj as ParagraphSuggestionAction + return ( + (typedObj === "moocfi/ai/generate-draft-from-notes" || + typedObj === "moocfi/ai/generate-continue-paragraph" || + typedObj === "moocfi/ai/generate-add-example" || + typedObj === "moocfi/ai/generate-add-counterpoint" || + typedObj === "moocfi/ai/generate-add-concluding-sentence" || + typedObj === "moocfi/fix-spelling" || + typedObj === "moocfi/ai/improve-clarity" || + typedObj === "moocfi/ai/improve-flow" || + typedObj === "moocfi/ai/improve-concise" || + typedObj === "moocfi/ai/improve-expand-detail" || + typedObj === "moocfi/ai/improve-academic-style" || + typedObj === "moocfi/ai/structure-create-topic-sentence" || + typedObj === "moocfi/ai/structure-reorder-sentences" || + typedObj === "moocfi/ai/structure-split-into-paragraphs" || + typedObj === "moocfi/ai/structure-combine-into-one" || + typedObj === "moocfi/ai/structure-to-bullets" || + typedObj === "moocfi/ai/structure-from-bullets" || + typedObj === "moocfi/ai/learning-simplify-beginners" || + typedObj === "moocfi/ai/learning-add-definitions" || + typedObj === "moocfi/ai/learning-add-analogy" || + typedObj === "moocfi/ai/learning-add-practice-question" || + typedObj === "moocfi/ai/learning-add-check-understanding" || + typedObj === "moocfi/ai/summaries-one-sentence" || + typedObj === "moocfi/ai/summaries-two-three-sentences" || + typedObj === "moocfi/ai/summaries-key-takeaway" || + typedObj === "moocfi/ai/tone-academic-formal" || + typedObj === "moocfi/ai/tone-friendly-conversational" || + typedObj === "moocfi/ai/tone-encouraging-supportive" || + typedObj === "moocfi/ai/tone-neutral-objective" || + typedObj === "moocfi/ai/tone-confident" || + typedObj === "moocfi/ai/tone-serious" || + typedObj === "moocfi/ai/translate-english" || + typedObj === "moocfi/ai/translate-finnish" || + typedObj === "moocfi/ai/translate-swedish") + ) +} + export function isDatabaseChapter(obj: unknown): obj is DatabaseChapter { const typedObj = obj as DatabaseChapter return ( @@ -5597,7 +5637,7 @@ export function isParagraphSuggestionRequest(obj: unknown): obj is ParagraphSugg (typedObj !== null && typeof typedObj === "object" || typeof typedObj === "function") && - typeof typedObj["action"] === "string" && + isParagraphSuggestionAction(typedObj["action"]) as boolean && typeof typedObj["content"] === "string" && typeof typedObj["is_html"] === "boolean" && (typedObj["meta"] === null || diff --git a/shared-module/packages/common/src/bindings.ts b/shared-module/packages/common/src/bindings.ts index ebe34175ba7..2b390a80456 100644 --- a/shared-module/packages/common/src/bindings.ts +++ b/shared-module/packages/common/src/bindings.ts @@ -181,6 +181,42 @@ export interface ChapterWithStatus { earliest_exercise_deadline_override: string | null } +export type ParagraphSuggestionAction = + | "moocfi/ai/generate-draft-from-notes" + | "moocfi/ai/generate-continue-paragraph" + | "moocfi/ai/generate-add-example" + | "moocfi/ai/generate-add-counterpoint" + | "moocfi/ai/generate-add-concluding-sentence" + | "moocfi/fix-spelling" + | "moocfi/ai/improve-clarity" + | "moocfi/ai/improve-flow" + | "moocfi/ai/improve-concise" + | "moocfi/ai/improve-expand-detail" + | "moocfi/ai/improve-academic-style" + | "moocfi/ai/structure-create-topic-sentence" + | "moocfi/ai/structure-reorder-sentences" + | "moocfi/ai/structure-split-into-paragraphs" + | "moocfi/ai/structure-combine-into-one" + | "moocfi/ai/structure-to-bullets" + | "moocfi/ai/structure-from-bullets" + | "moocfi/ai/learning-simplify-beginners" + | "moocfi/ai/learning-add-definitions" + | "moocfi/ai/learning-add-analogy" + | "moocfi/ai/learning-add-practice-question" + | "moocfi/ai/learning-add-check-understanding" + | "moocfi/ai/summaries-one-sentence" + | "moocfi/ai/summaries-two-three-sentences" + | "moocfi/ai/summaries-key-takeaway" + | "moocfi/ai/tone-academic-formal" + | "moocfi/ai/tone-friendly-conversational" + | "moocfi/ai/tone-encouraging-supportive" + | "moocfi/ai/tone-neutral-objective" + | "moocfi/ai/tone-confident" + | "moocfi/ai/tone-serious" + | "moocfi/ai/translate-english" + | "moocfi/ai/translate-finnish" + | "moocfi/ai/translate-swedish" + export interface DatabaseChapter { id: string created_at: string @@ -2714,7 +2750,7 @@ export interface ParagraphSuggestionContext { } export interface ParagraphSuggestionRequest { - action: string + action: ParagraphSuggestionAction content: string is_html: boolean meta: ParagraphSuggestionMeta | null diff --git a/shared-module/packages/common/src/locales/ar/cms.json b/shared-module/packages/common/src/locales/ar/cms.json index 0caee480d61..29d75a4f5f4 100644 --- a/shared-module/packages/common/src/locales/ar/cms.json +++ b/shared-module/packages/common/src/locales/ar/cms.json @@ -9,14 +9,9 @@ "ai-citations-add-source-needed": "إضافة علامات \"المصدر مطلوب\"", "ai-citations-avoid-plagiarism": "إعادة الصياغة لتجنب الانتحال", "ai-citations-suggest-citation": "اقتراح أماكن تحتاج إلى مرجع", + "ai-dialog-label-suggestion-n": "اقتراح {{number}}", "ai-dialog-title-apply": "تطبيق التغييرات على الفقرة", - "ai-generate-add-concluding-sentence": "إضافة جملة ختامية", - "ai-generate-add-counterpoint": "إضافة وجهة نظر معاكسة", - "ai-generate-add-example": "إضافة مثال", - "ai-generate-continue-paragraph": "متابعة هذه الفقرة", - "ai-generate-draft-from-notes": "إنشاء فقرة من الملاحظات…", "ai-group-citations-integrity": "المراجع والنزاهة", - "ai-group-generate": "إنشاء", "ai-group-improve": "تحسين", "ai-group-learning-support": "دعم التعلم", "ai-group-settings": "الإعدادات", @@ -28,20 +23,11 @@ "ai-improve-expand-detail": "التوسيع بمزيد من التفاصيل", "ai-improve-fix-spelling-grammar": "تصحيح الإملاء والقواعد", "ai-improve-flow": "تحسين السلاسة والانتقالات", - "ai-learning-add-analogy": "إضافة تشبيه أو مثال مشابه", - "ai-learning-add-check-understanding": "إضافة فحص سريع للفهم", - "ai-learning-add-definitions": "إضافة تعريفات للمصطلحات الأساسية", - "ai-learning-add-practice-question": "إضافة سؤال تدريبي قصير", "ai-learning-simplify-beginners": "تبسيط للمبتدئين", "ai-settings-remember-tone": "تذكّر النبرة المفضلة (لهذا المستند)", "ai-settings-set-audience": "تحديد الجمهور المستهدف…", "ai-settings-set-reading-level": "تحديد مستوى القراءة…", - "ai-structure-combine-into-one": "دمج في فقرة واحدة", - "ai-structure-create-topic-sentence": "إنشاء جملة موضوعية", - "ai-structure-from-bullets": "تحويل النقاط إلى فقرة", "ai-structure-reorder-sentences": "إعادة ترتيب الجمل بشكل منطقي", - "ai-structure-split-into-paragraphs": "تقسيم إلى فقرات أصغر", - "ai-structure-to-bullets": "تحويل إلى نقاط تعداد", "ai-submenu-tone-voice": "النبرة والصوت", "ai-submenu-translate": "الترجمة", "ai-summaries-key-takeaway": "أهم فكرة للطلاب", diff --git a/shared-module/packages/common/src/locales/en/cms.json b/shared-module/packages/common/src/locales/en/cms.json index c3077c70078..0de06166e6f 100644 --- a/shared-module/packages/common/src/locales/en/cms.json +++ b/shared-module/packages/common/src/locales/en/cms.json @@ -9,14 +9,9 @@ "ai-citations-add-source-needed": "Add “source needed” markers", "ai-citations-avoid-plagiarism": "Rewrite to avoid plagiarism", "ai-citations-suggest-citation": "Suggest where a citation is needed", + "ai-dialog-label-suggestion-n": "Suggestion {{number}}", "ai-dialog-title-apply": "Apply changes to paragraph", - "ai-generate-add-concluding-sentence": "Add a concluding sentence", - "ai-generate-add-counterpoint": "Add a counterpoint", - "ai-generate-add-example": "Add an example", - "ai-generate-continue-paragraph": "Continue this paragraph", - "ai-generate-draft-from-notes": "Draft paragraph from notes…", "ai-group-citations-integrity": "Citations & integrity", - "ai-group-generate": "Generate", "ai-group-improve": "Improve", "ai-group-learning-support": "Learning support", "ai-group-settings": "Settings", @@ -28,20 +23,11 @@ "ai-improve-expand-detail": "Expand with detail", "ai-improve-fix-spelling-grammar": "Fix spelling & grammar", "ai-improve-flow": "Improve flow & transitions", - "ai-learning-add-analogy": "Add an analogy", - "ai-learning-add-check-understanding": "Add a quick check-for-understanding", - "ai-learning-add-definitions": "Add definitions for key terms", - "ai-learning-add-practice-question": "Add a short practice question", "ai-learning-simplify-beginners": "Simplify for beginners", "ai-settings-remember-tone": "Remember preferred tone (this doc)", "ai-settings-set-audience": "Set audience…", "ai-settings-set-reading-level": "Set reading level…", - "ai-structure-combine-into-one": "Combine into one paragraph", - "ai-structure-create-topic-sentence": "Create topic sentence", - "ai-structure-from-bullets": "Turn bullets into paragraph", "ai-structure-reorder-sentences": "Reorder sentences for logic", - "ai-structure-split-into-paragraphs": "Split into smaller paragraphs", - "ai-structure-to-bullets": "Turn into bullet points", "ai-submenu-tone-voice": "Tone & Voice", "ai-submenu-translate": "Translate", "ai-summaries-key-takeaway": "Key takeaway for students", diff --git a/shared-module/packages/common/src/locales/fi/cms.json b/shared-module/packages/common/src/locales/fi/cms.json index 7830eb48096..5cca0129f3a 100644 --- a/shared-module/packages/common/src/locales/fi/cms.json +++ b/shared-module/packages/common/src/locales/fi/cms.json @@ -9,14 +9,9 @@ "ai-citations-add-source-needed": "Lisää \"lähde tarvitaan\" -merkinnät", "ai-citations-avoid-plagiarism": "Kirjoita uudelleen plagioinnin välttämiseksi", "ai-citations-suggest-citation": "Ehdota, mihin tarvitaan lähdeviite", + "ai-dialog-label-suggestion-n": "Ehdotus {{number}}", "ai-dialog-title-apply": "Ota muutokset käyttöön kappaleessa", - "ai-generate-add-concluding-sentence": "Lisää päätöslause", - "ai-generate-add-counterpoint": "Lisää vasta-argumentti", - "ai-generate-add-example": "Lisää esimerkki", - "ai-generate-continue-paragraph": "Jatka tätä kappaletta", - "ai-generate-draft-from-notes": "Luo kappale muistiinpanoista…", "ai-group-citations-integrity": "Lähteet ja eettisyys", - "ai-group-generate": "Luo", "ai-group-improve": "Paranna", "ai-group-learning-support": "Oppimisen tuki", "ai-group-settings": "Asetukset", @@ -28,20 +23,11 @@ "ai-improve-expand-detail": "Laajenna yksityiskohtia", "ai-improve-fix-spelling-grammar": "Korjaa oikeinkirjoitus ja kielioppi", "ai-improve-flow": "Paranna sujuvuutta ja siirtymiä", - "ai-learning-add-analogy": "Lisää vertauskuva tai analogia", - "ai-learning-add-check-understanding": "Lisää nopea ymmärryksen tarkistus", - "ai-learning-add-definitions": "Lisää keskeisten käsitteiden määritelmät", - "ai-learning-add-practice-question": "Lisää lyhyt harjoituskysymys", "ai-learning-simplify-beginners": "Yksinkertaista aloitteleville", "ai-settings-remember-tone": "Muista suosittu sävy (tämä asiakirja)", "ai-settings-set-audience": "Aseta kohderyhmä…", "ai-settings-set-reading-level": "Aseta luettavuustaso…", - "ai-structure-combine-into-one": "Yhdistä yhdeksi kappaleeksi", - "ai-structure-create-topic-sentence": "Luo kappaleen pääajatus", - "ai-structure-from-bullets": "Muuta luettelopisteet kappaleeksi", "ai-structure-reorder-sentences": "Järjestä lauseet loogiseen järjestykseen", - "ai-structure-split-into-paragraphs": "Jaa pienemmiksi kappaleiksi", - "ai-structure-to-bullets": "Muuta luettelopisteiksi", "ai-submenu-tone-voice": "Tyyli ja sävy", "ai-submenu-translate": "Käännös", "ai-summaries-key-takeaway": "Tärkein oppi opiskelijalle", diff --git a/shared-module/packages/common/src/locales/sv/cms.json b/shared-module/packages/common/src/locales/sv/cms.json index c447d3399fa..c5652e7bbb3 100644 --- a/shared-module/packages/common/src/locales/sv/cms.json +++ b/shared-module/packages/common/src/locales/sv/cms.json @@ -9,14 +9,9 @@ "ai-citations-add-source-needed": "Lägg till \"källa behövs\"-markeringar", "ai-citations-avoid-plagiarism": "Skriv om för att undvika plagiering", "ai-citations-suggest-citation": "Föreslå var källa behövs", + "ai-dialog-label-suggestion-n": "Förslag {{number}}", "ai-dialog-title-apply": "Verkställ ändringar i stycket", - "ai-generate-add-concluding-sentence": "Lägg till avslutande mening", - "ai-generate-add-counterpoint": "Lägg till motargument", - "ai-generate-add-example": "Lägg till ett exempel", - "ai-generate-continue-paragraph": "Fortsätt detta stycke", - "ai-generate-draft-from-notes": "Skapa stycke från anteckningar…", "ai-group-citations-integrity": "Källor och integritet", - "ai-group-generate": "Generera", "ai-group-improve": "Förbättra", "ai-group-learning-support": "Stöd för lärande", "ai-group-settings": "Inställningar", @@ -28,20 +23,11 @@ "ai-improve-expand-detail": "Utöka med fler detaljer", "ai-improve-fix-spelling-grammar": "Rätta stavning och grammatik", "ai-improve-flow": "Förbättra flyt och övergångar", - "ai-learning-add-analogy": "Lägg till en analogi", - "ai-learning-add-check-understanding": "Lägg till en snabb förståelsekontroll", - "ai-learning-add-definitions": "Lägg till definitioner för nyckelbegrepp", - "ai-learning-add-practice-question": "Lägg till en kort övningsfråga", "ai-learning-simplify-beginners": "Förenkla för nybörjare", "ai-settings-remember-tone": "Kom ihåg föredragen ton (detta dokument)", "ai-settings-set-audience": "Ställ in målgrupp…", "ai-settings-set-reading-level": "Ställ in läsnivå…", - "ai-structure-combine-into-one": "Slå ihop till ett stycke", - "ai-structure-create-topic-sentence": "Skapa ämnesmening", - "ai-structure-from-bullets": "Gör om punktlista till stycke", "ai-structure-reorder-sentences": "Ordna meningar logiskt", - "ai-structure-split-into-paragraphs": "Dela upp i mindre stycken", - "ai-structure-to-bullets": "Gör om till punktlista", "ai-submenu-tone-voice": "Ton och röst", "ai-submenu-translate": "Översätt", "ai-summaries-key-takeaway": "Viktigaste lärdom för studenter", diff --git a/shared-module/packages/common/src/locales/uk/cms.json b/shared-module/packages/common/src/locales/uk/cms.json index 6bb439b5fa4..f1010c27912 100644 --- a/shared-module/packages/common/src/locales/uk/cms.json +++ b/shared-module/packages/common/src/locales/uk/cms.json @@ -9,14 +9,9 @@ "ai-citations-add-source-needed": "Додати позначки «потрібне джерело»", "ai-citations-avoid-plagiarism": "Переписати, щоб уникнути плагіату", "ai-citations-suggest-citation": "Запропонувати, де потрібне посилання", + "ai-dialog-label-suggestion-n": "Пропозиція {{number}}", "ai-dialog-title-apply": "Застосувати зміни до абзацу", - "ai-generate-add-concluding-sentence": "Додати завершальне речення", - "ai-generate-add-counterpoint": "Додати контраргумент", - "ai-generate-add-example": "Додати приклад", - "ai-generate-continue-paragraph": "Продовжити цей абзац", - "ai-generate-draft-from-notes": "Створити абзац з нотаток…", "ai-group-citations-integrity": "Цитування та академічна доброчесність", - "ai-group-generate": "Створити", "ai-group-improve": "Покращити", "ai-group-learning-support": "Підтримка навчання", "ai-group-settings": "Налаштування", @@ -28,20 +23,11 @@ "ai-improve-expand-detail": "Розширити деталями", "ai-improve-fix-spelling-grammar": "Виправити орфографію та граматику", "ai-improve-flow": "Покращити зв’язність і переходи", - "ai-learning-add-analogy": "Додати аналогію", - "ai-learning-add-check-understanding": "Додати швидку перевірку розуміння", - "ai-learning-add-definitions": "Додати визначення ключових термінів", - "ai-learning-add-practice-question": "Додати коротке тренувальне запитання", "ai-learning-simplify-beginners": "Спростити для початківців", "ai-settings-remember-tone": "Запам’ятати бажаний тон (цей документ)", "ai-settings-set-audience": "Вказати аудиторію…", "ai-settings-set-reading-level": "Вказати рівень читання…", - "ai-structure-combine-into-one": "Об’єднати в один абзац", - "ai-structure-create-topic-sentence": "Створити тематичне речення", - "ai-structure-from-bullets": "Перетворити список на абзац", "ai-structure-reorder-sentences": "Упорядкувати речення логічно", - "ai-structure-split-into-paragraphs": "Розділити на менші абзаци", - "ai-structure-to-bullets": "Перетворити на маркований список", "ai-submenu-tone-voice": "Тон і стиль", "ai-submenu-translate": "Переклад", "ai-summaries-key-takeaway": "Головний висновок для студентів",