@priscilla-ai/vue 1.0.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/.editorconfig ADDED
@@ -0,0 +1,8 @@
1
+ [*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue,css,scss,sass,less,styl}]
2
+ charset = utf-8
3
+ indent_size = 2
4
+ indent_style = space
5
+ insert_final_newline = true
6
+ trim_trailing_whitespace = true
7
+ end_of_line = lf
8
+ max_line_length = 100
package/.gitattributes ADDED
@@ -0,0 +1 @@
1
+ * text=auto eol=lf
@@ -0,0 +1,6 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/prettierrc",
3
+ "semi": false,
4
+ "singleQuote": true,
5
+ "printWidth": 100
6
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "recommendations": [
3
+ "Vue.volar",
4
+ "dbaeumer.vscode-eslint",
5
+ "EditorConfig.EditorConfig",
6
+ "esbenp.prettier-vscode"
7
+ ]
8
+ }
package/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # Changelog
2
+
3
+ ## [1.0.0] - 18-03-2026
4
+ - First Release
5
+ - Vue 3 plugin for AI-powered code
6
+
7
+
package/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright <2025> <Dominik Takáč>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,202 @@
1
+ # Priscilla AI — Vue 3 Code Hint Assistant Plugin
2
+
3
+ A Vue 3 plugin that provides AI-powered code hints by extracting code from the DOM via XPath and sending it to an API for analysis. Built with TypeScript and Axios.
4
+
5
+ ## Plugin Figma Design
6
+ <img width="1956" height="1078" alt="image" src="https://github.com/user-attachments/assets/e1cee0bb-345b-4b77-a8cc-59ef428fcb6c" />
7
+
8
+
9
+ [Figma Link](https://www.figma.com/design/laysuH8lJanr37ZCR2HUpm/Bakalarka-komponent?node-id=0-1&t=GMB5zaetauqlHPvO-1)
10
+
11
+
12
+ ## Features
13
+
14
+ ✨ **XPath-based Code Extraction** — Extract code from any element using XPath expressions
15
+ 🤖 **AI-Powered Hints** — Get intelligent hints about your code from an LLM API
16
+ 📦 **Type-Safe** — Full TypeScript support with type definitions
17
+ ♿ **Accessible** — Built with ARIA labels and semantic HTML
18
+ 🎨 **Styled UI** — Clean, minimal assistant interface with loading states
19
+ ⚡ **Cancellable Requests** — Abort in-flight requests when unmounting or requesting new hints
20
+
21
+ ## Installation
22
+
23
+ ### npm
24
+ ```bash
25
+ npm install @priscilla/vue-ai-assistant
26
+ ```
27
+
28
+ ### pnpm
29
+ ```bash
30
+ pnpm add @priscilla/vue-ai-assistant
31
+ ```
32
+
33
+ ## Setup
34
+
35
+ **1. Register the plugin in your Vue app:**
36
+
37
+ ```typescript
38
+ // src/main.ts
39
+ import { createApp } from 'vue'
40
+ import App from './App.vue'
41
+ import { createPriscillaAI } from '@priscilla/vue-ai-assistant'
42
+
43
+ const app = createApp(App)
44
+
45
+ // Initialize with your API endpoint
46
+ app.use(createPriscillaAI('https://api.example.com/api/predict/hint'))
47
+
48
+ app.mount('#app')
49
+ ```
50
+
51
+ **2. The `PriscillaAI` component will be globally available.**
52
+
53
+ ## Usage
54
+
55
+ ### Basic Example
56
+
57
+ ```vue
58
+ <template>
59
+ <div>
60
+ <textarea id="code-block">
61
+ function fibonacci(n) {
62
+ if (n <= 1) return n
63
+ return fibonacci(n - 1) + fibonacci(n - 2)
64
+ }
65
+ </textarea>
66
+
67
+ <!-- Add the AI assistant -->
68
+ <PriscillaAI
69
+ xpath="//textarea[@id='code-block']"
70
+ :chapterId="12"
71
+ :programId="589"
72
+ />
73
+ </div>
74
+ </template>
75
+ ```
76
+
77
+ ## Props
78
+
79
+ | Prop | Type | Required | Description |
80
+ |------|------|----------|-------------|
81
+ | `xpath` | `string` | ✅ Yes | XPath expression to extract code from the DOM (must be valid and non-empty) |
82
+ | `chapterId` | `number` | ✅ Yes | Chapter identifier for the code context (must be positive integer) |
83
+ | `programId` | `number` | ✅ Yes | Program identifier for the code context (must be positive integer) |
84
+
85
+ ### Props Example
86
+
87
+ ```vue
88
+ <PriscillaAI
89
+ xpath="//textarea[@id='user-code']"
90
+ :chapterId="5"
91
+ :programId="102"
92
+ />
93
+ ```
94
+
95
+ ## Component Features
96
+
97
+ - 🎯 **Toggle Button** — Click the 🤖 icon to open/close the assistant
98
+ - 📝 **Code Display** — Shows extracted code for verification
99
+ - 💡 **Hint Display** — Displays AI-generated hints
100
+ - ⚠️ **Error Handling** — Shows clear error messages
101
+ - ⏳ **Loading States** — Visual feedback while fetching
102
+ - 🧹 **Clear Button** — Reset and clear all outputs
103
+
104
+ ## API Response Format
105
+
106
+ The API endpoint should return a response matching this interface:
107
+
108
+ ```typescript
109
+ interface PriscillaAIResponse {
110
+ hint?: string
111
+ }
112
+ ```
113
+
114
+ ### Example API Payload
115
+
116
+ **Request:**
117
+ ```json
118
+ {
119
+ "content": "function fibonacci(n) { if (n <= 1) return n; return fibonacci(n - 1) + fibonacci(n - 2); }",
120
+ "chapterId": 12,
121
+ "programId": 589
122
+ }
123
+ ```
124
+
125
+ **Response:**
126
+ ```json
127
+ {
128
+ "hint": "Consider using memoization to avoid recalculating fibonacci numbers. The current implementation has exponential time complexity.",
129
+ }
130
+ ```
131
+
132
+ ## TypeScript Support
133
+
134
+ Full TypeScript support is included. Import types as needed:
135
+
136
+ ```typescript
137
+ import type { PriscillaAIResponse } from '@priscilla/vue-ai-assistant'
138
+
139
+ // Use in your own code
140
+ const response: PriscillaAIResponse = await fetchHint()
141
+ ```
142
+
143
+ ## Styling
144
+
145
+ The component uses CSS classes that can be customized:
146
+
147
+ ```css
148
+ .priscilla-ai { /* Main container */ }
149
+ .assistant-button { /* Toggle button */ }
150
+ .assistant-window { /* Modal window */ }
151
+ .assistant-header { /* Header */ }
152
+ .assistant-body { /* Content area */ }
153
+ .assistant-footer { /* Footer with buttons */ }
154
+ .code-block { /* Code display */ }
155
+ .hint-text { /* Hint text */ }
156
+ .error-message { /* Error messages */ }
157
+ .loading-spinner { /* Loading indicator */ }
158
+ .primary-button { /* "Get Hint" button */ }
159
+ .secondary-button { /* "Clear" button */ }
160
+ ```
161
+
162
+ ## Error Handling
163
+
164
+ The component handles various error scenarios gracefully:
165
+
166
+ - **Invalid XPath** — Shows "Invalid XPath expression" error
167
+ - **No matching element** — Shows "No element matched the provided XPath" error
168
+ - **Empty content** — Shows "Matched element contains no extractable text" error
169
+ - **API errors** — Displays API status and error message
170
+ - **Network cancellation** — Silently handles request cancellation on unmount
171
+
172
+ ## Browser Support
173
+
174
+ - Chrome/Edge 90+
175
+ - Firefox 88+
176
+ - Safari 14+
177
+ - Any browser supporting Vue 3 and XPath
178
+
179
+ ## Project Structure
180
+
181
+ ```
182
+ src/
183
+ ├── components/
184
+ │ └── PriscillaAI.vue # Main component
185
+ ├── plugins/
186
+ │ └── priscillaAI.ts # Plugin setup
187
+ ├── types/
188
+ │ └── priscillaAI.ts # TypeScript interfaces
189
+ ├── styles/
190
+ │ └── index.css # Component styles
191
+ ├── App.vue # Example usage
192
+ └── main.ts # Plugin initialization
193
+ ```
194
+
195
+ ## License
196
+
197
+ MIT © 2026 Dominik Takáč
198
+
199
+ ## Author
200
+
201
+ Dominik Takáč
202
+ Bachelor Thesis Project
Binary file
package/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ (function(r,t){typeof exports=="object"&&typeof module<"u"?t(exports,require("vue"),require("axios")):typeof define=="function"&&define.amd?define(["exports","vue","axios"],t):(r=typeof globalThis<"u"?globalThis:r||self,t(r.PriscillaAI={},r.Vue,r.axios))})(this,(function(r,t,s){"use strict";const p=t.defineComponent({name:"PriscillaAI",props:{xpath:{type:String,required:!0,validator:e=>e.trim().length>0},chapterId:{type:Number,required:!0,validator:e=>Number.isInteger(e)&&e>0},programId:{type:Number,required:!0,validator:e=>Number.isInteger(e)&&e>0}},data(){return{isOpen:!1,loading:!1,hint:"",error:"",extractedContent:"",cancelSource:null}},computed:{hasResult(){return!!(this.hint||this.extractedContent)},buttonLabel(){return this.loading?"Fetching…":"Get Hint"}},beforeUnmount(){this.cancelSource?.cancel("Component unmounted")},methods:{toggle(){this.isOpen=!this.isOpen},extractCodeFromXPath(){let e;try{e=document.evaluate(this.xpath,document,null,XPathResult.FIRST_ORDERED_NODE_TYPE,null).singleNodeValue}catch{throw new Error(`Invalid XPath expression: "${this.xpath}"`)}if(!e)throw new Error("No element matched the provided XPath.");const n=e instanceof HTMLTextAreaElement?e.value:e.textContent;if(!n?.trim())throw new Error("Matched element contains no extractable text.");return n.trim()},async fetchHint(){this.cancelSource?.cancel("Superseded by newer request"),this.loading=!0,this.error="",this.hint="",this.extractedContent="";try{const e=this.extractCodeFromXPath();this.extractedContent=e;const n=l();if(!n)throw new Error("API endpoint not configured. Please initialise the plugin.");this.cancelSource=s.CancelToken.source();const{data:o}=await s.post(n,{content:e,chapterId:this.chapterId,programId:this.programId},{cancelToken:this.cancelSource.token});this.hint=o.hint?.trim()||"No hint available for this submission."}catch(e){if(s.isCancel(e))return;e instanceof Error?this.error=e.message:s.isAxiosError(e)?this.error=`API ${e.response?.status??"Error"}: ${e.message}`:this.error="An unexpected error occurred."}finally{this.loading=!1}},clearHint(){this.hint="",this.extractedContent="",this.error="",this.cancelSource?.cancel("Cleared by user")}}}),h=(e,n)=>{const o=e.__vccOpts||e;for(const[c,d]of n)o[c]=d;return o},m={class:"priscilla-ai"},g=["aria-expanded","aria-label"],u={"aria-hidden":"true"},f={key:0,class:"assistant-window",role:"dialog","aria-label":"Priscilla AI – Code Hint Assistant"},E={class:"assistant-body"},y={class:"code-block"},C={class:"hint-text"},b={key:2,class:"error-message",role:"alert"},k={key:3,class:"loading-spinner","aria-live":"polite"},N={class:"metadata"},I={class:"assistant-footer"},P=["disabled"];function V(e,n,o,c,d,B){return t.openBlock(),t.createElementBlock("div",m,[t.createElementVNode("button",{class:"assistant-button","aria-expanded":e.isOpen,"aria-label":e.isOpen?"Close hint assistant":"Open hint assistant",onClick:n[0]||(n[0]=(...i)=>e.toggle&&e.toggle(...i))},[t.createElementVNode("span",u,t.toDisplayString(e.isOpen?"✖️":"🤖"),1)],8,g),e.isOpen?(t.openBlock(),t.createElementBlock("div",f,[n[5]||(n[5]=t.createElementVNode("header",{class:"assistant-header"},"Priscilla AI — Code Hint Assistant",-1)),t.createElementVNode("div",E,[e.extractedContent?(t.openBlock(),t.createElementBlock(t.Fragment,{key:0},[n[3]||(n[3]=t.createElementVNode("p",null,[t.createElementVNode("strong",null,"Extracted Code:")],-1)),t.createElementVNode("pre",y,t.toDisplayString(e.extractedContent),1)],64)):t.createCommentVNode("",!0),e.hint?(t.openBlock(),t.createElementBlock(t.Fragment,{key:1},[n[4]||(n[4]=t.createElementVNode("p",null,[t.createElementVNode("strong",null,"💡 Hint:")],-1)),t.createElementVNode("p",C,t.toDisplayString(e.hint),1)],64)):t.createCommentVNode("",!0),e.error?(t.openBlock(),t.createElementBlock("p",b,"❌ "+t.toDisplayString(e.error),1)):t.createCommentVNode("",!0),e.loading?(t.openBlock(),t.createElementBlock("p",k,"⏳ Fetching hint…")):t.createCommentVNode("",!0),t.createElementVNode("p",N,[t.createElementVNode("small",null,"Chapter "+t.toDisplayString(e.chapterId)+" · Program "+t.toDisplayString(e.programId),1)])]),t.createElementVNode("footer",I,[t.createElementVNode("button",{class:"primary-button",disabled:e.loading,onClick:n[1]||(n[1]=(...i)=>e.fetchHint&&e.fetchHint(...i))},t.toDisplayString(e.buttonLabel),9,P),e.hasResult?(t.openBlock(),t.createElementBlock("button",{key:0,class:"secondary-button",onClick:n[2]||(n[2]=(...i)=>e.clearHint&&e.clearHint(...i))},"Clear")):t.createCommentVNode("",!0)])])):t.createCommentVNode("",!0)])}const A=h(p,[["render",V]]);let a="";function S(e){return{install(n){a=e,n.config.globalProperties.$priscillaAIEndpoint=a,n.component("PriscillaAI",A)}}}function l(){return a}r.createPriscillaAI=S,r.getPriscillaAIEndpoint=l,Object.defineProperty(r,Symbol.toStringTag,{value:"Module"})}));
package/dist/index.js ADDED
@@ -0,0 +1,177 @@
1
+ import { defineComponent as m, createElementBlock as r, openBlock as s, createElementVNode as n, createCommentVNode as a, toDisplayString as i, Fragment as p } from "vue";
2
+ import d from "axios";
3
+ const g = m({
4
+ name: "PriscillaAI",
5
+ props: {
6
+ xpath: {
7
+ type: String,
8
+ required: !0,
9
+ validator: (t) => t.trim().length > 0
10
+ },
11
+ chapterId: {
12
+ type: Number,
13
+ required: !0,
14
+ validator: (t) => Number.isInteger(t) && t > 0
15
+ },
16
+ programId: {
17
+ type: Number,
18
+ required: !0,
19
+ validator: (t) => Number.isInteger(t) && t > 0
20
+ }
21
+ },
22
+ data() {
23
+ return {
24
+ isOpen: !1,
25
+ loading: !1,
26
+ hint: "",
27
+ error: "",
28
+ extractedContent: "",
29
+ cancelSource: null
30
+ };
31
+ },
32
+ computed: {
33
+ hasResult() {
34
+ return !!(this.hint || this.extractedContent);
35
+ },
36
+ buttonLabel() {
37
+ return this.loading ? "Fetching…" : "Get Hint";
38
+ }
39
+ },
40
+ beforeUnmount() {
41
+ this.cancelSource?.cancel("Component unmounted");
42
+ },
43
+ methods: {
44
+ toggle() {
45
+ this.isOpen = !this.isOpen;
46
+ },
47
+ extractCodeFromXPath() {
48
+ let t;
49
+ try {
50
+ t = document.evaluate(
51
+ this.xpath,
52
+ document,
53
+ null,
54
+ XPathResult.FIRST_ORDERED_NODE_TYPE,
55
+ null
56
+ ).singleNodeValue;
57
+ } catch {
58
+ throw new Error(`Invalid XPath expression: "${this.xpath}"`);
59
+ }
60
+ if (!t)
61
+ throw new Error("No element matched the provided XPath.");
62
+ const e = t instanceof HTMLTextAreaElement ? t.value : t.textContent;
63
+ if (!e?.trim())
64
+ throw new Error("Matched element contains no extractable text.");
65
+ return e.trim();
66
+ },
67
+ async fetchHint() {
68
+ this.cancelSource?.cancel("Superseded by newer request"), this.loading = !0, this.error = "", this.hint = "", this.extractedContent = "";
69
+ try {
70
+ const t = this.extractCodeFromXPath();
71
+ this.extractedContent = t;
72
+ const e = $();
73
+ if (!e)
74
+ throw new Error("API endpoint not configured. Please initialise the plugin.");
75
+ this.cancelSource = d.CancelToken.source();
76
+ const { data: o } = await d.post(
77
+ e,
78
+ {
79
+ content: t,
80
+ chapterId: this.chapterId,
81
+ programId: this.programId
82
+ },
83
+ { cancelToken: this.cancelSource.token }
84
+ );
85
+ this.hint = o.hint?.trim() || "No hint available for this submission.";
86
+ } catch (t) {
87
+ if (d.isCancel(t)) return;
88
+ t instanceof Error ? this.error = t.message : d.isAxiosError(t) ? this.error = `API ${t.response?.status ?? "Error"}: ${t.message}` : this.error = "An unexpected error occurred.";
89
+ } finally {
90
+ this.loading = !1;
91
+ }
92
+ },
93
+ clearHint() {
94
+ this.hint = "", this.extractedContent = "", this.error = "", this.cancelSource?.cancel("Cleared by user");
95
+ }
96
+ }
97
+ }), f = (t, e) => {
98
+ const o = t.__vccOpts || t;
99
+ for (const [h, u] of e)
100
+ o[h] = u;
101
+ return o;
102
+ }, b = { class: "priscilla-ai" }, C = ["aria-expanded", "aria-label"], I = { "aria-hidden": "true" }, y = {
103
+ key: 0,
104
+ class: "assistant-window",
105
+ role: "dialog",
106
+ "aria-label": "Priscilla AI – Code Hint Assistant"
107
+ }, E = { class: "assistant-body" }, P = { class: "code-block" }, k = { class: "hint-text" }, A = {
108
+ key: 2,
109
+ class: "error-message",
110
+ role: "alert"
111
+ }, v = {
112
+ key: 3,
113
+ class: "loading-spinner",
114
+ "aria-live": "polite"
115
+ }, w = { class: "metadata" }, H = { class: "assistant-footer" }, O = ["disabled"];
116
+ function N(t, e, o, h, u, F) {
117
+ return s(), r("div", b, [
118
+ n("button", {
119
+ class: "assistant-button",
120
+ "aria-expanded": t.isOpen,
121
+ "aria-label": t.isOpen ? "Close hint assistant" : "Open hint assistant",
122
+ onClick: e[0] || (e[0] = (...l) => t.toggle && t.toggle(...l))
123
+ }, [
124
+ n("span", I, i(t.isOpen ? "✖️" : "🤖"), 1)
125
+ ], 8, C),
126
+ t.isOpen ? (s(), r("div", y, [
127
+ e[5] || (e[5] = n("header", { class: "assistant-header" }, "Priscilla AI — Code Hint Assistant", -1)),
128
+ n("div", E, [
129
+ t.extractedContent ? (s(), r(p, { key: 0 }, [
130
+ e[3] || (e[3] = n("p", null, [
131
+ n("strong", null, "Extracted Code:")
132
+ ], -1)),
133
+ n("pre", P, i(t.extractedContent), 1)
134
+ ], 64)) : a("", !0),
135
+ t.hint ? (s(), r(p, { key: 1 }, [
136
+ e[4] || (e[4] = n("p", null, [
137
+ n("strong", null, "💡 Hint:")
138
+ ], -1)),
139
+ n("p", k, i(t.hint), 1)
140
+ ], 64)) : a("", !0),
141
+ t.error ? (s(), r("p", A, "❌ " + i(t.error), 1)) : a("", !0),
142
+ t.loading ? (s(), r("p", v, "⏳ Fetching hint…")) : a("", !0),
143
+ n("p", w, [
144
+ n("small", null, "Chapter " + i(t.chapterId) + " · Program " + i(t.programId), 1)
145
+ ])
146
+ ]),
147
+ n("footer", H, [
148
+ n("button", {
149
+ class: "primary-button",
150
+ disabled: t.loading,
151
+ onClick: e[1] || (e[1] = (...l) => t.fetchHint && t.fetchHint(...l))
152
+ }, i(t.buttonLabel), 9, O),
153
+ t.hasResult ? (s(), r("button", {
154
+ key: 0,
155
+ class: "secondary-button",
156
+ onClick: e[2] || (e[2] = (...l) => t.clearHint && t.clearHint(...l))
157
+ }, "Clear")) : a("", !0)
158
+ ])
159
+ ])) : a("", !0)
160
+ ]);
161
+ }
162
+ const S = /* @__PURE__ */ f(g, [["render", N]]);
163
+ let c = "";
164
+ function x(t) {
165
+ return {
166
+ install(e) {
167
+ c = t, e.config.globalProperties.$priscillaAIEndpoint = c, e.component("PriscillaAI", S);
168
+ }
169
+ };
170
+ }
171
+ function $() {
172
+ return c;
173
+ }
174
+ export {
175
+ x as createPriscillaAI,
176
+ $ as getPriscillaAIEndpoint
177
+ };
package/dist/vue.css ADDED
@@ -0,0 +1 @@
1
+ @font-face{font-family:Roboto;font-style:normal;font-weight:100;font-stretch:100%;src:url(https://fonts.gstatic.com/s/roboto/v51/KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3GUBGEe.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}.app-container{display:flex;flex-direction:column;height:100vh}.navbar{display:flex;align-items:center;justify-content:center;background:#fff;border-bottom:1px solid #ddd;padding:10px 0;gap:20px}.nav-items{display:flex;gap:16px}.nav-item{background:#e5e5e5;width:40px;height:40px;border-radius:6px;display:flex;align-items:center;justify-content:center;cursor:pointer;position:relative}.main-content{flex:1;display:flex;flex-direction:column;justify-content:center;align-items:center;padding:40px}.code-section{background:#f5f5f5;padding:20px;border-radius:8px;max-width:600px;margin-top:30px}.code-section pre{background:#2d2d2d;color:#f8f8f2;padding:15px;border-radius:6px;overflow-x:auto;font-family:Courier New,monospace;font-size:14px;line-height:1.5}.priscilla-ai{position:relative;font-family:Roboto,sans-serif}.assistant-button{background:#e5e5e5;border:none;width:40px;height:40px;border-radius:6px;font-size:18px;cursor:pointer;transition:background-color .2s}.assistant-button:hover{background:#d0d0d0}.assistant-button:disabled{opacity:.6;cursor:not-allowed}.assistant-window{position:absolute;top:50px;right:0;width:350px;height:auto;max-height:500px;background:#f8f8f8;box-shadow:0 4px 12px #00000026;border-radius:8px;display:flex;flex-direction:column;z-index:1000}.assistant-header{background:#464767;color:#fff;padding:12px;font-weight:700;text-align:center;border-radius:8px 8px 0 0}.assistant-body{flex:1;padding:12px;overflow-y:auto;max-height:350px}.extracted-content{margin-bottom:12px}.code-block{background:#2d2d2d;color:#f8f8f2;padding:8px;border-radius:4px;font-family:Courier New,monospace;font-size:12px;overflow-x:auto;margin:8px 0;max-height:150px;overflow-y:auto}.hint-section{background:#e8f5e9;padding:10px;border-left:3px solid #4caf50;border-radius:4px;margin:10px 0}.hint-text{color:#2e7d32;margin:5px 0;font-size:14px}.error-message{background:#ffebee;color:#c62828;padding:10px;border-radius:4px;margin:10px 0;border-left:3px solid #f44336}.loading-spinner{text-align:center;padding:10px;color:#666}.metadata{font-size:12px;color:#999;margin-top:10px;padding-top:10px;border-top:1px solid #ddd}.assistant-footer{border-top:1px solid #ccc;padding:8px;display:flex;gap:8px;background:#fafafa;border-radius:0 0 8px 8px}.primary-button{flex:1;padding:8px 12px;border:none;background:#464767;color:#fff;border-radius:4px;cursor:pointer;white-space:nowrap;font-weight:500;transition:background-color .2s}.primary-button:hover:not(:disabled){background:#323549}.primary-button:disabled{opacity:.6;cursor:not-allowed}.secondary-button{padding:8px 12px;border:1px solid #ddd;background:#fff;color:#464767;border-radius:4px;cursor:pointer;white-space:nowrap;font-weight:500;transition:all .2s}.secondary-button:hover{background:#f5f5f5;border-color:#464767}.code-assistant{position:relative}.source-block{background:#efefef;padding:6px;margin-top:10px;font-family:monospace;font-size:12px}.mock-response{background:#e8f4f8;padding:8px;border-radius:4px;color:#333}.code-editor{width:100%;min-height:200px;font-family:monospace;font-size:14px;padding:12px;border-radius:6px;border:1px solid #ccc;background:#1e1e1e;color:#eaeaea;resize:vertical}
package/env.d.ts ADDED
@@ -0,0 +1 @@
1
+ /// <reference types="vite/client" />
@@ -0,0 +1,22 @@
1
+ import { globalIgnores } from 'eslint/config'
2
+ import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript'
3
+ import pluginVue from 'eslint-plugin-vue'
4
+ import skipFormatting from '@vue/eslint-config-prettier/skip-formatting'
5
+
6
+ // To allow more languages other than `ts` in `.vue` files, uncomment the following lines:
7
+ // import { configureVueProject } from '@vue/eslint-config-typescript'
8
+ // configureVueProject({ scriptLangs: ['ts', 'tsx'] })
9
+ // More info at https://github.com/vuejs/eslint-config-typescript/#advanced-setup
10
+
11
+ export default defineConfigWithVueTs(
12
+ {
13
+ name: 'app/files-to-lint',
14
+ files: ['**/*.{ts,mts,tsx,vue}'],
15
+ },
16
+
17
+ globalIgnores(['**/dist/**', '**/dist-ssr/**', '**/coverage/**']),
18
+
19
+ pluginVue.configs['flat/essential'],
20
+ vueTsConfigs.recommended,
21
+ skipFormatting,
22
+ )
package/index.html ADDED
@@ -0,0 +1,13 @@
1
+ <!DOCTYPE html>
2
+ <html lang="">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <link rel="icon" href="/favicon.ico">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Vite App</title>
8
+ </head>
9
+ <body>
10
+ <div id="app"></div>
11
+ <script type="module" src="/src/main.ts"></script>
12
+ </body>
13
+ </html>
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@priscilla-ai/vue",
3
+ "version": "1.0.0",
4
+ "description": "Vue 3 Plugin in Options Api for Priscilla learning portal",
5
+ "private": false,
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "sideEffects": false,
9
+ "keywords": ["vue", "vue3", "plugin", "priscilla", "ai", "learning", "portal", "hints", "code-assistant"],
10
+ "main": "dist/index.js",
11
+ "module": "dist/index.js",
12
+ "types": "dist/index.d.ts",
13
+ "exports": {
14
+ ".": {
15
+ "require": "./dist/index.js",
16
+ "types": "./dist/index.d.ts",
17
+ "import": "./dist/index.js"
18
+ }
19
+ },
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "https://github.com/imtaki/VueJs-Bachelor-Thesis"
23
+ },
24
+ "engines": {
25
+ "node": "^20.19.0 || >=22.12.0"
26
+ },
27
+ "scripts": {
28
+ "dev": "vite",
29
+ "build": "run-p type-check \"build-only {@}\" --",
30
+ "preview": "vite preview",
31
+ "build-only": "vite build",
32
+ "type-check": "vue-tsc --build",
33
+ "lint": "eslint . --fix --cache",
34
+ "format": "prettier --write --experimental-cli src/"
35
+ },
36
+ "dependencies": {
37
+ "axios": "^1.13.2",
38
+ "pinia": "^3.0.4",
39
+ "vue": "^3.5.25"
40
+ },
41
+ "devDependencies": {
42
+ "@tsconfig/node24": "^24.0.3",
43
+ "@types/node": "^24.10.1",
44
+ "@vitejs/plugin-vue": "^6.0.2",
45
+ "@vue/eslint-config-prettier": "^10.2.0",
46
+ "@vue/eslint-config-typescript": "^14.6.0",
47
+ "@vue/tsconfig": "^0.8.1",
48
+ "eslint": "^9.39.1",
49
+ "eslint-plugin-vue": "~10.5.1",
50
+ "jiti": "^2.6.1",
51
+ "npm-run-all2": "^8.0.4",
52
+ "prettier": "3.6.2",
53
+ "typescript": "~5.9.0",
54
+ "vite": "^7.2.4",
55
+ "vite-plugin-vue-devtools": "^8.0.5",
56
+ "vue-tsc": "^3.1.5"
57
+ }
58
+ }
Binary file
package/src/App.vue ADDED
@@ -0,0 +1,44 @@
1
+ <script lang="ts">
2
+ import { defineComponent } from 'vue'
3
+ import './styles/index.css'
4
+
5
+ export default defineComponent({
6
+ name: 'App',
7
+ data() {
8
+ return {
9
+ sourceCode: `function fibonacci(n) {
10
+ if (n <= 1) return n
11
+ return fibonacci(n - 1) + fibonacci(n - 2)
12
+ }`,
13
+ }
14
+ },
15
+ })
16
+ </script>
17
+
18
+ <template>
19
+ <div class="app-container">
20
+ <nav class="navbar">
21
+ <div class="nav-items">
22
+ <div class="nav-item">
23
+ <PriscillaAI xpath="//textarea[@id='code-block']" :chapterId="12" :programId="589" />
24
+ </div>
25
+ </div>
26
+ </nav>
27
+
28
+ <main class="main-content">
29
+ <h1>Welcome to Priscilla LLM</h1>
30
+ <p>This is a mock navbar + AI assistant example with code hint capability.</p>
31
+
32
+ <section class="code-section">
33
+ <h2>Code Example</h2>
34
+ <textarea
35
+ id="code-block"
36
+ v-model="sourceCode"
37
+ class="code-editor"
38
+ spellcheck="false"
39
+ ></textarea>
40
+ <p><small>Click the 🤖 icon in the navbar to get a hint about this code!</small></p>
41
+ </section>
42
+ </main>
43
+ </div>
44
+ </template>
@@ -0,0 +1,192 @@
1
+ <script lang="ts">
2
+ import { defineComponent } from 'vue'
3
+ import axios, { type CancelTokenSource } from 'axios'
4
+ import { getPriscillaAIEndpoint } from '@/plugins/priscillaAI'
5
+ import '../styles/index.css'
6
+ import type { PriscillaAIResponse } from '@/types/priscillaAI'
7
+
8
+ export default defineComponent({
9
+ name: 'PriscillaAI',
10
+
11
+ props: {
12
+ xpath: {
13
+ type: String,
14
+ required: true,
15
+ validator: (value: string) => value.trim().length > 0,
16
+ },
17
+ chapterId: {
18
+ type: Number,
19
+ required: true,
20
+ validator: (value: number) => Number.isInteger(value) && value > 0,
21
+ },
22
+ programId: {
23
+ type: Number,
24
+ required: true,
25
+ validator: (value: number) => Number.isInteger(value) && value > 0,
26
+ },
27
+ },
28
+
29
+ data() {
30
+ return {
31
+ isOpen: false,
32
+ loading: false,
33
+ hint: '',
34
+ error: '',
35
+ extractedContent: '',
36
+ cancelSource: null as CancelTokenSource | null,
37
+ }
38
+ },
39
+
40
+ computed: {
41
+ hasResult(): boolean {
42
+ return !!(this.hint || this.extractedContent)
43
+ },
44
+ buttonLabel(): string {
45
+ return this.loading ? 'Fetching…' : 'Get Hint'
46
+ },
47
+ },
48
+
49
+ beforeUnmount() {
50
+ this.cancelSource?.cancel('Component unmounted')
51
+ },
52
+
53
+ methods: {
54
+ toggle() {
55
+ this.isOpen = !this.isOpen
56
+ },
57
+
58
+ extractCodeFromXPath(): string {
59
+ let node: Node | null
60
+
61
+ try {
62
+ const result = document.evaluate(
63
+ this.xpath,
64
+ document,
65
+ null,
66
+ XPathResult.FIRST_ORDERED_NODE_TYPE,
67
+ null,
68
+ )
69
+ node = result.singleNodeValue
70
+ } catch {
71
+ throw new Error(`Invalid XPath expression: "${this.xpath}"`)
72
+ }
73
+
74
+ if (!node) {
75
+ throw new Error('No element matched the provided XPath.')
76
+ }
77
+
78
+ const content =
79
+ node instanceof HTMLTextAreaElement
80
+ ? node.value
81
+ : (node as HTMLElement).textContent
82
+
83
+ if (!content?.trim()) {
84
+ throw new Error('Matched element contains no extractable text.')
85
+ }
86
+
87
+ return content.trim()
88
+ },
89
+
90
+ async fetchHint() {
91
+ this.cancelSource?.cancel('Superseded by newer request')
92
+
93
+ this.loading = true
94
+ this.error = ''
95
+ this.hint = ''
96
+ this.extractedContent = ''
97
+
98
+ try {
99
+ const content = this.extractCodeFromXPath()
100
+ this.extractedContent = content
101
+
102
+ const endpoint = getPriscillaAIEndpoint()
103
+ if (!endpoint) {
104
+ throw new Error('API endpoint not configured. Please initialise the plugin.')
105
+ }
106
+
107
+ this.cancelSource = axios.CancelToken.source()
108
+
109
+ const { data } = await axios.post<PriscillaAIResponse>(
110
+ endpoint,
111
+ {
112
+ content,
113
+ chapterId: this.chapterId,
114
+ programId: this.programId,
115
+ },
116
+ { cancelToken: this.cancelSource.token },
117
+ )
118
+
119
+ this.hint = data.hint?.trim() || 'No hint available for this submission.'
120
+ } catch (err) {
121
+ if (axios.isCancel(err)) return
122
+
123
+ if (err instanceof Error) {
124
+ this.error = err.message
125
+ } else if (axios.isAxiosError(err)) {
126
+ this.error = `API ${err.response?.status ?? 'Error'}: ${err.message}`
127
+ } else {
128
+ this.error = 'An unexpected error occurred.'
129
+ }
130
+ } finally {
131
+ this.loading = false
132
+ }
133
+ },
134
+
135
+ clearHint() {
136
+ this.hint = ''
137
+ this.extractedContent = ''
138
+ this.error = ''
139
+ this.cancelSource?.cancel('Cleared by user')
140
+ },
141
+ },
142
+ })
143
+ </script>
144
+
145
+ <template>
146
+ <div class="priscilla-ai">
147
+ <button
148
+ class="assistant-button"
149
+ :aria-expanded="isOpen"
150
+ :aria-label="isOpen ? 'Close hint assistant' : 'Open hint assistant'"
151
+ @click="toggle"
152
+ >
153
+ <span aria-hidden="true">{{ isOpen ? '✖️' : '🤖' }}</span>
154
+ </button>
155
+
156
+ <div
157
+ v-if="isOpen"
158
+ class="assistant-window"
159
+ role="dialog"
160
+ aria-label="Priscilla AI – Code Hint Assistant"
161
+ >
162
+ <header class="assistant-header">Priscilla AI — Code Hint Assistant</header>
163
+
164
+ <div class="assistant-body">
165
+ <template v-if="extractedContent">
166
+ <p><strong>Extracted Code:</strong></p>
167
+ <pre class="code-block">{{ extractedContent }}</pre>
168
+ </template>
169
+
170
+ <template v-if="hint">
171
+ <p><strong>💡 Hint:</strong></p>
172
+ <p class="hint-text">{{ hint }}</p>
173
+ </template>
174
+
175
+ <p v-if="error" class="error-message" role="alert">❌ {{ error }}</p>
176
+
177
+ <p v-if="loading" class="loading-spinner" aria-live="polite">⏳ Fetching hint…</p>
178
+
179
+ <p class="metadata">
180
+ <small>Chapter {{ chapterId }} · Program {{ programId }}</small>
181
+ </p>
182
+ </div>
183
+
184
+ <footer class="assistant-footer">
185
+ <button class="primary-button" :disabled="loading" @click="fetchHint">
186
+ {{ buttonLabel }}
187
+ </button>
188
+ <button v-if="hasResult" class="secondary-button" @click="clearHint">Clear</button>
189
+ </footer>
190
+ </div>
191
+ </div>
192
+ </template>
package/src/main.ts ADDED
@@ -0,0 +1,11 @@
1
+ import { createApp } from 'vue'
2
+ import { createPinia } from 'pinia'
3
+ import App from './App.vue'
4
+ import { createPriscillaAI } from './plugins/priscillaAI'
5
+
6
+ const app = createApp(App)
7
+
8
+ app.use(createPriscillaAI('https://api.example.com/api/predict/hint'))
9
+ app.use(createPinia())
10
+
11
+ app.mount('#app')
@@ -0,0 +1,21 @@
1
+ import type { App } from 'vue'
2
+ import PriscillaAI from '@/components/PriscillaAI.vue'
3
+
4
+ let apiEndpoint: string = ''
5
+
6
+ export function createPriscillaAI(endpoint: string) {
7
+ return {
8
+ install(app: App) {
9
+ apiEndpoint = endpoint
10
+
11
+ app.config.globalProperties.$priscillaAIEndpoint = apiEndpoint
12
+
13
+ app.component('PriscillaAI', PriscillaAI)
14
+ },
15
+ }
16
+ }
17
+
18
+ export function getPriscillaAIEndpoint(): string {
19
+ return apiEndpoint
20
+ }
21
+
@@ -0,0 +1,261 @@
1
+ @font-face {
2
+ font-family: 'Roboto';
3
+ font-style: normal;
4
+ font-weight: 100;
5
+ font-stretch: 100%;
6
+ src: url(https://fonts.gstatic.com/s/roboto/v51/KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3GUBGEe.woff2)
7
+ format('woff2');
8
+ unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
9
+ }
10
+
11
+ .app-container {
12
+ display: flex;
13
+ flex-direction: column;
14
+ height: 100vh;
15
+ }
16
+
17
+ .navbar {
18
+ display: flex;
19
+ align-items: center;
20
+ justify-content: center;
21
+ background: #ffffff;
22
+ border-bottom: 1px solid #ddd;
23
+ padding: 10px 0;
24
+ gap: 20px;
25
+ }
26
+
27
+ .nav-items {
28
+ display: flex;
29
+ gap: 16px;
30
+ }
31
+
32
+ .nav-item {
33
+ background: #e5e5e5;
34
+ width: 40px;
35
+ height: 40px;
36
+ border-radius: 6px;
37
+ display: flex;
38
+ align-items: center;
39
+ justify-content: center;
40
+ cursor: pointer;
41
+ position: relative;
42
+ }
43
+
44
+ .main-content {
45
+ flex: 1;
46
+ display: flex;
47
+ flex-direction: column;
48
+ justify-content: center;
49
+ align-items: center;
50
+ padding: 40px;
51
+ }
52
+
53
+ .code-section {
54
+ background: #f5f5f5;
55
+ padding: 20px;
56
+ border-radius: 8px;
57
+ max-width: 600px;
58
+ margin-top: 30px;
59
+ }
60
+
61
+ .code-section pre {
62
+ background: #2d2d2d;
63
+ color: #f8f8f2;
64
+ padding: 15px;
65
+ border-radius: 6px;
66
+ overflow-x: auto;
67
+ font-family: 'Courier New', monospace;
68
+ font-size: 14px;
69
+ line-height: 1.5;
70
+ }
71
+
72
+ /* PriscillaAI Component Styles */
73
+ .priscilla-ai {
74
+ position: relative;
75
+ font-family: 'Roboto', sans-serif;
76
+ }
77
+
78
+ .assistant-button {
79
+ background: #e5e5e5;
80
+ border: none;
81
+ width: 40px;
82
+ height: 40px;
83
+ border-radius: 6px;
84
+ font-size: 18px;
85
+ cursor: pointer;
86
+ transition: background-color 0.2s;
87
+ }
88
+
89
+ .assistant-button:hover {
90
+ background: #d0d0d0;
91
+ }
92
+
93
+ .assistant-button:disabled {
94
+ opacity: 0.6;
95
+ cursor: not-allowed;
96
+ }
97
+
98
+ .assistant-window {
99
+ position: absolute;
100
+ top: 50px;
101
+ right: 0;
102
+ width: 350px;
103
+ height: auto;
104
+ max-height: 500px;
105
+ background: #f8f8f8;
106
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
107
+ border-radius: 8px;
108
+ display: flex;
109
+ flex-direction: column;
110
+ z-index: 1000;
111
+ }
112
+
113
+ .assistant-header {
114
+ background: #464767;
115
+ color: white;
116
+ padding: 12px;
117
+ font-weight: bold;
118
+ text-align: center;
119
+ border-radius: 8px 8px 0 0;
120
+ }
121
+
122
+ .assistant-body {
123
+ flex: 1;
124
+ padding: 12px;
125
+ overflow-y: auto;
126
+ max-height: 350px;
127
+ }
128
+
129
+ .extracted-content {
130
+ margin-bottom: 12px;
131
+ }
132
+
133
+ .code-block {
134
+ background: #2d2d2d;
135
+ color: #f8f8f2;
136
+ padding: 8px;
137
+ border-radius: 4px;
138
+ font-family: 'Courier New', monospace;
139
+ font-size: 12px;
140
+ overflow-x: auto;
141
+ margin: 8px 0;
142
+ max-height: 150px;
143
+ overflow-y: auto;
144
+ }
145
+
146
+ .hint-section {
147
+ background: #e8f5e9;
148
+ padding: 10px;
149
+ border-left: 3px solid #4caf50;
150
+ border-radius: 4px;
151
+ margin: 10px 0;
152
+ }
153
+
154
+ .hint-text {
155
+ color: #2e7d32;
156
+ margin: 5px 0;
157
+ font-size: 14px;
158
+ }
159
+
160
+ .error-message {
161
+ background: #ffebee;
162
+ color: #c62828;
163
+ padding: 10px;
164
+ border-radius: 4px;
165
+ margin: 10px 0;
166
+ border-left: 3px solid #f44336;
167
+ }
168
+
169
+ .loading-spinner {
170
+ text-align: center;
171
+ padding: 10px;
172
+ color: #666;
173
+ }
174
+
175
+ .metadata {
176
+ font-size: 12px;
177
+ color: #999;
178
+ margin-top: 10px;
179
+ padding-top: 10px;
180
+ border-top: 1px solid #ddd;
181
+ }
182
+
183
+ .assistant-footer {
184
+ border-top: 1px solid #ccc;
185
+ padding: 8px;
186
+ display: flex;
187
+ gap: 8px;
188
+ background: #fafafa;
189
+ border-radius: 0 0 8px 8px;
190
+ }
191
+
192
+ .primary-button {
193
+ flex: 1;
194
+ padding: 8px 12px;
195
+ border: none;
196
+ background: #464767;
197
+ color: white;
198
+ border-radius: 4px;
199
+ cursor: pointer;
200
+ white-space: nowrap;
201
+ font-weight: 500;
202
+ transition: background-color 0.2s;
203
+ }
204
+
205
+ .primary-button:hover:not(:disabled) {
206
+ background: #323549;
207
+ }
208
+
209
+ .primary-button:disabled {
210
+ opacity: 0.6;
211
+ cursor: not-allowed;
212
+ }
213
+
214
+ .secondary-button {
215
+ padding: 8px 12px;
216
+ border: 1px solid #ddd;
217
+ background: white;
218
+ color: #464767;
219
+ border-radius: 4px;
220
+ cursor: pointer;
221
+ white-space: nowrap;
222
+ font-weight: 500;
223
+ transition: all 0.2s;
224
+ }
225
+
226
+ .secondary-button:hover {
227
+ background: #f5f5f5;
228
+ border-color: #464767;
229
+ }
230
+
231
+ .code-assistant {
232
+ position: relative;
233
+ }
234
+
235
+ .source-block {
236
+ background: #efefef;
237
+ padding: 6px;
238
+ margin-top: 10px;
239
+ font-family: monospace;
240
+ font-size: 12px;
241
+ }
242
+
243
+ .mock-response {
244
+ background: #e8f4f8;
245
+ padding: 8px;
246
+ border-radius: 4px;
247
+ color: #333;
248
+ }
249
+
250
+ .code-editor {
251
+ width: 100%;
252
+ min-height: 200px;
253
+ font-family: monospace;
254
+ font-size: 14px;
255
+ padding: 12px;
256
+ border-radius: 6px;
257
+ border: 1px solid #ccc;
258
+ background: #1e1e1e;
259
+ color: #eaeaea;
260
+ resize: vertical;
261
+ }
@@ -0,0 +1,4 @@
1
+ export interface PriscillaAIResponse {
2
+ hint?: string
3
+ }
4
+
@@ -0,0 +1,12 @@
1
+ {
2
+ "extends": "@vue/tsconfig/tsconfig.dom.json",
3
+ "include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
4
+ "exclude": ["src/**/__tests__/*"],
5
+ "compilerOptions": {
6
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
7
+
8
+ "paths": {
9
+ "@/*": ["./src/*"]
10
+ }
11
+ }
12
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "files": [],
3
+ "references": [
4
+ {
5
+ "path": "./tsconfig.node.json"
6
+ },
7
+ {
8
+ "path": "./tsconfig.app.json"
9
+ }
10
+ ]
11
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "extends": "@tsconfig/node24/tsconfig.json",
3
+ "include": [
4
+ "vite.config.*",
5
+ "vitest.config.*",
6
+ "cypress.config.*",
7
+ "nightwatch.conf.*",
8
+ "playwright.config.*",
9
+ "eslint.config.*"
10
+ ],
11
+ "compilerOptions": {
12
+ "noEmit": true,
13
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
14
+
15
+ "module": "ESNext",
16
+ "moduleResolution": "Bundler",
17
+ "types": ["node"]
18
+ }
19
+ }
package/vite.config.ts ADDED
@@ -0,0 +1,29 @@
1
+ import { defineConfig } from 'vite'
2
+ import { fileURLToPath, URL } from 'node:url'
3
+ import vue from '@vitejs/plugin-vue'
4
+
5
+ export default defineConfig({
6
+ plugins: [vue()],
7
+ resolve: {
8
+ alias: {
9
+ '@': fileURLToPath(new URL('./src', import.meta.url))
10
+ }
11
+ },
12
+ build: {
13
+ lib: {
14
+ entry: fileURLToPath(new URL('./src/plugins/priscillaAI.ts', import.meta.url)),
15
+ name: 'PriscillaAI',
16
+ formats: ['es', 'umd'],
17
+ fileName: (format) => `index.${format === 'es' ? 'js' : 'cjs'}`
18
+ },
19
+ rollupOptions: {
20
+ external: ['vue', 'axios'],
21
+ output: {
22
+ globals: {
23
+ vue: 'Vue',
24
+ axios: 'axios'
25
+ }
26
+ }
27
+ }
28
+ }
29
+ })