mirror of
https://github.com/perstarkse/minne.git
synced 2026-07-10 14:52:45 +02:00
refactor: async-stream and improved reference handling
This commit is contained in:
Generated
+1
@@ -6041,6 +6041,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-openai",
|
"async-openai",
|
||||||
|
"async-stream",
|
||||||
"axum",
|
"axum",
|
||||||
"axum-htmx",
|
"axum-htmx",
|
||||||
"axum_session",
|
"axum_session",
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ edition = "2021"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.94"
|
anyhow = "1.0.94"
|
||||||
async-openai = "0.24.1"
|
async-openai = "0.24.1"
|
||||||
|
async-stream = "0.3.6"
|
||||||
axum = { version = "0.7.5", features = ["multipart", "macros"] }
|
axum = { version = "0.7.5", features = ["multipart", "macros"] }
|
||||||
axum-htmx = "0.6.0"
|
axum-htmx = "0.6.0"
|
||||||
axum_session = "0.14.4"
|
axum_session = "0.14.4"
|
||||||
|
|||||||
+132
-123
@@ -2057,6 +2057,21 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.indicator {
|
||||||
|
position: relative;
|
||||||
|
display: inline-flex;
|
||||||
|
width: max-content;
|
||||||
|
:where(.indicator-item) {
|
||||||
|
z-index: 1;
|
||||||
|
position: absolute;
|
||||||
|
white-space: nowrap;
|
||||||
|
top: var(--inidicator-t, 0);
|
||||||
|
bottom: var(--inidicator-b, auto);
|
||||||
|
left: var(--inidicator-s, auto);
|
||||||
|
right: var(--inidicator-e, 0);
|
||||||
|
translate: var(--inidicator-x, 50%) var(--indicator-y, -50%);
|
||||||
|
}
|
||||||
|
}
|
||||||
.collapse-title {
|
.collapse-title {
|
||||||
grid-column-start: 1;
|
grid-column-start: 1;
|
||||||
grid-row-start: 1;
|
grid-row-start: 1;
|
||||||
@@ -2158,21 +2173,9 @@
|
|||||||
.top-1 {
|
.top-1 {
|
||||||
top: calc(var(--spacing) * 1);
|
top: calc(var(--spacing) * 1);
|
||||||
}
|
}
|
||||||
.top-2 {
|
|
||||||
top: calc(var(--spacing) * 2);
|
|
||||||
}
|
|
||||||
.top-5 {
|
|
||||||
top: calc(var(--spacing) * 5);
|
|
||||||
}
|
|
||||||
.top-8 {
|
|
||||||
top: calc(var(--spacing) * 8);
|
|
||||||
}
|
|
||||||
.top-9 {
|
.top-9 {
|
||||||
top: calc(var(--spacing) * 9);
|
top: calc(var(--spacing) * 9);
|
||||||
}
|
}
|
||||||
.top-10 {
|
|
||||||
top: calc(var(--spacing) * 10);
|
|
||||||
}
|
|
||||||
.right-0 {
|
.right-0 {
|
||||||
right: calc(var(--spacing) * 0);
|
right: calc(var(--spacing) * 0);
|
||||||
}
|
}
|
||||||
@@ -2541,33 +2544,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.z-0 {
|
|
||||||
z-index: 0;
|
|
||||||
}
|
|
||||||
.z-1 {
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
.z-2 {
|
|
||||||
z-index: 2;
|
|
||||||
}
|
|
||||||
.z-3 {
|
|
||||||
z-index: 3;
|
|
||||||
}
|
|
||||||
.z-5 {
|
|
||||||
z-index: 5;
|
|
||||||
}
|
|
||||||
.z-20 {
|
.z-20 {
|
||||||
z-index: 20;
|
z-index: 20;
|
||||||
}
|
}
|
||||||
.z-50 {
|
.z-50 {
|
||||||
z-index: 50;
|
z-index: 50;
|
||||||
}
|
}
|
||||||
.z-\[\12 ަ��\$���w�����T�\:��G��\1e \] {
|
|
||||||
z-index: ަ��$���w�����T�:��G��;
|
|
||||||
}
|
|
||||||
.z-\[\12 ަ��\$���ӕ\/\12 h�Jc�\1a \\���\=�i\] {
|
|
||||||
z-index: ަ��$���ӕ/h�Jc�\���=�i;
|
|
||||||
}
|
|
||||||
.modal-box {
|
.modal-box {
|
||||||
grid-column-start: 1;
|
grid-column-start: 1;
|
||||||
grid-row-start: 1;
|
grid-row-start: 1;
|
||||||
@@ -2639,9 +2621,6 @@
|
|||||||
.list-col-wrap {
|
.list-col-wrap {
|
||||||
grid-row-start: 2;
|
grid-row-start: 2;
|
||||||
}
|
}
|
||||||
.float-left {
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
.container {
|
.container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@media (width >= 40rem) {
|
@media (width >= 40rem) {
|
||||||
@@ -2660,12 +2639,6 @@
|
|||||||
max-width: 96rem;
|
max-width: 96rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.m-2 {
|
|
||||||
margin: calc(var(--spacing) * 2);
|
|
||||||
}
|
|
||||||
.m-9 {
|
|
||||||
margin: calc(var(--spacing) * 9);
|
|
||||||
}
|
|
||||||
.filter {
|
.filter {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
@@ -2783,6 +2756,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.my-0 {
|
||||||
|
margin-block: calc(var(--spacing) * 0);
|
||||||
|
}
|
||||||
|
.my-2 {
|
||||||
|
margin-block: calc(var(--spacing) * 2);
|
||||||
|
}
|
||||||
.my-4 {
|
.my-4 {
|
||||||
margin-block: calc(var(--spacing) * 4);
|
margin-block: calc(var(--spacing) * 4);
|
||||||
}
|
}
|
||||||
@@ -3679,6 +3658,9 @@
|
|||||||
font-size: var(--text-xs);
|
font-size: var(--text-xs);
|
||||||
line-height: var(--tw-leading, var(--text-xs--line-height));
|
line-height: var(--tw-leading, var(--text-xs--line-height));
|
||||||
}
|
}
|
||||||
|
.mt-1 {
|
||||||
|
margin-top: calc(var(--spacing) * 1);
|
||||||
|
}
|
||||||
.mt-2 {
|
.mt-2 {
|
||||||
margin-top: calc(var(--spacing) * 2);
|
margin-top: calc(var(--spacing) * 2);
|
||||||
}
|
}
|
||||||
@@ -3719,6 +3701,9 @@
|
|||||||
.mb-10 {
|
.mb-10 {
|
||||||
margin-bottom: calc(var(--spacing) * 10);
|
margin-bottom: calc(var(--spacing) * 10);
|
||||||
}
|
}
|
||||||
|
.ml-1 {
|
||||||
|
margin-left: calc(var(--spacing) * 1);
|
||||||
|
}
|
||||||
.alert {
|
.alert {
|
||||||
display: grid;
|
display: grid;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -3814,20 +3799,6 @@
|
|||||||
background-image: radial-gradient( circle at 35% 30%, oklch(1 0 0 / calc(var(--depth) * 0.5)), transparent );
|
background-image: radial-gradient( circle at 35% 30%, oklch(1 0 0 / calc(var(--depth) * 0.5)), transparent );
|
||||||
box-shadow: 0 2px 3px -1px color-mix(in oklab, currentColor calc(var(--depth) * 100%), transparent);
|
box-shadow: 0 2px 3px -1px color-mix(in oklab, currentColor calc(var(--depth) * 100%), transparent);
|
||||||
}
|
}
|
||||||
.status\! {
|
|
||||||
display: inline-block !important;
|
|
||||||
aspect-ratio: 1 / 1 !important;
|
|
||||||
width: calc(0.25rem * 2) !important;
|
|
||||||
height: calc(0.25rem * 2) !important;
|
|
||||||
border-radius: var(--radius-selector) !important;
|
|
||||||
background-color: color-mix(in oklab, var(--color-base-content) 20%, transparent) !important;
|
|
||||||
background-position: center !important;
|
|
||||||
background-repeat: no-repeat !important;
|
|
||||||
vertical-align: middle !important;
|
|
||||||
color: color-mix(in oklab, var(--color-black) 30%, transparent) !important;
|
|
||||||
background-image: radial-gradient( circle at 35% 30%, oklch(1 0 0 / calc(var(--depth) * 0.5)), transparent ) !important;
|
|
||||||
box-shadow: 0 2px 3px -1px color-mix(in oklab, currentColor calc(var(--depth) * 100%), transparent) !important;
|
|
||||||
}
|
|
||||||
.kbd {
|
.kbd {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -4089,27 +4060,18 @@
|
|||||||
width: calc(var(--spacing) * 10);
|
width: calc(var(--spacing) * 10);
|
||||||
height: calc(var(--spacing) * 10);
|
height: calc(var(--spacing) * 10);
|
||||||
}
|
}
|
||||||
.h-4 {
|
.h-3 {
|
||||||
height: calc(var(--spacing) * 4);
|
height: calc(var(--spacing) * 3);
|
||||||
}
|
}
|
||||||
.h-5 {
|
.h-5 {
|
||||||
height: calc(var(--spacing) * 5);
|
height: calc(var(--spacing) * 5);
|
||||||
}
|
}
|
||||||
.h-6 {
|
|
||||||
height: calc(var(--spacing) * 6);
|
|
||||||
}
|
|
||||||
.h-9 {
|
|
||||||
height: calc(var(--spacing) * 9);
|
|
||||||
}
|
|
||||||
.h-24 {
|
.h-24 {
|
||||||
height: calc(var(--spacing) * 24);
|
height: calc(var(--spacing) * 24);
|
||||||
}
|
}
|
||||||
.h-32 {
|
.h-32 {
|
||||||
height: calc(var(--spacing) * 32);
|
height: calc(var(--spacing) * 32);
|
||||||
}
|
}
|
||||||
.h-36 {
|
|
||||||
height: calc(var(--spacing) * 36);
|
|
||||||
}
|
|
||||||
.min-h-\[100dvh\] {
|
.min-h-\[100dvh\] {
|
||||||
min-height: 100dvh;
|
min-height: 100dvh;
|
||||||
}
|
}
|
||||||
@@ -4122,11 +4084,8 @@
|
|||||||
.loading-sm {
|
.loading-sm {
|
||||||
width: calc(var(--size-selector, 0.25rem) * 5);
|
width: calc(var(--size-selector, 0.25rem) * 5);
|
||||||
}
|
}
|
||||||
.w-1 {
|
.w-3 {
|
||||||
width: calc(var(--spacing) * 1);
|
width: calc(var(--spacing) * 3);
|
||||||
}
|
|
||||||
.w-2 {
|
|
||||||
width: calc(var(--spacing) * 2);
|
|
||||||
}
|
}
|
||||||
.w-5 {
|
.w-5 {
|
||||||
width: calc(var(--spacing) * 5);
|
width: calc(var(--spacing) * 5);
|
||||||
@@ -4137,9 +4096,6 @@
|
|||||||
.w-72 {
|
.w-72 {
|
||||||
width: calc(var(--spacing) * 72);
|
width: calc(var(--spacing) * 72);
|
||||||
}
|
}
|
||||||
.w-\[���\}�C�9JO\1 �j\] {
|
|
||||||
width: ���}�C�9JO�j;
|
|
||||||
}
|
|
||||||
.w-full {
|
.w-full {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
@@ -4152,6 +4108,18 @@
|
|||||||
.max-w-3xl {
|
.max-w-3xl {
|
||||||
max-width: var(--container-3xl);
|
max-width: var(--container-3xl);
|
||||||
}
|
}
|
||||||
|
.max-w-\[10ch\] {
|
||||||
|
max-width: 10ch;
|
||||||
|
}
|
||||||
|
.max-w-\[20ch\] {
|
||||||
|
max-width: 20ch;
|
||||||
|
}
|
||||||
|
.max-w-\[50px\] {
|
||||||
|
max-width: 50px;
|
||||||
|
}
|
||||||
|
.max-w-\[90\%\] {
|
||||||
|
max-width: 90%;
|
||||||
|
}
|
||||||
.max-w-\[160px\] {
|
.max-w-\[160px\] {
|
||||||
max-width: 160px;
|
max-width: 160px;
|
||||||
}
|
}
|
||||||
@@ -4188,6 +4156,9 @@
|
|||||||
--tw-scale-z: 75%;
|
--tw-scale-z: 75%;
|
||||||
scale: var(--tw-scale-x) var(--tw-scale-y);
|
scale: var(--tw-scale-x) var(--tw-scale-y);
|
||||||
}
|
}
|
||||||
|
.rotate-180 {
|
||||||
|
rotate: 180deg;
|
||||||
|
}
|
||||||
.swap-rotate {
|
.swap-rotate {
|
||||||
.swap-on, .swap-indeterminate, input:indeterminate ~ .swap-on {
|
.swap-on, .swap-indeterminate, input:indeterminate ~ .swap-on {
|
||||||
rotate: 45deg;
|
rotate: 45deg;
|
||||||
@@ -4225,6 +4196,9 @@
|
|||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-position-x: -50%;
|
background-position-x: -50%;
|
||||||
}
|
}
|
||||||
|
.animate-pulse {
|
||||||
|
animation: var(--animate-pulse);
|
||||||
|
}
|
||||||
.link {
|
.link {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
text-decoration-line: underline;
|
text-decoration-line: underline;
|
||||||
@@ -4278,6 +4252,9 @@
|
|||||||
.justify-start {
|
.justify-start {
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
}
|
}
|
||||||
|
.gap-1 {
|
||||||
|
gap: calc(var(--spacing) * 1);
|
||||||
|
}
|
||||||
.gap-2 {
|
.gap-2 {
|
||||||
gap: calc(var(--spacing) * 2);
|
gap: calc(var(--spacing) * 2);
|
||||||
}
|
}
|
||||||
@@ -4337,6 +4314,9 @@
|
|||||||
.overflow-clip {
|
.overflow-clip {
|
||||||
overflow: clip;
|
overflow: clip;
|
||||||
}
|
}
|
||||||
|
.overflow-hidden {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
.overflow-x-auto {
|
.overflow-x-auto {
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
}
|
}
|
||||||
@@ -4355,9 +4335,8 @@
|
|||||||
.rounded-lg {
|
.rounded-lg {
|
||||||
border-radius: var(--radius-lg);
|
border-radius: var(--radius-lg);
|
||||||
}
|
}
|
||||||
.rounded-t-2xl {
|
.rounded-md {
|
||||||
border-top-left-radius: var(--radius-2xl);
|
border-radius: var(--radius-md);
|
||||||
border-top-right-radius: var(--radius-2xl);
|
|
||||||
}
|
}
|
||||||
.rounded-t-none {
|
.rounded-t-none {
|
||||||
border-top-left-radius: 0;
|
border-top-left-radius: 0;
|
||||||
@@ -4371,10 +4350,6 @@
|
|||||||
border-style: var(--tw-border-style);
|
border-style: var(--tw-border-style);
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
}
|
}
|
||||||
.border-2 {
|
|
||||||
border-style: var(--tw-border-style);
|
|
||||||
border-width: 2px;
|
|
||||||
}
|
|
||||||
.textarea-ghost {
|
.textarea-ghost {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
@@ -4391,14 +4366,17 @@
|
|||||||
color: var(--color-success-content);
|
color: var(--color-success-content);
|
||||||
--alert-color: var(--color-success);
|
--alert-color: var(--color-success);
|
||||||
}
|
}
|
||||||
|
.border-base-300 {
|
||||||
|
border-color: var(--color-base-300);
|
||||||
|
}
|
||||||
.border-base-content {
|
.border-base-content {
|
||||||
border-color: var(--color-base-content);
|
border-color: var(--color-base-content);
|
||||||
}
|
}
|
||||||
.border-base-content\/5 {
|
.border-base-content\/5 {
|
||||||
border-color: color-mix(in oklab, var(--color-base-content) 5%, transparent);
|
border-color: color-mix(in oklab, var(--color-base-content) 5%, transparent);
|
||||||
}
|
}
|
||||||
.border-primary {
|
.border-gray-200 {
|
||||||
border-color: var(--color-primary);
|
border-color: var(--color-gray-200);
|
||||||
}
|
}
|
||||||
.bg-accent {
|
.bg-accent {
|
||||||
background-color: var(--color-accent);
|
background-color: var(--color-accent);
|
||||||
@@ -4409,6 +4387,12 @@
|
|||||||
.bg-base-200 {
|
.bg-base-200 {
|
||||||
background-color: var(--color-base-200);
|
background-color: var(--color-base-200);
|
||||||
}
|
}
|
||||||
|
.bg-gray-50 {
|
||||||
|
background-color: var(--color-gray-50);
|
||||||
|
}
|
||||||
|
.bg-gray-200 {
|
||||||
|
background-color: var(--color-gray-200);
|
||||||
|
}
|
||||||
.bg-secondary {
|
.bg-secondary {
|
||||||
background-color: var(--color-secondary);
|
background-color: var(--color-secondary);
|
||||||
}
|
}
|
||||||
@@ -4419,12 +4403,6 @@
|
|||||||
--tw-gradient-position: to right in oklab,;
|
--tw-gradient-position: to right in oklab,;
|
||||||
background-image: linear-gradient(var(--tw-gradient-stops));
|
background-image: linear-gradient(var(--tw-gradient-stops));
|
||||||
}
|
}
|
||||||
.\!bg-none {
|
|
||||||
background-image: none !important;
|
|
||||||
}
|
|
||||||
.bg-none {
|
|
||||||
background-image: none;
|
|
||||||
}
|
|
||||||
.from-primary {
|
.from-primary {
|
||||||
--tw-gradient-from: var(--color-primary);
|
--tw-gradient-from: var(--color-primary);
|
||||||
--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position,) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
|
--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position,) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
|
||||||
@@ -4451,18 +4429,12 @@
|
|||||||
.p-2 {
|
.p-2 {
|
||||||
padding: calc(var(--spacing) * 2);
|
padding: calc(var(--spacing) * 2);
|
||||||
}
|
}
|
||||||
.p-3 {
|
|
||||||
padding: calc(var(--spacing) * 3);
|
|
||||||
}
|
|
||||||
.p-4 {
|
.p-4 {
|
||||||
padding: calc(var(--spacing) * 4);
|
padding: calc(var(--spacing) * 4);
|
||||||
}
|
}
|
||||||
.p-5 {
|
.p-5 {
|
||||||
padding: calc(var(--spacing) * 5);
|
padding: calc(var(--spacing) * 5);
|
||||||
}
|
}
|
||||||
.p-9 {
|
|
||||||
padding: calc(var(--spacing) * 9);
|
|
||||||
}
|
|
||||||
.menu-title {
|
.menu-title {
|
||||||
padding-inline: calc(0.25rem * 3);
|
padding-inline: calc(0.25rem * 3);
|
||||||
padding-block: calc(0.25rem * 2);
|
padding-block: calc(0.25rem * 2);
|
||||||
@@ -4470,6 +4442,11 @@
|
|||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
.badge-sm {
|
||||||
|
--size: calc(var(--size-selector, 0.25rem) * 5);
|
||||||
|
font-size: 0.75rem;
|
||||||
|
padding-inline: calc(0.25rem * 2.5 - var(--border));
|
||||||
|
}
|
||||||
.badge-xs {
|
.badge-xs {
|
||||||
--size: calc(var(--size-selector, 0.25rem) * 4);
|
--size: calc(var(--size-selector, 0.25rem) * 4);
|
||||||
font-size: 0.625rem;
|
font-size: 0.625rem;
|
||||||
@@ -4493,14 +4470,23 @@
|
|||||||
.px-1 {
|
.px-1 {
|
||||||
padding-inline: calc(var(--spacing) * 1);
|
padding-inline: calc(var(--spacing) * 1);
|
||||||
}
|
}
|
||||||
|
.px-2 {
|
||||||
|
padding-inline: calc(var(--spacing) * 2);
|
||||||
|
}
|
||||||
.px-4 {
|
.px-4 {
|
||||||
padding-inline: calc(var(--spacing) * 4);
|
padding-inline: calc(var(--spacing) * 4);
|
||||||
}
|
}
|
||||||
|
.py-0 {
|
||||||
|
padding-block: calc(var(--spacing) * 0);
|
||||||
|
}
|
||||||
|
.py-1 {
|
||||||
|
padding-block: calc(var(--spacing) * 1);
|
||||||
|
}
|
||||||
.py-4 {
|
.py-4 {
|
||||||
padding-block: calc(var(--spacing) * 4);
|
padding-block: calc(var(--spacing) * 4);
|
||||||
}
|
}
|
||||||
.pt-5 {
|
.pt-1 {
|
||||||
padding-top: calc(var(--spacing) * 5);
|
padding-top: calc(var(--spacing) * 1);
|
||||||
}
|
}
|
||||||
.pt-10 {
|
.pt-10 {
|
||||||
padding-top: calc(var(--spacing) * 10);
|
padding-top: calc(var(--spacing) * 10);
|
||||||
@@ -4517,18 +4503,15 @@
|
|||||||
.pb-0 {
|
.pb-0 {
|
||||||
padding-bottom: calc(var(--spacing) * 0);
|
padding-bottom: calc(var(--spacing) * 0);
|
||||||
}
|
}
|
||||||
.pb-4 {
|
|
||||||
padding-bottom: calc(var(--spacing) * 4);
|
|
||||||
}
|
|
||||||
.pb-8 {
|
|
||||||
padding-bottom: calc(var(--spacing) * 8);
|
|
||||||
}
|
|
||||||
.pb-10 {
|
.pb-10 {
|
||||||
padding-bottom: calc(var(--spacing) * 10);
|
padding-bottom: calc(var(--spacing) * 10);
|
||||||
}
|
}
|
||||||
.text-center {
|
.text-center {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
.text-left {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
.font-satoshi {
|
.font-satoshi {
|
||||||
font-family: Satoshi, sans-serif;
|
font-family: Satoshi, sans-serif;
|
||||||
}
|
}
|
||||||
@@ -4586,6 +4569,10 @@
|
|||||||
--tw-font-weight: var(--font-weight-light);
|
--tw-font-weight: var(--font-weight-light);
|
||||||
font-weight: var(--font-weight-light);
|
font-weight: var(--font-weight-light);
|
||||||
}
|
}
|
||||||
|
.font-medium {
|
||||||
|
--tw-font-weight: var(--font-weight-medium);
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
}
|
||||||
.font-semibold {
|
.font-semibold {
|
||||||
--tw-font-weight: var(--font-weight-semibold);
|
--tw-font-weight: var(--font-weight-semibold);
|
||||||
font-weight: var(--font-weight-semibold);
|
font-weight: var(--font-weight-semibold);
|
||||||
@@ -4603,6 +4590,10 @@
|
|||||||
.whitespace-pre-wrap {
|
.whitespace-pre-wrap {
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
.badge-neutral {
|
||||||
|
--badge-color: var(--color-neutral);
|
||||||
|
color: var(--color-neutral-content);
|
||||||
|
}
|
||||||
.badge-primary {
|
.badge-primary {
|
||||||
--badge-color: var(--color-primary);
|
--badge-color: var(--color-primary);
|
||||||
color: var(--color-primary-content);
|
color: var(--color-primary-content);
|
||||||
@@ -4625,12 +4616,21 @@
|
|||||||
.text-base-content\/60 {
|
.text-base-content\/60 {
|
||||||
color: color-mix(in oklab, var(--color-base-content) 60%, transparent);
|
color: color-mix(in oklab, var(--color-base-content) 60%, transparent);
|
||||||
}
|
}
|
||||||
|
.text-blue-500 {
|
||||||
|
color: var(--color-blue-500);
|
||||||
|
}
|
||||||
.text-error {
|
.text-error {
|
||||||
color: var(--color-error);
|
color: var(--color-error);
|
||||||
}
|
}
|
||||||
|
.text-gray-400 {
|
||||||
|
color: var(--color-gray-400);
|
||||||
|
}
|
||||||
.text-gray-500 {
|
.text-gray-500 {
|
||||||
color: var(--color-gray-500);
|
color: var(--color-gray-500);
|
||||||
}
|
}
|
||||||
|
.text-gray-700 {
|
||||||
|
color: var(--color-gray-700);
|
||||||
|
}
|
||||||
.text-primary {
|
.text-primary {
|
||||||
color: var(--color-primary);
|
color: var(--color-primary);
|
||||||
}
|
}
|
||||||
@@ -4672,6 +4672,9 @@
|
|||||||
.underline {
|
.underline {
|
||||||
text-decoration-line: underline;
|
text-decoration-line: underline;
|
||||||
}
|
}
|
||||||
|
.opacity-50 {
|
||||||
|
opacity: 50%;
|
||||||
|
}
|
||||||
.opacity-60 {
|
.opacity-60 {
|
||||||
opacity: 60%;
|
opacity: 60%;
|
||||||
}
|
}
|
||||||
@@ -4722,6 +4725,11 @@
|
|||||||
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
|
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
|
||||||
transition-duration: var(--tw-duration, var(--default-transition-duration));
|
transition-duration: var(--tw-duration, var(--default-transition-duration));
|
||||||
}
|
}
|
||||||
|
.transition-transform {
|
||||||
|
transition-property: transform, translate, scale, rotate;
|
||||||
|
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
|
||||||
|
transition-duration: var(--tw-duration, var(--default-transition-duration));
|
||||||
|
}
|
||||||
.ease-in {
|
.ease-in {
|
||||||
--tw-ease: var(--ease-in);
|
--tw-ease: var(--ease-in);
|
||||||
transition-timing-function: var(--ease-in);
|
transition-timing-function: var(--ease-in);
|
||||||
@@ -4734,9 +4742,6 @@
|
|||||||
--tw-ease: var(--ease-out);
|
--tw-ease: var(--ease-out);
|
||||||
transition-timing-function: var(--ease-out);
|
transition-timing-function: var(--ease-out);
|
||||||
}
|
}
|
||||||
.\[a-zA-Z\:\\-\] {
|
|
||||||
a-zA-Z: \-;
|
|
||||||
}
|
|
||||||
.btn-accent {
|
.btn-accent {
|
||||||
--btn-color: var(--color-accent);
|
--btn-color: var(--color-accent);
|
||||||
--btn-fg: var(--color-accent-content);
|
--btn-fg: var(--color-accent-content);
|
||||||
@@ -4785,6 +4790,13 @@
|
|||||||
--tw-prose-td-borders: color-mix(in oklab, var(--color-base-content) 20%, transparent);
|
--tw-prose-td-borders: color-mix(in oklab, var(--color-base-content) 20%, transparent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.hover\:text-blue-700 {
|
||||||
|
&:hover {
|
||||||
|
@media (hover: hover) {
|
||||||
|
color: var(--color-blue-700);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
.hover\:text-gray-700 {
|
.hover\:text-gray-700 {
|
||||||
&:hover {
|
&:hover {
|
||||||
@media (hover: hover) {
|
@media (hover: hover) {
|
||||||
@@ -4792,21 +4804,24 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.hover\:underline {
|
||||||
|
&:hover {
|
||||||
|
@media (hover: hover) {
|
||||||
|
text-decoration-line: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.focus\:outline-none {
|
||||||
|
&:focus {
|
||||||
|
--tw-outline-style: none;
|
||||||
|
outline-style: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
.sm\:mt-4 {
|
.sm\:mt-4 {
|
||||||
@media (width >= 40rem) {
|
@media (width >= 40rem) {
|
||||||
margin-top: calc(var(--spacing) * 4);
|
margin-top: calc(var(--spacing) * 4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.sm\:mb-2 {
|
|
||||||
@media (width >= 40rem) {
|
|
||||||
margin-bottom: calc(var(--spacing) * 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.sm\:mb-5 {
|
|
||||||
@media (width >= 40rem) {
|
|
||||||
margin-bottom: calc(var(--spacing) * 5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.sm\:max-w-md {
|
.sm\:max-w-md {
|
||||||
@media (width >= 40rem) {
|
@media (width >= 40rem) {
|
||||||
max-width: var(--container-md);
|
max-width: var(--container-md);
|
||||||
@@ -4828,12 +4843,6 @@
|
|||||||
border-bottom-left-radius: var(--radius-2xl);
|
border-bottom-left-radius: var(--radius-2xl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.sm\:rounded-b-none {
|
|
||||||
@media (width >= 40rem) {
|
|
||||||
border-bottom-right-radius: 0;
|
|
||||||
border-bottom-left-radius: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.sm\:px-0 {
|
.sm\:px-0 {
|
||||||
@media (width >= 40rem) {
|
@media (width >= 40rem) {
|
||||||
padding-inline: calc(var(--spacing) * 0);
|
padding-inline: calc(var(--spacing) * 0);
|
||||||
|
|||||||
@@ -1,15 +1,24 @@
|
|||||||
use std::{pin::Pin, time::Duration};
|
use std::{pin::Pin, sync::Arc, time::Duration};
|
||||||
|
|
||||||
|
use async_stream::stream;
|
||||||
use axum::{
|
use axum::{
|
||||||
extract::{Query, State},
|
extract::{Query, State},
|
||||||
response::{sse::Event, Sse},
|
response::{
|
||||||
|
sse::{Event, KeepAlive},
|
||||||
|
Sse,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use axum_session_auth::AuthSession;
|
use axum_session_auth::AuthSession;
|
||||||
use axum_session_surreal::SessionSurrealPool;
|
use axum_session_surreal::SessionSurrealPool;
|
||||||
use futures::{stream, Stream, StreamExt, TryStreamExt};
|
use futures::{
|
||||||
|
stream::{self, once},
|
||||||
|
Stream, StreamExt, TryStreamExt,
|
||||||
|
};
|
||||||
use json_stream_parser::JsonStreamParser;
|
use json_stream_parser::JsonStreamParser;
|
||||||
use serde::Deserialize;
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_json::from_str;
|
||||||
use surrealdb::{engine::any::Any, Surreal};
|
use surrealdb::{engine::any::Any, Surreal};
|
||||||
|
use tokio::sync::{mpsc::channel, Mutex};
|
||||||
use tracing::{error, info};
|
use tracing::{error, info};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -19,7 +28,7 @@ use crate::{
|
|||||||
create_chat_request, create_user_message, format_entities_json, LLMResponseFormat,
|
create_chat_request, create_user_message, format_entities_json, LLMResponseFormat,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
server::AppState,
|
server::{routes::html::render_template, AppState},
|
||||||
storage::{
|
storage::{
|
||||||
db::{get_item, store_item, SurrealDbClient},
|
db::{get_item, store_item, SurrealDbClient},
|
||||||
types::{
|
types::{
|
||||||
@@ -29,6 +38,7 @@ use crate::{
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Error handling function
|
||||||
fn create_error_stream(
|
fn create_error_stream(
|
||||||
message: impl Into<String>,
|
message: impl Into<String>,
|
||||||
) -> Pin<Box<dyn Stream<Item = Result<Event, axum::Error>> + Send>> {
|
) -> Pin<Box<dyn Stream<Item = Result<Event, axum::Error>> + Send>> {
|
||||||
@@ -127,8 +137,9 @@ pub async fn get_response_stream(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 5. Create channel for collecting complete response
|
// 5. Create channel for collecting complete response
|
||||||
let (tx, mut rx) = tokio::sync::mpsc::channel::<String>(1000);
|
let (tx, mut rx) = channel::<String>(1000);
|
||||||
let tx_clone = tx.clone();
|
let tx_clone = tx.clone();
|
||||||
|
let (tx_final, mut rx_final) = channel::<Vec<String>>(1);
|
||||||
|
|
||||||
// 6. Set up the collection task for DB storage
|
// 6. Set up the collection task for DB storage
|
||||||
let db_client = state.surreal_db_client.clone();
|
let db_client = state.surreal_db_client.clone();
|
||||||
@@ -142,13 +153,15 @@ pub async fn get_response_stream(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Try to extract structured data
|
// Try to extract structured data
|
||||||
if let Ok(response) = serde_json::from_str::<LLMResponseFormat>(&full_json) {
|
if let Ok(response) = from_str::<LLMResponseFormat>(&full_json) {
|
||||||
let references: Vec<String> = response
|
let references: Vec<String> = response
|
||||||
.references
|
.references
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|r| r.reference)
|
.map(|r| r.reference)
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
let _ = tx_final.send(references.clone()).await;
|
||||||
|
|
||||||
let ai_message = Message::new(
|
let ai_message = Message::new(
|
||||||
user_message.conversation_id,
|
user_message.conversation_id,
|
||||||
MessageRole::AI,
|
MessageRole::AI,
|
||||||
@@ -176,7 +189,7 @@ pub async fn get_response_stream(
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Create a shared state for tracking the JSON parsing
|
// Create a shared state for tracking the JSON parsing
|
||||||
let json_state = std::sync::Arc::new(tokio::sync::Mutex::new(StreamParserState::new()));
|
let json_state = Arc::new(Mutex::new(StreamParserState::new()));
|
||||||
|
|
||||||
// 7. Create the response event stream
|
// 7. Create the response event stream
|
||||||
let event_stream = openai_stream
|
let event_stream = openai_stream
|
||||||
@@ -185,7 +198,7 @@ pub async fn get_response_stream(
|
|||||||
let tx_storage = tx_clone.clone();
|
let tx_storage = tx_clone.clone();
|
||||||
let json_state = json_state.clone();
|
let json_state = json_state.clone();
|
||||||
|
|
||||||
async move {
|
stream! {
|
||||||
match result {
|
match result {
|
||||||
Ok(response) => {
|
Ok(response) => {
|
||||||
let content = response
|
let content = response
|
||||||
@@ -200,29 +213,71 @@ pub async fn get_response_stream(
|
|||||||
|
|
||||||
// Process through JSON parser
|
// Process through JSON parser
|
||||||
let mut state = json_state.lock().await;
|
let mut state = json_state.lock().await;
|
||||||
|
|
||||||
let display_content = state.process_chunk(&content);
|
let display_content = state.process_chunk(&content);
|
||||||
drop(state);
|
drop(state);
|
||||||
if !display_content.is_empty() {
|
if !display_content.is_empty() {
|
||||||
return Ok(Event::default()
|
yield Ok(Event::default()
|
||||||
.event("chat_message")
|
.event("chat_message")
|
||||||
.data(display_content));
|
.data(display_content));
|
||||||
}
|
}
|
||||||
|
// If display_content is empty, don't yield anything
|
||||||
// Empty or filtered content
|
|
||||||
Ok(Event::default().event("chat_message").data(""))
|
|
||||||
} else {
|
|
||||||
Ok(Event::default().event("chat_message").data(""))
|
|
||||||
}
|
}
|
||||||
|
// If content is empty, don't yield anything
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
yield Ok(Event::default()
|
||||||
|
.event("error")
|
||||||
|
.data(format!("Stream error: {}", e)));
|
||||||
}
|
}
|
||||||
Err(e) => Ok(Event::default()
|
|
||||||
.event("error")
|
|
||||||
.data(format!("Stream error: {}", e))),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.buffered(10)
|
.flatten()
|
||||||
.chain(stream::once(async {
|
.chain(stream::once(async move {
|
||||||
|
if let Some(references) = rx_final.recv().await {
|
||||||
|
// Don't send any event if references is empty
|
||||||
|
if references.is_empty() {
|
||||||
|
return Ok(Event::default().event("empty")); // This event won't be sent
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare data for template
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct ReferenceData {
|
||||||
|
references: Vec<String>,
|
||||||
|
user_message_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render template with references
|
||||||
|
match render_template(
|
||||||
|
"chat/reference_list.html",
|
||||||
|
ReferenceData {
|
||||||
|
references,
|
||||||
|
user_message_id: user_message.id,
|
||||||
|
},
|
||||||
|
state.templates.clone(),
|
||||||
|
) {
|
||||||
|
Ok(html) => {
|
||||||
|
// Extract the String from Html<String>
|
||||||
|
let html_string = html.0; // Convert Html<String> to String
|
||||||
|
|
||||||
|
// Return the rendered HTML
|
||||||
|
Ok(Event::default().event("references").data(html_string))
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
// Handle template rendering error
|
||||||
|
Ok(Event::default()
|
||||||
|
.event("error")
|
||||||
|
.data("Failed to render references"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Handle case where no references were received
|
||||||
|
Ok(Event::default()
|
||||||
|
.event("error")
|
||||||
|
.data("Failed to retrieve references"))
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.chain(once(async {
|
||||||
Ok(Event::default()
|
Ok(Event::default()
|
||||||
.event("close_stream")
|
.event("close_stream")
|
||||||
.data("Stream complete"))
|
.data("Stream complete"))
|
||||||
@@ -230,7 +285,7 @@ pub async fn get_response_stream(
|
|||||||
|
|
||||||
info!("OpenAI streaming started");
|
info!("OpenAI streaming started");
|
||||||
Sse::new(event_stream.boxed()).keep_alive(
|
Sse::new(event_stream.boxed()).keep_alive(
|
||||||
axum::response::sse::KeepAlive::new()
|
KeepAlive::new()
|
||||||
.interval(Duration::from_secs(15))
|
.interval(Duration::from_secs(15))
|
||||||
.text("keep-alive"),
|
.text("keep-alive"),
|
||||||
)
|
)
|
||||||
@@ -259,7 +314,6 @@ impl StreamParserState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get the current state of the JSON
|
// Get the current state of the JSON
|
||||||
// The get_result() method returns a &Value, not a Result
|
|
||||||
let json = self.parser.get_result();
|
let json = self.parser.get_result();
|
||||||
|
|
||||||
// Check if we're in the answer field
|
// Check if we're in the answer field
|
||||||
@@ -283,46 +337,3 @@ impl StreamParserState {
|
|||||||
String::new()
|
String::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 7. Create the response event stream
|
|
||||||
// let event_stream = openai_stream
|
|
||||||
// .map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send + Sync>)
|
|
||||||
// .map(move |result| {
|
|
||||||
// let tx = tx_clone.clone();
|
|
||||||
// async move {
|
|
||||||
// match result {
|
|
||||||
// Ok(response) => {
|
|
||||||
// let content = response
|
|
||||||
// .choices
|
|
||||||
// .first()
|
|
||||||
// .and_then(|choice| choice.delta.content.clone())
|
|
||||||
// .unwrap_or_default();
|
|
||||||
|
|
||||||
// if !content.is_empty() {
|
|
||||||
// let _ = tx.send(content.clone()).await;
|
|
||||||
// Ok(Event::default().event("chat_message").data(content))
|
|
||||||
// } else {
|
|
||||||
// Ok(Event::default().event("chat_message").data(""))
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// Err(e) => Ok(Event::default()
|
|
||||||
// .event("error")
|
|
||||||
// .data(format!("Stream error: {}", e))),
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// .buffered(10)
|
|
||||||
// .chain(stream::once(async {
|
|
||||||
// Ok(Event::default()
|
|
||||||
// .event("close_stream")
|
|
||||||
// .data("Stream complete"))
|
|
||||||
// }));
|
|
||||||
|
|
||||||
// info!("OpenAI streaming started");
|
|
||||||
|
|
||||||
// Sse::new(event_stream.boxed()).keep_alive(
|
|
||||||
// axum::response::sse::KeepAlive::new()
|
|
||||||
// .interval(Duration::from_secs(15))
|
|
||||||
// .text("keep-alive"),
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<div class="relative my-2">
|
||||||
|
<button id="references-toggle-{{user_message_id}}"
|
||||||
|
class="text-xs text-blue-500 hover:text-blue-700 hover:underline focus:outline-none flex items-center">
|
||||||
|
References
|
||||||
|
{% include "icons/chevron_icon.html" %}
|
||||||
|
</button>
|
||||||
|
<div id="references-content-{{user_message_id}}" class="hidden max-w-full mt-1">
|
||||||
|
<div class="flex flex-wrap">
|
||||||
|
{% for reference in references %}
|
||||||
|
<span class="badge badge-sm text-xs truncate max-w-[20ch] overflow-hidden text-left block tooltip"
|
||||||
|
hx-get="/knowledge/{{reference}}" hx-trigger="mouseenter once delay:500ms"
|
||||||
|
hx-target="#tooltip-content-{{loop.index}}-{{user_message_id}}" hx-swap="innerHTML">
|
||||||
|
{{reference}}
|
||||||
|
<div id="tooltip-content-{{loop.index}}-{{user_message_id}}" class="tooltip-content">
|
||||||
|
<!-- Loading indicator while content is fetched -->
|
||||||
|
<div class="animate-pulse text-gray-400 text-xs">Loading...</div>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
document.getElementById('references-toggle-{{user_message_id}}').addEventListener('click', function () {
|
||||||
|
const content = document.getElementById('references-content-{{user_message_id}}');
|
||||||
|
const icon = document.getElementById('toggle-icon');
|
||||||
|
content.classList.toggle('hidden');
|
||||||
|
icon.classList.toggle('rotate-180');
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -1,14 +1,16 @@
|
|||||||
<div class="chat chat-end">
|
<div class="chat chat-end">
|
||||||
<div class="chat-header">User</div>
|
|
||||||
<div class="chat-bubble">
|
<div class="chat-bubble">
|
||||||
{{user_message.content}}
|
{{user_message.content}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="chat chat-start">
|
<div class="chat chat-start">
|
||||||
<div class="chat-header">AI</div>
|
<div hx-ext="sse" sse-connect="/chat/response-stream?message_id={{user_message.id}}" sse-close="close_stream"
|
||||||
<div class="chat-bubble" hx-ext="sse" sse-connect="/chat/response-stream?message_id={{user_message.id}}"
|
hx-swap="beforeend">
|
||||||
sse-swap="chat_message" sse-close="close_stream" hx-swap="beforeend">
|
<div class="chat-bubble" sse-swap="chat_message">
|
||||||
<span class="loading loading-dots loading-sm loading-id-{{user_message.id}}"></span>
|
<span class="loading loading-dots loading-sm loading-id-{{user_message.id}}"></span>
|
||||||
|
</div>
|
||||||
|
<div class="chat-footer opacity-50 max-w-[90%] flex-wrap" sse-swap="references">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<svg class="w-3 h-3 ml-1 transition-transform" id="toggle-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
|
||||||
|
fill="currentColor">
|
||||||
|
<path fill-rule="evenodd"
|
||||||
|
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
|
||||||
|
clip-rule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 326 B |
Reference in New Issue
Block a user