@folio-agent/widget 0.5.0 → 0.6.1

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.
package/dist/styles.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const WIDGET_STYLES = "\n :host {\n all: initial;\n color: inherit;\n color-scheme: inherit;\n font-family: var(--folio-agent-font, inherit);\n\n --_surface: var(--folio-agent-surface, Canvas);\n --_text: var(--folio-agent-text, CanvasText);\n --_accent: var(--folio-agent-accent, var(--_text));\n --_accent-contrast: var(--folio-agent-accent-contrast, var(--_surface));\n --_muted: var(--folio-agent-muted, color-mix(in srgb, var(--_text) 62%, var(--_surface)));\n --_hair: color-mix(in srgb, var(--_text) 12%, transparent);\n --_glass: color-mix(in srgb, var(--_surface) 76%, transparent);\n --_glass-strong: color-mix(in srgb, var(--_surface) 94%, transparent);\n --_field: color-mix(in srgb, var(--_text) 5%, color-mix(in srgb, var(--_surface) 80%, transparent));\n --_chip: color-mix(in srgb, var(--_surface) 60%, transparent);\n --_focus: color-mix(in srgb, var(--_accent) 70%, var(--_text));\n }\n * {\n box-sizing: border-box;\n }\n button,\n textarea {\n font: inherit;\n }\n svg {\n display: block;\n }\n\n .toggle {\n position: fixed;\n right: 20px;\n bottom: calc(20px + env(safe-area-inset-bottom, 0px));\n z-index: 2147483000;\n display: inline-flex;\n align-items: center;\n gap: 8px;\n height: 46px;\n padding: 0 18px 0 14px;\n border-radius: 999px;\n border: 1px solid color-mix(in srgb, var(--_accent-contrast) 14%, transparent);\n background: color-mix(in srgb, var(--_accent) 88%, transparent);\n -webkit-backdrop-filter: blur(16px) saturate(1.6);\n backdrop-filter: blur(16px) saturate(1.6);\n color: var(--_accent-contrast);\n font-size: 14px;\n font-weight: 600;\n letter-spacing: 0.02em;\n cursor: pointer;\n box-shadow: 0 10px 30px rgba(16, 20, 28, 0.22);\n transition: transform 0.28s cubic-bezier(0.2, 0.8, 0.2, 1), opacity 0.2s ease;\n }\n .toggle:hover {\n transform: translateY(-1px);\n }\n .toggle:active {\n transform: scale(0.97);\n }\n .toggle svg {\n width: 20px;\n height: 20px;\n flex: none;\n }\n .root.open .toggle {\n opacity: 0;\n transform: scale(0.85);\n pointer-events: none;\n }\n\n .panel {\n position: fixed;\n right: 20px;\n bottom: calc(20px + env(safe-area-inset-bottom, 0px));\n z-index: 2147483001;\n width: min(400px, calc(100vw - 40px));\n height: min(620px, calc(100dvh - 40px));\n display: flex;\n flex-direction: column;\n border-radius: 22px;\n border: 1px solid var(--_hair);\n background: var(--_glass);\n -webkit-backdrop-filter: blur(28px) saturate(1.8);\n backdrop-filter: blur(28px) saturate(1.8);\n box-shadow: 0 24px 64px rgba(16, 20, 28, 0.2), 0 2px 6px rgba(16, 20, 28, 0.08);\n color: var(--_text);\n overflow: hidden;\n transform-origin: calc(100% - 40px) calc(100% - 20px);\n opacity: 0;\n visibility: hidden;\n transform: translateY(10px) scale(0.9);\n transition:\n opacity 0.2s ease,\n transform 0.32s cubic-bezier(0.2, 0.8, 0.2, 1),\n visibility 0s linear 0.32s;\n }\n .root.open .panel {\n opacity: 1;\n visibility: visible;\n transform: none;\n transition:\n opacity 0.22s ease,\n transform 0.38s cubic-bezier(0.2, 0.9, 0.25, 1.05),\n visibility 0s;\n }\n\n .header {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 14px 12px 12px 18px;\n border-bottom: 1px solid var(--_hair);\n }\n .titles {\n flex: 1;\n min-width: 0;\n }\n .heading {\n font-size: 15px;\n font-weight: 700;\n line-height: 1.3;\n }\n .subheading {\n font-size: 12px;\n line-height: 1.4;\n color: var(--_muted);\n }\n .close {\n flex: none;\n display: grid;\n place-items: center;\n width: 34px;\n height: 34px;\n border: 0;\n border-radius: 50%;\n background: color-mix(in srgb, var(--_text) 7%, transparent);\n color: var(--_text);\n cursor: pointer;\n }\n .close:hover {\n background: color-mix(in srgb, var(--_text) 12%, transparent);\n }\n .close svg {\n width: 16px;\n height: 16px;\n }\n\n .messages {\n flex: 1;\n overflow-y: auto;\n overscroll-behavior: contain;\n padding: 18px 18px 8px;\n display: flex;\n flex-direction: column;\n gap: 14px;\n font-size: 15px;\n line-height: 1.7;\n }\n .message,\n .greeting {\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n animation: fade-in 0.28s ease both;\n }\n .message.assistant,\n .greeting {\n align-self: stretch;\n }\n .message.user {\n align-self: flex-end;\n max-width: 85%;\n padding: 8px 14px;\n border-radius: 18px 18px 6px 18px;\n background: color-mix(in srgb, var(--_accent) 14%, var(--_surface));\n border: 1px solid color-mix(in srgb, var(--_accent) 28%, var(--_surface));\n }\n @keyframes fade-in {\n from { opacity: 0; transform: translateY(4px); }\n to { opacity: 1; transform: none; }\n }\n\n .suggestions {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 8px;\n }\n .suggestions button {\n padding: 7px 14px;\n border: 1px solid var(--_hair);\n border-radius: 999px;\n background: var(--_chip);\n color: var(--_text);\n font-size: 14px;\n text-align: left;\n cursor: pointer;\n transition: background 0.15s ease;\n }\n .suggestions button:hover {\n background: color-mix(in srgb, var(--_text) 8%, transparent);\n }\n\n .typing {\n display: inline-flex;\n gap: 5px;\n padding: 8px 0;\n }\n .typing span {\n width: 7px;\n height: 7px;\n border-radius: 50%;\n background: var(--_muted);\n animation: typing-dot 1.1s ease-in-out infinite;\n }\n .typing span:nth-child(2) {\n animation-delay: 0.15s;\n }\n .typing span:nth-child(3) {\n animation-delay: 0.3s;\n }\n @keyframes typing-dot {\n 0%, 80%, 100% { opacity: 0.25; transform: translateY(0); }\n 40% { opacity: 1; transform: translateY(-3px); }\n }\n\n form {\n padding: 8px 12px calc(10px + env(safe-area-inset-bottom, 0px));\n }\n .field {\n display: flex;\n align-items: flex-end;\n gap: 8px;\n padding: 6px 6px 6px 14px;\n border: 1px solid var(--_hair);\n border-radius: 22px;\n background: var(--_field);\n transition: border-color 0.15s ease;\n }\n .field:focus-within {\n border-color: color-mix(in srgb, var(--_text) 32%, transparent);\n }\n textarea {\n flex: 1;\n min-width: 0;\n max-height: 144px;\n padding: 6px 0;\n border: 0;\n outline: 0;\n resize: none;\n background: transparent;\n color: var(--_text);\n /* 16px \u672A\u6E80\u3060\u3068 iOS Safari \u304C\u30D5\u30A9\u30FC\u30AB\u30B9\u6642\u306B\u30DA\u30FC\u30B8\u3092\u62E1\u5927\u3059\u308B */\n font-size: 16px;\n line-height: 1.5;\n }\n textarea::placeholder {\n color: var(--_muted);\n }\n .send {\n flex: none;\n display: grid;\n place-items: center;\n width: 34px;\n height: 34px;\n border: 0;\n border-radius: 50%;\n background: var(--_accent);\n color: var(--_accent-contrast);\n cursor: pointer;\n transition: opacity 0.15s ease, transform 0.15s ease;\n }\n .send:disabled {\n opacity: 0.25;\n cursor: default;\n }\n .send:not(:disabled):active {\n transform: scale(0.92);\n }\n .send svg {\n width: 18px;\n height: 18px;\n }\n .disclosure {\n margin: 8px 0 0;\n font-size: 11px;\n line-height: 1.5;\n text-align: center;\n color: var(--_muted);\n }\n .disclosure a {\n color: inherit;\n }\n\n .toggle:focus-visible,\n .close:focus-visible,\n .send:focus-visible,\n .suggestions button:focus-visible {\n outline: 2px solid var(--_focus);\n outline-offset: 2px;\n }\n\n @media (max-width: 640px) {\n .toggle {\n right: 16px;\n bottom: calc(16px + env(safe-area-inset-bottom, 0px));\n }\n /* \u9AD8\u3055\u3068\u4E0A\u7AEF\u306F visualViewport \u304B\u3089 JS \u304C\u6D41\u3057\u8FBC\u3080\u3002\u30AD\u30FC\u30DC\u30FC\u30C9\u8868\u793A\u4E2D\u3082\u5165\u529B\u6B04\u3092\u898B\u3048\u308B\u4F4D\u7F6E\u306B\u4FDD\u3064\u305F\u3081 */\n .panel {\n top: var(--vv-top, 0px);\n right: 0;\n bottom: auto;\n left: 0;\n width: 100%;\n height: var(--vv-height, 100dvh);\n border: 0;\n border-radius: 0;\n background: var(--_glass-strong);\n transform-origin: 50% 100%;\n transform: translateY(32px);\n }\n .header {\n padding-top: calc(12px + env(safe-area-inset-top, 0px));\n }\n }\n\n @media (prefers-reduced-motion: reduce) {\n .toggle,\n .panel,\n .root.open .panel {\n transform: none;\n }\n .panel {\n transition: opacity 0.15s linear, visibility 0s linear 0.15s;\n }\n .root.open .panel {\n transition: opacity 0.15s linear;\n }\n .message,\n .greeting,\n .typing span {\n animation: none;\n }\n }\n";
1
+ export declare const WIDGET_STYLES = "\n :host {\n all: initial;\n color: inherit;\n color-scheme: inherit;\n font-family: var(--folio-agent-font, inherit);\n\n --_surface: var(--folio-agent-surface, Canvas);\n --_text: var(--folio-agent-text, CanvasText);\n --_accent: var(--folio-agent-accent, var(--_text));\n --_accent-contrast: var(--folio-agent-accent-contrast, var(--_surface));\n --_muted: var(--folio-agent-muted, color-mix(in srgb, var(--_text) 62%, var(--_surface)));\n --_hair: color-mix(in srgb, var(--_text) 12%, transparent);\n --_glass: color-mix(in srgb, var(--_surface) 76%, transparent);\n --_glass-strong: color-mix(in srgb, var(--_surface) 94%, transparent);\n --_field: color-mix(in srgb, var(--_text) 5%, color-mix(in srgb, var(--_surface) 80%, transparent));\n --_chip: color-mix(in srgb, var(--_surface) 60%, transparent);\n --_focus: color-mix(in srgb, var(--_accent) 70%, var(--_text));\n }\n * {\n box-sizing: border-box;\n }\n button,\n textarea {\n font: inherit;\n }\n svg {\n display: block;\n }\n /* \u30DB\u30B9\u30C8\u5074\u306E ::selection \u306F Shadow DOM \u306E\u4E2D\u3078\u5C4A\u304B\u306A\u3044\u306E\u3067\u3001\u3053\u3053\u3067 accent \u304B\u3089\u4F5C\u308B\u3002\n \u6307\u5B9A\u3057\u306A\u3044\u3068\u30D6\u30E9\u30A6\u30B6\u65E2\u5B9A\u306E\u9752\u306B\u306A\u308A\u3001\u30B5\u30A4\u30C8\u306E\u914D\u8272\u304B\u3089\u6D6E\u304F */\n ::selection {\n background: color-mix(in srgb, var(--_accent) 32%, transparent);\n color: var(--_text);\n }\n\n .toggle {\n position: fixed;\n right: 20px;\n bottom: calc(20px + env(safe-area-inset-bottom, 0px));\n z-index: 2147483000;\n display: inline-flex;\n align-items: center;\n gap: 8px;\n height: 46px;\n padding: 0 18px 0 14px;\n border-radius: 999px;\n border: 1px solid color-mix(in srgb, var(--_accent-contrast) 14%, transparent);\n background: color-mix(in srgb, var(--_accent) 88%, transparent);\n -webkit-backdrop-filter: blur(16px) saturate(1.6);\n backdrop-filter: blur(16px) saturate(1.6);\n color: var(--_accent-contrast);\n font-size: 14px;\n font-weight: 600;\n letter-spacing: 0.02em;\n cursor: pointer;\n box-shadow: 0 10px 30px rgba(16, 20, 28, 0.22);\n transition: transform 0.28s cubic-bezier(0.2, 0.8, 0.2, 1), opacity 0.2s ease;\n }\n .toggle:hover {\n transform: translateY(-1px);\n }\n .toggle:active {\n transform: scale(0.97);\n }\n .toggle svg {\n width: 20px;\n height: 20px;\n flex: none;\n }\n .root.open .toggle {\n opacity: 0;\n transform: scale(0.85);\n pointer-events: none;\n }\n\n .panel {\n position: fixed;\n right: 20px;\n bottom: calc(20px + env(safe-area-inset-bottom, 0px));\n z-index: 2147483001;\n width: min(400px, calc(100vw - 40px));\n height: min(620px, calc(100dvh - 40px));\n display: flex;\n flex-direction: column;\n border-radius: 22px;\n border: 1px solid var(--_hair);\n background: var(--_glass);\n -webkit-backdrop-filter: blur(28px) saturate(1.8);\n backdrop-filter: blur(28px) saturate(1.8);\n box-shadow: 0 24px 64px rgba(16, 20, 28, 0.2), 0 2px 6px rgba(16, 20, 28, 0.08);\n color: var(--_text);\n overflow: hidden;\n transform-origin: calc(100% - 40px) calc(100% - 20px);\n opacity: 0;\n visibility: hidden;\n transform: translateY(10px) scale(0.9);\n transition:\n opacity 0.2s ease,\n transform 0.32s cubic-bezier(0.2, 0.8, 0.2, 1),\n visibility 0s linear 0.32s;\n }\n .root.open .panel {\n opacity: 1;\n visibility: visible;\n transform: none;\n transition:\n opacity 0.22s ease,\n transform 0.38s cubic-bezier(0.2, 0.9, 0.25, 1.05),\n visibility 0s;\n }\n\n .header {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 14px 12px 12px 18px;\n border-bottom: 1px solid var(--_hair);\n }\n .titles {\n flex: 1;\n min-width: 0;\n }\n .heading {\n font-size: 15px;\n font-weight: 700;\n line-height: 1.3;\n }\n .subheading {\n font-size: 12px;\n line-height: 1.4;\n color: var(--_muted);\n }\n .close,\n .reset {\n flex: none;\n display: grid;\n place-items: center;\n width: 34px;\n height: 34px;\n border: 0;\n border-radius: 50%;\n background: color-mix(in srgb, var(--_text) 7%, transparent);\n color: var(--_text);\n cursor: pointer;\n }\n .reset {\n background: transparent;\n color: var(--_muted);\n }\n .reset[hidden] {\n display: none;\n }\n .close:hover,\n .reset:hover {\n background: color-mix(in srgb, var(--_text) 12%, transparent);\n }\n .close svg,\n .reset svg {\n width: 16px;\n height: 16px;\n }\n\n .messages {\n flex: 1;\n overflow-y: auto;\n overscroll-behavior: contain;\n padding: 18px 18px 8px;\n display: flex;\n flex-direction: column;\n gap: 14px;\n font-size: 15px;\n line-height: 1.7;\n }\n .message,\n .greeting {\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n animation: fade-in 0.28s ease both;\n }\n .message.assistant,\n .greeting {\n align-self: stretch;\n }\n .message.user {\n align-self: flex-end;\n max-width: 85%;\n padding: 8px 14px;\n border-radius: 18px 18px 6px 18px;\n background: color-mix(in srgb, var(--_accent) 14%, var(--_surface));\n border: 1px solid color-mix(in srgb, var(--_accent) 28%, var(--_surface));\n }\n @keyframes fade-in {\n from { opacity: 0; transform: translateY(4px); }\n to { opacity: 1; transform: none; }\n }\n\n .suggestions {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 8px;\n }\n .suggestions button {\n padding: 7px 14px;\n border: 1px solid var(--_hair);\n border-radius: 999px;\n background: var(--_chip);\n color: var(--_text);\n font-size: 14px;\n text-align: left;\n cursor: pointer;\n transition: background 0.15s ease;\n }\n .suggestions button:hover {\n background: color-mix(in srgb, var(--_text) 8%, transparent);\n }\n\n .typing {\n display: inline-flex;\n gap: 5px;\n padding: 8px 0;\n }\n .typing span {\n width: 7px;\n height: 7px;\n border-radius: 50%;\n background: var(--_muted);\n animation: typing-dot 1.1s ease-in-out infinite;\n }\n .typing span:nth-child(2) {\n animation-delay: 0.15s;\n }\n .typing span:nth-child(3) {\n animation-delay: 0.3s;\n }\n @keyframes typing-dot {\n 0%, 80%, 100% { opacity: 0.25; transform: translateY(0); }\n 40% { opacity: 1; transform: translateY(-3px); }\n }\n\n form {\n padding: 8px 12px calc(10px + env(safe-area-inset-bottom, 0px));\n }\n .field {\n display: flex;\n align-items: flex-end;\n gap: 8px;\n padding: 6px 6px 6px 14px;\n border: 1px solid var(--_hair);\n border-radius: 22px;\n background: var(--_field);\n transition: border-color 0.15s ease;\n }\n .field:focus-within {\n border-color: color-mix(in srgb, var(--_text) 32%, transparent);\n }\n textarea {\n flex: 1;\n min-width: 0;\n max-height: 144px;\n padding: 6px 0;\n border: 0;\n outline: 0;\n resize: none;\n background: transparent;\n color: var(--_text);\n caret-color: var(--_accent);\n /* 16px \u672A\u6E80\u3060\u3068 iOS Safari \u304C\u30D5\u30A9\u30FC\u30AB\u30B9\u6642\u306B\u30DA\u30FC\u30B8\u3092\u62E1\u5927\u3059\u308B */\n font-size: 16px;\n line-height: 1.5;\n }\n textarea::placeholder {\n color: var(--_muted);\n }\n .send {\n flex: none;\n display: grid;\n place-items: center;\n width: 34px;\n height: 34px;\n border: 0;\n border-radius: 50%;\n background: var(--_accent);\n color: var(--_accent-contrast);\n cursor: pointer;\n transition: opacity 0.15s ease, transform 0.15s ease;\n }\n .send:disabled {\n opacity: 0.25;\n cursor: default;\n }\n .send:not(:disabled):active {\n transform: scale(0.92);\n }\n .send svg {\n width: 18px;\n height: 18px;\n }\n .disclosure {\n margin: 8px 0 0;\n font-size: 11px;\n line-height: 1.5;\n text-align: center;\n color: var(--_muted);\n }\n .disclosure a {\n color: inherit;\n }\n\n .toggle:focus-visible,\n .close:focus-visible,\n .reset:focus-visible,\n .send:focus-visible,\n .suggestions button:focus-visible {\n outline: 2px solid var(--_focus);\n outline-offset: 2px;\n }\n\n @media (max-width: 640px) {\n .toggle {\n right: 16px;\n bottom: calc(16px + env(safe-area-inset-bottom, 0px));\n }\n /* \u9AD8\u3055\u3068\u4E0A\u7AEF\u306F visualViewport \u304B\u3089 JS \u304C\u6D41\u3057\u8FBC\u3080\u3002\u30AD\u30FC\u30DC\u30FC\u30C9\u8868\u793A\u4E2D\u3082\u5165\u529B\u6B04\u3092\u898B\u3048\u308B\u4F4D\u7F6E\u306B\u4FDD\u3064\u305F\u3081 */\n .panel {\n top: var(--vv-top, 0px);\n right: 0;\n bottom: auto;\n left: 0;\n width: 100%;\n height: var(--vv-height, 100dvh);\n border: 0;\n border-radius: 0;\n background: var(--_glass-strong);\n transform-origin: 50% 100%;\n transform: translateY(32px);\n }\n .header {\n padding-top: calc(12px + env(safe-area-inset-top, 0px));\n }\n }\n\n @media (prefers-reduced-motion: reduce) {\n .toggle,\n .panel,\n .root.open .panel {\n transform: none;\n }\n .panel {\n transition: opacity 0.15s linear, visibility 0s linear 0.15s;\n }\n .root.open .panel {\n transition: opacity 0.15s linear;\n }\n .message,\n .greeting,\n .typing span {\n animation: none;\n }\n }\n";
package/dist/styles.js CHANGED
@@ -33,6 +33,12 @@ export const WIDGET_STYLES = `
33
33
  svg {
34
34
  display: block;
35
35
  }
36
+ /* ホスト側の ::selection は Shadow DOM の中へ届かないので、ここで accent から作る。
37
+ 指定しないとブラウザ既定の青になり、サイトの配色から浮く */
38
+ ::selection {
39
+ background: color-mix(in srgb, var(--_accent) 32%, transparent);
40
+ color: var(--_text);
41
+ }
36
42
 
37
43
  .toggle {
38
44
  position: fixed;
@@ -113,7 +119,7 @@ export const WIDGET_STYLES = `
113
119
  .header {
114
120
  display: flex;
115
121
  align-items: center;
116
- gap: 12px;
122
+ gap: 6px;
117
123
  padding: 14px 12px 12px 18px;
118
124
  border-bottom: 1px solid var(--_hair);
119
125
  }
