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
OpenDAS
ollama
Commits
ad4ffdf7
Commit
ad4ffdf7
authored
Jun 27, 2023
by
Jeffrey Morgan
Browse files
desktop: load model from file
parent
a12df7fa
Changes
8
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
273 additions
and
153 deletions
+273
-153
desktop/README.md
desktop/README.md
+16
-0
desktop/forge.config.ts
desktop/forge.config.ts
+2
-1
desktop/package-lock.json
desktop/package-lock.json
+127
-85
desktop/package.json
desktop/package.json
+6
-3
desktop/src/app.css
desktop/src/app.css
+18
-0
desktop/src/app.tsx
desktop/src/app.tsx
+92
-59
desktop/src/index.ts
desktop/src/index.ts
+12
-3
desktop/src/preload.ts
desktop/src/preload.ts
+0
-2
No files found.
desktop/README.md
0 → 100644
View file @
ad4ffdf7
# Desktop
The Ollama desktop experience
## Running
```
npm install
npm start
```
## Packaging
```
npm run package
```
desktop/forge.config.ts
View file @
ad4ffdf7
...
...
@@ -13,7 +13,7 @@ const config: ForgeConfig = {
packagerConfig
:
{
asar
:
true
,
icon
:
'
./images/icon
'
,
extraResource
:
[
'
../
server/dist/server
'
],
extraResource
:
[
'
../
dist/ollama
'
],
},
rebuildConfig
:
{},
makers
:
[
new
MakerSquirrel
({}),
new
MakerZIP
({},
[
'
darwin
'
]),
new
MakerRpm
({}),
new
MakerDeb
({})],
...
...
@@ -24,6 +24,7 @@ const config: ForgeConfig = {
devContentSecurityPolicy
:
`default-src * 'unsafe-eval' 'unsafe-inline'`
,
renderer
:
{
config
:
rendererConfig
,
nodeIntegration
:
true
,
entryPoints
:
[
{
html
:
'
./src/index.html
'
,
...
...
desktop/package-lock.json
View file @
ad4ffdf7
This diff is collapsed.
Click to expand it.
desktop/package.json
View file @
ad4ffdf7
{
"name"
:
"olama"
,
"productName"
:
"
o
llama"
,
"name"
:
"ol
l
ama"
,
"productName"
:
"
O
llama"
,
"version"
:
"0.0.1"
,
"description"
:
"
O
llama"
,
"description"
:
"
o
llama"
,
"main"
:
".webpack/main"
,
"scripts"
:
{
"start"
:
"electron-forge start"
,
...
...
@@ -46,6 +46,8 @@
"postcss-import"
:
"^15.1.0"
,
"postcss-loader"
:
"^7.3.3"
,
"postcss-preset-env"
:
"^8.5.1"
,
"prettier"
:
"^2.8.8"
,
"prettier-plugin-tailwindcss"
:
"^0.3.0"
,
"style-loader"
:
"^3.3.3"
,
"tailwindcss"
:
"^3.3.2"
,
"ts-loader"
:
"^9.4.3"
,
...
...
@@ -56,6 +58,7 @@
"webpack-dev-server"
:
"^4.15.1"
},
"dependencies"
:
{
"@electron/remote"
:
"^2.0.10"
,
"@types/node"
:
"^20.3.1"
,
"electron-squirrel-startup"
:
"^1.0.0"
,
"react"
:
"^18.2.0"
,
...
...
desktop/src/app.css
View file @
ad4ffdf7
...
...
@@ -10,3 +10,21 @@ body {
.drag
{
-webkit-app-region
:
drag
;
}
.blink
{
-webkit-animation
:
1s
blink
step-end
infinite
;
-moz-animation
:
1s
blink
step-end
infinite
;
-ms-animation
:
1s
blink
step-end
infinite
;
-o-animation
:
1s
blink
step-end
infinite
;
animation
:
1s
blink
step-end
infinite
;
}
@keyframes
blink
{
from
,
to
{
color
:
transparent
;
}
50
%
{
color
:
black
;
}
}
desktop/src/app.tsx
View file @
ad4ffdf7
import
{
useState
}
from
'
react
'
import
path
from
'
path
'
import
os
from
'
os
'
import
{
dialog
,
getCurrentWindow
}
from
'
@electron/remote
'
const
API_URL
=
'
http://127.0.0.1:5001
'
type
Message
=
{
sender
:
string
sender
:
'
bot
'
|
'
human
'
content
:
string
}
async
function
completion
(
prompt
:
string
,
callback
:
(
res
:
string
)
=>
void
)
{
const
userInfo
=
os
.
userInfo
()
async
function
generate
(
prompt
:
string
,
model
:
string
,
callback
:
(
res
:
string
)
=>
void
)
{
const
result
=
await
fetch
(
`
${
API_URL
}
/generate`
,
{
method
:
'
POST
'
,
headers
:
{
'
Content-Type
'
:
'
application/json
'
,
},
body
:
JSON
.
stringify
({
prompt
:
prompt
,
model
:
'
ggml-model-q4_0
'
,
prompt
,
model
,
}),
})
if
(
!
result
.
ok
||
!
result
.
body
)
{
if
(
!
result
.
ok
)
{
return
}
...
...
@@ -54,37 +59,62 @@ async function completion(prompt: string, callback: (res: string) => void) {
export
default
function
()
{
const
[
prompt
,
setPrompt
]
=
useState
(
''
)
const
[
messages
,
setMessages
]
=
useState
<
Message
[]
>
([])
const
[
model
,
setModel
]
=
useState
(
''
)
const
[
generating
,
setGenerating
]
=
useState
(
false
)
return
(
<
div
className
=
'flex min-h-screen flex-1 flex-col justify-between bg-white'
>
<
header
className
=
'drag sticky top-0 z-50 flex w-full flex-row items-center border-b border-black/
5
bg-
gray-50/75 p-3
backdrop-blur-md'
>
<
header
className
=
'drag sticky top-0 z-50 flex
h-14
w-full flex-row items-center border-b border-black/
10
bg-
white/75
backdrop-blur-md'
>
<
div
className
=
'mx-auto w-full max-w-xl leading-none'
>
<
h1
className
=
'text-sm font-medium'
>
LLaMa
</
h1
>
<
h2
className
=
'text-xs text-black/50'
>
Meta Platforms, Inc.
</
h2
>
<
h1
className
=
'text-sm font-medium'
>
{
path
.
basename
(
model
).
replace
(
'
.bin
'
,
''
)
}
</
h1
>
</
div
>
</
header
>
{
model
?
(
<
section
className
=
'mx-auto mb-10 w-full max-w-xl flex-1 break-words'
>
{
messages
.
map
((
m
,
i
)
=>
(
<
div
className
=
'my-4 flex gap-4'
key
=
{
i
}
>
<
div
className
=
'flex-none pr-1 text-lg'
>
{
m
.
sender
===
'
human
'
?
(
<
div
className
=
'
bg-neutral-200 text-neutral-700 text-sm h-6 w-6 rounded-md flex items-center justify-center mt-px
'
>
H
<
div
className
=
'
mt-px flex h-6 w-6 items-center justify-center rounded-md bg-neutral-200 text-sm text-neutral-700
'
>
{
userInfo
.
username
[
0
].
toUpperCase
()
}
</
div
>
)
:
(
<
div
className
=
'
bg-blue-600 text-white text-sm h-6 w-6 rounded-md flex items-center justify-center mt-0.5
'
>
L
<
div
className
=
'
mt-0.5 flex h-6 w-6 items-center justify-center rounded-md bg-blue-600 text-sm text-white
'
>
{
path
.
basename
(
model
)[
0
].
toUpperCase
()
}
</
div
>
)
}
</
div
>
<
div
className
=
'flex-1 text-gray-800'
>
{
m
.
content
}
{
m
.
sender
===
'
bot
'
&&
<
span
className
=
'relative -top-[3px] left-1 text-[10px]'
>
⬤
</
span
>
}
{
m
.
sender
===
'
bot
'
&&
generating
&&
(
<
span
className
=
'blink relative -top-[3px] left-1 text-[10px]'
>
█
</
span
>
)
}
</
div
>
</
div
>
))
}
</
section
>
)
:
(
<
section
className
=
'flex flex-1 select-none flex-col items-center justify-center pb-20'
>
<
h2
className
=
'text-3xl font-light text-neutral-400'
>
No model selected
</
h2
>
<
button
onClick
=
{
async
()
=>
{
const
res
=
await
dialog
.
showOpenDialog
(
getCurrentWindow
(),
{
properties
:
[
'
openFile
'
,
'
multiSelections
'
],
})
if
(
res
.
canceled
)
{
return
}
setModel
(
res
.
filePaths
[
0
])
}
}
className
=
'rounded-dm my-8 rounded-md bg-blue-600 px-4 py-2 text-sm text-white hover:brightness-110'
>
Open file...
</
button
>
</
section
>
)
}
<
div
className
=
'sticky bottom-0 bg-gradient-to-b from-transparent to-white'
>
{
model
&&
(
<
textarea
autoFocus
rows
=
{
1
}
...
...
@@ -94,31 +124,34 @@ export default function () {
className
=
'mx-auto my-4 block w-full max-w-xl resize-none rounded-xl border border-gray-200 px-5 py-3.5 text-[15px] shadow-lg shadow-black/5 focus:outline-none'
onKeyDownCapture
=
{
async
e
=>
{
if
(
e
.
key
===
'
Enter
'
&&
!
e
.
shiftKey
)
{
e
.
preventDefault
()
// Prevents the newline character from being inserted
// Perform your desired action here, such as submitting the form or handling the entered text
await
setMessages
(
messages
=>
{
return
[...
messages
,
{
sender
:
'
human
'
,
content
:
prompt
}]
})
e
.
preventDefault
()
const
index
=
messages
.
length
+
1
completion
(
prompt
,
res
=>
{
setMessages
(
messages
=>
{
let
message
=
messages
[
index
]
if
(
!
message
)
{
message
=
{
sender
:
'
bot
'
,
content
:
''
}
if
(
generating
)
{
return
}
message
.
content
=
message
.
content
+
res
if
(
!
prompt
)
{
return
}
return
[...
messages
.
slice
(
0
,
index
),
message
]
})
await
setMessages
(
message
s
=>
{
return
[...
messages
,
{
sender
:
'
human
'
,
content
:
prompt
},
{
sender
:
'
bot
'
,
content
:
''
}]
})
setPrompt
(
''
)
setGenerating
(
true
)
await
generate
(
prompt
,
model
,
res
=>
{
setMessages
(
messages
=>
{
let
last
=
messages
[
messages
.
length
-
1
]
return
[...
messages
.
slice
(
0
,
messages
.
length
-
1
),
{
...
last
,
content
:
last
.
content
+
res
}]
})
})
setGenerating
(
false
)
}
}
}
></
textarea
>
)
}
</
div
>
</
div
>
)
...
...
desktop/src/index.ts
View file @
ad4ffdf7
import
{
app
,
BrowserWindow
}
from
'
electron
'
import
{
app
,
BrowserWindow
,
ipcMain
,
dialog
}
from
'
electron
'
import
{
spawn
}
from
'
child_process
'
import
*
as
path
from
'
path
'
require
(
'
@electron/remote/main
'
).
initialize
()
// This allows TypeScript to pick up the magic constants that's auto-generated by Forge's Webpack
// plugin that tells the Electron app where to look for the Webpack-bundled app code (depending on
// whether you're running in development or production).
...
...
@@ -21,9 +24,15 @@ const createWindow = (): void => {
minWidth
:
400
,
minHeight
:
300
,
titleBarStyle
:
'
hiddenInset
'
,
transparent
:
true
,
webPreferences
:
{
preload
:
MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY
,
nodeIntegration
:
true
,
contextIsolation
:
false
,
},
})
require
(
'
@electron/remote/main
'
).
enable
(
mainWindow
.
webContents
)
// and load the index.html of the app.
mainWindow
.
loadURL
(
MAIN_WINDOW_WEBPACK_ENTRY
)
}
...
...
@@ -34,7 +43,7 @@ if (app.isPackaged) {
console
.
log
(
resources
)
// Start the executable
const
exec
=
path
.
join
(
resources
,
'
serve
r
'
)
const
exec
=
path
.
join
(
resources
,
'
serve
'
,
'
--port
'
,
'
5001
'
)
console
.
log
(
`Starting
${
exec
}
`
)
const
proc
=
spawn
(
exec
)
proc
.
stdout
.
on
(
'
data
'
,
data
=>
{
...
...
desktop/src/preload.ts
View file @
ad4ffdf7
// See the Electron documentation for details on how to use preload scripts:
// https://www.electronjs.org/docs/latest/tutorial/process-model#preload-scripts
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