twisted-binary/laravel-feedback-widget

AI-powered feedback widget for Laravel + Inertia + Vue apps that creates GitHub issues.

Maintainers

Package info

github.com/twisted-binary/laravel-feedback-widget

pkg:composer/twisted-binary/laravel-feedback-widget

Statistics

Installs: 699

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

1.6.0 2026-04-01 11:02 UTC

This package is auto-updated.

Last update: 2026-04-01 11:02:24 UTC


README

An AI-powered feedback widget for Laravel + Inertia + Vue apps. Users describe bugs, feature requests, or general feedback through a guided chat — the AI structures it into a well-formatted GitHub issue automatically.

Features

  • Guided chat — AI asks follow-up questions to gather the right details for each feedback type (bug, feature, feedback)
  • Auto-structured issues — Produces well-formatted GitHub issues with proper sections (Steps to Reproduce, Expected Behavior, etc.)
  • Screenshot uploads — Users can paste or attach screenshots for bug reports
  • Star ratings — Optional 5-star rating for general feedback
  • GitHub App auth — Uses GitHub App installation tokens (not PATs) for secure issue creation
  • Zero config UI — Drop <FeedbackWidget /> into any layout; routes, bindings, and Inertia props are handled by the service provider
  • AI cost tracking — Per-request token usage logged to a feedback_ai_costs table with conversation-level grouping

Requirements

  • PHP 8.4+
  • Laravel 12+
  • Inertia.js v2 (Vue 3)
  • An OpenAI API key
  • A GitHub App with issue write permissions

Installation

1. Composer

composer require twisted-binary/laravel-feedback-widget

2. Vite config

The Vue components ship as raw SFCs inside the Composer package. Add a resolve alias so Vite can find them:

import path from 'node:path';

export default defineConfig({
    resolve: {
        alias: {
            '@twisted-binary/feedback-widget': path.resolve(
                'vendor/twisted-binary/laravel-feedback-widget/resources/js/index.ts',
            ),
        },
    },
    // ...
});

3. Publish config (optional)

php artisan vendor:publish --tag=feedback-widget-config

This publishes config/feedback-widget.php where you can customize routes, middleware, throttling, and more.

4. Publish and run migrations (optional)

AI cost tracking is enabled by default. To create the feedback_ai_costs table:

php artisan vendor:publish --tag=feedback-widget-migrations
php artisan migrate

To disable cost tracking entirely, set FEEDBACK_WIDGET_TRACK_AI_COSTS=false in your .env.

OpenAI API Setup

This package uses the OpenAI API to power the guided feedback chat. Here's how to get set up:

1. Create an OpenAI account

Go to platform.openai.com and sign up or log in.

2. Add billing

Navigate to SettingsBilling and add a payment method. The API is pay-per-use — the default gpt-4o-mini model is very affordable for short feedback conversations.

3. Generate an API key

Go to API keys (platform.openai.com/api-keys) → Create new secret key.

Give it a name (e.g. feedback-widget) and copy the key — you won't be able to see it again.

4. Add to your .env

OPENAI_API_KEY=sk-...
OPENAI_FEEDBACK_MODEL=gpt-4o-mini    # optional, defaults to gpt-4o-mini

The OPENAI_FEEDBACK_MODEL setting is optional. gpt-4o-mini is the default and works well for feedback conversations. You can switch to gpt-4o for higher quality responses at a higher cost.

GitHub App Setup

This package uses a GitHub App (not a personal access token) to create issues. Here's how to set one up:

1. Create the GitHub App

Go to github.com/settings/apps/new and configure:

Field Value
App name Something like myapp-feedback
Homepage URL Your app URL
Webhook Uncheck "Active" (not needed)
Permissions → Repository → Issues Read & write
Where can this app be installed? Only on this account

Click Create GitHub App. Note the App ID shown on the next page.

2. Generate a private key

On the app settings page, scroll to Private keysGenerate a private key. A .pem file downloads.

3. Install the app on your repo

On the app settings page, click Install App in the left sidebar → Install on your account → select Only select repositories → pick your feedback repo → Install.

Note the Installation ID from the URL after installing:

https://github.com/settings/installations/INSTALLATION_ID

4. Base64-encode the private key

cat your-app.private-key.pem | base64 | tr -d '\n'

Copy the output — you'll use it as GITHUB_APP_PRIVATE_KEY below.

Environment Variables

Add these to your .env:

# OpenAI
OPENAI_API_KEY=sk-...
OPENAI_FEEDBACK_MODEL=gpt-4o-mini    # optional, defaults to gpt-4o-mini

# GitHub App
GITHUB_APP_ID=123456
GITHUB_APP_PRIVATE_KEY=base64:...     # base64-encoded PEM private key
GITHUB_APP_INSTALLATION_ID=789
GITHUB_REPO_OWNER=your-org
GITHUB_REPO_NAME=your-repo
GITHUB_FEEDBACK_LABEL=user-feedback   # optional

# Widget
FEEDBACK_WIDGET_APP_NAME=MyApp        # optional, used in AI prompts
FEEDBACK_WIDGET_ROUTE_PREFIX=feedback  # optional
FEEDBACK_WIDGET_LOCALE=en             # optional, defaults to app()->getLocale()
FEEDBACK_WIDGET_TRACK_AI_COSTS=true   # optional, defaults to true

Usage

Add the widget to your layout:

<script setup>
import { FeedbackWidget } from '@twisted-binary/feedback-widget';
</script>

<template>
    <!-- your layout content -->
    <FeedbackWidget />
