@mulmochat-plugin/quiz 0.1.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.
package/README.md ADDED
@@ -0,0 +1,159 @@
1
+ # MulmoChat Plugin Quiz
2
+
3
+ MulmoChat用のクイズプラグイン。複数選択式のクイズをユーザーに提示します。
4
+
5
+ ## 概要
6
+
7
+ このプラグインは、MulmoChatのプラグインシステムのリファレンス実装です。外部依存がなく、サーバー通信も不要なシンプルな構造のため、新しいプラグインを作成する際のテンプレートとして使用できます。
8
+
9
+ ## インストール
10
+
11
+ ### MulmoChatへの追加
12
+
13
+ 1. プラグインをビルド:
14
+ ```bash
15
+ cd MulmoChatPluginQuiz
16
+ yarn install
17
+ yarn build
18
+ yarn pack
19
+ ```
20
+
21
+ 2. MulmoChatの`package.json`に依存関係を追加:
22
+ ```json
23
+ {
24
+ "dependencies": {
25
+ "mulmochat-plugin-quiz": "file:../MulmoChatPluginQuiz/mulmochat-plugin-quiz-v0.1.0.tgz"
26
+ }
27
+ }
28
+ ```
29
+
30
+ 3. MulmoChatの`src/tools/index.ts`でインポート:
31
+ ```typescript
32
+ import type { ToolPlugin } from "./types";
33
+
34
+ // Quiz plugin from npm package
35
+ import { QuizPlugin as QuizPluginImport } from "mulmochat-plugin-quiz";
36
+ const QuizPlugin = QuizPluginImport as { plugin: ToolPlugin };
37
+
38
+ // pluginListに追加
39
+ const pluginList = [
40
+ // ... other plugins
41
+ QuizPlugin,
42
+ ];
43
+ ```
44
+
45
+ 4. MulmoChatで依存関係を再インストール:
46
+ ```bash
47
+ cd MulmoChat
48
+ yarn install --force
49
+ ```
50
+
51
+ ## 開発
52
+
53
+ ### セットアップ
54
+
55
+ ```bash
56
+ yarn install
57
+ ```
58
+
59
+ ### 開発サーバー
60
+
61
+ ```bash
62
+ yarn dev
63
+ ```
64
+
65
+ http://localhost:5173/ でデモページが表示されます。
66
+
67
+ ### ビルド
68
+
69
+ ```bash
70
+ yarn build
71
+ ```
72
+
73
+ ### 型チェック
74
+
75
+ ```bash
76
+ yarn typecheck
77
+ ```
78
+
79
+ ## プラグイン構造
80
+
81
+ ```
82
+ MulmoChatPluginQuiz/
83
+ ├── src/
84
+ │ ├── index.ts # エクスポート定義
85
+ │ ├── plugin.ts # プラグイン実装
86
+ │ ├── types.ts # 型定義
87
+ │ ├── views/
88
+ │ │ └── QuizView.vue # メインビューコンポーネント
89
+ │ └── previews/
90
+ │ └── QuizPreview.vue # サイドバープレビュー
91
+ ├── demo/ # 開発用デモページ
92
+ ├── package.json
93
+ ├── vite.config.ts
94
+ └── tsconfig.json
95
+ ```
96
+
97
+ ## 新しいプラグインの作成
98
+
99
+ このリポジトリをベースに新しいプラグインを作成する手順:
100
+
101
+ 1. リポジトリをコピー:
102
+ ```bash
103
+ cp -r MulmoChatPluginQuiz MulmoChatPluginYourPlugin
104
+ cd MulmoChatPluginYourPlugin
105
+ rm -rf .git node_modules dist *.tgz
106
+ git init
107
+ ```
108
+
109
+ 2. `package.json`を編集:
110
+ - `name`: `mulmochat-plugin-yourplugin`
111
+ - `description`: プラグインの説明
112
+
113
+ 3. `src/plugin.ts`を編集:
114
+ - `TOOL_DEFINITION`: ツール名、説明、パラメータを定義
115
+ - `execute`: ツール実行ロジックを実装
116
+ - 必要に応じて型定義を追加
117
+
118
+ 4. `src/views/YourView.vue`を作成:
119
+ - メイン表示コンポーネント
120
+ - Props: `selectedResult`, `sendTextMessage`
121
+ - Emit: `updateResult`
122
+
123
+ 5. `src/previews/YourPreview.vue`を作成:
124
+ - サイドバー用のプレビューコンポーネント
125
+ - Props: `result`
126
+
127
+ 6. `src/index.ts`でエクスポートを更新
128
+
129
+ 7. `demo/App.vue`をプラグインに合わせて更新
130
+
131
+ ## ToolPlugin インターフェース
132
+
133
+ ```typescript
134
+ interface ToolPlugin<T, J, A> {
135
+ toolDefinition: {
136
+ type: "function";
137
+ name: string;
138
+ description: string;
139
+ parameters?: {
140
+ type: "object";
141
+ properties: Record<string, JsonSchemaProperty>;
142
+ required: string[];
143
+ };
144
+ };
145
+ execute: (context: ToolContext, args: A) => Promise<ToolResult<T, J>>;
146
+ generatingMessage: string;
147
+ isEnabled: (startResponse?: StartApiResponse | null) => boolean;
148
+ viewComponent?: Component;
149
+ previewComponent?: Component;
150
+ // Optional
151
+ systemPrompt?: string;
152
+ fileUpload?: FileUploadConfig;
153
+ config?: ToolPluginConfig;
154
+ }
155
+ ```
156
+
157
+ ## ライセンス
158
+
159
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,3 @@
1
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("vue"),k={class:"quiz-container"},E={key:0,class:"quiz-content"},b={key:0,class:"quiz-title"},z={class:"questions-list"},N={class:"question-text"},V={class:"question-number"},B={class:"choices-list"},S=["name","value","onUpdate:modelValue"],Q={class:"choice-text"},D={class:"choice-letter"},$={class:"submit-section"},C=["disabled"],A={class:"progress-text"},T=e.defineComponent({__name:"QuizView",props:{selectedResult:{},sendTextMessage:{type:Function}},emits:["updateResult"],setup(l,{emit:u}){const t=l,i=u,s=e.ref(null),o=e.ref([]);e.watch(()=>t.selectedResult,n=>{n?.toolName==="putQuestions"&&n.jsonData&&(s.value=n.jsonData,n.viewState?.userAnswers?o.value=n.viewState.userAnswers:o.value=new Array(s.value.questions.length).fill(null))},{immediate:!0}),e.watch(o,n=>{if(t.selectedResult&&n){const d={...t.selectedResult,viewState:{userAnswers:n}};i("updateResult",d)}},{deep:!0});const r=e.computed(()=>o.value.filter(n=>n!==null).length),p=e.computed(()=>s.value&&r.value===s.value.questions.length);function q(n,d){return o.value[n]===d?"choice-selected":"choice-default"}function f(){if(!s.value||!p.value)return;const d=`Here are my answers:
2
+ ${o.value.map((c,a)=>{if(c===null)return null;const m=a+1,h=String.fromCharCode(65+c),v=s.value.questions[a].choices[c];return`Q${m}: ${h} - ${v}`}).filter(c=>c!==null).join(`
3
+ `)}`;t.sendTextMessage(d)}return(n,d)=>(e.openBlock(),e.createElementBlock("div",k,[s.value?(e.openBlock(),e.createElementBlock("div",E,[s.value.title?(e.openBlock(),e.createElementBlock("h2",b,e.toDisplayString(s.value.title),1)):e.createCommentVNode("",!0),e.createElementVNode("div",z,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(s.value.questions,(c,a)=>(e.openBlock(),e.createElementBlock("div",{key:a,class:"question-card"},[e.createElementVNode("div",N,[e.createElementVNode("span",V,e.toDisplayString(a+1)+".",1),e.createTextVNode(" "+e.toDisplayString(c.question),1)]),e.createElementVNode("div",B,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(c.choices,(m,h)=>(e.openBlock(),e.createElementBlock("label",{key:h,class:e.normalizeClass([q(a,h),"choice-label"])},[e.withDirectives(e.createElementVNode("input",{type:"radio",name:`question-${a}`,value:h,"onUpdate:modelValue":v=>o.value[a]=v,class:"choice-radio"},null,8,S),[[e.vModelRadio,o.value[a]]]),e.createElementVNode("span",Q,[e.createElementVNode("span",D,e.toDisplayString(String.fromCharCode(65+h))+".",1),e.createTextVNode(" "+e.toDisplayString(m),1)])],2))),128))])]))),128))]),e.createElementVNode("div",$,[e.createElementVNode("button",{onClick:f,disabled:!p.value,class:e.normalizeClass([p.value?"submit-btn-active":"submit-btn-disabled","submit-btn"])}," Submit Answers ",10,C)]),e.createElementVNode("div",A,e.toDisplayString(r.value)+" / "+e.toDisplayString(s.value.questions.length)+" questions answered ",1)])):e.createCommentVNode("",!0)]))}}),g=(l,u)=>{const t=l.__vccOpts||l;for(const[i,s]of u)t[i]=s;return t},y=g(T,[["__scopeId","data-v-f85309ce"]]),x={class:"quiz-preview"},O={key:0,class:"preview-content"},j={class:"preview-title"},M={class:"preview-count"},P={class:"count-badge"},L={class:"preview-question"},F={class:"preview-choices"},R={key:0,class:"more-choices"},U=e.defineComponent({__name:"QuizPreview",props:{result:{}},setup(l){const u=l,t=e.computed(()=>u.result.jsonData);return(i,s)=>(e.openBlock(),e.createElementBlock("div",x,[t.value?(e.openBlock(),e.createElementBlock("div",O,[e.createElementVNode("div",j,e.toDisplayString(t.value.title||"Quiz"),1),e.createElementVNode("div",M,[e.createElementVNode("span",P,e.toDisplayString(t.value.questions.length)+" "+e.toDisplayString(t.value.questions.length===1?"Question":"Questions"),1)]),e.createElementVNode("div",L,e.toDisplayString(t.value.questions[0]?.question),1),e.createElementVNode("div",F,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(Math.min(t.value.questions[0]?.choices.length||0,4),(o,r)=>(e.openBlock(),e.createElementBlock("div",{key:r,class:"choice-dot"}))),128)),(t.value.questions[0]?.choices.length||0)>4?(e.openBlock(),e.createElementBlock("div",R," +"+e.toDisplayString(t.value.questions[0].choices.length-4),1)):e.createCommentVNode("",!0)])])):e.createCommentVNode("",!0)]))}}),w=g(U,[["__scopeId","data-v-29efb94d"]]),H="putQuestions",I={type:"function",name:H,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"]}},J=async(l,u)=>{try{const{title:t,questions:i}=u;if(!i||!Array.isArray(i)||i.length===0)throw new Error("At least one question is required");for(let o=0;o<i.length;o++){const r=i[o];if(!r.question||typeof r.question!="string")throw new Error(`Question ${o+1} must have a question text`);if(!Array.isArray(r.choices)||r.choices.length<2)throw new Error(`Question ${o+1} must have at least 2 choices`);if(r.choices.length>6)throw new Error(`Question ${o+1} cannot have more than 6 choices`)}const s={title:t,questions:i};return{message:`Quiz presented with ${i.length} question${i.length>1?"s":""}`,jsonData:s,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."}}},_={toolDefinition:I,execute:J,generatingMessage:"Preparing quiz...",isEnabled:()=>!0,viewComponent:y,previewComponent:w},W={plugin:_};exports.QuizPlugin=W;exports.QuizPreview=w;exports.QuizView=y;exports.default=_;exports.plugin=_;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * MulmoChat Quiz Plugin
3
+ *
4
+ * A plugin for presenting multiple choice quizzes to users.
5
+ *
6
+ * @packageDocumentation
7
+ *
8
+ * @example Basic usage
9
+ * ```typescript
10
+ * import { plugin } from "mulmochat-plugin-quiz";
11
+ *
12
+ * // Plugin is ready to use
13
+ * const tools = [plugin.toolDefinition];
14
+ * ```
15
+ *
16
+ * @example Using Vue components directly
17
+ * ```typescript
18
+ * import { QuizView, QuizPreview } from "mulmochat-plugin-quiz";
19
+ * ```
20
+ */
21
+ export type { ToolContext, ToolResult, ToolPlugin, ToolDefinition, JsonSchemaProperty, StartApiResponse, FileUploadConfig, ToolPluginConfig, ToolSample, } from "./types";
22
+ export type { QuizQuestion, QuizData, QuizArgs, QuizResult } from "./plugin";
23
+ export { plugin } from "./plugin";
24
+ export { plugin as default } from "./plugin";
25
+ /**
26
+ * QuizPlugin module matching MulmoChat's expected plugin structure.
27
+ * Use this for direct import: `import { QuizPlugin } from "mulmochat-plugin-quiz"`
28
+ */
29
+ export declare const QuizPlugin: {
30
+ plugin: import("./types").ToolPlugin<never, import("./plugin").QuizData, import("./plugin").QuizArgs>;
31
+ };
32
+ export { default as QuizView } from "./views/QuizView.vue";
33
+ export { default as QuizPreview } from "./previews/QuizPreview.vue";
package/dist/index.js ADDED
@@ -0,0 +1,218 @@
1
+ import { defineComponent as k, ref as z, watch as Q, computed as f, createElementBlock as r, openBlock as c, createCommentVNode as m, createElementVNode as i, toDisplayString as a, Fragment as y, renderList as b, createTextVNode as $, normalizeClass as A, withDirectives as D, vModelRadio as E } from "vue";
2
+ const S = { class: "quiz-container" }, N = {
3
+ key: 0,
4
+ class: "quiz-content"
5
+ }, O = {
6
+ key: 0,
7
+ class: "quiz-title"
8
+ }, j = { class: "questions-list" }, V = { class: "question-text" }, M = { class: "question-number" }, P = { class: "choices-list" }, L = ["name", "value", "onUpdate:modelValue"], R = { class: "choice-text" }, B = { class: "choice-letter" }, F = { class: "submit-section" }, U = ["disabled"], H = { class: "progress-text" }, I = /* @__PURE__ */ k({
9
+ __name: "QuizView",
10
+ props: {
11
+ selectedResult: {},
12
+ sendTextMessage: { type: Function }
13
+ },
14
+ emits: ["updateResult"],
15
+ setup(d, { emit: p }) {
16
+ const e = d, n = p, t = z(null), s = z([]);
17
+ Q(
18
+ () => e.selectedResult,
19
+ (o) => {
20
+ o?.toolName === "putQuestions" && o.jsonData && (t.value = o.jsonData, o.viewState?.userAnswers ? s.value = o.viewState.userAnswers : s.value = new Array(t.value.questions.length).fill(null));
21
+ },
22
+ { immediate: !0 }
23
+ ), Q(
24
+ s,
25
+ (o) => {
26
+ if (e.selectedResult && o) {
27
+ const v = {
28
+ ...e.selectedResult,
29
+ viewState: {
30
+ userAnswers: o
31
+ }
32
+ };
33
+ n("updateResult", v);
34
+ }
35
+ },
36
+ { deep: !0 }
37
+ );
38
+ const u = f(() => s.value.filter((o) => o !== null).length), g = f(() => t.value && u.value === t.value.questions.length);
39
+ function T(o, v) {
40
+ return s.value[o] === v ? "choice-selected" : "choice-default";
41
+ }
42
+ function x() {
43
+ if (!t.value || !g.value) return;
44
+ const v = `Here are my answers:
45
+ ${s.value.map((l, h) => {
46
+ if (l === null) return null;
47
+ const q = h + 1, _ = String.fromCharCode(65 + l), w = t.value.questions[h].choices[l];
48
+ return `Q${q}: ${_} - ${w}`;
49
+ }).filter((l) => l !== null).join(`
50
+ `)}`;
51
+ e.sendTextMessage(v);
52
+ }
53
+ return (o, v) => (c(), r("div", S, [
54
+ t.value ? (c(), r("div", N, [
55
+ t.value.title ? (c(), r("h2", O, a(t.value.title), 1)) : m("", !0),
56
+ i("div", j, [
57
+ (c(!0), r(y, null, b(t.value.questions, (l, h) => (c(), r("div", {
58
+ key: h,
59
+ class: "question-card"
60
+ }, [
61
+ i("div", V, [
62
+ i("span", M, a(h + 1) + ".", 1),
63
+ $(" " + a(l.question), 1)
64
+ ]),
65
+ i("div", P, [
66
+ (c(!0), r(y, null, b(l.choices, (q, _) => (c(), r("label", {
67
+ key: _,
68
+ class: A([T(h, _), "choice-label"])
69
+ }, [
70
+ D(i("input", {
71
+ type: "radio",
72
+ name: `question-${h}`,
73
+ value: _,
74
+ "onUpdate:modelValue": (w) => s.value[h] = w,
75
+ class: "choice-radio"
76
+ }, null, 8, L), [
77
+ [E, s.value[h]]
78
+ ]),
79
+ i("span", R, [
80
+ i("span", B, a(String.fromCharCode(65 + _)) + ".", 1),
81
+ $(" " + a(q), 1)
82
+ ])
83
+ ], 2))), 128))
84
+ ])
85
+ ]))), 128))
86
+ ]),
87
+ i("div", F, [
88
+ i("button", {
89
+ onClick: x,
90
+ disabled: !g.value,
91
+ class: A([g.value ? "submit-btn-active" : "submit-btn-disabled", "submit-btn"])
92
+ }, " Submit Answers ", 10, U)
93
+ ]),
94
+ i("div", H, a(u.value) + " / " + a(t.value.questions.length) + " questions answered ", 1)
95
+ ])) : m("", !0)
96
+ ]));
97
+ }
98
+ }), C = (d, p) => {
99
+ const e = d.__vccOpts || d;
100
+ for (const [n, t] of p)
101
+ e[n] = t;
102
+ return e;
103
+ }, J = /* @__PURE__ */ C(I, [["__scopeId", "data-v-f85309ce"]]), W = { class: "quiz-preview" }, G = {
104
+ key: 0,
105
+ class: "preview-content"
106
+ }, K = { class: "preview-title" }, X = { class: "preview-count" }, Y = { class: "count-badge" }, Z = { class: "preview-question" }, ee = { class: "preview-choices" }, te = {
107
+ key: 0,
108
+ class: "more-choices"
109
+ }, se = /* @__PURE__ */ k({
110
+ __name: "QuizPreview",
111
+ props: {
112
+ result: {}
113
+ },
114
+ setup(d) {
115
+ const p = d, e = f(() => p.result.jsonData);
116
+ return (n, t) => (c(), r("div", W, [
117
+ e.value ? (c(), r("div", G, [
118
+ i("div", K, a(e.value.title || "Quiz"), 1),
119
+ i("div", X, [
120
+ i("span", Y, a(e.value.questions.length) + " " + a(e.value.questions.length === 1 ? "Question" : "Questions"), 1)
121
+ ]),
122
+ i("div", Z, a(e.value.questions[0]?.question), 1),
123
+ i("div", ee, [
124
+ (c(!0), r(y, null, b(Math.min(e.value.questions[0]?.choices.length || 0, 4), (s, u) => (c(), r("div", {
125
+ key: u,
126
+ class: "choice-dot"
127
+ }))), 128)),
128
+ (e.value.questions[0]?.choices.length || 0) > 4 ? (c(), r("div", te, " +" + a(e.value.questions[0].choices.length - 4), 1)) : m("", !0)
129
+ ])
130
+ ])) : m("", !0)
131
+ ]));
132
+ }
133
+ }), ie = /* @__PURE__ */ C(se, [["__scopeId", "data-v-29efb94d"]]), oe = "putQuestions", ne = {
134
+ type: "function",
135
+ name: oe,
136
+ description: "Present a set of multiple choice questions to test the user's knowledge or abilities. Each question should have 2-6 answer choices.",
137
+ parameters: {
138
+ type: "object",
139
+ properties: {
140
+ title: {
141
+ type: "string",
142
+ description: "Optional title for the quiz (e.g., 'JavaScript Basics Quiz')"
143
+ },
144
+ questions: {
145
+ type: "array",
146
+ description: "Array of multiple choice questions",
147
+ items: {
148
+ type: "object",
149
+ properties: {
150
+ question: {
151
+ type: "string",
152
+ description: "The question text"
153
+ },
154
+ choices: {
155
+ type: "array",
156
+ description: "Array of answer choices (2-6 choices)",
157
+ items: {
158
+ type: "string"
159
+ },
160
+ minItems: 2,
161
+ maxItems: 6
162
+ },
163
+ correctAnswer: {
164
+ type: "number",
165
+ description: "Optional: The index of the correct answer (0-based). Include this if you want to track correct answers."
166
+ }
167
+ },
168
+ required: ["question", "choices"]
169
+ },
170
+ minItems: 1
171
+ }
172
+ },
173
+ required: ["questions"]
174
+ }
175
+ }, re = async (d, p) => {
176
+ try {
177
+ const { title: e, questions: n } = p;
178
+ if (!n || !Array.isArray(n) || n.length === 0)
179
+ throw new Error("At least one question is required");
180
+ for (let s = 0; s < n.length; s++) {
181
+ const u = n[s];
182
+ if (!u.question || typeof u.question != "string")
183
+ throw new Error(`Question ${s + 1} must have a question text`);
184
+ if (!Array.isArray(u.choices) || u.choices.length < 2)
185
+ throw new Error(`Question ${s + 1} must have at least 2 choices`);
186
+ if (u.choices.length > 6)
187
+ throw new Error(`Question ${s + 1} cannot have more than 6 choices`);
188
+ }
189
+ const t = {
190
+ title: e,
191
+ questions: n
192
+ };
193
+ return {
194
+ message: `Quiz presented with ${n.length} question${n.length > 1 ? "s" : ""}`,
195
+ jsonData: t,
196
+ 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."
197
+ };
198
+ } catch (e) {
199
+ return console.error("Quiz creation error", e), {
200
+ message: `Quiz error: ${e instanceof Error ? e.message : "Unknown error"}`,
201
+ instructions: "Acknowledge that there was an error creating the quiz and suggest trying again."
202
+ };
203
+ }
204
+ }, ce = {
205
+ toolDefinition: ne,
206
+ execute: re,
207
+ generatingMessage: "Preparing quiz...",
208
+ isEnabled: () => !0,
209
+ viewComponent: J,
210
+ previewComponent: ie
211
+ }, ue = { plugin: ce };
212
+ export {
213
+ ue as QuizPlugin,
214
+ ie as QuizPreview,
215
+ J as QuizView,
216
+ ce as default,
217
+ ce as plugin
218
+ };
@@ -0,0 +1,35 @@
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
+ * // Use plugin directly
10
+ * ```
11
+ */
12
+ import type { ToolPlugin, ToolResult } from "./types";
13
+ /** Single quiz question */
14
+ export interface QuizQuestion {
15
+ question: string;
16
+ choices: string[];
17
+ correctAnswer?: number;
18
+ }
19
+ /** Quiz data stored in result.jsonData */
20
+ export interface QuizData {
21
+ title?: string;
22
+ questions: QuizQuestion[];
23
+ userAnswers?: number[];
24
+ }
25
+ /** Arguments passed to the quiz tool */
26
+ export interface QuizArgs {
27
+ title?: string;
28
+ questions: QuizQuestion[];
29
+ }
30
+ /** Quiz tool result type */
31
+ export type QuizResult = ToolResult<never, QuizData>;
32
+ /**
33
+ * Quiz plugin instance
34
+ */
35
+ export declare const plugin: ToolPlugin<never, QuizData, QuizArgs>;
@@ -0,0 +1,6 @@
1
+ import type { ToolResult } from "../types";
2
+ type __VLS_Props = {
3
+ result: ToolResult;
4
+ };
5
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
6
+ export default _default;
package/dist/quiz.css ADDED
@@ -0,0 +1 @@
1
+ .quiz-container[data-v-f85309ce]{width:100%;height:100%;overflow-y:auto;padding:2rem;background:#1a1a2e}.quiz-content[data-v-f85309ce]{max-width:48rem;width:100%;margin:0 auto}.quiz-title[data-v-f85309ce]{color:#f0f0f0;font-size:1.875rem;font-weight:700;margin-bottom:2rem;text-align:center}.questions-list[data-v-f85309ce]{display:flex;flex-direction:column;gap:1.5rem}.question-card[data-v-f85309ce]{background:#2d2d44;border-radius:.5rem;padding:1.5rem;border:2px solid #3d3d5c}.question-text[data-v-f85309ce]{color:#fff;font-size:1.125rem;font-weight:600;margin-bottom:1rem}.question-number[data-v-f85309ce]{color:#60a5fa;margin-right:.5rem}.choices-list[data-v-f85309ce]{display:flex;flex-direction:column;gap:.75rem}.choice-label[data-v-f85309ce]{display:flex;align-items:flex-start;padding:1rem;border-radius:.5rem;cursor:pointer;transition:all .2s;border:2px solid transparent}.choice-default[data-v-f85309ce]{border-color:#4b4b6b}.choice-default[data-v-f85309ce]:hover{border-color:#6b6b8b;background:#6b6b8b33}.choice-selected[data-v-f85309ce]{border-color:#3b82f6;background:#3b82f633}.choice-radio[data-v-f85309ce]{margin-top:.25rem;margin-right:.75rem;height:1rem;width:1rem;flex-shrink:0}.choice-text[data-v-f85309ce]{color:#fff;flex:1}.choice-letter[data-v-f85309ce]{font-weight:600;margin-right:.5rem}.submit-section[data-v-f85309ce]{margin-top:2rem;display:flex;justify-content:center}.submit-btn[data-v-f85309ce]{padding:.75rem 2rem;border-radius:.5rem;color:#fff;font-weight:600;font-size:1.125rem;transition:background-color .2s;border:none;cursor:pointer}.submit-btn-active[data-v-f85309ce]{background-color:#2563eb}.submit-btn-active[data-v-f85309ce]:hover{background-color:#1d4ed8}.submit-btn-disabled[data-v-f85309ce]{background-color:#4b5563;cursor:not-allowed;opacity:.5}.progress-text[data-v-f85309ce]{margin-top:1rem;text-align:center;color:#9ca3af;font-size:.875rem}.quiz-preview[data-v-29efb94d]{padding:.75rem;background:#eff6ff;border-radius:.375rem}.preview-content[data-v-29efb94d]{display:flex;flex-direction:column;gap:.5rem}.preview-title[data-v-29efb94d]{font-size:.875rem;font-weight:600;color:#1f2937;text-align:center}.preview-count[data-v-29efb94d]{text-align:center}.count-badge[data-v-29efb94d]{display:inline-block;background:#2563eb;color:#fff;font-size:.75rem;font-weight:700;padding:.25rem .75rem;border-radius:9999px}.preview-question[data-v-29efb94d]{font-size:.75rem;color:#4b5563;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}.preview-choices[data-v-29efb94d]{display:flex;justify-content:center;gap:.25rem}.choice-dot[data-v-29efb94d]{width:.5rem;height:.5rem;background:#9ca3af;border-radius:9999px}.more-choices[data-v-29efb94d]{font-size:.75rem;color:#6b7280}
@@ -0,0 +1,126 @@
1
+ /**
2
+ * MulmoChat Plugin Types
3
+ * Core interfaces for building MulmoChat plugins
4
+ */
5
+ import type { Component } from "vue";
6
+ /**
7
+ * Context passed to plugin execute function
8
+ */
9
+ export interface ToolContext {
10
+ currentResult?: ToolResult<unknown> | null;
11
+ userPreferences?: Record<string, unknown>;
12
+ getPluginConfig?: <T = unknown>(key: string) => T | undefined;
13
+ }
14
+ /**
15
+ * Result returned from plugin execution
16
+ */
17
+ export interface ToolResult<T = unknown, J = unknown> {
18
+ toolName?: string;
19
+ uuid?: string;
20
+ message: string;
21
+ title?: string;
22
+ jsonData?: J;
23
+ instructions?: string;
24
+ instructionsRequired?: boolean;
25
+ updating?: boolean;
26
+ cancelled?: boolean;
27
+ data?: T;
28
+ viewState?: Record<string, unknown>;
29
+ }
30
+ /**
31
+ * JSON Schema property definition for tool parameters
32
+ */
33
+ export interface JsonSchemaProperty {
34
+ type?: string;
35
+ description?: string;
36
+ enum?: string[];
37
+ items?: JsonSchemaProperty;
38
+ minimum?: number;
39
+ maximum?: number;
40
+ minItems?: number;
41
+ maxItems?: number;
42
+ properties?: Record<string, JsonSchemaProperty>;
43
+ required?: string[];
44
+ additionalProperties?: boolean;
45
+ oneOf?: JsonSchemaProperty[];
46
+ [key: string]: unknown;
47
+ }
48
+ /**
49
+ * API response from server start endpoint
50
+ */
51
+ export interface StartApiResponse {
52
+ hasOpenAIApiKey?: boolean;
53
+ hasAnthropicApiKey?: boolean;
54
+ hasGoogleApiKey?: boolean;
55
+ [key: string]: unknown;
56
+ }
57
+ /**
58
+ * Tool definition for OpenAI-compatible function calling
59
+ */
60
+ export interface ToolDefinition {
61
+ type: "function";
62
+ name: string;
63
+ description: string;
64
+ parameters?: {
65
+ type: "object";
66
+ properties: Record<string, JsonSchemaProperty>;
67
+ required: string[];
68
+ additionalProperties?: boolean;
69
+ };
70
+ }
71
+ /**
72
+ * File upload configuration
73
+ */
74
+ export interface FileUploadConfig {
75
+ acceptedTypes: string[];
76
+ handleUpload: (fileData: string, fileName: string, ...args: unknown[]) => ToolResult<unknown, unknown>;
77
+ }
78
+ /**
79
+ * Plugin configuration
80
+ */
81
+ export interface ToolPluginConfig {
82
+ key: string;
83
+ defaultValue: unknown;
84
+ component: Component;
85
+ }
86
+ /**
87
+ * Sample arguments for testing
88
+ */
89
+ export interface ToolSample {
90
+ name: string;
91
+ args: Record<string, unknown>;
92
+ }
93
+ /**
94
+ * Main plugin interface
95
+ * @template T - Type of data stored in result.data
96
+ * @template J - Type of data stored in result.jsonData
97
+ * @template A - Type of arguments passed to execute
98
+ */
99
+ export interface ToolPlugin<T = unknown, J = unknown, A extends object = object> {
100
+ /** Tool definition for LLM function calling */
101
+ toolDefinition: ToolDefinition;
102
+ /** Execute the plugin with given context and arguments */
103
+ execute: (context: ToolContext, args: A) => Promise<ToolResult<T, J>>;
104
+ /** Message shown while generating */
105
+ generatingMessage: string;
106
+ /** Message shown while waiting for user action */
107
+ waitingMessage?: string;
108
+ /** Message shown during file upload */
109
+ uploadMessage?: string;
110
+ /** Check if plugin is enabled based on server capabilities */
111
+ isEnabled: (startResponse?: StartApiResponse | null) => boolean;
112
+ /** Delay in ms after execution before proceeding */
113
+ delayAfterExecution?: number;
114
+ /** Vue component for full view */
115
+ viewComponent?: Component;
116
+ /** Vue component for preview/thumbnail */
117
+ previewComponent?: Component;
118
+ /** System prompt additions for this plugin */
119
+ systemPrompt?: string;
120
+ /** Optional file upload configuration */
121
+ fileUpload?: FileUploadConfig;
122
+ /** Optional plugin-specific configuration */
123
+ config?: ToolPluginConfig;
124
+ /** Optional sample arguments for testing */
125
+ samples?: ToolSample[];
126
+ }
@@ -0,0 +1,11 @@
1
+ import type { ToolResult } from "../types";
2
+ type __VLS_Props = {
3
+ selectedResult: ToolResult;
4
+ sendTextMessage: (text?: string) => void;
5
+ };
6
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
7
+ updateResult: (result: ToolResult<unknown, unknown>) => any;
8
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
9
+ onUpdateResult?: ((result: ToolResult<unknown, unknown>) => any) | undefined;
10
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
11
+ export default _default;
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@mulmochat-plugin/quiz",
3
+ "version": "0.1.0",
4
+ "description": "Quiz plugin for MulmoChat",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ },
15
+ "./style.css": "./dist/mulmochat-plugin-quiz.css"
16
+ },
17
+ "files": [
18
+ "dist"
19
+ ],
20
+ "scripts": {
21
+ "dev": "vite",
22
+ "build": "vite build && vue-tsc --emitDeclarationOnly",
23
+ "typecheck": "vue-tsc --noEmit",
24
+ "lint": "eslint src"
25
+ },
26
+ "peerDependencies": {
27
+ "vue": "^3.4.0"
28
+ },
29
+ "devDependencies": {
30
+ "@vitejs/plugin-vue": "^5.0.0",
31
+ "typescript": "~5.8.0",
32
+ "vite": "^7.0.0",
33
+ "vue": "^3.4.0",
34
+ "vue-tsc": "^2.2.0"
35
+ },
36
+ "keywords": [
37
+ "mulmochat",
38
+ "plugin",
39
+ "quiz"
40
+ ],
41
+ "license": "MIT"
42
+ }