diff --git a/ollama/build.properties b/ollama/build.properties index ae139c3..ce776d9 100644 --- a/ollama/build.properties +++ b/ollama/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Thu Mar 27 12:33:37 GMT 2025 +#Thu Mar 27 21:02:48 GMT 2025 stageCount=0 libraryProject= baseVersion=15.0 publishVersion=15.0.0 -buildCount=17 +buildCount=59 baseBetaVersion=15.0.1 diff --git a/ollama/src/main/AndroidManifest.xml b/ollama/src/main/AndroidManifest.xml index f9dfa9c..3d38280 100644 --- a/ollama/src/main/AndroidManifest.xml +++ b/ollama/src/main/AndroidManifest.xml @@ -8,13 +8,12 @@ + android:name=".App" + android:networkSecurityConfig="@xml/network_security_config"> - \ No newline at end of file + diff --git a/ollama/src/main/java/cc/winboll/studio/ollama/MainActivity.java b/ollama/src/main/java/cc/winboll/studio/ollama/MainActivity.java index 29a23b5..54ce2bd 100644 --- a/ollama/src/main/java/cc/winboll/studio/ollama/MainActivity.java +++ b/ollama/src/main/java/cc/winboll/studio/ollama/MainActivity.java @@ -1,42 +1,88 @@ package cc.winboll.studio.ollama; -import android.app.Activity; import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.support.v7.app.AppCompatActivity; import android.view.View; import android.widget.Button; import android.widget.EditText; +import android.widget.ScrollView; import android.widget.TextView; -import cc.winboll.studio.libappbase.LogView; -public class MainActivity extends Activity { +public class MainActivity extends AppCompatActivity { - LogView mLogView; - TextView mtvMeaasge; - EditText metAsk; - Button mbtSend; + public final static int MSG_APPEND = 0; + + private Handler _Handler = new Handler(Looper.getMainLooper()); + private TextView mtvMessage; + private EditText metAsk; + private Button mbtSend; + private ScrollView msvMessage; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); - mLogView = findViewById(R.id.logview); - mLogView.start(); - - mtvMeaasge = findViewById(R.id.message_tv); - metAsk = findViewById(R.id.ask_et); - mbtSend = findViewById(R.id.send_bt); + mtvMessage = (TextView) findViewById(R.id.message_tv); + metAsk = (EditText) findViewById(R.id.ask_et); + mbtSend = (Button) findViewById(R.id.send_bt); + msvMessage = findViewById(R.id.message_sv); + + mbtSend.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + sendQuestion(); + } + }); } - @Override - protected void onResume() { - super.onResume(); - mLogView.start(); - } - - public void onSend(View view) { - OllamaClient.SyncAskThread thread = new OllamaClient.SyncAskThread(mtvMeaasge.getText().toString()); - thread.start(); - mtvMeaasge.setText(""); + + // + // 设置输入框获得焦点的类 + // +// static class MyHandler extends Handler { +// WeakReference mActivity; +// MyHandler(MainActivity activity) { +// mActivity = new WeakReference(activity); +// } +// public void handleMessage(Message msg) { +// MainActivity theActivity = mActivity.get(); +// switch (msg.what) { +// case MSG_APPEND: +// theActivity.mtvMessage.append((String)msg.obj); +// break; +// default: +// break; +// } +// super.handleMessage(msg); +// } +// } + + private void sendQuestion() { + final String question = metAsk.getText().toString().trim(); + if (!question.equals("")) { + mtvMessage.append("\n\nI :" + metAsk.getText().toString() + "\nOllama : "); + metAsk.setText(""); + new OllamaClient.SyncAskThread(question, new OllamaClient.OnAnswerCallback() { + @Override + public void onAnswer(final String answer) { + _Handler.post(new Runnable() { + @Override + public void run() { + mtvMessage.append(answer); + msvMessage.post(new Runnable(){ + @Override + public void run() { + msvMessage.fullScroll(View.FOCUS_DOWN); + } + }); + } + }); + } + }).start(); + } } } + diff --git a/ollama/src/main/java/cc/winboll/studio/ollama/OllamaClient.java b/ollama/src/main/java/cc/winboll/studio/ollama/OllamaClient.java index f31a602..2d2f00c 100644 --- a/ollama/src/main/java/cc/winboll/studio/ollama/OllamaClient.java +++ b/ollama/src/main/java/cc/winboll/studio/ollama/OllamaClient.java @@ -5,103 +5,218 @@ package cc.winboll.studio.ollama; * @Date 2025/03/27 19:55:28 * @Describe 简单Http协议访问客户端 */ +import cc.winboll.studio.libappbase.LogUtils; import java.io.IOException; +import java.util.concurrent.TimeUnit; +import okhttp3.Call; +import okhttp3.Callback; import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; -import org.json.JSONArray; -import org.json.JSONObject; +import okhttp3.ResponseBody; +import okio.Buffer; +import okio.Source; import org.json.JSONException; -import cc.winboll.studio.libappbase.LogUtils; +import org.json.JSONObject; public class OllamaClient { public static final String TAG = "OllamaClient"; - //private static final String API_BASE_URL = "http://localhost:11434"; - private static final String API_BASE_URL = "https://ollama.winboll.cc"; - private static final OkHttpClient client = new OkHttpClient(); + private static final String API_BASE_URL = "https://ollama-api.winboll.cc"; + //private static final String API_BASE_URL = "http://10.8.0.10:11434"; + //private static final OkHttpClient client = new OkHttpClient(); + private static final OkHttpClient client = new OkHttpClient.Builder() + .connectTimeout(60, TimeUnit.SECONDS) + .readTimeout(60, TimeUnit.SECONDS) + .writeTimeout(60, TimeUnit.SECONDS) + .build(); // 1. 生成文本示例 - static void generateText(String prompt, String model) { +// static void generateText(String prompt, String model) { +// String url = API_BASE_URL + "/api/generate"; +// try { +// JSONObject payload = new JSONObject() +// .put("model", model) +// .put("prompt", prompt) +// .put("temperature", 0.7) +// .put("max_tokens", 200); +// +// Request request = new Request.Builder() +// .url(url) +// .post(RequestBody.create(payload.toString(), MediaType.get("application/json"))) +// .build(); +// +// Response response = client.newCall(request).execute(); +// if (response.isSuccessful()) { +// String result = response.body().string(); +// String formattedStream = OllamaResponseFormatter.formatStreamingResponse(result); +// +// // 输出示例: +//// [2025-03-27T19:34:29.274955439Z] [llama3.1:8b] It looks like you might have miss +//// [2025-03-27T19:34:30.482553089Z] [llama3.1:8b] pelled the word "Ollama" or perhaps said something that is not a standard word in the English language. However, I'm here to provide information and assistance on various topics, so please let me know what you meant by "Ollama." Was it related to a name, place, movie, game, or something else? +// +// LogUtils.d(TAG, formattedStream); +//// JSONObject json = new JSONObject(result); +//// LogUtils.d(TAG, "生成结果: " + json.getString("response")); +// //System.out.println("生成结果: " + json.getString("response")); +// } else { +// LogUtils.d(TAG, "请求失败: " + response.code()); +// //System.out.println("请求失败: " + response.code() + " " + response.message()); +// } +// } catch (JSONException|IOException e) { +// LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); +// } +// } + + // 实时输出流式响应的函数 + static void generateTextStream(String prompt, String model, final OnAnswerCallback callback) { String url = API_BASE_URL + "/api/generate"; try { JSONObject payload = new JSONObject() .put("model", model) .put("prompt", prompt) .put("temperature", 0.7) - .put("max_tokens", 200); + .put("max_tokens", 200) + .put("stream", true); // 启用流式响应 Request request = new Request.Builder() .url(url) .post(RequestBody.create(payload.toString(), MediaType.get("application/json"))) .build(); - Response response = client.newCall(request).execute(); - if (response.isSuccessful()) { - String result = response.body().string(); - JSONObject json = new JSONObject(result); - LogUtils.d(TAG, "生成结果: " + json.getString("response")); - //System.out.println("生成结果: " + json.getString("response")); - } else { - LogUtils.d(TAG, "请求失败: " + response.code()); - //System.out.println("请求失败: " + response.code() + " " + response.message()); - } - } catch (JSONException|IOException e) { - LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); + LogUtils.d(TAG, "Request request"); + client.newCall(request).enqueue(new Callback() { + @Override + public void onFailure(Call call, IOException e) { + LogUtils.d(TAG, "请求失败: " + e.getMessage()); + } + + @Override + public void onResponse(Call call, Response response) throws IOException { + if (!response.isSuccessful()) { + LogUtils.d(TAG, "请求失败: " + response.code()); + return; + } + + try (ResponseBody body = response.body()) { + if (body == null) return; + + // 实时流式解析 + LogUtils.d(TAG, "实时流式解析"); + Source source = body.source(); + Buffer buffer = new Buffer(); + StringBuilder fullResponse = new StringBuilder(); + boolean isDone = false; + + while (!isDone && source.read(buffer, 1024) != -1) { + //LogUtils.d(TAG, "!isDone"); + String chunk = buffer.readUtf8(); + String[] lines = chunk.split("\n"); + + for (String line : lines) { + LogUtils.d(TAG, line); + if (line.trim().startsWith("{\"model\":")) { + LogUtils.d(TAG, line); + String jsonStr = line; + if (jsonStr.equals("[DONE]")) { + isDone = true; + LogUtils.d(TAG, "流式生成完成"); + break; + } + + try { + //LogUtils.d(TAG, jsonStr); + JSONObject json = new JSONObject(jsonStr); + //LogUtils.d(TAG, json.toString()); + String responseText = json.getString("response"); + //LogUtils.d(TAG, responseText); + fullResponse.append(responseText); + + // 实时输出 + callback.onAnswer(responseText); + LogUtils.d(TAG, "实时响应: " + responseText); + + // 处理完成状态 + if (json.getBoolean("done")) { + isDone = true; + String doneReason = json.optString("done_reason", "unknown"); + LogUtils.d(TAG, "生成完成 (原因: " + doneReason + ")"); + LogUtils.d(TAG, "完整回答: " + fullResponse.toString()); + } + } catch (JSONException e) { + LogUtils.d(TAG, "JSON解析错误: " + e.getMessage()); + } + } + } + } + } + } + }); + } catch (JSONException e) { + LogUtils.d(TAG, "JSON格式错误: " + e.getMessage()); } } + + // 2. 获取模型列表示例 static void getModelList() { - String url = API_BASE_URL + "/api/models"; + String url = API_BASE_URL + "/v1/models"; LogUtils.d(TAG, "url : " + url); Request request = new Request.Builder().url(url).build(); try { Response response = client.newCall(request).execute(); if (response.isSuccessful()) { - JSONArray models = new JSONArray(response.body().string()); - //System.out.println("可用模型列表:"); - LogUtils.d(TAG, "可用模型列表:"); - for (int i = 0; i < models.length(); i++) { - JSONObject model = models.getJSONObject(i); - LogUtils.d(TAG, "- " + model.getString("name") + " (" + model.getString("size") + ")"); - //System.out.println("- " + model.getString("name") + " (" + model.getString("size") + ")"); - } + LogUtils.d(TAG, response.body().string()); +// JSONArray models = new JSONArray(response.body().string()); +// //System.out.println("可用模型列表:"); +// LogUtils.d(TAG, "可用模型列表:"); +// for (int i = 0; i < models.length(); i++) { +// JSONObject model = models.getJSONObject(i); +// LogUtils.d(TAG, "- " + model.getString("name") + " (" + model.getString("size") + ")"); +// //System.out.println("- " + model.getString("name") + " (" + model.getString("size") + ")"); +// } } else { LogUtils.d(TAG, "获取模型列表失败: " + response.code()); //System.out.println("获取模型列表失败: " + response.code()); } - } catch (JSONException | IOException e) { + } catch (IOException e) { LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); } } - static void unittest(String ask) { + static void unittest(String ask, OnAnswerCallback callback) { // 获取模型列表 getModelList(); // 生成文本 - generateText(ask, "llama2"); + generateTextStream(ask, "llama3.1:8b", callback); } - + public static class SyncAskThread extends Thread { - String ask; - public SyncAskThread(String ask) { + private String ask; + private OnAnswerCallback callback; + + public SyncAskThread(String ask, OnAnswerCallback callback) { this.ask = ask; + this.callback = callback; } @Override public void run() { super.run(); LogUtils.d(TAG, "run() start."); - unittest(this.ask); + unittest(ask, callback); LogUtils.d(TAG, "run() end."); } - + } + + public interface OnAnswerCallback { + void onAnswer(String answer); } } diff --git a/ollama/src/main/java/cc/winboll/studio/ollama/OllamaResponseFormatter.java b/ollama/src/main/java/cc/winboll/studio/ollama/OllamaResponseFormatter.java new file mode 100644 index 0000000..baeda4f --- /dev/null +++ b/ollama/src/main/java/cc/winboll/studio/ollama/OllamaResponseFormatter.java @@ -0,0 +1,72 @@ +package cc.winboll.studio.ollama; + +/** + * @Author ZhanGSKen@AliYun.Com + * @Date 2025/03/28 03:38:50 + */ +import org.json.JSONArray; +import org.json.JSONObject; + +public class OllamaResponseFormatter { + public static final String TAG = "OllamaResponseFormatter"; + + // 处理模型列表响应 + public static String formatModelList(String jsonResponse) { + try { + JSONObject json = new JSONObject(jsonResponse); + JSONArray models = json.getJSONArray("data"); + StringBuilder sb = new StringBuilder(); + sb.append("可用模型列表:\n"); + + for (int i = 0; i < models.length(); i++) { + JSONObject model = models.getJSONObject(i); + String modelId = model.getString("id"); + sb.append(String.format("-%d. %s\n", i + 1, modelId)); + } + return sb.toString(); + } catch (Exception e) { + return "格式解析错误: " + e.getMessage(); + } + } + + // 处理流式生成文本响应 + public static String formatStreamingResponse(String jsonResponse) { + try { + JSONObject json = new JSONObject(jsonResponse); + StringBuilder sb = new StringBuilder(); + String responseText = json.getString("response"); + boolean isDone = json.getBoolean("done"); + + // 添加时间戳和模型标识 + String timestamp = json.getString("created_at"); + String modelName = json.getString("model"); + sb.append(String.format("[%s] [%s] ", timestamp, modelName)); + + // 处理响应内容 + if (responseText.isEmpty() && isDone) { + sb.append("生成完成\n"); + } else { + sb.append(responseText); + if (isDone) { + String doneReason = json.optString("done_reason", "unknown"); + sb.append(String.format(" (完成原因: %s)\n", doneReason)); + } + } + return sb.toString(); + } catch (Exception e) { + return "格式解析错误: " + e.getMessage(); + } + } + + // 使用示例 + public static void main(String[] args) { + // 模型列表测试 + String modelListJson = "{\"object\":\"list\",\"data\":[{...}]}"; + System.out.println(formatModelList(modelListJson)); + + // 流式响应测试 + String streamingJson = "{\"model\":\"llama3.1:8b\",\"created_at\":\"2025-03-27T19:34:29.274955439Z\",\"response\":\"It\",\"done\":false}"; + System.out.println(formatStreamingResponse(streamingJson)); + } +} + diff --git a/ollama/src/main/res/layout/activity_main.xml b/ollama/src/main/res/layout/activity_main.xml index 39dd559..acd82ae 100644 --- a/ollama/src/main/res/layout/activity_main.xml +++ b/ollama/src/main/res/layout/activity_main.xml @@ -16,12 +16,13 @@ + android:layout_weight="1.0" + android:id="@+id/message_sv"> @@ -45,8 +46,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Send" - android:id="@+id/send_bt" - android:onClick="onSend"/> + android:id="@+id/send_bt"/> diff --git a/ollama/src/main/res/values/styles.xml b/ollama/src/main/res/values/styles.xml index 6799c28..fe7702a 100644 --- a/ollama/src/main/res/values/styles.xml +++ b/ollama/src/main/res/values/styles.xml @@ -1,5 +1,5 @@ - - - - \ No newline at end of file + + + + diff --git a/ollama/src/main/res/xml/network_security_config.xml b/ollama/src/main/res/xml/network_security_config.xml new file mode 100644 index 0000000..47f934c --- /dev/null +++ b/ollama/src/main/res/xml/network_security_config.xml @@ -0,0 +1,6 @@ + + + + 10.8.0.10 + +