jekyll-ai-chatbot 1.0.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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +100 -0
- data/lib/jekyll-ai-chatbot/core.rb +122 -0
- data/lib/jekyll-ai-chatbot/version.rb +5 -0
- data/lib/jekyll-ai-chatbot.rb +45 -0
- metadata +72 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 8534449e9a21053a99ef60381c817b176a9bf265775a970d21516ec20157d6f8
|
|
4
|
+
data.tar.gz: 3591194a072e9bb4586737c972a0ab516093ffd7bde1d3b48bcc693d60b30955
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 34567640711fe7db9ded7543f9d3b092e054f26779679302c98826d648513138041aec90a2e3d35fffa03bc0a80923e6625117442987e7e6fd057b616e15d4bd
|
|
7
|
+
data.tar.gz: b944e557c0005ee750e8ef0367dee607ef6cd7a2cb2192fef5b09387b80f49ac48b0cf217727f58fe9a5e0e66210e8b5c5cd429a2bd15bc48f8f972617731d32
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Asyntai
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# jekyll-ai-chatbot
|
|
2
|
+
|
|
3
|
+
Add the [Asyntai](https://asyntai.com) AI chat assistant to your Jekyll site.
|
|
4
|
+
It answers visitor questions from your own content, in more than 80 languages.
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
Add the gem to your `Gemfile`, in the `jekyll_plugins` group:
|
|
9
|
+
|
|
10
|
+
```ruby
|
|
11
|
+
group :jekyll_plugins do
|
|
12
|
+
gem "jekyll-ai-chatbot"
|
|
13
|
+
end
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Run `bundle install`. Then add your widget ID to `_config.yml`:
|
|
17
|
+
|
|
18
|
+
```yaml
|
|
19
|
+
asyntai:
|
|
20
|
+
widget_id: asyntai_1a2b3c4d5e6f
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
If your site lists its plugins in `_config.yml` instead of the Gemfile group,
|
|
24
|
+
add it there too:
|
|
25
|
+
|
|
26
|
+
```yaml
|
|
27
|
+
plugins:
|
|
28
|
+
- jekyll-ai-chatbot
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Build the site. The chat bubble is on every page.
|
|
32
|
+
|
|
33
|
+
## Where the widget ID comes from
|
|
34
|
+
|
|
35
|
+
Open the Install page in your [Asyntai dashboard](https://asyntai.com/dashboard).
|
|
36
|
+
The widget ID is the value of `data-asyntai-id` in the snippet shown there. It
|
|
37
|
+
starts with `asyntai_`.
|
|
38
|
+
|
|
39
|
+
Creating an account is free, and the Free plan is enough to try the widget.
|
|
40
|
+
|
|
41
|
+
## Options
|
|
42
|
+
|
|
43
|
+
All options go under `asyntai:` in `_config.yml`.
|
|
44
|
+
|
|
45
|
+
| Option | Type | Default | What it does |
|
|
46
|
+
| --- | --- | --- | --- |
|
|
47
|
+
| `widget_id` | string | required | Your Asyntai widget ID |
|
|
48
|
+
| `enable_in_development` | boolean | `true` | Set to `false` to keep the widget off the site while `JEKYLL_ENV` is `development` |
|
|
49
|
+
| `script_url` | string | the Asyntai CDN | Advanced. Load the widget from another URL |
|
|
50
|
+
|
|
51
|
+
A missing or malformed `widget_id` stops the build with a message that says how
|
|
52
|
+
to fix it.
|
|
53
|
+
|
|
54
|
+
Jekyll uses `development` when `JEKYLL_ENV` is not set, also for `jekyll build`.
|
|
55
|
+
If you set `enable_in_development: false`, build with
|
|
56
|
+
`JEKYLL_ENV=production jekyll build` to get the widget.
|
|
57
|
+
|
|
58
|
+
To keep the widget off one page, add this to its front matter:
|
|
59
|
+
|
|
60
|
+
```yaml
|
|
61
|
+
asyntai: false
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## What the plugin does
|
|
65
|
+
|
|
66
|
+
- It adds the Asyntai loader to every HTML page, just before `</body>`.
|
|
67
|
+
That includes pages, posts, collection documents and pagination pages.
|
|
68
|
+
- The loader waits for the page's `load` event, so your own content always
|
|
69
|
+
renders first.
|
|
70
|
+
- Feeds, JSON, CSS, Sass and other non-HTML files stay as they are.
|
|
71
|
+
- A page that already has the Asyntai snippet keeps it and gets no second one.
|
|
72
|
+
|
|
73
|
+
Tested with Jekyll 3.10 and 4.4.
|
|
74
|
+
|
|
75
|
+
## GitHub Pages
|
|
76
|
+
|
|
77
|
+
The classic GitHub Pages build runs only the plugins on its own approved list.
|
|
78
|
+
To use this gem on GitHub Pages, build the site with GitHub Actions
|
|
79
|
+
(the "Jekyll" workflow in your repository's Pages settings). Then the gem runs
|
|
80
|
+
like on any other host.
|
|
81
|
+
|
|
82
|
+
## Answering from your content
|
|
83
|
+
|
|
84
|
+
Asyntai reads your published pages. Add your site URL in the dashboard, and the
|
|
85
|
+
assistant answers from the same pages the widget sits on. You can also add extra
|
|
86
|
+
documents and question and answer pairs.
|
|
87
|
+
|
|
88
|
+
## Removing it
|
|
89
|
+
|
|
90
|
+
Remove the gem from your `Gemfile` and the `asyntai:` block from `_config.yml`,
|
|
91
|
+
then build again. The widget is gone.
|
|
92
|
+
|
|
93
|
+
## Support
|
|
94
|
+
|
|
95
|
+
Email [hello@asyntai.com](mailto:hello@asyntai.com), or read the
|
|
96
|
+
[Jekyll guide](https://asyntai.com/documentation/integrations/jekyll/).
|
|
97
|
+
|
|
98
|
+
## Licence
|
|
99
|
+
|
|
100
|
+
MIT
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Asyntai AI chatbot for Jekyll: the logic, without the hooks, so it can be
|
|
4
|
+
# tested outside a Jekyll build.
|
|
5
|
+
#
|
|
6
|
+
# Adds the Asyntai chat widget to every HTML page the site builds. After a page
|
|
7
|
+
# is rendered, the plugin puts the ordinary embed loader just before </body>
|
|
8
|
+
# and changes nothing else about the page.
|
|
9
|
+
|
|
10
|
+
require "json"
|
|
11
|
+
require "jekyll-ai-chatbot/version"
|
|
12
|
+
|
|
13
|
+
module JekyllAiChatbot
|
|
14
|
+
DEFAULT_SCRIPT_URL = "https://widget.asyntai.com/static/js/chat-widget.js"
|
|
15
|
+
WIDGET_ID_PATTERN = /\Aasyntai_[A-Za-z0-9]+\z/
|
|
16
|
+
NAME = "jekyll-ai-chatbot"
|
|
17
|
+
CONFIG_KEY = "asyntai"
|
|
18
|
+
|
|
19
|
+
class ConfigurationError < StandardError; end
|
|
20
|
+
|
|
21
|
+
Settings = Struct.new(:widget_id, :script_url, :enable_in_development)
|
|
22
|
+
|
|
23
|
+
module_function
|
|
24
|
+
|
|
25
|
+
# Reads the `asyntai:` block of _config.yml. A wrong option stops the build
|
|
26
|
+
# with a clear message instead of producing a site with no chat.
|
|
27
|
+
def validate_options(options)
|
|
28
|
+
unless options.nil? || options.is_a?(Hash)
|
|
29
|
+
raise ConfigurationError,
|
|
30
|
+
"#{NAME}: \"#{CONFIG_KEY}\" in _config.yml must be a block with " \
|
|
31
|
+
"widget_id inside it, for example:\n\n" \
|
|
32
|
+
" #{CONFIG_KEY}:\n widget_id: asyntai_1a2b3c4d5e6f"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
opts = (options || {}).each_with_object({}) { |(k, v), h| h[k.to_s] = v }
|
|
36
|
+
widget_id = opts["widget_id"].is_a?(String) ? opts["widget_id"].strip : ""
|
|
37
|
+
|
|
38
|
+
if widget_id.empty?
|
|
39
|
+
raise ConfigurationError,
|
|
40
|
+
"#{NAME}: \"widget_id\" is required. Add it to _config.yml under " \
|
|
41
|
+
"\"#{CONFIG_KEY}:\". Copy it from the Install page of your Asyntai " \
|
|
42
|
+
"dashboard, for example asyntai_1a2b3c4d5e6f."
|
|
43
|
+
end
|
|
44
|
+
unless WIDGET_ID_PATTERN.match?(widget_id)
|
|
45
|
+
raise ConfigurationError,
|
|
46
|
+
"#{NAME}: \"#{widget_id}\" does not look like a widget ID. It starts with " \
|
|
47
|
+
"asyntai_, for example asyntai_1a2b3c4d5e6f."
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
script_url = opts["script_url"].is_a?(String) && !opts["script_url"].strip.empty? ?
|
|
51
|
+
opts["script_url"].strip : DEFAULT_SCRIPT_URL
|
|
52
|
+
|
|
53
|
+
Settings.new(
|
|
54
|
+
widget_id,
|
|
55
|
+
script_url,
|
|
56
|
+
# Set to false to keep the widget off the site while JEKYLL_ENV is development.
|
|
57
|
+
opts["enable_in_development"] != false
|
|
58
|
+
)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# JSON string, safe inside an HTML <script> element.
|
|
62
|
+
def js_string(value)
|
|
63
|
+
JSON.generate(value.to_s).gsub("<", "\\u003c")
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# The loader waits for the page's load event before it fetches the widget.
|
|
67
|
+
# An ordinary `<script async src>` holds the load event open until its own
|
|
68
|
+
# fetch finishes, so an unreachable server would delay the host page. This
|
|
69
|
+
# pattern cannot.
|
|
70
|
+
def build_loader(widget_id, script_url)
|
|
71
|
+
"(function(){var l=function(){" \
|
|
72
|
+
"if(document.querySelector(\"script[data-asyntai-id]\"))return;" \
|
|
73
|
+
"var s=document.createElement(\"script\");" \
|
|
74
|
+
"s.src=#{js_string(script_url)};s.async=true;" \
|
|
75
|
+
"s.setAttribute(\"data-asyntai-id\",#{js_string(widget_id)});" \
|
|
76
|
+
"document.head.appendChild(s)};" \
|
|
77
|
+
"if(document.readyState===\"complete\"){l()}" \
|
|
78
|
+
"else{window.addEventListener(\"load\",l)}})();"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def build_tag(settings)
|
|
82
|
+
"<script data-asyntai-loader>#{build_loader(settings.widget_id, settings.script_url)}</script>"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Puts the tag before the last </body>, or before </html> when the page has no
|
|
86
|
+
# body tag. A fragment with neither is left alone: it is not a whole page.
|
|
87
|
+
# A page that already carries an Asyntai snippet is left alone too.
|
|
88
|
+
def inject_into_html(html, tag)
|
|
89
|
+
return html unless html.is_a?(String)
|
|
90
|
+
return html if html.include?("data-asyntai-id")
|
|
91
|
+
|
|
92
|
+
lower = html.downcase
|
|
93
|
+
at = lower.rindex("</body>") || lower.rindex("</html>")
|
|
94
|
+
return html if at.nil?
|
|
95
|
+
|
|
96
|
+
html[0...at] + tag + html[at..-1]
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Only pages that are written to disk as .html get the widget. Feeds, JSON,
|
|
100
|
+
# CSS, Sass and pages with `published: false` or no output stay as they are.
|
|
101
|
+
def html_output?(document)
|
|
102
|
+
if document.respond_to?(:write?)
|
|
103
|
+
return false unless document.write?
|
|
104
|
+
end
|
|
105
|
+
ext = document.respond_to?(:output_ext) ? document.output_ext.to_s.downcase : ""
|
|
106
|
+
[".html", ".htm"].include?(ext)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# A page can opt out with `asyntai: false` in its front matter.
|
|
110
|
+
def opted_out?(document)
|
|
111
|
+
data = document.respond_to?(:data) ? document.data : nil
|
|
112
|
+
data.is_a?(Hash) && data[CONFIG_KEY] == false
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def apply(document, settings, tag, environment)
|
|
116
|
+
return if !settings.enable_in_development && environment.to_s == "development"
|
|
117
|
+
return unless html_output?(document)
|
|
118
|
+
return if opted_out?(document)
|
|
119
|
+
|
|
120
|
+
document.output = inject_into_html(document.output, tag)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Asyntai AI chatbot for Jekyll.
|
|
4
|
+
#
|
|
5
|
+
# In _config.yml:
|
|
6
|
+
#
|
|
7
|
+
# plugins:
|
|
8
|
+
# - jekyll-ai-chatbot
|
|
9
|
+
#
|
|
10
|
+
# asyntai:
|
|
11
|
+
# widget_id: asyntai_1a2b3c4d5e6f
|
|
12
|
+
|
|
13
|
+
require "jekyll"
|
|
14
|
+
require "jekyll-ai-chatbot/core"
|
|
15
|
+
|
|
16
|
+
module JekyllAiChatbot
|
|
17
|
+
# Settings of the site being built, read once per build.
|
|
18
|
+
@settings = nil
|
|
19
|
+
@tag = nil
|
|
20
|
+
|
|
21
|
+
class << self
|
|
22
|
+
attr_reader :settings, :tag
|
|
23
|
+
|
|
24
|
+
def configure(site)
|
|
25
|
+
@settings = validate_options(site.config[CONFIG_KEY])
|
|
26
|
+
@tag = build_tag(@settings)
|
|
27
|
+
rescue ConfigurationError => e
|
|
28
|
+
raise Jekyll::Errors::FatalException, e.message
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# `after_reset` runs at the start of every build, also on each rebuild of
|
|
34
|
+
# `jekyll serve`, so a changed widget ID in _config.yml is picked up.
|
|
35
|
+
Jekyll::Hooks.register :site, :after_reset do |site|
|
|
36
|
+
JekyllAiChatbot.configure(site)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Pages, posts and collection documents. Low priority, so the widget goes in
|
|
40
|
+
# after other plugins change the page.
|
|
41
|
+
Jekyll::Hooks.register [:pages, :documents], :post_render, priority: :low do |doc|
|
|
42
|
+
next if JekyllAiChatbot.settings.nil?
|
|
43
|
+
|
|
44
|
+
JekyllAiChatbot.apply(doc, JekyllAiChatbot.settings, JekyllAiChatbot.tag, Jekyll.env)
|
|
45
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: jekyll-ai-chatbot
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Asyntai
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-10-27 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: jekyll
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '3.7'
|
|
20
|
+
- - "<"
|
|
21
|
+
- !ruby/object:Gem::Version
|
|
22
|
+
version: '5.0'
|
|
23
|
+
type: :runtime
|
|
24
|
+
prerelease: false
|
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
26
|
+
requirements:
|
|
27
|
+
- - ">="
|
|
28
|
+
- !ruby/object:Gem::Version
|
|
29
|
+
version: '3.7'
|
|
30
|
+
- - "<"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '5.0'
|
|
33
|
+
description: Adds the Asyntai AI chatbot to every page of your Jekyll site. It answers
|
|
34
|
+
visitor questions from your own content, in 80+ languages.
|
|
35
|
+
email: hello@asyntai.com
|
|
36
|
+
executables: []
|
|
37
|
+
extensions: []
|
|
38
|
+
extra_rdoc_files: []
|
|
39
|
+
files:
|
|
40
|
+
- LICENSE
|
|
41
|
+
- README.md
|
|
42
|
+
- lib/jekyll-ai-chatbot.rb
|
|
43
|
+
- lib/jekyll-ai-chatbot/core.rb
|
|
44
|
+
- lib/jekyll-ai-chatbot/version.rb
|
|
45
|
+
homepage: https://asyntai.com/documentation/integrations/jekyll/
|
|
46
|
+
licenses:
|
|
47
|
+
- MIT
|
|
48
|
+
metadata:
|
|
49
|
+
homepage_uri: https://asyntai.com/documentation/integrations/jekyll/
|
|
50
|
+
source_code_uri: https://github.com/asyntai/jekyll-ai-chatbot
|
|
51
|
+
bug_tracker_uri: https://github.com/asyntai/jekyll-ai-chatbot/issues
|
|
52
|
+
rubygems_mfa_required: 'true'
|
|
53
|
+
post_install_message:
|
|
54
|
+
rdoc_options: []
|
|
55
|
+
require_paths:
|
|
56
|
+
- lib
|
|
57
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '2.5'
|
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
63
|
+
requirements:
|
|
64
|
+
- - ">="
|
|
65
|
+
- !ruby/object:Gem::Version
|
|
66
|
+
version: '0'
|
|
67
|
+
requirements: []
|
|
68
|
+
rubygems_version: 3.5.22
|
|
69
|
+
signing_key:
|
|
70
|
+
specification_version: 4
|
|
71
|
+
summary: Add the Asyntai AI chat assistant to your Jekyll site.
|
|
72
|
+
test_files: []
|