Cobbl

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',
    })
  },
})

Next Steps

On this page