@mulmochat-plugin/quiz 0.1.4 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,8 @@
1
+ /**
2
+ * MulmoChat Plugin Core Exports
3
+ *
4
+ * Framework-agnostic types and plugin logic.
5
+ * Import from "@mulmochat-plugin/quiz/core"
6
+ */
7
+ export type { BackendType, ToolContextApp, ToolContext, ToolResult, ToolResultComplete, JsonSchemaProperty, ToolDefinition, StartApiResponse, ToolSample, InputHandler, FileInputHandler, ClipboardImageInputHandler, UrlInputHandler, TextInputHandler, FileUploadConfig, ConfigValue, ConfigFieldSchema, StringFieldSchema, NumberFieldSchema, BooleanFieldSchema, SelectFieldSchema, MultiSelectFieldSchema, SelectOption, PluginConfigSchema, ViewComponentProps, PreviewComponentProps, ToolPluginCore, QuizQuestion, QuizData, QuizArgs, } from "./types";
8
+ export { pluginCore, TOOL_NAME, TOOL_DEFINITION, SAMPLES, executeQuiz } from "./plugin";
@@ -0,0 +1,12 @@
1
+ /**
2
+ * MulmoChat Quiz Plugin Core (Framework-agnostic)
3
+ *
4
+ * Contains the plugin logic without UI components.
5
+ * Can be used by any framework (Vue, React, etc.)
6
+ */
7
+ import type { ToolPluginCore, ToolContext, ToolResult, ToolDefinition, ToolSample, QuizData, QuizArgs } from "./types";
8
+ export declare const TOOL_NAME = "putQuestions";
9
+ export declare const TOOL_DEFINITION: ToolDefinition;
10
+ export declare const SAMPLES: ToolSample[];
11
+ export declare const executeQuiz: (_context: ToolContext, args: QuizArgs) => Promise<ToolResult<never, QuizData>>;
12
+ export declare const pluginCore: ToolPluginCore<never, QuizData, QuizArgs>;
@@ -0,0 +1,242 @@
1
+ /**
2
+ * MulmoChat Plugin Core Types (Framework-agnostic)
3
+ *
4
+ * These types can be used by any framework implementation (Vue, React, etc.)
5
+ */
6
+ /**
7
+ * Backend types that plugins can declare they use.
8
+ * App layer manages actual provider/model settings for each type.
9
+ */
10
+ export type BackendType = "textLLM" | "imageGen" | "audio" | "search" | "browse" | "map" | "mulmocast";
11
+ /**
12
+ * App interface provided to plugins via context.app
13
+ * Contains backend functions and config accessors
14
+ */
15
+ export interface ToolContextApp extends Record<string, (...args: any[]) => any> {
16
+ getConfig: <T = unknown>(key: string) => T | undefined;
17
+ setConfig: (key: string, value: unknown) => void;
18
+ }
19
+ /**
20
+ * Context passed to plugin execute function
21
+ */
22
+ export interface ToolContext {
23
+ currentResult?: ToolResult<unknown> | null;
24
+ app?: ToolContextApp;
25
+ }
26
+ /**
27
+ * Result returned from plugin execution
28
+ */
29
+ export interface ToolResult<T = unknown, J = unknown> {
30
+ toolName?: string;
31
+ uuid?: string;
32
+ message: string;
33
+ title?: string;
34
+ jsonData?: J;
35
+ instructions?: string;
36
+ instructionsRequired?: boolean;
37
+ updating?: boolean;
38
+ cancelled?: boolean;
39
+ data?: T;
40
+ viewState?: Record<string, unknown>;
41
+ }
42
+ /**
43
+ * Complete tool result with required fields
44
+ */
45
+ export interface ToolResultComplete<T = unknown, J = unknown> extends ToolResult<T, J> {
46
+ toolName: string;
47
+ uuid: string;
48
+ }
49
+ /**
50
+ * JSON Schema property definition for tool parameters
51
+ */
52
+ export interface JsonSchemaProperty {
53
+ type?: string;
54
+ description?: string;
55
+ enum?: string[];
56
+ items?: JsonSchemaProperty;
57
+ minimum?: number;
58
+ maximum?: number;
59
+ minItems?: number;
60
+ maxItems?: number;
61
+ properties?: Record<string, JsonSchemaProperty>;
62
+ required?: string[];
63
+ additionalProperties?: boolean;
64
+ oneOf?: JsonSchemaProperty[];
65
+ [key: string]: unknown;
66
+ }
67
+ /**
68
+ * Tool definition for OpenAI-compatible function calling
69
+ */
70
+ export interface ToolDefinition {
71
+ type: "function";
72
+ name: string;
73
+ description: string;
74
+ parameters?: {
75
+ type: "object";
76
+ properties: Record<string, JsonSchemaProperty>;
77
+ required: string[];
78
+ additionalProperties?: boolean;
79
+ };
80
+ }
81
+ /**
82
+ * API response from server start endpoint
83
+ */
84
+ export interface StartApiResponse {
85
+ hasOpenAIApiKey?: boolean;
86
+ hasAnthropicApiKey?: boolean;
87
+ hasGoogleApiKey?: boolean;
88
+ [key: string]: unknown;
89
+ }
90
+ /**
91
+ * Sample arguments for testing
92
+ */
93
+ export interface ToolSample {
94
+ name: string;
95
+ args: Record<string, unknown>;
96
+ }
97
+ /**
98
+ * File input handler
99
+ */
100
+ export interface FileInputHandler {
101
+ type: "file";
102
+ acceptedTypes: string[];
103
+ handleInput: (fileData: string, fileName: string) => ToolResult<unknown, unknown>;
104
+ }
105
+ /**
106
+ * Clipboard image input handler
107
+ */
108
+ export interface ClipboardImageInputHandler {
109
+ type: "clipboard-image";
110
+ handleInput: (imageData: string) => ToolResult<unknown, unknown>;
111
+ }
112
+ /**
113
+ * URL input handler
114
+ */
115
+ export interface UrlInputHandler {
116
+ type: "url";
117
+ patterns?: string[];
118
+ handleInput: (url: string) => ToolResult<unknown, unknown>;
119
+ }
120
+ /**
121
+ * Text input handler
122
+ */
123
+ export interface TextInputHandler {
124
+ type: "text";
125
+ patterns?: string[];
126
+ handleInput: (text: string) => ToolResult<unknown, unknown>;
127
+ }
128
+ /**
129
+ * Union of all input handler types
130
+ */
131
+ export type InputHandler = FileInputHandler | ClipboardImageInputHandler | UrlInputHandler | TextInputHandler;
132
+ /**
133
+ * Legacy file upload config (for backward compatibility)
134
+ * @deprecated Use InputHandler instead
135
+ */
136
+ export interface FileUploadConfig {
137
+ acceptedTypes: string[];
138
+ handleUpload: (fileData: string, fileName: string, ...args: unknown[]) => ToolResult<unknown, unknown>;
139
+ }
140
+ export type ConfigValue = string | number | boolean | string[];
141
+ interface BaseFieldSchema {
142
+ label: string;
143
+ description?: string;
144
+ required?: boolean;
145
+ }
146
+ export interface StringFieldSchema extends BaseFieldSchema {
147
+ type: "string";
148
+ placeholder?: string;
149
+ minLength?: number;
150
+ maxLength?: number;
151
+ pattern?: string;
152
+ }
153
+ export interface NumberFieldSchema extends BaseFieldSchema {
154
+ type: "number";
155
+ min?: number;
156
+ max?: number;
157
+ step?: number;
158
+ }
159
+ export interface BooleanFieldSchema extends BaseFieldSchema {
160
+ type: "boolean";
161
+ }
162
+ export interface SelectOption {
163
+ value: string;
164
+ label: string;
165
+ description?: string;
166
+ disabled?: boolean;
167
+ }
168
+ export interface SelectFieldSchema extends BaseFieldSchema {
169
+ type: "select";
170
+ options: SelectOption[];
171
+ }
172
+ export interface MultiSelectFieldSchema extends BaseFieldSchema {
173
+ type: "multiselect";
174
+ options: SelectOption[];
175
+ minItems?: number;
176
+ maxItems?: number;
177
+ }
178
+ export type ConfigFieldSchema = StringFieldSchema | NumberFieldSchema | BooleanFieldSchema | SelectFieldSchema | MultiSelectFieldSchema;
179
+ /**
180
+ * Plugin configuration schema (JSON Schema based)
181
+ */
182
+ export interface PluginConfigSchema {
183
+ key: string;
184
+ defaultValue: ConfigValue;
185
+ schema: ConfigFieldSchema;
186
+ }
187
+ /**
188
+ * Standard props for View components
189
+ */
190
+ export interface ViewComponentProps<T = unknown, J = unknown> {
191
+ selectedResult: ToolResultComplete<T, J>;
192
+ sendTextMessage: (text?: string) => void;
193
+ onUpdateResult?: (result: Partial<ToolResult<T, J>>) => void;
194
+ pluginConfigs?: Record<string, unknown>;
195
+ }
196
+ /**
197
+ * Standard props for Preview components
198
+ */
199
+ export interface PreviewComponentProps<T = unknown, J = unknown> {
200
+ result: ToolResultComplete<T, J>;
201
+ isSelected?: boolean;
202
+ onSelect?: () => void;
203
+ }
204
+ /**
205
+ * Core plugin interface - framework agnostic
206
+ * Does not include UI components
207
+ */
208
+ export interface ToolPluginCore<T = unknown, J = unknown, A extends object = object> {
209
+ toolDefinition: ToolDefinition;
210
+ execute: (context: ToolContext, args: A) => Promise<ToolResult<T, J>>;
211
+ generatingMessage: string;
212
+ waitingMessage?: string;
213
+ uploadMessage?: string;
214
+ isEnabled: (startResponse?: StartApiResponse | null) => boolean;
215
+ delayAfterExecution?: number;
216
+ systemPrompt?: string;
217
+ inputHandlers?: InputHandler[];
218
+ /** @deprecated Use inputHandlers instead */
219
+ fileUpload?: FileUploadConfig;
220
+ /** New JSON Schema based config (framework-agnostic) */
221
+ configSchema?: PluginConfigSchema;
222
+ samples?: ToolSample[];
223
+ backends?: BackendType[];
224
+ }
225
+ /** Single quiz question */
226
+ export interface QuizQuestion {
227
+ question: string;
228
+ choices: string[];
229
+ correctAnswer?: number;
230
+ }
231
+ /** Quiz data stored in result.jsonData */
232
+ export interface QuizData {
233
+ title?: string;
234
+ questions: QuizQuestion[];
235
+ userAnswers?: number[];
236
+ }
237
+ /** Arguments passed to the quiz tool */
238
+ export interface QuizArgs {
239
+ title?: string;
240
+ questions: QuizQuestion[];
241
+ }
242
+ export {};
package/dist/core.cjs ADDED
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const i="putQuestions",o={type:"function",name:i,description:"Present a set of multiple choice questions to test the user's knowledge or abilities. Each question should have 2-6 answer choices.",parameters:{type:"object",properties:{title:{type:"string",description:"Optional title for the quiz (e.g., 'JavaScript Basics Quiz')"},questions:{type:"array",description:"Array of multiple choice questions",items:{type:"object",properties:{question:{type:"string",description:"The question text"},choices:{type:"array",description:"Array of answer choices (2-6 choices)",items:{type:"string"},minItems:2,maxItems:6},correctAnswer:{type:"number",description:"Optional: The index of the correct answer (0-based). Include this if you want to track correct answers."}},required:["question","choices"]},minItems:1}},required:["questions"]}},n=[{name:"JavaScript Quiz",args:{title:"JavaScript Basics",questions:[{question:"What does 'const' do in JavaScript?",choices:["Declares a constant variable","Declares a mutable variable","Creates a function","Imports a module"],correctAnswer:0},{question:"Which method adds an element to the end of an array?",choices:["pop()","shift()","push()","unshift()"],correctAnswer:2},{question:"What is the output of: typeof null?",choices:['"null"','"undefined"','"object"','"boolean"'],correctAnswer:2}]}},{name:"World Capitals",args:{title:"World Capitals Quiz",questions:[{question:"What is the capital of Japan?",choices:["Osaka","Kyoto","Tokyo","Hiroshima"],correctAnswer:2},{question:"What is the capital of Australia?",choices:["Sydney","Melbourne","Canberra","Brisbane"],correctAnswer:2}]}},{name:"Simple Yes/No",args:{questions:[{question:"Is the Earth round?",choices:["Yes","No"],correctAnswer:0}]}}],a=async(l,c)=>{try{const{title:s,questions:e}=c;if(!e||!Array.isArray(e)||e.length===0)throw new Error("At least one question is required");for(let t=0;t<e.length;t++){const r=e[t];if(!r.question||typeof r.question!="string")throw new Error(`Question ${t+1} must have a question text`);if(!Array.isArray(r.choices)||r.choices.length<2)throw new Error(`Question ${t+1} must have at least 2 choices`);if(r.choices.length>6)throw new Error(`Question ${t+1} cannot have more than 6 choices`)}const u={title:s,questions:e};return{message:`Quiz presented with ${e.length} question${e.length>1?"s":""}`,jsonData:u,instructions:"The quiz has been presented to the user. Wait for the user to submit their answers. They will tell you their answers in text format."}}catch(s){return console.error("Quiz creation error",s),{message:`Quiz error: ${s instanceof Error?s.message:"Unknown error"}`,instructions:"Acknowledge that there was an error creating the quiz and suggest trying again."}}},h={toolDefinition:o,execute:a,generatingMessage:"Preparing quiz...",isEnabled:()=>!0,samples:n};exports.SAMPLES=n;exports.TOOL_DEFINITION=o;exports.TOOL_NAME=i;exports.executeQuiz=a;exports.pluginCore=h;
package/dist/core.js ADDED
@@ -0,0 +1,144 @@
1
+ const n = "putQuestions", a = {
2
+ type: "function",
3
+ name: n,
4
+ description: "Present a set of multiple choice questions to test the user's knowledge or abilities. Each question should have 2-6 answer choices.",
5
+ parameters: {
6
+ type: "object",
7
+ properties: {
8
+ title: {
9
+ type: "string",
10
+ description: "Optional title for the quiz (e.g., 'JavaScript Basics Quiz')"
11
+ },
12
+ questions: {
13
+ type: "array",
14
+ description: "Array of multiple choice questions",
15
+ items: {
16
+ type: "object",
17
+ properties: {
18
+ question: {
19
+ type: "string",
20
+ description: "The question text"
21
+ },
22
+ choices: {
23
+ type: "array",
24
+ description: "Array of answer choices (2-6 choices)",
25
+ items: {
26
+ type: "string"
27
+ },
28
+ minItems: 2,
29
+ maxItems: 6
30
+ },
31
+ correctAnswer: {
32
+ type: "number",
33
+ description: "Optional: The index of the correct answer (0-based). Include this if you want to track correct answers."
34
+ }
35
+ },
36
+ required: ["question", "choices"]
37
+ },
38
+ minItems: 1
39
+ }
40
+ },
41
+ required: ["questions"]
42
+ }
43
+ }, c = [
44
+ {
45
+ name: "JavaScript Quiz",
46
+ args: {
47
+ title: "JavaScript Basics",
48
+ questions: [
49
+ {
50
+ question: "What does 'const' do in JavaScript?",
51
+ choices: [
52
+ "Declares a constant variable",
53
+ "Declares a mutable variable",
54
+ "Creates a function",
55
+ "Imports a module"
56
+ ],
57
+ correctAnswer: 0
58
+ },
59
+ {
60
+ question: "Which method adds an element to the end of an array?",
61
+ choices: ["pop()", "shift()", "push()", "unshift()"],
62
+ correctAnswer: 2
63
+ },
64
+ {
65
+ question: "What is the output of: typeof null?",
66
+ choices: ['"null"', '"undefined"', '"object"', '"boolean"'],
67
+ correctAnswer: 2
68
+ }
69
+ ]
70
+ }
71
+ },
72
+ {
73
+ name: "World Capitals",
74
+ args: {
75
+ title: "World Capitals Quiz",
76
+ questions: [
77
+ {
78
+ question: "What is the capital of Japan?",
79
+ choices: ["Osaka", "Kyoto", "Tokyo", "Hiroshima"],
80
+ correctAnswer: 2
81
+ },
82
+ {
83
+ question: "What is the capital of Australia?",
84
+ choices: ["Sydney", "Melbourne", "Canberra", "Brisbane"],
85
+ correctAnswer: 2
86
+ }
87
+ ]
88
+ }
89
+ },
90
+ {
91
+ name: "Simple Yes/No",
92
+ args: {
93
+ questions: [
94
+ {
95
+ question: "Is the Earth round?",
96
+ choices: ["Yes", "No"],
97
+ correctAnswer: 0
98
+ }
99
+ ]
100
+ }
101
+ }
102
+ ], u = async (h, o) => {
103
+ try {
104
+ const { title: s, questions: e } = o;
105
+ if (!e || !Array.isArray(e) || e.length === 0)
106
+ throw new Error("At least one question is required");
107
+ for (let t = 0; t < e.length; t++) {
108
+ const r = e[t];
109
+ if (!r.question || typeof r.question != "string")
110
+ throw new Error(`Question ${t + 1} must have a question text`);
111
+ if (!Array.isArray(r.choices) || r.choices.length < 2)
112
+ throw new Error(`Question ${t + 1} must have at least 2 choices`);
113
+ if (r.choices.length > 6)
114
+ throw new Error(`Question ${t + 1} cannot have more than 6 choices`);
115
+ }
116
+ const i = {
117
+ title: s,
118
+ questions: e
119
+ };
120
+ return {
121
+ message: `Quiz presented with ${e.length} question${e.length > 1 ? "s" : ""}`,
122
+ jsonData: i,
123
+ instructions: "The quiz has been presented to the user. Wait for the user to submit their answers. They will tell you their answers in text format."
124
+ };
125
+ } catch (s) {
126
+ return console.error("Quiz creation error", s), {
127
+ message: `Quiz error: ${s instanceof Error ? s.message : "Unknown error"}`,
128
+ instructions: "Acknowledge that there was an error creating the quiz and suggest trying again."
129
+ };
130
+ }
131
+ }, l = {
132
+ toolDefinition: a,
133
+ execute: u,
134
+ generatingMessage: "Preparing quiz...",
135
+ isEnabled: () => !0,
136
+ samples: c
137
+ };
138
+ export {
139
+ c as SAMPLES,
140
+ a as TOOL_DEFINITION,
141
+ n as TOOL_NAME,
142
+ u as executeQuiz,
143
+ l as pluginCore
144
+ };
package/dist/index.cjs CHANGED
@@ -1,3 +1 @@
1
- "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("vue"),y="putQuestions",w={type:"function",name:y,description:"Present a set of multiple choice questions to test the user's knowledge or abilities. Each question should have 2-6 answer choices.",parameters:{type:"object",properties:{title:{type:"string",description:"Optional title for the quiz (e.g., 'JavaScript Basics Quiz')"},questions:{type:"array",description:"Array of multiple choice questions",items:{type:"object",properties:{question:{type:"string",description:"The question text"},choices:{type:"array",description:"Array of answer choices (2-6 choices)",items:{type:"string"},minItems:2,maxItems:6},correctAnswer:{type:"number",description:"Optional: The index of the correct answer (0-based). Include this if you want to track correct answers."}},required:["question","choices"]},minItems:1}},required:["questions"]}},b=[{name:"Geography Quiz",args:{title:"World Geography",questions:[{question:"What is the capital of France?",choices:["London","Berlin","Paris","Madrid"],correctAnswer:2},{question:"Which is the largest ocean?",choices:["Atlantic","Indian","Arctic","Pacific"],correctAnswer:3},{question:"Mount Everest is located in which mountain range?",choices:["Alps","Andes","Himalayas","Rockies"],correctAnswer:2}]}},{name:"Science Quiz",args:{title:"Science Basics",questions:[{question:"Which planet is known as the Red Planet?",choices:["Venus","Mars","Jupiter","Saturn"],correctAnswer:1},{question:"What is the chemical symbol for water?",choices:["O2","CO2","H2O","NaCl"],correctAnswer:2},{question:"How many bones are in the adult human body?",choices:["186","206","226","246"],correctAnswer:1},{question:"What is the speed of light?",choices:["300,000 km/s","150,000 km/s","500,000 km/s","1,000,000 km/s"],correctAnswer:0}]}},{name:"Math Quiz",args:{title:"Math Challenge",questions:[{question:"What is 15 x 15?",choices:["200","215","225","250"],correctAnswer:2},{question:"What is the square root of 144?",choices:["10","11","12","13"],correctAnswer:2}]}},{name:"No Correct Answer",args:{title:"Opinion Poll",questions:[{question:"What is your favorite color?",choices:["Red","Blue","Green","Yellow"]},{question:"Which season do you prefer?",choices:["Spring","Summer","Autumn","Winter"]}]}},{name:"Single Question",args:{title:"Quick Question",questions:[{question:"Is this quiz plugin working correctly?",choices:["Yes","No"],correctAnswer:0}]}}],_={class:"size-full overflow-y-auto p-8 bg-[#1a1a2e]"},q={key:0,class:"max-w-3xl w-full mx-auto"},x={key:0,class:"text-[#f0f0f0] text-3xl font-bold mb-8 text-center"},k={class:"flex flex-col gap-6"},E={class:"text-white text-lg font-semibold mb-4"},A={class:"text-blue-400 mr-2"},S={class:"flex flex-col gap-3"},N=["name","value","onUpdate:modelValue"],B={class:"text-white flex-1"},V={class:"font-semibold mr-2"},C={class:"mt-8 flex justify-center"},D=["disabled"],z={class:"mt-4 text-center text-gray-400 text-sm"},Q=e.defineComponent({__name:"View",props:{selectedResult:{},sendTextMessage:{type:Function}},emits:["updateResult"],setup(h,{emit:d}){const t=h,r=d,o=e.ref(null),s=e.ref([]);e.watch(()=>t.selectedResult,n=>{n?.toolName==="putQuestions"&&n.jsonData&&(o.value=n.jsonData,n.viewState?.userAnswers?s.value=n.viewState.userAnswers:s.value=new Array(o.value.questions.length).fill(null))},{immediate:!0}),e.watch(s,n=>{if(t.selectedResult&&n){const l={...t.selectedResult,viewState:{userAnswers:n}};r("updateResult",l)}},{deep:!0});const i=e.computed(()=>s.value.filter(n=>n!==null).length),m=e.computed(()=>o.value&&i.value===o.value.questions.length);function f(n,l){return s.value[n]===l?"border-blue-500 bg-blue-500/20":"border-[#4b4b6b] hover:border-[#6b6b8b] hover:bg-[#6b6b8b]/20"}function v(){if(!o.value||!m.value)return;const l=`Here are my answers:
2
- ${s.value.map((a,c)=>{if(a===null)return null;const p=c+1,u=String.fromCharCode(65+a),g=o.value.questions[c].choices[a];return`Q${p}: ${u} - ${g}`}).filter(a=>a!==null).join(`
3
- `)}`;t.sendTextMessage(l)}return(n,l)=>(e.openBlock(),e.createElementBlock("div",_,[o.value?(e.openBlock(),e.createElementBlock("div",q,[o.value.title?(e.openBlock(),e.createElementBlock("h2",x,e.toDisplayString(o.value.title),1)):e.createCommentVNode("",!0),e.createElementVNode("div",k,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(o.value.questions,(a,c)=>(e.openBlock(),e.createElementBlock("div",{key:c,class:"bg-[#2d2d44] rounded-lg p-6 border-2 border-[#3d3d5c]"},[e.createElementVNode("div",E,[e.createElementVNode("span",A,e.toDisplayString(c+1)+".",1),e.createTextVNode(" "+e.toDisplayString(a.question),1)]),e.createElementVNode("div",S,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(a.choices,(p,u)=>(e.openBlock(),e.createElementBlock("label",{key:u,class:e.normalizeClass([f(c,u),"flex items-start p-4 rounded-lg cursor-pointer transition-all duration-200 border-2"])},[e.withDirectives(e.createElementVNode("input",{type:"radio",name:`question-${c}`,value:u,"onUpdate:modelValue":g=>s.value[c]=g,class:"mt-1 mr-3 size-4 shrink-0"},null,8,N),[[e.vModelRadio,s.value[c]]]),e.createElementVNode("span",B,[e.createElementVNode("span",V,e.toDisplayString(String.fromCharCode(65+u))+".",1),e.createTextVNode(" "+e.toDisplayString(p),1)])],2))),128))])]))),128))]),e.createElementVNode("div",C,[e.createElementVNode("button",{onClick:v,disabled:!m.value,class:e.normalizeClass([m.value?"bg-blue-600 hover:bg-blue-700":"bg-gray-600 cursor-not-allowed opacity-50","py-3 px-8 rounded-lg text-white font-semibold text-lg transition-colors border-none cursor-pointer"])}," Submit Answers ",10,D)]),e.createElementVNode("div",z,e.toDisplayString(i.value)+" / "+e.toDisplayString(o.value.questions.length)+" questions answered ",1)])):e.createCommentVNode("",!0)]))}}),$={class:"p-3 bg-blue-50 rounded-md"},T={key:0,class:"flex flex-col gap-2"},M={class:"text-sm font-semibold text-gray-800 text-center"},O={class:"text-center"},W={class:"inline-block bg-blue-600 text-white text-xs font-bold py-1 px-3 rounded-full"},j={class:"text-xs text-gray-600 overflow-hidden line-clamp-2"},P={class:"flex justify-center gap-1"},L={key:0,class:"text-xs text-gray-500"},R=e.defineComponent({__name:"Preview",props:{result:{}},setup(h){const d=h,t=e.computed(()=>d.result.jsonData);return(r,o)=>(e.openBlock(),e.createElementBlock("div",$,[t.value?(e.openBlock(),e.createElementBlock("div",T,[e.createElementVNode("div",M,e.toDisplayString(t.value.title||"Quiz"),1),e.createElementVNode("div",O,[e.createElementVNode("span",W,e.toDisplayString(t.value.questions.length)+" "+e.toDisplayString(t.value.questions.length===1?"Question":"Questions"),1)]),e.createElementVNode("div",j,e.toDisplayString(t.value.questions[0]?.question),1),e.createElementVNode("div",P,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(Math.min(t.value.questions[0]?.choices.length||0,4),(s,i)=>(e.openBlock(),e.createElementBlock("div",{key:i,class:"size-2 bg-gray-400 rounded-full"}))),128)),(t.value.questions[0]?.choices.length||0)>4?(e.openBlock(),e.createElementBlock("div",L," +"+e.toDisplayString(t.value.questions[0].choices.length-4),1)):e.createCommentVNode("",!0)])])):e.createCommentVNode("",!0)]))}}),F=async(h,d)=>{try{const{title:t,questions:r}=d;if(!r||!Array.isArray(r)||r.length===0)throw new Error("At least one question is required");for(let s=0;s<r.length;s++){const i=r[s];if(!i.question||typeof i.question!="string")throw new Error(`Question ${s+1} must have a question text`);if(!Array.isArray(i.choices)||i.choices.length<2)throw new Error(`Question ${s+1} must have at least 2 choices`);if(i.choices.length>6)throw new Error(`Question ${s+1} cannot have more than 6 choices`)}const o={title:t,questions:r};return{message:`Quiz presented with ${r.length} question${r.length>1?"s":""}`,jsonData:o,instructions:"The quiz has been presented to the user. Wait for the user to submit their answers. They will tell you their answers in text format."}}catch(t){return console.error("Quiz creation error",t),{message:`Quiz error: ${t instanceof Error?t.message:"Unknown error"}`,instructions:"Acknowledge that there was an error creating the quiz and suggest trying again."}}},H={toolDefinition:w,execute:F,generatingMessage:"Preparing quiz...",isEnabled:()=>!0,viewComponent:Q,previewComponent:R,samples:b},G={plugin:H};exports.default=G;
1
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("./core.cjs");exports.SAMPLES=e.SAMPLES;exports.TOOL_DEFINITION=e.TOOL_DEFINITION;exports.TOOL_NAME=e.TOOL_NAME;exports.default=e.pluginCore;exports.executeQuiz=e.executeQuiz;exports.pluginCore=e.pluginCore;
package/dist/index.d.ts CHANGED
@@ -1,11 +1,19 @@
1
1
  /**
2
- * MulmoChat Plugin
2
+ * MulmoChat Quiz Plugin
3
3
  *
4
- * See package.json for plugin details.
4
+ * Default export is the framework-agnostic core.
5
+ * For Vue implementation, import from "@mulmochat-plugin/quiz/vue"
6
+ *
7
+ * @example Default (Core - framework-agnostic)
8
+ * ```typescript
9
+ * import { pluginCore, TOOL_NAME, QuizData } from "@mulmochat-plugin/quiz";
10
+ * ```
11
+ *
12
+ * @example Vue implementation
13
+ * ```typescript
14
+ * import QuizPlugin from "@mulmochat-plugin/quiz/vue";
15
+ * import "@mulmochat-plugin/quiz/style.css";
16
+ * ```
5
17
  */
6
- import "./style.css";
7
- import type { ToolPlugin } from "./common";
8
- declare const _default: {
9
- plugin: ToolPlugin;
10
- };
11
- export default _default;
18
+ export * from "./core";
19
+ export { pluginCore as default } from "./core";
package/dist/index.js CHANGED
@@ -1,314 +1,9 @@
1
- import { defineComponent as z, ref as x, watch as A, computed as _, createElementBlock as i, openBlock as r, createCommentVNode as f, createElementVNode as s, toDisplayString as a, Fragment as y, renderList as q, createTextVNode as k, normalizeClass as Q, withDirectives as C, vModelRadio as E } from "vue";
2
- const T = "putQuestions", M = {
3
- type: "function",
4
- name: T,
5
- description: "Present a set of multiple choice questions to test the user's knowledge or abilities. Each question should have 2-6 answer choices.",
6
- parameters: {
7
- type: "object",
8
- properties: {
9
- title: {
10
- type: "string",
11
- description: "Optional title for the quiz (e.g., 'JavaScript Basics Quiz')"
12
- },
13
- questions: {
14
- type: "array",
15
- description: "Array of multiple choice questions",
16
- items: {
17
- type: "object",
18
- properties: {
19
- question: {
20
- type: "string",
21
- description: "The question text"
22
- },
23
- choices: {
24
- type: "array",
25
- description: "Array of answer choices (2-6 choices)",
26
- items: {
27
- type: "string"
28
- },
29
- minItems: 2,
30
- maxItems: 6
31
- },
32
- correctAnswer: {
33
- type: "number",
34
- description: "Optional: The index of the correct answer (0-based). Include this if you want to track correct answers."
35
- }
36
- },
37
- required: ["question", "choices"]
38
- },
39
- minItems: 1
40
- }
41
- },
42
- required: ["questions"]
43
- }
44
- }, W = [
45
- {
46
- name: "Geography Quiz",
47
- args: {
48
- title: "World Geography",
49
- questions: [
50
- {
51
- question: "What is the capital of France?",
52
- choices: ["London", "Berlin", "Paris", "Madrid"],
53
- correctAnswer: 2
54
- },
55
- {
56
- question: "Which is the largest ocean?",
57
- choices: ["Atlantic", "Indian", "Arctic", "Pacific"],
58
- correctAnswer: 3
59
- },
60
- {
61
- question: "Mount Everest is located in which mountain range?",
62
- choices: ["Alps", "Andes", "Himalayas", "Rockies"],
63
- correctAnswer: 2
64
- }
65
- ]
66
- }
67
- },
68
- {
69
- name: "Science Quiz",
70
- args: {
71
- title: "Science Basics",
72
- questions: [
73
- {
74
- question: "Which planet is known as the Red Planet?",
75
- choices: ["Venus", "Mars", "Jupiter", "Saturn"],
76
- correctAnswer: 1
77
- },
78
- {
79
- question: "What is the chemical symbol for water?",
80
- choices: ["O2", "CO2", "H2O", "NaCl"],
81
- correctAnswer: 2
82
- },
83
- {
84
- question: "How many bones are in the adult human body?",
85
- choices: ["186", "206", "226", "246"],
86
- correctAnswer: 1
87
- },
88
- {
89
- question: "What is the speed of light?",
90
- choices: [
91
- "300,000 km/s",
92
- "150,000 km/s",
93
- "500,000 km/s",
94
- "1,000,000 km/s"
95
- ],
96
- correctAnswer: 0
97
- }
98
- ]
99
- }
100
- },
101
- {
102
- name: "Math Quiz",
103
- args: {
104
- title: "Math Challenge",
105
- questions: [
106
- {
107
- question: "What is 15 x 15?",
108
- choices: ["200", "215", "225", "250"],
109
- correctAnswer: 2
110
- },
111
- {
112
- question: "What is the square root of 144?",
113
- choices: ["10", "11", "12", "13"],
114
- correctAnswer: 2
115
- }
116
- ]
117
- }
118
- },
119
- {
120
- name: "No Correct Answer",
121
- args: {
122
- title: "Opinion Poll",
123
- questions: [
124
- {
125
- question: "What is your favorite color?",
126
- choices: ["Red", "Blue", "Green", "Yellow"]
127
- },
128
- {
129
- question: "Which season do you prefer?",
130
- choices: ["Spring", "Summer", "Autumn", "Winter"]
131
- }
132
- ]
133
- }
134
- },
135
- {
136
- name: "Single Question",
137
- args: {
138
- title: "Quick Question",
139
- questions: [
140
- {
141
- question: "Is this quiz plugin working correctly?",
142
- choices: ["Yes", "No"],
143
- correctAnswer: 0
144
- }
145
- ]
146
- }
147
- }
148
- ], D = { class: "size-full overflow-y-auto p-8 bg-[#1a1a2e]" }, N = {
149
- key: 0,
150
- class: "max-w-3xl w-full mx-auto"
151
- }, O = {
152
- key: 0,
153
- class: "text-[#f0f0f0] text-3xl font-bold mb-8 text-center"
154
- }, j = { class: "flex flex-col gap-6" }, P = { class: "text-white text-lg font-semibold mb-4" }, R = { class: "text-blue-400 mr-2" }, V = { class: "flex flex-col gap-3" }, B = ["name", "value", "onUpdate:modelValue"], L = { class: "text-white flex-1" }, F = { class: "font-semibold mr-2" }, H = { class: "mt-8 flex justify-center" }, G = ["disabled"], U = { class: "mt-4 text-center text-gray-400 text-sm" }, J = /* @__PURE__ */ z({
155
- __name: "View",
156
- props: {
157
- selectedResult: {},
158
- sendTextMessage: { type: Function }
159
- },
160
- emits: ["updateResult"],
161
- setup(g, { emit: p }) {
162
- const e = g, c = p, o = x(null), t = x([]);
163
- A(
164
- () => e.selectedResult,
165
- (n) => {
166
- n?.toolName === "putQuestions" && n.jsonData && (o.value = n.jsonData, n.viewState?.userAnswers ? t.value = n.viewState.userAnswers : t.value = new Array(o.value.questions.length).fill(null));
167
- },
168
- { immediate: !0 }
169
- ), A(
170
- t,
171
- (n) => {
172
- if (e.selectedResult && n) {
173
- const d = {
174
- ...e.selectedResult,
175
- viewState: {
176
- userAnswers: n
177
- }
178
- };
179
- c("updateResult", d);
180
- }
181
- },
182
- { deep: !0 }
183
- );
184
- const l = _(() => t.value.filter((n) => n !== null).length), v = _(() => o.value && l.value === o.value.questions.length);
185
- function $(n, d) {
186
- return t.value[n] === d ? "border-blue-500 bg-blue-500/20" : "border-[#4b4b6b] hover:border-[#6b6b8b] hover:bg-[#6b6b8b]/20";
187
- }
188
- function S() {
189
- if (!o.value || !v.value) return;
190
- const d = `Here are my answers:
191
- ${t.value.map((u, h) => {
192
- if (u === null) return null;
193
- const w = h + 1, m = String.fromCharCode(65 + u), b = o.value.questions[h].choices[u];
194
- return `Q${w}: ${m} - ${b}`;
195
- }).filter((u) => u !== null).join(`
196
- `)}`;
197
- e.sendTextMessage(d);
198
- }
199
- return (n, d) => (r(), i("div", D, [
200
- o.value ? (r(), i("div", N, [
201
- o.value.title ? (r(), i("h2", O, a(o.value.title), 1)) : f("", !0),
202
- s("div", j, [
203
- (r(!0), i(y, null, q(o.value.questions, (u, h) => (r(), i("div", {
204
- key: h,
205
- class: "bg-[#2d2d44] rounded-lg p-6 border-2 border-[#3d3d5c]"
206
- }, [
207
- s("div", P, [
208
- s("span", R, a(h + 1) + ".", 1),
209
- k(" " + a(u.question), 1)
210
- ]),
211
- s("div", V, [
212
- (r(!0), i(y, null, q(u.choices, (w, m) => (r(), i("label", {
213
- key: m,
214
- class: Q([$(h, m), "flex items-start p-4 rounded-lg cursor-pointer transition-all duration-200 border-2"])
215
- }, [
216
- C(s("input", {
217
- type: "radio",
218
- name: `question-${h}`,
219
- value: m,
220
- "onUpdate:modelValue": (b) => t.value[h] = b,
221
- class: "mt-1 mr-3 size-4 shrink-0"
222
- }, null, 8, B), [
223
- [E, t.value[h]]
224
- ]),
225
- s("span", L, [
226
- s("span", F, a(String.fromCharCode(65 + m)) + ".", 1),
227
- k(" " + a(w), 1)
228
- ])
229
- ], 2))), 128))
230
- ])
231
- ]))), 128))
232
- ]),
233
- s("div", H, [
234
- s("button", {
235
- onClick: S,
236
- disabled: !v.value,
237
- class: Q([v.value ? "bg-blue-600 hover:bg-blue-700" : "bg-gray-600 cursor-not-allowed opacity-50", "py-3 px-8 rounded-lg text-white font-semibold text-lg transition-colors border-none cursor-pointer"])
238
- }, " Submit Answers ", 10, G)
239
- ]),
240
- s("div", U, a(l.value) + " / " + a(o.value.questions.length) + " questions answered ", 1)
241
- ])) : f("", !0)
242
- ]));
243
- }
244
- }), Y = { class: "p-3 bg-blue-50 rounded-md" }, I = {
245
- key: 0,
246
- class: "flex flex-col gap-2"
247
- }, K = { class: "text-sm font-semibold text-gray-800 text-center" }, X = { class: "text-center" }, Z = { class: "inline-block bg-blue-600 text-white text-xs font-bold py-1 px-3 rounded-full" }, ee = { class: "text-xs text-gray-600 overflow-hidden line-clamp-2" }, te = { class: "flex justify-center gap-1" }, se = {
248
- key: 0,
249
- class: "text-xs text-gray-500"
250
- }, oe = /* @__PURE__ */ z({
251
- __name: "Preview",
252
- props: {
253
- result: {}
254
- },
255
- setup(g) {
256
- const p = g, e = _(() => p.result.jsonData);
257
- return (c, o) => (r(), i("div", Y, [
258
- e.value ? (r(), i("div", I, [
259
- s("div", K, a(e.value.title || "Quiz"), 1),
260
- s("div", X, [
261
- s("span", Z, a(e.value.questions.length) + " " + a(e.value.questions.length === 1 ? "Question" : "Questions"), 1)
262
- ]),
263
- s("div", ee, a(e.value.questions[0]?.question), 1),
264
- s("div", te, [
265
- (r(!0), i(y, null, q(Math.min(e.value.questions[0]?.choices.length || 0, 4), (t, l) => (r(), i("div", {
266
- key: l,
267
- class: "size-2 bg-gray-400 rounded-full"
268
- }))), 128)),
269
- (e.value.questions[0]?.choices.length || 0) > 4 ? (r(), i("div", se, " +" + a(e.value.questions[0].choices.length - 4), 1)) : f("", !0)
270
- ])
271
- ])) : f("", !0)
272
- ]));
273
- }
274
- }), ne = async (g, p) => {
275
- try {
276
- const { title: e, questions: c } = p;
277
- if (!c || !Array.isArray(c) || c.length === 0)
278
- throw new Error("At least one question is required");
279
- for (let t = 0; t < c.length; t++) {
280
- const l = c[t];
281
- if (!l.question || typeof l.question != "string")
282
- throw new Error(`Question ${t + 1} must have a question text`);
283
- if (!Array.isArray(l.choices) || l.choices.length < 2)
284
- throw new Error(`Question ${t + 1} must have at least 2 choices`);
285
- if (l.choices.length > 6)
286
- throw new Error(`Question ${t + 1} cannot have more than 6 choices`);
287
- }
288
- const o = {
289
- title: e,
290
- questions: c
291
- };
292
- return {
293
- message: `Quiz presented with ${c.length} question${c.length > 1 ? "s" : ""}`,
294
- jsonData: o,
295
- instructions: "The quiz has been presented to the user. Wait for the user to submit their answers. They will tell you their answers in text format."
296
- };
297
- } catch (e) {
298
- return console.error("Quiz creation error", e), {
299
- message: `Quiz error: ${e instanceof Error ? e.message : "Unknown error"}`,
300
- instructions: "Acknowledge that there was an error creating the quiz and suggest trying again."
301
- };
302
- }
303
- }, ie = {
304
- toolDefinition: M,
305
- execute: ne,
306
- generatingMessage: "Preparing quiz...",
307
- isEnabled: () => !0,
308
- viewComponent: J,
309
- previewComponent: oe,
310
- samples: W
311
- }, ae = { plugin: ie };
1
+ import { SAMPLES as O, TOOL_DEFINITION as o, TOOL_NAME as r, pluginCore as i, executeQuiz as l, pluginCore as p } from "./core.js";
312
2
  export {
313
- ae as default
3
+ O as SAMPLES,
4
+ o as TOOL_DEFINITION,
5
+ r as TOOL_NAME,
6
+ i as default,
7
+ l as executeQuiz,
8
+ p as pluginCore
314
9
  };
@@ -1,4 +1,4 @@
1
- import type { ToolResult } from "../common";
1
+ import type { ToolResult } from "../core/types";
2
2
  type __VLS_Props = {
3
3
  result: ToolResult;
4
4
  };
@@ -1,4 +1,4 @@
1
- import type { ToolResult } from "../common";
1
+ import type { ToolResult } from "../core/types";
2
2
  type __VLS_Props = {
3
3
  selectedResult: ToolResult;
4
4
  sendTextMessage: (text?: string) => void;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * MulmoChat Quiz Plugin - Vue Implementation
3
+ *
4
+ * Full Vue plugin with UI components.
5
+ * Import from "@mulmochat-plugin/quiz/vue"
6
+ */
7
+ import "../style.css";
8
+ import type { ToolPlugin, QuizData, QuizArgs } from "./types";
9
+ import View from "./View.vue";
10
+ import Preview from "./Preview.vue";
11
+ /**
12
+ * Quiz plugin instance with Vue components
13
+ */
14
+ export declare const plugin: ToolPlugin<never, QuizData, QuizArgs>;
15
+ export type { ToolPlugin, ToolPluginConfig } from "./types";
16
+ export type { BackendType, ToolContextApp, ToolContext, ToolResult, ToolResultComplete, JsonSchemaProperty, ToolDefinition, StartApiResponse, ToolSample, InputHandler, FileUploadConfig, ConfigValue, ConfigFieldSchema, PluginConfigSchema, ViewComponentProps, PreviewComponentProps, ToolPluginCore, QuizQuestion, QuizData, QuizArgs, } from "./types";
17
+ export { TOOL_NAME, TOOL_DEFINITION, SAMPLES, executeQuiz, pluginCore } from "../core/plugin";
18
+ export { View, Preview };
19
+ declare const _default: {
20
+ plugin: ToolPlugin<never, QuizData, QuizArgs>;
21
+ };
22
+ export default _default;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * MulmoChat Plugin Vue Types
3
+ *
4
+ * Vue-specific types that extend the core plugin interface.
5
+ */
6
+ import type { Component } from "vue";
7
+ import type { ToolPluginCore } from "../core/types";
8
+ type VueComponent = Component<any>;
9
+ /**
10
+ * Legacy Vue component-based config
11
+ * @deprecated Use PluginConfigSchema instead
12
+ */
13
+ export interface ToolPluginConfig {
14
+ key: string;
15
+ defaultValue: unknown;
16
+ component: VueComponent;
17
+ }
18
+ /**
19
+ * Vue plugin interface - extends core with Vue components
20
+ */
21
+ export interface ToolPlugin<T = unknown, J = unknown, A extends object = object> extends ToolPluginCore<T, J, A> {
22
+ /** Vue component for full view */
23
+ viewComponent?: VueComponent;
24
+ /** Vue component for preview/thumbnail */
25
+ previewComponent?: VueComponent;
26
+ /**
27
+ * Legacy Vue component-based config (for backward compatibility)
28
+ * @deprecated Use configSchema instead
29
+ */
30
+ config?: ToolPluginConfig;
31
+ }
32
+ export type { BackendType, ToolContextApp, ToolContext, ToolResult, ToolResultComplete, JsonSchemaProperty, ToolDefinition, StartApiResponse, ToolSample, InputHandler, FileInputHandler, ClipboardImageInputHandler, UrlInputHandler, TextInputHandler, FileUploadConfig, ConfigValue, ConfigFieldSchema, PluginConfigSchema, ViewComponentProps, PreviewComponentProps, ToolPluginCore, QuizQuestion, QuizData, QuizArgs, } from "../core/types";
package/dist/vue.cjs ADDED
@@ -0,0 +1,3 @@
1
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const a=require("./core.cjs"),e=require("vue"),k={class:"size-full overflow-y-auto p-8 bg-[#1a1a2e]"},E={key:0,class:"max-w-3xl w-full mx-auto"},N={key:0,class:"text-[#f0f0f0] text-3xl font-bold mb-8 text-center"},S={class:"flex flex-col gap-6"},w={class:"text-white text-lg font-semibold mb-4"},V={class:"text-blue-400 mr-2"},B={class:"flex flex-col gap-3"},C=["name","value","onUpdate:modelValue"],D={class:"text-white flex-1"},T={class:"font-semibold mr-2"},q={class:"mt-8 flex justify-center"},$=["disabled"],O={class:"mt-4 text-center text-gray-400 text-sm"},h=e.defineComponent({__name:"View",props:{selectedResult:{},sendTextMessage:{type:Function}},emits:["updateResult"],setup(d,{emit:m}){const o=d,_=m,s=e.ref(null),l=e.ref([]);e.watch(()=>o.selectedResult,t=>{t?.toolName===a.TOOL_NAME&&t.jsonData&&(s.value=t.jsonData,t.viewState?.userAnswers?l.value=t.viewState.userAnswers:l.value=new Array(s.value.questions.length).fill(null))},{immediate:!0}),e.watch(l,t=>{if(o.selectedResult&&t){const i={...o.selectedResult,viewState:{userAnswers:t}};_("updateResult",i)}},{deep:!0});const u=e.computed(()=>l.value.filter(t=>t!==null).length),p=e.computed(()=>s.value&&u.value===s.value.questions.length);function x(t,i){return l.value[t]===i?"border-blue-500 bg-blue-500/20":"border-[#4b4b6b] hover:border-[#6b6b8b] hover:bg-[#6b6b8b]/20"}function y(){if(!s.value||!p.value)return;const i=`Here are my answers:
2
+ ${l.value.map((n,r)=>{if(n===null)return null;const v=r+1,c=String.fromCharCode(65+n),g=s.value.questions[r].choices[n];return`Q${v}: ${c} - ${g}`}).filter(n=>n!==null).join(`
3
+ `)}`;o.sendTextMessage(i)}return(t,i)=>(e.openBlock(),e.createElementBlock("div",k,[s.value?(e.openBlock(),e.createElementBlock("div",E,[s.value.title?(e.openBlock(),e.createElementBlock("h2",N,e.toDisplayString(s.value.title),1)):e.createCommentVNode("",!0),e.createElementVNode("div",S,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(s.value.questions,(n,r)=>(e.openBlock(),e.createElementBlock("div",{key:r,class:"bg-[#2d2d44] rounded-lg p-6 border-2 border-[#3d3d5c]"},[e.createElementVNode("div",w,[e.createElementVNode("span",V,e.toDisplayString(r+1)+".",1),e.createTextVNode(" "+e.toDisplayString(n.question),1)]),e.createElementVNode("div",B,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(n.choices,(v,c)=>(e.openBlock(),e.createElementBlock("label",{key:c,class:e.normalizeClass([x(r,c),"flex items-start p-4 rounded-lg cursor-pointer transition-all duration-200 border-2"])},[e.withDirectives(e.createElementVNode("input",{type:"radio",name:`question-${r}`,value:c,"onUpdate:modelValue":g=>l.value[r]=g,class:"mt-1 mr-3 size-4 shrink-0"},null,8,C),[[e.vModelRadio,l.value[r]]]),e.createElementVNode("span",D,[e.createElementVNode("span",T,e.toDisplayString(String.fromCharCode(65+c))+".",1),e.createTextVNode(" "+e.toDisplayString(v),1)])],2))),128))])]))),128))]),e.createElementVNode("div",q,[e.createElementVNode("button",{onClick:y,disabled:!p.value,class:e.normalizeClass([p.value?"bg-blue-600 hover:bg-blue-700":"bg-gray-600 cursor-not-allowed opacity-50","py-3 px-8 rounded-lg text-white font-semibold text-lg transition-colors border-none cursor-pointer"])}," Submit Answers ",10,$)]),e.createElementVNode("div",O,e.toDisplayString(u.value)+" / "+e.toDisplayString(s.value.questions.length)+" questions answered ",1)])):e.createCommentVNode("",!0)]))}}),A={class:"p-3 bg-blue-50 rounded-md"},L={key:0,class:"flex flex-col gap-2"},M={class:"text-sm font-semibold text-gray-800 text-center"},z={class:"text-center"},j={class:"inline-block bg-blue-600 text-white text-xs font-bold py-1 px-3 rounded-full"},Q={class:"text-xs text-gray-600 overflow-hidden line-clamp-2"},F={class:"flex justify-center gap-1"},P={key:0,class:"text-xs text-gray-500"},b=e.defineComponent({__name:"Preview",props:{result:{}},setup(d){const m=d,o=e.computed(()=>m.result.jsonData);return(_,s)=>(e.openBlock(),e.createElementBlock("div",A,[o.value?(e.openBlock(),e.createElementBlock("div",L,[e.createElementVNode("div",M,e.toDisplayString(o.value.title||"Quiz"),1),e.createElementVNode("div",z,[e.createElementVNode("span",j,e.toDisplayString(o.value.questions.length)+" "+e.toDisplayString(o.value.questions.length===1?"Question":"Questions"),1)]),e.createElementVNode("div",Q,e.toDisplayString(o.value.questions[0]?.question),1),e.createElementVNode("div",F,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(Math.min(o.value.questions[0]?.choices.length||0,4),(l,u)=>(e.openBlock(),e.createElementBlock("div",{key:u,class:"size-2 bg-gray-400 rounded-full"}))),128)),(o.value.questions[0]?.choices.length||0)>4?(e.openBlock(),e.createElementBlock("div",P," +"+e.toDisplayString(o.value.questions[0].choices.length-4),1)):e.createCommentVNode("",!0)])])):e.createCommentVNode("",!0)]))}}),f={...a.pluginCore,viewComponent:h,previewComponent:b},R={plugin:f};exports.SAMPLES=a.SAMPLES;exports.TOOL_DEFINITION=a.TOOL_DEFINITION;exports.TOOL_NAME=a.TOOL_NAME;exports.executeQuiz=a.executeQuiz;exports.pluginCore=a.pluginCore;exports.Preview=b;exports.View=h;exports.default=R;exports.plugin=f;
package/dist/vue.js ADDED
@@ -0,0 +1,145 @@
1
+ import { TOOL_NAME as A, pluginCore as D } from "./core.js";
2
+ import { SAMPLES as ue, TOOL_DEFINITION as ce, executeQuiz as de } from "./core.js";
3
+ import { defineComponent as S, ref as C, watch as $, computed as g, createElementBlock as l, openBlock as n, createCommentVNode as m, createElementVNode as e, toDisplayString as i, Fragment as x, renderList as w, createTextVNode as k, normalizeClass as q, withDirectives as N, vModelRadio as V } from "vue";
4
+ const j = { class: "size-full overflow-y-auto p-8 bg-[#1a1a2e]" }, M = {
5
+ key: 0,
6
+ class: "max-w-3xl w-full mx-auto"
7
+ }, Q = {
8
+ key: 0,
9
+ class: "text-[#f0f0f0] text-3xl font-bold mb-8 text-center"
10
+ }, E = { class: "flex flex-col gap-6" }, L = { class: "text-white text-lg font-semibold mb-4" }, O = { class: "text-blue-400 mr-2" }, R = { class: "flex flex-col gap-3" }, F = ["name", "value", "onUpdate:modelValue"], P = { class: "text-white flex-1" }, B = { class: "font-semibold mr-2" }, U = { class: "mt-8 flex justify-center" }, H = ["disabled"], G = { class: "mt-4 text-center text-gray-400 text-sm" }, J = /* @__PURE__ */ S({
11
+ __name: "View",
12
+ props: {
13
+ selectedResult: {},
14
+ sendTextMessage: { type: Function }
15
+ },
16
+ emits: ["updateResult"],
17
+ setup(_, { emit: h }) {
18
+ const s = _, y = h, o = C(null), a = C([]);
19
+ $(
20
+ () => s.selectedResult,
21
+ (t) => {
22
+ t?.toolName === A && t.jsonData && (o.value = t.jsonData, t.viewState?.userAnswers ? a.value = t.viewState.userAnswers : a.value = new Array(o.value.questions.length).fill(null));
23
+ },
24
+ { immediate: !0 }
25
+ ), $(
26
+ a,
27
+ (t) => {
28
+ if (s.selectedResult && t) {
29
+ const c = {
30
+ ...s.selectedResult,
31
+ viewState: {
32
+ userAnswers: t
33
+ }
34
+ };
35
+ y("updateResult", c);
36
+ }
37
+ },
38
+ { deep: !0 }
39
+ );
40
+ const v = g(() => a.value.filter((t) => t !== null).length), p = g(() => o.value && v.value === o.value.questions.length);
41
+ function T(t, c) {
42
+ return a.value[t] === c ? "border-blue-500 bg-blue-500/20" : "border-[#4b4b6b] hover:border-[#6b6b8b] hover:bg-[#6b6b8b]/20";
43
+ }
44
+ function z() {
45
+ if (!o.value || !p.value) return;
46
+ const c = `Here are my answers:
47
+ ${a.value.map((r, u) => {
48
+ if (r === null) return null;
49
+ const b = u + 1, d = String.fromCharCode(65 + r), f = o.value.questions[u].choices[r];
50
+ return `Q${b}: ${d} - ${f}`;
51
+ }).filter((r) => r !== null).join(`
52
+ `)}`;
53
+ s.sendTextMessage(c);
54
+ }
55
+ return (t, c) => (n(), l("div", j, [
56
+ o.value ? (n(), l("div", M, [
57
+ o.value.title ? (n(), l("h2", Q, i(o.value.title), 1)) : m("", !0),
58
+ e("div", E, [
59
+ (n(!0), l(x, null, w(o.value.questions, (r, u) => (n(), l("div", {
60
+ key: u,
61
+ class: "bg-[#2d2d44] rounded-lg p-6 border-2 border-[#3d3d5c]"
62
+ }, [
63
+ e("div", L, [
64
+ e("span", O, i(u + 1) + ".", 1),
65
+ k(" " + i(r.question), 1)
66
+ ]),
67
+ e("div", R, [
68
+ (n(!0), l(x, null, w(r.choices, (b, d) => (n(), l("label", {
69
+ key: d,
70
+ class: q([T(u, d), "flex items-start p-4 rounded-lg cursor-pointer transition-all duration-200 border-2"])
71
+ }, [
72
+ N(e("input", {
73
+ type: "radio",
74
+ name: `question-${u}`,
75
+ value: d,
76
+ "onUpdate:modelValue": (f) => a.value[u] = f,
77
+ class: "mt-1 mr-3 size-4 shrink-0"
78
+ }, null, 8, F), [
79
+ [V, a.value[u]]
80
+ ]),
81
+ e("span", P, [
82
+ e("span", B, i(String.fromCharCode(65 + d)) + ".", 1),
83
+ k(" " + i(b), 1)
84
+ ])
85
+ ], 2))), 128))
86
+ ])
87
+ ]))), 128))
88
+ ]),
89
+ e("div", U, [
90
+ e("button", {
91
+ onClick: z,
92
+ disabled: !p.value,
93
+ class: q([p.value ? "bg-blue-600 hover:bg-blue-700" : "bg-gray-600 cursor-not-allowed opacity-50", "py-3 px-8 rounded-lg text-white font-semibold text-lg transition-colors border-none cursor-pointer"])
94
+ }, " Submit Answers ", 10, H)
95
+ ]),
96
+ e("div", G, i(v.value) + " / " + i(o.value.questions.length) + " questions answered ", 1)
97
+ ])) : m("", !0)
98
+ ]));
99
+ }
100
+ }), K = { class: "p-3 bg-blue-50 rounded-md" }, W = {
101
+ key: 0,
102
+ class: "flex flex-col gap-2"
103
+ }, X = { class: "text-sm font-semibold text-gray-800 text-center" }, Y = { class: "text-center" }, Z = { class: "inline-block bg-blue-600 text-white text-xs font-bold py-1 px-3 rounded-full" }, I = { class: "text-xs text-gray-600 overflow-hidden line-clamp-2" }, ee = { class: "flex justify-center gap-1" }, te = {
104
+ key: 0,
105
+ class: "text-xs text-gray-500"
106
+ }, se = /* @__PURE__ */ S({
107
+ __name: "Preview",
108
+ props: {
109
+ result: {}
110
+ },
111
+ setup(_) {
112
+ const h = _, s = g(() => h.result.jsonData);
113
+ return (y, o) => (n(), l("div", K, [
114
+ s.value ? (n(), l("div", W, [
115
+ e("div", X, i(s.value.title || "Quiz"), 1),
116
+ e("div", Y, [
117
+ e("span", Z, i(s.value.questions.length) + " " + i(s.value.questions.length === 1 ? "Question" : "Questions"), 1)
118
+ ]),
119
+ e("div", I, i(s.value.questions[0]?.question), 1),
120
+ e("div", ee, [
121
+ (n(!0), l(x, null, w(Math.min(s.value.questions[0]?.choices.length || 0, 4), (a, v) => (n(), l("div", {
122
+ key: v,
123
+ class: "size-2 bg-gray-400 rounded-full"
124
+ }))), 128)),
125
+ (s.value.questions[0]?.choices.length || 0) > 4 ? (n(), l("div", te, " +" + i(s.value.questions[0].choices.length - 4), 1)) : m("", !0)
126
+ ])
127
+ ])) : m("", !0)
128
+ ]));
129
+ }
130
+ }), oe = {
131
+ ...D,
132
+ viewComponent: J,
133
+ previewComponent: se
134
+ }, ie = { plugin: oe };
135
+ export {
136
+ se as Preview,
137
+ ue as SAMPLES,
138
+ ce as TOOL_DEFINITION,
139
+ A as TOOL_NAME,
140
+ J as View,
141
+ ie as default,
142
+ de as executeQuiz,
143
+ oe as plugin,
144
+ D as pluginCore
145
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mulmochat-plugin/quiz",
3
- "version": "0.1.4",
3
+ "version": "0.2.0",
4
4
  "description": "Quiz plugin for MulmoChat",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -12,6 +12,16 @@
12
12
  "import": "./dist/index.js",
13
13
  "require": "./dist/index.cjs"
14
14
  },
15
+ "./core": {
16
+ "types": "./dist/core/index.d.ts",
17
+ "import": "./dist/core.js",
18
+ "require": "./dist/core.cjs"
19
+ },
20
+ "./vue": {
21
+ "types": "./dist/vue/index.d.ts",
22
+ "import": "./dist/vue.js",
23
+ "require": "./dist/vue.cjs"
24
+ },
15
25
  "./style.css": "./dist/style.css"
16
26
  },
17
27
  "files": [
@@ -1,7 +0,0 @@
1
- /**
2
- * MulmoChat Plugin Common
3
- *
4
- * Shared types and utilities for building MulmoChat plugins.
5
- * Import from "@mulmochat-plugin/quiz/common" or copy to your plugin.
6
- */
7
- export type { ToolContext, ToolContextApp, ToolResult, ToolPlugin, ToolDefinition, JsonSchemaProperty, StartApiResponse, FileUploadConfig, ToolPluginConfig, ToolSample, } from "./types";
@@ -1,142 +0,0 @@
1
- /**
2
- * MulmoChat Plugin Common Types
3
- *
4
- * Core interfaces for building MulmoChat plugins.
5
- * These types are plugin-agnostic and can be used by any plugin implementation.
6
- */
7
- import type { Component } from "vue";
8
- /**
9
- * Backend types that plugins can declare they use.
10
- * App layer manages actual provider/model settings for each type.
11
- */
12
- export type BackendType = "textLLM" | "imageGen" | "audio" | "search" | "browse" | "map" | "mulmocast";
13
- /**
14
- * App interface provided to plugins via context.app
15
- * Contains backend functions and config accessors
16
- */
17
- export interface ToolContextApp extends Record<string, (...args: any[]) => any> {
18
- getConfig: <T = unknown>(key: string) => T | undefined;
19
- setConfig: (key: string, value: unknown) => void;
20
- }
21
- /**
22
- * Context passed to plugin execute function
23
- */
24
- export interface ToolContext {
25
- currentResult?: ToolResult<unknown> | null;
26
- app?: ToolContextApp;
27
- }
28
- /**
29
- * Result returned from plugin execution
30
- */
31
- export interface ToolResult<T = unknown, J = unknown> {
32
- toolName?: string;
33
- uuid?: string;
34
- message: string;
35
- title?: string;
36
- jsonData?: J;
37
- instructions?: string;
38
- instructionsRequired?: boolean;
39
- updating?: boolean;
40
- cancelled?: boolean;
41
- data?: T;
42
- viewState?: Record<string, unknown>;
43
- }
44
- /**
45
- * JSON Schema property definition for tool parameters
46
- */
47
- export interface JsonSchemaProperty {
48
- type?: string;
49
- description?: string;
50
- enum?: string[];
51
- items?: JsonSchemaProperty;
52
- minimum?: number;
53
- maximum?: number;
54
- minItems?: number;
55
- maxItems?: number;
56
- properties?: Record<string, JsonSchemaProperty>;
57
- required?: string[];
58
- additionalProperties?: boolean;
59
- oneOf?: JsonSchemaProperty[];
60
- [key: string]: unknown;
61
- }
62
- /**
63
- * API response from server start endpoint
64
- */
65
- export interface StartApiResponse {
66
- hasOpenAIApiKey?: boolean;
67
- hasAnthropicApiKey?: boolean;
68
- hasGoogleApiKey?: boolean;
69
- [key: string]: unknown;
70
- }
71
- /**
72
- * Tool definition for OpenAI-compatible function calling
73
- */
74
- export interface ToolDefinition {
75
- type: "function";
76
- name: string;
77
- description: string;
78
- parameters?: {
79
- type: "object";
80
- properties: Record<string, JsonSchemaProperty>;
81
- required: string[];
82
- additionalProperties?: boolean;
83
- };
84
- }
85
- /**
86
- * File upload configuration
87
- */
88
- export interface FileUploadConfig {
89
- acceptedTypes: string[];
90
- handleUpload: (fileData: string, fileName: string, ...args: unknown[]) => ToolResult<unknown, unknown>;
91
- }
92
- /**
93
- * Plugin configuration
94
- */
95
- export interface ToolPluginConfig {
96
- key: string;
97
- defaultValue: unknown;
98
- component: Component;
99
- }
100
- /**
101
- * Sample arguments for testing
102
- */
103
- export interface ToolSample {
104
- name: string;
105
- args: Record<string, unknown>;
106
- }
107
- /**
108
- * Main plugin interface
109
- * @template T - Type of data stored in result.data
110
- * @template J - Type of data stored in result.jsonData
111
- * @template A - Type of arguments passed to execute
112
- */
113
- export interface ToolPlugin<T = unknown, J = unknown, A extends object = object> {
114
- /** Tool definition for LLM function calling */
115
- toolDefinition: ToolDefinition;
116
- /** Execute the plugin with given context and arguments */
117
- execute: (context: ToolContext, args: A) => Promise<ToolResult<T, J>>;
118
- /** Message shown while generating */
119
- generatingMessage: string;
120
- /** Message shown while waiting for user action */
121
- waitingMessage?: string;
122
- /** Message shown during file upload */
123
- uploadMessage?: string;
124
- /** Check if plugin is enabled based on server capabilities */
125
- isEnabled: (startResponse?: StartApiResponse | null) => boolean;
126
- /** Delay in ms after execution before proceeding */
127
- delayAfterExecution?: number;
128
- /** Vue component for full view */
129
- viewComponent?: Component;
130
- /** Vue component for preview/thumbnail */
131
- previewComponent?: Component;
132
- /** System prompt additions for this plugin */
133
- systemPrompt?: string;
134
- /** Optional file upload configuration */
135
- fileUpload?: FileUploadConfig;
136
- /** Optional plugin-specific configuration */
137
- config?: ToolPluginConfig;
138
- /** Optional sample arguments for testing */
139
- samples?: ToolSample[];
140
- /** Backend types this plugin uses (e.g., ["textLLM", "imageGen"]) */
141
- backends?: BackendType[];
142
- }
@@ -1,18 +0,0 @@
1
- /**
2
- * MulmoChat Quiz Plugin
3
- *
4
- * A plugin for presenting multiple choice quizzes to users.
5
- *
6
- * @example Basic usage
7
- * ```typescript
8
- * import { plugin } from "@mulmochat-plugin/quiz";
9
- * import "@mulmochat-plugin/quiz/style.css";
10
- * // Use plugin directly
11
- * ```
12
- */
13
- import type { ToolPlugin } from "../common";
14
- import type { QuizData, QuizArgs } from "./types";
15
- /**
16
- * Quiz plugin instance
17
- */
18
- export declare const plugin: ToolPlugin<never, QuizData, QuizArgs>;
@@ -1,5 +0,0 @@
1
- /**
2
- * Quiz Sample Data
3
- */
4
- import type { ToolSample } from "../common";
5
- export declare const SAMPLES: ToolSample[];
@@ -1,47 +0,0 @@
1
- /**
2
- * Quiz Tool Definition
3
- */
4
- export declare const TOOL_NAME = "putQuestions";
5
- export declare const TOOL_DEFINITION: {
6
- type: "function";
7
- name: string;
8
- description: string;
9
- parameters: {
10
- type: "object";
11
- properties: {
12
- title: {
13
- type: string;
14
- description: string;
15
- };
16
- questions: {
17
- type: string;
18
- description: string;
19
- items: {
20
- type: string;
21
- properties: {
22
- question: {
23
- type: string;
24
- description: string;
25
- };
26
- choices: {
27
- type: string;
28
- description: string;
29
- items: {
30
- type: string;
31
- };
32
- minItems: number;
33
- maxItems: number;
34
- };
35
- correctAnswer: {
36
- type: string;
37
- description: string;
38
- };
39
- };
40
- required: string[];
41
- };
42
- minItems: number;
43
- };
44
- };
45
- required: string[];
46
- };
47
- };
@@ -1,20 +0,0 @@
1
- /**
2
- * Quiz Types
3
- */
4
- /** Single quiz question */
5
- export interface QuizQuestion {
6
- question: string;
7
- choices: string[];
8
- correctAnswer?: number;
9
- }
10
- /** Quiz data stored in result.jsonData */
11
- export interface QuizData {
12
- title?: string;
13
- questions: QuizQuestion[];
14
- userAnswers?: number[];
15
- }
16
- /** Arguments passed to the quiz tool */
17
- export interface QuizArgs {
18
- title?: string;
19
- questions: QuizQuestion[];
20
- }