Framework Integration
Integrate the feedback widget with popular JavaScript frameworks
Vue.js
<template>
<div>
<p>{{ response }}</p>
<div ref="feedbackContainer"></div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted, watch } from 'vue'
import { cobblWidget } from '@cobbl-ai/feedback-widget'
const props = defineProps(['runId'])
const feedbackContainer = ref(null)
let widget = null
onMounted(() => {
widget = cobblWidget.create({ runId: props.runId })
widget.mount(feedbackContainer.value)
})
watch(
() => props.runId,
(newRunId) => {
widget?.update({ runId: newRunId })
}
)
onUnmounted(() => {
widget?.destroy()
})
</script>Svelte
<script>
import { onMount, onDestroy } from 'svelte'
import { cobblWidget } from '@cobbl-ai/feedback-widget'
export let runId
let container
let widget
onMount(() => {
widget = cobblWidget.create({ runId })
widget.mount(container)
})
$: if (widget) {
widget.update({ runId })
}
onDestroy(() => {
widget?.destroy()
})
</script>
<div bind:this={container}></div>Angular
import {
Component,
Input,
ElementRef,
ViewChild,
OnInit,
OnDestroy,
OnChanges,
} from '@angular/core'
import { cobblWidget } from '@cobbl-ai/feedback-widget'
import type { WidgetInstance } from '@cobbl-ai/feedback-widget'
@Component({
selector: 'app-feedback-widget',
template: '<div #feedbackContainer></div>',
})
export class FeedbackWidgetComponent implements OnInit, OnDestroy, OnChanges {
@Input() runId!: string
@ViewChild('feedbackContainer', { static: true }) container!: ElementRef
private widget: WidgetInstance | null = null
ngOnInit() {
this.widget = cobblWidget.create({ runId: this.runId })
this.widget.mount(this.container.nativeElement)
}
ngOnChanges() {
if (this.widget) {
this.widget.update({ runId: this.runId })
}
}
ngOnDestroy() {
this.widget?.destroy()
}
}Next.js
'use client'
import { FeedbackWidget } from '@cobbl-ai/feedback-widget/react'
export default function Page() {
return (
<div>
<h1>AI Response</h1>
<p>Your AI-generated content here...</p>
<FeedbackWidget runId="your-run-id" />
</div>
)
}Astro
---
const runId = 'your-run-id'
---
<html>
<head>
<title>Feedback Widget</title>
</head>
<body>
<h1>AI Response</h1>
<p>Your AI-generated content here...</p>
<div id="feedback-container"></div>
<script>
import { cobblWidget } from '@cobbl-ai/feedback-widget'
const widget = cobblWidget.create({
runId: document.currentScript?.dataset.runId || '',
})
widget.mount('#feedback-container')
</script>
</body>
</html>SolidJS
import { onMount, onCleanup, createEffect } from 'solid-js'
import { cobblWidget } from '@cobbl-ai/feedback-widget'
interface FeedbackWidgetProps {
runId: string
}
export const FeedbackWidget = (props: FeedbackWidgetProps) => {
let container: HTMLDivElement | undefined
let widget: ReturnType<typeof cobblWidget.create> | null = null
onMount(() => {
if (container) {
widget = cobblWidget.create({ runId: props.runId })
widget.mount(container)
}
})
createEffect(() => {
widget?.update({ runId: props.runId })
})
onCleanup(() => {
widget?.destroy()
})
return <div ref={container} />
}Preact
Preact is React-compatible, so you can use the React component directly:
import { FeedbackWidget } from '@cobbl-ai/feedback-widget/react'
export const App = () => {
return <FeedbackWidget runId="your-run-id" />
}Remix
import { FeedbackWidget } from '@cobbl-ai/feedback-widget/react'
export default function Index() {
return (
<div>
<h1>AI Response</h1>
<p>Your AI-generated content here...</p>
<FeedbackWidget runId="your-run-id" />
</div>
)
}Alpine.js
<!DOCTYPE html>
<html>
<head>
<script
defer
src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"
></script>
<script src="https://cdn.jsdelivr.net/npm/@cobbl-ai/feedback-widget"></script>
</head>
<body>
<div x-data="feedbackWidget()" x-init="init()">
<h1>AI Response</h1>
<p>Your AI-generated content here...</p>
<div x-ref="container"></div>
</div>
<script>
const feedbackWidget = () => ({
widget: null,
runId: 'your-run-id',
init() {
this.widget = cobblWidget.create({ runId: this.runId })
this.widget.mount(this.$refs.container)
},
destroy() {
this.widget?.destroy()
},
})
</script>
</body>
</html>Lit
import { LitElement, html } from 'lit'
import { customElement, property } from 'lit/decorators.js'
import { cobblWidget } from '@cobbl-ai/feedback-widget'
import type { WidgetInstance } from '@cobbl-ai/feedback-widget'
@customElement('feedback-widget')
export class FeedbackWidget extends LitElement {
@property({ type: String }) runId = ''
private widget: WidgetInstance | null = null
private container: HTMLDivElement | null = null
protected firstUpdated() {
this.container = this.shadowRoot?.querySelector(
'#container'
) as HTMLDivElement
if (this.container) {
this.widget = cobblWidget.create({ runId: this.runId })
this.widget.mount(this.container)
}
}
protected updated(changedProperties: Map<string, any>) {
if (changedProperties.has('runId') && this.widget) {
this.widget.update({ runId: this.runId })
}
}
disconnectedCallback() {
super.disconnectedCallback()
this.widget?.destroy()
}
render() {
return html`<div id="container"></div>`
}
}Common Patterns
Conditional Rendering
Only mount the widget when a run ID is available:
import type { WidgetInstance } from '@cobbl-ai/feedback-widget'
let widget: WidgetInstance | null = null
const mountWidget = () => {
if (!runId) return
widget = cobblWidget.create({ runId })
widget.mount(container)
}
const cleanupWidget = () => {
widget?.destroy()
}Error Handling
const widget = cobblWidget.create({
runId: 'your-run-id',
onError: (error) => {
// Log to your error tracking service
console.error('Feedback submission failed:', error)
// Show user-friendly message
toast.error('Failed to submit feedback. Please try again.')
},
})Analytics Integration
const widget = cobblWidget.create({
runId: 'your-run-id',
onSuccess: (feedbackId) => {
// Track feedback submission
analytics.track('feedback_submitted', {
feedbackId,
runId: 'your-run-id',
})
},
})