</template>

Note: The widget uses a singleton pattern — only one instance per page is supported.

That's it. The service provider automatically:

  • Registers routes (POST /feedback/chat, POST /feedback/issue)
  • Binds the chat and issue services
  • Shares route URLs to the frontend via Inertia props

Configuration

The full config file (config/feedback-widget.php):

return [
    'route_prefix' => env('FEEDBACK_WIDGET_ROUTE_PREFIX', 'feedback'),
    'middleware' => ['web', 'auth', 'verified'],
    'throttle' => [
        'chat' => '30,1',   // 30 requests per minute
        'issue' => '5,1',   // 5 requests per minute
    ],
    'openai_model' => env('OPENAI_FEEDBACK_MODEL', 'gpt-4o-mini'),
    'github' => [
        'app_id' => env('GITHUB_APP_ID'),
        'private_key' => env('GITHUB_APP_PRIVATE_KEY'),
        'installation_id' => env('GITHUB_APP_INSTALLATION_ID'),
        'repo_owner' => env('GITHUB_REPO_OWNER'),
        'repo_name' => env('GITHUB_REPO_NAME'),
        'feedback_label' => env('GITHUB_FEEDBACK_LABEL', 'user-feedback'),
    ],
    'screenshot_disk' => 'public',
    'screenshot_path' => 'feedback-screenshots',
    'app_name' => env('FEEDBACK_WIDGET_APP_NAME', env('APP_NAME', 'the application')),
    'locale' => env('FEEDBACK_WIDGET_LOCALE', null), // null = app()->getLocale()
    'track_ai_costs' => env('FEEDBACK_WIDGET_TRACK_AI_COSTS', true),
];

Multilanguage Support

AI Response Language (locale)

Set FEEDBACK_WIDGET_LOCALE in your .env to control the language the AI responds in. When not set, it falls back to app()->getLocale(). For English (en), no extra instruction is prepended to the AI prompt.

Per-user locale

Leave FEEDBACK_WIDGET_LOCALE unset and the widget will automatically use whatever locale your app has set for the current request. If you already set the locale per user via middleware (e.g. app()->setLocale($user->locale)), the AI will respond in that user's language with no extra configuration.

UI Translations (translations prop)

All UI strings have English defaults. To translate the widget, pass a translations prop with your overrides:

<script setup>
import { FeedbackWidget } from '@twisted-binary/feedback-widget';
import type { FeedbackTranslations } from '@twisted-binary/feedback-widget';

const translations: Partial<FeedbackTranslations> = {
    header: 'Envoyer un commentaire',
    bugLabel: 'Bug',
    featureLabel: 'Fonctionnalité',
    feedbackLabel: 'Avis',
    inputPlaceholder: 'Tapez votre message...',
    successMessage: 'Merci pour votre retour !',
    // ... override only the strings you need
};
</script>

<template>
    <FeedbackWidget :translations="translations" />
</template>

You only need to pass the strings you want to override — all others fall back to the English defaults. See the FeedbackTranslations interface for the full list of translatable strings.

Exports

The package exports:

import { FeedbackWidget, FeedbackChatPanel, useFeedbackChat } from '@twisted-binary/feedback-widget';
import { defaultTranslations } from '@twisted-binary/feedback-widget';
import type { FeedbackTranslations } from '@twisted-binary/feedback-widget';
Export Description
FeedbackWidget Full widget with FAB button + chat panel (drop into layout)
FeedbackChatPanel Chat panel only (if you want a custom trigger)
useFeedbackChat Composable with all state and methods
defaultTranslations Default English translations object
FeedbackTranslations TypeScript interface for all translatable strings

AI Cost Tracking

Every chat request logs token usage (prompt tokens, completion tokens, model) to the feedback_ai_costs table. Each conversation is grouped by a UUID conversation_id so you can aggregate usage per feedback session.

Query examples:

use TwistedBinary\FeedbackWidget\Models\FeedbackAiCost;

// Total tokens for a conversation
FeedbackAiCost::forConversation($id)->sum('total_tokens');

// All costs for a user
FeedbackAiCost::forUser($userId)->get();

// Daily token usage
FeedbackAiCost::whereDate('created_at', today())->sum('total_tokens');

To disable, set track_ai_costs to false in your config or .env. No rows will be inserted and the migration is still publishable but optional.

Events

The package dispatches a FeedbackAiCostRecorded event after each AI chat request, allowing your application to react to AI usage (e.g. external billing, analytics, alerting).

Event properties

Property Type Description
userId ?int Authenticated user ID, or null if guest
model string AI model used (e.g. gpt-4o-mini)
promptTokens int Input tokens consumed
completionTokens int Output tokens consumed
feedbackType string Feedback type (bug, feature, feedback)
conversationId string UUID grouping messages in the same session
createdAt Carbon Timestamp of the recorded cost

Listening for the event

use TwistedBinary\FeedbackWidget\Events\FeedbackAiCostRecorded;

Event::listen(FeedbackAiCostRecorded::class, function (FeedbackAiCostRecorded $event) {
    // Report to an external service, log, etc.
});

The event is dispatched synchronously. If you need async processing, dispatch a queued job from your listener.

How It Works

  1. User clicks the floating action button
  2. Selects feedback type (bug / feature / feedback)
  3. Chats with the AI assistant which guides them through the details
  4. AI produces a structured title + body and asks for confirmation
  5. On confirmation, a GitHub issue is created with proper labels
  6. User sees a success state

License

MIT