Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
chenpangpang
open-webui
Commits
9c1aa924
Unverified
Commit
9c1aa924
authored
Feb 22, 2024
by
Timothy Jaeryang Baek
Committed by
GitHub
Feb 22, 2024
Browse files
Merge branch 'main' into feat/delete-message
parents
a3a0e183
b0f3ae57
Changes
64
Hide whitespace changes
Inline
Side-by-side
Showing
20 changed files
with
573 additions
and
62 deletions
+573
-62
kubernetes/helm/values.yaml
kubernetes/helm/values.yaml
+13
-7
kubernetes/manifest/base/ollama-service.yaml
kubernetes/manifest/base/ollama-service.yaml
+1
-1
kubernetes/manifest/base/ollama-statefulset.yaml
kubernetes/manifest/base/ollama-statefulset.yaml
+7
-3
kubernetes/manifest/base/open-webui.yaml
kubernetes/manifest/base/open-webui.yaml
+1
-1
kubernetes/manifest/base/webui-deployment.yaml
kubernetes/manifest/base/webui-deployment.yaml
+19
-9
kubernetes/manifest/base/webui-ingress.yaml
kubernetes/manifest/base/webui-ingress.yaml
+4
-4
kubernetes/manifest/base/webui-pvc.yaml
kubernetes/manifest/base/webui-pvc.yaml
+12
-0
kubernetes/manifest/base/webui-service.yaml
kubernetes/manifest/base/webui-service.yaml
+3
-3
kubernetes/manifest/kustomization.yaml
kubernetes/manifest/kustomization.yaml
+1
-1
kubernetes/manifest/patches/ollama-statefulset-gpu.yaml
kubernetes/manifest/patches/ollama-statefulset-gpu.yaml
+1
-1
package-lock.json
package-lock.json
+2
-2
package.json
package.json
+2
-2
run.sh
run.sh
+2
-2
src/lib/apis/auths/index.ts
src/lib/apis/auths/index.ts
+57
-0
src/lib/apis/images/index.ts
src/lib/apis/images/index.ts
+266
-0
src/lib/apis/index.ts
src/lib/apis/index.ts
+2
-2
src/lib/apis/ollama/index.ts
src/lib/apis/ollama/index.ts
+12
-2
src/lib/components/admin/Settings/General.svelte
src/lib/components/admin/Settings/General.svelte
+33
-1
src/lib/components/chat/Messages.svelte
src/lib/components/chat/Messages.svelte
+23
-12
src/lib/components/chat/Messages/ResponseMessage.svelte
src/lib/components/chat/Messages/ResponseMessage.svelte
+112
-9
No files found.
kubernetes/helm/values.yaml
View file @
9c1aa924
namespace
:
o
llama-namespace
namespace
:
o
pen-webui
ollama
:
replicaCount
:
1
image
:
ollama/ollama:latest
servicePort
:
11434
resources
:
limi
ts
:
reques
ts
:
cpu
:
"
2000m"
memory
:
"
2Gi"
limits
:
cpu
:
"
4000m"
memory
:
"
4Gi"
nvidia.com/gpu
:
"
0"
volumeSize
:
1
Gi
volumeSize
:
30
Gi
nodeSelector
:
{}
tolerations
:
[]
service
:
...
...
@@ -19,19 +22,22 @@ ollama:
webui
:
replicaCount
:
1
image
:
ghcr.io/o
llama
-webui/o
llama
-webui:main
image
:
ghcr.io/o
pen
-webui/o
pen
-webui:main
servicePort
:
8080
resources
:
limi
ts
:
reques
ts
:
cpu
:
"
500m"
memory
:
"
500Mi"
limits
:
cpu
:
"
1000m"
memory
:
"
1Gi"
ingress
:
enabled
:
true
annotations
:
# Use appropriate annotations for your Ingress controller, e.g., for NGINX:
# nginx.ingress.kubernetes.io/rewrite-target: /
host
:
o
llama
.minikube.local
volumeSize
:
1
Gi
host
:
o
pen-webui
.minikube.local
volumeSize
:
2
Gi
nodeSelector
:
{}
tolerations
:
[]
service
:
...
...
kubernetes/manifest/base/ollama-service.yaml
View file @
9c1aa924
...
...
@@ -2,7 +2,7 @@ apiVersion: v1
kind
:
Service
metadata
:
name
:
ollama-service
namespace
:
o
llama-namespace
namespace
:
o
pen-webui
spec
:
selector
:
app
:
ollama
...
...
kubernetes/manifest/base/ollama-statefulset.yaml
View file @
9c1aa924
...
...
@@ -2,7 +2,7 @@ apiVersion: apps/v1
kind
:
StatefulSet
metadata
:
name
:
ollama
namespace
:
o
llama-namespace
namespace
:
o
pen-webui
spec
:
serviceName
:
"
ollama"
replicas
:
1
...
...
@@ -20,9 +20,13 @@ spec:
ports
:
-
containerPort
:
11434
resources
:
limi
ts
:
reques
ts
:
cpu
:
"
2000m"
memory
:
"
2Gi"
limits
:
cpu
:
"
4000m"
memory
:
"
4Gi"
nvidia.com/gpu
:
"
0"
volumeMounts
:
-
name
:
ollama-volume
mountPath
:
/root/.ollama
...
...
@@ -34,4 +38,4 @@ spec:
accessModes
:
[
"
ReadWriteOnce"
]
resources
:
requests
:
storage
:
1Gi
\ No newline at end of file
storage
:
30Gi
\ No newline at end of file
kubernetes/manifest/base/o
llama-namespace
.yaml
→
kubernetes/manifest/base/o
pen-webui
.yaml
View file @
9c1aa924
apiVersion
:
v1
kind
:
Namespace
metadata
:
name
:
ollama-namespace
\ No newline at end of file
name
:
open-webui
\ No newline at end of file
kubernetes/manifest/base/webui-deployment.yaml
View file @
9c1aa924
apiVersion
:
apps/v1
kind
:
Deployment
metadata
:
name
:
o
llama
-webui-deployment
namespace
:
o
llama-namespace
name
:
o
pen
-webui-deployment
namespace
:
o
pen-webui
spec
:
replicas
:
1
selector
:
matchLabels
:
app
:
o
llama
-webui
app
:
o
pen
-webui
template
:
metadata
:
labels
:
app
:
o
llama
-webui
app
:
o
pen
-webui
spec
:
containers
:
-
name
:
o
llama
-webui
image
:
ghcr.io/o
llama
-webui/o
llama
-webui:main
-
name
:
o
pen
-webui
image
:
ghcr.io/o
pen
-webui/o
pen
-webui:main
ports
:
-
containerPort
:
8080
resources
:
limi
ts
:
reques
ts
:
cpu
:
"
500m"
memory
:
"
500Mi"
limits
:
cpu
:
"
1000m"
memory
:
"
1Gi"
env
:
-
name
:
OLLAMA_API_BASE_URL
value
:
"
http://ollama-service.ollama-namespace.svc.cluster.local:11434/api"
tty
:
true
\ No newline at end of file
value
:
"
http://ollama-service.open-webui.svc.cluster.local:11434/api"
tty
:
true
volumeMounts
:
-
name
:
webui-volume
mountPath
:
/app/backend/data
volumes
:
-
name
:
webui-volume
persistentVolumeClaim
:
claimName
:
ollama-webui-pvc
\ No newline at end of file
kubernetes/manifest/base/webui-ingress.yaml
View file @
9c1aa924
apiVersion
:
networking.k8s.io/v1
kind
:
Ingress
metadata
:
name
:
o
llama
-webui-ingress
namespace
:
o
llama-namespace
name
:
o
pen
-webui-ingress
namespace
:
o
pen-webui
#annotations:
# Use appropriate annotations for your Ingress controller, e.g., for NGINX:
# nginx.ingress.kubernetes.io/rewrite-target: /
spec
:
rules
:
-
host
:
o
llama
.minikube.local
-
host
:
o
pen-webui
.minikube.local
http
:
paths
:
-
path
:
/
pathType
:
Prefix
backend
:
service
:
name
:
o
llama
-webui-service
name
:
o
pen
-webui-service
port
:
number
:
8080
kubernetes/manifest/base/webui-pvc.yaml
0 → 100644
View file @
9c1aa924
apiVersion
:
v1
kind
:
PersistentVolumeClaim
metadata
:
labels
:
app
:
ollama-webui
name
:
ollama-webui-pvc
namespace
:
ollama-namespace
spec
:
accessModes
:
[
"
ReadWriteOnce"
]
resources
:
requests
:
storage
:
2Gi
\ No newline at end of file
kubernetes/manifest/base/webui-service.yaml
View file @
9c1aa924
apiVersion
:
v1
kind
:
Service
metadata
:
name
:
o
llama
-webui-service
namespace
:
o
llama-namespace
name
:
o
pen
-webui-service
namespace
:
o
pen-webui
spec
:
type
:
NodePort
# Use LoadBalancer if you're on a cloud that supports it
selector
:
app
:
o
llama
-webui
app
:
o
pen
-webui
ports
:
-
protocol
:
TCP
port
:
8080
...
...
kubernetes/manifest/kustomization.yaml
View file @
9c1aa924
resources
:
-
base/o
llama-namespace
.yaml
-
base/o
pen-webui
.yaml
-
base/ollama-service.yaml
-
base/ollama-statefulset.yaml
-
base/webui-deployment.yaml
...
...
kubernetes/manifest/patches/ollama-statefulset-gpu.yaml
View file @
9c1aa924
...
...
@@ -2,7 +2,7 @@ apiVersion: apps/v1
kind
:
StatefulSet
metadata
:
name
:
ollama
namespace
:
o
llama-namespace
namespace
:
o
pen-webui
spec
:
selector
:
matchLabels
:
...
...
package-lock.json
View file @
9c1aa924
{
"name"
:
"o
llama
-webui"
,
"name"
:
"o
pen
-webui"
,
"version"
:
"0.0.1"
,
"lockfileVersion"
:
2
,
"requires"
:
true
,
"packages"
:
{
""
:
{
"name"
:
"o
llama
-webui"
,
"name"
:
"o
pen
-webui"
,
"version"
:
"0.0.1"
,
"dependencies"
:
{
"@sveltejs/adapter-node"
:
"^1.3.1"
,
...
...
package.json
View file @
9c1aa924
{
"name"
:
"o
llama
-webui"
,
"version"
:
"0.0
.
1"
,
"name"
:
"o
pen
-webui"
,
"version"
:
"0.
1.0-1
01"
,
"private"
:
true
,
"scripts"
:
{
"dev"
:
"vite dev --host"
,
...
...
run.sh
View file @
9c1aa924
#!/bin/bash
image_name
=
"o
llama
-webui"
container_name
=
"o
llama
-webui"
image_name
=
"o
pen
-webui"
container_name
=
"o
pen
-webui"
host_port
=
3000
container_port
=
8080
...
...
src/lib/apis/auths/index.ts
View file @
9c1aa924
...
...
@@ -261,3 +261,60 @@ export const toggleSignUpEnabledStatus = async (token: string) => {
return
res
;
};
export
const
getJWTExpiresDuration
=
async
(
token
:
string
)
=>
{
let
error
=
null
;
const
res
=
await
fetch
(
`
${
WEBUI_API_BASE_URL
}
/auths/token/expires`
,
{
method
:
'
GET
'
,
headers
:
{
'
Content-Type
'
:
'
application/json
'
,
Authorization
:
`Bearer
${
token
}
`
}
})
.
then
(
async
(
res
)
=>
{
if
(
!
res
.
ok
)
throw
await
res
.
json
();
return
res
.
json
();
})
.
catch
((
err
)
=>
{
console
.
log
(
err
);
error
=
err
.
detail
;
return
null
;
});
if
(
error
)
{
throw
error
;
}
return
res
;
};
export
const
updateJWTExpiresDuration
=
async
(
token
:
string
,
duration
:
string
)
=>
{
let
error
=
null
;
const
res
=
await
fetch
(
`
${
WEBUI_API_BASE_URL
}
/auths/token/expires/update`
,
{
method
:
'
POST
'
,
headers
:
{
'
Content-Type
'
:
'
application/json
'
,
Authorization
:
`Bearer
${
token
}
`
},
body
:
JSON
.
stringify
({
duration
:
duration
})
})
.
then
(
async
(
res
)
=>
{
if
(
!
res
.
ok
)
throw
await
res
.
json
();
return
res
.
json
();
})
.
catch
((
err
)
=>
{
console
.
log
(
err
);
error
=
err
.
detail
;
return
null
;
});
if
(
error
)
{
throw
error
;
}
return
res
;
};
src/lib/apis/images/index.ts
0 → 100644
View file @
9c1aa924
import
{
IMAGES_API_BASE_URL
}
from
'
$lib/constants
'
;
export
const
getImageGenerationEnabledStatus
=
async
(
token
:
string
=
''
)
=>
{
let
error
=
null
;
const
res
=
await
fetch
(
`
${
IMAGES_API_BASE_URL
}
/enabled`
,
{
method
:
'
GET
'
,
headers
:
{
Accept
:
'
application/json
'
,
'
Content-Type
'
:
'
application/json
'
,
...(
token
&&
{
authorization
:
`Bearer
${
token
}
`
})
}
})
.
then
(
async
(
res
)
=>
{
if
(
!
res
.
ok
)
throw
await
res
.
json
();
return
res
.
json
();
})
.
catch
((
err
)
=>
{
console
.
log
(
err
);
if
(
'
detail
'
in
err
)
{
error
=
err
.
detail
;
}
else
{
error
=
'
Server connection failed
'
;
}
return
null
;
});
if
(
error
)
{
throw
error
;
}
return
res
;
};
export
const
toggleImageGenerationEnabledStatus
=
async
(
token
:
string
=
''
)
=>
{
let
error
=
null
;
const
res
=
await
fetch
(
`
${
IMAGES_API_BASE_URL
}
/enabled/toggle`
,
{
method
:
'
GET
'
,
headers
:
{
Accept
:
'
application/json
'
,
'
Content-Type
'
:
'
application/json
'
,
...(
token
&&
{
authorization
:
`Bearer
${
token
}
`
})
}
})
.
then
(
async
(
res
)
=>
{
if
(
!
res
.
ok
)
throw
await
res
.
json
();
return
res
.
json
();
})
.
catch
((
err
)
=>
{
console
.
log
(
err
);
if
(
'
detail
'
in
err
)
{
error
=
err
.
detail
;
}
else
{
error
=
'
Server connection failed
'
;
}
return
null
;
});
if
(
error
)
{
throw
error
;
}
return
res
;
};
export
const
getAUTOMATIC1111Url
=
async
(
token
:
string
=
''
)
=>
{
let
error
=
null
;
const
res
=
await
fetch
(
`
${
IMAGES_API_BASE_URL
}
/url`
,
{
method
:
'
GET
'
,
headers
:
{
Accept
:
'
application/json
'
,
'
Content-Type
'
:
'
application/json
'
,
...(
token
&&
{
authorization
:
`Bearer
${
token
}
`
})
}
})
.
then
(
async
(
res
)
=>
{
if
(
!
res
.
ok
)
throw
await
res
.
json
();
return
res
.
json
();
})
.
catch
((
err
)
=>
{
console
.
log
(
err
);
if
(
'
detail
'
in
err
)
{
error
=
err
.
detail
;
}
else
{
error
=
'
Server connection failed
'
;
}
return
null
;
});
if
(
error
)
{
throw
error
;
}
return
res
.
AUTOMATIC1111_BASE_URL
;
};
export
const
updateAUTOMATIC1111Url
=
async
(
token
:
string
=
''
,
url
:
string
)
=>
{
let
error
=
null
;
const
res
=
await
fetch
(
`
${
IMAGES_API_BASE_URL
}
/url/update`
,
{
method
:
'
POST
'
,
headers
:
{
Accept
:
'
application/json
'
,
'
Content-Type
'
:
'
application/json
'
,
...(
token
&&
{
authorization
:
`Bearer
${
token
}
`
})
},
body
:
JSON
.
stringify
({
url
:
url
})
})
.
then
(
async
(
res
)
=>
{
if
(
!
res
.
ok
)
throw
await
res
.
json
();
return
res
.
json
();
})
.
catch
((
err
)
=>
{
console
.
log
(
err
);
if
(
'
detail
'
in
err
)
{
error
=
err
.
detail
;
}
else
{
error
=
'
Server connection failed
'
;
}
return
null
;
});
if
(
error
)
{
throw
error
;
}
return
res
.
AUTOMATIC1111_BASE_URL
;
};
export
const
getDiffusionModels
=
async
(
token
:
string
=
''
)
=>
{
let
error
=
null
;
const
res
=
await
fetch
(
`
${
IMAGES_API_BASE_URL
}
/models`
,
{
method
:
'
GET
'
,
headers
:
{
Accept
:
'
application/json
'
,
'
Content-Type
'
:
'
application/json
'
,
...(
token
&&
{
authorization
:
`Bearer
${
token
}
`
})
}
})
.
then
(
async
(
res
)
=>
{
if
(
!
res
.
ok
)
throw
await
res
.
json
();
return
res
.
json
();
})
.
catch
((
err
)
=>
{
console
.
log
(
err
);
if
(
'
detail
'
in
err
)
{
error
=
err
.
detail
;
}
else
{
error
=
'
Server connection failed
'
;
}
return
null
;
});
if
(
error
)
{
throw
error
;
}
return
res
;
};
export
const
getDefaultDiffusionModel
=
async
(
token
:
string
=
''
)
=>
{
let
error
=
null
;
const
res
=
await
fetch
(
`
${
IMAGES_API_BASE_URL
}
/models/default`
,
{
method
:
'
GET
'
,
headers
:
{
Accept
:
'
application/json
'
,
'
Content-Type
'
:
'
application/json
'
,
...(
token
&&
{
authorization
:
`Bearer
${
token
}
`
})
}
})
.
then
(
async
(
res
)
=>
{
if
(
!
res
.
ok
)
throw
await
res
.
json
();
return
res
.
json
();
})
.
catch
((
err
)
=>
{
console
.
log
(
err
);
if
(
'
detail
'
in
err
)
{
error
=
err
.
detail
;
}
else
{
error
=
'
Server connection failed
'
;
}
return
null
;
});
if
(
error
)
{
throw
error
;
}
return
res
.
model
;
};
export
const
updateDefaultDiffusionModel
=
async
(
token
:
string
=
''
,
model
:
string
)
=>
{
let
error
=
null
;
const
res
=
await
fetch
(
`
${
IMAGES_API_BASE_URL
}
/models/default/update`
,
{
method
:
'
POST
'
,
headers
:
{
Accept
:
'
application/json
'
,
'
Content-Type
'
:
'
application/json
'
,
...(
token
&&
{
authorization
:
`Bearer
${
token
}
`
})
},
body
:
JSON
.
stringify
({
model
:
model
})
})
.
then
(
async
(
res
)
=>
{
if
(
!
res
.
ok
)
throw
await
res
.
json
();
return
res
.
json
();
})
.
catch
((
err
)
=>
{
console
.
log
(
err
);
if
(
'
detail
'
in
err
)
{
error
=
err
.
detail
;
}
else
{
error
=
'
Server connection failed
'
;
}
return
null
;
});
if
(
error
)
{
throw
error
;
}
return
res
.
model
;
};
export
const
imageGenerations
=
async
(
token
:
string
=
''
,
prompt
:
string
)
=>
{
let
error
=
null
;
const
res
=
await
fetch
(
`
${
IMAGES_API_BASE_URL
}
/generations`
,
{
method
:
'
POST
'
,
headers
:
{
Accept
:
'
application/json
'
,
'
Content-Type
'
:
'
application/json
'
,
...(
token
&&
{
authorization
:
`Bearer
${
token
}
`
})
},
body
:
JSON
.
stringify
({
prompt
:
prompt
})
})
.
then
(
async
(
res
)
=>
{
if
(
!
res
.
ok
)
throw
await
res
.
json
();
return
res
.
json
();
})
.
catch
((
err
)
=>
{
console
.
log
(
err
);
if
(
'
detail
'
in
err
)
{
error
=
err
.
detail
;
}
else
{
error
=
'
Server connection failed
'
;
}
return
null
;
});
if
(
error
)
{
throw
error
;
}
return
res
;
};
src/lib/apis/index.ts
View file @
9c1aa924
import
{
WEBUI_
API_
BASE_URL
}
from
'
$lib/constants
'
;
import
{
WEBUI_BASE_URL
}
from
'
$lib/constants
'
;
export
const
getBackendConfig
=
async
()
=>
{
let
error
=
null
;
const
res
=
await
fetch
(
`
${
WEBUI_
API_
BASE_URL
}
/`
,
{
const
res
=
await
fetch
(
`
${
WEBUI_BASE_URL
}
/
api/config
`
,
{
method
:
'
GET
'
,
headers
:
{
'
Content-Type
'
:
'
application/json
'
...
...
src/lib/apis/ollama/index.ts
View file @
9c1aa924
...
...
@@ -133,9 +133,19 @@ export const getOllamaModels = async (token: string = '') => {
});
};
export
const
generateTitle
=
async
(
token
:
string
=
''
,
model
:
string
,
prompt
:
string
)
=>
{
// TODO: migrate to backend
export
const
generateTitle
=
async
(
token
:
string
=
''
,
template
:
string
,
model
:
string
,
prompt
:
string
)
=>
{
let
error
=
null
;
template
=
template
.
replace
(
/{{prompt}}/g
,
prompt
);
console
.
log
(
template
);
const
res
=
await
fetch
(
`
${
OLLAMA_API_BASE_URL
}
/generate`
,
{
method
:
'
POST
'
,
headers
:
{
...
...
@@ -144,7 +154,7 @@ export const generateTitle = async (token: string = '', model: string, prompt: s
},
body
:
JSON
.
stringify
({
model
:
model
,
prompt
:
`Create a concise, 3-5 word phrase as a header for the following query, strictly adhering to the 3-5 word limit and avoiding the use of the word 'title':
${
prompt
}
`
,
prompt
:
template
,
stream
:
false
})
})
...
...
src/lib/components/admin/Settings/General.svelte
View file @
9c1aa924
<script lang="ts">
import {
getDefaultUserRole,
getJWTExpiresDuration,
getSignUpEnabledStatus,
toggleSignUpEnabledStatus,
updateDefaultUserRole
updateDefaultUserRole,
updateJWTExpiresDuration
} from '$lib/apis/auths';
import { onMount } from 'svelte';
export let saveHandler: Function;
let signUpEnabled = true;
let defaultUserRole = 'pending';
let JWTExpiresIn = '';
const toggleSignUpEnabled = async () => {
signUpEnabled = await toggleSignUpEnabledStatus(localStorage.token);
...
...
@@ -19,9 +22,14 @@
defaultUserRole = await updateDefaultUserRole(localStorage.token, role);
};
const updateJWTExpiresDurationHandler = async (duration) => {
JWTExpiresIn = await updateJWTExpiresDuration(localStorage.token, duration);
};
onMount(async () => {
signUpEnabled = await getSignUpEnabledStatus(localStorage.token);
defaultUserRole = await getDefaultUserRole(localStorage.token);
JWTExpiresIn = await getJWTExpiresDuration(localStorage.token);
});
</script>
...
...
@@ -29,6 +37,7 @@
class="flex flex-col h-full justify-between space-y-3 text-sm"
on:submit|preventDefault={() => {
// console.log('submit');
updateJWTExpiresDurationHandler(JWTExpiresIn);
saveHandler();
}}
>
...
...
@@ -94,6 +103,29 @@
</select>
</div>
</div>
<hr class=" dark:border-gray-700 my-3" />
<div class=" w-full justify-between">
<div class="flex w-full justify-between">
<div class=" self-center text-xs font-medium">JWT Expiration</div>
</div>
<div class="flex mt-2 space-x-2">
<input
class="w-full rounded py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none border border-gray-100 dark:border-gray-600"
type="text"
placeholder={`e.g.) "30m","1h", "10d". `}
bind:value={JWTExpiresIn}
/>
</div>
<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
Valid time units: <span class=" text-gray-300 font-medium"
>'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.</span
>
</div>
</div>
</div>
</div>
...
...
src/lib/components/chat/Messages.svelte
View file @
9c1aa924
...
...
@@ -11,6 +11,7 @@
import ResponseMessage from './Messages/ResponseMessage.svelte';
import Placeholder from './Messages/Placeholder.svelte';
import Spinner from '../common/Spinner.svelte';
import { imageGenerations } from '$lib/apis/images';
export let chatId = '';
export let sendPrompt: Function;
...
...
@@ -328,18 +329,28 @@
{/if}
{:else}
<ResponseMessage
{message}
modelfiles={selectedModelfiles}
siblings={history.messages[message.parentId]?.childrenIds ?? []}
isLastMessage={messageIdx + 1 === messages.length}
{confirmEditResponseMessage}
{showPreviousMessage}
{showNextMessage}
{rateMessage}
{copyToClipboard}
{continueGeneration}
{regenerateResponse}
/>
{message}
modelfiles={selectedModelfiles}
siblings={history.messages[message.parentId]?.childrenIds ?? []}
isLastMessage={messageIdx + 1 === messages.length}
{confirmEditResponseMessage}
{showPreviousMessage}
{showNextMessage}
{rateMessage}
{copyToClipboard}
{continueGeneration}
{regenerateResponse}
on:save={async (e) => {
console.log('save', e);
const message = e.detail;
history.messages[message.id] = message;
await updateChatById(localStorage.token, chatId, {
messages: messages,
history: history
});
}}
/>
{/if}
</div>
</div>
...
...
src/lib/components/chat/Messages/ResponseMessage.svelte
View file @
9c1aa924
...
...
@@ -2,21 +2,25 @@
import toast from 'svelte-french-toast';
import dayjs from 'dayjs';
import { marked } from 'marked';
import { settings } from '$lib/stores';
import tippy from 'tippy.js';
import auto_render from 'katex/dist/contrib/auto-render.mjs';
import 'katex/dist/katex.min.css';
import { createEventDispatcher } from 'svelte';
import { onMount, tick } from 'svelte';
const dispatch = createEventDispatcher();
import { config, settings } from '$lib/stores';
import { synthesizeOpenAISpeech } from '$lib/apis/openai';
import { imageGenerations } from '$lib/apis/images';
import { extractSentences } from '$lib/utils';
import Name from './Name.svelte';
import ProfileImage from './ProfileImage.svelte';
import Skeleton from './Skeleton.svelte';
import CodeBlock from './CodeBlock.svelte';
import { synthesizeOpenAISpeech } from '$lib/apis/openai';
import { extractSentences } from '$lib/utils';
export let modelfiles = [];
export let message;
export let siblings;
...
...
@@ -43,6 +47,8 @@
let loadingSpeech = false;
let generatingImage = false;
$: tokens = marked.lexer(message.content);
const renderer = new marked.Renderer();
...
...
@@ -81,7 +87,9 @@
}<br/>
prompt_token/s: ${
Math.round(
((message.info.prompt_eval_count ?? 0) / (message.info.prompt_eval_duration / 1000000000)) * 100
((message.info.prompt_eval_count ?? 0) /
(message.info.prompt_eval_duration / 1000000000)) *
100
) / 100 ?? 'N/A'
} tokens<br/>
total_duration: ${
...
...
@@ -114,10 +122,11 @@
// customised options
// • auto-render specific keys, e.g.:
delimiters: [
{ left: '$$', right: '$$', display: true },
// { left: '$', right: '$', display: false },
{ left: '\\(', right: '\\)', display: true },
{ left: '\\[', right: '\\]', display: true }
{ left: '$$', right: '$$', display: false },
{ left: '$', right: '$', display: false },
{ left: '\\(', right: '\\)', display: false },
{ left: '\\[', right: '\\]', display: false },
{ left: '[ ', right: ' ]', display: false }
],
// • rendering keys, e.g.:
throwOnError: false
...
...
@@ -264,6 +273,23 @@
renderStyling();
};
const generateImage = async (message) => {
generatingImage = true;
const res = await imageGenerations(localStorage.token, message.content);
console.log(res);
if (res) {
message.files = res.images.map((image) => ({
type: 'image',
url: `data:image/png;base64,${image}`
}));
dispatch('save', message);
}
generatingImage = false;
};
onMount(async () => {
await tick();
renderStyling();
...
...
@@ -292,6 +318,18 @@
{#if message.content === ''}
<Skeleton />
{:else}
{#if message.files}
<div class="my-2.5 w-full flex overflow-x-auto gap-2 flex-wrap">
{#each message.files as file}
<div>
{#if file.type === 'image'}
<img src={file.url} alt="input" class=" max-h-96 rounded-lg" draggable="false" />
{/if}
</div>
{/each}
</div>
{/if}
<div
class="prose chat-{message.role} w-full max-w-full dark:prose-invert prose-headings:my-0 prose-p:m-0 prose-p:-mb-6 prose-pre:my-0 prose-table:my-0 prose-blockquote:my-0 prose-img:my-0 prose-ul:-my-4 prose-ol:-my-4 prose-li:-my-3 prose-ul:-mb-6 prose-ol:-mb-8 prose-li:-mb-4 whitespace-pre-line"
>
...
...
@@ -592,6 +630,71 @@
{/if}
</button>
{#if $config.images}
<button
class="{isLastMessage
? 'visible'
: 'invisible group-hover:visible'} p-1 rounded dark:hover:text-white hover:text-black transition"
on:click={() => {
if (!generatingImage) {
generateImage(message);
}
}}
>
{#if generatingImage}
<svg
class=" w-4 h-4"
fill="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
><style>
.spinner_S1WN {
animation: spinner_MGfb 0.8s linear infinite;
animation-delay: -0.8s;
}
.spinner_Km9P {
animation-delay: -0.65s;
}
.spinner_JApP {
animation-delay: -0.5s;
}
@keyframes spinner_MGfb {
93.75%,
100% {
opacity: 0.2;
}
}
</style><circle class="spinner_S1WN" cx="4" cy="12" r="3" /><circle
class="spinner_S1WN spinner_Km9P"
cx="12"
cy="12"
r="3"
/><circle
class="spinner_S1WN spinner_JApP"
cx="20"
cy="12"
r="3"
/></svg
>
{:else}
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-4 h-4"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="m2.25 15.75 5.159-5.159a2.25 2.25 0 0 1 3.182 0l5.159 5.159m-1.5-1.5 1.409-1.409a2.25 2.25 0 0 1 3.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 0 0 1.5-1.5V6a1.5 1.5 0 0 0-1.5-1.5H3.75A1.5 1.5 0 0 0 2.25 6v12a1.5 1.5 0 0 0 1.5 1.5Zm10.5-11.25h.008v.008h-.008V8.25Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z"
/>
</svg>
{/if}
</button>
{/if}
{#if message.info}
<button
class=" {isLastMessage
...
...
Prev
1
2
3
4
Next
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment