Skip to content

DOC-677 update build process #745

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 32 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
6affc69
openai php
markzegarelli Jun 3, 2025
462fe2c
DOC-677 fix build and test
markzegarelli Jun 3, 2025
a37ec55
DOC-677 update prompt
markzegarelli Jun 3, 2025
7b8b7f7
DOC-677 update prompt
markzegarelli Jun 3, 2025
8a7d98a
DOC-677 account management
markzegarelli Jun 3, 2025
77d3cd5
DOC-677 exp advanced techniques
markzegarelli Jun 3, 2025
c8ff37c
DOC-677 Ampli
markzegarelli Jun 3, 2025
42eb1a5
DOC-677 style updates
markzegarelli Jun 3, 2025
e01a670
DOC-677 use ai summary for meta
markzegarelli Jun 3, 2025
59a7dd3
DOC-677 analytics
markzegarelli Jun 3, 2025
6ce0dc8
DOC-677 audiences
markzegarelli Jun 3, 2025
9b47efe
DOC-677 billing and use
markzegarelli Jun 3, 2025
4ba4431
DOC-677 cdp
markzegarelli Jun 3, 2025
f6c7cbc
DOC-677 charts
markzegarelli Jun 3, 2025
55cf90e
DOC-677 compass
markzegarelli Jun 3, 2025
92ea479
DOC-677 data
markzegarelli Jun 3, 2025
2a005b3
DOC-677 data tables
markzegarelli Jun 3, 2025
bdcd8e8
DOC-677 engagement matrix
markzegarelli Jun 3, 2025
20722cf
DOC-677 event seg
markzegarelli Jun 3, 2025
2115b0a
DOC-677 experiment
markzegarelli Jun 3, 2025
3cd334d
DOC-677 experiment again
markzegarelli Jun 3, 2025
745a484
DOC-677 funnel analysis
markzegarelli Jun 3, 2025
13acf6c
DOC-677 get started
markzegarelli Jun 3, 2025
edbac84
DOC-677 gs
markzegarelli Jun 3, 2025
1983050
DOC-677 impact analysis
markzegarelli Jun 3, 2025
c78c2eb
DOC-677 journeys
markzegarelli Jun 3, 2025
edad3ff
DOC-677 lifecycle
markzegarelli Jun 3, 2025
d0aa5c1
DOC-677 rest o content
markzegarelli Jun 3, 2025
fbe0ea8
DOC-677 remove summary
markzegarelli Jun 4, 2025
5e5326e
Merge branch 'main' into DOC-677-fix
markzegarelli Jun 10, 2025
ebdcdfc
Merge branch 'main' into DOC-677-fix
markzegarelli Jul 8, 2025
643d0cb
DOC-677 merge main
markzegarelli Jul 8, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"git.ignoreLimitWarning": true
}
177 changes: 177 additions & 0 deletions app/Actions/AISummarize.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
<?php

namespace App\Actions;

use Statamic\Actions\Action;
use Statamic\Contracts\Entries\Entry;
use Statamic\Facades\Entry as EntryAPI;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use OpenAI; // Official OpenAI PHP Client
use OpenAI\Exceptions\ErrorException as OpenAIErrorException;

