@gui-chat-plugin/camera 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,67 @@
1
+ # @gui-chat-plugin/camera
2
+
3
+ [![npm version](https://badge.fury.io/js/%40gui-chat-plugin%2Fcamera.svg)](https://www.npmjs.com/package/@gui-chat-plugin/camera)
4
+
5
+ A camera photo capture plugin for [MulmoChat](https://github.com/receptron/MulmoChat).
6
+
7
+ ## Overview
8
+
9
+ This plugin allows users to take photos using their device's camera. It opens a camera preview modal where users can select their camera (if multiple are available) and capture a photo.
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ yarn add @gui-chat-plugin/camera
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ ### Vue Implementation (for MulmoChat)
20
+
21
+ ```typescript
22
+ // In src/tools/index.ts
23
+ import CameraPlugin from "@gui-chat-plugin/camera/vue";
24
+
25
+ const pluginList = [
26
+ // ... other plugins
27
+ CameraPlugin,
28
+ ];
29
+
30
+ // In src/main.ts
31
+ import "@gui-chat-plugin/camera/style.css";
32
+ ```
33
+
34
+ ### Core Only (Framework-agnostic)
35
+
36
+ ```typescript
37
+ import { TOOL_NAME, TOOL_DEFINITION, SYSTEM_PROMPT } from "@gui-chat-plugin/camera";
38
+ ```
39
+
40
+ ## Package Exports
41
+
42
+ | Export | Description |
43
+ |--------|-------------|
44
+ | `@gui-chat-plugin/camera` | Core (framework-agnostic) |
45
+ | `@gui-chat-plugin/camera/vue` | Vue implementation |
46
+ | `@gui-chat-plugin/camera/style.css` | Tailwind CSS styles |
47
+
48
+ ## Test Prompts
49
+
50
+ 1. "Take a photo"
51
+ 2. "Open the camera"
52
+ 3. "I want to take a selfie"
53
+ 4. "Capture an image from my camera"
54
+
55
+ ## Development
56
+
57
+ ```bash
58
+ yarn install
59
+ yarn dev # Start dev server
60
+ yarn build # Build
61
+ yarn typecheck # Type check
62
+ yarn lint # Lint
63
+ ```
64
+
65
+ ## License
66
+
67
+ MIT
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Camera Plugin Tool Definition
3
+ */
4
+ export declare const TOOL_NAME = "takePhoto";
5
+ export declare const TOOL_DEFINITION: {
6
+ type: "function";
7
+ name: string;
8
+ description: string;
9
+ parameters: {
10
+ type: "object";
11
+ properties: {};
12
+ required: string[];
13
+ };
14
+ };
15
+ export declare const SYSTEM_PROMPT = "When the user asks to take a photo, use selfie, or capture an image from the camera, you MUST use the takePhoto API.";
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Camera Plugin - Core (Framework-agnostic)
3
+ */
4
+ export type { CameraArgs, ImageToolData } from "./types";
5
+ export { TOOL_NAME, TOOL_DEFINITION, SYSTEM_PROMPT } from "./definition";
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Camera Plugin Types
3
+ */
4
+ /** Camera takes no arguments - empty interface */
5
+ export type CameraArgs = Record<string, never>;
6
+ /** Image data returned by the camera */
7
+ export interface ImageToolData {
8
+ imageData: string;
9
+ prompt: string;
10
+ }
package/dist/core.cjs ADDED
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e="takePhoto",t={type:"function",name:e,description:"Take a photo using the device camera.",parameters:{type:"object",properties:{},required:[]}},o=`When the user asks to take a photo, use selfie, or capture an image from the camera, you MUST use the ${e} API.`;exports.SYSTEM_PROMPT=o;exports.TOOL_DEFINITION=t;exports.TOOL_NAME=e;
package/dist/core.js ADDED
@@ -0,0 +1,15 @@
1
+ const e = "takePhoto", t = {
2
+ type: "function",
3
+ name: e,
4
+ description: "Take a photo using the device camera.",
5
+ parameters: {
6
+ type: "object",
7
+ properties: {},
8
+ required: []
9
+ }
10
+ }, o = `When the user asks to take a photo, use selfie, or capture an image from the camera, you MUST use the ${e} API.`;
11
+ export {
12
+ o as SYSTEM_PROMPT,
13
+ t as TOOL_DEFINITION,
14
+ e as TOOL_NAME
15
+ };
package/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const O=require("./core.cjs");exports.SYSTEM_PROMPT=O.SYSTEM_PROMPT;exports.TOOL_DEFINITION=O.TOOL_DEFINITION;exports.TOOL_NAME=O.TOOL_NAME;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Camera Plugin
3
+ *
4
+ * Main entry point - re-exports core module.
5
+ */
6
+ export * from "./core";
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ import { SYSTEM_PROMPT as E, TOOL_DEFINITION as I, TOOL_NAME as M } from "./core.js";
2
+ export {
3
+ E as SYSTEM_PROMPT,
4
+ I as TOOL_DEFINITION,
5
+ M as TOOL_NAME
6
+ };
package/dist/style.css ADDED
@@ -0,0 +1 @@
1
+ @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-y-reverse:0;--tw-border-style:solid;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-blue-500:oklch(62.3% .214 259.815);--color-blue-600:oklch(54.6% .245 262.881);--color-blue-700:oklch(48.8% .243 264.376);--color-gray-100:oklch(96.7% .003 264.542);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-gray-800:oklch(27.8% .033 256.848);--color-gray-900:oklch(21% .034 264.665);--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-md:28rem;--container-2xl:42rem;--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--text-5xl:3rem;--text-5xl--line-height:1;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--radius-lg:.5rem;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.inset-0{inset:calc(var(--spacing)*0)}.z-50{z-index:50}.container{width:100%}@media(min-width:40rem){.container{max-width:40rem}}@media(min-width:48rem){.container{max-width:48rem}}@media(min-width:64rem){.container{max-width:64rem}}@media(min-width:80rem){.container{max-width:80rem}}@media(min-width:96rem){.container{max-width:96rem}}.mx-4{margin-inline:calc(var(--spacing)*4)}.mt-4{margin-top:calc(var(--spacing)*4)}.mb-2{margin-bottom:calc(var(--spacing)*2)}.mb-4{margin-bottom:calc(var(--spacing)*4)}.mb-8{margin-bottom:calc(var(--spacing)*8)}.block{display:block}.contents{display:contents}.flex{display:flex}.h-auto{height:auto}.h-full{height:100%}.max-h-full{max-height:100%}.min-h-0{min-height:calc(var(--spacing)*0)}.min-h-\[100px\]{min-height:100px}.min-h-full{min-height:100%}.min-h-screen{min-height:100vh}.w-full{width:100%}.max-w-2xl{max-width:var(--container-2xl)}.max-w-full{max-width:100%}.max-w-md{max-width:var(--container-md)}.flex-1{flex:1}.flex-shrink-0{flex-shrink:0}.flex-col{flex-direction:column}.items-center{align-items:center}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.gap-2{gap:calc(var(--spacing)*2)}.gap-4{gap:calc(var(--spacing)*4)}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*4)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*4)*calc(1 - var(--tw-space-y-reverse)))}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-lg{border-radius:var(--radius-lg)}.border{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-gray-200{border-color:var(--color-gray-200)}.border-gray-300{border-color:var(--color-gray-300)}.bg-black{background-color:var(--color-black)}.bg-blue-600{background-color:var(--color-blue-600)}.bg-gray-100{background-color:var(--color-gray-100)}.bg-gray-200{background-color:var(--color-gray-200)}.bg-white{background-color:var(--color-white)}.object-contain{object-fit:contain}.p-3{padding:calc(var(--spacing)*3)}.p-4{padding:calc(var(--spacing)*4)}.p-8{padding:calc(var(--spacing)*8)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.px-6{padding-inline:calc(var(--spacing)*6)}.py-2{padding-block:calc(var(--spacing)*2)}.pt-4{padding-top:calc(var(--spacing)*4)}.pb-2{padding-bottom:calc(var(--spacing)*2)}.text-center{text-align:center}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-5xl{font-size:var(--text-5xl);line-height:var(--tw-leading,var(--text-5xl--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.text-gray-400{color:var(--color-gray-400)}.text-gray-700{color:var(--color-gray-700)}.text-gray-900{color:var(--color-gray-900)}.text-white{color:var(--color-white)}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a),0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}@media(hover:hover){.hover\:bg-blue-700:hover{background-color:var(--color-blue-700)}.hover\:bg-gray-300:hover{background-color:var(--color-gray-300)}.hover\:text-gray-600:hover{color:var(--color-gray-600)}}.focus\:border-transparent:focus{border-color:#0000}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-blue-500:focus{--tw-ring-color:var(--color-blue-500)}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:bg-gray-400:disabled{background-color:var(--color-gray-400)}@media(prefers-color-scheme:dark){.dark\:border-gray-600{border-color:var(--color-gray-600)}.dark\:border-gray-700{border-color:var(--color-gray-700)}.dark\:bg-gray-700{background-color:var(--color-gray-700)}.dark\:bg-gray-800{background-color:var(--color-gray-800)}.dark\:bg-gray-900{background-color:var(--color-gray-900)}.dark\:text-gray-300{color:var(--color-gray-300)}.dark\:text-white{color:var(--color-white)}@media(hover:hover){.dark\:hover\:bg-gray-600:hover{background-color:var(--color-gray-600)}.dark\:hover\:text-gray-300:hover{color:var(--color-gray-300)}}}}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}
@@ -0,0 +1,9 @@
1
+ declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
2
+ capture: (imageData: string) => any;
3
+ cancel: () => any;
4
+ }, string, import("vue").PublicProps, Readonly<{}> & Readonly<{
5
+ onCapture?: ((imageData: string) => any) | undefined;
6
+ onCancel?: (() => any) | undefined;
7
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
8
+ declare const _default: typeof __VLS_export;
9
+ export default _default;
@@ -0,0 +1,8 @@
1
+ import type { ToolResult } from "gui-chat-protocol/vue";
2
+ import type { ImageToolData } from "../core/types";
3
+ type __VLS_Props = {
4
+ result: ToolResult<ImageToolData>;
5
+ };
6
+ declare const __VLS_export: 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>;
7
+ declare const _default: typeof __VLS_export;
8
+ export default _default;
@@ -0,0 +1,8 @@
1
+ import type { ToolResult } from "gui-chat-protocol/vue";
2
+ import type { ImageToolData } from "../core/types";
3
+ type __VLS_Props = {
4
+ selectedResult: ToolResult<ImageToolData>;
5
+ };
6
+ declare const __VLS_export: 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>;
7
+ declare const _default: typeof __VLS_export;
8
+ export default _default;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Camera Plugin - Vue Implementation
3
+ */
4
+ import "../style.css";
5
+ import type { ToolPlugin } from "gui-chat-protocol/vue";
6
+ import type { CameraArgs, ImageToolData } from "../core/types";
7
+ import CameraCapture from "./CameraCapture.vue";
8
+ import View from "./View.vue";
9
+ import Preview from "./Preview.vue";
10
+ export declare const plugin: ToolPlugin<ImageToolData, unknown, CameraArgs>;
11
+ export type { CameraArgs, ImageToolData } from "../core/types";
12
+ export { TOOL_NAME, TOOL_DEFINITION, SYSTEM_PROMPT } from "../core/definition";
13
+ export { CameraCapture, View, Preview };
14
+ declare const _default: {
15
+ plugin: ToolPlugin<ImageToolData, unknown, CameraArgs, import("gui-chat-protocol/vue").InputHandler, Record<string, unknown>>;
16
+ };
17
+ export default _default;
package/dist/vue.cjs ADDED
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("vue"),m=require("./core.cjs"),w={class:"fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-75"},C={class:"relative bg-white dark:bg-gray-900 rounded-lg shadow-xl max-w-2xl w-full mx-4"},N={key:0,class:"px-4 pt-4 pb-2"},V=["value"],D={class:"p-4"},O={class:"relative bg-black rounded-lg overflow-hidden",style:{"aspect-ratio":"4/3"}},T={key:0,class:"absolute inset-0 flex items-center justify-center text-white text-center p-4"},B={class:"flex items-center justify-center gap-4 p-4 border-t border-gray-200 dark:border-gray-700"},M=["disabled"],h=e.defineComponent({__name:"CameraCapture",emits:["capture","cancel"],setup(c,{emit:g}){const l=g,o=e.ref(null),s=e.ref(null),r=e.ref(""),i=e.ref([]),d=e.ref("");e.onMounted(async()=>{try{await k(),await v()}catch(t){console.error("Camera initialization failed:",t),r.value=t instanceof Error?t.message:"Failed to access camera. Please grant camera permissions."}}),e.onUnmounted(()=>{p()});const k=async()=>{try{(await navigator.mediaDevices.getUserMedia({video:!0,audio:!1})).getTracks().forEach(u=>u.stop());const a=await navigator.mediaDevices.enumerateDevices();i.value=a.filter(u=>u.kind==="videoinput");const n=i.value.find(u=>u.label.toLowerCase().includes("front"));d.value=n?.deviceId||i.value[0]?.deviceId||""}catch(t){console.error("Failed to enumerate cameras:",t),i.value=[]}},v=async t=>{p(),r.value="";try{const a={video:t?{deviceId:{exact:t}}:{facingMode:"user"},audio:!1};s.value=await navigator.mediaDevices.getUserMedia(a),o.value&&(o.value.srcObject=s.value)}catch(a){console.error("Camera access failed:",a),r.value=a instanceof Error?a.message:"Failed to access camera. Please grant camera permissions."}},_=async()=>{d.value&&await v(d.value)},p=()=>{s.value&&(s.value.getTracks().forEach(t=>t.stop()),s.value=null),o.value&&(o.value.srcObject=null)},E=()=>{if(!(!o.value||r.value))try{const t=document.createElement("canvas");t.width=o.value.videoWidth,t.height=o.value.videoHeight;const a=t.getContext("2d");if(!a)throw new Error("Failed to get canvas context");a.drawImage(o.value,0,0,t.width,t.height);const n=t.toDataURL("image/png");p(),l("capture",n)}catch(t){console.error("Capture failed:",t),r.value="Failed to capture photo"}},f=()=>{p(),l("cancel")};return(t,a)=>(e.openBlock(),e.createElementBlock("div",w,[e.createElementVNode("div",C,[e.createElementVNode("div",{class:"flex items-center justify-between p-4 border-b border-gray-200 dark:border-gray-700"},[a[1]||(a[1]=e.createElementVNode("h3",{class:"text-lg font-semibold text-gray-900 dark:text-white"}," Take Photo ",-1)),e.createElementVNode("button",{onClick:f,class:"text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"}," ✕ ")]),i.value.length>1?(e.openBlock(),e.createElementBlock("div",N,[a[2]||(a[2]=e.createElementVNode("label",{for:"camera-select",class:"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2"}," Select Camera ",-1)),e.withDirectives(e.createElementVNode("select",{id:"camera-select","onUpdate:modelValue":a[0]||(a[0]=n=>d.value=n),onChange:_,class:"w-full px-3 py-2 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent"},[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(i.value,n=>(e.openBlock(),e.createElementBlock("option",{key:n.deviceId,value:n.deviceId},e.toDisplayString(n.label||`Camera ${i.value.indexOf(n)+1}`),9,V))),128))],544),[[e.vModelSelect,d.value]])])):e.createCommentVNode("",!0),e.createElementVNode("div",D,[e.createElementVNode("div",O,[e.createElementVNode("video",{ref_key:"videoRef",ref:o,autoplay:"",playsinline:"",class:"w-full h-full object-contain"},null,512),r.value?(e.openBlock(),e.createElementBlock("div",T,[e.createElementVNode("div",null,[a[3]||(a[3]=e.createElementVNode("span",{class:"text-5xl mb-2"},"⚠️",-1)),e.createElementVNode("p",null,e.toDisplayString(r.value),1)])])):e.createCommentVNode("",!0)])]),e.createElementVNode("div",B,[e.createElementVNode("button",{onClick:f,class:"px-6 py-2 bg-gray-200 dark:bg-gray-700 text-gray-900 dark:text-white rounded-lg hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors"}," Cancel "),e.createElementVNode("button",{onClick:E,disabled:!!r.value,class:"px-6 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed transition-colors flex items-center gap-2"},[...a[4]||(a[4]=[e.createTextVNode(" 📷 ",-1),e.createElementVNode("span",null,"Capture",-1)])],8,M)])])]))}}),P={class:"w-full h-full overflow-y-auto"},S={class:"min-h-full flex flex-col items-center justify-center p-4"},I={class:"flex-1 flex items-center justify-center min-h-0"},$=["src"],j={key:0,class:"mt-4 p-3 bg-gray-100 dark:bg-gray-800 rounded-lg max-w-full flex-shrink-0"},F={class:"text-sm text-gray-700 dark:text-gray-300"},y=e.defineComponent({__name:"View",props:{selectedResult:{}},setup(c){return(g,l)=>(e.openBlock(),e.createElementBlock("div",P,[e.createElementVNode("div",S,[e.createElementVNode("div",I,[e.createElementVNode("img",{src:c.selectedResult.data?.imageData,class:"max-w-full max-h-full object-contain rounded",alt:"Captured photo"},null,8,$)]),c.selectedResult.data?.prompt?(e.openBlock(),e.createElementBlock("div",j,[e.createElementVNode("p",F,[l[0]||(l[0]=e.createElementVNode("span",{class:"font-medium"},"Prompt:",-1)),e.createTextVNode(" "+e.toDisplayString(c.selectedResult.data.prompt),1)])])):e.createCommentVNode("",!0)])]))}}),R={class:"min-h-[100px] flex items-center justify-center"},L=["src"],U={key:1,class:"text-gray-400 text-sm"},b=e.defineComponent({__name:"Preview",props:{result:{}},setup(c){return(g,l)=>(e.openBlock(),e.createElementBlock("div",R,[c.result.data?.imageData?(e.openBlock(),e.createElementBlock("img",{key:0,src:c.result.data.imageData,class:"max-w-full h-auto rounded",alt:"Captured photo"},null,8,L)):(e.openBlock(),e.createElementBlock("div",U,"No image yet"))]))}}),A=async(c,g)=>new Promise(l=>{const o=document.createElement("div");document.body.appendChild(o);const s=e.createApp(h,{onCapture:r=>{s.unmount(),document.body.removeChild(o),l({data:{imageData:r,prompt:`Photo taken at ${new Date().toLocaleString()}`},message:"photo captured successfully",instructions:"Acknowledge that the photo was taken and has been already presented to the user. You can describe what you see in the photo if appropriate."})},onCancel:()=>{s.unmount(),document.body.removeChild(o),l({message:"photo capture cancelled by user",cancelled:!0})}});s.mount(o)}),x={toolDefinition:m.TOOL_DEFINITION,execute:A,generatingMessage:"Opening camera...",waitingMessage:"Taking photo...",isEnabled:()=>typeof navigator<"u"&&!!navigator.mediaDevices&&!!navigator.mediaDevices.getUserMedia,viewComponent:y,previewComponent:b,systemPrompt:m.SYSTEM_PROMPT},Y={plugin:x};exports.SYSTEM_PROMPT=m.SYSTEM_PROMPT;exports.TOOL_DEFINITION=m.TOOL_DEFINITION;exports.TOOL_NAME=m.TOOL_NAME;exports.CameraCapture=h;exports.Preview=b;exports.View=y;exports.default=Y;exports.plugin=x;
package/dist/vue.js ADDED
@@ -0,0 +1,224 @@
1
+ import { defineComponent as x, ref as p, onMounted as E, onUnmounted as $, createElementBlock as n, openBlock as l, createElementVNode as a, createCommentVNode as h, withDirectives as P, Fragment as j, renderList as M, toDisplayString as y, vModelSelect as O, createTextVNode as _, createApp as T } from "vue";
2
+ import { SYSTEM_PROMPT as I, TOOL_DEFINITION as F } from "./core.js";
3
+ import { TOOL_NAME as ce } from "./core.js";
4
+ const R = { class: "fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-75" }, S = { class: "relative bg-white dark:bg-gray-900 rounded-lg shadow-xl max-w-2xl w-full mx-4" }, N = {
5
+ key: 0,
6
+ class: "px-4 pt-4 pb-2"
7
+ }, L = ["value"], U = { class: "p-4" }, V = {
8
+ class: "relative bg-black rounded-lg overflow-hidden",
9
+ style: { "aspect-ratio": "4/3" }
10
+ }, A = {
11
+ key: 0,
12
+ class: "absolute inset-0 flex items-center justify-center text-white text-center p-4"
13
+ }, z = { class: "flex items-center justify-center gap-4 p-4 border-t border-gray-200 dark:border-gray-700" }, B = ["disabled"], Y = /* @__PURE__ */ x({
14
+ __name: "CameraCapture",
15
+ emits: ["capture", "cancel"],
16
+ setup(d, { emit: v }) {
17
+ const i = v, r = p(null), c = p(null), s = p(""), u = p([]), m = p("");
18
+ E(async () => {
19
+ try {
20
+ await k(), await b();
21
+ } catch (e) {
22
+ console.error("Camera initialization failed:", e), s.value = e instanceof Error ? e.message : "Failed to access camera. Please grant camera permissions.";
23
+ }
24
+ }), $(() => {
25
+ f();
26
+ });
27
+ const k = async () => {
28
+ try {
29
+ (await navigator.mediaDevices.getUserMedia({
30
+ video: !0,
31
+ audio: !1
32
+ })).getTracks().forEach((g) => g.stop());
33
+ const t = await navigator.mediaDevices.enumerateDevices();
34
+ u.value = t.filter(
35
+ (g) => g.kind === "videoinput"
36
+ );
37
+ const o = u.value.find(
38
+ (g) => g.label.toLowerCase().includes("front")
39
+ );
40
+ m.value = o?.deviceId || u.value[0]?.deviceId || "";
41
+ } catch (e) {
42
+ console.error("Failed to enumerate cameras:", e), u.value = [];
43
+ }
44
+ }, b = async (e) => {
45
+ f(), s.value = "";
46
+ try {
47
+ const t = {
48
+ video: e ? { deviceId: { exact: e } } : { facingMode: "user" },
49
+ audio: !1
50
+ };
51
+ c.value = await navigator.mediaDevices.getUserMedia(t), r.value && (r.value.srcObject = c.value);
52
+ } catch (t) {
53
+ console.error("Camera access failed:", t), s.value = t instanceof Error ? t.message : "Failed to access camera. Please grant camera permissions.";
54
+ }
55
+ }, C = async () => {
56
+ m.value && await b(m.value);
57
+ }, f = () => {
58
+ c.value && (c.value.getTracks().forEach((e) => e.stop()), c.value = null), r.value && (r.value.srcObject = null);
59
+ }, D = () => {
60
+ if (!(!r.value || s.value))
61
+ try {
62
+ const e = document.createElement("canvas");
63
+ e.width = r.value.videoWidth, e.height = r.value.videoHeight;
64
+ const t = e.getContext("2d");
65
+ if (!t)
66
+ throw new Error("Failed to get canvas context");
67
+ t.drawImage(r.value, 0, 0, e.width, e.height);
68
+ const o = e.toDataURL("image/png");
69
+ f(), i("capture", o);
70
+ } catch (e) {
71
+ console.error("Capture failed:", e), s.value = "Failed to capture photo";
72
+ }
73
+ }, w = () => {
74
+ f(), i("cancel");
75
+ };
76
+ return (e, t) => (l(), n("div", R, [
77
+ a("div", S, [
78
+ a("div", { class: "flex items-center justify-between p-4 border-b border-gray-200 dark:border-gray-700" }, [
79
+ t[1] || (t[1] = a("h3", { class: "text-lg font-semibold text-gray-900 dark:text-white" }, " Take Photo ", -1)),
80
+ a("button", {
81
+ onClick: w,
82
+ class: "text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
83
+ }, " ✕ ")
84
+ ]),
85
+ u.value.length > 1 ? (l(), n("div", N, [
86
+ t[2] || (t[2] = a("label", {
87
+ for: "camera-select",
88
+ class: "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2"
89
+ }, " Select Camera ", -1)),
90
+ P(a("select", {
91
+ id: "camera-select",
92
+ "onUpdate:modelValue": t[0] || (t[0] = (o) => m.value = o),
93
+ onChange: C,
94
+ class: "w-full px-3 py-2 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent"
95
+ }, [
96
+ (l(!0), n(j, null, M(u.value, (o) => (l(), n("option", {
97
+ key: o.deviceId,
98
+ value: o.deviceId
99
+ }, y(o.label || `Camera ${u.value.indexOf(o) + 1}`), 9, L))), 128))
100
+ ], 544), [
101
+ [O, m.value]
102
+ ])
103
+ ])) : h("", !0),
104
+ a("div", U, [
105
+ a("div", V, [
106
+ a("video", {
107
+ ref_key: "videoRef",
108
+ ref: r,
109
+ autoplay: "",
110
+ playsinline: "",
111
+ class: "w-full h-full object-contain"
112
+ }, null, 512),
113
+ s.value ? (l(), n("div", A, [
114
+ a("div", null, [
115
+ t[3] || (t[3] = a("span", { class: "text-5xl mb-2" }, "⚠️", -1)),
116
+ a("p", null, y(s.value), 1)
117
+ ])
118
+ ])) : h("", !0)
119
+ ])
120
+ ]),
121
+ a("div", z, [
122
+ a("button", {
123
+ onClick: w,
124
+ class: "px-6 py-2 bg-gray-200 dark:bg-gray-700 text-gray-900 dark:text-white rounded-lg hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors"
125
+ }, " Cancel "),
126
+ a("button", {
127
+ onClick: D,
128
+ disabled: !!s.value,
129
+ class: "px-6 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed transition-colors flex items-center gap-2"
130
+ }, [...t[4] || (t[4] = [
131
+ _(" 📷 ", -1),
132
+ a("span", null, "Capture", -1)
133
+ ])], 8, B)
134
+ ])
135
+ ])
136
+ ]));
137
+ }
138
+ }), H = { class: "w-full h-full overflow-y-auto" }, W = { class: "min-h-full flex flex-col items-center justify-center p-4" }, q = { class: "flex-1 flex items-center justify-center min-h-0" }, G = ["src"], J = {
139
+ key: 0,
140
+ class: "mt-4 p-3 bg-gray-100 dark:bg-gray-800 rounded-lg max-w-full flex-shrink-0"
141
+ }, K = { class: "text-sm text-gray-700 dark:text-gray-300" }, Q = /* @__PURE__ */ x({
142
+ __name: "View",
143
+ props: {
144
+ selectedResult: {}
145
+ },
146
+ setup(d) {
147
+ return (v, i) => (l(), n("div", H, [
148
+ a("div", W, [
149
+ a("div", q, [
150
+ a("img", {
151
+ src: d.selectedResult.data?.imageData,
152
+ class: "max-w-full max-h-full object-contain rounded",
153
+ alt: "Captured photo"
154
+ }, null, 8, G)
155
+ ]),
156
+ d.selectedResult.data?.prompt ? (l(), n("div", J, [
157
+ a("p", K, [
158
+ i[0] || (i[0] = a("span", { class: "font-medium" }, "Prompt:", -1)),
159
+ _(" " + y(d.selectedResult.data.prompt), 1)
160
+ ])
161
+ ])) : h("", !0)
162
+ ])
163
+ ]));
164
+ }
165
+ }), X = { class: "min-h-[100px] flex items-center justify-center" }, Z = ["src"], ee = {
166
+ key: 1,
167
+ class: "text-gray-400 text-sm"
168
+ }, te = /* @__PURE__ */ x({
169
+ __name: "Preview",
170
+ props: {
171
+ result: {}
172
+ },
173
+ setup(d) {
174
+ return (v, i) => (l(), n("div", X, [
175
+ d.result.data?.imageData ? (l(), n("img", {
176
+ key: 0,
177
+ src: d.result.data.imageData,
178
+ class: "max-w-full h-auto rounded",
179
+ alt: "Captured photo"
180
+ }, null, 8, Z)) : (l(), n("div", ee, "No image yet"))
181
+ ]));
182
+ }
183
+ }), ae = async (d, v) => new Promise((i) => {
184
+ const r = document.createElement("div");
185
+ document.body.appendChild(r);
186
+ const c = T(Y, {
187
+ onCapture: (s) => {
188
+ c.unmount(), document.body.removeChild(r), i({
189
+ data: {
190
+ imageData: s,
191
+ prompt: `Photo taken at ${(/* @__PURE__ */ new Date()).toLocaleString()}`
192
+ },
193
+ message: "photo captured successfully",
194
+ instructions: "Acknowledge that the photo was taken and has been already presented to the user. You can describe what you see in the photo if appropriate."
195
+ });
196
+ },
197
+ onCancel: () => {
198
+ c.unmount(), document.body.removeChild(r), i({
199
+ message: "photo capture cancelled by user",
200
+ cancelled: !0
201
+ });
202
+ }
203
+ });
204
+ c.mount(r);
205
+ }), re = {
206
+ toolDefinition: F,
207
+ execute: ae,
208
+ generatingMessage: "Opening camera...",
209
+ waitingMessage: "Taking photo...",
210
+ isEnabled: () => typeof navigator < "u" && !!navigator.mediaDevices && !!navigator.mediaDevices.getUserMedia,
211
+ viewComponent: Q,
212
+ previewComponent: te,
213
+ systemPrompt: I
214
+ }, ne = { plugin: re };
215
+ export {
216
+ Y as CameraCapture,
217
+ te as Preview,
218
+ I as SYSTEM_PROMPT,
219
+ F as TOOL_DEFINITION,
220
+ ce as TOOL_NAME,
221
+ Q as View,
222
+ ne as default,
223
+ re as plugin
224
+ };
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@gui-chat-plugin/camera",
3
+ "version": "0.1.0",
4
+ "description": "Camera photo capture plugin for GUIChat",
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
+ "./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
+ },
25
+ "./style.css": "./dist/style.css"
26
+ },
27
+ "files": [
28
+ "dist"
29
+ ],
30
+ "scripts": {
31
+ "dev": "vite",
32
+ "build": "vite build && vue-tsc -p tsconfig.build.json --emitDeclarationOnly",
33
+ "typecheck": "vue-tsc --noEmit",
34
+ "lint": "eslint src demo"
35
+ },
36
+ "peerDependencies": {
37
+ "gui-chat-protocol": "^0.0.1",
38
+ "vue": "^3.5.0"
39
+ },
40
+ "devDependencies": {
41
+ "@tailwindcss/vite": "^4.1.18",
42
+ "@typescript-eslint/eslint-plugin": "^8.53.0",
43
+ "@typescript-eslint/parser": "^8.53.0",
44
+ "@vitejs/plugin-vue": "^6.0.3",
45
+ "eslint": "^9.39.2",
46
+ "eslint-plugin-vue": "^10.6.2",
47
+ "globals": "^17.0.0",
48
+ "gui-chat-protocol": "^0.0.1",
49
+ "tailwindcss": "^4.1.18",
50
+ "typescript": "~5.9.3",
51
+ "vite": "^7.3.1",
52
+ "vue": "^3.5.27",
53
+ "vue-eslint-parser": "^10.2.0",
54
+ "vue-tsc": "^3.2.2"
55
+ },
56
+ "keywords": [
57
+ "guichat",
58
+ "plugin",
59
+ "camera",
60
+ "photo"
61
+ ],
62
+ "license": "MIT"
63
+ }