@@ -131,7 +137,8 @@ export const WIDGET_STYLES = `
131
137
  line-height: 1.4;
132
138
  color: var(--_muted);
133
139
  }
134
- .close {
140
+ .close,
141
+ .reset {
135
142
  flex: none;
136
143
  display: grid;
137
144
  place-items: center;
@@ -143,10 +150,19 @@ export const WIDGET_STYLES = `
143
150
  color: var(--_text);
144
151
  cursor: pointer;
145
152
  }
146
- .close:hover {
153
+ .reset {
154
+ background: transparent;
155
+ color: var(--_muted);
156
+ }
157
+ .reset[hidden] {
158
+ display: none;
159
+ }
160
+ .close:hover,
161
+ .reset:hover {
147
162
  background: color-mix(in srgb, var(--_text) 12%, transparent);
148
163
  }
149
- .close svg {
164
+ .close svg,
165
+ .reset svg {
150
166
  width: 16px;
151
167
  height: 16px;
152
168
  }
@@ -255,6 +271,7 @@ export const WIDGET_STYLES = `
255
271
  resize: none;
256
272
  background: transparent;
257
273
  color: var(--_text);
274
+ caret-color: var(--_accent);
258
275
  /* 16px 未満だと iOS Safari がフォーカス時にページを拡大する */
259
276
  font-size: 16px;
260
277
  line-height: 1.5;
@@ -299,6 +316,7 @@ export const WIDGET_STYLES = `
299
316
 
300
317
  .toggle:focus-visible,
301
318
  .close:focus-visible,
319
+ .reset:focus-visible,
302
320
  .send:focus-visible,
303
321
  .suggestions button:focus-visible {
304
322
  outline: 2px solid var(--_focus);
@@ -5,6 +5,7 @@ const WIDGET_TEXT = {
5
5
  headingText: "このサイトについて質問",
6
6
  subheadingText: "公開している情報をもとに AI が答えます",
7
7
  closeLabel: "閉じる",
8
+ resetLabel: "新しい会話",
8
9
  greetingText: "制作実績・記事の内容・仕事の依頼について答えます。",
9
10
  suggestions: ["どんな制作実績がありますか?", "どんな考え方で仕事をしていますか?", "仕事を依頼するには?"],
10
11
  inputPlaceholder: "質問を入力",
@@ -21,6 +22,7 @@ const WIDGET_TEXT = {
21
22
  headingText: "Ask about this site",
22
23
  subheadingText: "AI answers from what this site publishes",
23
24
  closeLabel: "Close",
25
+ resetLabel: "New conversation",
24
26
  greetingText: "Ask about past work, articles, or how to request a project.",
25
27
  suggestions: ["What have you built?", "How do you approach your work?", "How can I request a project?"],
26
28
  inputPlaceholder: "Type a question",
@@ -35,6 +37,7 @@ const WIDGET_TEXT = {
35
37
  };
36
38
  const CHAT_ICON = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 12.5c0 3.9-3.6 7-8 7-1.1 0-2.2-.2-3.1-.6L4.5 20l1.2-3.4C4.6 15.5 4 14 4 12.5c0-3.9 3.6-7 8-7s8 3.1 8 7Z"/><path d="M9 12.5h.01M12 12.5h.01M15 12.5h.01" stroke-width="2.4"/></svg>`;
37
39
  const CLOSE_ICON = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg>`;
40
+ const RESET_ICON = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 20h8"/><path d="M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4Z"/></svg>`;
38
41
  const SEND_ICON = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 19V5M6 11l6-6 6 6"/></svg>`;
39
42
  const COMPACT_QUERY = "(max-width: 640px)";
40
43
  const FINE_POINTER_QUERY = "(pointer: fine)";
@@ -60,12 +63,15 @@ export class FolioAgentWidgetElement extends HTMLElement {
60
63
  #toggleEl;
61
64
  #panelEl;
62
65
  #closeEl;
66
+ #resetEl;
63
67
  #messagesEl;
64
68
  #suggestionsEl;
65
69
  #typingEl;
66
70
  #inputEl;
67
71
  #sendEl;
68
72
  #scrollLocked = false;
73
+ // 会話をやり直すたびに進める。やり直す前に送った質問の回答が遅れて届いても、描画しない
74
+ #conversation = 0;
69
75
  #previousOverflow = "";
70
76
  #onViewportChange = () => this.#syncViewport();
71
77
  connectedCallback() {
@@ -122,23 +128,55 @@ export class FolioAgentWidgetElement extends HTMLElement {
122
128
  close.setAttribute("aria-label", this.#text.closeLabel);
123
129
  close.innerHTML = CLOSE_ICON;
124
130
  close.addEventListener("click", () => this.#setOpen(false));
125
- header.append(titles, close);
131
+ const reset = element("button", "reset");
132
+ reset.type = "button";
133
+ reset.hidden = true;
134
+ reset.setAttribute("aria-label", this.#text.resetLabel);
135
+ reset.title = this.#text.resetLabel;
136
+ reset.innerHTML = RESET_ICON;
137
+ reset.addEventListener("click", () => this.#resetConversation());
138
+ header.append(titles, reset, close);
126
139
  const messages = element("div", "messages");
127
140
  messages.setAttribute("aria-live", "polite");
141
+ panel.append(header, messages, this.#buildForm());
142
+ this.#panelEl = panel;
143
+ this.#closeEl = close;
144
+ this.#resetEl = reset;
145
+ this.#messagesEl = messages;
146
+ this.#renderIntro();
147
+ return panel;
148
+ }
149
+ #renderIntro() {
150
+ if (!this.#messagesEl)
151
+ return;
128
152
  const greeting = this.getAttribute("greeting") ?? this.#text.greetingText;
129
153
  if (greeting.trim()) {
130
154
  const greetingEl = element("div", "greeting");
131
155
  greetingEl.textContent = greeting;
132
- messages.appendChild(greetingEl);
156
+ this.#messagesEl.appendChild(greetingEl);
133
157
  }
134
158
  const suggestions = this.#buildSuggestions();
135
159
  if (suggestions)
136
- messages.appendChild(suggestions);
137
- panel.append(header, messages, this.#buildForm());
138
- this.#panelEl = panel;
139
- this.#closeEl = close;
140
- this.#messagesEl = messages;
141
- return panel;
160
+ this.#messagesEl.appendChild(suggestions);
161
+ }
162
+ #resetConversation() {
163
+ if (!this.#messagesEl)
164
+ return;
165
+ this.#conversation += 1;
166
+ this.#messages = [];
167
+ this.#setPending(false);
168
+ this.#messagesEl.replaceChildren();
169
+ this.#suggestionsEl = undefined;
170
+ this.#renderIntro();
171
+ if (this.#resetEl)
172
+ this.#resetEl.hidden = true;
173
+ if (this.#inputEl)
174
+ this.#inputEl.value = "";
175
+ this.#syncInput();
176
+ if (matches(FINE_POINTER_QUERY))
177
+ this.#inputEl?.focus();
178
+ else
179
+ this.#closeEl?.focus({ preventScroll: true });
142
180
  }
143
181
  #buildSuggestions() {
144
182
  const attr = this.getAttribute("suggestions");
@@ -302,6 +340,7 @@ export class FolioAgentWidgetElement extends HTMLElement {
302
340
  this.#appendMessage({ role: "assistant", text: this.#text.configErrorText });
303
341
  return;
304
342
  }
343
+ const conversation = this.#conversation;
305
344
  this.#setPending(true);
306
345
  let answer;
307
346
  try {
@@ -316,11 +355,15 @@ export class FolioAgentWidgetElement extends HTMLElement {
316
355
  catch {
317
356
  answer = this.#text.networkErrorText;
318
357
  }
358
+ if (conversation !== this.#conversation)
359
+ return;
319
360
  this.#setPending(false);
320
361
  this.#appendMessage({ role: "assistant", text: answer });
321
362
  }
322
363
  #appendMessage(message) {
323
364
  this.#messages.push(message);
365
+ if (this.#resetEl)
366
+ this.#resetEl.hidden = false;
324
367
  if (!this.#messagesEl)
325
368
  return;
326
369
  const el = element("div", `message ${message.role}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@folio-agent/widget",
3
- "version": "0.5.0",
3
+ "version": "0.6.1",
4
4
  "description": "Framework-agnostic reception chat widget (silent until clicked)",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -32,6 +32,6 @@
32
32
  "prepublishOnly": "tsc -b --force"
33
33
  },
34
34
  "devDependencies": {
35
- "happy-dom": "^20.14.0"
35
+ "happy-dom": "^20.14.5"
36
36
  }
37
37
  }