class AISummarize extends Action
{
protected const CONTENT_FIELD = 'content';
protected const SUMMARY_FIELD = 'ai_summary';

public static function title()
{
return __("Summarize with AI");
}

public function visibleTo($item)
{
return $item instanceof Entry;
}

/**
* The run method
*
* @return mixed
*/
public function run($items, $values)
{
$items->each(function ($entry) {
if (!$entry instanceof Entry) {
return;
}

$contentToSummarize = $entry->get(self::CONTENT_FIELD);

if (empty($contentToSummarize)) {
Log::info("[OpenAISummarizer] Entry ID {$entry->id()} has no content in field '" . self::CONTENT_FIELD . "'. Skipping.");
return;
}

$textContent = $this->extractText($contentToSummarize);

if (empty(trim($textContent))) {
Log::info("[OpenAISummarizer] Entry ID {$entry->id()} has no extractable text in field '" . self::CONTENT_FIELD . "'. Skipping.");
return;
}

try {
$summary = $this->fetchSummaryFromOpenAI($textContent);

if ($summary) {
$mutableEntry = EntryAPI::find($entry->id());
$mutableEntry->set(self::SUMMARY_FIELD, $summary);
$mutableEntry->save();
Log::info("[OpenAISummarizer] Successfully summarized and updated Entry ID {$entry->id()}.");
} else {
Log::warning("[OpenAISummarizer] Failed to get a valid summary from OpenAI for Entry ID {$entry->id()}.");
}
} catch (\Exception $e) {
Log::error("[OpenAISummarizer] Error processing Entry ID {$entry->id()}: " . $e->getMessage());
// For a better user experience in the CP, you might want to collect errors
// and return them, rather than just logging.
}
});
return __('Summary generated 🫡');
}

/**
* Extracts plain text from various field types.
* Customize this based on the fieldtypes you use for your 'content' field.
*
* @param mixed $contentFieldData The data from the content field.
* @return string
*/
protected function extractText($contentFieldData): string
{
if (is_string($contentFieldData)) {
return strip_tags($contentFieldData);
}

if (is_array($contentFieldData)) {
// Simplified example for Bard-like fieldtypes (array of sets)
$textBlocks = [];
foreach ($contentFieldData as $set) {
if (isset($set['type'])) {
switch ($set['type']) {
case 'text':
case 'paragraph':
case 'heading':
if (isset($set['text']) && is_string($set['text'])) {
$textBlocks[] = strip_tags($set['text']);
} elseif (isset($set['content']) && is_array($set['content'])) {
// Handle ProseMirror structure often found in Bard's 'text' type
foreach ($set['content'] as $proseItem) {
if (isset($proseItem['type']) && $proseItem['type'] === 'text' && isset($proseItem['text'])) {
$textBlocks[] = strip_tags($proseItem['text']);
}
}
}
break;
case 'code_block':
// You might want to exclude code blocks or handle them differently
break;
// Add more cases for other set types (e.g., 'image' for alt text, 'quote')
// case 'quote':
// if (isset($set['quote']) && is_string($set['quote'])) {
// $textBlocks[] = strip_tags($set['quote']);
// }
// break;
}
}
}
return implode("\n\n", $textBlocks); // Join paragraphs with double newlines
}
return ''; // Default fallback
}

/**
* Fetches a summary from the OpenAI API.
*
* @param string $text The text to summarize.
* @return string|null The summary, or null on failure.
* @throws \Exception If the API key is missing or API call fails.
*/
protected function fetchSummaryFromOpenAI(string $text): ?string
{
$apiKey = env('OPENAI_API_KEY');

if (!$apiKey) {
Log::error("[OpenAISummarizer] OpenAI API Key is not configured in .env (OPENAI_API_KEY).");
throw new \Exception('OpenAI API Key is not configured.');
}

try {
$client = OpenAI::client($apiKey);

// Consider making the model and prompt details configurable
$model = 'gpt-3.5-turbo'; // Or 'gpt-4', 'gpt-4o', etc.
$prompt = "Summarize the following Amplitude technical documentation in no more than 100 words. Use direct, active voice, present tense, and simple, direct language. Avoid instructions. Frame the response directly to the reader. Use the word 'you' instead of 'users'. Avoid the phrase 'The Amplitude technical documentation'. Write for both human readers and search engines by including the most important keywords and a clear description of the content's purpose. The full text is:\n\n\"" . mb_strimwidth($text, 0, 15000, "...") . "\"\n\nSummary:"; // Truncate input if too long for the model's context window

Log::info("[OpenAISummarizer] Sending text to OpenAI (model: {$model}). Text length: " . strlen($text));

$response = $client->chat()->create([
'model' => $model,
'messages' => [
['role' => 'system', 'content' => "Respond in direct, simple communication. Use contractions wherever possible. Use the present tense. Frame the response around the user and what they can do with functionality. Don't provide instructions, just summarize the content of the article, and what it enables for them. Avoid weasel words like utilize."],
['role' => 'user', 'content' => $prompt],
],
'max_tokens' => 150, // Adjust based on desired summary length
'temperature' => 0.6, // Lower for more factual, higher for more creative
]);

$summary = $response->choices[0]->message->content;

if ($summary) {
Log::info("[OpenAISummarizer] Summary received from OpenAI: " . substr(trim($summary), 0, 100) . "...");
return trim($summary);
} else {
Log::warning("[OpenAISummarizer] OpenAI API returned an empty summary for text (snippet): " . substr($text, 0, 100) . "...");
return null;
}

} catch (OpenAIErrorException $e) {
Log::error("[OpenAISummarizer] OpenAI API Error: " . $e->getMessage() . " (Type: " . $e->type() . ", Code: " . $e->code() . ")");
throw new \Exception("OpenAI API Error: " . $e->getMessage());
} catch (\Exception $e) {
Log::error("[OpenAISummarizer] General error fetching summary from OpenAI: " . $e->getMessage());
throw $e; // Re-throw general exceptions
}
}
}
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"laravel/framework": "^10.8",
"laravel/sanctum": "^3.2",
"laravel/tinker": "^2.8",
"openai-php/client": "*",
"pecotamic/sitemap": "^1.4",
"spatie/fork": "^1.2",
"statamic/cms": "5.52.0",
Expand Down
Loading
Loading