ollama-chat 0.9.0__tar.gz

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.
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2024 Craig A. Hobbs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,89 @@
1
+ Metadata-Version: 2.1
2
+ Name: ollama-chat
3
+ Version: 0.9.0
4
+ Summary: ollama-chat
5
+ Home-page: https://github.com/craigahobbs/ollama-chat
6
+ Author: Craig A. Hobbs
7
+ Author-email: craigahobbs@gmail.com
8
+ License: MIT
9
+ Keywords: ollama-chat
10
+ Classifier: Development Status :: 5 - Production/Stable
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3.8
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Utilities
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: chisel>=1.2.7
23
+ Requires-Dist: ollama>=0.1.9
24
+ Requires-Dist: waitress>=3.0.0
25
+
26
+ # ollama-chat
27
+
28
+ [![PyPI - Status](https://img.shields.io/pypi/status/ollama-chat)](https://pypi.org/project/ollama-chat/)
29
+ [![PyPI](https://img.shields.io/pypi/v/ollama-chat)](https://pypi.org/project/ollama-chat/)
30
+ [![GitHub](https://img.shields.io/github/license/craigahobbs/ollama-chat)](https://github.com/craigahobbs/ollama-chat/blob/main/LICENSE)
31
+ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/ollama-chat)](https://pypi.org/project/ollama-chat/)
32
+
33
+ **Ollama Chat** is a simple yet useful web chat client for
34
+ [Ollama](https://ollama.com)
35
+ that allows you to chat locally (and privately) with
36
+ [open-source LLMs](https://ollama.com/library).
37
+
38
+
39
+ # Installation
40
+
41
+ To get up and running with Ollama Chat follows these steps:
42
+
43
+ 1. Install and start [Ollama](https://ollama.com)
44
+
45
+ 2. Install Ollama Chat
46
+
47
+ ~~~
48
+ pip install ollama-chat
49
+ ~~~
50
+
51
+
52
+ # Starting Ollama Chat
53
+
54
+ To start Ollama Chat, open a terminal prompt and run the Ollama Chat application:
55
+
56
+ ~~~
57
+ ollama-chat
58
+ ~~~
59
+
60
+ A web browser is launched and opens the Ollama Chat web application.
61
+
62
+ By default, a configuration file, "ollama-chat.json", is created in the current directory to save
63
+ your conversations.
64
+
65
+
66
+ ## Future Features
67
+
68
+ In no particular order...
69
+
70
+ - Save conversation as Markdown file
71
+
72
+ - Conversation title edit
73
+
74
+ - File / Directory / URL text inclusion in prompt
75
+
76
+ - Local model management (pull, rm)
77
+ - [Models JSON](https://huggingface.co/api/models)
78
+
79
+ - Prompt library
80
+
81
+
82
+ ## Development
83
+
84
+ This package is developed using [python-build](https://github.com/craigahobbs/python-build#readme).
85
+ It was started using [python-template](https://github.com/craigahobbs/python-template#readme) as follows:
86
+
87
+ ~~~
88
+ template-specialize python-template/template/ ollama-chat/ -k package ollama-chat -k name 'Craig A. Hobbs' -k email 'craigahobbs@gmail.com' -k github 'craigahobbs' -k noapi 1
89
+ ~~~
@@ -0,0 +1,64 @@
1
+ # ollama-chat
2
+
3
+ [![PyPI - Status](https://img.shields.io/pypi/status/ollama-chat)](https://pypi.org/project/ollama-chat/)
4
+ [![PyPI](https://img.shields.io/pypi/v/ollama-chat)](https://pypi.org/project/ollama-chat/)
5
+ [![GitHub](https://img.shields.io/github/license/craigahobbs/ollama-chat)](https://github.com/craigahobbs/ollama-chat/blob/main/LICENSE)
6
+ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/ollama-chat)](https://pypi.org/project/ollama-chat/)
7
+
8
+ **Ollama Chat** is a simple yet useful web chat client for
9
+ [Ollama](https://ollama.com)
10
+ that allows you to chat locally (and privately) with
11
+ [open-source LLMs](https://ollama.com/library).
12
+
13
+
14
+ # Installation
15
+
16
+ To get up and running with Ollama Chat follows these steps:
17
+
18
+ 1. Install and start [Ollama](https://ollama.com)
19
+
20
+ 2. Install Ollama Chat
21
+
22
+ ~~~
23
+ pip install ollama-chat
24
+ ~~~
25
+
26
+
27
+ # Starting Ollama Chat
28
+
29
+ To start Ollama Chat, open a terminal prompt and run the Ollama Chat application:
30
+
31
+ ~~~
32
+ ollama-chat
33
+ ~~~
34
+
35
+ A web browser is launched and opens the Ollama Chat web application.
36
+
37
+ By default, a configuration file, "ollama-chat.json", is created in the current directory to save
38
+ your conversations.
39
+
40
+
41
+ ## Future Features
42
+
43
+ In no particular order...
44
+
45
+ - Save conversation as Markdown file
46
+
47
+ - Conversation title edit
48
+
49
+ - File / Directory / URL text inclusion in prompt
50
+
51
+ - Local model management (pull, rm)
52
+ - [Models JSON](https://huggingface.co/api/models)
53
+
54
+ - Prompt library
55
+
56
+
57
+ ## Development
58
+
59
+ This package is developed using [python-build](https://github.com/craigahobbs/python-build#readme).
60
+ It was started using [python-template](https://github.com/craigahobbs/python-template#readme) as follows:
61
+
62
+ ~~~
63
+ template-specialize python-template/template/ ollama-chat/ -k package ollama-chat -k name 'Craig A. Hobbs' -k email 'craigahobbs@gmail.com' -k github 'craigahobbs' -k noapi 1
64
+ ~~~
@@ -0,0 +1,2 @@
1
+ [build-system]
2
+ requires = ["setuptools", "wheel"]
@@ -0,0 +1,46 @@
1
+ [metadata]
2
+ name = ollama-chat
3
+ version = 0.9.0
4
+ url = https://github.com/craigahobbs/ollama-chat
5
+ author = Craig A. Hobbs
6
+ author_email = craigahobbs@gmail.com
7
+ license = MIT
8
+ description = ollama-chat
9
+ long_description = file:README.md
10
+ long_description_content_type = text/markdown
11
+ keywords = ollama-chat
12
+ classifiers =
13
+ Development Status :: 5 - Production/Stable
14
+ Intended Audience :: Developers
15
+ License :: OSI Approved :: MIT License
16
+ Operating System :: OS Independent
17
+ Programming Language :: Python :: 3.8
18
+ Programming Language :: Python :: 3.9
19
+ Programming Language :: Python :: 3.10
20
+ Programming Language :: Python :: 3.11
21
+ Programming Language :: Python :: 3.12
22
+ Topic :: Utilities
23
+
24
+ [options]
25
+ packages = ollama_chat
26
+ package_dir =
27
+ = src
28
+ install_requires =
29
+ chisel >= 1.2.7
30
+ ollama >= 0.1.9
31
+ waitress >= 3.0.0
32
+
33
+ [options.entry_points]
34
+ console_scripts =
35
+ ollama-chat = ollama_chat.main:main
36
+
37
+ [options.package_data]
38
+ ollama_chat = \
39
+ static/index.html, \
40
+ static/ollamaChat.mds, \
41
+ static/ollamaChat.smd
42
+
43
+ [egg_info]
44
+ tag_build =
45
+ tag_date = 0
46
+
File without changes
@@ -0,0 +1,12 @@
1
+ # Licensed under the MIT License
2
+ # https://github.com/craigahobbs/ollama-chat/blob/main/LICENSE
3
+
4
+ """
5
+ ollama-chat top-level script environment
6
+ """
7
+
8
+ from .main import main
9
+
10
+
11
+ if __name__ == '__main__': # pragma: no cover
12
+ main()
@@ -0,0 +1,309 @@
1
+ # Licensed under the MIT License
2
+ # https://github.com/craigahobbs/ollama-chat/blob/main/LICENSE
3
+
4
+ """
5
+ The ollama-chat back-end application
6
+ """
7
+
8
+ from contextlib import contextmanager
9
+ import copy
10
+ import json
11
+ import os
12
+ import importlib.resources as pkg_resources
13
+ import threading
14
+ import uuid
15
+
16
+ import chisel
17
+ import ollama
18
+ import schema_markdown
19
+
20
+ from .ollama import OllamaChat, config_conversation
21
+
22
+
23
+ class OllamaChatApplication(chisel.Application):
24
+ """
25
+ The ollama-chat back-end API WSGI application class
26
+ """
27
+
28
+ __slots__ = ('config', 'chats')
29
+
30
+
31
+ def __init__(self, config_path):
32
+ super().__init__()
33
+ self.config = ConfigManager(config_path)
34
+ self.chats = {}
35
+
36
+ # Add the chisel documentation application
37
+ self.add_requests(chisel.create_doc_requests())
38
+
39
+ # Add the APIs
40
+ self.add_request(delete_conversation)
41
+ self.add_request(delete_conversation_exchange)
42
+ self.add_request(get_conversation)
43
+ self.add_request(get_conversations)
44
+ self.add_request(get_model)
45
+ self.add_request(get_models)
46
+ self.add_request(regenerate_conversation_exchange)
47
+ self.add_request(reply_conversation)
48
+ self.add_request(set_model)
49
+ self.add_request(start_conversation)
50
+ self.add_request(stop_conversation)
51
+
52
+ # Add the ollama-chat statics
53
+ self.add_static(
54
+ 'index.html',
55
+ 'text/html; charset=utf-8',
56
+ (('GET', None), ('GET', '/')),
57
+ 'The Ollama Chat application HTML'
58
+ )
59
+ self.add_static(
60
+ 'ollamaChat.bare',
61
+ 'text/plain; charset=utf-8',
62
+ (('GET', None),),
63
+ 'The Ollama Chat application BareScript'
64
+ )
65
+
66
+
67
+ def add_static(self, filename, content_type, urls, doc):
68
+ with pkg_resources.open_binary('ollama_chat.static', filename) as fh:
69
+ self.add_request(chisel.StaticRequest(
70
+ filename,
71
+ fh.read(),
72
+ content_type=content_type,
73
+ urls=urls,
74
+ doc=doc,
75
+ doc_group='Ollama Chat Statics'
76
+ ))
77
+
78
+
79
+ class ConfigManager:
80
+ __slots__ = ('config_path', 'config_lock', 'config')
81
+
82
+
83
+ DEFAULT_MODEL = 'llama3:latest'
84
+
85
+
86
+ def __init__(self, config_path):
87
+ self.config_path = config_path
88
+ self.config_lock = threading.Lock()
89
+
90
+ # Ensure the config file exists with default config if it doesn't exist
91
+ if os.path.isfile(config_path):
92
+ with open(config_path, 'r', encoding='utf-8') as fh_config:
93
+ self.config = schema_markdown.validate_type(OLLAMA_CHAT_TYPES, 'OllamaChatConfig', json.loads(fh_config.read()))
94
+ else:
95
+ self.config = {'model': ConfigManager.DEFAULT_MODEL, 'conversations': []}
96
+
97
+
98
+ @contextmanager
99
+ def __call__(self, save=False):
100
+ # Acquire the config lock
101
+ self.config_lock.acquire()
102
+
103
+ try:
104
+ # If no model is set, set the default model
105
+ is_saving = save
106
+ if 'model' not in self.config:
107
+ self.config['model'] = ConfigManager.DEFAULT_MODEL
108
+ is_saving = True
109
+
110
+ # Yield the config on context entry
111
+ yield self.config
112
+
113
+ # Save the config file on context exit, if requested
114
+ if is_saving and not self.config.get('noSave'):
115
+ with open(self.config_path, 'w', encoding='utf-8') as fh_config:
116
+ json.dump(self.config, fh_config, indent=4, sort_keys = True)
117
+ finally:
118
+ # Release the config lock
119
+ self.config_lock.release()
120
+
121
+
122
+ # The Ollama Chat type model
123
+ with pkg_resources.open_text('ollama_chat.static', 'ollamaChat.smd') as cm_smd:
124
+ OLLAMA_CHAT_TYPES = schema_markdown.parse_schema_markdown(cm_smd.read())
125
+
126
+
127
+ @chisel.action(name='getModels', types=OLLAMA_CHAT_TYPES)
128
+ def get_models(unused_ctx, unused_req):
129
+ return {
130
+ 'models': [
131
+ {
132
+ 'model': model['name'],
133
+ 'size': model['size']
134
+ }
135
+ for model in ollama.list()['models']
136
+ ]
137
+ }
138
+
139
+
140
+ @chisel.action(name='getModel', types=OLLAMA_CHAT_TYPES)
141
+ def get_model(ctx, unused_req):
142
+ with ctx.app.config() as config:
143
+ return {'model': config['model']}
144
+
145
+
146
+ @chisel.action(name='setModel', types=OLLAMA_CHAT_TYPES)
147
+ def set_model(ctx, req):
148
+ with ctx.app.config(save=True) as config:
149
+ config['model'] = req['model']
150
+
151
+
152
+ @chisel.action(name='getConversations', types=OLLAMA_CHAT_TYPES)
153
+ def get_conversations(ctx, unused_req):
154
+ conversations = []
155
+ with ctx.app.config() as config:
156
+ for conversation in config['conversations']:
157
+ info = dict(conversation)
158
+ del info['exchanges']
159
+ info['generating'] = conversation['id'] in ctx.app.chats
160
+ conversations.append(info)
161
+ return {
162
+ 'conversations': conversations
163
+ }
164
+
165
+
166
+ @chisel.action(name='startConversation', types=OLLAMA_CHAT_TYPES)
167
+ def start_conversation(ctx, req):
168
+ with ctx.app.config() as config:
169
+ # Compute the conversation title
170
+ user_prompt = req['user']
171
+ max_title_len = 50
172
+ if len(user_prompt) <= max_title_len:
173
+ title = user_prompt
174
+ else:
175
+ title_suffix = '...'
176
+ title = f'{user_prompt[:max_title_len - len(title_suffix)]}{title_suffix}'
177
+
178
+ # Create the new conversation object
179
+ id_ = str(uuid.uuid4())
180
+ model = config['model']
181
+ conversation = {
182
+ 'id': id_,
183
+ 'model': model,
184
+ 'title': title,
185
+ 'exchanges': [
186
+ {
187
+ 'user': req['user'],
188
+ 'model': ''
189
+ }
190
+ ]
191
+ }
192
+
193
+ # Add the new conversation to the application config
194
+ config['conversations'].insert(0, conversation)
195
+
196
+ # Start the model chat
197
+ ctx.app.chats[id_] = OllamaChat(ctx.app, id_)
198
+
199
+ # Return the new conversation ID
200
+ return {'id': id_}
201
+
202
+
203
+ @chisel.action(name='stopConversation', types=OLLAMA_CHAT_TYPES)
204
+ def stop_conversation(ctx, req):
205
+ with ctx.app.config() as config:
206
+ id_ = req['id']
207
+ conversation = config_conversation(config, id_)
208
+ if conversation is None:
209
+ raise chisel.ActionError('UnknownConversationID')
210
+
211
+ # Not generating?
212
+ chat = ctx.app.chats.get(id_)
213
+ if chat is None:
214
+ return
215
+
216
+ # Stop the conversation
217
+ chat.stop = True
218
+ del ctx.app.chats[id_]
219
+
220
+
221
+ @chisel.action(name='getConversation', types=OLLAMA_CHAT_TYPES)
222
+ def get_conversation(ctx, req):
223
+ with ctx.app.config() as config:
224
+ id_ = req['id']
225
+ conversation = config_conversation(config, id_)
226
+ if conversation is None:
227
+ raise chisel.ActionError('UnknownConversationID')
228
+
229
+ # Return the conversation
230
+ return {
231
+ 'conversation': copy.deepcopy(conversation),
232
+ 'generating': id_ in ctx.app.chats
233
+ }
234
+
235
+
236
+ @chisel.action(name='replyConversation', types=OLLAMA_CHAT_TYPES)
237
+ def reply_conversation(ctx, req):
238
+ with ctx.app.config() as config:
239
+ id_ = req['id']
240
+ conversation = config_conversation(config, id_)
241
+ if conversation is None:
242
+ raise chisel.ActionError('UnknownConversationID')
243
+
244
+ # Busy?
245
+ if id_ in ctx.app.chats:
246
+ raise chisel.ActionError('ConversationBusy')
247
+
248
+ # Add the reply exchange
249
+ conversation['exchanges'].append({
250
+ 'user': req['user'],
251
+ 'model': ''
252
+ })
253
+
254
+ # Start the model chat
255
+ ctx.app.chats[id_] = OllamaChat(ctx.app, id_)
256
+
257
+
258
+ @chisel.action(name='deleteConversation', types=OLLAMA_CHAT_TYPES)
259
+ def delete_conversation(ctx, req):
260
+ with ctx.app.config(save=True) as config:
261
+ id_ = req['id']
262
+ conversation = config_conversation(config, id_)
263
+ if conversation is None:
264
+ raise chisel.ActionError('UnknownConversationID')
265
+
266
+ # Busy?
267
+ if id_ in ctx.app.chats:
268
+ raise chisel.ActionError('ConversationBusy')
269
+
270
+ # Delete the conversation
271
+ config['conversations'] = [conversation for conversation in config['conversations'] if conversation['id'] != id_]
272
+
273
+
274
+ @chisel.action(name='deleteConversationExchange', types=OLLAMA_CHAT_TYPES)
275
+ def delete_conversation_exchange(ctx, req):
276
+ with ctx.app.config(save=True) as config:
277
+ id_ = req['id']
278
+ conversation = config_conversation(config, id_)
279
+ if conversation is None:
280
+ raise chisel.ActionError('UnknownConversationID')
281
+
282
+ # Busy?
283
+ if id_ in ctx.app.chats:
284
+ raise chisel.ActionError('ConversationBusy')
285
+
286
+ # Delete the most recent exchange (but not the last one)
287
+ exchanges = conversation['exchanges']
288
+ if len(exchanges) > 1:
289
+ del exchanges[-1]
290
+
291
+
292
+ @chisel.action(name='regenerateConversationExchange', types=OLLAMA_CHAT_TYPES)
293
+ def regenerate_conversation_exchange(ctx, req):
294
+ with ctx.app.config(save=True) as config:
295
+ id_ = req['id']
296
+ conversation = config_conversation(config, id_)
297
+ if conversation is None:
298
+ raise chisel.ActionError('UnknownConversationID')
299
+
300
+ # Busy?
301
+ if id_ in ctx.app.chats:
302
+ raise chisel.ActionError('ConversationBusy')
303
+
304
+ # Reset the most recent exchange's model response
305
+ exchanges = conversation['exchanges']
306
+ exchanges[-1]['model'] = ''
307
+
308
+ # Start the model chat
309
+ ctx.app.chats[id_] = OllamaChat(ctx.app, id_)
@@ -0,0 +1,58 @@
1
+ # Licensed under the MIT License
2
+ # https://github.com/craigahobbs/ollama-chat/blob/main/LICENSE
3
+
4
+ """
5
+ ollama-chat command-line script main module
6
+ """
7
+
8
+ import argparse
9
+ import threading
10
+ import webbrowser
11
+
12
+ import waitress
13
+
14
+ from .app import OllamaChatApplication
15
+
16
+
17
+ def main(argv=None):
18
+ """
19
+ ollama-chat command-line script main entry point
20
+ """
21
+
22
+ # Command line arguments
23
+ parser = argparse.ArgumentParser(prog='ollama-chat')
24
+ parser.add_argument('-c', metavar='FILE', dest='config', default='ollama-chat.json',
25
+ help='the configuration file (default is "ollama-chat.json")')
26
+ parser.add_argument('-p', metavar='N', dest='port', type=int, default=8080,
27
+ help='the application port (default is 8080)')
28
+ parser.add_argument('-n', dest='no_browser', action='store_true',
29
+ help="don't open a web browser")
30
+ parser.add_argument('-q', dest='quiet', action='store_true',
31
+ help="don't display access logging")
32
+ args = parser.parse_args(args=argv)
33
+
34
+ # Construct the URL
35
+ host = '127.0.0.1'
36
+ url = f'http://{host}:{args.port}/'
37
+
38
+ # Launch the web browser on a thread so the WSGI application can startup first
39
+ if not args.no_browser:
40
+ webbrowser_thread = threading.Thread(target=webbrowser.open, args=(url,))
41
+ webbrowser_thread.daemon = True
42
+ webbrowser_thread.start()
43
+
44
+ # Create the WSGI application
45
+ wsgiapp = OllamaChatApplication(args.config)
46
+
47
+ # Wrap the WSGI application and the start_response function so we can log status and environ
48
+ def wsgiapp_wrap(environ, start_response):
49
+ def log_start_response(status, response_headers):
50
+ if not args.quiet:
51
+ print(f'ollama-chat: {status[0:3]} {environ["REQUEST_METHOD"]} {environ["PATH_INFO"]} {environ["QUERY_STRING"]}')
52
+ return start_response(status, response_headers)
53
+ return wsgiapp(environ, log_start_response)
54
+
55
+ # Host the application
56
+ if not args.quiet:
57
+ print(f'ollama-chat: Serving at {url} ...')
58
+ waitress.serve(wsgiapp_wrap, port=args.port)
@@ -0,0 +1,79 @@
1
+ # Licensed under the MIT License
2
+ # https://github.com/craigahobbs/ollama-chat/blob/main/LICENSE
3
+
4
+ """
5
+ The ollama chat manager
6
+ """
7
+
8
+ import threading
9
+
10
+ import ollama
11
+
12
+
13
+ class OllamaChat():
14
+ """
15
+ The ollama chat manager class
16
+ """
17
+
18
+ __slots__ = ('app', 'conversation_id', 'stop')
19
+
20
+
21
+ def __init__(self, app, conversation_id):
22
+ self.app = app
23
+ self.conversation_id = conversation_id
24
+ self.stop = False
25
+
26
+ # Start the chat thread
27
+ chat_thread = threading.Thread(target=OllamaChat.chat_thread_fn, args=(self,))
28
+ chat_thread.daemon = True
29
+ chat_thread.start()
30
+
31
+
32
+ @staticmethod
33
+ def chat_thread_fn(chat):
34
+ try:
35
+ # Create the Ollama messages from the conversation
36
+ messages = []
37
+ with chat.app.config() as config:
38
+ conversation = config_conversation(config, chat.conversation_id)
39
+ model = conversation['model']
40
+ for exchange in conversation['exchanges']:
41
+ messages.append({'role': 'user', 'content': exchange['user']})
42
+ if exchange['model'] != '':
43
+ messages.append({'role': 'assistant', 'content': exchange['model']})
44
+
45
+ # Start the chat
46
+ stream = ollama.chat(model=model, messages=messages, stream=True)
47
+
48
+ # Stream the chat response
49
+ for chunk in stream:
50
+ # If stopped, return immediately. The chat is deleted by the stopper.
51
+ if chat.stop:
52
+ stream.close()
53
+ break
54
+
55
+ # Update the conversation
56
+ with chat.app.config() as config:
57
+ conversation = config_conversation(config, chat.conversation_id)
58
+ exchange = conversation['exchanges'][-1]
59
+ exchange['model'] += chunk['message']['content']
60
+
61
+ except Exception as exc: # pylint: disable=broad-exception-caught
62
+ # Communicate the error
63
+ with chat.app.config() as config:
64
+ conversation = config_conversation(config, chat.conversation_id)
65
+ exchange = conversation['exchanges'][-1]
66
+ exchange['model'] += f'\n**ERROR:** {exc}'
67
+
68
+ # Save the conversation
69
+ with chat.app.config(save=True):
70
+ # Delete the application's chat entry
71
+ if chat.conversation_id in chat.app.chats:
72
+ del chat.app.chats[chat.conversation_id]
73
+
74
+
75
+ def config_conversation(config, id_):
76
+ """
77
+ Helper to find a conversation by ID
78
+ """
79
+ return next((conv for conv in config['conversations'] if conv['id'] == id_), None)
@@ -0,0 +1,214 @@
1
+ # Licensed under the MIT License
2
+ # https://github.com/craigahobbs/ollama-chat/blob/main/LICENSE
3
+
4
+
5
+ group "Ollama Chat JSON"
6
+
7
+
8
+ # The Ollama Chat config file format
9
+ struct OllamaChatConfig
10
+
11
+ # The current model name
12
+ optional string model
13
+
14
+ # The saved conversations
15
+ Conversation[] conversations
16
+
17
+ # If true, don't save the config file
18
+ optional bool noSave
19
+
20
+
21
+ # The user-model conversation info
22
+ struct ConversationInfo
23
+
24
+ # The conversation identifier
25
+ string id
26
+
27
+ # The model name
28
+ string model
29
+
30
+ # The conversation title
31
+ string title
32
+
33
+
34
+ # A user-model conversation
35
+ struct Conversation (ConversationInfo)
36
+
37
+ # The conversation's exchanges
38
+ ConversationExchange[len > 0] exchanges
39
+
40
+
41
+ # A conversation user-model exchange
42
+ struct ConversationExchange
43
+
44
+ # The user prompt
45
+ string(len > 0) user
46
+
47
+ # The model response
48
+ string model
49
+
50
+
51
+ group "Ollama Chat API"
52
+
53
+
54
+ # Get the current model name
55
+ action getModels
56
+ urls
57
+ GET
58
+
59
+ output
60
+ # The local models
61
+ ModelInfo[] models
62
+
63
+
64
+ # Model information struct
65
+ struct ModelInfo
66
+
67
+ # The current model name
68
+ string model
69
+
70
+ # The model size, in bytes
71
+ int size
72
+
73
+
74
+ # Get the current model name
75
+ action getModel
76
+ urls
77
+ GET
78
+
79
+ output
80
+ # The current model name
81
+ string model
82
+
83
+
84
+ # Set the current model name
85
+ action setModel
86
+ urls
87
+ POST
88
+
89
+ input
90
+ # The model name
91
+ string model
92
+
93
+
94
+ # Get the list of conversations
95
+ action getConversations
96
+ urls
97
+ GET
98
+
99
+ output
100
+ # The conversations
101
+ ConversationInfoEx[] conversations
102
+
103
+
104
+ # Conversation information struct with generating state
105
+ struct ConversationInfoEx (ConversationInfo)
106
+
107
+ # If True, the latest exchange is actively generating
108
+ bool generating
109
+
110
+
111
+ # Start a conversation
112
+ action startConversation
113
+ urls
114
+ POST
115
+
116
+ input
117
+ # The user prompt
118
+ string(len > 0) user
119
+
120
+ output
121
+ # The new conversation ID
122
+ string id
123
+
124
+
125
+ # Stop a generating conversation
126
+ action stopConversation
127
+ urls
128
+ POST
129
+
130
+ input
131
+ # The conversation identifier
132
+ string id
133
+
134
+ errors
135
+ UnknownConversationID
136
+
137
+
138
+ # Get a conversation
139
+ action getConversation
140
+ urls
141
+ GET
142
+
143
+ query
144
+ # The conversation identifier
145
+ string id
146
+
147
+ output
148
+ # The conversation
149
+ Conversation conversation
150
+
151
+ # If True, the latest exchange is actively generating
152
+ bool generating
153
+
154
+ errors
155
+ UnknownConversationID
156
+
157
+
158
+ # Reply to a conversation
159
+ action replyConversation
160
+ urls
161
+ POST
162
+
163
+ input
164
+ # The conversation identifier
165
+ string id
166
+
167
+ # The user reply prompt
168
+ string(len > 0) user
169
+
170
+ errors
171
+ UnknownConversationID
172
+ ConversationBusy
173
+
174
+
175
+ # Delete a conversation
176
+ action deleteConversation
177
+ urls
178
+ POST
179
+
180
+ input
181
+ # The conversation identifier
182
+ string id
183
+
184
+ errors
185
+ UnknownConversationID
186
+ ConversationBusy
187
+
188
+
189
+ # Delete the most recent exchange of a conversation
190
+ action deleteConversationExchange
191
+ urls
192
+ POST
193
+
194
+ input
195
+ # The conversation identifier
196
+ string id
197
+
198
+ errors
199
+ UnknownConversationID
200
+ ConversationBusy
201
+
202
+
203
+ # Regnerate the model's response of the most recent exchange of a conversation
204
+ action regenerateConversationExchange
205
+ urls
206
+ POST
207
+
208
+ input
209
+ # The conversation identifier
210
+ string id
211
+
212
+ errors
213
+ UnknownConversationID
214
+ ConversationBusy
@@ -0,0 +1,89 @@
1
+ Metadata-Version: 2.1
2
+ Name: ollama-chat
3
+ Version: 0.9.0
4
+ Summary: ollama-chat
5
+ Home-page: https://github.com/craigahobbs/ollama-chat
6
+ Author: Craig A. Hobbs
7
+ Author-email: craigahobbs@gmail.com
8
+ License: MIT
9
+ Keywords: ollama-chat
10
+ Classifier: Development Status :: 5 - Production/Stable
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3.8
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Utilities
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: chisel>=1.2.7
23
+ Requires-Dist: ollama>=0.1.9
24
+ Requires-Dist: waitress>=3.0.0
25
+
26
+ # ollama-chat
27
+
28
+ [![PyPI - Status](https://img.shields.io/pypi/status/ollama-chat)](https://pypi.org/project/ollama-chat/)
29
+ [![PyPI](https://img.shields.io/pypi/v/ollama-chat)](https://pypi.org/project/ollama-chat/)
30
+ [![GitHub](https://img.shields.io/github/license/craigahobbs/ollama-chat)](https://github.com/craigahobbs/ollama-chat/blob/main/LICENSE)
31
+ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/ollama-chat)](https://pypi.org/project/ollama-chat/)
32
+
33
+ **Ollama Chat** is a simple yet useful web chat client for
34
+ [Ollama](https://ollama.com)
35
+ that allows you to chat locally (and privately) with
36
+ [open-source LLMs](https://ollama.com/library).
37
+
38
+
39
+ # Installation
40
+
41
+ To get up and running with Ollama Chat follows these steps:
42
+
43
+ 1. Install and start [Ollama](https://ollama.com)
44
+
45
+ 2. Install Ollama Chat
46
+
47
+ ~~~
48
+ pip install ollama-chat
49
+ ~~~
50
+
51
+
52
+ # Starting Ollama Chat
53
+
54
+ To start Ollama Chat, open a terminal prompt and run the Ollama Chat application:
55
+
56
+ ~~~
57
+ ollama-chat
58
+ ~~~
59
+
60
+ A web browser is launched and opens the Ollama Chat web application.
61
+
62
+ By default, a configuration file, "ollama-chat.json", is created in the current directory to save
63
+ your conversations.
64
+
65
+
66
+ ## Future Features
67
+
68
+ In no particular order...
69
+
70
+ - Save conversation as Markdown file
71
+
72
+ - Conversation title edit
73
+
74
+ - File / Directory / URL text inclusion in prompt
75
+
76
+ - Local model management (pull, rm)
77
+ - [Models JSON](https://huggingface.co/api/models)
78
+
79
+ - Prompt library
80
+
81
+
82
+ ## Development
83
+
84
+ This package is developed using [python-build](https://github.com/craigahobbs/python-build#readme).
85
+ It was started using [python-template](https://github.com/craigahobbs/python-template#readme) as follows:
86
+
87
+ ~~~
88
+ template-specialize python-template/template/ ollama-chat/ -k package ollama-chat -k name 'Craig A. Hobbs' -k email 'craigahobbs@gmail.com' -k github 'craigahobbs' -k noapi 1
89
+ ~~~
@@ -0,0 +1,16 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ setup.cfg
5
+ src/ollama_chat/__init__.py
6
+ src/ollama_chat/__main__.py
7
+ src/ollama_chat/app.py
8
+ src/ollama_chat/main.py
9
+ src/ollama_chat/ollama.py
10
+ src/ollama_chat.egg-info/PKG-INFO
11
+ src/ollama_chat.egg-info/SOURCES.txt
12
+ src/ollama_chat.egg-info/dependency_links.txt
13
+ src/ollama_chat.egg-info/entry_points.txt
14
+ src/ollama_chat.egg-info/requires.txt
15
+ src/ollama_chat.egg-info/top_level.txt
16
+ src/ollama_chat/static/ollamaChat.smd
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ ollama-chat = ollama_chat.main:main
@@ -0,0 +1,3 @@
1
+ chisel>=1.2.7
2
+ ollama>=0.1.9
3
+ waitress>=3.0.0
@@ -0,0 +1 @@
1
+ ollama_chat